静音 PHP 7 中的“声明......应该兼容”警告

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36079651/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-26 00:42:06  来源:igfitidea点击:

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

phpphp-7php-7.2php-7.1php-7.0

提问by sanmai

After upgrade to PHP 7 the logs almost choked on this kind of errors:

升级到 PHP 7 后,日志几乎被这种错误窒息:

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

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

How do I silence these and only these errors in PHP 7?

如何在 PHP 7 中使这些错误以及仅这些错误静音?

  • Before PHP 7 they were E_STRICTtype of warnings which could be easily dealt with. Now they're just plain old warnings. Since I dowant to know about other warnings, I can't just turn off all warnings altogether.

  • I don't have a mental capacity to rewrite these legacy APIs not even mentioning all the software that uses them. Guess what, nobody's going to pay for that too. Neither I develop them in the first place so I'm not the one for blame. (Unit tests? Not in the fashion ten years ago.)

  • I would like to avoid any trickerywith func_get_argsand similar as much as possible.

  • Not really I want to downgrade to PHP 5.

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

  • 在 PHP 7 之前,它们是可以轻松处理E_STRICT的警告类型。现在它们只是普通的旧警告。因为我确实想知道其他警告,所以我不能完全关闭所有警告。

  • 我没有重写这些遗留 API 的心理能力,甚至没有提到使用它们的所有软件。你猜怎么着,没有人会为此付出代价。我一开始都没有开发它们,所以我不应该受到责备。(单元测试?十年前不流行。)

  • 我想尽可能避免与和相似的任何诡计func_get_args

  • 我并不是真的想降级到 PHP 5。

  • 我仍然想知道其他错误和警告。

Is there a clean and nice way to accomplish this?

有没有一种干净漂亮的方法来实现这一点?

回答by sanmai

1. Workaround

1. 解决方法

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 truefor warnings beginning with Declaration ofwhich basically tells PHP that a warning was taken care of. That's why PHP won't report this warning elsewhere.

这个错误处理程序返回true以警告开头的警告,Declaration of它基本上告诉 PHP 一个警告已被处理。这就是 PHP 不会在其他地方报告此警告的原因。

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

另外,此代码只能在 PHP 7 或更高版本中运行。



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

2. 适当的解决方案

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 Bis a subclass of A. Note that you do not necessarily will remove any LSP violations by following these examples.

至于实际修复其他人的遗留代码,在许多情况下,这既简单又易于管理。在下面的示例中,类BA. 请注意,您不一定会按照这些示例删除任何 LSP 违规。

  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_argsmagic. 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.

  1. 有些情况很容易。如果在子类中缺少默认参数,只需添加它并继续。例如在这种情况下:

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

    你会这样做:

    - public function foo()
    + public function foo($bar = null)
    
  2. 如果您在子类中添加了其他约束,请将它们从定义中删除,同时在函数体内移动。

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

    您可能希望根据严重性使用断言或抛出异常。

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

    如果您看到这些约束纯粹用于文档目的,请将它们移到它们所属的地方。

    - protected function setValue(Baz $baz)
    + /**
    +  * @param Baz $baz
    +  */
    + protected function setValue($baz)
      {
    +     /** @var $baz Baz */
    
  3. 如果子类的参数比超类少,并且可以在超类中将它们设为可选,只需在子类中添加占位符即可。给定错误字符串:

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

    你会这样做:

    - public function foo($param = '')
    + public function foo($param = '', $_ = null)
    
  4. 如果您看到在子类中需要一些参数,请自行处理。

    - protected function foo($bar)
    + protected function foo($bar = null)
      {
    +     if (empty($bar['key'])) {
    +         throw new Exception("Invalid argument");
    +     }
    
  5. 有时可能更容易改变超类方法以完全排除可选参数,回到func_get_args魔术。不要忘记记录缺少的参数。

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

    当然,如果您必须删除多个参数,这会变得非常乏味。

  6. 如果你严重违反了替代原则,事情就会变得更有趣。如果您没有输入参数,那么这很容易。只需将所有额外参数设为可选,然后检查它们是否存在。给定错误:

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

    你会这样做:

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

    请注意,我们不能func_get_args()在这里使用,因为它不考虑默认(非传递)参数。我们只剩下func_num_args().

  7. 如果你有一个完整的层次结构和一个不同的接口,它可能更容易进一步分化。重命名每个类中定义冲突的函数。然后在单个中间父级中为这些类添加代理函数:

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

    这样 LSP 仍然会被违反,虽然没有警告,但您可以保留子类中的所有类型检查。

回答by Matt Browne

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() {}
}

回答by Andrea

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

如果您必须消除错误,则可以在已消除的、立即调用的函数表达式中声明该类:

<?php

// unsilenced
class 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):

如果您需要保持 PHP 5 兼容性,请注意上述代码仅适用于 PHP 7,因为PHP 5 没有统一的表达式语法。要使其与 PHP 5 一起使用,您需要在调用该函数之前将其分配给一个变量(或使其成为命名函数):

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

回答by Simba

PHP 7 removes the E_STRICTerror level. Info about this can be found in the PHP7 compatibility notes. You might also want to read the proposal documentwhere it was discussed while PHP 7 was being developed.

PHP 7 删除了E_STRICT错误级别。有关这方面的信息可以在PHP7 兼容性说明中找到。您可能还想阅读在 PHP 7 开发过程中讨论过的提案文档

The simple fact is this: The E_STRICTnotices were introduced a number of versions ago, in an attempt to notify developers that they were using bad practice, but initially without trying to force any changes. However recent versions, and PHP 7 in particular, have become more strict about these things.

