This commit is contained in:
Marek
2026-03-24 00:04:55 +01:00
commit c5229e48ed
4225 changed files with 511461 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* ClosureLoader loads service definitions from a PHP closure.
*
* The Closure has access to the container as its first argument.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ClosureLoader extends Loader
{
public function __construct(
private ContainerBuilder $container,
?string $env = null,
) {
parent::__construct($env);
}
public function load(mixed $resource, ?string $type = null): mixed
{
return $resource($this->container, $this->env);
}
public function supports(mixed $resource, ?string $type = null): bool
{
return $resource instanceof \Closure;
}
}

View File

@@ -0,0 +1,145 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\Config\Loader\ParamConfigurator;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\ExpressionLanguage\Expression;
abstract class AbstractConfigurator
{
public const FACTORY = 'unknown';
/**
* @var \Closure(mixed, bool):mixed|null
*/
public static ?\Closure $valuePreProcessor = null;
/** @internal */
protected Definition|Alias|null $definition = null;
public function __call(string $method, array $args): mixed
{
if (method_exists($this, 'set'.$method)) {
return $this->{'set'.$method}(...$args);
}
throw new \BadMethodCallException(\sprintf('Call to undefined method "%s::%s()".', static::class, $method));
}
public function __serialize(): array
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __unserialize(array $data): void
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
/**
* Checks that a value is valid, optionally replacing Definition and Reference configurators by their configure value.
*
* @param bool $allowServices whether Definition and Reference are allowed; by default, only scalars, arrays and enum are
*
* @return mixed the value, optionally cast to a Definition/Reference
*/
public static function processValue(mixed $value, bool $allowServices = false): mixed
{
if (\is_array($value)) {
foreach ($value as $k => $v) {
$value[$k] = static::processValue($v, $allowServices);
}
return self::$valuePreProcessor ? (self::$valuePreProcessor)($value, $allowServices) : $value;
}
if (self::$valuePreProcessor) {
$value = (self::$valuePreProcessor)($value, $allowServices);
}
if ($value instanceof ReferenceConfigurator) {
$reference = new Reference($value->id, $value->invalidBehavior);
return $value instanceof ClosureReferenceConfigurator ? new ServiceClosureArgument($reference) : $reference;
}
if ($value instanceof InlineServiceConfigurator) {
$def = $value->definition;
$value->definition = null;
return $def;
}
if ($value instanceof ParamConfigurator) {
return (string) $value;
}
if ($value instanceof self) {
throw new InvalidArgumentException(\sprintf('"%s()" can be used only at the root of service configuration files.', $value::FACTORY));
}
switch (true) {
case null === $value:
case \is_scalar($value):
case $value instanceof \UnitEnum:
return $value;
case $value instanceof \Closure:
return self::processClosure($value);
case $value instanceof ArgumentInterface:
case $value instanceof Definition:
case $value instanceof Expression:
case $value instanceof Parameter:
case $value instanceof AbstractArgument:
case $value instanceof Reference:
if ($allowServices) {
return $value;
}
}
throw new InvalidArgumentException(\sprintf('Cannot use values of type "%s" in service configuration files.', get_debug_type($value)));
}
/**
* Converts a named closure to dumpable callable.
*
* @throws InvalidArgumentException if the closure is anonymous or references a non-static method
*/
private static function processClosure(\Closure $closure): callable
{
$function = new \ReflectionFunction($closure);
if ($function->isAnonymous()) {
throw new InvalidArgumentException('Anonymous closure not supported. The closure must be created from a static method or a global function.');
}
// Convert global_function(...) closure into 'global_function'
if (!$class = $function->getClosureCalledClass()) {
return $function->name;
}
// Convert Class::method(...) closure into ['Class', 'method']
if ($function->isStatic()) {
return [$class->name, $function->name];
}
throw new InvalidArgumentException(\sprintf('The method "%s::%s(...)" is not static.', $class->name, $function->name));
}
}

View File

@@ -0,0 +1,115 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
abstract class AbstractServiceConfigurator extends AbstractConfigurator
{
private array $defaultTags = [];
public function __construct(
protected ServicesConfigurator $parent,
Definition $definition,
protected ?string $id = null,
array $defaultTags = [],
) {
$this->definition = $definition;
$this->defaultTags = $defaultTags;
}
public function __destruct()
{
// default tags should be added last
foreach ($this->defaultTags as $name => $attributes) {
foreach ($attributes as $attribute) {
$this->definition->addTag($name, $attribute);
}
}
$this->defaultTags = [];
}
/**
* Registers a service.
*/
final public function set(?string $id, ?string $class = null): ServiceConfigurator
{
$this->__destruct();
return $this->parent->set($id, $class);
}
/**
* Creates an alias.
*/
final public function alias(string $id, string $referencedId): AliasConfigurator
{
$this->__destruct();
return $this->parent->alias($id, $referencedId);
}
/**
* Registers a PSR-4 namespace using a glob pattern.
*/
final public function load(string $namespace, string $resource): PrototypeConfigurator
{
$this->__destruct();
return $this->parent->load($namespace, $resource);
}
/**
* Gets an already defined service definition.
*
* @throws ServiceNotFoundException if the service definition does not exist
*/
final public function get(string $id): ServiceConfigurator
{
$this->__destruct();
return $this->parent->get($id);
}
/**
* Removes an already defined service definition or alias.
*/
final public function remove(string $id): ServicesConfigurator
{
$this->__destruct();
return $this->parent->remove($id);
}
/**
* Registers a stack of decorator services.
*
* @param InlineServiceConfigurator[]|ReferenceConfigurator[] $services
*/
final public function stack(string $id, array $services): AliasConfigurator
{
$this->__destruct();
return $this->parent->stack($id, $services);
}
/**
* Registers a service.
*/
final public function __invoke(string $id, ?string $class = null): ServiceConfigurator
{
$this->__destruct();
return $this->parent->set($id, $class);
}
}

View File

@@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Alias;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class AliasConfigurator extends AbstractServiceConfigurator
{
use Traits\DeprecateTrait;
use Traits\PublicTrait;
public const FACTORY = 'alias';
public function __construct(ServicesConfigurator $parent, Alias $alias)
{
$this->parent = $parent;
$this->definition = $alias;
}
}

View File

@@ -0,0 +1,176 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
// For the phpdoc to remain compatible with the generation of per-app App class,
// this file should have no "use" statements: all symbols referenced by
// the phpdoc need to be in the current namespace or be root-scoped.
/**
* This class provides array-shapes for configuring the services and bundles of an application.
*
* Services declared with the config() method below are autowired and autoconfigured by default.
*
* This is for apps only. Bundles SHOULD NOT use it.
*
* Example:
*
* ```php
* // config/services.php
* namespace Symfony\Component\DependencyInjection\Loader\Configurator;
*
* return App::config([
* 'services' => [
* 'App\\' => [
* 'resource' => '../src/',
* ],
* ],
* ]);
* ```
*
* @psalm-type ImportsConfig = list<string|array{
* resource: string,
* type?: string|null,
* ignore_errors?: bool,
* }>
* @psalm-type ParametersConfig = array<string, scalar|\UnitEnum|array<scalar|\UnitEnum|array<mixed>|\Symfony\Component\Config\Loader\ParamConfigurator|null>|\Symfony\Component\Config\Loader\ParamConfigurator|null>
* @psalm-type ArgumentsType = list<mixed>|array<string, mixed>
* @psalm-type CallType = array<string, ArgumentsType>|array{0:string, 1?:ArgumentsType, 2?:bool}|array{method:string, arguments?:ArgumentsType, returns_clone?:bool}
* @psalm-type TagsType = list<string|array<string, array<string, mixed>>> // arrays inside the list must have only one element, with the tag name as the key
* @psalm-type CallbackType = string|array{0:string|ReferenceConfigurator,1:string}|\Closure|ReferenceConfigurator|ExpressionConfigurator
* @psalm-type DeprecationType = array{package: string, version: string, message?: string}
* @psalm-type DefaultsType = array{
* public?: bool,
* tags?: TagsType,
* resource_tags?: TagsType,
* autowire?: bool,
* autoconfigure?: bool,
* bind?: array<string, mixed>,
* }
* @psalm-type InstanceofType = array{
* shared?: bool,
* lazy?: bool|string,
* public?: bool,
* properties?: array<string, mixed>,
* configurator?: CallbackType,
* calls?: list<CallType>,
* tags?: TagsType,
* resource_tags?: TagsType,
* autowire?: bool,
* bind?: array<string, mixed>,
* constructor?: string,
* }
* @psalm-type DefinitionType = array{
* class?: string,
* file?: string,
* parent?: string,
* shared?: bool,
* synthetic?: bool,
* lazy?: bool|string,
* public?: bool,
* abstract?: bool,
* deprecated?: DeprecationType,
* factory?: CallbackType,
* configurator?: CallbackType,
* arguments?: ArgumentsType,
* properties?: array<string, mixed>,
* calls?: list<CallType>,
* tags?: TagsType,
* resource_tags?: TagsType,
* decorates?: string,
* decoration_inner_name?: string,
* decoration_priority?: int,
* decoration_on_invalid?: 'exception'|'ignore'|null,
* autowire?: bool,
* autoconfigure?: bool,
* bind?: array<string, mixed>,
* constructor?: string,
* from_callable?: CallbackType,
* }
* @psalm-type AliasType = string|array{
* alias: string,
* public?: bool,
* deprecated?: DeprecationType,
* }
* @psalm-type PrototypeType = array{
* resource: string,
* namespace?: string,
* exclude?: string|list<string>,
* parent?: string,
* shared?: bool,
* lazy?: bool|string,
* public?: bool,
* abstract?: bool,
* deprecated?: DeprecationType,
* factory?: CallbackType,
* arguments?: ArgumentsType,
* properties?: array<string, mixed>,
* configurator?: CallbackType,
* calls?: list<CallType>,
* tags?: TagsType,
* resource_tags?: TagsType,
* autowire?: bool,
* autoconfigure?: bool,
* bind?: array<string, mixed>,
* constructor?: string,
* }
* @psalm-type StackType = array{
* stack: list<DefinitionType|AliasType|PrototypeType|array<class-string, ArgumentsType|null>>,
* public?: bool,
* deprecated?: DeprecationType,
* }
* @psalm-type ServicesConfig = array{
* _defaults?: DefaultsType,
* _instanceof?: InstanceofType,
* ...<string, DefinitionType|AliasType|PrototypeType|StackType|ArgumentsType|null>
* }
* @psalm-type ExtensionType = array<string, mixed>
* @psalm-type ConfigType = array{
* imports?: ImportsConfig,
* parameters?: ParametersConfig,
* services?: ServicesConfig,
* ...<string, ExtensionType|array{ // extra keys must follow the when@%env% pattern or match an extension alias
* imports?: ImportsConfig,
* parameters?: ParametersConfig,
* services?: ServicesConfig,
* ...<string, ExtensionType>,
* }>
* }
*/
class AppReference
{
/**
* @param ConfigType $config
*
* @psalm-return ConfigType
*/
public static function config(array $config): array
{
$defaults = [
'_defaults' => [
'autowire' => true,
'autoconfigure' => true,
],
];
if (\is_array($config['services'] ?? null)) {
$config['services'] = array_replace_recursive($defaults, $config['services']);
}
foreach ($config as $key => $value) {
if (str_starts_with($key, 'when@') && \is_array($value['services'] ?? null)) {
$config[$key]['services'] = array_replace_recursive($defaults, $value['services']);
}
}
return $config;
}
}

View File

@@ -0,0 +1,16 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
class ClosureReferenceConfigurator extends ReferenceConfigurator
{
}

View File

@@ -0,0 +1,200 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\Config\Loader\ParamConfigurator;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Loader\UndefinedExtensionHandler;
use Symfony\Component\ExpressionLanguage\Expression;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ContainerConfigurator extends AbstractConfigurator
{
public const FACTORY = 'container';
private array $instanceof;
private int $anonymousCount = 0;
public function __construct(
private ContainerBuilder $container,
private PhpFileLoader $loader,
array &$instanceof,
private string $path,
private string $file,
private ?string $env = null,
) {
$this->instanceof = &$instanceof;
}
final public function extension(string $namespace, array $config, bool $prepend = false): void
{
if ($prepend) {
$this->container->prependExtensionConfig($namespace, static::processValue($config));
return;
}
if (!$this->container->hasExtension($namespace)) {
$extensions = array_filter(array_map(fn (ExtensionInterface $ext) => $ext->getAlias(), $this->container->getExtensions()));
throw new InvalidArgumentException(UndefinedExtensionHandler::getErrorMessage($namespace, $this->file, $namespace, $extensions));
}
$this->container->loadFromExtension($namespace, static::processValue($config));
}
final public function import(string $resource, ?string $type = null, bool|string $ignoreErrors = false): void
{
$this->loader->setCurrentDir(\dirname($this->path));
$this->loader->import($resource, $type, $ignoreErrors, $this->file);
}
final public function parameters(): ParametersConfigurator
{
return new ParametersConfigurator($this->container);
}
final public function services(): ServicesConfigurator
{
return new ServicesConfigurator($this->container, $this->loader, $this->instanceof, $this->path, $this->anonymousCount);
}
/**
* Get the current environment to be able to write conditional configuration.
*/
final public function env(): ?string
{
return $this->env;
}
final public function withPath(string $path): static
{
$clone = clone $this;
$clone->path = $clone->file = $path;
$clone->loader->setCurrentDir(\dirname($path));
return $clone;
}
}
/**
* Creates a parameter.
*/
function param(string $name): ParamConfigurator
{
return new ParamConfigurator($name);
}
/**
* Creates a reference to a service.
*/
function service(string $serviceId): ReferenceConfigurator
{
return new ReferenceConfigurator($serviceId);
}
/**
* Creates an inline service.
*/
function inline_service(?string $class = null): InlineServiceConfigurator
{
return new InlineServiceConfigurator(new Definition($class));
}
/**
* Creates a service locator.
*
* @param array<ReferenceConfigurator|InlineServiceConfigurator> $values
*/
function service_locator(array $values): ServiceLocatorArgument
{
$values = AbstractConfigurator::processValue($values, true);
return new ServiceLocatorArgument($values);
}
/**
* Creates a lazy iterator.
*
* @param ReferenceConfigurator[] $values
*/
function iterator(array $values): IteratorArgument
{
return new IteratorArgument(AbstractConfigurator::processValue($values, true));
}
/**
* Creates a lazy iterator by tag name.
*/
function tagged_iterator(string $tag, ?string $indexAttribute = null, ?string $defaultIndexMethod = null, ?string $defaultPriorityMethod = null, string|array $exclude = [], bool $excludeSelf = true): TaggedIteratorArgument
{
return new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod, (array) $exclude, $excludeSelf);
}
/**
* Creates a service locator by tag name.
*/
function tagged_locator(string $tag, ?string $indexAttribute = null, ?string $defaultIndexMethod = null, ?string $defaultPriorityMethod = null, string|array $exclude = [], bool $excludeSelf = true): ServiceLocatorArgument
{
return new ServiceLocatorArgument(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, true, $defaultPriorityMethod, (array) $exclude, $excludeSelf));
}
/**
* Creates an expression.
*/
function expr(string $expression): ExpressionConfigurator
{
return new ExpressionConfigurator($expression);
}
/**
* Creates an abstract argument.
*/
function abstract_arg(string $description): AbstractArgument
{
return new AbstractArgument($description);
}
/**
* Creates an environment variable reference.
*/
function env(string $name): EnvConfigurator
{
return new EnvConfigurator($name);
}
/**
* Creates a closure service reference.
*/
function service_closure(string $serviceId): ClosureReferenceConfigurator
{
return new ClosureReferenceConfigurator($serviceId);
}
/**
* Creates a closure.
*/
function closure(string|array|\Closure|ReferenceConfigurator|Expression $callable): InlineServiceConfigurator
{
return (new InlineServiceConfigurator(new Definition('Closure')))
->factory(['Closure', 'fromCallable'])
->args([$callable]);
}

