Abstract Classes
Sometimes a base class exists purely to be extended — it captures shared structure and shared logic, but on its own it doesn't represent a complete, usable thing. A generic Shape has an area, but "a shape" with no further detail can't actually compute one; only a concrete shape like a Rectangle or Circle can. PHP's abstract keyword lets you say exactly that: this class defines a template other classes must finish, and it should never be instantiated by itself.
abstract class and abstract function
An abstract Shape base class
<?php
abstract class Shape {
// No body — every concrete subclass must supply its own.
abstract public function area(): float;
// A regular, fully-implemented method, shared by all subclasses.
public function describe(): string
{
return sprintf('%s with area %.2f', static::class, $this->area());
}
}
class Rectangle extends Shape {
public function __construct(
private float $width,
private float $height,
) {
}
public function area(): float
{
return $this->width * $this->height;
}
}
$rect = new Rectangle(4, 5);
echo $rect->describe(), "\n";Rectangle with area 20.00
An abstract class can never be instantiated directly
Because Shape::area() has no implementation, PHP has no idea what
(new Shape())->area() should even do — so it refuses to let you
create a bare Shape at all.
Attempting to instantiate an abstract class
<?php
abstract class Shape {
abstract public function area(): float;
}
$shape = new Shape();PHP Fatal error: Uncaught Error: Cannot instantiate abstract class Shape
Every concrete subclass must implement the abstract methods
If a subclass doesn't implement every abstract method it inherits, PHP requires that subclass to also be declared abstract — it can't quietly ignore the obligation and still be instantiable.
A subclass that forgets area() must stay abstract
<?php
abstract class Shape {
abstract public function area(): float;
}
// Missing area() — this line alone would be a fatal error unless
// Triangle is also marked abstract:
abstract class Triangle extends Shape {
public function __construct(
protected float $base,
protected float $height,
) {
}
// area() still not implemented — Triangle stays abstract on purpose,
// perhaps waiting for RightTriangle / EquilateralTriangle to finish it.
}
class RightTriangle extends Triangle {
public function area(): float
{
return 0.5 * $this->base * $this->height;
}
}
$t = new RightTriangle(6, 4);
echo $t->area(), "\n";12
Triangle is a legitimate middle layer: it adds shared fields ($base, $height) without yet committing to a formula for area, because that formula genuinely differs between a right triangle and an equilateral one. Only RightTriangle, which supplies area(), can actually be instantiated.
Abstract classes can still have constructors and properties
It's a common misconception that an abstract class is "just a list of method signatures." It can have real properties, a real constructor, and fully-implemented methods — the only requirement is that it contains at least one abstract method (or is deliberately marked abstract even with none, though that's rare in practice). Shape::describe() above already demonstrated this: it's a working method that every subclass inherits unchanged.
Abstract class vs. interface
Both an abstract class and an interface let you demand that other classes implement certain methods, which raises the natural question of when to reach for which. Use an abstract class when your subclasses genuinely share implementation and state, and the relationship is a real "is-a" hierarchy — Rectangle and RightTriangle really are shapes, and they benefit from inheriting describe() for free. Reach for an interface (covered on its own page) instead when you only need to guarantee a contract — "this object can compute an area," full stop — across classes that may be otherwise unrelated and share no implementation at all. A useful rule of thumb: if you catch yourself writing an abstract class where every method is abstract and there's no shared state, that's usually an interface wearing the wrong hat.
abstract classcannot be instantiated withnew— doing so is a fatal Error.abstract function methodName(): ReturnType;has no body; it is a contract the subclass must fulfill.A subclass that does not implement every inherited abstract method must itself be declared
abstract.Abstract classes can still hold real properties, constructors, and fully-implemented methods alongside their abstract ones.
Choose an abstract class for shared implementation plus a real is-a hierarchy; choose an interface for a pure contract across unrelated classes.