Do you want more PHPStan violations?
Edit 2019–07–08: Good news! PHPStan 0.11.10 includes support for inferring private property type from constructor! https://github.com/phpstan/phpstan/releases/tag/0.11.10:
Turn on with inferPrivatePropertyTypeFromConstructor: true
We use PHPStan a lot and we love it. Some of us even donate each month some money via Patreon.
I faced an issue few months ago, and I hit the very same one today while I started a big refactoring of an application. And I totally forgot about it 😂 Memory sucks :)
So let’s talk about it, and see how to solve it. And now I hope I will remember to fix it on every project I’m working on.
Consider this piece of code:
class Model
{
}
class ServiceA
{
private $serviceB;
public function __construct(ServiceB $serviceB)
{
$this->serviceB = $serviceB;
}
public function init()
{
// This line will generate a FatalError (DateTime != Model)
$this->serviceB->process(new DateTime());
}
}
class ServiceB
{
public function process(Model $model)
{
}
}
I’m expecting to get this error:
Parameter #1 $model of method ServiceB::process() expects Model, DateTime given.
But why is it valid? Because PHPStan does not infer type from constructor argument. And this is totally valid to not do so.
A solution would be to add some PHPDoc:
class ServiceA
{
/** @var ServiceB */
private $serviceB;
}
In the Symfony community, we have some standards. Additionally, I hate useless PHPDoc. I don’t want to pollute my code by adding them everywhere.
Symfony’s coding standards state to not add the PHPDoc to a property if it can be inferred from the constructor, it is also the default configuration of PHP CS Fixer and it is considered a best practice by some.
Since PHPStan has a nice plugin system, I wrote a plugin to automatically add theses type-hint in PHPStan engine few months ago. I used it in my current application and I was able to find 45 new violations \o/
You can read the code and add it to your project if you want to.
<?php
use PHPStan\Broker\Broker;
use PHPStan\Broker\BrokerFactory as PhpstanBrokerFactory;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\Php\PhpPropertyReflection;
use PHPStan\Reflection\PropertiesClassReflectionExtension;
use PHPStan\Reflection\PropertyReflection;
use PHPStan\Type\ObjectType;
class BrokerFactory extends PhpstanBrokerFactory
{
public function create(): Broker
{
$broker = parent::create();
$r = new \ReflectionProperty($broker, 'propertiesClassReflectionExtensions');
$r->setAccessible(true);
$propertiesClassReflectionExtensions = $r->getValue($broker);
array_unshift($propertiesClassReflectionExtensions, new PropertiesClassReflectionBasedOnConstructor());
$r->setValue($broker, $propertiesClassReflectionExtensions);
return $broker;
}
}
class PropertiesClassReflectionBasedOnConstructor implements PropertiesClassReflectionExtension
{
public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
{
return $this->extractFromConstructor($classReflection->getNativeReflection(), $propertyName);
}
public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection
{
$nativeClassReflection = $classReflection->getNativeReflection();
foreach ($nativeClassReflection->getConstructor()->getParameters() as $parameter) {
if ($propertyName !== $parameter->name) {
continue;
}
$type = new ObjectType((string) $parameter->getType());
return new PhpPropertyReflection($classReflection, $type, $nativeClassReflection->getProperty($propertyName), false, false);
}
}
private function extractFromConstructor(\ReflectionClass $nativeClassReflection, string $propertyName)
{
if (!$nativeClassReflection->hasProperty($propertyName)) {
return false;
}
$constructor = $nativeClassReflection->getConstructor();
if (!$constructor) {
return false;
}
foreach ($constructor->getParameters() as $parameter) {
if ($propertyName !== $parameter->name) {
continue;
}
if (!$parameter->getType()) {
return false;
}
if ($parameter->isOptional()) {
return false;
}
return class_exists((string) $parameter->getType());
}
if ($parentClass = $nativeClassReflection->getParentClass()) {
try {
$reflectionClass = new \ReflectionClass($parentClass);
} catch (\ReflectionException $e) {
return false;
}
return $this->extractFromConstructor($reflectionClass, $propertyName);
}
return false;
}
}
Commentaires et discussions
Using PHPStan to analyse Symfony Console Application
If you want to use the PHPStan Symfony analysis and encounter an error with PHPParser, you should create a new Symfony environment with inlining deactivated. Here is why! PHPStan is a very handy tool that will analyze your code and tell you what leftover errors there might be. It…
Lire la suite de l’article Using PHPStan to analyse Symfony Console Application
Nos articles sur le même sujet
Ces clients ont profité de notre expertise
Nous avons développé un outil statistique complet développé pour ORPI. Basé sur PHP, Symfony et Elasticsearch, cet outil offre à toutes les agences du réseau une visibilité accrue sur leurs annonces. Il garantit également une transparence totale envers les clients, en fournissant des statistiques détaillées sur les visualisations et les contacts de…
Groupama Épargne Salariale digitalise son expérience client en leur permettant d’effectuer leurs versements d’épargne salariale en ligne. L’application offre aux entreprises une interface web claire et dynamique, composé d’un tunnel de versement complet : import des salariés via fichier Excel, rappel des contrats souscrits et des plans disponibles, …
À l’occasion de la 12e édition du concours Europan Europe, JoliCode a conçu la plateforme technique du concours. Ce site permet la présentation des différents sites pour lesquels il y a un appel à projets, et encadre le processus de recueil des projets soumis par des milliers d’architectes candidats. L’application gère également toute la partie post-concours…