Skip to content

Commit

Permalink
Merge pull request #1469 from mbabker/attribute-driver
Browse files Browse the repository at this point in the history
Create an attribute driver decoupled from the annotations driver, deprecate the annotation based attribute reader
  • Loading branch information
goetas committed Feb 3, 2023
2 parents 12666c1 + e501fec commit 576d226
Show file tree
Hide file tree
Showing 7 changed files with 401 additions and 283 deletions.
278 changes: 22 additions & 256 deletions src/Metadata/Driver/AnnotationDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,279 +5,45 @@
namespace JMS\Serializer\Metadata\Driver;

use Doctrine\Common\Annotations\Reader;
use JMS\Serializer\Annotation\Accessor;
use JMS\Serializer\Annotation\AccessorOrder;
use JMS\Serializer\Annotation\AccessType;
use JMS\Serializer\Annotation\Discriminator;
use JMS\Serializer\Annotation\Exclude;
use JMS\Serializer\Annotation\ExclusionPolicy;
use JMS\Serializer\Annotation\Expose;
use JMS\Serializer\Annotation\Groups;
use JMS\Serializer\Annotation\Inline;
use JMS\Serializer\Annotation\MaxDepth;
use JMS\Serializer\Annotation\PostDeserialize;
use JMS\Serializer\Annotation\PostSerialize;
use JMS\Serializer\Annotation\PreSerialize;
use JMS\Serializer\Annotation\ReadOnlyProperty;
use JMS\Serializer\Annotation\SerializedName;
use JMS\Serializer\Annotation\Since;
use JMS\Serializer\Annotation\SkipWhenEmpty;
use JMS\Serializer\Annotation\Type;
use JMS\Serializer\Annotation\Until;
use JMS\Serializer\Annotation\VirtualProperty;
use JMS\Serializer\Annotation\XmlAttribute;
use JMS\Serializer\Annotation\XmlAttributeMap;
use JMS\Serializer\Annotation\XmlDiscriminator;
use JMS\Serializer\Annotation\XmlElement;
use JMS\Serializer\Annotation\XmlKeyValuePairs;
use JMS\Serializer\Annotation\XmlList;
use JMS\Serializer\Annotation\XmlMap;
use JMS\Serializer\Annotation\XmlNamespace;
use JMS\Serializer\Annotation\XmlRoot;
use JMS\Serializer\Annotation\XmlValue;
use JMS\Serializer\Exception\InvalidMetadataException;
use JMS\Serializer\Expression\CompilableExpressionEvaluatorInterface;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\ExpressionPropertyMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;
use JMS\Serializer\Metadata\VirtualPropertyMetadata;
use JMS\Serializer\Naming\PropertyNamingStrategyInterface;
use JMS\Serializer\Type\Parser;
use JMS\Serializer\Type\ParserInterface;
use Metadata\ClassMetadata as BaseClassMetadata;
use Metadata\Driver\DriverInterface;
use Metadata\MethodMetadata;

