From ae6936dda4fab2604bdf6de40b0b70b41b2423c4 Mon Sep 17 00:00:00 2001 From: Alexander Schnitzler <git@alexanderschnitzler.de> Date: Sun, 27 Jan 2019 19:48:56 +0100 Subject: [PATCH] [!!!][TASK] Replace config.persistence.classes typoscript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch removes support for the configuration of persistence related classes via typoscript. This is done as typoscript is too variable, i.e. the configuration may change depending on the day, the hour and whatever possibility typoscript has when it comes to conditions. The functionality must not vanish completely, but the configuration should be immutable and predictable at an early stage of the runtime. To achieve this, the configuration has to be added to files like EXT:Configuration/Extbase/Persistence/Classes.php This patch is considered breaking as the configuration via typoscript stops working immediately and the configuration syntax slightly changed. The easiest way to migrate to the new syntax is to have a look at configuration files in core extensions. Releases: master Resolves: #87623 Change-Id: Id1ceceafd10ec647507bca8078ebf62fe1b02d2a Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/59570 Tested-by: TYPO3com <noreply@typo3.com> Tested-by: Georg Ringer <georg.ringer@gmail.com> Tested-by: Jörg Bösche <typo3@joergboesche.de> Tested-by: Susanne Moog <look@susi.dev> Reviewed-by: Georg Ringer <georg.ringer@gmail.com> Reviewed-by: Jörg Bösche <typo3@joergboesche.de> Reviewed-by: Susanne Moog <look@susi.dev> --- .../Extbase/Persistence/Classes.php | 37 ++++ .../Configuration/TypoScript/setup.typoscript | 25 +-- .../Extbase/Persistence/Classes.php | 30 +++ .../Configuration/TypoScript/setup.typoscript | 24 +-- .../Classes/Cache/Frontend/NullFrontend.php | 156 ++++++++++++++++ ...sistenceclassesTyposcriptConfiguration.rst | 94 ++++++++++ .../Extbase/Persistence/Classes.php | 22 +++ .../Configuration/TypoScript/setup.typoscript | 25 --- .../sysext/extbase/Classes/Core/Bootstrap.php | 19 ++ .../Persistence/ClassesConfiguration.php | 84 +++++++++ .../ClassesConfigurationFactory.php | 139 ++++++++++++++ .../Persistence/Fixtures/Domain/Model/A.php | 25 +++ .../Persistence/Fixtures/Domain/Model/B.php | 23 +++ .../Persistence/Fixtures/Domain/Model/C.php | 23 +++ .../Classes/Persistence/Generic/Backend.php | 1 + .../Generic/Mapper/DataMapFactory.php | 76 +++----- .../Extbase/Persistence/Classes.php | 117 ++++++++++++ .../Extbase/Persistence/Classes.php | 23 +++ .../ext_typoscript_setup.typoscript | 29 --- .../Extbase/Persistence/Classes.php | 8 + .../b/ext_typoscript_setup.typoscript | 9 - .../ClassesConfigurationFactoryTest.php | 103 +++++++++++ .../Persistence/ClassesConfigurationTest.php | 173 ++++++++++++++++++ .../extbase/ext_typoscript_setup.typoscript | 78 -------- 24 files changed, 1102 insertions(+), 241 deletions(-) create mode 100644 typo3/sysext/belog/Configuration/Extbase/Persistence/Classes.php create mode 100644 typo3/sysext/beuser/Configuration/Extbase/Persistence/Classes.php create mode 100644 typo3/sysext/core/Classes/Cache/Frontend/NullFrontend.php create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Breaking-87623-ReplaceConfigpersistenceclassesTyposcriptConfiguration.rst create mode 100644 typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial/Configuration/Extbase/Persistence/Classes.php create mode 100644 typo3/sysext/extbase/Classes/Persistence/ClassesConfiguration.php create mode 100644 typo3/sysext/extbase/Classes/Persistence/ClassesConfigurationFactory.php create mode 100644 typo3/sysext/extbase/Classes/Persistence/Fixtures/Domain/Model/A.php create mode 100644 typo3/sysext/extbase/Classes/Persistence/Fixtures/Domain/Model/B.php create mode 100644 typo3/sysext/extbase/Classes/Persistence/Fixtures/Domain/Model/C.php create mode 100644 typo3/sysext/extbase/Configuration/Extbase/Persistence/Classes.php create mode 100644 typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Configuration/Extbase/Persistence/Classes.php delete mode 100644 typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/ext_typoscript_setup.typoscript create mode 100644 typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/class_overriding/b/Configuration/Extbase/Persistence/Classes.php delete mode 100644 typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/class_overriding/b/ext_typoscript_setup.typoscript create mode 100644 typo3/sysext/extbase/Tests/Unit/Persistence/ClassesConfigurationFactoryTest.php create mode 100644 typo3/sysext/extbase/Tests/Unit/Persistence/ClassesConfigurationTest.php diff --git a/typo3/sysext/belog/Configuration/Extbase/Persistence/Classes.php b/typo3/sysext/belog/Configuration/Extbase/Persistence/Classes.php new file mode 100644 index 000000000000..8132214ce705 --- /dev/null +++ b/typo3/sysext/belog/Configuration/Extbase/Persistence/Classes.php @@ -0,0 +1,37 @@ +<?php +declare(strict_types = 1); + +return [ + \TYPO3\CMS\Belog\Domain\Model\LogEntry::class => [ + 'tableName' => 'sys_log', + 'properties' => [ + 'backendUserUid' => [ + 'fieldName' => 'userid' + ], + 'recordUid' => [ + 'fieldName' => 'recuid' + ], + 'tableName' => [ + 'fieldName' => 'tablename' + ], + 'recordPid' => [ + 'fieldName' => 'recpid' + ], + 'detailsNumber' => [ + 'fieldName' => 'details_nr' + ], + 'ip' => [ + 'fieldName' => 'IP' + ], + 'workspaceUid' => [ + 'fieldName' => 'workspace' + ], + 'newId' => [ + 'fieldName' => 'NEWid' + ], + ] + ], + \TYPO3\CMS\Belog\Domain\Model\Workspace::class => [ + 'tableName' => 'sys_workspace', + ], +]; diff --git a/typo3/sysext/belog/Configuration/TypoScript/setup.typoscript b/typo3/sysext/belog/Configuration/TypoScript/setup.typoscript index 9e515186d209..31ba3f7f04b9 100644 --- a/typo3/sysext/belog/Configuration/TypoScript/setup.typoscript +++ b/typo3/sysext/belog/Configuration/TypoScript/setup.typoscript @@ -1,27 +1,4 @@ module.tx_belog { - persistence.classes { - TYPO3\CMS\Belog\Domain\Model\LogEntry { - mapping { - tableName = sys_log - columns { - userid.mapOnProperty = backendUserUid - recuid.mapOnProperty = recordUid - tablename.mapOnProperty = tableName - recpid.mapOnProperty = recordPid - details_nr.mapOnProperty = detailsNumber - IP.mapOnProperty = ip - workspace.mapOnProperty = workspaceUid - NEWid.mapOnProperty = newId - } - } - } - TYPO3\CMS\Belog\Domain\Model\Workspace { - mapping { - tableName = sys_workspace - } - } - } - settings { selectableNumberOfLogEntries { 20 = 20 @@ -54,4 +31,4 @@ module.tx_belog { -1 = actionErrors } } -} \ No newline at end of file +} diff --git a/typo3/sysext/beuser/Configuration/Extbase/Persistence/Classes.php b/typo3/sysext/beuser/Configuration/Extbase/Persistence/Classes.php new file mode 100644 index 000000000000..9a94608ed8ac --- /dev/null +++ b/typo3/sysext/beuser/Configuration/Extbase/Persistence/Classes.php @@ -0,0 +1,30 @@ +<?php +declare(strict_types = 1); + +return [ + \TYPO3\CMS\Beuser\Domain\Model\BackendUser::class => [ + 'tableName' => 'be_users', + 'properties' => [ + 'allowedLanguages' => [ + 'fieldName' => 'allowed_languages' + ], + 'fileMountPoints' => [ + 'fieldName' => 'file_mountpoints' + ], + 'dbMountPoints' => [ + 'fieldName' => 'db_mountpoints' + ], + 'backendUserGroups' => [ + 'fieldName' => 'usergroup' + ], + ] + ], + \TYPO3\CMS\Beuser\Domain\Model\BackendUserGroup::class => [ + 'tableName' => 'be_groups', + 'properties' => [ + 'subGroups' => [ + 'fieldName' => 'subgroup' + ], + ] + ], +]; diff --git a/typo3/sysext/beuser/Configuration/TypoScript/setup.typoscript b/typo3/sysext/beuser/Configuration/TypoScript/setup.typoscript index 6ec5be88f0b9..ca8870f1e71a 100644 --- a/typo3/sysext/beuser/Configuration/TypoScript/setup.typoscript +++ b/typo3/sysext/beuser/Configuration/TypoScript/setup.typoscript @@ -1,26 +1,4 @@ // Model/table mapping -config.tx_extbase.persistence.classes { - TYPO3\CMS\Beuser\Domain\Model\BackendUser { - mapping { - tableName = be_users - columns { - allowed_languages.mapOnProperty = allowedLanguages - file_mountpoints.mapOnProperty = fileMountPoints - db_mountpoints.mapOnProperty = dbMountPoints - usergroup.mapOnProperty = backendUserGroups - } - } - } - TYPO3\CMS\Beuser\Domain\Model\BackendUserGroup { - mapping { - tableName = be_groups - columns { - subgroup.mapOnProperty = subGroups - } - } - } -} - module.tx_beuser { persistence { storagePid = 0 @@ -33,4 +11,4 @@ module.tx_beuser { // or if there are other settings set. dummy = foo } -} \ No newline at end of file +} diff --git a/typo3/sysext/core/Classes/Cache/Frontend/NullFrontend.php b/typo3/sysext/core/Classes/Cache/Frontend/NullFrontend.php new file mode 100644 index 000000000000..2b604cbf8f7b --- /dev/null +++ b/typo3/sysext/core/Classes/Cache/Frontend/NullFrontend.php @@ -0,0 +1,156 @@ +<?php +declare(strict_types = 1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ +namespace TYPO3\CMS\Core\Cache\Frontend; + +use TYPO3\CMS\Core\Cache\Backend\NullBackend; + +/** + * Class TYPO3\CMS\Core\Cache\Frontend\NullFrontend + */ +class NullFrontend implements FrontendInterface +{ + /** + * @var string + */ + private $identifier; + + /** + * @param string $identifier + */ + public function __construct(string $identifier) + { + $this->identifier = $identifier; + } + + /** + * Returns this cache's identifier + * + * @return string The identifier for this cache + */ + public function getIdentifier() + { + return $this->identifier; + } + + /** + * Returns the backend used by this cache + * + * @return \TYPO3\CMS\Core\Cache\Backend\BackendInterface The backend used by this cache + */ + public function getBackend() + { + return new NullBackend(''); + } + + /** + * Saves data in the cache. + * + * @param string $entryIdentifier Something which identifies the data - depends on concrete cache + * @param mixed $data The data to cache - also depends on the concrete cache implementation + * @param array $tags Tags to associate with this cache entry + * @param int $lifetime Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited lifetime. + */ + public function set($entryIdentifier, $data, array $tags = [], $lifetime = null) + { + } + + /** + * Finds and returns data from the cache. + * + * @param string $entryIdentifier Something which identifies the cache entry - depends on concrete cache + * @return mixed + */ + public function get($entryIdentifier) + { + return null; + } + + /** + * Checks if a cache entry with the specified identifier exists. + * + * @param string $entryIdentifier An identifier specifying the cache entry + * @return bool TRUE if such an entry exists, FALSE if not + */ + public function has($entryIdentifier) + { + return false; + } + + /** + * Removes the given cache entry from the cache. + * + * @param string $entryIdentifier An identifier specifying the cache entry + * @return bool TRUE if such an entry exists, FALSE if not + */ + public function remove($entryIdentifier) + { + return false; + } + + /** + * Removes all cache entries of this cache. + */ + public function flush() + { + } + + /** + * Removes all cache entries of this cache which are tagged by the specified tag. + * + * @param string $tag The tag the entries must have + */ + public function flushByTag($tag) + { + } + + /** + * Removes all cache entries of this cache which are tagged by any of the specified tags. + * + * @param string[] $tags List of tags + */ + public function flushByTags(array $tags) + { + } + + /** + * Does garbage collection + */ + public function collectGarbage() + { + } + + /** + * Checks the validity of an entry identifier. Returns TRUE if it's valid. + * + * @param string $identifier An identifier to be checked for validity + * @return bool + */ + public function isValidEntryIdentifier($identifier) + { + return false; + } + + /** + * Checks the validity of a tag. Returns TRUE if it's valid. + * + * @param string $tag A tag to be checked for validity + * @return bool + */ + public function isValidTag($tag) + { + return false; + } +} diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-87623-ReplaceConfigpersistenceclassesTyposcriptConfiguration.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-87623-ReplaceConfigpersistenceclassesTyposcriptConfiguration.rst new file mode 100644 index 000000000000..06abf95795e1 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Breaking-87623-ReplaceConfigpersistenceclassesTyposcriptConfiguration.rst @@ -0,0 +1,94 @@ +.. include:: ../../Includes.txt + +============================================================================== +Breaking: #87623 - Replace config.persistence.classes typoscript configuration +============================================================================== + +See :issue:`87623` + +Description +=========== + +The configuration of classes in the context of the Extbase persistence is no longer possible via typoscript. +All typoscript concerning the configuration of classes in that context needs to be converted to php, residing +in `EXT:Configuration/Extbase/Persistence/Classes.php`. + + +Impact +====== + +Unless converted to php, the configuration in typoscript does no longer have any effect and therefore the following things do no longer work: + +- Overwriting table names for models whose table name derived by conventions differ from the desired one. +- The mapping of database field names to model property names +- The definition of model sub classes which is necessary for a proper implementation of single table inheritance. + + +Affected Installations +====================== + +All installations that configure persistence related classes via typoscript. + + +Migration +========= + +Every extension that used typoscript for such configuration must provide a php configuration class called: +`EXT:Configuration/Extbase/Persistence/Classes.php` + +The migration is best described by an example: + +.. code-block:: typoscript + + config.tx_extbase { + persistence { + classes { + TYPO3\CMS\Extbase\Domain\Model\FileMount { + mapping { + tableName = sys_filemounts + columns { + title.mapOnProperty = title + path.mapOnProperty = path + base.mapOnProperty = isAbsolutePath + } + } + } + } + } + } + +This configuration will look like this, defined in php: + +.. code-block:: php + + <?php + declare(strict_types = 1); + + return [ + \TYPO3\CMS\Extbase\Domain\Model\FileMount::class => [ + 'tableName' => 'sys_filemounts', + 'properties' => [ + 'title' => [ + 'fieldName' => 'title' + ], + 'path' => [ + 'fieldName' => 'path' + ], + 'isAbsolutePath' => [ + 'fieldName' => 'base' + ], + ], + ], + ]; + +A few things are noteworthy here: + +- The typoscript node `mapping` has been dropped and all sub nodes like `tableName` and `columns` are now located directly + in the top node, i.e. the class name. +- The mapping of columns changed due to the fact that `mapOnProperty` has been dropped and the mapping direction changed. + With typoscript the top nodes were called like the class names which indicates the mapping direction model to table. But + then, one had to define a mapping by columns instead of properties, which means, the mapping directions was reversed, + forcing you to map database table fields on properties. This was quite confusing and the configuration is now eased as + one can always think in the model to table mapping direction. + +.. index:: TypoScript, NotScanned, ext:extbase diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial/Configuration/Extbase/Persistence/Classes.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial/Configuration/Extbase/Persistence/Classes.php new file mode 100644 index 000000000000..e7d694043702 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial/Configuration/Extbase/Persistence/Classes.php @@ -0,0 +1,22 @@ +<?php +declare(strict_types = 1); + +return [ + \OliverHader\IrreTutorial\Domain\Model\Content::class => [ + 'tableName' => 'tt_content', + 'properties' => [ + 'hotels' => [ + 'fieldName' => 'tx_irretutorial_1nff_hotels' + ], + ] + ], + \OliverHader\IrreTutorial\Domain\Model\Hotel::class => [ + 'tableName' => 'tx_irretutorial_1nff_hotel', + ], + \OliverHader\IrreTutorial\Domain\Model\Offer::class => [ + 'tableName' => 'tx_irretutorial_1nff_offer', + ], + \OliverHader\IrreTutorial\Domain\Model\Price::class => [ + 'tableName' => 'tx_irretutorial_1nff_price', + ], +]; diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial/Configuration/TypoScript/setup.typoscript b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial/Configuration/TypoScript/setup.typoscript index b614b9378dca..9882ab7d7622 100644 --- a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial/Configuration/TypoScript/setup.typoscript +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial/Configuration/TypoScript/setup.typoscript @@ -6,31 +6,6 @@ plugin.tx_irretutorial { } persistence { storagePid.data = page:uid - classes { - OliverHader\IrreTutorial\Domain\Model\Content { - mapping { - tableName = tt_content - columns { - tx_irretutorial_1nff_hotels.mapOnProperty = hotels - } - } - } - OliverHader\IrreTutorial\Domain\Model\Hotel { - mapping { - tableName = tx_irretutorial_1nff_hotel - } - } - OliverHader\IrreTutorial\Domain\Model\Offer { - mapping { - tableName = tx_irretutorial_1nff_offer - } - } - OliverHader\IrreTutorial\Domain\Model\Price { - mapping { - tableName = tx_irretutorial_1nff_price - } - } - } } } diff --git a/typo3/sysext/extbase/Classes/Core/Bootstrap.php b/typo3/sysext/extbase/Classes/Core/Bootstrap.php index 4b19deaa7552..d23405a29420 100644 --- a/typo3/sysext/extbase/Classes/Core/Bootstrap.php +++ b/typo3/sysext/extbase/Classes/Core/Bootstrap.php @@ -19,8 +19,11 @@ namespace TYPO3\CMS\Extbase\Core; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Backend\Routing\Route; +use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Core\Environment; +use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Mvc\Web\Response as ExtbaseResponse; +use TYPO3\CMS\Extbase\Persistence\ClassesConfigurationFactory; /** * Creates a request an dispatches it to the controller which was specified @@ -30,6 +33,11 @@ use TYPO3\CMS\Extbase\Mvc\Web\Response as ExtbaseResponse; */ class Bootstrap implements \TYPO3\CMS\Extbase\Core\BootstrapInterface { + /** + * @var array + */ + public static $persistenceClasses = []; + /** * Back reference to the parent content object * This has to be public as it is set directly from TYPO3 @@ -75,6 +83,7 @@ class Bootstrap implements \TYPO3\CMS\Extbase\Core\BootstrapInterface } $this->initializeObjectManager(); $this->initializeConfiguration($configuration); + $this->initializePersistenceClassesConfiguration(); $this->initializePersistence(); } @@ -110,6 +119,16 @@ class Bootstrap implements \TYPO3\CMS\Extbase\Core\BootstrapInterface // todo: to fetch this configuration from the configuration manager. } + /** + * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException + */ + private function initializePersistenceClassesConfiguration(): void + { + $cacheManager = GeneralUtility::makeInstance(CacheManager::class); + GeneralUtility::makeInstance(ClassesConfigurationFactory::class, $cacheManager) + ->createClassesConfiguration(); + } + /** * Initializes the persistence framework * diff --git a/typo3/sysext/extbase/Classes/Persistence/ClassesConfiguration.php b/typo3/sysext/extbase/Classes/Persistence/ClassesConfiguration.php new file mode 100644 index 000000000000..71b561e4ffd2 --- /dev/null +++ b/typo3/sysext/extbase/Classes/Persistence/ClassesConfiguration.php @@ -0,0 +1,84 @@ +<?php +declare(strict_types = 1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ +namespace TYPO3\CMS\Extbase\Persistence; + +/** + * Class TYPO3\CMS\Extbase\Persistence\ClassesConfiguration + */ +class ClassesConfiguration +{ + /** + * @var array + */ + private $configuration; + + /** + * @param array $configuration + */ + public function __construct(array $configuration) + { + $this->configuration = $configuration; + } + + /** + * @param string $className + * @return bool + */ + public function hasClass(string $className): bool + { + return array_key_exists($className, $this->configuration); + } + + /** + * @param string $className + * @return array|null + */ + public function getConfigurationFor(string $className): ?array + { + return $this->configuration[$className] ?? null; + } + + /** + * Resolves all subclasses for the given set of (sub-)classes. + * The whole classes configuration is used to determine all subclasses recursively. + * + * @param string $className + * @return array An numeric array that contains all available subclasses-strings as values. + */ + public function getSubClasses(string $className): array + { + return $this->resolveSubClassesRecursive($className); + } + + /** + * @param string $className + * @param array $subClasses + * @return array + */ + private function resolveSubClassesRecursive(string $className, array $subClasses = []): array + { + foreach ($this->configuration[$className]['subclasses'] ?? [] as $subclass) { + if (in_array($subclass, $subClasses, true)) { + continue; + } + + $subClasses[] = $subclass; + $subClasses = $this->resolveSubClassesRecursive($subclass, $subClasses); + } + + return $subClasses; + } +} diff --git a/typo3/sysext/extbase/Classes/Persistence/ClassesConfigurationFactory.php b/typo3/sysext/extbase/Classes/Persistence/ClassesConfigurationFactory.php new file mode 100644 index 000000000000..baf346f4ca95 --- /dev/null +++ b/typo3/sysext/extbase/Classes/Persistence/ClassesConfigurationFactory.php @@ -0,0 +1,139 @@ +<?php +declare(strict_types = 1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ +namespace TYPO3\CMS\Extbase\Persistence; + +use TYPO3\CMS\Core\Cache\CacheManager; +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; +use TYPO3\CMS\Core\Cache\Frontend\NullFrontend; +use TYPO3\CMS\Core\Core\Environment; +use TYPO3\CMS\Core\Package\PackageManager; +use TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\ArrayUtility; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; +use TYPO3\CMS\Extbase\DomainObject\AbstractValueObject; + +/** + * Class TYPO3\CMS\Extbase\Persistence\ClassesConfiguration + */ +final class ClassesConfigurationFactory implements SingletonInterface +{ + /** + * @var FrontendInterface + */ + private $cacheFrontend; + + /** + * @param CacheManager|null $cacheManager can be null to disable caching in this factory + */ + public function __construct(CacheManager $cacheManager = null) + { + $cacheIdentifier = 'extbase'; + + $cacheFrontend = new NullFrontend($cacheIdentifier); + if ($cacheManager !== null) { + try { + $cacheFrontend = $cacheManager->getCache($cacheIdentifier); + } catch (\TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException $e) { + // Handling this exception is not needed as $cacheFrontend is + // a NullFrontend at this moment. + } + } + + $this->cacheFrontend = $cacheFrontend; + } + + /** + * @return ClassesConfiguration + */ + public function createClassesConfiguration(): ClassesConfiguration + { + $cacheEntryIdentifier = 'PersistenceClasses_' . sha1(TYPO3_version . Environment::getProjectPath()); + + if ($this->cacheFrontend->has($cacheEntryIdentifier)) { + return new ClassesConfiguration($this->cacheFrontend->get($cacheEntryIdentifier)); + } + + $classes = []; + foreach (GeneralUtility::makeInstance(PackageManager::class)->getActivePackages() as $activePackage) { + $persistenceClassesFile = $activePackage->getPackagePath() . 'Configuration/Extbase/Persistence/Classes.php'; + if (file_exists($persistenceClassesFile)) { + $definedClasses = require $persistenceClassesFile; + if (is_array($definedClasses)) { + ArrayUtility::mergeRecursiveWithOverrule( + $classes, + $definedClasses, + true, + false + ); + } + } + } + + $classes = $this->inheritPropertiesFromParentClasses($classes); + + $this->cacheFrontend->set($cacheEntryIdentifier, $classes); + + return new ClassesConfiguration($classes); + } + + /** + * todo: this method is flawed, see https://forge.typo3.org/issues/87566 + * + * @param array $classes + * @return array + */ + private function inheritPropertiesFromParentClasses(array $classes): array + { + foreach (array_keys($classes) as $className) { + if (!isset($classes[$className]['properties'])) { + $classes[$className]['properties'] = []; + } + + /* + * At first we need to clean the list of parent classes. + * This methods is expected to be called for models that either inherit + * AbstractEntity or AbstractValueObject, therefore we want to know all + * parents of $className until one of these parents. + */ + $relevantParentClasses = []; + $parentClasses = class_parents($className); + while (null !== $parentClass = array_shift($parentClasses)) { + if (in_array($parentClass, [AbstractEntity::class, AbstractValueObject::class], true)) { + break; + } + + $relevantParentClasses[] = $parentClass; + } + + /* + * Once we found all relevant parent classes of $class, we can check their + * property configuration and merge theirs with the current one. This is necessary + * to get the property configuration of parent classes in the current one to not + * miss data in the model later on. + */ + foreach ($relevantParentClasses as $currentClassName) { + if (null === $properties = $classes[$currentClassName]['properties'] ?? null) { + continue; + } + + ArrayUtility::mergeRecursiveWithOverrule($classes[$className]['properties'], $properties, true, false); + } + } + + return $classes; + } +} diff --git a/typo3/sysext/extbase/Classes/Persistence/Fixtures/Domain/Model/A.php b/typo3/sysext/extbase/Classes/Persistence/Fixtures/Domain/Model/A.php new file mode 100644 index 000000000000..4295c43650e1 --- /dev/null +++ b/typo3/sysext/extbase/Classes/Persistence/Fixtures/Domain/Model/A.php @@ -0,0 +1,25 @@ +<?php +declare(strict_types = 1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ +namespace TYPO3\CMS\Extbase\Persistence\Fixtures\Domain\Model; + +use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; + +/** + * Class TYPO3\CMS\Extbase\Persistence\Fixtures\Domain\Model\A + */ +class A extends AbstractEntity +{ +} diff --git a/typo3/sysext/extbase/Classes/Persistence/Fixtures/Domain/Model/B.php b/typo3/sysext/extbase/Classes/Persistence/Fixtures/Domain/Model/B.php new file mode 100644 index 000000000000..07ac115273c1 --- /dev/null +++ b/typo3/sysext/extbase/Classes/Persistence/Fixtures/Domain/Model/B.php @@ -0,0 +1,23 @@ +<?php +declare(strict_types = 1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ +namespace TYPO3\CMS\Extbase\Persistence\Fixtures\Domain\Model; + +/** + * Class TYPO3\CMS\Extbase\Persistence\Fixtures\Domain\Model\B + */ +class B extends A +{ +} diff --git a/typo3/sysext/extbase/Classes/Persistence/Fixtures/Domain/Model/C.php b/typo3/sysext/extbase/Classes/Persistence/Fixtures/Domain/Model/C.php new file mode 100644 index 000000000000..ef740a9b1c6f --- /dev/null +++ b/typo3/sysext/extbase/Classes/Persistence/Fixtures/Domain/Model/C.php @@ -0,0 +1,23 @@ +<?php +declare(strict_types = 1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ +namespace TYPO3\CMS\Extbase\Persistence\Fixtures\Domain\Model; + +/** + * Class TYPO3\CMS\Extbase\Persistence\Fixtures\Domain\Model\C + */ +class C extends B +{ +} diff --git a/typo3/sysext/extbase/Classes/Persistence/Generic/Backend.php b/typo3/sysext/extbase/Classes/Persistence/Generic/Backend.php index 9e0f1fa6b0ba..07a25978b1a7 100644 --- a/typo3/sysext/extbase/Classes/Persistence/Generic/Backend.php +++ b/typo3/sysext/extbase/Classes/Persistence/Generic/Backend.php @@ -1082,6 +1082,7 @@ class Backend implements \TYPO3\CMS\Extbase\Persistence\Generic\BackendInterface } } $className = get_class($object); + // todo: decide what to do with this option. if (isset($frameworkConfiguration['persistence']['classes'][$className]) && !empty($frameworkConfiguration['persistence']['classes'][$className]['newRecordStoragePid'])) { return (int)$frameworkConfiguration['persistence']['classes'][$className]['newRecordStoragePid']; } diff --git a/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapFactory.php b/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapFactory.php index 81120fc035af..49acc13e0f0b 100644 --- a/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapFactory.php +++ b/typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapFactory.php @@ -16,6 +16,8 @@ namespace TYPO3\CMS\Extbase\Persistence\Generic\Mapper; use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Database\Query\QueryHelper; +use TYPO3\CMS\Extbase\Persistence\ClassesConfiguration; +use TYPO3\CMS\Extbase\Persistence\ClassesConfigurationFactory; use TYPO3\CMS\Extbase\Reflection\ClassSchema\Exception\NoSuchPropertyException; /** @@ -56,6 +58,11 @@ class DataMapFactory implements \TYPO3\CMS\Core\SingletonInterface */ protected $dataMaps = []; + /** + * @var ClassesConfiguration + */ + private $classesConfiguration; + /** * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager @@ -66,7 +73,8 @@ class DataMapFactory implements \TYPO3\CMS\Core\SingletonInterface \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService, \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager, \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager, - \TYPO3\CMS\Core\Cache\CacheManager $cacheManager + \TYPO3\CMS\Core\Cache\CacheManager $cacheManager, + ClassesConfigurationFactory $classesConfigurationFactory ) { $this->reflectionService = $reflectionService; $this->configurationManager = $configurationManager; @@ -74,6 +82,7 @@ class DataMapFactory implements \TYPO3\CMS\Core\SingletonInterface $this->cacheManager = $cacheManager; $this->dataMapCache = $this->cacheManager->getCache('extbase'); + $this->classesConfiguration = $classesConfigurationFactory->createClassesConfiguration(); } /** @@ -121,46 +130,28 @@ class DataMapFactory implements \TYPO3\CMS\Core\SingletonInterface $recordType = null; $subclasses = []; $tableName = $this->resolveTableName($className); - $columnMapping = []; - $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK); - $classSettings = $frameworkConfiguration['persistence']['classes'][$className] ?? null; - if ($classSettings !== null) { - if (isset($classSettings['subclasses']) && is_array($classSettings['subclasses'])) { - $subclasses = $this->resolveSubclassesRecursive($frameworkConfiguration['persistence']['classes'], $classSettings['subclasses']); - } - if (isset($classSettings['mapping']['recordType']) && $classSettings['mapping']['recordType'] !== '') { + $fieldNameToPropertyNameMapping = []; + if ($this->classesConfiguration->hasClass($className)) { + $classSettings = $this->classesConfiguration->getConfigurationFor($className); + $subclasses = $this->classesConfiguration->getSubClasses($className); + if (isset($classSettings['recordType']) && $classSettings['recordType'] !== '') { $recordType = $classSettings['mapping']['recordType']; } - if (isset($classSettings['mapping']['tableName']) && $classSettings['mapping']['tableName'] !== '') { - $tableName = $classSettings['mapping']['tableName']; + if (isset($classSettings['tableName']) && $classSettings['tableName'] !== '') { + $tableName = $classSettings['tableName']; } - $classHierarchy = array_merge([$className], class_parents($className)); - foreach ($classHierarchy as $currentClassName) { - if (in_array($currentClassName, [\TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class, \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject::class])) { - break; - } - $currentClassSettings = $frameworkConfiguration['persistence']['classes'][$currentClassName]; - if ($currentClassSettings !== null) { - if (isset($currentClassSettings['mapping']['columns']) && is_array($currentClassSettings['mapping']['columns'])) { - \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($columnMapping, $currentClassSettings['mapping']['columns'], true, false); - } - } + foreach ($classSettings['properties'] ?? [] as $propertyName => $propertyDefinition) { + $fieldNameToPropertyNameMapping[$propertyDefinition['fieldName']] = $propertyName; } } /** @var \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap $dataMap */ $dataMap = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class, $className, $tableName, $recordType, $subclasses); $dataMap = $this->addMetaDataColumnNames($dataMap, $tableName); - // $classPropertyNames = $this->reflectionService->getClassPropertyNames($className); - $tcaColumnsDefinition = $this->getColumnsDefinition($tableName); - \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($tcaColumnsDefinition, $columnMapping); - // @todo Is this is too powerful? - foreach ($tcaColumnsDefinition as $columnName => $columnDefinition) { - if (isset($columnDefinition['mapOnProperty'])) { - $propertyName = $columnDefinition['mapOnProperty']; - } else { - $propertyName = \TYPO3\CMS\Core\Utility\GeneralUtility::underscoredToLowerCamelCase($columnName); - } + foreach ($this->getColumnsDefinition($tableName) as $columnName => $columnDefinition) { + $propertyName = $fieldNameToPropertyNameMapping[$columnName] + ?? \TYPO3\CMS\Core\Utility\GeneralUtility::underscoredToLowerCamelCase($columnName); + // @todo: shall we really create column maps for non existing properties? // @todo: check why this could happen in the first place. TCA definitions for non existing model properties? $columnMap = $this->createColumnMap($columnName, $propertyName); @@ -199,27 +190,6 @@ class DataMapFactory implements \TYPO3\CMS\Core\SingletonInterface return $tableName; } - /** - * Resolves all subclasses for the given set of (sub-)classes. - * The whole classes configuration is used to determine all subclasses recursively. - * - * @param array $classesConfiguration The framework configuration part [persistence][classes]. - * @param array $subclasses An array of subclasses defined via TypoScript - * @return array An numeric array that contains all available subclasses-strings as values. - */ - protected function resolveSubclassesRecursive(array $classesConfiguration, array $subclasses) - { - $allSubclasses = []; - foreach ($subclasses as $subclass) { - $allSubclasses[] = $subclass; - if (isset($classesConfiguration[$subclass]['subclasses']) && is_array($classesConfiguration[$subclass]['subclasses'])) { - $childSubclasses = $this->resolveSubclassesRecursive($classesConfiguration, $classesConfiguration[$subclass]['subclasses']); - $allSubclasses = array_merge($allSubclasses, $childSubclasses); - } - } - return $allSubclasses; - } - /** * Returns the TCA ctrl section of the specified table; or NULL if not set * diff --git a/typo3/sysext/extbase/Configuration/Extbase/Persistence/Classes.php b/typo3/sysext/extbase/Configuration/Extbase/Persistence/Classes.php new file mode 100644 index 000000000000..d8c8426d8887 --- /dev/null +++ b/typo3/sysext/extbase/Configuration/Extbase/Persistence/Classes.php @@ -0,0 +1,117 @@ +<?php +declare(strict_types = 1); + +return [ + \TYPO3\CMS\Extbase\Domain\Model\FileMount::class => [ + 'tableName' => 'sys_filemounts', + 'properties' => [ + 'title' => [ + 'fieldName' => 'title' + ], + 'path' => [ + 'fieldName' => 'path' + ], + 'isAbsolutePath' => [ + 'fieldName' => 'base' + ], + ], + ], + \TYPO3\CMS\Extbase\Domain\Model\FileReference::class => [ + 'tableName' => 'sys_file_reference', + ], + \TYPO3\CMS\Extbase\Domain\Model\File::class => [ + 'tableName' => 'sys_file', + ], + \TYPO3\CMS\Extbase\Domain\Model\BackendUser::class => [ + 'tableName' => 'be_users', + 'properties' => [ + 'userName' => [ + 'fieldName' => 'username' + ], + 'isAdministrator' => [ + 'fieldName' => 'admin' + ], + 'isDisabled' => [ + 'fieldName' => 'disable' + ], + 'realName' => [ + 'fieldName' => 'realName' + ], + 'startDateAndTime' => [ + 'fieldName' => 'starttime' + ], + 'endDateAndTime' => [ + 'fieldName' => 'endtime' + ], + 'ipLockIsDisabled' => [ + 'fieldName' => 'disableIPlock' + ], + 'lastLoginDateAndTime' => [ + 'fieldName' => 'lastlogin' + ], + ], + ], + \TYPO3\CMS\Extbase\Domain\Model\BackendUserGroup::class => [ + 'tableName' => 'be_groups', + 'properties' => [ + 'subGroups' => [ + 'fieldName' => 'subgroup' + ], + 'modules' => [ + 'fieldName' => 'groupMods' + ], + 'tablesListening' => [ + 'fieldName' => 'tables_select' + ], + 'tablesModify' => [ + 'fieldName' => 'tables_modify' + ], + 'pageTypes' => [ + 'fieldName' => 'pagetypes_select' + ], + 'allowedExcludeFields' => [ + 'fieldName' => 'non_exclude_fields' + ], + 'explicitlyAllowAndDeny' => [ + 'fieldName' => 'explicit_allowdeny' + ], + 'allowedLanguages' => [ + 'fieldName' => 'allowed_languages' + ], + 'workspacePermission' => [ + 'fieldName' => 'workspace_perms' + ], + 'databaseMounts' => [ + 'fieldName' => 'db_mountpoints' + ], + 'fileOperationPermissions' => [ + 'fieldName' => 'file_permissions' + ], + 'lockToDomain' => [ + 'fieldName' => 'lockToDomain' + ], + 'tsConfig' => [ + 'fieldName' => 'TSconfig' + ], + ], + ], + \TYPO3\CMS\Extbase\Domain\Model\FrontendUser::class => [ + 'tableName' => 'fe_users', + 'properties' => [ + 'lockToDomain' => [ + 'fieldName' => 'lockToDomain' + ], + ], + ], + \TYPO3\CMS\Extbase\Domain\Model\FrontendUserGroup::class => [ + 'tableName' => 'fe_groups', + 'properties' => [ + 'lockToDomain' => [ + 'fieldName' => 'lockToDomain' + ], + ], + ], + \TYPO3\CMS\Extbase\Domain\Model\Category::class => [ + 'tableName' => 'sys_category', + ], +]; diff --git a/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Configuration/Extbase/Persistence/Classes.php b/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Configuration/Extbase/Persistence/Classes.php new file mode 100644 index 000000000000..e317d2f8c985 --- /dev/null +++ b/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Configuration/Extbase/Persistence/Classes.php @@ -0,0 +1,23 @@ +<?php +declare(strict_types = 1); + +return [ + \ExtbaseTeam\BlogExample\Domain\Model\Administrator::class => [ + 'tableName' => 'fe_users', + 'recordType' => \ExtbaseTeam\BlogExample\Domain\Model\Administrator::class + ], + \ExtbaseTeam\BlogExample\Domain\Model\TtContent::class => [ + 'tableName' => 'tt_content', + 'properties' => [ + 'uid' => [ + 'fieldName' => 'uid' + ], + 'pid' => [ + 'fieldName' => 'pid' + ], + 'header' => [ + 'fieldName' => 'header' + ], + ], + ], +]; diff --git a/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/ext_typoscript_setup.typoscript b/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/ext_typoscript_setup.typoscript deleted file mode 100644 index 05a80663cb43..000000000000 --- a/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/ext_typoscript_setup.typoscript +++ /dev/null @@ -1,29 +0,0 @@ - # global configuration - -config.tx_extbase { - persistence{ - classes { - Extbase\Domain\ModelFrontendUser { - subclasses { - ExtbaseTeam\BlogExample\Domain\Model\Administrator = ExtbaseTeam\BlogExample\Domain\Model\Administrator - } - } - ExtbaseTeam\BlogExample\Domain\Model\Administrator { - mapping { - tableName = fe_users - recordType = ExtbaseTeam\BlogExample\Domain\Model\Administrator - } - } - ExtbaseTeam\BlogExample\Domain\Model\TtContent { - mapping { - tableName = tt_content - columns { - uid.mapOnProperty = uid - pid.mapOnProperty = pid - header.mapOnProperty = header - } - } - } - } - } -} \ No newline at end of file diff --git a/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/class_overriding/b/Configuration/Extbase/Persistence/Classes.php b/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/class_overriding/b/Configuration/Extbase/Persistence/Classes.php new file mode 100644 index 000000000000..f3b842322a4e --- /dev/null +++ b/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/class_overriding/b/Configuration/Extbase/Persistence/Classes.php @@ -0,0 +1,8 @@ +<?php +declare(strict_types = 1); + +return [ + \ExtbaseTeam\B\Domain\Model\B::class => [ + 'tableName' => 'tx_a_domain_model_a' + ] +]; diff --git a/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/class_overriding/b/ext_typoscript_setup.typoscript b/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/class_overriding/b/ext_typoscript_setup.typoscript deleted file mode 100644 index 3ae18ddcb792..000000000000 --- a/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/class_overriding/b/ext_typoscript_setup.typoscript +++ /dev/null @@ -1,9 +0,0 @@ -config.tx_extbase.persistence { - classes { - ExtbaseTeam\B\Domain\Model\B { - mapping { - tableName = tx_a_domain_model_a - } - } - } -} diff --git a/typo3/sysext/extbase/Tests/Unit/Persistence/ClassesConfigurationFactoryTest.php b/typo3/sysext/extbase/Tests/Unit/Persistence/ClassesConfigurationFactoryTest.php new file mode 100644 index 000000000000..9badd87ac059 --- /dev/null +++ b/typo3/sysext/extbase/Tests/Unit/Persistence/ClassesConfigurationFactoryTest.php @@ -0,0 +1,103 @@ +<?php +declare(strict_types = 1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ +namespace TYPO3\CMS\Extbase\Tests\Unit\Persistence; + +use TYPO3\CMS\Extbase\Persistence\ClassesConfigurationFactory; +use TYPO3\TestingFramework\Core\Unit\UnitTestCase; + +/** + * Class TYPO3\CMS\Extbase\Tests\Unit\Persistence\ClassesConfigurationTest + */ +class ClassesConfigurationFactoryTest extends UnitTestCase +{ + /** + * @test + */ + public function inheritPropertiesFromParentClasses(): void + { + $classesConfigurationFactory = new ClassesConfigurationFactory(); + + $classes = [ + \TYPO3\CMS\Extbase\Persistence\Fixtures\Domain\Model\A::class => [ + 'properties' => [ + 'propertiesFromA' => [ + 'fieldName' => 'field_name_a' + ] + ] + ], + \TYPO3\CMS\Extbase\Persistence\Fixtures\Domain\Model\B::class => [ + 'properties' => [ + 'propertiesFromA' => [ + 'fieldName' => 'field_name_z' + ], + 'propertiesFromB' => [ + 'fieldName' => 'field_name_b' + ] + ] + ], + \TYPO3\CMS\Extbase\Persistence\Fixtures\Domain\Model\C::class => [ + 'properties' => [ + 'columnNameC' => [ + 'fieldName' => 'field_name_c' + ] + ] + ], + ]; + + $reflectionMethod = (new \ReflectionClass($classesConfigurationFactory)) + ->getMethod('inheritPropertiesFromParentClasses'); + $reflectionMethod->setAccessible(true); + $classes = $reflectionMethod->invoke($classesConfigurationFactory, $classes); + + static::assertSame( + [ + \TYPO3\CMS\Extbase\Persistence\Fixtures\Domain\Model\A::class => [ + 'properties' => [ + 'propertiesFromA' => [ + 'fieldName' => 'field_name_a' + ], + ] + ], + \TYPO3\CMS\Extbase\Persistence\Fixtures\Domain\Model\B::class => [ + 'properties' => [ + 'propertiesFromA' => [ + // todo: this is flawed, we'd actually expect field_name_z here + // todo: see https://forge.typo3.org/issues/87566 + 'fieldName' => 'field_name_a' + ], + 'propertiesFromB' => [ + 'fieldName' => 'field_name_b' + ], + ] + ], + \TYPO3\CMS\Extbase\Persistence\Fixtures\Domain\Model\C::class => [ + 'properties' => [ + 'columnNameC' => [ + 'fieldName' => 'field_name_c' + ], + 'propertiesFromA' => [ + 'fieldName' => 'field_name_a' + ], + 'propertiesFromB' => [ + 'fieldName' => 'field_name_b' + ], + ] + ], + ], + $classes + ); + } +} diff --git a/typo3/sysext/extbase/Tests/Unit/Persistence/ClassesConfigurationTest.php b/typo3/sysext/extbase/Tests/Unit/Persistence/ClassesConfigurationTest.php new file mode 100644 index 000000000000..10960d48c1ff --- /dev/null +++ b/typo3/sysext/extbase/Tests/Unit/Persistence/ClassesConfigurationTest.php @@ -0,0 +1,173 @@ +<?php +declare(strict_types = 1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ +namespace TYPO3\CMS\Extbase\Tests\Unit\Persistence; + +use TYPO3\CMS\Extbase\Persistence\ClassesConfiguration; +use TYPO3\TestingFramework\Core\Unit\UnitTestCase; + +/** + * Class TYPO3\CMS\Extbase\Tests\Unit\Persistence\ClassesConfigurationTest + */ +class ClassesConfigurationTest extends UnitTestCase +{ + /** + * @test + */ + public function hasClassReturnsTrue(): void + { + $className = 'ClassName'; + $classesConfiguration = new ClassesConfiguration([$className => []]); + static::assertTrue($classesConfiguration->hasClass($className)); + } + + /** + * @test + */ + public function hasClassReturnsFalse(): void + { + $className = 'ClassName'; + $classesConfiguration = new ClassesConfiguration([]); + static::assertFalse($classesConfiguration->hasClass($className)); + } + + /** + * @test + */ + public function getConfigurationForReturnsArray(): void + { + $configuration = [ + 'ClassName' => [ + 'tableName' => 'table' + ] + ]; + $classesConfiguration = new ClassesConfiguration($configuration); + static::assertSame( + $configuration['ClassName'], + $classesConfiguration->getConfigurationFor('ClassName') + ); + } + + /** + * @test + */ + public function getConfigurationForReturnsNull(): void + { + $classesConfiguration = new ClassesConfiguration([]); + static::assertNull($classesConfiguration->getConfigurationFor('ClassName')); + } + + /** + * @return array + */ + public function resolveSubclassesRecursiveDataProvider(): array + { + return [ + [ + [ + 'B', + 'C', + 'A', + ], + [ + 'A' => [ + 'subclasses' => [ + 'B', + ] + ], + 'B' => [ + 'subclasses' => [ + 'C' + ] + ], + 'C' => [ + 'subclasses' => [ + 'A' + ] + ], + ], + 'A' + ], + [ + [ + 'A', + 'B', + 'C', + ], + [ + 'A' => [ + 'subclasses' => [ + 'B', + ] + ], + 'B' => [ + 'subclasses' => [ + 'C' + ] + ], + 'C' => [ + 'subclasses' => [ + 'A' + ] + ], + ], + 'C' + ], + [ + [ + 'C', + 'A', + 'B', + ], + [ + 'A' => [ + 'subclasses' => [ + 'C', + ] + ], + 'B' => [ + 'subclasses' => [ + 'C', + 'B', + ] + ], + 'C' => [ + 'subclasses' => [ + 'A', + 'B', + ] + ], + ], + 'B' + ], + ]; + } + + /** + * @dataProvider resolveSubclassesRecursiveDataProvider + * @test + * @param array $expected + * @param array $configuration + * @param string $className + */ + public function getSubclasses(array $expected, array $configuration, string $className): void + { + $classesConfiguration = new ClassesConfiguration($configuration); + static::assertSame( + $expected, + $classesConfiguration->getSubClasses($className) + ); + } +} diff --git a/typo3/sysext/extbase/ext_typoscript_setup.typoscript b/typo3/sysext/extbase/ext_typoscript_setup.typoscript index 4cb1f9bbb326..14f31a929b0f 100644 --- a/typo3/sysext/extbase/ext_typoscript_setup.typoscript +++ b/typo3/sysext/extbase/ext_typoscript_setup.typoscript @@ -9,84 +9,6 @@ config.tx_extbase { persistence{ enableAutomaticCacheClearing = 1 updateReferenceIndex = 0 - classes { - TYPO3\CMS\Extbase\Domain\Model\FileMount { - mapping { - tableName = sys_filemounts - columns { - title.mapOnProperty = title - path.mapOnProperty = path - base.mapOnProperty = isAbsolutePath - } - } - } - TYPO3\CMS\Extbase\Domain\Model\FileReference { - mapping { - tableName = sys_file_reference - } - } - TYPO3\CMS\Extbase\Domain\Model\File { - mapping { - tableName = sys_file - } - } - TYPO3\CMS\Extbase\Domain\Model\BackendUser { - mapping { - tableName = be_users - columns { - username.mapOnProperty = userName - admin.mapOnProperty = isAdministrator - disable.mapOnProperty = isDisabled - realName.mapOnProperty = realName - starttime.mapOnProperty = startDateAndTime - endtime.mapOnProperty = endDateAndTime - disableIPlock.mapOnProperty = ipLockIsDisabled - lastlogin.mapOnProperty = lastLoginDateAndTime - } - } - } - TYPO3\CMS\Extbase\Domain\Model\BackendUserGroup { - mapping { - tableName = be_groups - columns { - subgroup.mapOnProperty = subGroups - groupMods.mapOnProperty = modules - tables_select.mapOnProperty = tablesListening - tables_modify.mapOnProperty = tablesModify - pagetypes_select.mapOnProperty = pageTypes - non_exclude_fields.mapOnProperty = allowedExcludeFields - explicit_allowdeny.mapOnProperty = explicitlyAllowAndDeny - allowed_languages.mapOnProperty = allowedLanguages - workspace_perms.mapOnProperty = workspacePermission - db_mountpoints.mapOnProperty = databaseMounts - file_permissions.mapOnProperty = fileOperationPermissions - lockToDomain.mapOnProperty = lockToDomain - TSconfig.mapOnProperty = tsConfig - } - } - } - TYPO3\CMS\Extbase\Domain\Model\FrontendUser { - mapping { - tableName = fe_users - columns { - lockToDomain.mapOnProperty = lockToDomain - } - } - } - TYPO3\CMS\Extbase\Domain\Model\FrontendUserGroup { - mapping { - tableName = fe_groups - columns { - lockToDomain.mapOnProperty = lockToDomain - } - } - } - TYPO3\CMS\Extbase\Domain\Model\Category { - mapping { - tableName = sys_category - } - } - } } features { # if enabled, default controller and/or action is skipped when creating URIs through the URI Builder (see https://wiki.typo3.org/Skip_default_arguments_in_URIs) -- GitLab