Pārlūkot izejas kodu

tweaked DI container

Johannes Schmitt 14 gadi atpakaļ
vecāks
revīzija
db5e180d37

+ 19 - 1
src/Symfony/Bundle/DoctrineBundle/Tests/DependencyInjection/AbstractDoctrineExtensionTest.php

@@ -62,6 +62,9 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
         $loadXml = new XmlFileLoader($container, __DIR__.'/Fixtures/config/xml');
         $loadXml->load('dbal_service_multiple_connections.xml');
         $loader->dbalLoad(array(), $container);
+
+        $container->getCompilerPassConfig()->setOptimizationPasses(array());
+        $container->getCompilerPassConfig()->setRemovingPasses(array());
         $container->freeze();
 
         // doctrine.dbal.mysql_connection
@@ -82,6 +85,9 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
         $loadXml = new XmlFileLoader($container, __DIR__.'/Fixtures/config/xml');
         $loadXml->load('dbal_service_single_connection.xml');
         $loader->dbalLoad(array(), $container);
+
+        $container->getCompilerPassConfig()->setOptimizationPasses(array());
+        $container->getCompilerPassConfig()->setRemovingPasses(array());
         $container->freeze();
 
         // doctrine.dbal.mysql_connection
@@ -126,7 +132,7 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
             'auto_generate_proxy_classes' => true,
             'mappings' => array('YamlBundle' => array()),
         );
-        
+
         $loader->dbalLoad(array(), $container);
         $loader->ormLoad($config, $container);
 
@@ -203,6 +209,8 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
 
         $this->loadFromFile($container, 'orm_service_simple_single_entity_manager');
 
+        $container->getCompilerPassConfig()->setOptimizationPasses(array());
+        $container->getCompilerPassConfig()->setRemovingPasses(array());
         $container->freeze();
 
         $definition = $container->getDefinition('doctrine.dbal.default_connection');
@@ -237,6 +245,8 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
 
         $this->loadFromFile($container, 'orm_service_single_entity_manager');
 
+        $container->getCompilerPassConfig()->setOptimizationPasses(array());
+        $container->getCompilerPassConfig()->setRemovingPasses(array());
         $container->freeze();
 
         $definition = $container->getDefinition('doctrine.dbal.default_connection');
@@ -274,6 +284,8 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
 
         $this->loadFromFile($container, 'orm_service_multiple_entity_managers');
 
+        $container->getCompilerPassConfig()->setOptimizationPasses(array());
+        $container->getCompilerPassConfig()->setRemovingPasses(array());
         $container->freeze();
 
         $definition = $container->getDefinition('doctrine.dbal.conn1_connection');
@@ -440,6 +452,8 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
 
         $this->loadFromFile($container, 'orm_service_multiple_entity_managers');
 
+        $container->getCompilerPassConfig()->setOptimizationPasses(array());
+        $container->getCompilerPassConfig()->setRemovingPasses(array());
         $container->freeze();
 
         $definition = $container->getDefinition('doctrine.orm.dm1_metadata_cache');
@@ -457,6 +471,8 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
 
         $this->loadFromFile($container, 'orm_service_simple_single_entity_manager');
 
+        $container->getCompilerPassConfig()->setOptimizationPasses(array());
+        $container->getCompilerPassConfig()->setRemovingPasses(array());
         $container->freeze();
 
         $definition = $container->getDefinition('doctrine.orm.default_metadata_cache');
@@ -478,6 +494,8 @@ abstract class AbstractDoctrineExtensionTest extends TestCase
 
         $this->loadFromFile($container, 'orm_imports');
 
+        $container->getCompilerPassConfig()->setOptimizationPasses(array());
+        $container->getCompilerPassConfig()->setRemovingPasses(array());
         $container->freeze();
 
         $this->assertEquals('apc', $container->getParameter('doctrine.orm.metadata_cache_driver'));

+ 12 - 0
src/Symfony/Bundle/DoctrineMongoDBBundle/Tests/DependencyInjection/AbstractMongoDBExtensionTest.php

@@ -109,6 +109,8 @@ abstract class AbstractMongoDBExtensionTest extends TestCase
 
         $this->loadFromFile($container, 'mongodb_service_simple_single_connection');
 