View File

@@ -0,0 +1,96 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class DefaultsConfigurator extends AbstractServiceConfigurator
{
use Traits\AutoconfigureTrait;
use Traits\AutowireTrait;
use Traits\BindTrait;
use Traits\PublicTrait;
public const FACTORY = 'defaults';
public function __construct(
ServicesConfigurator $parent,
Definition $definition,
private ?string $path = null,
) {
parent::__construct($parent, $definition, null, []);
}
/**
* Adds a tag for this definition.
*
* @return $this
*
* @throws InvalidArgumentException when an invalid tag name or attribute is provided
*/
final public function tag(string $name, array $attributes = []): static
{
if ('' === $name) {
throw new InvalidArgumentException('The tag name in "_defaults" must be a non-empty string.');
}
$this->validateAttributes($name, $attributes);
$this->definition->addTag($name, $attributes);
return $this;
}
/**
* Adds a resource tag for this definition.
*
* @return $this
*
* @throws InvalidArgumentException when an invalid tag name or attribute is provided
*/
final public function resourceTag(string $name, array $attributes = []): static
{
if ('' === $name) {
throw new InvalidArgumentException('The resource tag name in "_defaults" must be a non-empty string.');
}
$this->validateAttributes($name, $attributes);
$this->definition->addResourceTag($name, $attributes);
return $this;
}
/**
* Defines an instanceof-conditional to be applied to following service definitions.
*/
final public function instanceof(string $fqcn): InstanceofConfigurator
{
return $this->parent->instanceof($fqcn);
}
private function validateAttributes(string $tag, array $attributes, array $path = []): void
{
foreach ($attributes as $name => $value) {
if (\is_array($value)) {
$this->validateAttributes($tag, $value, [...$path, $name]);
} elseif (!\is_scalar($value ?? '')) {
$name = implode('.', [...$path, $name]);
throw new InvalidArgumentException(\sprintf('Tag "%s", attribute "%s" in "_defaults" must be of a scalar-type or an array of scalar-type.', $tag, $name));
}
}
}
}

View File

@@ -0,0 +1,236 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\Config\Loader\ParamConfigurator;
class EnvConfigurator extends ParamConfigurator
{
/**
* @var string[]
*/
private array $stack;
public function __construct(string $name)
{
$this->stack = explode(':', $name);
}
public function __toString(): string
{
return '%env('.implode(':', $this->stack).')%';
}
/**
* @return $this
*/
public function __call(string $name, array $arguments): static
{
$processor = strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], '\1_\2', $name));
$this->custom($processor, ...$arguments);
return $this;
}
/**
* @return $this
*/
public function custom(string $processor, ...$args): static
{
array_unshift($this->stack, $processor, ...$args);
return $this;
}
/**
* @return $this
*/
public function base64(): static
{
array_unshift($this->stack, 'base64');
return $this;
}
/**
* @return $this
*/
public function bool(): static
{
array_unshift($this->stack, 'bool');
return $this;
}
/**
* @return $this
*/
public function not(): static
{
array_unshift($this->stack, 'not');
return $this;
}
/**
* @return $this
*/
public function const(): static
{
array_unshift($this->stack, 'const');
return $this;
}
/**
* @return $this
*/
public function csv(): static
{
array_unshift($this->stack, 'csv');
return $this;
}
/**
* @return $this
*/
public function file(): static
{
array_unshift($this->stack, 'file');
return $this;
}
/**
* @return $this
*/
public function float(): static
{
array_unshift($this->stack, 'float');
return $this;
}
/**
* @return $this
*/
public function int(): static
{
array_unshift($this->stack, 'int');
return $this;
}
/**
* @return $this
*/
public function json(): static
{
array_unshift($this->stack, 'json');
return $this;
}
/**
* @return $this
*/
public function key(string $key): static
{
array_unshift($this->stack, 'key', $key);
return $this;
}
/**
* @return $this
*/
public function url(): static
{
array_unshift($this->stack, 'url');
return $this;
}
/**
* @return $this
*/
public function queryString(): static
{
array_unshift($this->stack, 'query_string');
return $this;
}
/**
* @return $this
*/
public function resolve(): static
{
array_unshift($this->stack, 'resolve');
return $this;
}
/**
* @return $this
*/
public function default(string $fallbackParam): static
{
array_unshift($this->stack, 'default', $fallbackParam);
return $this;
}
/**
* @return $this
*/
public function string(): static
{
array_unshift($this->stack, 'string');
return $this;
}
/**
* @return $this
*/
public function trim(): static
{
array_unshift($this->stack, 'trim');
return $this;
}
/**
* @return $this
*/
public function require(): static
{
array_unshift($this->stack, 'require');
return $this;
}
/**
* @param class-string<\BackedEnum> $backedEnumClassName
*
* @return $this
*/
public function enum(string $backedEnumClassName): static
{
array_unshift($this->stack, 'enum', $backedEnumClassName);
return $this;
}
}

View File

