Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
<?php declare(strict_types = 1);
namespace App\PHPStan;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\MethodsClassReflectionExtension;
use PHPStan\Analyser\OutOfClassScope;
use PHPStan\Type\ObjectType;
/**
* Extension to check a method call on a generic type. A method call on the generic class
* is forwarded to the real class using magic method __call. This
* extension will help PHPStan to find the method in the real class.
*/
class EntityClassReflectionExtension implements MethodsClassReflectionExtension
{
/**
* Returns true when class T (which is the real class) has a method
* with the given name.
* @param ClassReflection $classReflection
* @param string $methodName
* @return bool
*/
public function hasMethod(
ClassReflection $classReflection,
string $methodName
): bool {
if ($classReflection->getName() === 'GenericEntity') {
return $this->findMethod(
$this->getT($classReflection),
$methodName
) != null;
}
return false;
}
/**
* Returns the method from the type T.
* @param ClassReflection $classReflection
* @param string $methodName
* @return MethodReflection
*/
public function getMethod(
ClassReflection $classReflection,
string $methodName
): MethodReflection {
return $this->findMethod(
$this->getT($classReflection),
$methodName
);
}
/**
* T is the real class. Return the ObjectType of T.
* @param ClassReflection $classReflection
* @return ObjectType
*/
private function getT(ClassReflection $classReflection): ObjectType
{
return $classReflection
->getActiveTemplateTypeMap()
->getType('T');
}
/**
* Find the method on the ObjectType of T.
* @param ObjectType $type
* @param string $method
*/
private function findMethod(
ObjectType $type,
string $method
): ?MethodReflection {
if (!$type->hasMethod($method)->yes()) {
return null;
}
return $type->getMethod($method, new OutOfClassScope());
}
}
<?php
/**
* A simple generic class to test the extension.
* @template T
*/
class GenericEntity
{
/**
* The id of the entity
* @var int
*/
private $id;
/**
* The real object
* @var T
*/
private $obj;
/**
* Constructor
* @param int $id The id of the entity
* @param T $domain The domain entity
*/
public function __construct(int $id, $obj)
{
$this->id = $id;
$this->obj = $obj;
}
/**
* Returns the id.
* @return int The id of the entity
*/
public function id(): int
{
return $this->id;
}
public function __call($method, $args)
{
return $this->obj->{$method}(...$args);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment