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

tempestphp / tempest-framework / 14112896474

27 Mar 2025 05:24PM UTC coverage: 79.673% (+0.3%) from 79.334%
14112896474

Pull #1076

github

web-flow
Merge a57778677 into 6af05d563
Pull Request #1076: refactor(database): remove `DatabaseModel` interface

550 of 564 new or added lines in 40 files covered. (97.52%)

1 existing line in 1 file now uncovered.

10759 of 13504 relevant lines covered (79.67%)

93.05 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

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

25
/**
26
 * @template TModelClass of object
27
 */
28
final class SelectQueryBuilder
29
{
30
    /** @var class-string<TModelClass> $modelClass */
31
    private readonly string $modelClass;
32

33
    private ?ModelDefinition $modelDefinition;
34

35
    private SelectStatement $select;
36

37
    private array $relations = [];
38

39
    private array $bindings = [];
40

41
    public function __construct(string|object $model, ?ImmutableArray $columns = null)
52✔
42
    {
43
        $this->modelDefinition = ModelDefinition::tryFrom($model);
52✔
44
        $this->modelClass = is_object($model) ? $model::class : $model;
52✔
45

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

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

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

61
        if ($result === []) {
32✔
62
            return null;
9✔
63
        }
64

65
        return $result[array_key_first($result)];
31✔
66
    }
67

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

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

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

89
        do {
90
            $data = $this->clone()
1✔
91
                ->limit($amountPerChunk)
1✔
92
                ->offset($offset)
1✔
93
                ->all();
1✔
94

95
            $offset += count($data);
1✔
96

97
            $closure($data);
1✔
98
        } while ($data !== []);
1✔
99
    }
100

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

106
        $this->bind(...$bindings);
26✔
107

108
        return $this;
26✔
109
    }
110

111
    public function andWhere(string $where, mixed ...$bindings): self
1✔
112
    {
113
        return $this->where("AND {$where}", ...$bindings);
1✔
114
    }
115

116
    public function orWhere(string $where, mixed ...$bindings): self
1✔
117
    {
118
        return $this->where("OR {$where}", ...$bindings);
1✔
119
    }
120

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

126
        return $this->where("{$field} = :{$field->name}", ...[$field->name => $value]);
23✔
127
    }
128

129
    /** @return self<TModelClass> */
130
    public function orderBy(string $statement): self
2✔
131
    {
132
        $this->select->orderBy[] = new OrderByStatement($statement);
2✔
133

134
        return $this;
2✔
135
    }
136

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

142
        return $this;
3✔
143
    }
144

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

150
        return $this;
2✔
151
    }
152

153
    /** @return self<TModelClass> */
154
    public function with(string ...$relations): self
35✔
155
    {
156
        $this->relations = [...$this->relations, ...$relations];
35✔
157

158
        return $this;
35✔
159
    }
160

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

166
        return $this;
1✔
167
    }
168

169
    /** @return self<TModelClass> */
170
    public function bind(mixed ...$bindings): self
26✔
171
    {
172
        $this->bindings = [...$this->bindings, ...$bindings];
26✔
173

174
        return $this;
26✔
175
    }
176

NEW
177
    public function toSql(): string
×
178
    {
NEW
179
        return $this->build()->getSql();
×
180
    }
181

182
    public function build(array $bindings = []): Query
52✔
183
    {
184
        $resolvedRelations = $this->resolveRelations();
52✔
185

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

191
        return new Query($this->select, [...$this->bindings, ...$bindings]);
52✔
192
    }
193

194
    private function clone(): self
1✔
195
    {
196
        return clone $this;
1✔
197
    }
198

199
    private function resolveTable(string|object $model): TableDefinition
52✔
200
    {
201
        if ($this->modelDefinition === null) {
52✔
202
            return new TableDefinition($model);
2✔
203
        }
204

205
        return $this->modelDefinition->getTableDefinition();
50✔
206
    }
207

208
    private function resolveColumns(): ImmutableArray
51✔
209
    {
210
        if ($this->modelDefinition === null) {
51✔
211
            return arr();
1✔
212
        }
213

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

220
    private function resolveRelations(): ImmutableArray
52✔
221
    {
222
        if ($this->modelDefinition === null) {
52✔
223
            return arr();
2✔
224
        }
225

226
        $relations = $this->modelDefinition->getEagerRelations();
50✔
227

228
        foreach ($this->relations as $relationName) {
50✔
229
            foreach ($this->modelDefinition->getRelations($relationName) as $relation) {
19✔
230
                $relations[$relation->getRelationName()] = $relation;
19✔
231
            }
232
        }
233

234
        return arr($relations);
50✔
235
    }
236
}
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

© 2026 Coveralls, Inc