Przeglądaj źródła

made the DI config validation more strict to catch errors early

Johannes Schmitt 14 lat temu
rodzic
commit
f29a5f74a1

+ 62 - 0
src/Symfony/Component/DependencyInjection/Compiler/CheckDefinitionValidityPass.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace Symfony\Component\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * This pass validates each definition individually only taking the information
+ * into account which is contained in the definition itself.
+ *
+ * Later passes can rely on the following, and specifically do not need to
+ * perform these checks themself:
+ *
+ * - non synthetic services always have a class set
+ * - synthetic services are always public
+ * - synthetic services are always of non-prototype scope
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class CheckDefinitionValidityPass implements CompilerPassInterface
+{
+    public function process(ContainerBuilder $container)
+    {
+        foreach ($container->getDefinitions() as $id => $definition) {
+            // synthetic service is public
+            if ($definition->isSynthetic() && !$definition->isPublic()) {
+                throw new \RuntimeException(sprintf(
+                    'A synthetic service ("%s") must be public.',
+                    $id
+                ));
+            }
+
+            // synthetic service has non-prototype scope
+            if ($definition->isSynthetic() && ContainerInterface::SCOPE_PROTOTYPE === $definition->getScope()) {
+                throw new \RuntimeException(sprintf(
+                    'A synthetic service ("%s") cannot be of scope "prototype".',
+                    $id
+                ));
+            }
+
+            // non-synthetic service has class
+            if (!$definition->isSynthetic() && !$definition->getClass()) {
+                if ($definition->getFactoryService()) {
+                    throw new \RuntimeException(sprintf(
+                        'Please add the class to service "%s" even if it is constructed '
+                       .'by a factory since we might need to add method calls based on '
+                       .'interface injection, or other compile-time checks.',
+                       $id
+                    ));
+                }
+
+                throw new \RuntimeException(sprintf(
+                    'The definition for "%s" has no class. If you intend to inject '
+                   .'this service dynamically at runtime, please mark it as synthetic=true, '
+                   .'otherwise specify a class to get rid of this error.',
+                   $id
+                ));
+            }
+        }
+    }
+}

+ 4 - 0
src/Symfony/Component/DependencyInjection/Compiler/CheckReferenceScopePass.php

@@ -44,6 +44,10 @@ class CheckReferenceScopePass implements CompilerPassInterface
         }
 
         foreach ($container->getDefinitions() as $id => $definition) {
+            if ($definition->isSynthetic()) {
+                continue;
+            }
+
             $this->currentId = $id;
             $this->currentScope = $scope = $definition->getScope();
 

+ 1 - 0
src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php

@@ -43,6 +43,7 @@ class PassConfig
 
         $this->optimizationPasses = array(
             new ResolveParameterPlaceHoldersPass(),
+            new CheckDefinitionValidityPass(),
             new ResolveReferencesToAliasesPass(),
             new ResolveInterfaceInjectorsPass(),
             new ResolveInvalidReferencesPass(),

+ 5 - 1
src/Symfony/Component/DependencyInjection/Compiler/ResolveInterfaceInjectorsPass.php

@@ -26,6 +26,10 @@ class ResolveInterfaceInjectorsPass implements CompilerPassInterface
     public function process(ContainerBuilder $container)
     {
         foreach ($container->getDefinitions() as $definition) {
+            if ($definition->isSynthetic()) {
+                continue;
+            }
+
             $loaded = false;
             foreach ($container->getInterfaceInjectors() as $injector) {
                 if (null !== $definition->getFactoryService()) {
@@ -38,7 +42,7 @@ class ResolveInterfaceInjectorsPass implements CompilerPassInterface
                     require_once $definition->getFile();
                 }
 
-                if (null !== $definition->getClass() && $injector->supports($definition->getClass())) {
+                if ($injector->supports($definition->getClass())) {
                     $injector->processDefinition($definition);
                 }
             }

+ 4 - 0
src/Symfony/Component/DependencyInjection/Compiler/ResolveInvalidReferencesPass.php

@@ -40,6 +40,10 @@ class ResolveInvalidReferencesPass implements CompilerPassInterface
     {
         $this->container = $container;
         foreach ($container->getDefinitions() as $definition) {
+            if ($definition->isSynthetic()) {
+                continue;
+            }
+
             $definition->setArguments(
                 $this->processArguments($definition->getArguments())
             );

+ 2 - 2
src/Symfony/Component/DependencyInjection/Compiler/ResolveReferencesToAliasesPass.php

@@ -61,8 +61,8 @@ class ResolveReferencesToAliasesPass implements CompilerPassInterface
 
     protected function getDefinitionId($id)
     {
-        if ($this->container->hasAlias($id)) {
-            return $this->getDefinitionId((string) $this->container->getAlias($id));
+        while ($this->container->hasAlias($id)) {
+            $id = (string) $this->container->getAlias($id);
         }
 
         return $id;

+ 8 - 8
tests/Symfony/Tests/Component/DependencyInjection/Compiler/IntegrationTest.php

@@ -32,18 +32,18 @@ class IntegrationTest extends \PHPUnit_Framework_TestCase
         $container = new ContainerBuilder();
 
         $a = $container
-            ->register('a')
+            ->register('a', '\stdClass')
             ->addArgument(new Reference('c'))
         ;
 
         $b = $container
-            ->register('b')
+            ->register('b', '\stdClass')
             ->addArgument(new Reference('c'))
             ->setPublic(false)
         ;
 
         $c = $container
-            ->register('c')
+            ->register('c', '\stdClass')
             ->setPublic(false)
         ;
 
@@ -61,14 +61,14 @@ class IntegrationTest extends \PHPUnit_Framework_TestCase
         $container = new ContainerBuilder();
 
         $a = $container
-            ->register('a')
+            ->register('a', '\stdClass')
             ->addArgument(new Reference('b'))
         ;
 
         $container->setAlias('b', new Alias('c', false));
 
         $c = $container
-            ->register('c')
+            ->register('c', '\stdClass')
             ->setPublic(false)
         ;
 
@@ -86,19 +86,19 @@ class IntegrationTest extends \PHPUnit_Framework_TestCase
         $container = new ContainerBuilder();
 
         $container
-            ->register('a')
+            ->register('a', '\stdClass')
             ->addArgument(new Reference('b'))
             ->addMethodCall('setC', array(new Reference('c')))
         ;
 
         $container
-            ->register('b')
+            ->register('b', '\stdClass')
             ->addArgument(new Reference('c'))
             ->setPublic(false)
         ;
 
         $container
-            ->register('c')
+            ->register('c', '\stdClass')
             ->setPublic(false)
         ;