Initial Drupal 11 with DDEV setup

This commit is contained in:
gluebox
2025-10-08 11:39:17 -04:00
commit 89ef74b305
25344 changed files with 2599172 additions and 0 deletions

View File

@ -0,0 +1,123 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Mapping\Loader;
use Symfony\Component\Validator\Attribute\HasNamedArguments;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\MappingException;
/**
* Base loader for validation metadata.
*
* This loader supports the loading of constraints from Symfony's default
* namespace (see {@link DEFAULT_NAMESPACE}) using the short class names of
* those constraints. Constraints can also be loaded using their fully
* qualified class names. At last, namespace aliases can be defined to load
* constraints with the syntax "alias:ShortName".
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
abstract class AbstractLoader implements LoaderInterface
{
/**
* The namespace to load constraints from by default.
*/
public const DEFAULT_NAMESPACE = '\\Symfony\\Component\\Validator\\Constraints\\';
protected array $namespaces = [];
/**
* @var array<class-string, bool>
*/
private array $namedArgumentsCache = [];
/**
* Adds a namespace alias.
*
* The namespace alias can be used to reference constraints from specific
* namespaces in {@link newConstraint()}:
*
* $this->addNamespaceAlias('mynamespace', '\\Acme\\Package\\Constraints\\');
*
* $constraint = $this->newConstraint('mynamespace:NotNull');
*/
protected function addNamespaceAlias(string $alias, string $namespace): void
{
$this->namespaces[$alias] = $namespace;
}
/**
* Creates a new constraint instance for the given constraint name.
*
* @param string $name The constraint name. Either a constraint relative
* to the default constraint namespace, or a fully
* qualified class name. Alternatively, the constraint
* may be preceded by a namespace alias and a colon.
* The namespace alias must have been defined using
* {@link addNamespaceAlias()}.
* @param mixed $options The constraint options
*
* @throws MappingException If the namespace prefix is undefined
*/
protected function newConstraint(string $name, mixed $options = null): Constraint
{
if (str_contains($name, '\\') && class_exists($name)) {
$className = $name;
} elseif (str_contains($name, ':')) {
[$prefix, $className] = explode(':', $name, 2);
if (!isset($this->namespaces[$prefix])) {
throw new MappingException(\sprintf('Undefined namespace prefix "%s".', $prefix));
}
$className = $this->namespaces[$prefix].$className;
} else {
$className = self::DEFAULT_NAMESPACE.$name;
}
if ($this->namedArgumentsCache[$className] ??= (bool) (new \ReflectionMethod($className, '__construct'))->getAttributes(HasNamedArguments::class)) {
if (null === $options) {
return new $className();
}
if (!\is_array($options)) {
return new $className($options);
}
if (1 === \count($options) && isset($options['value'])) {
return new $className($options['value']);
}
if (array_is_list($options)) {
return new $className($options);
}
try {
return new $className(...$options);
} catch (\Error $e) {
if (str_starts_with($e->getMessage(), 'Unknown named parameter ')) {
return new $className($options);
}
throw $e;
}
}
if ($options) {
trigger_deprecation('symfony/validator', '7.3', 'Using constraints not supporting named arguments is deprecated. Try adding the HasNamedArguments attribute to %s.', $className);
return new $className($options);
}
return new $className();
}
}

View File

