final class- від класу не можна успадкуватися.final public function- метод не можна перевизначити в нащадку.final const(PHP 8.1) - константу не можна перевизначити.
final class InvoiceNumberGenerator
{
public function next(): string { /* ... */ }
}
class Report extends InvoiceNumberGenerator {} // Fatal error
Чому final за замовчуванням:
- Успадкування - найсильніший зв'язок. Нащадок залежить від усіх деталей предка. Будь-яка зміна в класі може зламати чужого нащадка, про якого автор навіть не знає.
- Свобода змінювати клас. Якщо клас
final, його внутрішню будову (protected-методи, порядок викликів) можна змінювати вільно: зовні доступний лише публічний API. - Свідомі рішення. Прибрати
final, коли з'явилася реальна потреба в успадкуванні, - одна правка. Повернутиfinal, коли на клас уже успадковуються, - ламаюча зміна. - Підштовхує до композиції й інтерфейсів замість ієрархій.
Типове заперечення - «а як мокати в тестах?». Мокати слід інтерфейси, а не конкретні класи. Якщо клас треба підмінити, - він реалізує інтерфейс, і тест підставляє іншу реалізацію. Для зовнішніх final-класів бібліотек є обхідні шляхи (пакет dg/bypass-finals), але це сигнал щодо дизайну.
Де final не ставлять: базові класи, призначені для успадкування (Model, Controller, абстрактні класи), і класи, які фреймворк проксує чи розширює під час виконання (наприклад, моделі Doctrine з лінивими проксі).