Стандартным вопросом на PHP собеседованиях выступает вопрос про наследование, может ли класс наследовать нескольким классам или нет. На поверхности, конечно лежит ответ – «нет не может, класс всегда наследует только одному родителю». Далее обычно идет что-то «но если сильно нужно, то можно, так как есть трейты» и на этом все кончается. Как правило, и соискатель и интервьюер множественное наследование в деле не испытывали, и обоим просто добавить тут нечего.
А добавит есть что... Дело в том, что трейт - это механизм повторного использования кода. И этот механизм никак не влияет на типизацию, а без типизации наследование не наследование!
В случае использования обычного наследования мы имеем
class ParentClass {}
class ChildClass extends ParentClass {}
$testObject = new ChildClass();
var_dump(is_a(testObject, ChildClass::class)); // Выведет TRUE
var_dump(is_a(testObject, ParentClass::class)); // Выведет TRUE
В случае "наследования" с помощью трейтов, такое уже не прокатит, is_a()
, is_subclass_of()
и instanceof
- никак не отреагируют на используемый трейт. И это проблема, так как множественное наследование - это не только повторное использование кода, но и возможность использовать объекты класса потомка там, где могли использоваться объекты класса-родителя...
Но решение проблемы с типизацией есть и оно лежит на поверхности, просто обычно на собеседованиях о нем как-то забывают - это интерфейсы.
Таким образом, для реализации множественного наследования в PHP нужно одновременно использовать трейты и интерфейсы
Благодаря интерфейсам:
Решена проблема типизации
Использование констант
Благодаря трейтам:
Повторное использование методов и свойств
Использование констант (начиная с PHP 8.2)
В качестве примера - класс "русалка", наследующий "рыб" и "человека", и класс "морской дом", который при создании получает "владельца". Владельцем "морского дома" может быть только рыба.
Т.е. благодаря наследованию, владельцем морского дома может быть не только рыба, но и русалка, но никак не человек.
// Интерфейс "рыба"
interface FishInterface {}
// Интерфейс "человек"
interface HumanInterface {}
// трейт "рыба"
trait FishTrait {}
// трейт "человек"
trait HumanTrait {}
// класс "русалка"
class Mermaid implements FishInterface, HumanInterface
{
use FishTrait;
use HumanTrait;
}
// * * *
// Класс "морской дом"
// Владельца может быть "рыба", "русалка", но не "человек"
class SeaHome
{
public function __construct(public FishInterface $owner) {}
}