insteadof
Ключевое слово "insteadof" в PHP указывает, какой трейт использовать вместо другого при конфликте методов. Разбираем синтаксис и примеры применения.
Ключевое слово PHP insteadof
insteadof — это ключевое слово PHP, используемое внутри блока use для разрешения конфликта методов между двумя и более трейтами. Когда класс использует несколько трейтов, каждый из которых определяет метод с одним и тем же именем, PHP не может определить, какой из них применить, и выбрасывает фатальную ошибку. insteadof указывает PHP, метод какого трейта следует оставить, а метод какого трейта — отбросить.
На этой странице описана проблема, которую решает это ключевое слово, его точный синтаксис, то, как оно сочетается с ключевым словом as для сохранения «проигравшего» метода, а также важные тонкости.
Проблема, которую решает insteadof
Трейт — это повторно используемый блок методов, который копируется в класс во время компиляции. Если два трейта определяют метод с одним именем, а класс использует оба, PHP не может сделать выбор. Результатом является фатальная ошибка коллизии:
<?php
trait FileLogger {
function log() { echo "Writing to a file."; }
}
trait DatabaseLogger {
function log() { echo "Writing to the database."; }
}
class Service {
use FileLogger, DatabaseLogger; // No conflict resolution
}
// PHP Fatal error: Trait method DatabaseLogger::log has not been
// applied as Service::log, because of collision with FileLogger::logКласс вообще не создаётся — это ошибка времени компиляции, а не времени выполнения. insteadof позволяет указать PHP, какой метод применить.
Синтаксис
insteadof записывается внутри блока в фигурных скобках оператора use:
use TraitWeKeep, TraitWeDrop {
TraitWeKeep::methodName insteadof TraitWeDrop;
}Читается это так: «использовать methodName из TraitWeKeep вместо TraitWeDrop». У трейта, указанного после insteadof, его версия метода удаляется из класса.
<?php
trait FileLogger {
function log() { echo "Writing to a file."; }
}
trait DatabaseLogger {
function log() { echo "Writing to the database."; }
}
class Service {
use FileLogger, DatabaseLogger {
FileLogger::log insteadof DatabaseLogger;
}
}
$service = new Service();
$service->log();
// Output: Writing to a file.Разрешение конфликта между тремя и более трейтами
Если конфликтуют три и более трейта, перечислите все трейты, которые нужно исключить, после insteadof через запятую:
<?php
trait Json { function format() { echo "JSON output"; } }
trait Xml { function format() { echo "XML output"; } }
trait Csv { function format() { echo "CSV output"; } }
class Report {
use Json, Xml, Csv {
Json::format insteadof Xml, Csv;
}
}
$report = new Report();
$report->format();
// Output: JSON outputСохранение другого метода с помощью as
insteadof удаляет метод — но зачастую вы не хотите его терять, а просто хотите использовать его под другим именем, чтобы оба метода были доступны. Именно это делает ключевое слово as: оно создаёт псевдоним для исключённого метода под новым именем, сохраняя его после разрешения конфликта.
<?php
trait FileLogger {
function log($msg) { return "[file] $msg"; }
}
trait DatabaseLogger {
function log($msg) { return "[db] $msg"; }
}
class Service {
use FileLogger, DatabaseLogger {
FileLogger::log insteadof DatabaseLogger; // FileLogger::log becomes log()
DatabaseLogger::log as logToDb; // DatabaseLogger::log survives as logToDb()
}
}
$service = new Service();
echo $service->log("started"), "\n"; // [file] started
echo $service->logToDb("started"), "\n"; // [db] startedinsteadof и as почти всегда используются вместе: insteadof выбирает победителя, as сохраняет проигравшего под псевдонимом. Ключевое слово as также может изменить видимость метода (например, FileLogger::log as protected;).
Подводные камни
insteadofразрешает только коллизии имён. Если у двух методов разные имена, конфликта нет иinsteadofне нужен.- Необходимо ссылаться на метод, который реально существует в указанном трейте, используя синтаксис
Трейт::метод. Опечатка приводит к фатальной ошибке. - Поведение не объединяется. Исключённый метод просто не применяется;
insteadofникогда не вызывает оба метода одновременно. - Метод дочернего класса всё равно имеет приоритет над трейтом. Порядок разрешения методов следующий: методы, определённые непосредственно в классе, переопределяют методы трейтов, а методы трейтов переопределяют унаследованные (от родительского класса) методы.
Когда использовать
Используйте insteadof каждый раз, когда вы создаёте класс из нескольких трейтов и два из них предоставляют метод с одним именем — это часто встречается при подключении сторонних трейтов, которые вы не контролируете. В сочетании с as это позволяет компоновать поведение из независимых трейтов без переименования их исходного кода, сохраняя классы модульными и свободными от конфликтов.