@ -0,0 +1,96 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Mapping\Loader;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Constraints\GroupSequence;
use Symfony\Component\Validator\Constraints\GroupSequenceProvider;
use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Mapping\ClassMetadata;
/**
* Loads validation metadata using PHP attributes.
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Alexander M. Turek <me@derrabus.de>
* @author Alexandre Daubois <alex.daubois@gmail.com>
*/
class AttributeLoader implements LoaderInterface
{
public function loadClassMetadata(ClassMetadata $metadata): bool
{
$reflClass = $metadata->getReflectionClass();
$className = $reflClass->name;
$success = false;
foreach ($this->getAttributes($reflClass) as $constraint) {
if ($constraint instanceof GroupSequence) {
$metadata->setGroupSequence($constraint->groups);
} elseif ($constraint instanceof GroupSequenceProvider) {
$metadata->setGroupProvider($constraint->provider);
$metadata->setGroupSequenceProvider(true);
} elseif ($constraint instanceof Constraint) {
$metadata->addConstraint($constraint);
}
$success = true;
}
foreach ($reflClass->getProperties() as $property) {
if ($property->getDeclaringClass()->name === $className) {
foreach ($this->getAttributes($property) as $constraint) {
if ($constraint instanceof Constraint) {
$metadata->addPropertyConstraint($property->name, $constraint);
}
$success = true;
}
}
}
foreach ($reflClass->getMethods() as $method) {
if ($method->getDeclaringClass()->name === $className) {
foreach ($this->getAttributes($method) as $constraint) {
if ($constraint instanceof Callback) {
$constraint->callback = $method->getName();
$metadata->addConstraint($constraint);
} elseif ($constraint instanceof Constraint) {
if (preg_match('/^(get|is|has)(.+)$/i', $method->name, $matches)) {
$metadata->addGetterMethodConstraint(lcfirst($matches[2]), $matches[0], $constraint);
} else {
throw new MappingException(\sprintf('The constraint on "%s::%s()" cannot be added. Constraints can only be added on methods beginning with "get", "is" or "has".', $className, $method->name));
}
}
$success = true;
}
}
}
return $success;
}
private function getAttributes(\ReflectionMethod|\ReflectionClass|\ReflectionProperty $reflection): iterable
{
foreach ($reflection->getAttributes(GroupSequence::class) as $attribute) {
yield $attribute->newInstance();
}
foreach ($reflection->getAttributes(GroupSequenceProvider::class) as $attribute) {
yield $attribute->newInstance();
}
foreach ($reflection->getAttributes(Constraint::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
yield $attribute->newInstance();
}
}
}

View File

@ -0,0 +1,34 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Mapping\Loader;
use Symfony\Component\Validator\Mapping\AutoMappingStrategy;
use Symfony\Component\Validator\Mapping\ClassMetadata;
/**
* Utility methods to create auto mapping loaders.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
trait AutoMappingTrait
{
private function isAutoMappingEnabledForClass(ClassMetadata $metadata, ?string $classValidatorRegexp = null): bool
{
// Check if AutoMapping constraint is set first
if (AutoMappingStrategy::NONE !== $strategy = $metadata->getAutoMappingStrategy()) {
return AutoMappingStrategy::ENABLED === $strategy;
}
// Fallback on the config
return null !== $classValidatorRegexp && preg_match($classValidatorRegexp, $metadata->getClassName());
}
}

View File

@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Mapping\Loader;
use Symfony\Component\Validator\Exception\MappingException;
/**
* Base loader for loading validation metadata from a file.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @see YamlFileLoader
* @see XmlFileLoader
*/
abstract class FileLoader extends AbstractLoader
{
/**
* Creates a new loader.
*
* @param string $file The mapping file to load
*
* @throws MappingException If the file does not exist or is not readable
*/
public function __construct(
protected string $file,
) {
if (!is_file($file)) {
throw new MappingException(\sprintf('The mapping file "%s" does not exist.', $file));
}
if (!is_readable($file)) {
throw new MappingException(\sprintf('The mapping file "%s" is not readable.', $file));
}
if (!stream_is_local($this->file)) {
throw new MappingException(\sprintf('The mapping file "%s" is not a local file.', $file));
}
}
}

View File

@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Mapping\Loader;
/**
* Base loader for loading validation metadata from a list of files.
*
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @see YamlFilesLoader
* @see XmlFilesLoader
*/
abstract class FilesLoader extends LoaderChain
{
/**
* Creates a new loader.
*
* @param array $paths An array of file paths
*/
public function __construct(array $paths)
{
parent::__construct($this->getFileLoaders($paths));
}
/**
* Returns an array of file loaders for the given file paths.
*
* @return LoaderInterface[]
*/
protected function getFileLoaders(array $paths): array
{
$loaders = [];
foreach ($paths as $path) {
$loaders[] = $this->getFileLoaderInstance($path);
}
return $loaders;
}
/**
* Creates a loader for the given file path.
*/
abstract protected function getFileLoaderInstance(string $path): LoaderInterface;
}

View File

@ -0,0 +1,61 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Mapping\Loader;
use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Mapping\ClassMetadata;
/**
* Loads validation metadata from multiple {@link LoaderInterface} instances.
*
* Pass the loaders when constructing the chain. Once
* {@link loadClassMetadata()} is called, that method will be called on all
* loaders in the chain.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class LoaderChain implements LoaderInterface
{
/**
* @param LoaderInterface[] $loaders The metadata loaders to use
*
* @throws MappingException If any of the loaders has an invalid type
*/
public function __construct(
protected array $loaders,
) {
foreach ($loaders as $loader) {
if (!$loader instanceof LoaderInterface) {
throw new MappingException(\sprintf('Class "%s" is expected to implement LoaderInterface.', get_debug_type($loader)));
}
}
}
public function loadClassMetadata(ClassMetadata $metadata): bool
{
$success = false;
foreach ($this->loaders as $loader) {
$success = $loader->loadClassMetadata($metadata) || $success;
}
return $success;
}
/**
* @return LoaderInterface[]
*/
public function getLoaders(): array
{
return $this->loaders;
}
}