+        $container->getCompilerPassConfig()->setOptimizationPasses(array());
+        $container->getCompilerPassConfig()->setRemovingPasses(array());
         $container->freeze();
 
         $definition = $container->getDefinition('doctrine.odm.mongodb.default_connection');
@@ -141,6 +143,8 @@ abstract class AbstractMongoDBExtensionTest extends TestCase
 
         $this->loadFromFile($container, 'mongodb_service_single_connection');
 
+        $container->getCompilerPassConfig()->setOptimizationPasses(array());
+        $container->getCompilerPassConfig()->setRemovingPasses(array());
         $container->freeze();
 
         $definition = $container->getDefinition('doctrine.odm.mongodb.default_connection');
@@ -167,6 +171,8 @@ abstract class AbstractMongoDBExtensionTest extends TestCase
 
         $this->loadFromFile($container, 'mongodb_service_multiple_connections');
 
+        $container->getCompilerPassConfig()->setOptimizationPasses(array());
+        $container->getCompilerPassConfig()->setRemovingPasses(array());
         $container->freeze();
 
         $definition = $container->getDefinition('doctrine.odm.mongodb.conn1_connection');
@@ -259,6 +265,8 @@ abstract class AbstractMongoDBExtensionTest extends TestCase
 
         $this->loadFromFile($container, 'mongodb_service_multiple_connections');
 
+        $container->getCompilerPassConfig()->setOptimizationPasses(array());
+        $container->getCompilerPassConfig()->setRemovingPasses(array());
         $container->freeze();
 
         $definition = $container->getDefinition('doctrine.odm.mongodb.dm1_metadata_cache');
@@ -276,6 +284,8 @@ abstract class AbstractMongoDBExtensionTest extends TestCase
 
         $this->loadFromFile($container, 'mongodb_service_simple_single_connection');
 
+        $container->getCompilerPassConfig()->setOptimizationPasses(array());
+        $container->getCompilerPassConfig()->setRemovingPasses(array());
         $container->freeze();
 
         $definition = $container->getDefinition('doctrine.odm.mongodb.default_metadata_cache');
@@ -302,6 +312,8 @@ abstract class AbstractMongoDBExtensionTest extends TestCase
 
         $this->loadFromFile($container, 'odm_imports');
 
+        $container->getCompilerPassConfig()->setOptimizationPasses(array());
+        $container->getCompilerPassConfig()->setRemovingPasses(array());
         $container->freeze();
 
         $this->assertEquals('apc', $container->getParameter('doctrine.odm.mongodb.metadata_cache_driver'));

+ 17 - 17
src/Symfony/Bundle/FrameworkBundle/Resources/config/security.xml

@@ -85,11 +85,11 @@
             <argument type="service" id="security.access.decision_manager" />
         </service>
 
-        <service id="security.role_hierarchy" class="%security.role_hierarchy.class%">
+        <service id="security.role_hierarchy" class="%security.role_hierarchy.class%" public="false">
             <argument>%security.role_hierarchy.roles%</argument>
         </service>
 
-        <service id="security.account_checker" class="%security.account_checker.class%" />
+        <service id="security.account_checker" class="%security.account_checker.class%" public="false" />
 
         <service id="security.encoder.sha1" class="%security.encoder.digest.class%">
             <argument>sha1</argument>
@@ -101,57 +101,57 @@
 
         <service id="security.encoder.plain" class="%security.encoder.plain.class%" />
 
-        <service id="security.logout.handler.session" class="%security.logout.handler.session.class%"></service>
+        <service id="security.logout.handler.session" class="%security.logout.handler.session.class%" public="false"></service>
 
-        <service id="security.authentication.listener.anonymous" class="%security.authentication.listener.anonymous.class%">
+        <service id="security.authentication.listener.anonymous" class="%security.authentication.listener.anonymous.class%" public="false">
             <argument type="service" id="security.context" />
             <argument>%security.anonymous.key%</argument>
             <argument type="service" id="logger" on-invalid="null" />
         </service>
 
-        <service id="security.authentication.trust_resolver" class="%security.authentication.trust_resolver.class%">
+        <service id="security.authentication.trust_resolver" class="%security.authentication.trust_resolver.class%" public="false">
             <argument>%security.authentication.trust_resolver.anonymous_class%</argument>
             <argument>%security.authentication.trust_resolver.rememberme_class%</argument>
         </service>
 
