Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / vendor / symfony / dependency-injection / Loader / YamlFileLoader.php
index cd9bcbba100876a45086b3f427fbe34942285f83..fa27767b40388666b95c64d52f5ff8dec711880a 100644 (file)
 
 namespace Symfony\Component\DependencyInjection\Loader;
 
-use Symfony\Component\DependencyInjection\DefinitionDecorator;
 use Symfony\Component\DependencyInjection\Alias;
+use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
+use Symfony\Component\DependencyInjection\Argument\BoundArgument;
+use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
+use Symfony\Component\DependencyInjection\ChildDefinition;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\DependencyInjection\Definition;
 use Symfony\Component\DependencyInjection\Reference;
 use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
 use Symfony\Component\DependencyInjection\Exception\RuntimeException;
-use Symfony\Component\Config\Resource\FileResource;
 use Symfony\Component\Yaml\Exception\ParseException;
 use Symfony\Component\Yaml\Parser as YamlParser;
+use Symfony\Component\Yaml\Tag\TaggedValue;
 use Symfony\Component\Yaml\Yaml;
 use Symfony\Component\ExpressionLanguage\Expression;
 
 /**
  * YamlFileLoader loads YAML files service definitions.
  *
- * The YAML format does not support anonymous services (cf. the XML loader).
- *
  * @author Fabien Potencier <fabien@symfony.com>
  */
 class YamlFileLoader extends FileLoader
 {
-    private static $keywords = array(
+    private static $serviceKeywords = array(
         'alias' => 'alias',
         'parent' => 'parent',
         'class' => 'class',
@@ -55,10 +58,55 @@ class YamlFileLoader extends FileLoader
         'decoration_priority' => 'decoration_priority',
         'autowire' => 'autowire',
         'autowiring_types' => 'autowiring_types',
+        'autoconfigure' => 'autoconfigure',
+        'bind' => 'bind',
+    );
+
+    private static $prototypeKeywords = array(
+        'resource' => 'resource',
+        'namespace' => 'namespace',
+        'exclude' => 'exclude',
+        'parent' => 'parent',
+        'shared' => 'shared',
+        'lazy' => 'lazy',
+        'public' => 'public',
+        'abstract' => 'abstract',
+        'deprecated' => 'deprecated',
+        'factory' => 'factory',
+        'arguments' => 'arguments',
+        'properties' => 'properties',
+        'configurator' => 'configurator',
+        'calls' => 'calls',
+        'tags' => 'tags',
+        'autowire' => 'autowire',
+        'autoconfigure' => 'autoconfigure',
+        'bind' => 'bind',
+    );
+
+    private static $instanceofKeywords = array(
+        'shared' => 'shared',
+        'lazy' => 'lazy',
+        'public' => 'public',
+        'properties' => 'properties',
+        'configurator' => 'configurator',
+        'calls' => 'calls',
+        'tags' => 'tags',
+        'autowire' => 'autowire',
+    );
+
+    private static $defaultsKeywords = array(
+        'public' => 'public',
+        'tags' => 'tags',
+        'autowire' => 'autowire',
+        'autoconfigure' => 'autoconfigure',
+        'bind' => 'bind',
     );
 
     private $yamlParser;
 
+    private $anonymousServicesCount;
+    private $anonymousServicesSuffix;
+
     /**
      * {@inheritdoc}
      */
@@ -68,7 +116,7 @@ class YamlFileLoader extends FileLoader
 
         $content = $this->loadFile($path);
 
-        $this->container->addResource(new FileResource($path));
+        $this->container->fileExists($path);
 
         // empty file
         if (null === $content) {
@@ -81,11 +129,11 @@ class YamlFileLoader extends FileLoader
         // parameters
         if (isset($content['parameters'])) {
             if (!is_array($content['parameters'])) {
-                throw new InvalidArgumentException(sprintf('The "parameters" key should contain an array in %s. Check your YAML syntax.', $resource));
+                throw new InvalidArgumentException(sprintf('The "parameters" key should contain an array in %s. Check your YAML syntax.', $path));
             }
 
             foreach ($content['parameters'] as $key => $value) {
-                $this->container->setParameter($key, $this->resolveServices($value));
+                $this->container->setParameter($key, $this->resolveServices($value, $path, true));
             }
         }
 
@@ -93,7 +141,14 @@ class YamlFileLoader extends FileLoader
         $this->loadFromExtensions($content);
 
         // services
-        $this->parseDefinitions($content, $resource);
+        $this->anonymousServicesCount = 0;
+        $this->anonymousServicesSuffix = ContainerBuilder::hash($path);
+        $this->setCurrentDir(dirname($path));
+        try {
+            $this->parseDefinitions($content, $path);
+        } finally {
+            $this->instanceof = array();
+        }
     }
 
     /**
@@ -101,7 +156,15 @@ class YamlFileLoader extends FileLoader
      */
     public function supports($resource, $type = null)
     {
-        return is_string($resource) && in_array(pathinfo($resource, PATHINFO_EXTENSION), array('yml', 'yaml'), true);
+        if (!is_string($resource)) {
+            return false;
+        }
+
+        if (null === $type && in_array(pathinfo($resource, PATHINFO_EXTENSION), array('yaml', 'yml'), true)) {
+            return true;
+        }
+
+        return in_array($type, array('yaml', 'yml'), true);
     }
 
     /**
@@ -123,11 +186,14 @@ class YamlFileLoader extends FileLoader
         $defaultDirectory = dirname($file);
         foreach ($content['imports'] as $import) {
             if (!is_array($import)) {
-                throw new InvalidArgumentException(sprintf('The values in the "imports" key should be arrays in %s. Check your YAML syntax.', $file));
+                $import = array('resource' => $import);
+            }
+            if (!isset($import['resource'])) {
+                throw new InvalidArgumentException(sprintf('An import should provide a resource in %s. Check your YAML syntax.', $file));
             }
 
             $this->setCurrentDir($defaultDirectory);
-            $this->import($import['resource'], null, isset($import['ignore_errors']) ? (bool) $import['ignore_errors'] : false, $file);
+            $this->import($import['resource'], isset($import['type']) ? $import['type'] : null, isset($import['ignore_errors']) ? (bool) $import['ignore_errors'] : false, $file);
         }
     }
 
@@ -147,37 +213,159 @@ class YamlFileLoader extends FileLoader
             throw new InvalidArgumentException(sprintf('The "services" key should contain an array in %s. Check your YAML syntax.', $file));
         }
 
+        if (array_key_exists('_instanceof', $content['services'])) {
+            $instanceof = $content['services']['_instanceof'];
+            unset($content['services']['_instanceof']);
+
+            if (!is_array($instanceof)) {
+                throw new InvalidArgumentException(sprintf('Service "_instanceof" key must be an array, "%s" given in "%s".', gettype($instanceof), $file));
+            }
+            $this->instanceof = array();
+            $this->isLoadingInstanceof = true;
+            foreach ($instanceof as $id => $service) {
+                if (!$service || !is_array($service)) {
+                    throw new InvalidArgumentException(sprintf('Type definition "%s" must be a non-empty array within "_instanceof" in %s. Check your YAML syntax.', $id, $file));
+                }
+                if (is_string($service) && 0 === strpos($service, '@')) {
+                    throw new InvalidArgumentException(sprintf('Type definition "%s" cannot be an alias within "_instanceof" in %s. Check your YAML syntax.', $id, $file));
+                }
+                $this->parseDefinition($id, $service, $file, array());
+            }
+        }
+
+        $this->isLoadingInstanceof = false;
+        $defaults = $this->parseDefaults($content, $file);
         foreach ($content['services'] as $id => $service) {
-            $this->parseDefinition($id, $service, $file);
+            $this->parseDefinition($id, $service, $file, $defaults);
         }
     }
 
+    /**
+     * @param array  $content
+     * @param string $file
+     *
+     * @return array
+     *
+     * @throws InvalidArgumentException
+     */
+    private function parseDefaults(array &$content, $file)
+    {
+        if (!array_key_exists('_defaults', $content['services'])) {
+            return array();
+        }
+        $defaults = $content['services']['_defaults'];
+        unset($content['services']['_defaults']);
+
+        if (!is_array($defaults)) {
+            throw new InvalidArgumentException(sprintf('Service "_defaults" key must be an array, "%s" given in "%s".', gettype($defaults), $file));
+        }
+
+        foreach ($defaults as $key => $default) {
+            if (!isset(self::$defaultsKeywords[$key])) {
+                throw new InvalidArgumentException(sprintf('The configuration key "%s" cannot be used to define a default value in "%s". Allowed keys are "%s".', $key, $file, implode('", "', self::$defaultsKeywords)));
+            }
+        }
+
+        if (isset($defaults['tags'])) {
+            if (!is_array($tags = $defaults['tags'])) {
+                throw new InvalidArgumentException(sprintf('Parameter "tags" in "_defaults" must be an array in %s. Check your YAML syntax.', $file));
+            }
+
+            foreach ($tags as $tag) {
+                if (!is_array($tag)) {
+                    $tag = array('name' => $tag);
+                }
+
+                if (!isset($tag['name'])) {
+                    throw new InvalidArgumentException(sprintf('A "tags" entry in "_defaults" is missing a "name" key in %s.', $file));
+                }
+                $name = $tag['name'];
+                unset($tag['name']);
+
+                if (!is_string($name) || '' === $name) {
+                    throw new InvalidArgumentException(sprintf('The tag name in "_defaults" must be a non-empty string in %s.', $file));
+                }
+
+                foreach ($tag as $attribute => $value) {
+                    if (!is_scalar($value) && null !== $value) {
+                        throw new InvalidArgumentException(sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type in %s. Check your YAML syntax.', $name, $attribute, $file));
+                    }
+                }
+            }
+        }
+
+        if (isset($defaults['bind'])) {
+            if (!is_array($defaults['bind'])) {
+                throw new InvalidArgumentException(sprintf('Parameter "bind" in "_defaults" must be an array in %s. Check your YAML syntax.', $file));
+            }
+
+            $defaults['bind'] = array_map(function ($v) { return new BoundArgument($v); }, $this->resolveServices($defaults['bind'], $file));
+        }
+
+        return $defaults;
+    }
+
+    /**
+     * @param array $service
+     *
+     * @return bool
+     */
+    private function isUsingShortSyntax(array $service)
+    {
+        foreach ($service as $key => $value) {
+            if (is_string($key) && ('' === $key || '$' !== $key[0])) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     /**
      * Parses a definition.
      *
      * @param string       $id
      * @param array|string $service
      * @param string       $file
+     * @param array        $defaults
      *
      * @throws InvalidArgumentException When tags are invalid
      */
-    private function parseDefinition($id, $service, $file)
+    private function parseDefinition($id, $service, $file, array $defaults)
     {
+        if (preg_match('/^_[a-zA-Z0-9_]*$/', $id)) {
+            @trigger_error(sprintf('Service names that start with an underscore are deprecated since Symfony 3.3 and will be reserved in 4.0. Rename the "%s" service or define it in XML instead.', $id), E_USER_DEPRECATED);
+        }
         if (is_string($service) && 0 === strpos($service, '@')) {
-            $this->container->setAlias($id, substr($service, 1));
+            $this->container->setAlias($id, $alias = new Alias(substr($service, 1)));
+            if (isset($defaults['public'])) {
+                $alias->setPublic($defaults['public']);
+            }
 
             return;
         }
 
+        if (is_array($service) && $this->isUsingShortSyntax($service)) {
+            $service = array('arguments' => $service);
+        }
+
+        if (null === $service) {
+            $service = array();
+        }
+
         if (!is_array($service)) {
             throw new InvalidArgumentException(sprintf('A service definition must be an array or a string starting with "@" but %s found for service "%s" in %s. Check your YAML syntax.', gettype($service), $id, $file));
         }
 
-        static::checkDefinition($id, $service, $file);
+        $this->checkDefinition($id, $service, $file);
 
         if (isset($service['alias'])) {
-            $public = !array_key_exists('public', $service) || (bool) $service['public'];
-            $this->container->setAlias($id, new Alias($service['alias'], $public));
+            $this->container->setAlias($id, $alias = new Alias($service['alias']));
+            if (array_key_exists('public', $service)) {
+                $alias->setPublic($service['public']);
+            } elseif (isset($defaults['public'])) {
+                $alias->setPublic($defaults['public']);
+            }
 
             foreach ($service as $key => $value) {
                 if (!in_array($key, array('alias', 'public'))) {
@@ -188,10 +376,42 @@ class YamlFileLoader extends FileLoader
             return;
         }
 
-        if (isset($service['parent'])) {
-            $definition = new DefinitionDecorator($service['parent']);
+        if ($this->isLoadingInstanceof) {
+            $definition = new ChildDefinition('');
+        } elseif (isset($service['parent'])) {
+            if (!empty($this->instanceof)) {
+                throw new InvalidArgumentException(sprintf('The service "%s" cannot use the "parent" option in the same file where "_instanceof" configuration is defined as using both is not supported. Move your child definitions to a separate file.', $id));
+            }
+
+            foreach ($defaults as $k => $v) {
+                if ('tags' === $k) {
+                    // since tags are never inherited from parents, there is no confusion
+                    // thus we can safely add them as defaults to ChildDefinition
+                    continue;
+                }
+                if ('bind' === $k) {
+                    throw new InvalidArgumentException(sprintf('Attribute "bind" on service "%s" cannot be inherited from "_defaults" when a "parent" is set. Move your child definitions to a separate file.', $id));
+                }
+                if (!isset($service[$k])) {
+                    throw new InvalidArgumentException(sprintf('Attribute "%s" on service "%s" cannot be inherited from "_defaults" when a "parent" is set. Move your child definitions to a separate file or define this attribute explicitly.', $k, $id));
+                }
+            }
+
+            $definition = new ChildDefinition($service['parent']);
         } else {
             $definition = new Definition();
+
+            if (isset($defaults['public'])) {
+                $definition->setPublic($defaults['public']);
+            }
+            if (isset($defaults['autowire'])) {
+                $definition->setAutowired($defaults['autowire']);
+            }
+            if (isset($defaults['autoconfigure'])) {
+                $definition->setAutoconfigured($defaults['autoconfigure']);
+            }
+
+            $definition->setChanges(array());
         }
 
         if (isset($service['class'])) {
@@ -231,11 +451,11 @@ class YamlFileLoader extends FileLoader
         }
 
         if (isset($service['arguments'])) {
-            $definition->setArguments($this->resolveServices($service['arguments']));
+            $definition->setArguments($this->resolveServices($service['arguments'], $file));
         }
 
         if (isset($service['properties'])) {
-            $definition->setProperties($this->resolveServices($service['properties']));
+            $definition->setProperties($this->resolveServices($service['properties'], $file));
         }
 
         if (isset($service['configurator'])) {
@@ -250,45 +470,50 @@ class YamlFileLoader extends FileLoader
             foreach ($service['calls'] as $call) {
                 if (isset($call['method'])) {
                     $method = $call['method'];
-                    $args = isset($call['arguments']) ? $this->resolveServices($call['arguments']) : array();
+                    $args = isset($call['arguments']) ? $this->resolveServices($call['arguments'], $file) : array();
                 } else {
                     $method = $call[0];
-                    $args = isset($call[1]) ? $this->resolveServices($call[1]) : array();
+                    $args = isset($call[1]) ? $this->resolveServices($call[1], $file) : array();
                 }
 
+                if (!is_array($args)) {
+                    throw new InvalidArgumentException(sprintf('The second parameter for function call "%s" must be an array of its arguments for service "%s" in %s. Check your YAML syntax.', $method, $id, $file));
+                }
                 $definition->addMethodCall($method, $args);
             }
         }
 
-        if (isset($service['tags'])) {
-            if (!is_array($service['tags'])) {
-                throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
-            }
+        $tags = isset($service['tags']) ? $service['tags'] : array();
+        if (!is_array($tags)) {
+            throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
+        }
 
-            foreach ($service['tags'] as $tag) {
-                if (!is_array($tag)) {
-                    throw new InvalidArgumentException(sprintf('A "tags" entry must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
-                }
+        if (isset($defaults['tags'])) {
+            $tags = array_merge($tags, $defaults['tags']);
+        }
 
-                if (!isset($tag['name'])) {
-                    throw new InvalidArgumentException(sprintf('A "tags" entry is missing a "name" key for service "%s" in %s.', $id, $file));
-                }
+        foreach ($tags as $tag) {
+            if (!is_array($tag)) {
+                $tag = array('name' => $tag);
+            }
 
-                if (!is_string($tag['name']) || '' === $tag['name']) {
-                    throw new InvalidArgumentException(sprintf('The tag name for service "%s" in %s must be a non-empty string.', $id, $file));
-                }
+            if (!isset($tag['name'])) {
+                throw new InvalidArgumentException(sprintf('A "tags" entry is missing a "name" key for service "%s" in %s.', $id, $file));
+            }
+            $name = $tag['name'];
+            unset($tag['name']);
 
-                $name = $tag['name'];
-                unset($tag['name']);
+            if (!is_string($name) || '' === $name) {
+                throw new InvalidArgumentException(sprintf('The tag name for service "%s" in %s must be a non-empty string.', $id, $file));
+            }
 
-                foreach ($tag as $attribute => $value) {
-                    if (!is_scalar($value) && null !== $value) {
-                        throw new InvalidArgumentException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in %s. Check your YAML syntax.', $id, $name, $attribute, $file));
-                    }
+            foreach ($tag as $attribute => $value) {
+                if (!is_scalar($value) && null !== $value) {
+                    throw new InvalidArgumentException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in %s. Check your YAML syntax.', $id, $name, $attribute, $file));
                 }
-
-                $definition->addTag($name, $tag);
             }
+
+            $definition->addTag($name, $tag);
         }
 
         if (isset($service['decorates'])) {
@@ -323,7 +548,43 @@ class YamlFileLoader extends FileLoader
             }
         }
 
-        $this->container->setDefinition($id, $definition);
+        if (isset($defaults['bind']) || isset($service['bind'])) {
+            // deep clone, to avoid multiple process of the same instance in the passes
+            $bindings = isset($defaults['bind']) ? unserialize(serialize($defaults['bind'])) : array();
+
+            if (isset($service['bind'])) {
+                if (!is_array($service['bind'])) {
+                    throw new InvalidArgumentException(sprintf('Parameter "bind" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
+                }
+
+                $bindings = array_merge($bindings, $this->resolveServices($service['bind'], $file));
+            }
+
+            $definition->setBindings($bindings);
+        }
+
+        if (isset($service['autoconfigure'])) {
+            if (!$definition instanceof ChildDefinition) {
+                $definition->setAutoconfigured($service['autoconfigure']);
+            } elseif ($service['autoconfigure']) {
+                throw new InvalidArgumentException(sprintf('The service "%s" cannot have a "parent" and also have "autoconfigure". Try setting "autoconfigure: false" for the service.', $id));
+            }
+        }
+
+        if (array_key_exists('namespace', $service) && !array_key_exists('resource', $service)) {
+            throw new InvalidArgumentException(sprintf('A "resource" attribute must be set when the "namespace" attribute is set for service "%s" in %s. Check your YAML syntax.', $id, $file));
+        }
+
+        if (array_key_exists('resource', $service)) {
+            if (!is_string($service['resource'])) {
+                throw new InvalidArgumentException(sprintf('A "resource" attribute must be of type string for service "%s" in %s. Check your YAML syntax.', $id, $file));
+            }
+            $exclude = isset($service['exclude']) ? $service['exclude'] : null;
+            $namespace = isset($service['namespace']) ? $service['namespace'] : $id;
+            $this->registerClasses($definition, $namespace, $service['resource'], $exclude);
+        } else {
+            $this->setDefinition($id, $definition);
+        }
     }
 
     /**
@@ -348,7 +609,7 @@ class YamlFileLoader extends FileLoader
             if (false !== strpos($callable, ':') && false === strpos($callable, '::')) {
                 $parts = explode(':', $callable);
 
-                return array($this->resolveServices('@'.$parts[0]), $parts[1]);
+                return array($this->resolveServices('@'.$parts[0], $file), $parts[1]);
             }
 
             return $callable;
@@ -356,7 +617,11 @@ class YamlFileLoader extends FileLoader
 
         if (is_array($callable)) {
             if (isset($callable[0]) && isset($callable[1])) {
-                return array($this->resolveServices($callable[0]), $callable[1]);
+                return array($this->resolveServices($callable[0], $file), $callable[1]);
+            }
+
+            if ('factory' === $parameter && isset($callable[1]) && null === $callable[0]) {
+                return $callable;
             }
 
             throw new InvalidArgumentException(sprintf('Parameter "%s" must contain an array with two elements for service "%s" in %s. Check your YAML syntax.', $parameter, $id, $file));
@@ -392,10 +657,18 @@ class YamlFileLoader extends FileLoader
             $this->yamlParser = new YamlParser();
         }
 
+        $prevErrorHandler = set_error_handler(function ($level, $message, $script, $line) use ($file, &$prevErrorHandler) {
+            $message = E_USER_DEPRECATED === $level ? preg_replace('/ on line \d+/', ' in "'.$file.'"$0', $message) : $message;
+
+            return $prevErrorHandler ? $prevErrorHandler($level, $message, $script, $line) : false;
+        });
+
         try {
-            $configuration = $this->yamlParser->parse(file_get_contents($file), Yaml::PARSE_CONSTANT);
+            $configuration = $this->yamlParser->parseFile($file, Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS);
         } catch (ParseException $e) {
             throw new InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $file), 0, $e);
+        } finally {
+            restore_error_handler();
         }
 
         return $this->validate($configuration, $file);
@@ -444,20 +717,79 @@ class YamlFileLoader extends FileLoader
     /**
      * Resolves services.
      *
-     * @param string|array $value
+     * @param mixed  $value
+     * @param string $file
+     * @param bool   $isParameter
      *
-     * @return array|string|Reference
+     * @return array|string|Reference|ArgumentInterface
      */
-    private function resolveServices($value)
+    private function resolveServices($value, $file, $isParameter = false)
     {
+        if ($value instanceof TaggedValue) {
+            $argument = $value->getValue();
+            if ('iterator' === $value->getTag()) {
+                if (!is_array($argument)) {
+                    throw new InvalidArgumentException(sprintf('"!iterator" tag only accepts sequences in "%s".', $file));
+                }
+                $argument = $this->resolveServices($argument, $file, $isParameter);
+                try {
+                    return new IteratorArgument($argument);
+                } catch (InvalidArgumentException $e) {
+                    throw new InvalidArgumentException(sprintf('"!iterator" tag only accepts arrays of "@service" references in "%s".', $file));
+                }
+            }
+            if ('tagged' === $value->getTag()) {
+                if (!is_string($argument) || !$argument) {
+                    throw new InvalidArgumentException(sprintf('"!tagged" tag only accepts non empty string in "%s".', $file));
+                }
+
+                return new TaggedIteratorArgument($argument);
+            }
+            if ('service' === $value->getTag()) {
+                if ($isParameter) {
+                    throw new InvalidArgumentException(sprintf('Using an anonymous service in a parameter is not allowed in "%s".', $file));
+                }
+
+                $isLoadingInstanceof = $this->isLoadingInstanceof;
+                $this->isLoadingInstanceof = false;
+                $instanceof = $this->instanceof;
+                $this->instanceof = array();
+
+                $id = sprintf('%d_%s', ++$this->anonymousServicesCount, preg_replace('/^.*\\\\/', '', isset($argument['class']) ? $argument['class'] : '').$this->anonymousServicesSuffix);
+                $this->parseDefinition($id, $argument, $file, array());
+
+                if (!$this->container->hasDefinition($id)) {
+                    throw new InvalidArgumentException(sprintf('Creating an alias using the tag "!service" is not allowed in "%s".', $file));
+                }
+
+                $this->container->getDefinition($id)->setPublic(false);
+
+                $this->isLoadingInstanceof = $isLoadingInstanceof;
+                $this->instanceof = $instanceof;
+
+                return new Reference($id);
+            }
+
+            throw new InvalidArgumentException(sprintf('Unsupported tag "!%s".', $value->getTag()));
+        }
+
         if (is_array($value)) {
-            $value = array_map(array($this, 'resolveServices'), $value);
+            foreach ($value as $k => $v) {
+                $value[$k] = $this->resolveServices($v, $file, $isParameter);
+            }
         } elseif (is_string($value) && 0 === strpos($value, '@=')) {
+            if (!class_exists(Expression::class)) {
+                throw new \LogicException(sprintf('The "@=" expression syntax cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'));
+            }
+
             return new Expression(substr($value, 2));
         } elseif (is_string($value) && 0 === strpos($value, '@')) {
             if (0 === strpos($value, '@@')) {
                 $value = substr($value, 1);
                 $invalidBehavior = null;
+            } elseif (0 === strpos($value, '@!')) {
+                $value = substr($value, 2);
+                $invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE;
             } elseif (0 === strpos($value, '@?')) {
                 $value = substr($value, 2);
                 $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
@@ -467,6 +799,7 @@ class YamlFileLoader extends FileLoader
             }
 
             if ('=' === substr($value, -1)) {
+                @trigger_error(sprintf('The "=" suffix that used to disable strict references in Symfony 2.x is deprecated since Symfony 3.3 and will be unsupported in 4.0. Remove it in "%s".', $value), E_USER_DEPRECATED);
                 $value = substr($value, 0, -1);
             }
 
@@ -480,8 +813,6 @@ class YamlFileLoader extends FileLoader
 
     /**
      * Loads from Extensions.
-     *
-     * @param array $content
      */
     private function loadFromExtensions(array $content)
     {
@@ -490,7 +821,7 @@ class YamlFileLoader extends FileLoader
                 continue;
             }
 
-            if (!is_array($values)) {
+            if (!is_array($values) && null !== $values) {
                 $values = array();
             }
 
@@ -505,15 +836,23 @@ class YamlFileLoader extends FileLoader
      * @param array  $definition The service definition to check
      * @param string $file       The loaded YAML file
      */
-    private static function checkDefinition($id, array $definition, $file)
+    private function checkDefinition($id, array $definition, $file)
     {
+        if ($throw = $this->isLoadingInstanceof) {
+            $keywords = self::$instanceofKeywords;
+        } elseif ($throw = (isset($definition['resource']) || isset($definition['namespace']))) {
+            $keywords = self::$prototypeKeywords;
+        } else {
+            $keywords = self::$serviceKeywords;
+        }
+
         foreach ($definition as $key => $value) {
-            if (!isset(static::$keywords[$key])) {
-                @trigger_error(sprintf('The configuration key "%s" is unsupported for service definition "%s" in "%s". Allowed configuration keys are "%s". The YamlFileLoader object will raise an exception instead in Symfony 4.0 when detecting an unsupported service configuration key.', $key, $id, $file, implode('", "', static::$keywords)), E_USER_DEPRECATED);
-                // @deprecated Uncomment the following statement in Symfony 4.0
-                // and also update the corresponding unit test to make it expect
-                // an InvalidArgumentException exception.
-                //throw new InvalidArgumentException(sprintf('The configuration key "%s" is unsupported for service definition "%s" in "%s". Allowed configuration keys are "%s".', $key, $id, $file, implode('", "', static::$keywords)));
+            if (!isset($keywords[$key])) {
+                if ($throw) {
+                    throw new InvalidArgumentException(sprintf('The configuration key "%s" is unsupported for definition "%s" in "%s". Allowed configuration keys are "%s".', $key, $id, $file, implode('", "', $keywords)));
+                }
+
+                @trigger_error(sprintf('The configuration key "%s" is unsupported for service definition "%s" in "%s". Allowed configuration keys are "%s". The YamlFileLoader object will raise an exception instead in Symfony 4.0 when detecting an unsupported service configuration key.', $key, $id, $file, implode('", "', $keywords)), E_USER_DEPRECATED);
             }
         }
     }