如何让 phpstan 推断我的 Laravel 收集管道的类型?
How to get phpstan to infer the type for my Laravel Collection pipeline?
鉴于我的 class
<?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
只是一个虚拟的class。唯一相关的是它采用 stdClass
实例并将其转换为 ?Foo
实例。
class Foo
{
public static function get(\stdClass $std): ?Foo
{
// @phpstan-ignore-next-line
return (bool) $std ? new static() : null;
}
}
Illuminate\Support\Collection
class 本身不是通用的。所以写 Collection<Foo>
是错误的。这会导致错误消息,如 Illuminate\Support\Collection&iterable<Illuminate\Support\Collection&iterable<stdClass>>
您有两个选择:
安装 Larastan. It's a PHPStan extension for Laravel. And it has stub files 使 Illuminate\Support\Collection
class 通用。
或者,如果您只是使用 illuminate/collections
独立包而没有完整的 Laravel 应用程序,您可以编写自己的存根文件。
来自 PHPStan docs:
... you can write a stub file with the right PHPDoc. It’s like source code, but PHPStan only reads PHPDocs from it. So the namespace and class/interface/trait/method/function names must match with the original source you’re describing. But method bodies can stay empty, PHPStan is only interested in the 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) {}
}
鉴于我的 class
<?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
只是一个虚拟的class。唯一相关的是它采用 stdClass
实例并将其转换为 ?Foo
实例。
class Foo
{
public static function get(\stdClass $std): ?Foo
{
// @phpstan-ignore-next-line
return (bool) $std ? new static() : null;
}
}
Illuminate\Support\Collection
class 本身不是通用的。所以写 Collection<Foo>
是错误的。这会导致错误消息,如 Illuminate\Support\Collection&iterable<Illuminate\Support\Collection&iterable<stdClass>>
您有两个选择:
安装 Larastan. It's a PHPStan extension for Laravel. And it has stub files 使
Illuminate\Support\Collection
class 通用。或者,如果您只是使用
illuminate/collections
独立包而没有完整的 Laravel 应用程序,您可以编写自己的存根文件。 来自 PHPStan docs:
... you can write a stub file with the right PHPDoc. It’s like source code, but PHPStan only reads PHPDocs from it. So the namespace and class/interface/trait/method/function names must match with the original source you’re describing. But method bodies can stay empty, PHPStan is only interested in the 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) {}
}