Skip to content

Commit 21ca22c

Browse files
authored
refactor(database)!: query builder refactor (#1367)
1 parent e096186 commit 21ca22c

20 files changed

+430
-190
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,15 @@
3737
"symfony/var-exporter": "^7.1",
3838
"symfony/yaml": "^7.3",
3939
"tempest/highlight": "^2.11.4",
40-
"vlucas/phpdotenv": "^5.6.1",
40+
"vlucas/phpdotenv": "^5.6",
4141
"voku/portable-ascii": "^2.0.3"
4242
},
4343
"require-dev": {
4444
"azure-oss/storage-blob-flysystem": "^1.2",
4545
"carthage-software/mago": "0.26.1",
4646
"guzzlehttp/psr7": "^2.6.1",
4747
"league/flysystem-aws-s3-v3": "^3.0",
48+
"league/flysystem-azure-blob-storage": "^3.0",
4849
"league/flysystem-ftp": "^3.0",
4950
"league/flysystem-google-cloud-storage": "^3.0",
5051
"league/flysystem-memory": "^3.0",

packages/database/src/Builder/ModelInspector.php

Lines changed: 53 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
use Tempest\Database\Eager;
99
use Tempest\Database\HasMany;
1010
use Tempest\Database\HasOne;
11+
use Tempest\Database\Id;
1112
use Tempest\Database\Relation;
1213
use Tempest\Database\Table;
1314
use Tempest\Database\Virtual;
14-
use Tempest\Mapper\CastWith;
1515
use Tempest\Mapper\SerializeWith;
1616
use Tempest\Reflection\ClassReflector;
1717
use Tempest\Reflection\PropertyReflector;
@@ -27,53 +27,61 @@
2727

2828
final class ModelInspector
2929
{
30-
private ?ClassReflector $modelClass;
30+
private(set) ?ClassReflector $reflector;
3131

32-
private object|string $model;
32+
private(set) object|string $instance;
3333

3434
public function __construct(object|string $model)
3535
{
3636
if ($model instanceof HasMany) {
3737
$model = $model->property->getIterableType()->asClass();
38-
$this->modelClass = $model;
38+
$this->reflector = $model;
3939
} elseif ($model instanceof BelongsTo || $model instanceof HasOne) {
4040
$model = $model->property->getType()->asClass();
41-
$this->modelClass = $model;
41+
$this->reflector = $model;
4242
} elseif ($model instanceof ClassReflector) {
43-
$this->modelClass = $model;
43+
$this->reflector = $model;
4444
} else {
4545
try {
46-
$this->modelClass = new ClassReflector($model);
46+
$this->reflector = new ClassReflector($model);
4747
} catch (ReflectionException) {
48-
$this->modelClass = null;
48+
$this->reflector = null;
4949
}
5050
}
5151

52-
$this->model = $model;
52+
$this->instance = $model;
5353
}
5454

5555
public function isObjectModel(): bool
5656
{
57-
return $this->modelClass !== null;
57+
return $this->reflector !== null;
5858
}
5959

6060
public function getTableDefinition(): TableDefinition
6161
{
6262
if (! $this->isObjectModel()) {
63-
return new TableDefinition($this->model);
63+
return new TableDefinition($this->instance);
6464
}
6565

66-
$specificName = $this->modelClass
66+
$specificName = $this->reflector
6767
->getAttribute(Table::class)
6868
?->name;
6969

7070
$conventionalName = get(DatabaseConfig::class)
7171
->namingStrategy
72-
->getName($this->modelClass->getName());
72+
->getName($this->reflector->getName());
7373

7474
return new TableDefinition($specificName ?? $conventionalName);
7575
}
7676

77+
public function getFieldDefinition(string $field): FieldDefinition
78+
{
79+
return new FieldDefinition(
80+
$this->getTableDefinition(),
81+
$field,
82+
);
83+
}
84+
7785
public function getTableName(): string
7886
{
7987
return $this->getTableDefinition()->name;
@@ -85,14 +93,14 @@ public function getPropertyValues(): array
8593
return [];
8694
}
8795

88-
if (! is_object($this->model)) {
96+
if (! is_object($this->instance)) {
8997
return [];
9098
}
9199

92100
$values = [];
93101

94-
foreach ($this->modelClass->getProperties() as $property) {
95-
if (! $property->isInitialized($this->model)) {
102+
foreach ($this->reflector->getProperties() as $property) {
103+
if (! $property->isInitialized($this->instance)) {
96104
continue;
97105
}
98106

@@ -102,7 +110,7 @@ public function getPropertyValues(): array
102110

103111
$name = $property->getName();
104112

105-
$values[$name] = $property->getValue($this->model);
113+
$values[$name] = $property->getValue($this->instance);
106114
}
107115

108116
return $values;
@@ -122,11 +130,11 @@ public function getBelongsTo(string $name): ?BelongsTo
122130
return $this->getBelongsTo($singularizedName);
123131
}
124132

125-
if (! $this->modelClass->hasProperty($name)) {
133+
if (! $this->reflector->hasProperty($name)) {
126134
return null;
127135
}
128136

129-
$property = $this->modelClass->getProperty($name);
137+
$property = $this->reflector->getProperty($name);
130138

131139
if ($belongsTo = $property->getAttribute(BelongsTo::class)) {
132140
return $belongsTo;
@@ -168,11 +176,11 @@ public function getHasOne(string $name): ?HasOne
168176
return $this->getHasOne($singularizedName);
169177
}
170178

171-
if (! $this->modelClass->hasProperty($name)) {
179+
if (! $this->reflector->hasProperty($name)) {
172180
return null;
173181
}
174182

175-
$property = $this->modelClass->getProperty($name);
183+
$property = $this->reflector->getProperty($name);
176184

177185
if ($hasOne = $property->getAttribute(HasOne::class)) {
178186
return $hasOne;
@@ -189,11 +197,11 @@ public function getHasMany(string $name): ?HasMany
189197

190198
$name = str($name)->camel();
191199

192-
if (! $this->modelClass->hasProperty($name)) {
200+
if (! $this->reflector->hasProperty($name)) {
193201
return null;
194202
}
195203

196-
$property = $this->modelClass->getProperty($name);
204+
$property = $this->reflector->getProperty($name);
197205

198206
if ($hasMany = $property->getAttribute(HasMany::class)) {
199207
return $hasMany;
@@ -235,7 +243,7 @@ public function getSelectFields(): ImmutableArray
235243

236244
$selectFields = arr();
237245

238-
foreach ($this->modelClass->getPublicProperties() as $property) {
246+
foreach ($this->reflector->getPublicProperties() as $property) {
239247
$relation = $this->getRelation($property->getName());
240248

241249
if ($relation instanceof HasMany || $relation instanceof HasOne) {
@@ -297,7 +305,7 @@ public function resolveEagerRelations(string $parent = ''): array
297305

298306
$relations = [];
299307

300-
foreach ($this->modelClass->getPublicProperties() as $property) {
308+
foreach ($this->reflector->getPublicProperties() as $property) {
301309
if (! $property->hasAttribute(Eager::class)) {
302310
continue;
303311
}
@@ -334,7 +342,7 @@ public function validate(mixed ...$data): void
334342
$failingRules = [];
335343

336344
foreach ($data as $key => $value) {
337-
$property = $this->modelClass->getProperty($key);
345+
$property = $this->reflector->getProperty($key);
338346

339347
if ($property->hasAttribute(SkipValidation::class)) {
340348
continue;
@@ -351,26 +359,39 @@ public function validate(mixed ...$data): void
351359
}
352360

353361
if ($failingRules !== []) {
354-
throw new ValidationFailed($this->modelClass->getName(), $failingRules);
362+
throw new ValidationFailed($this->reflector->getName(), $failingRules);
355363
}
356364
}
357365

358366
public function getName(): string
359367
{
360-
if ($this->isObjectModel()) {
361-
return $this->modelClass->getName();
368+
if ($this->reflector) {
369+
return $this->reflector->getName();
362370
}
363371

364-
return $this->modelClass;
372+
return $this->instance;
373+
}
374+
375+
public function getPrimaryFieldName(): string
376+
{
377+
return $this->getTableDefinition()->name . '.' . $this->getPrimaryKey();
365378
}
366379

367380
public function getPrimaryKey(): string
368381
{
369382
return 'id';
370383
}
371384

372-
public function getPrimaryField(): string
385+
public function getPrimaryKeyValue(): ?Id
373386
{
374-
return $this->getTableDefinition()->name . '.' . $this->getPrimaryKey();
387+
if (! $this->isObjectModel()) {
388+
return null;
389+
}
390+
391+
if (! is_object($this->instance)) {
392+
return null;
393+
}
394+
395+
return $this->instance->{$this->getPrimaryKey()};
375396
}
376397
}

packages/database/src/Builder/QueryBuilders/BuildsQuery.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44

55
use Tempest\Database\Query;
66

7+
/**
8+
* @template TModelClass
9+
*/
710
interface BuildsQuery
811
{
912
public function build(mixed ...$bindings): Query;
13+
14+
/** @return self<TModelClass> */
15+
public function bind(mixed ...$bindings): self;
1016
}

packages/database/src/Builder/QueryBuilders/CountQueryBuilder.php

Lines changed: 22 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,40 @@
44

55
namespace Tempest\Database\Builder\QueryBuilders;
66

7-
use Tempest\Database\Builder\ModelDefinition;
8-
use Tempest\Database\Builder\TableDefinition;
7+
use Tempest\Database\Builder\ModelInspector;
98
use Tempest\Database\Exceptions\CannotCountDistinctWithoutSpecifyingAColumn;
109
use Tempest\Database\OnDatabase;
1110
use Tempest\Database\Query;
1211
use Tempest\Database\QueryStatements\CountStatement;
13-
use Tempest\Database\QueryStatements\WhereStatement;
12+
use Tempest\Database\QueryStatements\HasWhereStatements;
1413
use Tempest\Support\Conditions\HasConditions;
1514

15+
use function Tempest\Database\model;
16+
1617
/**
1718
* @template TModelClass of object
19+
* @implements \Tempest\Database\Builder\QueryBuilders\BuildsQuery<TModelClass>
20+
* @uses \Tempest\Database\Builder\QueryBuilders\HasWhereQueryBuilderMethods<TModelClass>
1821
*/
1922
final class CountQueryBuilder implements BuildsQuery
2023
{
21-
use HasConditions, OnDatabase;
22-
23-
private ?ModelDefinition $modelDefinition;
24+
use HasConditions, OnDatabase, HasWhereQueryBuilderMethods;
2425

2526
private CountStatement $count;
2627

2728
private array $bindings = [];
2829

29-
public function __construct(string|object $model, ?string $column = null)
30-
{
31-
$this->modelDefinition = ModelDefinition::tryFrom($model);
30+
private ModelInspector $model;
31+
32+
public function __construct(
33+
/** @var class-string<TModelClass>|string|TModelClass $model */
34+
string|object $model,
35+
?string $column = null,
36+
) {
37+
$this->model = model($model);
3238

3339
$this->count = new CountStatement(
34-
table: $this->resolveTable($model),
40+
table: $this->model->getTableDefinition(),
3541
column: $column,
3642
);
3743
}
@@ -53,34 +59,6 @@ public function distinct(): self
5359
return $this;
5460
}
5561

56-
/** @return self<TModelClass> */
57-
public function where(string $where, mixed ...$bindings): self
58-
{
59-
$this->count->where[] = new WhereStatement($where);
60-
61-
$this->bind(...$bindings);
62-
63-
return $this;
64-
}
65-
66-
public function andWhere(string $where, mixed ...$bindings): self
67-
{
68-
return $this->where("AND {$where}", ...$bindings);
69-
}
70-
71-
public function orWhere(string $where, mixed ...$bindings): self
72-
{
73-
return $this->where("OR {$where}", ...$bindings);
74-
}
75-
76-
/** @return self<TModelClass> */
77-
public function whereField(string $field, mixed $value): self
78-
{
79-
$field = $this->modelDefinition->getFieldDefinition($field);
80-
81-
return $this->where("{$field} = :{$field->name}", ...[$field->name => $value]);
82-
}
83-
8462
/** @return self<TModelClass> */
8563
public function bind(mixed ...$bindings): self
8664
{
@@ -99,12 +77,13 @@ public function build(mixed ...$bindings): Query
9977
return new Query($this->count, [...$this->bindings, ...$bindings])->onDatabase($this->onDatabase);
10078
}
10179

102-
private function resolveTable(string|object $model): TableDefinition
80+
private function getStatementForWhere(): HasWhereStatements
10381
{
104-
if ($this->modelDefinition === null) {
105-
return new TableDefinition($model);
106-
}
82+
return $this->count;
83+
}
10784

108-
return $this->modelDefinition->getTableDefinition();
85+
private function getModel(): ModelInspector
86+
{
87+
return $this->model;
10988
}
11089
}

0 commit comments

Comments
 (0)