From f1cca7db61cb6ecaede0b23a4b81af54c8370fe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Zrn=C3=ADk?= Date: Wed, 13 Oct 2021 16:02:54 +0200 Subject: [PATCH] Improved saving strategy --- src/Repository/BaseEntity.php | 14 +- src/Repository/BaseRepository.php | 377 +----------------------------- src/Repository/Saver/Saver.php | 190 +++++++++++++++ 3 files changed, 198 insertions(+), 383 deletions(-) create mode 100644 src/Repository/Saver/Saver.php diff --git a/src/Repository/BaseEntity.php b/src/Repository/BaseEntity.php index f177177..c6ee6c1 100644 --- a/src/Repository/BaseEntity.php +++ b/src/Repository/BaseEntity.php @@ -911,11 +911,10 @@ public function setPrimaryKeyValue(mixed $newPrimaryKeyValue): void } /** - * @param string[] $savedHashList * @return BaseEntity[] * @throws ReflectionFailedException */ - public function getSubEntities(array $savedHashList = []): array + public function getSubEntities(): array { $reflection = self::getReflectionClass(static::class); $subEntities = []; @@ -938,16 +937,7 @@ public function getSubEntities(array $savedHashList = []): array } } - $allowedSubEntities = []; - /** @var BaseEntity $possibleSubEntity */ - foreach ($subEntities as $possibleSubEntity) { - if (!in_array($possibleSubEntity->hash(), $savedHashList, true)) { - $allowedSubEntities[] = $possibleSubEntity; - $savedHashList[] = $possibleSubEntity->hash(); - } - } - - return $allowedSubEntities; + return $subEntities; } public function hash(): string diff --git a/src/Repository/BaseRepository.php b/src/Repository/BaseRepository.php index f6d7a99..c82cf25 100644 --- a/src/Repository/BaseRepository.php +++ b/src/Repository/BaseRepository.php @@ -10,6 +10,7 @@ use PDO; use Zrnik\MkSQL\Exceptions\InvalidArgumentException; use Zrnik\MkSQL\Repository\Fetcher\Fetcher; +use Zrnik\MkSQL\Repository\Saver\Saver; use Zrnik\MkSQL\Utilities\Reflection; use function count; use function is_array; @@ -28,172 +29,18 @@ public function getPdo(): PDO /** * @param BaseEntity|BaseEntity[] $entities - * @return string[] */ - public function save(BaseEntity|array $entities): array - { - /** @var string[] $savedHashList */ - $savedHashList = []; - $this->saveReal($entities, $savedHashList); - return $savedHashList; - } - - /** - * @param BaseEntity|BaseEntity[] $entities - * @param string[] $savedHashList - */ - public function saveReal(BaseEntity|array $entities, array &$savedHashList): void + public function save(BaseEntity|array $entities): void { if ($entities instanceof BaseEntity) { /** @var BaseEntity[] $entities */ $entities = [$entities]; } - /** @var BaseEntity[] $insert */ - $insert = []; - /** @var BaseEntity[] $update */ - $update = []; - /** @var BaseEntity $entity */ - foreach ($entities as $entity) { - if ($entity->getPrimaryKeyValue() === null) { - $insert[] = $entity; - } else { - $update[] = $entity; - } - } - - if (count($insert) > 0) { - $this->insert($insert, $savedHashList); - } - - if (count($update) > 0) { - $this->update($update, $savedHashList); - } + $saver = new Saver($this->pdo); + $saver->saveEntities($entities); } - /** - * @param BaseEntity[] $entities - * @param string[] $savedHashList - */ - private function insert(array $entities, array &$savedHashList): void - { - $subEntitiesToSave = []; - - foreach ($entities as $entity) { - - $data = $entity->toArray(); - - if ($entity->getPrimaryKeyValue() !== null) { - // Hello sir, I would like to inform you, - // that the process of saving already - // given a primary key value to you... - - // would you mind do 'update' - // instead of 'saving'? - - // Thank you very much! - $this->saveReal([$entity], $savedHashList); - continue; - } - - $sql = sprintf( - 'INSERT INTO %s (%s) VALUES (%s)', - $entity::getTableName(), - implode( - ',', - array_keys($data) - ), implode( - ',', - array_map( - static function (string $key) { - return ':' . $key; - }, - array_keys($data) - ) - ) - ); - - $convertedKeyData = []; - - foreach ($data as $key => $value) { - $convertedKeyData[':' . $key] = $value; - } - - $statement = $this->pdo->prepare($sql); - - $statement->execute($convertedKeyData); - - $entity->setPrimaryKeyValue($this->pdo->lastInsertId()); - - $subEntities = $entity->getSubEntities($savedHashList); - - - foreach ($subEntities as $subEntity) { - $subEntitiesToSave[] = $subEntity; - } - - $entity->updateRawData(); - } - - $this->saveReal($subEntitiesToSave, $savedHashList); - } - - /** - * @param BaseEntity[] $entities - * @param string[] $savedHashList - */ - private function update(array $entities, array &$savedHashList): void - { - $subEntitiesToSave = []; - - foreach ($entities as $entity) { - - $data = $entity->toArray(); - $primaryKeyName = $entity::getPrimaryKeyName(); - $primaryKeyValue = $data[$primaryKeyName]; - unset($data[$primaryKeyName]); - - if ($primaryKeyValue === null) { - $this->saveReal([$entity], $savedHashList); - continue; - } - - $sql = sprintf( - /** @lang */ 'UPDATE %s SET %s WHERE %s=%s', - $entity::getTableName(), - implode( - ', ', - array_map( - static function ($key) { - return $key . '=:' . $key; - }, - array_keys($data) - ) - ), - $primaryKeyName, - ':' . $primaryKeyName - ); - - $statement = $this->pdo->prepare($sql); - - $data[$primaryKeyName] = $primaryKeyValue; - - $statement->execute($data); - - $subEntities = $entity->getSubEntities($savedHashList); - - foreach ($subEntities as $subEntity) { - $subEntitiesToSave[] = $subEntity; - $savedHashList[] = $subEntity->hash(); - } - - $entity->updateRawData(); - } - - $this->saveReal($subEntitiesToSave, $savedHashList); - } - - /** * @param class-string $baseEntityClassString * @param mixed $primaryKeyValue @@ -277,220 +124,8 @@ public function getResultsByKeys( array $values = [], ): array { - return (new Fetcher($this->getPdo()))->getResultsByKeys($baseEntityClassString, $key, $values); - - - /* * @var BaseEntity $baseEntity * / - $baseEntity = $baseEntityClassString; - - if ($fetchObjectStorage === null) { - $fetchObjectStorage = new FetchObjectStorage(); - } - - $tableName = $baseEntity::getTableName(); - - $sql = sprintf('SELECT * FROM %s', $tableName); - - if ($key !== null) { - - // Check if the key has `#[ColumnName]` attribute - foreach (BaseEntity::getReflectionClass($baseEntity)->getProperties() as $reflectionProperty) { - if ($reflectionProperty->getName() === $key) { - $columnNameAttribute = Reflection::propertyGetAttribute($reflectionProperty, ColumnName::class); - if ($columnNameAttribute !== null) { - $key = Reflection::attributeGetArgument($columnNameAttribute); - } - } - } - - if (count($values) === 0) { - return []; - } - - $sql = sprintf( - 'SELECT * FROM %s WHERE %s IN (%s)', - $tableName, $key, str_repeat('?,', count($values) - 1) . '?' - ); - } - - $statement = $this->pdo->prepare($sql); - - $statement->execute($values); - $results = $statement->fetchAll(); - if ($results === false) { - return []; - } - - //region Sub-Fetches - - $usedPrimaryKeyList = []; - //region PrimaryKeyList - foreach ($results as $result) { - $usedPrimaryKeyList[] = $result[$baseEntity::getPrimaryKeyName()]; - } - //endregion - - $subElements = []; - - foreach (BaseEntity::getReflectionClass($baseEntity)->getProperties() as $thisEntityProperty) { - $fetchArrayAttribute = Reflection::propertyGetAttribute($thisEntityProperty, FetchArray::class); - if ($fetchArrayAttribute !== null) { - - $subEntityClassName = Reflection::attributeGetArgument($fetchArrayAttribute); - /** @var ?string $pointer * / - $pointer = null; - $pointerProperty = null; - foreach (BaseEntity::getReflectionClass($subEntityClassName)->getProperties() as $subEntityProperty) { - $subEntityForeignKeyAttribute = Reflection::propertyGetAttribute($subEntityProperty, ForeignKey::class); - if (($subEntityForeignKeyAttribute !== null) && Reflection::attributeGetArgument($subEntityForeignKeyAttribute) === $baseEntityClassString) { - $pointerProperty = $subEntityProperty->getName(); - $columnNamePointerAttribute = Reflection::propertyGetAttribute($subEntityProperty, ColumnName::class); - if ($columnNamePointerAttribute !== null) { - $pointer = Reflection::attributeGetArgument($columnNamePointerAttribute); - } else { - $pointer = $pointerProperty; - } - } - } - - if ($pointer === null) { - continue; - } - - $subElements[$thisEntityProperty->getName()] = [ - 'className' => $subEntityClassName, - 'pointingColumnName' => $pointer, - 'pointingPropertyName' => $pointerProperty, - 'data' => [] - ]; - - $subResults = $this->getResultsByKeys( - $subEntityClassName, $pointer, $usedPrimaryKeyList, $level + 1, $fetchObjectStorage - ); - - foreach ($subResults as $subResult) { - $raw = $subResult->getRawData(); - $dataKey = $raw[$pointer]; - if (!array_key_exists($dataKey, $subElements[$thisEntityProperty->getName()]['data'])) { - $subElements[$thisEntityProperty->getName()]['data'][$dataKey] = []; - } - $subElements[$thisEntityProperty->getName()]['data'][$dataKey][] = $subResult; - } - } - } - - $resultEntities = []; - - foreach ($results as $data) { - - $primaryKeyValue = $data[$baseEntity::getPrimaryKeyName()]; - - foreach ($subElements as $subElementKey => $subElementProperties) { - if (array_key_exists($primaryKeyValue, $subElementProperties['data'])) { - $data[$subElementKey] = $subElementProperties['data'][$primaryKeyValue]; - } - } - - $entity = - $fetchObjectStorage->getObject( - $baseEntityClassString, $primaryKeyValue, - function () use ($baseEntity, $data) { - return $baseEntity::fromIterable( - $data - ); - } - ); - - - foreach ($subElements as $subElementKey => $subElementProperties) { - $subElementEntityPointingPropertyName = $subElementProperties['pointingPropertyName']; - foreach ($entity->$subElementKey as $subElementEntity) { - $subElementEntity->$subElementEntityPointingPropertyName = $entity; - } - } - - - /*if ($level !== 0) { - foreach ($entity::getReflectionClass($entity)->getProperties() as $reflectionProperty) { - if ( - Reflection::propertyHasAttribute($reflectionProperty, ForeignKey::class) - && !$reflectionProperty->isInitialized($entity) - ) { - - //dump("Create Foreign Key " . $reflectionProperty->getName() ); - - $type = $reflectionProperty->getType(); - if ($type instanceof ReflectionNamedType) { - $propertyName = $reflectionProperty->getName(); - $columnName = $reflectionProperty->getName(); - $columnNameAttribute = Reflection::propertyGetAttribute($reflectionProperty, ColumnName::class); - - if ($columnNameAttribute !== null) { - $columnName = Reflection::attributeGetArgument($columnNameAttribute) ?? $columnName; - } - - $primaryKeyValueOfForeignKey = $entity->getRawData()[$columnName]; - $foreignKeyEntityBaseEntityClassName = $type->getName(); - - - $entity->$propertyName = $fetchObjectStorage->getObject( - $foreignKeyEntityBaseEntityClassName, $primaryKeyValueOfForeignKey, - function () use ($type, $primaryKeyValueOfForeignKey, $fetchObjectStorage) { - return $this->getResultByPrimaryKey( - $type->getName(), $primaryKeyValueOfForeignKey, $fetchObjectStorage - ); - } - ); - } - } - } - }* / - - - // Is it level 0? Check if any `#[ForeignKey]` is unset, and if yes, fetch it! - foreach ($entity::getReflectionClass($entity)->getProperties() as $reflectionProperty) { - if (Reflection::propertyHasAttribute($reflectionProperty, ForeignKey::class) && !$reflectionProperty->isInitialized($entity)) { - $type = $reflectionProperty->getType(); - if ($type instanceof ReflectionNamedType) { - - /** @var BaseEntity $foreignTypeBaseEntity * / - $foreignTypeBaseEntity = $type->getName(); - - $propertyName = $reflectionProperty->getName(); - $columnName = $reflectionProperty->getName(); - $columnNameAttribute = Reflection::propertyGetAttribute($reflectionProperty, ColumnName::class); - - - - if ($columnNameAttribute !== null) { - $columnName = Reflection::attributeGetArgument($columnNameAttribute) ?? $columnName; - } - - $primaryKeyValueOfForeignKey = $entity->getRawData()[$columnName]; - - if ($level === 0) { - $entity->$propertyName = $this->getResultByPrimaryKey( - $type->getName(), $primaryKeyValueOfForeignKey, $fetchObjectStorage - ); - } else { - if ($fetchObjectStorage->hasObject($type->getName(), $primaryKeyValueOfForeignKey)) { - $entity->$propertyName = $fetchObjectStorage->getStoredObject($type->getName(), $primaryKeyValueOfForeignKey); - } else { - // Only prepare: - $entity->$propertyName = $foreignTypeBaseEntity::prepare($primaryKeyValueOfForeignKey); - //dump('Skipped - notExists!'); - } - } - } - } - } - - - $resultEntities[] = $entity; - } - - return $fetchObjectStorage->linkRecursiveObjects($resultEntities); - */ + return (new Fetcher($this->getPdo())) + ->getResultsByKeys($baseEntityClassString, $key, $values); } /** diff --git a/src/Repository/Saver/Saver.php b/src/Repository/Saver/Saver.php new file mode 100644 index 0000000..b5e3ef8 --- /dev/null +++ b/src/Repository/Saver/Saver.php @@ -0,0 +1,190 @@ +listAllEntities($entities); + foreach ($entityList as $classEntityList) { + foreach($classEntityList as $entity) { + if($entity->getPrimaryKeyValue() === null) { + $this->insert($entity); + } else { + $this->update($entity); + } + } + } + } + + /** + * @param BaseEntity[] $entities + * @return array, array> + */ + private function listAllEntities(array $entities): array + { + $entityList = []; + + foreach($entities as $entity) { + $this->fillEntityListRecursively($entity, $entityList); + } + + return $entityList; + } + + /** + * @param BaseEntity $entity + * @param array, array> $entityList + */ + private function fillEntityListRecursively(BaseEntity $entity, array &$entityList): void + { + if(!array_key_exists($entity::class, $entityList)) { + $entityList[$entity::class] = []; + } + + if(!array_key_exists($entity->hash(), $entityList[$entity::class])) { + $entityList[$entity::class][$entity->hash()] = $entity; + + foreach($this->subEntitiesOf($entity) as $subEntity) { + $this->fillEntityListRecursively($subEntity, $entityList); + } + + foreach($this->supEntitiesOf($entity) as $subEntity) { + $this->fillEntityListRecursively($subEntity, $entityList); + } + + } + } + + /** + * @param BaseEntity $entity + * @return BaseEntity[] + */ + private function subEntitiesOf(BaseEntity $entity): array + { + $reflection = BaseEntity::getReflectionClass($entity); + + $subEntities = []; + + foreach ($reflection->getProperties() as $property) { + $fetchArrayAttribute = Reflection::propertyGetAttribute($property, FetchArray::class); + if($fetchArrayAttribute !== null) { + $propertyName = $property->getName(); + foreach($entity->$propertyName as $subEntity) { + $subEntities[] = $subEntity; + } + } + } + + return $subEntities; + } + + /** + * @param BaseEntity $entity + * @return BaseEntity[] + */ + private function supEntitiesOf(BaseEntity $entity): array + { + $reflection = BaseEntity::getReflectionClass($entity); + + $subEntities = []; + + foreach ($reflection->getProperties() as $property) { + + if(!$property->isInitialized($entity)) { + continue; + } + + $foreignKeyAttribute = Reflection::propertyGetAttribute($property, ForeignKey::class); + if($foreignKeyAttribute !== null) { + $propertyName = $property->getName(); + $subEntities[] = $entity->$propertyName; + } + } + + return $subEntities; + } + + private function update(BaseEntity $entity): void + { + $data = $entity->toArray(); + $primaryKeyName = $entity::getPrimaryKeyName(); + $primaryKeyValue = $data[$primaryKeyName]; + unset($data[$primaryKeyName]); + + $sql = sprintf( + /** @lang */ 'UPDATE %s SET %s WHERE %s=%s', + $entity::getTableName(), + implode( + ', ', + array_map( + static function ($key) { + return $key . '=:' . $key; + }, + array_keys($data) + ) + ), + $primaryKeyName, + ':' . $primaryKeyName + ); + + $statement = $this->pdo->prepare($sql); + + $data[$primaryKeyName] = $primaryKeyValue; + + $statement->execute($data); + + $entity->updateRawData(); + } + + private function insert(BaseEntity $entity): void + { + $data = $entity->toArray(); + + $sql = sprintf( + 'INSERT INTO %s (%s) VALUES (%s)', + $entity::getTableName(), + implode( + ',', + array_keys($data) + ), implode( + ',', + array_map( + static function (string $key) { + return ':' . $key; + }, + array_keys($data) + ) + ) + ); + + $convertedKeyData = []; + + foreach ($data as $key => $value) { + $convertedKeyData[':' . $key] = $value; + } + + $statement = $this->pdo->prepare($sql); + + $statement->execute($convertedKeyData); + + $entity->setPrimaryKeyValue($this->pdo->lastInsertId()); + + $entity->updateRawData(); + } +}