diff --git a/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php index e3a724cd24..3ed12fc3f6 100644 --- a/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php @@ -13,7 +13,6 @@ namespace Symfony\Bundle\DebugBundle\DependencyInjection; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; -use Symfony\Component\VarDumper\Dumper\HtmlDumper; /** * DebugExtension configuration structure. @@ -51,21 +50,13 @@ class Configuration implements ConfigurationInterface ->example('php://stderr, or tcp://%env(VAR_DUMPER_SERVER)% when using the "server:dump" command') ->defaultNull() ->end() - ->end() - ; - - if (method_exists(HtmlDumper::class, 'setTheme')) { - $rootNode - ->children() - ->enumNode('theme') - ->info('Changes the color of the dump() output when rendered directly on the templating. "dark" (default) or "light"') - ->example('dark') - ->values(['dark', 'light']) - ->defaultValue('dark') - ->end() + ->enumNode('theme') + ->info('Changes the color of the dump() output when rendered directly on the templating. "dark" (default) or "light"') + ->example('dark') + ->values(['dark', 'light']) + ->defaultValue('dark') ->end() ; - } return $treeBuilder; } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index a2bb4d8e26..ee44ac7d56 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -21,6 +21,7 @@ use Symfony\Component\Config\Definition\Builder\NodeBuilder; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\Form\Form; use Symfony\Component\HttpClient\HttpClient; @@ -30,6 +31,7 @@ use Symfony\Component\Lock\Store\SemaphoreStore; use Symfony\Component\Mailer\Mailer; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Notifier\Notifier; +use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter; use Symfony\Component\Serializer\Serializer; @@ -108,8 +110,19 @@ class Configuration implements ConfigurationInterface ->end() ; + $willBeAvailable = static function (string $package, string $class, string $parentPackage = null) { + $parentPackages = (array) $parentPackage; + $parentPackages[] = 'symfony/framework-bundle'; + + return ContainerBuilder::willBeAvailable($package, $class, $parentPackages); + }; + + $enableIfStandalone = static function (string $package, string $class) use ($willBeAvailable) { + return !class_exists(FullStack::class) && $willBeAvailable($package, $class) ? 'canBeDisabled' : 'canBeEnabled'; + }; + $this->addCsrfSection($rootNode); - $this->addFormSection($rootNode); + $this->addFormSection($rootNode, $enableIfStandalone); $this->addHttpCacheSection($rootNode); $this->addEsiSection($rootNode); $this->addSsiSection($rootNode); @@ -119,25 +132,25 @@ class Configuration implements ConfigurationInterface $this->addRouterSection($rootNode); $this->addSessionSection($rootNode); $this->addRequestSection($rootNode); - $this->addAssetsSection($rootNode); - $this->addTranslatorSection($rootNode); - $this->addValidationSection($rootNode); - $this->addAnnotationsSection($rootNode); - $this->addSerializerSection($rootNode); - $this->addPropertyAccessSection($rootNode); - $this->addPropertyInfoSection($rootNode); - $this->addCacheSection($rootNode); + $this->addAssetsSection($rootNode, $enableIfStandalone); + $this->addTranslatorSection($rootNode, $enableIfStandalone); + $this->addValidationSection($rootNode, $enableIfStandalone, $willBeAvailable); + $this->addAnnotationsSection($rootNode, $willBeAvailable); + $this->addSerializerSection($rootNode, $enableIfStandalone, $willBeAvailable); + $this->addPropertyAccessSection($rootNode, $willBeAvailable); + $this->addPropertyInfoSection($rootNode, $enableIfStandalone); + $this->addCacheSection($rootNode, $willBeAvailable); $this->addPhpErrorsSection($rootNode); - $this->addWebLinkSection($rootNode); - $this->addLockSection($rootNode); - $this->addMessengerSection($rootNode); + $this->addWebLinkSection($rootNode, $enableIfStandalone); + $this->addLockSection($rootNode, $enableIfStandalone); + $this->addMessengerSection($rootNode, $enableIfStandalone); $this->addRobotsIndexSection($rootNode); - $this->addHttpClientSection($rootNode); - $this->addMailerSection($rootNode); + $this->addHttpClientSection($rootNode, $enableIfStandalone); + $this->addMailerSection($rootNode, $enableIfStandalone); $this->addSecretsSection($rootNode); - $this->addNotifierSection($rootNode); - $this->addRateLimiterSection($rootNode); - $this->addUidSection($rootNode); + $this->addNotifierSection($rootNode, $enableIfStandalone); + $this->addRateLimiterSection($rootNode, $enableIfStandalone); + $this->addUidSection($rootNode, $enableIfStandalone); return $treeBuilder; } @@ -176,13 +189,13 @@ class Configuration implements ConfigurationInterface ; } - private function addFormSection(ArrayNodeDefinition $rootNode) + private function addFormSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('form') ->info('form configuration') - ->{!class_exists(FullStack::class) && class_exists(Form::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/form', Form::class)}() ->children() ->arrayNode('csrf_protection') ->treatFalseLike(['enabled' => false]) @@ -675,13 +688,13 @@ class Configuration implements ConfigurationInterface ; } - private function addAssetsSection(ArrayNodeDefinition $rootNode) + private function addAssetsSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('assets') ->info('assets configuration') - ->{!class_exists(FullStack::class) && class_exists(Package::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/asset', Package::class)}() ->fixXmlConfig('base_url') ->children() ->scalarNode('version_strategy')->defaultNull()->end() @@ -763,13 +776,13 @@ class Configuration implements ConfigurationInterface ; } - private function addTranslatorSection(ArrayNodeDefinition $rootNode) + private function addTranslatorSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('translator') ->info('translator configuration') - ->{!class_exists(FullStack::class) && class_exists(Translator::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/translation', Translator::class)}() ->fixXmlConfig('fallback') ->fixXmlConfig('path') ->fixXmlConfig('enabled_locale') @@ -816,16 +829,16 @@ class Configuration implements ConfigurationInterface ; } - private function addValidationSection(ArrayNodeDefinition $rootNode) + private function addValidationSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone, callable $willBeAvailable) { $rootNode ->children() ->arrayNode('validation') ->info('validation configuration') - ->{!class_exists(FullStack::class) && class_exists(Validation::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/validator', Validation::class)}() ->children() ->scalarNode('cache')->end() - ->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && class_exists(Annotation::class) ? 'defaultTrue' : 'defaultFalse'}()->end() + ->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && $willBeAvailable('doctrine/annotations', Annotation::class, 'symfony/validator') ? 'defaultTrue' : 'defaultFalse'}()->end() ->arrayNode('static_method') ->defaultValue(['loadValidatorMetadata']) ->prototype('scalar')->end() @@ -906,15 +919,15 @@ class Configuration implements ConfigurationInterface ; } - private function addAnnotationsSection(ArrayNodeDefinition $rootNode) + private function addAnnotationsSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) { $rootNode ->children() ->arrayNode('annotations') ->info('annotation configuration') - ->{class_exists(Annotation::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$willBeAvailable('doctrine/annotations', Annotation::class) ? 'canBeDisabled' : 'canBeEnabled'}() ->children() - ->scalarNode('cache')->defaultValue(interface_exists(Cache::class) ? 'php_array' : 'none')->end() + ->scalarNode('cache')->defaultValue($willBeAvailable('doctrine/cache', Cache::class, 'doctrine/annotation') ? 'php_array' : 'none')->end() ->scalarNode('file_cache_dir')->defaultValue('%kernel.cache_dir%/annotations')->end() ->booleanNode('debug')->defaultValue($this->debug)->end() ->end() @@ -923,15 +936,15 @@ class Configuration implements ConfigurationInterface ; } - private function addSerializerSection(ArrayNodeDefinition $rootNode) + private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone, $willBeAvailable) { $rootNode ->children() ->arrayNode('serializer') ->info('serializer configuration') - ->{!class_exists(FullStack::class) && class_exists(Serializer::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/serializer', Serializer::class)}() ->children() - ->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && class_exists(Annotation::class) ? 'defaultTrue' : 'defaultFalse'}()->end() + ->booleanNode('enable_annotations')->{!class_exists(FullStack::class) && $willBeAvailable('doctrine/annotations', Annotation::class, 'symfony/serializer') ? 'defaultTrue' : 'defaultFalse'}()->end() ->scalarNode('name_converter')->end() ->scalarNode('circular_reference_handler')->end() ->scalarNode('max_depth_handler')->end() @@ -950,13 +963,14 @@ class Configuration implements ConfigurationInterface ; } - private function addPropertyAccessSection(ArrayNodeDefinition $rootNode) + private function addPropertyAccessSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) { $rootNode ->children() ->arrayNode('property_access') ->addDefaultsIfNotSet() ->info('Property access configuration') + ->{$willBeAvailable('symfony/property-access', PropertyAccessor::class) ? 'canBeDisabled' : 'canBeEnabled'}() ->children() ->booleanNode('magic_call')->defaultFalse()->end() ->booleanNode('magic_get')->defaultTrue()->end() @@ -969,19 +983,19 @@ class Configuration implements ConfigurationInterface ; } - private function addPropertyInfoSection(ArrayNodeDefinition $rootNode) + private function addPropertyInfoSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('property_info') ->info('Property info configuration') - ->{!class_exists(FullStack::class) && interface_exists(PropertyInfoExtractorInterface::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/property-info', PropertyInfoExtractorInterface::class)}() ->end() ->end() ; } - private function addCacheSection(ArrayNodeDefinition $rootNode) + private function addCacheSection(ArrayNodeDefinition $rootNode, callable $willBeAvailable) { $rootNode ->children() @@ -1008,7 +1022,7 @@ class Configuration implements ConfigurationInterface ->scalarNode('default_psr6_provider')->end() ->scalarNode('default_redis_provider')->defaultValue('redis://localhost')->end() ->scalarNode('default_memcached_provider')->defaultValue('memcached://localhost')->end() - ->scalarNode('default_pdo_provider')->defaultValue(class_exists(Connection::class) ? 'database_connection' : null)->end() + ->scalarNode('default_pdo_provider')->defaultValue($willBeAvailable('doctrine/dbal', Connection::class) ? 'database_connection' : null)->end() ->arrayNode('pools') ->useAttributeAsKey('name') ->prototype('array') @@ -1117,13 +1131,13 @@ class Configuration implements ConfigurationInterface ; } - private function addLockSection(ArrayNodeDefinition $rootNode) + private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('lock') ->info('Lock configuration') - ->{!class_exists(FullStack::class) && class_exists(Lock::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/lock', Lock::class)}() ->beforeNormalization() ->ifString()->then(function ($v) { return ['enabled' => true, 'resources' => $v]; }) ->end() @@ -1179,25 +1193,25 @@ class Configuration implements ConfigurationInterface ; } - private function addWebLinkSection(ArrayNodeDefinition $rootNode) + private function addWebLinkSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('web_link') ->info('web links configuration') - ->{!class_exists(FullStack::class) && class_exists(HttpHeaderSerializer::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/weblink', HttpHeaderSerializer::class)}() ->end() ->end() ; } - private function addMessengerSection(ArrayNodeDefinition $rootNode) + private function addMessengerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('messenger') ->info('Messenger configuration') - ->{!class_exists(FullStack::class) && interface_exists(MessageBusInterface::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/messenger', MessageBusInterface::class)}() ->fixXmlConfig('transport') ->fixXmlConfig('bus', 'buses') ->validate() @@ -1392,13 +1406,13 @@ class Configuration implements ConfigurationInterface ; } - private function addHttpClientSection(ArrayNodeDefinition $rootNode) + private function addHttpClientSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('http_client') ->info('HTTP Client configuration') - ->{!class_exists(FullStack::class) && class_exists(HttpClient::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/http-client', HttpClient::class)}() ->fixXmlConfig('scoped_client') ->beforeNormalization() ->always(function ($config) { @@ -1728,13 +1742,13 @@ class Configuration implements ConfigurationInterface ; } - private function addMailerSection(ArrayNodeDefinition $rootNode) + private function addMailerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('mailer') ->info('Mailer configuration') - ->{!class_exists(FullStack::class) && class_exists(Mailer::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/mailer', Mailer::class)}() ->validate() ->ifTrue(function ($v) { return isset($v['dsn']) && \count($v['transports']); }) ->thenInvalid('"dsn" and "transports" cannot be used together.') @@ -1784,13 +1798,13 @@ class Configuration implements ConfigurationInterface ; } - private function addNotifierSection(ArrayNodeDefinition $rootNode) + private function addNotifierSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('notifier') ->info('Notifier configuration') - ->{!class_exists(FullStack::class) && class_exists(Notifier::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/notifier', Notifier::class)}() ->fixXmlConfig('chatter_transport') ->children() ->arrayNode('chatter_transports') @@ -1833,13 +1847,13 @@ class Configuration implements ConfigurationInterface ; } - private function addRateLimiterSection(ArrayNodeDefinition $rootNode) + private function addRateLimiterSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('rate_limiter') ->info('Rate limiter configuration') - ->{!class_exists(FullStack::class) && class_exists(TokenBucketLimiter::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/rate-limiter', TokenBucketLimiter::class)}() ->fixXmlConfig('limiter') ->beforeNormalization() ->ifTrue(function ($v) { return \is_array($v) && !isset($v['limiters']) && !isset($v['limiter']); }) @@ -1901,13 +1915,13 @@ class Configuration implements ConfigurationInterface ; } - private function addUidSection(ArrayNodeDefinition $rootNode) + private function addUidSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone) { $rootNode ->children() ->arrayNode('uid') ->info('Uid configuration') - ->{class_exists(UuidFactory::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->{$enableIfStandalone('symfony/uid', UuidFactory::class)}() ->addDefaultsIfNotSet() ->children() ->enumNode('default_uuid_version') diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 158e5291ab..57a1d05793 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -14,6 +14,7 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; use Doctrine\Common\Annotations\AnnotationRegistry; use Doctrine\Common\Annotations\Reader; use Http\Client\HttpClient; +use phpDocumentor\Reflection\DocBlockFactoryInterface; use Psr\Cache\CacheItemPoolInterface; use Psr\Container\ContainerInterface as PsrContainerInterface; use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface; @@ -63,6 +64,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\Finder\Finder; use Symfony\Component\Form\ChoiceList\Factory\CachingFactoryDecorator; +use Symfony\Component\Form\Form; use Symfony\Component\Form\FormTypeExtensionInterface; use Symfony\Component\Form\FormTypeGuesserInterface; use Symfony\Component\Form\FormTypeInterface; @@ -149,6 +151,7 @@ use Symfony\Component\RateLimiter\RateLimiterFactory; use Symfony\Component\RateLimiter\Storage\CacheStorage; use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader; use Symfony\Component\Routing\Loader\AnnotationFileLoader; +use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Serializer\Encoder\DecoderInterface; @@ -167,6 +170,7 @@ use Symfony\Component\Uid\UuidV4; use Symfony\Component\Validator\ConstraintValidatorInterface; use Symfony\Component\Validator\Mapping\Loader\PropertyInfoLoader; use Symfony\Component\Validator\ObjectInitializerInterface; +use Symfony\Component\Validator\Validation; use Symfony\Component\WebLink\HttpHeaderSerializer; use Symfony\Component\Workflow; use Symfony\Component\Workflow\WorkflowInterface; @@ -200,6 +204,7 @@ class FrameworkExtension extends Extension private $mailerConfigEnabled = false; private $httpClientConfigEnabled = false; private $notifierConfigEnabled = false; + private $propertyAccessConfigEnabled = false; private $lockConfigEnabled = false; /** @@ -216,7 +221,7 @@ class FrameworkExtension extends Extension $loader->load('fragment_renderer.php'); $loader->load('error_renderer.php'); - if (interface_exists(PsrEventDispatcherInterface::class)) { + if (ContainerBuilder::willBeAvailable('psr/event-dispatcher', PsrEventDispatcherInterface::class, ['symfony/framework-bundle'])) { $container->setAlias(PsrEventDispatcherInterface::class, 'event_dispatcher'); } @@ -256,11 +261,11 @@ class FrameworkExtension extends Extension } // If the slugger is used but the String component is not available, we should throw an error - if (!interface_exists(SluggerInterface::class)) { + if (!ContainerBuilder::willBeAvailable('symfony/string', SluggerInterface::class, ['symfony/framework-bundle'])) { $container->register('slugger', 'stdClass') ->addError('You cannot use the "slugger" service since the String component is not installed. Try running "composer require symfony/string".'); } else { - if (!interface_exists(LocaleAwareInterface::class)) { + if (!ContainerBuilder::willBeAvailable('symfony/translation', LocaleAwareInterface::class, ['symfony/framework-bundle'])) { $container->register('slugger', 'stdClass') ->addError('You cannot use the "slugger" service since the Translation contracts are not installed. Try running "composer require symfony/translation".'); } @@ -329,19 +334,19 @@ class FrameworkExtension extends Extension } if (null === $config['csrf_protection']['enabled']) { - $config['csrf_protection']['enabled'] = $this->sessionConfigEnabled && !class_exists(FullStack::class) && interface_exists(CsrfTokenManagerInterface::class); + $config['csrf_protection']['enabled'] = $this->sessionConfigEnabled && !class_exists(FullStack::class) && ContainerBuilder::willBeAvailable('symfony/security-csrf', CsrfTokenManagerInterface::class, ['symfony/framework-bundle']); } $this->registerSecurityCsrfConfiguration($config['csrf_protection'], $container, $loader); if ($this->isConfigEnabled($container, $config['form'])) { - if (!class_exists(\Symfony\Component\Form\Form::class)) { + if (!class_exists(Form::class)) { throw new LogicException('Form support cannot be enabled as the Form component is not installed. Try running "composer require symfony/form".'); } $this->formConfigEnabled = true; $this->registerFormConfiguration($config, $container, $loader); - if (class_exists(\Symfony\Component\Validator\Validation::class)) { + if (ContainerBuilder::willBeAvailable('symfony/validator', Validation::class, ['symfony/framework-bundle', 'symfony/form'])) { $config['validation']['enabled'] = true; } else { $container->setParameter('validator.translation_domain', 'validators'); @@ -469,7 +474,7 @@ class FrameworkExtension extends Extension 'Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController', ]); - if (class_exists(MimeTypes::class)) { + if (ContainerBuilder::willBeAvailable('symfony/mime', MimeTypes::class, ['symfony/framework-bundle'])) { $loader->load('mime_type.php'); } @@ -602,7 +607,7 @@ class FrameworkExtension extends Extension $container->setParameter('form.type_extension.csrf.enabled', false); } - if (!class_exists(Translator::class)) { + if (!ContainerBuilder::willBeAvailable('symfony/translation', Translator::class, ['symfony/framework-bundle', 'symfony/form'])) { $container->removeDefinition('form.type_extension.upload.validator'); } if (!method_exists(CachingFactoryDecorator::class, 'reset')) { @@ -986,7 +991,7 @@ class FrameworkExtension extends Extension $container->getDefinition('routing.loader')->replaceArgument(2, ['_locale' => $enabledLocales]); } - if (!class_exists(ExpressionLanguage::class)) { + if (!ContainerBuilder::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/framework-bundle', 'symfony/routing'])) { $container->removeDefinition('router.expression_language_provider'); } @@ -1225,18 +1230,18 @@ class FrameworkExtension extends Extension $dirs = []; $transPaths = []; $nonExistingDirs = []; - if (class_exists(\Symfony\Component\Validator\Validation::class)) { - $r = new \ReflectionClass(\Symfony\Component\Validator\Validation::class); + if (ContainerBuilder::willBeAvailable('symfony/validator', Validation::class, ['symfony/framework-bundle', 'symfony/translation'])) { + $r = new \ReflectionClass(Validation::class); $dirs[] = $transPaths[] = \dirname($r->getFileName()).'/Resources/translations'; } - if (class_exists(\Symfony\Component\Form\Form::class)) { - $r = new \ReflectionClass(\Symfony\Component\Form\Form::class); + if (ContainerBuilder::willBeAvailable('symfony/form', Form::class, ['symfony/framework-bundle', 'symfony/translation'])) { + $r = new \ReflectionClass(Form::class); $dirs[] = $transPaths[] = \dirname($r->getFileName()).'/Resources/translations'; } - if (class_exists(\Symfony\Component\Security\Core\Exception\AuthenticationException::class)) { - $r = new \ReflectionClass(\Symfony\Component\Security\Core\Exception\AuthenticationException::class); + if (ContainerBuilder::willBeAvailable('symfony/security-core', AuthenticationException::class, ['symfony/framework-bundle', 'symfony/translation'])) { + $r = new \ReflectionClass(AuthenticationException::class); $dirs[] = $transPaths[] = \dirname($r->getFileName(), 2).'/Resources/translations'; } @@ -1336,7 +1341,7 @@ class FrameworkExtension extends Extension return; } - if (!class_exists(\Symfony\Component\Validator\Validation::class)) { + if (!class_exists(Validation::class)) { throw new LogicException('Validation support cannot be enabled as the Validator component is not installed. Try running "composer require symfony/validator".'); } @@ -1403,8 +1408,8 @@ class FrameworkExtension extends Extension $files['yaml' === $extension ? 'yml' : $extension][] = $path; }; - if (interface_exists(\Symfony\Component\Form\FormInterface::class)) { - $reflClass = new \ReflectionClass(\Symfony\Component\Form\FormInterface::class); + if (ContainerBuilder::willBeAvailable('symfony/form', Form::class, ['symfony/framework-bundle', 'symfony/validator'])) { + $reflClass = new \ReflectionClass(Form::class); $fileRecorder('xml', \dirname($reflClass->getFileName()).'/Resources/config/validation.xml'); } @@ -1466,7 +1471,7 @@ class FrameworkExtension extends Extension } if (!class_exists(\Doctrine\Common\Annotations\Annotation::class)) { - throw new LogicException('Annotations cannot be enabled as the Doctrine Annotation library is not installed.'); + throw new LogicException('Annotations cannot be enabled as the Doctrine Annotation library is not installed. Try running "composer require doctrine/annotations".'); } $loader->load('annotations.php'); @@ -1478,7 +1483,7 @@ class FrameworkExtension extends Extension if ('none' !== $config['cache']) { if (!class_exists(\Doctrine\Common\Cache\CacheProvider::class)) { - throw new LogicException('Annotations cannot be enabled as the Doctrine Cache library is not installed.'); + throw new LogicException('Annotations cannot be cached as the Doctrine Cache library is not installed. Try running "composer require doctrine/cache".'); } $cacheService = $config['cache']; @@ -1521,7 +1526,7 @@ class FrameworkExtension extends Extension private function registerPropertyAccessConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader) { - if (!class_exists(PropertyAccessor::class)) { + if (!$this->propertyAccessConfigEnabled = $this->isConfigEnabled($container, $config)) { return; } @@ -1570,7 +1575,7 @@ class FrameworkExtension extends Extension throw new InvalidArgumentException(sprintf('Invalid value "%s" set as "decryption_env_var": only "word" characters are allowed.', $config['decryption_env_var'])); } - if (class_exists(LazyString::class)) { + if (ContainerBuilder::willBeAvailable('symfony/string', LazyString::class, ['symfony/framework-bundle'])) { $container->getDefinition('secrets.decryption_key')->replaceArgument(1, $config['decryption_env_var']); } else { $container->getDefinition('secrets.vault')->replaceArgument(1, "%env({$config['decryption_env_var']})%"); @@ -1610,7 +1615,7 @@ class FrameworkExtension extends Extension $chainLoader = $container->getDefinition('serializer.mapping.chain_loader'); - if (!class_exists(PropertyAccessor::class)) { + if (!$this->propertyAccessConfigEnabled) { $container->removeAlias('serializer.property_accessor'); $container->removeDefinition('serializer.normalizer.object'); } @@ -1619,7 +1624,7 @@ class FrameworkExtension extends Extension $container->removeDefinition('serializer.encoder.yaml'); } - if (!class_exists(UnwrappingDenormalizer::class) || !class_exists(PropertyAccessor::class)) { + if (!class_exists(UnwrappingDenormalizer::class) || !$this->propertyAccessConfigEnabled) { $container->removeDefinition('serializer.denormalizer.unwrapping'); } @@ -1703,7 +1708,7 @@ class FrameworkExtension extends Extension $loader->load('property_info.php'); - if (interface_exists(\phpDocumentor\Reflection\DocBlockFactoryInterface::class)) { + if (ContainerBuilder::willBeAvailable('phpdocumentor/reflection-docblock', DocBlockFactoryInterface::class, ['symfony/framework-bundle', 'symfony/property-info'])) { $definition = $container->register('property_info.php_doc_extractor', 'Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor'); $definition->addTag('property_info.description_extractor', ['priority' => -1000]); $definition->addTag('property_info.type_extractor', ['priority' => -1001]); @@ -1784,19 +1789,19 @@ class FrameworkExtension extends Extension $loader->load('messenger.php'); - if (class_exists(AmqpTransportFactory::class)) { + if (ContainerBuilder::willBeAvailable('symfony/amqp-messenger', AmqpTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { $container->getDefinition('messenger.transport.amqp.factory')->addTag('messenger.transport_factory'); } - if (class_exists(RedisTransportFactory::class)) { + if (ContainerBuilder::willBeAvailable('symfony/redis-messenger', RedisTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { $container->getDefinition('messenger.transport.redis.factory')->addTag('messenger.transport_factory'); } - if (class_exists(AmazonSqsTransportFactory::class)) { + if (ContainerBuilder::willBeAvailable('symfony/amazon-sqs-messenger', AmazonSqsTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { $container->getDefinition('messenger.transport.sqs.factory')->addTag('messenger.transport_factory'); } - if (class_exists(BeanstalkdTransportFactory::class)) { + if (ContainerBuilder::willBeAvailable('symfony/beanstalkd-messenger', BeanstalkdTransportFactory::class, ['symfony/framework-bundle', 'symfony/messenger'])) { $container->getDefinition('messenger.transport.beanstalkd.factory')->addTag('messenger.transport_factory'); } @@ -2070,12 +2075,12 @@ class FrameworkExtension extends Extension unset($options['retry_failed']); $container->getDefinition('http_client')->setArguments([$options, $config['max_host_connections'] ?? 6]); - if (!$hasPsr18 = interface_exists(ClientInterface::class)) { + if (!$hasPsr18 = ContainerBuilder::willBeAvailable('psr/http-client', ClientInterface::class, ['symfony/framework-bundle', 'symfony/http-client'])) { $container->removeDefinition('psr18.http_client'); $container->removeAlias(ClientInterface::class); } - if (!interface_exists(HttpClient::class)) { + if (!ContainerBuilder::willBeAvailable('php-http/httplug', HttpClient::class, ['symfony/framework-bundle', 'symfony/http-client'])) { $container->removeDefinition(HttpClient::class); } @@ -2205,7 +2210,9 @@ class FrameworkExtension extends Extension ]; foreach ($classToServices as $class => $service) { - if (!class_exists($class)) { + $package = substr($service, \strlen('mailer.transport_factory.')); + + if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-mailer', 'gmail' === $package ? 'google' : $package), $class, ['symfony/framework-bundle', 'symfony/mailer'])) { $container->removeDefinition($service); } } @@ -2302,16 +2309,26 @@ class FrameworkExtension extends Extension ClickatellTransportFactory::class => 'notifier.transport_factory.clickatell', ]; + $parentPackages = ['symfony/framework-bundle', 'symfony/notifier']; + foreach ($classToServices as $class => $service) { - if (!class_exists($class)) { + switch ($package = substr($service, \strlen('notifier.transport_factory.'))) { + case 'freemobile': $package = 'free-mobile'; break; + case 'googlechat': $package = 'google-chat'; break; + case 'linkedin': $package = 'linked-in'; break; + case 'ovhcloud': $package = 'ovh-cloud'; break; + case 'rocketchat': $package = 'rocket-chat'; break; + } + + if (!ContainerBuilder::willBeAvailable(sprintf('symfony/%s-notifier', $package), $class, $parentPackages)) { $container->removeDefinition($service); } } - if (class_exists(MercureTransportFactory::class) && class_exists(MercureBundle::class)) { + if (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages) && ContainerBuilder::willBeAvailable('symfony/mercure-bundle', MercureBundle::class, $parentPackages)) { $container->getDefinition($classToServices[MercureTransportFactory::class]) ->replaceArgument('$publisherLocator', new ServiceLocatorArgument(new TaggedIteratorArgument('mercure.publisher', null, null, true))); - } elseif (class_exists(MercureTransportFactory::class)) { + } elseif (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages)) { $container->removeDefinition($classToServices[MercureTransportFactory::class]); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index bab22e1d23..20e8720eb3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -443,6 +443,7 @@ class ConfigurationTest extends TestCase 'mapping' => ['paths' => []], ], 'property_access' => [ + 'enabled' => true, 'magic_call' => false, 'magic_get' => true, 'magic_set' => true, @@ -566,7 +567,7 @@ class ConfigurationTest extends TestCase 'limiters' => [], ], 'uid' => [ - 'enabled' => class_exists(UuidFactory::class), + 'enabled' => !class_exists(FullStack::class) && class_exists(UuidFactory::class), 'default_uuid_version' => 6, 'name_based_uuid_version' => 5, 'time_based_uuid_version' => 6, diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 5c782a25bf..23b20c1aed 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\SecurityBundle\DependencyInjection; +use Symfony\Bridge\Twig\Extension\LogoutUrlExtension; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FirewallListenerFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory; @@ -31,6 +32,7 @@ use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\PasswordHasher\Hasher\NativePasswordHasher; use Symfony\Component\PasswordHasher\Hasher\Pbkdf2PasswordHasher; @@ -42,7 +44,6 @@ use Symfony\Component\Security\Core\Encoder\SodiumPasswordEncoder; use Symfony\Component\Security\Core\User\ChainUserProvider; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\Event\CheckPassportEvent; -use Twig\Extension\AbstractExtension; /** * SecurityExtension. @@ -130,7 +131,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface $loader->load('security_legacy.php'); } - if (class_exists(AbstractExtension::class)) { + if ($container::willBeAvailable('symfony/twig-bridge', LogoutUrlExtension::class, ['symfony/security-bundle'])) { $loader->load('templating_twig.php'); } @@ -141,7 +142,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface $loader->load('security_debug.php'); } - if (!class_exists(\Symfony\Component\ExpressionLanguage\ExpressionLanguage::class)) { + if (!$container::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/security-bundle'])) { $container->removeDefinition('security.expression_language'); $container->removeDefinition('security.access.expression_voter'); } @@ -982,8 +983,8 @@ class SecurityExtension extends Extension implements PrependExtensionInterface return $this->expressions[$id]; } - if (!class_exists(\Symfony\Component\ExpressionLanguage\ExpressionLanguage::class)) { - throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + if (!$container::willBeAvailable('symfony/expression-language', ExpressionLanguage::class, ['symfony/security-bundle'])) { + throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); } $container diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 82b2399602..05688dd016 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -19,7 +19,7 @@ "php": ">=7.2.5", "ext-xml": "*", "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^5.2", + "symfony/dependency-injection": "^5.3", "symfony/deprecation-contracts": "^2.1", "symfony/event-dispatcher": "^5.1", "symfony/http-kernel": "^5.0", diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php index eb6eecc95f..ae3f9df3be 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php @@ -11,10 +11,14 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler; +use Symfony\Component\Asset\Packages; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Workflow\Workflow; +use Symfony\Component\Yaml\Yaml; /** * @author Jean-François Simon @@ -23,19 +27,19 @@ class ExtensionPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { - if (!class_exists(\Symfony\Component\Asset\Packages::class)) { + if (!$container::willBeAvailable('symfony/asset', Packages::class, ['symfony/twig-bundle'])) { $container->removeDefinition('twig.extension.assets'); } - if (!class_exists(\Symfony\Component\ExpressionLanguage\Expression::class)) { + if (!$container::willBeAvailable('symfony/expression-language', Expression::class, ['symfony/twig-bundle'])) { $container->removeDefinition('twig.extension.expression'); } - if (!interface_exists(\Symfony\Component\Routing\Generator\UrlGeneratorInterface::class)) { + if (!$container::willBeAvailable('symfony/routing', UrlGeneratorInterface::class, ['symfony/twig-bundle'])) { $container->removeDefinition('twig.extension.routing'); } - if (!class_exists(\Symfony\Component\Yaml\Yaml::class)) { + if (!$container::willBeAvailable('symfony/yaml', Yaml::class, ['symfony/twig-bundle'])) { $container->removeDefinition('twig.extension.yaml'); } @@ -111,7 +115,7 @@ class ExtensionPass implements CompilerPassInterface $container->getDefinition('twig.extension.expression')->addTag('twig.extension'); } - if (!class_exists(Workflow::class) || !$container->has('workflow.registry')) { + if (!$container::willBeAvailable('symfony/workflow', Workflow::class, ['symfony/twig-bundle']) || !$container->has('workflow.registry')) { $container->removeDefinition('workflow.twig_extension'); } else { $container->getDefinition('workflow.twig_extension')->addTag('twig.extension'); diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php index 5ccc9a1a04..20095eb45a 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php @@ -17,6 +17,7 @@ use Symfony\Component\Console\Application; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Form\Form; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\Mailer\Mailer; use Symfony\Component\Translation\Translator; @@ -37,19 +38,19 @@ class TwigExtension extends Extension $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('twig.php'); - if (class_exists(\Symfony\Component\Form\Form::class)) { + if ($container::willBeAvailable('symfony/form', Form::class, ['symfony/twig-bundle'])) { $loader->load('form.php'); } - if (class_exists(Application::class)) { + if ($container::willBeAvailable('symfony/console', Application::class, ['symfony/twig-bundle'])) { $loader->load('console.php'); } - if (class_exists(Mailer::class)) { + if ($container::willBeAvailable('symfony/mailer', Mailer::class, ['symfony/twig-bundle'])) { $loader->load('mailer.php'); } - if (!class_exists(Translator::class)) { + if (!$container::willBeAvailable('symfony/translation', Translator::class, ['symfony/twig-bundle'])) { $container->removeDefinition('twig.translation.extractor'); } diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index 7c5c15fbce..6bc5bf6179 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -27,7 +27,7 @@ "require-dev": { "symfony/asset": "^4.4|^5.0", "symfony/stopwatch": "^4.4|^5.0", - "symfony/dependency-injection": "^5.2", + "symfony/dependency-injection": "^5.3", "symfony/expression-language": "^4.4|^5.0", "symfony/finder": "^4.4|^5.0", "symfony/form": "^4.4|^5.0", @@ -40,7 +40,7 @@ "doctrine/cache": "~1.0" }, "conflict": { - "symfony/dependency-injection": "<5.2", + "symfony/dependency-injection": "<5.3", "symfony/framework-bundle": "<5.0", "symfony/translation": "<5.0" }, diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index c08e7cb75d..1e45c669e8 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -9,6 +9,7 @@ CHANGELOG * Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8 * Add autoconfigurable attributes * Add support for per-env configuration in loaders + * Add `ContainerBuilder::willBeAvailable()` to help with conditional configuration 5.2.0 ----- diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php index 85478da5e9..4881e4fe10 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php @@ -198,7 +198,7 @@ abstract class AbstractRecursivePass implements CompilerPassInterface { if (null === $this->expressionLanguage) { if (!class_exists(ExpressionLanguage::class)) { - throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); } $providers = $this->container->getExpressionLanguageProviders(); diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index a3be650343..6c266bae0a 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection; +use Composer\InstalledVersions; use Psr\Container\ContainerInterface as PsrContainerInterface; use Symfony\Component\Config\Resource\ClassExistenceResource; use Symfony\Component\Config\Resource\ComposerResource; @@ -1467,6 +1468,34 @@ class ContainerBuilder extends Container implements TaggedContainerInterface $this->getCompiler()->log($pass, $this->resolveEnvPlaceholders($message)); } + /** + * Checks whether a class is available and will remain available in the "no-dev" mode of Composer. + * + * When parent packages are provided and if any of them is in dev-only mode, + * the class will be considered available even if it is also in dev-only mode. + */ + final public static function willBeAvailable(string $package, string $class, array $parentPackages): bool + { + if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) { + return false; + } + + if (!class_exists(InstalledVersions::class) || !InstalledVersions::isInstalled($package) || InstalledVersions::isInstalled($package, false)) { + return true; + } + + // the package is installed but in dev-mode only, check if this applies to one of the parent packages too + + $rootPackage = InstalledVersions::getRootPackage()['name'] ?? ''; + foreach ($parentPackages as $parentPackage) { + if ($rootPackage === $parentPackage || (InstalledVersions::isInstalled($parentPackage) && !InstalledVersions::isInstalled($parentPackage, false))) { + return true; + } + } + + return false; + } + /** * Gets removed binding ids. *