[!!!][TASK] Replace config.persistence.classes typoscript 70/59570/13
authorAlexander Schnitzler <git@alexanderschnitzler.de>
Sun, 27 Jan 2019 18:48:56 +0000 (19:48 +0100)
committerSusanne Moog <look@susi.dev>
Sun, 14 Apr 2019 14:27:46 +0000 (16:27 +0200)
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>
24 files changed:
typo3/sysext/belog/Configuration/Extbase/Persistence/Classes.php [new file with mode: 0644]
typo3/sysext/belog/Configuration/TypoScript/setup.typoscript
typo3/sysext/beuser/Configuration/Extbase/Persistence/Classes.php [new file with mode: 0644]
typo3/sysext/beuser/Configuration/TypoScript/setup.typoscript
typo3/sysext/core/Classes/Cache/Frontend/NullFrontend.php [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Breaking-87623-ReplaceConfigpersistenceclassesTyposcriptConfiguration.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial/Configuration/Extbase/Persistence/Classes.php [new file with mode: 0644]
typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial/Configuration/TypoScript/setup.typoscript
typo3/sysext/extbase/Classes/Core/Bootstrap.php
typo3/sysext/extbase/Classes/Persistence/ClassesConfiguration.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Persistence/ClassesConfigurationFactory.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Persistence/Fixtures/Domain/Model/A.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Persistence/Fixtures/Domain/Model/B.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Persistence/Fixtures/Domain/Model/C.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Persistence/Generic/Backend.php
typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapFactory.php
typo3/sysext/extbase/Configuration/Extbase/Persistence/Classes.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Configuration/Extbase/Persistence/Classes.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/ext_typoscript_setup.typoscript [deleted file]
typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/class_overriding/b/Configuration/Extbase/Persistence/Classes.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/class_overriding/b/ext_typoscript_setup.typoscript [deleted file]
typo3/sysext/extbase/Tests/Unit/Persistence/ClassesConfigurationFactoryTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Unit/Persistence/ClassesConfigurationTest.php [new file with mode: 0644]
typo3/sysext/extbase/ext_typoscript_setup.typoscript

diff --git a/typo3/sysext/belog/Configuration/Extbase/Persistence/Classes.php b/typo3/sysext/belog/Configuration/Extbase/Persistence/Classes.php
new file mode 100644 (file)
index 0000000..8132214
--- /dev/null
@@ -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',
+    ],
+];
index 9e51518..31ba3f7 100644 (file)
@@ -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 (file)
index 0000000..9a94608
--- /dev/null
@@ -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'
+            ],
+        ]
+    ],
+];
index 6ec5be8..ca8870f 100644 (file)
@@ -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 (file)
index 0000000..2b604cb
--- /dev/null
@@ -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 (file)
index 0000000..06abf95
--- /dev/null
@@ -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 (file)
index 0000000..e7d6940
--- /dev/null
@@ -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',
+    ],
+];
index b614b93..9882ab7 100644 (file)
@@ -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
-                               }
-                       }
-               }
        }
 }
 
index 4b19dea..d23405a 100644 (file)
@@ -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
@@ -31,6 +34,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();
     }
 
@@ -111,6 +120,16 @@ class Bootstrap implements \TYPO3\CMS\Extbase\Core\BootstrapInterface
     }
 
     /**
+     * @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
      *
      * @see initialize()
diff --git a/typo3/sysext/extbase/Classes/Persistence/ClassesConfiguration.php b/typo3/sysext/extbase/Classes/Persistence/ClassesConfiguration.php
new file mode 100644 (file)
index 0000000..71b561e
--- /dev/null
@@ -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 (file)
index 0000000..baf346f
--- /dev/null
@@ -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 (file)
index 0000000..4295c43
--- /dev/null
@@ -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 (file)
index 0000000..07ac115
--- /dev/null
@@ -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 (file)
index 0000000..ef740a9
--- /dev/null
@@ -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
+{
+}
index 9e0f1fa..07a2597 100644 (file)
@@ -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'];
             }
index 81120fc..49acc13 100644 (file)
@@ -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;
 
 /**
@@ -57,6 +59,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
      * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
@@ -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);
@@ -200,27 +191,6 @@ class DataMapFactory implements \TYPO3\CMS\Core\SingletonInterface
     }
 
     /**
-     * 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
      *
      * @param string $tableName An optional table name to fetch the columns definition from
diff --git a/typo3/sysext/extbase/Configuration/Extbase/Persistence/Classes.php b/typo3/sysext/extbase/Configuration/Extbase/Persistence/Classes.php
new file mode 100644 (file)
index 0000000..d8c8426
--- /dev/null
@@ -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 (file)
index 0000000..e317d2f
--- /dev/null
@@ -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 (file)
index 05a8066..0000000
+++ /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 (file)
index 0000000..f3b8423
--- /dev/null
@@ -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 (file)
index 3ae18dd..0000000
+++ /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 (file)
index 0000000..9badd87
--- /dev/null
@@ -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 (file)
index 0000000..10960d4
--- /dev/null
@@ -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)
+        );
+    }
+}
index 4cb1f9b..14f31a9 100644 (file)
@@ -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)