PHPFixing
  • Privacy Policy
  • TOS
  • Ask Question
  • Contact Us
  • Home
  • PHP
  • Programming
  • SQL Injection
  • Web3.0

Wednesday, November 23, 2022

[FIXED] How to get phpstan to infer the type for my Laravel Collection pipeline?

 November 23, 2022     illuminate-container, laravel, php, phpstan     No comments   

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:

  1. Installing Larastan. It's a PHPStan extension for Laravel. And it has stub files that makes Illuminate\Support\Collection class generic.

  2. 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)
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg
Newer Post Older Post Home

0 Comments:

Post a Comment

Note: Only a member of this blog may post a comment.

Total Pageviews

Featured Post

Why Learn PHP Programming

Why Learn PHP Programming A widely-used open source scripting language PHP is one of the most popular programming languages in the world. It...

Subscribe To

Posts
Atom
Posts
Comments
Atom
Comments

Copyright © PHPFixing