<?php
namespace Symfony\Component\PropertyInfo\Util;
use phpDocumentor\Reflection\PseudoType;
use phpDocumentor\Reflection\PseudoTypes\ConstExpression;
use phpDocumentor\Reflection\PseudoTypes\List_;
use phpDocumentor\Reflection\Type as DocType;
use phpDocumentor\Reflection\Types\Array_;
use phpDocumentor\Reflection\Types\Collection;
use phpDocumentor\Reflection\Types\Compound;
use phpDocumentor\Reflection\Types\Integer;
use phpDocumentor\Reflection\Types\Null_;
use phpDocumentor\Reflection\Types\Nullable;
use phpDocumentor\Reflection\Types\String_;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeIdentifier;
class_exists(List_::class);
final class PhpDocTypeHelper
{
public function getType(DocType $varType): ?Type
{
if ($varType instanceof ConstExpression) {
return null;
}
$nullable = false;
if ($varType instanceof Nullable) {
$nullable = true;
$varType = $varType->getActualType();
}
if (!$varType instanceof Compound) {
if ($varType instanceof Null_) {
$nullable = true;
}
$type = $this->createType($varType);
return $nullable ? Type::nullable($type) : $type;
}
$varTypes = [];
for ($typeIndex = 0; $varType->has($typeIndex); ++$typeIndex) {
$type = $varType->get($typeIndex);
if ($type instanceof ConstExpression) {
return null;
}
if ($type instanceof Null_) {
$nullable = true;
continue;
}
if ($type instanceof Nullable) {
$nullable = true;
$type = $type->getActualType();
}
$varTypes[] = $type;
}
$unionTypes = [];
foreach ($varTypes as $varType) {
if (null !== $t = $this->createType($varType)) {
$unionTypes[] = $t;
}
}
$type = 1 === \count($unionTypes) ? $unionTypes[0] : Type::union(...$unionTypes);
return $nullable ? Type::nullable($type) : $type;
}
private function createType(DocType $docType): ?Type
{
$docTypeString = (string) $docType;
if ('mixed[]' === $docTypeString) {
$docTypeString = 'array';
}
if ($docType instanceof Collection) {
$fqsen = $docType->getFqsen();
if ($fqsen && 'list' === $fqsen->getName() && !class_exists(List_::class, false) && !class_exists((string) $fqsen)) {
return Type::list($this->getType($docType->getValueType()));
}
[$phpType, $class] = $this->getPhpTypeAndClass((string) $fqsen);
$variableTypes = [];
if (null !== $valueType = $this->getType($docType->getValueType())) {
$variableTypes[] = $valueType;
}
if (null !== $keyType = $this->getType($docType->getKeyType())) {
$variableTypes[] = $keyType;
}
$type = null !== $class ? Type::object($class) : Type::builtin($phpType);
return Type::collection($type, ...$variableTypes);
}
if (!$docTypeString) {
return null;
}
if (str_ends_with($docTypeString, '[]') && $docType instanceof Array_) {
return Type::list($this->getType($docType->getValueType()));
}
if (str_starts_with($docTypeString, 'list<') && $docType instanceof Array_) {
$collectionValueType = $this->getType($docType->getValueType());
return Type::list($collectionValueType);
}
if (str_starts_with($docTypeString, 'array<') && $docType instanceof Array_) {
$collectionKeyType = $this->getType($docType->getKeyType());
$collectionValueType = $this->getType($docType->getValueType());
return Type::array($collectionValueType, $collectionKeyType);
}
$docTypeString = match ($docTypeString) {
'integer' => 'int',
'boolean' => 'bool',
'double' => 'float',
'callback' => 'callable',
'void' => 'null',
default => $docTypeString,
};
[$phpType, $class] = $this->getPhpTypeAndClass($docTypeString);
if ('array' === $docTypeString) {
return Type::array();
}
if (null === $class) {
return Type::builtin($phpType);
}
if ($docType instanceof PseudoType) {
if ($docType->underlyingType() instanceof Integer) {
return Type::int();
} elseif ($docType->underlyingType() instanceof String_) {
return Type::string();
} else {
return null;
}
}
return Type::object($class);
}
private function getPhpTypeAndClass(string $docType): array
{
if (\in_array($docType, TypeIdentifier::values(), true)) {
return [$docType, null];
}
if (\in_array($docType, ['parent', 'self', 'static'], true)) {
return ['object', $docType];
}
return ['object', ltrim($docType, '\\')];
}
}