View File

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Mapping\Loader;
use Symfony\Component\Validator\Mapping\ClassMetadata;
/**
* Loads validation metadata into {@link ClassMetadata} instances.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface LoaderInterface
{
/**
* Loads validation metadata into a {@link ClassMetadata} instance.
*/
public function loadClassMetadata(ClassMetadata $metadata): bool;
}

View File

@ -0,0 +1,326 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Mapping\Loader;
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\PropertyInfo\Type as PropertyInfoType;
use Symfony\Component\TypeInfo\Type as TypeInfoType;
use Symfony\Component\TypeInfo\Type\BuiltinType;
use Symfony\Component\TypeInfo\Type\CollectionType;
use Symfony\Component\TypeInfo\Type\CompositeTypeInterface;
use Symfony\Component\TypeInfo\Type\IntersectionType;
use Symfony\Component\TypeInfo\Type\NullableType;
use Symfony\Component\TypeInfo\Type\ObjectType;
use Symfony\Component\TypeInfo\Type\UnionType;
use Symfony\Component\TypeInfo\Type\WrappingTypeInterface;
use Symfony\Component\TypeInfo\TypeIdentifier;
use Symfony\Component\Validator\Constraints\All;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Constraints\Type;
use Symfony\Component\Validator\Mapping\AutoMappingStrategy;
use Symfony\Component\Validator\Mapping\ClassMetadata;
/**
* Guesses and loads the appropriate constraints using PropertyInfo.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
final class PropertyInfoLoader implements LoaderInterface
{
use AutoMappingTrait;
public function __construct(
private PropertyListExtractorInterface $listExtractor,
private PropertyTypeExtractorInterface $typeExtractor,
private PropertyAccessExtractorInterface $accessExtractor,
private ?string $classValidatorRegexp = null,
) {
}
public function loadClassMetadata(ClassMetadata $metadata): bool
{
$className = $metadata->getClassName();
if (!$properties = $this->listExtractor->getProperties($className)) {
return false;
}
$loaded = false;
$enabledForClass = $this->isAutoMappingEnabledForClass($metadata, $this->classValidatorRegexp);
foreach ($properties as $property) {
if (false === $this->accessExtractor->isWritable($className, $property)) {
continue;
}
if (!property_exists($className, $property)) {
continue;
}
$types = $this->getPropertyTypes($className, $property);
if (null === $types) {
continue;
}
$enabledForProperty = $enabledForClass;
$hasTypeConstraint = false;
$hasNotNullConstraint = false;
$hasNotBlankConstraint = false;
$allConstraint = null;
foreach ($metadata->getPropertyMetadata($property) as $propertyMetadata) {
// Enabling or disabling auto-mapping explicitly always takes precedence
if (AutoMappingStrategy::DISABLED === $propertyMetadata->getAutoMappingStrategy()) {
continue 2;
}
if (AutoMappingStrategy::ENABLED === $propertyMetadata->getAutoMappingStrategy()) {
$enabledForProperty = true;
}
foreach ($propertyMetadata->getConstraints() as $constraint) {
if ($constraint instanceof Type) {
$hasTypeConstraint = true;
} elseif ($constraint instanceof NotNull) {
$hasNotNullConstraint = true;
} elseif ($constraint instanceof NotBlank) {
$hasNotBlankConstraint = true;
} elseif ($constraint instanceof All) {
$allConstraint = $constraint;
}
}
}
if (!$enabledForProperty) {
continue;
}
$loaded = true;
// BC layer for PropertyTypeExtractorInterface::getTypes().
// Can be removed as soon as PropertyTypeExtractorInterface::getTypes() is removed (8.0).
if (\is_array($types)) {
$builtinTypes = [];
$nullable = false;
$scalar = true;
foreach ($types as $type) {
$builtinTypes[] = $type->getBuiltinType();
if ($scalar && !\in_array($type->getBuiltinType(), ['int', 'float', 'string', 'bool'], true)) {
$scalar = false;
}
if (!$nullable && $type->isNullable()) {
$nullable = true;
}
}
if (!$hasTypeConstraint) {
if (1 === \count($builtinTypes)) {
if ($types[0]->isCollection() && \count($collectionValueType = $types[0]->getCollectionValueTypes()) > 0) {
[$collectionValueType] = $collectionValueType;
$this->handleAllConstraintLegacy($property, $allConstraint, $collectionValueType, $metadata);
}
$metadata->addPropertyConstraint($property, $this->getTypeConstraintLegacy($builtinTypes[0], $types[0]));
} elseif ($scalar) {
$metadata->addPropertyConstraint($property, new Type(type: 'scalar'));
}
}
} else {
if ($hasTypeConstraint) {
continue;
}
$type = $types;
// BC layer for type-info < 7.2
if (!class_exists(NullableType::class)) {
$nullable = false;
if ($type instanceof UnionType && $type->isNullable()) {
$nullable = true;
$type = $type->asNonNullable();
}
} else {
$nullable = $type->isNullable();
if ($type instanceof NullableType) {
$type = $type->getWrappedType();
}
}
if ($type instanceof NullableType) {
$type = $type->getWrappedType();
}
if ($type instanceof CollectionType) {
$this->handleAllConstraint($property, $allConstraint, $type->getCollectionValueType(), $metadata);
}
if (null !== $typeConstraint = $this->getTypeConstraint($type)) {
$metadata->addPropertyConstraint($property, $typeConstraint);
}
}
if (!$nullable && !$hasNotBlankConstraint && !$hasNotNullConstraint) {
$metadata->addPropertyConstraint($property, new NotNull());
}
}
return $loaded;
}
/**
* BC layer for PropertyTypeExtractorInterface::getTypes().
* Can be removed as soon as PropertyTypeExtractorInterface::getTypes() is removed (8.0).
*
* @return TypeInfoType|list<PropertyInfoType>|null
*/
private function getPropertyTypes(string $className, string $property): TypeInfoType|array|null
{
if (class_exists(TypeInfoType::class) && method_exists($this->typeExtractor, 'getType')) {
return $this->typeExtractor->getType($className, $property);
}
return $this->typeExtractor->getTypes($className, $property);
}
/**
* BC layer for PropertyTypeExtractorInterface::getTypes().
* Can be removed as soon as PropertyTypeExtractorInterface::getTypes() is removed (8.0).
*/
private function getTypeConstraintLegacy(string $builtinType, PropertyInfoType $type): Type
{
if (PropertyInfoType::BUILTIN_TYPE_OBJECT === $builtinType && null !== $className = $type->getClassName()) {
return new Type(type: $className);
}
return new Type(type: $builtinType);
}
private function getTypeConstraint(TypeInfoType $type): ?Type
{
// BC layer for type-info < 7.2
if (!interface_exists(CompositeTypeInterface::class)) {
if ($type instanceof UnionType || $type instanceof IntersectionType) {
return ($type->isA(TypeIdentifier::INT) || $type->isA(TypeIdentifier::FLOAT) || $type->isA(TypeIdentifier::STRING) || $type->isA(TypeIdentifier::BOOL)) ? new Type(['type' => 'scalar']) : null;
}
$baseType = $type->getBaseType();
if ($baseType instanceof ObjectType) {
return new Type(type: $baseType->getClassName());
}
if (TypeIdentifier::MIXED !== $baseType->getTypeIdentifier()) {
return new Type(type: $baseType->getTypeIdentifier()->value);
}
return null;
}
if ($type instanceof CompositeTypeInterface) {
return $type->isIdentifiedBy(
TypeIdentifier::INT,
TypeIdentifier::FLOAT,
TypeIdentifier::STRING,
TypeIdentifier::BOOL,
TypeIdentifier::TRUE,
TypeIdentifier::FALSE,
) ? new Type(type: 'scalar') : null;
}
while ($type instanceof WrappingTypeInterface) {
$type = $type->getWrappedType();
}
if ($type instanceof ObjectType) {
return new Type(type: $type->getClassName());
}
if ($type instanceof BuiltinType && TypeIdentifier::MIXED !== $type->getTypeIdentifier()) {
return new Type(type: $type->getTypeIdentifier()->value);
}
return null;
}
private function handleAllConstraint(string $property, ?All $allConstraint, TypeInfoType $type, ClassMetadata $metadata): void
{
$containsTypeConstraint = false;
$containsNotNullConstraint = false;
if (null !== $allConstraint) {
foreach ($allConstraint->constraints as $constraint) {
if ($constraint instanceof Type) {
$containsTypeConstraint = true;
} elseif ($constraint instanceof NotNull) {
$containsNotNullConstraint = true;
}
}
}
$constraints = [];
if (!$containsNotNullConstraint && !$type->isNullable()) {
$constraints[] = new NotNull();
}
if (!$containsTypeConstraint && null !== $typeConstraint = $this->getTypeConstraint($type)) {
$constraints[] = $typeConstraint;
}
if (!$constraints) {
return;
}
if (null === $allConstraint) {
$metadata->addPropertyConstraint($property, new All(constraints: $constraints));
} else {
$allConstraint->constraints = array_merge($allConstraint->constraints, $constraints);
}
}
/**
* BC layer for PropertyTypeExtractorInterface::getTypes().
* Can be removed as soon as PropertyTypeExtractorInterface::getTypes() is removed (8.0).
*/
private function handleAllConstraintLegacy(string $property, ?All $allConstraint, PropertyInfoType $propertyInfoType, ClassMetadata $metadata): void
{
$containsTypeConstraint = false;
$containsNotNullConstraint = false;
if (null !== $allConstraint) {
foreach ($allConstraint->constraints as $constraint) {
if ($constraint instanceof Type) {
$containsTypeConstraint = true;
} elseif ($constraint instanceof NotNull) {
$containsNotNullConstraint = true;
}
}
}
$constraints = [];
if (!$containsNotNullConstraint && !$propertyInfoType->isNullable()) {
$constraints[] = new NotNull();
}
if (!$containsTypeConstraint) {
$constraints[] = $this->getTypeConstraintLegacy($propertyInfoType->getBuiltinType(), $propertyInfoType);
}
if (null === $allConstraint) {
$metadata->addPropertyConstraint($property, new All(constraints: $constraints));
} else {
$allConstraint->constraints = array_merge($allConstraint->constraints, $constraints);
}
}
}

