diff --git a/core/composer.json b/core/composer.json index afb170a..442f4a6 100644 --- a/core/composer.json +++ b/core/composer.json @@ -5,18 +5,18 @@ "license": "GPL-2.0+", "require": { "php": ">=5.5.9", - "symfony/class-loader": "2.7.*", - "symfony/console": "2.7.*", - "symfony/dependency-injection": "2.7.*", - "symfony/event-dispatcher": "2.7.*", - "symfony/http-foundation": "~2.7.2", - "symfony/http-kernel": "2.7.*", - "symfony/routing": "2.7.*", - "symfony/serializer": "2.7.*", - "symfony/translation": "2.7.*", - "symfony/validator": "2.7.*", - "symfony/process": "2.7.*", - "symfony/yaml": "2.7.*", + "symfony/class-loader": "~2.8", + "symfony/console": "~2.8", + "symfony/dependency-injection": "~2.8", + "symfony/event-dispatcher": "~2.8", + "symfony/http-foundation": "~2.8", + "symfony/http-kernel": "~2.8", + "symfony/routing": "~2.8", + "symfony/serializer": "~2.8", + "symfony/translation": "~2.8", + "symfony/validator": "~2.8", + "symfony/process": "~2.8", + "symfony/yaml": "~2.8", "twig/twig": "^1.23.1", "doctrine/common": "2.5.*", "doctrine/annotations": "1.2.*", diff --git a/core/core.services.yml b/core/core.services.yml index afccb31..415d3ce 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -497,6 +497,7 @@ services: entity.manager: class: Drupal\Core\Entity\EntityManager parent: container.trait + deprecated: %service_id%-no-warning # @todo Remove this tag in https://www.drupal.org/node/2549143. tags: - { name: plugin_manager_cache_clear } @@ -1015,7 +1016,7 @@ services: class: Symfony\Component\HttpKernel\EventListener\RouterListener tags: - { name: event_subscriber } - arguments: ['@router', '@router.request_context', NULL, '@request_stack'] + arguments: ['@router', '@request_stack', '@router.request_context', NULL] bare_html_page_renderer: class: Drupal\Core\Render\BareHtmlPageRenderer arguments: ['@renderer', '@html_response.attachments_processor'] @@ -1297,71 +1298,71 @@ services: calls: - [setContainer, ['@service_container']] arguments: ['feed.writer.'] -# Zend Feed reader plugins. Plugins should be set as prototype scope. +# Zend Feed reader plugins. Plugin instances should not be shared. feed.reader.dublincoreentry: class: Zend\Feed\Reader\Extension\DublinCore\Entry - scope: prototype + shared: false feed.reader.dublincorefeed: class: Zend\Feed\Reader\Extension\DublinCore\Feed - scope: prototype + shared: false feed.reader.contententry: class: Zend\Feed\Reader\Extension\Content\Entry - scope: prototype + shared: false feed.reader.atomentry: class: Zend\Feed\Reader\Extension\Atom\Entry - scope: prototype + shared: false feed.reader.atomfeed: class: Zend\Feed\Reader\Extension\Atom\Feed - scope: prototype + shared: false feed.reader.slashentry: class: Zend\Feed\Reader\Extension\Slash\Entry - scope: prototype + shared: false feed.reader.wellformedwebentry: class: Zend\Feed\Reader\Extension\WellFormedWeb\Entry - scope: prototype + shared: false feed.reader.threadentry: class: Zend\Feed\Reader\Extension\Thread\Entry - scope: prototype + shared: false feed.reader.podcastentry: class: Zend\Feed\Reader\Extension\Podcast\Entry - scope: prototype + shared: false feed.reader.podcastfeed: class: Zend\Feed\Reader\Extension\Podcast\Feed - scope: prototype + shared: false # Zend Feed writer plugins. Plugins should be set as prototype scope. feed.writer.atomrendererfeed: class: Zend\Feed\Writer\Extension\Atom\Renderer\Feed - scope: prototype + shared: false feed.writer.contentrendererentry: class: Zend\Feed\Writer\Extension\Content\Renderer\Entry - scope: prototype + shared: false feed.writer.dublincorerendererentry: class: Zend\Feed\Writer\Extension\DublinCore\Renderer\Entry - scope: prototype + shared: false feed.writer.dublincorerendererfeed: class: Zend\Feed\Writer\Extension\DublinCore\Renderer\Feed - scope: prototype + shared: false feed.writer.itunesentry: class: Zend\Feed\Writer\Extension\ITunes\Entry - scope: prototype + shared: false feed.writer.itunesfeed: class: Zend\Feed\Writer\Extension\ITunes\Feed - scope: prototype + shared: false feed.writer.itunesrendererentry: class: Zend\Feed\Writer\Extension\ITunes\Renderer\Entry - scope: prototype + shared: false feed.writer.itunesrendererfeed: class: Zend\Feed\Writer\Extension\ITunes\Renderer\Feed - scope: prototype + shared: false feed.writer.slashrendererentry: class: Zend\Feed\Writer\Extension\Slash\Renderer\Entry - scope: prototype + shared: false feed.writer.threadingrendererentry: class: Zend\Feed\Writer\Extension\Threading\Renderer\Entry - scope: prototype + shared: false feed.writer.wellformedwebrendererentry: class: Zend\Feed\Writer\Extension\WellFormedWeb\Renderer\Entry - scope: prototype + shared: false theme.manager: class: Drupal\Core\Theme\ThemeManager arguments: ['@app.root', '@theme.negotiator', '@theme.initialization', '@request_stack', '@module_handler'] diff --git a/core/lib/Drupal/Component/DependencyInjection/Container.php b/core/lib/Drupal/Component/DependencyInjection/Container.php index df25ae8..201b3d1 100644 --- a/core/lib/Drupal/Component/DependencyInjection/Container.php +++ b/core/lib/Drupal/Component/DependencyInjection/Container.php @@ -9,6 +9,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\IntrospectableContainerInterface; +use Symfony\Component\DependencyInjection\ResettableContainerInterface; use Symfony\Component\DependencyInjection\ScopeInterface; use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; @@ -54,7 +55,7 @@ * * @ingroup container */ -class Container implements IntrospectableContainerInterface { +class Container implements IntrospectableContainerInterface, ResettableContainerInterface { /** * The parameters of the container. @@ -182,11 +183,7 @@ public function get($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_IN } catch (\Exception $e) { unset($this->loading[$id]); - - // Remove a potentially shared service that was constructed incompletely. - if (array_key_exists($id, $this->services)) { - unset($this->services[$id]); - } + unset($this->services[$id]); if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalid_behavior) { return; @@ -201,6 +198,17 @@ public function get($id, $invalid_behavior = ContainerInterface::EXCEPTION_ON_IN } /** + * {@inheritdoc} + */ + public function reset() { + if (!empty($this->scopedServices)) { + throw new LogicException('Resetting the container is not allowed when a scope is active.'); + } + + $this->services = []; + } + + /** * Creates a service from a service definition. * * @param array $definition @@ -359,6 +367,10 @@ protected function createService(array $definition, $id) { * {@inheritdoc} */ public function set($id, $service, $scope = ContainerInterface::SCOPE_CONTAINER) { + if (!in_array($scope, array('container', 'request')) || ('request' === $scope && 'request' !== $id)) { + @trigger_error('The concept of container scopes is deprecated since version 2.8 and will be removed in 3.0. Omit the third parameter.', E_USER_DEPRECATED); + } + $this->services[$id] = $service; } @@ -585,6 +597,10 @@ protected function getParameterAlternatives($name) { * {@inheritdoc} */ public function enterScope($name) { + if ('request' !== $name) { + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + } + throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__)); } @@ -592,6 +608,10 @@ public function enterScope($name) { * {@inheritdoc} */ public function leaveScope($name) { + if ('request' !== $name) { + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + } + throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__)); } @@ -599,6 +619,11 @@ public function leaveScope($name) { * {@inheritdoc} */ public function addScope(ScopeInterface $scope) { + + $name = $scope->getName(); + if ('request' !== $name) { + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + } throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__)); } @@ -606,6 +631,10 @@ public function addScope(ScopeInterface $scope) { * {@inheritdoc} */ public function hasScope($name) { + if ('request' !== $name) { + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + } + throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__)); } @@ -613,6 +642,8 @@ public function hasScope($name) { * {@inheritdoc} */ public function isScopeActive($name) { + @trigger_error('The '.__METHOD__.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + throw new \BadMethodCallException(sprintf("'%s' is not supported by Drupal 8.", __FUNCTION__)); } @@ -626,4 +657,11 @@ public function getServiceIds() { return array_keys($this->serviceDefinitions + $this->services); } + /** + * Ensure that cloning doesn't work. + */ + private function __clone() + { + } + } diff --git a/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php b/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php index 23290b6..663c0d6 100644 --- a/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php +++ b/core/lib/Drupal/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumper.php @@ -252,6 +252,10 @@ protected function getServiceDefinition(Definition $definition) { } } + if ($definition->isShared() === FALSE) { + $service['shared'] = $definition->isShared(); + } + if (($decorated = $definition->getDecoratedService()) !== NULL) { throw new InvalidArgumentException("The 'decorated' definition is not supported by the Drupal 8 run-time container. The Container Builder should have resolved that during the DecoratorServicePass compiler pass."); } diff --git a/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php b/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php index 1edaec3..ff72be4 100644 --- a/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php +++ b/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php @@ -9,8 +9,12 @@ use Symfony\Component\DependencyInjection\ContainerBuilder as SymfonyContainerBuilder; use Symfony\Component\DependencyInjection\Container as SymfonyContainer; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\InactiveScopeException; /** * Drupal's dependency injection container builder. @@ -30,6 +34,146 @@ public function __construct(ParameterBagInterface $parameterBag = NULL) { } /** + * Creates a service for a service definition. + * + * Overrides the parent implementation, but just changes one line about + * deprecations, see below. + * + * @param \Symfony\Component\DependencyInjection\Definition $definition + * @param string $id + * @param bool|true $tryProxy + * + * @return mixed|object + */ + public function createService(Definition $definition, $id, $tryProxy = true) + { + if ($definition->isSynthetic()) { + 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()) { + // @this line is changed. + if ($definition->getDeprecationMessage($id) != ($id . '-no-warning')) { + @trigger_error($definition->getDeprecationMessage($id), E_USER_DEPRECATED); + } + } + + if ($tryProxy && $definition->isLazy()) { + $container = $this; + + $proxy = $this + ->getProxyInstantiator() + ->instantiateProxy( + $container, + $definition, + $id, function () use ($definition, $id, $container) { + return $container->createService($definition, $id, false); + } + ); + $this->shareService($definition, $proxy, $id); + + return $proxy; + } + + $parameterBag = $this->getParameterBag(); + + if (null !== $definition->getFile()) { + require_once $parameterBag->resolveValue($definition->getFile()); + } + + $arguments = $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments()))); + + if (null !== $factory = $definition->getFactory()) { + if (is_array($factory)) { + $factory = array($this->resolveServices($parameterBag->resolveValue($factory[0])), $factory[1]); + } elseif (!is_string($factory)) { + throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id)); + } + + $service = call_user_func_array($factory, $arguments); + + if (!$definition->isDeprecated() && is_array($factory) && is_string($factory[0])) { + $r = new \ReflectionClass($factory[0]); + + if (0 < strpos($r->getDocComment(), "\n * @deprecated ")) { + @trigger_error(sprintf('The "%s" service relies on the deprecated "%s" factory class. It should either be deprecated or its factory upgraded.', $id, $r->name), E_USER_DEPRECATED); + } + } + } elseif (null !== $definition->getFactoryMethod(false)) { + if (null !== $definition->getFactoryClass(false)) { + $factory = $parameterBag->resolveValue($definition->getFactoryClass(false)); + } elseif (null !== $definition->getFactoryService(false)) { + $factory = $this->get($parameterBag->resolveValue($definition->getFactoryService(false))); + } else { + throw new RuntimeException(sprintf('Cannot create service "%s" from factory method without a factory service or factory class.', $id)); + } + + $service = call_user_func_array(array($factory, $definition->getFactoryMethod(false)), $arguments); + } else { + $r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass())); + + $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments); + + if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ")) { + // Skip deprecation notices for deprecations which opt out. + @trigger_error(sprintf('The "%s" service relies on the deprecated "%s" class. It should either be deprecated or its implementation upgraded.', $id, $r->name), E_USER_DEPRECATED); + } + } + + if ($tryProxy || !$definition->isLazy()) { + // share only if proxying failed, or if not a proxy + $this->shareService($definition, $service, $id); + } + + foreach ($definition->getMethodCalls() as $call) { + $this->callMethod($service, $call); + } + + $properties = $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties()))); + foreach ($properties as $name => $value) { + $service->$name = $value; + } + + if ($callable = $definition->getConfigurator()) { + if (is_array($callable)) { + $callable[0] = $parameterBag->resolveValue($callable[0]); + + if ($callable[0] instanceof Reference) { + $callable[0] = $this->get((string) $callable[0], $callable[0]->getInvalidBehavior()); + } elseif ($callable[0] instanceof Definition) { + $callable[0] = $this->createService($callable[0], null); + } + } + + if (!is_callable($callable)) { + throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', get_class($service))); + } + + call_user_func($callable, $service); + } + + return $service; + } + + /** + * Direct copy of the parent function. + */ + protected function shareService(Definition $definition, $service, $id) + { + if ($definition->isShared() && self::SCOPE_PROTOTYPE !== $scope = $definition->getScope(false)) { + if (self::SCOPE_CONTAINER !== $scope && !isset($this->scopedServices[$scope])) { + throw new InactiveScopeException($id, $scope); + } + + $this->services[$lowerId = strtolower($id)] = $service; + + if (self::SCOPE_CONTAINER !== $scope) { + $this->scopedServices[$scope][$lowerId] = $service; + } + } + } + + /** * Overrides Symfony\Component\DependencyInjection\ContainerBuilder::set(). * * Drupal's container builder can be used at runtime after compilation, so we diff --git a/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php b/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php index 4c7fea9..1c2bf81 100644 --- a/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php +++ b/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php @@ -159,8 +159,15 @@ private function parseDefinition($id, $service, $file) $definition->setClass($service['class']); } + if (isset($service['shared'])) { + $definition->setShared($service['shared']); + } + if (isset($service['scope'])) { - $definition->setScope($service['scope']); + if ('request' !== $id) { + @trigger_error(sprintf('The "scope" key of service "%s" in file "%s" is deprecated since version 2.8 and will be removed in 3.0.', $id, $file), E_USER_DEPRECATED); + } + $definition->setScope($service['scope'], false); } if (isset($service['synthetic'])) { @@ -183,6 +190,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) { @@ -275,7 +286,30 @@ private function parseDefinition($id, $service, $file) if (isset($service['decorates'])) { $renameId = isset($service['decoration_inner_name']) ? $service['decoration_inner_name'] : null; - $definition->setDecoratedService($service['decorates'], $renameId); + $priority = isset($service['decoration_priority']) ? $service['decoration_priority'] : 0; + $definition->setDecoratedService($service['decorates'], $renameId, $priority); + } + + if (isset($service['autowire'])) { + $definition->setAutowired($service['autowire']); + } + + if (isset($service['autowiring_types'])) { + if (is_string($service['autowiring_types'])) { + $definition->addAutowiringType($service['autowiring_types']); + } else { + if (!is_array($service['autowiring_types'])) { + throw new InvalidArgumentException(sprintf('Parameter "autowiring_types" must be a string or an array for service "%s" in %s. Check your YAML syntax.', $id, $file)); + } + + foreach ($service['autowiring_types'] as $autowiringType) { + if (!is_string($autowiringType)) { + throw new InvalidArgumentException(sprintf('A "autowiring_types" attribute must be of type string for service "%s" in %s. Check your YAML syntax.', $id, $file)); + } + + $definition->addAutowiringType($autowiringType); + } + } } $this->container->setDefinition($id, $definition); diff --git a/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php b/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php index ca9a2e4..5a4e57f 100644 --- a/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php +++ b/core/tests/Drupal/Tests/Component/DependencyInjection/Dumper/OptimizedPhpArrayDumperTest.php @@ -358,6 +358,10 @@ public function getDefinitionsDataProvider() { 'shared' => FALSE, ) + $base_service_definition; + $service_definitions[] = array( + 'shared' => FALSE, + ) + $base_service_definition; + // Test factory. $service_definitions[] = array( 'factory' => array(new Reference('bar'), 'factoryMethod'), @@ -397,6 +401,7 @@ public function getDefinitionsDataProvider() { $definition->getProperties()->willReturn($service_definition['properties']); $definition->getMethodCalls()->willReturn($service_definition['calls']); $definition->getScope()->willReturn($service_definition['scope']); + $definition->isShared()->willReturn($service_definition['shared']); $definition->getDecoratedService()->willReturn(NULL); $definition->getFactory()->willReturn($service_definition['factory']); $definition->getConfigurator()->willReturn($service_definition['configurator']); diff --git a/core/tests/Drupal/Tests/Component/EventDispatcher/ContainerAwareEventDispatcherTest.php b/core/tests/Drupal/Tests/Component/EventDispatcher/ContainerAwareEventDispatcherTest.php index f36d836..01d259a 100644 --- a/core/tests/Drupal/Tests/Component/EventDispatcher/ContainerAwareEventDispatcherTest.php +++ b/core/tests/Drupal/Tests/Component/EventDispatcher/ContainerAwareEventDispatcherTest.php @@ -13,6 +13,7 @@ use Symfony\Component\EventDispatcher\Tests\AbstractEventDispatcherTest; use Symfony\Component\EventDispatcher\Tests\CallableClass; use Symfony\Component\EventDispatcher\Tests\TestEventListener; +use Symfony\Component\EventDispatcher\Tests\ContainerAwareEventDispatcherTest as SymfonyContainerAwareEventDispatcherTest; /** * Unit tests for the ContainerAwareEventDispatcher. @@ -27,7 +28,7 @@ * * @group Symfony */ -class ContainerAwareEventDispatcherTest extends AbstractEventDispatcherTest +class ContainerAwareEventDispatcherTest extends SymfonyContainerAwareEventDispatcherTest { protected function createEventDispatcher() { @@ -175,4 +176,11 @@ public function testRemoveService() $otherService = $container->get('other_listener_service'); $this->assertTrue($otherService->preFooInvoked); } -} + + public function testGetListenerPriority() + { + // Override the parent test as our implementation doesn't provider + // getListenerPriority(). + } + + } diff --git a/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/BackendCompilerPassTest.php b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/BackendCompilerPassTest.php index 2efb13f..ee13b93 100644 --- a/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/BackendCompilerPassTest.php +++ b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/BackendCompilerPassTest.php @@ -72,7 +72,8 @@ public function providerTestProcess() { // Configure a manual alias for the service, so ensure that it is not // overridden by the default backend. - $container = clone $container; + $container = $this->getMysqlContainer($service); + $container->setParameter('default_backend', 'mysql'); $container->setDefinition('mariadb.service', new Definition($prefix . 'MariaDb')); $container->setAlias('service', new Alias('mariadb.service')); $data[] = array($prefix . 'MariaDb', $container);