web-dev-qa-db-ja.com

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タイプの警告でした これは簡単に処理できます 。今、それらは単なる古い警告です。私doは他の警告について知りたいので、すべての警告をすべてオフにすることはできません。

  • 私はこれらのレガシーAPIを書き換える精神的な能力を持っていません。それらを使用するすべてのソフトウェアについても言及していません。誰もそれに対してお金を払うつもりはありません。そもそもそれらを開発していないので、私は責任があるわけではない(単体テストですか?10年前のやり方ではありません。)

  • any trickery with func_get_argsを避け、可能な限り同様にしたいと思います。

  • PHP 5にダウングレードしたいわけではありません。

  • 私はまだ他のエラーや警告について知りたいです。

これを達成するためのきれいで素敵な方法はありますか?

62
sanmai

1.回避策

すべてのコードを修正することは常に可能であるとは限らないため、-あなたが書いていない、特にレガシーコード...

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

このエラーハンドラは、PHPに基本的に警告が処理されたことを伝えるDeclaration ofで始まる警告に対してtrueを返します。 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 magicにフォールバックする方が簡単な場合があります。欠落している引数を文書化することを忘れないでください。

      /**
    +  * @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に違反することになりますが、サブクラスにあるすべての型チェックを保持することができます。

97
sanmai

エラーを黙らせる場合は、黙ってすぐに呼び出される関数式内でクラスを宣言できます。

<?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には式 。 PHP 5で機能させるには、変数を呼び出す前に関数を変数に割り当てる必要があります(または名前付き関数にします)。

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

実際にコードを修正して警告が出ないようにする場合:デフォルト値を指定する限り、サブクラス内のオーバーライドされたメソッドに追加のパラメーターを追加できることを知ると便利です。たとえば、これにより警告がトリガーされますが:

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

PHP 7はE_STRICTエラーレベルを削除します。これについての情報は PHP7互換性ノート にあります。また、 [提案文書 PHP 7の開発中に議論された場所を読むこともできます。

簡単な事実は次のとおりです。E_STRICT通知は、開発者に悪い慣習を使用していることを開発者に通知するために、最初は変更を強制することなく、多くのバージョンで導入されました。ただし、最近のバージョン、特にPHP 7では、これらのことについてより厳格になっています。

あなたが経験しているエラーは典型的なケースです:

親クラスの同じ名前のメソッドをオーバーライドするメソッドをクラスに定義しましたが、オーバーライドメソッドには異なる引数シグネチャがあります。

ほとんどの最新のプログラミング言語では、実際にはこれがまったく許可されません。 PHPは、開発者がこのようなものを回避できるようにするために使用されていましたが、特にPHP 7で、すべてのバージョンで言語がより厳しくなりました。下位互換性を損なう重要な変更を行うことを正当化できるように、具体的に数を指定します。

問題は、警告メッセージをすでに無視しているためです。あなたの質問は、これがあなたが継続したい解決策であることを暗示していますが、「厳格」や「非推奨」のようなメッセージは、あなたのコードが将来のバージョンで壊れる可能性があるという明示的な警告として扱われるべきです。過去数年間それらを無視することにより、あなたは現在の状況に効果的に自分自身を置きました。 (それはあなたが聞きたいことではなく、現在状況を本当に助けていないことを知っていますが、それを明確にすることが重要です)

本当に探している種類の回避策はありません。PHP言語は進化しています。あなたのコードも進化する必要があるPHP 7に固執したいです。コードを実際に修正できない場合は、すべての警告を抑制するか、これらの警告がログに散らばってしまうようにする必要があります。

PHP 7に固執する場合に知っておく必要があるもう1つのことは、このバージョンには、非常に微妙なものも含めて、他にも多くの互換性の問題があることです。コードが報告しているようなエラーがある状態にある場合、それはおそらくかなり前から存在しており、おそらくPHP 7で問題を引き起こす他の問題があることを意味しますこのようなコードの場合、PHP 7.にコミットする前に、コードのより徹底的な監査を行うことをお勧めします。それを行う準備ができていない場合、または見つかったバグを修正する準備ができていない場合(そして、あなたの質問からの含意はあなたではないということです)、PHP 7はおそらくあなたにとってアップグレードが多すぎることをお勧めします。

PHP 5.6に戻すオプションがあります。あなたはそれをしたくないと言ったのは知っていますが、短期から中期のソリューションとして、それはあなたのために物事を簡単にします。率直に言って、私はそれがあなたの最良の選択肢かもしれないと思います。

17
Simba

私は同意します:最初の投稿の例は悪い習慣です。その例がある場合はどうでしょうか:

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を追加してオプションにすることはできません...

この特定の例でこの警告が間違っていると思いますか?

9
Zaziffic

私もこの問題を抱えていました。親クラスの関数をオーバーライドするクラスがありますが、オーバーライドのパラメーターの数は異なります。いくつかの簡単な回避策を考えることができますが、マイナーなコード変更が必要です。

  1. サブクラスの関数の名前を変更します(したがって、親関数をオーバーライドしなくなります)-または-
  2. 親関数のパラメーターを変更しますが、追加のパラメーターをオプションにします(たとえば、関数func($ var1、$ var2 = null)-これは最も簡単で、コードの変更が少なくて済む場合があります。親が他の多くの場所を使用している場合、私の場合は#1を使用しました。

  3. 可能であれば、サブクラス関数で追加のパラメーターを渡す代わりに、グローバルを使用して追加のパラメーターを取得します。これは理想的なコーディングではありません。とにかく可能なバンドエイド。

6
AeonTrek