diff --git a/.gitattributes b/.gitattributes index 84c7add05..14c3c3594 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,3 @@ /Tests export-ignore /phpunit.xml.dist export-ignore -/.gitattributes export-ignore -/.gitignore export-ignore +/.git* export-ignore diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..4689c4dad --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +Please do not submit any Pull Requests here. They will be closed. +--- + +Please submit your PR here instead: +https://github.com/symfony/symfony + +This repository is what we call a "subtree split": a read-only subset of that main repository. +We're looking forward to your PR there! diff --git a/.github/workflows/close-pull-request.yml b/.github/workflows/close-pull-request.yml new file mode 100644 index 000000000..e55b47817 --- /dev/null +++ b/.github/workflows/close-pull-request.yml @@ -0,0 +1,20 @@ +name: Close Pull Request + +on: + pull_request_target: + types: [opened] + +jobs: + run: + runs-on: ubuntu-latest + steps: + - uses: superbrothers/close-pull-request@v3 + with: + comment: | + Thanks for your Pull Request! We love contributions. + + However, you should instead open your PR on the main repository: + https://github.com/symfony/symfony + + This repository is what we call a "subtree split": a read-only subset of that main repository. + We're looking forward to your PR there! diff --git a/Builder/ClassBuilder.php b/Builder/ClassBuilder.php index 619ebd857..5ae8bda16 100644 --- a/Builder/ClassBuilder.php +++ b/Builder/ClassBuilder.php @@ -20,7 +20,6 @@ */ class ClassBuilder { - private string $namespace; private string $name; /** @var Property[] */ @@ -33,9 +32,10 @@ class ClassBuilder private array $implements = []; private bool $allowExtraKeys = false; - public function __construct(string $namespace, string $name) - { - $this->namespace = $namespace; + public function __construct( + private string $namespace, + string $name, + ) { $this->name = ucfirst($this->camelCase($name)).'Config'; } @@ -63,11 +63,11 @@ public function build(): string } unset($path[$key]); } - $require .= sprintf('require_once __DIR__.\DIRECTORY_SEPARATOR.\'%s\';', implode('\'.\DIRECTORY_SEPARATOR.\'', $path))."\n"; + $require .= \sprintf('require_once __DIR__.\DIRECTORY_SEPARATOR.\'%s\';', implode('\'.\DIRECTORY_SEPARATOR.\'', $path))."\n"; } $use = $require ? "\n" : ''; foreach (array_keys($this->use) as $statement) { - $use .= sprintf('use %s;', $statement)."\n"; + $use .= \sprintf('use %s;', $statement)."\n"; } $implements = [] === $this->implements ? '' : 'implements '.implode(', ', $this->implements); @@ -82,7 +82,7 @@ public function build(): string } } - $content = strtr(' $this->namespace, 'REQUIRE' => $require, 'USE' => $use, 'CLASS' => $this->getName(), 'IMPLEMENTS' => $implements, 'BODY' => $body]); - - return $content; } public function addRequire(self $class): void @@ -126,8 +124,8 @@ public function addProperty(string $name, ?string $classType = null, ?string $de $property->setType($classType); } $this->properties[] = $property; - $defaultValue = null !== $defaultValue ? sprintf(' = %s', $defaultValue) : ''; - $property->setContent(sprintf('private $%s%s;', $property->getName(), $defaultValue)); + $defaultValue = null !== $defaultValue ? \sprintf(' = %s', $defaultValue) : ''; + $property->setContent(\sprintf('private $%s%s;', $property->getName(), $defaultValue)); return $property; } diff --git a/Builder/ConfigBuilderGenerator.php b/Builder/ConfigBuilderGenerator.php index d43d814eb..08e91c2d1 100644 --- a/Builder/ConfigBuilderGenerator.php +++ b/Builder/ConfigBuilderGenerator.php @@ -37,11 +37,10 @@ class ConfigBuilderGenerator implements ConfigBuilderGeneratorInterface * @var ClassBuilder[] */ private array $classes = []; - private string $outputDir; - public function __construct(string $outputDir) - { - $this->outputDir = $outputDir; + public function __construct( + private string $outputDir, + ) { } /** @@ -115,7 +114,7 @@ private function buildNode(NodeInterface $node, ClassBuilder $class, string $nam $child instanceof PrototypedArrayNode => $this->handlePrototypedArrayNode($child, $class, $namespace), $child instanceof VariableNode => $this->handleVariableNode($child, $class), $child instanceof ArrayNode => $this->handleArrayNode($child, $class, $namespace), - default => throw new \RuntimeException(sprintf('Unknown node "%s".', $child::class)), + default => throw new \RuntimeException(\sprintf('Unknown node "%s".', $child::class)), }; } } @@ -130,9 +129,9 @@ private function handleArrayNode(ArrayNode $node, ClassBuilder $class, string $n $hasNormalizationClosures = $this->hasNormalizationClosures($node); $comment = $this->getComment($node); if ($hasNormalizationClosures) { - $comment = sprintf(" * @template TValue\n * @param TValue \$value\n%s", $comment); - $comment .= sprintf(' * @return %s|$this'."\n", $childClass->getFqcn()); - $comment .= sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn()); + $comment = \sprintf(" * @template TValue\n * @param TValue \$value\n%s", $comment); + $comment .= \sprintf(' * @return %s|$this'."\n", $childClass->getFqcn()); + $comment .= \sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn()); } if ('' !== $comment) { $comment = "/**\n$comment*/\n"; @@ -282,9 +281,9 @@ public function NAME(string $VAR, TYPE $VALUE): static $comment = $this->getComment($node); if ($hasNormalizationClosures) { - $comment = sprintf(" * @template TValue\n * @param TValue \$value\n%s", $comment); - $comment .= sprintf(' * @return %s|$this'."\n", $childClass->getFqcn()); - $comment .= sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn()); + $comment = \sprintf(" * @template TValue\n * @param TValue \$value\n%s", $comment); + $comment .= \sprintf(' * @return %s|$this'."\n", $childClass->getFqcn()); + $comment .= \sprintf(' * @psalm-return (TValue is array ? %s : static)'."\n ", $childClass->getFqcn()); } if ('' !== $comment) { $comment = "/**\n$comment*/\n"; @@ -426,7 +425,7 @@ private function getComment(BaseNode $node): string } if ($node instanceof EnumNode) { - $comment .= sprintf(' * @param ParamConfigurator|%s $value', implode('|', array_unique(array_map(fn ($a) => !$a instanceof \UnitEnum ? var_export($a, true) : '\\'.ltrim(var_export($a, true), '\\'), $node->getValues()))))."\n"; + $comment .= \sprintf(' * @param ParamConfigurator|%s $value', implode('|', array_unique(array_map(fn ($a) => !$a instanceof \UnitEnum ? var_export($a, true) : '\\'.ltrim(var_export($a, true), '\\'), $node->getValues()))))."\n"; } else { $parameterTypes = $this->getParameterTypes($node); $comment .= ' * @param ParamConfigurator|'.implode('|', $parameterTypes).' $value'."\n"; @@ -579,7 +578,7 @@ public function NAME(string $key, mixed $value): static private function getSubNamespace(ClassBuilder $rootClass): string { - return sprintf('%s\\%s', $rootClass->getNamespace(), substr($rootClass->getName(), 0, -6)); + return \sprintf('%s\\%s', $rootClass->getNamespace(), substr($rootClass->getName(), 0, -6)); } private function hasNormalizationClosures(NodeInterface $node): bool diff --git a/Builder/Method.php b/Builder/Method.php index c97c98649..8fba068a9 100644 --- a/Builder/Method.php +++ b/Builder/Method.php @@ -20,11 +20,9 @@ */ class Method { - private string $content; - - public function __construct(string $content) - { - $this->content = $content; + public function __construct( + private string $content, + ) { } public function getContent(): string diff --git a/Builder/Property.php b/Builder/Property.php index cf2f8d549..ede5351c7 100644 --- a/Builder/Property.php +++ b/Builder/Property.php @@ -20,17 +20,15 @@ */ class Property { - private string $name; - private string $originalName; private bool $array = false; private bool $scalarsAllowed = false; private ?string $type = null; private ?string $content = null; - public function __construct(string $originalName, string $name) - { - $this->name = $name; - $this->originalName = $originalName; + public function __construct( + private string $originalName, + private string $name, + ) { } public function getName(): string diff --git a/CHANGELOG.md b/CHANGELOG.md index 094d5abba..e38639e4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,28 @@ CHANGELOG ========= +7.2 +--- + + * Add `#[WhenNot]` attribute to prevent service from being registered in a specific environment + * Generate a meta file in JSON format for resource tracking + * Add `SkippingResourceChecker` + * Add support for `defaultNull()` on `BooleanNode` + * Add `StringNode` and `StringNodeDefinition` + * Add `ArrayNodeDefinition::stringPrototype()` method + * Add `NodeBuilder::stringNode()` method + +7.1 +--- + + * Allow custom meta location in `ResourceCheckerConfigCache` + * Allow custom meta location in `ConfigCache` + +7.0 +--- + + * Require explicit argument when calling `NodeBuilder::setParent()` + 6.3 --- diff --git a/ConfigCache.php b/ConfigCache.php index 89519417d..400b6162c 100644 --- a/ConfigCache.php +++ b/ConfigCache.php @@ -11,7 +11,9 @@ namespace Symfony\Component\Config; +use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\Config\Resource\SelfCheckingResourceChecker; +use Symfony\Component\Config\Resource\SkippingResourceChecker; /** * ConfigCache caches arbitrary content in files on disk. @@ -25,22 +27,27 @@ */ class ConfigCache extends ResourceCheckerConfigCache { - private bool $debug; - /** - * @param string $file The absolute cache path - * @param bool $debug Whether debugging is enabled or not + * @param string $file The absolute cache path + * @param bool $debug Whether debugging is enabled or not + * @param string|null $metaFile The absolute path to the meta file + * @param class-string[]|null $skippedResourceTypes */ - public function __construct(string $file, bool $debug) - { - $this->debug = $debug; - + public function __construct( + string $file, + private bool $debug, + ?string $metaFile = null, + array|null $skippedResourceTypes = null, + ) { $checkers = []; - if (true === $this->debug) { - $checkers = [new SelfCheckingResourceChecker()]; + if ($this->debug) { + if (null !== $skippedResourceTypes) { + $checkers[] = new SkippingResourceChecker($skippedResourceTypes); + } + $checkers[] = new SelfCheckingResourceChecker(); } - parent::__construct($file, $checkers); + parent::__construct($file, $checkers, $metaFile); } /** diff --git a/ConfigCacheFactory.php b/ConfigCacheFactory.php index 39adad1e1..27a18e751 100644 --- a/ConfigCacheFactory.php +++ b/ConfigCacheFactory.php @@ -22,14 +22,12 @@ */ class ConfigCacheFactory implements ConfigCacheFactoryInterface { - private bool $debug; - /** * @param bool $debug The debug flag to pass to ConfigCache */ - public function __construct(bool $debug) - { - $this->debug = $debug; + public function __construct( + private bool $debug, + ) { } public function cache(string $file, callable $callback): ConfigCacheInterface diff --git a/ConfigCacheInterface.php b/ConfigCacheInterface.php index f8d270634..7b9d38897 100644 --- a/ConfigCacheInterface.php +++ b/ConfigCacheInterface.php @@ -39,9 +39,7 @@ public function isFresh(): bool; * @param string $content The content to write into the cache * @param ResourceInterface[]|null $metadata An array of ResourceInterface instances * - * @return void - * * @throws \RuntimeException When the cache file cannot be written */ - public function write(string $content, ?array $metadata = null); + public function write(string $content, ?array $metadata = null): void; } diff --git a/Definition/ArrayNode.php b/Definition/ArrayNode.php index 1448220cd..5301b7243 100644 --- a/Definition/ArrayNode.php +++ b/Definition/ArrayNode.php @@ -22,20 +22,17 @@ */ class ArrayNode extends BaseNode implements PrototypeNodeInterface { - protected $xmlRemappings = []; - protected $children = []; - protected $allowFalse = false; - protected $allowNewKeys = true; - protected $addIfNotSet = false; - protected $performDeepMerging = true; - protected $ignoreExtraKeys = false; - protected $removeExtraKeys = true; - protected $normalizeKeys = true; - - /** - * @return void - */ - public function setNormalizeKeys(bool $normalizeKeys) + protected array $xmlRemappings = []; + protected array $children = []; + protected bool $allowFalse = false; + protected bool $allowNewKeys = true; + protected bool $addIfNotSet = false; + protected bool $performDeepMerging = true; + protected bool $ignoreExtraKeys = false; + protected bool $removeExtraKeys = true; + protected bool $normalizeKeys = true; + + public function setNormalizeKeys(bool $normalizeKeys): void { $this->normalizeKeys = $normalizeKeys; } @@ -80,10 +77,8 @@ public function getChildren(): array * Sets the xml remappings that should be performed. * * @param array $remappings An array of the form [[string, string]] - * - * @return void */ - public function setXmlRemappings(array $remappings) + public function setXmlRemappings(array $remappings): void { $this->xmlRemappings = $remappings; } @@ -101,40 +96,32 @@ public function getXmlRemappings(): array /** * Sets whether to add default values for this array if it has not been * defined in any of the configuration files. - * - * @return void */ - public function setAddIfNotSet(bool $boolean) + public function setAddIfNotSet(bool $boolean): void { $this->addIfNotSet = $boolean; } /** * Sets whether false is allowed as value indicating that the array should be unset. - * - * @return void */ - public function setAllowFalse(bool $allow) + public function setAllowFalse(bool $allow): void { $this->allowFalse = $allow; } /** * Sets whether new keys can be defined in subsequent configurations. - * - * @return void */ - public function setAllowNewKeys(bool $allow) + public function setAllowNewKeys(bool $allow): void { $this->allowNewKeys = $allow; } /** * Sets if deep merging should occur. - * - * @return void */ - public function setPerformDeepMerging(bool $boolean) + public function setPerformDeepMerging(bool $boolean): void { $this->performDeepMerging = $boolean; } @@ -144,10 +131,8 @@ public function setPerformDeepMerging(bool $boolean) * * @param bool $boolean To allow extra keys * @param bool $remove To remove extra keys - * - * @return void */ - public function setIgnoreExtraKeys(bool $boolean, bool $remove = true) + public function setIgnoreExtraKeys(bool $boolean, bool $remove = true): void { $this->ignoreExtraKeys = $boolean; $this->removeExtraKeys = $this->ignoreExtraKeys && $remove; @@ -161,10 +146,7 @@ public function shouldIgnoreExtraKeys(): bool return $this->ignoreExtraKeys; } - /** - * @return void - */ - public function setName(string $name) + public function setName(string $name): void { $this->name = $name; } @@ -177,7 +159,7 @@ public function hasDefaultValue(): bool public function getDefaultValue(): mixed { if (!$this->hasDefaultValue()) { - throw new \RuntimeException(sprintf('The node at path "%s" has no default value.', $this->getPath())); + throw new \RuntimeException(\sprintf('The node at path "%s" has no default value.', $this->getPath())); } $defaults = []; @@ -193,19 +175,17 @@ public function getDefaultValue(): mixed /** * Adds a child node. * - * @return void - * * @throws \InvalidArgumentException when the child node has no name * @throws \InvalidArgumentException when the child node's name is not unique */ - public function addChild(NodeInterface $node) + public function addChild(NodeInterface $node): void { $name = $node->getName(); if ('' === $name) { throw new \InvalidArgumentException('Child nodes must be named.'); } if (isset($this->children[$name])) { - throw new \InvalidArgumentException(sprintf('A child node named "%s" already exists.', $name)); + throw new \InvalidArgumentException(\sprintf('A child node named "%s" already exists.', $name)); } $this->children[$name] = $node; @@ -218,15 +198,15 @@ public function addChild(NodeInterface $node) protected function finalizeValue(mixed $value): mixed { if (false === $value) { - throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s.', $this->getPath(), json_encode($value))); + throw new UnsetKeyException(\sprintf('Unsetting key for path "%s", value: false.', $this->getPath())); } foreach ($this->children as $name => $child) { if (!\array_key_exists($name, $value)) { if ($child->isRequired()) { - $message = sprintf('The child config "%s" under "%s" must be configured', $name, $this->getPath()); + $message = \sprintf('The child config "%s" under "%s" must be configured', $name, $this->getPath()); if ($child->getInfo()) { - $message .= sprintf(': %s', $child->getInfo()); + $message .= \sprintf(': %s', $child->getInfo()); } else { $message .= '.'; } @@ -258,13 +238,10 @@ protected function finalizeValue(mixed $value): mixed return $value; } - /** - * @return void - */ - protected function validateType(mixed $value) + protected function validateType(mixed $value): void { if (!\is_array($value) && (!$this->allowFalse || false !== $value)) { - $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "array", but got "%s"', $this->getPath(), get_debug_type($value))); + $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "array", but got "%s"', $this->getPath(), get_debug_type($value))); if ($hint = $this->getInfo()) { $ex->addHint($hint); } @@ -315,13 +292,13 @@ protected function normalizeValue(mixed $value): mixed } } - $msg = sprintf('Unrecognized option%s "%s" under "%s"', 1 === \count($value) ? '' : 's', implode(', ', array_keys($value)), $this->getPath()); + $msg = \sprintf('Unrecognized option%s "%s" under "%s"', 1 === \count($value) ? '' : 's', implode(', ', array_keys($value)), $this->getPath()); if (\count($guesses)) { asort($guesses); - $msg .= sprintf('. Did you mean "%s"?', implode('", "', array_keys($guesses))); + $msg .= \sprintf('. Did you mean "%s"?', implode('", "', array_keys($guesses))); } else { - $msg .= sprintf('. Available option%s %s "%s".', 1 === \count($proposals) ? '' : 's', 1 === \count($proposals) ? 'is' : 'are', implode('", "', $proposals)); + $msg .= \sprintf('. Available option%s %s "%s".', 1 === \count($proposals) ? '' : 's', 1 === \count($proposals) ? 'is' : 'are', implode('", "', $proposals)); } $ex = new InvalidConfigurationException($msg); @@ -370,7 +347,7 @@ protected function mergeValues(mixed $leftSide, mixed $rightSide): mixed // no conflict if (!\array_key_exists($k, $leftSide)) { if (!$this->allowNewKeys) { - $ex = new InvalidConfigurationException(sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file. If you are trying to overwrite an element, make sure you redefine it with the same name.', $this->getPath())); + $ex = new InvalidConfigurationException(\sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file. If you are trying to overwrite an element, make sure you redefine it with the same name.', $this->getPath())); $ex->setPath($this->getPath()); throw $ex; diff --git a/Definition/BaseNode.php b/Definition/BaseNode.php index 6e2a19227..9cfd69239 100644 --- a/Definition/BaseNode.php +++ b/Definition/BaseNode.php @@ -29,32 +29,31 @@ abstract class BaseNode implements NodeInterface private static array $placeholderUniquePrefixes = []; private static array $placeholders = []; - protected $name; - protected $parent; - protected $normalizationClosures = []; - protected $normalizedTypes = []; - protected $finalValidationClosures = []; - protected $allowOverwrite = true; - protected $required = false; - protected $deprecation = []; - protected $equivalentValues = []; - protected $attributes = []; - protected $pathSeparator; + protected string $name; + protected array $normalizationClosures = []; + protected array $normalizedTypes = []; + protected array $finalValidationClosures = []; + protected bool $allowOverwrite = true; + protected bool $required = false; + protected array $deprecation = []; + protected array $equivalentValues = []; + protected array $attributes = []; private mixed $handlingPlaceholder = null; /** * @throws \InvalidArgumentException if the name contains a period */ - public function __construct(?string $name, ?NodeInterface $parent = null, string $pathSeparator = self::DEFAULT_PATH_SEPARATOR) - { + public function __construct( + ?string $name, + protected ?NodeInterface $parent = null, + protected string $pathSeparator = self::DEFAULT_PATH_SEPARATOR, + ) { if (str_contains($name = (string) $name, $pathSeparator)) { throw new \InvalidArgumentException('The name must not contain ".'.$pathSeparator.'".'); } $this->name = $name; - $this->parent = $parent; - $this->pathSeparator = $pathSeparator; } /** @@ -98,10 +97,7 @@ public static function resetPlaceholders(): void self::$placeholders = []; } - /** - * @return void - */ - public function setAttribute(string $key, mixed $value) + public function setAttribute(string $key, mixed $value): void { $this->attributes[$key] = $value; } @@ -121,28 +117,20 @@ public function getAttributes(): array return $this->attributes; } - /** - * @return void - */ - public function setAttributes(array $attributes) + public function setAttributes(array $attributes): void { $this->attributes = $attributes; } - /** - * @return void - */ - public function removeAttribute(string $key) + public function removeAttribute(string $key): void { unset($this->attributes[$key]); } /** * Sets an info message. - * - * @return void */ - public function setInfo(string $info) + public function setInfo(string $info): void { $this->setAttribute('info', $info); } @@ -157,10 +145,8 @@ public function getInfo(): ?string /** * Sets the example configuration for this node. - * - * @return void */ - public function setExample(string|array $example) + public function setExample(string|array $example): void { $this->setAttribute('example', $example); } @@ -175,20 +161,16 @@ public function getExample(): string|array|null /** * Adds an equivalent value. - * - * @return void */ - public function addEquivalentValue(mixed $originalValue, mixed $equivalentValue) + public function addEquivalentValue(mixed $originalValue, mixed $equivalentValue): void { $this->equivalentValues[] = [$originalValue, $equivalentValue]; } /** * Set this node as required. - * - * @return void */ - public function setRequired(bool $boolean) + public function setRequired(bool $boolean): void { $this->required = $boolean; } @@ -202,10 +184,8 @@ public function setRequired(bool $boolean) * @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 void */ - public function setDeprecated(string $package, string $version, string $message = 'The child node "%node%" at path "%path%" is deprecated.') + public function setDeprecated(string $package, string $version, string $message = 'The child node "%node%" at path "%path%" is deprecated.'): void { $this->deprecation = [ 'package' => $package, @@ -216,10 +196,8 @@ public function setDeprecated(string $package, string $version, string $message /** * Sets if this node can be overridden. - * - * @return void */ - public function setAllowOverwrite(bool $allow) + public function setAllowOverwrite(bool $allow): void { $this->allowOverwrite = $allow; } @@ -228,10 +206,8 @@ public function setAllowOverwrite(bool $allow) * Sets the closures used for normalization. * * @param \Closure[] $closures An array of Closures used for normalization - * - * @return void */ - public function setNormalizationClosures(array $closures) + public function setNormalizationClosures(array $closures): void { $this->normalizationClosures = $closures; } @@ -240,10 +216,8 @@ public function setNormalizationClosures(array $closures) * Sets the list of types supported by normalization. * * see ExprBuilder::TYPE_* constants. - * - * @return void */ - public function setNormalizedTypes(array $types) + public function setNormalizedTypes(array $types): void { $this->normalizedTypes = $types; } @@ -262,10 +236,8 @@ public function getNormalizedTypes(): array * Sets the closures used for final validation. * * @param \Closure[] $closures An array of Closures used for final validation - * - * @return void */ - public function setFinalValidationClosures(array $closures) + public function setFinalValidationClosures(array $closures): void { $this->finalValidationClosures = $closures; } @@ -313,7 +285,7 @@ public function getPath(): string final public function merge(mixed $leftSide, mixed $rightSide): mixed { if (!$this->allowOverwrite) { - throw new ForbiddenOverwriteException(sprintf('Configuration path "%s" cannot be overwritten. You have to define all options for this path, and any of its sub-paths in one configuration section.', $this->getPath())); + throw new ForbiddenOverwriteException(\sprintf('Configuration path "%s" cannot be overwritten. You have to define all options for this path, and any of its sub-paths in one configuration section.', $this->getPath())); } if ($leftSide !== $leftPlaceholders = self::resolvePlaceholderValue($leftSide)) { @@ -432,7 +404,7 @@ final public function finalize(mixed $value): mixed throw $e; } catch (\Exception $e) { - throw new InvalidConfigurationException(sprintf('Invalid configuration for path "%s": ', $this->getPath()).$e->getMessage(), $e->getCode(), $e); + throw new InvalidConfigurationException(\sprintf('Invalid configuration for path "%s": ', $this->getPath()).$e->getMessage(), $e->getCode(), $e); } } @@ -442,11 +414,9 @@ final public function finalize(mixed $value): mixed /** * Validates the type of a Node. * - * @return void - * * @throws InvalidTypeException when the value is invalid */ - abstract protected function validateType(mixed $value); + abstract protected function validateType(mixed $value): void; /** * Normalizes the value. @@ -507,7 +477,7 @@ private static function resolvePlaceholderValue(mixed $value): mixed private function doValidateType(mixed $value): void { if (null !== $this->handlingPlaceholder && !$this->allowPlaceholders()) { - $e = new InvalidTypeException(sprintf('A dynamic value is not compatible with a "%s" node type at path "%s".', static::class, $this->getPath())); + $e = new InvalidTypeException(\sprintf('A dynamic value is not compatible with a "%s" node type at path "%s".', static::class, $this->getPath())); $e->setPath($this->getPath()); throw $e; @@ -523,7 +493,7 @@ private function doValidateType(mixed $value): void $validTypes = $this->getValidPlaceholderTypes(); if ($validTypes && array_diff($knownTypes, $validTypes)) { - $e = new InvalidTypeException(sprintf( + $e = new InvalidTypeException(\sprintf( 'Invalid type for path "%s". Expected %s, but got %s.', $this->getPath(), 1 === \count($validTypes) ? '"'.reset($validTypes).'"' : 'one of "'.implode('", "', $validTypes).'"', diff --git a/Definition/BooleanNode.php b/Definition/BooleanNode.php index 7ec903cd6..b4ed0f0eb 100644 --- a/Definition/BooleanNode.php +++ b/Definition/BooleanNode.php @@ -20,13 +20,23 @@ */ class BooleanNode extends ScalarNode { - /** - * @return void - */ - protected function validateType(mixed $value) + public function __construct( + ?string $name, + ?NodeInterface $parent = null, + string $pathSeparator = self::DEFAULT_PATH_SEPARATOR, + private bool $nullable = false, + ) { + parent::__construct($name, $parent, $pathSeparator); + } + + protected function validateType(mixed $value): void { if (!\is_bool($value)) { - $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "bool", but got "%s".', $this->getPath(), get_debug_type($value))); + if (null === $value && $this->nullable) { + return; + } + + $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "bool%s", but got "%s".', $this->getPath(), $this->nullable ? '" or "null' : '', get_debug_type($value))); if ($hint = $this->getInfo()) { $ex->addHint($hint); } diff --git a/Definition/Builder/ArrayNodeDefinition.php b/Definition/Builder/ArrayNodeDefinition.php index 7a82334ee..6b75ba137 100644 --- a/Definition/Builder/ArrayNodeDefinition.php +++ b/Definition/Builder/ArrayNodeDefinition.php @@ -23,19 +23,19 @@ */ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinitionInterface { - protected $performDeepMerging = true; - protected $ignoreExtraKeys = false; - protected $removeExtraKeys = true; - protected $children = []; - protected $prototype; - protected $atLeastOne = false; - protected $allowNewKeys = true; - protected $key; - protected $removeKeyItem; - protected $addDefaults = false; - protected $addDefaultChildren = false; - protected $nodeBuilder; - protected $normalizeKeys = true; + protected bool $performDeepMerging = true; + protected bool $ignoreExtraKeys = false; + protected bool $removeExtraKeys = true; + protected array $children = []; + protected NodeDefinition $prototype; + protected bool $atLeastOne = false; + protected bool $allowNewKeys = true; + protected ?string $key = null; + protected bool $removeKeyItem = false; + protected bool $addDefaults = false; + protected int|string|array|false|null $addDefaultChildren = false; + protected NodeBuilder $nodeBuilder; + protected bool $normalizeKeys = true; public function __construct(?string $name, ?NodeParentInterface $parent = null) { @@ -45,10 +45,7 @@ public function __construct(?string $name, ?NodeParentInterface $parent = null) $this->trueEquivalent = []; } - /** - * @return void - */ - public function setBuilder(NodeBuilder $builder) + public function setBuilder(NodeBuilder $builder): void { $this->nodeBuilder = $builder; } @@ -76,6 +73,11 @@ public function scalarPrototype(): ScalarNodeDefinition return $this->prototype('scalar'); } + public function stringPrototype(): StringNodeDefinition + { + return $this->prototype('string'); + } + public function booleanPrototype(): BooleanNodeDefinition { return $this->prototype('boolean'); @@ -374,7 +376,7 @@ protected function createNode(): NodeInterface if ($this->default) { if (!\is_array($this->defaultValue)) { - throw new \InvalidArgumentException(sprintf('%s: the default value of an array node has to be an array.', $node->getPath())); + throw new \InvalidArgumentException(\sprintf('%s: the default value of an array node has to be an array.', $node->getPath())); } $node->setDefaultValue($this->defaultValue); @@ -425,61 +427,57 @@ protected function createNode(): NodeInterface /** * Validate the configuration of a concrete node. * - * @return void - * * @throws InvalidDefinitionException */ - protected function validateConcreteNode(ArrayNode $node) + protected function validateConcreteNode(ArrayNode $node): void { $path = $node->getPath(); if (null !== $this->key) { - throw new InvalidDefinitionException(sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s".', $path)); + throw new InvalidDefinitionException(\sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s".', $path)); } if (false === $this->allowEmptyValue) { - throw new InvalidDefinitionException(sprintf('->cannotBeEmpty() is not applicable to concrete nodes at path "%s".', $path)); + throw new InvalidDefinitionException(\sprintf('->cannotBeEmpty() is not applicable to concrete nodes at path "%s".', $path)); } if (true === $this->atLeastOne) { - throw new InvalidDefinitionException(sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s".', $path)); + throw new InvalidDefinitionException(\sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s".', $path)); } if ($this->default) { - throw new InvalidDefinitionException(sprintf('->defaultValue() is not applicable to concrete nodes at path "%s".', $path)); + throw new InvalidDefinitionException(\sprintf('->defaultValue() is not applicable to concrete nodes at path "%s".', $path)); } if (false !== $this->addDefaultChildren) { - throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s".', $path)); + throw new InvalidDefinitionException(\sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s".', $path)); } } /** * Validate the configuration of a prototype node. * - * @return void - * * @throws InvalidDefinitionException */ - protected function validatePrototypeNode(PrototypedArrayNode $node) + protected function validatePrototypeNode(PrototypedArrayNode $node): void { $path = $node->getPath(); if ($this->addDefaults) { - throw new InvalidDefinitionException(sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s".', $path)); + throw new InvalidDefinitionException(\sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s".', $path)); } if (false !== $this->addDefaultChildren) { if ($this->default) { - throw new InvalidDefinitionException(sprintf('A default value and default children might not be used together at path "%s".', $path)); + throw new InvalidDefinitionException(\sprintf('A default value and default children might not be used together at path "%s".', $path)); } if (null !== $this->key && (null === $this->addDefaultChildren || \is_int($this->addDefaultChildren) && $this->addDefaultChildren > 0)) { - throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() should set default children names as ->useAttributeAsKey() is used at path "%s".', $path)); + throw new InvalidDefinitionException(\sprintf('->addDefaultChildrenIfNoneSet() should set default children names as ->useAttributeAsKey() is used at path "%s".', $path)); } if (null === $this->key && (\is_string($this->addDefaultChildren) || \is_array($this->addDefaultChildren))) { - throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() might not set default children names as ->useAttributeAsKey() is not used at path "%s".', $path)); + throw new InvalidDefinitionException(\sprintf('->addDefaultChildrenIfNoneSet() might not set default children names as ->useAttributeAsKey() is not used at path "%s".', $path)); } } } @@ -504,7 +502,7 @@ public function find(string $nodePath): NodeDefinition : substr($nodePath, 0, $pathSeparatorPos); if (null === $node = ($this->children[$firstPathSegment] ?? null)) { - throw new \RuntimeException(sprintf('Node with name "%s" does not exist in the current node "%s".', $firstPathSegment, $this->name)); + throw new \RuntimeException(\sprintf('Node with name "%s" does not exist in the current node "%s".', $firstPathSegment, $this->name)); } if (false === $pathSeparatorPos) { diff --git a/Definition/Builder/BooleanNodeDefinition.php b/Definition/Builder/BooleanNodeDefinition.php index 15e63961a..bc03c9c94 100644 --- a/Definition/Builder/BooleanNodeDefinition.php +++ b/Definition/Builder/BooleanNodeDefinition.php @@ -33,7 +33,7 @@ public function __construct(?string $name, ?NodeParentInterface $parent = null) */ protected function instantiateNode(): BooleanNode { - return new BooleanNode($this->name, $this->parent, $this->pathSeparator); + return new BooleanNode($this->name, $this->parent, $this->pathSeparator, null === $this->nullEquivalent); } /** @@ -43,4 +43,20 @@ public function cannotBeEmpty(): static { throw new InvalidDefinitionException('->cannotBeEmpty() is not applicable to BooleanNodeDefinition.'); } + + public function defaultNull(): static + { + $this->nullEquivalent = null; + + return parent::defaultNull(); + } + + public function defaultValue(mixed $value): static + { + if (null === $value) { + $this->nullEquivalent = null; + } + + return parent::defaultValue($value); + } } diff --git a/Definition/Builder/BuilderAwareInterface.php b/Definition/Builder/BuilderAwareInterface.php index bb40307e1..cf646a140 100644 --- a/Definition/Builder/BuilderAwareInterface.php +++ b/Definition/Builder/BuilderAwareInterface.php @@ -20,8 +20,6 @@ interface BuilderAwareInterface { /** * Sets a custom children builder. - * - * @return void */ - public function setBuilder(NodeBuilder $builder); + public function setBuilder(NodeBuilder $builder): void; } diff --git a/Definition/Builder/ExprBuilder.php b/Definition/Builder/ExprBuilder.php index 93cdb49dd..e802df2ee 100644 --- a/Definition/Builder/ExprBuilder.php +++ b/Definition/Builder/ExprBuilder.php @@ -26,15 +26,13 @@ class ExprBuilder public const TYPE_NULL = 'null'; public const TYPE_ARRAY = 'array'; - protected $node; + public string $allowedTypes; + public ?\Closure $ifPart = null; + public ?\Closure $thenPart = null; - public $allowedTypes; - public $ifPart; - public $thenPart; - - public function __construct(NodeDefinition $node) - { - $this->node = $node; + public function __construct( + protected NodeDefinition $node, + ) { } /** @@ -102,7 +100,7 @@ public function ifNull(): static */ public function ifEmpty(): static { - $this->ifPart = static fn ($v) => empty($v); + $this->ifPart = static fn ($v) => !$v; $this->allowedTypes = self::TYPE_ANY; return $this; @@ -196,7 +194,7 @@ public function thenEmptyArray(): static */ public function thenInvalid(string $message): static { - $this->thenPart = static fn ($v) => throw new \InvalidArgumentException(sprintf($message, json_encode($v))); + $this->thenPart = static fn ($v) => throw new \InvalidArgumentException(\sprintf($message, json_encode($v))); return $this; } diff --git a/Definition/Builder/MergeBuilder.php b/Definition/Builder/MergeBuilder.php index f8980a6e0..b90865cfc 100644 --- a/Definition/Builder/MergeBuilder.php +++ b/Definition/Builder/MergeBuilder.php @@ -18,13 +18,12 @@ */ class MergeBuilder { - protected $node; - public $allowFalse = false; - public $allowOverwrite = true; + public bool $allowFalse = false; + public bool $allowOverwrite = true; - public function __construct(NodeDefinition $node) - { - $this->node = $node; + public function __construct( + protected NodeDefinition $node, + ) { } /** diff --git a/Definition/Builder/NodeBuilder.php b/Definition/Builder/NodeBuilder.php index 93069d437..b3fa7bfd5 100644 --- a/Definition/Builder/NodeBuilder.php +++ b/Definition/Builder/NodeBuilder.php @@ -18,8 +18,8 @@ */ class NodeBuilder implements NodeParentInterface { - protected $parent; - protected $nodeMapping; + protected (NodeDefinition&ParentNodeDefinitionInterface)|null $parent = null; + protected array $nodeMapping; public function __construct() { @@ -31,6 +31,7 @@ public function __construct() 'float' => FloatNodeDefinition::class, 'array' => ArrayNodeDefinition::class, 'enum' => EnumNodeDefinition::class, + 'string' => StringNodeDefinition::class, ]; } @@ -39,11 +40,8 @@ public function __construct() * * @return $this */ - public function setParent(?ParentNodeDefinitionInterface $parent = null): static + public function setParent((NodeDefinition&ParentNodeDefinitionInterface)|null $parent): static { - if (1 > \func_num_args()) { - trigger_deprecation('symfony/form', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); - } $this->parent = $parent; return $this; @@ -105,12 +103,18 @@ public function variableNode(string $name): VariableNodeDefinition return $this->node($name, 'variable'); } + /** + * Creates a child string node. + */ + public function stringNode(string $name): StringNodeDefinition + { + return $this->node($name, 'string'); + } + /** * Returns the parent node. - * - * @return NodeDefinition&ParentNodeDefinitionInterface */ - public function end() + public function end(): NodeDefinition&ParentNodeDefinitionInterface { return $this->parent; } @@ -190,13 +194,13 @@ protected function getNodeClass(string $type): string $type = strtolower($type); if (!isset($this->nodeMapping[$type])) { - throw new \RuntimeException(sprintf('The node type "%s" is not registered.', $type)); + throw new \RuntimeException(\sprintf('The node type "%s" is not registered.', $type)); } $class = $this->nodeMapping[$type]; if (!class_exists($class)) { - throw new \RuntimeException(sprintf('The node class "%s" does not exist.', $class)); + throw new \RuntimeException(\sprintf('The node class "%s" does not exist.', $class)); } return $class; diff --git a/Definition/Builder/NodeDefinition.php b/Definition/Builder/NodeDefinition.php index cf2173e17..54e976e24 100644 --- a/Definition/Builder/NodeDefinition.php +++ b/Definition/Builder/NodeDefinition.php @@ -22,21 +22,21 @@ */ abstract class NodeDefinition implements NodeParentInterface { - protected $name; - protected $normalization; - protected $validation; - protected $defaultValue; - protected $default = false; - protected $required = false; - protected $deprecation = []; - protected $merge; - protected $allowEmptyValue = true; - protected $nullEquivalent; - protected $trueEquivalent = true; - protected $falseEquivalent = false; - protected $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR; - protected $parent; - protected $attributes = []; + protected ?string $name = null; + protected NormalizationBuilder $normalization; + protected ValidationBuilder $validation; + protected mixed $defaultValue; + protected bool $default = false; + protected bool $required = false; + protected array $deprecation = []; + protected MergeBuilder $merge; + protected bool $allowEmptyValue = true; + protected mixed $nullEquivalent = null; + protected mixed $trueEquivalent = true; + protected mixed $falseEquivalent = false; + protected string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR; + protected NodeParentInterface|NodeInterface|null $parent; + protected array $attributes = []; public function __construct(?string $name, ?NodeParentInterface $parent = null) { @@ -90,8 +90,10 @@ public function attribute(string $key, mixed $value): static /** * Returns the parent node. + * + * @return NodeParentInterface|NodeBuilder|self|ArrayNodeDefinition|VariableNodeDefinition */ - public function end(): NodeParentInterface|NodeBuilder|self|ArrayNodeDefinition|VariableNodeDefinition|null + public function end(): NodeParentInterface { return $this->parent; } diff --git a/Definition/Builder/NormalizationBuilder.php b/Definition/Builder/NormalizationBuilder.php index 1f6b34441..8a8141c19 100644 --- a/Definition/Builder/NormalizationBuilder.php +++ b/Definition/Builder/NormalizationBuilder.php @@ -18,14 +18,13 @@ */ class NormalizationBuilder { - protected $node; - public $before = []; - public $declaredTypes = []; - public $remappings = []; + public array $before = []; + public array $declaredTypes = []; + public array $remappings = []; - public function __construct(NodeDefinition $node) - { - $this->node = $node; + public function __construct( + protected NodeDefinition $node, + ) { } /** diff --git a/Definition/Builder/NumericNodeDefinition.php b/Definition/Builder/NumericNodeDefinition.php index 890910c95..06dc97994 100644 --- a/Definition/Builder/NumericNodeDefinition.php +++ b/Definition/Builder/NumericNodeDefinition.php @@ -20,8 +20,8 @@ */ abstract class NumericNodeDefinition extends ScalarNodeDefinition { - protected $min; - protected $max; + protected int|float|null $min = null; + protected int|float|null $max = null; /** * Ensures that the value is smaller than the given reference. @@ -33,7 +33,7 @@ abstract class NumericNodeDefinition extends ScalarNodeDefinition public function max(int|float $max): static { if (isset($this->min) && $this->min > $max) { - throw new \InvalidArgumentException(sprintf('You cannot define a max(%s) as you already have a min(%s).', $max, $this->min)); + throw new \InvalidArgumentException(\sprintf('You cannot define a max(%s) as you already have a min(%s).', $max, $this->min)); } $this->max = $max; @@ -50,7 +50,7 @@ public function max(int|float $max): static public function min(int|float $min): static { if (isset($this->max) && $this->max < $min) { - throw new \InvalidArgumentException(sprintf('You cannot define a min(%s) as you already have a max(%s).', $min, $this->max)); + throw new \InvalidArgumentException(\sprintf('You cannot define a min(%s) as you already have a max(%s).', $min, $this->max)); } $this->min = $min; diff --git a/Definition/Builder/StringNodeDefinition.php b/Definition/Builder/StringNodeDefinition.php new file mode 100644 index 000000000..c86f1bfd7 --- /dev/null +++ b/Definition/Builder/StringNodeDefinition.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\StringNode; + +/** + * This class provides a fluent interface for defining a node. + * + * @author Raffaele Carelle + */ +class StringNodeDefinition extends ScalarNodeDefinition +{ + public function __construct(?string $name, ?NodeParentInterface $parent = null) + { + parent::__construct($name, $parent); + + $this->nullEquivalent = ''; + } + + protected function instantiateNode(): StringNode + { + return new StringNode($this->name, $this->parent, $this->pathSeparator); + } +} diff --git a/Definition/Builder/TreeBuilder.php b/Definition/Builder/TreeBuilder.php index f7da3e794..5170e19d1 100644 --- a/Definition/Builder/TreeBuilder.php +++ b/Definition/Builder/TreeBuilder.php @@ -20,15 +20,8 @@ */ class TreeBuilder implements NodeParentInterface { - /** - * @var NodeInterface|null - */ - protected $tree; - - /** - * @var NodeDefinition - */ - protected $root; + protected ?NodeInterface $tree = null; + protected ?NodeDefinition $root = null; public function __construct(string $name, string $type = 'array', ?NodeBuilder $builder = null) { @@ -54,10 +47,7 @@ public function buildTree(): NodeInterface return $this->tree ??= $this->root->getNode(true); } - /** - * @return void - */ - public function setPathSeparator(string $separator) + public function setPathSeparator(string $separator): void { // unset last built as changing path separator changes all nodes $this->tree = null; diff --git a/Definition/Builder/ValidationBuilder.php b/Definition/Builder/ValidationBuilder.php index 64623d6d6..ad2239349 100644 --- a/Definition/Builder/ValidationBuilder.php +++ b/Definition/Builder/ValidationBuilder.php @@ -18,12 +18,11 @@ */ class ValidationBuilder { - protected $node; - public $rules = []; + public array $rules = []; - public function __construct(NodeDefinition $node) - { - $this->node = $node; + public function __construct( + protected NodeDefinition $node, + ) { } /** diff --git a/Definition/ConfigurationInterface.php b/Definition/ConfigurationInterface.php index 7b5d443fe..97a325bb6 100644 --- a/Definition/ConfigurationInterface.php +++ b/Definition/ConfigurationInterface.php @@ -22,8 +22,6 @@ interface ConfigurationInterface { /** * Generates the configuration tree builder. - * - * @return TreeBuilder */ - public function getConfigTreeBuilder(); + public function getConfigTreeBuilder(): TreeBuilder; } diff --git a/Definition/Dumper/XmlReferenceDumper.php b/Definition/Dumper/XmlReferenceDumper.php index aac2d8456..2131c7131 100644 --- a/Definition/Dumper/XmlReferenceDumper.php +++ b/Definition/Dumper/XmlReferenceDumper.php @@ -31,18 +31,12 @@ class XmlReferenceDumper { private ?string $reference = null; - /** - * @return string - */ - public function dump(ConfigurationInterface $configuration, ?string $namespace = null) + public function dump(ConfigurationInterface $configuration, ?string $namespace = null): string { return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace); } - /** - * @return string - */ - public function dumpNode(NodeInterface $node, ?string $namespace = null) + public function dumpNode(NodeInterface $node, ?string $namespace = null): string { $this->reference = ''; $this->writeNode($node, 0, true, $namespace); @@ -151,7 +145,7 @@ private function writeNode(NodeInterface $node, int $depth = 0, bool $root = fal if ($child instanceof BaseNode && $child->isDeprecated()) { $deprecation = $child->getDeprecation($child->getName(), $node->getPath()); - $comments[] = sprintf('Deprecated (%s)', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '').$deprecation['message']); + $comments[] = \sprintf('Deprecated (%s)', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '').$deprecation['message']); } if ($child instanceof EnumNode) { @@ -205,7 +199,7 @@ private function writeNode(NodeInterface $node, int $depth = 0, bool $root = fal $rootOpenTag = '<'.$rootName; if (1 >= ($attributesCount = \count($rootAttributes))) { if (1 === $attributesCount) { - $rootOpenTag .= sprintf(' %s="%s"', current(array_keys($rootAttributes)), $this->writeValue(current($rootAttributes))); + $rootOpenTag .= \sprintf(' %s="%s"', current(array_keys($rootAttributes)), $this->writeValue(current($rootAttributes))); } $rootOpenTag .= $rootIsEmptyTag ? ' />' : '>'; @@ -221,7 +215,7 @@ private function writeNode(NodeInterface $node, int $depth = 0, bool $root = fal $i = 1; foreach ($rootAttributes as $attrName => $attrValue) { - $attr = sprintf('%s="%s"', $attrName, $this->writeValue($attrValue)); + $attr = \sprintf('%s="%s"', $attrName, $this->writeValue($attrValue)); $this->writeLine($attr, $depth + 4); @@ -258,7 +252,7 @@ private function writeLine(string $text, int $indent = 0): void $indent = \strlen($text) + $indent; $format = '%'.$indent.'s'; - $this->reference .= sprintf($format, $text).\PHP_EOL; + $this->reference .= \sprintf($format, $text).\PHP_EOL; } /** @@ -286,7 +280,7 @@ private function writeValue(mixed $value): string return 'null'; } - if (empty($value)) { + if (!$value) { return ''; } diff --git a/Definition/Dumper/YamlReferenceDumper.php b/Definition/Dumper/YamlReferenceDumper.php index abcf1bd9e..267444d92 100644 --- a/Definition/Dumper/YamlReferenceDumper.php +++ b/Definition/Dumper/YamlReferenceDumper.php @@ -29,24 +29,18 @@ class YamlReferenceDumper { private ?string $reference = null; - /** - * @return string - */ - public function dump(ConfigurationInterface $configuration) + public function dump(ConfigurationInterface $configuration): string { return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree()); } - /** - * @return string - */ - public function dumpAtPath(ConfigurationInterface $configuration, string $path) + public function dumpAtPath(ConfigurationInterface $configuration, string $path): string { $rootNode = $node = $configuration->getConfigTreeBuilder()->buildTree(); foreach (explode('.', $path) as $step) { if (!$node instanceof ArrayNode) { - throw new \UnexpectedValueException(sprintf('Unable to find node at path "%s.%s".', $rootNode->getName(), $path)); + throw new \UnexpectedValueException(\sprintf('Unable to find node at path "%s.%s".', $rootNode->getName(), $path)); } /** @var NodeInterface[] $children */ @@ -60,16 +54,13 @@ public function dumpAtPath(ConfigurationInterface $configuration, string $path) } } - throw new \UnexpectedValueException(sprintf('Unable to find node at path "%s.%s".', $rootNode->getName(), $path)); + throw new \UnexpectedValueException(\sprintf('Unable to find node at path "%s.%s".', $rootNode->getName(), $path)); } return $this->dumpNode($node); } - /** - * @return string - */ - public function dumpNode(NodeInterface $node) + public function dumpNode(NodeInterface $node): string { $this->reference = ''; $this->writeNode($node); @@ -130,7 +121,7 @@ private function writeNode(NodeInterface $node, ?NodeInterface $parentNode = nul // deprecated? if ($node instanceof BaseNode && $node->isDeprecated()) { $deprecation = $node->getDeprecation($node->getName(), $parentNode ? $parentNode->getPath() : $node->getPath()); - $comments[] = sprintf('Deprecated (%s)', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '').$deprecation['message']); + $comments[] = \sprintf('Deprecated (%s)', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '').$deprecation['message']); } // example @@ -142,12 +133,12 @@ private function writeNode(NodeInterface $node, ?NodeInterface $parentNode = nul $comments = \count($comments) ? '# '.implode(', ', $comments) : ''; $key = $prototypedArray ? '-' : $node->getName().':'; - $text = rtrim(sprintf('%-21s%s %s', $key, $default, $comments), ' '); + $text = rtrim(\sprintf('%-21s%s %s', $key, $default, $comments), ' '); if ($node instanceof BaseNode && $info = $node->getInfo()) { $this->writeLine(''); // indenting multi-line info - $info = str_replace("\n", sprintf("\n%".($depth * 4).'s# ', ' '), $info); + $info = str_replace("\n", \sprintf("\n%".($depth * 4).'s# ', ' '), $info); $this->writeLine('# '.$info, $depth * 4); } @@ -189,7 +180,7 @@ private function writeLine(string $text, int $indent = 0): void $indent = \strlen($text) + $indent; $format = '%'.$indent.'s'; - $this->reference .= sprintf($format, $text)."\n"; + $this->reference .= \sprintf($format, $text)."\n"; } private function writeArray(array $array, int $depth, bool $asComment = false): void @@ -208,7 +199,7 @@ private function writeArray(array $array, int $depth, bool $asComment = false): if ($isIndexed) { $this->writeLine($prefix.'- '.$val, $depth * 4); } else { - $this->writeLine(sprintf('%s%-20s %s', $prefix, $key.':', $val), $depth * 4); + $this->writeLine(\sprintf('%s%-20s %s', $prefix, $key.':', $val), $depth * 4); } if (\is_array($value)) { diff --git a/Definition/EnumNode.php b/Definition/EnumNode.php index f5acbe906..b253406c8 100644 --- a/Definition/EnumNode.php +++ b/Definition/EnumNode.php @@ -34,11 +34,11 @@ public function __construct(?string $name, ?NodeInterface $parent = null, array } if (!$value instanceof \UnitEnum) { - throw new \InvalidArgumentException(sprintf('"%s" only supports scalar, enum, or null values, "%s" given.', __CLASS__, get_debug_type($value))); + throw new \InvalidArgumentException(\sprintf('"%s" only supports scalar, enum, or null values, "%s" given.', __CLASS__, get_debug_type($value))); } if ($value::class !== ($enumClass ??= $value::class)) { - throw new \InvalidArgumentException(sprintf('"%s" only supports one type of enum, "%s" and "%s" passed.', __CLASS__, $enumClass, $value::class)); + throw new \InvalidArgumentException(\sprintf('"%s" only supports one type of enum, "%s" and "%s" passed.', __CLASS__, $enumClass, $value::class)); } } @@ -46,10 +46,7 @@ public function __construct(?string $name, ?NodeInterface $parent = null, array $this->values = $values; } - /** - * @return array - */ - public function getValues() + public function getValues(): array { return $this->values; } @@ -68,10 +65,7 @@ public function getPermissibleValues(string $separator): string }, $this->values))); } - /** - * @return void - */ - protected function validateType(mixed $value) + protected function validateType(mixed $value): void { if ($value instanceof \UnitEnum) { return; @@ -85,7 +79,7 @@ protected function finalizeValue(mixed $value): mixed $value = parent::finalizeValue($value); if (!\in_array($value, $this->values, true)) { - $ex = new InvalidConfigurationException(sprintf('The value %s is not allowed for path "%s". Permissible values: %s', json_encode($value), $this->getPath(), $this->getPermissibleValues(', '))); + $ex = new InvalidConfigurationException(\sprintf('The value %s is not allowed for path "%s". Permissible values: %s', json_encode($value), $this->getPath(), $this->getPermissibleValues(', '))); $ex->setPath($this->getPath()); throw $ex; diff --git a/Definition/Exception/InvalidConfigurationException.php b/Definition/Exception/InvalidConfigurationException.php index 794447bf8..e3f29f8e6 100644 --- a/Definition/Exception/InvalidConfigurationException.php +++ b/Definition/Exception/InvalidConfigurationException.php @@ -22,10 +22,7 @@ class InvalidConfigurationException extends Exception private ?string $path = null; private bool $containsHints = false; - /** - * @return void - */ - public function setPath(string $path) + public function setPath(string $path): void { $this->path = $path; } @@ -37,10 +34,8 @@ public function getPath(): ?string /** * Adds extra information that is suffixed to the original exception message. - * - * @return void */ - public function addHint(string $hint) + public function addHint(string $hint): void { if (!$this->containsHints) { $this->message .= "\nHint: ".$hint; diff --git a/Definition/FloatNode.php b/Definition/FloatNode.php index ce4193e09..8b922804b 100644 --- a/Definition/FloatNode.php +++ b/Definition/FloatNode.php @@ -20,10 +20,7 @@ */ class FloatNode extends NumericNode { - /** - * @return void - */ - protected function validateType(mixed $value) + protected function validateType(mixed $value): void { // Integers are also accepted, we just cast them if (\is_int($value)) { @@ -31,7 +28,7 @@ protected function validateType(mixed $value) } if (!\is_float($value)) { - $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "float", but got "%s".', $this->getPath(), get_debug_type($value))); + $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "float", but got "%s".', $this->getPath(), get_debug_type($value))); if ($hint = $this->getInfo()) { $ex->addHint($hint); } diff --git a/Definition/IntegerNode.php b/Definition/IntegerNode.php index 4a3e3253c..6fa3e6032 100644 --- a/Definition/IntegerNode.php +++ b/Definition/IntegerNode.php @@ -20,13 +20,10 @@ */ class IntegerNode extends NumericNode { - /** - * @return void - */ - protected function validateType(mixed $value) + protected function validateType(mixed $value): void { if (!\is_int($value)) { - $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "int", but got "%s".', $this->getPath(), get_debug_type($value))); + $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "int", but got "%s".', $this->getPath(), get_debug_type($value))); if ($hint = $this->getInfo()) { $ex->addHint($hint); } diff --git a/Definition/Loader/DefinitionFileLoader.php b/Definition/Loader/DefinitionFileLoader.php index 940b894f7..09147f791 100644 --- a/Definition/Loader/DefinitionFileLoader.php +++ b/Definition/Loader/DefinitionFileLoader.php @@ -81,7 +81,7 @@ private function executeCallback(callable $callback, DefinitionConfigurator $con $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").', $parameter->getName(), $path, DefinitionConfigurator::class)); + throw new \InvalidArgumentException(\sprintf('Could not resolve argument "$%s" for "%s". You must typehint it (for example with "%s").', $parameter->getName(), $path, DefinitionConfigurator::class)); } $arguments[] = match ($reflectionType->getName()) { diff --git a/Definition/NumericNode.php b/Definition/NumericNode.php index 22359fd25..a97850c9d 100644 --- a/Definition/NumericNode.php +++ b/Definition/NumericNode.php @@ -20,14 +20,14 @@ */ class NumericNode extends ScalarNode { - protected $min; - protected $max; - - public function __construct(?string $name, ?NodeInterface $parent = null, int|float|null $min = null, int|float|null $max = null, string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR) - { + public function __construct( + ?string $name, + ?NodeInterface $parent = null, + protected int|float|null $min = null, + protected int|float|null $max = null, + string $pathSeparator = BaseNode::DEFAULT_PATH_SEPARATOR, + ) { parent::__construct($name, $parent, $pathSeparator); - $this->min = $min; - $this->max = $max; } protected function finalizeValue(mixed $value): mixed @@ -36,10 +36,10 @@ protected function finalizeValue(mixed $value): mixed $errorMsg = null; if (isset($this->min) && $value < $this->min) { - $errorMsg = sprintf('The value %s is too small for path "%s". Should be greater than or equal to %s', $value, $this->getPath(), $this->min); + $errorMsg = \sprintf('The value %s is too small for path "%s". Should be greater than or equal to %s', $value, $this->getPath(), $this->min); } if (isset($this->max) && $value > $this->max) { - $errorMsg = sprintf('The value %s is too big for path "%s". Should be less than or equal to %s', $value, $this->getPath(), $this->max); + $errorMsg = \sprintf('The value %s is too big for path "%s". Should be less than or equal to %s', $value, $this->getPath(), $this->max); } if (isset($errorMsg)) { $ex = new InvalidConfigurationException($errorMsg); diff --git a/Definition/PrototypeNodeInterface.php b/Definition/PrototypeNodeInterface.php index 9dce7444b..109889f37 100644 --- a/Definition/PrototypeNodeInterface.php +++ b/Definition/PrototypeNodeInterface.php @@ -20,8 +20,6 @@ interface PrototypeNodeInterface extends NodeInterface { /** * Sets the name of the node. - * - * @return void */ - public function setName(string $name); + public function setName(string $name): void; } diff --git a/Definition/PrototypedArrayNode.php b/Definition/PrototypedArrayNode.php index c105ac1f3..a901dab78 100644 --- a/Definition/PrototypedArrayNode.php +++ b/Definition/PrototypedArrayNode.php @@ -23,12 +23,12 @@ */ class PrototypedArrayNode extends ArrayNode { - protected $prototype; - protected $keyAttribute; - protected $removeKeyAttribute = false; - protected $minNumberOfElements = 0; - protected $defaultValue = []; - protected $defaultChildren; + protected PrototypeNodeInterface $prototype; + protected ?string $keyAttribute = null; + protected bool $removeKeyAttribute = false; + protected int $minNumberOfElements = 0; + protected array $defaultValue = []; + protected ?array $defaultChildren = null; /** * @var NodeInterface[] An array of the prototypes of the simplified value children */ @@ -37,10 +37,8 @@ class PrototypedArrayNode extends ArrayNode /** * Sets the minimum number of elements that a prototype based node must * contain. By default this is zero, meaning no elements. - * - * @return void */ - public function setMinNumberOfElements(int $number) + public function setMinNumberOfElements(int $number): void { $this->minNumberOfElements = $number; } @@ -68,10 +66,8 @@ public function setMinNumberOfElements(int $number) * * @param string $attribute The name of the attribute which value is to be used as a key * @param bool $remove Whether or not to remove the key - * - * @return void */ - public function setKeyAttribute(string $attribute, bool $remove = true) + public function setKeyAttribute(string $attribute, bool $remove = true): void { $this->keyAttribute = $attribute; $this->removeKeyAttribute = $remove; @@ -87,10 +83,8 @@ public function getKeyAttribute(): ?string /** * Sets the default value of this node. - * - * @return void */ - public function setDefaultValue(array $value) + public function setDefaultValue(array $value): void { $this->defaultValue = $value; } @@ -104,10 +98,8 @@ public function hasDefaultValue(): bool * Adds default children when none are set. * * @param int|string|array|null $children The number of children|The child name|The children names to be added - * - * @return void */ - public function setAddChildrenIfNoneSet(int|string|array|null $children = ['defaults']) + public function setAddChildrenIfNoneSet(int|string|array|null $children = ['defaults']): void { if (null === $children) { $this->defaultChildren = ['defaults']; @@ -137,10 +129,8 @@ public function getDefaultValue(): mixed /** * Sets the node prototype. - * - * @return void */ - public function setPrototype(PrototypeNodeInterface $node) + public function setPrototype(PrototypeNodeInterface $node): void { $this->prototype = $node; } @@ -156,11 +146,9 @@ public function getPrototype(): PrototypeNodeInterface /** * Disable adding concrete children for prototyped nodes. * - * @return never - * * @throws Exception */ - public function addChild(NodeInterface $node) + public function addChild(NodeInterface $node): never { throw new Exception('A prototyped array node cannot have concrete children.'); } @@ -168,7 +156,7 @@ public function addChild(NodeInterface $node) protected function finalizeValue(mixed $value): mixed { if (false === $value) { - throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s.', $this->getPath(), json_encode($value))); + throw new UnsetKeyException(\sprintf('Unsetting key for path "%s", value: false.', $this->getPath())); } foreach ($value as $k => $v) { @@ -181,7 +169,7 @@ protected function finalizeValue(mixed $value): mixed } if (\count($value) < $this->minNumberOfElements) { - $ex = new InvalidConfigurationException(sprintf('The path "%s" should have at least %d element(s) defined.', $this->getPath(), $this->minNumberOfElements)); + $ex = new InvalidConfigurationException(\sprintf('The path "%s" should have at least %d element(s) defined.', $this->getPath(), $this->minNumberOfElements)); $ex->setPath($this->getPath()); throw $ex; @@ -206,7 +194,7 @@ protected function normalizeValue(mixed $value): mixed foreach ($value as $k => $v) { if (null !== $this->keyAttribute && \is_array($v)) { if (!isset($v[$this->keyAttribute]) && \is_int($k) && $isList) { - $ex = new InvalidConfigurationException(sprintf('The attribute "%s" must be set for path "%s".', $this->keyAttribute, $this->getPath())); + $ex = new InvalidConfigurationException(\sprintf('The attribute "%s" must be set for path "%s".', $this->keyAttribute, $this->getPath())); $ex->setPath($this->getPath()); throw $ex; @@ -229,17 +217,15 @@ protected function normalizeValue(mixed $value): mixed $valuePrototype = current($this->valuePrototypes) ?: clone $children['value']; $valuePrototype->parent = $this; $originalClosures = $this->prototype->normalizationClosures; - if (\is_array($originalClosures)) { - $valuePrototypeClosures = $valuePrototype->normalizationClosures; - $valuePrototype->normalizationClosures = \is_array($valuePrototypeClosures) ? array_merge($originalClosures, $valuePrototypeClosures) : $originalClosures; - } + $valuePrototypeClosures = $valuePrototype->normalizationClosures; + $valuePrototype->normalizationClosures = array_merge($originalClosures, $valuePrototypeClosures); $this->valuePrototypes[$k] = $valuePrototype; } } } if (\array_key_exists($k, $normalized)) { - $ex = new DuplicateKeyException(sprintf('Duplicate key "%s" for path "%s".', $k, $this->getPath())); + $ex = new DuplicateKeyException(\sprintf('Duplicate key "%s" for path "%s".', $k, $this->getPath())); $ex->setPath($this->getPath()); throw $ex; @@ -280,7 +266,7 @@ protected function mergeValues(mixed $leftSide, mixed $rightSide): mixed // no conflict if (!\array_key_exists($k, $leftSide)) { if (!$this->allowNewKeys) { - $ex = new InvalidConfigurationException(sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file.', $this->getPath())); + $ex = new InvalidConfigurationException(\sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file.', $this->getPath())); $ex->setPath($this->getPath()); throw $ex; diff --git a/Definition/ScalarNode.php b/Definition/ScalarNode.php index e11fa1ee1..341769249 100644 --- a/Definition/ScalarNode.php +++ b/Definition/ScalarNode.php @@ -27,13 +27,10 @@ */ class ScalarNode extends VariableNode { - /** - * @return void - */ - protected function validateType(mixed $value) + protected function validateType(mixed $value): void { if (!\is_scalar($value) && null !== $value) { - $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected "scalar", but got "%s".', $this->getPath(), get_debug_type($value))); + $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "scalar", but got "%s".', $this->getPath(), get_debug_type($value))); if ($hint = $this->getInfo()) { $ex->addHint($hint); } diff --git a/Definition/StringNode.php b/Definition/StringNode.php new file mode 100644 index 000000000..6687b8825 --- /dev/null +++ b/Definition/StringNode.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; + +/** + * This node represents a String value in the config tree. + * + * @author Raffaele Carelle + */ +class StringNode extends ScalarNode +{ + protected function validateType(mixed $value): void + { + if (!\is_string($value)) { + $ex = new InvalidTypeException(\sprintf('Invalid type for path "%s". Expected "string", but got "%s".', $this->getPath(), get_debug_type($value))); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + } + + protected function getValidPlaceholderTypes(): array + { + return ['string']; + } +} diff --git a/Definition/VariableNode.php b/Definition/VariableNode.php index 6bdc65b4e..ed1b903a1 100644 --- a/Definition/VariableNode.php +++ b/Definition/VariableNode.php @@ -23,14 +23,11 @@ */ class VariableNode extends BaseNode implements PrototypeNodeInterface { - protected $defaultValueSet = false; - protected $defaultValue; - protected $allowEmptyValue = true; + protected bool $defaultValueSet = false; + protected mixed $defaultValue = null; + protected bool $allowEmptyValue = true; - /** - * @return void - */ - public function setDefaultValue(mixed $value) + public function setDefaultValue(mixed $value): void { $this->defaultValueSet = true; $this->defaultValue = $value; @@ -52,26 +49,18 @@ public function getDefaultValue(): mixed * Sets if this node is allowed to have an empty value. * * @param bool $boolean True if this entity will accept empty values - * - * @return void */ - public function setAllowEmptyValue(bool $boolean) + public function setAllowEmptyValue(bool $boolean): void { $this->allowEmptyValue = $boolean; } - /** - * @return void - */ - public function setName(string $name) + public function setName(string $name): void { $this->name = $name; } - /** - * @return void - */ - protected function validateType(mixed $value) + protected function validateType(mixed $value): void { } @@ -80,7 +69,7 @@ protected function finalizeValue(mixed $value): mixed // deny environment variables only when using custom validators // this avoids ever passing an empty value to final validation closures if (!$this->allowEmptyValue && $this->isHandlingPlaceholder() && $this->finalValidationClosures) { - $e = new InvalidConfigurationException(sprintf('The path "%s" cannot contain an environment variable when empty values are not allowed by definition and are validated.', $this->getPath())); + $e = new InvalidConfigurationException(\sprintf('The path "%s" cannot contain an environment variable when empty values are not allowed by definition and are validated.', $this->getPath())); if ($hint = $this->getInfo()) { $e->addHint($hint); } @@ -90,7 +79,7 @@ protected function finalizeValue(mixed $value): mixed } if (!$this->allowEmptyValue && $this->isValueEmpty($value)) { - $ex = new InvalidConfigurationException(sprintf('The path "%s" cannot contain an empty value, but got %s.', $this->getPath(), json_encode($value))); + $ex = new InvalidConfigurationException(\sprintf('The path "%s" cannot contain an empty value, but got %s.', $this->getPath(), json_encode($value))); if ($hint = $this->getInfo()) { $ex->addHint($hint); } @@ -123,6 +112,6 @@ protected function mergeValues(mixed $leftSide, mixed $rightSide): mixed */ protected function isValueEmpty(mixed $value): bool { - return empty($value); + return !$value; } } diff --git a/Exception/FileLoaderImportCircularReferenceException.php b/Exception/FileLoaderImportCircularReferenceException.php index 2d2a4de00..d39bde046 100644 --- a/Exception/FileLoaderImportCircularReferenceException.php +++ b/Exception/FileLoaderImportCircularReferenceException.php @@ -20,7 +20,7 @@ class FileLoaderImportCircularReferenceException extends LoaderLoadException { public function __construct(array $resources, int $code = 0, ?\Throwable $previous = null) { - $message = sprintf('Circular reference detected in "%s" ("%s" > "%s").', $this->varToString($resources[0]), implode('" > "', $resources), $resources[0]); + $message = \sprintf('Circular reference detected in "%s" ("%s" > "%s").', $this->varToString($resources[0]), implode('" > "', $resources), $resources[0]); \Exception::__construct($message, $code, $previous); } diff --git a/Exception/FileLocatorFileNotFoundException.php b/Exception/FileLocatorFileNotFoundException.php index a3fcc901b..d9e5b4f36 100644 --- a/Exception/FileLocatorFileNotFoundException.php +++ b/Exception/FileLocatorFileNotFoundException.php @@ -18,19 +18,16 @@ */ class FileLocatorFileNotFoundException extends \InvalidArgumentException { - private array $paths; - - public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null, array $paths = []) - { + public function __construct( + string $message = '', + int $code = 0, + ?\Throwable $previous = null, + private array $paths = [], + ) { parent::__construct($message, $code, $previous); - - $this->paths = $paths; } - /** - * @return array - */ - public function getPaths() + public function getPaths(): array { return $this->paths; } diff --git a/Exception/LoaderLoadException.php b/Exception/LoaderLoadException.php index 2b40688a5..ce486a35f 100644 --- a/Exception/LoaderLoadException.php +++ b/Exception/LoaderLoadException.php @@ -31,7 +31,7 @@ public function __construct(mixed $resource, ?string $sourceResource = null, int try { $resource = json_encode($resource, \JSON_THROW_ON_ERROR); } catch (\JsonException) { - $resource = sprintf('resource of type "%s"', get_debug_type($resource)); + $resource = \sprintf('resource of type "%s"', get_debug_type($resource)); } } @@ -42,60 +42,57 @@ public function __construct(mixed $resource, ?string $sourceResource = null, int // Trim the trailing period of the previous message. We only want 1 period remove so no rtrim... if (str_ends_with($previous->getMessage(), '.')) { $trimmedMessage = substr($previous->getMessage(), 0, -1); - $message .= sprintf('%s', $trimmedMessage).' in '; + $message .= \sprintf('%s', $trimmedMessage).' in '; } else { - $message .= sprintf('%s', $previous->getMessage()).' in '; + $message .= \sprintf('%s', $previous->getMessage()).' in '; } $message .= $resource.' '; // show tweaked trace to complete the human readable sentence if (null === $sourceResource) { - $message .= sprintf('(which is loaded in resource "%s")', $resource); + $message .= \sprintf('(which is loaded in resource "%s")', $resource); } else { - $message .= sprintf('(which is being imported from "%s")', $sourceResource); + $message .= \sprintf('(which is being imported from "%s")', $sourceResource); } $message .= '.'; // if there's no previous message, present it the default way } elseif (null === $sourceResource) { - $message .= sprintf('Cannot load resource "%s".', $resource); + $message .= \sprintf('Cannot load resource "%s".', $resource); } else { - $message .= sprintf('Cannot import resource "%s" from "%s".', $resource, $sourceResource); + $message .= \sprintf('Cannot import resource "%s" from "%s".', $resource, $sourceResource); } // Is the resource located inside a bundle? if ('@' === $resource[0]) { $parts = explode(\DIRECTORY_SEPARATOR, $resource); $bundle = substr($parts[0], 1); - $message .= sprintf(' Make sure the "%s" bundle is correctly registered and loaded in the application kernel class.', $bundle); - $message .= sprintf(' If the bundle is registered, make sure the bundle path "%s" is not empty.', $resource); + $message .= \sprintf(' Make sure the "%s" bundle is correctly registered and loaded in the application kernel class.', $bundle); + $message .= \sprintf(' If the bundle is registered, make sure the bundle path "%s" is not empty.', $resource); } elseif (null !== $type) { - $message .= sprintf(' Make sure there is a loader supporting the "%s" type.', $type); + $message .= \sprintf(' Make sure there is a loader supporting the "%s" type.', $type); } parent::__construct($message, $code, $previous); } - /** - * @return string - */ - protected function varToString(mixed $var) + protected function varToString(mixed $var): string { if (\is_object($var)) { - return sprintf('Object(%s)', $var::class); + return \sprintf('Object(%s)', $var::class); } if (\is_array($var)) { $a = []; foreach ($var as $k => $v) { - $a[] = sprintf('%s => %s', $k, $this->varToString($v)); + $a[] = \sprintf('%s => %s', $k, $this->varToString($v)); } - return sprintf('Array(%s)', implode(', ', $a)); + return \sprintf('Array(%s)', implode(', ', $a)); } if (\is_resource($var)) { - return sprintf('Resource(%s)', get_resource_type($var)); + return \sprintf('Resource(%s)', get_resource_type($var)); } if (null === $var) { diff --git a/Exception/LogicException.php b/Exception/LogicException.php new file mode 100644 index 000000000..d227aece4 --- /dev/null +++ b/Exception/LogicException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Exception; + +class LogicException extends \LogicException +{ +} diff --git a/FileLocator.php b/FileLocator.php index 99c35bd26..3a5064edc 100644 --- a/FileLocator.php +++ b/FileLocator.php @@ -20,7 +20,7 @@ */ class FileLocator implements FileLocatorInterface { - protected $paths; + protected array $paths; /** * @param string|string[] $paths A path or an array of paths where to look for resources @@ -35,7 +35,7 @@ public function __construct(string|array $paths = []) * * @psalm-return ($first is true ? string : string[]) */ - public function locate(string $name, ?string $currentPath = null, bool $first = true) + public function locate(string $name, ?string $currentPath = null, bool $first = true): string|array { if ('' === $name) { throw new \InvalidArgumentException('An empty file name is not valid to be located.'); @@ -43,7 +43,7 @@ public function locate(string $name, ?string $currentPath = null, bool $first = if ($this->isAbsolutePath($name)) { if (!file_exists($name)) { - throw new FileLocatorFileNotFoundException(sprintf('The file "%s" does not exist.', $name), 0, null, [$name]); + throw new FileLocatorFileNotFoundException(\sprintf('The file "%s" does not exist.', $name), 0, null, [$name]); } return $name; @@ -70,7 +70,7 @@ public function locate(string $name, ?string $currentPath = null, bool $first = } if (!$filepaths) { - throw new FileLocatorFileNotFoundException(sprintf('The file "%s" does not exist (in: "%s").', $name, implode('", "', $paths)), 0, null, $notfound); + throw new FileLocatorFileNotFoundException(\sprintf('The file "%s" does not exist (in: "%s").', $name, implode('", "', $paths)), 0, null, $notfound); } return $filepaths; @@ -86,7 +86,8 @@ private function isAbsolutePath(string $file): bool && ':' === $file[1] && ('\\' === $file[2] || '/' === $file[2]) ) - || null !== parse_url($file, \PHP_URL_SCHEME) + || parse_url($file, \PHP_URL_SCHEME) + || str_starts_with($file, 'phar:///') // "parse_url()" doesn't handle absolute phar path, despite being valid ) { return true; } diff --git a/FileLocatorInterface.php b/FileLocatorInterface.php index 755cf018a..87cecf477 100644 --- a/FileLocatorInterface.php +++ b/FileLocatorInterface.php @@ -32,5 +32,5 @@ interface FileLocatorInterface * * @psalm-return ($first is true ? string : string[]) */ - public function locate(string $name, ?string $currentPath = null, bool $first = true); + public function locate(string $name, ?string $currentPath = null, bool $first = true): string|array; } diff --git a/Loader/FileLoader.php b/Loader/FileLoader.php index 8275ffcd3..8d9d84627 100644 --- a/Loader/FileLoader.php +++ b/Loader/FileLoader.php @@ -25,24 +25,21 @@ */ abstract class FileLoader extends Loader { - protected static $loading = []; - - protected $locator; + protected static array $loading = []; private ?string $currentDir = null; - public function __construct(FileLocatorInterface $locator, ?string $env = null) - { - $this->locator = $locator; + public function __construct( + protected FileLocatorInterface $locator, + ?string $env = null, + ) { parent::__construct($env); } /** * Sets the current directory. - * - * @return void */ - public function setCurrentDir(string $dir) + public function setCurrentDir(string $dir): void { $this->currentDir = $dir; } @@ -64,13 +61,11 @@ public function getLocator(): FileLocatorInterface * @param string|null $sourceResource The original resource importing the new resource * @param string|string[]|null $exclude Glob patterns to exclude from the import * - * @return mixed - * * @throws LoaderLoadException * @throws FileLoaderImportCircularReferenceException * @throws FileLocatorFileNotFoundException */ - public function import(mixed $resource, ?string $type = null, bool $ignoreErrors = false, ?string $sourceResource = null, string|array|null $exclude = null) + public function import(mixed $resource, ?string $type = null, bool $ignoreErrors = false, ?string $sourceResource = null, string|array|null $exclude = null): mixed { if (\is_string($resource) && \strlen($resource) !== ($i = strcspn($resource, '*?{[')) && !str_contains($resource, "\n")) { $excluded = []; diff --git a/Loader/Loader.php b/Loader/Loader.php index 66c38bbea..28e5877bf 100644 --- a/Loader/Loader.php +++ b/Loader/Loader.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Config\Loader; use Symfony\Component\Config\Exception\LoaderLoadException; +use Symfony\Component\Config\Exception\LogicException; /** * Loader is the abstract class used by all built-in loaders. @@ -20,33 +21,31 @@ */ abstract class Loader implements LoaderInterface { - protected $resolver; - protected $env; + protected ?LoaderResolverInterface $resolver = null; - public function __construct(?string $env = null) - { - $this->env = $env; + public function __construct( + protected ?string $env = null, + ) { } public function getResolver(): LoaderResolverInterface { + if (null === $this->resolver) { + throw new LogicException('Cannot get a resolver if none was set.'); + } + return $this->resolver; } - /** - * @return void - */ - public function setResolver(LoaderResolverInterface $resolver) + public function setResolver(LoaderResolverInterface $resolver): void { $this->resolver = $resolver; } /** * Imports a resource. - * - * @return mixed */ - public function import(mixed $resource, ?string $type = null) + public function import(mixed $resource, ?string $type = null): mixed { return $this->resolve($resource, $type)->load($resource, $type); } diff --git a/Loader/LoaderInterface.php b/Loader/LoaderInterface.php index 190d2c630..6ed1893a5 100644 --- a/Loader/LoaderInterface.php +++ b/Loader/LoaderInterface.php @@ -21,32 +21,24 @@ interface LoaderInterface /** * Loads a resource. * - * @return mixed - * * @throws \Exception If something went wrong */ - public function load(mixed $resource, ?string $type = null); + public function load(mixed $resource, ?string $type = null): mixed; /** * Returns whether this class supports the given resource. * * @param mixed $resource A resource - * - * @return bool */ - public function supports(mixed $resource, ?string $type = null); + public function supports(mixed $resource, ?string $type = null): bool; /** * Gets the loader resolver. - * - * @return LoaderResolverInterface */ - public function getResolver(); + public function getResolver(): LoaderResolverInterface; /** * Sets the loader resolver. - * - * @return void */ - public function setResolver(LoaderResolverInterface $resolver); + public function setResolver(LoaderResolverInterface $resolver): void; } diff --git a/Loader/LoaderResolver.php b/Loader/LoaderResolver.php index 72ab33411..8308d7e89 100644 --- a/Loader/LoaderResolver.php +++ b/Loader/LoaderResolver.php @@ -47,10 +47,7 @@ public function resolve(mixed $resource, ?string $type = null): LoaderInterface| return false; } - /** - * @return void - */ - public function addLoader(LoaderInterface $loader) + public function addLoader(LoaderInterface $loader): void { $this->loaders[] = $loader; $loader->setResolver($this); diff --git a/Loader/ParamConfigurator.php b/Loader/ParamConfigurator.php index d91de6a73..ed1045270 100644 --- a/Loader/ParamConfigurator.php +++ b/Loader/ParamConfigurator.php @@ -18,11 +18,9 @@ */ class ParamConfigurator { - private string $name; - - public function __construct(string $name) - { - $this->name = $name; + public function __construct( + private string $name, + ) { } public function __toString(): string diff --git a/Resource/ClassExistenceResource.php b/Resource/ClassExistenceResource.php index eab04b8d0..e7851740c 100644 --- a/Resource/ClassExistenceResource.php +++ b/Resource/ClassExistenceResource.php @@ -23,7 +23,6 @@ */ class ClassExistenceResource implements SelfCheckingResourceInterface { - private string $resource; private ?array $exists = null; private static int $autoloadLevel = 0; @@ -34,9 +33,10 @@ class ClassExistenceResource implements SelfCheckingResourceInterface * @param string $resource The fully-qualified class name * @param bool|null $exists Boolean when the existence check has already been done */ - public function __construct(string $resource, ?bool $exists = null) - { - $this->resource = $resource; + public function __construct( + private string $resource, + ?bool $exists = null, + ) { if (null !== $exists) { $this->exists = [$exists, null]; } @@ -158,10 +158,10 @@ public static function throwOnRequiredClass(string $class, ?\Exception $previous throw $previous; } - $message = sprintf('Class "%s" not found.', $class); + $message = \sprintf('Class "%s" not found.', $class); if ($class !== (self::$autoloadedClass ?? $class)) { - $message = substr_replace($message, sprintf(' while loading "%s"', self::$autoloadedClass), -1, 0); + $message = substr_replace($message, \sprintf(' while loading "%s"', self::$autoloadedClass), -1, 0); } if (null !== $previous) { diff --git a/Resource/DirectoryResource.php b/Resource/DirectoryResource.php index df486a085..5fd5f65c9 100644 --- a/Resource/DirectoryResource.php +++ b/Resource/DirectoryResource.php @@ -21,7 +21,6 @@ class DirectoryResource implements SelfCheckingResourceInterface { private string $resource; - private ?string $pattern; /** * @param string $resource The file path to the resource @@ -29,13 +28,14 @@ class DirectoryResource implements SelfCheckingResourceInterface * * @throws \InvalidArgumentException */ - public function __construct(string $resource, ?string $pattern = null) - { + public function __construct( + string $resource, + private ?string $pattern = null, + ) { $resolvedResource = realpath($resource) ?: (file_exists($resource) ? $resource : false); - $this->pattern = $pattern; if (false === $resolvedResource || !is_dir($resolvedResource)) { - throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist.', $resource)); + throw new \InvalidArgumentException(\sprintf('The directory "%s" does not exist.', $resource)); } $this->resource = $resolvedResource; diff --git a/Resource/FileExistenceResource.php b/Resource/FileExistenceResource.php index 666866ee4..4722537d8 100644 --- a/Resource/FileExistenceResource.php +++ b/Resource/FileExistenceResource.php @@ -23,16 +23,14 @@ */ class FileExistenceResource implements SelfCheckingResourceInterface { - private string $resource; - private bool $exists; /** * @param string $resource The file path to the resource */ - public function __construct(string $resource) - { - $this->resource = $resource; + public function __construct( + private string $resource, + ) { $this->exists = file_exists($resource); } diff --git a/Resource/FileResource.php b/Resource/FileResource.php index 6e8f9bdb3..44e5bbc57 100644 --- a/Resource/FileResource.php +++ b/Resource/FileResource.php @@ -34,7 +34,7 @@ public function __construct(string $resource) $resolvedResource = realpath($resource) ?: (file_exists($resource) ? $resource : false); if (false === $resolvedResource) { - throw new \InvalidArgumentException(sprintf('The file "%s" does not exist.', $resource)); + throw new \InvalidArgumentException(\sprintf('The file "%s" does not exist.', $resource)); } $this->resource = $resolvedResource; diff --git a/Resource/GlobResource.php b/Resource/GlobResource.php index 2aedc84b3..e6a348e11 100644 --- a/Resource/GlobResource.php +++ b/Resource/GlobResource.php @@ -28,10 +28,7 @@ class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface { private string $prefix; - private string $pattern; - private bool $recursive; private string $hash; - private bool $forExclusion; private array $excludedPrefixes; private int $globBrace; @@ -42,18 +39,20 @@ class GlobResource implements \IteratorAggregate, SelfCheckingResourceInterface * * @throws \InvalidArgumentException */ - public function __construct(string $prefix, string $pattern, bool $recursive, bool $forExclusion = false, array $excludedPrefixes = []) - { + public function __construct( + string $prefix, + private string $pattern, + private bool $recursive, + private bool $forExclusion = false, + array $excludedPrefixes = [], + ) { ksort($excludedPrefixes); $resolvedPrefix = realpath($prefix) ?: (file_exists($prefix) ? $prefix : false); - $this->pattern = $pattern; - $this->recursive = $recursive; - $this->forExclusion = $forExclusion; $this->excludedPrefixes = $excludedPrefixes; $this->globBrace = \defined('GLOB_BRACE') ? \GLOB_BRACE : 0; if (false === $resolvedPrefix) { - throw new \InvalidArgumentException(sprintf('The path "%s" does not exist.', $prefix)); + throw new \InvalidArgumentException(\sprintf('The path "%s" does not exist.', $prefix)); } $this->prefix = $resolvedPrefix; diff --git a/Resource/ReflectionClassResource.php b/Resource/ReflectionClassResource.php index dbd47e66d..e039329ca 100644 --- a/Resource/ReflectionClassResource.php +++ b/Resource/ReflectionClassResource.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Config\Resource; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\Messenger\Handler\MessageSubscriberInterface; use Symfony\Contracts\Service\ServiceSubscriberInterface; /** @@ -24,14 +23,14 @@ class ReflectionClassResource implements SelfCheckingResourceInterface { private array $files = []; private string $className; - private \ReflectionClass $classReflector; private array $excludedVendors = []; private string $hash; - public function __construct(\ReflectionClass $classReflector, array $excludedVendors = []) - { + public function __construct( + private \ReflectionClass $classReflector, + array $excludedVendors = [], + ) { $this->className = $classReflector->name; - $this->classReflector = $classReflector; $this->excludedVendors = $excludedVendors; } @@ -82,7 +81,7 @@ private function loadFiles(\ReflectionClass $class): void $file = $class->getFileName(); if (false !== $file && is_file($file)) { foreach ($this->excludedVendors as $vendor) { - if (str_starts_with($file, $vendor) && false !== strpbrk(substr($file, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) { + if (\in_array($file[\strlen($vendor)] ?? '', ['/', \DIRECTORY_SEPARATOR], true) && str_starts_with($file, $vendor)) { $file = false; break; } @@ -155,6 +154,13 @@ private function generateSignature(\ReflectionClass $class): iterable } foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) { + foreach ($this->excludedVendors as $vendor) { + $file = $m->getFileName(); + if (\in_array($file[\strlen($vendor)] ?? '', ['/', \DIRECTORY_SEPARATOR], true) && str_starts_with($file, $vendor)) { + continue 2; + } + } + foreach ($m->getAttributes() as $a) { $attributes[] = [$a->getName(), (string) $a]; } @@ -191,13 +197,6 @@ private function generateSignature(\ReflectionClass $class): iterable yield print_r($class->name::getSubscribedEvents(), true); } - if (interface_exists(MessageSubscriberInterface::class, false) && $class->isSubclassOf(MessageSubscriberInterface::class)) { - yield MessageSubscriberInterface::class; - foreach ($class->name::getHandledMessages() as $key => $value) { - yield $key.print_r($value, true); - } - } - if (interface_exists(ServiceSubscriberInterface::class, false) && $class->isSubclassOf(ServiceSubscriberInterface::class)) { yield ServiceSubscriberInterface::class; yield print_r($class->name::getSubscribedServices(), true); diff --git a/Resource/SkippingResourceChecker.php b/Resource/SkippingResourceChecker.php new file mode 100644 index 000000000..0f0934c82 --- /dev/null +++ b/Resource/SkippingResourceChecker.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +use Symfony\Component\Config\ResourceCheckerInterface; + +class SkippingResourceChecker implements ResourceCheckerInterface +{ + private array $skippedResourceTypes; + + /** + * @param class-string[] $skippedResourceTypes + */ + public function __construct(array $skippedResourceTypes = []) + { + $this->skippedResourceTypes = array_flip($skippedResourceTypes); + } + + public function supports(ResourceInterface $metadata): bool + { + return !$this->skippedResourceTypes || isset($this->skippedResourceTypes[$metadata::class]); + } + + public function isFresh(ResourceInterface $resource, int $timestamp): bool + { + return true; + } +} diff --git a/ResourceCheckerConfigCache.php b/ResourceCheckerConfigCache.php index 1e58d21ed..c201a3dcb 100644 --- a/ResourceCheckerConfigCache.php +++ b/ResourceCheckerConfigCache.php @@ -23,21 +23,19 @@ */ class ResourceCheckerConfigCache implements ConfigCacheInterface { - private string $file; - - /** - * @var iterable - */ - private iterable $resourceCheckers; + private string $metaFile; /** * @param string $file The absolute cache path * @param iterable $resourceCheckers The ResourceCheckers to use for the freshness check + * @param string|null $metaFile The absolute path to the meta file, defaults to $file.meta if null */ - public function __construct(string $file, iterable $resourceCheckers = []) - { - $this->file = $file; - $this->resourceCheckers = $resourceCheckers; + public function __construct( + private string $file, + private iterable $resourceCheckers = [], + ?string $metaFile = null, + ) { + $this->metaFile = $metaFile ?? $file.'.meta'; } public function getPath(): string @@ -68,7 +66,7 @@ public function isFresh(): bool return true; // shortcut - if we don't have any checkers we don't need to bother with the meta file at all } - $metadata = $this->getMetaFile(); + $metadata = $this->metaFile; if (!is_file($metadata)) { return false; @@ -105,11 +103,9 @@ public function isFresh(): bool * @param string $content The content to write in the cache * @param ResourceInterface[] $metadata An array of metadata * - * @return void - * * @throws \RuntimeException When cache file can't be written */ - public function write(string $content, ?array $metadata = null) + public function write(string $content, ?array $metadata = null): void { $mode = 0666; $umask = umask(); @@ -122,9 +118,23 @@ public function write(string $content, ?array $metadata = null) } if (null !== $metadata) { - $filesystem->dumpFile($this->getMetaFile(), serialize($metadata)); + $filesystem->dumpFile($this->metaFile, $ser = serialize($metadata)); + try { + $filesystem->chmod($this->metaFile, $mode, $umask); + } catch (IOException) { + // discard chmod failure (some filesystem may not support it) + } + + $ser = preg_replace_callback('/;O:(\d+):"/', static fn ($m) => ';O:'.(9 + $m[1]).':"Tracking\\', $ser); + $ser = preg_replace_callback('/s:(\d+):"\0[^\0]++\0/', static fn ($m) => 's:'.($m[1] - \strlen($m[0]) + 6).':"', $ser); + $ser = unserialize($ser); + $ser = @json_encode($ser) ?: []; + $ser = str_replace('"__PHP_Incomplete_Class_Name":"Tracking\\\\', '"@type":"', $ser); + $ser = \sprintf('{"resources":%s}', $ser); + + $filesystem->dumpFile($this->metaFile.'.json', $ser); try { - $filesystem->chmod($this->getMetaFile(), $mode, $umask); + $filesystem->chmod($this->metaFile.'.json', $mode, $umask); } catch (IOException) { // discard chmod failure (some filesystem may not support it) } @@ -135,18 +145,10 @@ public function write(string $content, ?array $metadata = null) } } - /** - * Gets the meta file path. - */ - private function getMetaFile(): string - { - return $this->file.'.meta'; - } - private function safelyUnserialize(string $file): mixed { $meta = false; - $content = file_get_contents($file); + $content = (new Filesystem())->readFile($file); $signalingException = new \UnexpectedValueException(); $prevUnserializeHandler = ini_set('unserialize_callback_func', self::class.'::handleUnserializeCallback'); $prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$prevErrorHandler, $signalingException) { diff --git a/ResourceCheckerConfigCacheFactory.php b/ResourceCheckerConfigCacheFactory.php index 97d52006f..595855ccb 100644 --- a/ResourceCheckerConfigCacheFactory.php +++ b/ResourceCheckerConfigCacheFactory.php @@ -19,14 +19,12 @@ */ class ResourceCheckerConfigCacheFactory implements ConfigCacheFactoryInterface { - private iterable $resourceCheckers = []; - /** * @param iterable $resourceCheckers */ - public function __construct(iterable $resourceCheckers = []) - { - $this->resourceCheckers = $resourceCheckers; + public function __construct( + private iterable $resourceCheckers = [], + ) { } public function cache(string $file, callable $callable): ConfigCacheInterface diff --git a/ResourceCheckerInterface.php b/ResourceCheckerInterface.php index 6b1c6c5fb..13ae03f45 100644 --- a/ResourceCheckerInterface.php +++ b/ResourceCheckerInterface.php @@ -29,17 +29,13 @@ interface ResourceCheckerInterface /** * Queries the ResourceChecker whether it can validate a given * resource or not. - * - * @return bool */ - public function supports(ResourceInterface $metadata); + public function supports(ResourceInterface $metadata): bool; /** * Validates the resource. * * @param int $timestamp The timestamp at which the cache associated with this resource was created - * - * @return bool */ - public function isFresh(ResourceInterface $resource, int $timestamp); + public function isFresh(ResourceInterface $resource, int $timestamp): bool; } diff --git a/Tests/Builder/GeneratedConfigTest.php b/Tests/Builder/GeneratedConfigTest.php index 722df54cb..680010f00 100644 --- a/Tests/Builder/GeneratedConfigTest.php +++ b/Tests/Builder/GeneratedConfigTest.php @@ -40,8 +40,6 @@ class GeneratedConfigTest extends TestCase protected function setup(): void { - parent::setup(); - $this->tempDir = []; } @@ -49,8 +47,6 @@ protected function tearDown(): void { (new Filesystem())->remove($this->tempDir); $this->tempDir = []; - - parent::tearDown(); } public static function fixtureNames() @@ -92,7 +88,6 @@ public function testConfig(string $name, string $alias) // $this->generateConfigBuilder('Symfony\\Component\\Config\\Tests\\Builder\\Fixtures\\'.$name, $expectedCode); // $this->markTestIncomplete('Re-comment the line above and relaunch the tests'); - $outputDir = sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid('sf_config_builder', true); $configBuilder = $this->generateConfigBuilder('Symfony\\Component\\Config\\Tests\\Builder\\Fixtures\\'.$name, $outputDir); $callback($configBuilder); @@ -162,12 +157,12 @@ public function testSetExtraKeyMethodIsNotGeneratedWhenAllowExtraKeysIsFalse() /** * Generate the ConfigBuilder or return an already generated instance. */ - private function generateConfigBuilder(string $configurationClass, ?string $outputDir = null) + private function generateConfigBuilder(string $configurationClass, ?string &$outputDir = null) { - $outputDir ??= sys_get_temp_dir().\DIRECTORY_SEPARATOR.uniqid('sf_config_builder', true); - if (!str_contains($outputDir, __DIR__)) { - $this->tempDir[] = $outputDir; - } + $outputDir = tempnam(sys_get_temp_dir(), 'sf_config_builder_'); + unlink($outputDir); + mkdir($outputDir); + $this->tempDir[] = $outputDir; $configuration = new $configurationClass(); $rootNode = $configuration->getConfigTreeBuilder()->buildTree(); diff --git a/Tests/ConfigCacheTest.php b/Tests/ConfigCacheTest.php index f4ead1d52..e639fb220 100644 --- a/Tests/ConfigCacheTest.php +++ b/Tests/ConfigCacheTest.php @@ -13,21 +13,24 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Config\Resource\FileResource; use Symfony\Component\Config\Resource\SelfCheckingResourceChecker; use Symfony\Component\Config\Tests\Resource\ResourceStub; class ConfigCacheTest extends TestCase { private string $cacheFile; + private string $metaFile; protected function setUp(): void { $this->cacheFile = tempnam(sys_get_temp_dir(), 'config_'); + $this->metaFile = tempnam(sys_get_temp_dir(), 'config_'); } protected function tearDown(): void { - $files = [$this->cacheFile, $this->cacheFile.'.meta']; + $files = [$this->cacheFile, $this->cacheFile.'.meta', $this->metaFile]; foreach ($files as $file) { if (file_exists($file)) { @@ -103,4 +106,14 @@ public static function debugModes(): array [false], ]; } + + public function testCacheWithCustomMetaFile() + { + $this->assertStringEqualsFile($this->metaFile, ''); + + $cache = new ConfigCache($this->cacheFile, false, $this->metaFile); + $cache->write('foo', [new FileResource(__FILE__)]); + + $this->assertStringNotEqualsFile($this->metaFile, ''); + } } diff --git a/Tests/Definition/BooleanNodeTest.php b/Tests/Definition/BooleanNodeTest.php index f617148ff..9358a975d 100644 --- a/Tests/Definition/BooleanNodeTest.php +++ b/Tests/Definition/BooleanNodeTest.php @@ -26,6 +26,33 @@ public function testNormalize(bool $value) $this->assertSame($value, $node->normalize($value)); } + public function testNullValueOnNullable() + { + $node = new BooleanNode('test', null, '.', true); + + $this->assertNull($node->normalize(null)); + } + + public function testNullValueOnNotNullable() + { + $node = new BooleanNode('test', null, '.', false); + + $this->expectException(InvalidTypeException::class); + $this->expectExceptionMessage('Invalid type for path "test". Expected "bool", but got "null".'); + + $this->assertNull($node->normalize(null)); + } + + public function testInvalidValueOnNullable() + { + $node = new BooleanNode('test', null, '.', true); + + $this->expectException(InvalidTypeException::class); + $this->expectExceptionMessage('Invalid type for path "test". Expected "bool" or "null", but got "int".'); + + $node->normalize(123); + } + /** * @dataProvider getValidValues */ @@ -50,7 +77,6 @@ public static function getValidValues(): array */ public function testNormalizeThrowsExceptionOnInvalidValues($value) { - $node = new BooleanNode('test'); $this->expectException(InvalidTypeException::class); @@ -61,7 +87,6 @@ public function testNormalizeThrowsExceptionOnInvalidValues($value) public static function getInvalidValues(): array { return [ - [null], [''], ['foo'], [0], diff --git a/Tests/Definition/Builder/ArrayNodeDefinitionTest.php b/Tests/Definition/Builder/ArrayNodeDefinitionTest.php index b91faf17c..ca376ea8a 100644 --- a/Tests/Definition/Builder/ArrayNodeDefinitionTest.php +++ b/Tests/Definition/Builder/ArrayNodeDefinitionTest.php @@ -239,7 +239,7 @@ public function testUnsetChild() ->children() ->scalarNode('value') ->beforeNormalization() - ->ifTrue(fn ($value) => empty($value)) + ->ifTrue(fn ($value) => !$value) ->thenUnset() ->end() ->end() @@ -267,6 +267,12 @@ public function testPrototypeBoolean() $this->assertEquals($node->prototype('boolean'), $node->booleanPrototype()); } + public function testPrototypeString() + { + $node = new ArrayNodeDefinition('root'); + $this->assertEquals($node->prototype('string'), $node->stringPrototype()); + } + public function testPrototypeInteger() { $node = new ArrayNodeDefinition('root'); diff --git a/Tests/Definition/Builder/BooleanNodeDefinitionTest.php b/Tests/Definition/Builder/BooleanNodeDefinitionTest.php index 7a4706a3e..67b3aaf64 100644 --- a/Tests/Definition/Builder/BooleanNodeDefinitionTest.php +++ b/Tests/Definition/Builder/BooleanNodeDefinitionTest.php @@ -25,6 +25,30 @@ public function testCannotBeEmptyThrowsAnException() $def->cannotBeEmpty(); } + public function testBooleanNodeWithDefaultNull() + { + $def = new BooleanNodeDefinition('foo'); + $def->defaultNull(); + + $node = $def->getNode(); + $this->assertTrue($node->hasDefaultValue()); + $this->assertNull($node->getDefaultValue()); + + $this->assertNull($node->normalize(null)); + } + + public function testBooleanNodeWithDefaultValueAtNull() + { + $def = new BooleanNodeDefinition('foo'); + $def->defaultValue(null); + + $node = $def->getNode(); + $this->assertTrue($node->hasDefaultValue()); + $this->assertNull($node->getDefaultValue()); + + $this->assertNull($node->normalize(null)); + } + public function testSetDeprecated() { $def = new BooleanNodeDefinition('foo'); diff --git a/Tests/Definition/Builder/NodeBuilderTest.php b/Tests/Definition/Builder/NodeBuilderTest.php index 1338c58a6..bee64388c 100644 --- a/Tests/Definition/Builder/NodeBuilderTest.php +++ b/Tests/Definition/Builder/NodeBuilderTest.php @@ -15,6 +15,7 @@ use Symfony\Component\Config\Definition\Builder\FloatNodeDefinition; use Symfony\Component\Config\Definition\Builder\IntegerNodeDefinition; use Symfony\Component\Config\Definition\Builder\NodeBuilder as BaseNodeBuilder; +use Symfony\Component\Config\Definition\Builder\StringNodeDefinition; use Symfony\Component\Config\Definition\Builder\VariableNodeDefinition as BaseVariableNodeDefinition; class NodeBuilderTest extends TestCase @@ -86,6 +87,14 @@ public function testNumericNodeCreation() $node = $builder->floatNode('bar')->min(3.0)->max(5.0); $this->assertInstanceOf(FloatNodeDefinition::class, $node); } + + public function testStringNodeCreation() + { + $builder = new BaseNodeBuilder(); + + $node = $builder->stringNode('foo bar'); + $this->assertInstanceOf(StringNodeDefinition::class, $node); + } } class SomeNodeDefinition extends BaseVariableNodeDefinition diff --git a/Tests/Definition/PrototypedArrayNodeTest.php b/Tests/Definition/PrototypedArrayNodeTest.php index abf0de777..f1868a04a 100644 --- a/Tests/Definition/PrototypedArrayNodeTest.php +++ b/Tests/Definition/PrototypedArrayNodeTest.php @@ -291,51 +291,51 @@ public static function getDataForKeyRemovedLeftValueOnly(): array $variableValue = new VariableNode('value'); return [ - [ - $scalarValue, - [ - ['id' => 'option1', 'value' => 'value1'], - ], - ['option1' => 'value1'], - ], - - [ - $scalarValue, - [ - ['id' => 'option1', 'value' => 'value1'], - ['id' => 'option2', 'value' => 'value2', 'foo' => 'foo2'], - ], - [ - 'option1' => 'value1', - 'option2' => ['value' => 'value2', 'foo' => 'foo2'], - ], - ], - - [ - $arrayValue, - [ - [ - 'id' => 'option1', - 'value' => ['foo' => 'foo1', 'bar' => 'bar1'], - ], - ], - [ - 'option1' => ['foo' => 'foo1', 'bar' => 'bar1'], - ], - ], - - [$variableValue, - [ - [ - 'id' => 'option1', 'value' => ['foo' => 'foo1', 'bar' => 'bar1'], - ], - ['id' => 'option2', 'value' => 'value2'], - ], - [ - 'option1' => ['foo' => 'foo1', 'bar' => 'bar1'], - 'option2' => 'value2', - ], - ], + [ + $scalarValue, + [ + ['id' => 'option1', 'value' => 'value1'], + ], + ['option1' => 'value1'], + ], + + [ + $scalarValue, + [ + ['id' => 'option1', 'value' => 'value1'], + ['id' => 'option2', 'value' => 'value2', 'foo' => 'foo2'], + ], + [ + 'option1' => 'value1', + 'option2' => ['value' => 'value2', 'foo' => 'foo2'], + ], + ], + + [ + $arrayValue, + [ + [ + 'id' => 'option1', + 'value' => ['foo' => 'foo1', 'bar' => 'bar1'], + ], + ], + [ + 'option1' => ['foo' => 'foo1', 'bar' => 'bar1'], + ], + ], + + [$variableValue, + [ + [ + 'id' => 'option1', 'value' => ['foo' => 'foo1', 'bar' => 'bar1'], + ], + ['id' => 'option2', 'value' => 'value2'], + ], + [ + 'option1' => ['foo' => 'foo1', 'bar' => 'bar1'], + 'option2' => 'value2', + ], + ], ]; } diff --git a/Tests/Definition/ScalarNodeTest.php b/Tests/Definition/ScalarNodeTest.php index eea3d49b4..9ccd910c2 100644 --- a/Tests/Definition/ScalarNodeTest.php +++ b/Tests/Definition/ScalarNodeTest.php @@ -127,8 +127,6 @@ public function testNormalizeThrowsExceptionWithErrorMessage() /** * @dataProvider getValidNonEmptyValues - * - * @param mixed $value */ public function testValidNonEmptyValues($value) { diff --git a/Tests/Definition/StringNodeTest.php b/Tests/Definition/StringNodeTest.php new file mode 100644 index 000000000..ddc219d24 --- /dev/null +++ b/Tests/Definition/StringNodeTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Tests\Definition; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; +use Symfony\Component\Config\Definition\StringNode; + +class StringNodeTest extends TestCase +{ + /** + * @testWith [""] + * ["valid string"] + */ + public function testNormalize(string $value) + { + $node = new StringNode('test'); + $this->assertSame($value, $node->normalize($value)); + } + + /** + * @testWith [null] + * [false] + * [true] + * [0] + * [1] + * [0.0] + * [0.1] + * [{}] + * [{"foo": "bar"}] + */ + public function testNormalizeThrowsExceptionOnInvalidValues($value) + { + $node = new StringNode('test'); + + $this->expectException(InvalidTypeException::class); + + $node->normalize($value); + } +} diff --git a/Tests/FileLocatorTest.php b/Tests/FileLocatorTest.php index d042bff7d..beb005a35 100644 --- a/Tests/FileLocatorTest.php +++ b/Tests/FileLocatorTest.php @@ -38,6 +38,7 @@ public static function getIsAbsolutePathTests(): array ['\\server\\foo.xml'], ['https://server/foo.xml'], ['phar://server/foo.xml'], + ['phar:///server/foo.xml'], ]; } diff --git a/Tests/Loader/LoaderTest.php b/Tests/Loader/LoaderTest.php index 70bfb8fc1..263bde26d 100644 --- a/Tests/Loader/LoaderTest.php +++ b/Tests/Loader/LoaderTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Config\Exception\LoaderLoadException; +use Symfony\Component\Config\Exception\LogicException; use Symfony\Component\Config\Loader\Loader; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\Config\Loader\LoaderResolverInterface; @@ -29,6 +30,14 @@ public function testGetSetResolver() $this->assertSame($resolver, $loader->getResolver(), '->setResolver() sets the resolver loader'); } + public function testGetResolverWithoutSetResolver() + { + $this->expectException(LogicException::class); + + $loader = new ProjectLoader1(); + $loader->getResolver(); + } + public function testResolve() { $resolvedLoader = $this->createMock(LoaderInterface::class); @@ -46,6 +55,14 @@ public function testResolve() $this->assertSame($resolvedLoader, $loader->resolve('foo.xml'), '->resolve() finds a loader'); } + public function testResolveWithoutSetResolver() + { + $this->expectException(LoaderLoadException::class); + + $loader = new ProjectLoader1(); + $loader->resolve('foo.xml'); + } + public function testResolveWhenResolverCannotFindLoader() { $resolver = $this->createMock(LoaderResolverInterface::class); diff --git a/Tests/Resource/GlobResourceTest.php b/Tests/Resource/GlobResourceTest.php index 4d1eb4f1f..c2698b033 100644 --- a/Tests/Resource/GlobResourceTest.php +++ b/Tests/Resource/GlobResourceTest.php @@ -41,7 +41,6 @@ public function testIterator() $paths = iterator_to_array($resource); - $file = $dir.'/Resource'.\DIRECTORY_SEPARATOR.'ConditionalClass.php'; $this->assertEquals([$file => $file], $paths); $this->assertInstanceOf(\SplFileInfo::class, current($paths)); $this->assertSame($dir, $resource->getPrefix()); diff --git a/Tests/Resource/ReflectionClassResourceTest.php b/Tests/Resource/ReflectionClassResourceTest.php index be2c075fc..e7e36a063 100644 --- a/Tests/Resource/ReflectionClassResourceTest.php +++ b/Tests/Resource/ReflectionClassResourceTest.php @@ -14,7 +14,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Config\Resource\ReflectionClassResource; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\Messenger\Handler\MessageSubscriberInterface; use Symfony\Contracts\Service\ServiceSubscriberInterface; class ReflectionClassResourceTest extends TestCase @@ -64,7 +63,7 @@ public function testIsFreshForDeletedResources() /** * @dataProvider provideHashedSignature */ - public function testHashedSignature(bool $changeExpected, int $changedLine, ?string $changedCode, ?\Closure $setContext = null) + public function testHashedSignature(bool $changeExpected, int $changedLine, ?string $changedCode, int $resourceClassNameSuffix, ?\Closure $setContext = null) { if ($setContext) { $setContext(); @@ -95,7 +94,7 @@ public function testHashedSignature(bool $changeExpected, int $changedLine, ?str static $expectedSignature, $generateSignature; if (null === $expectedSignature) { - eval(sprintf($code, $class = 'Foo'.str_replace('.', '_', uniqid('', true)))); + eval(\sprintf($code, $class = 'Foo'.(string) $resourceClassNameSuffix)); $r = new \ReflectionClass(ReflectionClassResource::class); $generateSignature = $r->getMethod('generateSignature'); $generateSignature = $generateSignature->getClosure($r->newInstanceWithoutConstructor()); @@ -106,7 +105,7 @@ public function testHashedSignature(bool $changeExpected, int $changedLine, ?str if (null !== $changedCode) { $code[$changedLine] = $changedCode; } - eval(sprintf(implode("\n", $code), $class = 'Foo'.str_replace('.', '_', uniqid('', true)))); + eval(\sprintf(implode("\n", $code), $class = 'Bar'.(string) $resourceClassNameSuffix)); $signature = implode("\n", iterator_to_array($generateSignature(new \ReflectionClass($class)))); if ($changeExpected) { @@ -118,49 +117,50 @@ public function testHashedSignature(bool $changeExpected, int $changedLine, ?str public static function provideHashedSignature(): iterable { - yield [false, 0, "// line change\n\n"]; - yield [true, 0, '/** class docblock */']; - yield [true, 0, '#[Foo]']; - yield [true, 0, '#[Foo(new MissingClass)]']; - yield [true, 1, 'abstract class %s']; - yield [true, 1, 'final class %s']; - yield [true, 1, 'class %s extends Exception']; - yield [true, 1, 'class %s implements '.DummyInterface::class]; - yield [true, 3, 'const FOO = 456;']; - yield [true, 3, 'const BAR = 123;']; - yield [true, 4, '/** pub docblock */']; - yield [true, 5, 'protected $pub = [];']; - yield [true, 5, 'public $pub = [123];']; - yield [true, 5, '#[Foo(new MissingClass)] public $pub = [];']; - yield [true, 6, '/** prot docblock */']; - yield [true, 7, 'private $prot;']; - yield [false, 8, '/** priv docblock */']; - yield [false, 9, 'private $priv = 123;']; - yield [true, 10, '/** pub docblock */']; - yield [true, 11, 'public function pub(...$arg) {}']; - yield [true, 11, 'public function pub($arg = null): Foo {}']; - yield [false, 11, "public function pub(\$arg = null) {\nreturn 123;\n}"]; - yield [true, 12, '/** prot docblock */']; - yield [true, 13, 'protected function prot($a = [123]) {}']; - yield [true, 13, '#[Foo] protected function prot($a = []) {}']; - yield [true, 13, 'protected function prot(#[Foo] $a = []) {}']; - yield [true, 13, '#[Foo(new MissingClass)] protected function prot($a = []) {}']; - yield [true, 13, 'protected function prot(#[Foo(new MissingClass)] $a = []) {}']; - yield [false, 14, '/** priv docblock */']; - yield [false, 15, '']; + $i = 0; + yield [false, 0, "// line change\n\n", ++$i]; + yield [true, 0, '/** class docblock */', ++$i]; + yield [true, 0, '#[Foo]', ++$i]; + yield [true, 0, '#[Foo(new MissingClass)]', ++$i]; + yield [true, 1, 'abstract class %s', ++$i]; + yield [true, 1, 'final class %s', ++$i]; + yield [true, 1, 'class %s extends Exception', ++$i]; + yield [true, 1, 'class %s implements '.DummyInterface::class, ++$i]; + yield [true, 3, 'const FOO = 456;', ++$i]; + yield [true, 3, 'const BAR = 123;', ++$i]; + yield [true, 4, '/** pub docblock */', ++$i]; + yield [true, 5, 'protected $pub = [];', ++$i]; + yield [true, 5, 'public $pub = [123];', ++$i]; + yield [true, 5, '#[Foo(new MissingClass)] public $pub = [];', ++$i]; + yield [true, 6, '/** prot docblock */', ++$i]; + yield [true, 7, 'private $prot;', ++$i]; + yield [false, 8, '/** priv docblock */', ++$i]; + yield [false, 9, 'private $priv = 123;', ++$i]; + yield [true, 10, '/** pub docblock */', ++$i]; + yield [true, 11, 'public function pub(...$arg) {}', ++$i]; + yield [true, 11, 'public function pub($arg = null): Foo {}', ++$i]; + yield [false, 11, "public function pub(\$arg = null) {\nreturn 123;\n}", ++$i]; + yield [true, 12, '/** prot docblock */', ++$i]; + yield [true, 13, 'protected function prot($a = [123]) {}', ++$i]; + yield [true, 13, '#[Foo] protected function prot($a = []) {}', ++$i]; + yield [true, 13, 'protected function prot(#[Foo] $a = []) {}', ++$i]; + yield [true, 13, '#[Foo(new MissingClass)] protected function prot($a = []) {}', ++$i]; + yield [true, 13, 'protected function prot(#[Foo(new MissingClass)] $a = []) {}', ++$i]; + yield [false, 14, '/** priv docblock */', ++$i]; + yield [false, 15, null, ++$i]; // PHP7.4 typed properties without default value are // undefined, make sure this doesn't throw an error - yield [true, 5, 'public array $pub;']; - yield [false, 7, 'protected int $prot;']; - yield [false, 9, 'private string $priv;']; - yield [true, 17, 'public function __construct(private $bar = new \stdClass()) {}']; - yield [true, 17, 'public function ccc($bar = new \stdClass()) {}']; - yield [true, 17, 'public function ccc($bar = new MissingClass()) {}']; - yield [true, 17, 'public function ccc($bar = 187) {}']; - yield [true, 17, 'public function ccc($bar = ANOTHER_ONE_THAT_WILL_NEVER_BE_DEFINED_CCCCCCCCC) {}']; - yield [true, 17, 'public function ccc($bar = parent::BOOM) {}']; - yield [false, 17, null, static function () { \define('A_CONSTANT_THAT_FOR_SURE_WILL_NEVER_BE_DEFINED_CCCCCC', 'foo'); }]; + yield [true, 5, 'public array $pub;', ++$i]; + yield [false, 7, 'protected int $prot;', ++$i]; + yield [false, 9, 'private string $priv;', ++$i]; + yield [true, 17, 'public function __construct(private $bar = new \stdClass()) {}', ++$i]; + yield [true, 17, 'public function ccc($bar = new \stdClass()) {}', ++$i]; + yield [true, 17, 'public function ccc($bar = new MissingClass()) {}', ++$i]; + yield [true, 17, 'public function ccc($bar = 187) {}', ++$i]; + yield [true, 17, 'public function ccc($bar = ANOTHER_ONE_THAT_WILL_NEVER_BE_DEFINED_CCCCCCCCC) {}', ++$i]; + yield [true, 17, 'public function ccc($bar = parent::BOOM) {}', ++$i]; + yield [false, 17, null, ++$i, static function () { \define('A_CONSTANT_THAT_FOR_SURE_WILL_NEVER_BE_DEFINED_CCCCCC', 'foo'); }]; } public function testEventSubscriber() @@ -175,27 +175,6 @@ public function testEventSubscriber() $this->assertTrue($res->isFresh(0)); } - /** - * @group legacy - */ - public function testMessageSubscriber() - { - $res = new ReflectionClassResource(new \ReflectionClass(TestMessageSubscriber::class)); - $this->assertTrue($res->isFresh(0)); - - TestMessageSubscriberConfigHolder::$handledMessages = ['SomeMessageClass' => []]; - $this->assertFalse($res->isFresh(0)); - - $res = new ReflectionClassResource(new \ReflectionClass(TestMessageSubscriber::class)); - $this->assertTrue($res->isFresh(0)); - - TestMessageSubscriberConfigHolder::$handledMessages = ['OtherMessageClass' => []]; - $this->assertFalse($res->isFresh(0)); - - $res = new ReflectionClassResource(new \ReflectionClass(TestMessageSubscriber::class)); - $this->assertTrue($res->isFresh(0)); - } - public function testServiceSubscriber() { $res = new ReflectionClassResource(new \ReflectionClass(TestServiceSubscriber::class)); @@ -232,22 +211,6 @@ public static function getSubscribedEvents(): array } } -if (interface_exists(MessageSubscriberInterface::class)) { - class TestMessageSubscriber implements MessageSubscriberInterface - { - public static function getHandledMessages(): iterable - { - foreach (TestMessageSubscriberConfigHolder::$handledMessages as $key => $subscribedMessage) { - yield $key => $subscribedMessage; - } - } - } - class TestMessageSubscriberConfigHolder - { - public static array $handledMessages = []; - } -} - class TestServiceSubscriber implements ServiceSubscriberInterface { public static array $subscribedServices = []; diff --git a/Tests/ResourceCheckerConfigCacheTest.php b/Tests/ResourceCheckerConfigCacheTest.php index 37f30a49d..3dbbe4334 100644 --- a/Tests/ResourceCheckerConfigCacheTest.php +++ b/Tests/ResourceCheckerConfigCacheTest.php @@ -20,15 +20,17 @@ class ResourceCheckerConfigCacheTest extends TestCase { private string $cacheFile; + private string $metaFile; protected function setUp(): void { $this->cacheFile = tempnam(sys_get_temp_dir(), 'config_'); + $this->metaFile = tempnam(sys_get_temp_dir(), 'config_'); } protected function tearDown(): void { - $files = [$this->cacheFile, "{$this->cacheFile}.meta"]; + $files = [$this->cacheFile, $this->cacheFile.'.meta', $this->cacheFile.'.meta.json', $this->metaFile, $this->metaFile.'.json']; foreach ($files as $file) { if (file_exists($file)) { @@ -148,4 +150,24 @@ public function testCacheIsNotFreshIfNotExistsMetaFile() $this->assertFalse($cache->isFresh()); } + + public function testCacheWithCustomMetaFile() + { + $this->assertStringEqualsFile($this->metaFile, ''); + + $checker = $this->createMock(ResourceCheckerInterface::class); + $cache = new ResourceCheckerConfigCache($this->cacheFile, [$checker], $this->metaFile); + $cache->write('foo', [new FileResource(__FILE__)]); + + $this->assertStringNotEqualsFile($this->metaFile, ''); + + $this->assertStringEqualsFile($this->metaFile.'.json', json_encode([ + 'resources' => [ + [ + '@type' => FileResource::class, + 'resource' => __FILE__, + ], + ], + ])); + } } diff --git a/Tests/Util/XmlUtilsTest.php b/Tests/Util/XmlUtilsTest.php index 89f1014aa..2bc31e3bb 100644 --- a/Tests/Util/XmlUtilsTest.php +++ b/Tests/Util/XmlUtilsTest.php @@ -196,7 +196,7 @@ public function testLoadEmptyXmlFile() $file = __DIR__.'/../Fixtures/foo.xml'; $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage(sprintf('File "%s" does not contain valid XML, it is empty.', $file)); + $this->expectExceptionMessage(\sprintf('File "%s" does not contain valid XML, it is empty.', $file)); XmlUtils::loadFile($file); } @@ -216,7 +216,7 @@ public function testLoadWrongEmptyXMLWithErrorHandler() XmlUtils::loadFile($file); $this->fail('An exception should have been raised'); } catch (\InvalidArgumentException $e) { - $this->assertEquals(sprintf('File "%s" does not contain valid XML, it is empty.', $file), $e->getMessage()); + $this->assertEquals(\sprintf('File "%s" does not contain valid XML, it is empty.', $file), $e->getMessage()); } } finally { restore_error_handler(); diff --git a/Util/XmlUtils.php b/Util/XmlUtils.php index eb6f0f51a..02ebe7dbe 100644 --- a/Util/XmlUtils.php +++ b/Util/XmlUtils.php @@ -13,6 +13,7 @@ use Symfony\Component\Config\Util\Exception\InvalidXmlException; use Symfony\Component\Config\Util\Exception\XmlParsingException; +use Symfony\Component\Filesystem\Filesystem; /** * XMLUtils is a bunch of utility methods to XML operations. @@ -79,12 +80,12 @@ public static function parse(string $content, string|callable|null $schemaOrCall $valid = false; } } elseif (is_file($schemaOrCallable)) { - $schemaSource = file_get_contents((string) $schemaOrCallable); + $schemaSource = (new Filesystem())->readFile($schemaOrCallable); $valid = @$dom->schemaValidateSource($schemaSource); } else { libxml_use_internal_errors($internalErrors); - throw new XmlParsingException(sprintf('Invalid XSD file: "%s".', $schemaOrCallable)); + throw new XmlParsingException(\sprintf('Invalid XSD file: "%s".', $schemaOrCallable)); } if (!$valid) { @@ -115,23 +116,23 @@ public static function parse(string $content, string|callable|null $schemaOrCall public static function loadFile(string $file, string|callable|null $schemaOrCallable = null): \DOMDocument { if (!is_file($file)) { - throw new \InvalidArgumentException(sprintf('Resource "%s" is not a file.', $file)); + throw new \InvalidArgumentException(\sprintf('Resource "%s" is not a file.', $file)); } if (!is_readable($file)) { - throw new \InvalidArgumentException(sprintf('File "%s" is not readable.', $file)); + throw new \InvalidArgumentException(\sprintf('File "%s" is not readable.', $file)); } - $content = @file_get_contents($file); + $content = (new Filesystem())->readFile($file); if ('' === trim($content)) { - throw new \InvalidArgumentException(sprintf('File "%s" does not contain valid XML, it is empty.', $file)); + throw new \InvalidArgumentException(\sprintf('File "%s" does not contain valid XML, it is empty.', $file)); } try { return static::parse($content, $schemaOrCallable); } catch (InvalidXmlException $e) { - throw new XmlParsingException(sprintf('The XML file "%s" is not valid.', $file), 0, $e->getPrevious()); + throw new XmlParsingException(\sprintf('The XML file "%s" is not valid.', $file), 0, $e->getPrevious()); } } @@ -155,7 +156,7 @@ public static function loadFile(string $file, string|callable|null $schemaOrCall */ public static function convertDomElementToArray(\DOMElement $element, bool $checkPrefix = true): mixed { - $prefix = (string) $element->prefix; + $prefix = $element->prefix; $empty = true; $config = []; foreach ($element->attributes as $name => $node) { @@ -173,7 +174,7 @@ public static function convertDomElementToArray(\DOMElement $element, bool $chec $nodeValue = trim($node->nodeValue); $empty = false; } - } elseif ($checkPrefix && $prefix != (string) $node->prefix) { + } elseif ($checkPrefix && $prefix != $node->prefix) { continue; } elseif (!$node instanceof \DOMComment) { $value = static::convertDomElementToArray($node, $checkPrefix); @@ -238,14 +239,11 @@ public static function phpize(string|\Stringable $value): mixed } } - /** - * @return array - */ - protected static function getXmlErrors(bool $internalErrors) + protected static function getXmlErrors(bool $internalErrors): array { $errors = []; foreach (libxml_get_errors() as $error) { - $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)', + $errors[] = \sprintf('[%s %s] %s (in %s - line %d, column %d)', \LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR', $error->code, trim($error->message), diff --git a/composer.json b/composer.json index dbd34f13b..37206042a 100644 --- a/composer.json +++ b/composer.json @@ -16,20 +16,20 @@ } ], "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^7.1", "symfony/polyfill-ctype": "~1.8" }, "require-dev": { - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/yaml": "^6.4|^7.0" }, "conflict": { - "symfony/finder": "<5.4", + "symfony/finder": "<6.4", "symfony/service-contracts": "<2.5" }, "autoload": {