From 34657910da078dbe86dab274f8aaab65ac1dc62d Mon Sep 17 00:00:00 2001 From: homersimpsons Date: Wed, 24 Mar 2021 09:40:06 +0100 Subject: [PATCH] ResultIterator: Correctly wire classes to iterators (#252) Co-authored-by: Guillaume --- src/AbstractTDBMObject.php | 27 ++-- src/AlterableResultIterator.php | 7 +- src/DbRow.php | 16 +-- src/InnerResultIterator.php | 4 +- src/TDBMService.php | 71 ++++++---- src/Utils/BeanDescriptor.php | 79 ++++++++---- .../DirectForeignKeyMethodDescriptor.php | 28 +++- .../ManyToManyRelationshipPathDescriptor.php | 15 ++- src/Utils/ObjectBeanPropertyDescriptor.php | 35 +++-- src/Utils/PivotTableMethodsDescriptor.php | 43 +++++-- tests/Dao/TestUserDao.php | 33 ++--- tests/TDBMAbstractServiceTest.php | 2 +- tests/TDBMServiceTest.php | 121 ++++++++++-------- .../DirectForeignKeyMethodDescriptorTest.php | 2 +- .../Utils/PivotTableMethodsDescriptorTest.php | 2 +- 15 files changed, 294 insertions(+), 191 deletions(-) diff --git a/src/AbstractTDBMObject.php b/src/AbstractTDBMObject.php index 10df8c56..09e03e28 100644 --- a/src/AbstractTDBMObject.php +++ b/src/AbstractTDBMObject.php @@ -251,27 +251,15 @@ protected function set(string $var, $value, ?string $tableName = null): void } } - /** - * @param string $foreignKeyName - * @param AbstractTDBMObject $bean - */ - protected function setRef(string $foreignKeyName, AbstractTDBMObject $bean = null, string $tableName = null): void + protected function setRef(string $foreignKeyName, ?AbstractTDBMObject $bean, string $tableName, string $className, string $resultIteratorClass): void { - if ($tableName === null) { - if (count($this->dbRows) > 1) { - throw new TDBMException('This object is based on several tables. You must specify which table you are retrieving data from.'); - } elseif (count($this->dbRows) === 1) { - $tableName = (string) array_keys($this->dbRows)[0]; - } else { - throw new TDBMException('Please specify a table for this object.'); - } - } + assert($bean === null || is_a($bean, $className), new TDBMInvalidArgumentException('$bean should be `null` or `' . $className . '`. `' . ($bean === null ? 'null' : get_class($bean)) . '` provided.')); if (!isset($this->dbRows[$tableName])) { $this->registerTable($tableName); } - $oldLinkedBean = $this->dbRows[$tableName]->getRef($foreignKeyName); + $oldLinkedBean = $this->dbRows[$tableName]->getRef($foreignKeyName, $className, $resultIteratorClass); if ($oldLinkedBean !== null) { $oldLinkedBean->removeManyToOneRelationship($tableName, $foreignKeyName, $this); } @@ -291,7 +279,7 @@ protected function setRef(string $foreignKeyName, AbstractTDBMObject $bean = nul * * @return AbstractTDBMObject|null */ - protected function getRef(string $foreignKeyName, ?string $tableName = null) : ?AbstractTDBMObject + protected function getRef(string $foreignKeyName, string $tableName, string $className, string $resultIteratorClass) : ?AbstractTDBMObject { $tableName = $this->checkTableName($tableName); @@ -299,7 +287,7 @@ protected function getRef(string $foreignKeyName, ?string $tableName = null) : ? return null; } - return $this->dbRows[$tableName]->getRef($foreignKeyName); + return $this->dbRows[$tableName]->getRef($foreignKeyName, $className, $resultIteratorClass); } /** @@ -525,15 +513,16 @@ private function removeManyToOneRelationship(string $tableName, string $foreignK * * @return AlterableResultIterator */ - protected function retrieveManyToOneRelationshipsStorage(string $tableName, string $foreignKeyName, array $searchFilter, string $orderString = null) : AlterableResultIterator + protected function retrieveManyToOneRelationshipsStorage(string $tableName, string $foreignKeyName, array $searchFilter, ?string $orderString, string $resultIteratorClass) : AlterableResultIterator { + assert(is_a($resultIteratorClass, ResultIterator::class, true), new TDBMInvalidArgumentException('$resultIteratorClass should be a `'. ResultIterator::class. '`. `' . $resultIteratorClass . '` provided.')); $key = $tableName.'___'.$foreignKeyName; $alterableResultIterator = $this->getManyToOneAlterableResultIterator($tableName, $foreignKeyName); if ($this->status === TDBMObjectStateEnum::STATE_DETACHED || $this->status === TDBMObjectStateEnum::STATE_NEW || (isset($this->manyToOneRelationships[$key]) && $this->manyToOneRelationships[$key]->getUnderlyingResultIterator() !== null)) { return $alterableResultIterator; } - $unalteredResultIterator = $this->tdbmService->findObjects($tableName, $searchFilter, [], $orderString); + $unalteredResultIterator = $this->tdbmService->findObjects($tableName, $searchFilter, [], $orderString, [], null, null, $resultIteratorClass); $alterableResultIterator->setResultIterator($unalteredResultIterator->getIterator()); diff --git a/src/AlterableResultIterator.php b/src/AlterableResultIterator.php index f02ffb4a..3ed53efc 100644 --- a/src/AlterableResultIterator.php +++ b/src/AlterableResultIterator.php @@ -75,11 +75,8 @@ public function add($object): void { $this->alterations->attach($object, 'add'); - if ($this->resultArray !== null) { - $foundKey = array_search($object, $this->resultArray, true); - if ($foundKey === false) { - $this->resultArray[] = $object; - } + if ($this->resultArray !== null && !in_array($object, $this->resultArray, true)) { + $this->resultArray[] = $object; } } diff --git a/src/DbRow.php b/src/DbRow.php index 84d2780e..942ba346 100644 --- a/src/DbRow.php +++ b/src/DbRow.php @@ -235,16 +235,6 @@ public function set(string $var, $value): void { $this->_dbLoadIfNotLoaded(); - /* - // Ok, let's start by checking the column type - $type = $this->db_connection->getColumnType($this->dbTableName, $var); - - // Throws an exception if the type is not ok. - if (!$this->db_connection->checkType($value, $type)) { - throw new TDBMException("Error! Invalid value passed for attribute '$var' of table '$this->dbTableName'. Passed '$value', but expecting '$type'"); - } - */ - /*if ($var == $this->getPrimaryKey() && isset($this->dbRow[$var])) throw new TDBMException("Error! Changing primary key value is forbidden.");*/ $this->dbRow[$var] = $value; @@ -275,7 +265,7 @@ public function setRef(string $foreignKeyName, AbstractTDBMObject $bean = null): * * @return AbstractTDBMObject|null */ - public function getRef(string $foreignKeyName) : ?AbstractTDBMObject + public function getRef(string $foreignKeyName, string $className, string $resultIteratorClass) : ?AbstractTDBMObject { if (array_key_exists($foreignKeyName, $this->references)) { return $this->references[$foreignKeyName]; @@ -303,9 +293,9 @@ public function getRef(string $foreignKeyName) : ?AbstractTDBMObject // If the foreign key points to the primary key, let's use findObjectByPk if ($this->tdbmService->getPrimaryKeyColumns($foreignTableName) === $foreignColumns) { - return $this->tdbmService->findObjectByPk($foreignTableName, $filter, [], true); + return $this->tdbmService->findObjectByPk($foreignTableName, $filter, [], true, $className, $resultIteratorClass); } else { - return $this->tdbmService->findObject($foreignTableName, $filter); + return $this->tdbmService->findObject($foreignTableName, $filter, [], [], $className, $resultIteratorClass); } } } diff --git a/src/InnerResultIterator.php b/src/InnerResultIterator.php index 62a3162b..aaf7123e 100644 --- a/src/InnerResultIterator.php +++ b/src/InnerResultIterator.php @@ -209,7 +209,9 @@ public function next(): void list($actualClassName, $mainBeanTableName, $tablesUsed) = $this->tdbmService->_getClassNameFromBeanData($beanData); - if ($this->className !== null) { + // @TODO (gua) this is a weird hack to be able to force a TDBMObject... + // `$this->className` could be used to override `$actualClassName` + if ($this->className !== null && is_a($this->className, TDBMObject::class, true)) { $actualClassName = $this->className; } diff --git a/src/TDBMService.php b/src/TDBMService.php index 7234feea..19dd6809 100644 --- a/src/TDBMService.php +++ b/src/TDBMService.php @@ -165,6 +165,10 @@ class TDBMService * @var string */ private $beanNamespace; + /** + * @var string + */ + private $resultIteratorNamespace; /** * @var NamingStrategyInterface @@ -213,6 +217,7 @@ public function __construct(ConfigurationInterface $configuration) } $this->orderByAnalyzer = new OrderByAnalyzer($this->cache, $this->cachePrefix); $this->beanNamespace = $configuration->getBeanNamespace(); + $this->resultIteratorNamespace = $configuration->getResultIteratorNamespace(); $this->namingStrategy = $configuration->getNamingStrategy(); $this->configuration = $configuration; } @@ -364,7 +369,17 @@ private function deleteAllConstraintWithThisObject(AbstractTDBMObject $obj): voi foreach ($incomingFks as $incomingFk) { $filter = SafeFunctions::arrayCombine($incomingFk->getUnquotedLocalColumns(), $pks); - $results = $this->findObjects($incomingFk->getLocalTableName(), $filter); + $localTableName = $incomingFk->getLocalTableName(); + $results = $this->findObjects( + $localTableName, + $filter, + [], + null, + [], + null, + $this->beanNamespace . '\\' . $this->namingStrategy->getBeanClassName($localTableName), + $this->resultIteratorNamespace . '\\' . $this->namingStrategy->getResultIteratorClassName($localTableName) + ); foreach ($results as $bean) { $this->deleteCascade($bean); @@ -1134,7 +1149,7 @@ private function exploreChildrenTablesRelationships(SchemaAnalyzer $schemaAnalyz * * @throws TDBMException */ - public function findObjects(string $mainTable, $filter = null, array $parameters = array(), $orderString = null, array $additionalTablesFetch = array(), ?int $mode = null, string $className = null, string $resultIteratorClass = ResultIterator::class): ResultIterator + public function findObjects(string $mainTable, $filter, array $parameters, $orderString, array $additionalTablesFetch, ?int $mode, ?string $className, string $resultIteratorClass): ResultIterator { if (!is_a($resultIteratorClass, ResultIterator::class, true)) { throw new TDBMInvalidArgumentException('$resultIteratorClass should be a `'. ResultIterator::class. '`. `' . $resultIteratorClass . '` provided.'); @@ -1171,7 +1186,7 @@ public function findObjects(string $mainTable, $filter = null, array $parameters * * @throws TDBMException */ - public function findObjectsFromSql(string $mainTable, string $from, $filter = null, array $parameters = array(), $orderString = null, ?int $mode = null, string $className = null, string $resultIteratorClass = ResultIterator::class): ResultIterator + public function findObjectsFromSql(string $mainTable, string $from, $filter, array $parameters, $orderString, ?int $mode, ?string $className, string $resultIteratorClass): ResultIterator { if (!is_a($resultIteratorClass, ResultIterator::class, true)) { throw new TDBMInvalidArgumentException('$resultIteratorClass should be a `'. ResultIterator::class. '`. `' . $resultIteratorClass . '` provided.'); @@ -1205,15 +1220,17 @@ public function findObjectsFromSql(string $mainTable, string $from, $filter = nu * * @throws TDBMException */ - public function findObjectByPk(string $table, array $primaryKeys, array $additionalTablesFetch = array(), bool $lazy = false, string $className = null): AbstractTDBMObject + public function findObjectByPk(string $table, array $primaryKeys, array $additionalTablesFetch, bool $lazy, string $className, string $resultIteratorClass): AbstractTDBMObject { + assert(is_a($resultIteratorClass, ResultIterator::class, true), new TDBMInvalidArgumentException('$resultIteratorClass should be a `'. ResultIterator::class. '`. `' . $resultIteratorClass . '` provided.')); + assert(is_a($className, AbstractTDBMObject::class, true), new TDBMInvalidArgumentException('$className should be a `'. AbstractTDBMObject::class. '`. `' . $className . '` provided.')); $primaryKeys = $this->_getPrimaryKeysFromObjectData($table, $primaryKeys); $hash = $this->getObjectHash($primaryKeys); $dbRow = $this->objectStorage->get($table, $hash); if ($dbRow !== null) { $bean = $dbRow->getTDBMObject(); - if ($className !== null && !is_a($bean, $className)) { + if (!is_a($bean, $className)) { throw new TDBMException("TDBM cannot create a bean of class '".$className."'. The requested object was already loaded and its class is '".get_class($bean)."'"); } @@ -1226,20 +1243,12 @@ public function findObjectByPk(string $table, array $primaryKeys, array $additio $tables = $this->_getRelatedTablesByInheritance($table); // Only allowed if no inheritance. if (count($tables) === 1) { - if ($className === null) { - try { - $className = $this->getBeanClassName($table); - } catch (TDBMInvalidArgumentException $e) { - $className = TDBMObject::class; - } - } - // Let's construct the bean if (!isset($this->reflectionClassCache[$className])) { $this->reflectionClassCache[$className] = new \ReflectionClass($className); } // Let's bypass the constructor when creating the bean! - /** @var AbstractTDBMObject */ + /** @var AbstractTDBMObject $bean */ $bean = $this->reflectionClassCache[$className]->newInstanceWithoutConstructor(); $bean->_constructLazy($table, $primaryKeys, $this); @@ -1249,7 +1258,7 @@ public function findObjectByPk(string $table, array $primaryKeys, array $additio // Did not find the object in cache? Let's query it! try { - return $this->findObjectOrFail($table, $primaryKeys, [], $additionalTablesFetch, $className); + return $this->findObjectOrFail($table, $primaryKeys, [], $additionalTablesFetch, $className, $resultIteratorClass); } catch (NoBeanFoundException $exception) { throw NoBeanFoundException::missPrimaryKeyRecord($table, $primaryKeys, $this->getBeanClassName($table), $exception); } @@ -1262,15 +1271,16 @@ public function findObjectByPk(string $table, array $primaryKeys, array $additio * @param string|array|null $filter The SQL filters to apply to the query (the WHERE part). All columns must be prefixed by the table name (in the form: table.column) * @param mixed[] $parameters * @param string[] $additionalTablesFetch - * @param string $className Optional: The name of the class to instantiate. This class must extend the TDBMObject class. If none is specified, a TDBMObject instance will be returned + * @param string $className The name of the class to instantiate. This class must extend the TDBMObject class. If none is specified, a TDBMObject instance will be returned * * @return AbstractTDBMObject|null The object we want, or null if no object matches the filters * * @throws TDBMException */ - public function findObject(string $mainTable, $filter = null, array $parameters = array(), array $additionalTablesFetch = array(), string $className = null) : ?AbstractTDBMObject + public function findObject(string $mainTable, $filter, array $parameters, array $additionalTablesFetch, string $className, string $resultIteratorClass) : ?AbstractTDBMObject { - $objects = $this->findObjects($mainTable, $filter, $parameters, null, $additionalTablesFetch, self::MODE_ARRAY, $className); + assert(is_a($resultIteratorClass, ResultIterator::class, true), new TDBMInvalidArgumentException('$resultIteratorClass should be a `'. ResultIterator::class. '`. `' . $resultIteratorClass . '` provided.')); + $objects = $this->findObjects($mainTable, $filter, $parameters, null, $additionalTablesFetch, self::MODE_ARRAY, $className, $resultIteratorClass); return $this->getAtMostOneObjectOrFail($objects, $mainTable, $filter, $parameters); } @@ -1323,9 +1333,10 @@ private function getAtMostOneObjectOrFail(ResultIterator $objects, string $mainT * * @throws TDBMException */ - public function findObjectFromSql(string $mainTable, string $from, $filter = null, array $parameters = array(), ?string $className = null) : ?AbstractTDBMObject + public function findObjectFromSql(string $mainTable, string $from, $filter, array $parameters, ?string $className, string $resultIteratorClass) : ?AbstractTDBMObject { - $objects = $this->findObjectsFromSql($mainTable, $from, $filter, $parameters, null, self::MODE_ARRAY, $className); + assert(is_a($resultIteratorClass, ResultIterator::class, true), new TDBMInvalidArgumentException('$resultIteratorClass should be a `'. ResultIterator::class. '`. `' . $resultIteratorClass . '` provided.')); + $objects = $this->findObjectsFromSql($mainTable, $from, $filter, $parameters, null, self::MODE_ARRAY, $className, $resultIteratorClass); return $this->getAtMostOneObjectOrFail($objects, $mainTable, $filter, $parameters); } @@ -1342,7 +1353,7 @@ public function findObjectFromSql(string $mainTable, string $from, $filter = nul * * @throws TDBMException */ - public function findObjectsFromRawSql(string $mainTable, string $sql, array $parameters = array(), ?int $mode = null, string $className = null, string $sqlCount = null, string $resultIteratorClass = ResultIterator::class): ResultIterator + public function findObjectsFromRawSql(string $mainTable, string $sql, array $parameters, ?int $mode, ?string $className, ?string $sqlCount, string $resultIteratorClass): ResultIterator { if (!is_a($resultIteratorClass, ResultIterator::class, true)) { throw new TDBMInvalidArgumentException('$resultIteratorClass should be a `'. ResultIterator::class. '`. `' . $resultIteratorClass . '` provided.'); @@ -1367,15 +1378,16 @@ public function findObjectsFromRawSql(string $mainTable, string $sql, array $par * @param string|array|null $filter The SQL filters to apply to the query (the WHERE part). All columns must be prefixed by the table name (in the form: table.column) * @param mixed[] $parameters * @param string[] $additionalTablesFetch - * @param string $className Optional: The name of the class to instantiate. This class must extend the TDBMObject class. If none is specified, a TDBMObject instance will be returned + * @param string $className The name of the class to instantiate. This class must extend the TDBMObject class. If none is specified, a TDBMObject instance will be returned * * @return AbstractTDBMObject The object we want * * @throws TDBMException */ - public function findObjectOrFail(string $mainTable, $filter = null, array $parameters = array(), array $additionalTablesFetch = array(), string $className = null): AbstractTDBMObject + public function findObjectOrFail(string $mainTable, $filter, array $parameters, array $additionalTablesFetch, string $className, string $resultIteratorClass): AbstractTDBMObject { - $bean = $this->findObject($mainTable, $filter, $parameters, $additionalTablesFetch, $className); + assert(is_a($resultIteratorClass, ResultIterator::class, true), new TDBMInvalidArgumentException('$resultIteratorClass should be a `'. ResultIterator::class. '`. `' . $resultIteratorClass . '` provided.')); + $bean = $this->findObject($mainTable, $filter, $parameters, $additionalTablesFetch, $className, $resultIteratorClass); if ($bean === null) { throw NoBeanFoundException::missFilterRecord($mainTable); } @@ -1458,7 +1470,16 @@ private function fromCache(string $key, callable $closure) */ public function _getRelatedBeans(ManyToManyRelationshipPathDescriptor $pathDescriptor, AbstractTDBMObject $bean): ResultIterator { - return $this->findObjectsFromSql($pathDescriptor->getTargetName(), $pathDescriptor->getPivotFrom(), $pathDescriptor->getPivotWhere(), $pathDescriptor->getPivotParams($this->getPrimaryKeyValues($bean))); + return $this->findObjectsFromSql( + $pathDescriptor->getTargetName(), + $pathDescriptor->getPivotFrom(), + $pathDescriptor->getPivotWhere(), + $pathDescriptor->getPivotParams($this->getPrimaryKeyValues($bean)), + null, + null, + null, + $pathDescriptor->getResultIteratorClass() + ); } /** diff --git a/src/Utils/BeanDescriptor.php b/src/Utils/BeanDescriptor.php index 777af0aa..5c27f694 100644 --- a/src/Utils/BeanDescriptor.php +++ b/src/Utils/BeanDescriptor.php @@ -313,7 +313,7 @@ private function getPropertiesForTable(Table $table): array continue 2; } } - $propertyDescriptor = new ObjectBeanPropertyDescriptor($table, $fk, $this->namingStrategy, $this->beanNamespace, $this->annotationParser, $this->registry->getBeanForTableName($fk->getForeignTableName())); + $propertyDescriptor = new ObjectBeanPropertyDescriptor($table, $fk, $this->namingStrategy, $this->beanNamespace, $this->annotationParser, $this->registry->getBeanForTableName($fk->getForeignTableName()), $this->resultIteratorNamespace); // Check that this property is not an inheritance relationship $parentRelationship = $this->schemaAnalyzer->getParentRelationship($table->getName()); if ($parentRelationship !== null && $parentRelationship->getName() === $fk->getName()) { @@ -421,7 +421,7 @@ private function getDirectForeignKeysDescriptors(): array $descriptors = []; foreach ($fks as $fk) { - $desc = new DirectForeignKeyMethodDescriptor($fk, $this->table, $this->namingStrategy, $this->annotationParser, $this->beanNamespace); + $desc = new DirectForeignKeyMethodDescriptor($fk, $this->table, $this->namingStrategy, $this->annotationParser, $this->beanNamespace, $this->resultIteratorNamespace); $this->checkForDuplicate($desc); $descriptors[] = $desc; } @@ -445,13 +445,13 @@ private function getPivotTableDescriptors(): array if ($fks[0]->getForeignTableName() === $this->table->getName()) { list($localFk, $remoteFk) = $fks; - $desc = new PivotTableMethodsDescriptor($table, $localFk, $remoteFk, $this->namingStrategy, $this->beanNamespace, $this->annotationParser); + $desc = new PivotTableMethodsDescriptor($table, $localFk, $remoteFk, $this->namingStrategy, $this->annotationParser, $this->beanNamespace, $this->resultIteratorNamespace); $this->checkForDuplicate($desc); $descs[] = $desc; } if ($fks[1]->getForeignTableName() === $this->table->getName()) { list($remoteFk, $localFk) = $fks; - $desc = new PivotTableMethodsDescriptor($table, $localFk, $remoteFk, $this->namingStrategy, $this->beanNamespace, $this->annotationParser); + $desc = new PivotTableMethodsDescriptor($table, $localFk, $remoteFk, $this->namingStrategy, $this->annotationParser, $this->beanNamespace, $this->resultIteratorNamespace); $this->checkForDuplicate($desc); $descs[] = $desc; } @@ -765,6 +765,8 @@ public function generateDaoPhpCode(): ?FileGenerator $baseClassName = $this->namingStrategy->getBaseDaoClassName($tableName); $beanClassWithoutNameSpace = $this->namingStrategy->getBeanClassName($tableName); $beanClassName = $this->beanNamespace.'\\'.$beanClassWithoutNameSpace; + $resultIteratorClassWithoutNameSpace = $this->getResultIteratorClassName(); + $resultIteratorClass = $this->resultIteratorNamespace.'\\'.$resultIteratorClassWithoutNameSpace; $findByDaoCodeMethods = $this->generateFindByDaoCode($this->beanNamespace, $beanClassWithoutNameSpace, $class); @@ -855,7 +857,7 @@ public function generateDaoPhpCode(): ?FileGenerator } else { \$orderBy = null; } -return \$this->tdbmService->findObjects('$tableName', null, [], \$orderBy, [], null, null, \\$this->resultIteratorNamespace\\{$this->getResultIteratorClassName()}::class); +return \$this->tdbmService->findObjects('$tableName', null, [], \$orderBy, [], null, \\$beanClassName::class, \\$resultIteratorClass::class); EOF; $findAllMethod = new MethodGenerator( @@ -865,7 +867,7 @@ public function generateDaoPhpCode(): ?FileGenerator $findAllBody, (new DocBlockGenerator("Get all $beanClassWithoutNameSpace records."))->setWordWrap(false) ); - $findAllMethod->setReturnType($this->resultIteratorNamespace . '\\' . $this->getResultIteratorClassName()); + $findAllMethod->setReturnType($resultIteratorClass); $findAllMethod = $this->codeGeneratorListener->onBaseDaoFindAllGenerated($findAllMethod, $this, $this->configuration, $class); if ($findAllMethod !== null) { $class->addMethodFromGenerator($findAllMethod); @@ -895,7 +897,7 @@ public function generateDaoPhpCode(): ?FileGenerator 'getById', $parameters, MethodGenerator::FLAG_PUBLIC, - "return \$this->tdbmService->findObjectByPk('$tableName', [" . implode(', ', $primaryKeyFilter) . "], [], \$$lazyLoadingParameterName);", + "return \$this->tdbmService->findObjectByPk('$tableName', [" . implode(', ', $primaryKeyFilter) . "], [], \$$lazyLoadingParameterName, \\$beanClassName::class, \\$resultIteratorClass::class);", (new DocBlockGenerator( "Get $beanClassWithoutNameSpace specified by its ID (its primary key).", 'If the primary key does not exist, an exception is thrown.', @@ -945,7 +947,7 @@ public function generateDaoPhpCode(): ?FileGenerator if (\$this->defaultSort && \$orderBy == null) { \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection; } -return \$this->tdbmService->findObjects('$tableName', \$filter, \$parameters, \$orderBy, \$additionalTablesFetch, \$mode, null, \\$this->resultIteratorNamespace\\{$this->getResultIteratorClassName()}::class); +return \$this->tdbmService->findObjects('$tableName', \$filter, \$parameters, \$orderBy, \$additionalTablesFetch, \$mode, \\$beanClassName::class, \\$resultIteratorClass::class); EOF; @@ -972,7 +974,7 @@ public function generateDaoPhpCode(): ?FileGenerator ] ))->setWordWrap(false) ); - $findMethod->setReturnType($this->resultIteratorNamespace . '\\' . $this->getResultIteratorClassName()); + $findMethod->setReturnType($resultIteratorClass); $findMethod = $this->codeGeneratorListener->onBaseDaoFindGenerated($findMethod, $this, $this->configuration, $class); if ($findMethod !== null) { $class->addMethodFromGenerator($findMethod); @@ -982,7 +984,7 @@ public function generateDaoPhpCode(): ?FileGenerator if (\$this->defaultSort && \$orderBy == null) { \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection; } -return \$this->tdbmService->findObjectsFromSql('$tableName', \$from, \$filter, \$parameters, \$orderBy, \$mode, null, \\$this->resultIteratorNamespace\\{$this->getResultIteratorClassName()}::class); +return \$this->tdbmService->findObjectsFromSql('$tableName', \$from, \$filter, \$parameters, \$orderBy, \$mode, \\$beanClassName::class, \\$resultIteratorClass::class); EOF; $findFromSqlMethod = new MethodGenerator( @@ -1014,14 +1016,14 @@ public function generateDaoPhpCode(): ?FileGenerator ] ))->setWordWrap(false) ); - $findFromSqlMethod->setReturnType($this->resultIteratorNamespace . '\\' . $this->getResultIteratorClassName()); + $findFromSqlMethod->setReturnType($resultIteratorClass); $findFromSqlMethod = $this->codeGeneratorListener->onBaseDaoFindFromSqlGenerated($findFromSqlMethod, $this, $this->configuration, $class); if ($findFromSqlMethod !== null) { $class->addMethodFromGenerator($findFromSqlMethod); } $findFromRawSqlMethodBody = <<tdbmService->findObjectsFromRawSql('$tableName', \$sql, \$parameters, \$mode, null, \$countSql, \\$this->resultIteratorNamespace\\{$this->getResultIteratorClassName()}::class); +return \$this->tdbmService->findObjectsFromRawSql('$tableName', \$sql, \$parameters, \$mode, \\$beanClassName::class, \$countSql, \\$resultIteratorClass::class); EOF; $findFromRawSqlMethod = new MethodGenerator( @@ -1049,14 +1051,14 @@ public function generateDaoPhpCode(): ?FileGenerator ] ))->setWordWrap(false) ); - $findFromRawSqlMethod->setReturnType($this->resultIteratorNamespace . '\\' . $this->getResultIteratorClassName()); + $findFromRawSqlMethod->setReturnType($resultIteratorClass); $findFromRawSqlMethod = $this->codeGeneratorListener->onBaseDaoFindFromRawSqlGenerated($findFromRawSqlMethod, $this, $this->configuration, $class); if ($findFromRawSqlMethod !== null) { $class->addMethodFromGenerator($findFromRawSqlMethod); } $findOneMethodBody = <<tdbmService->findObject('$tableName', \$filter, \$parameters, \$additionalTablesFetch); +return \$this->tdbmService->findObject('$tableName', \$filter, \$parameters, \$additionalTablesFetch, \\$beanClassName::class, \\$resultIteratorClass::class); EOF; @@ -1087,7 +1089,7 @@ public function generateDaoPhpCode(): ?FileGenerator } $findOneFromSqlMethodBody = <<tdbmService->findObjectFromSql('$tableName', \$from, \$filter, \$parameters); +return \$this->tdbmService->findObjectFromSql('$tableName', \$from, \$filter, \$parameters, \\$beanClassName::class, \\$resultIteratorClass::class); EOF; $findOneFromSqlMethod = new MethodGenerator( @@ -1164,6 +1166,7 @@ public function generateResultIteratorPhpCode(): ?FileGenerator $tableName = $this->table->getName(); + $classNameWithoutNamespace = $this->namingStrategy->getResultIteratorClassName($tableName); $className = $this->namingStrategy->getResultIteratorClassName($tableName); $baseClassName = $this->namingStrategy->getBaseResultIteratorClassName($tableName); $beanClassWithoutNameSpace = $this->namingStrategy->getBeanClassName($tableName); @@ -1173,12 +1176,18 @@ public function generateResultIteratorPhpCode(): ?FileGenerator <<addUse(ResultIterator::class); $class->setName($baseClassName); - $class->setExtendedClass(ResultIterator::class); + $extends = $this->getExtendedResultIteratorClassName(); + if ($extends === null) { + $class->addUse(ResultIterator::class); + $class->setExtendedClass(ResultIterator::class); + } else { + $class->addUse($this->resultIteratorNamespace . '\\' . $extends); + $class->setExtendedClass($extends); + } $class->setDocBlock((new DocBlockGenerator( "The $baseClassName class will iterate over results of $beanClassWithoutNameSpace class.", @@ -1288,7 +1297,7 @@ private function generateFindByDaoCodeForIndex(Index $index, string $beanNamespa $fk = $this->isPartOfForeignKey($this->table, $this->table->getColumn($column)); if ($fk !== null) { if (!isset($elements[$fk->getName()])) { - $elements[$fk->getName()] = new ObjectBeanPropertyDescriptor($this->table, $fk, $this->namingStrategy, $this->beanNamespace, $this->annotationParser, $this->registry->getBeanForTableName($fk->getForeignTableName())); + $elements[$fk->getName()] = new ObjectBeanPropertyDescriptor($this->table, $fk, $this->namingStrategy, $this->beanNamespace, $this->annotationParser, $this->registry->getBeanForTableName($fk->getForeignTableName()), $this->resultIteratorNamespace); } } else { $elements[] = new ScalarBeanPropertyDescriptor($this->table, $this->table->getColumn($column), $this->namingStrategy, $this->annotationParser); @@ -1455,24 +1464,31 @@ private function generateGetUsedTablesCode(): MethodGenerator private function generateOnDeleteCode(): ?MethodGenerator { - $code = ''; + $setRefsToNullCode = ['parent::onDelete();']; $relationships = $this->getPropertiesForTable($this->table); foreach ($relationships as $relationship) { if ($relationship instanceof ObjectBeanPropertyDescriptor) { $tdbmFk = ForeignKey::createFromFk($relationship->getForeignKey()); - $code .= '$this->setRef('.var_export($tdbmFk->getCacheKey(), true).', null, '.var_export($this->table->getName(), true).");\n"; + $foreignTableName = $tdbmFk->getForeignTableName(); + $setRefsToNullCode[] = sprintf( + '$this->setRef(%s, %s, %s, %s, %s);', + var_export($tdbmFk->getCacheKey(), true), + 'null', + var_export($this->table->getName(), true), + '\\' . $this->beanNamespace . '\\' . $this->namingStrategy->getBeanClassName($foreignTableName) . '::class', + '\\' . $this->resultIteratorNamespace . '\\' . $this->namingStrategy->getResultIteratorClassName($foreignTableName) . '::class' + ); } } - if (!$code) { + if (count($setRefsToNullCode) === 1) { return null; } $method = new MethodGenerator('onDelete'); $method->setDocBlock(new DocBlockGenerator('Method called when the bean is removed from database.')); $method->setReturnType('void'); - $method->setBody('parent::onDelete(); -'.$code); + $method->setBody(implode(PHP_EOL, $setRefsToNullCode)); return $method; } @@ -1649,9 +1665,20 @@ public function getExtendedBeanClassName(): ?string $parentFk = $this->schemaAnalyzer->getParentRelationship($this->table->getName()); if ($parentFk !== null) { return $this->namingStrategy->getBeanClassName($parentFk->getForeignTableName()); - } else { - return null; } + return null; + } + + /** + * Returns the extended result iterator class name (without the namespace), or null if the result iterator is not extended. + */ + public function getExtendedResultIteratorClassName(): ?string + { + $parentFk = $this->schemaAnalyzer->getParentRelationship($this->table->getName()); + if ($parentFk !== null) { + return $this->namingStrategy->getResultIteratorClassName($parentFk->getForeignTableName()); + } + return null; } /** diff --git a/src/Utils/DirectForeignKeyMethodDescriptor.php b/src/Utils/DirectForeignKeyMethodDescriptor.php index 835c2b88..9a11239c 100644 --- a/src/Utils/DirectForeignKeyMethodDescriptor.php +++ b/src/Utils/DirectForeignKeyMethodDescriptor.php @@ -41,6 +41,10 @@ class DirectForeignKeyMethodDescriptor implements RelationshipMethodDescriptorIn * @var string */ private $beanNamespace; + /** + * @var string + */ + private $resultIteratorNamespace; /** * @param ForeignKeyConstraint $fk The foreign key pointing to our bean @@ -54,13 +58,15 @@ public function __construct( Table $mainTable, NamingStrategyInterface $namingStrategy, AnnotationParser $annotationParser, - string $beanNamespace + string $beanNamespace, + string $resultIteratorNamespace ) { $this->foreignKey = $fk; $this->mainTable = $mainTable; $this->namingStrategy = $namingStrategy; $this->annotationParser = $annotationParser; $this->beanNamespace = $beanNamespace; + $this->resultIteratorNamespace = $resultIteratorNamespace; } /** @@ -107,6 +113,16 @@ public function getBeanClassName(): string return $this->namingStrategy->getBeanClassName($this->foreignKey->getLocalTableName()); } + /** + * Returns the name of the class that will be returned by the getter (short name). + * + * @return string + */ + public function getResultIteratorClassName(): string + { + return $this->namingStrategy->getResultIteratorClassName($this->foreignKey->getLocalTableName()); + } + /** * Requests the use of an alternative name for this method. */ @@ -136,10 +152,11 @@ public function getCode() : array $getter->setReturnType('?' . $classType); $code = sprintf( - 'return $this->retrieveManyToOneRelationshipsStorage(%s, %s, %s)->first();', + 'return $this->retrieveManyToOneRelationshipsStorage(%s, %s, %s, null, %s)->first();', var_export($this->foreignKey->getLocalTableName(), true), var_export($tdbmFk->getCacheKey(), true), - $this->getFilters($this->foreignKey) + $this->getFilters($this->foreignKey), + '\\' . $this->resultIteratorNamespace . '\\' . $this->getResultIteratorClassName() .'::class' ); } else { $getterDocBlock = new DocBlockGenerator(sprintf('Returns the list of %s pointing to this bean via the %s column.', $beanClass, implode(', ', $this->foreignKey->getUnquotedLocalColumns()))); @@ -149,10 +166,11 @@ public function getCode() : array $getter->setReturnType(AlterableResultIterator::class); $code = sprintf( - 'return $this->retrieveManyToOneRelationshipsStorage(%s, %s, %s);', + 'return $this->retrieveManyToOneRelationshipsStorage(%s, %s, %s, null, %s);', var_export($this->foreignKey->getLocalTableName(), true), var_export($tdbmFk->getCacheKey(), true), - $this->getFilters($this->foreignKey) + $this->getFilters($this->foreignKey), + '\\' . $this->resultIteratorNamespace . '\\' . $this->getResultIteratorClassName() .'::class' ); } diff --git a/src/Utils/ManyToManyRelationshipPathDescriptor.php b/src/Utils/ManyToManyRelationshipPathDescriptor.php index 7ae7e083..3b3a1a12 100644 --- a/src/Utils/ManyToManyRelationshipPathDescriptor.php +++ b/src/Utils/ManyToManyRelationshipPathDescriptor.php @@ -3,6 +3,8 @@ namespace TheCodingMachine\TDBM\Utils; use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use TheCodingMachine\TDBM\ResultIterator; +use TheCodingMachine\TDBM\TDBMInvalidArgumentException; use function var_export; class ManyToManyRelationshipPathDescriptor @@ -28,6 +30,10 @@ class ManyToManyRelationshipPathDescriptor * @var array */ private $whereKeys; + /** + * @var string + */ + private $resultIteratorClass; /** * ManyToManyRelationshipPathDescriptor constructor. @@ -37,13 +43,15 @@ class ManyToManyRelationshipPathDescriptor * @param string[] $joinLocalKeys * @param string[] $whereKeys */ - public function __construct(string $targetTable, string $pivotTable, array $joinForeignKeys, array $joinLocalKeys, array $whereKeys) + public function __construct(string $targetTable, string $pivotTable, array $joinForeignKeys, array $joinLocalKeys, array $whereKeys, string $resultIteratorClass) { + assert(is_a($resultIteratorClass, ResultIterator::class, true), new TDBMInvalidArgumentException('$resultIteratorClass should be a `'. ResultIterator::class. '`. `' . $resultIteratorClass . '` provided.')); $this->targetTable = $targetTable; $this->pivotTable = $pivotTable; $this->joinForeignKeys = $joinForeignKeys; $this->joinLocalKeys = $joinLocalKeys; $this->whereKeys = $whereKeys; + $this->resultIteratorClass = $resultIteratorClass; } public static function generateModelKey(ForeignKeyConstraint $remoteFk, ForeignKeyConstraint $localFk): string @@ -95,4 +103,9 @@ public function getPivotParams(array $primaryKeys): array } return $params; } + + public function getResultIteratorClass(): string + { + return $this->resultIteratorClass; + } } diff --git a/src/Utils/ObjectBeanPropertyDescriptor.php b/src/Utils/ObjectBeanPropertyDescriptor.php index a96e9128..bfa3ad7f 100644 --- a/src/Utils/ObjectBeanPropertyDescriptor.php +++ b/src/Utils/ObjectBeanPropertyDescriptor.php @@ -33,6 +33,10 @@ class ObjectBeanPropertyDescriptor extends AbstractBeanPropertyDescriptor * @var BeanDescriptor */ private $foreignBeanDescriptor; + /** + * @var string + */ + private $resultIteratorNamespace; /** * ObjectBeanPropertyDescriptor constructor. @@ -49,7 +53,8 @@ public function __construct( NamingStrategyInterface $namingStrategy, string $beanNamespace, AnnotationParser $annotationParser, - BeanDescriptor $foreignBeanDescriptor + BeanDescriptor $foreignBeanDescriptor, + string $resultIteratorNamespace ) { parent::__construct($table, $namingStrategy); $this->foreignKey = $foreignKey; @@ -58,6 +63,7 @@ public function __construct( $this->table = $table; $this->namingStrategy = $namingStrategy; $this->foreignBeanDescriptor = $foreignBeanDescriptor; + $this->resultIteratorNamespace = $resultIteratorNamespace; } /** @@ -153,25 +159,27 @@ public function isPrimaryKey(): bool public function getGetterSetterCode(): array { $tableName = $this->table->getName(); + $foreignTableName = $this->foreignKey->getForeignTableName(); $getterName = $this->getGetterName(); $setterName = $this->getSetterName(); $isNullable = !$this->isCompulsory(); - $referencedBeanName = $this->namingStrategy->getBeanClassName($this->foreignKey->getForeignTableName()); + $referencedBeanName = $this->namingStrategy->getBeanClassName($foreignTableName); + $referencedResultIteratorName = $this->namingStrategy->getResultIteratorClassName($foreignTableName); $getter = new MethodGenerator($getterName); $getter->setDocBlock(new DocBlockGenerator('Returns the ' . $referencedBeanName . ' object bound to this object via the ' . implode(' and ', $this->foreignKey->getUnquotedLocalColumns()) . ' column.')); - /*$types = [ $referencedBeanName ]; - if ($isNullable) { - $types[] = 'null'; - } - $getter->getDocBlock()->setTag(new ReturnTag($types));*/ - $getter->setReturnType(($isNullable ? '?' : '') . $this->beanNamespace . '\\' . $referencedBeanName); $tdbmFk = ForeignKey::createFromFk($this->foreignKey); - $getter->setBody('return $this->getRef(' . var_export($tdbmFk->getCacheKey(), true) . ', ' . var_export($tableName, true) . ');'); + $getter->setBody(sprintf( + 'return $this->getRef(%s, %s, %s, %s);', + var_export($tdbmFk->getCacheKey(), true), + var_export($tableName, true), + '\\' . $this->beanNamespace . '\\' . $referencedBeanName . '::class', + '\\' . $this->resultIteratorNamespace . '\\' . $referencedResultIteratorName . '::class' + )); if ($this->isGetterProtected()) { $getter->setVisibility(AbstractMemberGenerator::VISIBILITY_PROTECTED); @@ -185,7 +193,14 @@ public function getGetterSetterCode(): array $setter->setReturnType('void'); - $setter->setBody('$this->setRef(' . var_export($tdbmFk->getCacheKey(), true) . ', $object, ' . var_export($tableName, true) . ');'); + $setter->setBody(sprintf( + '$this->setRef(%s, %s, %s, %s, %s);', + var_export($tdbmFk->getCacheKey(), true), + '$object', + var_export($tableName, true), + '\\' . $this->beanNamespace . '\\' . $referencedBeanName . '::class', + '\\' . $this->resultIteratorNamespace . '\\' . $referencedResultIteratorName . '::class' + )); if ($this->isSetterProtected()) { $setter->setVisibility(AbstractMemberGenerator::VISIBILITY_PROTECTED); diff --git a/src/Utils/PivotTableMethodsDescriptor.php b/src/Utils/PivotTableMethodsDescriptor.php index c94e347f..51cc6027 100644 --- a/src/Utils/PivotTableMethodsDescriptor.php +++ b/src/Utils/PivotTableMethodsDescriptor.php @@ -61,6 +61,10 @@ class PivotTableMethodsDescriptor implements RelationshipMethodDescriptorInterfa * @var string */ private $pathKey; + /** + * @var string + */ + private $resultIteratorNamespace; /** * @param Table $pivotTable The pivot table @@ -68,14 +72,22 @@ class PivotTableMethodsDescriptor implements RelationshipMethodDescriptorInterfa * @param ForeignKeyConstraint $remoteFk * @param NamingStrategyInterface $namingStrategy */ - public function __construct(Table $pivotTable, ForeignKeyConstraint $localFk, ForeignKeyConstraint $remoteFk, NamingStrategyInterface $namingStrategy, string $beanNamespace, AnnotationParser $annotationParser) - { + public function __construct( + Table $pivotTable, + ForeignKeyConstraint $localFk, + ForeignKeyConstraint $remoteFk, + NamingStrategyInterface $namingStrategy, + AnnotationParser $annotationParser, + string $beanNamespace, + string $resultIteratorNamespace + ) { $this->pivotTable = $pivotTable; $this->localFk = $localFk; $this->remoteFk = $remoteFk; $this->namingStrategy = $namingStrategy; - $this->beanNamespace = $beanNamespace; $this->annotationParser = $annotationParser; + $this->beanNamespace = $beanNamespace; + $this->resultIteratorNamespace = $resultIteratorNamespace; $this->pathKey = ManyToManyRelationshipPathDescriptor::generateModelKey($this->remoteFk, $this->localFk); } @@ -108,6 +120,16 @@ public function getBeanClassName(): string return $this->namingStrategy->getBeanClassName($this->remoteFk->getForeignTableName()); } + /** + * Returns the name of the class that will be returned by the getter (short name). + * + * @return string + */ + public function getResultIteratorClassName(): string + { + return $this->namingStrategy->getResultIteratorClassName($this->remoteFk->getForeignTableName()); + } + /** * Returns the plural name. * @@ -155,12 +177,15 @@ private function isAutoPivot(): bool public function getManyToManyRelationshipInstantiationCode(): string { - return 'new \TheCodingMachine\TDBM\Utils\ManyToManyRelationshipPathDescriptor('.var_export($this->remoteFk->getForeignTableName(), true). - ', '.var_export($this->remoteFk->getLocalTableName(), true). - ', '.$this->getArrayInlineCode($this->remoteFk->getUnquotedForeignColumns()). - ', '.$this->getArrayInlineCode($this->remoteFk->getUnquotedLocalColumns()). - ', '.$this->getArrayInlineCode($this->localFk->getUnquotedLocalColumns()). - ')'; + return sprintf( + "new \\TheCodingMachine\\TDBM\\Utils\\ManyToManyRelationshipPathDescriptor(%s, %s, %s, %s, %s, %s)", + var_export($this->remoteFk->getForeignTableName(), true), + var_export($this->remoteFk->getLocalTableName(), true), + $this->getArrayInlineCode($this->remoteFk->getUnquotedForeignColumns()), + $this->getArrayInlineCode($this->remoteFk->getUnquotedLocalColumns()), + $this->getArrayInlineCode($this->localFk->getUnquotedLocalColumns()), + '\\' . $this->resultIteratorNamespace . '\\' . $this->getResultIteratorClassName() . '::class' + ); } /** diff --git a/tests/Dao/TestUserDao.php b/tests/Dao/TestUserDao.php index 754f650d..f30a8438 100644 --- a/tests/Dao/TestUserDao.php +++ b/tests/Dao/TestUserDao.php @@ -6,6 +6,7 @@ use TheCodingMachine\TDBM\Test\Dao\Bean\CountryBean; use TheCodingMachine\TDBM\Test\Dao\Bean\UserBean; use TheCodingMachine\TDBM\Test\Dao\Generated\UserBaseDao; +use TheCodingMachine\TDBM\Test\ResultIterator\UserResultIterator; use TheCodingMachine\TDBM\UncheckedOrderBy; /** @@ -15,20 +16,16 @@ class TestUserDao extends UserBaseDao { /** * Returns the list of users by alphabetical order. - * - * @return UserBean[] */ - public function getUsersByAlphabeticalOrder() + public function getUsersByAlphabeticalOrder(): UserResultIterator { // The third parameter will be used in the "ORDER BY" clause of the SQL query. return $this->find(null, [], 'login ASC'); } /** * Returns the list of users by alphabetical order. - * - * @return UserBean[] */ - public function getUsersFromSqlByAlphabeticalOrder() + public function getUsersFromSqlByAlphabeticalOrder(): UserResultIterator { // The third parameter will be used in the "ORDER BY" clause of the SQL query. return $this->findFromSql('users', null, [], 'users.login ASC'); @@ -36,20 +33,16 @@ public function getUsersFromSqlByAlphabeticalOrder() /** * Returns the list of users by alphabetical order. - * - * @return UserBean[] */ - public function getUsersByCountryOrder() + public function getUsersByCountryOrder(): UserResultIterator { // The third parameter will be used in the "ORDER BY" clause of the SQL query. return $this->find(null, [], 'country.label ASC', ['country']); } /** * Returns the list of users by alphabetical order. - * - * @return UserBean[] */ - public function getUsersFromSqlByCountryOrder() + public function getUsersFromSqlByCountryOrder(): UserResultIterator { // The third parameter will be used in the "ORDER BY" clause of the SQL query. return $this->findFromSql('users JOIN country ON country.id = users.country_id', null, [], 'country.label ASC'); @@ -60,10 +53,8 @@ public function getUsersFromSqlByCountryOrder() * * @param string $login * @param string $mode - * - * @return \TheCodingMachine\TDBM\ResultIterator|UserBean[] */ - public function getUsersByLoginStartingWith($login = null, $mode = null) + public function getUsersByLoginStartingWith($login = null, $mode = null): UserResultIterator { return $this->find('login LIKE :login', ['login' => $login.'%'], null, [], $mode); } @@ -87,30 +78,24 @@ public function getUsersByManagerId($managerId) /** * Triggers an error because table "contacts" does not exist. - * - * @return \TheCodingMachine\TDBM\ResultIterator|UserBean[] */ - public function getUsersWrongTableName() + public function getUsersWrongTableName(): UserResultIterator { return $this->find('contacts.manager_id = 1'); } /** * Returns a list of users, sorted by a table on an external column. - * - * @return \TheCodingMachine\TDBM\ResultIterator|UserBean[] */ - public function getUsersByCountryName() + public function getUsersByCountryName(): UserResultIterator { return $this->find(null, [], 'country.label DESC'); } /** * A test to sort by function. - * - * @return \TheCodingMachine\TDBM\ResultIterator|UserBean[] */ - public function getUsersByReversedCountryName() + public function getUsersByReversedCountryName(): UserResultIterator { return $this->find(null, [], new UncheckedOrderBy('REVERSE(country.label) ASC')); } diff --git a/tests/TDBMAbstractServiceTest.php b/tests/TDBMAbstractServiceTest.php index 9e0fe4f2..86e5ff80 100644 --- a/tests/TDBMAbstractServiceTest.php +++ b/tests/TDBMAbstractServiceTest.php @@ -325,7 +325,7 @@ private static function initSchema(Connection $connection): void // Tables using @Json annotations $db->table('accounts') - ->column('id')->integer()->primaryKey()->autoIncrement() + ->column('id')->integer()->primaryKey()->autoIncrement()->notNull() ->column('name')->string(); $db->table('nodes') diff --git a/tests/TDBMServiceTest.php b/tests/TDBMServiceTest.php index 3c500463..2988a0ba 100644 --- a/tests/TDBMServiceTest.php +++ b/tests/TDBMServiceTest.php @@ -24,6 +24,11 @@ use Psr\Log\LogLevel; use Psr\Log\NullLogger; use TheCodingMachine\TDBM\Test\Dao\Bean\ContactBean; +use TheCodingMachine\TDBM\Test\ResultIterator\ContactResultIterator; +use TheCodingMachine\TDBM\Test\ResultIterator\CountryResultIterator; +use TheCodingMachine\TDBM\Test\ResultIterator\PersonResultIterator; +use TheCodingMachine\TDBM\Test\ResultIterator\RoleResultIterator; +use TheCodingMachine\TDBM\Test\ResultIterator\UserResultIterator; use Wa72\SimpleLogger\ArrayLogger; class TDBMServiceTest extends TDBMAbstractServiceTest @@ -127,7 +132,7 @@ public function testInsertMultipleDataAtOnceInInheritance(): void public function testCompleteSave(): void { - $beans = $this->tdbmService->findObjects('users', 'users.login = :login', ['login' => 'jane.doe'], null, [], null, TDBMObject::class); + $beans = $this->tdbmService->findObjects('users', 'users.login = :login', ['login' => 'jane.doe'], null, [], null, TDBMObject::class, ResultIterator::class); $jane = $beans[0]; $jane->setProperty('country_id', 2, 'users'); @@ -137,7 +142,7 @@ public function testCompleteSave(): void public function testCompleteSave2(): void { - $beans = $this->tdbmService->findObjects('users', 'users.login = :login', ['login' => 'jane.doe'], null, [], null, TDBMObject::class); + $beans = $this->tdbmService->findObjects('users', 'users.login = :login', ['login' => 'jane.doe'], null, [], null, TDBMObject::class, ResultIterator::class); $jane = $beans[0]; $this->assertEquals(2, $jane->getProperty('country_id', 'users')); @@ -210,8 +215,8 @@ public function testFindObjects(): void $result = $magicQuery->parse("SELECT DISTINCT users.id, users.login FROM users"); var_dump($result);*/ - $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class); - $beans2 = $this->tdbmService->findObjects('contact', 'contact.id = :id', ['id' => 1], null, [], null, TDBMObject::class); + $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class, ContactResultIterator::class); + $beans2 = $this->tdbmService->findObjects('contact', 'contact.id = :id', ['id' => 1], null, [], null, TDBMObject::class, ContactResultIterator::class); foreach ($beans as $bean) { $bean1 = $bean; @@ -243,7 +248,7 @@ public function testRawSqlFilterCountriesByUserCount(): void HAVING COUNT(users.id) > 1; SQL; /** @var Test\Dao\Bean\CountryBean[]|\Porpaginas\Result $beans */ - $beans = $this->tdbmService->findObjectsFromRawSql('country', $sql, [], null, Test\Dao\Bean\CountryBean::class); + $beans = $this->tdbmService->findObjectsFromRawSql('country', $sql, [], null, Test\Dao\Bean\CountryBean::class, null, CountryResultIterator::class); $count = 0; foreach ($beans as $country) { @@ -266,7 +271,7 @@ public function testRawSqlOrderCountriesByUserCount(): void SQL; /** @var Test\Dao\Bean\CountryBean[]|\Porpaginas\Result $beans */ - $beans = $this->tdbmService->findObjectsFromRawSql('country', $sql, [], null, Test\Dao\Bean\CountryBean::class); + $beans = $this->tdbmService->findObjectsFromRawSql('country', $sql, [], null, Test\Dao\Bean\CountryBean::class, null, CountryResultIterator::class); $count = 0; foreach ($beans as $country) { @@ -295,7 +300,7 @@ public function testRawSqlOrderUsersByCustomRoleOrder(): void SQL; /** @var Test\Dao\Bean\UserBean[]|\Porpaginas\Result $beans */ - $beans = $this->tdbmService->findObjectsFromRawSql('contact', $sql, [], null, Test\Dao\Bean\UserBean::class); + $beans = $this->tdbmService->findObjectsFromRawSql('contact', $sql, [], null, Test\Dao\Bean\UserBean::class, null, UserResultIterator::class); function getCustomOrder(Test\Dao\Bean\UserBean $contact) { @@ -319,7 +324,7 @@ function getCustomOrder(Test\Dao\Bean\UserBean $contact) public function testArrayAccess(): void { - $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class); + $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class, ResultIterator::class); $this->assertTrue(isset($beans[0])); $this->assertFalse(isset($beans[42])); @@ -346,7 +351,7 @@ public function testArrayAccess(): void public function testArrayAccessException(): void { $this->expectException('TheCodingMachine\TDBM\TDBMInvalidOffsetException'); - $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class); + $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class, ContactResultIterator::class); $beans[-1]; } @@ -358,7 +363,7 @@ public function testArrayAccessException(): void public function testArrayAccessException2(): void { $this->expectException('TheCodingMachine\TDBM\TDBMInvalidOffsetException'); - $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class); + $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class, ContactResultIterator::class); $beans['foo']; } @@ -370,7 +375,7 @@ public function testArrayAccessException2(): void public function testBeanGetException(): void { $this->expectException('TheCodingMachine\TDBM\TDBMException'); - $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class); + $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class, ResultIterator::class); $bean = $beans[0]; // we don't specify the table on inheritance table => exception. @@ -384,7 +389,7 @@ public function testBeanGetException(): void public function testBeanSetException(): void { $this->expectException('TheCodingMachine\TDBM\TDBMException'); - $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class); + $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class, ResultIterator::class); $bean = $beans[0]; // we don't specify the table on inheritance table => exception. @@ -393,7 +398,7 @@ public function testBeanSetException(): void public function testTake(): void { - $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class); + $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class, ResultIterator::class); $page = $beans->take(0, 2); @@ -420,7 +425,7 @@ public function testTake(): void public function testTakeInCursorMode(): void { - $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], TDBMService::MODE_CURSOR, TDBMObject::class); + $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], TDBMService::MODE_CURSOR, TDBMObject::class, ContactResultIterator::class); $page = $beans->take(0, 2); @@ -447,7 +452,7 @@ public function testTakeInCursorMode(): void public function testMap(): void { - $beans = $this->tdbmService->findObjects('person', null, [], 'person.id ASC', [], null, TDBMObject::class); + $beans = $this->tdbmService->findObjects('person', null, [], 'person.id ASC', [], null, TDBMObject::class, ResultIterator::class); $results = $beans->map(function ($item) { return $item->getProperty('id', 'person'); @@ -472,7 +477,7 @@ public function testMap(): void public function testUnsetException(): void { $this->expectException('TheCodingMachine\TDBM\TDBMException'); - $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class); + $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class, ContactResultIterator::class); unset($beans[0]); } @@ -484,7 +489,7 @@ public function testUnsetException(): void public function testSetException(): void { $this->expectException('TheCodingMachine\TDBM\TDBMException'); - $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class); + $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class, ContactResultIterator::class); $beans[0] = 'foo'; } @@ -496,7 +501,7 @@ public function testSetException(): void public function testPageUnsetException(): void { $this->expectException('TheCodingMachine\TDBM\TDBMException'); - $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class); + $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class, ContactResultIterator::class); $page = $beans->take(0, 1); unset($page[0]); } @@ -508,14 +513,14 @@ public function testPageUnsetException(): void public function testPageSetException(): void { $this->expectException('TheCodingMachine\TDBM\TDBMException'); - $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class); + $beans = $this->tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class, ContactResultIterator::class); $page = $beans->take(0, 1); $page[0] = 'foo'; } public function testToArray(): void { - $beans = $this->tdbmService->findObjects('contact', 'contact.id = :id', ['id' => 1], null, [], null, TDBMObject::class); + $beans = $this->tdbmService->findObjects('contact', 'contact.id = :id', ['id' => 1], null, [], null, TDBMObject::class, ResultIterator::class); $beanArray = $beans->toArray(); @@ -525,7 +530,7 @@ public function testToArray(): void public function testCursorMode(): void { - $beans = $this->tdbmService->findObjects('contact', 'contact.id = :id', ['id' => 1], null, [], TDBMService::MODE_CURSOR, TDBMObject::class); + $beans = $this->tdbmService->findObjects('contact', 'contact.id = :id', ['id' => 1], null, [], TDBMService::MODE_CURSOR, TDBMObject::class, ContactResultIterator::class); $this->assertInstanceOf('\\TheCodingMachine\\TDBM\\ResultIterator', $beans); @@ -557,7 +562,7 @@ public function testCursorMode(): void public function testSetFetchMode(): void { $this->tdbmService->setFetchMode(TDBMService::MODE_CURSOR); - $beans = $this->tdbmService->findObjects('contact', 'contact.id = :id', ['id' => 1], null, [], null, TDBMObject::class); + $beans = $this->tdbmService->findObjects('contact', 'contact.id = :id', ['id' => 1], null, [], null, TDBMObject::class, ContactResultIterator::class); $this->assertInstanceOf('\\TheCodingMachine\\TDBM\\ResultIterator', $beans); @@ -588,7 +593,7 @@ public function testInvalidSetFetchMode(): void public function testCursorModeException(): void { $this->expectException('TheCodingMachine\TDBM\TDBMException'); - $beans = $this->tdbmService->findObjects('contact', 'contact.id = :id', ['id' => 1], null, [], 99); + $beans = $this->tdbmService->findObjects('contact', 'contact.id = :id', ['id' => 1], null, [], 99, ContactBean::class, ContactResultIterator::class); } /** @@ -598,18 +603,18 @@ public function testCursorModeException(): void public function testTableNameException(): void { $this->expectException('TheCodingMachine\TDBM\TDBMException'); - $beans = $this->tdbmService->findObjects('foo bar'); + $beans = $this->tdbmService->findObjects('foo bar', null, [], null, [], null, AbstractTDBMObject::class, ResultIterator::class); } public function testLinkedTableFetch(): void { - $beans = $this->tdbmService->findObjects('contact', 'contact.id = :id', ['id' => 1], null, ['country'], null, TDBMObject::class); + $beans = $this->tdbmService->findObjects('contact', 'contact.id = :id', ['id' => 1], null, ['country'], null, TDBMObject::class, ContactResultIterator::class); $this->assertInstanceOf(ResultIterator::class, $beans); } public function testFindObject(): void { - $bean = $this->tdbmService->findObject('contact', 'contact.id = :id', ['id' => -42], [], TDBMObject::class); + $bean = $this->tdbmService->findObject('contact', 'contact.id = :id', ['id' => -42], [], TDBMObject::class, ContactResultIterator::class); $this->assertNull($bean); } @@ -620,20 +625,17 @@ public function testFindObject(): void public function testFindObjectOrFail(): void { $this->expectException('TheCodingMachine\TDBM\NoBeanFoundException'); - $bean = $this->tdbmService->findObjectOrFail('contact', 'contact.id = :id', ['id' => -42], [], TDBMObject::class); + $bean = $this->tdbmService->findObjectOrFail('contact', 'contact.id = :id', ['id' => -42], [], TDBMObject::class, ContactResultIterator::class); } + /** + * @throws NoBeanFoundException + */ public function testFindObjectByPkException(): void { - try { - $bean = $this->tdbmService->findObjectByPk('contact', ['id' => -42], [], false, TDBMObject::class); - $this->assertEquals(true, false); - } catch (NoBeanFoundException $exception) { - $this->assertEquals($exception->getMessage(), "No result found for query on table 'contact' for 'id' = -42"); - $this->assertEquals($exception->getPrimaryKeys(), ['id' => -42]); - $this->assertEquals($exception->getTableName(), 'contact'); - $this->assertEquals($exception->getClassName(), ContactBean::class); - } + $this->expectException(NoBeanFoundException::class); + $this->expectExceptionMessage("No result found for query on table 'contact' for 'id' = -42"); + $bean = $this->tdbmService->findObjectByPk('contact', ['id' => -42], [], false, TDBMObject::class, ContactResultIterator::class); } /** @@ -643,14 +645,14 @@ public function testFindObjectDuplicateRow(): void { $this->expectException(DuplicateRowException::class); - $bean = $this->tdbmService->findObject('contact'); + $bean = $this->tdbmService->findObject('contact', null, [], [], TDBMObject::class, ContactResultIterator::class); } public function testFindObjectsByBean(): void { - $countryBean = $this->tdbmService->findObject('country', 'id = :id', ['id' => 1], [], TDBMObject::class); + $countryBean = $this->tdbmService->findObject('country', 'id = :id', ['id' => 1], [], TDBMObject::class, ResultIterator::class); - $users = $this->tdbmService->findObjects('users', $countryBean, [], null, [], null, TDBMObject::class); + $users = $this->tdbmService->findObjects('users', $countryBean, [], null, [], null, TDBMObject::class, ResultIterator::class); $this->assertCount(1, $users); $this->assertEquals('jean.dupont', $users[0]->getProperty('login', 'users')); } @@ -675,7 +677,10 @@ public function testFindObjectsFromSql(): void 'roles JOIN roles_rights ON roles.id = roles_rights.role_id JOIN rights ON rights.label = roles_rights.right_label', 'rights.label = :right', array('right' => 'CAN_SING'), - 'roles.name DESC' + 'roles.name DESC', + null, + null, + RoleResultIterator::class ); $this->assertCount(2, $roles); $this->assertInstanceOf(AbstractTDBMObject::class, $roles[0]); @@ -693,7 +698,10 @@ public function testFindObjectsFromSqlBadTableName(): void 'roles JOIN roles_rights ON roles.id = roles_rights.role_id JOIN rights ON rights.label = roles_rights.right_label', 'rights.label = :right', array('right' => 'CAN_SING'), - 'name DESC' + 'name DESC', + null, + null, + ResultIterator::class ); } @@ -709,7 +717,10 @@ public function testFindObjectsFromSqlGroupBy(): void 'roles JOIN roles_rights ON roles.id = roles_rights.role_id JOIN rights ON rights.label = roles_rights.right_label', 'rights.label = :right GROUP BY roles.name', array('right' => 'CAN_SING'), - 'name DESC' + 'name DESC', + null, + null, + RoleResultIterator::class ); $role = $roles[0]; } @@ -724,7 +735,11 @@ public function testFindObjectsFromRawSqlBadTableName(): void $this->tdbmService->findObjectsFromRawSql( '#{azerty', 'roles JOIN roles_rights ON roles.id = roles_rights.role_id JOIN rights ON rights.label = roles_rights.right_label WHERE rights.label = :right', - array('right' => 'CAN_SING') + array('right' => 'CAN_SING'), + null, + TDBMObject::class, + null, + ResultIterator::class ); } @@ -734,7 +749,9 @@ public function testFindObjectFromSql(): void 'roles', 'roles JOIN roles_rights ON roles.id = roles_rights.role_id JOIN rights ON rights.label = roles_rights.right_label', 'rights.label = :right AND name = :name', - array('right' => 'CAN_SING', 'name' => 'Singers') + array('right' => 'CAN_SING', 'name' => 'Singers'), + null, + RoleResultIterator::class ); $this->assertInstanceOf(AbstractTDBMObject::class, $role); } @@ -750,7 +767,9 @@ public function testFindObjectFromSqlException(): void 'roles', 'roles JOIN roles_rights ON roles.id = roles_rights.role_id JOIN rights ON rights.label = roles_rights.right_label', 'rights.label = :right', - array('right' => 'CAN_SING') + array('right' => 'CAN_SING'), + null, + RoleResultIterator::class ); } @@ -763,7 +782,8 @@ public function testFindObjectsFromSqlHierarchyDown(): void array('name' => 'Robert Marley', 'name2' => 'Bill Shakespeare'), null, null, - TDBMObject::class + TDBMObject::class, + PersonResultIterator::class ); $this->assertCount(2, $users); $this->assertSame('robert.marley', $users[0]->getProperty('login', 'users')); @@ -778,7 +798,8 @@ public function testFindObjectsFromSqlHierarchyUp(): void array('login' => 'robert.marley', 'login2' => 'bill.shakespeare'), 'users.login DESC', null, - TDBMObject::class + TDBMObject::class, + UserResultIterator::class ); $this->assertCount(2, $users); $this->assertSame('Robert Marley', $users[0]->getProperty('name', 'person')); @@ -790,7 +811,7 @@ public function testLogger(): void $tdbmService = new TDBMService(new Configuration('TheCodingMachine\\TDBM\\Test\\Dao\\Bean', 'TheCodingMachine\\TDBM\\Test\\Dao', self::getConnection(), $this->getNamingStrategy(), null, null, $arrayLogger)); $tdbmService->setLogLevel(LogLevel::DEBUG); - $beans = $tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class); + $beans = $tdbmService->findObjects('contact', null, [], 'contact.id ASC', [], null, TDBMObject::class, ContactResultIterator::class); $beans->first(); $this->assertNotEmpty($arrayLogger->get()); @@ -798,14 +819,14 @@ public function testLogger(): void public function testFindObjectsCountWithOneToManyLink(): void { - $countries = $this->tdbmService->findObjects('country', "users.status = 'on' OR users.status = 'off'"); + $countries = $this->tdbmService->findObjects('country', "users.status = 'on' OR users.status = 'off'", [], null, [], null, null, CountryResultIterator::class); $this->assertEquals(3, $countries->count()); } public function testFindObjectsFromSqlCountWithOneToManyLink(): void { - $countries = $this->tdbmService->findObjectsFromSql('country', 'country LEFT JOIN users ON country.id = users.country_id', "users.status = 'on' OR users.status = 'off'"); + $countries = $this->tdbmService->findObjectsFromSql('country', 'country LEFT JOIN users ON country.id = users.country_id', "users.status = 'on' OR users.status = 'off'", [], null, null, null, CountryResultIterator::class); $this->assertEquals(3, $countries->count()); } diff --git a/tests/Utils/DirectForeignKeyMethodDescriptorTest.php b/tests/Utils/DirectForeignKeyMethodDescriptorTest.php index 7b5cb335..51b8e48c 100644 --- a/tests/Utils/DirectForeignKeyMethodDescriptorTest.php +++ b/tests/Utils/DirectForeignKeyMethodDescriptorTest.php @@ -15,7 +15,7 @@ public function testGetForeignKey(): void $table = $this->createMock(Table::class); $ns = $this->createMock(DefaultNamingStrategy::class); $ap = $this->createMock(AnnotationParser::class); - $descriptor = new DirectForeignKeyMethodDescriptor($fk, $table, $ns, $ap, ''); + $descriptor = new DirectForeignKeyMethodDescriptor($fk, $table, $ns, $ap, '', ''); $this->assertSame($fk, $descriptor->getForeignKey()); $this->assertSame($table, $descriptor->getMainTable()); diff --git a/tests/Utils/PivotTableMethodsDescriptorTest.php b/tests/Utils/PivotTableMethodsDescriptorTest.php index 434c5124..ad742005 100644 --- a/tests/Utils/PivotTableMethodsDescriptorTest.php +++ b/tests/Utils/PivotTableMethodsDescriptorTest.php @@ -17,7 +17,7 @@ public function testGetters(): void $remoteFk = new ForeignKeyConstraint(['foo2'], new Table('table2'), ['lol2']); $remoteFk->setLocalTable(new Table('table3')); $ns = $this->createMock(DefaultNamingStrategy::class); - $descriptor = new PivotTableMethodsDescriptor($table, $localFk, $remoteFk, $ns, 'Bean\Namespace', AnnotationParser::buildWithDefaultAnnotations([])); + $descriptor = new PivotTableMethodsDescriptor($table, $localFk, $remoteFk, $ns, AnnotationParser::buildWithDefaultAnnotations([]), 'Bean\Namespace', 'ResultIterator\Namespace'); $this->assertSame($table, $descriptor->getPivotTable()); $this->assertSame($localFk, $descriptor->getLocalFk());