• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tempestphp / tempest-framework / 14631166702

23 Apr 2025 11:43AM UTC coverage: 80.194% (-1.0%) from 81.234%
14631166702

push

github

web-flow
fix(support): fix psr-4 namespace path generation with dots and slashes in the composer path (#1166)

1 of 1 new or added line in 1 file covered. (100.0%)

12 existing lines in 4 files now uncovered.

11758 of 14662 relevant lines covered (80.19%)

105.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.62
/src/Tempest/Database/src/Builder/QueryBuilders/SelectQueryBuilder.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Tempest\Database\Builder\QueryBuilders;
6

7
use Closure;
8
use Tempest\Database\Builder\FieldDefinition;
9
use Tempest\Database\Builder\ModelDefinition;
10
use Tempest\Database\Builder\TableDefinition;
11
use Tempest\Database\Id;
12
use Tempest\Database\Query;
13
use Tempest\Database\QueryStatements\JoinStatement;
14
use Tempest\Database\QueryStatements\OrderByStatement;
15
use Tempest\Database\QueryStatements\RawStatement;
16
use Tempest\Database\QueryStatements\SelectStatement;
17
use Tempest\Database\QueryStatements\WhereStatement;
18
use Tempest\Database\Virtual;
19
use Tempest\Support\Arr\ImmutableArray;
20
use Tempest\Support\Conditions\HasConditions;
21

22
use function Tempest\map;
23
use function Tempest\reflect;
24
use function Tempest\Support\arr;
25

26
/**
27
 * @template TModelClass of object
28
 */
29
final class SelectQueryBuilder
30
{
31
    use HasConditions;
32

33
    /** @var class-string<TModelClass> $modelClass */
34
    private readonly string $modelClass;
35

36
    private ?ModelDefinition $modelDefinition;
37

38
    private SelectStatement $select;
39

40
    private array $relations = [];
41

42
    private array $bindings = [];
43

44
    public function __construct(string|object $model, ?ImmutableArray $columns = null)
62✔
45
    {
46
        $this->modelDefinition = ModelDefinition::tryFrom($model);
62✔
47
        $this->modelClass = is_object($model) ? $model::class : $model;
62✔
48

49
        $this->select = new SelectStatement(
62✔
50
            table: $this->resolveTable($model),
62✔
51
            columns: $columns ?? $this->resolveColumns(),
62✔
52
        );
62✔
53
    }
54

55
    /**
56
     * @return TModelClass|null
57
     */
58
    public function first(mixed ...$bindings): mixed
32✔
59
    {
60
        $query = $this->build($bindings);
32✔
61

62
        $result = map($query)->collection()->to($this->modelClass);
32✔
63

64
        if ($result === []) {
32✔
65
            return null;
9✔
66
        }
67

68
        return $result[array_key_first($result)];
31✔
69
    }
70

71
    /**
72
     * @return TModelClass|null
73
     */
74
    public function get(Id $id): mixed
21✔
75
    {
76
        return $this->whereField('id', $id)->first();
21✔
77
    }
78

79
    /** @return TModelClass[] */
80
    public function all(mixed ...$bindings): array
29✔
81
    {
82
        return map($this->build($bindings))->collection()->to($this->modelClass);
29✔
83
    }
84

85
    /**
86
     * @param Closure(TModelClass[] $models): void $closure
87
     */
88
    public function chunk(Closure $closure, int $amountPerChunk = 200): void
1✔
89
    {
90
        $offset = 0;
1✔
91

92
        do {
93
            $data = $this->clone()
1✔
94
                ->limit($amountPerChunk)
1✔
95
                ->offset($offset)
1✔
96
                ->all();
1✔
97

98
            $offset += count($data);
1✔
99

100
            $closure($data);
1✔
101
        } while ($data !== []);
1✔
102
    }
103

104
    /** @return self<TModelClass> */
105
    public function where(string $where, mixed ...$bindings): self
27✔
106
    {
107
        $this->select->where[] = new WhereStatement($where);
27✔
108

109
        $this->bind(...$bindings);
27✔
110

111
        return $this;
27✔
112
    }
113

114
    public function andWhere(string $where, mixed ...$bindings): self
2✔
115
    {
116
        return $this->where("AND {$where}", ...$bindings);
2✔
117
    }
118

119
    public function orWhere(string $where, mixed ...$bindings): self
2✔
120
    {
121
        return $this->where("OR {$where}", ...$bindings);
2✔
122
    }
123

124
    /** @return self<TModelClass> */
125
    public function whereField(string $field, mixed $value): self
23✔
126
    {
127
        $field = $this->modelDefinition->getFieldDefinition($field);
23✔
128

129
        return $this->where("{$field} = :{$field->name}", ...[$field->name => $value]);
23✔
130
    }
131

132
    /** @return self<TModelClass> */
133
    public function orderBy(string $statement): self
3✔
134
    {
135
        $this->select->orderBy[] = new OrderByStatement($statement);
3✔
136

137
        return $this;
3✔
138
    }
139

140
    /** @return self<TModelClass> */
141
    public function limit(int $limit): self
3✔
142
    {
143
        $this->select->limit = $limit;
3✔
144

145
        return $this;
3✔
146
    }
147

148
    /** @return self<TModelClass> */
149
    public function offset(int $offset): self
2✔
150
    {
151
        $this->select->offset = $offset;
2✔
152

153
        return $this;
2✔
154
    }
155

156
    /** @return self<TModelClass> */
157
    public function with(string ...$relations): self
44✔
158
    {
159
        $this->relations = [...$this->relations, ...$relations];
44✔
160

161
        return $this;
44✔
162
    }
163

164
    /** @return self<TModelClass> */
165
    public function raw(string $raw): self
1✔
166
    {
167
        $this->select->raw[] = new RawStatement($raw);
1✔
168

169
        return $this;
1✔
170
    }
171

172
    /** @return self<TModelClass> */
173
    public function bind(mixed ...$bindings): self
27✔
174
    {
175
        $this->bindings = [...$this->bindings, ...$bindings];
27✔
176

177
        return $this;
27✔
178
    }
179

UNCOV
180
    public function toSql(): string
×
181
    {
UNCOV
182
        return $this->build()->getSql();
×
183
    }
184

185
    public function build(array $bindings = []): Query
62✔
186
    {
187
        $resolvedRelations = $this->resolveRelations();
62✔
188

189
        foreach ($resolvedRelations as $relation) {
62✔
190
            $this->select->columns = $this->select->columns->append(...$relation->getFieldDefinitions()->map(fn (FieldDefinition $field) => (string) $field->withAlias()));
20✔
191
            $this->select->join[] = new JoinStatement($relation->getStatement());
20✔
192
        }
193

194
        return new Query($this->select, [...$this->bindings, ...$bindings]);
62✔
195
    }
196

197
    private function clone(): self
1✔
198
    {
199
        return clone $this;
1✔
200
    }
201

202
    private function resolveTable(string|object $model): TableDefinition
62✔
203
    {
204
        if ($this->modelDefinition === null) {
62✔
205
            return new TableDefinition($model);
3✔
206
        }
207

208
        return $this->modelDefinition->getTableDefinition();
59✔
209
    }
210

211
    private function resolveColumns(): ImmutableArray
60✔
212
    {
213
        if ($this->modelDefinition === null) {
60✔
214
            return arr();
1✔
215
        }
216

217
        return $this->modelDefinition
59✔
218
            ->getFieldDefinitions()
59✔
219
            ->filter(fn (FieldDefinition $field) => ! reflect($this->modelClass, $field->name)->hasAttribute(Virtual::class))
59✔
220
            ->map(fn (FieldDefinition $field) => (string) $field->withAlias());
59✔
221
    }
222

223
    private function resolveRelations(): ImmutableArray
62✔
224
    {
225
        if ($this->modelDefinition === null) {
62✔
226
            return arr();
3✔
227
        }
228

229
        $relations = $this->modelDefinition->getEagerRelations();
59✔
230

231
        foreach ($this->relations as $relationName) {
59✔
232
            foreach ($this->modelDefinition->getRelations($relationName) as $relation) {
19✔
233
                $relations[$relation->getRelationName()] = $relation;
19✔
234
            }
235
        }
236

237
        return arr($relations);
59✔
238
    }
239
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc