用docblock记录PHP中抽象工厂方法的返回类型



这个问题已经被问了一遍又一遍,但回复都有点老了,我有点绝望地希望能有所改变,因为"无法做到";回答。

背景:

class AbstractBuildObject {}
class Hammer extends AbstractBuildObject{}
class Nail extends AbstractBuildObject{}
class AbstractFactory{    
/**
* return $type
*/
public function build1(string $type): AbstractBuiltObject {
return new $type();
}
/**
* return any(AbstractBuiltObject)
*/
public function build2(string $someArg): AbstractBuiltObject {
$type = $this->decideTypeBasedOnArgsAndState($someArg);
return new $type();
}
}

我试图用上面的注释来表示我需要的东西。

return $type(或者理想情况下,return $type of AbstractBuiltObject应该提示在输入参数中指定返回类型。

在第二种情况下,any(AbstractBuiltObject)表示可以返回抽象类的任何派生具体。

所以我需要某种注释来实现我描述的效果。这些注释显然不起作用,我只是用它们来说明这个概念。

我知道有人可能会尝试使用像return Hammer|Nail这样的管道类型连接,但在我的情况下,工厂类应该在每次将新的具体实现添加到项目时进行修改,在build1的情况下也不够具体,我确切地知道返回类型应该是什么。

所以,简而言之,我需要这个至少在PhpStorm中工作:
(new AbstractFactory())->build1(Hammer::class)-> // I should have Hammer autocomplete here
(new AbstractFactory())->build2('foo')-> // I should have autocomplete of any concretion of the abstract here

关于在SOLID中打破D的哲学对话,如果你想要这样的东西来自动完成可用的方法Hammer:

(new AbstractFactory())->build1(Hammer::class)->

那么已经承诺专门为Hammer类编写这段代码。如果你要那样做,那么你最好这样做:

$hammer = (new AbstractFactory())->build1(Hammer::class);

如果你这样做了,那么你不妨这样做:

/**
* @var Hammer
*/
$hammer = (new AbstractFactory())->build1(Hammer::class);

然后你的自动补全在$hammer->应该工作。

我们采用的解决方案是:

<?php
class AbstractBuiltClass {
/**
* @return static
*/
public static function type(self $instance)
// change return type to :static when #PHP72 support is dropped and remove explicit typecheck
:self
{
if(!($instance instanceof static)){
throw new Exception();
}
return $instance;
}
}
class Hammer extends AbstractBuiltClass{
function hammerMethod(){}
}
Hammer::type($factory->get(Hammer::class))->hammerMethod();

可行解决方案的竞争者:

  1. 诗篇模板注释:https://psalm.dev/docs/annotating_code/templated_annotations/非常有前途,但尚未得到广泛支持
  2. 变量文档块(见Alex Howansky的回答)