-        <service id="security.authentication.retry_entry_point" class="%security.authentication.retry_entry_point.class%" />
+        <service id="security.authentication.retry_entry_point" class="%security.authentication.retry_entry_point.class%" public="false" />
 
-        <service id="security.authentication.form_entry_point" class="%security.authentication.form_entry_point.class%">
+        <service id="security.authentication.form_entry_point" class="%security.authentication.form_entry_point.class%" public="false">
             <argument>%security.authentication.form.login_path%</argument>
             <argument>%security.authentication.form.use_forward%</argument>
         </service>
 
-        <service id="security.authentication.basic_entry_point" class="%security.authentication.basic_entry_point.class%">
+        <service id="security.authentication.basic_entry_point" class="%security.authentication.basic_entry_point.class%" public="false">
             <argument>%security.authentication.basic_entry_point.realm%</argument>
         </service>
 
-        <service id="security.authentication.digest_entry_point" class="%security.authentication.digest_entry_point.class%">
+        <service id="security.authentication.digest_entry_point" class="%security.authentication.digest_entry_point.class%" public="false">
             <argument>%security.authentication.digest_entry_point.realm%</argument>
             <argument>%security.authentication.digest_entry_point.key%</argument>
         </service>
 
-        <service id="security.channel_listener" class="%security.channel_listener.class%">
+        <service id="security.channel_listener" class="%security.channel_listener.class%" public="false">
             <argument type="service" id="security.access_map" />
             <argument type="service" id="security.authentication.retry_entry_point" />
             <argument type="service" id="logger" on-invalid="null" />
         </service>
 
-        <service id="security.access.decision_manager" class="%security.access.decision_manager.class%">
+        <service id="security.access.decision_manager" class="%security.access.decision_manager.class%" public="false">
             <argument type="collection"></argument>
             <argument>%security.access.decision_manager.strategy%</argument>
             <argument>%security.access.decision_manager.allow_if_all_abstain%</argument>
             <argument>%security.access.decision_manager.allow_if_equal_granted_denied%</argument>
         </service>
-        <service id="security.access_map" class="%security.access_map.class%" />
+        <service id="security.access_map" class="%security.access_map.class%" public="false" />
 
-        <service id="security.access.simple_role_voter" class="%security.access.simple_role_voter.class%">
+        <service id="security.access.simple_role_voter" class="%security.access.simple_role_voter.class%" public="false">
             <tag name="security.voter" />
         </service>
-        <service id="security.access.authenticated_voter" class="%security.access.authenticated_voter.class%">
+        <service id="security.access.authenticated_voter" class="%security.access.authenticated_voter.class%" public="false">
             <argument type="service" id="security.authentication.trust_resolver" />
             <tag name="security.voter" />
         </service>
-        <service id="security.access.role_hierarchy_voter" class="%security.access.role_hierarchy_voter.class%">
+        <service id="security.access.role_hierarchy_voter" class="%security.access.role_hierarchy_voter.class%" public="false">
             <argument type="service" id="security.role_hierarchy" />
         </service>
 
@@ -159,9 +159,9 @@
             <tag name="kernel.listener" priority="128" />
             <argument type="service" id="security.firewall.map" />
         </service>
-        <service id="security.firewall.map" class="%security.firewall.map.class%" />
+        <service id="security.firewall.map" class="%security.firewall.map.class%" public="false" />
 
-        <service id="security.context_listener" class="%security.context_listener.class%">
+        <service id="security.context_listener" class="%security.context_listener.class%" public="false">
             <argument type="service" id="security.context" />
             <argument type="collection"></argument>
             <argument type="service" id="logger" on-invalid="null" />

+ 3 - 0
src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/SecurityExtensionTest.php

@@ -95,6 +95,9 @@ abstract class SecurityExtensionTest extends TestCase
         $security = new SecurityExtension();
         $container->registerExtension($security);
         $this->loadFromFile($container, $file);
+
+        $container->getCompilerPassConfig()->setOptimizationPasses(array());
+        $container->getCompilerPassConfig()->setRemovingPasses(array());
         $container->freeze();
 
         return $container;

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

