如何实现类型/类型安全的迭代器?

时间:2018-11-20 19:38:47

标签: php php-7 type-safety

我有如下代码,我想对其进行改进:     

// example type
class Stuff
{
    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }
}

// generator function
function searchStuff()
{
    yield new Stuff('Fou');
    yield new Stuff('Barre');
    yield new Stuff('Bazze');
}

// code that iterates over the results of the generator
$stuffIterator = searchStuff();
assert($stuffIterator instanceof Iterator);
foreach ($stuffIterator as $stuff) {
    /** @var Stuff $stuff */
    echo $stuff->getName() . PHP_EOL;
}

我要改进的是循环中的注释(第三行),我想将其完全删除。原因是

  • 使用适当的类型提示(由语言强制执行)应该没有必要
  • 它可能反映真实情况,也可能不反映现实,即代码更改容易中断
  • 键入它是不必要的工作,更糟糕的是,阅读它。

我朴素的方法是声明一个迭代器接口,该接口向通用Iterator接口添加适当的类型注释:

interface StuffIterator extends Iterator
{
    public function current(): Stuff;
}

这有一个缺点,我不能在函数上将其设置为“硬”注释,而只能将其设置为文档字符串注释,因为"Generators may only declare a return type of Generator, Iterator, Traversable, or iterable"不好,因为它不被强制执行。此外,我的IDE不会选择类型,但这是另一个问题。

另一种方法是编写一个实际的迭代器类,该类包装从函数返回的Generator。问题在于,该类也需要实例化,因此我必须调用$stuffGenerator = new StuffIterator(searchStuff());或编写另一个包装函数来做到这一点,而这两者都不是必需的。尽管如此,愚蠢的IDE仍未收到类型提示(grrrr ...!)。

所以,这是我的问题:这种方法有哪些替代方案?我可以想象像C ++或Java泛型这样的东西,但是,a,我不能简单地重写有问题的应用程序。

更多说明:

  • 示例代码有效,这不是问题,我关心的是可维护性,可读性和优雅性。
  • 我不能简单地返回一个数组,这时使用生成器很重要。因此,基于此方法的任何建议都不是解决方案。
  • 我目前正在使用PHP 7.1,但我不排除升级的可能性。如果该答案也需要升级,我会认为它是有效的。

1 个答案:

答案 0 :(得分:2)

一个很好的问题。我想您问题的答案不会像您预期的那样出现。该解决方案可能不是很好,但是可行。首先,您无法定义Generator以外的收益率返回类型,依此类推。您已经给出了答案。但是...

只需想象以下起点。

class Stuff
{
    protected $name;

    public function getName() : ?string
    {
        return $this->name;
    }

    public function setName(string $name) : Stuff
    {
        $this->name = $name;
        return $this;
    }
}

class StuffCollection extends \IteratorIterator
{
    public function __construct(Stuff ...$items)
    {
        parent::__construct(
            (function() use ($items) {
                yield from $items;
            })()
        );
    }

    public function current() : Stuff
    {
        return parent::current();
    }
}

我在这里做了什么?我们已经知道Stuff类。它没有新内容。新事物是StuffCollection类。由于是从IteratorIterator类扩展而来的,因此我们可以覆盖IteratorIterator::current()方法并为其提供类型提示。

$collection = new StuffCollection(
    (new Stuff())->setName('One'),
    (new Stuff())->setName('Two'),
    (new Stuff())->setName('Three')
);

foreach ($collection as $item) {
    var_dump(assert($item instance of Stuff));
    echo sprintf(
        'Class: %s. Calling getName method returns "%s" (%s)',
        get_class($item),
        $item->getName(),
        gettype($item->getName())
    ) . "<br>";
}

输出应该是...

bool(true) Class: Stuff. Calling getName method returns "One" (string)
bool(true) Class: Stuff. Calling getName method returns "Two" (string)
bool(true) Class: Stuff. Calling getName method returns "Three" (string)

那是什么意思?您确实不能直接在yield调用中定义返回类型。收益率将始终返回Generator实例。一种可能的解决方案是使用IteratorIterator类。

即使您的IDE也可以使用该解决方案。