在PHP 7

时间:2016-03-18 08:37:47

标签: 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_STRICT类型的警告which could be easily dealt with。现在他们只是陈旧的警告。由于我想知道其他警告,我不能完全关闭所有警告。

  • 我没有心智能力来重写这些遗留API,甚至没有提及使用它们的所有软件。猜猜看,没人会为此付出代价。我不是首先开发它们所以我不是那个应该责备的人。 (单元测试?十年前不是时尚。)

  • 我希望any trickeryfunc_get_args尽可能避免相似。

  • 我真的不想降级到PHP 5.

  • 我仍然想了解其他错误和警告。

有没有一种干净又好的方法来实现这一目标?

8 个答案:

答案 0 :(得分:94)

1。解决方法

因为并不总是能够纠正所有代码你没有写,尤其是遗留代码......

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

此错误处理程序返回true以获取以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);
}

2。适当的解决方案

至于实际修复其他人的遗留代码,有很多情况可以在简单和易于管理之间完成。在下面的示例中,类BA的子类。请注意,您不一定会通过以下示例删除任何LSP违规。

  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仍然会被违反,虽然没有警告,但你可以保留你在子类中的所有类型检查。

答案 1 :(得分:21)

如果必须使错误无效,您可以在默认的,立即调用的函数表达式中声明该类:

<?php

// unsilenced
class Fooable {
    public function foo($a, $b, $c) {}
}

// silenced
@(function () {
    class ExtendedFooable extends Fooable {
        public function foo($d) {}
    }
})();
但是,我强烈建议不要这样做。最好修复你的代码,而不是沉默有关它如何被破坏的警告。

如果您需要保持PHP 5兼容性,请注意上述代码仅适用于PHP 7,因为PHP 5 did not have uniform syntax for expressions。要使它与PHP 5一起使用,您需要在调用它之前将该函数分配给变量(或使其成为命名函数):

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

答案 2 :(得分:21)

对于那些想要实际纠正你的代码而不再触发警告的人:我发现只要你给它们提供默认值,你就可以向子类中的重写方法添加额外的参数。例如,虽然这会触发警告:

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

答案 3 :(得分:17)

PHP 7删除了E_STRICT错误级别。有关此信息可在PHP7 compatibility notes中找到。您可能还想阅读正在开发PHP 7时讨论过的the proposal document

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

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

您已在类中定义了一个方法,该方法会覆盖父类中具有相同名称的方法,但您的override方法具有不同的参数签名。

大多数现代编程语言实际上根本不允许这样做。 PHP曾经允许开发人员摆脱这样的东西,但是每个版本的语言都变得越来越严格,特别是现在使用PHP 7 - 他们专门使用了一个新的主要版本号,这样他们就可以证明做出重大改变可以打破向后兼容。

您遇到的问题是因为您已经忽略了警告消息。你的问题意味着这是你想要继续的解决方案,但是像&#34; strict&#34;和&#34;弃用&#34;应被视为明确警告您的代码可能在将来的版本中中断。通过忽略它们过去几年,你已经有效地置身于现在的状况。 (我知道那不是你想听到的,现在并没有真正帮助这种情况,但要明确这一点非常重要)

你真的没有找到你正在寻找的那种。 PHP语言在不断发展,如果你想坚持使用PHP 7,你的代码将需要进化也是如此。如果您真的无法修复代码,那么您将要么必须禁止所有警告,要么使用这些警告使您的日志混乱。

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

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

答案 4 :(得分:9)

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

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()没有相同的参数。此外,我无法通过在{...}之后添加= null来使其成为可选项。

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

答案 5 :(得分:6)

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

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

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

答案 6 :(得分:0)

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

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

我刚遇到这个问题,就走了这条路

答案 7 :(得分:0)

如果基类的参数少于派生类的参数,可以像这样向派生类添加额外的参数:

    $namespace = 'default';
    if (func_num_args() > 2) {
        $namespace = func_get_arg(2);
    }

通过这种方式,您添加了第三个“默认”参数,但不更改签名。 如果您有大量的代码调用它并且无法更改该代码,并且想要保持向后兼容性,我只会建议您这样做。

我在一些旧的 Joomla 代码 (v1.5) 中发现了这种情况,其中 JSession::set 添加了一个 $namespace 参数,但将 JObject 作为基类,而 JObject::set 没有这样的参数。