View File

@ -0,0 +1,61 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Mapping\Loader;
use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Mapping\ClassMetadata;
/**
* Loads validation metadata by calling a static method on the loaded class.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class StaticMethodLoader implements LoaderInterface
{
/**
* Creates a new loader.
*
* @param string $methodName The name of the static method to call
*/
public function __construct(
protected string $methodName = 'loadValidatorMetadata',
) {
}
public function loadClassMetadata(ClassMetadata $metadata): bool
{
/** @var \ReflectionClass $reflClass */
$reflClass = $metadata->getReflectionClass();
if (!$reflClass->isInterface() && $reflClass->hasMethod($this->methodName)) {
$reflMethod = $reflClass->getMethod($this->methodName);
if ($reflMethod->isAbstract()) {
return false;
}
if (!$reflMethod->isStatic()) {
throw new MappingException(\sprintf('The method "%s::%s()" should be static.', $reflClass->name, $this->methodName));
}
if ($reflMethod->getDeclaringClass()->name != $reflClass->name) {
return false;
}
$reflMethod->invoke(null, $metadata);
return true;
}
return false;
}
}

View File

@ -0,0 +1,236 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Mapping\Loader;
use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Mapping\ClassMetadata;
/**
* Loads validation metadata from an XML file.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class XmlFileLoader extends FileLoader
{
/**
* The XML nodes of the mapping file.
*
* @var \SimpleXMLElement[]
*/
protected array $classes;
public function __construct(string $file)
{
$this->file = $file;
}
public function loadClassMetadata(ClassMetadata $metadata): bool
{
if (!isset($this->classes)) {
$this->loadClassesFromXml();
}
if (isset($this->classes[$metadata->getClassName()])) {
$classDescription = $this->classes[$metadata->getClassName()];
$this->loadClassMetadataFromXml($metadata, $classDescription);
return true;
}
return false;
}
/**
* Return the names of the classes mapped in this file.
*
* @return string[]
*/
public function getMappedClasses(): array
{
if (!isset($this->classes)) {
$this->loadClassesFromXml();
}
return array_keys($this->classes);
}
/**
* Parses a collection of "constraint" XML nodes.
*
* @param \SimpleXMLElement $nodes The XML nodes
*
* @return Constraint[]
*/
protected function parseConstraints(\SimpleXMLElement $nodes): array
{
$constraints = [];
foreach ($nodes as $node) {
if (\count($node) > 0) {
if (\count($node->value) > 0) {
$options = [
'value' => $this->parseValues($node->value),
];
} elseif (\count($node->constraint) > 0) {
$options = $this->parseConstraints($node->constraint);
} elseif (\count($node->option) > 0) {
$options = $this->parseOptions($node->option);
} else {
$options = [];
}
} elseif ('' !== (string) $node) {
$options = XmlUtils::phpize(trim($node));
} else {
$options = null;
}
if (isset($options['groups']) && !\is_array($options['groups'])) {
$options['groups'] = (array) $options['groups'];
}
$constraints[] = $this->newConstraint((string) $node['name'], $options);
}
return $constraints;
}
/**
* Parses a collection of "value" XML nodes.
*
* @param \SimpleXMLElement $nodes The XML nodes
*/
protected function parseValues(\SimpleXMLElement $nodes): array
{
$values = [];
foreach ($nodes as $node) {
if (\count($node) > 0) {
if (\count($node->value) > 0) {
$value = $this->parseValues($node->value);
} elseif (\count($node->constraint) > 0) {
$value = $this->parseConstraints($node->constraint);
} else {
$value = [];
}
} else {
$value = trim($node);
}
if (isset($node['key'])) {
$values[(string) $node['key']] = $value;
} else {
$values[] = $value;
}
}
return $values;
}
/**
* Parses a collection of "option" XML nodes.
*
* @param \SimpleXMLElement $nodes The XML nodes
*/
protected function parseOptions(\SimpleXMLElement $nodes): array
{
$options = [];
foreach ($nodes as $node) {
if (\count($node) > 0) {
if (\count($node->value) > 0) {
$value = $this->parseValues($node->value);
} elseif (\count($node->constraint) > 0) {
$value = $this->parseConstraints($node->constraint);
} else {
$value = [];
}
} else {
$value = XmlUtils::phpize($node);
if (\is_string($value)) {
$value = trim($value);
}
}
$options[(string) $node['name']] = $value;
}
return $options;
}
/**
* Loads the XML class descriptions from the given file.
*
* @throws MappingException If the file could not be loaded
*/
protected function parseFile(string $path): \SimpleXMLElement
{
try {
$dom = XmlUtils::loadFile($path, __DIR__.'/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd');
} catch (\Exception $e) {
throw new MappingException($e->getMessage(), $e->getCode(), $e);
}
return simplexml_import_dom($dom);
}
private function loadClassesFromXml(): void
{
parent::__construct($this->file);
// This method may throw an exception. Do not modify the class'
// state before it completes
$xml = $this->parseFile($this->file);
$this->classes = [];
foreach ($xml->namespace as $namespace) {
$this->addNamespaceAlias((string) $namespace['prefix'], trim((string) $namespace));
}
foreach ($xml->class as $class) {
$this->classes[(string) $class['name']] = $class;
}
}
private function loadClassMetadataFromXml(ClassMetadata $metadata, \SimpleXMLElement $classDescription): void
{
if (\count($classDescription->{'group-sequence-provider'}) > 0) {
$metadata->setGroupProvider($classDescription->{'group-sequence-provider'}[0]->value ?: null);
$metadata->setGroupSequenceProvider(true);
}
foreach ($classDescription->{'group-sequence'} as $groupSequence) {
if (\count($groupSequence->value) > 0) {
$metadata->setGroupSequence($this->parseValues($groupSequence[0]->value));
}
}
foreach ($this->parseConstraints($classDescription->constraint) as $constraint) {
$metadata->addConstraint($constraint);
}
foreach ($classDescription->property as $property) {
foreach ($this->parseConstraints($property->constraint) as $constraint) {
$metadata->addPropertyConstraint((string) $property['name'], $constraint);
}
}
foreach ($classDescription->getter as $getter) {
foreach ($this->parseConstraints($getter->constraint) as $constraint) {
$metadata->addGetterConstraint((string) $getter['property'], $constraint);
}
}
}
}

