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