diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php
index 92bf97d9cffed..ab5f702d26d4d 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php
@@ -127,6 +127,9 @@ private function resolveDefinition(ContainerBuilder $container, DefinitionDecora
if ($parentDef->getFactoryService(false)) {
$def->setFactoryService($parentDef->getFactoryService(false));
}
+ if ($parentDef->isDeprecated()) {
+ $def->setDeprecated(true, $parentDef->getDeprecationMessage('%service_id%'));
+ }
$def->setFactory($parentDef->getFactory());
$def->setConfigurator($parentDef->getConfigurator());
$def->setFile($parentDef->getFile());
@@ -162,6 +165,9 @@ private function resolveDefinition(ContainerBuilder $container, DefinitionDecora
if (isset($changes['lazy'])) {
$def->setLazy($definition->isLazy());
}
+ if (isset($changes['deprecated'])) {
+ $def->setDeprecated($definition->isDeprecated(), $definition->getDeprecationMessage('%service_id%'));
+ }
if (isset($changes['decorated_service'])) {
$decoratedService = $definition->getDecoratedService();
if (null === $decoratedService) {
diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
index 5ac89cde57317..52405413af3d7 100644
--- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
+++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
@@ -931,6 +931,10 @@ public function createService(Definition $definition, $id, $tryProxy = true)
throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The DIC does not know how to construct this service.', $id));
}
+ if ($definition->isDeprecated()) {
+ @trigger_error($definition->getDeprecationMessage($id), E_USER_DEPRECATED);
+ }
+
if ($tryProxy && $definition->isLazy()) {
$container = $this;
diff --git a/src/Symfony/Component/DependencyInjection/Definition.php b/src/Symfony/Component/DependencyInjection/Definition.php
index 49d705cf7d670..8a39d4fb93882 100644
--- a/src/Symfony/Component/DependencyInjection/Definition.php
+++ b/src/Symfony/Component/DependencyInjection/Definition.php
@@ -30,6 +30,8 @@ class Definition
private $factoryMethod;
private $factoryService;
private $shared = true;
+ private $deprecated = false;
+ private $deprecationTemplate = 'The "%service_id%" service is deprecated. You should stop using it, as it will soon be removed.';
private $scope = ContainerInterface::SCOPE_CONTAINER;
private $properties = array();
private $calls = array();
@@ -829,6 +831,65 @@ public function isAbstract()
return $this->abstract;
}
+ /**
+ * Whether this definition is deprecated, that means it should not be called
+ * anymore.
+ *
+ * @param bool $status
+ * @param string $template Template message to use if the definition is deprecated
+ *
+ * @return Definition the current instance
+ *
+ * @throws InvalidArgumentException When the message template is invalid.
+ *
+ * @api
+ */
+ public function setDeprecated($status = true, $template = null)
+ {
+ if (null !== $template) {
+ if (preg_match('#[\r\n]|\*/#', $template)) {
+ throw new InvalidArgumentException('Invalid characters found in deprecation template.');
+ }
+
+ if (false === strpos($template, '%service_id%')) {
+ throw new InvalidArgumentException('The deprecation template must contain the "%service_id%" placeholder.');
+ }
+
+ $this->deprecationTemplate = $template;
+ }
+
+ $this->deprecated = (bool) $status;
+
+ return $this;
+ }
+
+ /**
+ * Whether this definition is deprecated, that means it should not be called
+ * anymore.
+ *
+ * @return bool
+ *
+ * @api
+ */
+ public function isDeprecated()
+ {
+ return $this->deprecated;
+ }
+
+ /**
+ * Message to use if this definition is deprecated.
+ *
+ * @param string $id Service id relying on this definition
+ *
+ * @return string
+ *
+ * @api
+ */
+ public function getDeprecationMessage($id)
+ {
+ return str_replace('%service_id%', $id, $this->deprecationTemplate);
+ }
+
/**
* Sets a configurator to call after the service is fully initialized.
*
diff --git a/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php b/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php
index 7764079d5109a..87004c29b4142 100644
--- a/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php
+++ b/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php
@@ -180,6 +180,16 @@ public function setDecoratedService($id, $renamedId = null, $priority = 0)
return parent::setDecoratedService($id, $renamedId, $priority);
}
+ /**
+ * {@inheritdoc}
+ */
+ public function setDeprecated($boolean = true, $template = null)
+ {
+ $this->changes['deprecated'] = true;
+
+ return parent::setDeprecated($boolean, $template);
+ }
+
/**
* Gets an argument to pass to the service constructor/factory method.
*
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
index c1c6f80f70b77..1cdeb2d35b4c8 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
@@ -592,7 +592,15 @@ private function addService($id, $definition)
$return[] = sprintf("@throws InactiveScopeException when the '%s' service is requested while the '%s' scope is not active", $id, $scope);
}
- $return = implode("\n * ", $return);
+ if ($definition->isDeprecated()) {
+ if ($return && 0 === strpos($return[count($return) - 1], '@return')) {
+ $return[] = '';
+ }
+
+ $return[] = sprintf('@deprecated %s', $definition->getDeprecationMessage($id));
+ }
+
+ $return = str_replace("\n * \n", "\n *\n", implode("\n * ", $return));
$doc = '';
if ($definition->isShared() && ContainerInterface::SCOPE_PROTOTYPE !== $scope) {
@@ -652,6 +660,10 @@ private function addService($id, $definition)
if ($definition->isSynthetic()) {
$code .= sprintf(" throw new RuntimeException('You have requested a synthetic service (\"%s\"). The DIC does not know how to construct this service.');\n }\n", $id);
} else {
+ if ($definition->isDeprecated()) {
+ $code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", var_export($definition->getDeprecationMessage($id), true));
+ }
+
$code .=
$this->addServiceInclude($id, $definition).
$this->addServiceLocalTempVariables($id, $definition).
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
index c405c4ce902c3..d4fbd6a202ee1 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php
@@ -201,6 +201,13 @@ private function addService($definition, $id, \DOMElement $parent)
$service->appendChild($factory);
}
+ if ($definition->isDeprecated()) {
+ $deprecated = $this->document->createElement('deprecated');
+ $deprecated->appendChild($this->document->createTextNode($definition->getDeprecationMessage('%service_id%')));
+
+ $service->appendChild($deprecated);
+ }
+
if ($callable = $definition->getConfigurator()) {
$configurator = $this->document->createElement('configurator');
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
index 3fac53e01c330..94929f84a19ec 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php
@@ -104,6 +104,10 @@ private function addService($id, $definition)
$code .= sprintf(" synchronized: true\n");
}
+ if ($definition->isDeprecated()) {
+ $code .= sprintf(" deprecated: %s\n", $definition->getDeprecationMessage('%service_id%'));
+ }
+
if ($definition->getFactoryClass(false)) {
$code .= sprintf(" factory_class: %s\n", $definition->getFactoryClass(false));
}
diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
index de2ea9852a5f4..8a0e4ac24a65a 100644
--- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
+++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
@@ -181,6 +181,10 @@ private function parseDefinition(\DOMElement $service, $file)
$definition->setFile($files[0]->nodeValue);
}
+ if ($deprecated = $this->getChildren($service, 'deprecated')) {
+ $definition->setDeprecated(true, $deprecated[0]->nodeValue);
+ }
+
$definition->setArguments($this->getArgumentsAsPhp($service, 'argument'));
$definition->setProperties($this->getArgumentsAsPhp($service, 'property'));
diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
index 74a7ed4f1ad3a..19191a760c6cd 100644
--- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
+++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php
@@ -196,6 +196,10 @@ private function parseDefinition($id, $service, $file)
$definition->setAbstract($service['abstract']);
}
+ if (array_key_exists('deprecated', $service)) {
+ $definition->setDeprecated(true, $service['deprecated']);
+ }
+
if (isset($service['factory'])) {
if (is_string($service['factory'])) {
if (strpos($service['factory'], ':') !== false && strpos($service['factory'], '::') === false) {
diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd
index 15bdd791dcf27..cbaa3606f7a04 100644
--- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd
+++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd
@@ -81,6 +81,7 @@
+
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionTemplatesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionTemplatesPassTest.php
index 2180483e2be90..675630933d555 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionTemplatesPassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionTemplatesPassTest.php
@@ -244,6 +244,36 @@ public function testSetDecoratedServiceOnServiceHasParent()
$this->assertEquals(array('foo', 'foo_inner', 0), $container->getDefinition('child1')->getDecoratedService());
}
+ public function testDecoratedServiceCopiesDeprecatedStatusFromParent()
+ {
+ $container = new ContainerBuilder();
+ $container->register('deprecated_parent')
+ ->setDeprecated(true)
+ ;
+
+ $container->setDefinition('decorated_deprecated_parent', new DefinitionDecorator('deprecated_parent'));
+
+ $this->process($container);
+
+ $this->assertTrue($container->getDefinition('decorated_deprecated_parent')->isDeprecated());
+ }
+
+ public function testDecoratedServiceCanOverwriteDeprecatedParentStatus()
+ {
+ $container = new ContainerBuilder();
+ $container->register('deprecated_parent')
+ ->setDeprecated(true)
+ ;
+
+ $container->setDefinition('decorated_deprecated_parent', new DefinitionDecorator('deprecated_parent'))
+ ->setDeprecated(false)
+ ;
+
+ $this->process($container);
+
+ $this->assertFalse($container->getDefinition('decorated_deprecated_parent')->isDeprecated());
+ }
+
protected function process(ContainerBuilder $container)
{
$pass = new ResolveDefinitionTemplatesPass();
diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
index 7e5564c92b0ea..511b29c939ab7 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php
@@ -63,6 +63,28 @@ public function testDefinitions()
}
}
+ public function testCreateDeprecatedService()
+ {
+ $definition = new Definition('stdClass');
+ $definition->setDeprecated(true);
+
+ $that = $this;
+ $wasTriggered = false;
+
+ set_error_handler(function ($errno, $errstr) use ($that, &$wasTriggered) {
+ $that->assertSame(E_USER_DEPRECATED, $errno);
+ $that->assertSame('The "deprecated_foo" service is deprecated. You should stop using it, as it will soon be removed.', $errstr);
+ $wasTriggered = true;
+ });
+
+ $builder = new ContainerBuilder();
+ $builder->createService($definition, 'deprecated_foo');
+
+ restore_error_handler();
+
+ $this->assertTrue($wasTriggered);
+ }
+
/**
* @covers Symfony\Component\DependencyInjection\ContainerBuilder::register
*/
diff --git a/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php b/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php
index e8df12295348f..ea3045df1acbc 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/DefinitionTest.php
@@ -233,6 +233,42 @@ public function testSetIsAbstract()
$this->assertTrue($def->isAbstract(), '->isAbstract() returns true if the instance must not be public.');
}
+ /**
+ * @covers Symfony\Component\DependencyInjection\Definition::setDeprecated
+ * @covers Symfony\Component\DependencyInjection\Definition::isDeprecated
+ * @covers Symfony\Component\DependencyInjection\Definition::hasCustomDeprecationTemplate
+ * @covers Symfony\Component\DependencyInjection\Definition::getDeprecationMessage
+ */
+ public function testSetIsDeprecated()
+ {
+ $def = new Definition('stdClass');
+ $this->assertFalse($def->isDeprecated(), '->isDeprecated() returns false by default');
+ $this->assertSame($def, $def->setDeprecated(true), '->setDeprecated() implements a fluent interface');
+ $this->assertTrue($def->isDeprecated(), '->isDeprecated() returns true if the instance should not be used anymore.');
+ $this->assertSame('The "deprecated_service" service is deprecated. You should stop using it, as it will soon be removed.', $def->getDeprecationMessage('deprecated_service'), '->getDeprecationMessage() should return a formatted message template');
+ }
+
+ /**
+ * @dataProvider invalidDeprecationMessageProvider
+ * @covers Symfony\Component\DependencyInjection\Definition::setDeprecated
+ * @expectedException Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
+ */
+ public function testSetDeprecatedWithInvalidDeprecationTemplate($message)
+ {
+ $def = new Definition('stdClass');
+ $def->setDeprecated(false, $message);
+ }
+
+ public function invalidDeprecationMessageProvider()
+ {
+ return array(
+ "With \rs" => array("invalid \r message %service_id%"),
+ "With \ns" => array("invalid \n message %service_id%"),
+ 'With */s' => array('invalid */ message %service_id%'),
+ 'message not containing require %service_id% variable' => array('this is deprecated'),
+ );
+ }
+
/**
* @covers Symfony\Component\DependencyInjection\Definition::setConfigurator
* @covers Symfony\Component\DependencyInjection\Definition::getConfigurator
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php
index 5d63e348e5de2..96f334fdd153f 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container9.php
@@ -89,6 +89,10 @@
->register('decorator_service_with_name', 'stdClass')
->setDecoratedService('decorated', 'decorated.pif-pouf')
;
+$container
+ ->register('deprecated_service', 'stdClass')
+ ->setDeprecated(true)
+;
$container
->register('new_factory', 'FactoryClass')
->setProperty('foo', 'bar')
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot
index b3b424e2e73c7..f6536980aa54f 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/graphviz/services9.dot
@@ -17,6 +17,7 @@ digraph sc {
node_decorated [label="decorated\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_decorator_service [label="decorator_service\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_decorator_service_with_name [label="decorator_service_with_name\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
+ node_deprecated_service [label="deprecated_service\nstdClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_new_factory [label="new_factory\nFactoryClass\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_factory_service [label="factory_service\nBar\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_new_factory_service [label="new_factory_service\nFooBarBaz\n", shape=record, fillcolor="#eeeeee", style="filled"];
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php
index 5977c1c6e7a71..675f17ec2a717 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9.php
@@ -33,6 +33,7 @@ public function __construct()
'decorated' => 'getDecoratedService',
'decorator_service' => 'getDecoratorServiceService',
'decorator_service_with_name' => 'getDecoratorServiceWithNameService',
+ 'deprecated_service' => 'getDeprecatedServiceService',
'factory_service' => 'getFactoryServiceService',
'foo' => 'getFooService',
'foo.baz' => 'getFoo_BazService',
@@ -143,6 +144,23 @@ protected function getDecoratorServiceWithNameService()
return $this->services['decorator_service_with_name'] = new \stdClass();
}
+ /**
+ * Gets the 'deprecated_service' service.
+ *
+ * This service is shared.
+ * This method always returns the same instance of the service.
+ *
+ * @return \stdClass A stdClass instance.
+ *
+ * @deprecated The "deprecated_service" service is deprecated. You should stop using it, as it will soon be removed.
+ */
+ protected function getDeprecatedServiceService()
+ {
+ @trigger_error('The "deprecated_service" service is deprecated. You should stop using it, as it will soon be removed.', E_USER_DEPRECATED);
+
+ return $this->services['deprecated_service'] = new \stdClass();
+ }
+
/**
* Gets the 'factory_service' service.
*
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php
index 8c1e71f437a83..c616432bacc53 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php
@@ -37,6 +37,7 @@ public function __construct()
'configured_service' => 'getConfiguredServiceService',
'decorator_service' => 'getDecoratorServiceService',
'decorator_service_with_name' => 'getDecoratorServiceWithNameService',
+ 'deprecated_service' => 'getDeprecatedServiceService',
'factory_service' => 'getFactoryServiceService',
'foo' => 'getFooService',
'foo.baz' => 'getFoo_BazService',
@@ -144,6 +145,23 @@ protected function getDecoratorServiceWithNameService()
return $this->services['decorator_service_with_name'] = new \stdClass();
}
+ /**
+ * Gets the 'deprecated_service' service.
+ *
+ * This service is shared.
+ * This method always returns the same instance of the service.
+ *
+ * @return \stdClass A stdClass instance.
+ *
+ * @deprecated The "deprecated_service" service is deprecated. You should stop using it, as it will soon be removed.
+ */
+ protected function getDeprecatedServiceService()
+ {
+ @trigger_error('The "deprecated_service" service is deprecated. You should stop using it, as it will soon be removed.', E_USER_DEPRECATED);
+
+ return $this->services['deprecated_service'] = new \stdClass();
+ }
+
/**
* Gets the 'factory_service' service.
*
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml
index f1e6e98efaf67..4ddb8655c55f2 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services9.xml
@@ -87,6 +87,9 @@
+
+ The "%service_id%" service is deprecated. You should stop using it, as it will soon be removed.
+
bar
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml
index ddb5d3a96abdf..0525b3068cbaa 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services9.yml
@@ -76,6 +76,9 @@ services:
class: stdClass
decorates: decorated
decoration_inner_name: decorated.pif-pouf
+ deprecated_service:
+ class: stdClass
+ deprecated: The "%service_id%" service is deprecated. You should stop using it, as it will soon be removed.
new_factory:
class: FactoryClass
public: false