Страницы

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

понедельник, 15 октября 2018 г.

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

С переходом на 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 - не вариант.
С учётом вышесказанного, как можно освободить логи сервера только от этих ошибок?


Ответ

Если не засовывать голову в песок, что есть сил стараясь не трогать старый код, которые пока еще работает, то есть ряд случаев когда исправления могут быть внесены либо просто, либо выполнимо. В примерах ниже класс 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); // отличный интерфейс }
Таким образом вы не устраните нарушение принципа подстановки (новые классы все также нельзя вызывать будто они старые), но вы сохраните всевозможные проверки на типы данных, которые есть в сигнатурах функций.

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

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