沉默“声明…应该兼容”PHP 7中的警告

升级到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 7中使这些错误消失,只有这些错误?

  • 在PHP 7之前,它们是易于处理的 E_STRICTtypes的警告。 现在,他们只是普通的老警告。 由于我想知道其他警告,所以我不能完全closures所有的警告。

  • 我没有脑力来重写这些传统的API,甚至没有提及所有使用它们的软件。 猜猜看,没有人会为此付出代价。 我也没有开发他们,所以我不是一个怪。 (unit testing吗?十年前不是这样。)

  • 我想尽可能避免与func_get_args和类似的任何欺骗 。

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

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

有没有一个干净的,不错的方法来完成这个?

由于并不总是可以纠正你所写的所有代码,特别是传统的代码。

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

这个error handling程序返回true ,以Declaration of开头Declaration of它基本上告诉PHP警告已经被处理。 这就是为什么PHP不会在其他地方报告此警告。

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


如果你只想在特定的代码库中发生这种情况,那么你可以检查一个有错误的文件是否属于这个代码库或一个感兴趣的库:

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

至于实际修复别人的遗留代码,有很多情况下可以在简单和易于pipe理的情况下完成。 在下面的例子中, B类是A一个子类。 请注意,通过遵循这些示例,您不一定会删除任何LSP冲突。

  1. 有些情况非常简单。 如果在子类中缺less缺省参数,只需添加它并继续。 例如在这种情况下:

     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) 

    您可能要使用断言或根据严重性抛出exception。

     - 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. 如果你的子类的参数比超类less,你可以在超类中使它们成为可选的,只需要在子类中添加占位符即可。 鉴于错误string:

     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魔术。 不要忘记logging缺less的论点。

      /** + * @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. 如果你严重违反替代原则,事情会变得更有趣。 如果你没有input参数,那很简单。 只要使所有额外的参数可选,然后检查他们的存在。 鉴于错误:

     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,虽然没有警告,但你可以保留所有types的检查你有子类。

对于那些想要真正纠正你的代码的人,不要再触发警告了:只要你给他们默认值,我发现你可以在子类中添加额外的参数给重写的方法。 举个例子,虽然这会引发警告:

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

这不会:

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

PHP 7删除了E_STRICT错误级别。 有关这方面的信息可以在PHP7兼容性说明中find 。 在开发PHP 7时,您可能还需要阅读提案文档 。

简单的事实是: E_STRICT通知在很多版本中被引入,试图通知开发者他们正在使用不好的做法,但最初并没有试图强制任何改变。 但是最近的版本,特别是PHP 7,对这些事情变得更加严格。

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

你已经在你的类中定义了一个覆盖父类中同名方法的方法,但是你的覆盖方法有一个不同的参数签名。

大多数现代编程语言实际上都不允许这样做。 PHP曾经允许开发人员摆脱这样的困境,但是对于每个版本,语言都变得越来越严格,特别是现在使用PHP 7的时候 – 他们特别使用了一个新的主要版本号,这样他们就可以certificate重要的变化向后兼容。

你的问题是因为你已经忽略了警告信息。 你的问题意味着这是你想要继续的解决scheme,但是像“strict”和“deprecated”这样的消息应该被视为明确的警告,说明你的代码可能会在未来的版本中被破坏。 在过去的几年中忽视他们,你已经有效地把自己置于你现在的状况。 (我知道这不是你想听到的,现在也没有真正帮助这个情况,但重要的是要清楚)

真的没有你想要的那种工作。 PHP语言正在发展,如果你想坚持PHP 7,你的代码也需要发展。 如果你真的不能修复代码,那么你将不得不压制所有的警告,否则就会忍受这些警告,使你的日志混乱。

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

您可以select恢复到PHP 5.6。 我知道你说过你不想这么做,但作为一个中短期的解决scheme,它会让你更容易。 坦率地说,我认为这可能是你最好的select。

如果你必须保持沉默,你可以在一个静音的立即调用的函数expression式中声明这个类:

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

不过,我强烈build议不要这样做。 修正你的代码比沉默关于如何破坏的警告更好。


如果您需要维护PHP 5兼容性,请注意,上述代码仅适用于PHP 7,因为PHP 5没有统一的expression式语法 。 为了使它与PHP 5一起工作,你需要在调用它之前把这个函数赋值给一个variables(或者使它成为一个命名函数):

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

我同意:第一篇文章中的例子是不好的做法。 现在如果你有这样的例子:

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

我相信这是一个合法的代码结构,但是这会在我的日志中引发警告,因为“displayProperties”没有相同的参数。 此外,我不能让他们后面添加一个“=空”他们可选…

我是否正确地认为这个警告在这个具体的例子中是错误的?

我也有这个问题。 我有一个类重写父类的函数,但重写有不同数量的参数。 我可以考虑一些简单的工作 – 但是需要稍作修改。

  1. 更改子类中函数的名称(所以不再覆盖父函数) – 或 –
  2. 更改父函数的参数,但是使额外的参数可选(例如,函数func($ var1,$ var2 = null) – 这可能是最简单的,并且需要更less的代码更改,但是可能不值得在如果它的父母使用了很多其他的地方,那么我就和我一样。

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