Model/src/EntityPropertyInstance.php

200 lines
6.2 KiB
PHP

<?php
namespace Nest\Model;
use Nest\Database\Query\Raw;
use Nest\Database\Exceptions\UnknownDatabaseException;
use Nest\Model\Query\EntityQuery;
use function Nest\Utils\array_first;
use function Nest\Utils\array_first_or_val;
use function Nest\Utils\str_snake_singularize;
class EntityPropertyInstance extends ReadableEntityPropertyBlueprint
{
/**
* Goal entity instance.
* @var Entity
*/
private Entity $entity;
/**
* Create an entity property instance from an entity property definition.
* @param EntityPropertyBlueprint $entityProperty The entity property to copy from.
*/
public function __construct(EntityPropertyBlueprint $entityProperty)
{
// Copy all fields.
$this->class = $entityProperty->class;
$this->multiple = $entityProperty->multiple;
$this->allowInline = $entityProperty->allowInline;
$this->mode = $entityProperty->mode;
$this->localKey = $entityProperty->localKey;
$this->relatedKey = $entityProperty->relatedKey;
$this->pivotTable = $entityProperty->pivotTable;
$this->pivotLocalKey = $entityProperty->pivotLocalKey;
$this->pivotRelatedKey = $entityProperty->pivotRelatedKey;
$this->eagerLoad = $entityProperty->eagerLoad;
// Initialize goal entity.
$this->entity = new ($this->getClass())();
}
/**
* Create a new related entity.
* @return Entity A new related entity instance.
*/
public function newEntity(): Entity
{
return $this->entity->new();
}
/**
* Determine if the property can be inline loaded.
* @return bool True if the property can be inline loaded.
*/
public function canInlineLoad(): bool
{
return !$this->isMultiple() && $this->isInlineAllowed();
}
/**
* Build a query to retrieve relations for the given entities.
* @param Entity[] $entities Entities for which to retrieve relations.
* @return EntityQuery Built entities query.
* @throws UnknownDatabaseException
*/
public function queryFor(array $entities): EntityQuery
{
// Initialize query.
$query = $this->entity->query();
if (empty($entities))
// No entities, return a simple query without conditions.
return $query;
// Get a reference entity.
$referenceEntity = array_first($entities);
// Entity primary key.
$entityPrimaryKey = array_first_or_val($referenceEntity->getPrimaryFields());
// Get keys from entities list.
$entitiesKeys = array_column($entities, $entityPrimaryKey);
switch ($this->getMode())
{ // Build query depending on the relation mode.
case EntityPropertyMode::LOCAL:
$query
->select(new Raw("\"{$referenceEntity->getTableName()}\".\"$entityPrimaryKey\" AS \"__reference_key\""))
// Generate SELECT for the related entity.
->select(...$this->entity->sqlSelectFields())
->innerJoin($referenceEntity->getTableName())->on(
"{$this->entity->getTableName()}.".(
$this->getRelatedKey() ?? array_first_or_val($this->entity->getPrimaryFields())
),
"=",
"{$referenceEntity->getTableName()}.".(
$this->getLocalKey() ?? str_snake_singularize($this->entity->getTableName())."_id"
),
)->whereIn(
"{$referenceEntity->getTableName()}.$entityPrimaryKey", $entitiesKeys
);
break;
case EntityPropertyMode::RELATED:
$query
->select(new Raw("\"{$this->entity->getTableName()}\".\"".(
$this->getRelatedKey() ?? str_snake_singularize($referenceEntity->getTableName())."_id"
)."\" AS \"__reference_key\""))
// Generate SELECT for the related entity.
->select(...$this->entity->sqlSelectFields())
->whereIn(
"{$this->entity->getTableName()}.".(
$this->getRelatedKey() ?? str_snake_singularize($referenceEntity->getTableName())."_id"
)."",
$entitiesKeys
);
break;
case EntityPropertyMode::PIVOT:
$query
->select(new Raw("\"{$this->entity->getTableName()}\".\"".(
$this->getPivotLocalKey() ?? str_snake_singularize($referenceEntity->getTableName())."_id"
)."\" AS \"__reference_key\""))
// Generate SELECT for the related entity.
->select(...$this->entity->sqlSelectFields())
->innerJoin($this->getPivotTable())->on(
"{$query->getTableName()}.".(
$this->getRelatedKey() ?? $query->getPrimaryKeyName()
),
"=",
"{$this->getPivotTable()}.".(
$this->getPivotLocalKey() ?? str_snake_singularize($query->getTableName())."_id"
),
)
->whereIn(
"{$this->getPivotTable()}.".(
$this->getPivotLocalKey() ?? str_snake_singularize($referenceEntity->getTableName())."_id"
),
$entitiesKeys
);
break;
}
return $query;
}
/**
* Setup inline loading for the current property in an entity query.
* @param EntityQuery $query The entity query to alter.
* @return void
*/
public function setupInlineLoading(EntityQuery $query): void
{
switch ($this->getMode())
{ // Add join depending on the relation mode.
case EntityPropertyMode::LOCAL:
$query
->leftJoin($this->entity->getTableName())->on(
"{$query->getTableName()}.".(
$this->getLocalKey() ?? str_snake_singularize($this->entity->getTableName())."_id"
),
"=",
"{$this->entity->getTableName()}.".(
$this->getRelatedKey() ?? array_first_or_val($this->entity->getPrimaryFields())
),
);
break;
case EntityPropertyMode::RELATED:
$query
->leftJoin($this->entity->getTableName())->on(
"{$query->getTableName()}.".(
$this->getLocalKey() ?? $query->getPrimaryKeyName()
),
"=",
"{$this->entity->getTableName()}.".(
$this->getRelatedKey() ?? str_snake_singularize($query->getTableName())."_id"
),
);
break;
case EntityPropertyMode::PIVOT:
$query
->leftJoin($this->getPivotTable())->on(
"{$query->getTableName()}.".(
$this->getLocalKey() ?? $query->getPrimaryKeyName()
),
"=",
"{$this->getPivotTable()}.".(
$this->getPivotLocalKey() ?? str_snake_singularize($query->getTableName())."_id"
),
)
->leftJoin($this->entity->getTableName())->on(
"{$this->getPivotTable()}.".(
$this->getPivotRelatedKey() ?? str_snake_singularize($this->entity->getTableName())."_id"
),
"=",
"{$this->entity->getTableName()}.".(
$this->getRelatedKey() ?? array_first_or_val($this->entity->getPrimaryFields())
),
);
break;
}
}
}