@@ -0,0 +1,91 @@
+<?php
+
+namespace Symfony\Component\DependencyInjection\Compiler;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * Compiler Pass Configuration
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class PassConfig
+{
+    const TYPE_OPTIMIZE = 'optimization';
+    const TYPE_REMOVE = 'removing';
+
+    protected $mergePass;
+    protected $optimizationPasses;
+    protected $removingPasses;
+
+    public function __construct()
+    {
+        $this->mergePass = new MergeExtensionConfigurationPass();
+
+        $this->optimizationPasses = array(
+            new ResolveParameterPlaceHoldersPass(),
+            new ResolveInterfaceInjectorsPass(),
+        );
+
+        $this->removingPasses = array(
+            new RemoveUnusedDefinitionsPass(),
+        );
+    }
+
+    public function getPasses()
+    {
+        return array_merge(
+            array($this->mergePass),
+            $this->optimizationPasses,
+            $this->removingPasses
+        );
+    }
+
+    public function addPass(CompilerPassInterface $pass, $type = self::TYPE_OPTIMIZE)
+    {
+        $property = $type.'Passes';
+        if (!isset($this->$property)) {
+            throw new \InvalidArgumentException(sprintf('Invalid type "%s".', $type));
+        }
+
+        $passes = &$this->$property;
+        $passes[] = $pass;
+    }
+
+    public function getOptimizationPasses()
+    {
+        return $this->optimizationPasses;
+    }
+
+    public function getRemovingPasses()
+    {
+        return $this->removingPasses;
+    }
+
+    public function getMergePass()
+    {
+        return $this->mergePass;
+    }
+
+    public function setMergePass(CompilerPassInterface $pass)
+    {
+        $this->mergePass = $pass;
+    }
+
+    public function setOptimizationPasses(array $passes)
+    {
+        $this->optimizationPasses = $passes;
+    }
+
+    public function setRemovingPasses(array $passes)
+    {
+        $this->removingPasses = $passes;
+    }
+}

+ 70 - 0
src/Symfony/Component/DependencyInjection/Compiler/RemoveUnusedDefinitionsPass.php