一个简单的事实是:这些E_STRICT通知是在多个版本之前引入的,目的是通知开发人员他们使用了不好的做法,但最初并没有试图强制进行任何更改。然而,最近的版本,尤其是 PHP 7,对这些事情变得更加严格。

The error you're experiencing is a classic case:

您遇到的错误是一个经典案例:

You have defined a method in your class that overrides a method of 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 at all. PHP used to allow developers to get away with stuff like this, but the language is becoming more strict with every version, especially now with PHP 7 -- they went with a new major version number specifically so that they could justify making significant changes that break backward compatibility.

大多数现代编程语言实际上根本不允许这样做。PHP 曾经允许开发人员摆脱这样的事情,但该语言对每个版本都变得更加严格,尤其是现在 PHP 7——他们专门使用了一个新的主要版本号,以便他们可以证明进行重大更改是合理的向后兼容性。

The problem you have is because you've already been ignoring the warning messages. Your question implies that this is the solution you want to continue with, but messages like "strict" and "deprecated" should be treated as an explicit warning that your code is likely to break in future versions. By ignoring them for the past number of years, you have effectively placed yourself in the situation you have now. (I know that's not what you want to hear, and doesn't really help the situation now, but it's important to make it clear)

您遇到的问题是因为您已经忽略了警告消息。您的问题暗示这是您想要继续使用的解决方案,但应将“严格”和“已弃用”之类的消息视为您的代码在未来版本中可能会中断的明确警告。通过在过去几年中忽略它们,您实际上已经将自己置于现在的境地。(我知道这不是您想听到的,并且对现在的情况没有真正帮助,但重要的是要说清楚)

There really isn't a work around of the kind you're looking for.The PHP language is evolving, and if you want to stick with PHP 7 your code will need to evolve too. If you really can't fix the code, then you will either have to suppress all warnings or else live with these warnings cluttering up your logs.

确实没有您正在寻找的那种解决方法。PHP 语言在不断发展,如果您想坚持使用 PHP 7,您的代码也需要不断发展。如果您真的无法修复代码,那么您要么必须取消所有警告,要么忍受这些警告使您的日志变得混乱。

The other 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 quite subtle. If your code is in a state where it has errors like the one you're reporting, it means that it's probably been around for quite a while, and likely has other issues that will cause you problems in PHP 7. For code like this, I would suggest doing a more thorough audit of the code before committing to PHP 7. If you're not prepared to do that, or not prepared to fix the bugs that are found (and the implication from your question is that you are not), then I'd suggest that PHP 7 is probably an upgrade too far for you.

如果您打算坚持使用 PHP 7,您需要知道的另一件事是,该版本还有许多其他兼容性问题,包括一些非常微妙的问题。如果您的代码处于像您报告的那样有错误的状态,这意味着它可能已经存在了很长时间,并且可能存在其他问题,这些问题会导致您在 PHP 7 中出现问题。对于这样的代码,我建议在提交到 PHP 7 之前对代码进行更彻底的审核。如果您不准备这样做,或者不准备修复发现的错误(并且您的问题暗示您没有准备好) ,那么我建议 PHP 7 可能对你来说升级太远了。

You do have the option of reverting to PHP 5.6. I know you said you don't want to do that, but as a short-to-medium term solution it will make things easier for you. Frankly, I think it might be your best option.

您可以选择恢复到 PHP 5.6。我知道你说过你不想这样做,但作为一个中短期解决方案,它会让你的事情变得更容易。坦率地说,我认为这可能是你最好的选择。

回答by Zaziffic

I agree: the example in the first post is bad practice. Now what if you have that 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, nevertheless this will raise a warning in my logs because the displayProperties()do not have the same parameters. Moreover I can't make them optional by adding a = nullafter them...

我相信这是一个合法的代码结构,但是这会在我的日志中引发警告,因为displayProperties()它们没有相同的参数。此外,我不能通过= null在它们后面添加一个来使它们成为可选的...

Am I right thinking this warning is wrong in this specific example please?

我认为这个警告在这个具体例子中是错误的吗?

回答by AeonTrek

I had this issue as well. I have a class that overrides a function of the parent class, but the override has different num of parameters. I can think of a few easy work arounds - but do require minor code change.

我也有这个问题。我有一个覆盖父类函数的类,但覆盖的参数数量不同。我可以想到一些简单的解决方法 - 但确实需要少量的代码更改。

  1. change the name of the function in the subclass (so it no longer overrides parent function) -or-
  2. change the parameters of the parent function, but make the extra parameters optional (e.g., function func($var1, $var2=null) - this may be easiest and require less code changes. But it may not be worth to change this in the parent if its used so many other places. So I went with #1 in my case.

  3. If possible, instead of passing the extra params in the subclass function, use global to pull in the extra params. This is not ideal coding; but a possible band-aid anyway.

  1. 更改子类中函数的名称(因此它不再覆盖父函数)-或-
  2. 更改父函数的参数,但将额外的参数设为可选(例如,function func($var1, $var2=null) - 这可能是最简单的并且需要更少的代码更改。但在parent 如果它在很多其他地方使用过。所以在我的情况下我选择了 #1。

  3. 如果可能,不要在子类函数中传递额外的参数,而是使用 global 拉入额外的参数。这不是理想的编码;但无论如何可能是创可贴。

回答by Brian Dillingham

You can remove the parent class method definition altogether and intercept it with a magic method.

您可以完全删除父类方法定义并使用魔术方法拦截它。

public function __call($name, $args)
{
    if($name == 'do') {
        // do things with the unknown # of args
    } else {
        throw new \Exception("Unknown method $name", 500);
    }
}

I just ran into this problem and went this route

我刚遇到这个问题并走这条路