01 · THE IDEAREAD → TRY → LEARN
Why abstract classes exist
Sometimes several classes share most of their behavior but differ in one or two details. You want the shared code written once, and you want PHP to insist that every variation fills in the missing pieces. An abstract class does both jobs. It can hold ordinary methods with real code, and it can declare abstract methods that have a signature but no body.
The main rules are short:
- A class marked
abstractcannot be created withnew. - A class that contains an abstract method must itself be abstract.
- A child class must implement every abstract method it inherits, or it must be abstract too.
- An abstract method declares only its signature and whether it is
publicorprotected.
A first example: the template method
The usual pattern is a public method in the parent that calls an abstract method the child supplies. The parent controls the overall flow, and the child supplies one detail.
abstract class Greeter {
abstract protected function name(): string;
public function greet(): string {
return "Hello, " . $this->name() . "!";
}
}
class Guest extends Greeter {
protected function name(): string {
return "guest";
}
}
$g = new Guest();
echo $g->greet(), "\n";Hello, guest!
greet() is written once in Greeter. Guest only has to answer the question "what is the name?". Notice that name() stays protected: outside code calls greet(), and the hook is an internal detail.
You cannot instantiate the abstract type
An abstract class is an incomplete blueprint. Trying to build one directly raises an Error, which you can catch and inspect.
abstract class Base {}
try {
new Base();
} catch (Error $e) {
echo $e->getMessage(), "\n";
}Cannot instantiate abstract class Base
This is a feature. Any variable typed as Base is guaranteed to hold a concrete, fully working subclass.
Signature compatibility
A child must follow the usual inheritance and signature compatibility rules. It may make a protected method public, and it may add extra optional parameters that the parent did not declare. The parent's abstract signature only lists the arguments that every caller can rely on.
Worked example: a concrete subclass behind a wrapper
In exercises you often write a wrapper function that hides the class details. Callers pass plain values and get plain values back, while the function builds the right subclass internally.
abstract class Label {
abstract public function format(string $text);
}
class Upper extends Label {
public function format(string $text, string $suffix = "!") {
return strtoupper($text) . $suffix;
}
}
function labels(array $words): array {
$label = new Upper();
return array_map(fn(string $w) => $label->format($w), $words);
}
echo json_encode(labels(["a", "bc"])), "\n";["A!","BC!"]
Upper adds an optional $suffix parameter, which is allowed, and the wrapper labels() returns an ordinary array that is easy to test.
Abstract properties in PHP 8.4
PHP 8.4 also lets an abstract class declare abstract properties, either public or protected. A declaration such as abstract public string $title { get; } says that children must provide a publicly readable $title, either as a normal property or as a property with hooks. A child may be more permissive than required, for example by making a protected requirement public, but it may not be less accessible than the requirement.
Common mistakes
- Giving an abstract method a body. Abstract methods have only a signature.
- Forgetting to implement one abstract method in a child, which causes a fatal error.
- Making the child's method less visible than the parent's declaration.
- Using an abstract class when you only need a list of method names. If there is no shared code or state, an interface may express the idea more simply.
Your exercise
The starter already contains an abstract class Discount with an abstract percent() method and a finished apply() method that uses integer cent arithmetic. Write concrete subclasses and complete discountedPrice(string $kind, int $cents): int:
"member"takes 10 percent off."holiday"takes 25 percent off."none"and any unknown kind take 0 percent off.
apply() must truncate fractions with intdiv, so 995 cents with a member discount becomes 895, not 896. Your wrapper picks the subclass, creates it and returns apply($cents).
Recap
Abstract classes combine shared code with required hooks. They cannot be instantiated, children must implement every abstract method, and signatures must stay compatible. Putting a concrete subclass behind a small wrapper function keeps your public API simple and easy to test.
Functions · Arrays · Types
- Implement a local concrete subclass behind a wrapper function
- Implement and test a deterministic PHP 8.4 function