final
Ключевое слово final в PHP позволяет пометить метод, класс или константу как финальные, запрещая их переопределение в дочерних классах.
Ключевое слово final в PHP
final — это ключевое слово объектно-ориентированного программирования в PHP, которое блокирует часть класса, запрещая подклассам её изменять. Его можно применить к классу (ни один другой класс не сможет его наследовать), к методу (ни один подкласс не сможет его переопределить) или, начиная с PHP 8.1, к константе (ни один подкласс не сможет её переопределить).
Пометить что-либо как final — это намеренное архитектурное решение: «это поведение является намеренным, и дочерние классы не должны его изменять». При грамотном использовании это предотвращает случайные переопределения, документирует намерения прямо в коде и позволяет рассуждать о классе, не беспокоясь о том, что подкласс незаметно изменил его работу. На этой странице рассмотрены все формы final, когда к нему стоит прибегать, и типичные ошибки, на которых спотыкаются разработчики.
Уже знакомы с подклассами и переопределением методов?
finalимеет смысл только в контексте наследования PHP и классов и объектов.
Финальные классы
final-класс не может быть расширен. Любая попытка объявить подкласс приводит к фатальной ошибке на этапе компиляции:
<?php
final class PaymentGateway
{
public function charge(float $amount): string
{
return "Charged $amount";
}
}
// Fatal error: Class CryptoGateway cannot extend final class PaymentGateway
class CryptoGateway extends PaymentGateway {}Используйте финальный класс, когда его поведение завершено и наследование было бы злоупотреблением — например, для объекта-значения, сервиса с контролируемым контрактом или чувствительного к безопасности класса, где переопределяющий подкласс мог бы обойти проверку.
Финальные методы
Можно сохранить класс расширяемым, но заблокировать отдельные методы. final-метод может быть унаследован и вызван подклассами, но не может быть переопределён:
<?php
class Account
{
protected float $balance = 0;
// Subclasses may add behavior, but must not change how withdrawing works.
final public function withdraw(float $amount): void
{
if ($amount > $this->balance) {
throw new RuntimeException("Insufficient funds");
}
$this->balance -= $amount;
}
}
class SavingsAccount extends Account
{
public function addInterest(float $rate): void
{
$this->balance += $this->balance * $rate;
}
// Fatal error: Cannot override final method Account::withdraw()
// public function withdraw(float $amount): void {}
}Это наиболее распространённое использование final: вы хотите, чтобы класс в целом был открыт для расширения, но конкретный метод обеспечивает соблюдение инварианта (здесь — невозможность снять сумму, превышающую остаток), который ни один подкласс не должен иметь возможности нарушить.
private-метод фактически уже является финальным — он не виден подклассам, поэтому пометка приватного метода как final избыточна (в PHP 8 такое использование вызывает предупреждение для final private, кроме конструктора).
Финальные константы (PHP 8.1+)
До PHP 8.1 подкласс мог переопределить унаследованную константу. В PHP 8.1 поддержка final для констант была добавлена, чтобы это можно было предотвратить:
<?php
class HttpStatus
{
final public const OK = 200;
}
class ApiStatus extends HttpStatus
{
// Fatal error: ApiStatus::OK cannot override final constant HttpStatus::OK
// public const OK = 201;
}Если вам нужны константы, являющиеся частью фиксированного контракта — коды состояний, имена ролей, ключи конфигурации — final гарантирует соответствие всех подклассов. Подробнее читайте в разделе константы класса.
Финальные свойства (PHP 8.4+)
До PHP 8.4 final нельзя было применять к свойствам вообще. В PHP 8.4 добавлены финальные свойства: final-свойство по-прежнему можно читать и записывать, но подкласс не может его переобъявить.
<?php
class Car
{
final public string $model = "Toyota";
}
class Toyota extends Car
{
// Fatal error: Cannot override final property Car::$model
// public string $model = "Corolla";
}Обратите внимание, что final управляет переобъявлением, а не изменяемостью — значение по-прежнему можно переназначить во время выполнения. Чтобы сделать значение неизменяемым после создания объекта, используйте свойство readonly; эти два модификатора нередко комбинируют (final public readonly string $model). В PHP 8.3 и более ранних версиях пометка любого свойства как final является фатальной ошибкой.
final и abstract
final и abstract — противоположности и не могут быть объединены:
abstractозначает обязан быть реализован подклассом — требует наследования.finalозначает не может быть расширен или переопределён — запрещает наследование.
Объявление final abstract class является фатальной ошибкой. Смотрите абстрактные классы — другую сторону этой медали.
Когда использовать final
- Защита инварианта.
final-метод гарантирует, что критическая логика (валидация, проверки безопасности, биллинг) не будет молчаливо изменена. - Обозначение «завершённых» классов.
final-класс сообщает читателям кода, что дизайн намеренный и не предназначен для создания подклассов. - Предпочтение композиции перед наследованием. Если вы предпочитаете, чтобы пользователи вашего класса оборачивали его, а не расширяли,
finalподтолкнёт их именно к этому.
Распространённый контраргумент: final может затруднить тестирование или создание моков, поскольку финальный класс нельзя подклассировать. Современный ответ — зависеть от интерфейсов (и мокать интерфейс, а не конкретный класс) — смотрите интерфейсы. Прибегайте к final, когда ограничение является подлинной частью дизайна, а не по привычке.
Заключение
Ключевое слово final закрывает класс, метод или константу от изменения подклассами, превращая архитектурное намерение в правило, проверяемое компилятором. Используйте его для классов, которые завершены, для методов, защищающих инварианты, и для констант, образующих фиксированный контракт — но помните, что оно не применимо к свойствам (до PHP 8.4) и не может сочетаться с abstract.