View File

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Mapping\Loader;
/**
* Loads validation metadata from a list of XML files.
*
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @see FilesLoader
*/
class XmlFilesLoader extends FilesLoader
{
public function getFileLoaderInstance(string $file): LoaderInterface
{
return new XmlFileLoader($file);
}
}

View File

@ -0,0 +1,192 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Mapping\Loader;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Parser as YamlParser;
use Symfony\Component\Yaml\Yaml;
/**
* Loads validation metadata from a YAML file.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class YamlFileLoader extends FileLoader
{
protected array $classes;
public function __construct(string $file)
{
$this->file = $file;
}
/**
* Caches the used YAML parser.
*/
private YamlParser $yamlParser;
public function loadClassMetadata(ClassMetadata $metadata): bool
{
if (!isset($this->classes)) {
$this->loadClassesFromYaml();
}
if (isset($this->classes[$metadata->getClassName()])) {
$classDescription = $this->classes[$metadata->getClassName()];
$this->loadClassMetadataFromYaml($metadata, $classDescription);
return true;
}
return false;
}
/**
* Return the names of the classes mapped in this file.
*
* @return string[]
*/
public function getMappedClasses(): array
{
if (!isset($this->classes)) {
$this->loadClassesFromYaml();
}
return array_keys($this->classes);
}
/**
* Parses a collection of YAML nodes.
*
* @param array $nodes The YAML nodes
*
* @return array<array|scalar|Constraint>
*/
protected function parseNodes(array $nodes): array
{
$values = [];
foreach ($nodes as $name => $childNodes) {
if (is_numeric($name) && \is_array($childNodes) && 1 === \count($childNodes)) {
$options = current($childNodes);
if (\is_array($options)) {
$options = $this->parseNodes($options);
}
if (null !== $options && (!\is_array($options) || array_is_list($options))) {
$options = [
'value' => $options,
];
}
$values[] = $this->newConstraint(key($childNodes), $options);
} else {
if (\is_array($childNodes)) {
$childNodes = $this->parseNodes($childNodes);
}
$values[$name] = $childNodes;
}
}
return $values;
}
/**
* Loads the YAML class descriptions from the given file.
*
* @throws \InvalidArgumentException If the file could not be loaded or did
* not contain a YAML array
*/
private function parseFile(string $path): array
{
try {
$classes = $this->yamlParser->parseFile($path, Yaml::PARSE_CONSTANT);
} catch (ParseException $e) {
throw new \InvalidArgumentException(\sprintf('The file "%s" does not contain valid YAML: ', $path).$e->getMessage(), 0, $e);
}
// empty file
if (null === $classes) {
return [];
}
// not an array
if (!\is_array($classes)) {
throw new \InvalidArgumentException(\sprintf('The file "%s" must contain a YAML array.', $this->file));
}
return $classes;
}
private function loadClassesFromYaml(): void
{
parent::__construct($this->file);
$this->yamlParser ??= new YamlParser();
$this->classes = $this->parseFile($this->file);
if (isset($this->classes['namespaces'])) {
foreach ($this->classes['namespaces'] as $alias => $namespace) {
$this->addNamespaceAlias($alias, $namespace);
}
unset($this->classes['namespaces']);
}
}
private function loadClassMetadataFromYaml(ClassMetadata $metadata, array $classDescription): void
{
if (isset($classDescription['group_sequence_provider'])) {
if (\is_string($classDescription['group_sequence_provider'])) {
$metadata->setGroupProvider($classDescription['group_sequence_provider']);
}
$metadata->setGroupSequenceProvider(
(bool) $classDescription['group_sequence_provider']
);
}
if (isset($classDescription['group_sequence'])) {
$metadata->setGroupSequence($classDescription['group_sequence']);
}
if (isset($classDescription['constraints']) && \is_array($classDescription['constraints'])) {
foreach ($this->parseNodes($classDescription['constraints']) as $constraint) {
$metadata->addConstraint($constraint);
}
}
if (isset($classDescription['properties']) && \is_array($classDescription['properties'])) {
foreach ($classDescription['properties'] as $property => $constraints) {
if (null !== $constraints) {
foreach ($this->parseNodes($constraints) as $constraint) {
$metadata->addPropertyConstraint($property, $constraint);
}
}
}
}
if (isset($classDescription['getters']) && \is_array($classDescription['getters'])) {
foreach ($classDescription['getters'] as $getter => $constraints) {
if (null !== $constraints) {
foreach ($this->parseNodes($constraints) as $constraint) {
$metadata->addGetterConstraint($getter, $constraint);
}
}
}
}
}
}

