The final Keyword
What Does final Mean? In simple terms: final means "this cannot be changed by child classes". A final class cannot be extended at all; a final method cannot be overridden; a final constant cannot be r
What Does final Mean?
In simple terms: final means "this cannot be changed by child classes". A final class cannot be extended at all; a final method cannot be overridden; a final constant cannot be redefined. It is a way of saying: "this part is finished and must work exactly as written".
Technically: The final modifier can be applied to classes, methods, class constants (PHP 8.1+), and properties (PHP 8.4+). PHP checks these restrictions when classes are declared, so any violation is a compile-time fatal error.
| Usage | Effect |
|---|---|
final class Money | No class can extends Money |
final public function process() | Child classes cannot override process() |
final public const VERSION = 2; | Child classes cannot redefine VERSION (PHP 8.1+) |
final public string $name | Child classes cannot redeclare or override the property (PHP 8.4+) |
Why Use final?
Inheritance is powerful, but it also allows child classes to change behavior in ways the original author never intended. final protects:
- Invariants — rules that must always hold (for example, that a
Moneyobject is never negative). - Security-relevant logic — validation or authorization steps that must not be skipped.
- Design intent — classes not designed for extension, whose internals may change at any time.
Final Classes
<?php
final class EmailAddress
{
private string $value;
public function __construct(string $email)
{
$email = strtolower(trim($email));
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
throw new InvalidArgumentException("Invalid email address: $email");
}
$this->value = $email;
}
public function value(): string
{
return $this->value;
}
public function domain(): string
{
return substr($this->value, strrpos($this->value, '@') + 1);
}
}
$email = new EmailAddress(' Asha@GoCourse.in ');
echo $email->value() . "\n";
echo $email->domain() . "\n";
Output
asha@gocourse.in
gocourse.in
Trying to extend it fails as soon as the child class is declared:
class LooseEmailAddress extends EmailAddress {}
PHP Fatal error: Class LooseEmailAddress cannot extend final class EmailAddress
Why This Class Should Be Final
EmailAddress guarantees that every instance contains a valid, normalized address. A subclass could override value() to return something else, or skip the constructor's validation by defining its own constructor — and since a subclass object is also an EmailAddress, every function accepting EmailAddress could receive invalid data. Making the class final keeps the guarantee absolute.
Typical Candidates for Final Classes
| Kind of Class | Reason |
|---|---|
Value objects (Money, EmailAddress, DateRange) | Their validation and equality rules must not change |
| Services implementing an interface | Extension happens by writing another implementation, not by subclassing |
| Data transfer objects | Simple data carriers with no reason to be extended |
| Internal classes of a library | Their internals can change without breaking users |
Many modern projects and coding standards declare classes final by default and remove the keyword only when extension is deliberately designed. Composition and interfaces then cover most needs that inheritance would otherwise address.
Final Methods
A final method can be inherited and used, but not overridden. This is useful when a parent class defines a fixed procedure that calls customizable steps — the Template Method pattern:
<?php
abstract class PaymentProcessor
{
final public function process(int $amountPaise, int $orderId): string
{
if ($amountPaise <= 0) {
throw new InvalidArgumentException('Amount must be positive.');
}
$reference = $this->charge($amountPaise, $orderId);
return sprintf('Charged ₹%s via %s (ref %s)', number_format($amountPaise / 100, 2), $this->name(), $reference);
}
abstract protected function charge(int $amountPaise, int $orderId): string;
abstract protected function name(): string;
}
final class UpiProcessor extends PaymentProcessor
{
protected function charge(int $amountPaise, int $orderId): string
{
return "UPI-$orderId";
}
protected function name(): string
{
return 'UPI';
}
}
$upi = new UpiProcessor();
echo $upi->process(129900, 501) . "\n";
try {
$upi->process(0, 502);
} catch (InvalidArgumentException $e) {
echo "Error: " . $e->getMessage() . "\n";
}
Output
Charged ₹1,299.00 via UPI (ref UPI-501)
Error: Amount must be positive.
Why process() Is Final
process()contains the validation that every payment must pass. If a subclass could override it, a careless implementation might skip the amount check.- Subclasses customize only the steps they are meant to:
charge()andname(). - Overriding
process()is rejected:
PHP Fatal error: Cannot override final method PaymentProcessor::process()
UpiProcessor itself is also final: it is a complete implementation, not a base for further classes. (Abstract classes are covered in PHP Abstract Classes.)
Final Constants (PHP 8.1+)
<?php
class ApiClient
{
final public const VERSION = 'v2';
public const TIMEOUT = 10;
}
class SlowApiClient extends ApiClient
{
public const TIMEOUT = 30; // allowed: not final
// public const VERSION = 'v3'; // fatal error
}
echo SlowApiClient::VERSION . ' / ' . SlowApiClient::TIMEOUT . "\n";
Output
v2 / 30
Redefining VERSION in the child would cause:
PHP Fatal error: SlowApiClient::VERSION cannot override final constant ApiClient::VERSION
Constants declared in interfaces can also be overridden by implementing classes since PHP 8.1, unless they are marked final.
Things to Know
Private Methods and final
A private method is never overridden (child classes cannot see it), so final private makes no sense. PHP 8 emits a warning: "Private methods cannot be final as they are never overridden by other classes". The only exception is the constructor, where final private function __construct() is allowed.
Enums Are Implicitly Final
Enums cannot be extended (see PHP Enums).
final and Testing
Some mocking tools create test doubles by extending classes, which is impossible for final classes. The recommended solution is to depend on interfaces: the code under test receives an interface, and tests provide a different implementation. This is also better design, which is one reason "final by default" works well in practice.
When Not to Use final
| Situation | Reason |
|---|---|
| A base class explicitly designed for extension | Its purpose is to be extended (often it is abstract) |
| Framework classes users are expected to extend (controllers, models) | Extension is the intended usage |
| Public libraries where users rely on extending a class | Adding final later is a breaking change |
Removing final later is always backward compatible; adding it later breaks every subclass. That asymmetry is another argument for starting with final and opening classes only when needed.
Common Mistakes
Extending a Final Class
Effect: Fatal error when the child class is declared.
Fix: Use composition: wrap the final class in a new class, or implement the same interface.
Making Everything in a Base Class Final
Effect: The base class cannot be customized at all.
Fix: Make the workflow methods final and keep designated extension points protected and overridable (or abstract).
Using final private Methods
Effect: A warning and no effect.
Fix: Remove final from private methods.
Adding final to Published Library Classes Without Notice
Effect: Breaks users' subclasses.
Fix: Announce it as a breaking change in a major version.
Best Practices
- Declare value objects, DTOs, and concrete service implementations
final. - Use final methods to protect workflows and validation in base classes.
- Use final constants for values that must stay the same throughout a class hierarchy.
- Prefer interfaces and composition for extensibility and testability.
- Remove
finaldeliberately, only when a class is designed for extension.
Official reference: https://www.php.net/manual/en/language.oop5.final.php