如何让 phpstan 推断我的 Laravel Collection 管道的类型?

k0p*_*kus 3 php laravel illuminate-container phpstan

鉴于我的班级

<?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>
    }
}
Run Code Online (Sandbox Code Playgroud)

我非常困惑为什么 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)
Run Code Online (Sandbox Code Playgroud)

为什么 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

将传递。


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

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

Can*_*ral 5

Illuminate\\Support\\Collection类本身不是通用的。所以写法Collection<Foo>是错误的。这会导致错误消息,例如Illuminate\\Support\\Collection&iterable<Illuminate\\Support\\Collection&iterable<stdClass>>

\n

您有两个选择:

\n
    \n
  1. 安装拉拉斯坦。它是 Laravel 的 PHPStan 扩展。它具有使类通用的存根文件。Illuminate\\Support\\Collection

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

    \n
  4. \n
\n
\n

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

\n
\n

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

\n
<?php\n\nnamespace Illuminate\\Support;\n\n/**\n * @template TKey\n * @template TValue\n * @implements \\ArrayAccess<TKey, TValue>\n * @implements Enumerable<TKey, TValue>\n */\nclass Collection implements \\ArrayAccess, Enumerable\n{\n    /**\n     * @template TReturn\n     * @param callable(TValue, TKey): TReturn $callable\n     * @return static<TKey, TReturn>\n     */\n    public function map($callable) {}\n}\n
Run Code Online (Sandbox Code Playgroud)\n