Что такое принцип подстановки Лисков из SOLID?
Что проверяют
Держите ли вы в голове контракт, который язык не проверяет. PHP многое контролирует сам: наследник не может сузить тип параметра и не может расширить тип возвращаемого значения, иначе класс просто не загрузится. Но ровно этим проверка и заканчивается, а LSP про смысл, и интервьюер смотрит, понимаете ли вы, что остаётся на совести разработчика.
Как отвечать
Правило: код, написанный против базового типа или интерфейса, обязан продолжать работать, если подставить наследника. Проверяемые следствия те же, что в теории: предусловия не усиливаются, постусловия не ослабляются, инварианты сохраняются, новых исключений наследник не бросает.
Типичное нарушение, которое PHP пропустит:
class Importer
{
public function import(array $rows): int
{
return count($rows);
}
}
class StrictImporter extends Importer
{
public function import(array $rows): int
{
if ($rows === []) {
throw new InvalidArgumentException('empty list');
}
return parent::import($rows);
}
}Сигнатуры совпадают, наследник усилил предусловие, и вызывающий код, спокойно передававший пустой массив, падает после подмены реализации. То же случается, когда наследник возвращает пустую строку там, где база обещала непустую.
Где спотыкаются
Считают, что если PHP пропустил сигнатуру, принцип соблюдён. Ковариантность возвращаемых типов и контравариантность параметров закрывают только формальную часть, а поведение язык не видит.
Мешают LSP с посторонними темами вроде анемичных моделей. Связи нет: анемичная модель может идеально соблюдать подстановку, а богатая доменная модель нарушать её на первом же наследнике.
Забывают, что контракт ломается и при реализации интерфейса. Класс, который в методе интерфейса кеширует результат и при повторном вызове отдаёт устаревшие данные, формально реализует все методы и обманывает ожидание вызывающего.