diff --git a/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php index e3a724cd2448a..3ed12fc3f692d 100644 --- a/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php @@ -13,7 +13,6 @@ 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 @@ public function getConfigTreeBuilder() ->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 a2bb4d8e2638e..ee44ac7d56a0e 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\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\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 @@ public function getConfigTreeBuilder() ->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 @@ public function getConfigTreeBuilder() $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 @@ private function addCsrfSection(ArrayNodeDefinition $rootNode) ; } - 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 @@ private function addRequestSection(ArrayNodeDefinition $rootNode) ; } - 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 @@ private function addAssetsSection(ArrayNodeDefinition $rootNode) ; } - 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 @@ private function addTranslatorSection(ArrayNodeDefinition $rootNode) ; } - 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 @@ private function addValidationSection(ArrayNodeDefinition $rootNode) ; } - 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 @@ private function addAnnotationsSection(ArrayNodeDefinition $rootNode) ; } - 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 @@ private function addSerializerSection(ArrayNodeDefinition $rootNode) ; } - 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 @@ private function addPropertyAccessSection(ArrayNodeDefinition $rootNode) ; } - 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 @@ private function addCacheSection(ArrayNodeDefinition $rootNode) ->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 @@ private function addPhpErrorsSection(ArrayNodeDefinition $rootNode) ; } - 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 @@ private function addLockSection(ArrayNodeDefinition $rootNode) ; } - 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 @@ private function addRobotsIndexSection(ArrayNodeDefinition $rootNode) ; } - 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 @@ private function addHttpClientRetrySection() ; } - 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 @@ private function addMailerSection(ArrayNodeDefinition $rootNode) ; } - 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 @@ private function addNotifierSection(ArrayNodeDefinition $rootNode) ; } - 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 @@ private function addRateLimiterSection(ArrayNodeDefinition $rootNode) ; } - 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 158e5291abe7b..57a1d057937f2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -14,6 +14,7 @@ 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\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\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\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 @@ public function load(array $configs, ContainerBuilder $container) $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 @@ public function load(array $configs, ContainerBuilder $container) } // 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 @@ public function load(array $configs, ContainerBuilder $container) } 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 @@ public function load(array $configs, ContainerBuilder $container) '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 @@ private function registerFormConfiguration(array $config, ContainerBuilder $cont $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 @@ private function registerRouterConfiguration(array $config, ContainerBuilder $co $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 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder $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 @@ private function registerValidationConfiguration(array $config, ContainerBuilder 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 @@ private function registerValidatorMapping(ContainerBuilder $container, array $co $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 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde } 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 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde 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 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde 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 @@ private function registerSecretsConfiguration(array $config, ContainerBuilder $c 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 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $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 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $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 @@ private function registerPropertyInfoConfiguration(ContainerBuilder $container, $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 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder $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 @@ private function registerHttpClientConfiguration(array $config, ContainerBuilder 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 @@ private function registerMailerConfiguration(array $config, ContainerBuilder $co ]; 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 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ 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 bab22e1d23769..20e8720eb396f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -443,6 +443,7 @@ protected static function getBundleDefaultConfig() 'mapping' => ['paths' => []], ], 'property_access' => [ + 'enabled' => true, 'magic_call' => false, 'magic_get' => true, 'magic_set' => true, @@ -566,7 +567,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor '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 5c782a25bf87e..23b20c1aedc45 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\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\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 @@ public function load(array $configs, ContainerBuilder $container) $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 @@ public function load(array $configs, ContainerBuilder $container) $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 @@ private function createExpression(ContainerBuilder $container, string $expressio 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 82b239960200e..05688dd016c2b 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 eb6eecc95fac8..ae3f9df3befd0 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 @@ public function process(ContainerBuilder $container) $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 5ccc9a1a04c3a..20095eb45a79d 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php @@ -17,6 +17,7 @@ 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 @@ public function load(array $configs, ContainerBuilder $container) $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 7c5c15fbce4d5..6bc5bf61794bd 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 c08e7cb75df25..1e45c669e89e4 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 85478da5e9b16..4881e4fe10320 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AbstractRecursivePass.php @@ -198,7 +198,7 @@ private function getExpressionLanguage(): ExpressionLanguage { 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 a3be650343152..6c266bae0a9b8 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 @@ public function log(CompilerPassInterface $pass, string $message) $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. * pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy