web-dev-qa-db-fra.com

Silence "Les déclarations ... doivent être compatibles" dans PHP 7

Après la mise à niveau vers PHP 7, les journaux s’étouffaient presque 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 supprimer ces erreurs et uniquement ces erreurs dans PHP 7?

  • Avant PHP 7 ils étaient E_STRICT type d’avertissements qui pourrait être facilement traité . Maintenant, ils sont tout simplement vieux avertissements. Puisque je souhaite connaître d’autres avertissements, je ne peux pas tout simplement les désactiver.

  • Je n'ai pas la capacité mentale de réécrire ces API héritées 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 blâmable. (Tests unitaires? Pas à la mode il y a dix ans.)

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

  • Pas vraiment, je veux rétrograder à PHP 5.

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

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

62
sanmai

1. Contournement

Puisqu'il n'est pas toujours possible de corriger tout le code vous n'avez pas écrit, en particulier l'ancien ...

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, Ce qui indique à PHP qu'un avertissement a été pris en charge. C'est pourquoi PHP ne signalera pas cet avertissement ailleurs.

De plus, ce code ne fonctionnera que dans PHP 7 ou supérieur.


Si vous souhaitez que cela ne se produise que pour une base de code spécifique, vous pouvez alors 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 correction du code hérité de quelqu'un d'autre, il existe un certain nombre de cas dans lesquels cela pourrait être fait entre facile et gérable. Dans les exemples ci-dessous, la classe B est une sous-classe de A. Notez que vous ne supprimerez pas nécessairement les violations de LSP en suivant ces exemples.

  1. Certains cas sont assez faciles. S'il manque un argument par défaut dans une sous-classe, ajoutez-le et passez à la suite. 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 à l'intérieur du corps de la fonction.

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

    Vous souhaiterez peut-être utiliser des assertions ou émettre 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 là où elles appartiennent.

    - 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 facultatifs 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 requis dans une sous-classe, prenez l'affaire 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 super-classe pour exclure un argument optionnel, en retombant dans la 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 plusieurs arguments.

  6. Les choses deviennent beaucoup plus intéressantes si vous avez de graves violations du principe de substitution. Si vous n'avez pas tapé d'arguments, c'est facile. Il suffit de rendre tous les arguments supplémentaires facultatifs, puis de vérifier leur présence. Erreur donnée:

    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 pourrions pas utiliser func_get_args() ici car il ne prend pas en compte les arguments par défaut (non passés). Il ne nous reste 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 diverger encore plus loin. 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é, bien que sans avertissement, mais vous garderez toutes les vérifications de type que vous avez dans les sous-classes.

97
sanmai

Si vous devez faire taire l'erreur, vous pouvez déclarer la classe dans une expression de fonction silencieuse, appelée immédiatement:

<?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 que de faire taire les avertissements sur la manière dont il est cassé.


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

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

Pour ceux qui veulent réellement corriger votre code afin qu'il ne déclenche plus l'avertissement: j'ai trouvé utile d'apprendre que vous pouvez ajouter des paramètres supplémentaires aux méthodes surchargées dans les sous-classes, à condition de leur attribuer des valeurs par défaut. Ainsi, par exemple, 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() {}
}
21
Matt Browne

PHP 7 supprime le E_STRICT niveau d'erreur. Des informations à ce sujet sont disponibles dans le Notes de compatibilité PHP7 . Vous voudrez peut-être aussi lire le document de proposition où il a été discuté pendant que PHP 7 était en cours de développement.

Le simple fait est la suivante: le E_STRICT les avis ont été introduits il y a un certain nombre de versions, dans le but de notifier aux développeurs qu'ils utilisaient de mauvaises pratiques, mais au départ sans essayer de forcer des modifications. Cependant, les versions récentes, et PHP 7 en particulier, sont devenues plus strictes à ce sujet.

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 parent, mais votre méthode de substitution a une signature d'argument différente.

La plupart des langages de programmation modernes ne le permettraient pas du tout. PHP était utilisé pour permettre aux développeurs de s’en tirer avec ce genre de choses, mais le langage devient de plus en plus strict avec chaque version, en particulier maintenant avec PHP 7 - ils est allé avec un nouveau numéro de version majeur spécifiquement afin qu'ils puissent justifier d'apporter des modifications significatives qui brisent la compatibilité en amont.

Le problème que vous avez, c'est parce que vous avez déjà ignoré les messages d'avertissement. Votre question implique que vous souhaitiez continuer à utiliser cette solution. Toutefois, des messages tels que "strict" et "obsolète" doivent être traités comme un avertissement explicite indiquant que votre code risque de ne plus être disponible. En les ignorant depuis plusieurs années, vous vous êtes effectivement placé dans la situation actuelle. (Je sais que ce n'est pas ce que vous voulez entendre et que cela n'aide pas vraiment la situation, mais il est important que tout soit clair.)

Il n’existe pas vraiment de solution de ce type que vous recherchez. La langue PHP évolue, et si vous voulez vous en tenir à PHP 7, votre code devra également évoluer. Si vous ne pouvez vraiment pas le corriger, vous devrez alors supprimer tous les avertissements ou vivre avec ceux-ci. avertissements encombrant vos journaux.

L’autre chose à savoir si vous envisagez de vous en tenir à PHP 7 est qu’il existe un certain nombre d’autres pauses de compatibilité avec cette version, y compris certaines qui sont assez subtiles. Si votre code est inséré un état dans lequel il y a des erreurs comme celle que vous signalez, cela signifie que cela existe probablement depuis assez longtemps et que d'autres problèmes risquent de vous causer des problèmes dans PHP 7. Pour code comme celui-ci, je suggérerais de faire un audit plus approfondi du code avant de s’engager à PHP 7.). Si vous n'êtes pas prêt à le faire, ou prêt à corriger les bugs trouvés (et votre question implique implicitement que vous ne l'êtes pas), alors je suggérerais que PHP 7 soit probablement une mise à niveau trop avancée pour vous.

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

17
Simba

Je suis d'accord: l'exemple dans le premier post 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 crois que ceci est une structure de code légitime, néanmoins cela va déclencher un avertissement dans mes journaux car les displayProperties() n'ont pas les mêmes paramètres. 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 faux dans cet exemple spécifique, s'il vous plaît?

9
Zaziffic

J'ai eu ce problème aussi. J'ai une classe qui remplace une fonction de la classe parente, mais le remplacement a un nombre différent de paramètres. Je peux penser à quelques solutions de contournement simples - mais nécessite un changement mineur du code.

  1. modifier le nom de la fonction dans la sous-classe (afin qu'il ne remplace plus la fonction parent) -ou-
  2. changez les paramètres de la fonction parent, mais rendez les paramètres supplémentaires facultatifs (par exemple, la fonction func ($ var1, $ var2 = null) - cela peut être plus simple et nécessiter moins de modifications de code. Mais cela ne vaut peut-être pas la peine de le changer dans le menu contextuel. parent si c’est utilisé dans tant d’autres endroits, j’ai donc choisi le n ° 1 dans mon cas.

  3. Si possible, au lieu de transmettre les paramètres supplémentaires dans la fonction de sous-classe, utilisez global pour extraire les paramètres supplémentaires. Ce n'est pas un codage idéal; mais un pansement possible quand même.

6
AeonTrek