annotate vendor/symfony/dependency-injection/Loader/YamlFileLoader.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 /*
Chris@0 4 * This file is part of the Symfony package.
Chris@0 5 *
Chris@0 6 * (c) Fabien Potencier <fabien@symfony.com>
Chris@0 7 *
Chris@0 8 * For the full copyright and license information, please view the LICENSE
Chris@0 9 * file that was distributed with this source code.
Chris@0 10 */
Chris@0 11
Chris@0 12 namespace Symfony\Component\DependencyInjection\Loader;
Chris@0 13
Chris@0 14 use Symfony\Component\DependencyInjection\Alias;
Chris@14 15 use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
Chris@14 16 use Symfony\Component\DependencyInjection\Argument\BoundArgument;
Chris@14 17 use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
Chris@14 18 use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
Chris@14 19 use Symfony\Component\DependencyInjection\ChildDefinition;
Chris@14 20 use Symfony\Component\DependencyInjection\ContainerBuilder;
Chris@0 21 use Symfony\Component\DependencyInjection\ContainerInterface;
Chris@0 22 use Symfony\Component\DependencyInjection\Definition;
Chris@0 23 use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
Chris@0 24 use Symfony\Component\DependencyInjection\Exception\RuntimeException;
Chris@17 25 use Symfony\Component\DependencyInjection\Reference;
Chris@17 26 use Symfony\Component\ExpressionLanguage\Expression;
Chris@0 27 use Symfony\Component\Yaml\Exception\ParseException;
Chris@0 28 use Symfony\Component\Yaml\Parser as YamlParser;
Chris@14 29 use Symfony\Component\Yaml\Tag\TaggedValue;
Chris@0 30 use Symfony\Component\Yaml\Yaml;
Chris@0 31
Chris@0 32 /**
Chris@0 33 * YamlFileLoader loads YAML files service definitions.
Chris@0 34 *
Chris@0 35 * @author Fabien Potencier <fabien@symfony.com>
Chris@0 36 */
Chris@0 37 class YamlFileLoader extends FileLoader
Chris@0 38 {
Chris@17 39 private static $serviceKeywords = [
Chris@0 40 'alias' => 'alias',
Chris@0 41 'parent' => 'parent',
Chris@0 42 'class' => 'class',
Chris@0 43 'shared' => 'shared',
Chris@0 44 'synthetic' => 'synthetic',
Chris@0 45 'lazy' => 'lazy',
Chris@0 46 'public' => 'public',
Chris@0 47 'abstract' => 'abstract',
Chris@0 48 'deprecated' => 'deprecated',
Chris@0 49 'factory' => 'factory',
Chris@0 50 'file' => 'file',
Chris@0 51 'arguments' => 'arguments',
Chris@0 52 'properties' => 'properties',
Chris@0 53 'configurator' => 'configurator',
Chris@0 54 'calls' => 'calls',
Chris@0 55 'tags' => 'tags',
Chris@0 56 'decorates' => 'decorates',
Chris@0 57 'decoration_inner_name' => 'decoration_inner_name',
Chris@0 58 'decoration_priority' => 'decoration_priority',
Chris@0 59 'autowire' => 'autowire',
Chris@0 60 'autowiring_types' => 'autowiring_types',
Chris@14 61 'autoconfigure' => 'autoconfigure',
Chris@14 62 'bind' => 'bind',
Chris@17 63 ];
Chris@14 64
Chris@17 65 private static $prototypeKeywords = [
Chris@14 66 'resource' => 'resource',
Chris@14 67 'namespace' => 'namespace',
Chris@14 68 'exclude' => 'exclude',
Chris@14 69 'parent' => 'parent',
Chris@14 70 'shared' => 'shared',
Chris@14 71 'lazy' => 'lazy',
Chris@14 72 'public' => 'public',
Chris@14 73 'abstract' => 'abstract',
Chris@14 74 'deprecated' => 'deprecated',
Chris@14 75 'factory' => 'factory',
Chris@14 76 'arguments' => 'arguments',
Chris@14 77 'properties' => 'properties',
Chris@14 78 'configurator' => 'configurator',
Chris@14 79 'calls' => 'calls',
Chris@14 80 'tags' => 'tags',
Chris@14 81 'autowire' => 'autowire',
Chris@14 82 'autoconfigure' => 'autoconfigure',
Chris@14 83 'bind' => 'bind',
Chris@17 84 ];
Chris@14 85
Chris@17 86 private static $instanceofKeywords = [
Chris@14 87 'shared' => 'shared',
Chris@14 88 'lazy' => 'lazy',
Chris@14 89 'public' => 'public',
Chris@14 90 'properties' => 'properties',
Chris@14 91 'configurator' => 'configurator',
Chris@14 92 'calls' => 'calls',
Chris@14 93 'tags' => 'tags',
Chris@14 94 'autowire' => 'autowire',
Chris@17 95 ];
Chris@14 96
Chris@17 97 private static $defaultsKeywords = [
Chris@14 98 'public' => 'public',
Chris@14 99 'tags' => 'tags',
Chris@14 100 'autowire' => 'autowire',
Chris@14 101 'autoconfigure' => 'autoconfigure',
Chris@14 102 'bind' => 'bind',
Chris@17 103 ];
Chris@0 104
Chris@0 105 private $yamlParser;
Chris@0 106
Chris@14 107 private $anonymousServicesCount;
Chris@14 108 private $anonymousServicesSuffix;
Chris@14 109
Chris@0 110 /**
Chris@0 111 * {@inheritdoc}
Chris@0 112 */
Chris@0 113 public function load($resource, $type = null)
Chris@0 114 {
Chris@0 115 $path = $this->locator->locate($resource);
Chris@0 116
Chris@0 117 $content = $this->loadFile($path);
Chris@0 118
Chris@14 119 $this->container->fileExists($path);
Chris@0 120
Chris@0 121 // empty file
Chris@0 122 if (null === $content) {
Chris@0 123 return;
Chris@0 124 }
Chris@0 125
Chris@0 126 // imports
Chris@0 127 $this->parseImports($content, $path);
Chris@0 128
Chris@0 129 // parameters
Chris@0 130 if (isset($content['parameters'])) {
Chris@17 131 if (!\is_array($content['parameters'])) {
Chris@14 132 throw new InvalidArgumentException(sprintf('The "parameters" key should contain an array in %s. Check your YAML syntax.', $path));
Chris@0 133 }
Chris@0 134
Chris@0 135 foreach ($content['parameters'] as $key => $value) {
Chris@14 136 $this->container->setParameter($key, $this->resolveServices($value, $path, true));
Chris@0 137 }
Chris@0 138 }
Chris@0 139
Chris@0 140 // extensions
Chris@0 141 $this->loadFromExtensions($content);
Chris@0 142
Chris@0 143 // services
Chris@14 144 $this->anonymousServicesCount = 0;
Chris@17 145 $this->anonymousServicesSuffix = '~'.ContainerBuilder::hash($path);
Chris@17 146 $this->setCurrentDir(\dirname($path));
Chris@14 147 try {
Chris@14 148 $this->parseDefinitions($content, $path);
Chris@14 149 } finally {
Chris@17 150 $this->instanceof = [];
Chris@14 151 }
Chris@0 152 }
Chris@0 153
Chris@0 154 /**
Chris@0 155 * {@inheritdoc}
Chris@0 156 */
Chris@0 157 public function supports($resource, $type = null)
Chris@0 158 {
Chris@17 159 if (!\is_string($resource)) {
Chris@14 160 return false;
Chris@14 161 }
Chris@14 162
Chris@17 163 if (null === $type && \in_array(pathinfo($resource, PATHINFO_EXTENSION), ['yaml', 'yml'], true)) {
Chris@14 164 return true;
Chris@14 165 }
Chris@14 166
Chris@17 167 return \in_array($type, ['yaml', 'yml'], true);
Chris@0 168 }
Chris@0 169
Chris@0 170 /**
Chris@0 171 * Parses all imports.
Chris@0 172 *
Chris@0 173 * @param array $content
Chris@0 174 * @param string $file
Chris@0 175 */
Chris@0 176 private function parseImports(array $content, $file)
Chris@0 177 {
Chris@0 178 if (!isset($content['imports'])) {
Chris@0 179 return;
Chris@0 180 }
Chris@0 181
Chris@17 182 if (!\is_array($content['imports'])) {
Chris@0 183 throw new InvalidArgumentException(sprintf('The "imports" key should contain an array in %s. Check your YAML syntax.', $file));
Chris@0 184 }
Chris@0 185
Chris@17 186 $defaultDirectory = \dirname($file);
Chris@0 187 foreach ($content['imports'] as $import) {
Chris@17 188 if (!\is_array($import)) {
Chris@17 189 $import = ['resource' => $import];
Chris@14 190 }
Chris@14 191 if (!isset($import['resource'])) {
Chris@14 192 throw new InvalidArgumentException(sprintf('An import should provide a resource in %s. Check your YAML syntax.', $file));
Chris@0 193 }
Chris@0 194
Chris@0 195 $this->setCurrentDir($defaultDirectory);
Chris@14 196 $this->import($import['resource'], isset($import['type']) ? $import['type'] : null, isset($import['ignore_errors']) ? (bool) $import['ignore_errors'] : false, $file);
Chris@0 197 }
Chris@0 198 }
Chris@0 199
Chris@0 200 /**
Chris@0 201 * Parses definitions.
Chris@0 202 *
Chris@0 203 * @param array $content
Chris@0 204 * @param string $file
Chris@0 205 */
Chris@0 206 private function parseDefinitions(array $content, $file)
Chris@0 207 {
Chris@0 208 if (!isset($content['services'])) {
Chris@0 209 return;
Chris@0 210 }
Chris@0 211
Chris@17 212 if (!\is_array($content['services'])) {
Chris@0 213 throw new InvalidArgumentException(sprintf('The "services" key should contain an array in %s. Check your YAML syntax.', $file));
Chris@0 214 }
Chris@0 215
Chris@18 216 if (\array_key_exists('_instanceof', $content['services'])) {
Chris@14 217 $instanceof = $content['services']['_instanceof'];
Chris@14 218 unset($content['services']['_instanceof']);
Chris@14 219
Chris@17 220 if (!\is_array($instanceof)) {
Chris@17 221 throw new InvalidArgumentException(sprintf('Service "_instanceof" key must be an array, "%s" given in "%s".', \gettype($instanceof), $file));
Chris@14 222 }
Chris@17 223 $this->instanceof = [];
Chris@14 224 $this->isLoadingInstanceof = true;
Chris@14 225 foreach ($instanceof as $id => $service) {
Chris@17 226 if (!$service || !\is_array($service)) {
Chris@14 227 throw new InvalidArgumentException(sprintf('Type definition "%s" must be a non-empty array within "_instanceof" in %s. Check your YAML syntax.', $id, $file));
Chris@14 228 }
Chris@17 229 if (\is_string($service) && 0 === strpos($service, '@')) {
Chris@14 230 throw new InvalidArgumentException(sprintf('Type definition "%s" cannot be an alias within "_instanceof" in %s. Check your YAML syntax.', $id, $file));
Chris@14 231 }
Chris@17 232 $this->parseDefinition($id, $service, $file, []);
Chris@14 233 }
Chris@14 234 }
Chris@14 235
Chris@14 236 $this->isLoadingInstanceof = false;
Chris@14 237 $defaults = $this->parseDefaults($content, $file);
Chris@0 238 foreach ($content['services'] as $id => $service) {
Chris@14 239 $this->parseDefinition($id, $service, $file, $defaults);
Chris@0 240 }
Chris@0 241 }
Chris@0 242
Chris@0 243 /**
Chris@14 244 * @param array $content
Chris@14 245 * @param string $file
Chris@14 246 *
Chris@14 247 * @return array
Chris@14 248 *
Chris@14 249 * @throws InvalidArgumentException
Chris@14 250 */
Chris@14 251 private function parseDefaults(array &$content, $file)
Chris@14 252 {
Chris@18 253 if (!\array_key_exists('_defaults', $content['services'])) {
Chris@17 254 return [];
Chris@14 255 }
Chris@14 256 $defaults = $content['services']['_defaults'];
Chris@14 257 unset($content['services']['_defaults']);
Chris@14 258
Chris@17 259 if (!\is_array($defaults)) {
Chris@17 260 throw new InvalidArgumentException(sprintf('Service "_defaults" key must be an array, "%s" given in "%s".', \gettype($defaults), $file));
Chris@14 261 }
Chris@14 262
Chris@14 263 foreach ($defaults as $key => $default) {
Chris@14 264 if (!isset(self::$defaultsKeywords[$key])) {
Chris@14 265 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)));
Chris@14 266 }
Chris@14 267 }
Chris@14 268
Chris@14 269 if (isset($defaults['tags'])) {
Chris@17 270 if (!\is_array($tags = $defaults['tags'])) {
Chris@14 271 throw new InvalidArgumentException(sprintf('Parameter "tags" in "_defaults" must be an array in %s. Check your YAML syntax.', $file));
Chris@14 272 }
Chris@14 273
Chris@14 274 foreach ($tags as $tag) {
Chris@17 275 if (!\is_array($tag)) {
Chris@17 276 $tag = ['name' => $tag];
Chris@14 277 }
Chris@14 278
Chris@14 279 if (!isset($tag['name'])) {
Chris@14 280 throw new InvalidArgumentException(sprintf('A "tags" entry in "_defaults" is missing a "name" key in %s.', $file));
Chris@14 281 }
Chris@14 282 $name = $tag['name'];
Chris@14 283 unset($tag['name']);
Chris@14 284
Chris@17 285 if (!\is_string($name) || '' === $name) {
Chris@14 286 throw new InvalidArgumentException(sprintf('The tag name in "_defaults" must be a non-empty string in %s.', $file));
Chris@14 287 }
Chris@14 288
Chris@14 289 foreach ($tag as $attribute => $value) {
Chris@14 290 if (!is_scalar($value) && null !== $value) {
Chris@14 291 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));
Chris@14 292 }
Chris@14 293 }
Chris@14 294 }
Chris@14 295 }
Chris@14 296
Chris@14 297 if (isset($defaults['bind'])) {
Chris@17 298 if (!\is_array($defaults['bind'])) {
Chris@14 299 throw new InvalidArgumentException(sprintf('Parameter "bind" in "_defaults" must be an array in %s. Check your YAML syntax.', $file));
Chris@14 300 }
Chris@14 301
Chris@14 302 $defaults['bind'] = array_map(function ($v) { return new BoundArgument($v); }, $this->resolveServices($defaults['bind'], $file));
Chris@14 303 }
Chris@14 304
Chris@14 305 return $defaults;
Chris@14 306 }
Chris@14 307
Chris@14 308 /**
Chris@14 309 * @param array $service
Chris@14 310 *
Chris@14 311 * @return bool
Chris@14 312 */
Chris@14 313 private function isUsingShortSyntax(array $service)
Chris@14 314 {
Chris@14 315 foreach ($service as $key => $value) {
Chris@17 316 if (\is_string($key) && ('' === $key || '$' !== $key[0])) {
Chris@14 317 return false;
Chris@14 318 }
Chris@14 319 }
Chris@14 320
Chris@14 321 return true;
Chris@14 322 }
Chris@14 323
Chris@14 324 /**
Chris@0 325 * Parses a definition.
Chris@0 326 *
Chris@0 327 * @param string $id
Chris@0 328 * @param array|string $service
Chris@0 329 * @param string $file
Chris@14 330 * @param array $defaults
Chris@0 331 *
Chris@0 332 * @throws InvalidArgumentException When tags are invalid
Chris@0 333 */
Chris@14 334 private function parseDefinition($id, $service, $file, array $defaults)
Chris@0 335 {
Chris@14 336 if (preg_match('/^_[a-zA-Z0-9_]*$/', $id)) {
Chris@14 337 @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);
Chris@14 338 }
Chris@17 339 if (\is_string($service) && 0 === strpos($service, '@')) {
Chris@14 340 $this->container->setAlias($id, $alias = new Alias(substr($service, 1)));
Chris@14 341 if (isset($defaults['public'])) {
Chris@14 342 $alias->setPublic($defaults['public']);
Chris@14 343 }
Chris@0 344
Chris@0 345 return;
Chris@0 346 }
Chris@0 347
Chris@17 348 if (\is_array($service) && $this->isUsingShortSyntax($service)) {
Chris@17 349 $service = ['arguments' => $service];
Chris@14 350 }
Chris@14 351
Chris@14 352 if (null === $service) {
Chris@17 353 $service = [];
Chris@14 354 }
Chris@14 355
Chris@17 356 if (!\is_array($service)) {
Chris@17 357 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));
Chris@0 358 }
Chris@0 359
Chris@14 360 $this->checkDefinition($id, $service, $file);
Chris@0 361
Chris@0 362 if (isset($service['alias'])) {
Chris@14 363 $this->container->setAlias($id, $alias = new Alias($service['alias']));
Chris@18 364 if (\array_key_exists('public', $service)) {
Chris@14 365 $alias->setPublic($service['public']);
Chris@14 366 } elseif (isset($defaults['public'])) {
Chris@14 367 $alias->setPublic($defaults['public']);
Chris@14 368 }
Chris@0 369
Chris@0 370 foreach ($service as $key => $value) {
Chris@17 371 if (!\in_array($key, ['alias', 'public'])) {
Chris@0 372 @trigger_error(sprintf('The configuration key "%s" is unsupported for the service "%s" which is defined as an alias in "%s". Allowed configuration keys for service aliases are "alias" and "public". The YamlFileLoader will raise an exception in Symfony 4.0, instead of silently ignoring unsupported attributes.', $key, $id, $file), E_USER_DEPRECATED);
Chris@0 373 }
Chris@0 374 }
Chris@0 375
Chris@0 376 return;
Chris@0 377 }
Chris@0 378
Chris@14 379 if ($this->isLoadingInstanceof) {
Chris@14 380 $definition = new ChildDefinition('');
Chris@14 381 } elseif (isset($service['parent'])) {
Chris@14 382 if (!empty($this->instanceof)) {
Chris@14 383 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));
Chris@14 384 }
Chris@14 385
Chris@14 386 foreach ($defaults as $k => $v) {
Chris@14 387 if ('tags' === $k) {
Chris@14 388 // since tags are never inherited from parents, there is no confusion
Chris@14 389 // thus we can safely add them as defaults to ChildDefinition
Chris@14 390 continue;
Chris@14 391 }
Chris@14 392 if ('bind' === $k) {
Chris@14 393 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));
Chris@14 394 }
Chris@14 395 if (!isset($service[$k])) {
Chris@14 396 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));
Chris@14 397 }
Chris@14 398 }
Chris@14 399
Chris@14 400 $definition = new ChildDefinition($service['parent']);
Chris@0 401 } else {
Chris@0 402 $definition = new Definition();
Chris@14 403
Chris@14 404 if (isset($defaults['public'])) {
Chris@14 405 $definition->setPublic($defaults['public']);
Chris@14 406 }
Chris@14 407 if (isset($defaults['autowire'])) {
Chris@14 408 $definition->setAutowired($defaults['autowire']);
Chris@14 409 }
Chris@14 410 if (isset($defaults['autoconfigure'])) {
Chris@14 411 $definition->setAutoconfigured($defaults['autoconfigure']);
Chris@14 412 }
Chris@14 413
Chris@17 414 $definition->setChanges([]);
Chris@0 415 }
Chris@0 416
Chris@0 417 if (isset($service['class'])) {
Chris@0 418 $definition->setClass($service['class']);
Chris@0 419 }
Chris@0 420
Chris@0 421 if (isset($service['shared'])) {
Chris@0 422 $definition->setShared($service['shared']);
Chris@0 423 }
Chris@0 424
Chris@0 425 if (isset($service['synthetic'])) {
Chris@0 426 $definition->setSynthetic($service['synthetic']);
Chris@0 427 }
Chris@0 428
Chris@0 429 if (isset($service['lazy'])) {
Chris@0 430 $definition->setLazy($service['lazy']);
Chris@0 431 }
Chris@0 432
Chris@0 433 if (isset($service['public'])) {
Chris@0 434 $definition->setPublic($service['public']);
Chris@0 435 }
Chris@0 436
Chris@0 437 if (isset($service['abstract'])) {
Chris@0 438 $definition->setAbstract($service['abstract']);
Chris@0 439 }
Chris@0 440
Chris@18 441 if (\array_key_exists('deprecated', $service)) {
Chris@0 442 $definition->setDeprecated(true, $service['deprecated']);
Chris@0 443 }
Chris@0 444
Chris@0 445 if (isset($service['factory'])) {
Chris@0 446 $definition->setFactory($this->parseCallable($service['factory'], 'factory', $id, $file));
Chris@0 447 }
Chris@0 448
Chris@0 449 if (isset($service['file'])) {
Chris@0 450 $definition->setFile($service['file']);
Chris@0 451 }
Chris@0 452
Chris@0 453 if (isset($service['arguments'])) {
Chris@14 454 $definition->setArguments($this->resolveServices($service['arguments'], $file));
Chris@0 455 }
Chris@0 456
Chris@0 457 if (isset($service['properties'])) {
Chris@14 458 $definition->setProperties($this->resolveServices($service['properties'], $file));
Chris@0 459 }
Chris@0 460
Chris@0 461 if (isset($service['configurator'])) {
Chris@0 462 $definition->setConfigurator($this->parseCallable($service['configurator'], 'configurator', $id, $file));
Chris@0 463 }
Chris@0 464
Chris@0 465 if (isset($service['calls'])) {
Chris@17 466 if (!\is_array($service['calls'])) {
Chris@0 467 throw new InvalidArgumentException(sprintf('Parameter "calls" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
Chris@0 468 }
Chris@0 469
Chris@0 470 foreach ($service['calls'] as $call) {
Chris@0 471 if (isset($call['method'])) {
Chris@0 472 $method = $call['method'];
Chris@17 473 $args = isset($call['arguments']) ? $this->resolveServices($call['arguments'], $file) : [];
Chris@0 474 } else {
Chris@0 475 $method = $call[0];
Chris@17 476 $args = isset($call[1]) ? $this->resolveServices($call[1], $file) : [];
Chris@0 477 }
Chris@0 478
Chris@17 479 if (!\is_array($args)) {
Chris@14 480 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));
Chris@14 481 }
Chris@0 482 $definition->addMethodCall($method, $args);
Chris@0 483 }
Chris@0 484 }
Chris@0 485
Chris@17 486 $tags = isset($service['tags']) ? $service['tags'] : [];
Chris@17 487 if (!\is_array($tags)) {
Chris@14 488 throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
Chris@14 489 }
Chris@14 490
Chris@14 491 if (isset($defaults['tags'])) {
Chris@14 492 $tags = array_merge($tags, $defaults['tags']);
Chris@14 493 }
Chris@14 494
Chris@14 495 foreach ($tags as $tag) {
Chris@17 496 if (!\is_array($tag)) {
Chris@17 497 $tag = ['name' => $tag];
Chris@0 498 }
Chris@0 499
Chris@14 500 if (!isset($tag['name'])) {
Chris@14 501 throw new InvalidArgumentException(sprintf('A "tags" entry is missing a "name" key for service "%s" in %s.', $id, $file));
Chris@14 502 }
Chris@14 503 $name = $tag['name'];
Chris@14 504 unset($tag['name']);
Chris@14 505
Chris@17 506 if (!\is_string($name) || '' === $name) {
Chris@14 507 throw new InvalidArgumentException(sprintf('The tag name for service "%s" in %s must be a non-empty string.', $id, $file));
Chris@14 508 }
Chris@14 509
Chris@14 510 foreach ($tag as $attribute => $value) {
Chris@14 511 if (!is_scalar($value) && null !== $value) {
Chris@14 512 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));
Chris@0 513 }
Chris@14 514 }
Chris@0 515
Chris@14 516 $definition->addTag($name, $tag);
Chris@0 517 }
Chris@0 518
Chris@0 519 if (isset($service['decorates'])) {
Chris@0 520 if ('' !== $service['decorates'] && '@' === $service['decorates'][0]) {
Chris@0 521 throw new InvalidArgumentException(sprintf('The value of the "decorates" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s").', $id, $service['decorates'], substr($service['decorates'], 1)));
Chris@0 522 }
Chris@0 523
Chris@0 524 $renameId = isset($service['decoration_inner_name']) ? $service['decoration_inner_name'] : null;
Chris@0 525 $priority = isset($service['decoration_priority']) ? $service['decoration_priority'] : 0;
Chris@0 526 $definition->setDecoratedService($service['decorates'], $renameId, $priority);
Chris@0 527 }
Chris@0 528
Chris@0 529 if (isset($service['autowire'])) {
Chris@0 530 $definition->setAutowired($service['autowire']);
Chris@0 531 }
Chris@0 532
Chris@0 533 if (isset($service['autowiring_types'])) {
Chris@17 534 if (\is_string($service['autowiring_types'])) {
Chris@0 535 $definition->addAutowiringType($service['autowiring_types']);
Chris@0 536 } else {
Chris@17 537 if (!\is_array($service['autowiring_types'])) {
Chris@0 538 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));
Chris@0 539 }
Chris@0 540
Chris@0 541 foreach ($service['autowiring_types'] as $autowiringType) {
Chris@17 542 if (!\is_string($autowiringType)) {
Chris@0 543 throw new InvalidArgumentException(sprintf('A "autowiring_types" attribute must be of type string for service "%s" in %s. Check your YAML syntax.', $id, $file));
Chris@0 544 }
Chris@0 545
Chris@0 546 $definition->addAutowiringType($autowiringType);
Chris@0 547 }
Chris@0 548 }
Chris@0 549 }
Chris@0 550
Chris@14 551 if (isset($defaults['bind']) || isset($service['bind'])) {
Chris@14 552 // deep clone, to avoid multiple process of the same instance in the passes
Chris@17 553 $bindings = isset($defaults['bind']) ? unserialize(serialize($defaults['bind'])) : [];
Chris@14 554
Chris@14 555 if (isset($service['bind'])) {
Chris@17 556 if (!\is_array($service['bind'])) {
Chris@14 557 throw new InvalidArgumentException(sprintf('Parameter "bind" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file));
Chris@14 558 }
Chris@14 559
Chris@14 560 $bindings = array_merge($bindings, $this->resolveServices($service['bind'], $file));
Chris@14 561 }
Chris@14 562
Chris@14 563 $definition->setBindings($bindings);
Chris@14 564 }
Chris@14 565
Chris@14 566 if (isset($service['autoconfigure'])) {
Chris@14 567 if (!$definition instanceof ChildDefinition) {
Chris@14 568 $definition->setAutoconfigured($service['autoconfigure']);
Chris@14 569 } elseif ($service['autoconfigure']) {
Chris@14 570 throw new InvalidArgumentException(sprintf('The service "%s" cannot have a "parent" and also have "autoconfigure". Try setting "autoconfigure: false" for the service.', $id));
Chris@14 571 }
Chris@14 572 }
Chris@14 573
Chris@18 574 if (\array_key_exists('namespace', $service) && !\array_key_exists('resource', $service)) {
Chris@14 575 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));
Chris@14 576 }
Chris@14 577
Chris@18 578 if (\array_key_exists('resource', $service)) {
Chris@17 579 if (!\is_string($service['resource'])) {
Chris@14 580 throw new InvalidArgumentException(sprintf('A "resource" attribute must be of type string for service "%s" in %s. Check your YAML syntax.', $id, $file));
Chris@14 581 }
Chris@14 582 $exclude = isset($service['exclude']) ? $service['exclude'] : null;
Chris@14 583 $namespace = isset($service['namespace']) ? $service['namespace'] : $id;
Chris@14 584 $this->registerClasses($definition, $namespace, $service['resource'], $exclude);
Chris@14 585 } else {
Chris@14 586 $this->setDefinition($id, $definition);
Chris@14 587 }
Chris@0 588 }
Chris@0 589
Chris@0 590 /**
Chris@0 591 * Parses a callable.
Chris@0 592 *
Chris@0 593 * @param string|array $callable A callable
Chris@0 594 * @param string $parameter A parameter (e.g. 'factory' or 'configurator')
Chris@0 595 * @param string $id A service identifier
Chris@0 596 * @param string $file A parsed file
Chris@0 597 *
Chris@17 598 * @throws InvalidArgumentException When errors occur
Chris@0 599 *
Chris@0 600 * @return string|array A parsed callable
Chris@0 601 */
Chris@0 602 private function parseCallable($callable, $parameter, $id, $file)
Chris@0 603 {
Chris@17 604 if (\is_string($callable)) {
Chris@0 605 if ('' !== $callable && '@' === $callable[0]) {
Chris@0 606 throw new InvalidArgumentException(sprintf('The value of the "%s" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s").', $parameter, $id, $callable, substr($callable, 1)));
Chris@0 607 }
Chris@0 608
Chris@0 609 if (false !== strpos($callable, ':') && false === strpos($callable, '::')) {
Chris@0 610 $parts = explode(':', $callable);
Chris@0 611
Chris@17 612 return [$this->resolveServices('@'.$parts[0], $file), $parts[1]];
Chris@0 613 }
Chris@0 614
Chris@0 615 return $callable;
Chris@0 616 }
Chris@0 617
Chris@17 618 if (\is_array($callable)) {
Chris@0 619 if (isset($callable[0]) && isset($callable[1])) {
Chris@17 620 return [$this->resolveServices($callable[0], $file), $callable[1]];
Chris@14 621 }
Chris@14 622
Chris@14 623 if ('factory' === $parameter && isset($callable[1]) && null === $callable[0]) {
Chris@14 624 return $callable;
Chris@0 625 }
Chris@0 626
Chris@0 627 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));
Chris@0 628 }
Chris@0 629
Chris@0 630 throw new InvalidArgumentException(sprintf('Parameter "%s" must be a string or an array for service "%s" in %s. Check your YAML syntax.', $parameter, $id, $file));
Chris@0 631 }
Chris@0 632
Chris@0 633 /**
Chris@0 634 * Loads a YAML file.
Chris@0 635 *
Chris@0 636 * @param string $file
Chris@0 637 *
Chris@0 638 * @return array The file content
Chris@0 639 *
Chris@0 640 * @throws InvalidArgumentException when the given file is not a local file or when it does not exist
Chris@0 641 */
Chris@0 642 protected function loadFile($file)
Chris@0 643 {
Chris@0 644 if (!class_exists('Symfony\Component\Yaml\Parser')) {
Chris@0 645 throw new RuntimeException('Unable to load YAML config files as the Symfony Yaml Component is not installed.');
Chris@0 646 }
Chris@0 647
Chris@0 648 if (!stream_is_local($file)) {
Chris@0 649 throw new InvalidArgumentException(sprintf('This is not a local file "%s".', $file));
Chris@0 650 }
Chris@0 651
Chris@0 652 if (!file_exists($file)) {
Chris@0 653 throw new InvalidArgumentException(sprintf('The file "%s" does not exist.', $file));
Chris@0 654 }
Chris@0 655
Chris@0 656 if (null === $this->yamlParser) {
Chris@0 657 $this->yamlParser = new YamlParser();
Chris@0 658 }
Chris@0 659
Chris@14 660 $prevErrorHandler = set_error_handler(function ($level, $message, $script, $line) use ($file, &$prevErrorHandler) {
Chris@14 661 $message = E_USER_DEPRECATED === $level ? preg_replace('/ on line \d+/', ' in "'.$file.'"$0', $message) : $message;
Chris@14 662
Chris@14 663 return $prevErrorHandler ? $prevErrorHandler($level, $message, $script, $line) : false;
Chris@14 664 });
Chris@14 665
Chris@0 666 try {
Chris@14 667 $configuration = $this->yamlParser->parseFile($file, Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS);
Chris@0 668 } catch (ParseException $e) {
Chris@17 669 throw new InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML: %s', $file, $e->getMessage()), 0, $e);
Chris@14 670 } finally {
Chris@14 671 restore_error_handler();
Chris@0 672 }
Chris@0 673
Chris@0 674 return $this->validate($configuration, $file);
Chris@0 675 }
Chris@0 676
Chris@0 677 /**
Chris@0 678 * Validates a YAML file.
Chris@0 679 *
Chris@0 680 * @param mixed $content
Chris@0 681 * @param string $file
Chris@0 682 *
Chris@0 683 * @return array
Chris@0 684 *
Chris@0 685 * @throws InvalidArgumentException When service file is not valid
Chris@0 686 */
Chris@0 687 private function validate($content, $file)
Chris@0 688 {
Chris@0 689 if (null === $content) {
Chris@0 690 return $content;
Chris@0 691 }
Chris@0 692
Chris@17 693 if (!\is_array($content)) {
Chris@0 694 throw new InvalidArgumentException(sprintf('The service file "%s" is not valid. It should contain an array. Check your YAML syntax.', $file));
Chris@0 695 }
Chris@0 696
Chris@0 697 foreach ($content as $namespace => $data) {
Chris@17 698 if (\in_array($namespace, ['imports', 'parameters', 'services'])) {
Chris@0 699 continue;
Chris@0 700 }
Chris@0 701
Chris@0 702 if (!$this->container->hasExtension($namespace)) {
Chris@0 703 $extensionNamespaces = array_filter(array_map(function ($ext) { return $ext->getAlias(); }, $this->container->getExtensions()));
Chris@17 704 throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in %s). Looked for namespace "%s", found %s', $namespace, $file, $namespace, $extensionNamespaces ? sprintf('"%s"', implode('", "', $extensionNamespaces)) : 'none'));
Chris@0 705 }
Chris@0 706 }
Chris@0 707
Chris@0 708 return $content;
Chris@0 709 }
Chris@0 710
Chris@0 711 /**
Chris@0 712 * Resolves services.
Chris@0 713 *
Chris@14 714 * @param mixed $value
Chris@14 715 * @param string $file
Chris@14 716 * @param bool $isParameter
Chris@0 717 *
Chris@14 718 * @return array|string|Reference|ArgumentInterface
Chris@0 719 */
Chris@14 720 private function resolveServices($value, $file, $isParameter = false)
Chris@0 721 {
Chris@14 722 if ($value instanceof TaggedValue) {
Chris@14 723 $argument = $value->getValue();
Chris@14 724 if ('iterator' === $value->getTag()) {
Chris@17 725 if (!\is_array($argument)) {
Chris@14 726 throw new InvalidArgumentException(sprintf('"!iterator" tag only accepts sequences in "%s".', $file));
Chris@14 727 }
Chris@14 728 $argument = $this->resolveServices($argument, $file, $isParameter);
Chris@14 729 try {
Chris@14 730 return new IteratorArgument($argument);
Chris@14 731 } catch (InvalidArgumentException $e) {
Chris@14 732 throw new InvalidArgumentException(sprintf('"!iterator" tag only accepts arrays of "@service" references in "%s".', $file));
Chris@14 733 }
Chris@14 734 }
Chris@14 735 if ('tagged' === $value->getTag()) {
Chris@17 736 if (!\is_string($argument) || !$argument) {
Chris@14 737 throw new InvalidArgumentException(sprintf('"!tagged" tag only accepts non empty string in "%s".', $file));
Chris@14 738 }
Chris@14 739
Chris@14 740 return new TaggedIteratorArgument($argument);
Chris@14 741 }
Chris@14 742 if ('service' === $value->getTag()) {
Chris@14 743 if ($isParameter) {
Chris@14 744 throw new InvalidArgumentException(sprintf('Using an anonymous service in a parameter is not allowed in "%s".', $file));
Chris@14 745 }
Chris@14 746
Chris@14 747 $isLoadingInstanceof = $this->isLoadingInstanceof;
Chris@14 748 $this->isLoadingInstanceof = false;
Chris@14 749 $instanceof = $this->instanceof;
Chris@17 750 $this->instanceof = [];
Chris@14 751
Chris@14 752 $id = sprintf('%d_%s', ++$this->anonymousServicesCount, preg_replace('/^.*\\\\/', '', isset($argument['class']) ? $argument['class'] : '').$this->anonymousServicesSuffix);
Chris@17 753 $this->parseDefinition($id, $argument, $file, []);
Chris@14 754
Chris@14 755 if (!$this->container->hasDefinition($id)) {
Chris@14 756 throw new InvalidArgumentException(sprintf('Creating an alias using the tag "!service" is not allowed in "%s".', $file));
Chris@14 757 }
Chris@14 758
Chris@14 759 $this->container->getDefinition($id)->setPublic(false);
Chris@14 760
Chris@14 761 $this->isLoadingInstanceof = $isLoadingInstanceof;
Chris@14 762 $this->instanceof = $instanceof;
Chris@14 763
Chris@14 764 return new Reference($id);
Chris@14 765 }
Chris@14 766
Chris@14 767 throw new InvalidArgumentException(sprintf('Unsupported tag "!%s".', $value->getTag()));
Chris@14 768 }
Chris@14 769
Chris@17 770 if (\is_array($value)) {
Chris@14 771 foreach ($value as $k => $v) {
Chris@14 772 $value[$k] = $this->resolveServices($v, $file, $isParameter);
Chris@14 773 }
Chris@17 774 } elseif (\is_string($value) && 0 === strpos($value, '@=')) {
Chris@14 775 if (!class_exists(Expression::class)) {
Chris@14 776 throw new \LogicException(sprintf('The "@=" expression syntax cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".'));
Chris@14 777 }
Chris@14 778
Chris@0 779 return new Expression(substr($value, 2));
Chris@17 780 } elseif (\is_string($value) && 0 === strpos($value, '@')) {
Chris@0 781 if (0 === strpos($value, '@@')) {
Chris@0 782 $value = substr($value, 1);
Chris@0 783 $invalidBehavior = null;
Chris@14 784 } elseif (0 === strpos($value, '@!')) {
Chris@14 785 $value = substr($value, 2);
Chris@14 786 $invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE;
Chris@0 787 } elseif (0 === strpos($value, '@?')) {
Chris@0 788 $value = substr($value, 2);
Chris@0 789 $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
Chris@0 790 } else {
Chris@0 791 $value = substr($value, 1);
Chris@0 792 $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
Chris@0 793 }
Chris@0 794
Chris@0 795 if ('=' === substr($value, -1)) {
Chris@14 796 @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);
Chris@0 797 $value = substr($value, 0, -1);
Chris@0 798 }
Chris@0 799
Chris@0 800 if (null !== $invalidBehavior) {
Chris@0 801 $value = new Reference($value, $invalidBehavior);
Chris@0 802 }
Chris@0 803 }
Chris@0 804
Chris@0 805 return $value;
Chris@0 806 }
Chris@0 807
Chris@0 808 /**
Chris@0 809 * Loads from Extensions.
Chris@0 810 */
Chris@0 811 private function loadFromExtensions(array $content)
Chris@0 812 {
Chris@0 813 foreach ($content as $namespace => $values) {
Chris@17 814 if (\in_array($namespace, ['imports', 'parameters', 'services'])) {
Chris@0 815 continue;
Chris@0 816 }
Chris@0 817
Chris@17 818 if (!\is_array($values) && null !== $values) {
Chris@17 819 $values = [];
Chris@0 820 }
Chris@0 821
Chris@0 822 $this->container->loadFromExtension($namespace, $values);
Chris@0 823 }
Chris@0 824 }
Chris@0 825
Chris@0 826 /**
Chris@0 827 * Checks the keywords used to define a service.
Chris@0 828 *
Chris@0 829 * @param string $id The service name
Chris@0 830 * @param array $definition The service definition to check
Chris@0 831 * @param string $file The loaded YAML file
Chris@0 832 */
Chris@14 833 private function checkDefinition($id, array $definition, $file)
Chris@0 834 {
Chris@14 835 if ($throw = $this->isLoadingInstanceof) {
Chris@14 836 $keywords = self::$instanceofKeywords;
Chris@14 837 } elseif ($throw = (isset($definition['resource']) || isset($definition['namespace']))) {
Chris@14 838 $keywords = self::$prototypeKeywords;
Chris@14 839 } else {
Chris@14 840 $keywords = self::$serviceKeywords;
Chris@14 841 }
Chris@14 842
Chris@0 843 foreach ($definition as $key => $value) {
Chris@14 844 if (!isset($keywords[$key])) {
Chris@14 845 if ($throw) {
Chris@14 846 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)));
Chris@14 847 }
Chris@14 848
Chris@14 849 @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);
Chris@0 850 }
Chris@0 851 }
Chris@0 852 }
Chris@0 853 }