Polymorphism means code can call the same method name on different types of objects, and each one runs its own version — the earlier Interfaces lesson already builds this in practice; this lesson names the concept and covers the type declarations that make it safe to rely on.
interface Shape {
public function area(): float;
}
class Circle implements Shape {
public function __construct(private float $radius) {}
public function area(): float {
return pi() * $this->radius ** 2;
}
}
class Rectangle implements Shape {
public function __construct(private float $width, private float $height) {}
public function area(): float {
return $this->width * $this->height;
}
}
function printArea(Shape $shape): void {
echo $shape->area() . PHP_EOL;
}
printArea(new Circle(5));
printArea(new Rectangle(4, 6));
// printArea doesn't know or care which shape it received —
// each one calculates its own area() correctlyThe Shape $shape parameter type is what makes this safe — PHP guarantees anything passed in has an area() method, without printArea needing to check what kind of shape it received.
| Where | Example |
|---|---|
| Parameter type | function area(Shape $shape) |
| Return type | function area(): float |
| Property type | private float $radius; |
| Union type — more than one allowed | function process(int|string $id) |
| Nullable type | function find(int $id): ?User |
Type-hinting an interface, as printArea(Shape $shape) does, is also the foundation of dependency injection — a class that depends on an interface rather than one specific class can have any matching implementation "injected" into it, which is what makes swapping a real database for a test double possible without changing the class itself. It's a full topic on its own, worth knowing the name and the idea now.
PHP's typing is runtime-checked
PHP checks types at runtime, not compile time — a wrong type still causes an error, just when that line of code actually runs rather than before the script starts.