Issue
Given my 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>
}
}
I am highely confused why phpstan (0.12.64) would fail with:
18: [ERROR] Method PhpstanIssue::whyDoesThisFail() should return
Illuminate\Support\Collection&iterable<Foo> but returns
Illuminate\Support\Collection&iterable<Illuminate\Support\Collection&iterable<stdClass>>. (phpstan)
Why can't phpstan infer the proper result type of this pipe? How can I make phpstan understand the pipe?
I can verify that my code works within a phpunit testcase:
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);
}
}
}
will pass.
My Foo
is just a dummy class for the sake of this question. It's only relevant that it takes a stdClass
instance and transforms it into a ?Foo
one.
class Foo
{
public static function get(\stdClass $std): ?Foo
{
// @phpstan-ignore-next-line
return (bool) $std ? new static() : null;
}
}
Solution
Illuminate\Support\Collection
class is not generic on its own. So writing Collection<Foo>
is wrong. That causes the error messages like Illuminate\Support\Collection&iterable<Illuminate\Support\Collection&iterable<stdClass>>
You have two options:
Installing Larastan. It's a PHPStan extension for Laravel. And it has stub files that makes
Illuminate\Support\Collection
class generic.Or if you are just using the
illuminate/collections
standalone package without full Laravel app you can write your own stub files. From 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.
For your example the following stub file should be enough:
<?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) {}
}
Answered By - Can Vural Answer Checked By - Candace Johnson (PHPFixing Volunteer)
0 Comments:
Post a Comment
Note: Only a member of this blog may post a comment.