With the transition to PHP 7, the error log for many old projects was packed to capacity with messages of this type:

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

This is just one example of an error. There are many variants of such errors.

The following points complicate the problem:

  • The project code is endless. Test coverage is zero. Refactor and correct all errors will take decent time. No one will pay for this work. In short, correcting all these errors except for very simple ones is not an option.

  • It would be possible to disable all warnings altogether, but then there is a risk of missing some really important errors. Disabling warnings altogether is not an option.

  • I would not like to lose all those improvements in terms of speed and stability that are in PHP 7. Support PHP 5 already consider that it is over , it means that you will need to switch to PHP 7 anyway. Returning to PHP 5 is not an option.

Considering the above, how can you free server logs only from these errors?

    3 answers 3

    If you do not stick your head in the sand , that there are forces trying not to touch the old code that is still working, then there are a number of cases where corrections can be made either simply or doable. In the examples below, class B is a subclass of A The examples below will not necessarily eliminate fundamental violations of the principle of substitution (LSP), but at least PHP will not swear about them.

    1. A special simple case is when the subclass method has a new argument that does not have a default value. Just add the default value and live on. Example:

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

      You will do:

       - public function foo() + public function foo($bar = null) 
    2. If restrictions are added to the subclass method, then they should be removed from the method signature, transferring to the function body.

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

      You will want to use assert or throw an exception, depending on the severity of the error.

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

      If you see that the restrictions in the method signature are used only for documentation purposes, then transfer them to where the method parameters are documented, that is, in a comment.

       - protected function setValue(Baz $baz) + /** + * @param Baz $baz + */ + protected function setValue($baz) { + /** @var $baz Baz */ 
    3. If there are fewer arguments in the subclass method than in the superclass, and you can make the arguments optional in the superclass, it will suffice to replace the unused arguments with stubs.

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

      You will do:

       - public function foo($param = '') + public function foo($param = '', $_ = null) 
    4. If you see that some arguments in the subclass have become mandatory, remove the requirement of mandatoryness from the method signature in the method body.

       - protected function foo($bar) + protected function foo($bar = null) { + if (empty($bar['key'])) { + throw new Exception("Invalid argument"); + } 
    5. Sometimes it may be easier to change the superclass method by removing the optional arguments, going to magic using func_get_args . Remember to document this virtual 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 

      It is clear that removing more than one argument in this way can be difficult.

    6. Everything is much more interesting if you have serious violations of the Liska principle. If your arguments do not require a type, everything is simple. It is enough to make all the arguments optional, then manually checking their presence. With this error:

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

      You will do:

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

      Note that we cannot use the func_get_args() function in this case because this function does not take arguments with default values. They simply will not be in the return value.

    7. If you have a whole family of classes with a very different interface, it may make sense to change them even more. Rename the function that violates the substitution principle. Then add a proxy method to call a new, renamed method in a single subclass of the entire hierarchy of deviating classes.

       function save($arg = null) // соответствует родителю { $args = func_get_args(); return $this->saveExtra(...$args); // отличный интерфейс } 

      Thus, you do not eliminate the violation of the principle of substitution (new classes still cannot be called as if they are old), but you will keep all sorts of checks on the data types that are in the function signatures.

      Such errors as were provided with PHP 5.0 and the very appearance of type hinting in PHP remained.

      The only thing is that first the E_STRICT level E_STRICT not included in E_ALL and had to be specified explicitly, then, starting with PHP 5.4, it began to enter. After that, in PHP 7.0, errors from E_STRICT redistributed to other types of errors . The community agreed on the opinion that breach of the base class contract is a bad thing and should be noticeable, so the error was assigned the E_WARNING level. It is a pity, of course, that in 2004 they changed the initial implementation of this check, where this type of error was generated by E_COMPILE_ERROR .

      Well, who is to blame, that neither you nor your project colleagues for so many years have ever listened to what the developer’s closest friend says - the compiler of your programming language.

      What to do next:

      • to correct. It will take a long time to refactor such blunders, and this indicates the overall quality of the code. Surely not even E_NOTICE included.
      • ignore errors in the log
      • hide your head in the sand and hide mistakes

      Settings "such varnings deduce, and such not" - fortunately not.


      The specific error described in the question can be transferred to runtime. In the base class, one parameter is specified without a type with a default value, the child class may take additional optional method arguments, and this is not a violation of the class contract.

       class ExampleParent { public function dosome ($c = null) {} } class Example extends ExampleParent { /* * переименовываем существующий метод * в будущем коде используем его вместо dosome */ public function readablenamemethod(Foo $a, Bar $b, $c = 0) { } /** * на его место ставим заглушку и вручную проверяем аргументы на совместимость * @deprecated */ public function dosome ($a = null, $b = null, $c = null) { if ($a instanceof Foo and $b instanceof Bar) { // на такой набор параметров мы хотим реагировать return $this->readablenamemethod($a, $b, $c ?? 0); } throw new \InvalidArgumentException('wrong arguments given'); } } 

      If the base class requires some class argument, and the child in this place wants a scalar or a completely different object - then change the contract of the base class with type hinting to check in runtime.

      • The question is about the fix. If you remove the first part before the strip, the answer will have a chance to be accepted. The first part does not answer the question. The answer should be exhaustive: so far only one option has been considered for correcting such errors. I can think of five or seven at once. - sanmai
      • 3
        Indicate that the transition to PHP7 did not significantly change anything and hit on the hands for govnokod (maybe not you, but others who read) - the first and main part of the answer. How to live on and perform incremental refactoring is the second. How to hide your head in the sand is not a solution. You can think of more ways - well, describe them in your answer. - Shallow
      • Calls of dissatisfied customers on Sunday evening change everything. If you have no other life except in writing programs, then yes, this answer is suitable for you . What does it matter who wrote what, and who got what hands from where? - sanmai
      • @sanmai and whether from a fig clients are interested in in logs? - Pavel Mayorov
      • Clients do not care about the logs, but if you break the site with a "govnokod" or with unsuccessful refactoring, then everything changes for them immediately. - sanmai

      It is not always possible to fix the old code that you, moreover, did not write. For example, take any library from PEAR and enjoy. About the code that did not know the tests, just keep quiet. Because you can do this:

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

      This error handler marks warnings as processed, returning true for all messages beginning on the specified line. Such processed warnings will not be written to the log. The code will only work in PHP 7 and later.

      If the problem occurs only in some part of the code, which can be allocated by directory or paths to the files, then you can silence these errors only for certain files:

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