102 lines
3.3 KiB
PHP
102 lines
3.3 KiB
PHP
<?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\Attribute\Ignore;
|
|
|
|
/**
|
|
* Provides methods to detect accessor name collisions during serialization.
|
|
*
|
|
* @internal
|
|
*/
|
|
trait AccessorCollisionResolverTrait
|
|
{
|
|
private function getAttributeNameFromAccessor(\ReflectionClass $class, \ReflectionMethod $method, bool $andMutator): ?string
|
|
{
|
|
$methodName = $method->name;
|
|
|
|
$i = match ($methodName[0]) {
|
|
's' => $andMutator && str_starts_with($methodName, 'set') ? 3 : null,
|
|
'g' => str_starts_with($methodName, 'get') ? 3 : null,
|
|
'h' => str_starts_with($methodName, 'has') ? 3 : null,
|
|
'c' => str_starts_with($methodName, 'can') ? 3 : null,
|
|
'i' => str_starts_with($methodName, 'is') ? 2 : null,
|
|
default => null,
|
|
};
|
|
|
|
// ctype_lower check to find out if method looks like accessor but actually is not, e.g. hash, cancel
|
|
if (null === $i || ctype_lower($methodName[$i] ?? 'a') || (!$andMutator && $method->isStatic())) {
|
|
return null;
|
|
}
|
|
|
|
if ('s' === $methodName[0] ? !$method->getNumberOfParameters() : ($method->getNumberOfRequiredParameters() || \in_array((string) $method->getReturnType(), ['void', 'never'], true))) {
|
|
return null;
|
|
}
|
|
|
|
$attributeName = substr($methodName, $i);
|
|
|
|
if (!$class->hasProperty($attributeName)) {
|
|
$attributeName = lcfirst($attributeName);
|
|
}
|
|
|
|
return $attributeName;
|
|
}
|
|
|
|
private function hasPropertyForAccessor(\ReflectionClass $class, string $propName): bool
|
|
{
|
|
do {
|
|
if ($class->hasProperty($propName)) {
|
|
return true;
|
|
}
|
|
} while ($class = $class->getParentClass());
|
|
|
|
return false;
|
|
}
|
|
|
|
private function hasAttributeNameCollision(\ReflectionClass $class, string $attributeName, string $methodName): bool
|
|
{
|
|
if ($this->hasPropertyForAccessor($class, $attributeName)) {
|
|
return true;
|
|
}
|
|
|
|
if ($class->hasMethod($attributeName)) {
|
|
$candidate = $class->getMethod($attributeName);
|
|
if ($candidate->getName() !== $methodName && $this->isReadableAccessorMethod($candidate)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
$ucAttributeName = ucfirst($attributeName);
|
|
foreach (['get', 'is', 'has', 'can'] as $prefix) {
|
|
$candidateName = $prefix.$ucAttributeName;
|
|
if ($candidateName === $methodName || !$class->hasMethod($candidateName)) {
|
|
continue;
|
|
}
|
|
|
|
if ($this->isReadableAccessorMethod($class->getMethod($candidateName))) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function isReadableAccessorMethod(\ReflectionMethod $method): bool
|
|
{
|
|
return $method->isPublic()
|
|
&& !$method->isStatic()
|
|
&& !$method->getAttributes(Ignore::class)
|
|
&& !$method->getNumberOfRequiredParameters()
|
|
&& !\in_array((string) $method->getReturnType(), ['void', 'never'], true);
|
|
}
|
|
}
|