Created
December 13, 2020 00:33
-
-
Save voku/80722c9310c21719fbcfbfeb565cc6f9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace vdmg\App\scripts\githooks\StandardVdmg\PHPStanHelper; | |
use PhpParser\Node\Expr\FuncCall; | |
use PHPStan\Analyser\Scope; | |
use PHPStan\Reflection\FunctionReflection; | |
use PHPStan\Reflection\ParametersAcceptorSelector; | |
use PHPStan\Reflection\Php\UniversalObjectCratesClassReflectionExtension; | |
use PHPStan\Type\ArrayType; | |
use PHPStan\Type\NullType; | |
use PHPStan\Type\Type; | |
use PHPStan\Type\TypeCombinator; | |
final class PropertyArrayDynamicReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension { | |
/** | |
* @var \PHPStan\Reflection\ReflectionProvider | |
*/ | |
private $reflectionProvider; | |
/** | |
* @var string[] | |
*/ | |
private $universalObjectCratesClasses; | |
/** | |
* @param \PHPStan\Reflection\ReflectionProvider $reflectionProvider | |
* @param string[] $universalObjectCratesClasses | |
*/ | |
public function __construct( | |
\PHPStan\Reflection\ReflectionProvider $reflectionProvider, | |
array $universalObjectCratesClasses | |
) { | |
$this->reflectionProvider = $reflectionProvider; | |
$this->universalObjectCratesClasses = $universalObjectCratesClasses; | |
} | |
/** | |
* @param \PHPStan\Reflection\FunctionReflection $functionReflection | |
* | |
* @return bool | |
*/ | |
public function isFunctionSupported(FunctionReflection $functionReflection): bool { | |
return \strtolower($functionReflection->getName()) === 'propertyarray'; | |
} | |
/** | |
* @param \PHPStan\Reflection\FunctionReflection $functionReflection | |
* @param \PhpParser\Node\Expr\FuncCall $functionCall | |
* @param \PHPStan\Analyser\Scope $scope | |
* | |
* @return \PHPStan\Type\Type | |
*/ | |
public function getTypeFromFunctionCall( | |
FunctionReflection $functionReflection, | |
FuncCall $functionCall, | |
Scope $scope | |
): Type { | |
if (count($functionCall->args) <= 1) { | |
/** @noinspection PhpParamsInspection */ | |
return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); | |
} | |
$objectArrayArg = $functionCall->args[0]->value ?? null; | |
$propertyResultArrayValueArg = $functionCall->args[1]->value ?? null; | |
$propertyResultArrayKeyArg = $functionCall->args[2]->value ?? null; | |
if ($objectArrayArg !== null && $propertyResultArrayValueArg !== null) { | |
$objectArrayArgType = $scope->getType($objectArrayArg); | |
$objectArrayKeyType = $objectArrayArgType->getIterableKeyType(); | |
$objectArrayType = $objectArrayArgType->getIterableValueType(); | |
$classNames = $objectArrayType->getReferencedClasses(); | |
$classPropertyTypes = $this->getClassProperties($classNames); | |
$propertyResultArrayValues = $this->getPropertiesTypes( | |
$classNames, | |
$scope, | |
$propertyResultArrayValueArg, | |
$classPropertyTypes | |
); | |
if ($propertyResultArrayKeyArg !== null) { | |
$propertyResultArrayKeys = $this->getPropertiesTypes( | |
$classNames, | |
$scope, | |
$propertyResultArrayKeyArg, | |
$classPropertyTypes | |
); | |
$returnTypes = []; | |
foreach ($propertyResultArrayValues as $propertyResultArrayValue) { | |
foreach ($propertyResultArrayKeys as $propertyResultArrayKey) { | |
$returnTypes[] = new ArrayType($propertyResultArrayKey, $propertyResultArrayValue); | |
} | |
} | |
return TypeCombinator::union(...$returnTypes); | |
} | |
$returnTypes = []; | |
foreach ($propertyResultArrayValues as $propertyResultArrayValue) { | |
$returnTypes[] = new ArrayType($objectArrayKeyType, $propertyResultArrayValue); | |
} | |
return TypeCombinator::union(...$returnTypes); | |
} | |
// fallback | |
/** @noinspection PhpParamsInspection */ | |
return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); | |
} | |
/** | |
* @param string[] $classNames | |
* @param \PHPStan\Analyser\Scope $scope | |
* @param \PhpParser\Node\Expr $propertyResultArrayValueArg | |
* @param \PHPStan\Type\Type[][] $classPropertyTypes | |
* | |
* @return \PHPStan\Type\Type[] | |
* @phpstan-param array<class-string, array<string, \PHPStan\Type\Type>> $classPropertyTypes | |
*/ | |
private function getPropertiesTypes( | |
array $classNames, | |
Scope $scope, | |
\PhpParser\Node\Expr $propertyResultArrayValueArg, | |
array $classPropertyTypes | |
): array { | |
// init | |
$propertyResultArrayValues = []; | |
foreach ($classNames as $className) { | |
$propertyResultArrayValueNameArgType = $scope->getType($propertyResultArrayValueArg); | |
$propertyResultArrayValueNames = \PHPStan\Type\TypeUtils::getConstantStrings($propertyResultArrayValueNameArgType); | |
foreach ($propertyResultArrayValueNames as $propertyResultArrayValueName) { | |
$propertyResultArrayValueName = $propertyResultArrayValueName->getValue(); | |
if (isset($classPropertyTypes[$className][$propertyResultArrayValueName])) { | |
$propertyResultArrayValues[] = $classPropertyTypes[$className][$propertyResultArrayValueName]; | |
} else { | |
$propertyResultArrayValues[] = new NullType(); | |
} | |
} | |
} | |
return $propertyResultArrayValues; | |
} | |
/** | |
* @param string[] $classNames | |
* | |
* @return array | |
* @phpstan-return array<class-string, array<string, \PHPStan\Type\Type>> | |
*/ | |
private function getClassProperties(array $classNames): array { | |
// init | |
$classPropertyTypes = []; | |
foreach ($classNames as $className) { | |
$classReflection = $this->reflectionProvider->getClass($className); | |
/** @noinspection NotOptimalIfConditionsInspection */ | |
if ( | |
!$classReflection->getNativeReflection()->isUserDefined() | |
|| | |
UniversalObjectCratesClassReflectionExtension::isUniversalObjectCrate( | |
$this->reflectionProvider, | |
$this->universalObjectCratesClasses, | |
$classReflection | |
) | |
) { | |
continue; | |
} | |
do { | |
foreach ($classReflection->getNativeReflection()->getProperties() as $nativeProperty) { | |
$declaringClass = $this->reflectionProvider->getClass($nativeProperty->getDeclaringClass()->getName()); | |
$declaringClassProperty = $declaringClass->getNativeProperty($nativeProperty->getName()); | |
$declaringClassPropertyName = $nativeProperty->getName(); | |
if ($nativeProperty->isPrivate()) { | |
$declaringClassPropertyName = sprintf( | |
"\0%s\0%s", | |
$declaringClass->getName(), | |
$declaringClassPropertyName | |
); | |
} elseif ($nativeProperty->isProtected()) { | |
$declaringClassPropertyName = sprintf( | |
"\0*\0%s", | |
$declaringClassPropertyName | |
); | |
} | |
$classPropertyTypes[$className][$declaringClassPropertyName] = $declaringClassProperty->getReadableType(); | |
} | |
$classReflection = $classReflection->getParentClass(); | |
} while ($classReflection !== false); | |
} | |
return $classPropertyTypes; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment