Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/auth/src/Install/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function grantPermission(string|UnitEnum|Permission $permission): self
{
$permission = $this->resolvePermission($permission);

new UserPermission(
UserPermission::new(
user: $this,
permission: $permission,
)->save();
Expand Down
7 changes: 3 additions & 4 deletions packages/auth/src/Install/UserPermission.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ final class UserPermission
{
use IsDatabaseModel;

public function __construct(
public User $user,
public Permission $permission,
) {}
public User $user;

public Permission $permission;
}
115 changes: 112 additions & 3 deletions packages/database/src/BelongsTo.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,121 @@
namespace Tempest\Database;

use Attribute;
use Tempest\Database\Builder\ModelInspector;
use Tempest\Database\QueryStatements\FieldStatement;
use Tempest\Database\QueryStatements\JoinStatement;
use Tempest\Reflection\PropertyReflector;
use Tempest\Support\Arr\ImmutableArray;

use function Tempest\Support\str;

#[Attribute(Attribute::TARGET_PROPERTY)]
final readonly class BelongsTo
final class BelongsTo implements Relation
{
public PropertyReflector $property;

public string $name {
get => $this->property->getName();
}

private ?string $parent = null;

public function __construct(
public string $localPropertyName,
public string $inversePropertyName = 'id',
private readonly ?string $relationJoin = null,
private readonly ?string $ownerJoin = null,
) {}

public function setParent(string $name): self
{
$this->parent = $name;

return $this;
}

public function getOwnerFieldName(): string
{
if ($this->ownerJoin) {
if (str_contains($this->ownerJoin, '.')) {
return explode('.', $this->ownerJoin)[1];
} else {
return $this->ownerJoin;
}
}

$relationModel = model($this->property->getType()->asClass());

return str($relationModel->getTableName())->singularizeLastWord() . '_' . $relationModel->getPrimaryKey();
}

public function getSelectFields(): ImmutableArray
{
$relationModel = model($this->property->getType()->asClass());

return $relationModel
->getSelectFields()
->map(function ($field) use ($relationModel) {
return new FieldStatement(
$relationModel->getTableName() . '.' . $field,
)
->withAlias(
sprintf('%s.%s', $this->property->getName(), $field),
)
->withAliasPrefix($this->parent);
});
}

public function getJoinStatement(): JoinStatement
{
$relationModel = model($this->property->getType()->asClass());
$ownerModel = model($this->property->getClass());

$relationJoin = $this->getRelationJoin($relationModel);
$ownerJoin = $this->getOwnerJoin($ownerModel);

// LEFT JOIN authors ON authors.id = books.author_id
return new JoinStatement(sprintf(
'LEFT JOIN %s ON %s = %s',
$relationModel->getTableName(),
$relationJoin,
$ownerJoin,
));
}

private function getRelationJoin(ModelInspector $relationModel): string
{
$relationJoin = $this->relationJoin;

if ($relationJoin && ! strpos($relationJoin, '.')) {
$relationJoin = sprintf('%s.%s', $relationModel->getTableName(), $relationJoin);
}

if ($relationJoin) {
return $relationJoin;
}

return sprintf(
'%s.%s',
$relationModel->getTableName(),
$relationModel->getPrimaryKey(),
);
}

private function getOwnerJoin(ModelInspector $ownerModel): string
{
$ownerJoin = $this->ownerJoin;

if ($ownerJoin && ! strpos($ownerJoin, '.')) {
$ownerJoin = sprintf('%s.%s', $ownerModel->getTableName(), $ownerJoin);
}

if ($ownerJoin) {
return $ownerJoin;
}

return sprintf(
'%s.%s',
$ownerModel->getTableName(),
$this->getOwnerFieldName(),
);
}
}
1 change: 1 addition & 0 deletions packages/database/src/Builder/FieldDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

use function Tempest\get;

// TODO: remove
final class FieldDefinition implements Stringable
{
public function __construct(
Expand Down
82 changes: 1 addition & 81 deletions packages/database/src/Builder/ModelDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,14 @@
namespace Tempest\Database\Builder;

use ReflectionException;
use Tempest\Database\BelongsTo;
use Tempest\Database\Builder\Relations\BelongsToRelation;
use Tempest\Database\Builder\Relations\HasManyRelation;
use Tempest\Database\Builder\Relations\HasOneRelation;
use Tempest\Database\Config\DatabaseConfig;
use Tempest\Database\Eager;
use Tempest\Database\HasMany;
use Tempest\Database\HasOne;
use Tempest\Database\Table;
use Tempest\Reflection\ClassReflector;
use Tempest\Support\Arr\ImmutableArray;

use function Tempest\get;

// TODO: remove
final readonly class ModelDefinition
{
private ClassReflector $modelClass;
Expand All @@ -41,80 +35,6 @@ public function __construct(string|object $model)
}
}

/** @return \Tempest\Database\Builder\Relations\Relation[] */
public function getRelations(string $relationName): array
{
$relations = [];
$relationNames = explode('.', $relationName);
$alias = $this->getTableDefinition()->name;
$class = $this->modelClass;

foreach ($relationNames as $relationNamePart) {
$property = $class->getProperty($relationNamePart);

if ($property->hasAttribute(HasMany::class)) {
/** @var HasMany $relationAttribute */
$relationAttribute = $property->getAttribute(HasMany::class);
$relations[] = HasManyRelation::fromAttribute($relationAttribute, $property, $alias);
$class = HasManyRelation::getRelationModelClass($property, $relationAttribute)->getType()->asClass();
$alias .= ".{$property->getName()}";
} elseif ($property->getType()->isIterable()) {
$relations[] = HasManyRelation::fromInference($property, $alias);
$class = $property->getIterableType()->asClass();
$alias .= ".{$property->getName()}[]";
} elseif ($property->hasAttribute(HasOne::class)) {
$relations[] = new HasOneRelation($property, $alias);
$class = $property->getType()->asClass();
$alias .= ".{$property->getName()}";
} elseif ($property->hasAttribute(BelongsTo::class)) {
/** @var BelongsTo $relationAttribute */
$relationAttribute = $property->getAttribute(BelongsTo::class);
$relations[] = BelongsToRelation::fromAttribute($relationAttribute, $property, $alias);
$class = $property->getType()->asClass();
$alias .= ".{$property->getName()}";
} else {
$relations[] = BelongsToRelation::fromInference($property, $alias);
$class = $property->getType()->asClass();
$alias .= ".{$property->getName()}";
}
}

return $relations;
}

/** @return \Tempest\Database\Builder\Relations\Relation[] */
public function getEagerRelations(): array
{
$relations = [];

foreach ($this->buildEagerRelationNames($this->modelClass) as $relationName) {
foreach ($this->getRelations($relationName) as $relation) {
$relations[$relation->getRelationName()] = $relation;
}
}

return $relations;
}

private function buildEagerRelationNames(ClassReflector $class): array
{
$relations = [];

foreach ($class->getPublicProperties() as $property) {
if (! $property->hasAttribute(Eager::class)) {
continue;
}

$relations[] = $property->getName();

foreach ($this->buildEagerRelationNames($property->getType()->asClass()) as $childRelation) {
$relations[] = "{$property->getName()}.{$childRelation}";
}
}

return $relations;
}

public function getTableDefinition(): TableDefinition
{
$specificName = $this->modelClass
Expand Down
Loading
Loading