class AnnotationDriver implements DriverInterface
class AnnotationDriver extends AnnotationOrAttributeDriver
{
use ExpressionMetadataTrait;

/**
* @var Reader
*/
private $reader;

/**
* @var ParserInterface
*/
private $typeParser;
/**
* @var PropertyNamingStrategyInterface
*/
private $namingStrategy;

public function __construct(Reader $reader, PropertyNamingStrategyInterface $namingStrategy, ?ParserInterface $typeParser = null, ?CompilableExpressionEvaluatorInterface $expressionEvaluator = null)
{
parent::__construct($namingStrategy, $typeParser, $expressionEvaluator);

$this->reader = $reader;
$this->typeParser = $typeParser ?: new Parser();
$this->namingStrategy = $namingStrategy;
$this->expressionEvaluator = $expressionEvaluator;
}

public function loadMetadataForClass(\ReflectionClass $class): ?BaseClassMetadata
/**
* @return list<object>
*/
protected function getClassAnnotations(\ReflectionClass $class): array
{
$classMetadata = new ClassMetadata($name = $class->name);
$fileResource = $class->getFilename();
if (false !== $fileResource) {
$classMetadata->fileResources[] = $fileResource;
}

$propertiesMetadata = [];
$propertiesAnnotations = [];

$exclusionPolicy = ExclusionPolicy::NONE;
$excludeAll = false;
$classAccessType = PropertyMetadata::ACCESS_TYPE_PROPERTY;
$readOnlyClass = false;
foreach ($this->reader->getClassAnnotations($class) as $annot) {
if ($annot instanceof ExclusionPolicy) {
$exclusionPolicy = $annot->policy;
} elseif ($annot instanceof XmlRoot) {
$classMetadata->xmlRootName = $annot->name;
$classMetadata->xmlRootNamespace = $annot->namespace;
$classMetadata->xmlRootPrefix = $annot->prefix;
} elseif ($annot instanceof XmlNamespace) {
$classMetadata->registerNamespace($annot->uri, $annot->prefix);
} elseif ($annot instanceof Exclude) {
if (null !== $annot->if) {
$classMetadata->excludeIf = $this->parseExpression($annot->if);
} else {
$excludeAll = true;
}
} elseif ($annot instanceof AccessType) {
$classAccessType = $annot->type;
} elseif ($annot instanceof ReadOnlyProperty) {
$readOnlyClass = true;
} elseif ($annot instanceof AccessorOrder) {
$classMetadata->setAccessorOrder($annot->order, $annot->custom);
} elseif ($annot instanceof Discriminator) {
if ($annot->disabled) {
$classMetadata->discriminatorDisabled = true;
} else {
$classMetadata->setDiscriminator($annot->field, $annot->map, $annot->groups);
}
} elseif ($annot instanceof XmlDiscriminator) {
$classMetadata->xmlDiscriminatorAttribute = (bool) $annot->attribute;
$classMetadata->xmlDiscriminatorCData = (bool) $annot->cdata;
$classMetadata->xmlDiscriminatorNamespace = $annot->namespace ? (string) $annot->namespace : null;
} elseif ($annot instanceof VirtualProperty) {
$virtualPropertyMetadata = new ExpressionPropertyMetadata(
$name,
$annot->name,
$this->parseExpression($annot->exp)
);
$propertiesMetadata[] = $virtualPropertyMetadata;
$propertiesAnnotations[] = $annot->options;
}
}

foreach ($class->getMethods() as $method) {
if ($method->class !== $name) {
continue;
}

$methodAnnotations = $this->reader->getMethodAnnotations($method);

foreach ($methodAnnotations as $annot) {
if ($annot instanceof PreSerialize) {
$classMetadata->addPreSerializeMethod(new MethodMetadata($name, $method->name));
continue 2;
} elseif ($annot instanceof PostDeserialize) {
$classMetadata->addPostDeserializeMethod(new MethodMetadata($name, $method->name));
continue 2;
} elseif ($annot instanceof PostSerialize) {
$classMetadata->addPostSerializeMethod(new MethodMetadata($name, $method->name));
continue 2;
} elseif ($annot instanceof VirtualProperty) {
$virtualPropertyMetadata = new VirtualPropertyMetadata($name, $method->name);
$propertiesMetadata[] = $virtualPropertyMetadata;
$propertiesAnnotations[] = $methodAnnotations;
continue 2;
}
}
}

if (!$excludeAll) {
foreach ($class->getProperties() as $property) {
if ($property->class !== $name || (isset($property->info) && $property->info['class'] !== $name)) {
continue;
}

$propertiesMetadata[] = new PropertyMetadata($name, $property->getName());
$propertiesAnnotations[] = $this->reader->getPropertyAnnotations($property);
}

foreach ($propertiesMetadata as $propertyKey => $propertyMetadata) {
$isExclude = false;
$isExpose = $propertyMetadata instanceof VirtualPropertyMetadata
|| $propertyMetadata instanceof ExpressionPropertyMetadata;
$propertyMetadata->readOnly = $propertyMetadata->readOnly || $readOnlyClass;
$accessType = $classAccessType;
$accessor = [null, null];

$propertyAnnotations = $propertiesAnnotations[$propertyKey];

foreach ($propertyAnnotations as $annot) {
if ($annot instanceof Since) {
$propertyMetadata->sinceVersion = $annot->version;
} elseif ($annot instanceof Until) {
$propertyMetadata->untilVersion = $annot->version;
} elseif ($annot instanceof SerializedName) {
$propertyMetadata->serializedName = $annot->name;
} elseif ($annot instanceof SkipWhenEmpty) {
$propertyMetadata->skipWhenEmpty = true;
} elseif ($annot instanceof Expose) {
$isExpose = true;
if (null !== $annot->if) {
$propertyMetadata->excludeIf = $this->parseExpression('!(' . $annot->if . ')');
}
} elseif ($annot instanceof Exclude) {
if (null !== $annot->if) {
$propertyMetadata->excludeIf = $this->parseExpression($annot->if);
} else {
$isExclude = true;
}
} elseif ($annot instanceof Type) {
$propertyMetadata->setType($this->typeParser->parse($annot->name));
} elseif ($annot instanceof XmlElement) {
$propertyMetadata->xmlAttribute = false;
$propertyMetadata->xmlElementCData = $annot->cdata;
$propertyMetadata->xmlNamespace = $annot->namespace;
} elseif ($annot instanceof XmlList) {
$propertyMetadata->xmlCollection = true;
$propertyMetadata->xmlCollectionInline = $annot->inline;
$propertyMetadata->xmlEntryName = $annot->entry;
$propertyMetadata->xmlEntryNamespace = $annot->namespace;
$propertyMetadata->xmlCollectionSkipWhenEmpty = $annot->skipWhenEmpty;
} elseif ($annot instanceof XmlMap) {
$propertyMetadata->xmlCollection = true;
$propertyMetadata->xmlCollectionInline = $annot->inline;
$propertyMetadata->xmlEntryName = $annot->entry;
$propertyMetadata->xmlEntryNamespace = $annot->namespace;
$propertyMetadata->xmlKeyAttribute = $annot->keyAttribute;
} elseif ($annot instanceof XmlKeyValuePairs) {
$propertyMetadata->xmlKeyValuePairs = true;
} elseif ($annot instanceof XmlAttribute) {
$propertyMetadata->xmlAttribute = true;
$propertyMetadata->xmlNamespace = $annot->namespace;
} elseif ($annot instanceof XmlValue) {
$propertyMetadata->xmlValue = true;
$propertyMetadata->xmlElementCData = $annot->cdata;
} elseif ($annot instanceof AccessType) {
$accessType = $annot->type;
} elseif ($annot instanceof ReadOnlyProperty) {
$propertyMetadata->readOnly = $annot->readOnly;
} elseif ($annot instanceof Accessor) {
$accessor = [$annot->getter, $annot->setter];
} elseif ($annot instanceof Groups) {
$propertyMetadata->groups = $annot->groups;
foreach ((array) $propertyMetadata->groups as $groupName) {
if (false !== strpos($groupName, ',')) {
throw new InvalidMetadataException(sprintf(
'Invalid group name "%s" on "%s", did you mean to create multiple groups?',
implode(', ', $propertyMetadata->groups),
$propertyMetadata->class . '->' . $propertyMetadata->name
));
}
}
} elseif ($annot instanceof Inline) {
$propertyMetadata->inline = true;
} elseif ($annot instanceof XmlAttributeMap) {
$propertyMetadata->xmlAttributeMap = true;
} elseif ($annot instanceof MaxDepth) {
$propertyMetadata->maxDepth = $annot->depth;
}
}

if ($propertyMetadata->inline) {
$classMetadata->isList = $classMetadata->isList || PropertyMetadata::isCollectionList($propertyMetadata->type);
$classMetadata->isMap = $classMetadata->isMap || PropertyMetadata::isCollectionMap($propertyMetadata->type);

if ($classMetadata->isMap && $classMetadata->isList) {
throw new InvalidMetadataException('Can not have an inline map and and inline map on the same class');
}
}

if (!$propertyMetadata->serializedName) {
$propertyMetadata->serializedName = $this->namingStrategy->translateName($propertyMetadata);
}

foreach ($propertyAnnotations as $annot) {
if ($annot instanceof VirtualProperty && null !== $annot->name) {
$propertyMetadata->name = $annot->name;
}
}
return $this->reader->getClassAnnotations($class);
}

if (
(ExclusionPolicy::NONE === $exclusionPolicy && !$isExclude)
|| (ExclusionPolicy::ALL === $exclusionPolicy && $isExpose)
) {
$propertyMetadata->setAccessor($accessType, $accessor[0], $accessor[1]);
$classMetadata->addPropertyMetadata($propertyMetadata);
}
}
}
/**
* @return list<object>
*/
protected function getMethodAnnotations(\ReflectionMethod $method): array
{
return $this->reader->getMethodAnnotations($method);
}

return $classMetadata;
/**
* @return list<object>
*/
protected function getPropertyAnnotations(\ReflectionProperty $property): array
{
return $this->reader->getPropertyAnnotations($property);
}
}

0 comments on commit 576d226

Please sign in to comment.