@@ -0,0 +1,70 @@
+<?php
+
+namespace Symfony\Component\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * Removes unused service definitions from the container
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class RemoveUnusedDefinitionsPass implements CompilerPassInterface
+{
+    public function process(ContainerBuilder $container)
+    {
+        $hasChanged = false;
+        $aliases = $container->getAliases();
+        foreach ($container->getDefinitions() as $id => $definition) {
+            if ($definition->isPublic()) {
+                continue;
+            }
+
+            if (!in_array($id, $aliases, true) && !$this->isReferenced($container, $id)) {
+                $container->remove($id);
+                $hasChanged = true;
+            }
+        }
+
+        if ($hasChanged) {
+            $this->process($container);
+        }
+    }
+
+    protected function isReferenced(ContainerBuilder $container, $id)
+    {
+        foreach ($container->getDefinitions() as $definition) {
+            if ($this->isReferencedByArgument($id, $definition->getArguments())) {
+                return true;
+            }
+
+            foreach ($definition->getMethodCalls() as $arguments)
+            {
+                if ($this->isReferencedByArgument($id, $arguments)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    protected function isReferencedByArgument($id, $argument)
+    {
+        if (is_array($argument)) {
+            foreach ($argument as $arg) {
+                if ($this->isReferencedByArgument($id, $arg)) {
+                    return true;
+                }
+            }
+        } else if ($argument instanceof Reference) {
+            if ($id === (string) $argument) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}

+ 2 - 3
src/Symfony/Component/DependencyInjection/Compiler/ResolveInterfaceInjectorsPass.php

@@ -30,9 +30,8 @@ class ResolveInterfaceInjectorsPass implements CompilerPassInterface
                 if (null !== $definition->getFactoryService()) {
                     continue;
                 }
-                $defClass = $container->getParameterBag()->resolveValue($definition->getClass());
-                $definition->setClass($defClass);
-                if ($injector->supports($defClass)) {
+
+                if ($injector->supports($definition->getClass())) {
                     $injector->processDefinition($definition);
                 }
             }

+ 90 - 0
src/Symfony/Component/DependencyInjection/Compiler/ResolveParameterPlaceHoldersPass.php

@@ -0,0 +1,90 @@
+<?php
+
+namespace Symfony\Component\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * Resolves all parameter placeholders "%somevalue%" to their real values.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class ResolveParameterPlaceHoldersPass implements CompilerPassInterface
+{
+    protected $parameterBag;
+
+    public function process(ContainerBuilder $container)
+    {
+        $this->parameterBag = $container->getParameterBag();
+
+        foreach ($container->getDefinitions() as $id => $definition) {
+            $definition->setClass($this->resolveValue($definition->getClass()));
+            $definition->setArguments($this->resolveValue($definition->getArguments()));
+
+            $calls = array();
+            foreach ($definition->getMethodCalls() as $name => $arguments) {
+                $calls[$this->resolveValue($name)] = $this->resolveValue($arguments);
+            }
+            $definition->setMethodCalls($calls);
+        }
+
+        $aliases = array();
+        foreach ($container->getAliases() as $name => $target) {
+            $aliases[$this->resolveValue($name)] = $this->resolveValue($target);
+        }
+        $container->setAliases($aliases);
+
+        $injectors = array();
+        foreach ($container->getInterfaceInjectors() as $class => $injector) {
+            $injector->setClass($this->resolveValue($injector->getClass()));
+            $injectors[$this->resolveValue($class)] = $injector;
+        }
+        $container->setInterfaceInjectors($injectors);
+    }
+
+    protected function resolveValue($value)
+    {
+        if (is_array($value)) {
+            $resolved = array();
+            foreach ($value as $k => $v) {
+                $resolved[$this->resolveValue($k)] = $this->resolveValue($v);
+            }
+
+            return $resolved;
+        } else if (is_string($value)) {
+            return $this->resolveString($value);
+        } else {
+            return $value;
+        }
+    }
+
+    public function resolveString($value)
+    {
+        if (preg_match('/^%[^%]+%$/', $value)) {
+            return $this->resolveValue($this->parameterBag->resolveValue($value));
+        }
+
+        $self = $this;
+        $parameterBag = $this->parameterBag;
+        return preg_replace_callback('/(?<!%)%[^%]+%/',
+            function($parameter) use ($self, $parameterBag) {
+                $resolved = $parameterBag->resolveValue($parameter[0]);
+                if (!is_string($resolved)) {
+                    throw new \RuntimeException('You can only embed strings in other parameters.');
+                }
+
+                return $self->resolveString($resolved);
+            },
+            $value
+        );
+    }
+}

+ 17 - 22
src/Symfony/Component/DependencyInjection/ContainerBuilder.php

@@ -2,6 +2,10 @@
 
 namespace Symfony\Component\DependencyInjection;
 
+use Symfony\Component\DependencyInjection\Compiler\PassConfig;
+
+use Symfony\Component\DependencyInjection\Compiler\RemoveUnusedDefinitionsPass;
+
 use Symfony\Component\DependencyInjection\Compiler\ResolveInterfaceInjectorsPass;
 use Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPass;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
@@ -35,7 +39,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
     protected $resources        = array();
     protected $extensionConfigs = array();
     protected $injectors        = array();
-    protected $compilerPasses;
+    protected $compilerPassConfig;
 
     /**
      * Constructor
@@ -45,10 +49,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
     {
         parent::__construct($parameterBag);
 
-        $passes = array();
-        $passes[] = new MergeExtensionConfigurationPass();
-        $passes[] = new ResolveInterfaceInjectorsPass();
-        $this->compilerPasses = $passes;
+        $this->compilerPassConfig = new PassConfig();
     }
 
     /**
@@ -154,28 +155,17 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
      */
     public function addCompilerPass(CompilerPassInterface $pass)
     {
-        $this->compilerPasses[] = $pass;
-    }
-
-    /**
-     * Returns the currently registered compiler passes
-     *
-     * @return array
-     */
-    public function getCompilerPasses()
-    {
-        return $this->compilerPasses;
+        $this->compilerPassConfig->addPass($pass);
     }
 
     /**
-     * Overwrites all existing passes
+     * Returns the compiler pass config which can then be modified
      *
-     * @param array $passes
-     * @return void
+     * @return PassConfig
      */
-    public function setCompilerPasses(array $passes)
+    public function getCompilerPassConfig()
     {
-        $this->compilerPasses = $passes;
+        return $this->compilerPassConfig;
     }
 
     /**
@@ -341,7 +331,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
      */
     public function freeze()
     {
-        foreach ($this->compilerPasses as $pass) {
+        foreach ($this->compilerPassConfig->getPasses() as $pass) {
             $pass->process($this);
         }
 
@@ -472,6 +462,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
         });
     }
 
+    public function setInterfaceInjectors(array $injectors)
+    {
+        $this->injectors = $injectors;
+    }
+
     /**
      * Registers a service definition.
      *

+ 25 - 0
src/Symfony/Component/DependencyInjection/Definition.php

@@ -27,6 +27,7 @@ class Definition
     protected $calls;
     protected $configurator;
     protected $tags;
+    protected $public;
 
     /**
      * Constructor.
@@ -41,6 +42,7 @@ class Definition
         $this->calls = array();
         $this->shared = true;
         $this->tags = array();
+        $this->public = true;
     }
 
     /**
@@ -337,6 +339,29 @@ class Definition
         return $this->shared;
     }
 
+    /**
+     * Sets the visibility of this service.
+     *
+     * @param Boolean $boolean
+     * @return Definition The current instance
+     */
+    public function setPublic($boolean)
+    {
+        $this->public = (Boolean) $boolean;
+
+        return $this;
+    }
+
+    /**
+     * Whether this service is public facing
+     *
+     * @return Boolean
+     */
+    public function isPublic()
+    {
+        return $this->public;
+    }
+
     /**
      * Sets a configurator to call after the service is fully initialized.
      *

+ 12 - 1
src/Symfony/Component/DependencyInjection/InterfaceInjector.php

@@ -44,6 +44,17 @@ class InterfaceInjector
         return $this->class;
     }
 
+    /**
+     * Sets the interface class
+     *
+     * @param string $class
+     * @return void
+     */
+    public function setClass($class)
+    {
+        $this->class = $class;
+    }
+
     /**
      * Adds method calls if Definition is of required interface
      *
@@ -80,7 +91,7 @@ class InterfaceInjector
     {
         if (is_string($object)) {
             $reflection = new \ReflectionClass($object);
-            
+
             return $reflection->isSubClassOf($this->class)
                    || $object === $this->class;
         }

+ 1 - 1
src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php

@@ -131,7 +131,7 @@ class XmlFileLoader extends FileLoader
 
         $definition = new Definition();
 
-        foreach (array('class', 'shared', 'factory-method', 'factory-service') as $key) {
+        foreach (array('class', 'shared', 'public', 'factory-method', 'factory-service') as $key) {
             if (isset($service[$key])) {
                 $method = 'set'.str_replace('-', '', $key);
                 $definition->$method((string) $service->getAttributeAsPhp($key));

+ 4 - 0
src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php

@@ -141,6 +141,10 @@ class YamlFileLoader extends FileLoader
             $definition->setShared($service['shared']);
         }
 
+        if (isset($service['public'])) {
+            $definition->setPublic($service['public']);
+        }
+
         if (isset($service['factory_method'])) {
             $definition->setFactoryMethod($service['factory_method']);
         }

+ 1 - 0
src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd

@@ -102,6 +102,7 @@
     <xsd:attribute name="id" type="xsd:string" />
     <xsd:attribute name="class" type="xsd:string" />
     <xsd:attribute name="shared" type="boolean" />
+    <xsd:attribute name="public" type="boolean" />
     <xsd:attribute name="factory-method" type="xsd:string" />
     <xsd:attribute name="factory-service" type="xsd:string" />
     <xsd:attribute name="alias" type="xsd:string" />

+ 12 - 0
tests/Symfony/Tests/Component/DependencyInjection/DefinitionTest.php

@@ -112,6 +112,18 @@ class DefinitionTest extends \PHPUnit_Framework_TestCase
         $this->assertFalse($def->isShared(), '->isShared() returns false if the instance must not be shared');
     }
 
+    /**
+     * @covers Symfony\Component\DependencyInjection\Definition::setPublic
+     * @covers Symfony\Component\DependencyInjection\Definition::isPublic
+     */
+    public function testSetIsPublic()
+    {
+        $def = new Definition('stdClass');
+        $this->assertTrue($def->isPublic(), '->isPublic() returns true by default');
+        $this->assertSame($def, $def->setPublic(false), '->setPublic() implements a fluent interface');
+        $this->assertFalse($def->isPublic(), '->isPublic() returns false if the instance must not be public.');
+    }
+
     /**
      * @covers Symfony\Component\DependencyInjection\Definition::setConfigurator
      * @covers Symfony\Component\DependencyInjection\Definition::getConfigurator