1

I'm running PHPStan in my project on level 9 and I'm closing in on zero errors now. One problem I can't find a solution for though is in the CRUD part of my project.

With >50 complex entities that require permissions, have relations etc. of course I could not write the CRUD part n-times, repeating the same logic and templates again and again. Instead I have a central CrudController and the name of an entity as well as the ID of a specific entry that e.g. needs to be updated are part of the URL:

.../crud/[Entity]/[ID] such as .../crud/Article/123

The controller action (Symfony here = the class method) then takes the name of the entity in variable $entity to load the specific entry with the ID in $id. So instead of

$entry = new Article(...);

I have to load the correct entity like this:

$entry = new $entity(...);

Everything in my CRUD already works. The issue here is that PHPStan doesn't understand the type of the object that is being returned. I've tried various ways like

/** @var $entity $entry */

above the declaration of variable $entry but to no avail. PHPStan just throws errors like

PHPDoc tag @var contains unknown class ... or PHPDoc tag @var has invalid value...

Here's a very basic version of this issue in a playground: https://phpstan.org/r/d9cb71bc-f2c5-4387-a3d6-841656824d41

Without something like @var anything else that happens with $entry then throws errors, e.g.:

$id = $entry->getId(); throws Call to undefined method object::getId().

I've read about generics / templates etc. in the documentation but that wasn't successful. I can't change the entities or their PHPDoc comments. So I guess I have to rely on @var above the variable declaration. But I also can't add all possible entity names like so:

/** @var Article|Blog|Statistic|... $entry */

So is there a clean way to tell PHPDoc that the object type is dynamic and based on $entity?

JamesApril
  • 11
  • 2

1 Answers1

2

The phpstan/doctrine extension will take care of most of these issues for you. However, PHPStan doesn't actually run any of your code, so it can't possibly handle the situation where you have an unknown dynamic class name in a variable at run time. You could just ignore:

// @phpstan-ignore-next-line
$id = $entry->getId();

Alternatively, if getId() is the only method that you use in this manner, you might create an interface for that method:

interface IdInterface {
    public function getId();
}

And then have all your entities implement that:

class Article implements IdInterface {
    public function getId() {
        return $this->id;
    }
    // ...
}

Then you could typehint on the interface:

/** @var IdInterface $entry */
$entry = new $entity(...);
$id = $entry->getId();
Alex Howansky
  • 50,515
  • 8
  • 78
  • 98
  • Exactly, and PhpStan is an awesome tool because it detects unsafe code. And your code could be unsafe. PhpStan shall report you these lines! The method purposed by Alex is necessary. If you want a safe code, you have to add something like `if (!$entry implements IdInterface) throw new LogicException)`. Because your code currently works, but after some years, if another developer updates it, ... – Alexandre Tranchant Oct 24 '22 at 06:12
  • Ok thanks! I get that PHPStan doesn't actually run the code but some special declaration that the returned object type is dynamic should be implemented. For now I've decided to add these errors to my baseline so at least they won't show up again and again in the IDE. – JamesApril Oct 30 '22 at 12:48
  • What would such a declaration cause PHPStan to do? Skip it? It can't provide its analysis unless it knows the type, and not being able to determine the type is a problem that you should be concerned about. – Alex Howansky Oct 31 '22 at 13:20
  • Please don't use `i̶g̶n̶o̶r̶e̶-̶n̶e̶x̶t̶-̶l̶i̶n̶e̶` rather use `ignore-line` on that 1 specific line to avoid future misplacement of the ignore statement by future editors :-) – jave.web Feb 20 '23 at 13:28