NMRelation cleanup / improvement
This commit is contained in:
parent
f14a7a4762
commit
13f7866d42
@ -13,6 +13,7 @@ namespace Core\API\Database {
|
||||
use Core\API\DatabaseAPI;
|
||||
use Core\API\Parameter\StringType;
|
||||
use Core\Objects\Context;
|
||||
use Core\Objects\DatabaseEntity\Controller\DatabaseEntity;
|
||||
|
||||
class Status extends DatabaseAPI {
|
||||
|
||||
@ -48,6 +49,10 @@ namespace Core\API\Database {
|
||||
$classPath = "\\$baseDir\\Objects\\DatabaseEntity\\$className";
|
||||
if (isClass($classPath)) {
|
||||
$class = new \ReflectionClass($classPath);
|
||||
if (!$class->isSubclassOf(DatabaseEntity::class)) {
|
||||
$class = null;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -62,8 +62,9 @@ namespace Core\API\Groups {
|
||||
return false;
|
||||
}
|
||||
|
||||
$nmTable = User::getHandler($sql)->getNMRelation("groups")->getTableName();
|
||||
$memberCount = new Alias($sql->select(new Count())
|
||||
->from(NMRelation::buildTableName("User", "Group"))
|
||||
->from($nmTable)
|
||||
->whereEq("group_id", new Column("Group.id")), "memberCount");
|
||||
|
||||
$groupsQuery = $this->createPaginationQuery($sql, [$memberCount]);
|
||||
@ -119,7 +120,7 @@ namespace Core\API\Groups {
|
||||
|
||||
protected function _execute(): bool {
|
||||
$sql = $this->context->getSQL();
|
||||
$nmTable = NMRelation::buildTableName(User::class, Group::class);
|
||||
$nmTable = User::getHandler($sql)->getNMRelation("groups")->getTableName();
|
||||
$condition = new Compare("group_id", $this->getParam("id"));
|
||||
$nmJoin = new InnerJoin($nmTable, "$nmTable.user_id", "User.id");
|
||||
if (!$this->initPagination($sql, User::class, $condition, 100, [$nmJoin])) {
|
||||
|
@ -30,7 +30,7 @@ class CondIn extends Condition {
|
||||
$values = implode(",", $values);
|
||||
$values = "($values)";
|
||||
} else if($haystack instanceof Select) {
|
||||
$values = $haystack->build($params);
|
||||
$values = $haystack->getExpression($sql, $params);
|
||||
} else {
|
||||
$sql->getLogger()->error("Unsupported in-expression value: " . get_class($haystack));
|
||||
return false;
|
||||
|
16
Core/Objects/DatabaseEntity/Attribute/BigInt.class.php
Normal file
16
Core/Objects/DatabaseEntity/Attribute/BigInt.class.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Objects\DatabaseEntity\Attribute;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)] class BigInt {
|
||||
|
||||
private bool $unsigned;
|
||||
|
||||
public function __construct(bool $unsigned = false) {
|
||||
$this->unsigned = $unsigned;
|
||||
}
|
||||
|
||||
public function isUnsigned(): bool {
|
||||
return $this->unsigned;
|
||||
}
|
||||
}
|
@ -14,5 +14,4 @@ class Multiple {
|
||||
public function getClassName(): string {
|
||||
return $this->className;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Objects\DatabaseEntity\Attribute;
|
||||
|
||||
|
||||
namespace Core\Objects\DatabaseEntity\Attribute;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class MultipleReference {
|
||||
|
||||
private string $className;
|
||||
private string $thisProperty;
|
||||
private string $relProperty;
|
||||
|
||||
public function __construct(string $className, string $thisProperty, string $relProperty) {
|
||||
$this->className = $className;
|
||||
$this->thisProperty = $thisProperty;
|
||||
$this->relProperty = $relProperty;
|
||||
}
|
||||
|
||||
public function getClassName(): string {
|
||||
return $this->className;
|
||||
}
|
||||
|
||||
public function getThisProperty(): string {
|
||||
return $this->thisProperty;
|
||||
}
|
||||
|
||||
public function getRelProperty(): string {
|
||||
return $this->relProperty;
|
||||
}
|
||||
}
|
@ -2,6 +2,15 @@
|
||||
|
||||
namespace Core\Objects\DatabaseEntity\Attribute;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)] class Unique {
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY|\Attribute::TARGET_CLASS)] class Unique {
|
||||
|
||||
private array $columns;
|
||||
|
||||
public function __construct(string ...$columns) {
|
||||
$this->columns = $columns;
|
||||
}
|
||||
|
||||
public function getColumns(): array {
|
||||
return $this->columns;
|
||||
}
|
||||
}
|
@ -226,6 +226,7 @@ abstract class DatabaseEntity implements ArrayAccess, JsonSerializable {
|
||||
if (!$handler || $allowOverride) {
|
||||
$handler = new DatabaseEntityHandler($sql, $class);
|
||||
self::$handlers[$class->getShortName()] = $handler;
|
||||
$handler->init();
|
||||
}
|
||||
|
||||
return $handler;
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace Core\Objects\DatabaseEntity\Controller;
|
||||
|
||||
use Core\Driver\Logger\Logger;
|
||||
use Core\Driver\SQL\Column\BigIntColumn;
|
||||
use Core\Driver\SQL\Column\BoolColumn;
|
||||
use Core\Driver\SQL\Column\Column;
|
||||
use Core\Driver\SQL\Column\DateTimeColumn;
|
||||
@ -10,14 +11,11 @@ use Core\Driver\SQL\Column\EnumColumn;
|
||||
use Core\Driver\SQL\Column\IntColumn;
|
||||
use Core\Driver\SQL\Column\JsonColumn;
|
||||
use Core\Driver\SQL\Column\StringColumn;
|
||||
use Core\Driver\SQL\Condition\CondAnd;
|
||||
use Core\Driver\SQL\Condition\CondBool;
|
||||
use Core\Driver\SQL\Condition\CondIn;
|
||||
use Core\Driver\SQL\Condition\Condition;
|
||||
use Core\Driver\SQL\Column\DoubleColumn;
|
||||
use Core\Driver\SQL\Column\FloatColumn;
|
||||
use Core\Driver\SQL\Condition\CondNot;
|
||||
use Core\Driver\SQL\Condition\CondOr;
|
||||
use Core\Driver\SQL\Constraint\ForeignKey;
|
||||
use Core\Driver\SQL\Join\InnerJoin;
|
||||
use Core\Driver\SQL\Query\CreateProcedure;
|
||||
@ -30,12 +28,14 @@ use Core\Driver\SQL\Strategy\SetNullStrategy;
|
||||
use Core\Driver\SQL\Strategy\UpdateStrategy;
|
||||
use Core\Driver\SQL\Type\CurrentColumn;
|
||||
use Core\Driver\SQL\Type\CurrentTable;
|
||||
use Core\Objects\DatabaseEntity\Attribute\BigInt;
|
||||
use Core\Objects\DatabaseEntity\Attribute\Enum;
|
||||
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||
use Core\Objects\DatabaseEntity\Attribute\ExtendingEnum;
|
||||
use Core\Objects\DatabaseEntity\Attribute\Json;
|
||||
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
||||
use Core\Objects\DatabaseEntity\Attribute\Multiple;
|
||||
use Core\Objects\DatabaseEntity\Attribute\MultipleReference;
|
||||
use Core\Objects\DatabaseEntity\Attribute\Transient;
|
||||
use Core\Objects\DatabaseEntity\Attribute\Unique;
|
||||
|
||||
@ -56,6 +56,7 @@ class DatabaseEntityHandler implements Persistable {
|
||||
public function __construct(SQL $sql, \ReflectionClass $entityClass) {
|
||||
$this->sql = $sql;
|
||||
$className = $entityClass->getName();
|
||||
|
||||
$this->logger = new Logger($entityClass->getShortName(), $sql);
|
||||
$this->entityClass = $entityClass;
|
||||
if (!$this->entityClass->isSubclassOf(DatabaseEntity::class)) {
|
||||
@ -70,6 +71,13 @@ class DatabaseEntityHandler implements Persistable {
|
||||
$this->nmRelations = []; // table name => NMRelation
|
||||
$this->extendingClasses = []; // enum value => \ReflectionClass
|
||||
$this->extendingProperty = null; // only one attribute can hold the type of the extending class
|
||||
}
|
||||
|
||||
public function init() {
|
||||
$uniqueColumns = self::getAttribute($this->entityClass, Unique::class);
|
||||
if ($uniqueColumns) {
|
||||
$this->constraints[] = new \Core\Driver\SQL\Constraint\Unique($uniqueColumns->getColumns());
|
||||
}
|
||||
|
||||
foreach ($this->entityClass->getProperties() as $property) {
|
||||
$propertyName = $property->getName();
|
||||
@ -132,8 +140,13 @@ class DatabaseEntityHandler implements Persistable {
|
||||
if ($enum) {
|
||||
$this->columns[$propertyName] = new EnumColumn($columnName, $enum->getValues(), $nullable, $defaultValue);
|
||||
} else {
|
||||
$maxLength = self::getAttribute($property, MaxLength::class);
|
||||
$this->columns[$propertyName] = new StringColumn($columnName, $maxLength?->getValue(), $nullable, $defaultValue);
|
||||
$bigInt = self::getAttribute($property, BigInt::class);
|
||||
if ($bigInt) {
|
||||
$this->columns[$propertyName] = new BigIntColumn($columnName, $nullable, $defaultValue, $bigInt->isUnsigned());
|
||||
} else {
|
||||
$maxLength = self::getAttribute($property, MaxLength::class);
|
||||
$this->columns[$propertyName] = new StringColumn($columnName, $maxLength?->getValue(), $nullable, $defaultValue);
|
||||
}
|
||||
}
|
||||
} else if ($propertyTypeName === 'int') {
|
||||
$this->columns[$propertyName] = new IntColumn($columnName, $nullable, $defaultValue);
|
||||
@ -152,25 +165,26 @@ class DatabaseEntityHandler implements Persistable {
|
||||
} else {
|
||||
|
||||
$multiple = self::getAttribute($property, Multiple::class);
|
||||
if (!$multiple) {
|
||||
$multipleReference = self::getAttribute($property, MultipleReference::class);
|
||||
if (!$multiple && !$multipleReference) {
|
||||
$this->raiseError("Cannot persist class '$className': Property '$propertyName' has non persist-able type: $propertyTypeName. " .
|
||||
"Is the 'Multiple' attribute missing?");
|
||||
"Is the 'Multiple' or 'MultipleReference' attribute missing?");
|
||||
}
|
||||
|
||||
try {
|
||||
$refClass = $multiple->getClassName();
|
||||
$refClass = $multiple ? $multiple->getClassName() : $multipleReference->getClassName();
|
||||
$requestedClass = new \ReflectionClass($refClass);
|
||||
if ($requestedClass->isSubclassOf(DatabaseEntity::class)) {
|
||||
$nmTableName = NMRelation::buildTableName($this->getTableName(), $requestedClass->getShortName());
|
||||
$nmRelation = $this->nmRelations[$nmTableName] ?? null;
|
||||
if (!$nmRelation) {
|
||||
$otherHandler = DatabaseEntity::getHandler($this->sql, $requestedClass);
|
||||
$otherNM = $otherHandler->getNMRelations();
|
||||
$nmRelation = $otherNM[$nmTableName] ?? (new NMRelation($this, $otherHandler));
|
||||
$this->nmRelations[$nmTableName] = $nmRelation;
|
||||
$otherHandler = DatabaseEntity::getHandler($this->sql, $requestedClass);
|
||||
if ($multiple) {
|
||||
$nmRelation = new NMRelation($this, $property, $otherHandler);
|
||||
$this->nmRelations[$propertyName] = $nmRelation;
|
||||
} else {
|
||||
$thisProperty = $multipleReference->getThisProperty();
|
||||
$relProperty = $multipleReference->getRelProperty();
|
||||
$nmRelationReference = new NMRelationReference($otherHandler, $thisProperty, $relProperty);
|
||||
$this->nmRelations[$propertyName] = $nmRelationReference;
|
||||
}
|
||||
|
||||
$this->nmRelations[$nmTableName]->addProperty($this, $property);
|
||||
} else {
|
||||
$this->raiseError("Cannot persist class '$className': Property '$propertyName' of type multiple can " .
|
||||
"only reference DatabaseEntity types, but got: $refClass");
|
||||
@ -212,7 +226,11 @@ class DatabaseEntityHandler implements Persistable {
|
||||
}
|
||||
}
|
||||
|
||||
public static function getAttribute(\ReflectionProperty $property, string $attributeClass): ?object {
|
||||
public function getNMRelations(): array {
|
||||
return $this->nmRelations;
|
||||
}
|
||||
|
||||
public static function getAttribute(\ReflectionProperty|\ReflectionClass $property, string $attributeClass): ?object {
|
||||
$attributes = $property->getAttributes($attributeClass);
|
||||
$attribute = array_shift($attributes);
|
||||
return $attribute?->newInstance();
|
||||
@ -241,10 +259,6 @@ class DatabaseEntityHandler implements Persistable {
|
||||
return $this->relations;
|
||||
}
|
||||
|
||||
public function getNMRelations(): array {
|
||||
return $this->nmRelations;
|
||||
}
|
||||
|
||||
public function getColumnName(string $property): string {
|
||||
if ($property === "id") {
|
||||
return "$this->tableName.id";
|
||||
@ -267,12 +281,20 @@ class DatabaseEntityHandler implements Persistable {
|
||||
}
|
||||
|
||||
public function dependsOn(): array {
|
||||
$foreignTables = array_map(function (DatabaseEntityHandler $relationHandler) {
|
||||
return $relationHandler->getTableName();
|
||||
}, $this->relations);
|
||||
$foreignTables = array_filter(array_map(
|
||||
function (DatabaseEntityHandler $relationHandler) {
|
||||
return $relationHandler->getTableName();
|
||||
}, $this->relations),
|
||||
function ($tableName) {
|
||||
return $tableName !== $this->getTableName();
|
||||
});
|
||||
return array_unique($foreignTables);
|
||||
}
|
||||
|
||||
public function getNMRelation(string $property): Persistable {
|
||||
return $this->nmRelations[$property];
|
||||
}
|
||||
|
||||
public static function getPrefixedRow(array $row, string $prefix): array {
|
||||
$rel_row = [];
|
||||
foreach ($row as $relKey => $relValue) {
|
||||
@ -355,11 +377,10 @@ class DatabaseEntityHandler implements Persistable {
|
||||
}
|
||||
|
||||
// init n:m / 1:n properties with empty arrays
|
||||
foreach ($this->nmRelations as $nmRelation) {
|
||||
foreach ($nmRelation->getProperties($this) as $property) {
|
||||
$property->setAccessible(true);
|
||||
$property->setValue($entity, []);
|
||||
}
|
||||
foreach ($this->nmRelations as $propertyName => $nmRelation) {
|
||||
$property = $this->properties[$propertyName];
|
||||
$property->setAccessible(true);
|
||||
$property->setValue($entity, []);
|
||||
}
|
||||
|
||||
$this->properties["id"]->setAccessible(true);
|
||||
@ -377,57 +398,32 @@ class DatabaseEntityHandler implements Persistable {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($this->nmRelations as $nmTable => $nmRelation) {
|
||||
|
||||
$thisIdColumn = $nmRelation->getIdColumn($this);
|
||||
$thisTableName = $this->getTableName();
|
||||
$dataColumns = $nmRelation->getDataColumns();
|
||||
$otherHandler = $nmRelation->getOtherHandler($this);
|
||||
$refIdColumn = $nmRelation->getIdColumn($otherHandler);
|
||||
foreach ($this->nmRelations as $nmProperty => $nmRelation) {
|
||||
$property = $this->properties[$nmProperty];
|
||||
$nmTable = $nmRelation->getTableName();
|
||||
|
||||
if ($nmRelation instanceof NMRelation) {
|
||||
$thisIdColumn = $nmRelation->getIdColumn($this);
|
||||
$otherHandler = $nmRelation->getOtherHandler($this);
|
||||
$refIdColumn = $nmRelation->getIdColumn($otherHandler);
|
||||
} else if ($nmRelation instanceof NMRelationReference) {
|
||||
$thisIdColumn = self::buildColumnName($nmRelation->getThisProperty());
|
||||
$refIdColumn = self::buildColumnName($nmRelation->getRefProperty());
|
||||
} else {
|
||||
throw new \Exception("updateNM not implemented for type: " . get_class($nmRelation));
|
||||
}
|
||||
|
||||
// delete from n:m table if no longer exists
|
||||
$doDelete = true;
|
||||
$deleteStatement = $this->sql->delete($nmTable)
|
||||
->whereEq($thisIdColumn, $entity->getId()); // this condition is important
|
||||
|
||||
if (!empty($dataColumns)) {
|
||||
$conditions = [];
|
||||
$doDelete = false;
|
||||
foreach ($dataColumns[$thisTableName] as $propertyName => $columnName) {
|
||||
if ($properties !== null && !in_array($propertyName, $properties)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$property = $this->properties[$propertyName];
|
||||
$entityIds = array_keys($property->getValue($entity));
|
||||
if (!empty($entityIds)) {
|
||||
$conditions[] = new CondAnd(
|
||||
new CondBool($columnName),
|
||||
new CondNot(new CondIn(new Column($refIdColumn), $entityIds)),
|
||||
);
|
||||
}
|
||||
if ($properties === null || in_array($nmProperty, $properties)) {
|
||||
$entityIds = array_keys($property->getValue($entity));
|
||||
if (!empty($entityIds)) {
|
||||
$deleteStatement->where(
|
||||
new CondNot(new CondIn(new Column($refIdColumn), $entityIds))
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($conditions)) {
|
||||
$deleteStatement->where(new CondOr(...$conditions));
|
||||
$doDelete = true;
|
||||
}
|
||||
} else {
|
||||
$property = next($nmRelation->getProperties($this));
|
||||
if ($properties !== null && !in_array($property->getName(), $properties)) {
|
||||
$doDelete = false;
|
||||
} else {
|
||||
$entityIds = array_keys($property->getValue($entity));
|
||||
if (!empty($entityIds)) {
|
||||
$deleteStatement->where(
|
||||
new CondNot(new CondIn(new Column($refIdColumn), $entityIds))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($doDelete) {
|
||||
$deleteStatement->execute();
|
||||
}
|
||||
}
|
||||
@ -442,52 +438,52 @@ class DatabaseEntityHandler implements Persistable {
|
||||
}
|
||||
|
||||
$success = true;
|
||||
foreach ($this->nmRelations as $nmTable => $nmRelation) {
|
||||
$otherHandler = $nmRelation->getOtherHandler($this);
|
||||
$thisIdColumn = $nmRelation->getIdColumn($this);
|
||||
$thisTableName = $this->getTableName();
|
||||
$refIdColumn = $nmRelation->getIdColumn($otherHandler);
|
||||
$dataColumns = $nmRelation->getDataColumns();
|
||||
foreach ($this->nmRelations as $nmProperty => $nmRelation) {
|
||||
|
||||
$columns = [
|
||||
$thisIdColumn,
|
||||
$refIdColumn,
|
||||
];
|
||||
|
||||
if (!empty($dataColumns)) {
|
||||
$columns = array_merge($columns, array_values($dataColumns[$thisTableName]));
|
||||
if ($properties !== null && !in_array($nmProperty, $properties)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$statement = $this->sql->insert($nmTable, $columns);
|
||||
if ($ignoreExisting) {
|
||||
$statement->onDuplicateKeyStrategy(new UpdateStrategy($nmRelation->getAllColumns(), [
|
||||
$thisIdColumn => $entity->getId()
|
||||
]));
|
||||
if ($nmRelation instanceof NMRelation) {
|
||||
$otherHandler = $nmRelation->getOtherHandler($this);
|
||||
$thisIdColumn = $nmRelation->getIdColumn($this);
|
||||
$refIdColumn = $nmRelation->getIdColumn($otherHandler);
|
||||
} else if ($nmRelation instanceof NMRelationReference) {
|
||||
$thisIdColumn = self::buildColumnName($nmRelation->getThisProperty());
|
||||
$refIdColumn = self::buildColumnName($nmRelation->getRefProperty());
|
||||
} else {
|
||||
throw new \Exception("insertNM not implemented for type: " . get_class($nmRelation));
|
||||
}
|
||||
|
||||
$doInsert = false;
|
||||
foreach ($nmRelation->getProperties($this) as $property) {
|
||||
if ($properties !== null && !in_array($property->getName(), $properties)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$property->setAccessible(true);
|
||||
$relEntities = $property->getValue($entity);
|
||||
foreach ($relEntities as $relEntity) {
|
||||
$relEntityId = (is_int($relEntity) ? $relEntity : $relEntity->getId());
|
||||
$nmRow = [$entity->getId(), $relEntityId];
|
||||
if (!empty($dataColumns)) {
|
||||
foreach (array_keys($dataColumns[$thisTableName]) as $propertyName) {
|
||||
$nmRow[] = $property->getName() === $propertyName;
|
||||
}
|
||||
$property = $this->properties[$nmProperty];
|
||||
$property->setAccessible(true);
|
||||
$relEntities = $property->getValue($entity);
|
||||
if (!empty($relEntities)) {
|
||||
if ($nmRelation instanceof NMRelation) {
|
||||
$columns = [$thisIdColumn, $refIdColumn];
|
||||
$nmTable = $nmRelation->getTableName();
|
||||
$statement = $this->sql->insert($nmTable, $columns);
|
||||
if ($ignoreExisting) {
|
||||
$statement->onDuplicateKeyStrategy(new UpdateStrategy($columns, [
|
||||
$thisIdColumn => $entity->getId()
|
||||
]));
|
||||
}
|
||||
$statement->addRow(...$nmRow);
|
||||
$doInsert = true;
|
||||
}
|
||||
}
|
||||
foreach ($relEntities as $relEntity) {
|
||||
$relEntityId = (is_int($relEntity) ? $relEntity : $relEntity->getId());
|
||||
$statement->addRow($entity->getId(), $relEntityId);
|
||||
}
|
||||
$success = $statement->execute() && $success;
|
||||
} else if ($nmRelation instanceof NMRelationReference) {
|
||||
$otherHandler = $nmRelation->getRelHandler();
|
||||
$thisIdProperty = $otherHandler->properties[$nmRelation->getThisProperty()];
|
||||
$thisIdProperty->setAccessible(true);
|
||||
|
||||
if ($doInsert) {
|
||||
$success = $statement->execute() && $success;
|
||||
foreach ($relEntities as $relEntity) {
|
||||
$thisIdProperty->setValue($relEntity, $entity);
|
||||
}
|
||||
|
||||
$success = $otherHandler->getInsertQuery($relEntities)->execute() && $success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -500,7 +496,7 @@ class DatabaseEntityHandler implements Persistable {
|
||||
foreach ($entities as $entity) {
|
||||
foreach ($this->relations as $propertyName => $relHandler) {
|
||||
$property = $this->properties[$propertyName];
|
||||
if ($property->isInitialized($entity) || true) {
|
||||
if ($property->isInitialized($entity)) {
|
||||
$relEntity = $this->properties[$propertyName]->getValue($entity);
|
||||
if ($relEntity) {
|
||||
$relHandler->fetchNMRelations([$relEntity->getId() => $relEntity], true);
|
||||
@ -515,66 +511,66 @@ class DatabaseEntityHandler implements Persistable {
|
||||
}
|
||||
|
||||
$entityIds = array_keys($entities);
|
||||
foreach ($this->nmRelations as $nmTable => $nmRelation) {
|
||||
$otherHandler = $nmRelation->getOtherHandler($this);
|
||||
foreach ($this->nmRelations as $nmProperty => $nmRelation) {
|
||||
$nmTable = $nmRelation->getTableName();
|
||||
$property = $this->properties[$nmProperty];
|
||||
$property->setAccessible(true);
|
||||
|
||||
$thisIdColumn = $nmRelation->getIdColumn($this);
|
||||
$thisProperties = $nmRelation->getProperties($this);
|
||||
$thisTableName = $this->getTableName();
|
||||
if ($nmRelation instanceof NMRelation) {
|
||||
$thisIdColumn = $nmRelation->getIdColumn($this);
|
||||
$otherHandler = $nmRelation->getOtherHandler($this);
|
||||
$refIdColumn = $nmRelation->getIdColumn($otherHandler);
|
||||
$refTableName = $otherHandler->getTableName();
|
||||
|
||||
$refIdColumn = $nmRelation->getIdColumn($otherHandler);
|
||||
$refProperties = $nmRelation->getProperties($otherHandler);
|
||||
$refTableName = $otherHandler->getTableName();
|
||||
$relEntityQuery = DatabaseEntityQuery::fetchAll($otherHandler)
|
||||
->addJoin(new InnerJoin($nmTable, "$nmTable.$refIdColumn", "$refTableName.id"))
|
||||
->addSelectValue(new Column($thisIdColumn))
|
||||
->where(new CondIn(new Column($thisIdColumn), $entityIds));
|
||||
|
||||
$dataColumns = $nmRelation->getDataColumns();
|
||||
|
||||
$relEntityQuery = DatabaseEntityQuery::fetchAll($otherHandler)
|
||||
->addJoin(new InnerJoin($nmTable, "$nmTable.$refIdColumn", "$refTableName.id"))
|
||||
->where(new CondIn(new Column($thisIdColumn), $entityIds));
|
||||
|
||||
$relEntityQuery->addSelectValue(new Column($thisIdColumn));
|
||||
foreach ($dataColumns as $tableDataColumns) {
|
||||
foreach ($tableDataColumns as $columnName) {
|
||||
$relEntityQuery->addSelectValue(new Column($columnName));
|
||||
}
|
||||
}
|
||||
|
||||
$rows = $relEntityQuery->executeSQL();
|
||||
if (!is_array($rows)) {
|
||||
$this->logger->error("Error fetching n:m relations from table: '$nmTable': " . $this->sql->getLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
$relEntities = [];
|
||||
foreach ($rows as $row) {
|
||||
$relId = $row["id"];
|
||||
if (!isset($relEntities[$relId])) {
|
||||
$relEntity = $otherHandler->entityFromRow($row, [], $recursive);
|
||||
$relEntities[$relId] = $relEntity;
|
||||
$rows = $relEntityQuery->executeSQL();
|
||||
if (!is_array($rows)) {
|
||||
$this->logger->error("Error fetching n:m relations from table: '$nmTable': " . $this->sql->getLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
$thisEntity = $entities[$row[$thisIdColumn]];
|
||||
$relEntity = $relEntities[$relId];
|
||||
$mappings = [
|
||||
[$refProperties, $refTableName, $relEntity, $thisEntity],
|
||||
[$thisProperties, $thisTableName, $thisEntity, $relEntity],
|
||||
];
|
||||
|
||||
foreach ($mappings as $mapping) {
|
||||
list($properties, $tableName, $targetEntity, $entityToAdd) = $mapping;
|
||||
foreach ($properties as $propertyName => $property) {
|
||||
$addToProperty = empty($dataColumns);
|
||||
if (!$addToProperty) {
|
||||
$columnName = $dataColumns[$tableName][$propertyName] ?? null;
|
||||
$addToProperty = ($columnName && $this->sql->parseBool($row[$columnName]));
|
||||
}
|
||||
|
||||
if ($addToProperty) {
|
||||
$targetArray = $property->getValue($targetEntity);
|
||||
$targetArray[$entityToAdd->getId()] = $entityToAdd;
|
||||
$property->setValue($targetEntity, $targetArray);
|
||||
}
|
||||
$relEntities = [];
|
||||
foreach ($rows as $row) {
|
||||
$relId = $row["id"];
|
||||
if (!isset($relEntities[$relId])) {
|
||||
$relEntity = $otherHandler->entityFromRow($row, [], $recursive);
|
||||
$relEntities[$relId] = $relEntity;
|
||||
}
|
||||
|
||||
$thisEntity = $entities[$row[$thisIdColumn]];
|
||||
$relEntity = $relEntities[$relId];
|
||||
|
||||
$targetArray = $property->getValue($thisEntity);
|
||||
$targetArray[$relEntity->getId()] = $relEntity;
|
||||
$property->setValue($thisEntity, $targetArray);
|
||||
}
|
||||
} else if ($nmRelation instanceof NMRelationReference) {
|
||||
$otherHandler = $nmRelation->getRelHandler();
|
||||
$thisIdColumn = self::buildColumnName($nmRelation->getThisProperty());
|
||||
$relIdColumn = self::buildColumnName($nmRelation->getRefProperty());
|
||||
|
||||
$relEntityQuery = DatabaseEntityQuery::fetchAll($otherHandler)
|
||||
->where(new CondIn(new Column($thisIdColumn), $entityIds));
|
||||
$rows = $relEntityQuery->executeSQL();
|
||||
if (!is_array($rows)) {
|
||||
$this->logger->error("Error fetching n:m relations from table: '$nmTable': " . $this->sql->getLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
$thisIdProperty = $otherHandler->properties[$nmRelation->getThisProperty()];
|
||||
$thisIdProperty->setAccessible(true);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$relEntity = $otherHandler->entityFromRow($row, [], $recursive);
|
||||
$thisEntity = $entities[$row[$thisIdColumn]];
|
||||
$thisIdProperty->setValue($relEntity, $thisEntity);
|
||||
$targetArray = $property->getValue($thisEntity);
|
||||
$targetArray[$row[$relIdColumn]] = $relEntity;
|
||||
$property->setValue($thisEntity, $targetArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,135 +2,65 @@
|
||||
|
||||
namespace Core\Objects\DatabaseEntity\Controller;
|
||||
|
||||
# TODO: Allow more than 2 relations here?
|
||||
|
||||
use Core\Driver\SQL\Query\CreateTable;
|
||||
use Core\Driver\SQL\SQL;
|
||||
use Core\Driver\SQL\Strategy\CascadeStrategy;
|
||||
|
||||
class NMRelation implements Persistable {
|
||||
|
||||
private DatabaseEntityHandler $handlerA;
|
||||
private DatabaseEntityHandler $handlerB;
|
||||
private array $properties;
|
||||
private DatabaseEntityHandler $thisHandler;
|
||||
private DatabaseEntityHandler $otherHandler;
|
||||
private \ReflectionProperty $property;
|
||||
private string $tableName;
|
||||
|
||||
public function __construct(DatabaseEntityHandler $handlerA, DatabaseEntityHandler $handlerB) {
|
||||
$this->handlerA = $handlerA;
|
||||
$this->handlerB = $handlerB;
|
||||
$tableNameA = $handlerA->getTableName();
|
||||
$tableNameB = $handlerB->getTableName();
|
||||
if ($tableNameA === $tableNameB) {
|
||||
throw new \Exception("Cannot create N:M Relation with only one table");
|
||||
}
|
||||
|
||||
$this->properties = [
|
||||
$tableNameA => [],
|
||||
$tableNameB => [],
|
||||
];
|
||||
}
|
||||
|
||||
public function addProperty(DatabaseEntityHandler $src, \ReflectionProperty $property): void {
|
||||
$this->properties[$src->getTableName()][$property->getName()] = $property;
|
||||
public function __construct(DatabaseEntityHandler $thisHandler, \ReflectionProperty $thisProperty, DatabaseEntityHandler $otherHandler) {
|
||||
$this->thisHandler = $thisHandler;
|
||||
$this->otherHandler = $otherHandler;
|
||||
$this->property = $thisProperty;
|
||||
$this->tableName = "NM_" . $thisHandler->getTableName() . "_" .
|
||||
DatabaseEntityHandler::buildColumnName($thisProperty->getName());
|
||||
}
|
||||
|
||||
public function getIdColumn(DatabaseEntityHandler $handler): string {
|
||||
return DatabaseEntityHandler::buildColumnName($handler->getTableName()) . "_id";
|
||||
}
|
||||
|
||||
public function getDataColumns(): array {
|
||||
|
||||
$referenceCount = 0;
|
||||
$columnsNeeded = false;
|
||||
|
||||
// if in one of the relations we have multiple references, we need to differentiate
|
||||
foreach ($this->properties as $refProperties) {
|
||||
$referenceCount += count($refProperties);
|
||||
if ($referenceCount > 1) {
|
||||
$columnsNeeded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$columns = [];
|
||||
if ($columnsNeeded) {
|
||||
foreach ($this->properties as $tableName => $properties) {
|
||||
$columns[$tableName] = [];
|
||||
foreach ($properties as $property) {
|
||||
$columnName = DatabaseEntityHandler::buildColumnName($tableName) . "_" .
|
||||
DatabaseEntityHandler::buildColumnName($property->getName());
|
||||
$columns[$tableName][$property->getName()] = $columnName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
public function getAllColumns(): array {
|
||||
$relIdA = $this->getIdColumn($this->handlerA);
|
||||
$relIdB = $this->getIdColumn($this->handlerB);
|
||||
|
||||
$columns = [$relIdA, $relIdB];
|
||||
|
||||
foreach ($this->getDataColumns() as $dataColumns) {
|
||||
foreach ($dataColumns as $columnName) {
|
||||
$columns[] = $columnName;
|
||||
}
|
||||
}
|
||||
|
||||
return $columns;
|
||||
public function getProperty(): \ReflectionProperty {
|
||||
return $this->property;
|
||||
}
|
||||
|
||||
public function getTableQuery(SQL $sql): CreateTable {
|
||||
|
||||
$tableNameA = $this->handlerA->getTableName();
|
||||
$tableNameB = $this->handlerB->getTableName();
|
||||
$thisTable = $this->thisHandler->getTableName();
|
||||
$otherTable = $this->otherHandler->getTableName();
|
||||
$thisIdColumn = $this->getIdColumn($this->thisHandler);
|
||||
$otherIdColumn = $this->getIdColumn($this->otherHandler);
|
||||
|
||||
$columns = $this->getAllColumns();
|
||||
list ($relIdA, $relIdB) = $columns;
|
||||
$dataColumns = array_slice($columns, 2);
|
||||
$query = $sql->createTable(self::buildTableName($tableNameA, $tableNameB))
|
||||
->addInt($relIdA)
|
||||
->addInt($relIdB)
|
||||
->foreignKey($relIdA, $tableNameA, "id", new CascadeStrategy())
|
||||
->foreignKey($relIdB, $tableNameB, "id", new CascadeStrategy());
|
||||
|
||||
foreach ($dataColumns as $dataColumn) {
|
||||
$query->addBool($dataColumn, false);
|
||||
}
|
||||
|
||||
$query->unique(...$columns);
|
||||
return $query;
|
||||
}
|
||||
|
||||
public static function buildTableName(string ...$tables): string {
|
||||
// in case of class passed here
|
||||
$tables = array_map(function ($t) { return isClass($t) ? getClassName($t) : $t; }, $tables);
|
||||
sort($tables);
|
||||
return "NM_" . implode("_", $tables);
|
||||
return $sql->createTable($this->tableName)
|
||||
->addInt($thisIdColumn)
|
||||
->addInt($otherIdColumn)
|
||||
->foreignKey($thisIdColumn, $thisTable, "id", new CascadeStrategy())
|
||||
->foreignKey($otherIdColumn, $otherTable, "id", new CascadeStrategy())
|
||||
->unique($thisIdColumn, $otherIdColumn);
|
||||
}
|
||||
|
||||
public function dependsOn(): array {
|
||||
return [$this->handlerA->getTableName(), $this->handlerB->getTableName()];
|
||||
return [$this->thisHandler->getTableName(), $this->otherHandler->getTableName()];
|
||||
}
|
||||
|
||||
public function getTableName(): string {
|
||||
return self::buildTableName(...$this->dependsOn());
|
||||
return $this->tableName;
|
||||
}
|
||||
|
||||
public function getCreateQueries(SQL $sql): array {
|
||||
return [$this->getTableQuery($sql)];
|
||||
}
|
||||
|
||||
public function getProperties(DatabaseEntityHandler $handler): array {
|
||||
return $this->properties[$handler->getTableName()];
|
||||
}
|
||||
|
||||
public function getOtherHandler(DatabaseEntityHandler $handler): DatabaseEntityHandler {
|
||||
if ($handler === $this->handlerA) {
|
||||
return $this->handlerB;
|
||||
if ($handler === $this->thisHandler) {
|
||||
return $this->thisHandler;
|
||||
} else {
|
||||
return $this->handlerA;
|
||||
return $this->otherHandler;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Objects\DatabaseEntity\Controller;
|
||||
|
||||
use Core\Driver\SQL\SQL;
|
||||
|
||||
class NMRelationReference implements Persistable {
|
||||
|
||||
private DatabaseEntityHandler $handler;
|
||||
private string $thisProperty;
|
||||
private string $refProperty;
|
||||
|
||||
public function __construct(DatabaseEntityHandler $handler, string $thisProperty, string $refProperty) {
|
||||
$this->handler = $handler;
|
||||
$this->thisProperty = $thisProperty;
|
||||
$this->refProperty = $refProperty;
|
||||
}
|
||||
|
||||
public function dependsOn(): array {
|
||||
return [$this->handler->getTableName()];
|
||||
}
|
||||
|
||||
public function getTableName(): string {
|
||||
return $this->handler->getTableName();
|
||||
}
|
||||
|
||||
public function getCreateQueries(SQL $sql): array {
|
||||
return []; // nothing to do here, will be managed by other handler
|
||||
}
|
||||
|
||||
public function getThisProperty(): string {
|
||||
return $this->thisProperty;
|
||||
}
|
||||
|
||||
public function getRefProperty(): string {
|
||||
return $this->refProperty;
|
||||
}
|
||||
|
||||
public function getRelHandler(): DatabaseEntityHandler {
|
||||
return $this->handler;
|
||||
}
|
||||
}
|
@ -29,7 +29,7 @@ class Group extends DatabaseEntity {
|
||||
}
|
||||
|
||||
public function getMembers(SQL $sql): array {
|
||||
$nmTable = NMRelation::buildTableName(User::class, Group::class);
|
||||
$nmTable = User::getHandler($sql)->getNMRelation("groups")->getTableName();
|
||||
$users = User::findBy(User::createBuilder($sql, false)
|
||||
->innerJoin($nmTable, "user_id", "User.id")
|
||||
->whereEq("group_id", $this->id));
|
||||
|
@ -92,7 +92,7 @@ class User extends DatabaseEntity {
|
||||
return [
|
||||
'id' => $this->getId(),
|
||||
'username' => $this->name,
|
||||
'language' => $this->language->getName(),
|
||||
'language' => isset($this->language) ? $this->language->getName() : null,
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user