Silence "Declaration ... should be compatible" warnings in PHP 7 Silence "Declaration ... should be compatible" warnings in PHP 7 php php

Silence "Declaration ... should be compatible" warnings in PHP 7


1. Workaround

Since it is not always possible to correct all the code you did not write, especially the legacy 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 beginning with Declaration of which basically tells PHP that a warning was taken care of. That's why PHP won't report this warning elsewhere.

Plus, this code will only run in PHP 7 or higher.


If you want this to happen only in regard to a specific codebase, then you could check if a file with an error belongs to that codebase or a 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. Proper solution

As for actually fixing someone else's legacy code, there is a number of cases where this could be done between easy and manageable. In examples below class B is a subclass of A. Note that you do not necessarily will remove any LSP violations by following these examples.

  1. Some cases are pretty easy. If in a subclass there's a missing default argument, just add it and move on. E.g. 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 you have additional constrains added in a subclass, remove them from the definition, while moving inside the function's body.

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

    You may want to use assertions or throw an exception depending on a severity.

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

    If you see that the constraints are being used purely for documentation purposes, move them where they belong.

    - protected function setValue(Baz $baz)+ /**+  * @param Baz $baz+  */+ protected function setValue($baz)  {+     /** @var $baz Baz */
  3. If you subclass has less arguments than a superclass, and you could make them optional in the superclass, just add placeholders in the subclass. Given 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 arguments made required in a subclass, take the matter in your hands.

    - 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 alter the superclass method to exclude an optional argument altogether, falling back to func_get_args magic. Do not forget 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

    Sure this can become very tedious if you have to remove more than one argument.

  6. Things get much more interesting if you have serious violations of substitution principle. If you do not have typed arguments, then it is easy. Just make all extra arguments optional, then check for their presence. Given 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");+     }

    Note that we couldn't use func_get_args() here because it does not account for default (non-passed) arguments. We are left with only func_num_args().

  7. If you have a whole hierarchies of classes with a diverging interface, it may be easier diverge it even further. Rename a function with conflicting definition in every class. Then add a proxy function in a single intermediary parent for these classes:

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

    This way LSP would still be violated, although without a warning, but you get to keep all type checks you have in subclasses.


For those who want to actually correct your code so it no longer triggers the warning: I found it useful to learn that you can add additional parameters to overridden methods in subclasses as long as you give them default values. So for example, while this will trigger the warning:

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

This will not:

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


If you must silence the error, you can declare the class inside a silenced, immediately-invoked function expression:

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

I would strongly recommend against this, though. It is better to fix your code than to silence warnings about how it is broken.


If you need to maintain PHP 5 compatibility, be aware that the above code only works in PHP 7, because PHP 5 did not have uniform syntax for expressions. To make it work with PHP 5, you would need to assign the function to a variable before invoking it (or make it a named function):

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