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

tempestphp / tempest-framework / 14024978163

23 Mar 2025 05:55PM UTC coverage: 79.391% (-0.05%) from 79.441%
14024978163

push

github

web-flow
feat(view): cache Blade and Twig templates in internal storage (#1061)

2 of 2 new or added lines in 2 files covered. (100.0%)

912 existing lines in 110 files now uncovered.

10478 of 13198 relevant lines covered (79.39%)

91.09 hits per line

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

97.85
/src/Tempest/Database/src/Builder/ModelQueryBuilder.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Tempest\Database\Builder;
6

7
use Closure;
8
use Tempest\Database\DatabaseModel;
9
use Tempest\Database\Id;
10
use Tempest\Database\Query;
11
use Tempest\Database\Virtual;
12

13
use function Tempest\map;
14
use function Tempest\reflect;
15

16
/**
17
 * @template TModelClass of DatabaseModel
18
 */
19
final class ModelQueryBuilder
20
{
21
    private ModelDefinition $modelDefinition;
22

23
    private array $where = [];
24

25
    private array $orderBy = [];
26

27
    private ?int $limit = null;
28

29
    private ?int $offset = null;
30

31
    private array $raw = [];
32

33
    private array $relations = [];
34

35
    private array $bindings = [];
36

37
    public function __construct(
49✔
38
        /** @var class-string<TModelClass> $modelClass */
39
        private readonly string $modelClass,
40
    ) {
41
        $this->modelDefinition = new ModelDefinition($this->modelClass);
49✔
42
    }
43

44
    /**
45
     * @return TModelClass|null
46
     */
47
    public function first(mixed ...$bindings): mixed
32✔
48
    {
49
        $query = $this->build($bindings);
32✔
50

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

53
        if ($result === []) {
32✔
54
            return null;
9✔
55
        }
56

57
        return $result[array_key_first($result)];
31✔
58
    }
59

60
    /**
61
     * @return TModelClass|null
62
     */
63
    public function get(Id $id): mixed
21✔
64
    {
65
        return $this->whereField('id', $id)
21✔
66
            ->first();
21✔
67
    }
68

69
    /** @return TModelClass[] */
70
    public function all(mixed ...$bindings): array
20✔
71
    {
72
        return map($this->build($bindings))->collection()->to($this->modelClass);
20✔
73
    }
74

75
    /**
76
     * @param Closure(TModelClass[] $models): void $closure
77
     */
78
    public function chunk(Closure $closure, int $amountPerChunk = 200): void
1✔
79
    {
80
        $offset = 0;
1✔
81

82
        do {
83
            $data = $this->clone()
1✔
84
                ->limit($amountPerChunk)
1✔
85
                ->offset($offset)
1✔
86
                ->all();
1✔
87

88
            $offset += count($data);
1✔
89

90
            $closure($data);
1✔
91
        } while ($data !== []);
1✔
92
    }
93

94
    /** @return self<TModelClass> */
95
    public function where(string $where, mixed ...$bindings): self
25✔
96
    {
97
        $this->where[] = $where;
25✔
98

99
        $this->bind(...$bindings);
25✔
100

101
        return $this;
25✔
102
    }
103

104
    /** @return self<TModelClass> */
105
    public function orderBy(string $statement): self
1✔
106
    {
107
        $this->orderBy[] = $statement;
1✔
108

109
        return $this;
1✔
110
    }
111

112
    /** @return self<TModelClass> */
113
    public function limit(int $limit): self
3✔
114
    {
115
        $this->limit = $limit;
3✔
116

117
        return $this;
3✔
118
    }
119

120
    /** @return self<TModelClass> */
121
    public function offset(int $offset): self
2✔
122
    {
123
        $this->offset = $offset;
2✔
124

125
        return $this;
2✔
126
    }
127

128
    /** @return self<TModelClass> */
129
    public function raw(string $raw): self
1✔
130
    {
131
        $this->raw[] = $raw;
1✔
132

133
        return $this;
1✔
134
    }
135

136
    /** @return self<TModelClass> */
137
    public function whereField(string $field, mixed $value): self
23✔
138
    {
139
        $field = $this->modelDefinition->getFieldName($field);
23✔
140

141
        return $this->where("{$field} = :{$field->fieldName}", ...[$field->fieldName => $value]);
23✔
142
    }
143

144
    /** @return self<TModelClass> */
145
    public function with(string ...$relations): self
35✔
146
    {
147
        $this->relations = [...$this->relations, ...$relations];
35✔
148

149
        return $this;
35✔
150
    }
151

152
    /** @return self<TModelClass> */
153
    public function bind(mixed ...$bindings): self
25✔
154
    {
155
        $this->bindings = [...$this->bindings, ...$bindings];
25✔
156

157
        return $this;
25✔
158
    }
159

UNCOV
160
    public function toSql(): string
×
161
    {
UNCOV
162
        return $this->build([])->getSql();
×
163
    }
164

165
    private function build(array $bindings): Query
49✔
166
    {
167
        $modelDefinition = new ModelDefinition($this->modelClass);
49✔
168

169
        $relations = $this->getRelations($modelDefinition);
49✔
170

171
        $fields = $modelDefinition->getFieldNames();
49✔
172

173
        $fields = array_filter($fields, fn (FieldName $field) => ! reflect($this->modelClass, $field->fieldName)->hasAttribute(Virtual::class));
49✔
174

175
        foreach ($relations as $relation) {
49✔
176
            $fields = [...$fields, ...$relation->getFieldNames()];
20✔
177
        }
178

179
        $fields = implode(', ', array_map(
49✔
180
            fn (FieldName $fieldName) => $fieldName->withAlias(),
49✔
181
            $fields,
49✔
182
        ));
49✔
183

184
        $statements = [];
49✔
185

186
        $statements[] = sprintf(
49✔
187
            'SELECT %s FROM %s',
49✔
188
            $fields,
49✔
189
            $modelDefinition->getTableName(),
49✔
190
        );
49✔
191

192
        foreach ($relations as $relation) {
49✔
193
            $statements[] = $relation->getStatement();
20✔
194
        }
195

196
        if ($this->where !== []) {
49✔
197
            $statements[] = sprintf(
25✔
198
                'WHERE %s',
25✔
199
                implode(' AND ', $this->where),
25✔
200
            );
25✔
201
        }
202

203
        if ($this->orderBy !== []) {
49✔
204
            $statements[] = sprintf(
1✔
205
                'ORDER BY %s',
1✔
206
                implode(', ', $this->orderBy),
1✔
207
            );
1✔
208
        }
209

210
        if ($this->limit) {
49✔
211
            $statements[] = sprintf('LIMIT %s', $this->limit);
3✔
212
        }
213

214
        if ($this->offset) {
49✔
215
            $statements[] = sprintf('OFFSET %s', $this->offset);
2✔
216
        }
217

218
        if ($this->raw !== []) {
49✔
219
            $statements[] = implode(', ', $this->raw);
1✔
220
        }
221

222
        return new Query(implode(PHP_EOL, $statements), [...$this->bindings, ...$bindings]);
49✔
223
    }
224

225
    /** @return \Tempest\Database\Builder\Relations\Relation[] */
226
    private function getRelations(ModelDefinition $modelDefinition): array
49✔
227
    {
228
        $relations = $modelDefinition->getEagerRelations();
49✔
229

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

236
        return $relations;
49✔
237
    }
238

239
    private function clone(): self
1✔
240
    {
241
        return clone $this;
1✔
242
    }
243
}
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