【问题标题】:How to get phpstan to infer the type for my Laravel Collection pipeline?如何让 phpstan 推断我的 Laravel Collection 管道的类型?
【发布时间】:2021-02-19 18:20:34
【问题描述】:

鉴于我的课

<?php
declare(strict_types=1);

use Illuminate\Support\Collection;
use stdClass;

class PhpstanIssue
{
    /**
     * @param Collection<Collection<stdClass>> $collection
     *
     * @return Collection<Foo>
     */
    public function whyDoesThisFail(Collection $collection): Collection
    {
        return $collection
            ->flatten() // Collection<stdClass>
            ->map(static function (\stdClass $std): ?Foo {
                return Foo::get($std);
            }) // should now be Collection<?Foo>
            ->filter(); // should now be Collection<Foo>
    }
}

我很困惑为什么 phpstan (0.12.64) 会失败:

18: [ERROR] Method PhpstanIssue::whyDoesThisFail() should return
Illuminate\Support\Collection&iterable<Foo> but returns 
Illuminate\Support\Collection&iterable<Illuminate\Support\Collection&iterable<stdClass>>. (phpstan)

为什么 phpstan 不能推断出这个管道的正确结果类型?如何让 phpstan 理解管道?


我可以验证我的代码在 phpunit 测试用例中是否有效:

class MyCodeWorks extends TestCase
{
    public function testPipeline()
    {
        $result = (new PhpstanIssue())->whyDoesThisFail(
            new Collection(
                [
                    new Collection([new \stdClass(), new \stdClass()]),
                    new Collection([new \stdClass()]),
                ]
            )
        );

        self::assertCount(3, $result);
        foreach ($result as $item) {
            self::assertInstanceOf(Foo::class, $item);
        }
    }
}

会过去的。


为了这个问题,我的Foo 只是一个虚拟类。唯一相关的是它需要一个 stdClass 实例并将其转换为 ?Foo 实例。

class Foo
{
    public static function get(\stdClass $std): ?Foo
    {
        // @phpstan-ignore-next-line
        return (bool) $std ? new static() : null;
    }
}

【问题讨论】:

  • Illuminate\Support\Collection 默认不是泛型类。你也在使用 Larastan 吗?
  • @CanVural 不,我目前没有使用 Larastan。使用它可以实现吗?
  • 是的,Larastan 有将Collection 类标记为通用的存根文件。它应该适用于您的示例。虽然它也需要一个键类型。所以像Collection&lt;int, Collection&lt;int, stdClass&gt;&gt; 这样的东西,但如果你不想使用它,你也可以创建你的存根文件,将通用 PHPDocs 添加到 Collection 类。
  • @CanVural 我会接受它作为支持/批准 btw 的答案 :)
  • 我添加了一个答案。文档中解释了如何使用 PHPStan 注册该存根文件(我在答案中链接了该文件),所以我没有在这里重复。

标签: php laravel illuminate-container phpstan


【解决方案1】:

Illuminate\Support\Collection 类本身不是通用的。所以写Collection&lt;Foo&gt; 是错误的。这会导致像Illuminate\Support\Collection&amp;iterable&lt;Illuminate\Support\Collection&amp;iterable&lt;stdClass&gt;&gt;这样的错误消息

你有两个选择:

  1. 正在安装Larastan。这是 Laravel 的 PHPStan 扩展。它有 stub files 使 Illuminate\Support\Collection 类通用。

  2. 或者,如果您只是使用 illuminate/collections 独立包而没有完整的 Laravel 应用程序,您可以编写自己的存根文件。 来自PHPStan docs

...您可以使用正确的 PHPDoc 编写存根文件。它就像源代码,但 PHPStan 只从中读取 PHPDocs。因此命名空间和类/接口/特征/方法/函数名称必须与您描述的原始源匹配。但是方法体可以留空,PHPStan 只对 PHPDocs 感兴趣。

对于您的示例,以下存根文件就足够了:

<?php

namespace Illuminate\Support;

/**
 * @template TKey
 * @template TValue
 * @implements \ArrayAccess<TKey, TValue>
 * @implements Enumerable<TKey, TValue>
 */
class Collection implements \ArrayAccess, Enumerable
{
    /**
     * @template TReturn
     * @param callable(TValue, TKey): TReturn $callable
     * @return static<TKey, TReturn>
     */
    public function map($callable) {}
}

【讨论】:

    猜你喜欢
    • 2017-11-27
    • 2021-11-05
    • 1970-01-01
    • 2018-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-17
    • 1970-01-01
    相关资源
    最近更新 更多