NMRelation cleanup / improvement

This commit is contained in:
Roman 2023-01-10 22:12:05 +01:00
parent f14a7a4762
commit 13f7866d42
13 changed files with 303 additions and 272 deletions

@ -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;

@ -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,
];
}