Silence “Les déclarations doivent être compatibles” en PHP 7

Après la mise à jour vers PHP 7, les journaux se sont presque étouffés avec ce type d’erreurs:

PHP Warning: Declaration of Example::do($a, $b, $c) should be compatible with ParentOfExample::do($c = null) in Example.php on line 22548

Comment puis-je faire taire ces et seulement ces erreurs en PHP 7?

  • Avant PHP 7, il s’agissait des avertissements de type E_STRICT qui pouvaient être facilement traités . Maintenant, ils ne sont que de vieux avertissements. Comme je veux connaître d’autres avertissements, je ne peux pas simplement désactiver tous les avertissements.

  • Je n’ai pas la capacité mentale de réécrire ces API existantes sans même mentionner tous les logiciels qui les utilisent. Devinez quoi, personne ne va payer pour ça aussi. Je ne les développe pas non plus, donc je ne suis pas celui qui blâme. (Tests unitaires? Pas à la mode il y a dix ans.)

  • Je voudrais éviter toute supercherie avec func_get_args et similaire autant que possible.

  • Pas vraiment je veux passer à PHP 5.

  • Je veux toujours connaître d’autres erreurs et avertissements.

Existe-t-il un moyen propre et agréable d’y parvenir?

    1. Solution de contournement

    Comme il n’est pas toujours possible de corriger tout le code que vous n’avez pas écrit , en particulier l’ancien code …

     if (PHP_MAJOR_VERSION >= 7) { set_error_handler(function ($errno, $errstr) { return strpos($errstr, 'Declaration of') === 0; }, E_WARNING); } 

    Ce gestionnaire d’erreurs renvoie true pour les avertissements commençant par Declaration of qui indique à PHP qu’un avertissement a été pris en compte. C’est pourquoi PHP ne signalera pas cet avertissement ailleurs.

    De plus, ce code ne fonctionnera qu’en PHP 7 ou supérieur.


    Si vous voulez que cela se produise uniquement en ce qui concerne une base de code spécifique, vous pouvez vérifier si un fichier avec une erreur appartient à cette base de code ou à une bibliothèque d’intérêt:

     if (PHP_MAJOR_VERSION >= 7) { set_error_handler(function ($errno, $errstr, $file) { return strpos($file, 'path/to/legacy/library') !== false && strpos($errstr, 'Declaration of') === 0; }, E_WARNING); } 

    2. solution appropriée

    En ce qui concerne la réparation du code hérité de quelqu’un d’autre, il existe un certain nombre de cas où cela peut être fait entre simple et facile à gérer. Dans les exemples ci-dessous, la classe B est une sous-classe de A Notez que vous ne supprimerez pas nécessairement les violations LSP en suivant ces exemples.

    1. Certains cas sont assez faciles. Si dans une sous-classe il y a un argument par défaut manquant, ajoutez-le et continuez. Par exemple dans ce cas:

       Declaration of B::foo() should be compatible with A::foo($bar = null) 

      Vous feriez:

       - public function foo() + public function foo($bar = null) 
    2. Si vous avez ajouté des contraintes supplémentaires dans une sous-classe, supprimez-les de la définition tout en vous déplaçant dans le corps de la fonction.

       Declaration of B::add(Baz $baz) should be compatible with A::add($n) 

      Vous pouvez vouloir utiliser des assertions ou lancer une exception en fonction de la gravité.

       - public function add(Baz $baz) + public function add($baz) { + assert($baz instanceof Baz); 

      Si vous constatez que les contraintes sont utilisées uniquement à des fins de documentation, déplacez-les à leur place.

       - protected function setValue(Baz $baz) + /** + * @param Baz $baz + */ + protected function setValue($baz) { + /** @var $baz Baz */ 
    3. Si votre sous-classe a moins d’arguments qu’une super-classe et que vous pouvez les rendre facultatives dans la super-classe, ajoutez simplement des espaces réservés dans la sous-classe. Chaîne d’erreur donnée:

       Declaration of B::foo($param = '') should be compatible with A::foo($x = 40, $y = '') 

      Vous feriez:

       - public function foo($param = '') + public function foo($param = '', $_ = null) 
    4. Si vous voyez des arguments requirejs dans une sous-classe, prenez les choses en main.

       - protected function foo($bar) + protected function foo($bar = null) { + if (empty($bar['key'])) { + throw new Exception("Invalid argument"); + } 
    5. Parfois, il peut être plus facile de modifier la méthode de la superclasse pour exclure un argument optionnel, retombant dans func_get_args magie func_get_args . N’oubliez pas de documenter l’argument manquant.

        /** + * @param callable $bar */ - public function getFoo($bar = false) + public function getFoo() { + if (func_num_args() && $bar = func_get_arg(0)) { + // go on with $bar 

      Bien sûr, cela peut devenir très fastidieux si vous devez supprimer plus d’un argument.

    6. Les choses deviennent beaucoup plus intéressantes si vous avez de graves violations du principe de substitution. Si vous n’avez pas d’arguments tapés, alors c’est facile. Rendez tous les arguments supplémentaires facultatifs, puis vérifiez leur présence. Erreur:

       Declaration of B::save($key, $value) should be compatible with A::save($foo = NULL) 

      Vous feriez:

       - public function save($key, $value) + public function save($key = null, $value = null) { + if (func_num_args() < 2) { + throw new Exception("Required argument missing"); + } 

      Notez que nous ne pouvons pas utiliser func_get_args() car il ne prend pas en compte les arguments par défaut (non transmis). Il ne nous rest plus que func_num_args() .

    7. Si vous avez toute une hiérarchie de classes avec une interface divergente, il peut être plus facile de la diviser davantage. Renommez une fonction avec une définition en conflit dans chaque classe. Ajoutez ensuite une fonction proxy dans un seul parent intermédiaire pour ces classes:

       function save($arg = null) // conforms to the parent { $args = func_get_args(); return $this->saveExtra(...$args); // diverged interface } 

      De cette façon, le LSP serait toujours violé, mais sans avertissement, mais vous devez conserver toutes les vérifications de type que vous avez dans les sous-classes.

    Pour ceux qui veulent corriger votre code afin qu’il ne déclenche plus l’avertissement: j’ai trouvé utile d’apprendre que vous pouvez append des parameters supplémentaires aux méthodes surchargées dans les sous-classes tant que vous leur donnez des valeurs par défaut. Ainsi, par exemple, alors que cela déclenchera l’avertissement:

     //"Warning: Declaration of B::foo($arg1) should be compatible with A::foo()" class B extends A { function foo($arg1) {} } class A { function foo() {} } 

    Cela ne va pas:

     class B extends A { function foo($arg1 = null) {} } class A { function foo() {} } 

    Si vous devez faire taire l’erreur, vous pouvez déclarer la classe à l’intérieur d’une expression silencieuse, immédiatement appelée:

     < ?php // unsilenced class Fooable { public function foo($a, $b, $c) {} } // silenced @(function () { class ExtendedFooable extends Fooable { public function foo($d) {} } })(); 

    Je recommande fortement contre cela, cependant. Il est préférable de corriger votre code plutôt que de faire taire les avertissements sur la façon dont il est cassé.


    Si vous avez besoin de maintenir la compatibilité avec PHP 5, sachez que le code ci-dessus ne fonctionne qu'en PHP 7, car PHP 5 n'a pas de syntaxe uniforme pour les expressions . Pour que cela fonctionne avec PHP 5, vous devez assigner la fonction à une variable avant de l'invoquer (ou en faire une fonction nommée):

     $_ = function () { class ExtendedFooable extends Fooable { public function foo($d) {} } }; @$_(); unset($_); 

    PHP 7 supprime le niveau d’erreur E_STRICT . Des informations à ce sujet peuvent être trouvées dans les notes de compatibilité de PHP7 . Vous pourriez également vouloir lire le document de proposition où il a été discuté pendant le développement de PHP 7.

    Le simple fait est le suivant: les notifications E_STRICT ont été introduites il y a plusieurs versions, dans une tentative d’informer les développeurs qu’ils utilisaient de mauvaises pratiques, mais d’abord sans essayer de forcer les modifications. Cependant, les versions récentes, et PHP 7 en particulier, sont devenues plus ssortingctes à cet égard.

    L’erreur que vous rencontrez est un cas classique:

    Vous avez défini une méthode dans votre classe qui remplace une méthode du même nom dans la classe parente, mais votre méthode de remplacement a une signature d’argument différente.

    La plupart des langages de programmation modernes ne permettent pas cela du tout. PHP permettait aux développeurs de se débrouiller avec ce genre de choses, mais le langage devient de plus en plus ssortingct avec chaque version, surtout maintenant avec PHP 7 – ils ont utilisé un nouveau numéro de version majeur pour justifier des modifications significatives rétrocompatibilité.

    Le problème est que vous avez déjà ignoré les messages d’avertissement. Votre question implique que c’est la solution que vous souhaitez continuer, mais les messages comme “ssortingct” et “obsolète” doivent être traités comme un avertissement explicite que votre code est susceptible de se briser dans les futures versions. En les ignorant au cours des dernières années, vous vous êtes effectivement placé dans la situation actuelle. (Je sais que ce n’est pas ce que vous voulez entendre, et cela n’aide pas vraiment la situation maintenant, mais il est important de le préciser)

    Il n’y a vraiment pas de travail autour du genre que vous recherchez. Le langage PHP évolue et si vous souhaitez vous en tenir à PHP 7, votre code devra également évoluer. Si vous ne pouvez vraiment pas corriger le code, vous devrez soit supprimer tous les avertissements, soit vivre avec ces avertissements qui encombreront vos journaux.

    L’autre chose que vous devez savoir si vous envisagez d’utiliser PHP 7, c’est qu’il existe un certain nombre d’autres ruptures de compatibilité avec cette version, y compris certaines qui sont assez subtiles. Si votre code est dans un état où il y a des erreurs comme celles que vous signalez, cela signifie qu’il existe probablement depuis un certain temps et qu’il a probablement d’autres problèmes qui vous poseront des problèmes en PHP 7. Pour un code comme celui-ci, Je vous suggère de faire un audit plus approfondi du code avant de vous engager dans PHP 7. Si vous n’êtes pas prêt à le faire ou si vous n’êtes pas prêt à corriger les bogues trouvés (et votre question implique que vous ne l’êtes pas) , alors je suggère que PHP 7 est probablement une mise à niveau trop loin pour vous.

    Vous avez la possibilité de revenir à PHP 5.6. Je sais que vous avez dit que vous ne vouliez pas faire cela, mais en tant que solution à court et à moyen terme, cela vous facilitera les choses. Franchement, je pense que ce pourrait être votre meilleure option.

    Je suis d’accord: l’exemple du premier message est une mauvaise pratique. Maintenant, si vous avez cet exemple:

     class AnimalData { public $shout; } class BirdData extends AnimalData { public $wingNumber; } class DogData extends AnimalData { public $legNumber; } class AnimalManager { public static function displayProperties(AnimalData $animal) { var_dump($animal->shout); } } class BirdManager extends AnimalManager { public static function displayProperties(BirdData $bird) { self::displayProperties($bird); var_dump($bird->wingNumber); } } class DogManager extends AnimalManager { public static function displayProperties(DogData $dog) { self::displayProperties($dog); var_dump($dog->legNumber); } } 

    Je pense que c’est une structure de code légitime, néanmoins, cela déclenchera un avertissement dans mes journaux car les “displayProperties” n’ont pas les mêmes parameters. De plus, je ne peux pas les rendre facultatifs en ajoutant un “= null” après eux …

    Ai-je raison de penser que cet avertissement est erroné dans cet exemple spécifique, s’il vous plaît?

    J’ai eu ce problème aussi. J’ai une classe qui remplace une fonction de la classe parente, mais la substitution a un nombre différent de parameters. Je peux penser à quelques solutions faciles, mais nécessiter un changement de code mineur.

    1. changer le nom de la fonction dans la sous-classe (pour ne plus remplacer la fonction parente) – ou –
    2. changer les parameters de la fonction parente, mais rendre les parameters supplémentaires facultatifs (par exemple, la fonction func ($ var1, $ var2 = null) – cela peut être plus facile et nécessiter moins de changements de code. Mais cela ne vaut peut-être pas la peine de changer cela parent si il a utilisé tant d’autres endroits, alors je suis allé avec # 1 dans mon cas.

    3. Si possible, au lieu de passer les parameters supplémentaires dans la fonction de sous-classe, utilisez global pour extraire les parameters supplémentaires. Ce n’est pas le codage idéal; mais un pansement possible de toute façon.