‪TYPO3CMS  ‪main
ContainerBuilder.php
Go to the documentation of this file.
1 <?php
2 
3 declare(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 
19 
20 use Psr\Container\ContainerInterface;
21 use Symfony\Component\Config\FileLocator;
22 use Symfony\Component\DependencyInjection\ContainerBuilder as SymfonyContainerBuilder;
23 use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
24 use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
25 use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
30 use TYPO3\CMS\Core\Package\PackageManager;
31 
35 class ContainerBuilder
36 {
40  protected $cacheIdentifiers;
41 
45  protected $defaultServices;
46 
50  protected $serviceProviderRegistryServiceName = 'service_provider_registry';
51 
52  public function __construct(array $earlyInstances)
53  {
54  $this->defaultServices = $earlyInstances + [ self::class => $this ];
55  }
56 
60  public function warmupCache(PackageManager $packageManager, FrontendInterface $cache): void
61  {
62  $registry = new ServiceProviderRegistry($packageManager);
63  $containerBuilder = $this->buildContainer($packageManager, $registry);
64  $cacheIdentifier = $this->getCacheIdentifier($packageManager);
65  $this->dumpContainer($containerBuilder, $cache, $cacheIdentifier);
66  }
67 
68  public function createDependencyInjectionContainer(PackageManager $packageManager, FrontendInterface $cache, bool $failsafe = false): ContainerInterface
69  {
70  if (!$cache instanceof PhpFrontend) {
71  throw new \RuntimeException('Cache must be instance of PhpFrontend', 1582022226);
72  }
73  $serviceProviderRegistry = new ServiceProviderRegistry($packageManager, $failsafe);
74 
75  if ($failsafe) {
76  return new FailsafeContainer($serviceProviderRegistry, $this->defaultServices);
77  }
78 
79  $cacheIdentifier = $this->getCacheIdentifier($packageManager);
80  $containerClassName = $cacheIdentifier;
81 
82  $hasCache = $cache->requireOnce($cacheIdentifier) !== false;
83  if (!$hasCache) {
84  $containerBuilder = $this->buildContainer($packageManager, $serviceProviderRegistry);
85  $this->dumpContainer($containerBuilder, $cache, $cacheIdentifier);
86  $cache->requireOnce($cacheIdentifier);
87  }
88  $container = new $containerClassName();
89 
90  foreach ($this->defaultServices as $id => $service) {
91  $container->set('_early.' . $id, $service);
92  }
93 
94  $container->set($this->serviceProviderRegistryServiceName, $serviceProviderRegistry);
95 
96  return $container;
97  }
98 
99  protected function buildContainer(PackageManager $packageManager, ServiceProviderRegistry $registry): SymfonyContainerBuilder
100  {
101  $containerBuilder = new SymfonyContainerBuilder();
102 
103  $containerBuilder->addCompilerPass(new ServiceProviderCompilationPass($registry, $this->serviceProviderRegistryServiceName));
104 
105  $globalConfigDir = ‪Environment::getConfigPath();
106  // If the config folder is outside of the document root, we allow further services per-project
107  // This is usually the case in composer-based installations
109  if (file_exists($globalConfigDir . '/system/services.php')) {
110  $phpFileLoader = new PhpFileLoader($containerBuilder, new FileLocator($globalConfigDir . '/system'));
111  $phpFileLoader->load('services.php');
112  }
113  if (file_exists($globalConfigDir . '/system/services.yaml')) {
114  $yamlFileLoader = new YamlFileLoader($containerBuilder, new FileLocator($globalConfigDir . '/system'));
115  $yamlFileLoader->load('services.yaml');
116  }
117  }
118 
119  $packages = $packageManager->getActivePackages();
120  foreach ($packages as $package) {
121  $diConfigDir = $package->getPackagePath() . 'Configuration/';
122  if (file_exists($diConfigDir . 'Services.php')) {
123  $phpFileLoader = new PhpFileLoader($containerBuilder, new FileLocator($diConfigDir));
124  $phpFileLoader->load('Services.php');
125  }
126  if (file_exists($diConfigDir . 'Services.yaml')) {
127  $yamlFileLoader = new YamlFileLoader($containerBuilder, new FileLocator($diConfigDir));
128  $yamlFileLoader->load('Services.yaml');
129  }
130  }
131  // Store defaults entries in the DIC container
132  // We need to use a workaround using aliases for synthetic services
133  // But that's common in symfony (same technique is used to provide the
134  // Symfony container interface as well).
135  foreach (array_keys($this->defaultServices) as $id) {
136  $syntheticId = '_early.' . $id;
137  $containerBuilder->register($syntheticId)->setSynthetic(true)->setPublic(true);
138  $containerBuilder->setAlias($id, $syntheticId)->setPublic(true);
139  }
140 
141  // Optional service, set by BootService as back reference to the original bootService
142  $containerBuilder->register('_early.boot-service')->setSynthetic(true)->setPublic(true);
143 
144  $containerBuilder->compile();
145 
146  return $containerBuilder;
147  }
148 
149  protected function dumpContainer(SymfonyContainerBuilder $containerBuilder, FrontendInterface $cache, string $cacheIdentifier): string
150  {
151  $containerClassName = $cacheIdentifier;
152 
153  $phpDumper = new PhpDumper($containerBuilder);
154  $code = $phpDumper->dump(['class' => $containerClassName]);
155  $code = str_replace('<?php', '', $code);
156  // We need to patch the generated source code to use GeneralUtility::makeInstanceForDi() instead of `new`.
157  // This is ugly, but has to stay, as long as we support SingletonInstances to be created/retrieved
158  // through GeneralUtility::makeInstance.
159  $code = preg_replace('/new ([^\‍(\s]+)\‍(/', '\\TYPO3\\CMS\\Core\\Utility\\GeneralUtility::makeInstanceForDi(\\1::class, ', $code);
160  if ($code === null) {
161  throw new \RuntimeException('Could not generate container code', 1599767133);
162  }
163  $code = str_replace(', )', ')', $code);
164 
165  $cache->set($cacheIdentifier, $code);
166 
167  return $code;
168  }
169 
173  public function getCacheIdentifier(PackageManager $packageManager): string
174  {
175  $packageManagerCacheIdentifier = $packageManager->getCacheIdentifier() ?? '';
176  return $this->cacheIdentifiers[$packageManagerCacheIdentifier] ?? $this->createCacheIdentifier($packageManager, $packageManagerCacheIdentifier);
177  }
178 
179  protected function createCacheIdentifier(PackageManager $packageManager, string $additionalIdentifier): string
180  {
181  return $this->cacheIdentifiers[$additionalIdentifier] = (new PackageDependentCacheIdentifier($packageManager))->withPrefix('DependencyInjectionContainer')->toString();
182  }
183 }
‪TYPO3\CMS\Core\Core\Environment\isComposerMode
‪static isComposerMode()
Definition: Environment.php:137
‪TYPO3\CMS\Core\Cache\Frontend\PhpFrontend
Definition: PhpFrontend.php:25
‪TYPO3\CMS\Core\Core\Environment\getPublicPath
‪static getPublicPath()
Definition: Environment.php:187
‪TYPO3\CMS\Core\Package\Cache\PackageDependentCacheIdentifier
Definition: PackageDependentCacheIdentifier.php:30
‪TYPO3\CMS\Core\Core\Environment\getConfigPath
‪static getConfigPath()
Definition: Environment.php:212
‪TYPO3\CMS\Core\Core\Environment\getProjectPath
‪static string getProjectPath()
Definition: Environment.php:160
‪TYPO3\CMS\Core\DependencyInjection
Definition: AutowireInjectMethodsPass.php:18
‪TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
Definition: FrontendInterface.php:22
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:41