Страницы

Поиск по вопросам

суббота, 7 декабря 2019 г.

Как исправить или убрать ошибки вида “Declaration of … should be compatible with”, появившиеся с переходом на PHP 7

#php7


С переходом на PHP 7 лог ошибок по многим старым проектам оказался забит под завязку
сообщениями подобного вида:

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


Это только один пример ошибки. Вариантов таких ошибок может быть множество.

Усложняют проблему следующие моменты:


Код проекта - бесконечный. Покрытие тестами - нулевое. Отрефакторить и исправить
все ошибки займет приличное время. Никто не оплатит эту работу. Короче, исправлять
все эти ошибки за исключением совсем уж простых - не вариант.
Можно было бы вообще отключить все предупреждения, но тогда есть риск пропустить
какие-то действительно важные ошибки. Отключать предупреждения совсем - не вариант.
Не хотелось бы терять все те улучшения в части скорости и стабильности, которые есть
в PHP 7. Поддержка PHP 5 уже считай что закончилась, значит переходить на PHP 7 так
или иначе будет нужно. Возвращаться на PHP 5 - не вариант.


С учётом вышесказанного, как можно освободить логи сервера только от этих ошибок?
    


Ответы

Ответ 1



Если не засовывать голову в песок, что есть сил стараясь не трогать старый код, которые пока еще работает, то есть ряд случаев когда исправления могут быть внесены либо просто, либо выполнимо. В примерах ниже класс B является подклассом для A. Примеры ниже не обязательно устранят принципиальные нарушения принципа подстановки (LSP), но хотя бы PHP не будет о них ругаться. Особой простой случай когда у метода подкласса есть новый аргумент, у которого нет умолчального значения. Просто добавляете значение по умолчанию и живете дальше. Пример: Declaration of B::foo() should be compatible with A::foo($bar = null) Вы сделаете: - public function foo() + public function foo($bar = null) Если в методе подкласса добавляются ограничения, то следует убрать их из сигнатуры метода, перенеся в тело функции. Declaration of B::add(Baz $baz) should be compatible with A::add($n) Вы захотите использовать assert или кидать исключение, в зависимости от серьезности ошибки. - 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 */ Если в методе подкласса меньше аргументов, чем в суперклассе, и вы можете сделать аргументы опциональными в суперклассе, достаточно будет заменить неиспользуемые аргументы заглушками. Declaration of B::foo($param = '') should be compatible with A::foo($x = 40, $y = '') Вы сделаете: - public function foo($param = '') + public function foo($param = '', $_ = null) Если вы видите что какие-то аргументы в подкласс стали обязательными, уберите требование обязательности из сигнатуры метода в тело метода. - protected function foo($bar) + protected function foo($bar = null) { + if (empty($bar['key'])) { + throw new Exception("Invalid argument"); + } Иногда может быть проще изменить метод суперкласса, удалив опциональный аргументы, перейдя на использование магии 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 Понятно что убрать больше чем один аргумент таким образом может быть сложно. Все гораздо интересней если у вас есть серьезные нарушения принципа Лисковой. Если ваши аргументы не требуют типа, все просто. Достаточно сделать все аргументы опциональными, затем вручную проверяя их наличие. С такой ошибкой: 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() в этом случае потому что эта функция не учитывает аргументы со значениями по умолчанию. Их просто не будет в возвращенном значении. Если у вас есть целые семейства классов с сильно отличным интерфейсом, может иметь смысл изменить их еще больше. Переименуем функцию, нарушающую принцип подстановки. Потом добавьте метод-прокси для вызова нового, переименованного, метода в едином для всей иерархии отклоняющихся классов подклассе. function save($arg = null) // соответствует родителю { $args = func_get_args(); return $this->saveExtra(...$args); // отличный интерфейс } Таким образом вы не устраните нарушение принципа подстановки (новые классы все также нельзя вызывать будто они старые), но вы сохраните всевозможные проверки на типы данных, которые есть в сигнатурах функций.

Ответ 2



Такие ошибки как были предусмотрены с PHP 5.0 и самого появления type hinting в PHP так и остались. Единственное что сначала уровень E_STRICT не входил в E_ALL и должен был быть указан явно, затем начиная с PHP 5.4 начал входить. После этого в PHP 7.0 ошибки из E_STRICT перераспределили по другим типам ошибок. Сообщество сошлось на мнении, что нарушение контракта базового класса - вещь плохая и должна быть заметна, потому присвоили этой ошибке уровень E_WARNING. Жаль, конечно, что в 2004 году изменили изначальную реализацию этой проверки, где такой тип ошибок генерировал вовсе E_COMPILE_ERROR. Ну кто же виноват, что ни вы ни ваши коллеги по проекту за столько лет ни разу не послушали, что говорит ближайший друг разработчика - компилятор вашего языка программирования. Что делать дальше: исправлять. Рефакторить такие грубые ошибки будет долго и это говорит об общем качестве кода. Наверняка даже E_NOTICE не включали. игнорировать ошибки в логе прятать голову в песок и прятать ошибки Настроек "такие варнинги выводи, а такие нет" - к счастью нет. Конкретную описанную в вопросе ошибку можно перенести в рантайм. В базовом классе указан один параметр без типа с дефолтным значением, дочерний класс может принимать дополнительные необязательные аргументы метода и это не является нарушением контракта класса. class ExampleParent { public function dosome ($c = null) {} } class Example extends ExampleParent { /* * переименовываем существующий метод * в будущем коде используем его вместо dosome */ public function readablenamemethod(Foo $a, Bar $b, $c = 0) { } /** * на его место ставим заглушку и вручную проверяем аргументы на совместимость * @deprecated */ public function dosome ($a = null, $b = null, $c = null) { if ($a instanceof Foo and $b instanceof Bar) { // на такой набор параметров мы хотим реагировать return $this->readablenamemethod($a, $b, $c ?? 0); } throw new \InvalidArgumentException('wrong arguments given'); } } Если же базовый класс требует какой-то класс аргументом, а дочерний в этом месте хочет скаляр или совсем другой объект - то меняйте контракт базового класса с type hinting на проверку в рантайме.

Ответ 3



Не всегда возможно исправить старый код, который вы, к тому же, не писали. Например, берёте какую-нибудь библиотеку из PEAR и наслаждаетесь. Про код, не знавший тестов, просто молчу. Потому можно сделать так: if (PHP_MAJOR_VERSION >= 7) { set_error_handler(function ($errno, $errstr) { return strpos($errstr, 'Declaration of') === 0; }, E_WARNING); } Этот обработчик ошибок помечает предупреждения как обработанные, возвращая истину для всех сообщений, начинающихся с указанной строки. Такие обработанные преду­преж­де­ния не пойдут на запись в лог. Код будет работать только в PHP 7 и более поздних. Если же проблема возникает только в какой-то части кода, которую можно выделить по ката­логу или путям до файлов, то можно глушить эти ошибки только для определённых файлов: set_error_handler(function ($errno, $errstr, $file) { return strpos($file, 'path/to/legacy/library') !== false && strpos($errstr, 'Declaration of') === 0; }, E_WARNING);

Комментариев нет:

Отправить комментарий