1<?php
2
3declare(strict_types=1);
4
5/*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18namespace TYPO3\CMS\Extbase\Persistence;
19
20use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
21use TYPO3\CMS\Core\Package\PackageManager;
22use TYPO3\CMS\Core\Utility\ArrayUtility;
23use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
24use TYPO3\CMS\Extbase\DomainObject\AbstractValueObject;
25
26final class ClassesConfigurationFactory
27{
28    private FrontendInterface $cache;
29
30    private PackageManager $packageManager;
31
32    private string $cacheIdentifier;
33
34    public function __construct(FrontendInterface $cache, PackageManager $packageManager, string $cacheIdentifier)
35    {
36        $this->cache = $cache;
37        $this->packageManager = $packageManager;
38        $this->cacheIdentifier = $cacheIdentifier;
39    }
40
41    /**
42     * @return ClassesConfiguration
43     */
44    public function createClassesConfiguration(): ClassesConfiguration
45    {
46        $classesConfigurationCache = $this->cache->get($this->cacheIdentifier);
47        if ($classesConfigurationCache !== false) {
48            return new ClassesConfiguration($classesConfigurationCache);
49        }
50
51        $classes = [];
52        foreach ($this->packageManager->getActivePackages() as $activePackage) {
53            $persistenceClassesFile = $activePackage->getPackagePath() . 'Configuration/Extbase/Persistence/Classes.php';
54            if (file_exists($persistenceClassesFile)) {
55                $definedClasses = require $persistenceClassesFile;
56                if (is_array($definedClasses)) {
57                    ArrayUtility::mergeRecursiveWithOverrule(
58                        $classes,
59                        $definedClasses,
60                        true,
61                        false
62                    );
63                }
64            }
65        }
66
67        $classes = $this->inheritPropertiesFromParentClasses($classes);
68
69        $this->cache->set($this->cacheIdentifier, $classes);
70
71        return new ClassesConfiguration($classes);
72    }
73
74    /**
75     * todo: this method is flawed, see https://forge.typo3.org/issues/87566
76     *
77     * @param array $classes
78     * @return array
79     */
80    private function inheritPropertiesFromParentClasses(array $classes): array
81    {
82        foreach (array_keys($classes) as $className) {
83            if (!isset($classes[$className]['properties'])) {
84                $classes[$className]['properties'] = [];
85            }
86
87            /*
88             * At first we need to clean the list of parent classes.
89             * This methods is expected to be called for models that either inherit
90             * AbstractEntity or AbstractValueObject, therefore we want to know all
91             * parents of $className until one of these parents.
92             */
93            $relevantParentClasses = [];
94            $parentClasses = class_parents($className) ?: [];
95            while (null !== $parentClass = array_shift($parentClasses)) {
96                if (in_array($parentClass, [AbstractEntity::class, AbstractValueObject::class], true)) {
97                    break;
98                }
99
100                $relevantParentClasses[] = $parentClass;
101            }
102
103            /*
104             * Once we found all relevant parent classes of $class, we can check their
105             * property configuration and merge theirs with the current one. This is necessary
106             * to get the property configuration of parent classes in the current one to not
107             * miss data in the model later on.
108             */
109            foreach ($relevantParentClasses as $currentClassName) {
110                if (null === $properties = $classes[$currentClassName]['properties'] ?? null) {
111                    continue;
112                }
113
114                // Merge new properties over existing ones.
115                $classes[$className]['properties'] = array_replace_recursive($properties, $classes[$className]['properties'] ?? []);
116            }
117        }
118
119        return $classes;
120    }
121}
122