An abstract class is a base class that cannot be instantiated directly. It can contain ordinary methods with shared code and abstract methods that subclasses must implement. That combination is useful when related classes share a workflow but differ in one part, such as documents that all produce a heading but calculate their body differently.
This guide builds a small example, explains method signature and visibility rules, and compares an abstract base with an interface or composition. Abstract classes are an inheritance tool, so review classes and objects before using them. The goal is a clear relationship, not a deeper inheritance tree for its own sake.
What abstract changes
Put abstract before class to mark the base. An abstract method has a signature ending in a semicolon and no method body. It states an obligation for a concrete child. The base can also provide properties, constructors and complete methods that children reuse.
A class containing an abstract method must be abstract. A child that does not implement every inherited abstract method must remain abstract too. A concrete child supplies compatible implementations before objects of that class can be created. This makes missing required behavior a language-level error rather than a surprise when a particular method is called.
An abstract class can be useful even without abstract methods when direct instances would have no meaningful role. However, naming a class abstract should reflect a domain design decision. If the base is simply a collection of unrelated helper methods, a separate service or composed object may be more appropriate than inheritance.
Signatures and visibility must remain compatible
A concrete implementation must respect the inherited method contract. Parameter and return types follow PHP's inheritance compatibility rules. Changing a required string result into an unrelated array result does not satisfy callers expecting the base contract. Defaults, required arguments and reference behavior can also matter.
A child may make a protected method public, but cannot reduce public visibility to protected or private. The broader visibility can expose a hook callers did not previously see, so choose it deliberately. Abstract methods cannot be private because a child must be able to implement the obligation.
Use types to express the contract and domain checks to enforce valid values. A subclass returning any string technically satisfies a string return type, even if an empty report is forbidden. Shared validation or a clearly documented invariant helps maintain consistent behavior across implementations.
A template method controls a workflow
The example's render method is a simple template method: it defines common steps and calls a subclass hook. In a larger example, a base might validate input, call a specialized calculation and format the result. Keep the template small enough that a child author can understand which assumptions the base makes.
Mark a workflow method final when overriding it would violate an important invariant, provided inheritance is truly the right design. Final prevents children from replacing that method, while protected hooks provide the permitted variation. Do not make everything final and then add dozens of obscure hooks just to preserve an overcomplicated base.
Avoid calling overridable methods from a constructor before the child has initialized its state. A hook may depend on properties that are not ready yet. Prefer explicit post-construction operations or a constructor design that establishes all required state before invoking behavior.
Abstract class versus interface or composition
An interface describes a contract that unrelated classes can implement. It is useful when callers care about an operation rather than shared ancestry. An abstract class can also provide shared state and implementation, but a PHP class has only one direct parent, so choosing a base consumes that inheritance relationship.
Composition means storing another object and delegating work to it. If several classes need the same formatter, give them a formatter dependency instead of forcing them into a formatter-based inheritance tree. Composition is often easier to vary at runtime and test independently.
Use an abstract class when the classes are genuinely related and a shared workflow provides real value. Use an interface when the promise is the main relationship. Use composition when the common behavior is a service the objects use. These tools can cooperate, but each should have a clear responsibility.
PHP Abstract Classes Examples
Each example shows the code, the output it produces and what each part does.
Combine a shared heading with a required body
The public render method supplies the workflow. The protected body method is the variation point. Callers use one stable public operation without knowing which subclass creates the body. Creating new Report() would fail because the base is abstract and cannot provide a complete report on its own.
<?php
abstract class Report {
abstract protected function body(): string;
public function render(): string {
return 'Report: ' . $this->body();
}
}
class StockReport extends Report {
protected function body(): string {
return '8 items';
}
}
echo (new StockReport())->render() . PHP_EOL;Report: 8 itemsCommon PHP Abstract Classes Mistakes
Turning inheritance into hidden coupling
A base with many protected mutable properties can let every child affect every other shared assumption. Keep state private when children do not need direct access and expose narrow hooks. Avoid changing a base's hook semantics without reviewing all implementations. Shared code can reduce duplication while also broadening the impact of one edit.
The PHP abstract classes reference documents concrete implementation and compatibility requirements. Read errors about incompatible declarations as contract problems: the child is no longer safely substitutable where the base is expected. Do not remove types merely to silence that signal.
PHP Abstract Classes in Real Projects
Practical task: total a priced document
Create an abstract priced document with a shared total calculation and a required line-value method. A concrete invoice provides three integer-cent values. The base adds the lines and returns an integer total. This keeps aggregation common while leaving the source of lines to the child.
<?php
abstract class PricedDocument {
abstract protected function lines(): array;
public function totalCents(): int {
$total = 0;
foreach ($this->lines() as $value) {
$total += $value;
}
return $total;
}
}
class DemoInvoice extends PricedDocument {
protected function lines(): array {
return [125, 250, 75];
}
}
echo (new DemoInvoice())->totalCents() . PHP_EOL;450The array return type does not enforce integer elements, so a real base must document or validate that element contract. Try another child with an empty line list and decide whether zero is valid. If invoices and quotations differ in permissions or lifecycle, do not assume shared totaling means all their other behaviors belong in one base.
PHP Abstract Classes FAQ
Can an abstract class have a constructor?
Yes. A concrete child can reuse it or call it explicitly from its own constructor. The constructor still needs to establish a valid state.
Can I instantiate the abstract base?
No. Create an appropriate concrete child.
Can a child remain abstract?
Yes. It may implement some obligations and leave others for a later concrete subclass.
Is an abstract method always public?
No. It can be protected when it is an internal variation hook. It cannot be private.
Should every shared method move into a base?
No. Shared helper behavior can belong in a composed service. Inheritance should express a meaningful relationship and compatible contract.
PHP Abstract Classes Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Unrelated classes need the same formatter service. Which design should you consider first?
PHP Abstract Classes Quiz
2 questions to check what you learned.
Can an abstract class be instantiated directly?
- No
- Yes, if its name ends in Base
- Only without a constructor
Which visibility is invalid for an abstract method?
- public
- private
- protected