Почему в php нельзя уточнить класс аргумента при имплементации интерфейса?
1. Определяем интерфейс
interface TestInterface { public function testMethod(ArgInterface $arg); } |
2. Определяем ArgInterface
interface ArgInterface { } |
3. Имплементируем ArgInterface:
class Arg implements ArgInterface { public $id; } |
4. Имплементируем TestInterface, но $arg уточняем тип с ArgInterface на Arg, который реализует ArgInterface
class Test implements TestInterface { public function testMethod(Arg $arg) { echo $arg->id; } } |
Запускаем:
<?php interface TestInterface { public function testMethod(ArgInterface $arg); } interface ArgInterface { } class Arg implements ArgInterface { public $id; } class Test implements TestInterface { public function testMethod(Arg $arg) { echo $arg->id; } } $arg = new Arg(); $arg->id = 1; $test = new Test(); $test->testMethod($arg); |
Ничего работать не будет:
Fatal error: Declaration of Test::testMethod(Arg $arg) must be compatible with TestInterface::testMethod(ArgInterface $arg) in /home/user/scripts/code.php on line 18 |
Почему? Мы же уточняем тайпхинтинг наследником (имплементирующим классом), то есть принцип Лисков должен пропускать. Наследник же сохраняет всю функциональность родителя.
Есть ли этому поведению рациональное объяснение или это особенность языка?
Дополнительно:
Потому что это бы нарушало принцип подстановки Барбары Лисков.
Интерфейс предполагает, что ты можешь принимать любой объект, который реализует интерфейс ArgInterface.
=> Если ты сузишь тип, то ты уже по факту не сможешь поддерживать контракт.
По тому при реализации интерфейса можно только расширять тип принимаемых аргументов, но не сужать.
LSP нарушается - интерфейс требует чтобы реализация в качестве аргумента могла принимать любую реализацию ArgInterface, а вы пытаетесь ограничится только одной.
Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.
Пока нет других ответов. Будьте первым, кто поможет автору.
Ответить на вопрос
В PHP действительно нельзя уточнить класс аргумента при имплементации интерфейса из-за особенностей языка. Однако это не значит, что невозможно обеспечить типизацию аргументов при использовании интерфейсов. Давайте разберемся, почему это происходит и как можно обойти это ограничение.
В PHP интерфейсы определяются как контракты, которые класс должен реализовать. При этом интерфейсы не могут содержать информацию о типах аргументов методов, так как это противоречило бы динамической природе языка. То есть PHP является слабо типизированным языком, и в момент выполнения нельзя гарантировать тип аргумента.
Однако, начиная с версии PHP 7, была добавлена поддержка строгой типизации аргументов и возвращаемых значений. Это позволяет указывать типы для аргументов и возвращаемых значений функций и методов. Но, к сожалению, данная функциональность не распространяется на интерфейсы.
Чтобы обеспечить типизацию аргументов при использовании интерфейсов, можно использовать комментарии PHPDoc. Например, вот как можно указать тип аргумента в PHPDoc комментариях:
Хотя это не является строгой типизацией и не обеспечивает проверку типов во время выполнения, такой подход позволяет документировать ожидаемые типы аргументов и делает код более понятным для других разработчиков.
Еще одним способом обойти ограничения PHP в этом случае является использование абстрактных классов вместо интерфейсов. В абстрактных классах можно указывать типы аргументов для методов, что облегчает работу с типизацией. Однако, это может быть не всегда удобным, так как классы могут наследоваться только от одного абстрактного класса.
Таким образом, в PHP нельзя уточнить класс аргумента при имплементации интерфейса из-за особенностей языка, но существуют способы обеспечить типизацию аргументов при использовании интерфейсов, такие как использование PHPDoc комментариев или абстрактных классов.