Background
I do have classes (here: ClassA
and ClassB
) that shall be tested. As these classes are similar, I created an abstract test-class that implements an interface.
Problem
At level 7, PhpStan is unhappy and reports these issues:
Call to an undefined method object::getId()
Call to an undefined method object::getName()
I do know that it is unclear which object is calling the methods and thus the issue is reported and I know that the solution is by using GENERICS.
Question
I just started with the use of generics, I have no clue how to solve this correctly.
Anybody so kind to support me here? Thanks!
Code
see PhpStan Playground.
REMARK: The example here is simplified to support the discussion.
<?php
// ---------------------------------------- CLASSES
class ClassA {
public function getId(): int {
return 123;
}
}
class ClassB {
public function getName(): string {
return 'abc';
}
}
// ---------------------------------------- TESTS
/**
* @template T
*/
interface InterfaceForTest {
public function getClassName(): string;
}
/**
* @template T
* @implements InterfaceForTest<T>
*/
abstract class AbstractClassTest implements InterfaceForTest {
public function createObject(): object
{
$class = $this->getClassName();
$object = new $class();
return $object;
}
}
/**
* @template T
* @extends AbstractClassTest<T>
*/
class ClassATest extends AbstractClassTest {
public function getClassName(): string
{
return ClassA::class;
}
public function testA(): void
{
$obj = $this->createObject();
assert($obj->getId() === 123); // makes PHPStan unhappy at level 7
}
}
/**
* @template T
* @extends AbstractClassTest<T>
*/
class ClassBTest extends AbstractClassTest {
public function getClassName(): string
{
return ClassB::class;
}
public function testB(): void
{
$obj = $this->createObject();
assert($obj->getName() === 'abc'); // makes PHPStan unhappy at level 7
}
}
2
Answers
Solution
I found the solution. See below
You need to use generics more effectively by specifying the type of object that your abstract class will create and manipulate. This will involve using PHPDoc annotations to inform
PHPStan
about the types it can expect at runtime. To address PHPStan’s type-checking issues at level 7 in the given PHP code, I utilizedPHPDoc
annotations to clarify the types returned by methods in an abstract generic class system. Specifically, I refined theAbstractClassTest
and its subclasses (ClassATest
andClassBTest
) using @extends
and @method
annotations. These annotations explicitly define the specific class types (ClassA
orClassB
) that each subclass works with, and ensure that thecreateObject
() method in each subclass is understood to return instances of these specific classes. This setup guides PHPStan to correctly infer the available methods (getId
() andgetName
()) on objects returned bycreateObject
(), thereby preventing it from flagging these method calls as errors.Try this –>