1919
2020use Symfony \Component \DependencyInjection \Attribute \Autoconfigure ;
2121use Symfony \Component \DependencyInjection \Attribute \Autowire ;
22+ use Symfony \Component \Finder \Finder ;
2223use Symfony \Component \VarExporter \VarExporter ;
2324use TYPO3 \CMS \ContentBlocks \Schema \Exception \UndefinedSchemaException ;
2425use TYPO3 \CMS \ContentBlocks \Schema \Field \FieldCollection ;
2526use TYPO3 \CMS \ContentBlocks \Schema \Field \TcaField ;
2627use TYPO3 \CMS \Core \Cache \Frontend \PhpFrontend ;
28+ use TYPO3 \CMS \Core \Package \PackageManager ;
2729
2830/**
2931 * @todo This class is a factory and Root Schema at the same time.
@@ -40,12 +42,14 @@ public function __construct(
4042 #[Autowire(service: 'cache.core ' )]
4143 protected readonly PhpFrontend $ cache ,
4244 protected FieldTypeResolver $ typeResolver ,
45+ protected PackageManager $ packageManager ,
4346 ) {
44- // The schema must only be hydrated from previous caches,
45- // which were built in BeforeTcaOverridesEvent.
4647 if (($ schemas = $ this ->getFromCache ()) !== false ) {
4748 $ this ->schemas = $ schemas ;
49+ return ;
4850 }
51+ $ baseTca = $ this ->loadConfigurationTcaFiles ();
52+ $ this ->initialize ($ baseTca );
4953 }
5054
5155 /**
@@ -104,6 +108,52 @@ protected function build(string $schemaName, array $schemaDefinition): SimpleTca
104108 return $ schema ;
105109 }
106110
111+ /**
112+ * @todo You may wonder, why we copy this code from the Core TcaFactory.
113+ * @todo We used to fill this schema by using the BeforeTcaOverridesEvent.
114+ * @todo The reason we removed this dependency was that many deployment
115+ * @todo processes use `typo3 cache:flush` in their pipeline. This works
116+ * @todo well in local / staging environments, but in frequently visited
117+ * @todo production environments a concurrent hit can happen, which may
118+ * @todo produce a compiled Content Blocks cache entry, while the flush
119+ * @todo command erased the SimpleTcaSchema cache entry at the same time.
120+ * @todo The problem is, this cache can't recover itself, if TCA is
121+ * @todo already cached and the event won't fire again, leaving the system
122+ * @todo in a broken state.
123+ * @todo Example: "The field "pages" is missing the required "type" in Content Block".
124+ * @todo To circumvent this error, the functionality to create base TCA
125+ * @todo is added to this class. Now, the cache can rebuild itself.
126+ */
127+ private function loadConfigurationTcaFiles (): array
128+ {
129+ // To require TCA in a safe scoped environment avoiding local variable clashes.
130+ // Note: Return type 'mixed' is intended, otherwise broken TCA files with missing "return [];" statement would
131+ // emit a "return value must be of type array, int returned" PHP TypeError. This is mitigated by an array
132+ // check below.
133+ $ scopedReturnRequire = static function (string $ filename ): mixed {
134+ return require $ filename ;
135+ };
136+ // First load "full table" files from Configuration/TCA
137+ $ tca = [];
138+ $ activePackages = $ this ->packageManager ->getActivePackages ();
139+ foreach ($ activePackages as $ package ) {
140+ try {
141+ $ finder = Finder::create ()->files ()->sortByName ()->depth (0 )->name ('*.php ' )->in ($ package ->getPackagePath () . 'Configuration/TCA ' );
142+ } catch (\InvalidArgumentException ) {
143+ // No such directory in this package
144+ continue ;
145+ }
146+ foreach ($ finder as $ fileInfo ) {
147+ $ tcaOfTable = $ scopedReturnRequire ($ fileInfo ->getPathname ());
148+ if (is_array ($ tcaOfTable )) {
149+ $ tcaTableName = substr ($ fileInfo ->getBasename (), 0 , -4 );
150+ $ tca [$ tcaTableName ] = $ tcaOfTable ;
151+ }
152+ }
153+ }
154+ return $ tca ;
155+ }
156+
107157 protected function getFromCache (): false |array
108158 {
109159 return $ this ->cache ->require ('ContentBlocks_SimpleTcaSchema ' );
0 commit comments