@@ -0,0 +1,18 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\ExpressionLanguage\Expression;
class ExpressionConfigurator extends Expression
{
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Definition;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class FromCallableConfigurator extends AbstractServiceConfigurator
{
use Traits\AbstractTrait;
use Traits\AutoconfigureTrait;
use Traits\AutowireTrait;
use Traits\BindTrait;
use Traits\DecorateTrait;
use Traits\DeprecateTrait;
use Traits\LazyTrait;
use Traits\PublicTrait;
use Traits\ShareTrait;
use Traits\TagTrait;
public const FACTORY = 'services';
public function __construct(
private ServiceConfigurator $serviceConfigurator,
Definition $definition,
) {
parent::__construct($serviceConfigurator->parent, $definition, $serviceConfigurator->id);
}
public function __destruct()
{
$this->serviceConfigurator->__destruct();
}
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Definition;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class InlineServiceConfigurator extends AbstractConfigurator
{
use Traits\ArgumentTrait;
use Traits\AutowireTrait;
use Traits\BindTrait;
use Traits\CallTrait;
use Traits\ConfiguratorTrait;
use Traits\ConstructorTrait;
use Traits\FactoryTrait;
use Traits\FileTrait;
use Traits\LazyTrait;
use Traits\ParentTrait;
use Traits\PropertyTrait;
use Traits\TagTrait;
public const FACTORY = 'service';
private string $id = '[inline]';
private bool $allowParent = true;
private ?string $path = null;
public function __construct(Definition $definition)
{
$this->definition = $definition;
}
}

View File

@@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Definition;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class InstanceofConfigurator extends AbstractServiceConfigurator
{
use Traits\AutowireTrait;
use Traits\BindTrait;
use Traits\CallTrait;
use Traits\ConfiguratorTrait;
use Traits\ConstructorTrait;
use Traits\LazyTrait;
use Traits\PropertyTrait;
use Traits\PublicTrait;
use Traits\ShareTrait;
use Traits\TagTrait;
public const FACTORY = 'instanceof';
public function __construct(
ServicesConfigurator $parent,
Definition $definition,
string $id,
private ?string $path = null,
) {
parent::__construct($parent, $definition, $id, []);
}
/**
* Defines an instanceof-conditional to be applied to following service definitions.
*/
final public function instanceof(string $fqcn): self
{
return $this->parent->instanceof($fqcn);
}
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\ExpressionLanguage\Expression;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ParametersConfigurator extends AbstractConfigurator
{
public const FACTORY = 'parameters';
public function __construct(
private ContainerBuilder $container,
) {
}
/**
* @return $this
*/
final public function set(string $name, mixed $value): static
{
if ($value instanceof Expression) {
throw new InvalidArgumentException(\sprintf('Using an expression in parameter "%s" is not allowed.', $name));
}
$this->container->setParameter($name, static::processValue($value, true));
return $this;
}
/**
* @return $this
*/
final public function __invoke(string $name, mixed $value): static
{
return $this->set($name, $value);
}
}

View File

@@ -0,0 +1,86 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class PrototypeConfigurator extends AbstractServiceConfigurator
{
use Traits\AbstractTrait;
use Traits\ArgumentTrait;
use Traits\AutoconfigureTrait;
use Traits\AutowireTrait;
use Traits\BindTrait;
use Traits\CallTrait;
use Traits\ConfiguratorTrait;
use Traits\ConstructorTrait;
use Traits\DeprecateTrait;
use Traits\FactoryTrait;
use Traits\LazyTrait;
use Traits\ParentTrait;
use Traits\PropertyTrait;
use Traits\PublicTrait;
use Traits\ShareTrait;
use Traits\TagTrait;
public const FACTORY = 'load';
private ?array $excludes = null;
public function __construct(
ServicesConfigurator $parent,
private PhpFileLoader $loader,
Definition $defaults,
string $namespace,
private string $resource,
private bool $allowParent,
private ?string $path = null,
) {
$definition = new Definition();
$definition->setPublic($defaults->isPublic());
$definition->setAutowired($defaults->isAutowired());
$definition->setAutoconfigured($defaults->isAutoconfigured());
// deep clone, to avoid multiple process of the same instance in the passes
$definition->setBindings(unserialize(serialize($defaults->getBindings())));
$definition->setChanges([]);
parent::__construct($parent, $definition, $namespace, $defaults->getTags());
}
public function __destruct()
{
parent::__destruct();
if (isset($this->loader)) {
$this->loader->registerClasses($this->definition, $this->id, $this->resource, $this->excludes, $this->path);
}
unset($this->loader);
}
/**
* Excludes files from registration using glob patterns.
*
* @param string[]|string $excludes
*
* @return $this
*/
final public function exclude(array|string $excludes): static
{
$this->excludes = (array) $excludes;
return $this;
}
}

View File

@@ -0,0 +1,66 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ReferenceConfigurator extends AbstractConfigurator
{
/** @internal */
protected string $id;
/** @internal */
protected int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
public function __construct(string $id)
{
$this->id = $id;
}
/**
* @return $this
*/
final public function ignoreOnInvalid(): static
{
$this->invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
return $this;
}
/**
* @return $this
*/
final public function nullOnInvalid(): static
{
$this->invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
return $this;
}
/**
* @return $this
*/
final public function ignoreOnUninitialized(): static
{
$this->invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE;
return $this;
}
public function __toString(): string
{
return $this->id;
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ServiceConfigurator extends AbstractServiceConfigurator
{
use Traits\AbstractTrait;
use Traits\ArgumentTrait;
use Traits\AutoconfigureTrait;
use Traits\AutowireTrait;
use Traits\BindTrait;
use Traits\CallTrait;
use Traits\ClassTrait;
use Traits\ConfiguratorTrait;
use Traits\ConstructorTrait;
use Traits\DecorateTrait;
use Traits\DeprecateTrait;
use Traits\FactoryTrait;
use Traits\FileTrait;
use Traits\FromCallableTrait;
use Traits\LazyTrait;
use Traits\ParentTrait;
use Traits\PropertyTrait;
use Traits\PublicTrait;
use Traits\ShareTrait;
use Traits\SyntheticTrait;
use Traits\TagTrait;
public const FACTORY = 'services';
private bool $destructed = false;
public function __construct(
private ContainerBuilder $container,
private array $instanceof,
private bool $allowParent,
ServicesConfigurator $parent,
Definition $definition,
?string $id,
array $defaultTags,
private ?string $path = null,
) {
parent::__construct($parent, $definition, $id, $defaultTags);
}
public function __destruct()
{
if ($this->destructed) {
return;
}
$this->destructed = true;
parent::__destruct();
$this->container->removeBindings($this->id);
$this->container->setDefinition($this->id, $this->definition->setInstanceofConditionals($this->instanceof));
}
}

View File

@@ -0,0 +1,189 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ServicesConfigurator extends AbstractConfigurator
{
public const FACTORY = 'services';
private Definition $defaults;
private array $instanceof;
private string $anonymousHash;
private int $anonymousCount;
public function __construct(
private ContainerBuilder $container,
private PhpFileLoader $loader,
array &$instanceof,
private ?string $path = null,
int &$anonymousCount = 0,
) {
$this->defaults = new Definition();
$this->instanceof = &$instanceof;
$this->anonymousHash = ContainerBuilder::hash($path ?: mt_rand());
$this->anonymousCount = &$anonymousCount;
$instanceof = [];
}
/**
* Defines a set of defaults for following service definitions.
*/
final public function defaults(): DefaultsConfigurator
{
return new DefaultsConfigurator($this, $this->defaults = new Definition(), $this->path);
}
/**
* Defines an instanceof-conditional to be applied to following service definitions.
*/
final public function instanceof(string $fqcn): InstanceofConfigurator
{
$this->instanceof[$fqcn] = $definition = new ChildDefinition('');
return new InstanceofConfigurator($this, $definition, $fqcn, $this->path);
}
/**
* Registers a service.
*
* @param string|null $id The service id, or null to create an anonymous service
* @param string|null $class The class of the service, or null when $id is also the class name
*/
final public function set(?string $id, ?string $class = null): ServiceConfigurator
{
$defaults = $this->defaults;
$definition = new Definition();
if (null === $id) {
if (!$class) {
throw new \LogicException('Anonymous services must have a class name.');
}
$id = \sprintf('.%d_%s', ++$this->anonymousCount, preg_replace('/^.*\\\\/', '', $class).'~'.$this->anonymousHash);
} else {
$definition->setPublic($defaults->isPublic());
}
$definition->setAutowired($defaults->isAutowired());
$definition->setAutoconfigured($defaults->isAutoconfigured());
// deep clone, to avoid multiple process of the same instance in the passes
$definition->setBindings(unserialize(serialize($defaults->getBindings())));
$definition->setChanges([]);
$configurator = new ServiceConfigurator($this->container, $this->instanceof, true, $this, $definition, $id, $defaults->getTags(), $this->path);
return null !== $class ? $configurator->class($class) : $configurator;
}
/**
* Removes an already defined service definition or alias.
*
* @return $this
*/
final public function remove(string $id): static
{
$this->container->removeDefinition($id);
$this->container->removeAlias($id);
return $this;
}
/**
* Creates an alias.
*/
final public function alias(string $id, string $referencedId): AliasConfigurator
{
$ref = static::processValue($referencedId, true);
$alias = new Alias((string) $ref);
$alias->setPublic($this->defaults->isPublic());
$this->container->setAlias($id, $alias);
return new AliasConfigurator($this, $alias);
}
/**
* Registers a PSR-4 namespace using a glob pattern.
*/
final public function load(string $namespace, string $resource): PrototypeConfigurator
{
return new PrototypeConfigurator($this, $this->loader, $this->defaults, $namespace, $resource, true, $this->path);
}
/**
* Gets an already defined service definition.
*
* @throws ServiceNotFoundException if the service definition does not exist
*/
final public function get(string $id): ServiceConfigurator
{
$definition = $this->container->getDefinition($id);
return new ServiceConfigurator($this->container, $definition->getInstanceofConditionals(), true, $this, $definition, $id, []);
}
/**
* Registers a stack of decorator services.
*
* @param InlineServiceConfigurator[]|ReferenceConfigurator[] $services
*/
final public function stack(string $id, array $services): AliasConfigurator
{
foreach ($services as $i => $service) {
if ($service instanceof InlineServiceConfigurator) {
$definition = $service->definition->setInstanceofConditionals($this->instanceof);
$changes = $definition->getChanges();
$definition->setAutowired((isset($changes['autowired']) ? $definition : $this->defaults)->isAutowired());
$definition->setAutoconfigured((isset($changes['autoconfigured']) ? $definition : $this->defaults)->isAutoconfigured());
$definition->setBindings(array_merge($this->defaults->getBindings(), $definition->getBindings()));
$definition->setChanges($changes);
$services[$i] = $definition;
} elseif (!$service instanceof ReferenceConfigurator) {
throw new InvalidArgumentException(\sprintf('"%s()" expects a list of definitions as returned by "%s()" or "%s()", "%s" given at index "%s" for service "%s".', __METHOD__, InlineServiceConfigurator::FACTORY, ReferenceConfigurator::FACTORY, $service instanceof AbstractConfigurator ? $service::FACTORY.'()' : get_debug_type($service), $i, $id));
}
}
$alias = $this->alias($id, '');
$alias->definition = $this->set($id)
->parent('')
->args($services)
->tag('container.stack')
->definition;
return $alias;
}
/**
* Registers a service.
*/
final public function __invoke(string $id, ?string $class = null): ServiceConfigurator
{
return $this->set($id, $class);
}
public function __destruct()
{
$this->loader->registerAliasesForSinglyImplementedInterfaces();
}
}

View File

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait AbstractTrait
{
/**
* Whether this definition is abstract, that means it merely serves as a
* template for other definitions.
*
* @return $this
*/
final public function abstract(bool $abstract = true): static
{
$this->definition->setAbstract($abstract);
return $this;
}
}

View File

@@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait ArgumentTrait
{
/**
* Sets the arguments to pass to the service constructor/factory method.
*
* @return $this
*/
final public function args(array $arguments): static
{
$this->definition->setArguments(static::processValue($arguments, true));
return $this;
}
/**
* Sets one argument to pass to the service constructor/factory method.
*
* @return $this
*/
final public function arg(string|int $key, mixed $value): static
{
$this->definition->setArgument($key, static::processValue($value, true));
return $this;
}
}

View File

@@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
trait AutoconfigureTrait
{
/**
* Sets whether or not instanceof conditionals should be prepended with a global set.
*
* @return $this
*
* @throws InvalidArgumentException when a parent is already set
*/
final public function autoconfigure(bool $autoconfigured = true): static
{
$this->definition->setAutoconfigured($autoconfigured);
return $this;
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait AutowireTrait
{
/**
* Enables/disables autowiring.
*
* @return $this
*/
final public function autowire(bool $autowired = true): static
{
$this->definition->setAutowired($autowired);
return $this;
}
}

View File

@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Loader\Configurator\DefaultsConfigurator;
use Symfony\Component\DependencyInjection\Loader\Configurator\InstanceofConfigurator;
trait BindTrait
{
/**
* Sets bindings.
*
* Bindings map $named or FQCN arguments to values that should be
* injected in the matching parameters (of the constructor, of methods
* called and of controller actions).
*
* @param string $nameOrFqcn A parameter name with its "$" prefix, or a FQCN
* @param mixed $valueOrRef The value or reference to bind
*
* @return $this
*/
final public function bind(string $nameOrFqcn, mixed $valueOrRef): static
{
$valueOrRef = static::processValue($valueOrRef, true);
$bindings = $this->definition->getBindings();
$type = $this instanceof DefaultsConfigurator ? BoundArgument::DEFAULTS_BINDING : ($this instanceof InstanceofConfigurator ? BoundArgument::INSTANCEOF_BINDING : BoundArgument::SERVICE_BINDING);
$bindings[$nameOrFqcn] = new BoundArgument($valueOrRef, true, $type, $this->path ?? null);
$this->definition->setBindings($bindings);
return $this;
}
}

View File

@@ -0,0 +1,35 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
trait CallTrait
{
/**
* Adds a method to call after service initialization.
*
* @param string $method The method name to call
* @param array $arguments An array of arguments to pass to the method call
* @param bool $returnsClone Whether the call returns the service instance or not
*
* @return $this
*
* @throws InvalidArgumentException on empty $method param
*/
final public function call(string $method, array $arguments = [], bool $returnsClone = false): static
{
$this->definition->addMethodCall($method, static::processValue($arguments, true), $returnsClone);
return $this;
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait ClassTrait
{
/**
* Sets the service class.
*
* @return $this
*/
final public function class(?string $class): static
{
$this->definition->setClass($class);
return $this;
}
}

View File

@@ -0,0 +1,29 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator;
trait ConfiguratorTrait
{
/**
* Sets a configurator to call after the service is fully initialized.
*
* @return $this
*/
final public function configurator(string|array|\Closure|ReferenceConfigurator $configurator): static
{
$this->definition->setConfigurator(static::processValue($configurator, true));
return $this;
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait ConstructorTrait
{
/**
* Sets a static constructor.
*
* @return $this
*/
final public function constructor(string $constructor): static
{
$this->definition->setFactory([null, $constructor]);
return $this;
}
}

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
trait DecorateTrait
{
/**
* Sets the service that this service is decorating.
*
* @param string|null $id The decorated service id, use null to remove decoration
*
* @return $this
*
* @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals
*/
final public function decorate(?string $id, ?string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): static
{
$this->definition->setDecoratedService($id, $renamedId, $priority, $invalidBehavior);
return $this;
}
}

View File

@@ -0,0 +1,35 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
trait DeprecateTrait
{
/**
* Whether this definition is deprecated, that means it should not be called anymore.
*
* @param string $package The name of the composer package that is triggering the deprecation
* @param string $version The version of the package that introduced the deprecation
* @param string $message The deprecation message to use
*
* @return $this
*
* @throws InvalidArgumentException when the message template is invalid
*/
final public function deprecate(string $package, string $version, string $message): static
{
$this->definition->setDeprecated($package, $version, $message);
return $this;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator;
use Symfony\Component\ExpressionLanguage\Expression;
trait FactoryTrait
{
/**
* Sets a factory.
*
* @return $this
*/
final public function factory(string|array|\Closure|ReferenceConfigurator|Expression $factory): static
{
if (\is_string($factory) && 1 === substr_count($factory, ':')) {
$factoryParts = explode(':', $factory);
throw new InvalidArgumentException(\sprintf('Invalid factory "%s": the "service:method" notation is not available when using PHP-based DI configuration. Use "[service(\'%s\'), \'%s\']" instead.', $factory, $factoryParts[0], $factoryParts[1]));
}
if ($factory instanceof Expression) {
$factory = '@='.$factory;
}
$this->definition->setFactory(static::processValue($factory, true));
return $this;
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait FileTrait
{
/**
* Sets a file to require before creating the service.
*
* @return $this
*/
final public function file(string $file): static
{
$this->definition->setFile($file);
return $this;
}
}

View File

@@ -0,0 +1,64 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Loader\Configurator\FromCallableConfigurator;
use Symfony\Component\DependencyInjection\Loader\Configurator\ReferenceConfigurator;
use Symfony\Component\ExpressionLanguage\Expression;
trait FromCallableTrait
{
final public function fromCallable(string|array|\Closure|ReferenceConfigurator|Expression $callable): FromCallableConfigurator
{
if ($this->definition instanceof ChildDefinition) {
throw new InvalidArgumentException('The configuration key "parent" is unsupported when using "fromCallable()".');
}
foreach ([
'synthetic' => 'isSynthetic',
'factory' => 'getFactory',
'file' => 'getFile',
'arguments' => 'getArguments',
'properties' => 'getProperties',
'configurator' => 'getConfigurator',
'calls' => 'getMethodCalls',
] as $key => $method) {
if ($this->definition->$method()) {
throw new InvalidArgumentException(\sprintf('The configuration key "%s" is unsupported when using "fromCallable()".', $key));
}
}
$this->definition->setFactory(['Closure', 'fromCallable']);
if (\is_string($callable) && 1 === substr_count($callable, ':')) {
$parts = explode(':', $callable);
throw new InvalidArgumentException(\sprintf('Invalid callable "%s": the "service:method" notation is not available when using PHP-based DI configuration. Use "[service(\'%s\'), \'%s\']" instead.', $callable, $parts[0], $parts[1]));
}
if ($callable instanceof Expression) {
$callable = '@='.$callable;
}
$this->definition->setArguments([static::processValue($callable, true)]);
if ('Closure' !== ($this->definition->getClass() ?? 'Closure')) {
$this->definition->setLazy(true);
} else {
$this->definition->setClass('Closure');
}
return new FromCallableConfigurator($this, $this->definition);
}
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait LazyTrait
{
/**
* Sets the lazy flag of this service.
*
* @param bool|string $lazy A FQCN to derivate the lazy proxy from or `true` to make it extend from the definition's class
*
* @return $this
*/
final public function lazy(bool|string $lazy = true): static
{
$this->definition->setLazy((bool) $lazy);
if (\is_string($lazy)) {
$this->definition->addTag('proxy', ['interface' => $lazy]);
}
return $this;
}
}

View File

@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
trait ParentTrait
{
/**
* Sets the Definition to inherit from.
*
* @return $this
*
* @throws InvalidArgumentException when parent cannot be set
*/
final public function parent(string $parent): static
{
if (!$this->allowParent) {
throw new InvalidArgumentException(\sprintf('A parent cannot be defined when either "_instanceof" or "_defaults" are also defined for service prototype "%s".', $this->id));
}
if ($this->definition instanceof ChildDefinition) {
$this->definition->setParent($parent);
} else {
// cast Definition to ChildDefinition
$definition = serialize($this->definition);
$definition = substr_replace($definition, '53', 2, 2);
$definition = substr_replace($definition, 'Child', 44, 0);
$definition = unserialize($definition);
$this->definition = $definition->setParent($parent);
}
return $this;
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait PropertyTrait
{
/**
* Sets a specific property.
*
* @return $this
*/
final public function property(string $name, mixed $value): static
{
$this->definition->setProperty($name, static::processValue($value, true));
return $this;
}
}

View File

@@ -0,0 +1,35 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait PublicTrait
{
/**
* @return $this
*/
final public function public(): static
{
$this->definition->setPublic(true);
return $this;
}
/**
* @return $this
*/
final public function private(): static
{
$this->definition->setPublic(false);
return $this;
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait ShareTrait
{
/**
* Sets if the service must be shared or not.
*
* @return $this
*/
final public function share(bool $shared = true): static
{
$this->definition->setShared($shared);
return $this;
}
}

View File

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
trait SyntheticTrait
{
/**
* Sets whether this definition is synthetic, that is not constructed by the
* container, but dynamically injected.
*
* @return $this
*/
final public function synthetic(bool $synthetic = true): static
{
$this->definition->setSynthetic($synthetic);
return $this;
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader\Configurator\Traits;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
trait TagTrait
{
/**
* Adds a tag for this definition.
*
* @return $this
*/
final public function tag(string $name, array $attributes = []): static
{
if ('' === $name) {
throw new InvalidArgumentException(\sprintf('The tag name for service "%s" must be a non-empty string.', $this->id));
}
$this->validateAttributes($name, $attributes);
$this->definition->addTag($name, $attributes);
return $this;
}
/**
* Adds a resource tag for this definition.
*
* @return $this
*/
final public function resourceTag(string $name, array $attributes = []): static
{
if ('' === $name) {
throw new InvalidArgumentException(\sprintf('The resource tag name for service "%s" must be a non-empty string.', $this->id));
}
$this->validateAttributes($name, $attributes);
$this->definition->addResourceTag($name, $attributes);
return $this;
}
private function validateAttributes(string $tag, array $attributes, array $path = []): void
{
foreach ($attributes as $name => $value) {
if (\is_array($value)) {
$this->validateAttributes($tag, $value, [...$path, $name]);
} elseif (!\is_scalar($value ?? '')) {
$name = implode('.', [...$path, $name]);
throw new InvalidArgumentException(\sprintf('A tag attribute must be of a scalar-type or an array of scalar-types for service "%s", tag "%s", attribute "%s".', $this->id, $tag, $name));
}
}
}
}

View File

@@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader;
/**
* DirectoryLoader is a recursive loader to go through directories.
*
* @author Sebastien Lavoie <seb@wemakecustom.com>
*/
class DirectoryLoader extends FileLoader
{
public function load(mixed $file, ?string $type = null): mixed
{
$file = rtrim($file, '/');
$path = $this->locator->locate($file);
$this->container->fileExists($path, false);
foreach (scandir($path) as $dir) {
if ('.' !== $dir[0]) {
if (is_dir($path.'/'.$dir)) {
$dir .= '/'; // append / to allow recursion
}
$this->setCurrentDir($path);
$this->import($dir, null, false, $path);
}
}
return null;
}
public function supports(mixed $resource, ?string $type = null): bool
{
if ('directory' === $type) {
return true;
}
return null === $type && \is_string($resource) && str_ends_with($resource, '/');
}
}

View File

@@ -0,0 +1,412 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException;
use Symfony\Component\Config\Exception\LoaderLoadException;
use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Component\Config\Loader\FileLoader as BaseFileLoader;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Config\Resource\GlobResource;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Attribute\AsAlias;
use Symfony\Component\DependencyInjection\Attribute\Exclude;
use Symfony\Component\DependencyInjection\Attribute\When;
use Symfony\Component\DependencyInjection\Attribute\WhenNot;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\RegisterAutoconfigureAttributesPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
/**
* FileLoader is the abstract class used by all built-in loaders that are file based.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class FileLoader extends BaseFileLoader
{
public const ANONYMOUS_ID_REGEXP = '/^\.\d+_[^~]*+~[._a-zA-Z\d]{7}$/';
protected bool $isLoadingInstanceof = false;
protected array $instanceof = [];
protected array $interfaces = [];
protected array $singlyImplemented = [];
/** @var array<string, Alias> */
protected array $aliases = [];
protected bool $autoRegisterAliasesForSinglyImplementedInterfaces = true;
protected array $extensionConfigs = [];
protected int $importing = 0;
/**
* @param bool $prepend Whether to prepend extension config instead of appending them
*/
public function __construct(
protected ContainerBuilder $container,
FileLocatorInterface $locator,
?string $env = null,
protected bool $prepend = false,
) {
parent::__construct($locator, $env);
}
/**
* @param bool|string $ignoreErrors Whether errors should be ignored; pass "not_found" to ignore only when the loaded resource is not found
*/
public function import(mixed $resource, ?string $type = null, bool|string $ignoreErrors = false, ?string $sourceResource = null, $exclude = null): mixed
{
$args = \func_get_args();
if ($ignoreNotFound = 'not_found' === $ignoreErrors) {
$args[2] = false;
} elseif (!\is_bool($ignoreErrors)) {
throw new \TypeError(\sprintf('Invalid argument $ignoreErrors provided to "%s::import()": boolean or "not_found" expected, "%s" given.', static::class, get_debug_type($ignoreErrors)));
}
++$this->importing;
try {
return parent::import(...$args);
} catch (LoaderLoadException $e) {
if (!$ignoreNotFound || !($prev = $e->getPrevious()) instanceof FileLocatorFileNotFoundException) {
throw $e;
}
foreach ($prev->getTrace() as $frame) {
if ('import' === ($frame['function'] ?? null) && is_a($frame['class'] ?? '', Loader::class, true)) {
break;
}
}
if (__FILE__ !== $frame['file']) {
throw $e;
}
} finally {
--$this->importing;
$this->loadExtensionConfigs();
}
return null;
}
/**
* Registers a set of classes as services using PSR-4 for discovery.
*
* @param Definition $prototype A definition to use as template
* @param string $namespace The namespace prefix of classes in the scanned directory
* @param string $resource The directory to look for classes, glob-patterns allowed
* @param string|string[]|null $exclude A globbed path of files to exclude or an array of globbed paths of files to exclude
* @param string|null $source The path to the file that defines the auto-discovery rule
*/
public function registerClasses(Definition $prototype, string $namespace, string $resource, string|array|null $exclude = null, ?string $source = null): void
{
if (!str_ends_with($namespace, '\\')) {
throw new InvalidArgumentException(\sprintf('Namespace prefix must end with a "\\": "%s".', $namespace));
}
if (!preg_match('/^(?:[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+\\\\)++$/', $namespace)) {
throw new InvalidArgumentException(\sprintf('Namespace is not a valid PSR-4 prefix: "%s".', $namespace));
}
// This can happen with YAML files
if (\is_array($exclude) && \in_array(null, $exclude, true)) {
throw new InvalidArgumentException('The exclude list must not contain a "null" value.');
}
// This can happen with XML files
if (\is_array($exclude) && \in_array('', $exclude, true)) {
throw new InvalidArgumentException('The exclude list must not contain an empty value.');
}
$autoconfigureAttributes = new RegisterAutoconfigureAttributesPass();
$autoconfigureAttributes = $autoconfigureAttributes->accept($prototype) ? $autoconfigureAttributes : null;
$classes = $this->findClasses($namespace, $resource, (array) $exclude, $source);
$getPrototype = static fn () => clone $prototype;
$serialized = serialize($prototype);
// avoid deep cloning if no definitions are nested
if (strpos($serialized, 'O:48:"Symfony\Component\DependencyInjection\Definition"', 55)
|| strpos($serialized, 'O:53:"Symfony\Component\DependencyInjection\ChildDefinition"', 55)
) {
// prepare for deep cloning
foreach (['Arguments', 'Properties', 'MethodCalls', 'Configurator', 'Factory', 'Bindings'] as $key) {
$serialized = serialize($prototype->{'get'.$key}());
if (strpos($serialized, 'O:48:"Symfony\Component\DependencyInjection\Definition"')
|| strpos($serialized, 'O:53:"Symfony\Component\DependencyInjection\ChildDefinition"')
) {
$getPrototype = static fn () => $getPrototype()->{'set'.$key}(unserialize($serialized));
}
}
}
unset($serialized);
foreach ($classes as $class => $errorMessage) {
if (null === $errorMessage && $autoconfigureAttributes) {
$r = $this->container->getReflectionClass($class);
if ($r->getAttributes(Exclude::class)[0] ?? null) {
$this->addContainerExcludedTag($class, $source);
continue;
}
if ($this->env) {
$excluded = true;
$whenAttributes = $r->getAttributes(When::class, \ReflectionAttribute::IS_INSTANCEOF);
$notWhenAttributes = $r->getAttributes(WhenNot::class, \ReflectionAttribute::IS_INSTANCEOF);
if ($whenAttributes && $notWhenAttributes) {
throw new LogicException(\sprintf('The "%s" class cannot have both #[When] and #[WhenNot] attributes.', $class));
}
if (!$whenAttributes && !$notWhenAttributes) {
$excluded = false;
}
foreach ($whenAttributes as $attribute) {
if ($this->env === $attribute->newInstance()->env) {
$excluded = false;
break;
}
}
foreach ($notWhenAttributes as $attribute) {
if ($excluded = $this->env === $attribute->newInstance()->env) {
break;
}
}
if ($excluded) {
$this->addContainerExcludedTag($class, $source);
continue;
}
}
}
$r = null === $errorMessage ? $this->container->getReflectionClass($class) : null;
$abstract = $r?->isAbstract() || $r?->isInterface() ? '.abstract.' : '';
$this->setDefinition($abstract.$class, $definition = $getPrototype());
$definition->setClass($class);
if (null !== $errorMessage) {
$definition->addError($errorMessage);
continue;
}
if ($abstract) {
if ($r->isInterface()) {
$this->interfaces[] = $class;
}
$autoconfigureAttributes?->processClass($this->container, $r);
$definition->setAbstract(true)
->addTag('container.excluded', ['source' => 'because the class is abstract']);
continue;
}
$interfaces = [];
foreach (class_implements($class, false) as $interface) {
$this->singlyImplemented[$interface] = ($this->singlyImplemented[$interface] ?? $class) !== $class ? false : $class;
$interfaces[] = $interface;
}
if (!$autoconfigureAttributes) {
continue;
}
$r = $this->container->getReflectionClass($class);
$defaultAlias = 1 === \count($interfaces) ? $interfaces[0] : null;
foreach ($r->getAttributes(AsAlias::class, \ReflectionAttribute::IS_INSTANCEOF) as $attr) {
/** @var AsAlias $attribute */
$attribute = $attr->newInstance();
$alias = $attribute->id ?? $defaultAlias;
$public = $attribute->public;
if (null === $alias) {
throw new LogicException(\sprintf('Alias cannot be automatically determined for class "%s". If you have used the #[AsAlias] attribute with a class implementing multiple interfaces, add the interface you want to alias to the first parameter of #[AsAlias].', $class));
}
if (!$attribute->when || \in_array($this->env, $attribute->when, true)) {
if (isset($this->aliases[$alias])) {
throw new LogicException(\sprintf('The "%s" alias has already been defined with the #[AsAlias] attribute in "%s".', $alias, $this->aliases[$alias]));
}
$this->aliases[$alias] = new Alias($class, $public);
}
}
}
foreach ($this->aliases as $alias => $aliasDefinition) {
$this->container->setAlias($alias, $aliasDefinition);
}
if ($this->autoRegisterAliasesForSinglyImplementedInterfaces) {
$this->registerAliasesForSinglyImplementedInterfaces();
}
}
public function registerAliasesForSinglyImplementedInterfaces(): void
{
foreach ($this->interfaces as $interface) {
if (!empty($this->singlyImplemented[$interface]) && !isset($this->aliases[$interface]) && !$this->container->has($interface)) {
$this->container->setAlias($interface, $this->singlyImplemented[$interface]);
}
}
$this->interfaces = $this->singlyImplemented = $this->aliases = [];
}
final protected function loadExtensionConfig(string $namespace, array $config, string $file = '?'): void
{
if (!$this->prepend) {
$this->container->loadFromExtension($namespace, $config);
return;
}
if ($this->importing) {
if (!isset($this->extensionConfigs[$namespace])) {
$this->extensionConfigs[$namespace] = [];
}
array_unshift($this->extensionConfigs[$namespace], $config);
return;
}
$this->container->prependExtensionConfig($namespace, $config);
}
final protected function loadExtensionConfigs(): void
{
if ($this->importing || !$this->extensionConfigs) {
return;
}
foreach ($this->extensionConfigs as $namespace => $configs) {
foreach ($configs as $config) {
$this->container->prependExtensionConfig($namespace, $config);
}
}
$this->extensionConfigs = [];
}
/**
* Registers a definition in the container with its instanceof-conditionals.
*/
protected function setDefinition(string $id, Definition $definition): void
{
$this->container->removeBindings($id);
foreach ($definition->getTag('container.error') as $error) {
if (isset($error['message'])) {
$definition->addError($error['message']);
}
}
if ($this->isLoadingInstanceof) {
if (!$definition instanceof ChildDefinition) {
throw new InvalidArgumentException(\sprintf('Invalid type definition "%s": ChildDefinition expected, "%s" given.', $id, get_debug_type($definition)));
}
$this->instanceof[$id] = $definition;
} else {
$this->container->setDefinition($id, $definition->setInstanceofConditionals($this->instanceof));
}
}
private function findClasses(string $namespace, string $pattern, array $excludePatterns, ?string $source): array
{
$parameterBag = $this->container->getParameterBag();
$excludePaths = [];
$excludePrefix = null;
$excludePatterns = $parameterBag->unescapeValue($parameterBag->resolveValue($excludePatterns));
foreach ($excludePatterns as $excludePattern) {
foreach ($this->glob($excludePattern, true, $resource, true, true) as $path => $info) {
$excludePrefix ??= $resource->getPrefix();
// normalize Windows slashes and remove trailing slashes
$excludePaths[rtrim(str_replace('\\', '/', $path), '/')] = true;
}
}
$pattern = $parameterBag->unescapeValue($parameterBag->resolveValue($pattern));
$classes = [];
$prefixLen = null;
foreach ($this->glob($pattern, true, $resource, false, false, $excludePaths) as $path => $info) {
if (null === $prefixLen) {
$prefixLen = \strlen($resource->getPrefix());
if ($excludePrefix && !str_starts_with($excludePrefix, $resource->getPrefix())) {
throw new InvalidArgumentException(\sprintf('Invalid "exclude" pattern when importing classes for "%s": make sure your "exclude" pattern (%s) is a subset of the "resource" pattern (%s).', $namespace, $excludePattern, $pattern));
}
}
if (isset($excludePaths[str_replace('\\', '/', $path)])) {
continue;
}
if (!str_ends_with($path, '.php')) {
continue;
}
$class = $namespace.ltrim(str_replace('/', '\\', substr($path, $prefixLen, -4)), '\\');
if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+$/', $class)) {
continue;
}
try {
$r = $this->container->getReflectionClass($class);
} catch (\ReflectionException $e) {
$classes[$class] = $e->getMessage();
continue;
}
// check to make sure the expected class exists
if (!$r) {
throw new InvalidArgumentException(\sprintf('Expected to find class "%s" in file "%s" while importing services from resource "%s", but it was not found! Check the namespace prefix used with the resource.', $class, $path, $pattern));
}
if (!$r->isTrait()) {
$classes[$class] = null;
}
}
// track only for new & removed files
if ($resource instanceof GlobResource) {
$this->container->addResource($resource);
} else {
foreach ($resource as $path) {
$this->container->fileExists($path, false);
}
}
if (null !== $prefixLen) {
foreach ($excludePaths as $path => $_) {
$class = $namespace.ltrim(str_replace('/', '\\', substr($path, $prefixLen, str_ends_with($path, '.php') ? -4 : null)), '\\');
$this->addContainerExcludedTag($class, $source);
}
}
return $classes;
}
private function addContainerExcludedTag(string $class, ?string $source): void
{
if ($this->container->has($class)) {
return;
}
static $attributes = [];
if (null !== $source && !isset($attributes[$source])) {
$attributes[$source] = ['source' => \sprintf('in "%s/%s"', basename(\dirname($source)), basename($source))];
}
$this->container->register($class, $class)
->setAbstract(true)
->addTag('container.excluded', null !== $source ? $attributes[$source] : []);
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader;
/**
* GlobFileLoader loads files from a glob pattern.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class GlobFileLoader extends FileLoader
{
public function load(mixed $resource, ?string $type = null): mixed
{
foreach ($this->glob($resource, false, $globResource) as $path => $info) {
$this->import($path);
}
$this->container->addResource($globResource);
return null;
}
public function supports(mixed $resource, ?string $type = null): bool
{
return 'glob' === $type;
}
}

View File

@@ -0,0 +1,98 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
/**
* IniFileLoader loads parameters from INI files.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class IniFileLoader extends FileLoader
{
public function load(mixed $resource, ?string $type = null): mixed
{
$path = $this->locator->locate($resource);
$this->container->fileExists($path);
// first pass to catch parsing errors
$result = parse_ini_file($path, true);
if (false === $result || [] === $result) {
throw new InvalidArgumentException(\sprintf('The "%s" file is not valid.', $resource));
}
// real raw parsing
$result = parse_ini_file($path, true, \INI_SCANNER_RAW);
if (isset($result['parameters']) && \is_array($result['parameters'])) {
foreach ($result['parameters'] as $key => $value) {
if (\is_array($value)) {
$this->container->setParameter($key, array_map($this->phpize(...), $value));
} else {
$this->container->setParameter($key, $this->phpize($value));
}
}
}
if ($this->env && \is_array($result['parameters@'.$this->env] ?? null)) {
foreach ($result['parameters@'.$this->env] as $key => $value) {
$this->container->setParameter($key, $this->phpize($value));
}
}
return null;
}
public function supports(mixed $resource, ?string $type = null): bool
{
if (!\is_string($resource)) {
return false;
}
if (null === $type && 'ini' === pathinfo($resource, \PATHINFO_EXTENSION)) {
return true;
}
return 'ini' === $type;
}
/**
* Note that the following features are not supported:
* * strings with escaped quotes are not supported "foo\"bar";
* * string concatenation ("foo" "bar").
*/
private function phpize(string $value): mixed
{
// trim on the right as comments removal keep whitespaces
if ($value !== $v = rtrim($value)) {
$value = '""' === substr_replace($v, '', 1, -1) ? substr($v, 1, -1) : $v;
}
$lowercaseValue = strtolower($value);
return match (true) {
\defined($value) => \constant($value),
'yes' === $lowercaseValue,
'on' === $lowercaseValue => true,
'no' === $lowercaseValue,
'off' === $lowercaseValue,
'none' === $lowercaseValue => false,
isset($value[1]) && (
("'" === $value[0] && "'" === $value[\strlen($value) - 1])
|| ('"' === $value[0] && '"' === $value[\strlen($value) - 1])
) => substr($value, 1, -1), // quoted string
default => XmlUtils::phpize($value),
};
}
}

View File

@@ -0,0 +1,310 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\Config\Builder\ConfigBuilderGenerator;
use Symfony\Component\Config\Builder\ConfigBuilderGeneratorInterface;
use Symfony\Component\Config\Builder\ConfigBuilderInterface;
use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\DependencyInjection\Attribute\When;
use Symfony\Component\DependencyInjection\Attribute\WhenNot;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\Configurator\App;
use Symfony\Component\DependencyInjection\Loader\Configurator\AppReference;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
/**
* PhpFileLoader loads service definitions from a PHP file.
*
* The PHP file is required and the $container variable can be
* used within the file to change the container.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class PhpFileLoader extends FileLoader
{
protected bool $autoRegisterAliasesForSinglyImplementedInterfaces = false;
private ?ConfigBuilderGeneratorInterface $generator;
public function __construct(
ContainerBuilder $container,
FileLocatorInterface $locator,
?string $env = null,
ConfigBuilderGeneratorInterface|bool|null $generator = null,
bool $prepend = false,
) {
if (\is_bool($generator)) {
$prepend = $generator;
$generator = null;
}
$this->generator = $generator;
parent::__construct($container, $locator, $env, $prepend);
}
public function load(mixed $resource, ?string $type = null): mixed
{
// the container and loader variables are exposed to the included file below
$container = $this->container;
$loader = $this;
$path = $this->locator->locate($resource);
$this->setCurrentDir(\dirname($path));
$this->container->fileExists($path);
// Force load ContainerConfigurator to make env(), param() etc available.
class_exists(ContainerConfigurator::class);
// Expose AppReference::config() as App::config()
if (!class_exists(App::class)) {
class_alias(AppReference::class, App::class);
}
// the closure forbids access to the private scope in the included file
$load = \Closure::bind(static function ($path, $env) use ($container, $loader, $resource, $type) {
return include $path;
}, null, null);
$instanceof = $this->instanceof;
$this->instanceof = [];
try {
try {
if (1 === $result = $load($path, $this->env)) {
$result = null;
}
} catch (\Error $e) {
$load = \Closure::bind(function ($path, $env) use ($container, $loader, $resource, $type) {
return include $path;
}, $this, ProtectedPhpFileLoader::class);
if (1 === $result = $load($path, $this->env)) {
$result = null;
}
throw new LogicException(\sprintf('Using `$this` or its internal scope in config files is not supported anymore, use the `$loader` variable instead in "%s" on line %d.', $e->getFile(), $e->getLine()), $e->getCode(), $e);
}
if (\is_object($result) && \is_callable($result)) {
$this->callConfigurator($result, new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource, $this->env), $path);
} elseif (\is_array($result)) {
$yamlLoader = new YamlFileLoader($this->container, $this->locator, $this->env, $this->prepend);
$yamlLoader->setResolver($this->resolver ?? new LoaderResolver([$this]));
$loadContent = new \ReflectionMethod(YamlFileLoader::class, 'loadContent');
$result = ContainerConfigurator::processValue($result);
++$this->importing;
try {
$content = array_intersect_key($result, ['imports' => true, 'parameters' => true, 'services' => true]);
$loadContent->invoke($yamlLoader, $content, $path);
foreach ($result as $namespace => $config) {
if (\in_array($namespace, ['imports', 'parameters', 'services'], true)) {
continue;
}
if (str_starts_with($namespace, 'when@')) {
$knownEnvs = $this->container->hasParameter('.container.known_envs') ? array_flip($this->container->getParameter('.container.known_envs')) : [];
$this->container->setParameter('.container.known_envs', array_keys($knownEnvs + [substr($namespace, 5) => true]));
continue;
}
$this->loadExtensionConfig($namespace, $config);
}
// per-env configuration
if ($this->env && isset($result[$when = 'when@'.$this->env])) {
if (!\is_array($result[$when])) {
throw new InvalidArgumentException(\sprintf('The "%s" key should contain an array in "%s".', $when, $path));
}
$content = array_intersect_key($result[$when], ['imports' => true, 'parameters' => true, 'services' => true]);
$loadContent->invoke($yamlLoader, $content, $path);
foreach ($result[$when] as $namespace => $config) {
if (!\in_array($namespace, ['imports', 'parameters', 'services'], true) && !str_starts_with($namespace, 'when@')) {
$this->loadExtensionConfig($namespace, $config);
}
}
}
} finally {
--$this->importing;
}
}
$this->loadExtensionConfigs();
} finally {
$this->instanceof = $instanceof;
$this->registerAliasesForSinglyImplementedInterfaces();
}
return null;
}
public function supports(mixed $resource, ?string $type = null): bool
{
if (!\is_string($resource)) {
return false;
}
if (null === $type && 'php' === pathinfo($resource, \PATHINFO_EXTENSION)) {
return true;
}
return 'php' === $type;
}
/**
* Resolve the parameters to the $callback and execute it.
*/
private function callConfigurator(callable $callback, ContainerConfigurator $containerConfigurator, string $path): void
{
$callback = $callback(...);
$arguments = [];
$configBuilders = [];
$r = new \ReflectionFunction($callback);
$excluded = true;
$whenAttributes = $r->getAttributes(When::class, \ReflectionAttribute::IS_INSTANCEOF);
$notWhenAttributes = $r->getAttributes(WhenNot::class, \ReflectionAttribute::IS_INSTANCEOF);
if ($whenAttributes && $notWhenAttributes) {
throw new LogicException('Using both #[When] and #[WhenNot] attributes on the same target is not allowed.');
}
if (!$whenAttributes && !$notWhenAttributes) {
$excluded = false;
}
foreach ($whenAttributes as $attribute) {
if ($this->env === $attribute->newInstance()->env) {
$excluded = false;
break;
}
}
foreach ($notWhenAttributes as $attribute) {
if ($excluded = $this->env === $attribute->newInstance()->env) {
break;
}
}
if ($excluded) {
return;
}
foreach ($r->getParameters() as $parameter) {
$reflectionType = $parameter->getType();
if (!$reflectionType instanceof \ReflectionNamedType) {
throw new \InvalidArgumentException(\sprintf('Could not resolve argument "$%s" for "%s". You must typehint it (for example with "%s" or "%s").', $parameter->getName(), $path, ContainerConfigurator::class, ContainerBuilder::class));
}
$type = $reflectionType->getName();
switch ($type) {
case ContainerConfigurator::class:
$arguments[] = $containerConfigurator;
break;
case ContainerBuilder::class:
$arguments[] = $this->container;
break;
case FileLoader::class:
case self::class:
$arguments[] = $this;
break;
case 'string':
if (null !== $this->env && 'env' === $parameter->getName()) {
$arguments[] = $this->env;
break;
}
// no break
default:
try {
$configBuilder = $this->configBuilder($type);
} catch (InvalidArgumentException|\LogicException $e) {
throw new \InvalidArgumentException(\sprintf('Could not resolve argument "%s" for "%s".', $type.' $'.$parameter->getName(), $path), 0, $e);
}
trigger_deprecation('symfony/dependency-injection', '7.4', 'Using fluent builders for semantic configuration is deprecated, instantiate the "%s" class with the config array as argument and return it instead in "%s".', $type, $path);
$configBuilders[] = $configBuilder;
$arguments[] = $configBuilder;
}
}
++$this->importing;
try {
$callback(...$arguments);
} finally {
--$this->importing;
}
foreach ($configBuilders as $configBuilder) {
$this->loadExtensionConfig($configBuilder->getExtensionAlias(), ContainerConfigurator::processValue($configBuilder->toArray()));
}
}
/**
* @param string $namespace FQCN string for a class implementing ConfigBuilderInterface
*/
private function configBuilder(string $namespace): ConfigBuilderInterface
{
if (!class_exists(ConfigBuilderGenerator::class)) {
throw new \LogicException('You cannot use the config builder as the Config component is not installed. Try running "composer require symfony/config".');
}
if (null === $this->generator) {
throw new \LogicException('You cannot use the ConfigBuilders without providing a class implementing ConfigBuilderGeneratorInterface.');
}
// If class exists and implements ConfigBuilderInterface
if (class_exists($namespace) && is_subclass_of($namespace, ConfigBuilderInterface::class)) {
return new $namespace();
}
// If it does not start with Symfony\Config\ we don't know how to handle this
if (!str_starts_with($namespace, 'Symfony\\Config\\')) {
throw new InvalidArgumentException(\sprintf('Could not find or generate class "%s".', $namespace));
}
// Try to get the extension alias
$alias = Container::underscore(substr($namespace, 15, -6));
if (str_contains($alias, '\\')) {
throw new InvalidArgumentException('You can only use "root" ConfigBuilders from "Symfony\\Config\\" namespace. Nested classes like "Symfony\\Config\\Framework\\CacheConfig" cannot be used.');
}
if (!$this->container->hasExtension($alias)) {
$extensions = array_filter(array_map(fn (ExtensionInterface $ext) => $ext->getAlias(), $this->container->getExtensions()));
throw new InvalidArgumentException(UndefinedExtensionHandler::getErrorMessage($namespace, null, $alias, $extensions));
}
$extension = $this->container->getExtension($alias);
if (!$extension instanceof ConfigurationExtensionInterface) {
throw new \LogicException(\sprintf('You cannot use the config builder for "%s" because the extension does not implement "%s".', $namespace, ConfigurationExtensionInterface::class));
}
$configuration = $extension->getConfiguration([], $this->container);
$loader = $this->generator->build($configuration);
return $loader();
}
}
/**
* @internal
*/
final class ProtectedPhpFileLoader extends PhpFileLoader
{
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader;
class UndefinedExtensionHandler
{
private const BUNDLE_EXTENSIONS = [
'debug' => 'DebugBundle',
'doctrine' => 'DoctrineBundle',
'doctrine_migrations' => 'DoctrineMigrationsBundle',
'framework' => 'FrameworkBundle',
'maker' => 'MakerBundle',
'monolog' => 'MonologBundle',
'security' => 'SecurityBundle',
'twig' => 'TwigBundle',
'twig_component' => 'TwigComponentBundle',
'ux_icons' => 'UXIconsBundle',
'web_profiler' => 'WebProfilerBundle',
];
public static function getErrorMessage(string $extensionName, ?string $loadingFilePath, string $namespaceOrAlias, array $foundExtensionNamespaces): string
{
$message = '';
if (isset(self::BUNDLE_EXTENSIONS[$extensionName])) {
$message .= \sprintf('Did you forget to install or enable the %s? ', self::BUNDLE_EXTENSIONS[$extensionName]);
}
$message .= match (true) {
\is_string($loadingFilePath) => \sprintf('There is no extension able to load the configuration for "%s" (in "%s"). ', $extensionName, $loadingFilePath),
default => \sprintf('There is no extension able to load the configuration for "%s". ', $extensionName),
};
return $message.\sprintf('Looked for namespace "%s", found "%s".', $namespaceOrAlias, $foundExtensionNamespaces ? implode('", "', $foundExtensionNamespaces) : 'none');
}
}

View File

@@ -0,0 +1,913 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
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\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\ExpressionLanguage\Expression;
/**
* XmlFileLoader loads XML files service definitions.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated since Symfony 7.4, use another loader instead
*/
class XmlFileLoader extends FileLoader
{
public const NS = 'http://symfony.com/schema/dic/services';
protected bool $autoRegisterAliasesForSinglyImplementedInterfaces = false;
public function load(mixed $resource, ?string $type = null): mixed
{
trigger_deprecation('symfony/dependency-injection', '7.4', 'XML configuration format is deprecated, use YAML or PHP instead.');
$path = $this->locator->locate($resource);
$xml = $this->parseFileToDOM($path);
$this->container->fileExists($path);
$this->loadXml($xml, $path);
$xpath = new \DOMXPath($xml);
$xpath->registerNamespace('container', self::NS);
if ($this->env) {
foreach ($xpath->query(\sprintf('//container:when[@env="%s"]', $this->env)) ?: [] as $root) {
$this->loadXml($xml, $path, $root);
}
}
foreach ($xpath->query('//container:when') ?: [] as $root) {
$knownEnvs = $this->container->hasParameter('.container.known_envs') ? array_flip($this->container->getParameter('.container.known_envs')) : [];
$this->container->setParameter('.container.known_envs', array_keys($knownEnvs + [$root->getAttribute('env') => true]));
}
return null;
}
private function loadXml(\DOMDocument $xml, string $path, ?\DOMNode $root = null): void
{
$defaults = $this->getServiceDefaults($xml, $path, $root);
// anonymous services
$this->processAnonymousServices($xml, $path, $root);
// imports
$this->parseImports($xml, $path, $root);
// parameters
$this->parseParameters($xml, $path, $root);
// extensions
$this->loadFromExtensions($xml, $root);
// services
try {
$this->parseDefinitions($xml, $path, $defaults, $root);
} finally {
$this->instanceof = [];
$this->registerAliasesForSinglyImplementedInterfaces();
}
}
public function supports(mixed $resource, ?string $type = null): bool
{
if (!\is_string($resource)) {
return false;
}
if (null === $type && 'xml' === pathinfo($resource, \PATHINFO_EXTENSION)) {
return true;
}
return 'xml' === $type;
}
private function parseParameters(\DOMDocument $xml, string $file, ?\DOMNode $root = null): void
{
if ($parameters = $this->getChildren($root ?? $xml->documentElement, 'parameters')) {
$this->container->getParameterBag()->add($this->getArgumentsAsPhp($parameters[0], 'parameter', $file));
}
}
private function parseImports(\DOMDocument $xml, string $file, ?\DOMNode $root = null): void
{
$xpath = new \DOMXPath($xml);
$xpath->registerNamespace('container', self::NS);
if (false === $imports = $xpath->query('./container:imports/container:import', $root)) {
return;
}
$defaultDirectory = \dirname($file);
foreach ($imports as $import) {
$this->setCurrentDir($defaultDirectory);
$this->import($import->getAttribute('resource'), XmlUtils::phpize($import->getAttribute('type')) ?: null, XmlUtils::phpize($import->getAttribute('ignore-errors')) ?: false, $file);
}
}
private function parseDefinitions(\DOMDocument $xml, string $file, Definition $defaults, ?\DOMNode $root = null): void
{
$xpath = new \DOMXPath($xml);
$xpath->registerNamespace('container', self::NS);
if (false === $services = $xpath->query('./container:services/container:service|./container:services/container:prototype|./container:services/container:stack', $root)) {
return;
}
$this->setCurrentDir(\dirname($file));
$this->instanceof = [];
$this->isLoadingInstanceof = true;
$instanceof = $xpath->query('./container:services/container:instanceof', $root);
foreach ($instanceof as $service) {
$this->setDefinition((string) $service->getAttribute('id'), $this->parseDefinition($service, $file, new Definition()));
}
$this->isLoadingInstanceof = false;
foreach ($services as $service) {
if ('stack' === $service->tagName) {
$service->setAttribute('parent', '-');
$definition = $this->parseDefinition($service, $file, $defaults)
->setTags(array_merge_recursive(['container.stack' => [[]]], $defaults->getTags()))
;
$this->setDefinition($id = (string) $service->getAttribute('id'), $definition);
$stack = [];
foreach ($this->getChildren($service, 'service') as $k => $frame) {
$k = $frame->getAttribute('id') ?: $k;
$frame->setAttribute('id', $id.'" at index "'.$k);
if ($alias = $frame->getAttribute('alias')) {
$this->validateAlias($frame, $file);
$stack[$k] = new Reference($alias);
} else {
$stack[$k] = $this->parseDefinition($frame, $file, $defaults)
->setInstanceofConditionals($this->instanceof);
}
}
$definition->setArguments($stack);
} elseif (null !== $definition = $this->parseDefinition($service, $file, $defaults)) {
if ('prototype' === $service->tagName) {
$excludes = array_column($this->getChildren($service, 'exclude'), 'nodeValue');
if ($service->hasAttribute('exclude')) {
if (\count($excludes) > 0) {
throw new InvalidArgumentException('You cannot use both the attribute "exclude" and <exclude> tags at the same time.');
}
$excludes = [$service->getAttribute('exclude')];
}
$this->registerClasses($definition, (string) $service->getAttribute('namespace'), (string) $service->getAttribute('resource'), $excludes, $file);
} else {
$this->setDefinition((string) $service->getAttribute('id'), $definition);
}
}
}
}
private function getServiceDefaults(\DOMDocument $xml, string $file, ?\DOMNode $root = null): Definition
{
$xpath = new \DOMXPath($xml);
$xpath->registerNamespace('container', self::NS);
if (null === $defaultsNode = $xpath->query('./container:services/container:defaults', $root)->item(0)) {
return new Definition();
}
$defaultsNode->setAttribute('id', '<defaults>');
return $this->parseDefinition($defaultsNode, $file, new Definition());
}
/**
* Parses an individual Definition.
*/
private function parseDefinition(\DOMElement $service, string $file, Definition $defaults): ?Definition
{
if ($alias = $service->getAttribute('alias')) {
$this->validateAlias($service, $file);
$this->container->setAlias($service->getAttribute('id'), $alias = new Alias($alias));
if ($publicAttr = $service->getAttribute('public')) {
$alias->setPublic(XmlUtils::phpize($publicAttr));
} elseif ($defaults->getChanges()['public'] ?? false) {
$alias->setPublic($defaults->isPublic());
}
if ($deprecated = $this->getChildren($service, 'deprecated')) {
$message = $deprecated[0]->nodeValue ?: '';
$package = $deprecated[0]->getAttribute('package') ?: '';
$version = $deprecated[0]->getAttribute('version') ?: '';
if (!$deprecated[0]->hasAttribute('package')) {
throw new InvalidArgumentException(\sprintf('Missing attribute "package" at node "deprecated" in "%s".', $file));
}
if (!$deprecated[0]->hasAttribute('version')) {
throw new InvalidArgumentException(\sprintf('Missing attribute "version" at node "deprecated" in "%s".', $file));
}
$alias->setDeprecated($package, $version, $message);
}
return null;
}
if ($this->isLoadingInstanceof) {
$definition = new ChildDefinition('');
} elseif ($parent = $service->getAttribute('parent')) {
$definition = new ChildDefinition($parent);
} else {
$definition = new Definition();
}
if ($defaults->getChanges()['public'] ?? false) {
$definition->setPublic($defaults->isPublic());
}
$definition->setAutowired($defaults->isAutowired());
$definition->setAutoconfigured($defaults->isAutoconfigured());
$definition->setChanges([]);
foreach (['class', 'public', 'shared', 'synthetic', 'abstract'] as $key) {
if ($value = $service->getAttribute($key)) {
$method = 'set'.$key;
$definition->$method(XmlUtils::phpize($value));
}
}
if ($value = $service->getAttribute('lazy')) {
$definition->setLazy((bool) $value = XmlUtils::phpize($value));
if (\is_string($value)) {
$definition->addTag('proxy', ['interface' => $value]);
}
}
if ($value = $service->getAttribute('autowire')) {
$definition->setAutowired(XmlUtils::phpize($value));
}
if ($value = $service->getAttribute('autoconfigure')) {
$definition->setAutoconfigured(XmlUtils::phpize($value));
}
if ($files = $this->getChildren($service, 'file')) {
$definition->setFile($files[0]->nodeValue);
}
if ($deprecated = $this->getChildren($service, 'deprecated')) {
$message = $deprecated[0]->nodeValue ?: '';
$package = $deprecated[0]->getAttribute('package') ?: '';
$version = $deprecated[0]->getAttribute('version') ?: '';
if (!$deprecated[0]->hasAttribute('package')) {
throw new InvalidArgumentException(\sprintf('Missing attribute "package" at node "deprecated" in "%s".', $file));
}
if (!$deprecated[0]->hasAttribute('version')) {
throw new InvalidArgumentException(\sprintf('Missing attribute "version" at node "deprecated" in "%s".', $file));
}
$definition->setDeprecated($package, $version, $message);
}
$definition->setArguments($this->getArgumentsAsPhp($service, 'argument', $file, $definition instanceof ChildDefinition));
$definition->setProperties($this->getArgumentsAsPhp($service, 'property', $file));
if ($factories = $this->getChildren($service, 'factory')) {
$factory = $factories[0];
if ($function = $factory->getAttribute('function')) {
$definition->setFactory($function);
} elseif ($expression = $factory->getAttribute('expression')) {
if (!class_exists(Expression::class)) {
throw new \LogicException('The "expression" attribute cannot be used on factories without the ExpressionLanguage component. Try running "composer require symfony/expression-language".');
}
$definition->setFactory('@='.$expression);
} else {
if ($childService = $factory->getAttribute('service')) {
$class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
} else {
$class = $factory->hasAttribute('class') ? $factory->getAttribute('class') : null;
}
$definition->setFactory([$class, $factory->getAttribute('method') ?: '__invoke']);
}
}
if ($constructor = $service->getAttribute('constructor')) {
if (null !== $definition->getFactory()) {
throw new LogicException(\sprintf('The "%s" service cannot declare a factory as well as a constructor.', $service->getAttribute('id')));
}
$definition->setFactory([null, $constructor]);
}
if ($configurators = $this->getChildren($service, 'configurator')) {
$configurator = $configurators[0];
if ($function = $configurator->getAttribute('function')) {
$definition->setConfigurator($function);
} else {
if ($childService = $configurator->getAttribute('service')) {
$class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
} else {
$class = $configurator->getAttribute('class');
}
$definition->setConfigurator([$class, $configurator->getAttribute('method') ?: '__invoke']);
}
}
foreach ($this->getChildren($service, 'call') as $call) {
$definition->addMethodCall(
$call->getAttribute('method'),
$this->getArgumentsAsPhp($call, 'argument', $file),
XmlUtils::phpize($call->getAttribute('returns-clone')) ?: false
);
}
foreach (['tag', 'resource-tag'] as $type) {
foreach ($this->getChildren($service, $type) as $tag) {
$tagNameComesFromAttribute = $tag->childElementCount || '' === $tag->nodeValue;
if ('' === $tagName = $tagNameComesFromAttribute ? $tag->getAttribute('name') : $tag->nodeValue) {
throw new InvalidArgumentException(\sprintf('The tag name for service "%s" in "%s" must be a non-empty string.', $service->getAttribute('id'), $file));
}
$parameters = $this->getTagAttributes($tag, \sprintf('The attribute name of tag "%s" for service "%s" in %s must be a non-empty string.', $tagName, $service->getAttribute('id'), $file));
foreach ($tag->attributes as $name => $node) {
if ($tagNameComesFromAttribute && 'name' === $name) {
continue;
}
if (str_contains($name, '-') && !str_contains($name, '_') && !\array_key_exists($normalizedName = str_replace('-', '_', $name), $parameters)) {
$parameters[$normalizedName] = XmlUtils::phpize($node->nodeValue);
}
// keep not normalized key
$parameters[$name] = XmlUtils::phpize($node->nodeValue);
}
match ($type) {
'tag' => $definition->addTag($tagName, $parameters),
'resource-tag' => $definition->addResourceTag($tagName, $parameters),
};
}
}
$definition->setTags(array_merge_recursive($definition->getTags(), $defaults->getTags()));
$bindings = $this->getArgumentsAsPhp($service, 'bind', $file);
$bindingType = $this->isLoadingInstanceof ? BoundArgument::INSTANCEOF_BINDING : BoundArgument::SERVICE_BINDING;
foreach ($bindings as $argument => $value) {
$bindings[$argument] = new BoundArgument($value, true, $bindingType, $file);
}
// deep clone, to avoid multiple process of the same instance in the passes
$bindings = array_merge(unserialize(serialize($defaults->getBindings())), $bindings);
if ($bindings) {
$definition->setBindings($bindings);
}
if ($decorates = $service->getAttribute('decorates')) {
$decorationOnInvalid = $service->getAttribute('decoration-on-invalid') ?: 'exception';
if ('exception' === $decorationOnInvalid) {
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
} elseif ('ignore' === $decorationOnInvalid) {
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
} elseif ('null' === $decorationOnInvalid) {
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
} else {
throw new InvalidArgumentException(\sprintf('Invalid value "%s" for attribute "decoration-on-invalid" on service "%s". Did you mean "exception", "ignore" or "null" in "%s"?', $decorationOnInvalid, $service->getAttribute('id'), $file));
}
$renameId = $service->hasAttribute('decoration-inner-name') ? $service->getAttribute('decoration-inner-name') : null;
$priority = $service->hasAttribute('decoration-priority') ? $service->getAttribute('decoration-priority') : 0;
$definition->setDecoratedService($decorates, $renameId, $priority, $invalidBehavior);
}
if ($callable = $this->getChildren($service, 'from-callable')) {
if ($definition instanceof ChildDefinition) {
throw new InvalidArgumentException(\sprintf('Attribute "parent" is unsupported when using "<from-callable>" on service "%s".', $service->getAttribute('id')));
}
foreach ([
'Attribute "synthetic"' => 'isSynthetic',
'Attribute "file"' => 'getFile',
'Tag "<factory>"' => 'getFactory',
'Tag "<argument>"' => 'getArguments',
'Tag "<property>"' => 'getProperties',
'Tag "<configurator>"' => 'getConfigurator',
'Tag "<call>"' => 'getMethodCalls',
] as $key => $method) {
if ($definition->$method()) {
throw new InvalidArgumentException($key.\sprintf(' is unsupported when using "<from-callable>" on service "%s".', $service->getAttribute('id')));
}
}
$definition->setFactory(['Closure', 'fromCallable']);
if ('Closure' !== ($definition->getClass() ?? 'Closure')) {
$definition->setLazy(true);
} else {
$definition->setClass('Closure');
}
$callable = $callable[0];
if ($function = $callable->getAttribute('function')) {
$definition->setArguments([$function]);
} elseif ($expression = $callable->getAttribute('expression')) {
if (!class_exists(Expression::class)) {
throw new \LogicException('The "expression" attribute cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".');
}
$definition->setArguments(['@='.$expression]);
} else {
if ($childService = $callable->getAttribute('service')) {
$class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
} else {
$class = $callable->hasAttribute('class') ? $callable->getAttribute('class') : null;
}
$definition->setArguments([[$class, $callable->getAttribute('method') ?: '__invoke']]);
}
}
return $definition;
}
/**
* Parses an XML file to a \DOMDocument.
*
* @throws InvalidArgumentException When loading of XML file returns error
*/
private function parseFileToDOM(string $file): \DOMDocument
{
try {
$dom = XmlUtils::loadFile($file, $this->validateSchema(...));
} catch (\InvalidArgumentException $e) {
$invalidSecurityElements = [];
$errors = explode("\n", $e->getMessage());
foreach ($errors as $i => $error) {
if (preg_match("#^\[ERROR 1871] Element '\{http://symfony\.com/schema/dic/security}([^']+)'#", $error, $matches)) {
$invalidSecurityElements[$i] = $matches[1];
}
}
if ($invalidSecurityElements) {
$dom = XmlUtils::loadFile($file);
foreach ($invalidSecurityElements as $errorIndex => $tagName) {
foreach ($dom->getElementsByTagNameNS('http://symfony.com/schema/dic/security', $tagName) as $element) {
if (!$parent = $element->parentNode) {
continue;
}
if ('http://symfony.com/schema/dic/security' !== $parent->namespaceURI) {
continue;
}
if ('provider' === $parent->localName || 'firewall' === $parent->localName) {
unset($errors[$errorIndex]);
}
}
}
}
if ($errors) {
throw new InvalidArgumentException(\sprintf('Unable to parse file "%s": ', $file).implode("\n", $errors), $e->getCode(), $e);
}
}
$this->validateExtensions($dom, $file);
return $dom;
}
/**
* Processes anonymous services.
*/
private function processAnonymousServices(\DOMDocument $xml, string $file, ?\DOMNode $root = null): void
{
$definitions = [];
$count = 0;
$suffix = '~'.ContainerBuilder::hash($file);
$xpath = new \DOMXPath($xml);
$xpath->registerNamespace('container', self::NS);
// anonymous services as arguments/properties
if (false !== $nodes = $xpath->query('.//container:argument[@type="service"][not(@id)]|.//container:property[@type="service"][not(@id)]|.//container:bind[not(@id)]|.//container:factory[not(@service)]|.//container:configurator[not(@service)]', $root)) {
foreach ($nodes as $node) {
if ($services = $this->getChildren($node, 'service')) {
// give it a unique name
$id = \sprintf('.%d_%s', ++$count, preg_replace('/^.*\\\\/', '', $services[0]->getAttribute('class')).$suffix);
$node->setAttribute('id', $id);
$node->setAttribute('service', $id);
$definitions[$id] = [$services[0], $file];
$services[0]->setAttribute('id', $id);
// anonymous services are always private
// we could not use the constant false here, because of XML parsing
$services[0]->setAttribute('public', 'false');
}
}
}
// anonymous services "in the wild"
if (false !== $nodes = $xpath->query('.//container:services/container:service[not(@id)]', $root)) {
foreach ($nodes as $node) {
throw new InvalidArgumentException(\sprintf('Top-level services must have "id" attribute, none found in "%s" at line %d.', $file, $node->getLineNo()));
}
}
// resolve definitions
uksort($definitions, 'strnatcmp');
foreach (array_reverse($definitions) as $id => [$domElement, $file]) {
if (null !== $definition = $this->parseDefinition($domElement, $file, new Definition())) {
$this->setDefinition($id, $definition);
}
}
}
private function getArgumentsAsPhp(\DOMElement $node, string $name, string $file, bool $isChildDefinition = false): array
{
$arguments = [];
foreach ($this->getChildren($node, $name) as $arg) {
if ($arg->hasAttribute('name')) {
$arg->setAttribute('key', $arg->getAttribute('name'));
}
// this is used by ChildDefinition to overwrite a specific
// argument of the parent definition
if ($arg->hasAttribute('index')) {
$key = ($isChildDefinition ? 'index_' : '').$arg->getAttribute('index');
} elseif (!$arg->hasAttribute('key')) {
// Append an empty argument, then fetch its key to overwrite it later
$arguments[] = null;
$keys = array_keys($arguments);
$key = array_pop($keys);
} else {
$key = $arg->getAttribute('key');
}
switch ($arg->getAttribute('key-type')) {
case 'binary':
if (false === $key = base64_decode($key, true)) {
throw new InvalidArgumentException(\sprintf('Tag "<%s>" with key-type="binary" does not have a valid base64 encoded key in "%s".', $name, $file));
}
break;
case 'constant':
try {
$key = \constant(trim($key));
} catch (\Error) {
throw new InvalidArgumentException(\sprintf('The key "%s" is not a valid constant in "%s".', $key, $file));
}
break;
}
$trim = $arg->hasAttribute('trim') && XmlUtils::phpize($arg->getAttribute('trim'));
$onInvalid = $arg->getAttribute('on-invalid');
$invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
if ('ignore' == $onInvalid) {
$invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
} elseif ('ignore_uninitialized' == $onInvalid) {
$invalidBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE;
} elseif ('null' == $onInvalid) {
$invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE;
}
switch ($type = $arg->getAttribute('type')) {
case 'service':
if ('' === $arg->getAttribute('id')) {
throw new InvalidArgumentException(\sprintf('Tag "<%s>" with type="service" has no or empty "id" attribute in "%s".', $name, $file));
}
$arguments[$key] = new Reference($arg->getAttribute('id'), $invalidBehavior);
break;
case 'expression':
if (!class_exists(Expression::class)) {
throw new \LogicException('The type="expression" attribute cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".');
}
$arguments[$key] = new Expression($arg->nodeValue);
break;
case 'collection':
$arguments[$key] = $this->getArgumentsAsPhp($arg, $name, $file);
break;
case 'iterator':
$arg = $this->getArgumentsAsPhp($arg, $name, $file);
$arguments[$key] = new IteratorArgument($arg);
break;
case 'closure':
case 'service_closure':
if ('' !== $arg->getAttribute('id')) {
$arg = new Reference($arg->getAttribute('id'), $invalidBehavior);
} else {
$arg = $this->getArgumentsAsPhp($arg, $name, $file);
}
$arguments[$key] = match ($type) {
'service_closure' => new ServiceClosureArgument($arg),
'closure' => (new Definition('Closure'))
->setFactory(['Closure', 'fromCallable'])
->addArgument($arg),
};
break;
case 'service_locator':
$arg = $this->getArgumentsAsPhp($arg, $name, $file);
$arguments[$key] = new ServiceLocatorArgument($arg);
break;
case 'tagged':
trigger_deprecation('symfony/dependency-injection', '7.2', 'Type "tagged" is deprecated for tag <%s>, use "tagged_iterator" instead in "%s".', $name, $file);
// no break
case 'tagged_iterator':
case 'tagged_locator':
$forLocator = 'tagged_locator' === $type;
if (!$arg->getAttribute('tag')) {
throw new InvalidArgumentException(\sprintf('Tag "<%s>" with type="%s" has no or empty "tag" attribute in "%s".', $name, $type, $file));
}
$excludes = array_column($this->getChildren($arg, 'exclude'), 'nodeValue');
if ($arg->hasAttribute('exclude')) {
if (\count($excludes) > 0) {
throw new InvalidArgumentException('You cannot use both the attribute "exclude" and <exclude> tags at the same time.');
}
$excludes = [$arg->getAttribute('exclude')];
}
$arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by') ?: null, $arg->getAttribute('default-index-method') ?: null, $forLocator, $arg->getAttribute('default-priority-method') ?: null, $excludes, !$arg->hasAttribute('exclude-self') || XmlUtils::phpize($arg->getAttribute('exclude-self')));
if ($forLocator) {
$arguments[$key] = new ServiceLocatorArgument($arguments[$key]);
}
break;
case 'binary':
if (false === $value = base64_decode($arg->nodeValue)) {
throw new InvalidArgumentException(\sprintf('Tag "<%s>" with type="binary" is not a valid base64 encoded string.', $name));
}
$arguments[$key] = $value;
break;
case 'abstract':
$arguments[$key] = new AbstractArgument($arg->nodeValue);
break;
case 'string':
$arguments[$key] = $trim ? trim($arg->nodeValue) : $arg->nodeValue;
break;
case 'constant':
$arguments[$key] = \constant(trim($arg->nodeValue));
break;
default:
$arguments[$key] = XmlUtils::phpize($trim ? trim($arg->nodeValue) : $arg->nodeValue);
}
}
return $arguments;
}
/**
* Get child elements by name.
*
* @return \DOMElement[]
*/
private function getChildren(\DOMNode $node, string $name): array
{
$children = [];
foreach ($node->childNodes as $child) {
if ($child instanceof \DOMElement && $child->localName === $name && self::NS === $child->namespaceURI) {
$children[] = $child;
}
}
return $children;
}
private function getTagAttributes(\DOMNode $node, string $missingName): array
{
$parameters = [];
$children = $this->getChildren($node, 'attribute');
foreach ($children as $childNode) {
if ('' === $name = $childNode->getAttribute('name')) {
throw new InvalidArgumentException($missingName);
}
if ($this->getChildren($childNode, 'attribute')) {
$parameters[$name] = $this->getTagAttributes($childNode, $missingName);
} else {
if (str_contains($name, '-') && !str_contains($name, '_') && !\array_key_exists($normalizedName = str_replace('-', '_', $name), $parameters)) {
$parameters[$normalizedName] = XmlUtils::phpize($childNode->nodeValue);
}
// keep not normalized key
$parameters[$name] = XmlUtils::phpize($childNode->nodeValue);
}
}
return $parameters;
}
/**
* Validates a documents XML schema.
*
* @throws RuntimeException When extension references a non-existent XSD file
*/
public function validateSchema(\DOMDocument $dom): bool
{
$schemaLocations = ['http://symfony.com/schema/dic/services' => str_replace('\\', '/', __DIR__.'/schema/dic/services/services-1.0.xsd')];
if ($element = $dom->documentElement->getAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'schemaLocation')) {
$items = preg_split('/\s+/', $element);
for ($i = 0, $nb = \count($items); $i < $nb; $i += 2) {
if (!$this->container->hasExtension($items[$i])) {
continue;
}
if (($extension = $this->container->getExtension($items[$i])) && false !== $extension->getXsdValidationBasePath()) {
$ns = $extension->getNamespace();
$path = str_replace([$ns, str_replace('http://', 'https://', $ns)], str_replace('\\', '/', $extension->getXsdValidationBasePath()).'/', $items[$i + 1]);
if (!is_file($path)) {
throw new RuntimeException(\sprintf('Extension "%s" references a non-existent XSD file "%s".', get_debug_type($extension), $path));
}
$schemaLocations[$items[$i]] = $path;
}
}
}
$tmpfiles = [];
$imports = '';
foreach ($schemaLocations as $namespace => $location) {
$parts = explode('/', $location);
$locationstart = 'file:///';
if (0 === stripos($location, 'phar://')) {
$tmpfile = tempnam(sys_get_temp_dir(), 'symfony');
if ($tmpfile) {
copy($location, $tmpfile);
$tmpfiles[] = $tmpfile;
$parts = explode('/', str_replace('\\', '/', $tmpfile));
} else {
array_shift($parts);
$locationstart = 'phar:///';
}
} elseif ('\\' === \DIRECTORY_SEPARATOR && str_starts_with($location, '\\\\')) {
$locationstart = '';
}
$drive = '\\' === \DIRECTORY_SEPARATOR ? array_shift($parts).'/' : '';
$location = $locationstart.$drive.implode('/', array_map('rawurlencode', $parts));
$imports .= \sprintf(' <xsd:import namespace="%s" schemaLocation="%s" />'."\n", $namespace, $location);
}
$source = <<<EOF
<?xml version="1.0" encoding="utf-8" ?>
<xsd:schema xmlns="http://symfony.com/schema"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://symfony.com/schema"
elementFormDefault="qualified">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
$imports
</xsd:schema>
EOF
;
if ($this->shouldEnableEntityLoader()) {
$disableEntities = libxml_disable_entity_loader(false);
$valid = @$dom->schemaValidateSource($source);
libxml_disable_entity_loader($disableEntities);
} else {
$valid = @$dom->schemaValidateSource($source);
}
foreach ($tmpfiles as $tmpfile) {
@unlink($tmpfile);
}
return $valid;
}
private function shouldEnableEntityLoader(): bool
{
static $dom, $schema;
if (null === $dom) {
$dom = new \DOMDocument();
$dom->loadXML('<?xml version="1.0"?><test/>');
$tmpfile = tempnam(sys_get_temp_dir(), 'symfony');
register_shutdown_function(static function () use ($tmpfile) {
@unlink($tmpfile);
});
$schema = '<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:include schemaLocation="file:///'.rawurlencode(str_replace('\\', '/', $tmpfile)).'" />
</xsd:schema>';
file_put_contents($tmpfile, '<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="test" type="testType" />
<xsd:complexType name="testType"/>
</xsd:schema>');
}
return !@$dom->schemaValidateSource($schema);
}
private function validateAlias(\DOMElement $alias, string $file): void
{
foreach ($alias->attributes as $name => $node) {
if (!\in_array($name, ['alias', 'id', 'public'], true)) {
throw new InvalidArgumentException(\sprintf('Invalid attribute "%s" defined for alias "%s" in "%s".', $name, $alias->getAttribute('id'), $file));
}
}
foreach ($alias->childNodes as $child) {
if (!$child instanceof \DOMElement || self::NS !== $child->namespaceURI) {
continue;
}
if ('deprecated' !== $child->localName) {
throw new InvalidArgumentException(\sprintf('Invalid child element "%s" defined for alias "%s" in "%s".', $child->localName, $alias->getAttribute('id'), $file));
}
}
}
/**
* Validates an extension.
*
* @throws InvalidArgumentException When no extension is found corresponding to a tag
*/
private function validateExtensions(\DOMDocument $dom, string $file): void
{
foreach ($dom->documentElement->childNodes as $node) {
if (!$node instanceof \DOMElement || 'http://symfony.com/schema/dic/services' === $node->namespaceURI) {
continue;
}
// can it be handled by an extension?
if (!$this->prepend && !$this->container->hasExtension($node->namespaceURI)) {
$extensionNamespaces = array_filter(array_map(fn (ExtensionInterface $ext) => $ext->getNamespace(), $this->container->getExtensions()));
throw new InvalidArgumentException(UndefinedExtensionHandler::getErrorMessage($node->tagName, $file, $node->namespaceURI, $extensionNamespaces));
}
}
}
/**
* Loads from an extension.
*/
private function loadFromExtensions(\DOMDocument $xml): void
{
foreach ($xml->documentElement->childNodes as $node) {
if (!$node instanceof \DOMElement || self::NS === $node->namespaceURI) {
continue;
}
$values = static::convertDomElementToArray($node);
if (!\is_array($values)) {
$values = [];
}
$this->loadExtensionConfig($node->namespaceURI, $values);
}
$this->loadExtensionConfigs();
}
/**
* Converts a \DOMElement object to a PHP array.
*
* The following rules applies during the conversion:
*
* * Each tag is converted to a key value or an array
* if there is more than one "value"
*
* * The content of a tag is set under a "value" key (<foo>bar</foo>)
* if the tag also has some nested tags
*
* * The attributes are converted to keys (<foo foo="bar"/>)
*
* * The nested-tags are converted to keys (<foo><foo>bar</foo></foo>)
*
* @param \DOMElement $element A \DOMElement instance
*/
public static function convertDomElementToArray(\DOMElement $element): mixed
{
return XmlUtils::convertDomElementToArray($element, false);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,407 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://symfony.com/schema/dic/services"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://symfony.com/schema/dic/services"
elementFormDefault="qualified">
<xsd:annotation>
<xsd:documentation><![CDATA[
Symfony XML Services Schema, version 1.0
Authors: Fabien Potencier
This defines a way to describe PHP objects (services) and their
dependencies.
]]></xsd:documentation>
</xsd:annotation>
<xsd:element name="container" type="container" />
<xsd:complexType name="container">
<xsd:annotation>
<xsd:documentation><![CDATA[
The root element of a service file.
]]></xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:group ref="foreign" />
<xsd:sequence minOccurs="0">
<xsd:element name="imports" type="imports" />
<xsd:group ref="foreign" />
</xsd:sequence>
<xsd:sequence minOccurs="0">
<xsd:element name="parameters" type="parameters" />
<xsd:group ref="foreign" />
</xsd:sequence>
<xsd:sequence minOccurs="0">
<xsd:element name="services" type="services" />
<xsd:group ref="foreign" />
</xsd:sequence>
<xsd:sequence minOccurs="0" maxOccurs="unbounded">
<xsd:element name="when" type="when" />
</xsd:sequence>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="when">
<xsd:sequence>
<xsd:group ref="foreign" />
<xsd:sequence minOccurs="0">
<xsd:element name="imports" type="imports" />
<xsd:group ref="foreign" />
</xsd:sequence>
<xsd:sequence minOccurs="0">
<xsd:element name="parameters" type="parameters" />
<xsd:group ref="foreign" />
</xsd:sequence>
<xsd:sequence minOccurs="0">
<xsd:element name="services" type="services" />
<xsd:group ref="foreign" />
</xsd:sequence>
</xsd:sequence>
<xsd:attribute name="env" type="xsd:string" use="required" />
</xsd:complexType>
<xsd:group name="foreign">
<xsd:sequence>
<xsd:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:group>
<xsd:complexType name="services">
<xsd:annotation>
<xsd:documentation><![CDATA[
Enclosing element for the definition of all services
]]></xsd:documentation>
</xsd:annotation>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="service" type="service" minOccurs="1" />
<xsd:element name="prototype" type="prototype" minOccurs="0" />
<xsd:element name="defaults" type="defaults" minOccurs="0" maxOccurs="1" />
<xsd:element name="instanceof" type="instanceof" minOccurs="0" />
<xsd:element name="stack" type="stack" minOccurs="0" />
</xsd:choice>
</xsd:complexType>
<xsd:complexType name="imports">
<xsd:annotation>
<xsd:documentation><![CDATA[
Enclosing element for the import elements
]]></xsd:documentation>
</xsd:annotation>
<xsd:choice minOccurs="1" maxOccurs="unbounded">
<xsd:element name="import" type="import" />
</xsd:choice>
</xsd:complexType>
<xsd:complexType name="import">
<xsd:annotation>
<xsd:documentation><![CDATA[
Import an external resource defining other services or parameters
]]></xsd:documentation>
</xsd:annotation>
<xsd:attribute name="resource" type="xsd:string" use="required" />
<xsd:attribute name="ignore-errors" type="ignore_errors" />
<xsd:attribute name="type" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="callable">
<xsd:choice minOccurs="0" maxOccurs="1">
<xsd:element name="service" type="service" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="service" type="xsd:string" />
<xsd:attribute name="class" type="xsd:string" />
<xsd:attribute name="method" type="xsd:string" />
<xsd:attribute name="function" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="factory">
<xsd:choice minOccurs="0" maxOccurs="1">
<xsd:element name="service" type="service" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="service" type="xsd:string" />
<xsd:attribute name="class" type="xsd:string" />
<xsd:attribute name="method" type="xsd:string" />
<xsd:attribute name="function" type="xsd:string" />
<xsd:attribute name="expression" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="defaults">
<xsd:annotation>
<xsd:documentation><![CDATA[
Enclosing element for the service definitions' defaults for the current file
]]></xsd:documentation>
</xsd:annotation>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="bind" type="bind" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="public" type="boolean" />
<xsd:attribute name="autowire" type="boolean" />
<xsd:attribute name="autoconfigure" type="boolean" />
</xsd:complexType>
<xsd:complexType name="service">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="file" type="xsd:string" minOccurs="0" maxOccurs="1" />
<xsd:element name="argument" type="argument" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="configurator" type="callable" minOccurs="0" maxOccurs="1" />
<xsd:element name="factory" type="factory" minOccurs="0" maxOccurs="1" />
<xsd:element name="from-callable" type="factory" minOccurs="0" maxOccurs="1" />
<xsd:element name="deprecated" type="deprecated" minOccurs="0" maxOccurs="1" />
<xsd:element name="call" type="call" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="resource-tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="property" type="property" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="bind" type="bind" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="class" type="xsd:string" />
<xsd:attribute name="shared" type="boolean" />
<xsd:attribute name="public" type="boolean" />
<xsd:attribute name="synthetic" type="boolean" />
<xsd:attribute name="lazy" type="xsd:string" />
<xsd:attribute name="abstract" type="boolean" />
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="parent" type="xsd:string" />
<xsd:attribute name="decorates" type="xsd:string" />
<xsd:attribute name="decoration-on-invalid" type="invalid_decorated_service_sequence" />
<xsd:attribute name="decoration-inner-name" type="xsd:string" />
<xsd:attribute name="decoration-priority" type="xsd:integer" />
<xsd:attribute name="autowire" type="boolean" />
<xsd:attribute name="autoconfigure" type="boolean" />
<xsd:attribute name="constructor" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="instanceof">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="configurator" type="callable" minOccurs="0" maxOccurs="1" />
<xsd:element name="call" type="call" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="property" type="property" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="bind" type="bind" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:string" use="required" />
<xsd:attribute name="shared" type="boolean" />
<xsd:attribute name="public" type="boolean" />
<xsd:attribute name="lazy" type="xsd:string" />
<xsd:attribute name="autowire" type="boolean" />
<xsd:attribute name="autoconfigure" type="boolean" />
<xsd:attribute name="constructor" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="prototype">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="argument" type="argument" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="configurator" type="callable" minOccurs="0" maxOccurs="1" />
<xsd:element name="factory" type="factory" minOccurs="0" maxOccurs="1" />
<xsd:element name="deprecated" type="deprecated" minOccurs="0" maxOccurs="1" />
<xsd:element name="call" type="call" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="resource-tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="property" type="property" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="bind" type="bind" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="exclude" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="namespace" type="xsd:string" use="required" />
<xsd:attribute name="resource" type="xsd:string" use="required" />
<xsd:attribute name="exclude" type="xsd:string" />
<xsd:attribute name="shared" type="boolean" />
<xsd:attribute name="public" type="boolean" />
<xsd:attribute name="lazy" type="xsd:string" />
<xsd:attribute name="abstract" type="boolean" />
<xsd:attribute name="parent" type="xsd:string" />
<xsd:attribute name="autowire" type="boolean" />
<xsd:attribute name="autoconfigure" type="boolean" />
<xsd:attribute name="constructor" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="stack">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="service" type="service" minOccurs="1" />
<xsd:element name="deprecated" type="deprecated" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:attribute name="id" type="xsd:string" use="required" />
<xsd:attribute name="public" type="boolean" />
</xsd:complexType>
<xsd:complexType name="tag" mixed="true">
<xsd:choice minOccurs="0">
<xsd:element name="attribute" type="tag_attribute" maxOccurs="unbounded"/>
</xsd:choice>
<xsd:anyAttribute namespace="##any" processContents="lax" />
</xsd:complexType>
<xsd:complexType name="deprecated">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="package" type="xsd:string" use="required" />
<xsd:attribute name="version" type="xsd:string" use="required" />
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:complexType name="tag_attribute" mixed="true">
<xsd:choice minOccurs="0">
<xsd:element name="attribute" type="tag_attribute" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
<xsd:complexType name="parameters">
<xsd:choice minOccurs="1" maxOccurs="unbounded">
<xsd:element name="parameter" type="parameter" />
</xsd:choice>
<xsd:attribute name="type" type="parameter_type" />
<xsd:attribute name="key" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="parameter" mixed="true">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="parameter" type="parameter" />
</xsd:choice>
<xsd:attribute name="type" type="parameter_type" />
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="key" type="xsd:string" />
<xsd:attribute name="on-invalid" type="invalid_sequence" />
<xsd:attribute name="trim" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="property" mixed="true">
<xsd:choice minOccurs="0">
<xsd:element name="property" type="property" maxOccurs="unbounded" />
<xsd:element name="service" type="service" />
</xsd:choice>
<xsd:attribute name="key-type" type="key_type" />
<xsd:attribute name="type" type="argument_type" />
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="key" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="on-invalid" type="invalid_sequence" />
<xsd:attribute name="tag" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="bind" mixed="true">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="bind" type="bind_argument" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="service" type="service" />
</xsd:choice>
<xsd:attribute name="type" type="argument_type" />
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="key" type="xsd:string" use="required" />
<xsd:attribute name="on-invalid" type="invalid_sequence" />
<xsd:attribute name="method" type="xsd:string" />
<xsd:attribute name="tag" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="bind_argument" mixed="true">
<xsd:choice minOccurs="0">
<xsd:element name="bind" type="bind_argument" maxOccurs="unbounded" />
<xsd:element name="service" type="service" />
<xsd:element name="exclude" type="xsd:string" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="type" type="argument_type" />
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="key" type="xsd:string" />
<xsd:attribute name="index" type="xsd:integer" />
<xsd:attribute name="on-invalid" type="invalid_sequence" />
<xsd:attribute name="tag" type="xsd:string" />
<xsd:attribute name="index-by" type="xsd:string" />
<xsd:attribute name="default-index-method" type="xsd:string" />
<xsd:attribute name="default-priority-method" type="xsd:string" />
<xsd:attribute name="exclude" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="argument" mixed="true">
<xsd:choice minOccurs="0">
<xsd:element name="argument" type="argument" maxOccurs="unbounded" />
<xsd:element name="service" type="service" />
<xsd:element name="exclude" type="xsd:string" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="key-type" type="key_type" />
<xsd:attribute name="type" type="argument_type" />
<xsd:attribute name="id" type="xsd:string" />
<xsd:attribute name="key" type="xsd:string" />
<xsd:attribute name="index" type="xsd:integer" />
<xsd:attribute name="on-invalid" type="invalid_sequence" />
<xsd:attribute name="tag" type="xsd:string" />
<xsd:attribute name="index-by" type="xsd:string" />
<xsd:attribute name="default-index-method" type="xsd:string" />
<xsd:attribute name="default-priority-method" type="xsd:string" />
<xsd:attribute name="exclude" type="xsd:string" />
<xsd:attribute name="exclude-self" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="call">
<xsd:choice minOccurs="0">
<xsd:element name="argument" type="argument" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="method" type="xsd:string" />
<xsd:attribute name="returns-clone" type="boolean" />
</xsd:complexType>
<xsd:simpleType name="parameter_type">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="collection" />
<xsd:enumeration value="string" />
<xsd:enumeration value="constant" />
<xsd:enumeration value="binary" />
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="argument_type">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="abstract" />
<xsd:enumeration value="collection" />
<xsd:enumeration value="service" />
<xsd:enumeration value="expression" />
<xsd:enumeration value="string" />
<xsd:enumeration value="constant" />
<xsd:enumeration value="binary" />
<xsd:enumeration value="iterator" />
<xsd:enumeration value="closure" />
<xsd:enumeration value="service_closure" />
<xsd:enumeration value="service_locator" />
<!-- "tagged" is an alias of "tagged_iterator", using "tagged_iterator" is preferred. -->
<xsd:enumeration value="tagged" />
<xsd:enumeration value="tagged_iterator" />
<xsd:enumeration value="tagged_locator" />
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="key_type">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="binary" />
<xsd:enumeration value="constant" />
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="ignore_errors">
<xsd:restriction base="xsd:string">
<xsd:pattern value="(true|false|not_found)" />
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="invalid_sequence">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="null" />
<xsd:enumeration value="ignore" />
<xsd:enumeration value="exception" />
<xsd:enumeration value="ignore_uninitialized" />
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="invalid_decorated_service_sequence">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="null" />
<xsd:enumeration value="ignore" />
<xsd:enumeration value="exception" />
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="boolean">
<xsd:restriction base="xsd:string">
<xsd:pattern value="(%.+%|true|false)" />
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>

View File

@@ -0,0 +1,356 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Symfony Parameters and Services Configuration",
"description": "Defines application parameters and services, including imports and environment-specific conditionals.",
"$comment": "Root allows extension namespaces (framework:, doctrine:, etc.) via additionalProperties: true.",
"type": "object",
"properties": {
"imports": {
"type": "array",
"description": "Import other configuration resources.",
"$comment": "YAML examples:\n- { resource: 'services.yaml' }\n- { resource: packages/dev/services.yaml, ignore_errors: true }\n- 'other_file.yaml'",
"items": {
"oneOf": [
{ "type": "string" },
{
"type": "object",
"properties": {
"resource": { "type": "string" },
"type": { "type": ["string", "null"] },
"ignore_errors": { "type": "boolean" }
},
"required": ["resource"],
"additionalProperties": false
}
]
}
},
"parameters": {
"type": "object",
"description": "Defines application-wide parameters.",
"$comment": "Values support scalars, arrays and objects.",
"patternProperties": {
"^.+$": { "$ref": "#/$defs/parameterValue" }
},
"additionalProperties": false
},
"services": {
"type": "object",
"description": "Defines your application services.",
"$comment": "Service ids accept multiple shapes: object (full definition), '@alias' string (alias), null (empty definition), prototype (resource/namespace), stack, and short array syntax for arguments.",
"properties": {
"_defaults": {
"$ref": "#/$defs/serviceDefaults"
},
"_instanceof": {
"type": "object",
"patternProperties": {
"^.+$": { "$ref": "#/$defs/serviceInstanceof" }
},
"additionalProperties": false
}
},
"patternProperties": {
"^(?:[A-Za-z_\\x7f-\\xff][A-Za-z0-9_\\x7f-\\xff]*\\\\)+[A-Za-z_\\x7f-\\xff][A-Za-z0-9_\\x7f-\\xff]*$": {
"oneOf": [
{ "$ref": "#/$defs/serviceConcrete" },
{ "$ref": "#/$defs/serviceAliasObject" },
{ "$ref": "#/$defs/servicePrototype" },
{ "$ref": "#/$defs/serviceStack" },
{ "$ref": "#/$defs/serviceShortSyntax" },
{ "type": "null" },
{ "type": "string", "pattern": "^@.+" }
],
"$comment": "FQCN service ids: short array syntax is allowed here."
},
"^(?!_).+$": {
"oneOf": [
{ "$ref": "#/$defs/serviceConcrete" },
{ "$ref": "#/$defs/serviceAliasObject" },
{ "$ref": "#/$defs/servicePrototype" },
{ "$ref": "#/$defs/serviceStack" },
{ "type": "null" },
{ "type": "string", "pattern": "^@.+" }
],
"$comment": "Non-FQCN ids: short array syntax is not allowed."
}
},
"additionalProperties": false
}
},
"patternProperties": {
"^when@.+$": {
"$ref": "#",
"description": "A container for parameters and services that are only loaded in a specific environment."
}
},
"additionalProperties": true,
"$defs": {
"parameterValue": {
"oneOf": [
{ "type": "string" },
{ "type": "number" },
{ "type": "boolean" },
{ "type": "array" },
{ "type": "object" },
{ "type": "null" }
]
},
"scalarValue": {
"oneOf": [
{ "type": "string" },
{ "type": "number" },
{ "type": "boolean" },
{ "type": "null" }
]
},
"fqcn": {
"type": "string",
"pattern": "^(?:\\\\)?(?:[A-Za-z_\\\\x7f-\\\\xff][A-Za-z0-9_\\\\x7f-\\\\xff]*\\\\)+[A-Za-z_\\\\x7f-\\\\xff][A-Za-z0-9_\\\\x7f-\\\\xff]*$",
"$comment": "Fully-qualified class name, optional leading backslash; requires at least one namespace separator."
},
"arguments": {
"$comment": "Arguments can be an array (positional) or a map (named).",
"oneOf": [
{ "type": "array", "items": { "$ref": "#/$defs/parameterValue" } },
{ "type": "object", "additionalProperties": { "$ref": "#/$defs/parameterValue" } }
]
},
"tags": {
"$comment": "YAML examples:\n- ['kernel.event_subscriber', { name: 'kernel.event_listener', event: 'kernel.request' }, { my_tag: { attr: 1 } }].",
"type": "array",
"items": {
"oneOf": [
{ "type": "string" },
{
"type": "object",
"properties": { "name": { "type": "string" } },
"required": ["name"],
"additionalProperties": true
},
{
"type": "object",
"minProperties": 1,
"maxProperties": 1,
"additionalProperties": {
"type": "object",
"additionalProperties": { "$ref": "#/$defs/parameterValue" }
}
}
]
}
},
"calls": {
"$comment": "YAML examples:\n- ['setFoo', ['@bar'], true]\n- { method: 'setFoo', arguments: ['@bar'], returns_clone: true }\n- { setFoo: ['@bar'] }\n- { setFoo: !returns_clone ['@bar'] } (special YAML tag)",
"type": "array",
"items": {
"oneOf": [
{
"type": "array",
"minItems": 1,
"maxItems": 3,
"items": [
{ "type": "string" },
{
"oneOf": [
{ "type": "array", "items": { "$ref": "#/$defs/parameterValue" } },
{ "type": "object", "additionalProperties": { "$ref": "#/$defs/parameterValue" } }
]
},
{ "type": "boolean" }
]
},
{
"type": "object",
"properties": {
"method": { "type": "string" },
"arguments": { "$ref": "#/$defs/arguments" },
"returns_clone": { "type": "boolean" }
},
"required": ["method"],
"additionalProperties": false
},
{
"type": "object",
"minProperties": 1,
"maxProperties": 1,
"additionalProperties": { "$ref": "#/$defs/arguments" }
}
]
}
},
"deprecation": {
"$comment": "YamlFileLoader requires an object with 'package' and 'version'.",
"type": "object",
"properties": {
"package": { "type": "string" },
"version": { "type": "string" },
"message": { "type": "string" }
},
"required": ["package", "version"],
"additionalProperties": false
},
"callable": {
"$comment": "Either 'Class::method' or ['Class', 'method'].",
"oneOf": [
{ "type": "string" },
{
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": [ { "type": "string" }, { "type": "string" } ]
}
]
},
"serviceShortSyntax": {
"$comment": "Short syntax: service defined as arguments array. YAML example: App\\SomeService: ['@dependency', 'literal', 123]",
"type": "array",
"items": { "$ref": "#/$defs/parameterValue" }
},
"factoryCallable": {
"$comment": "Either '@service', 'Class::method', or [classOrService, method]. For ['null', method] the class is resolved from the service class when using 'constructor'.",
"oneOf": [
{ "type": "string" },
{
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": [ { "type": ["string", "null"] }, { "type": "string" } ]
}
]
},
"serviceAliasObject": {
"$comment": "Allowed keys for aliases are only 'alias', 'public', 'deprecated'.",
"type": "object",
"properties": {
"alias": { "type": "string" },
"public": { "type": "boolean" },
"deprecated": { "$ref": "#/$defs/deprecation" }
},
"required": ["alias"],
"additionalProperties": false
},
"serviceConcrete": {
"$comment": "'constructor' cannot be used together with 'factory'. If 'from_callable' is used, many other keys are not allowed (see YamlFileLoader).",
"type": "object",
"properties": {
"class": { "type": "string" },
"file": { "type": "string" },
"parent": { "type": "string" },
"shared": { "type": "boolean" },
"synthetic": { "type": "boolean" },
"lazy": { "oneOf": [ { "type": "boolean" }, { "$ref": "#/$defs/fqcn" } ] },
"public": { "type": "boolean" },
"abstract": { "type": "boolean" },
"deprecated": { "$ref": "#/$defs/deprecation" },
"factory": { "$ref": "#/$defs/factoryCallable" },
"configurator": { "$ref": "#/$defs/callable" },
"arguments": { "$ref": "#/$defs/arguments" },
"properties": { "type": "object", "additionalProperties": { "$ref": "#/$defs/parameterValue" } },
"calls": { "$ref": "#/$defs/calls" },
"tags": { "$ref": "#/$defs/tags" },
"resource_tags": { "$ref": "#/$defs/tags" },
"decorates": { "type": "string" },
"decoration_inner_name": { "type": "string" },
"decoration_priority": { "type": "integer" },
"decoration_on_invalid": { "oneOf": [ { "enum": ["exception", "ignore"] }, { "type": "null" } ], "$comment": "Use null (without quotes) for NULL_ON_INVALID_REFERENCE." },
"autowire": { "type": "boolean" },
"autoconfigure": { "type": "boolean" },
"bind": { "type": "object", "additionalProperties": { "$ref": "#/$defs/parameterValue" } },
"constructor": { "type": "string" },
"from_callable": { "$ref": "#/$defs/parameterValue", "$comment": "Will be wrapped by factory ['Closure','fromCallable'] and sets lazy=true unless class is 'Closure'." }
},
"additionalProperties": false
},
"servicePrototype": {
"$comment": "Prototype definitions register classes by scanning directories. 'namespace' requires 'resource'.",
"type": "object",
"properties": {
"resource": { "type": "string" },
"namespace": { "type": "string" },
"exclude": {
"oneOf": [
{ "type": "string" },
{ "type": "array", "items": { "type": "string" } }
]
},
"parent": { "type": "string" },
"shared": { "type": "boolean" },
"lazy": { "oneOf": [ { "type": "boolean" }, { "$ref": "#/$defs/fqcn" } ] },
"public": { "type": "boolean" },
"abstract": { "type": "boolean" },
"deprecated": { "$ref": "#/$defs/deprecation" },
"factory": { "$ref": "#/$defs/factoryCallable" },
"arguments": { "$ref": "#/$defs/arguments" },
"properties": { "type": "object", "additionalProperties": { "$ref": "#/$defs/parameterValue" } },
"configurator": { "$ref": "#/$defs/callable" },
"calls": { "$ref": "#/$defs/calls" },
"tags": { "$ref": "#/$defs/tags" },
"resource_tags": { "$ref": "#/$defs/tags" },
"autowire": { "type": "boolean" },
"autoconfigure": { "type": "boolean" },
"bind": { "type": "object", "additionalProperties": { "$ref": "#/$defs/parameterValue" } },
"constructor": { "type": "string" }
},
"required": ["resource"],
"allOf": [
{ "anyOf": [ { "not": { "required": ["namespace"] } }, { "required": ["resource"] } ] }
],
"additionalProperties": false
},
"serviceStack": {
"$comment": "A stack builds a service pipeline from multiple frames.",
"type": "object",
"properties": {
"stack": {
"type": "array",
"items": {
"oneOf": [
{ "$ref": "#/$defs/serviceConcrete" },
{ "$ref": "#/$defs/serviceAliasObject" },
{ "$ref": "#/$defs/servicePrototype" }
]
}
},
"public": { "type": "boolean" },
"deprecated": { "$ref": "#/$defs/deprecation" }
},
"required": ["stack"],
"additionalProperties": false
},
"serviceDefaults": {
"$comment": "Defaults applied to services declared in the current file.",
"type": "object",
"properties": {
"public": { "type": "boolean" },
"tags": { "$ref": "#/$defs/tags" },
"resource_tags": { "$ref": "#/$defs/tags" },
"autowire": { "type": "boolean" },
"autoconfigure": { "type": "boolean" },
"bind": {
"type": "object",
"additionalProperties": { "$ref": "#/$defs/parameterValue" }
}
},
"additionalProperties": false
},
"serviceInstanceof": {
"$comment": "Applied to services declared in the current file whose class matches the key (interface or parent class).",
"type": "object",
"properties": {
"shared": { "type": "boolean" },
"lazy": { "oneOf": [ { "type": "boolean" }, { "$ref": "#/$defs/fqcn" } ] },
"public": { "type": "boolean" },
"properties": { "type": "object", "additionalProperties": { "$ref": "#/$defs/parameterValue" } },
"configurator": { "$ref": "#/$defs/callable" },
"calls": { "$ref": "#/$defs/calls" },
"tags": { "$ref": "#/$defs/tags" },
"resource_tags": { "$ref": "#/$defs/tags" },
"autowire": { "type": "boolean" },
"bind": { "type": "object", "additionalProperties": { "$ref": "#/$defs/parameterValue" } },
"constructor": { "type": "string" }
},
"additionalProperties": false
}
}
}