View File

@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Mapping\Loader;
/**
* Loads validation metadata from a list of YAML files.
*
* @author Bulat Shakirzyanov <mallluhuct@gmail.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @see FilesLoader
*/
class YamlFilesLoader extends FilesLoader
{
public function getFileLoaderInstance(string $file): LoaderInterface
{
return new YamlFileLoader($file);
}
}

View File

@ -0,0 +1,163 @@
<?xml version="1.0" ?>
<xsd:schema xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://symfony.com/schema/dic/constraint-mapping"
elementFormDefault="qualified">
<xsd:annotation>
<xsd:documentation><![CDATA[
Symfony Validator Constraint Mapping Schema, version 1.0
Authors: Bernhard Schussek
A constraint mapping connects classes, properties and getters with
validation constraints.
]]></xsd:documentation>
</xsd:annotation>
<xsd:element name="constraint-mapping" type="constraint-mapping" />
<xsd:complexType name="constraint-mapping">
<xsd:annotation>
<xsd:documentation><![CDATA[
The root element of the constraint mapping definition.
]]></xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="namespace" type="namespace" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="class" type="class" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="namespace">
<xsd:annotation>
<xsd:documentation><![CDATA[
Contains the abbreviation for a namespace.
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="prefix" type="xsd:string" use="required" />
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:complexType name="class">
<xsd:annotation>
<xsd:documentation><![CDATA[
Contains constraints for a single class.
Nested elements may be class constraints, property and/or getter
definitions.
]]></xsd:documentation>
</xsd:annotation>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="group-sequence-provider" type="group-sequence-provider" minOccurs="0" maxOccurs="1" />
<xsd:element name="group-sequence" type="group-sequence" minOccurs="0" maxOccurs="1" />
<xsd:element name="constraint" type="constraint" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="property" type="property" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="getter" type="getter" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
<xsd:complexType name="group-sequence">
<xsd:annotation>
<xsd:documentation><![CDATA[
Contains the group sequence of a class. Each group should be written
into a "value" tag.
]]></xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="value" type="value" minOccurs="1" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="group-sequence-provider">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the name of the group sequence provider for a class.
]]></xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="value" type="value" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="property">
<xsd:annotation>
<xsd:documentation><![CDATA[
Contains constraints for a single property. The name of the property
should be given in the "name" option.
]]></xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="constraint" type="constraint" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
<xsd:complexType name="getter">
<xsd:annotation>
<xsd:documentation><![CDATA[
Contains constraints for a getter method. The name of the corresponding
property should be given in the "property" option.
]]></xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="constraint" type="constraint" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="property" type="xsd:string" use="required" />
</xsd:complexType>
<xsd:complexType name="constraint" mixed="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
Contains a constraint definition. The name of the constraint should be
given in the "name" option.
May contain a single value, multiple "constraint" elements,
multiple "value" elements or multiple "option" elements.
]]></xsd:documentation>
</xsd:annotation>
<xsd:choice minOccurs="0">
<xsd:element name="constraint" type="constraint" minOccurs="1" maxOccurs="unbounded" />
<xsd:element name="option" type="option" minOccurs="1" maxOccurs="unbounded" />
<xsd:element name="value" type="value" minOccurs="1" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
<xsd:complexType name="option" mixed="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
Contains a constraint option definition. The name of the option
should be given in the "name" option.
May contain a single value, multiple "value" elements or multiple
"constraint" elements.
]]></xsd:documentation>
</xsd:annotation>
<xsd:choice minOccurs="0">
<xsd:element name="constraint" type="constraint" minOccurs="1" maxOccurs="unbounded" />
<xsd:element name="value" type="value" minOccurs="1" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
<xsd:complexType name="value" mixed="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
A value of an element.
May contain a single value, multiple "value" elements or multiple
"constraint" elements.
]]></xsd:documentation>
</xsd:annotation>
<xsd:choice minOccurs="0">
<xsd:element name="constraint" type="constraint" minOccurs="1" maxOccurs="unbounded" />
<xsd:element name="value" type="value" minOccurs="1" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="key" type="xsd:string" use="optional" />
</xsd:complexType>
</xsd:schema>