<?php
declare(strict_types=1);
namespace JMS\Serializer\Metadata\Driver;
use JMS\Serializer\Metadata\ClassMetadata as SerializerClassMetadata;
use JMS\Serializer\Metadata\ExpressionPropertyMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;
use JMS\Serializer\Metadata\StaticPropertyMetadata;
use JMS\Serializer\Metadata\VirtualPropertyMetadata;
use JMS\Serializer\Type\Parser;
use JMS\Serializer\Type\ParserInterface;
use Metadata\ClassMetadata;
use Metadata\Driver\DriverInterface;
use ReflectionClass;
use ReflectionException;
use ReflectionProperty;
class TypedPropertiesDriver implements DriverInterface
{
/**
* @var DriverInterface
*/
protected $delegate;
/**
* @var ParserInterface
*/
protected $typeParser;
/**
* @var string[]
*/
private $allowList;
/**
* @param string[] $allowList
*/
public function __construct(DriverInterface $delegate, ?ParserInterface $typeParser = null, array $allowList = [])
{
$this->delegate = $delegate;
$this->typeParser = $typeParser ?: new Parser();
$this->allowList = array_merge($allowList, $this->getDefaultWhiteList());
}
private function getDefaultWhiteList(): array
{
return [
'int',
'float',
'bool',
'boolean',
'string',
'double',
'iterable',
'resource',
];
}
/**
* @return SerializerClassMetadata|null
*/
public function loadMetadataForClass(ReflectionClass $class): ?ClassMetadata
{
$classMetadata = $this->delegate->loadMetadataForClass($class);
\assert($classMetadata instanceof SerializerClassMetadata);
if (PHP_VERSION_ID <= 70400) {
return $classMetadata;
}
// We base our scan on the internal driver's property list so that we
// respect any internal allow/blocklist like in the AnnotationDriver
foreach ($classMetadata->propertyMetadata as $propertyMetadata) {
// If the inner driver provides a type, don't guess anymore.
if ($propertyMetadata->type || $this->isVirtualProperty($propertyMetadata)) {
continue;
}
try {
$propertyReflection = $this->getReflection($propertyMetadata);
$reflectionType = $propertyReflection->getType();
if ($this->shouldTypeHint($reflectionType)) {
$type = $reflectionType->getName();
$propertyMetadata->setType($this->typeParser->parse($type));
}
} catch (ReflectionException $e) {
continue;
}
}
return $classMetadata;
}
private function isVirtualProperty(PropertyMetadata $propertyMetadata): bool
{
return $propertyMetadata instanceof VirtualPropertyMetadata
|| $propertyMetadata instanceof StaticPropertyMetadata
|| $propertyMetadata instanceof ExpressionPropertyMetadata;
}
private function getReflection(PropertyMetadata $propertyMetadata): ReflectionProperty
{
return new ReflectionProperty($propertyMetadata->class, $propertyMetadata->name);
}
/**
* @phpstan-assert-if-true \ReflectionNamedType $reflectionType
*/
private function shouldTypeHint(?\ReflectionType $reflectionType): bool
{
if (!$reflectionType instanceof \ReflectionNamedType) {
return false;
}
if (in_array($reflectionType->getName(), $this->allowList, true)) {
return true;
}
return class_exists($reflectionType->getName())
|| interface_exists($reflectionType->getName());
}
}