The PHP final keyword prevents selected parts of an object-oriented design from being changed through inheritance.
It is useful when a class or method has reached the point where extending it would cause more trouble than flexibility. Think of it as a polite sign saying, “This code is complete. Please customise something else.”
You can use final to:
- Prevent a class from being extended.
- Prevent an inherited method from being overridden.
- Prevent a class constant from being redefined.
- Prevent a property from being overridden in PHP 8.4 and later.
The keyword affects inheritance only. A final class can still be instantiated, and its accessible methods, properties, and constants can still be used normally.
Quick answer
Declare a class as final when no child class should extend it. Declare an individual method, constant, or supported property as final when subclasses may extend the class but must not replace that particular member.
<?php
declare(strict_types=1);
final class InvoiceNumber
{
public function generate(int $invoiceId): string
{
return 'INV-' . str_pad((string) $invoiceId, 6, '0', STR_PAD_LEFT);
}
}
The InvoiceNumber class can be instantiated, but another class cannot extend it.
<?php
$generator = new InvoiceNumber();
echo $generator->generate(42); // INV-000042
Trying to inherit from it causes a fatal error:
<?php
class CustomInvoiceNumber extends InvoiceNumber
{
}
Fatal error: Class CustomInvoiceNumber cannot extend final class InvoiceNumber
This restriction is part of PHP’s normal inheritance rules. The official PHP final keyword documentation defines the exact restrictions for classes and class members.
PHP final keyword syntax
The position of final depends on what you want to protect.
<?php
final class CompletedService
{
}
class ParentService
{
final public function process(): void
{
}
final public const VERSION = '1.0';
}
A final class blocks inheritance completely. A final member is more selective: subclasses may still extend the parent class, but they cannot override that member.
Using final methods
A final method lets you keep a class open for inheritance while protecting one part of its behaviour.
This is useful when subclasses may add features, but a specific workflow must remain unchanged. Validation, permission checks, transaction handling, and identifier generation are common examples.
<?php
declare(strict_types=1);
class PaymentProcessor
{
final public function process(float $amount): string
{
if ($amount <= 0) {
throw new InvalidArgumentException(
'The payment amount must be greater than zero.'
);
}
return $this->charge($amount);
}
protected function charge(float $amount): string
{
return sprintf('Charged %.2f', $amount);
}
}
class CardPaymentProcessor extends PaymentProcessor
{
protected function charge(float $amount): string
{
return sprintf('Charged %.2f by card', $amount);
}
}
The child class can customise charge(), but it cannot replace the validation performed by process().
<?php
$processor = new CardPaymentProcessor();
echo $processor->process(125.50);
This outputs:
Charged 125.50 by card
An override of the final method is not allowed:
<?php
class UnsafePaymentProcessor extends PaymentProcessor
{
public function process(float $amount): string
{
return 'Payment accepted without validation';
}
}
PHP stops the class declaration with an error similar to this:
Fatal error: Cannot override final method PaymentProcessor::process()
Use a final method when the parent class owns the complete workflow but intentionally exposes smaller extension points. This approach is often safer than allowing a subclass to replace a public method containing important checks.
Using final class constants
PHP 8.1 added support for final class constants. A final constant can be inherited and read by a child class, but it cannot be redefined there.
<?php
declare(strict_types=1);
class ApiClient
{
final public const VERSION = 'v1';
public function getEndpoint(string $resource): string
{
return sprintf(
'/api/%s/%s',
self::VERSION,
ltrim($resource, '/')
);
}
}
class CustomerApiClient extends ApiClient
{
}
The child class inherits VERSION normally:
<?php
$client = new CustomerApiClient();
echo $client->getEndpoint('customers');
echo CustomerApiClient::VERSION;
However, redefining the final constant causes a fatal error:
<?php
class LegacyApiClient extends ApiClient
{
public const VERSION = 'v0';
}
Fatal error: LegacyApiClient::VERSION cannot override final constant ApiClient::VERSION
A final constant is helpful when its value forms part of a fixed class contract. Protocol versions, internal type identifiers, and reserved configuration keys are reasonable candidates.
Do not use it merely because a value is unlikely to change. The purpose of final is to prevent overriding through inheritance, not to make a value immutable everywhere.
Using final properties in PHP 8.4 and later
PHP 8.4 extended the final keyword to properties. A final property may be accessed or modified according to its visibility, but a child class cannot redeclare it.
<?php
declare(strict_types=1);
class UserRecord
{
final protected string $identifier;
public function __construct(string $identifier)
{
$this->identifier = $identifier;
}
public function getIdentifier(): string
{
return $this->identifier;
}
}
class AdminRecord extends UserRecord
{
}
The inherited property works as usual. The following child class does not:
<?php
class InvalidAdminRecord extends UserRecord
{
public string $identifier;
}
Fatal error: Cannot override final property UserRecord::$identifier
A final property is not the same as a readonly property. A readonly property restricts reassignment after initialization. A final property prevents a child class from redeclaring the property. These rules solve different problems.
Final properties require PHP 8.4 or later. Code intended for PHP 8.2 or PHP 8.3 must not use this syntax.
Final class vs final method
The choice depends on how much inheritance you want to prevent.
| Declaration | What it prevents | When to use it |
|---|---|---|
final class |
Any class from extending it | The entire implementation should remain closed to inheritance |
final method |
Child classes from overriding one method | Inheritance is allowed, but a specific workflow must remain unchanged |
final const |
Child classes from redefining one constant | The constant is part of a fixed class contract |
final property |
Child classes from redeclaring one property | The property definition must remain consistent across the hierarchy |
Do not mark a whole class as final when only one method needs protection. That removes every possible inheritance point, including ones that may be useful later.
Similarly, marking every method as final usually suggests that the class itself should have been final. A class that technically supports inheritance but offers no meaningful extension points is confusing to maintain.
Final class vs abstract class
A final class and an abstract class serve opposite purposes.
An abstract class is designed to be extended. It may define shared behaviour while requiring child classes to complete selected methods. A final class explicitly prevents extension.
<?php
declare(strict_types=1);
abstract class ReportExporter
{
abstract public function export(array $rows): string;
final protected function validateRows(array $rows): void
{
if ($rows === []) {
throw new InvalidArgumentException('Report data cannot be empty.');
}
}
}
final class CsvReportExporter extends ReportExporter
{
public function export(array $rows): string
{
$this->validateRows($rows);
$lines = [];
foreach ($rows as $row) {
$lines[] = implode(',', $row);
}
return implode(PHP_EOL, $lines);
}
}
In this example, ReportExporter is abstract because it defines a contract for child classes. Its validateRows() method is final because every exporter must use the same validation rule.
CsvReportExporter is final because the implementation is complete and is not intended to become another base class.
This combination is valid and practical. However, a class itself cannot be both abstract and final. An abstract class requires inheritance, while a final class forbids it.
For broader object-oriented design choices, see this guide to PHP OOP concepts.
Final classes can still implement interfaces
A final class cannot extend another class as a child, but it can extend a parent class and implement one or more interfaces when it is declared.
<?php
declare(strict_types=1);
interface Logger
{
public function log(string $message): void;
}
abstract class BaseLogger
{
protected function format(string $message): string
{
return sprintf(
'[%s] %s',
date('Y-m-d H:i:s'),
$message
);
}
}
final class FileLogger extends BaseLogger implements Logger
{
public function __construct(private readonly string $filePath)
{
}
public function log(string $message): void
{
file_put_contents(
$this->filePath,
$this->format($message) . PHP_EOL,
FILE_APPEND | LOCK_EX
);
}
}
The FileLogger class fulfils the Logger contract and reuses behaviour from BaseLogger. The final keyword only prevents another class from extending FileLogger.
This is one reason interfaces work well with final classes. Calling code depends on the interface, while the concrete implementation remains closed to inheritance. The PHPpot guide to PHP interfaces explains interface implementation in more detail.
Be careful with final private methods
A private method is not inherited as an overridable method. It belongs only to the class that declares it.
For that reason, declaring a private method as final does not normally make sense. Since PHP 8.0, PHP does not allow private methods to be declared final, except for a private constructor.
<?php
class Formatter
{
final private function clean(string $value): string
{
return trim($value);
}
}
This declaration produces a warning because the final modifier has no useful effect on the private method.
A private final constructor is the exception:
<?php
class Environment
{
final private function __construct()
{
}
public static function getName(): string
{
return 'production';
}
}
The private constructor prevents normal instantiation. Marking it final also prevents a child class from replacing the constructor with an accessible one.
Before combining final with visibility modifiers, it helps to understand how public, protected, and private members behave during inheritance. See the PHPpot guide to PHP access modifiers.
When should you use the final keyword?
Use final when allowing inheritance would break an intentional design rule, not simply because you do not expect anyone to extend the code.
Good candidates include:
- A public workflow that must always perform validation or authorization.
- A value object whose behaviour should remain predictable.
- A concrete implementation that callers access through an interface.
- A constant that forms part of a fixed protocol or internal contract.
- A property whose declaration must remain consistent in every child class.
For example, an authentication service may allow subclasses to customise how user records are loaded while keeping the login workflow final.
<?php
declare(strict_types=1);
abstract class AuthenticationService
{
final public function authenticate(
string $username,
string $password
): bool {
$user = $this->findUser($username);
if ($user === null) {
return false;
}
return password_verify($password, $user['password_hash']);
}
/**
* @return array{password_hash: string}|null
*/
abstract protected function findUser(string $username): ?array;
}
A child class may decide where the user comes from, such as a database or an API. It cannot skip the password verification performed by authenticate().
This is a practical use of the template method pattern: the parent class controls the overall operation while subclasses provide selected steps.
When final may be the wrong choice
Do not add final automatically to every class. It is a design restriction, and restrictions should have a reason.
Avoid using it when:
- The class is deliberately designed as a reusable base class.
- Applications are expected to extend the class to supply project-specific behaviour.
- You are still discovering the class’s useful extension points.
- Inheritance is part of the public API promised to library users.
Adding final to an existing public class can be a backward-compatibility break. Applications that already extend the class will fail as soon as they upgrade.
Library authors should therefore treat a new final declaration as an API decision, not a harmless cleanup.
Final does not make an object immutable
A final class cannot be extended, but its objects may still contain mutable properties.
<?php
declare(strict_types=1);
final class ShoppingCart
{
private array $items = [];
public function add(string $product): void
{
$this->items[] = $product;
}
public function getItems(): array
{
return $this->items;
}
}
The class is closed to inheritance, but calling add() still changes its state.
Immutability requires a separate design. You may use readonly properties, avoid mutating methods, or return a new object for each change. The final keyword alone provides none of those guarantees.
Final does not prevent composition
Closing a class to inheritance does not stop other classes from using it.
<?php
declare(strict_types=1);
final class TaxCalculator
{
public function calculate(float $subtotal): float
{
return $subtotal * 0.18;
}
}
final class OrderTotal
{
public function __construct(
private readonly TaxCalculator $taxCalculator
) {
}
public function calculate(float $subtotal): float
{
return $subtotal + $this->taxCalculator->calculate($subtotal);
}
}
OrderTotal uses TaxCalculator through composition instead of extending it. This keeps the responsibilities separate and avoids coupling the two classes through inheritance.
In many real projects, composition is the cleaner extension mechanism. A final class can still be injected, wrapped, decorated, or used behind an interface.
Final properties and private(set) visibility
PHP 8.4 introduced asymmetric property visibility. A property declared with private(set) is implicitly final because a child class cannot widen or replace its write behaviour.
<?php
declare(strict_types=1);
class Account
{
public private(set) string $accountNumber;
public function __construct(string $accountNumber)
{
$this->accountNumber = $accountNumber;
}
}
The property is publicly readable but can be written only inside Account. A child class cannot redeclare it.
You do not need to add the final keyword explicitly in this case. The restricted setter visibility already makes the property final.
Common errors and fixes
Cannot extend final class
Fatal error: Class ChildClass cannot extend final class ParentClass
Remove the inheritance relationship and use composition, or remove final from the parent only when inheritance is genuinely part of its design.
Cannot override final method
Fatal error: Cannot override final method ParentClass::methodName()
Keep the final method unchanged. Override a protected extension method called by it, when the parent class provides one.
Cannot override final constant
Fatal error: ChildClass::VALUE cannot override final constant ParentClass::VALUE
Use the inherited constant or declare a differently named constant for child-specific data.
Cannot override final property
Fatal error: Cannot override final property ParentClass::$propertyName
Use the inherited property as declared. When the child needs separate data, give the new property a different name instead of redeclaring the final one.
PHP final keyword FAQ
Can a final class implement an interface?
Yes. A final class can implement one or more interfaces. The final keyword only prevents another class from extending it.
Can a final class extend another class?
Yes. A class may extend a parent class and also be declared final. It inherits from the parent normally, but no further child class can extend it.
Can an abstract method be final?
No. An abstract method must be implemented by a child class, while a final method cannot be overridden. The two requirements contradict each other.
Can a static method be final?
Yes. A static method may be declared final to prevent child classes from overriding it.
<?php
declare(strict_types=1);
class ReferenceGenerator
{
final public static function create(int $id): string
{
return sprintf('REF-%06d', $id);
}
}
Can a constructor be final?
Yes. A constructor may be declared final. This prevents a child class from overriding the construction process.
<?php
declare(strict_types=1);
class Configuration
{
final public function __construct(
protected readonly array $settings
) {
}
}
Use this carefully. A final constructor can make subclasses difficult to initialise when they need additional dependencies.
Can traits contain final methods?
Yes. A trait method can be declared final. A class using the trait receives that restriction as part of the imported method.
<?php
declare(strict_types=1);
trait GeneratesToken
{
final public function generateToken(): string
{
return bin2hex(random_bytes(16));
}
}
class ApiSession
{
use GeneratesToken;
}
PHP also lets a class mark an imported trait method as final while resolving the trait method:
<?php
declare(strict_types=1);
trait FormatsReference
{
public function formatReference(int $id): string
{
return sprintf('REF-%06d', $id);
}
}
class OrderReference
{
use FormatsReference {
formatReference as final;
}
}
This syntax lets the trait remain reusable while one consuming class decides that the imported method must not be overridden.
Does final improve PHP performance?
Do not add final as a performance trick. Its main purpose is to express and enforce an inheritance rule.
Runtime optimisations may vary between PHP versions and execution environments. The design benefit is predictable behaviour, not a performance guarantee.
Conclusion
The PHP final keyword controls where inheritance must stop.
Use a final class to close the entire implementation. Use final methods, constants, and supported properties when subclasses may still extend the class but must not replace selected members.
The best use of final is intentional and narrow. Protect behaviour that forms part of a fixed contract, while leaving genuine extension points open. That gives future developers useful flexibility without handing them a screwdriver for every load-bearing wall.