我确实有需要测试的课程(此处:
ClassA
和ClassB
)。由于这些类很相似,我创建了一个实现接口的抽象测试类。
在第 7 级,PhpStan 不高兴并报告以下问题:
Call to an undefined method object::getId()
Call to an undefined method object::getName()
我确实知道不清楚哪个对象正在调用这些方法,因此报告了该问题,并且我知道解决方案是使用GENERICS。
我刚刚开始使用泛型,我不知道如何正确解决这个问题。 这里有人好心支持我吗?谢谢!
参见 PhpStan 游乐场。
备注: 这里的示例经过简化以支持讨论。
<?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 testA(): void
{
$obj = $this->createObject();
assert($obj->getName() === 'abc'); // makes PHPStan unhappy at level 7
}
}
您需要通过指定抽象类将创建和操作的对象类型来更有效地使用泛型。这将涉及使用 PHPDoc 注释来告知
PHPStan
它在运行时可以预期的类型。为了解决 PHPStan 在给定 PHP 代码中第 7 层的类型检查问题,我利用 PHPDoc
注释来阐明抽象泛型类系统中的方法返回的类型。具体来说,我使用 @AbstractClassTest
和 @ClassATest
注释细化了 ClassBTest
及其子类(extends
和 method
)。这些注释显式定义每个子类使用的特定类类型(ClassA
或 ClassB
),并确保每个子类中的 createObject
() 方法被理解为返回这些特定类的实例。此设置引导 PHPStan 正确推断 getId
() 返回的对象上的可用方法(getName
() 和 createObject
()),从而防止它将这些方法调用标记为错误。
试试这个-->
<?php
class ClassA {
public function getId(): int {
return 123;
}
}
class ClassB {
public function getName(): string {
return 'abc';
}
}
interface InterfaceForTest {
public function getClassName(): string;
}
/**
* @template T
* @implements InterfaceForTest<T>
*/
abstract class AbstractClassTest implements InterfaceForTest {
/**
* @return T
*/
public function createObject() {
$class = $this->getClassName();
return new $class();
}
}
/**
* @extends AbstractClassTest<ClassA>
* @method ClassA createObject()
*/
class ClassATest extends AbstractClassTest {
public function getClassName(): string {
return ClassA::class;
}
public function testA(): void {
$obj = $this->createObject();
assert($obj->getId() === 123);
}
}
/**
* @extends AbstractClassTest<ClassB>
* @method ClassB createObject()
*/
class ClassBTest extends AbstractClassTest {
public function getClassName(): string {
return ClassB::class;
}
public function testB(): void {
$obj = $this->createObject();
assert($obj->getName() === 'abc');
}
}