vendor/symfony/framework-bundle/DependencyInjection/FrameworkExtension.php line 299

  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <[email protected]>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Bundle\FrameworkBundle\DependencyInjection;
  11. use Composer\InstalledVersions;
  12. use Doctrine\Common\Annotations\Reader;
  13. use Http\Client\HttpAsyncClient;
  14. use Http\Client\HttpClient;
  15. use phpDocumentor\Reflection\DocBlockFactoryInterface;
  16. use phpDocumentor\Reflection\Types\ContextFactory;
  17. use PhpParser\Parser;
  18. use PHPStan\PhpDocParser\Parser\PhpDocParser;
  19. use Psr\Cache\CacheItemPoolInterface;
  20. use Psr\Clock\ClockInterface as PsrClockInterface;
  21. use Psr\Container\ContainerInterface as PsrContainerInterface;
  22. use Psr\Http\Client\ClientInterface;
  23. use Psr\Log\LoggerAwareInterface;
  24. use Symfony\Bridge\Monolog\Processor\DebugProcessor;
  25. use Symfony\Bridge\Twig\Extension\CsrfExtension;
  26. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  27. use Symfony\Bundle\FrameworkBundle\Routing\RouteLoaderInterface;
  28. use Symfony\Bundle\FullStack;
  29. use Symfony\Bundle\MercureBundle\MercureBundle;
  30. use Symfony\Component\Asset\PackageInterface;
  31. use Symfony\Component\AssetMapper\AssetMapper;
  32. use Symfony\Component\AssetMapper\Compiler\AssetCompilerInterface;
  33. use Symfony\Component\BrowserKit\AbstractBrowser;
  34. use Symfony\Component\Cache\Adapter\AdapterInterface;
  35. use Symfony\Component\Cache\Adapter\ArrayAdapter;
  36. use Symfony\Component\Cache\Adapter\ChainAdapter;
  37. use Symfony\Component\Cache\Adapter\TagAwareAdapter;
  38. use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
  39. use Symfony\Component\Cache\Marshaller\MarshallerInterface;
  40. use Symfony\Component\Cache\ResettableInterface;
  41. use Symfony\Component\Clock\ClockInterface;
  42. use Symfony\Component\Config\Definition\ConfigurationInterface;
  43. use Symfony\Component\Config\FileLocator;
  44. use Symfony\Component\Config\Loader\LoaderInterface;
  45. use Symfony\Component\Config\Resource\DirectoryResource;
  46. use Symfony\Component\Config\Resource\FileResource;
  47. use Symfony\Component\Config\ResourceCheckerInterface;
  48. use Symfony\Component\Console\Application;
  49. use Symfony\Component\Console\Command\Command;
  50. use Symfony\Component\Console\DataCollector\CommandDataCollector;
  51. use Symfony\Component\Console\Debug\CliRequest;
  52. use Symfony\Component\Console\Messenger\RunCommandMessageHandler;
  53. use Symfony\Component\DependencyInjection\Alias;
  54. use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
  55. use Symfony\Component\DependencyInjection\ChildDefinition;
  56. use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
  57. use Symfony\Component\DependencyInjection\ContainerBuilder;
  58. use Symfony\Component\DependencyInjection\ContainerInterface;
  59. use Symfony\Component\DependencyInjection\Definition;
  60. use Symfony\Component\DependencyInjection\EnvVarLoaderInterface;
  61. use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
  62. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  63. use Symfony\Component\DependencyInjection\Exception\LogicException;
  64. use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
  65. use Symfony\Component\DependencyInjection\Parameter;
  66. use Symfony\Component\DependencyInjection\Reference;
  67. use Symfony\Component\DependencyInjection\ServiceLocator;
  68. use Symfony\Component\Dotenv\Command\DebugCommand;
  69. use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
  70. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  71. use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
  72. use Symfony\Component\Finder\Finder;
  73. use Symfony\Component\Finder\Glob;
  74. use Symfony\Component\Form\Extension\HtmlSanitizer\Type\TextTypeHtmlSanitizerExtension;
  75. use Symfony\Component\Form\Form;
  76. use Symfony\Component\Form\FormTypeExtensionInterface;
  77. use Symfony\Component\Form\FormTypeGuesserInterface;
  78. use Symfony\Component\Form\FormTypeInterface;
  79. use Symfony\Component\HtmlSanitizer\HtmlSanitizer;
  80. use Symfony\Component\HtmlSanitizer\HtmlSanitizerConfig;
  81. use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface;
  82. use Symfony\Component\HttpClient\Messenger\PingWebhookMessageHandler;
  83. use Symfony\Component\HttpClient\MockHttpClient;
  84. use Symfony\Component\HttpClient\Retry\GenericRetryStrategy;
  85. use Symfony\Component\HttpClient\RetryableHttpClient;
  86. use Symfony\Component\HttpClient\ScopingHttpClient;
  87. use Symfony\Component\HttpClient\UriTemplateHttpClient;
  88. use Symfony\Component\HttpFoundation\Request;
  89. use Symfony\Component\HttpKernel\Attribute\AsController;
  90. use Symfony\Component\HttpKernel\Attribute\AsTargetedValueResolver;
  91. use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
  92. use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
  93. use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
  94. use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
  95. use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
  96. use Symfony\Component\HttpKernel\DependencyInjection\Extension;
  97. use Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator;
  98. use Symfony\Component\Lock\LockFactory;
  99. use Symfony\Component\Lock\LockInterface;
  100. use Symfony\Component\Lock\PersistingStoreInterface;
  101. use Symfony\Component\Lock\Store\StoreFactory;
  102. use Symfony\Component\Mailer\Bridge as MailerBridge;
  103. use Symfony\Component\Mailer\Command\MailerTestCommand;
  104. use Symfony\Component\Mailer\EventListener\MessengerTransportListener;
  105. use Symfony\Component\Mailer\Mailer;
  106. use Symfony\Component\Mercure\HubRegistry;
  107. use Symfony\Component\Messenger\Attribute\AsMessageHandler;
  108. use Symfony\Component\Messenger\Bridge as MessengerBridge;
  109. use Symfony\Component\Messenger\EventListener\StopWorkerOnSignalsListener;
  110. use Symfony\Component\Messenger\Handler\BatchHandlerInterface;
  111. use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
  112. use Symfony\Component\Messenger\MessageBus;
  113. use Symfony\Component\Messenger\MessageBusInterface;
  114. use Symfony\Component\Messenger\Middleware\RouterContextMiddleware;
  115. use Symfony\Component\Messenger\Transport\Serialization\SerializerInterface;
  116. use Symfony\Component\Messenger\Transport\TransportFactoryInterface as MessengerTransportFactoryInterface;
  117. use Symfony\Component\Messenger\Transport\TransportInterface;
  118. use Symfony\Component\Mime\Header\Headers;
  119. use Symfony\Component\Mime\MimeTypeGuesserInterface;
  120. use Symfony\Component\Mime\MimeTypes;
  121. use Symfony\Component\Notifier\Bridge as NotifierBridge;
  122. use Symfony\Component\Notifier\Bridge\FakeChat\FakeChatTransportFactory;
  123. use Symfony\Component\Notifier\Bridge\FakeSms\FakeSmsTransportFactory;
  124. use Symfony\Component\Notifier\ChatterInterface;
  125. use Symfony\Component\Notifier\Notifier;
  126. use Symfony\Component\Notifier\Recipient\Recipient;
  127. use Symfony\Component\Notifier\TexterInterface;
  128. use Symfony\Component\Notifier\Transport\TransportFactoryInterface as NotifierTransportFactoryInterface;
  129. use Symfony\Component\Process\Messenger\RunProcessMessageHandler;
  130. use Symfony\Component\PropertyAccess\PropertyAccessor;
  131. use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
  132. use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor;
  133. use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
  134. use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
  135. use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
  136. use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
  137. use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
  138. use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface;
  139. use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
  140. use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
  141. use Symfony\Component\RateLimiter\LimiterInterface;
  142. use Symfony\Component\RateLimiter\RateLimiterFactory;
  143. use Symfony\Component\RateLimiter\Storage\CacheStorage;
  144. use Symfony\Component\RemoteEvent\Attribute\AsRemoteEventConsumer;
  145. use Symfony\Component\RemoteEvent\RemoteEvent;
  146. use Symfony\Component\Routing\Loader\AttributeClassLoader;
  147. use Symfony\Component\Scheduler\Attribute\AsCronTask;
  148. use Symfony\Component\Scheduler\Attribute\AsPeriodicTask;
  149. use Symfony\Component\Scheduler\Attribute\AsSchedule;
  150. use Symfony\Component\Scheduler\Messenger\SchedulerTransportFactory;
  151. use Symfony\Component\Security\Core\AuthenticationEvents;
  152. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  153. use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
  154. use Symfony\Component\Semaphore\PersistingStoreInterface as SemaphoreStoreInterface;
  155. use Symfony\Component\Semaphore\Semaphore;
  156. use Symfony\Component\Semaphore\SemaphoreFactory;
  157. use Symfony\Component\Semaphore\Store\StoreFactory as SemaphoreStoreFactory;
  158. use Symfony\Component\Serializer\Encoder\DecoderInterface;
  159. use Symfony\Component\Serializer\Encoder\EncoderInterface;
  160. use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
  161. use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
  162. use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
  163. use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
  164. use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
  165. use Symfony\Component\Serializer\Serializer;
  166. use Symfony\Component\Stopwatch\Stopwatch;
  167. use Symfony\Component\String\LazyString;
  168. use Symfony\Component\String\Slugger\SluggerInterface;
  169. use Symfony\Component\Translation\Bridge as TranslationBridge;
  170. use Symfony\Component\Translation\Command\XliffLintCommand as BaseXliffLintCommand;
  171. use Symfony\Component\Translation\Extractor\PhpAstExtractor;
  172. use Symfony\Component\Translation\LocaleSwitcher;
  173. use Symfony\Component\Translation\PseudoLocalizationTranslator;
  174. use Symfony\Component\Translation\Translator;
  175. use Symfony\Component\Uid\Factory\UuidFactory;
  176. use Symfony\Component\Uid\UuidV4;
  177. use Symfony\Component\Validator\Constraints\ExpressionLanguageProvider;
  178. use Symfony\Component\Validator\ConstraintValidatorInterface;
  179. use Symfony\Component\Validator\GroupProviderInterface;
  180. use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader;
  181. use Symfony\Component\Validator\ObjectInitializerInterface;
  182. use Symfony\Component\Validator\Validation;
  183. use Symfony\Component\Validator\ValidatorBuilder;
  184. use Symfony\Component\Webhook\Controller\WebhookController;
  185. use Symfony\Component\WebLink\HttpHeaderSerializer;
  186. use Symfony\Component\Workflow;
  187. use Symfony\Component\Workflow\WorkflowInterface;
  188. use Symfony\Component\Yaml\Command\LintCommand as BaseYamlLintCommand;
  189. use Symfony\Component\Yaml\Yaml;
  190. use Symfony\Contracts\Cache\CacheInterface;
  191. use Symfony\Contracts\Cache\CallbackInterface;
  192. use Symfony\Contracts\Cache\TagAwareCacheInterface;
  193. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  194. use Symfony\Contracts\HttpClient\HttpClientInterface;
  195. use Symfony\Contracts\Service\ResetInterface;
  196. use Symfony\Contracts\Service\ServiceSubscriberInterface;
  197. use Symfony\Contracts\Translation\LocaleAwareInterface;
  198. /**
  199. * Process the configuration and prepare the dependency injection container with
  200. * parameters and services.
  201. */
  202. class FrameworkExtension extends Extension
  203. {
  204. private array $configsEnabled = [];
  205. /**
  206. * Responds to the app.config configuration parameter.
  207. *
  208. * @return void
  209. *
  210. * @throws LogicException
  211. */
  212. public function load(array $configs, ContainerBuilder $container)
  213. {
  214. $loader = new PhpFileLoader($container, new FileLocator(\dirname(__DIR__).'/Resources/config'));
  215. if (class_exists(InstalledVersions::class) && InstalledVersions::isInstalled('symfony/symfony') && 'symfony/symfony' !== (InstalledVersions::getRootPackage()['name'] ?? '')) {
  216. trigger_deprecation('symfony/symfony', '6.1', 'Requiring the "symfony/symfony" package is deprecated; replace it with standalone components instead.');
  217. }
  218. $loader->load('web.php');
  219. $loader->load('services.php');
  220. $loader->load('fragment_renderer.php');
  221. $loader->load('error_renderer.php');
  222. if (!ContainerBuilder::willBeAvailable('symfony/clock', ClockInterface::class, ['symfony/framework-bundle'])) {
  223. $container->removeDefinition('clock');
  224. $container->removeAlias(ClockInterface::class);
  225. $container->removeAlias(PsrClockInterface::class);
  226. }
  227. $container->registerAliasForArgument('parameter_bag', PsrContainerInterface::class);
  228. $loader->load('process.php');
  229. if (!class_exists(RunProcessMessageHandler::class)) {
  230. $container->removeDefinition('process.messenger.process_message_handler');
  231. }
  232. if ($this->hasConsole()) {
  233. $loader->load('console.php');
  234. if (!class_exists(BaseXliffLintCommand::class)) {
  235. $container->removeDefinition('console.command.xliff_lint');
  236. }
  237. if (!class_exists(BaseYamlLintCommand::class)) {
  238. $container->removeDefinition('console.command.yaml_lint');
  239. }
  240. if (!class_exists(DebugCommand::class)) {
  241. $container->removeDefinition('console.command.dotenv_debug');
  242. }
  243. if (!class_exists(RunCommandMessageHandler::class)) {
  244. $container->removeDefinition('console.messenger.application');
  245. $container->removeDefinition('console.messenger.execute_command_handler');
  246. }
  247. }
  248. // Load Cache configuration first as it is used by other components
  249. $loader->load('cache.php');
  250. $configuration = $this->getConfiguration($configs, $container);
  251. $config = $this->processConfiguration($configuration, $configs);
  252. // warmup config enabled
  253. $this->readConfigEnabled('annotations', $container, $config['annotations']);
  254. $this->readConfigEnabled('translator', $container, $config['translator']);
  255. $this->readConfigEnabled('property_access', $container, $config['property_access']);
  256. $this->readConfigEnabled('profiler', $container, $config['profiler']);
  257. $this->readConfigEnabled('workflows', $container, $config['workflows']);
  258. // A translator must always be registered (as support is included by
  259. // default in the Form and Validator component). If disabled, an identity
  260. // translator will be used and everything will still work as expected.
  261. if ($this->readConfigEnabled('translator', $container, $config['translator']) || $this->readConfigEnabled('form', $container, $config['form']) || $this->readConfigEnabled('validation', $container, $config['validation'])) {
  262. if (!class_exists(Translator::class) && $this->readConfigEnabled('translator', $container, $config['translator'])) {
  263. throw new LogicException('Translation support cannot be enabled as the Translation component is not installed. Try running "composer require symfony/translation".');
  264. }
  265. if (class_exists(Translator::class)) {
  266. $loader->load('identity_translator.php');
  267. }
  268. }
  269. $container->getDefinition('locale_listener')->replaceArgument(3, $config['set_locale_from_accept_language']);
  270. $container->getDefinition('response_listener')->replaceArgument(1, $config['set_content_language_from_locale']);
  271. $container->getDefinition('http_kernel')->replaceArgument(4, $config['handle_all_throwables'] ?? false);
  272. // If the slugger is used but the String component is not available, we should throw an error
  273. if (!ContainerBuilder::willBeAvailable('symfony/string', SluggerInterface::class, ['symfony/framework-bundle'])) {
  274. $container->register('slugger', SluggerInterface::class)
  275. ->addError('You cannot use the "slugger" service since the String component is not installed. Try running "composer require symfony/string".');
  276. } else {
  277. if (!ContainerBuilder::willBeAvailable('symfony/translation', LocaleAwareInterface::class, ['symfony/framework-bundle'])) {
  278. $container->register('slugger', SluggerInterface::class)
  279. ->addError('You cannot use the "slugger" service since the Translation contracts are not installed. Try running "composer require symfony/translation".');
  280. }
  281. if (!\extension_loaded('intl') && !\defined('PHPUNIT_COMPOSER_INSTALL')) {
  282. trigger_deprecation('', '', 'Please install the "intl" PHP extension for best performance.');
  283. }
  284. }
  285. if (isset($config['secret'])) {
  286. $container->setParameter('kernel.secret', $config['secret']);
  287. }
  288. $container->setParameter('kernel.http_method_override', $config['http_method_override']);
  289. $container->setParameter('kernel.trust_x_sendfile_type_header', $config['trust_x_sendfile_type_header']);
  290. $container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']);
  291. $container->setParameter('kernel.default_locale', $config['default_locale']);
  292. $container->setParameter('kernel.enabled_locales', $config['enabled_locales']);
  293. $container->setParameter('kernel.error_controller', $config['error_controller']);
  294. if (($config['trusted_proxies'] ?? false) && ($config['trusted_headers'] ?? false)) {
  295. $container->setParameter('kernel.trusted_proxies', $config['trusted_proxies']);
  296. $container->setParameter('kernel.trusted_headers', $this->resolveTrustedHeaders($config['trusted_headers']));
  297. }
  298. if (!$container->hasParameter('debug.file_link_format')) {
  299. $container->setParameter('debug.file_link_format', $config['ide']);
  300. }
  301. if (!empty($config['test'])) {
  302. $loader->load('test.php');
  303. if (!class_exists(AbstractBrowser::class)) {
  304. $container->removeDefinition('test.client');
  305. }
  306. }
  307. if ($this->readConfigEnabled('request', $container, $config['request'])) {
  308. $this->registerRequestConfiguration($config['request'], $container, $loader);
  309. }
  310. if ($this->readConfigEnabled('assets', $container, $config['assets'])) {
  311. if (!class_exists(\Symfony\Component\Asset\Package::class)) {
  312. throw new LogicException('Asset support cannot be enabled as the Asset component is not installed. Try running "composer require symfony/asset".');
  313. }
  314. $this->registerAssetsConfiguration($config['assets'], $container, $loader);
  315. }
  316. if ($this->readConfigEnabled('asset_mapper', $container, $config['asset_mapper'])) {
  317. if (!class_exists(AssetMapper::class)) {
  318. throw new LogicException('AssetMapper support cannot be enabled as the AssetMapper component is not installed. Try running "composer require symfony/asset-mapper".');
  319. }
  320. $this->registerAssetMapperConfiguration($config['asset_mapper'], $container, $loader, $this->readConfigEnabled('assets', $container, $config['assets']), $this->readConfigEnabled('http_client', $container, $config['http_client']));
  321. } else {
  322. $container->removeDefinition('cache.asset_mapper');
  323. }
  324. if ($this->readConfigEnabled('http_client', $container, $config['http_client'])) {
  325. $this->registerHttpClientConfiguration($config['http_client'], $container, $loader);
  326. }
  327. if ($this->readConfigEnabled('mailer', $container, $config['mailer'])) {
  328. $this->registerMailerConfiguration($config['mailer'], $container, $loader, $this->readConfigEnabled('webhook', $container, $config['webhook']));
  329. if (!$this->hasConsole() || !class_exists(MailerTestCommand::class)) {
  330. $container->removeDefinition('console.command.mailer_test');
  331. }
  332. }
  333. $propertyInfoEnabled = $this->readConfigEnabled('property_info', $container, $config['property_info']);
  334. $this->registerHttpCacheConfiguration($config['http_cache'], $container, $config['http_method_override']);
  335. $this->registerEsiConfiguration($config['esi'], $container, $loader);
  336. $this->registerSsiConfiguration($config['ssi'], $container, $loader);
  337. $this->registerFragmentsConfiguration($config['fragments'], $container, $loader);
  338. $this->registerTranslatorConfiguration($config['translator'], $container, $loader, $config['default_locale'], $config['enabled_locales']);
  339. $this->registerWorkflowConfiguration($config['workflows'], $container, $loader);
  340. $this->registerDebugConfiguration($config['php_errors'], $container, $loader);
  341. $this->registerRouterConfiguration($config['router'], $container, $loader, $config['enabled_locales']);
  342. $this->registerAnnotationsConfiguration($config['annotations'], $container, $loader);
  343. $this->registerPropertyAccessConfiguration($config['property_access'], $container, $loader);
  344. $this->registerSecretsConfiguration($config['secrets'], $container, $loader);
  345. $container->getDefinition('exception_listener')->replaceArgument(3, $config['exceptions']);
  346. if ($this->readConfigEnabled('serializer', $container, $config['serializer'])) {
  347. if (!class_exists(Serializer::class)) {
  348. throw new LogicException('Serializer support cannot be enabled as the Serializer component is not installed. Try running "composer require symfony/serializer-pack".');
  349. }
  350. $this->registerSerializerConfiguration($config['serializer'], $container, $loader);
  351. } else {
  352. $container->getDefinition('argument_resolver.request_payload')
  353. ->setArguments([])
  354. ->addError('You can neither use "#[MapRequestPayload]" nor "#[MapQueryString]" since the Serializer component is not '
  355. .(class_exists(Serializer::class) ? 'enabled. Try setting "framework.serializer.enabled" to true.' : 'installed. Try running "composer require symfony/serializer-pack".')
  356. )
  357. ->addTag('container.error')
  358. ->clearTag('kernel.event_subscriber');
  359. $container->removeDefinition('console.command.serializer_debug');
  360. }
  361. if ($propertyInfoEnabled) {
  362. $this->registerPropertyInfoConfiguration($container, $loader);
  363. }
  364. if ($this->readConfigEnabled('lock', $container, $config['lock'])) {
  365. $this->registerLockConfiguration($config['lock'], $container, $loader);
  366. }
  367. if ($this->readConfigEnabled('semaphore', $container, $config['semaphore'])) {
  368. $this->registerSemaphoreConfiguration($config['semaphore'], $container, $loader);
  369. }
  370. if ($this->readConfigEnabled('rate_limiter', $container, $config['rate_limiter'])) {
  371. if (!interface_exists(LimiterInterface::class)) {
  372. throw new LogicException('Rate limiter support cannot be enabled as the RateLimiter component is not installed. Try running "composer require symfony/rate-limiter".');
  373. }
  374. $this->registerRateLimiterConfiguration($config['rate_limiter'], $container, $loader);
  375. }
  376. if ($this->readConfigEnabled('web_link', $container, $config['web_link'])) {
  377. if (!class_exists(HttpHeaderSerializer::class)) {
  378. throw new LogicException('WebLink support cannot be enabled as the WebLink component is not installed. Try running "composer require symfony/weblink".');
  379. }
  380. $loader->load('web_link.php');
  381. }
  382. if ($this->readConfigEnabled('uid', $container, $config['uid'])) {
  383. if (!class_exists(UuidFactory::class)) {
  384. throw new LogicException('Uid support cannot be enabled as the Uid component is not installed. Try running "composer require symfony/uid".');
  385. }
  386. $this->registerUidConfiguration($config['uid'], $container, $loader);
  387. } else {
  388. $container->removeDefinition('argument_resolver.uid');
  389. }
  390. // register cache before session so both can share the connection services
  391. $this->registerCacheConfiguration($config['cache'], $container);
  392. if ($this->readConfigEnabled('session', $container, $config['session'])) {
  393. if (!\extension_loaded('session')) {
  394. throw new LogicException('Session support cannot be enabled as the session extension is not installed. See https://php.net/session.installation for instructions.');
  395. }
  396. $this->registerSessionConfiguration($config['session'], $container, $loader);
  397. if (!empty($config['test'])) {
  398. // test listener will replace the existing session listener
  399. // as we are aliasing to avoid duplicated registered events
  400. $container->setAlias('session_listener', 'test.session.listener');
  401. }
  402. } elseif (!empty($config['test'])) {
  403. $container->removeDefinition('test.session.listener');
  404. }
  405. // csrf depends on session being registered
  406. if (null === $config['csrf_protection']['enabled']) {
  407. $this->writeConfigEnabled('csrf_protection', $this->readConfigEnabled('session', $container, $config['session']) && !class_exists(FullStack::class) && ContainerBuilder::willBeAvailable('symfony/security-csrf', CsrfTokenManagerInterface::class, ['symfony/framework-bundle']), $config['csrf_protection']);
  408. }
  409. $this->registerSecurityCsrfConfiguration($config['csrf_protection'], $container, $loader);
  410. // form depends on csrf being registered
  411. if ($this->readConfigEnabled('form', $container, $config['form'])) {
  412. if (!class_exists(Form::class)) {
  413. throw new LogicException('Form support cannot be enabled as the Form component is not installed. Try running "composer require symfony/form".');
  414. }
  415. $this->registerFormConfiguration($config, $container, $loader);
  416. if (ContainerBuilder::willBeAvailable('symfony/validator', Validation::class, ['symfony/framework-bundle', 'symfony/form'])) {
  417. $this->writeConfigEnabled('validation', true, $config['validation']);
  418. } else {
  419. $container->setParameter('validator.translation_domain', 'validators');
  420. $container->removeDefinition('form.type_extension.form.validator');
  421. $container->removeDefinition('form.type_guesser.validator');
  422. }
  423. if (!$this->readConfigEnabled('html_sanitizer', $container, $config['html_sanitizer']) || !class_exists(TextTypeHtmlSanitizerExtension::class)) {
  424. $container->removeDefinition('form.type_extension.form.html_sanitizer');
  425. }
  426. } else {
  427. $container->removeDefinition('console.command.form_debug');
  428. }
  429. // validation depends on form, annotations being registered
  430. $this->registerValidationConfiguration($config['validation'], $container, $loader, $propertyInfoEnabled);
  431. $messengerEnabled = $this->readConfigEnabled('messenger', $container, $config['messenger']);
  432. if ($this->readConfigEnabled('scheduler', $container, $config['scheduler'])) {
  433. if (!$messengerEnabled) {
  434. throw new LogicException('Scheduler support cannot be enabled as the Messenger component is not '.(interface_exists(MessageBusInterface::class) ? 'enabled.' : 'installed. Try running "composer require symfony/messenger".'));
  435. }
  436. $this->registerSchedulerConfiguration($config['scheduler'], $container, $loader);
  437. } else {
  438. $container->removeDefinition('cache.scheduler');
  439. $container->removeDefinition('console.command.scheduler_debug');
  440. }
  441. // messenger depends on validation being registered
  442. if ($messengerEnabled) {
  443. $this->registerMessengerConfiguration($config['messenger'], $container, $loader, $this->readConfigEnabled('validation', $container, $config['validation']));
  444. } else {
  445. $container->removeDefinition('console.command.messenger_consume_messages');
  446. $container->removeDefinition('console.command.messenger_stats');
  447. $container->removeDefinition('console.command.messenger_debug');
  448. $container->removeDefinition('console.command.messenger_stop_workers');
  449. $container->removeDefinition('console.command.messenger_setup_transports');
  450. $container->removeDefinition('console.command.messenger_failed_messages_retry');
  451. $container->removeDefinition('console.command.messenger_failed_messages_show');
  452. $container->removeDefinition('console.command.messenger_failed_messages_remove');
  453. $container->removeDefinition('cache.messenger.restart_workers_signal');
  454. if ($container->hasDefinition('messenger.transport.amqp.factory') && !class_exists(MessengerBridge\Amqp\Transport\AmqpTransportFactory::class)) {
  455. if (class_exists(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class)) {
  456. $container->getDefinition('messenger.transport.amqp.factory')
  457. ->setClass(\Symfony\Component\Messenger\Transport\AmqpExt\AmqpTransportFactory::class)
  458. ->addTag('messenger.transport_factory');
  459. } else {
  460. $container->removeDefinition('messenger.transport.amqp.factory');
  461. }
  462. }
  463. if ($container->hasDefinition('messenger.transport.redis.factory') && !class_exists(MessengerBridge\Redis\Transport\RedisTransportFactory::class)) {
  464. if (class_exists(\Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class)) {
  465. $container->getDefinition('messenger.transport.redis.factory')
  466. ->setClass(\Symfony\Component\Messenger\Transport\RedisExt\RedisTransportFactory::class)
  467. ->addTag('messenger.transport_factory');
  468. } else {
  469. $container->removeDefinition('messenger.transport.redis.factory');
  470. }
  471. }
  472. }
  473. // notifier depends on messenger, mailer being registered
  474. if ($this->readConfigEnabled('notifier', $container, $config['notifier'])) {
  475. $this->registerNotifierConfiguration($config['notifier'], $container, $loader, $this->readConfigEnabled('webhook', $container, $config['webhook']));
  476. }
  477. // profiler depends on form, validation, translation, messenger, mailer, http-client, notifier, serializer being registered
  478. $this->registerProfilerConfiguration($config['profiler'], $container, $loader);
  479. if ($this->readConfigEnabled('webhook', $container, $config['webhook'])) {
  480. $this->registerWebhookConfiguration($config['webhook'], $container, $loader);
  481. // If Webhook is installed but the HttpClient or Serializer components are not available, we should throw an error
  482. if (!$this->readConfigEnabled('http_client', $container, $config['http_client'])) {
  483. $container->getDefinition('webhook.transport')
  484. ->setArguments([])
  485. ->addError('You cannot use the "webhook transport" service since the HttpClient component is not '
  486. .(class_exists(ScopingHttpClient::class) ? 'enabled. Try setting "framework.http_client.enabled" to true.' : 'installed. Try running "composer require symfony/http-client".')
  487. )
  488. ->addTag('container.error');
  489. }
  490. if (!$this->readConfigEnabled('serializer', $container, $config['serializer'])) {
  491. $container->getDefinition('webhook.body_configurator.json')
  492. ->setArguments([])
  493. ->addError('You cannot use the "webhook transport" service since the Serializer component is not '
  494. .(class_exists(Serializer::class) ? 'enabled. Try setting "framework.serializer.enabled" to true.' : 'installed. Try running "composer require symfony/serializer-pack".')
  495. )
  496. ->addTag('container.error');
  497. }
  498. }
  499. if ($this->readConfigEnabled('remote-event', $container, $config['remote-event'])) {
  500. $this->registerRemoteEventConfiguration($config['remote-event'], $container, $loader);
  501. }
  502. if ($this->readConfigEnabled('html_sanitizer', $container, $config['html_sanitizer'])) {
  503. if (!class_exists(HtmlSanitizerConfig::class)) {
  504. throw new LogicException('HtmlSanitizer support cannot be enabled as the HtmlSanitizer component is not installed. Try running "composer require symfony/html-sanitizer".');
  505. }
  506. $this->registerHtmlSanitizerConfiguration($config['html_sanitizer'], $container, $loader);
  507. }
  508. $this->addAnnotatedClassesToCompile([
  509. '**\\Controller\\',
  510. '**\\Entity\\',
  511. // Added explicitly so that we don't rely on the class map being dumped to make it work
  512. AbstractController::class,
  513. ]);
  514. if (ContainerBuilder::willBeAvailable('symfony/mime', MimeTypes::class, ['symfony/framework-bundle'])) {
  515. $loader->load('mime_type.php');
  516. }
  517. $container->registerForAutoconfiguration(PackageInterface::class)
  518. ->addTag('assets.package');
  519. $container->registerForAutoconfiguration(AssetCompilerInterface::class)
  520. ->addTag('asset_mapper.compiler');
  521. $container->registerForAutoconfiguration(Command::class)
  522. ->addTag('console.command');
  523. $container->registerForAutoconfiguration(ResourceCheckerInterface::class)
  524. ->addTag('config_cache.resource_checker');
  525. $container->registerForAutoconfiguration(EnvVarLoaderInterface::class)
  526. ->addTag('container.env_var_loader');
  527. $container->registerForAutoconfiguration(EnvVarProcessorInterface::class)
  528. ->addTag('container.env_var_processor');
  529. $container->registerForAutoconfiguration(CallbackInterface::class)
  530. ->addTag('container.reversible');
  531. $container->registerForAutoconfiguration(ServiceLocator::class)
  532. ->addTag('container.service_locator');
  533. $container->registerForAutoconfiguration(ServiceSubscriberInterface::class)
  534. ->addTag('container.service_subscriber');
  535. $container->registerForAutoconfiguration(ArgumentValueResolverInterface::class)
  536. ->addTag('controller.argument_value_resolver');
  537. $container->registerForAutoconfiguration(ValueResolverInterface::class)
  538. ->addTag('controller.argument_value_resolver');
  539. $container->registerForAutoconfiguration(AbstractController::class)
  540. ->addTag('controller.service_arguments');
  541. $container->registerForAutoconfiguration(DataCollectorInterface::class)
  542. ->addTag('data_collector');
  543. $container->registerForAutoconfiguration(FormTypeInterface::class)
  544. ->addTag('form.type');
  545. $container->registerForAutoconfiguration(FormTypeGuesserInterface::class)
  546. ->addTag('form.type_guesser');
  547. $container->registerForAutoconfiguration(FormTypeExtensionInterface::class)
  548. ->addTag('form.type_extension');
  549. $container->registerForAutoconfiguration(CacheClearerInterface::class)
  550. ->addTag('kernel.cache_clearer');
  551. $container->registerForAutoconfiguration(CacheWarmerInterface::class)
  552. ->addTag('kernel.cache_warmer');
  553. $container->registerForAutoconfiguration(EventDispatcherInterface::class)
  554. ->addTag('event_dispatcher.dispatcher');
  555. $container->registerForAutoconfiguration(EventSubscriberInterface::class)
  556. ->addTag('kernel.event_subscriber');
  557. $container->registerForAutoconfiguration(LocaleAwareInterface::class)
  558. ->addTag('kernel.locale_aware');
  559. $container->registerForAutoconfiguration(ResetInterface::class)
  560. ->addTag('kernel.reset', ['method' => 'reset']);
  561. if (!interface_exists(MarshallerInterface::class)) {
  562. $container->registerForAutoconfiguration(ResettableInterface::class)
  563. ->addTag('kernel.reset', ['method' => 'reset']);
  564. }
  565. $container->registerForAutoconfiguration(PropertyListExtractorInterface::class)
  566. ->addTag('property_info.list_extractor');
  567. $container->registerForAutoconfiguration(PropertyTypeExtractorInterface::class)
  568. ->addTag('property_info.type_extractor');
  569. $container->registerForAutoconfiguration(PropertyDescriptionExtractorInterface::class)
  570. ->addTag('property_info.description_extractor');
  571. $container->registerForAutoconfiguration(PropertyAccessExtractorInterface::class)
  572. ->addTag('property_info.access_extractor');
  573. $container->registerForAutoconfiguration(PropertyInitializableExtractorInterface::class)
  574. ->addTag('property_info.initializable_extractor');
  575. $container->registerForAutoconfiguration(EncoderInterface::class)
  576. ->addTag('serializer.encoder');
  577. $container->registerForAutoconfiguration(DecoderInterface::class)
  578. ->addTag('serializer.encoder');
  579. $container->registerForAutoconfiguration(NormalizerInterface::class)
  580. ->addTag('serializer.normalizer');
  581. $container->registerForAutoconfiguration(DenormalizerInterface::class)
  582. ->addTag('serializer.normalizer');
  583. $container->registerForAutoconfiguration(ConstraintValidatorInterface::class)
  584. ->addTag('validator.constraint_validator');
  585. $container->registerForAutoconfiguration(GroupProviderInterface::class)
  586. ->addTag('validator.group_provider');
  587. $container->registerForAutoconfiguration(ObjectInitializerInterface::class)
  588. ->addTag('validator.initializer');
  589. $container->registerForAutoconfiguration(MessageHandlerInterface::class)
  590. ->addTag('messenger.message_handler');
  591. $container->registerForAutoconfiguration(BatchHandlerInterface::class)
  592. ->addTag('messenger.message_handler');
  593. $container->registerForAutoconfiguration(MessengerTransportFactoryInterface::class)
  594. ->addTag('messenger.transport_factory');
  595. $container->registerForAutoconfiguration(MimeTypeGuesserInterface::class)
  596. ->addTag('mime.mime_type_guesser');
  597. $container->registerForAutoconfiguration(LoggerAwareInterface::class)
  598. ->addMethodCall('setLogger', [new Reference('logger')]);
  599. $container->registerAttributeForAutoconfiguration(AsEventListener::class, static function (ChildDefinition $definition, AsEventListener $attribute, \ReflectionClass|\ReflectionMethod $reflector) {
  600. $tagAttributes = get_object_vars($attribute);
  601. if ($reflector instanceof \ReflectionMethod) {
  602. if (isset($tagAttributes['method'])) {
  603. throw new LogicException(sprintf('AsEventListener attribute cannot declare a method on "%s::%s()".', $reflector->class, $reflector->name));
  604. }
  605. $tagAttributes['method'] = $reflector->getName();
  606. }
  607. $definition->addTag('kernel.event_listener', $tagAttributes);
  608. });
  609. $container->registerAttributeForAutoconfiguration(AsController::class, static function (ChildDefinition $definition, AsController $attribute): void {
  610. $definition->addTag('controller.service_arguments');
  611. });
  612. $container->registerAttributeForAutoconfiguration(AsRemoteEventConsumer::class, static function (ChildDefinition $definition, AsRemoteEventConsumer $attribute): void {
  613. $definition->addTag('remote_event.consumer', ['consumer' => $attribute->name]);
  614. });
  615. $container->registerAttributeForAutoconfiguration(AsMessageHandler::class, static function (ChildDefinition $definition, AsMessageHandler $attribute, \ReflectionClass|\ReflectionMethod $reflector): void {
  616. $tagAttributes = get_object_vars($attribute);
  617. $tagAttributes['from_transport'] = $tagAttributes['fromTransport'];
  618. unset($tagAttributes['fromTransport']);
  619. if ($reflector instanceof \ReflectionMethod) {
  620. if (isset($tagAttributes['method'])) {
  621. throw new LogicException(sprintf('AsMessageHandler attribute cannot declare a method on "%s::%s()".', $reflector->class, $reflector->name));
  622. }
  623. $tagAttributes['method'] = $reflector->getName();
  624. }
  625. $definition->addTag('messenger.message_handler', $tagAttributes);
  626. });
  627. $container->registerAttributeForAutoconfiguration(AsTargetedValueResolver::class, static function (ChildDefinition $definition, AsTargetedValueResolver $attribute): void {
  628. $definition->addTag('controller.targeted_value_resolver', $attribute->name ? ['name' => $attribute->name] : []);
  629. });
  630. $container->registerAttributeForAutoconfiguration(AsSchedule::class, static function (ChildDefinition $definition, AsSchedule $attribute): void {
  631. $definition->addTag('scheduler.schedule_provider', ['name' => $attribute->name]);
  632. });
  633. foreach ([AsPeriodicTask::class, AsCronTask::class] as $taskAttributeClass) {
  634. $container->registerAttributeForAutoconfiguration(
  635. $taskAttributeClass,
  636. static function (ChildDefinition $definition, AsPeriodicTask|AsCronTask $attribute, \ReflectionClass|\ReflectionMethod $reflector): void {
  637. $tagAttributes = get_object_vars($attribute) + [
  638. 'trigger' => match ($attribute::class) {
  639. AsPeriodicTask::class => 'every',
  640. AsCronTask::class => 'cron',
  641. },
  642. ];
  643. if ($reflector instanceof \ReflectionMethod) {
  644. if (isset($tagAttributes['method'])) {
  645. throw new LogicException(sprintf('"%s" attribute cannot declare a method on "%s::%s()".', $attribute::class, $reflector->class, $reflector->name));
  646. }
  647. $tagAttributes['method'] = $reflector->getName();
  648. }
  649. $definition->addTag('scheduler.task', $tagAttributes);
  650. }
  651. );
  652. }
  653. if (!$container->getParameter('kernel.debug')) {
  654. // remove tagged iterator argument for resource checkers
  655. $container->getDefinition('config_cache_factory')->setArguments([]);
  656. }
  657. if (!$config['disallow_search_engine_index']) {
  658. $container->removeDefinition('disallow_search_engine_index_response_listener');
  659. }
  660. $container->registerForAutoconfiguration(RouteLoaderInterface::class)
  661. ->addTag('routing.route_loader');
  662. $container->setParameter('container.behavior_describing_tags', [
  663. 'annotations.cached_reader',
  664. 'container.do_not_inline',
  665. 'container.service_locator',
  666. 'container.service_subscriber',
  667. 'kernel.event_subscriber',
  668. 'kernel.event_listener',
  669. 'kernel.locale_aware',
  670. 'kernel.reset',
  671. ]);
  672. }
  673. public function getConfiguration(array $config, ContainerBuilder $container): ?ConfigurationInterface
  674. {
  675. return new Configuration($container->getParameter('kernel.debug'));
  676. }
  677. protected function hasConsole(): bool
  678. {
  679. return class_exists(Application::class);
  680. }
  681. private function registerFormConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  682. {
  683. $loader->load('form.php');
  684. if (null === $config['form']['csrf_protection']['enabled']) {
  685. $this->writeConfigEnabled('form.csrf_protection', $config['csrf_protection']['enabled'], $config['form']['csrf_protection']);
  686. }
  687. if ($this->readConfigEnabled('form.csrf_protection', $container, $config['form']['csrf_protection'])) {
  688. if (!$container->hasDefinition('security.csrf.token_generator')) {
  689. throw new \LogicException('To use form CSRF protection, "framework.csrf_protection" must be enabled.');
  690. }
  691. $loader->load('form_csrf.php');
  692. $container->setParameter('form.type_extension.csrf.enabled', true);
  693. $container->setParameter('form.type_extension.csrf.field_name', $config['form']['csrf_protection']['field_name']);
  694. } else {
  695. $container->setParameter('form.type_extension.csrf.enabled', false);
  696. }
  697. if (!ContainerBuilder::willBeAvailable('symfony/translation', Translator::class, ['symfony/framework-bundle', 'symfony/form'])) {
  698. $container->removeDefinition('form.type_extension.upload.validator');
  699. }
  700. }
  701. private function registerHttpCacheConfiguration(array $config, ContainerBuilder $container, bool $httpMethodOverride): void
  702. {
  703. $options = $config;
  704. unset($options['enabled']);
  705. if (!$options['private_headers']) {
  706. unset($options['private_headers']);
  707. }
  708. if (!$options['skip_response_headers']) {
  709. unset($options['skip_response_headers']);
  710. }
  711. $container->getDefinition('http_cache')
  712. ->setPublic($config['enabled'])
  713. ->replaceArgument(3, $options);
  714. if ($httpMethodOverride) {
  715. $container->getDefinition('http_cache')
  716. ->addArgument((new Definition('void'))
  717. ->setFactory([Request::class, 'enableHttpMethodParameterOverride'])
  718. );
  719. }
  720. }
  721. private function registerEsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  722. {
  723. if (!$this->readConfigEnabled('esi', $container, $config)) {
  724. $container->removeDefinition('fragment.renderer.esi');
  725. return;
  726. }
  727. $loader->load('esi.php');
  728. }
  729. private function registerSsiConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  730. {
  731. if (!$this->readConfigEnabled('ssi', $container, $config)) {
  732. $container->removeDefinition('fragment.renderer.ssi');
  733. return;
  734. }
  735. $loader->load('ssi.php');
  736. }
  737. private function registerFragmentsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  738. {
  739. if (!$this->readConfigEnabled('fragments', $container, $config)) {
  740. $container->removeDefinition('fragment.renderer.hinclude');
  741. return;
  742. }
  743. $container->setParameter('fragment.renderer.hinclude.global_template', $config['hinclude_default_template']);
  744. $loader->load('fragment_listener.php');
  745. $container->setParameter('fragment.path', $config['path']);
  746. }
  747. private function registerProfilerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  748. {
  749. if (!$this->readConfigEnabled('profiler', $container, $config)) {
  750. // this is needed for the WebProfiler to work even if the profiler is disabled
  751. $container->setParameter('data_collector.templates', []);
  752. return;
  753. }
  754. $loader->load('profiling.php');
  755. $loader->load('collectors.php');
  756. $loader->load('cache_debug.php');
  757. if ($this->isInitializedConfigEnabled('form')) {
  758. $loader->load('form_debug.php');
  759. }
  760. if ($this->isInitializedConfigEnabled('validation')) {
  761. $loader->load('validator_debug.php');
  762. }
  763. if ($this->isInitializedConfigEnabled('translator')) {
  764. $loader->load('translation_debug.php');
  765. $container->getDefinition('translator.data_collector')->setDecoratedService('translator');
  766. }
  767. if ($this->isInitializedConfigEnabled('messenger')) {
  768. $loader->load('messenger_debug.php');
  769. }
  770. if ($this->isInitializedConfigEnabled('mailer')) {
  771. $loader->load('mailer_debug.php');
  772. }
  773. if ($this->isInitializedConfigEnabled('workflows')) {
  774. $loader->load('workflow_debug.php');
  775. }
  776. if ($this->isInitializedConfigEnabled('http_client')) {
  777. $loader->load('http_client_debug.php');
  778. }
  779. if ($this->isInitializedConfigEnabled('notifier')) {
  780. $loader->load('notifier_debug.php');
  781. }
  782. if ($this->isInitializedConfigEnabled('serializer') && $config['collect_serializer_data']) {
  783. $loader->load('serializer_debug.php');
  784. }
  785. $container->setParameter('profiler_listener.only_exceptions', $config['only_exceptions']);
  786. $container->setParameter('profiler_listener.only_main_requests', $config['only_main_requests']);
  787. // Choose storage class based on the DSN
  788. [$class] = explode(':', $config['dsn'], 2);
  789. if ('file' !== $class) {
  790. throw new \LogicException(sprintf('Driver "%s" is not supported for the profiler.', $class));
  791. }
  792. $container->setParameter('profiler.storage.dsn', $config['dsn']);
  793. $container->getDefinition('profiler')
  794. ->addArgument($config['collect'])
  795. ->addTag('kernel.reset', ['method' => 'reset']);
  796. $container->getDefinition('profiler_listener')
  797. ->addArgument($config['collect_parameter']);
  798. if (!$container->getParameter('kernel.debug') || !class_exists(CliRequest::class) || !$container->has('debug.stopwatch')) {
  799. $container->removeDefinition('console_profiler_listener');
  800. }
  801. if (!class_exists(CommandDataCollector::class)) {
  802. $container->removeDefinition('.data_collector.command');
  803. }
  804. }
  805. private function registerWorkflowConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  806. {
  807. if (!$config['enabled']) {
  808. $container->removeDefinition('console.command.workflow_dump');
  809. return;
  810. }
  811. if (!class_exists(Workflow\Workflow::class)) {
  812. throw new LogicException('Workflow support cannot be enabled as the Workflow component is not installed. Try running "composer require symfony/workflow".');
  813. }
  814. $loader->load('workflow.php');
  815. $registryDefinition = $container->getDefinition('workflow.registry');
  816. foreach ($config['workflows'] as $name => $workflow) {
  817. $type = $workflow['type'];
  818. $workflowId = sprintf('%s.%s', $type, $name);
  819. // Process Metadata (workflow + places (transition is done in the "create transition" block))
  820. $metadataStoreDefinition = new Definition(Workflow\Metadata\InMemoryMetadataStore::class, [[], [], null]);
  821. if ($workflow['metadata']) {
  822. $metadataStoreDefinition->replaceArgument(0, $workflow['metadata']);
  823. }
  824. $placesMetadata = [];
  825. foreach ($workflow['places'] as $place) {
  826. if ($place['metadata']) {
  827. $placesMetadata[$place['name']] = $place['metadata'];
  828. }
  829. }
  830. if ($placesMetadata) {
  831. $metadataStoreDefinition->replaceArgument(1, $placesMetadata);
  832. }
  833. // Create transitions
  834. $transitions = [];
  835. $guardsConfiguration = [];
  836. $transitionsMetadataDefinition = new Definition(\SplObjectStorage::class);
  837. // Global transition counter per workflow
  838. $transitionCounter = 0;
  839. foreach ($workflow['transitions'] as $transition) {
  840. if ('workflow' === $type) {
  841. $transitionDefinition = new Definition(Workflow\Transition::class, [$transition['name'], $transition['from'], $transition['to']]);
  842. $transitionId = sprintf('.%s.transition.%s', $workflowId, $transitionCounter++);
  843. $container->setDefinition($transitionId, $transitionDefinition);
  844. $transitions[] = new Reference($transitionId);
  845. if (isset($transition['guard'])) {
  846. $configuration = new Definition(Workflow\EventListener\GuardExpression::class);
  847. $configuration->addArgument(new Reference($transitionId));
  848. $configuration->addArgument($transition['guard']);
  849. $eventName = sprintf('workflow.%s.guard.%s', $name, $transition['name']);
  850. $guardsConfiguration[$eventName][] = $configuration;
  851. }
  852. if ($transition['metadata']) {
  853. $transitionsMetadataDefinition->addMethodCall('attach', [
  854. new Reference($transitionId),
  855. $transition['metadata'],
  856. ]);
  857. }
  858. } elseif ('state_machine' === $type) {
  859. foreach ($transition['from'] as $from) {
  860. foreach ($transition['to'] as $to) {
  861. $transitionDefinition = new Definition(Workflow\Transition::class, [$transition['name'], $from, $to]);
  862. $transitionId = sprintf('.%s.transition.%s', $workflowId, $transitionCounter++);
  863. $container->setDefinition($transitionId, $transitionDefinition);
  864. $transitions[] = new Reference($transitionId);
  865. if (isset($transition['guard'])) {
  866. $configuration = new Definition(Workflow\EventListener\GuardExpression::class);
  867. $configuration->addArgument(new Reference($transitionId));
  868. $configuration->addArgument($transition['guard']);
  869. $eventName = sprintf('workflow.%s.guard.%s', $name, $transition['name']);
  870. $guardsConfiguration[$eventName][] = $configuration;
  871. }
  872. if ($transition['metadata']) {
  873. $transitionsMetadataDefinition->addMethodCall('attach', [
  874. new Reference($transitionId),
  875. $transition['metadata'],
  876. ]);
  877. }
  878. }
  879. }
  880. }
  881. }
  882. $metadataStoreDefinition->replaceArgument(2, $transitionsMetadataDefinition);
  883. $container->setDefinition(sprintf('%s.metadata_store', $workflowId), $metadataStoreDefinition);
  884. // Create places
  885. $places = array_column($workflow['places'], 'name');
  886. $initialMarking = $workflow['initial_marking'] ?? [];
  887. // Create a Definition
  888. $definitionDefinition = new Definition(Workflow\Definition::class);
  889. $definitionDefinition->addArgument($places);
  890. $definitionDefinition->addArgument($transitions);
  891. $definitionDefinition->addArgument($initialMarking);
  892. $definitionDefinition->addArgument(new Reference(sprintf('%s.metadata_store', $workflowId)));
  893. // Create MarkingStore
  894. $markingStoreDefinition = null;
  895. if (isset($workflow['marking_store']['type']) || isset($workflow['marking_store']['property'])) {
  896. $markingStoreDefinition = new ChildDefinition('workflow.marking_store.method');
  897. $markingStoreDefinition->setArguments([
  898. 'state_machine' === $type, // single state
  899. $workflow['marking_store']['property'] ?? 'marking',
  900. ]);
  901. } elseif (isset($workflow['marking_store']['service'])) {
  902. $markingStoreDefinition = new Reference($workflow['marking_store']['service']);
  903. }
  904. // Create Workflow
  905. $workflowDefinition = new ChildDefinition(sprintf('%s.abstract', $type));
  906. $workflowDefinition->replaceArgument(0, new Reference(sprintf('%s.definition', $workflowId)));
  907. $workflowDefinition->replaceArgument(1, $markingStoreDefinition);
  908. $workflowDefinition->replaceArgument(3, $name);
  909. $workflowDefinition->replaceArgument(4, $workflow['events_to_dispatch']);
  910. $workflowDefinition->addTag('workflow', ['name' => $name]);
  911. if ('workflow' === $type) {
  912. $workflowDefinition->addTag('workflow.workflow', ['name' => $name]);
  913. } elseif ('state_machine' === $type) {
  914. $workflowDefinition->addTag('workflow.state_machine', ['name' => $name]);
  915. }
  916. // Store to container
  917. $container->setDefinition($workflowId, $workflowDefinition);
  918. $container->setDefinition(sprintf('%s.definition', $workflowId), $definitionDefinition);
  919. $container->registerAliasForArgument($workflowId, WorkflowInterface::class, $name.'.'.$type);
  920. $container->registerAliasForArgument($workflowId, WorkflowInterface::class, $name);
  921. // Validate Workflow
  922. if ('state_machine' === $workflow['type']) {
  923. $validator = new Workflow\Validator\StateMachineValidator();
  924. } else {
  925. $validator = new Workflow\Validator\WorkflowValidator();
  926. }
  927. $trs = array_map(fn (Reference $ref): Workflow\Transition => $container->get((string) $ref), $transitions);
  928. $realDefinition = new Workflow\Definition($places, $trs, $initialMarking);
  929. $validator->validate($realDefinition, $name);
  930. // Add workflow to Registry
  931. if ($workflow['supports']) {
  932. foreach ($workflow['supports'] as $supportedClassName) {
  933. $strategyDefinition = new Definition(Workflow\SupportStrategy\InstanceOfSupportStrategy::class, [$supportedClassName]);
  934. $registryDefinition->addMethodCall('addWorkflow', [new Reference($workflowId), $strategyDefinition]);
  935. }
  936. } elseif (isset($workflow['support_strategy'])) {
  937. $registryDefinition->addMethodCall('addWorkflow', [new Reference($workflowId), new Reference($workflow['support_strategy'])]);
  938. }
  939. // Enable the AuditTrail
  940. if ($workflow['audit_trail']['enabled']) {
  941. $listener = new Definition(Workflow\EventListener\AuditTrailListener::class);
  942. $listener->addTag('monolog.logger', ['channel' => 'workflow']);
  943. $listener->addTag('kernel.event_listener', ['event' => sprintf('workflow.%s.leave', $name), 'method' => 'onLeave']);
  944. $listener->addTag('kernel.event_listener', ['event' => sprintf('workflow.%s.transition', $name), 'method' => 'onTransition']);
  945. $listener->addTag('kernel.event_listener', ['event' => sprintf('workflow.%s.enter', $name), 'method' => 'onEnter']);
  946. $listener->addArgument(new Reference('logger'));
  947. $container->setDefinition(sprintf('.%s.listener.audit_trail', $workflowId), $listener);
  948. }
  949. // Add Guard Listener
  950. if ($guardsConfiguration) {
  951. if (!class_exists(ExpressionLanguage::class)) {
  952. throw new LogicException('Cannot guard workflows as the ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".');
  953. }
  954. if (!class_exists(AuthenticationEvents::class)) {
  955. throw new LogicException('Cannot guard workflows as the Security component is not installed. Try running "composer require symfony/security-core".');
  956. }
  957. $guard = new Definition(Workflow\EventListener\GuardListener::class);
  958. $guard->setArguments([
  959. $guardsConfiguration,
  960. new Reference('workflow.security.expression_language'),
  961. new Reference('security.token_storage'),
  962. new Reference('security.authorization_checker'),
  963. new Reference('security.authentication.trust_resolver'),
  964. new Reference('security.role_hierarchy'),
  965. new Reference('validator', ContainerInterface::NULL_ON_INVALID_REFERENCE),
  966. ]);
  967. foreach ($guardsConfiguration as $eventName => $config) {
  968. $guard->addTag('kernel.event_listener', ['event' => $eventName, 'method' => 'onTransition']);
  969. }
  970. $container->setDefinition(sprintf('.%s.listener.guard', $workflowId), $guard);
  971. $container->setParameter('workflow.has_guard_listeners', true);
  972. }
  973. }
  974. $listenerAttributes = [
  975. Workflow\Attribute\AsAnnounceListener::class,
  976. Workflow\Attribute\AsCompletedListener::class,
  977. Workflow\Attribute\AsEnterListener::class,
  978. Workflow\Attribute\AsEnteredListener::class,
  979. Workflow\Attribute\AsGuardListener::class,
  980. Workflow\Attribute\AsLeaveListener::class,
  981. Workflow\Attribute\AsTransitionListener::class,
  982. ];
  983. foreach ($listenerAttributes as $attribute) {
  984. $container->registerAttributeForAutoconfiguration($attribute, static function (ChildDefinition $definition, AsEventListener $attribute, \ReflectionClass|\ReflectionMethod $reflector) {
  985. $tagAttributes = get_object_vars($attribute);
  986. if ($reflector instanceof \ReflectionMethod) {
  987. if (isset($tagAttributes['method'])) {
  988. throw new LogicException(sprintf('"%s" attribute cannot declare a method on "%s::%s()".', $attribute::class, $reflector->class, $reflector->name));
  989. }
  990. $tagAttributes['method'] = $reflector->getName();
  991. }
  992. $definition->addTag('kernel.event_listener', $tagAttributes);
  993. });
  994. }
  995. }
  996. private function registerDebugConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  997. {
  998. $loader->load('debug_prod.php');
  999. $debug = $container->getParameter('kernel.debug');
  1000. if (class_exists(Stopwatch::class)) {
  1001. $container->register('debug.stopwatch', Stopwatch::class)
  1002. ->addArgument(true)
  1003. ->setPublic($debug)
  1004. ->addTag('kernel.reset', ['method' => 'reset']);
  1005. $container->setAlias(Stopwatch::class, new Alias('debug.stopwatch', false));
  1006. }
  1007. if ($debug && !$container->hasParameter('debug.container.dump')) {
  1008. $container->setParameter('debug.container.dump', '%kernel.build_dir%/%kernel.container_class%.xml');
  1009. }
  1010. if ($debug && class_exists(Stopwatch::class)) {
  1011. $loader->load('debug.php');
  1012. }
  1013. $definition = $container->findDefinition('debug.error_handler_configurator');
  1014. if (false === $config['log']) {
  1015. $definition->replaceArgument(0, null);
  1016. } elseif (true !== $config['log']) {
  1017. $definition->replaceArgument(1, $config['log']);
  1018. }
  1019. if (!$config['throw']) {
  1020. $container->setParameter('debug.error_handler.throw_at', 0);
  1021. }
  1022. if ($debug && class_exists(DebugProcessor::class)) {
  1023. $definition = new Definition(DebugProcessor::class);
  1024. $definition->addArgument(new Reference('.virtual_request_stack'));
  1025. $definition->addTag('kernel.reset', ['method' => 'reset']);
  1026. $container->setDefinition('debug.log_processor', $definition);
  1027. $container->register('debug.debug_logger_configurator', DebugLoggerConfigurator::class)
  1028. ->setArguments([new Reference('debug.log_processor'), '%kernel.runtime_mode.web%']);
  1029. }
  1030. }
  1031. private function registerRouterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, array $enabledLocales = []): void
  1032. {
  1033. if (!$this->readConfigEnabled('router', $container, $config)) {
  1034. $container->removeDefinition('console.command.router_debug');
  1035. $container->removeDefinition('console.command.router_match');
  1036. $container->removeDefinition('messenger.middleware.router_context');
  1037. return;
  1038. }
  1039. if (!class_exists(RouterContextMiddleware::class)) {
  1040. $container->removeDefinition('messenger.middleware.router_context');
  1041. }
  1042. $loader->load('routing.php');
  1043. if ($config['utf8']) {
  1044. $container->getDefinition('routing.loader')->replaceArgument(1, ['utf8' => true]);
  1045. }
  1046. if ($enabledLocales) {
  1047. $enabledLocales = implode('|', array_map('preg_quote', $enabledLocales));
  1048. $container->getDefinition('routing.loader')->replaceArgument(2, ['_locale' => $enabledLocales]);
  1049. }
  1050. if (!ContainerBuilder::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/framework-bundle', 'symfony/routing'])) {
  1051. $container->removeDefinition('router.expression_language_provider');
  1052. }
  1053. $container->setParameter('router.resource', $config['resource']);
  1054. $container->setParameter('router.cache_dir', $config['cache_dir']);
  1055. $router = $container->findDefinition('router.default');
  1056. $argument = $router->getArgument(2);
  1057. $argument['strict_requirements'] = $config['strict_requirements'];
  1058. if (isset($config['type'])) {
  1059. $argument['resource_type'] = $config['type'];
  1060. }
  1061. $router->replaceArgument(2, $argument);
  1062. $container->setParameter('request_listener.http_port', $config['http_port']);
  1063. $container->setParameter('request_listener.https_port', $config['https_port']);
  1064. if (null !== $config['default_uri']) {
  1065. $container->getDefinition('router.request_context')
  1066. ->replaceArgument(0, $config['default_uri']);
  1067. }
  1068. if ($this->isInitializedConfigEnabled('annotations') && (new \ReflectionClass(AttributeClassLoader::class))->hasProperty('reader')) {
  1069. $container->getDefinition('routing.loader.attribute')->setArguments([
  1070. new Reference('annotation_reader'),
  1071. '%kernel.environment%',
  1072. ]);
  1073. }
  1074. }
  1075. private function registerSessionConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  1076. {
  1077. $loader->load('session.php');
  1078. // session storage
  1079. $container->setAlias('session.storage.factory', $config['storage_factory_id']);
  1080. $options = ['cache_limiter' => '0'];
  1081. foreach (['name', 'cookie_lifetime', 'cookie_path', 'cookie_domain', 'cookie_secure', 'cookie_httponly', 'cookie_samesite', 'use_cookies', 'gc_maxlifetime', 'gc_probability', 'gc_divisor', 'sid_length', 'sid_bits_per_character'] as $key) {
  1082. if (isset($config[$key])) {
  1083. $options[$key] = $config[$key];
  1084. }
  1085. }
  1086. if ('auto' === ($options['cookie_secure'] ?? null)) {
  1087. $container->getDefinition('session.storage.factory.native')->replaceArgument(3, true);
  1088. $container->getDefinition('session.storage.factory.php_bridge')->replaceArgument(2, true);
  1089. }
  1090. $container->setParameter('session.storage.options', $options);
  1091. // session handler (the internal callback registered with PHP session management)
  1092. if (null === $config['handler_id']) {
  1093. $config['save_path'] = null;
  1094. $container->setAlias('session.handler', 'session.handler.native');
  1095. } else {
  1096. $container->resolveEnvPlaceholders($config['handler_id'], null, $usedEnvs);
  1097. if ($usedEnvs || preg_match('#^[a-z]++://#', $config['handler_id'])) {
  1098. $id = '.cache_connection.'.ContainerBuilder::hash($config['handler_id']);
  1099. $container->getDefinition('session.abstract_handler')
  1100. ->replaceArgument(0, $container->hasDefinition($id) ? new Reference($id) : $config['handler_id']);
  1101. $container->setAlias('session.handler', 'session.abstract_handler');
  1102. } else {
  1103. $container->setAlias('session.handler', $config['handler_id']);
  1104. }
  1105. }
  1106. $container->setParameter('session.save_path', $config['save_path']);
  1107. $container->setParameter('session.metadata.update_threshold', $config['metadata_update_threshold']);
  1108. }
  1109. private function registerRequestConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  1110. {
  1111. if ($config['formats']) {
  1112. $loader->load('request.php');
  1113. $listener = $container->getDefinition('request.add_request_formats_listener');
  1114. $listener->replaceArgument(0, $config['formats']);
  1115. }
  1116. }
  1117. private function registerAssetsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  1118. {
  1119. $loader->load('assets.php');
  1120. if ($config['version_strategy']) {
  1121. $defaultVersion = new Reference($config['version_strategy']);
  1122. } else {
  1123. $defaultVersion = $this->createVersion($container, $config['version'], $config['version_format'], $config['json_manifest_path'], '_default', $config['strict_mode']);
  1124. }
  1125. $defaultPackage = $this->createPackageDefinition($config['base_path'], $config['base_urls'], $defaultVersion);
  1126. $container->setDefinition('assets._default_package', $defaultPackage);
  1127. foreach ($config['packages'] as $name => $package) {
  1128. if (null !== $package['version_strategy']) {
  1129. $version = new Reference($package['version_strategy']);
  1130. } elseif (!\array_key_exists('version', $package) && null === $package['json_manifest_path']) {
  1131. // if neither version nor json_manifest_path are specified, use the default
  1132. $version = $defaultVersion;
  1133. } else {
  1134. // let format fallback to main version_format
  1135. $format = $package['version_format'] ?: $config['version_format'];
  1136. $version = $package['version'] ?? null;
  1137. $version = $this->createVersion($container, $version, $format, $package['json_manifest_path'], $name, $package['strict_mode']);
  1138. }
  1139. $packageDefinition = $this->createPackageDefinition($package['base_path'], $package['base_urls'], $version)
  1140. ->addTag('assets.package', ['package' => $name]);
  1141. $container->setDefinition('assets._package_'.$name, $packageDefinition);
  1142. $container->registerAliasForArgument('assets._package_'.$name, PackageInterface::class, $name.'.package');
  1143. }
  1144. }
  1145. private function registerAssetMapperConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $assetEnabled, bool $httpClientEnabled): void
  1146. {
  1147. $loader->load('asset_mapper.php');
  1148. if (!$assetEnabled) {
  1149. $container->removeDefinition('asset_mapper.asset_package');
  1150. }
  1151. if (!$httpClientEnabled) {
  1152. $container->register('asset_mapper.http_client', HttpClientInterface::class)
  1153. ->addTag('container.error')
  1154. ->addError('You cannot use the AssetMapper integration since the HttpClient component is not enabled. Try enabling the "framework.http_client" config option.');
  1155. }
  1156. $paths = $config['paths'];
  1157. foreach ($container->getParameter('kernel.bundles_metadata') as $name => $bundle) {
  1158. if ($container->fileExists($dir = $bundle['path'].'/Resources/public') || $container->fileExists($dir = $bundle['path'].'/public')) {
  1159. $paths[$dir] = sprintf('bundles/%s', preg_replace('/bundle$/', '', strtolower($name)));
  1160. }
  1161. }
  1162. $excludedPathPatterns = [];
  1163. foreach ($config['excluded_patterns'] as $path) {
  1164. $excludedPathPatterns[] = Glob::toRegex($path, true, false);
  1165. }
  1166. $container->getDefinition('asset_mapper.repository')
  1167. ->setArgument(0, $paths)
  1168. ->setArgument(2, $excludedPathPatterns)
  1169. ->setArgument(3, $config['exclude_dotfiles']);
  1170. $container->getDefinition('asset_mapper.public_assets_path_resolver')
  1171. ->setArgument(0, $config['public_prefix']);
  1172. $publicDirectory = $this->getPublicDirectory($container);
  1173. $publicAssetsDirectory = rtrim($publicDirectory.'/'.ltrim($config['public_prefix'], '/'), '/');
  1174. $container->getDefinition('asset_mapper.local_public_assets_filesystem')
  1175. ->setArgument(0, $publicDirectory)
  1176. ;
  1177. $container->getDefinition('asset_mapper.compiled_asset_mapper_config_reader')
  1178. ->setArgument(0, $publicAssetsDirectory);
  1179. if (!$config['server']) {
  1180. $container->removeDefinition('asset_mapper.dev_server_subscriber');
  1181. } else {
  1182. $container->getDefinition('asset_mapper.dev_server_subscriber')
  1183. ->setArgument(1, $config['public_prefix'])
  1184. ->setArgument(2, $config['extensions']);
  1185. }
  1186. $container->getDefinition('asset_mapper.compiler.css_asset_url_compiler')
  1187. ->setArgument(0, $config['missing_import_mode']);
  1188. $container->getDefinition('asset_mapper.compiler.javascript_import_path_compiler')
  1189. ->setArgument(1, $config['missing_import_mode']);
  1190. $container
  1191. ->getDefinition('asset_mapper.importmap.remote_package_storage')
  1192. ->replaceArgument(0, $config['vendor_dir'])
  1193. ;
  1194. $container
  1195. ->getDefinition('asset_mapper.mapped_asset_factory')
  1196. ->replaceArgument(2, $config['vendor_dir'])
  1197. ;
  1198. $container
  1199. ->getDefinition('asset_mapper.importmap.config_reader')
  1200. ->replaceArgument(0, $config['importmap_path'])
  1201. ;
  1202. $container
  1203. ->getDefinition('asset_mapper.importmap.renderer')
  1204. ->replaceArgument(3, $config['importmap_polyfill'])
  1205. ->replaceArgument(4, $config['importmap_script_attributes'])
  1206. ;
  1207. }
  1208. /**
  1209. * Returns a definition for an asset package.
  1210. */
  1211. private function createPackageDefinition(?string $basePath, array $baseUrls, Reference $version): Definition
  1212. {
  1213. if ($basePath && $baseUrls) {
  1214. throw new \LogicException('An asset package cannot have base URLs and base paths.');
  1215. }
  1216. $package = new ChildDefinition($baseUrls ? 'assets.url_package' : 'assets.path_package');
  1217. $package
  1218. ->replaceArgument(0, $baseUrls ?: $basePath)
  1219. ->replaceArgument(1, $version)
  1220. ;
  1221. return $package;
  1222. }
  1223. private function createVersion(ContainerBuilder $container, ?string $version, ?string $format, ?string $jsonManifestPath, string $name, bool $strictMode): Reference
  1224. {
  1225. // Configuration prevents $version and $jsonManifestPath from being set
  1226. if (null !== $version) {
  1227. $def = new ChildDefinition('assets.static_version_strategy');
  1228. $def
  1229. ->replaceArgument(0, $version)
  1230. ->replaceArgument(1, $format)
  1231. ;
  1232. $container->setDefinition('assets._version_'.$name, $def);
  1233. return new Reference('assets._version_'.$name);
  1234. }
  1235. if (null !== $jsonManifestPath) {
  1236. $def = new ChildDefinition('assets.json_manifest_version_strategy');
  1237. $def->replaceArgument(0, $jsonManifestPath);
  1238. $def->replaceArgument(2, $strictMode);
  1239. $container->setDefinition('assets._version_'.$name, $def);
  1240. return new Reference('assets._version_'.$name);
  1241. }
  1242. return new Reference('assets.empty_version_strategy');
  1243. }
  1244. private function registerTranslatorConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader, string $defaultLocale, array $enabledLocales): void
  1245. {
  1246. if (!$this->readConfigEnabled('translator', $container, $config)) {
  1247. $container->removeDefinition('console.command.translation_debug');
  1248. $container->removeDefinition('console.command.translation_extract');
  1249. $container->removeDefinition('console.command.translation_pull');
  1250. $container->removeDefinition('console.command.translation_push');
  1251. return;
  1252. }
  1253. $loader->load('translation.php');
  1254. if (!ContainerBuilder::willBeAvailable('symfony/translation', LocaleSwitcher::class, ['symfony/framework-bundle'])) {
  1255. $container->removeDefinition('translation.locale_switcher');
  1256. }
  1257. // don't use ContainerBuilder::willBeAvailable() as these are not needed in production
  1258. if (interface_exists(Parser::class) && class_exists(PhpAstExtractor::class)) {
  1259. $container->removeDefinition('translation.extractor.php');
  1260. } else {
  1261. $container->removeDefinition('translation.extractor.php_ast');
  1262. }
  1263. $loader->load('translation_providers.php');
  1264. // Use the "real" translator instead of the identity default
  1265. $container->setAlias('translator', 'translator.default')->setPublic(true);
  1266. $container->setAlias('translator.formatter', new Alias($config['formatter'], false));
  1267. $translator = $container->findDefinition('translator.default');
  1268. $translator->addMethodCall('setFallbackLocales', [$config['fallbacks'] ?: [$defaultLocale]]);
  1269. $defaultOptions = $translator->getArgument(4);
  1270. $defaultOptions['cache_dir'] = $config['cache_dir'];
  1271. $translator->setArgument(4, $defaultOptions);
  1272. $translator->setArgument(5, $enabledLocales);
  1273. $container->setParameter('translator.logging', $config['logging']);
  1274. $container->setParameter('translator.default_path', $config['default_path']);
  1275. // Discover translation directories
  1276. $dirs = [];
  1277. $transPaths = [];
  1278. $nonExistingDirs = [];
  1279. if (ContainerBuilder::willBeAvailable('symfony/validator', Validation::class, ['symfony/framework-bundle', 'symfony/translation'])) {
  1280. $r = new \ReflectionClass(Validation::class);
  1281. $dirs[] = $transPaths[] = \dirname($r->getFileName()).'/Resources/translations';
  1282. }
  1283. if (ContainerBuilder::willBeAvailable('symfony/form', Form::class, ['symfony/framework-bundle', 'symfony/translation'])) {
  1284. $r = new \ReflectionClass(Form::class);
  1285. $dirs[] = $transPaths[] = \dirname($r->getFileName()).'/Resources/translations';
  1286. }
  1287. if (ContainerBuilder::willBeAvailable('symfony/security-core', AuthenticationException::class, ['symfony/framework-bundle', 'symfony/translation'])) {
  1288. $r = new \ReflectionClass(AuthenticationException::class);
  1289. $dirs[] = $transPaths[] = \dirname($r->getFileName(), 2).'/Resources/translations';
  1290. }
  1291. $defaultDir = $container->getParameterBag()->resolveValue($config['default_path']);
  1292. foreach ($container->getParameter('kernel.bundles_metadata') as $name => $bundle) {
  1293. if ($container->fileExists($dir = $bundle['path'].'/Resources/translations') || $container->fileExists($dir = $bundle['path'].'/translations')) {
  1294. $dirs[] = $transPaths[] = $dir;
  1295. } else {
  1296. $nonExistingDirs[] = $dir;
  1297. }
  1298. }
  1299. foreach ($config['paths'] as $dir) {
  1300. if ($container->fileExists($dir)) {
  1301. $dirs[] = $transPaths[] = $dir;
  1302. } else {
  1303. throw new \UnexpectedValueException(sprintf('"%s" defined in translator.paths does not exist or is not a directory.', $dir));
  1304. }
  1305. }
  1306. if ($container->hasDefinition('console.command.translation_debug')) {
  1307. $container->getDefinition('console.command.translation_debug')->replaceArgument(5, $transPaths);
  1308. }
  1309. if ($container->hasDefinition('console.command.translation_extract')) {
  1310. $container->getDefinition('console.command.translation_extract')->replaceArgument(6, $transPaths);
  1311. }
  1312. if (null === $defaultDir) {
  1313. // allow null
  1314. } elseif ($container->fileExists($defaultDir)) {
  1315. $dirs[] = $defaultDir;
  1316. } else {
  1317. $nonExistingDirs[] = $defaultDir;
  1318. }
  1319. // Register translation resources
  1320. if ($dirs) {
  1321. $files = [];
  1322. foreach ($dirs as $dir) {
  1323. $finder = Finder::create()
  1324. ->followLinks()
  1325. ->files()
  1326. ->filter(fn (\SplFileInfo $file) => 2 <= substr_count($file->getBasename(), '.') && preg_match('/\.\w+$/', $file->getBasename()))
  1327. ->in($dir)
  1328. ->sortByName()
  1329. ;
  1330. foreach ($finder as $file) {
  1331. $fileNameParts = explode('.', basename($file));
  1332. $locale = $fileNameParts[\count($fileNameParts) - 2];
  1333. if (!isset($files[$locale])) {
  1334. $files[$locale] = [];
  1335. }
  1336. $files[$locale][] = (string) $file;
  1337. }
  1338. }
  1339. $projectDir = $container->getParameter('kernel.project_dir');
  1340. $options = array_merge(
  1341. $translator->getArgument(4),
  1342. [
  1343. 'resource_files' => $files,
  1344. 'scanned_directories' => $scannedDirectories = array_merge($dirs, $nonExistingDirs),
  1345. 'cache_vary' => [
  1346. 'scanned_directories' => array_map(fn ($dir) => str_starts_with($dir, $projectDir.'/') ? substr($dir, 1 + \strlen($projectDir)) : $dir, $scannedDirectories),
  1347. ],
  1348. ]
  1349. );
  1350. $translator->replaceArgument(4, $options);
  1351. }
  1352. if ($config['pseudo_localization']['enabled']) {
  1353. $options = $config['pseudo_localization'];
  1354. unset($options['enabled']);
  1355. $container
  1356. ->register('translator.pseudo', PseudoLocalizationTranslator::class)
  1357. ->setDecoratedService('translator', null, -1) // Lower priority than "translator.data_collector"
  1358. ->setArguments([
  1359. new Reference('translator.pseudo.inner'),
  1360. $options,
  1361. ]);
  1362. }
  1363. $classToServices = [
  1364. TranslationBridge\Crowdin\CrowdinProviderFactory::class => 'translation.provider_factory.crowdin',
  1365. TranslationBridge\Loco\LocoProviderFactory::class => 'translation.provider_factory.loco',
  1366. TranslationBridge\Lokalise\LokaliseProviderFactory::class => 'translation.provider_factory.lokalise',
  1367. TranslationBridge\Phrase\PhraseProviderFactory::class => 'translation.provider_factory.phrase',
  1368. ];
  1369. $parentPackages = ['symfony/framework-bundle', 'symfony/translation', 'symfony/http-client'];
  1370. foreach ($classToServices as $class => $service) {
  1371. $package = substr($service, \strlen('translation.provider_factory.'));
  1372. if (!$container->hasDefinition('http_client') || !ContainerBuilder::willBeAvailable(sprintf('symfony/%s-translation-provider', $package), $class, $parentPackages)) {
  1373. $container->removeDefinition($service);
  1374. }
  1375. }
  1376. if (!$config['providers']) {
  1377. return;
  1378. }
  1379. $locales = $enabledLocales;
  1380. foreach ($config['providers'] as $provider) {
  1381. if ($provider['locales']) {
  1382. $locales += $provider['locales'];
  1383. }
  1384. }
  1385. $locales = array_unique($locales);
  1386. $container->getDefinition('console.command.translation_pull')
  1387. ->replaceArgument(4, array_merge($transPaths, [$config['default_path']]))
  1388. ->replaceArgument(5, $locales)
  1389. ;
  1390. $container->getDefinition('console.command.translation_push')
  1391. ->replaceArgument(2, array_merge($transPaths, [$config['default_path']]))
  1392. ->replaceArgument(3, $locales)
  1393. ;
  1394. $container->getDefinition('translation.provider_collection_factory')
  1395. ->replaceArgument(1, $locales)
  1396. ;
  1397. $container->getDefinition('translation.provider_collection')->setArgument(0, $config['providers']);
  1398. }
  1399. private function registerValidationConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $propertyInfoEnabled): void
  1400. {
  1401. if (!$this->readConfigEnabled('validation', $container, $config)) {
  1402. $container->removeDefinition('console.command.validator_debug');
  1403. return;
  1404. }
  1405. if (!class_exists(Validation::class)) {
  1406. throw new LogicException('Validation support cannot be enabled as the Validator component is not installed. Try running "composer require symfony/validator".');
  1407. }
  1408. if (!isset($config['email_validation_mode'])) {
  1409. $config['email_validation_mode'] = 'loose';
  1410. }
  1411. $loader->load('validator.php');
  1412. $validatorBuilder = $container->getDefinition('validator.builder');
  1413. $container->setParameter('validator.translation_domain', $config['translation_domain']);
  1414. $files = ['xml' => [], 'yml' => []];
  1415. $this->registerValidatorMapping($container, $config, $files);
  1416. if (!empty($files['xml'])) {
  1417. $validatorBuilder->addMethodCall('addXmlMappings', [$files['xml']]);
  1418. }
  1419. if (!empty($files['yml'])) {
  1420. $validatorBuilder->addMethodCall('addYamlMappings', [$files['yml']]);
  1421. }
  1422. $definition = $container->findDefinition('validator.email');
  1423. $definition->replaceArgument(0, $config['email_validation_mode']);
  1424. if (\array_key_exists('enable_attributes', $config) && $config['enable_attributes']) {
  1425. $validatorBuilder->addMethodCall('enableAttributeMapping', [true]);
  1426. if ($this->isInitializedConfigEnabled('annotations') && method_exists(ValidatorBuilder::class, 'setDoctrineAnnotationReader')) {
  1427. $validatorBuilder->addMethodCall('setDoctrineAnnotationReader', [new Reference('annotation_reader')]);
  1428. }
  1429. }
  1430. if (\array_key_exists('static_method', $config) && $config['static_method']) {
  1431. foreach ($config['static_method'] as $methodName) {
  1432. $validatorBuilder->addMethodCall('addMethodMapping', [$methodName]);
  1433. }
  1434. }
  1435. if (!$container->getParameter('kernel.debug')) {
  1436. $validatorBuilder->addMethodCall('setMappingCache', [new Reference('validator.mapping.cache.adapter')]);
  1437. }
  1438. $container->setParameter('validator.auto_mapping', $config['auto_mapping']);
  1439. if (!$propertyInfoEnabled || !class_exists(PropertyInfoLoader::class)) {
  1440. $container->removeDefinition('validator.property_info_loader');
  1441. }
  1442. $container
  1443. ->getDefinition('validator.not_compromised_password')
  1444. ->setArgument(2, $config['not_compromised_password']['enabled'])
  1445. ->setArgument(3, $config['not_compromised_password']['endpoint'])
  1446. ;
  1447. if (!class_exists(ExpressionLanguage::class)) {
  1448. $container->removeDefinition('validator.expression_language');
  1449. $container->removeDefinition('validator.expression_language_provider');
  1450. } elseif (!class_exists(ExpressionLanguageProvider::class)) {
  1451. $container->removeDefinition('validator.expression_language_provider');
  1452. }
  1453. }
  1454. private function registerValidatorMapping(ContainerBuilder $container, array $config, array &$files): void
  1455. {
  1456. $fileRecorder = function ($extension, $path) use (&$files) {
  1457. $files['yaml' === $extension ? 'yml' : $extension][] = $path;
  1458. };
  1459. if (ContainerBuilder::willBeAvailable('symfony/form', Form::class, ['symfony/framework-bundle', 'symfony/validator'])) {
  1460. $reflClass = new \ReflectionClass(Form::class);
  1461. $fileRecorder('xml', \dirname($reflClass->getFileName()).'/Resources/config/validation.xml');
  1462. }
  1463. foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) {
  1464. $configDir = is_dir($bundle['path'].'/Resources/config') ? $bundle['path'].'/Resources/config' : $bundle['path'].'/config';
  1465. if (
  1466. $container->fileExists($file = $configDir.'/validation.yaml', false)
  1467. || $container->fileExists($file = $configDir.'/validation.yml', false)
  1468. ) {
  1469. $fileRecorder('yml', $file);
  1470. }
  1471. if ($container->fileExists($file = $configDir.'/validation.xml', false)) {
  1472. $fileRecorder('xml', $file);
  1473. }
  1474. if ($container->fileExists($dir = $configDir.'/validation', '/^$/')) {
  1475. $this->registerMappingFilesFromDir($dir, $fileRecorder);
  1476. }
  1477. }
  1478. $projectDir = $container->getParameter('kernel.project_dir');
  1479. if ($container->fileExists($dir = $projectDir.'/config/validator', '/^$/')) {
  1480. $this->registerMappingFilesFromDir($dir, $fileRecorder);
  1481. }
  1482. $this->registerMappingFilesFromConfig($container, $config, $fileRecorder);
  1483. }
  1484. private function registerMappingFilesFromDir(string $dir, callable $fileRecorder): void
  1485. {
  1486. foreach (Finder::create()->followLinks()->files()->in($dir)->name('/\.(xml|ya?ml)$/')->sortByName() as $file) {
  1487. $fileRecorder($file->getExtension(), $file->getRealPath());
  1488. }
  1489. }
  1490. private function registerMappingFilesFromConfig(ContainerBuilder $container, array $config, callable $fileRecorder): void
  1491. {
  1492. foreach ($config['mapping']['paths'] as $path) {
  1493. if (is_dir($path)) {
  1494. $this->registerMappingFilesFromDir($path, $fileRecorder);
  1495. $container->addResource(new DirectoryResource($path, '/^$/'));
  1496. } elseif ($container->fileExists($path, false)) {
  1497. if (!preg_match('/\.(xml|ya?ml)$/', $path, $matches)) {
  1498. throw new \RuntimeException(sprintf('Unsupported mapping type in "%s", supported types are XML & Yaml.', $path));
  1499. }
  1500. $fileRecorder($matches[1], $path);
  1501. } else {
  1502. throw new \RuntimeException(sprintf('Could not open file or directory "%s".', $path));
  1503. }
  1504. }
  1505. }
  1506. private function registerAnnotationsConfiguration(array $config, ContainerBuilder $container, LoaderInterface $loader): void
  1507. {
  1508. if (!$this->isInitializedConfigEnabled('annotations')) {
  1509. return;
  1510. }
  1511. if (!class_exists(\Doctrine\Common\Annotations\Annotation::class)) {
  1512. throw new LogicException('Annotations cannot be enabled as the Doctrine Annotation library is not installed. Try running "composer require doctrine/annotations".');
  1513. }
  1514. trigger_deprecation('symfony/framework-bundle', '6.4', 'Enabling the integration of Doctrine annotations is deprecated. Set the "framework.annotations.enabled" config option to false.');
  1515. $loader->load('annotations.php');
  1516. if ('none' === $config['cache']) {
  1517. $container->removeDefinition('annotations.cached_reader');
  1518. return;
  1519. }
  1520. if ('php_array' === $config['cache']) {
  1521. $cacheService = 'annotations.cache_adapter';
  1522. // Enable warmer only if PHP array is used for cache
  1523. $definition = $container->findDefinition('annotations.cache_warmer');
  1524. $definition->addTag('kernel.cache_warmer');
  1525. } else {
  1526. $cacheService = 'annotations.filesystem_cache_adapter';
  1527. $cacheDir = $container->getParameterBag()->resolveValue($config['file_cache_dir']);
  1528. if (!is_dir($cacheDir) && false === @mkdir($cacheDir, 0777, true) && !is_dir($cacheDir)) {
  1529. throw new \RuntimeException(sprintf('Could not create cache directory "%s".', $cacheDir));
  1530. }
  1531. $container
  1532. ->getDefinition('annotations.filesystem_cache_adapter')
  1533. ->replaceArgument(2, $cacheDir)
  1534. ;
  1535. }
  1536. $container
  1537. ->getDefinition('annotations.cached_reader')
  1538. ->replaceArgument(2, $config['debug'])
  1539. // reference the cache provider without using it until AddAnnotationsCachedReaderPass runs
  1540. ->addArgument(new ServiceClosureArgument(new Reference($cacheService)))
  1541. ;
  1542. $container->setAlias('annotation_reader', 'annotations.cached_reader');
  1543. $container->setAlias(Reader::class, new Alias('annotations.cached_reader', false));
  1544. $container->removeDefinition('annotations.psr_cached_reader');
  1545. }
  1546. private function registerPropertyAccessConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  1547. {
  1548. if (!$this->readConfigEnabled('property_access', $container, $config)) {
  1549. return;
  1550. }
  1551. $loader->load('property_access.php');
  1552. $magicMethods = PropertyAccessor::DISALLOW_MAGIC_METHODS;
  1553. $magicMethods |= $config['magic_call'] ? PropertyAccessor::MAGIC_CALL : 0;
  1554. $magicMethods |= $config['magic_get'] ? PropertyAccessor::MAGIC_GET : 0;
  1555. $magicMethods |= $config['magic_set'] ? PropertyAccessor::MAGIC_SET : 0;
  1556. $throw = PropertyAccessor::DO_NOT_THROW;
  1557. $throw |= $config['throw_exception_on_invalid_index'] ? PropertyAccessor::THROW_ON_INVALID_INDEX : 0;
  1558. $throw |= $config['throw_exception_on_invalid_property_path'] ? PropertyAccessor::THROW_ON_INVALID_PROPERTY_PATH : 0;
  1559. $container
  1560. ->getDefinition('property_accessor')
  1561. ->replaceArgument(0, $magicMethods)
  1562. ->replaceArgument(1, $throw)
  1563. ->replaceArgument(3, new Reference(PropertyReadInfoExtractorInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE))
  1564. ->replaceArgument(4, new Reference(PropertyWriteInfoExtractorInterface::class, ContainerInterface::NULL_ON_INVALID_REFERENCE))
  1565. ;
  1566. }
  1567. private function registerSecretsConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  1568. {
  1569. if (!$this->readConfigEnabled('secrets', $container, $config)) {
  1570. $container->removeDefinition('console.command.secrets_set');
  1571. $container->removeDefinition('console.command.secrets_list');
  1572. $container->removeDefinition('console.command.secrets_remove');
  1573. $container->removeDefinition('console.command.secrets_generate_key');
  1574. $container->removeDefinition('console.command.secrets_decrypt_to_local');
  1575. $container->removeDefinition('console.command.secrets_encrypt_from_local');
  1576. return;
  1577. }
  1578. $loader->load('secrets.php');
  1579. $container->getDefinition('secrets.vault')->replaceArgument(0, $config['vault_directory']);
  1580. if ($config['local_dotenv_file']) {
  1581. $container->getDefinition('secrets.local_vault')->replaceArgument(0, $config['local_dotenv_file']);
  1582. } else {
  1583. $container->removeDefinition('secrets.local_vault');
  1584. }
  1585. if ($config['decryption_env_var']) {
  1586. if (!preg_match('/^(?:[-.\w\\\\]*+:)*+\w++$/', $config['decryption_env_var'])) {
  1587. throw new InvalidArgumentException(sprintf('Invalid value "%s" set as "decryption_env_var": only "word" characters are allowed.', $config['decryption_env_var']));
  1588. }
  1589. if (ContainerBuilder::willBeAvailable('symfony/string', LazyString::class, ['symfony/framework-bundle'])) {
  1590. $container->getDefinition('secrets.decryption_key')->replaceArgument(1, $config['decryption_env_var']);
  1591. } else {
  1592. $container->getDefinition('secrets.vault')->replaceArgument(1, "%env({$config['decryption_env_var']})%");
  1593. $container->removeDefinition('secrets.decryption_key');
  1594. }
  1595. } else {
  1596. $container->getDefinition('secrets.vault')->replaceArgument(1, null);
  1597. $container->removeDefinition('secrets.decryption_key');
  1598. }
  1599. }
  1600. private function registerSecurityCsrfConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  1601. {
  1602. if (!$this->readConfigEnabled('csrf_protection', $container, $config)) {
  1603. return;
  1604. }
  1605. if (!class_exists(\Symfony\Component\Security\Csrf\CsrfToken::class)) {
  1606. throw new LogicException('CSRF support cannot be enabled as the Security CSRF component is not installed. Try running "composer require symfony/security-csrf".');
  1607. }
  1608. if (!$this->isInitializedConfigEnabled('session')) {
  1609. throw new \LogicException('CSRF protection needs sessions to be enabled.');
  1610. }
  1611. // Enable services for CSRF protection (even without forms)
  1612. $loader->load('security_csrf.php');
  1613. if (!class_exists(CsrfExtension::class)) {
  1614. $container->removeDefinition('twig.extension.security_csrf');
  1615. }
  1616. }
  1617. private function registerSerializerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  1618. {
  1619. $loader->load('serializer.php');
  1620. $chainLoader = $container->getDefinition('serializer.mapping.chain_loader');
  1621. if (!$this->isInitializedConfigEnabled('property_access')) {
  1622. $container->removeAlias('serializer.property_accessor');
  1623. $container->removeDefinition('serializer.normalizer.object');
  1624. }
  1625. if (!class_exists(Yaml::class)) {
  1626. $container->removeDefinition('serializer.encoder.yaml');
  1627. }
  1628. if (!$this->isInitializedConfigEnabled('property_access')) {
  1629. $container->removeDefinition('serializer.denormalizer.unwrapping');
  1630. }
  1631. if (!class_exists(Headers::class)) {
  1632. $container->removeDefinition('serializer.normalizer.mime_message');
  1633. }
  1634. if ($container->getParameter('kernel.debug')) {
  1635. $container->removeDefinition('serializer.mapping.cache_class_metadata_factory');
  1636. }
  1637. if (!$this->readConfigEnabled('translator', $container, $config)) {
  1638. $container->removeDefinition('serializer.normalizer.translatable');
  1639. }
  1640. $serializerLoaders = [];
  1641. if (isset($config['enable_attributes']) && $config['enable_attributes']) {
  1642. $annotationLoader = new Definition(
  1643. AttributeLoader::class,
  1644. [new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE)]
  1645. );
  1646. $serializerLoaders[] = $annotationLoader;
  1647. }
  1648. $fileRecorder = function ($extension, $path) use (&$serializerLoaders) {
  1649. $definition = new Definition(\in_array($extension, ['yaml', 'yml']) ? YamlFileLoader::class : XmlFileLoader::class, [$path]);
  1650. $serializerLoaders[] = $definition;
  1651. };
  1652. foreach ($container->getParameter('kernel.bundles_metadata') as $bundle) {
  1653. $configDir = is_dir($bundle['path'].'/Resources/config') ? $bundle['path'].'/Resources/config' : $bundle['path'].'/config';
  1654. if ($container->fileExists($file = $configDir.'/serialization.xml', false)) {
  1655. $fileRecorder('xml', $file);
  1656. }
  1657. if (
  1658. $container->fileExists($file = $configDir.'/serialization.yaml', false)
  1659. || $container->fileExists($file = $configDir.'/serialization.yml', false)
  1660. ) {
  1661. $fileRecorder('yml', $file);
  1662. }
  1663. if ($container->fileExists($dir = $configDir.'/serialization', '/^$/')) {
  1664. $this->registerMappingFilesFromDir($dir, $fileRecorder);
  1665. }
  1666. }
  1667. $projectDir = $container->getParameter('kernel.project_dir');
  1668. if ($container->fileExists($dir = $projectDir.'/config/serializer', '/^$/')) {
  1669. $this->registerMappingFilesFromDir($dir, $fileRecorder);
  1670. }
  1671. $this->registerMappingFilesFromConfig($container, $config, $fileRecorder);
  1672. $chainLoader->replaceArgument(0, $serializerLoaders);
  1673. $container->getDefinition('serializer.mapping.cache_warmer')->replaceArgument(0, $serializerLoaders);
  1674. if (isset($config['name_converter']) && $config['name_converter']) {
  1675. $container->getDefinition('serializer.name_converter.metadata_aware')->setArgument(1, new Reference($config['name_converter']));
  1676. }
  1677. $defaultContext = $config['default_context'] ?? [];
  1678. if ($defaultContext) {
  1679. $container->setParameter('serializer.default_context', $defaultContext);
  1680. }
  1681. if ($container->hasDefinition('serializer.normalizer.object')) {
  1682. $arguments = $container->getDefinition('serializer.normalizer.object')->getArguments();
  1683. $context = $arguments[6] ?? $defaultContext;
  1684. if (isset($config['circular_reference_handler']) && $config['circular_reference_handler']) {
  1685. $context += ['circular_reference_handler' => new Reference($config['circular_reference_handler'])];
  1686. $container->getDefinition('serializer.normalizer.object')->setArgument(5, null);
  1687. }
  1688. if ($config['max_depth_handler'] ?? false) {
  1689. $context += ['max_depth_handler' => new Reference($config['max_depth_handler'])];
  1690. }
  1691. $container->getDefinition('serializer.normalizer.object')->setArgument(6, $context);
  1692. }
  1693. $container->getDefinition('serializer.normalizer.property')->setArgument(5, $defaultContext);
  1694. }
  1695. private function registerPropertyInfoConfiguration(ContainerBuilder $container, PhpFileLoader $loader): void
  1696. {
  1697. if (!interface_exists(PropertyInfoExtractorInterface::class)) {
  1698. throw new LogicException('PropertyInfo support cannot be enabled as the PropertyInfo component is not installed. Try running "composer require symfony/property-info".');
  1699. }
  1700. $loader->load('property_info.php');
  1701. if (
  1702. ContainerBuilder::willBeAvailable('phpstan/phpdoc-parser', PhpDocParser::class, ['symfony/framework-bundle', 'symfony/property-info'])
  1703. && ContainerBuilder::willBeAvailable('phpdocumentor/type-resolver', ContextFactory::class, ['symfony/framework-bundle', 'symfony/property-info'])
  1704. ) {
  1705. $definition = $container->register('property_info.phpstan_extractor', PhpStanExtractor::class);
  1706. $definition->addTag('property_info.type_extractor', ['priority' => -1000]);
  1707. }
  1708. if (ContainerBuilder::willBeAvailable('phpdocumentor/reflection-docblock', DocBlockFactoryInterface::class, ['symfony/framework-bundle', 'symfony/property-info'], true)) {
  1709. $definition = $container->register('property_info.php_doc_extractor', PhpDocExtractor::class);
  1710. $definition->addTag('property_info.description_extractor', ['priority' => -1000]);
  1711. $definition->addTag('property_info.type_extractor', ['priority' => -1001]);
  1712. }
  1713. if ($container->getParameter('kernel.debug')) {
  1714. $container->removeDefinition('property_info.cache');
  1715. }
  1716. }
  1717. private function registerLockConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  1718. {
  1719. $loader->load('lock.php');
  1720. foreach ($config['resources'] as $resourceName => $resourceStores) {
  1721. if (0 === \count($resourceStores)) {
  1722. continue;
  1723. }
  1724. // Generate stores
  1725. $storeDefinitions = [];
  1726. foreach ($resourceStores as $resourceStore) {
  1727. $storeDsn = $container->resolveEnvPlaceholders($resourceStore, null, $usedEnvs);
  1728. $storeDefinition = new Definition(PersistingStoreInterface::class);
  1729. $storeDefinition
  1730. ->setFactory([StoreFactory::class, 'createStore'])
  1731. ->setArguments([$resourceStore])
  1732. ->addTag('lock.store');
  1733. $container->setDefinition($storeDefinitionId = '.lock.'.$resourceName.'.store.'.$container->hash($storeDsn), $storeDefinition);
  1734. $storeDefinition = new Reference($storeDefinitionId);
  1735. $storeDefinitions[] = $storeDefinition;
  1736. }
  1737. // Wrap array of stores with CombinedStore
  1738. if (\count($storeDefinitions) > 1) {
  1739. $combinedDefinition = new ChildDefinition('lock.store.combined.abstract');
  1740. $combinedDefinition->replaceArgument(0, $storeDefinitions);
  1741. $container->setDefinition($storeDefinitionId = '.lock.'.$resourceName.'.store.'.$container->hash($resourceStores), $combinedDefinition);
  1742. }
  1743. // Generate factories for each resource
  1744. $factoryDefinition = new ChildDefinition('lock.factory.abstract');
  1745. $factoryDefinition->replaceArgument(0, new Reference($storeDefinitionId));
  1746. $container->setDefinition('lock.'.$resourceName.'.factory', $factoryDefinition);
  1747. // provide alias for default resource
  1748. if ('default' === $resourceName) {
  1749. $container->setAlias('lock.factory', new Alias('lock.'.$resourceName.'.factory', false));
  1750. $container->setAlias(LockFactory::class, new Alias('lock.factory', false));
  1751. } else {
  1752. $container->registerAliasForArgument('lock.'.$resourceName.'.factory', LockFactory::class, $resourceName.'.lock.factory');
  1753. }
  1754. }
  1755. }
  1756. private function registerSemaphoreConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  1757. {
  1758. $loader->load('semaphore.php');
  1759. foreach ($config['resources'] as $resourceName => $resourceStore) {
  1760. $storeDsn = $container->resolveEnvPlaceholders($resourceStore, null, $usedEnvs);
  1761. $storeDefinition = new Definition(SemaphoreStoreInterface::class);
  1762. $storeDefinition->setFactory([SemaphoreStoreFactory::class, 'createStore']);
  1763. $storeDefinition->setArguments([$resourceStore]);
  1764. $container->setDefinition($storeDefinitionId = '.semaphore.'.$resourceName.'.store.'.$container->hash($storeDsn), $storeDefinition);
  1765. // Generate factories for each resource
  1766. $factoryDefinition = new ChildDefinition('semaphore.factory.abstract');
  1767. $factoryDefinition->replaceArgument(0, new Reference($storeDefinitionId));
  1768. $container->setDefinition('semaphore.'.$resourceName.'.factory', $factoryDefinition);
  1769. // Generate services for semaphore instances
  1770. $semaphoreDefinition = new Definition(Semaphore::class);
  1771. $semaphoreDefinition->setFactory([new Reference('semaphore.'.$resourceName.'.factory'), 'createSemaphore']);
  1772. $semaphoreDefinition->setArguments([$resourceName]);
  1773. // provide alias for default resource
  1774. if ('default' === $resourceName) {
  1775. $container->setAlias('semaphore.factory', new Alias('semaphore.'.$resourceName.'.factory', false));
  1776. $container->setAlias(SemaphoreFactory::class, new Alias('semaphore.factory', false));
  1777. } else {
  1778. $container->registerAliasForArgument('semaphore.'.$resourceName.'.factory', SemaphoreFactory::class, $resourceName.'.semaphore.factory');
  1779. }
  1780. }
  1781. }
  1782. private function registerSchedulerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  1783. {
  1784. if (!class_exists(SchedulerTransportFactory::class)) {
  1785. throw new LogicException('Scheduler support cannot be enabled as the Scheduler component is not installed. Try running "composer require symfony/scheduler".');
  1786. }
  1787. $loader->load('scheduler.php');
  1788. if (!$this->hasConsole()) {
  1789. $container->removeDefinition('console.command.scheduler_debug');
  1790. }
  1791. }
  1792. private function registerMessengerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $validationEnabled): void
  1793. {
  1794. if (!interface_exists(MessageBusInterface::class)) {
  1795. throw new LogicException('Messenger support cannot be enabled as the Messenger component is not installed. Try running "composer require symfony/messenger".');
  1796. }
  1797. if (!$this->hasConsole()) {
  1798. $container->removeDefinition('console.command.messenger_stats');
  1799. }
  1800. $loader->load('messenger.php');
  1801. if (!interface_exists(DenormalizerInterface::class)) {
  1802. $container->removeDefinition('serializer.normalizer.flatten_exception');
  1803. }
  1804. if (ContainerBuilder::willBeAvailable('symfony/amqp-messenger', MessengerBridge\Amqp\Transport\AmqpTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) {
  1805. $container->getDefinition('messenger.transport.amqp.factory')->addTag('messenger.transport_factory');
  1806. }
  1807. if (ContainerBuilder::willBeAvailable('symfony/redis-messenger', MessengerBridge\Redis\Transport\RedisTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) {
  1808. $container->getDefinition('messenger.transport.redis.factory')->addTag('messenger.transport_factory');
  1809. }
  1810. if (ContainerBuilder::willBeAvailable('symfony/amazon-sqs-messenger', MessengerBridge\AmazonSqs\Transport\AmazonSqsTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) {
  1811. $container->getDefinition('messenger.transport.sqs.factory')->addTag('messenger.transport_factory');
  1812. }
  1813. if (ContainerBuilder::willBeAvailable('symfony/beanstalkd-messenger', MessengerBridge\Beanstalkd\Transport\BeanstalkdTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) {
  1814. $container->getDefinition('messenger.transport.beanstalkd.factory')->addTag('messenger.transport_factory');
  1815. }
  1816. if ($config['stop_worker_on_signals'] && $this->hasConsole()) {
  1817. $container->getDefinition('console.command.messenger_consume_messages')
  1818. ->replaceArgument(8, $config['stop_worker_on_signals']);
  1819. $container->getDefinition('console.command.messenger_failed_messages_retry')
  1820. ->replaceArgument(6, $config['stop_worker_on_signals']);
  1821. }
  1822. if ($this->hasConsole() && $container->hasDefinition('messenger.listener.stop_worker_signals_listener')) {
  1823. $container->getDefinition('messenger.listener.stop_worker_signals_listener')->clearTag('kernel.event_subscriber');
  1824. }
  1825. if (!class_exists(StopWorkerOnSignalsListener::class)) {
  1826. $container->removeDefinition('messenger.listener.stop_worker_signals_listener');
  1827. } elseif ($config['stop_worker_on_signals']) {
  1828. $container->getDefinition('messenger.listener.stop_worker_signals_listener')->replaceArgument(0, $config['stop_worker_on_signals']);
  1829. }
  1830. if (null === $config['default_bus'] && 1 === \count($config['buses'])) {
  1831. $config['default_bus'] = key($config['buses']);
  1832. }
  1833. $defaultMiddleware = [
  1834. 'before' => [
  1835. ['id' => 'add_bus_name_stamp_middleware'],
  1836. ['id' => 'reject_redelivered_message_middleware'],
  1837. ['id' => 'dispatch_after_current_bus'],
  1838. ['id' => 'failed_message_processing_middleware'],
  1839. ],
  1840. 'after' => [
  1841. ['id' => 'send_message'],
  1842. ['id' => 'handle_message'],
  1843. ],
  1844. ];
  1845. foreach ($config['buses'] as $busId => $bus) {
  1846. $middleware = $bus['middleware'];
  1847. if ($bus['default_middleware']['enabled']) {
  1848. $defaultMiddleware['after'][0]['arguments'] = [$bus['default_middleware']['allow_no_senders']];
  1849. $defaultMiddleware['after'][1]['arguments'] = [$bus['default_middleware']['allow_no_handlers']];
  1850. $middleware = array_merge($defaultMiddleware['before'], $middleware, $defaultMiddleware['after']);
  1851. }
  1852. foreach ($middleware as $key => $middlewareItem) {
  1853. if (!$validationEnabled && \in_array($middlewareItem['id'], ['validation', 'messenger.middleware.validation'], true)) {
  1854. throw new LogicException('The Validation middleware is only available when the Validator component is installed and enabled. Try running "composer require symfony/validator".');
  1855. }
  1856. // argument to add_bus_name_stamp_middleware
  1857. if ('add_bus_name_stamp_middleware' === $middlewareItem['id']) {
  1858. $middleware[$key]['arguments'] = [$busId];
  1859. }
  1860. }
  1861. if ($container->getParameter('kernel.debug') && class_exists(Stopwatch::class)) {
  1862. array_unshift($middleware, ['id' => 'traceable', 'arguments' => [$busId]]);
  1863. }
  1864. $container->setParameter($busId.'.middleware', $middleware);
  1865. $container->register($busId, MessageBus::class)->addArgument([])->addTag('messenger.bus');
  1866. if ($busId === $config['default_bus']) {
  1867. $container->setAlias('messenger.default_bus', $busId)->setPublic(true);
  1868. $container->setAlias(MessageBusInterface::class, $busId);
  1869. } else {
  1870. $container->registerAliasForArgument($busId, MessageBusInterface::class);
  1871. }
  1872. }
  1873. if (empty($config['transports'])) {
  1874. $container->removeDefinition('messenger.transport.symfony_serializer');
  1875. $container->removeDefinition('messenger.transport.amqp.factory');
  1876. $container->removeDefinition('messenger.transport.redis.factory');
  1877. $container->removeDefinition('messenger.transport.sqs.factory');
  1878. $container->removeDefinition('messenger.transport.beanstalkd.factory');
  1879. $container->removeAlias(SerializerInterface::class);
  1880. } else {
  1881. $container->getDefinition('messenger.transport.symfony_serializer')
  1882. ->replaceArgument(1, $config['serializer']['symfony_serializer']['format'])
  1883. ->replaceArgument(2, $config['serializer']['symfony_serializer']['context']);
  1884. $container->setAlias('messenger.default_serializer', $config['serializer']['default_serializer']);
  1885. }
  1886. $failureTransports = [];
  1887. if ($config['failure_transport']) {
  1888. if (!isset($config['transports'][$config['failure_transport']])) {
  1889. throw new LogicException(sprintf('Invalid Messenger configuration: the failure transport "%s" is not a valid transport or service id.', $config['failure_transport']));
  1890. }
  1891. $container->setAlias('messenger.failure_transports.default', 'messenger.transport.'.$config['failure_transport']);
  1892. $failureTransports[] = $config['failure_transport'];
  1893. }
  1894. $failureTransportsByName = [];
  1895. foreach ($config['transports'] as $name => $transport) {
  1896. if ($transport['failure_transport']) {
  1897. $failureTransports[] = $transport['failure_transport'];
  1898. $failureTransportsByName[$name] = $transport['failure_transport'];
  1899. } elseif ($config['failure_transport']) {
  1900. $failureTransportsByName[$name] = $config['failure_transport'];
  1901. }
  1902. }
  1903. $senderAliases = [];
  1904. $transportRetryReferences = [];
  1905. $transportRateLimiterReferences = [];
  1906. foreach ($config['transports'] as $name => $transport) {
  1907. $serializerId = $transport['serializer'] ?? 'messenger.default_serializer';
  1908. $tags = [
  1909. 'alias' => $name,
  1910. 'is_failure_transport' => \in_array($name, $failureTransports),
  1911. ];
  1912. if (str_starts_with($transport['dsn'], 'sync://')) {
  1913. $tags['is_consumable'] = false;
  1914. }
  1915. $transportDefinition = (new Definition(TransportInterface::class))
  1916. ->setFactory([new Reference('messenger.transport_factory'), 'createTransport'])
  1917. ->setArguments([$transport['dsn'], $transport['options'] + ['transport_name' => $name], new Reference($serializerId)])
  1918. ->addTag('messenger.receiver', $tags)
  1919. ;
  1920. $container->setDefinition($transportId = 'messenger.transport.'.$name, $transportDefinition);
  1921. $senderAliases[$name] = $transportId;
  1922. if (null !== $transport['retry_strategy']['service']) {
  1923. $transportRetryReferences[$name] = new Reference($transport['retry_strategy']['service']);
  1924. } else {
  1925. $retryServiceId = sprintf('messenger.retry.multiplier_retry_strategy.%s', $name);
  1926. $retryDefinition = new ChildDefinition('messenger.retry.abstract_multiplier_retry_strategy');
  1927. $retryDefinition
  1928. ->replaceArgument(0, $transport['retry_strategy']['max_retries'])
  1929. ->replaceArgument(1, $transport['retry_strategy']['delay'])
  1930. ->replaceArgument(2, $transport['retry_strategy']['multiplier'])
  1931. ->replaceArgument(3, $transport['retry_strategy']['max_delay']);
  1932. $container->setDefinition($retryServiceId, $retryDefinition);
  1933. $transportRetryReferences[$name] = new Reference($retryServiceId);
  1934. }
  1935. if ($transport['rate_limiter']) {
  1936. if (!interface_exists(LimiterInterface::class)) {
  1937. throw new LogicException('Rate limiter cannot be used within Messenger as the RateLimiter component is not installed. Try running "composer require symfony/rate-limiter".');
  1938. }
  1939. $transportRateLimiterReferences[$name] = new Reference('limiter.'.$transport['rate_limiter']);
  1940. }
  1941. }
  1942. $senderReferences = [];
  1943. // alias => service_id
  1944. foreach ($senderAliases as $alias => $serviceId) {
  1945. $senderReferences[$alias] = new Reference($serviceId);
  1946. }
  1947. // service_id => service_id
  1948. foreach ($senderAliases as $serviceId) {
  1949. $senderReferences[$serviceId] = new Reference($serviceId);
  1950. }
  1951. foreach ($config['transports'] as $name => $transport) {
  1952. if ($transport['failure_transport']) {
  1953. if (!isset($senderReferences[$transport['failure_transport']])) {
  1954. throw new LogicException(sprintf('Invalid Messenger configuration: the failure transport "%s" is not a valid transport or service id.', $transport['failure_transport']));
  1955. }
  1956. }
  1957. }
  1958. $failureTransportReferencesByTransportName = array_map(fn ($failureTransportName) => $senderReferences[$failureTransportName], $failureTransportsByName);
  1959. $messageToSendersMapping = [];
  1960. foreach ($config['routing'] as $message => $messageConfiguration) {
  1961. if ('*' !== $message && !class_exists($message) && !interface_exists($message, false) && !preg_match('/^(?:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+\\\\)++\*$/', $message)) {
  1962. if (str_contains($message, '*')) {
  1963. throw new LogicException(sprintf('Invalid Messenger routing configuration: invalid namespace "%s" wildcard.', $message));
  1964. }
  1965. throw new LogicException(sprintf('Invalid Messenger routing configuration: class or interface "%s" not found.', $message));
  1966. }
  1967. // make sure senderAliases contains all senders
  1968. foreach ($messageConfiguration['senders'] as $sender) {
  1969. if (!isset($senderReferences[$sender])) {
  1970. throw new LogicException(sprintf('Invalid Messenger routing configuration: the "%s" class is being routed to a sender called "%s". This is not a valid transport or service id.', $message, $sender));
  1971. }
  1972. }
  1973. $messageToSendersMapping[$message] = $messageConfiguration['senders'];
  1974. }
  1975. $sendersServiceLocator = ServiceLocatorTagPass::register($container, $senderReferences);
  1976. $container->getDefinition('messenger.senders_locator')
  1977. ->replaceArgument(0, $messageToSendersMapping)
  1978. ->replaceArgument(1, $sendersServiceLocator)
  1979. ;
  1980. $container->getDefinition('messenger.retry.send_failed_message_for_retry_listener')
  1981. ->replaceArgument(0, $sendersServiceLocator)
  1982. ;
  1983. $container->getDefinition('messenger.retry_strategy_locator')
  1984. ->replaceArgument(0, $transportRetryReferences);
  1985. if (!$transportRateLimiterReferences) {
  1986. $container->removeDefinition('messenger.rate_limiter_locator');
  1987. } else {
  1988. $container->getDefinition('messenger.rate_limiter_locator')
  1989. ->replaceArgument(0, $transportRateLimiterReferences);
  1990. }
  1991. if (\count($failureTransports) > 0) {
  1992. if ($this->hasConsole()) {
  1993. $container->getDefinition('console.command.messenger_failed_messages_retry')
  1994. ->replaceArgument(0, $config['failure_transport']);
  1995. $container->getDefinition('console.command.messenger_failed_messages_show')
  1996. ->replaceArgument(0, $config['failure_transport']);
  1997. $container->getDefinition('console.command.messenger_failed_messages_remove')
  1998. ->replaceArgument(0, $config['failure_transport']);
  1999. }
  2000. $failureTransportsByTransportNameServiceLocator = ServiceLocatorTagPass::register($container, $failureTransportReferencesByTransportName);
  2001. $container->getDefinition('messenger.failure.send_failed_message_to_failure_transport_listener')
  2002. ->replaceArgument(0, $failureTransportsByTransportNameServiceLocator);
  2003. } else {
  2004. $container->removeDefinition('messenger.failure.send_failed_message_to_failure_transport_listener');
  2005. $container->removeDefinition('console.command.messenger_failed_messages_retry');
  2006. $container->removeDefinition('console.command.messenger_failed_messages_show');
  2007. $container->removeDefinition('console.command.messenger_failed_messages_remove');
  2008. }
  2009. if (!$container->hasDefinition('console.command.messenger_consume_messages')) {
  2010. $container->removeDefinition('messenger.listener.reset_services');
  2011. }
  2012. }
  2013. private function registerCacheConfiguration(array $config, ContainerBuilder $container): void
  2014. {
  2015. $version = new Parameter('container.build_id');
  2016. $container->getDefinition('cache.adapter.apcu')->replaceArgument(2, $version);
  2017. $container->getDefinition('cache.adapter.system')->replaceArgument(2, $version);
  2018. $container->getDefinition('cache.adapter.filesystem')->replaceArgument(2, $config['directory']);
  2019. if (isset($config['prefix_seed'])) {
  2020. $container->setParameter('cache.prefix.seed', $config['prefix_seed']);
  2021. }
  2022. if ($container->hasParameter('cache.prefix.seed')) {
  2023. // Inline any env vars referenced in the parameter
  2024. $container->setParameter('cache.prefix.seed', $container->resolveEnvPlaceholders($container->getParameter('cache.prefix.seed'), true));
  2025. }
  2026. foreach (['psr6', 'redis', 'memcached', 'doctrine_dbal', 'pdo'] as $name) {
  2027. if (isset($config[$name = 'default_'.$name.'_provider'])) {
  2028. $container->setAlias('cache.'.$name, new Alias(CachePoolPass::getServiceProvider($container, $config[$name]), false));
  2029. }
  2030. }
  2031. foreach (['app', 'system'] as $name) {
  2032. $config['pools']['cache.'.$name] = [
  2033. 'adapters' => [$config[$name]],
  2034. 'public' => true,
  2035. 'tags' => false,
  2036. ];
  2037. }
  2038. foreach ($config['pools'] as $name => $pool) {
  2039. $pool['adapters'] = $pool['adapters'] ?: ['cache.app'];
  2040. $isRedisTagAware = ['cache.adapter.redis_tag_aware'] === $pool['adapters'];
  2041. foreach ($pool['adapters'] as $provider => $adapter) {
  2042. if (($config['pools'][$adapter]['adapters'] ?? null) === ['cache.adapter.redis_tag_aware']) {
  2043. $isRedisTagAware = true;
  2044. } elseif ($config['pools'][$adapter]['tags'] ?? false) {
  2045. $pool['adapters'][$provider] = $adapter = '.'.$adapter.'.inner';
  2046. }
  2047. }
  2048. if (1 === \count($pool['adapters'])) {
  2049. if (!isset($pool['provider']) && !\is_int($provider)) {
  2050. $pool['provider'] = $provider;
  2051. }
  2052. $definition = new ChildDefinition($adapter);
  2053. } else {
  2054. $definition = new Definition(ChainAdapter::class, [$pool['adapters'], 0]);
  2055. $pool['reset'] = 'reset';
  2056. }
  2057. if ($isRedisTagAware && 'cache.app' === $name) {
  2058. $container->setAlias('cache.app.taggable', $name);
  2059. $definition->addTag('cache.taggable', ['pool' => $name]);
  2060. } elseif ($isRedisTagAware) {
  2061. $tagAwareId = $name;
  2062. $container->setAlias('.'.$name.'.inner', $name);
  2063. $definition->addTag('cache.taggable', ['pool' => $name]);
  2064. } elseif ($pool['tags']) {
  2065. if (true !== $pool['tags'] && ($config['pools'][$pool['tags']]['tags'] ?? false)) {
  2066. $pool['tags'] = '.'.$pool['tags'].'.inner';
  2067. }
  2068. $container->register($name, TagAwareAdapter::class)
  2069. ->addArgument(new Reference('.'.$name.'.inner'))
  2070. ->addArgument(true !== $pool['tags'] ? new Reference($pool['tags']) : null)
  2071. ->addMethodCall('setLogger', [new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)])
  2072. ->setPublic($pool['public'])
  2073. ->addTag('cache.taggable', ['pool' => $name])
  2074. ->addTag('monolog.logger', ['channel' => 'cache']);
  2075. $pool['name'] = $tagAwareId = $name;
  2076. $pool['public'] = false;
  2077. $name = '.'.$name.'.inner';
  2078. } elseif (!\in_array($name, ['cache.app', 'cache.system'], true)) {
  2079. $tagAwareId = '.'.$name.'.taggable';
  2080. $container->register($tagAwareId, TagAwareAdapter::class)
  2081. ->addArgument(new Reference($name))
  2082. ->addTag('cache.taggable', ['pool' => $name])
  2083. ;
  2084. }
  2085. if (!\in_array($name, ['cache.app', 'cache.system'], true)) {
  2086. $container->registerAliasForArgument($tagAwareId, TagAwareCacheInterface::class, $pool['name'] ?? $name);
  2087. $container->registerAliasForArgument($name, CacheInterface::class, $pool['name'] ?? $name);
  2088. $container->registerAliasForArgument($name, CacheItemPoolInterface::class, $pool['name'] ?? $name);
  2089. }
  2090. $definition->setPublic($pool['public']);
  2091. unset($pool['adapters'], $pool['public'], $pool['tags']);
  2092. $definition->addTag('cache.pool', $pool);
  2093. $container->setDefinition($name, $definition);
  2094. }
  2095. if (class_exists(PropertyAccessor::class)) {
  2096. $propertyAccessDefinition = $container->register('cache.property_access', AdapterInterface::class);
  2097. if (!$container->getParameter('kernel.debug')) {
  2098. $propertyAccessDefinition->setFactory([PropertyAccessor::class, 'createCache']);
  2099. $propertyAccessDefinition->setArguments(['', 0, $version, new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)]);
  2100. $propertyAccessDefinition->addTag('cache.pool', ['clearer' => 'cache.system_clearer']);
  2101. $propertyAccessDefinition->addTag('monolog.logger', ['channel' => 'cache']);
  2102. } else {
  2103. $propertyAccessDefinition->setClass(ArrayAdapter::class);
  2104. $propertyAccessDefinition->setArguments([0, false]);
  2105. }
  2106. }
  2107. }
  2108. private function registerHttpClientConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  2109. {
  2110. $loader->load('http_client.php');
  2111. $options = $config['default_options'] ?? [];
  2112. $retryOptions = $options['retry_failed'] ?? ['enabled' => false];
  2113. unset($options['retry_failed']);
  2114. $defaultUriTemplateVars = $options['vars'] ?? [];
  2115. unset($options['vars']);
  2116. $container->getDefinition('http_client.transport')->setArguments([$options, $config['max_host_connections'] ?? 6]);
  2117. if (!class_exists(PingWebhookMessageHandler::class)) {
  2118. $container->removeDefinition('http_client.messenger.ping_webhook_handler');
  2119. }
  2120. if (!$hasPsr18 = ContainerBuilder::willBeAvailable('psr/http-client', ClientInterface::class, ['symfony/framework-bundle', 'symfony/http-client'])) {
  2121. $container->removeDefinition('psr18.http_client');
  2122. $container->removeAlias(ClientInterface::class);
  2123. }
  2124. if (!$hasHttplug = ContainerBuilder::willBeAvailable('php-http/httplug', HttpAsyncClient::class, ['symfony/framework-bundle', 'symfony/http-client'])) {
  2125. $container->removeDefinition('httplug.http_client');
  2126. $container->removeAlias(HttpAsyncClient::class);
  2127. $container->removeAlias(HttpClient::class);
  2128. }
  2129. if ($this->readConfigEnabled('http_client.retry_failed', $container, $retryOptions)) {
  2130. $this->registerRetryableHttpClient($retryOptions, 'http_client', $container);
  2131. }
  2132. if (ContainerBuilder::willBeAvailable('guzzlehttp/uri-template', \GuzzleHttp\UriTemplate\UriTemplate::class, [])) {
  2133. $container->setAlias('http_client.uri_template_expander', 'http_client.uri_template_expander.guzzle');
  2134. } elseif (ContainerBuilder::willBeAvailable('rize/uri-template', \Rize\UriTemplate::class, [])) {
  2135. $container->setAlias('http_client.uri_template_expander', 'http_client.uri_template_expander.rize');
  2136. }
  2137. $container
  2138. ->getDefinition('http_client.uri_template')
  2139. ->setArgument(2, $defaultUriTemplateVars);
  2140. foreach ($config['scoped_clients'] as $name => $scopeConfig) {
  2141. if ($container->has($name)) {
  2142. throw new InvalidArgumentException(sprintf('Invalid scope name: "%s" is reserved.', $name));
  2143. }
  2144. $scope = $scopeConfig['scope'] ?? null;
  2145. unset($scopeConfig['scope']);
  2146. $retryOptions = $scopeConfig['retry_failed'] ?? ['enabled' => false];
  2147. unset($scopeConfig['retry_failed']);
  2148. if (null === $scope) {
  2149. $baseUri = $scopeConfig['base_uri'];
  2150. unset($scopeConfig['base_uri']);
  2151. $container->register($name, ScopingHttpClient::class)
  2152. ->setFactory([ScopingHttpClient::class, 'forBaseUri'])
  2153. ->setArguments([new Reference('http_client.transport'), $baseUri, $scopeConfig])
  2154. ->addTag('http_client.client')
  2155. ->addTag('kernel.reset', ['method' => 'reset', 'on_invalid' => 'ignore'])
  2156. ;
  2157. } else {
  2158. $container->register($name, ScopingHttpClient::class)
  2159. ->setArguments([new Reference('http_client.transport'), [$scope => $scopeConfig], $scope])
  2160. ->addTag('http_client.client')
  2161. ->addTag('kernel.reset', ['method' => 'reset', 'on_invalid' => 'ignore'])
  2162. ;
  2163. }
  2164. if ($this->readConfigEnabled('http_client.scoped_clients.'.$name.'.retry_failed', $container, $retryOptions)) {
  2165. $this->registerRetryableHttpClient($retryOptions, $name, $container);
  2166. }
  2167. $container
  2168. ->register($name.'.uri_template', UriTemplateHttpClient::class)
  2169. ->setDecoratedService($name, null, 7) // Between TraceableHttpClient (5) and RetryableHttpClient (10)
  2170. ->setArguments([
  2171. new Reference($name.'.uri_template.inner'),
  2172. new Reference('http_client.uri_template_expander', ContainerInterface::NULL_ON_INVALID_REFERENCE),
  2173. $defaultUriTemplateVars,
  2174. ]);
  2175. $container->registerAliasForArgument($name, HttpClientInterface::class);
  2176. if ($hasPsr18) {
  2177. $container->setDefinition('psr18.'.$name, new ChildDefinition('psr18.http_client'))
  2178. ->replaceArgument(0, new Reference($name));
  2179. $container->registerAliasForArgument('psr18.'.$name, ClientInterface::class, $name);
  2180. }
  2181. if ($hasHttplug) {
  2182. $container->setDefinition('httplug.'.$name, new ChildDefinition('httplug.http_client'))
  2183. ->replaceArgument(0, new Reference($name));
  2184. $container->registerAliasForArgument('httplug.'.$name, HttpAsyncClient::class, $name);
  2185. }
  2186. }
  2187. if ($responseFactoryId = $config['mock_response_factory'] ?? null) {
  2188. $container->register('http_client.mock_client', MockHttpClient::class)
  2189. ->setDecoratedService('http_client.transport', null, -10) // lower priority than TraceableHttpClient (5)
  2190. ->setArguments([new Reference($responseFactoryId)]);
  2191. }
  2192. }
  2193. private function registerRetryableHttpClient(array $options, string $name, ContainerBuilder $container): void
  2194. {
  2195. if (null !== $options['retry_strategy']) {
  2196. $retryStrategy = new Reference($options['retry_strategy']);
  2197. } else {
  2198. $retryStrategy = new ChildDefinition('http_client.abstract_retry_strategy');
  2199. $codes = [];
  2200. foreach ($options['http_codes'] as $code => $codeOptions) {
  2201. if ($codeOptions['methods']) {
  2202. $codes[$code] = $codeOptions['methods'];
  2203. } else {
  2204. $codes[] = $code;
  2205. }
  2206. }
  2207. $retryStrategy
  2208. ->replaceArgument(0, $codes ?: GenericRetryStrategy::DEFAULT_RETRY_STATUS_CODES)
  2209. ->replaceArgument(1, $options['delay'])
  2210. ->replaceArgument(2, $options['multiplier'])
  2211. ->replaceArgument(3, $options['max_delay'])
  2212. ->replaceArgument(4, $options['jitter']);
  2213. $container->setDefinition($name.'.retry_strategy', $retryStrategy);
  2214. $retryStrategy = new Reference($name.'.retry_strategy');
  2215. }
  2216. $container
  2217. ->register($name.'.retryable', RetryableHttpClient::class)
  2218. ->setDecoratedService($name, null, 10) // higher priority than TraceableHttpClient (5)
  2219. ->setArguments([new Reference($name.'.retryable.inner'), $retryStrategy, $options['max_retries'], new Reference('logger')])
  2220. ->addTag('monolog.logger', ['channel' => 'http_client']);
  2221. }
  2222. private function registerMailerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $webhookEnabled): void
  2223. {
  2224. if (!class_exists(Mailer::class)) {
  2225. throw new LogicException('Mailer support cannot be enabled as the component is not installed. Try running "composer require symfony/mailer".');
  2226. }
  2227. $loader->load('mailer.php');
  2228. $loader->load('mailer_transports.php');
  2229. if (!\count($config['transports']) && null === $config['dsn']) {
  2230. $config['dsn'] = 'smtp://null';
  2231. }
  2232. $transports = $config['dsn'] ? ['main' => $config['dsn']] : $config['transports'];
  2233. $container->getDefinition('mailer.transports')->setArgument(0, $transports);
  2234. $mailer = $container->getDefinition('mailer.mailer');
  2235. if (false === $messageBus = $config['message_bus']) {
  2236. $mailer->replaceArgument(1, null);
  2237. } else {
  2238. $mailer->replaceArgument(1, $messageBus ? new Reference($messageBus) : new Reference('messenger.default_bus', ContainerInterface::NULL_ON_INVALID_REFERENCE));
  2239. }
  2240. $classToServices = [
  2241. MailerBridge\Brevo\Transport\BrevoTransportFactory::class => 'mailer.transport_factory.brevo',
  2242. MailerBridge\Google\Transport\GmailTransportFactory::class => 'mailer.transport_factory.gmail',
  2243. MailerBridge\Infobip\Transport\InfobipTransportFactory::class => 'mailer.transport_factory.infobip',
  2244. MailerBridge\MailerSend\Transport\MailerSendTransportFactory::class => 'mailer.transport_factory.mailersend',
  2245. MailerBridge\Mailgun\Transport\MailgunTransportFactory::class => 'mailer.transport_factory.mailgun',
  2246. MailerBridge\Mailjet\Transport\MailjetTransportFactory::class => 'mailer.transport_factory.mailjet',
  2247. MailerBridge\MailPace\Transport\MailPaceTransportFactory::class => 'mailer.transport_factory.mailpace',
  2248. MailerBridge\Mailchimp\Transport\MandrillTransportFactory::class => 'mailer.transport_factory.mailchimp',
  2249. MailerBridge\OhMySmtp\Transport\OhMySmtpTransportFactory::class => 'mailer.transport_factory.ohmysmtp',
  2250. MailerBridge\Postmark\Transport\PostmarkTransportFactory::class => 'mailer.transport_factory.postmark',
  2251. MailerBridge\Scaleway\Transport\ScalewayTransportFactory::class => 'mailer.transport_factory.scaleway',
  2252. MailerBridge\Sendgrid\Transport\SendgridTransportFactory::class => 'mailer.transport_factory.sendgrid',
  2253. MailerBridge\Sendinblue\Transport\SendinblueTransportFactory::class => 'mailer.transport_factory.sendinblue',
  2254. MailerBridge\Amazon\Transport\SesTransportFactory::class => 'mailer.transport_factory.amazon',
  2255. ];
  2256. foreach ($classToServices as $class => $service) {
  2257. $package = substr($service, \strlen('mailer.transport_factory.'));
  2258. if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-mailer', 'gmail' === $package ? 'google' : $package), $class, ['symfony/framework-bundle', 'symfony/mailer'])) {
  2259. $container->removeDefinition($service);
  2260. }
  2261. }
  2262. if ($webhookEnabled) {
  2263. $webhookRequestParsers = [
  2264. MailerBridge\Brevo\Webhook\BrevoRequestParser::class => 'mailer.webhook.request_parser.brevo',
  2265. MailerBridge\Mailgun\Webhook\MailgunRequestParser::class => 'mailer.webhook.request_parser.mailgun',
  2266. MailerBridge\Mailjet\Webhook\MailjetRequestParser::class => 'mailer.webhook.request_parser.mailjet',
  2267. MailerBridge\Postmark\Webhook\PostmarkRequestParser::class => 'mailer.webhook.request_parser.postmark',
  2268. MailerBridge\Sendgrid\Webhook\SendgridRequestParser::class => 'mailer.webhook.request_parser.sendgrid',
  2269. ];
  2270. foreach ($webhookRequestParsers as $class => $service) {
  2271. $package = substr($service, \strlen('mailer.webhook.request_parser.'));
  2272. if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-mailer', 'gmail' === $package ? 'google' : $package), $class, ['symfony/framework-bundle', 'symfony/mailer'])) {
  2273. $container->removeDefinition($service);
  2274. }
  2275. }
  2276. }
  2277. $envelopeListener = $container->getDefinition('mailer.envelope_listener');
  2278. $envelopeListener->setArgument(0, $config['envelope']['sender'] ?? null);
  2279. $envelopeListener->setArgument(1, $config['envelope']['recipients'] ?? null);
  2280. if ($config['headers']) {
  2281. $headers = new Definition(Headers::class);
  2282. foreach ($config['headers'] as $name => $data) {
  2283. $value = $data['value'];
  2284. if (\in_array(strtolower($name), ['from', 'to', 'cc', 'bcc', 'reply-to'])) {
  2285. $value = (array) $value;
  2286. }
  2287. $headers->addMethodCall('addHeader', [$name, $value]);
  2288. }
  2289. $messageListener = $container->getDefinition('mailer.message_listener');
  2290. $messageListener->setArgument(0, $headers);
  2291. } else {
  2292. $container->removeDefinition('mailer.message_listener');
  2293. }
  2294. if (!class_exists(MessengerTransportListener::class)) {
  2295. $container->removeDefinition('mailer.messenger_transport_listener');
  2296. }
  2297. if ($webhookEnabled) {
  2298. $loader->load('mailer_webhook.php');
  2299. }
  2300. }
  2301. private function registerNotifierConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $webhookEnabled): void
  2302. {
  2303. if (!class_exists(Notifier::class)) {
  2304. throw new LogicException('Notifier support cannot be enabled as the component is not installed. Try running "composer require symfony/notifier".');
  2305. }
  2306. $loader->load('notifier.php');
  2307. $loader->load('notifier_transports.php');
  2308. if ($config['chatter_transports']) {
  2309. $container->getDefinition('chatter.transports')->setArgument(0, $config['chatter_transports']);
  2310. } else {
  2311. $container->removeDefinition('chatter');
  2312. $container->removeAlias(ChatterInterface::class);
  2313. }
  2314. if ($config['texter_transports']) {
  2315. $container->getDefinition('texter.transports')->setArgument(0, $config['texter_transports']);
  2316. } else {
  2317. $container->removeDefinition('texter');
  2318. $container->removeAlias(TexterInterface::class);
  2319. }
  2320. if ($this->isInitializedConfigEnabled('mailer')) {
  2321. $sender = $container->getDefinition('mailer.envelope_listener')->getArgument(0);
  2322. $container->getDefinition('notifier.channel.email')->setArgument(2, $sender);
  2323. } else {
  2324. $container->removeDefinition('notifier.channel.email');
  2325. }
  2326. foreach (['texter', 'chatter', 'notifier.channel.chat', 'notifier.channel.email', 'notifier.channel.sms', 'notifier.channel.push'] as $serviceId) {
  2327. if (!$container->hasDefinition($serviceId)) {
  2328. continue;
  2329. }
  2330. if (false === $messageBus = $config['message_bus']) {
  2331. $container->getDefinition($serviceId)->replaceArgument(1, null);
  2332. } else {
  2333. $container->getDefinition($serviceId)->replaceArgument(1, $messageBus ? new Reference($messageBus) : new Reference('messenger.default_bus', ContainerInterface::NULL_ON_INVALID_REFERENCE));
  2334. }
  2335. }
  2336. if ($this->isInitializedConfigEnabled('messenger')) {
  2337. if ($config['notification_on_failed_messages']) {
  2338. $container->getDefinition('notifier.failed_message_listener')->addTag('kernel.event_subscriber');
  2339. }
  2340. // as we have a bus, the channels don't need the transports
  2341. $container->getDefinition('notifier.channel.chat')->setArgument(0, null);
  2342. if ($container->hasDefinition('notifier.channel.email')) {
  2343. $container->getDefinition('notifier.channel.email')->setArgument(0, null);
  2344. }
  2345. $container->getDefinition('notifier.channel.sms')->setArgument(0, null);
  2346. $container->getDefinition('notifier.channel.push')->setArgument(0, null);
  2347. }
  2348. $container->getDefinition('notifier.channel_policy')->setArgument(0, $config['channel_policy']);
  2349. $container->registerForAutoconfiguration(NotifierTransportFactoryInterface::class)
  2350. ->addTag('chatter.transport_factory');
  2351. $container->registerForAutoconfiguration(NotifierTransportFactoryInterface::class)
  2352. ->addTag('texter.transport_factory');
  2353. $classToServices = [
  2354. NotifierBridge\AllMySms\AllMySmsTransportFactory::class => 'notifier.transport_factory.all-my-sms',
  2355. NotifierBridge\AmazonSns\AmazonSnsTransportFactory::class => 'notifier.transport_factory.amazon-sns',
  2356. NotifierBridge\Bandwidth\BandwidthTransportFactory::class => 'notifier.transport_factory.bandwidth',
  2357. NotifierBridge\Brevo\BrevoTransportFactory::class => 'notifier.transport_factory.brevo',
  2358. NotifierBridge\Chatwork\ChatworkTransportFactory::class => 'notifier.transport_factory.chatwork',
  2359. NotifierBridge\Clickatell\ClickatellTransportFactory::class => 'notifier.transport_factory.clickatell',
  2360. NotifierBridge\ClickSend\ClickSendTransportFactory::class => 'notifier.transport_factory.click-send',
  2361. NotifierBridge\ContactEveryone\ContactEveryoneTransportFactory::class => 'notifier.transport_factory.contact-everyone',
  2362. NotifierBridge\Discord\DiscordTransportFactory::class => 'notifier.transport_factory.discord',
  2363. NotifierBridge\Engagespot\EngagespotTransportFactory::class => 'notifier.transport_factory.engagespot',
  2364. NotifierBridge\Esendex\EsendexTransportFactory::class => 'notifier.transport_factory.esendex',
  2365. NotifierBridge\Expo\ExpoTransportFactory::class => 'notifier.transport_factory.expo',
  2366. NotifierBridge\Firebase\FirebaseTransportFactory::class => 'notifier.transport_factory.firebase',
  2367. NotifierBridge\FortySixElks\FortySixElksTransportFactory::class => 'notifier.transport_factory.forty-six-elks',
  2368. NotifierBridge\FreeMobile\FreeMobileTransportFactory::class => 'notifier.transport_factory.free-mobile',
  2369. NotifierBridge\GatewayApi\GatewayApiTransportFactory::class => 'notifier.transport_factory.gateway-api',
  2370. NotifierBridge\Gitter\GitterTransportFactory::class => 'notifier.transport_factory.gitter',
  2371. NotifierBridge\GoIp\GoIpTransportFactory::class => 'notifier.transport_factory.go-ip',
  2372. NotifierBridge\GoogleChat\GoogleChatTransportFactory::class => 'notifier.transport_factory.google-chat',
  2373. NotifierBridge\Infobip\InfobipTransportFactory::class => 'notifier.transport_factory.infobip',
  2374. NotifierBridge\Iqsms\IqsmsTransportFactory::class => 'notifier.transport_factory.iqsms',
  2375. NotifierBridge\Isendpro\IsendproTransportFactory::class => 'notifier.transport_factory.isendpro',
  2376. NotifierBridge\KazInfoTeh\KazInfoTehTransportFactory::class => 'notifier.transport_factory.kaz-info-teh',
  2377. NotifierBridge\LightSms\LightSmsTransportFactory::class => 'notifier.transport_factory.light-sms',
  2378. NotifierBridge\LineNotify\LineNotifyTransportFactory::class => 'notifier.transport_factory.line-notify',
  2379. NotifierBridge\LinkedIn\LinkedInTransportFactory::class => 'notifier.transport_factory.linked-in',
  2380. NotifierBridge\Mailjet\MailjetTransportFactory::class => 'notifier.transport_factory.mailjet',
  2381. NotifierBridge\Mastodon\MastodonTransportFactory::class => 'notifier.transport_factory.mastodon',
  2382. NotifierBridge\Mattermost\MattermostTransportFactory::class => 'notifier.transport_factory.mattermost',
  2383. NotifierBridge\Mercure\MercureTransportFactory::class => 'notifier.transport_factory.mercure',
  2384. NotifierBridge\MessageBird\MessageBirdTransportFactory::class => 'notifier.transport_factory.message-bird',
  2385. NotifierBridge\MessageMedia\MessageMediaTransportFactory::class => 'notifier.transport_factory.message-media',
  2386. NotifierBridge\MicrosoftTeams\MicrosoftTeamsTransportFactory::class => 'notifier.transport_factory.microsoft-teams',
  2387. NotifierBridge\Mobyt\MobytTransportFactory::class => 'notifier.transport_factory.mobyt',
  2388. NotifierBridge\Novu\NovuTransportFactory::class => 'notifier.transport_factory.novu',
  2389. NotifierBridge\Ntfy\NtfyTransportFactory::class => 'notifier.transport_factory.ntfy',
  2390. NotifierBridge\Octopush\OctopushTransportFactory::class => 'notifier.transport_factory.octopush',
  2391. NotifierBridge\OneSignal\OneSignalTransportFactory::class => 'notifier.transport_factory.one-signal',
  2392. NotifierBridge\OrangeSms\OrangeSmsTransportFactory::class => 'notifier.transport_factory.orange-sms',
  2393. NotifierBridge\OvhCloud\OvhCloudTransportFactory::class => 'notifier.transport_factory.ovh-cloud',
  2394. NotifierBridge\PagerDuty\PagerDutyTransportFactory::class => 'notifier.transport_factory.pager-duty',
  2395. NotifierBridge\Plivo\PlivoTransportFactory::class => 'notifier.transport_factory.plivo',
  2396. NotifierBridge\Pushover\PushoverTransportFactory::class => 'notifier.transport_factory.pushover',
  2397. NotifierBridge\Redlink\RedlinkTransportFactory::class => 'notifier.transport_factory.redlink',
  2398. NotifierBridge\RingCentral\RingCentralTransportFactory::class => 'notifier.transport_factory.ring-central',
  2399. NotifierBridge\RocketChat\RocketChatTransportFactory::class => 'notifier.transport_factory.rocket-chat',
  2400. NotifierBridge\Sendberry\SendberryTransportFactory::class => 'notifier.transport_factory.sendberry',
  2401. NotifierBridge\SimpleTextin\SimpleTextinTransportFactory::class => 'notifier.transport_factory.simple-textin',
  2402. NotifierBridge\Sendinblue\SendinblueTransportFactory::class => 'notifier.transport_factory.sendinblue',
  2403. NotifierBridge\Sinch\SinchTransportFactory::class => 'notifier.transport_factory.sinch',
  2404. NotifierBridge\Slack\SlackTransportFactory::class => 'notifier.transport_factory.slack',
  2405. NotifierBridge\Sms77\Sms77TransportFactory::class => 'notifier.transport_factory.sms77',
  2406. NotifierBridge\Smsapi\SmsapiTransportFactory::class => 'notifier.transport_factory.smsapi',
  2407. NotifierBridge\SmsBiuras\SmsBiurasTransportFactory::class => 'notifier.transport_factory.sms-biuras',
  2408. NotifierBridge\Smsc\SmscTransportFactory::class => 'notifier.transport_factory.smsc',
  2409. NotifierBridge\SmsFactor\SmsFactorTransportFactory::class => 'notifier.transport_factory.sms-factor',
  2410. NotifierBridge\Smsmode\SmsmodeTransportFactory::class => 'notifier.transport_factory.smsmode',
  2411. NotifierBridge\SpotHit\SpotHitTransportFactory::class => 'notifier.transport_factory.spot-hit',
  2412. NotifierBridge\Telegram\TelegramTransportFactory::class => 'notifier.transport_factory.telegram',
  2413. NotifierBridge\Telnyx\TelnyxTransportFactory::class => 'notifier.transport_factory.telnyx',
  2414. NotifierBridge\Termii\TermiiTransportFactory::class => 'notifier.transport_factory.termii',
  2415. NotifierBridge\TurboSms\TurboSmsTransportFactory::class => 'notifier.transport_factory.turbo-sms',
  2416. NotifierBridge\Twilio\TwilioTransportFactory::class => 'notifier.transport_factory.twilio',
  2417. NotifierBridge\Twitter\TwitterTransportFactory::class => 'notifier.transport_factory.twitter',
  2418. NotifierBridge\Vonage\VonageTransportFactory::class => 'notifier.transport_factory.vonage',
  2419. NotifierBridge\Yunpian\YunpianTransportFactory::class => 'notifier.transport_factory.yunpian',
  2420. NotifierBridge\Zendesk\ZendeskTransportFactory::class => 'notifier.transport_factory.zendesk',
  2421. NotifierBridge\Zulip\ZulipTransportFactory::class => 'notifier.transport_factory.zulip',
  2422. ];
  2423. $parentPackages = ['symfony/framework-bundle', 'symfony/notifier'];
  2424. foreach ($classToServices as $class => $service) {
  2425. $package = substr($service, \strlen('notifier.transport_factory.'));
  2426. if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-notifier', $package), $class, $parentPackages)) {
  2427. $container->removeDefinition($service);
  2428. }
  2429. }
  2430. if (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', NotifierBridge\Mercure\MercureTransportFactory::class, $parentPackages) && ContainerBuilder::willBeAvailable('symfony/mercure-bundle', MercureBundle::class, $parentPackages) && \in_array(MercureBundle::class, $container->getParameter('kernel.bundles'), true)) {
  2431. $container->getDefinition($classToServices[NotifierBridge\Mercure\MercureTransportFactory::class])
  2432. ->replaceArgument(0, new Reference(HubRegistry::class))
  2433. ->replaceArgument(1, new Reference('event_dispatcher', ContainerBuilder::NULL_ON_INVALID_REFERENCE))
  2434. ->addArgument(new Reference('http_client', ContainerBuilder::NULL_ON_INVALID_REFERENCE));
  2435. } elseif (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', NotifierBridge\Mercure\MercureTransportFactory::class, $parentPackages)) {
  2436. $container->removeDefinition($classToServices[NotifierBridge\Mercure\MercureTransportFactory::class]);
  2437. }
  2438. // don't use ContainerBuilder::willBeAvailable() as these are not needed in production
  2439. if (class_exists(FakeChatTransportFactory::class)) {
  2440. $container->getDefinition('notifier.transport_factory.fake-chat')
  2441. ->replaceArgument(0, new Reference('mailer', ContainerBuilder::NULL_ON_INVALID_REFERENCE))
  2442. ->replaceArgument(1, new Reference('logger', ContainerBuilder::NULL_ON_INVALID_REFERENCE))
  2443. ->addArgument(new Reference('event_dispatcher', ContainerBuilder::NULL_ON_INVALID_REFERENCE))
  2444. ->addArgument(new Reference('http_client', ContainerBuilder::NULL_ON_INVALID_REFERENCE));
  2445. } else {
  2446. $container->removeDefinition('notifier.transport_factory.fake-chat');
  2447. }
  2448. // don't use ContainerBuilder::willBeAvailable() as these are not needed in production
  2449. if (class_exists(FakeSmsTransportFactory::class)) {
  2450. $container->getDefinition('notifier.transport_factory.fake-sms')
  2451. ->replaceArgument(0, new Reference('mailer', ContainerBuilder::NULL_ON_INVALID_REFERENCE))
  2452. ->replaceArgument(1, new Reference('logger', ContainerBuilder::NULL_ON_INVALID_REFERENCE))
  2453. ->addArgument(new Reference('event_dispatcher', ContainerBuilder::NULL_ON_INVALID_REFERENCE))
  2454. ->addArgument(new Reference('http_client', ContainerBuilder::NULL_ON_INVALID_REFERENCE));
  2455. } else {
  2456. $container->removeDefinition('notifier.transport_factory.fake-sms');
  2457. }
  2458. if (isset($config['admin_recipients'])) {
  2459. $notifier = $container->getDefinition('notifier');
  2460. foreach ($config['admin_recipients'] as $i => $recipient) {
  2461. $id = 'notifier.admin_recipient.'.$i;
  2462. $container->setDefinition($id, new Definition(Recipient::class, [$recipient['email'], $recipient['phone']]));
  2463. $notifier->addMethodCall('addAdminRecipient', [new Reference($id)]);
  2464. }
  2465. }
  2466. if ($webhookEnabled) {
  2467. $loader->load('notifier_webhook.php');
  2468. $webhookRequestParsers = [
  2469. NotifierBridge\Twilio\Webhook\TwilioRequestParser::class => 'notifier.webhook.request_parser.twilio',
  2470. NotifierBridge\Vonage\Webhook\VonageRequestParser::class => 'notifier.webhook.request_parser.vonage',
  2471. ];
  2472. foreach ($webhookRequestParsers as $class => $service) {
  2473. $package = substr($service, \strlen('notifier.webhook.request_parser.'));
  2474. if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-notifier', $package), $class, ['symfony/framework-bundle', 'symfony/notifier'])) {
  2475. $container->removeDefinition($service);
  2476. }
  2477. }
  2478. }
  2479. }
  2480. private function registerWebhookConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  2481. {
  2482. if (!class_exists(WebhookController::class)) {
  2483. throw new LogicException('Webhook support cannot be enabled as the component is not installed. Try running "composer require symfony/webhook".');
  2484. }
  2485. $loader->load('webhook.php');
  2486. $parsers = [];
  2487. foreach ($config['routing'] as $type => $cfg) {
  2488. $parsers[$type] = [
  2489. 'parser' => new Reference($cfg['service']),
  2490. 'secret' => $cfg['secret'],
  2491. ];
  2492. }
  2493. $controller = $container->getDefinition('webhook.controller');
  2494. $controller->replaceArgument(0, $parsers);
  2495. $controller->replaceArgument(1, new Reference($config['message_bus']));
  2496. }
  2497. private function registerRemoteEventConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  2498. {
  2499. if (!class_exists(RemoteEvent::class)) {
  2500. throw new LogicException('RemoteEvent support cannot be enabled as the component is not installed. Try running "composer require symfony/remote-event".');
  2501. }
  2502. $loader->load('remote_event.php');
  2503. }
  2504. private function registerRateLimiterConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  2505. {
  2506. $loader->load('rate_limiter.php');
  2507. foreach ($config['limiters'] as $name => $limiterConfig) {
  2508. // default configuration (when used by other DI extensions)
  2509. $limiterConfig += ['lock_factory' => 'lock.factory', 'cache_pool' => 'cache.rate_limiter'];
  2510. $limiter = $container->setDefinition($limiterId = 'limiter.'.$name, new ChildDefinition('limiter'));
  2511. if (null !== $limiterConfig['lock_factory']) {
  2512. if (!interface_exists(LockInterface::class)) {
  2513. throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be installed. Try running "composer require symfony/lock".', $name));
  2514. }
  2515. if (!$this->isInitializedConfigEnabled('lock')) {
  2516. throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be configured.', $name));
  2517. }
  2518. $limiter->replaceArgument(2, new Reference($limiterConfig['lock_factory']));
  2519. }
  2520. unset($limiterConfig['lock_factory']);
  2521. if (null === $storageId = $limiterConfig['storage_service'] ?? null) {
  2522. $container->register($storageId = 'limiter.storage.'.$name, CacheStorage::class)->addArgument(new Reference($limiterConfig['cache_pool']));
  2523. }
  2524. $limiter->replaceArgument(1, new Reference($storageId));
  2525. unset($limiterConfig['storage_service'], $limiterConfig['cache_pool']);
  2526. $limiterConfig['id'] = $name;
  2527. $limiter->replaceArgument(0, $limiterConfig);
  2528. $container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name.'.limiter');
  2529. }
  2530. }
  2531. /**
  2532. * @deprecated since Symfony 6.2
  2533. *
  2534. * @return void
  2535. */
  2536. public static function registerRateLimiter(ContainerBuilder $container, string $name, array $limiterConfig)
  2537. {
  2538. trigger_deprecation('symfony/framework-bundle', '6.2', 'The "%s()" method is deprecated.', __METHOD__);
  2539. // default configuration (when used by other DI extensions)
  2540. $limiterConfig += ['lock_factory' => 'lock.factory', 'cache_pool' => 'cache.rate_limiter'];
  2541. $limiter = $container->setDefinition($limiterId = 'limiter.'.$name, new ChildDefinition('limiter'));
  2542. if (null !== $limiterConfig['lock_factory']) {
  2543. if (!interface_exists(LockInterface::class)) {
  2544. throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be installed. Try running "composer require symfony/lock".', $name));
  2545. }
  2546. if (!$container->hasDefinition('lock.factory.abstract')) {
  2547. throw new LogicException(sprintf('Rate limiter "%s" requires the Lock component to be configured.', $name));
  2548. }
  2549. $limiter->replaceArgument(2, new Reference($limiterConfig['lock_factory']));
  2550. }
  2551. unset($limiterConfig['lock_factory']);
  2552. if (null === $storageId = $limiterConfig['storage_service'] ?? null) {
  2553. $container->register($storageId = 'limiter.storage.'.$name, CacheStorage::class)->addArgument(new Reference($limiterConfig['cache_pool']));
  2554. }
  2555. $limiter->replaceArgument(1, new Reference($storageId));
  2556. unset($limiterConfig['storage_service'], $limiterConfig['cache_pool']);
  2557. $limiterConfig['id'] = $name;
  2558. $limiter->replaceArgument(0, $limiterConfig);
  2559. $container->registerAliasForArgument($limiterId, RateLimiterFactory::class, $name.'.limiter');
  2560. }
  2561. private function registerUidConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  2562. {
  2563. $loader->load('uid.php');
  2564. $container->getDefinition('uuid.factory')
  2565. ->setArguments([
  2566. $config['default_uuid_version'],
  2567. $config['time_based_uuid_version'],
  2568. $config['name_based_uuid_version'],
  2569. UuidV4::class,
  2570. $config['time_based_uuid_node'] ?? null,
  2571. $config['name_based_uuid_namespace'] ?? null,
  2572. ])
  2573. ;
  2574. if (isset($config['name_based_uuid_namespace'])) {
  2575. $container->getDefinition('name_based_uuid.factory')
  2576. ->setArguments([$config['name_based_uuid_namespace']]);
  2577. }
  2578. }
  2579. private function registerHtmlSanitizerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
  2580. {
  2581. $loader->load('html_sanitizer.php');
  2582. foreach ($config['sanitizers'] as $sanitizerName => $sanitizerConfig) {
  2583. $configId = 'html_sanitizer.config.'.$sanitizerName;
  2584. $def = $container->register($configId, HtmlSanitizerConfig::class);
  2585. // Base
  2586. if ($sanitizerConfig['allow_safe_elements']) {
  2587. $def->addMethodCall('allowSafeElements', [], true);
  2588. }
  2589. if ($sanitizerConfig['allow_static_elements']) {
  2590. $def->addMethodCall('allowStaticElements', [], true);
  2591. }
  2592. // Configures elements
  2593. foreach ($sanitizerConfig['allow_elements'] as $element => $attributes) {
  2594. $def->addMethodCall('allowElement', [$element, $attributes], true);
  2595. }
  2596. foreach ($sanitizerConfig['block_elements'] as $element) {
  2597. $def->addMethodCall('blockElement', [$element], true);
  2598. }
  2599. foreach ($sanitizerConfig['drop_elements'] as $element) {
  2600. $def->addMethodCall('dropElement', [$element], true);
  2601. }
  2602. // Configures attributes
  2603. foreach ($sanitizerConfig['allow_attributes'] as $attribute => $elements) {
  2604. $def->addMethodCall('allowAttribute', [$attribute, $elements], true);
  2605. }
  2606. foreach ($sanitizerConfig['drop_attributes'] as $attribute => $elements) {
  2607. $def->addMethodCall('dropAttribute', [$attribute, $elements], true);
  2608. }
  2609. // Force attributes
  2610. foreach ($sanitizerConfig['force_attributes'] as $element => $attributes) {
  2611. foreach ($attributes as $attrName => $attrValue) {
  2612. $def->addMethodCall('forceAttribute', [$element, $attrName, $attrValue], true);
  2613. }
  2614. }
  2615. // Settings
  2616. $def->addMethodCall('forceHttpsUrls', [$sanitizerConfig['force_https_urls']], true);
  2617. if ($sanitizerConfig['allowed_link_schemes']) {
  2618. $def->addMethodCall('allowLinkSchemes', [$sanitizerConfig['allowed_link_schemes']], true);
  2619. }
  2620. $def->addMethodCall('allowLinkHosts', [$sanitizerConfig['allowed_link_hosts']], true);
  2621. $def->addMethodCall('allowRelativeLinks', [$sanitizerConfig['allow_relative_links']], true);
  2622. if ($sanitizerConfig['allowed_media_schemes']) {
  2623. $def->addMethodCall('allowMediaSchemes', [$sanitizerConfig['allowed_media_schemes']], true);
  2624. }
  2625. $def->addMethodCall('allowMediaHosts', [$sanitizerConfig['allowed_media_hosts']], true);
  2626. $def->addMethodCall('allowRelativeMedias', [$sanitizerConfig['allow_relative_medias']], true);
  2627. // Custom attribute sanitizers
  2628. foreach ($sanitizerConfig['with_attribute_sanitizers'] as $serviceName) {
  2629. $def->addMethodCall('withAttributeSanitizer', [new Reference($serviceName)], true);
  2630. }
  2631. foreach ($sanitizerConfig['without_attribute_sanitizers'] as $serviceName) {
  2632. $def->addMethodCall('withoutAttributeSanitizer', [new Reference($serviceName)], true);
  2633. }
  2634. if ($sanitizerConfig['max_input_length']) {
  2635. $def->addMethodCall('withMaxInputLength', [$sanitizerConfig['max_input_length']], true);
  2636. }
  2637. // Create the sanitizer and link its config
  2638. $sanitizerId = 'html_sanitizer.sanitizer.'.$sanitizerName;
  2639. $container->register($sanitizerId, HtmlSanitizer::class)
  2640. ->addTag('html_sanitizer', ['sanitizer' => $sanitizerName])
  2641. ->addArgument(new Reference($configId));
  2642. if ('default' !== $sanitizerName) {
  2643. $container->registerAliasForArgument($sanitizerId, HtmlSanitizerInterface::class, $sanitizerName);
  2644. }
  2645. }
  2646. }
  2647. private function resolveTrustedHeaders(array $headers): int
  2648. {
  2649. $trustedHeaders = 0;
  2650. foreach ($headers as $h) {
  2651. $trustedHeaders |= match ($h) {
  2652. 'forwarded' => Request::HEADER_FORWARDED,
  2653. 'x-forwarded-for' => Request::HEADER_X_FORWARDED_FOR,
  2654. 'x-forwarded-host' => Request::HEADER_X_FORWARDED_HOST,
  2655. 'x-forwarded-proto' => Request::HEADER_X_FORWARDED_PROTO,
  2656. 'x-forwarded-port' => Request::HEADER_X_FORWARDED_PORT,
  2657. 'x-forwarded-prefix' => Request::HEADER_X_FORWARDED_PREFIX,
  2658. default => 0,
  2659. };
  2660. }
  2661. return $trustedHeaders;
  2662. }
  2663. public function getXsdValidationBasePath(): string|false
  2664. {
  2665. return \dirname(__DIR__).'/Resources/config/schema';
  2666. }
  2667. public function getNamespace(): string
  2668. {
  2669. return 'http://symfony.com/schema/dic/symfony';
  2670. }
  2671. protected function isConfigEnabled(ContainerBuilder $container, array $config): bool
  2672. {
  2673. throw new \LogicException('To prevent using outdated configuration, you must use the "readConfigEnabled" method instead.');
  2674. }
  2675. private function isInitializedConfigEnabled(string $path): bool
  2676. {
  2677. if (isset($this->configsEnabled[$path])) {
  2678. return $this->configsEnabled[$path];
  2679. }
  2680. throw new LogicException(sprintf('Can not read config enabled at "%s" because it has not been initialized.', $path));
  2681. }
  2682. private function readConfigEnabled(string $path, ContainerBuilder $container, array $config): bool
  2683. {
  2684. return $this->configsEnabled[$path] ??= parent::isConfigEnabled($container, $config);
  2685. }
  2686. private function writeConfigEnabled(string $path, bool $value, array &$config): void
  2687. {
  2688. if (isset($this->configsEnabled[$path])) {
  2689. throw new LogicException('Can not change config enabled because it has already been read.');
  2690. }
  2691. $this->configsEnabled[$path] = $value;
  2692. $config['enabled'] = $value;
  2693. }
  2694. private function getPublicDirectory(ContainerBuilder $container): string
  2695. {
  2696. $projectDir = $container->getParameter('kernel.project_dir');
  2697. $defaultPublicDir = $projectDir.'/public';
  2698. $composerFilePath = $projectDir.'/composer.json';
  2699. if (!file_exists($composerFilePath)) {
  2700. return $defaultPublicDir;
  2701. }
  2702. $container->addResource(new FileResource($composerFilePath));
  2703. $composerConfig = json_decode(file_get_contents($composerFilePath), true);
  2704. return isset($composerConfig['extra']['public-dir']) ? $projectDir.'/'.$composerConfig['extra']['public-dir'] : $defaultPublicDir;
  2705. }
  2706. }