Abstract Classes and Polymorphism
An abstract class in PHP is a class declared with the abstract keyword that cannot be instantiated directly — it exists solely to be extended. Abstract classes typically mix concrete methods (shared, working logic) with abstract methods (signatures with no body) that force every concrete subclass to supply its own implementation. This combination is powerful: the parent class owns the common workflow and state, while subclasses plug in the parts that genuinely vary. Polymorphism is the payoff — code written against the abstract type can call a method like render() or calculateTotal() on any subclass instance without knowing or caring which concrete class it actually is, because PHP dispatches the call to the correct overriding method at runtime.
Cricket analogy: Think of a base 'Bowler' blueprint in a coaching manual that fixes the run-up routine (shared) but leaves 'deliverBall()' blank, forcing a pacer like Bumrah and a spinner like Ashwin to each fill in their own action, yet the umpire just calls 'bowl' without caring which.
Declaring an abstract class
Both the class and each abstract method must carry the abstract keyword. An abstract method has no body — just a signature ending in a semicolon — and any class extending the abstract class must implement every abstract method or itself be declared abstract. Abstract classes can have constructors, typed properties, constants, and fully implemented (non-abstract) methods just like ordinary classes.
Cricket analogy: Like a fielding side that must have every position covered — keeper, slips, gully — before play starts, a PHP subclass must implement every abstract method or itself be declared abstract; meanwhile the team retains its fixed toss rules and kit (constructor, properties, constants).
<?php
declare(strict_types=1);
abstract class Shape
{
public function __construct(protected readonly string $name) {}
abstract public function area(): float;
public function describe(): string
{
return sprintf('%s has an area of %.2f', $this->name, $this->area());
}
}
final class Circle extends Shape
{
public function __construct(private readonly float $radius)
{
parent::__construct('Circle');
}
public function area(): float
{
return M_PI * $this->radius ** 2;
}
}
final class Rectangle extends Shape
{
public function __construct(private readonly float $width, private readonly float $height)
{
parent::__construct('Rectangle');
}
public function area(): float
{
return $this->width * $this->height;
}
}
/** @var Shape[] $shapes */
$shapes = [new Circle(3.0), new Rectangle(4.0, 5.0)];
foreach ($shapes as $shape) {
echo $shape->describe() . PHP_EOL;
}Polymorphism in practice
In the example above, describe() is written once on Shape yet automatically works correctly for every subtype, because it calls $this->area(), and PHP resolves that call to whichever area() override belongs to the actual runtime object — this is dynamic dispatch. The calling code in the foreach loop is fully decoupled from the concrete shape types; it only knows it has a Shape. Adding a new shape, say Triangle, requires zero changes to describe() or the loop — this is the open/closed principle in action, and it is the central practical benefit of designing around abstract classes and polymorphism rather than long if/match chains that branch on a type property.
Cricket analogy: A commentator says 'well bowled' without knowing if it's Bumrah's yorker or Ashwin's carrom ball — the call resolves to whichever bowler is actually bowling, just as describe() calls area() and PHP dispatches to whichever subclass's area() applies; adding a new bowler needs no script changes.
A useful mental model: an abstract class is a template with blanks. The template (concrete methods) is filled in once and reused by everyone; the blanks (abstract methods) are filled in differently by each subclass. Polymorphism is what lets callers use the finished template without knowing which set of blanks was used.
Attempting new Shape('generic') produces a fatal error — 'Cannot instantiate abstract class Shape.' A common mistake is forgetting to implement every abstract method in a subclass, which forces that subclass to also become abstract, silently propagating the restriction further than intended.
Abstract classes versus interfaces
Interfaces (covered in a separate topic) define a pure contract with no shared implementation and support multiple inheritance of type. Abstract classes allow shared state and partially implemented behaviour but, like any PHP class, support only single inheritance. A common and effective design combines both: implement an interface to declare the public contract, and provide an abstract base class implementing that interface with the boilerplate shared logic, letting concrete subclasses extend the abstract class and thereby automatically satisfy the interface.
Cricket analogy: An ICC playing-contract is a pure list of obligations (like an interface) any board can sign regardless of nationality, while a national academy (like an abstract class) shares training infrastructure but only one academy can raise a player; the best setups have a player sign the contract and train at an academy that already satisfies it.
- An
abstractclass cannot be instantiated; it exists only to be extended. - Abstract methods declare a signature with no body and force subclasses to implement them.
- Polymorphism lets calling code invoke a method on a base type reference while PHP dispatches to the correct subclass override at runtime.
- Abstract classes combine shared concrete logic with subclass-specific abstract methods, avoiding duplicated boilerplate.
- New subclasses can be added without modifying code that already depends on the abstract type — the open/closed principle.
- Unlike interfaces, abstract classes can hold state and concrete methods but still only support single inheritance.
Practice what you learned
1. What happens if you attempt `new Shape()` where `Shape` is declared `abstract class Shape`?
2. In the Shape/Circle/Rectangle example, why does `describe()` work correctly for both Circle and Rectangle without modification?
3. If a subclass of an abstract class does not implement all of the parent's abstract methods, what must happen?
4. What is a key structural difference between an abstract class and an interface in PHP?
5. Why is the ability to add a new `Triangle extends Shape` subclass without touching the `foreach` loop over `Shape[]` significant?
Was this page helpful?
You May Also Like
Classes and Objects
Explore PHP's object-oriented foundations: declaring classes, constructing objects, typed properties, visibility, and constructor promotion.
Inheritance and Interfaces
Understand how PHP classes extend one another via single inheritance and implement contracts via interfaces, including method overriding rules.
Traits in PHP
Traits let you share method implementations across unrelated classes without using inheritance, solving PHP's single-inheritance limitation for horizontal code reuse.
Magic Methods
Magic methods are special, double-underscore-prefixed hooks PHP invokes automatically for events like object construction, property access, and string conversion.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics