From 506bba8e6851785b6cae192121a359121d3d8045 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 26 Jan 2017 16:09:57 +0100 Subject: [PATCH] [DI][Config] Add ContainerBuilder::getClassExists() for checking and tracking class, interface or trait existence --- .../FrameworkExtension.php | 77 +++++++++++-------- .../DependencyInjection/SecurityExtension.php | 12 +-- .../Compiler/ExceptionListenerPass.php | 4 +- .../Compiler/ExtensionPass.php | 24 +++--- .../DependencyInjection/TwigExtension.php | 9 ++- src/Symfony/Bundle/TwigBundle/composer.json | 7 +- src/Symfony/Component/Config/CHANGELOG.md | 5 ++ .../Resource/ClassExistenceResource.php | 4 +- .../Config/Resource/FileExistenceResource.php | 9 ++- .../Resource/ClassExistenceResourceTest.php | 3 +- .../DependencyInjection/CHANGELOG.md | 1 + .../Compiler/FactoryReturnTypePass.php | 4 + .../DependencyInjection/ContainerBuilder.php | 40 +++++++++- .../DependencyInjection/Dumper/PhpDumper.php | 4 +- .../DependencyInjection/composer.json | 3 +- 15 files changed, 141 insertions(+), 65 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 1b9ff83f026dc..ee25bec15a730 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -11,8 +11,11 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection; +use Doctrine\Common\Annotations\Annotation; use Doctrine\Common\Annotations\Reader; +use phpDocumentor\Reflection\DocBlockFactoryInterface; use Symfony\Bridge\Monolog\Processor\DebugProcessor; +use Symfony\Component\Asset\Package; use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\DependencyInjection\Alias; @@ -26,16 +29,21 @@ use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\Resource\DirectoryResource; use Symfony\Component\Finder\Finder; +use Symfony\Component\Form\Form; +use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\Config\FileLocator; -use Symfony\Component\Config\Resource\ClassExistenceResource; use Symfony\Component\PropertyAccess\PropertyAccessor; +use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Serializer\Encoder\YamlEncoder; use Symfony\Component\Serializer\Encoder\CsvEncoder; use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory; use Symfony\Component\Serializer\Normalizer\DataUriNormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer; +use Symfony\Component\Templating\PhpEngine; +use Symfony\Component\Translation\Translator; +use Symfony\Component\Validator\Validation; use Symfony\Component\Workflow; use Symfony\Component\Workflow\SupportStrategy\ClassInstanceSupportStrategy; use Symfony\Component\Yaml\Yaml; @@ -85,8 +93,7 @@ public function load(array $configs, ContainerBuilder $container) $loader->load('fragment_renderer.xml'); - $container->addResource(new ClassExistenceResource(Application::class)); - if (class_exists(Application::class)) { + if ($container->classExists(Application::class)) { $loader->load('console.xml'); } @@ -105,16 +112,18 @@ public function load(array $configs, ContainerBuilder $container) // default in the Form and Validator component). If disabled, an identity // translator will be used and everything will still work as expected. if ($this->isConfigEnabled($container, $config['translator']) || $this->isConfigEnabled($container, $config['form']) || $this->isConfigEnabled($container, $config['validation'])) { - if (!class_exists('Symfony\Component\Translation\Translator') && $this->isConfigEnabled($container, $config['translator'])) { - throw new LogicException('Translation support cannot be enabled as the Translation component is not installed.'); - } + if (!$container->classExists(Translator::class)) { + if ($this->isConfigEnabled($container, $config['translator'])) { + throw new LogicException('Translation support cannot be enabled as the Translation component is not installed.'); + } - if (!class_exists('Symfony\Component\Translation\Translator') && $this->isConfigEnabled($container, $config['form'])) { - throw new LogicException('Form support cannot be enabled as the Translation component is not installed.'); - } + if ($this->isConfigEnabled($container, $config['form'])) { + throw new LogicException('Form support cannot be enabled as the Translation component is not installed.'); + } - if (!class_exists('Symfony\Component\Translation\Translator') && $this->isConfigEnabled($container, $config['validation'])) { - throw new LogicException('Validation support cannot be enabled as the Translation component is not installed.'); + if ($this->isConfigEnabled($container, $config['validation'])) { + throw new LogicException('Validation support cannot be enabled as the Translation component is not installed.'); + } } $loader->load('identity_translator.xml'); @@ -163,7 +172,7 @@ public function load(array $configs, ContainerBuilder $container) $this->registerFormConfiguration($config, $container, $loader); $config['validation']['enabled'] = true; - if (!class_exists('Symfony\Component\Validator\Validation')) { + if (!$container->classExists(Validation::class)) { throw new LogicException('The Validator component is required to use the Form component.'); } } @@ -171,7 +180,7 @@ public function load(array $configs, ContainerBuilder $container) $this->registerSecurityCsrfConfiguration($config['csrf_protection'], $container, $loader); if ($this->isConfigEnabled($container, $config['assets'])) { - if (!class_exists('Symfony\Component\Asset\Package')) { + if (!$container->classExists(Package::class)) { throw new LogicException('Asset support cannot be enabled as the Asset component is not installed.'); } @@ -179,7 +188,7 @@ public function load(array $configs, ContainerBuilder $container) } if ($this->isConfigEnabled($container, $config['templating'])) { - if (!class_exists('Symfony\Component\Templating\PhpEngine')) { + if (!$container->classExists(PhpEngine::class)) { throw new LogicException('Templating support cannot be enabled as the Templating component is not installed.'); } @@ -525,7 +534,7 @@ private function registerDebugConfiguration(array $config, ContainerBuilder $con $definition->replaceArgument(4, $debug); $definition->replaceArgument(6, $debug); - if ($debug && class_exists(DebugProcessor::class)) { + if ($debug && $container->classExists(DebugProcessor::class)) { $definition = new Definition(DebugProcessor::class); $definition->setPublic(false); $container->setDefinition('debug.log_processor', $definition); @@ -860,18 +869,18 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder // Discover translation directories $dirs = array(); - if (class_exists('Symfony\Component\Validator\Validation')) { - $r = new \ReflectionClass('Symfony\Component\Validator\Validation'); + if ($container->classExists(Validation::class)) { + $r = new \ReflectionClass(Validation::class); $dirs[] = dirname($r->getFileName()).'/Resources/translations'; } - if (class_exists('Symfony\Component\Form\Form')) { - $r = new \ReflectionClass('Symfony\Component\Form\Form'); + if ($container->classExists(Form::class)) { + $r = new \ReflectionClass(Form::class); $dirs[] = dirname($r->getFileName()).'/Resources/translations'; } - if (class_exists('Symfony\Component\Security\Core\Exception\AuthenticationException')) { - $r = new \ReflectionClass('Symfony\Component\Security\Core\Exception\AuthenticationException'); + if ($container->classExists(AuthenticationException::class)) { + $r = new \ReflectionClass(AuthenticationException::class); $dirs[] = dirname(dirname($r->getFileName())).'/Resources/translations'; } @@ -944,7 +953,7 @@ private function registerValidationConfiguration(array $config, ContainerBuilder return; } - if (!class_exists('Symfony\Component\Validator\Validation')) { + if (!$container->classExists(Validation::class)) { throw new LogicException('Validation support cannot be enabled as the Validator component is not installed.'); } @@ -999,8 +1008,8 @@ private function registerValidationConfiguration(array $config, ContainerBuilder private function getValidatorMappingFiles(ContainerBuilder $container, array &$files) { - if (interface_exists('Symfony\Component\Form\FormInterface')) { - $reflClass = new \ReflectionClass('Symfony\Component\Form\FormInterface'); + if ($container->classExists(FormInterface::class)) { + $reflClass = new \ReflectionClass(FormInterface::class); $files['xml'][] = $file = dirname($reflClass->getFileName()).'/Resources/config/validation.xml'; $container->addResource(new FileResource($file)); } @@ -1057,7 +1066,7 @@ private function registerAnnotationsConfiguration(array $config, ContainerBuilde return; } - if (!class_exists('Doctrine\Common\Annotations\Annotation')) { + if (!$container->classExists(Annotation::class)) { throw new LogicException('Annotations cannot be enabled as the Doctrine Annotation library is not installed.'); } @@ -1129,7 +1138,7 @@ private function registerSecurityCsrfConfiguration(array $config, ContainerBuild return; } - if (!class_exists('Symfony\Component\Security\Csrf\CsrfToken')) { + if (!$container->classExists('Symfony\Component\Security\Csrf\CsrfToken')) { throw new LogicException('CSRF support cannot be enabled as the Security CSRF component is not installed.'); } @@ -1150,34 +1159,34 @@ private function registerSecurityCsrfConfiguration(array $config, ContainerBuild */ private function registerSerializerConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) { - if (class_exists('Symfony\Component\Serializer\Normalizer\DataUriNormalizer')) { + if ($container->classExists(DataUriNormalizer::class)) { // Run after serializer.normalizer.object $definition = $container->register('serializer.normalizer.data_uri', DataUriNormalizer::class); $definition->setPublic(false); $definition->addTag('serializer.normalizer', array('priority' => -920)); } - if (class_exists('Symfony\Component\Serializer\Normalizer\DateTimeNormalizer')) { + if ($container->classExists(DateTimeNormalizer::class)) { // Run before serializer.normalizer.object $definition = $container->register('serializer.normalizer.datetime', DateTimeNormalizer::class); $definition->setPublic(false); $definition->addTag('serializer.normalizer', array('priority' => -910)); } - if (class_exists('Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer')) { + if ($container->classExists(JsonSerializableNormalizer::class)) { // Run before serializer.normalizer.object $definition = $container->register('serializer.normalizer.json_serializable', JsonSerializableNormalizer::class); $definition->setPublic(false); $definition->addTag('serializer.normalizer', array('priority' => -900)); } - if (class_exists(YamlEncoder::class) && defined('Symfony\Component\Yaml\Yaml::DUMP_OBJECT')) { + if ($container->classExists(YamlEncoder::class) && defined('Symfony\Component\Yaml\Yaml::DUMP_OBJECT')) { $definition = $container->register('serializer.encoder.yaml', YamlEncoder::class); $definition->setPublic(false); $definition->addTag('serializer.encoder'); } - if (class_exists(CsvEncoder::class)) { + if ($container->classExists(CsvEncoder::class)) { $definition = $container->register('serializer.encoder.csv', CsvEncoder::class); $definition->setPublic(false); $definition->addTag('serializer.encoder'); @@ -1252,7 +1261,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $container->getDefinition('serializer.mapping.class_metadata_factory')->replaceArgument( 1, new Reference($config['cache']) ); - } elseif (!$container->getParameter('kernel.debug') && class_exists(CacheClassMetadataFactory::class)) { + } elseif (!$container->getParameter('kernel.debug') && $container->classExists(CacheClassMetadataFactory::class)) { $cacheMetadataFactory = new Definition( CacheClassMetadataFactory::class, array( @@ -1282,7 +1291,7 @@ private function registerPropertyInfoConfiguration(array $config, ContainerBuild { $loader->load('property_info.xml'); - if (interface_exists('phpDocumentor\Reflection\DocBlockFactoryInterface')) { + if ($container->classExists(DocBlockFactoryInterface::class)) { $definition = $container->register('property_info.php_doc_extractor', 'Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor'); $definition->addTag('property_info.description_extractor', array('priority' => -1000)); $definition->addTag('property_info.type_extractor', array('priority' => -1001)); @@ -1324,7 +1333,7 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con $container->setDefinition($name, $definition); } - if (method_exists(PropertyAccessor::class, 'createCache')) { + if ($container->classExists(PropertyAccessor::class) && method_exists(PropertyAccessor::class, 'createCache')) { $propertyAccessDefinition = $container->register('cache.property_access', AdapterInterface::class); $propertyAccessDefinition->setPublic(false); $propertyAccessDefinition->setFactory(array(PropertyAccessor::class, 'createCache')); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 51de15b2151b0..2d588a274dab9 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -21,6 +21,8 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Config\FileLocator; +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\Security\Acl\Model\AclInterface; use Symfony\Component\Security\Core\Authorization\ExpressionLanguage; /** @@ -70,7 +72,7 @@ public function load(array $configs, ContainerBuilder $container) $loader->load('security_debug.xml'); } - if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + if (!$container->classExists(Expression::class)) { $container->removeDefinition('security.expression_language'); $container->removeDefinition('security.access.expression_voter'); } @@ -121,7 +123,7 @@ public function load(array $configs, ContainerBuilder $container) private function aclLoad($config, ContainerBuilder $container) { - if (!interface_exists('Symfony\Component\Security\Acl\Model\AclInterface')) { + if (!$container->classExists(AclInterface::class)) { throw new \LogicException('You must install symfony/security-acl in order to use the ACL functionality.'); } @@ -643,7 +645,7 @@ private function createExpression($container, $expression) ->register($id, 'Symfony\Component\ExpressionLanguage\SerializedParsedExpression') ->setPublic(false) ->addArgument($expression) - ->addArgument(serialize($this->getExpressionLanguage()->parse($expression, array('token', 'user', 'object', 'roles', 'request', 'trust_resolver'))->getNodes())) + ->addArgument(serialize($this->getExpressionLanguage($container)->parse($expression, array('token', 'user', 'object', 'roles', 'request', 'trust_resolver'))->getNodes())) ; return $this->expressions[$id] = new Reference($id); @@ -708,10 +710,10 @@ public function getConfiguration(array $config, ContainerBuilder $container) return new MainConfiguration($this->factories, $this->userProviderFactories); } - private function getExpressionLanguage() + private function getExpressionLanguage($container) { if (null === $this->expressionLanguage) { - if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + if (!$container->hasDefinition('security.expression_language')) { throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); } $this->expressionLanguage = new ExpressionLanguage(); diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExceptionListenerPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExceptionListenerPass.php index b7ebb2a406512..15e2a88667a11 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExceptionListenerPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExceptionListenerPass.php @@ -11,8 +11,10 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler; +use Symfony\Component\Debug\Exception\FlattenException; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Registers the Twig exception listener if Twig is registered as a templating engine. @@ -28,7 +30,7 @@ public function process(ContainerBuilder $container) } // register the exception controller only if Twig is enabled and required dependencies do exist - if (!class_exists('Symfony\Component\Debug\Exception\FlattenException') || !interface_exists('Symfony\Component\EventDispatcher\EventSubscriberInterface')) { + if (!$container->classExists(FlattenException::class) || !$container->classExists(EventSubscriberInterface::class)) { $container->removeDefinition('twig.exception_listener'); } elseif ($container->hasParameter('templating.engines')) { $engines = $container->getParameter('templating.engines'); diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php index 1491b04ac0545..75b7a7a0340e5 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php @@ -11,13 +11,15 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler; -use Symfony\Component\Config\Resource\ClassExistenceResource; +use Symfony\Component\Asset\Packages; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\Translation\TranslatorInterface; use Symfony\Component\Yaml\Parser as YamlParser; /** @@ -27,22 +29,23 @@ class ExtensionPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { - if (!class_exists('Symfony\Component\Asset\Packages')) { + if (!$container->classExists(Packages::class)) { $container->removeDefinition('twig.extension.assets'); } - if (!class_exists('Symfony\Component\ExpressionLanguage\Expression')) { + if (!$container->classExists(ExpressionLanguage::class)) { $container->removeDefinition('twig.extension.expression'); } - if (!interface_exists('Symfony\Component\Routing\Generator\UrlGeneratorInterface')) { + if (!$container->classExists(UrlGeneratorInterface::class)) { $container->removeDefinition('twig.extension.routing'); } - if (!interface_exists('Symfony\Component\Translation\TranslatorInterface')) { + + if (!$container->classExists(TranslatorInterface::class)) { $container->removeDefinition('twig.extension.trans'); } - if (!class_exists('Symfony\Component\Yaml\Yaml')) { + if (!$container->classExists(YamlParser::class)) { $container->removeDefinition('twig.extension.yaml'); } @@ -101,18 +104,15 @@ public function process(ContainerBuilder $container) $container->getDefinition('twig.extension.assets')->addTag('twig.extension'); } - $container->addResource(new ClassExistenceResource(YamlParser::class)); - if (class_exists(YamlParser::class)) { + if ($container->hasDefinition('twig.extension.yaml')) { $container->getDefinition('twig.extension.yaml')->addTag('twig.extension'); } - $container->addResource(new ClassExistenceResource(Stopwatch::class)); - if (class_exists(Stopwatch::class)) { + if ($container->classExists(Stopwatch::class)) { $container->getDefinition('twig.extension.debug.stopwatch')->addTag('twig.extension'); } - $container->addResource(new ClassExistenceResource(ExpressionLanguage::class)); - if (class_exists(ExpressionLanguage::class)) { + if ($container->hasDefinition('twig.extension.expression')) { $container->getDefinition('twig.extension.expression')->addTag('twig.extension'); } } diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php index 29a93cfe8ac2d..6593ea32fb9f4 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/TwigExtension.php @@ -16,7 +16,10 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\Form\Form; use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\Templating\EngineInterface; +use Symfony\Component\Translation\TranslatorInterface; /** * TwigExtension. @@ -37,15 +40,15 @@ public function load(array $configs, ContainerBuilder $container) $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('twig.xml'); - if (class_exists('Symfony\Component\Form\Form')) { + if ($container->classExists(Form::class)) { $loader->load('form.xml'); } - if (interface_exists('Symfony\Component\Templating\EngineInterface')) { + if ($container->classExists(EngineInterface::class)) { $loader->load('templating.xml'); } - if (!interface_exists('Symfony\Component\Translation\TranslatorInterface')) { + if (!$container->classExists(TranslatorInterface::class)) { $container->removeDefinition('twig.translation.extractor'); } diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index b0189f74d1c86..6301a9a900b57 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": ">=5.5.9", - "symfony/config": "~3.2", + "symfony/config": "~3.3", "symfony/twig-bridge": "^3.2.1", "symfony/http-foundation": "~2.8|~3.0", "symfony/http-kernel": "~2.8.16|~3.1.9|^3.2.2", @@ -26,7 +26,7 @@ "require-dev": { "symfony/asset": "~2.8|~3.0", "symfony/stopwatch": "~2.8|~3.0", - "symfony/dependency-injection": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3", "symfony/expression-language": "~2.8|~3.0", "symfony/finder": "~2.8|~3.0", "symfony/form": "~2.8|~3.0", @@ -36,6 +36,9 @@ "symfony/framework-bundle": "^3.2.2", "doctrine/annotations": "~1.0" }, + "conflict": { + "symfony/dependency-injection": "<3.3" + }, "autoload": { "psr-4": { "Symfony\\Bundle\\TwigBundle\\": "" }, "exclude-from-classmap": [ diff --git a/src/Symfony/Component/Config/CHANGELOG.md b/src/Symfony/Component/Config/CHANGELOG.md index b752df6fe2348..7eaa05bce3630 100644 --- a/src/Symfony/Component/Config/CHANGELOG.md +++ b/src/Symfony/Component/Config/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +3.3.0 +----- + + * made `ClassExistenceResource` to work with interfaces and traits + 3.0.0 ----- diff --git a/src/Symfony/Component/Config/Resource/ClassExistenceResource.php b/src/Symfony/Component/Config/Resource/ClassExistenceResource.php index 8a9df906a643d..0085b48ba5569 100644 --- a/src/Symfony/Component/Config/Resource/ClassExistenceResource.php +++ b/src/Symfony/Component/Config/Resource/ClassExistenceResource.php @@ -30,7 +30,7 @@ class ClassExistenceResource implements SelfCheckingResourceInterface, \Serializ public function __construct($resource) { $this->resource = $resource; - $this->exists = class_exists($resource); + $this->exists = class_exists($resource) || interface_exists($resource, false) || trait_exists($resource, false); } /** @@ -54,7 +54,7 @@ public function getResource() */ public function isFresh($timestamp) { - return class_exists($this->resource) === $this->exists; + return (class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false)) === $this->exists; } /** diff --git a/src/Symfony/Component/Config/Resource/FileExistenceResource.php b/src/Symfony/Component/Config/Resource/FileExistenceResource.php index 349402edf0494..52b252e0234f3 100644 --- a/src/Symfony/Component/Config/Resource/FileExistenceResource.php +++ b/src/Symfony/Component/Config/Resource/FileExistenceResource.php @@ -33,7 +33,6 @@ class FileExistenceResource implements SelfCheckingResourceInterface, \Serializa public function __construct($resource) { $this->resource = (string) $resource; - $this->exists = file_exists($resource); } /** @@ -57,6 +56,10 @@ public function getResource() */ public function isFresh($timestamp) { + if (null === $this->exists) { + $this->exists = file_exists($this->resource); + } + return file_exists($this->resource) === $this->exists; } @@ -65,6 +68,10 @@ public function isFresh($timestamp) */ public function serialize() { + if (null === $this->exists) { + $this->exists = file_exists($this->resource); + } + return serialize(array($this->resource, $this->exists)); } diff --git a/src/Symfony/Component/Config/Tests/Resource/ClassExistenceResourceTest.php b/src/Symfony/Component/Config/Tests/Resource/ClassExistenceResourceTest.php index 20d5b3308d3fc..66408b20f77b9 100644 --- a/src/Symfony/Component/Config/Tests/Resource/ClassExistenceResourceTest.php +++ b/src/Symfony/Component/Config/Tests/Resource/ClassExistenceResourceTest.php @@ -36,7 +36,7 @@ public function testIsFreshWhenClassDoesNotExist() eval(<<assertTrue($res->isFresh(time())); + $this->assertTrue(class_exists('Symfony\Component\Config\Tests\Resource\ClassExistenceResourceTest', false)); } } diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index ad32230ff59fe..a09fe10d69f45 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 3.3.0 ----- + * added `ContainerBuilder::classExists()` for checking and tracking class, interface or trait existence * added support for omitting the factory class name in a service definition if the definition class is set * deprecated case insensitivity of service identifiers * added "iterator" argument type for lazy iteration over a set of values and services diff --git a/src/Symfony/Component/DependencyInjection/Compiler/FactoryReturnTypePass.php b/src/Symfony/Component/DependencyInjection/Compiler/FactoryReturnTypePass.php index 351ba365766a3..1629097bc2e8c 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/FactoryReturnTypePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/FactoryReturnTypePass.php @@ -11,6 +11,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; +use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; @@ -65,6 +66,9 @@ private function updateDefinition(ContainerBuilder $container, $id, Definition $ if (is_string($factory)) { try { $m = new \ReflectionFunction($factory); + if (false !== $m->getFileName() && file_exists($m->getFileName())) { + $container->addResource(new FileResource($m->getFileName())); + } } catch (\ReflectionException $e) { return; } diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index ee342f13a6a7c..1e272e97ca6c8 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -25,6 +25,8 @@ use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag; +use Symfony\Component\Config\Resource\ClassExistenceResource; +use Symfony\Component\Config\Resource\FileExistenceResource; use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface; @@ -103,6 +105,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface */ private $envCounters = array(); + /** + * @var \ReflectionClass[] + */ + private $classReflectors = array(); + /** * Sets the track resources flag. * @@ -278,6 +285,37 @@ public function addClassResource(\ReflectionClass $class) return $this; } + /** + * Checks whether the requested class, interface or trait exists and registers the result for resource tracking. + * + * @param string $class + * + * @return bool + * + * @final + */ + public function classExists($class) + { + if (isset($this->classReflectors[$class])) { + return (bool) $this->classReflectors[$class]; + } + $exists = class_exists($class) || interface_exists($class, false) || trait_exists($class, false); + + if ($this->trackResources) { + if (!$exists) { + $this->addResource(new ClassExistenceResource($class)); + $this->classReflectors[$class] = false; + } else { + $class = $this->classReflectors[$class] = new \ReflectionClass($class); + if (!$class->isInternal() && false !== ($file = $class->getFileName()) && file_exists($file)) { + $this->addResource(new FileExistenceResource($file)); + } + } + } + + return $exists; + } + /** * Loads the configuration for an extension. * @@ -983,7 +1021,7 @@ public function resolveServices($value) if ('service_container' === $id = (string) $reference) { $class = parent::class; } elseif (!$this->hasDefinition($id) && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) { - return null; + return; } else { $class = $parameterBag->resolveValue($this->findDefinition($id)->getClass()); } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 5f836b0544d66..5887c3eb87601 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -265,7 +265,7 @@ private function addProxyClasses() array($this->getProxyDumper(), 'isProxyCandidate') ); $code = ''; - $strip = '' === $this->docStar && method_exists('Symfony\Component\HttpKernel\Kernel', 'stripComments'); + $strip = '' === $this->docStar; foreach ($definitions as $definition) { $proxyCode = "\n".$this->getProxyDumper()->getProxyCode($definition); @@ -1672,7 +1672,7 @@ private function getNextVariableName() private function getExpressionLanguage() { if (null === $this->expressionLanguage) { - if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + if (!$this->container->classExists(Expression::class)) { throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); } $providers = $this->container->getExpressionLanguageProviders(); diff --git a/src/Symfony/Component/DependencyInjection/composer.json b/src/Symfony/Component/DependencyInjection/composer.json index b658759e7b7d2..0d08d092be6de 100644 --- a/src/Symfony/Component/DependencyInjection/composer.json +++ b/src/Symfony/Component/DependencyInjection/composer.json @@ -20,7 +20,7 @@ }, "require-dev": { "symfony/yaml": "~3.2", - "symfony/config": "~2.8|~3.0", + "symfony/config": "~3.3", "symfony/expression-language": "~2.8|~3.0" }, "suggest": { @@ -30,6 +30,7 @@ "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them" }, "conflict": { + "symfony/config": "<3.3", "symfony/yaml": "<3.2" }, "autoload": {