Silence "Declaration ... must be compatible" in PHP 7

After upgrading to PHP 7, the logs almost suffocated from these errors:

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

How to disable these and only these errors in PHP 7?

  • Before PHP 7, they were E_STRICT type of warnings that could be easily dealt with . Now they are just old warnings. Since I do want to know about other warnings, I cannot completely disable all warnings.

  • I do not have the mental ability to rewrite these legacy APIs without even mentioning all the software that uses them. Guess what, no one will pay for this either. I do not develop them in the first place, so I am not to blame. (Unit tests? Not in vogue ten years ago.)

  • I would like to avoid any func_get_args with func_get_args and as much as possible.

  • Actually I do not want to switch to PHP 5.

  • I still want to know about other errors and warnings.

Is there a clean and good way to accomplish this?

+57
php-7
Mar 18 '16 at 8:37
source share
6 answers

1. Workaround

Since it is not always possible to fix all the code that you did not write, especially the old one ...

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

This error handler returns true for warnings starting with the Declaration of which basically tells PHP that the warning was handled. This is why PHP will not report this warning elsewhere.

In addition, this code will only work in PHP 7 or higher.




If you want this to happen only in relation to a specific code base, then you can check whether the file with the error belongs to that code base or library of interest:

 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. The right decision

Regarding the actual correction of another legacy code, there are a number of cases where this can be done between simple and manageable. In the examples below, class B is a subclass of A Note that you do not necessarily remove any LSP violations by following these examples.

  1. Some cases are quite simple. If there is no default argument in the subclass, just add it and move on. For example, in this case:

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

    You would do:

     - public function foo() + public function foo($bar = null) 
  2. If additional restrictions are added to the subclass, remove them from the definition by moving inside the function body.

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

    You can use claims or throw an exception depending on the severity.

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

    If you see that restrictions are used solely for documentation purposes, move them to where they belong.

     - protected function setValue(Baz $baz) + /** + * @param Baz $baz + */ + protected function setValue($baz) { + /** @var $baz Baz */ 
  3. If your subclass has fewer arguments than the superclass, and you can make them optional in the superclass, just add placeholders to the subclass. The specified error string:

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

    You would do:

     - public function foo($param = '') + public function foo($param = '', $_ = null) 
  4. If you see some of the arguments required in a subclass, take matters into your own hands.

     - protected function foo($bar) + protected function foo($bar = null) { + if (empty($bar['key'])) { + throw new Exception("Invalid argument"); + } 
  5. It can sometimes be easier to change the superclass method to completely eliminate the optional argument, returning to the magic of func_get_args . Remember to document the missing argument.

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

    Of course, this can become very tedious if you need to remove more than one argument.

  6. Everything becomes much more interesting if you have serious violations of the principle of substitution. If you have no typed arguments, then this is easy. Just make all additional arguments optional, and then check for their presence. This error:

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

    You would do:

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

    Please note that we could not use func_get_args() here, because it does not take into account the default arguments (not passed). We only have func_num_args() left.

  7. If you have entire class hierarchies with a diverging interface, it might be easier to go even further. Rename a function with a conflicting definition in each class. Then add the proxy function in one parent broker for these classes:

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

    Thus, the LSP will still be violated, albeit without warning, but you will keep all the type checks that you have in subclasses.

+87
Mar 24 '16 at 9:22
source share

If you must disable this error, you can declare the class inside a forced expression with an immediate call:

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

I would highly recommend against this. Better fixing your code than disabling warnings about how it broke.




If you need to maintain compatibility with PHP 5, keep in mind that the above code only works in PHP 7, because PHP 5 does not have a single syntax for expressions . To make it work with PHP 5, you need to assign a function to a variable before calling it (or make it a named function):

 $_ = function () { class ExtendedFooable extends Fooable { public function foo($d) {} } }; @$_(); unset($_); 
+21
Mar 18 '16 at 12:21
source share

For those who want to really fix your code so that it no longer triggers a warning: it was useful for me to find out that you can add additional parameters to overridden methods in subclasses as long as you give them default values. So, for example, until this causes a warning:

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

It will not be:

 class B extends A { function foo($arg1 = null) {} } class A { function foo() {} } 
+21
May 6 '16 at 9:36
source share

PHP 7 removes the error level E_STRICT . Information on this can be found in the PHP7 compatibility notes . You can also read the application document where it was discussed when PHP 7 was being developed.

A simple fact: E_STRICT notifications were introduced several versions ago, in an attempt to notify developers that they are using bad practice, but initially without any changes. However, recent versions and PHP 7 in particular, have become more stringent with respect to these things.

The error you are experiencing is a classic case:

You have defined a method in your class that overrides a method with the same name in the parent class, but your override method has a different argument signature.

Most modern programming languages ​​would not actually allow this. PHP used to let developers get away with such things, but the language is becoming more strict with each version, especially now with PHP 7 - they came with a new major version number specifically so that they could justify the significant changes that break backward compatibility.

The problem is that you have already ignored the warning messages. Your question implies that this is the solution you want to continue, but messages like "strict" and "outdated" should be considered as a clear warning that your code is likely to break in future versions. By ignoring them over the past few years, you have actually put yourself in the situation you are currently in. (I know that not what you want to hear, and actually does not help now, but it is important to clarify clearly)

Actually, the job you are looking for does not exist. The PHP language is evolving, and if you want to stick with PHP 7, your code must also evolve. If you really cannot fix the code, you will either have to suppress all warnings or live with these warnings cluttering your logs.

Another thing you need to know if you plan to stick with PHP 7 is that there are a number of other compatibility breaks with this version, including some that are pretty subtle. If your code is in a state in which it has errors similar to the one you are reporting, this means that it is probably quite a long time, and probably has other problems that will cause problems in PHP 7. For such code , I would suggest conducting a more thorough audit of the code before making PHP 7. If you are not ready to do this or are not ready to fix the errors found (and it is understood that your question is that you are not), then I would suggest that PHP 7 is probably too much for you.

You have the opportunity to return to PHP 5.6. I know that you said that you do not want to do this, but in the short term it will make your life easier. Honestly, I think this might be your best option.

+17
Mar 18 '16 at 9:53 on
source share

I agree: the example in the first post is bad practice. Now, if you have this example:

 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); } } 

I believe this is a legitimate code structure, however it will raise a warning in my logs because "displayProperties" do not have the same parameters. Moreover, I cannot make them optional by adding "= null" after them ...

Do I think that this warning is incorrect in this particular example?

+8
Mar 16 '17 at 17:19
source share

I also had this problem. I have a class that overrides the function of the parent class, but the override has a different number of parameters. I can come up with a few simple works - but this requires a slight code change.

  • change the name of the function in the subclass (so that it no longer overlaps the parent function) -or -
  • change the parameters of the parent function, but add additional parameters (for example, function func ($ var1, $ var2 = null) - this may be the easiest and require less code changes. But it may not be worth changing it in the parent if it used so many other places, so I went with No. 1 in my case.

  • If possible, instead of passing additional parameters to subclass functions, use global to pull additional parameters. This is not perfect coding; but possible lane assistance anyway.

+6
Aug 18 '16 at 15:05
source share



All Articles