first commit

This commit is contained in:
2024-07-15 12:33:27 +02:00
commit ce50ae282b
22084 changed files with 2623791 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
<?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\Serializer\Mapping\Loader;
trigger_deprecation('symfony/serializer', '6.4', 'The "%s" class is deprecated, use "%s" instead.', AnnotationLoader::class, AttributeLoader::class);
class_exists(AttributeLoader::class);
if (false) {
/**
* @deprecated since Symfony 6.4, to be removed in 7.0, use {@link AttributeLoader} instead
*/
class AnnotationLoader extends AttributeLoader
{
}
}

View File

@@ -0,0 +1,311 @@
<?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\Serializer\Mapping\Loader;
use Doctrine\Common\Annotations\Reader;
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Attribute\DiscriminatorMap;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Attribute\Ignore;
use Symfony\Component\Serializer\Attribute\MaxDepth;
use Symfony\Component\Serializer\Attribute\SerializedName;
use Symfony\Component\Serializer\Attribute\SerializedPath;
use Symfony\Component\Serializer\Exception\MappingException;
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
/**
* Loader for PHP attributes.
*
* @author Kévin Dunglas <dunglas@gmail.com>
* @author Alexander M. Turek <me@derrabus.de>
* @author Alexandre Daubois <alex.daubois@gmail.com>
*/
class AttributeLoader implements LoaderInterface
{
private const KNOWN_ATTRIBUTES = [
DiscriminatorMap::class,
Groups::class,
Ignore::class,
MaxDepth::class,
SerializedName::class,
SerializedPath::class,
Context::class,
];
public function __construct(
private readonly ?Reader $reader = null,
) {
if ($reader) {
trigger_deprecation('symfony/serializer', '6.4', 'Passing a "%s" instance as argument 1 to "%s()" is deprecated, pass null or omit the parameter instead.', get_debug_type($reader), __METHOD__);
}
}
public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
{
$reflectionClass = $classMetadata->getReflectionClass();
$className = $reflectionClass->name;
$loaded = false;
$classGroups = [];
$classContextAnnotation = null;
$attributesMetadata = $classMetadata->getAttributesMetadata();
foreach ($this->loadAttributes($reflectionClass) as $annotation) {
if ($annotation instanceof DiscriminatorMap) {
$classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping(
$annotation->getTypeProperty(),
$annotation->getMapping()
));
continue;
}
if ($annotation instanceof Groups) {
$classGroups = $annotation->getGroups();
continue;
}
if ($annotation instanceof Context) {
$classContextAnnotation = $annotation;
}
}
foreach ($reflectionClass->getProperties() as $property) {
if (!isset($attributesMetadata[$property->name])) {
$attributesMetadata[$property->name] = new AttributeMetadata($property->name);
$classMetadata->addAttributeMetadata($attributesMetadata[$property->name]);
}
if ($property->getDeclaringClass()->name === $className) {
if ($classContextAnnotation) {
$this->setAttributeContextsForGroups($classContextAnnotation, $attributesMetadata[$property->name]);
}
foreach ($classGroups as $group) {
$attributesMetadata[$property->name]->addGroup($group);
}
foreach ($this->loadAttributes($property) as $annotation) {
if ($annotation instanceof Groups) {
foreach ($annotation->getGroups() as $group) {
$attributesMetadata[$property->name]->addGroup($group);
}
} elseif ($annotation instanceof MaxDepth) {
$attributesMetadata[$property->name]->setMaxDepth($annotation->getMaxDepth());
} elseif ($annotation instanceof SerializedName) {
$attributesMetadata[$property->name]->setSerializedName($annotation->getSerializedName());
} elseif ($annotation instanceof SerializedPath) {
$attributesMetadata[$property->name]->setSerializedPath($annotation->getSerializedPath());
} elseif ($annotation instanceof Ignore) {
$attributesMetadata[$property->name]->setIgnore(true);
} elseif ($annotation instanceof Context) {
$this->setAttributeContextsForGroups($annotation, $attributesMetadata[$property->name]);
}
$loaded = true;
}
}
}
foreach ($reflectionClass->getMethods() as $method) {
if ($method->getDeclaringClass()->name !== $className) {
continue;
}
if (0 === stripos($method->name, 'get') && $method->getNumberOfRequiredParameters()) {
continue; /* matches the BC behavior in `Symfony\Component\Serializer\Normalizer\ObjectNormalizer::extractAttributes` */
}
$accessorOrMutator = preg_match('/^(get|is|has|set)(.+)$/i', $method->name, $matches);
if ($accessorOrMutator) {
$attributeName = lcfirst($matches[2]);
if (isset($attributesMetadata[$attributeName])) {
$attributeMetadata = $attributesMetadata[$attributeName];
} else {
$attributesMetadata[$attributeName] = $attributeMetadata = new AttributeMetadata($attributeName);
$classMetadata->addAttributeMetadata($attributeMetadata);
}
}
foreach ($this->loadAttributes($method) as $annotation) {
if ($annotation instanceof Groups) {
if (!$accessorOrMutator) {
throw new MappingException(sprintf('Groups on "%s::%s()" cannot be added. Groups can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name));
}
foreach ($annotation->getGroups() as $group) {
$attributeMetadata->addGroup($group);
}
} elseif ($annotation instanceof MaxDepth) {
if (!$accessorOrMutator) {
throw new MappingException(sprintf('MaxDepth on "%s::%s()" cannot be added. MaxDepth can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name));
}
$attributeMetadata->setMaxDepth($annotation->getMaxDepth());
} elseif ($annotation instanceof SerializedName) {
if (!$accessorOrMutator) {
throw new MappingException(sprintf('SerializedName on "%s::%s()" cannot be added. SerializedName can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name));
}
$attributeMetadata->setSerializedName($annotation->getSerializedName());
} elseif ($annotation instanceof SerializedPath) {
if (!$accessorOrMutator) {
throw new MappingException(sprintf('SerializedPath on "%s::%s()" cannot be added. SerializedPath can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name));
}
$attributeMetadata->setSerializedPath($annotation->getSerializedPath());
} elseif ($annotation instanceof Ignore) {
if ($accessorOrMutator) {
$attributeMetadata->setIgnore(true);
}
} elseif ($annotation instanceof Context) {
if (!$accessorOrMutator) {
throw new MappingException(sprintf('Context on "%s::%s()" cannot be added. Context can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name));
}
$this->setAttributeContextsForGroups($annotation, $attributeMetadata);
}
$loaded = true;
}
}
return $loaded;
}
private function loadAttributes(\ReflectionMethod|\ReflectionClass|\ReflectionProperty $reflector): iterable
{
foreach ($reflector->getAttributes() as $attribute) {
if ($this->isKnownAttribute($attribute->getName())) {
try {
yield $attribute->newInstance();
} catch (\Error $e) {
if (\Error::class !== $e::class) {
throw $e;
}
$on = match (true) {
$reflector instanceof \ReflectionClass => ' on class '.$reflector->name,
$reflector instanceof \ReflectionMethod => sprintf(' on "%s::%s()"', $reflector->getDeclaringClass()->name, $reflector->name),
$reflector instanceof \ReflectionProperty => sprintf(' on "%s::$%s"', $reflector->getDeclaringClass()->name, $reflector->name),
default => '',
};
throw new MappingException(sprintf('Could not instantiate attribute "%s"%s.', $attribute->getName(), $on), 0, $e);
}
}
}
if (null === $this->reader) {
return;
}
if ($reflector instanceof \ReflectionClass) {
yield from $this->getClassAnnotations($reflector);
}
if ($reflector instanceof \ReflectionMethod) {
yield from $this->getMethodAnnotations($reflector);
}
if ($reflector instanceof \ReflectionProperty) {
yield from $this->getPropertyAnnotations($reflector);
}
}
/**
* @deprecated since Symfony 6.4 without replacement
*/
public function loadAnnotations(\ReflectionMethod|\ReflectionClass|\ReflectionProperty $reflector): iterable
{
trigger_deprecation('symfony/serializer', '6.4', 'Method "%s()" is deprecated without replacement.', __METHOD__);
return $this->loadAttributes($reflector);
}
private function setAttributeContextsForGroups(Context $annotation, AttributeMetadataInterface $attributeMetadata): void
{
if ($annotation->getContext()) {
$attributeMetadata->setNormalizationContextForGroups($annotation->getContext(), $annotation->getGroups());
$attributeMetadata->setDenormalizationContextForGroups($annotation->getContext(), $annotation->getGroups());
}
if ($annotation->getNormalizationContext()) {
$attributeMetadata->setNormalizationContextForGroups($annotation->getNormalizationContext(), $annotation->getGroups());
}
if ($annotation->getDenormalizationContext()) {
$attributeMetadata->setDenormalizationContextForGroups($annotation->getDenormalizationContext(), $annotation->getGroups());
}
}
private function isKnownAttribute(string $attributeName): bool
{
foreach (self::KNOWN_ATTRIBUTES as $knownAttribute) {
if (is_a($attributeName, $knownAttribute, true)) {
return true;
}
}
return false;
}
/**
* @return object[]
*/
private function getClassAnnotations(\ReflectionClass $reflector): array
{
if ($annotations = array_filter(
$this->reader->getClassAnnotations($reflector),
fn (object $annotation): bool => $this->isKnownAttribute($annotation::class),
)) {
trigger_deprecation('symfony/serializer', '6.4', 'Class "%s" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.', $reflector->getName());
}
return $annotations;
}
/**
* @return object[]
*/
private function getMethodAnnotations(\ReflectionMethod $reflector): array
{
if ($annotations = array_filter(
$this->reader->getMethodAnnotations($reflector),
fn (object $annotation): bool => $this->isKnownAttribute($annotation::class),
)) {
trigger_deprecation('symfony/serializer', '6.4', 'Method "%s::%s()" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.', $reflector->getDeclaringClass()->getName(), $reflector->getName());
}
return $annotations;
}
/**
* @return object[]
*/
private function getPropertyAnnotations(\ReflectionProperty $reflector): array
{
if ($annotations = array_filter(
$this->reader->getPropertyAnnotations($reflector),
fn (object $annotation): bool => $this->isKnownAttribute($annotation::class),
)) {
trigger_deprecation('symfony/serializer', '6.4', 'Property "%s::$%s" uses Doctrine Annotations to configure serialization, which is deprecated. Use PHP attributes instead.', $reflector->getDeclaringClass()->getName(), $reflector->getName());
}
return $annotations;
}
}
if (!class_exists(AnnotationLoader::class, false)) {
class_alias(AttributeLoader::class, AnnotationLoader::class);
}

View File

@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Mapping\Loader;
use Symfony\Component\Serializer\Exception\MappingException;
/**
* Base class for all file based loaders.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
abstract class FileLoader implements LoaderInterface
{
protected $file;
/**
* @param string $file The mapping file to load
*
* @throws MappingException if the mapping file does not exist or is not readable
*/
public function __construct(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));
}
$this->file = $file;
}
}

View File

@@ -0,0 +1,64 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Serializer\Mapping\Loader;
use Symfony\Component\Serializer\Exception\MappingException;
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
/**
* Calls multiple {@link LoaderInterface} instances in a chain.
*
* This class accepts multiple instances of LoaderInterface to be passed to the
* constructor. When {@link loadClassMetadata()} is called, the same method is called
* in <em>all</em> of these loaders, regardless of whether any of them was
* successful or not.
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class LoaderChain implements LoaderInterface
{
/**
* Accepts a list of LoaderInterface instances.
*
* @param LoaderInterface[] $loaders An array of LoaderInterface instances
*
* @throws MappingException If any of the loaders does not implement LoaderInterface
*/
public function __construct(private readonly 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(ClassMetadataInterface $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,24 @@
<?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\Serializer\Mapping\Loader;
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
/**
* Loads {@link ClassMetadataInterface}.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface LoaderInterface
{
public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool;
}

View File

@@ -0,0 +1,182 @@
<?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\Serializer\Mapping\Loader;
use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException;
use Symfony\Component\PropertyAccess\PropertyPath;
use Symfony\Component\Serializer\Exception\MappingException;
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
/**
* Loads XML mapping files.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class XmlFileLoader extends FileLoader
{
/**
* An array of {@class \SimpleXMLElement} instances.
*
* @var \SimpleXMLElement[]|null
*/
private ?array $classes = null;
public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
{
if (!$this->classes ??= $this->getClassesFromXml()) {
return false;
}
$attributesMetadata = $classMetadata->getAttributesMetadata();
if (isset($this->classes[$classMetadata->getName()])) {
$xml = $this->classes[$classMetadata->getName()];
foreach ($xml->attribute as $attribute) {
$attributeName = (string) $attribute['name'];
if (isset($attributesMetadata[$attributeName])) {
$attributeMetadata = $attributesMetadata[$attributeName];
} else {
$attributeMetadata = new AttributeMetadata($attributeName);
$classMetadata->addAttributeMetadata($attributeMetadata);
}
foreach ($attribute->group as $group) {
$attributeMetadata->addGroup((string) $group);
}
if (isset($attribute['max-depth'])) {
$attributeMetadata->setMaxDepth((int) $attribute['max-depth']);
}
if (isset($attribute['serialized-name'])) {
$attributeMetadata->setSerializedName((string) $attribute['serialized-name']);
}
if (isset($attribute['serialized-path'])) {
try {
$attributeMetadata->setSerializedPath(new PropertyPath((string) $attribute['serialized-path']));
} catch (InvalidPropertyPathException) {
throw new MappingException(sprintf('The "serialized-path" value must be a valid property path for the attribute "%s" of the class "%s".', $attributeName, $classMetadata->getName()));
}
}
if (isset($attribute['ignore'])) {
$attributeMetadata->setIgnore(XmlUtils::phpize($attribute['ignore']));
}
foreach ($attribute->context as $node) {
$groups = (array) $node->group;
$context = $this->parseContext($node->entry);
$attributeMetadata->setNormalizationContextForGroups($context, $groups);
$attributeMetadata->setDenormalizationContextForGroups($context, $groups);
}
foreach ($attribute->normalization_context as $node) {
$groups = (array) $node->group;
$context = $this->parseContext($node->entry);
$attributeMetadata->setNormalizationContextForGroups($context, $groups);
}
foreach ($attribute->denormalization_context as $node) {
$groups = (array) $node->group;
$context = $this->parseContext($node->entry);
$attributeMetadata->setDenormalizationContextForGroups($context, $groups);
}
}
if (isset($xml->{'discriminator-map'})) {
$mapping = [];
foreach ($xml->{'discriminator-map'}->mapping as $element) {
$elementAttributes = $element->attributes();
$mapping[(string) $elementAttributes->type] = (string) $elementAttributes->class;
}
$classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping(
(string) $xml->{'discriminator-map'}->attributes()->{'type-property'},
$mapping
));
}
return true;
}
return false;
}
/**
* Return the names of the classes mapped in this file.
*
* @return string[]
*/
public function getMappedClasses(): array
{
return array_keys($this->classes ??= $this->getClassesFromXml());
}
/**
* Parses an XML File.
*
* @throws MappingException
*/
private function parseFile(string $file): \SimpleXMLElement
{
try {
$dom = XmlUtils::loadFile($file, __DIR__.'/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd');
} catch (\Exception $e) {
throw new MappingException($e->getMessage(), $e->getCode(), $e);
}
return simplexml_import_dom($dom);
}
private function getClassesFromXml(): array
{
$xml = $this->parseFile($this->file);
$classes = [];
foreach ($xml->class as $class) {
$classes[(string) $class['name']] = $class;
}
return $classes;
}
private function parseContext(\SimpleXMLElement $nodes): array
{
$context = [];
foreach ($nodes as $node) {
if (\count($node) > 0) {
if (\count($node->entry) > 0) {
$value = $this->parseContext($node->entry);
} else {
$value = [];
}
} else {
$value = XmlUtils::phpize($node);
}
if (isset($node['name'])) {
$context[(string) $node['name']] = $value;
} else {
$context[] = $value;
}
}
return $context;
}
}

View File

@@ -0,0 +1,173 @@
<?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\Serializer\Mapping\Loader;
use Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException;
use Symfony\Component\PropertyAccess\PropertyPath;
use Symfony\Component\Serializer\Exception\MappingException;
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Yaml\Yaml;
/**
* YAML File Loader.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class YamlFileLoader extends FileLoader
{
private ?Parser $yamlParser = null;
/**
* An array of YAML class descriptions.
*/
private ?array $classes = null;
public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
{
if (!$this->classes ??= $this->getClassesFromYaml()) {
return false;
}
if (!isset($this->classes[$classMetadata->getName()])) {
return false;
}
$yaml = $this->classes[$classMetadata->getName()];
if (isset($yaml['attributes']) && \is_array($yaml['attributes'])) {
$attributesMetadata = $classMetadata->getAttributesMetadata();
foreach ($yaml['attributes'] as $attribute => $data) {
if (isset($attributesMetadata[$attribute])) {
$attributeMetadata = $attributesMetadata[$attribute];
} else {
$attributeMetadata = new AttributeMetadata($attribute);
$classMetadata->addAttributeMetadata($attributeMetadata);
}
if (isset($data['groups'])) {
if (!\is_array($data['groups'])) {
throw new MappingException(sprintf('The "groups" key must be an array of strings in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName()));
}
foreach ($data['groups'] as $group) {
if (!\is_string($group)) {
throw new MappingException(sprintf('Group names must be strings in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName()));
}
$attributeMetadata->addGroup($group);
}
}
if (isset($data['max_depth'])) {
if (!\is_int($data['max_depth'])) {
throw new MappingException(sprintf('The "max_depth" value must be an integer in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName()));
}
$attributeMetadata->setMaxDepth($data['max_depth']);
}
if (isset($data['serialized_name'])) {
if (!\is_string($data['serialized_name']) || '' === $data['serialized_name']) {
throw new MappingException(sprintf('The "serialized_name" value must be a non-empty string in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName()));
}
$attributeMetadata->setSerializedName($data['serialized_name']);
}
if (isset($data['serialized_path'])) {
try {
$attributeMetadata->setSerializedPath(new PropertyPath((string) $data['serialized_path']));
} catch (InvalidPropertyPathException) {
throw new MappingException(sprintf('The "serialized_path" value must be a valid property path in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName()));
}
}
if (isset($data['ignore'])) {
if (!\is_bool($data['ignore'])) {
throw new MappingException(sprintf('The "ignore" value must be a boolean in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName()));
}
$attributeMetadata->setIgnore($data['ignore']);
}
foreach ($data['contexts'] ?? [] as $line) {
$groups = $line['groups'] ?? [];
if ($context = $line['context'] ?? false) {
$attributeMetadata->setNormalizationContextForGroups($context, $groups);
$attributeMetadata->setDenormalizationContextForGroups($context, $groups);
}
if ($context = $line['normalization_context'] ?? false) {
$attributeMetadata->setNormalizationContextForGroups($context, $groups);
}
if ($context = $line['denormalization_context'] ?? false) {
$attributeMetadata->setDenormalizationContextForGroups($context, $groups);
}
}
}
}
if (isset($yaml['discriminator_map'])) {
if (!isset($yaml['discriminator_map']['type_property'])) {
throw new MappingException(sprintf('The "type_property" key must be set for the discriminator map of the class "%s" in "%s".', $classMetadata->getName(), $this->file));
}
if (!isset($yaml['discriminator_map']['mapping'])) {
throw new MappingException(sprintf('The "mapping" key must be set for the discriminator map of the class "%s" in "%s".', $classMetadata->getName(), $this->file));
}
$classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping(
$yaml['discriminator_map']['type_property'],
$yaml['discriminator_map']['mapping']
));
}
return true;
}
/**
* Return the names of the classes mapped in this file.
*
* @return string[]
*/
public function getMappedClasses(): array
{
return array_keys($this->classes ??= $this->getClassesFromYaml());
}
private function getClassesFromYaml(): array
{
if (!stream_is_local($this->file)) {
throw new MappingException(sprintf('This is not a local file "%s".', $this->file));
}
$this->yamlParser ??= new Parser();
$classes = $this->yamlParser->parseFile($this->file, Yaml::PARSE_CONSTANT);
if (empty($classes)) {
return [];
}
if (!\is_array($classes)) {
throw new MappingException(sprintf('The file "%s" must contain a YAML array.', $this->file));
}
return $classes;
}
}

View File

@@ -0,0 +1,115 @@
<?xml version="1.0" ?>
<xsd:schema xmlns="http://symfony.com/schema/dic/serializer-mapping"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://symfony.com/schema/dic/serializer-mapping"
elementFormDefault="qualified">
<xsd:annotation>
<xsd:documentation><![CDATA[
Symfony Serializer Mapping Schema, version 1.0
Authors: Kévin Dunglas, Samuel Roze
A serializer mapping connects attributes with serialization groups.
]]></xsd:documentation>
</xsd:annotation>
<xsd:element name="serializer" type="serializer" />
<xsd:complexType name="serializer">
<xsd:annotation>
<xsd:documentation><![CDATA[
The root element of the serializer mapping definition.
]]></xsd:documentation>
</xsd:annotation>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="class" type="class" />
</xsd:choice>
</xsd:complexType>
<xsd:complexType name="class">
<xsd:annotation>
<xsd:documentation><![CDATA[
Contains serialization groups for a single class.
Nested elements may be class property and/or getter definitions.
]]></xsd:documentation>
</xsd:annotation>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="attribute" type="attribute" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="discriminator-map" type="discriminator-map" />
</xsd:choice>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
<xsd:complexType name="discriminator-map">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="mapping" type="discriminator-map-mapping" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="type-property" type="xsd:string" use="required" />
</xsd:complexType>
<xsd:complexType name="discriminator-map-mapping">
<xsd:attribute name="type" type="xsd:string" use="required" />
<xsd:attribute name="class" type="xsd:string" use="required" />
</xsd:complexType>
<xsd:complexType name="attribute">
<xsd:annotation>
<xsd:documentation><![CDATA[
Contains serialization groups and max depth for attributes. The name of the attribute should be given in the "name" option.
]]></xsd:documentation>
</xsd:annotation>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="group" type="xsd:string" maxOccurs="unbounded" />
<xsd:element name="context" type="context" maxOccurs="unbounded" />
<xsd:element name="normalization_context" type="context" maxOccurs="unbounded" />
<xsd:element name="denormalization_context" type="context" maxOccurs="unbounded" />
</xsd:choice>
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="max-depth">
<xsd:simpleType>
<xsd:restriction base="xsd:integer">
<xsd:minInclusive value="0" />
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="serialized-name">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:minLength value="1" />
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="serialized-path">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:minLength value="1" />
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="ignore" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="context">
<xsd:choice maxOccurs="unbounded">
<xsd:element name="group" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="entry" type="context-root-entry" maxOccurs="unbounded" />
</xsd:choice>
</xsd:complexType>
<xsd:complexType name="context-root-entry" mixed="true">
<xsd:sequence minOccurs="0">
<xsd:element name="entry" type="context-entry" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute type="xsd:string" name="name" use="required" />
</xsd:complexType>
<xsd:complexType name="context-entry" mixed="true">
<xsd:sequence minOccurs="0">
<xsd:element name="entry" type="context-entry" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute type="xsd:string" name="name" />
</xsd:complexType>
</xsd:schema>