Skip to content

Commit 9c390ea

Browse files
authored
refactor(database): improve data mapping and add select()->join() (#1244)
1 parent e8705a7 commit 9c390ea

File tree

81 files changed

+2111
-1155
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+2111
-1155
lines changed

packages/auth/src/Install/User.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public function grantPermission(string|UnitEnum|Permission $permission): self
4040
{
4141
$permission = $this->resolvePermission($permission);
4242

43-
new UserPermission(
43+
UserPermission::new(
4444
user: $this,
4545
permission: $permission,
4646
)->save();

packages/auth/src/Install/UserPermission.php

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ final class UserPermission
1010
{
1111
use IsDatabaseModel;
1212

13-
public function __construct(
14-
public User $user,
15-
public Permission $permission,
16-
) {}
13+
public User $user;
14+
15+
public Permission $permission;
1716
}

packages/database/src/BelongsTo.php

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,121 @@
55
namespace Tempest\Database;
66

77
use Attribute;
8+
use Tempest\Database\Builder\ModelInspector;
9+
use Tempest\Database\QueryStatements\FieldStatement;
10+
use Tempest\Database\QueryStatements\JoinStatement;
11+
use Tempest\Reflection\PropertyReflector;
12+
use Tempest\Support\Arr\ImmutableArray;
13+
14+
use function Tempest\Support\str;
815

916
#[Attribute(Attribute::TARGET_PROPERTY)]
10-
final readonly class BelongsTo
17+
final class BelongsTo implements Relation
1118
{
19+
public PropertyReflector $property;
20+
21+
public string $name {
22+
get => $this->property->getName();
23+
}
24+
25+
private ?string $parent = null;
26+
1227
public function __construct(
13-
public string $localPropertyName,
14-
public string $inversePropertyName = 'id',
28+
private readonly ?string $relationJoin = null,
29+
private readonly ?string $ownerJoin = null,
1530
) {}
31+
32+
public function setParent(string $name): self
33+
{
34+
$this->parent = $name;
35+
36+
return $this;
37+
}
38+
39+
public function getOwnerFieldName(): string
40+
{
41+
if ($this->ownerJoin) {
42+
if (str_contains($this->ownerJoin, '.')) {
43+
return explode('.', $this->ownerJoin)[1];
44+
} else {
45+
return $this->ownerJoin;
46+
}
47+
}
48+
49+
$relationModel = model($this->property->getType()->asClass());
50+
51+
return str($relationModel->getTableName())->singularizeLastWord() . '_' . $relationModel->getPrimaryKey();
52+
}
53+
54+
public function getSelectFields(): ImmutableArray
55+
{
56+
$relationModel = model($this->property->getType()->asClass());
57+
58+
return $relationModel
59+
->getSelectFields()
60+
->map(function ($field) use ($relationModel) {
61+
return new FieldStatement(
62+
$relationModel->getTableName() . '.' . $field,
63+
)
64+
->withAlias(
65+
sprintf('%s.%s', $this->property->getName(), $field),
66+
)
67+
->withAliasPrefix($this->parent);
68+
});
69+
}
70+
71+
public function getJoinStatement(): JoinStatement
72+
{
73+
$relationModel = model($this->property->getType()->asClass());
74+
$ownerModel = model($this->property->getClass());
75+
76+
$relationJoin = $this->getRelationJoin($relationModel);
77+
$ownerJoin = $this->getOwnerJoin($ownerModel);
78+
79+
// LEFT JOIN authors ON authors.id = books.author_id
80+
return new JoinStatement(sprintf(
81+
'LEFT JOIN %s ON %s = %s',
82+
$relationModel->getTableName(),
83+
$relationJoin,
84+
$ownerJoin,
85+
));
86+
}
87+
88+
private function getRelationJoin(ModelInspector $relationModel): string
89+
{
90+
$relationJoin = $this->relationJoin;
91+
92+
if ($relationJoin && ! strpos($relationJoin, '.')) {
93+
$relationJoin = sprintf('%s.%s', $relationModel->getTableName(), $relationJoin);
94+
}
95+
96+
if ($relationJoin) {
97+
return $relationJoin;
98+
}
99+
100+
return sprintf(
101+
'%s.%s',
102+
$relationModel->getTableName(),
103+
$relationModel->getPrimaryKey(),
104+
);
105+
}
106+
107+
private function getOwnerJoin(ModelInspector $ownerModel): string
108+
{
109+
$ownerJoin = $this->ownerJoin;
110+
111+
if ($ownerJoin && ! strpos($ownerJoin, '.')) {
112+
$ownerJoin = sprintf('%s.%s', $ownerModel->getTableName(), $ownerJoin);
113+
}
114+
115+
if ($ownerJoin) {
116+
return $ownerJoin;
117+
}
118+
119+
return sprintf(
120+
'%s.%s',
121+
$ownerModel->getTableName(),
122+
$this->getOwnerFieldName(),
123+
);
124+
}
16125
}

packages/database/src/Builder/FieldDefinition.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
use function Tempest\get;
1313

14+
// TODO: remove
1415
final class FieldDefinition implements Stringable
1516
{
1617
public function __construct(

packages/database/src/Builder/ModelDefinition.php

Lines changed: 1 addition & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,14 @@
55
namespace Tempest\Database\Builder;
66

77
use ReflectionException;
8-
use Tempest\Database\BelongsTo;
9-
use Tempest\Database\Builder\Relations\BelongsToRelation;
10-
use Tempest\Database\Builder\Relations\HasManyRelation;
11-
use Tempest\Database\Builder\Relations\HasOneRelation;
128
use Tempest\Database\Config\DatabaseConfig;
13-
use Tempest\Database\Eager;
14-
use Tempest\Database\HasMany;
15-
use Tempest\Database\HasOne;
169
use Tempest\Database\Table;
1710
use Tempest\Reflection\ClassReflector;
1811
use Tempest\Support\Arr\ImmutableArray;
1912

2013
use function Tempest\get;
2114

15+
// TODO: remove
2216
final readonly class ModelDefinition
2317
{
2418
private ClassReflector $modelClass;
@@ -41,80 +35,6 @@ public function __construct(string|object $model)
4135
}
4236
}
4337

44-
/** @return \Tempest\Database\Builder\Relations\Relation[] */
45-
public function getRelations(string $relationName): array
46-
{
47-
$relations = [];
48-
$relationNames = explode('.', $relationName);
49-
$alias = $this->getTableDefinition()->name;
50-
$class = $this->modelClass;
51-
52-
foreach ($relationNames as $relationNamePart) {
53-
$property = $class->getProperty($relationNamePart);
54-
55-
if ($property->hasAttribute(HasMany::class)) {
56-
/** @var HasMany $relationAttribute */
57-
$relationAttribute = $property->getAttribute(HasMany::class);
58-
$relations[] = HasManyRelation::fromAttribute($relationAttribute, $property, $alias);
59-
$class = HasManyRelation::getRelationModelClass($property, $relationAttribute)->getType()->asClass();
60-
$alias .= ".{$property->getName()}";
61-
} elseif ($property->getType()->isIterable()) {
62-
$relations[] = HasManyRelation::fromInference($property, $alias);
63-
$class = $property->getIterableType()->asClass();
64-
$alias .= ".{$property->getName()}[]";
65-
} elseif ($property->hasAttribute(HasOne::class)) {
66-
$relations[] = new HasOneRelation($property, $alias);
67-
$class = $property->getType()->asClass();
68-
$alias .= ".{$property->getName()}";
69-
} elseif ($property->hasAttribute(BelongsTo::class)) {
70-
/** @var BelongsTo $relationAttribute */
71-
$relationAttribute = $property->getAttribute(BelongsTo::class);
72-
$relations[] = BelongsToRelation::fromAttribute($relationAttribute, $property, $alias);
73-
$class = $property->getType()->asClass();
74-
$alias .= ".{$property->getName()}";
75-
} else {
76-
$relations[] = BelongsToRelation::fromInference($property, $alias);
77-
$class = $property->getType()->asClass();
78-
$alias .= ".{$property->getName()}";
79-
}
80-
}
81-
82-
return $relations;
83-
}
84-
85-
/** @return \Tempest\Database\Builder\Relations\Relation[] */
86-
public function getEagerRelations(): array
87-
{
88-
$relations = [];
89-
90-
foreach ($this->buildEagerRelationNames($this->modelClass) as $relationName) {
91-
foreach ($this->getRelations($relationName) as $relation) {
92-
$relations[$relation->getRelationName()] = $relation;
93-
}
94-
}
95-
96-
return $relations;
97-
}
98-
99-
private function buildEagerRelationNames(ClassReflector $class): array
100-
{
101-
$relations = [];
102-
103-
foreach ($class->getPublicProperties() as $property) {
104-
if (! $property->hasAttribute(Eager::class)) {
105-
continue;
106-
}
107-
108-
$relations[] = $property->getName();
109-
110-
foreach ($this->buildEagerRelationNames($property->getType()->asClass()) as $childRelation) {
111-
$relations[] = "{$property->getName()}.{$childRelation}";
112-
}
113-
}
114-
115-
return $relations;
116-
}
117-
11838
public function getTableDefinition(): TableDefinition
11939
{
12040
$specificName = $this->modelClass

0 commit comments

Comments
 (0)