Step 1 of 7 · Reading · ~5 min
Learn
Classes, Inheritance, Errors
Inheritance: extends, abstract, final
One class can build on another. PHP lets a class extend exactly one parent, and that single-parent limit is deliberate — it pushes real PHP toward shallow family trees. When you need a type to satisfy several unrelated capabilities, you reach for interfaces and traits instead, which the next lesson covers.
Extending a class
<?php
class Vehicle {
public int $wheels;
public function __construct(int $wheels) {
$this->wheels = $wheels;
}
public function describe(): string {
return "vehicle with {$this->wheels} wheels";
}
}
class Motorbike extends Vehicle {
public function __construct() {
parent::__construct(2);
}
public function describe(): string {
return "motorbike: " . parent::describe();
}
}
$m = new Motorbike();
echo $m->describe(), "\n";
echo $m->wheels, "\n";
//> motorbike: vehicle with 2 wheels
//> 2
extendsnames the single parent.Motorbikegets$wheelsanddescribe()for free.- Redefining a method overrides it. PHP needs no keyword for that.
parent::describe()runs the version you just overrode, which is how you extend behaviour instead of replacing it.
Grader note: every class in this lesson declares its properties in the class body and assigns them in the constructor. PHP 8.0's constructor property promotion writes the same thing as
public function __construct(private string $model) {}, and PHP 8.1'sreadonlymakes such a property write-once. Both are parse errors on this course's PHP 7.4 grader — it answerssyntax error, unexpected 'private' (T_PRIVATE), expecting variable (T_VARIABLE)— and a parse error means not one line of your file runs. Write the long form here.
What if the child skips parent::__construct()?
Nothing warns you. PHP never calls the parent constructor for you, so the parent's setup simply does not happen:
<?php
// Vehicle as above; Trailer defines its own constructor and forgets the call
class Trailer extends Vehicle {
public function __construct() {
}
}
$t = new Trailer();
echo $t->wheels;
$wheels is a typed property, so on PHP 7.4 it does not quietly default to null — it is uninitialized, and the first read stops the program with Error: Typed property Vehicle::$wheels must not be accessed before initialization. Adding parent::__construct(2); as the first line of the child constructor fixes it.
abstract — a base that cannot stand on its own
<?php
abstract class Shape {
abstract public function area(): float;
public function describe(): string {
return sprintf('area %.2f', $this->area());
}
}
class Circle extends Shape {
private float $radius;
public function __construct(float $radius) {
$this->radius = $radius;
}
public function area(): float {
return M_PI * $this->radius ** 2;
}
}
class Panel extends Shape {
private float $w;
private float $h;
public function __construct(float $w, float $h) {
$this->w = $w;
$this->h = $h;
}
public function area(): float {
return $this->w * $this->h;
}
}
foreach ([new Circle(2.0), new Panel(3.0, 4.0)] as $s) {
echo $s->describe(), "\n";
}
//> area 12.57
//> area 12.00
describe() is written once and calls an area() that does not exist yet. Each subclass supplies it, and PHP enforces that:
| If you... | PHP answers |
|---|---|
write new Shape() | Cannot instantiate abstract class Shape |
extend Shape without writing area() | Class Blob contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Shape::area) |
That second message arrives before your program starts: PHP compiles the whole file before running any of it, so a broken class declaration replaces your output rather than interrupting it.
final — closing the door
<?php
class Router {
final public function dispatch(): string {
return "dispatched";
}
}
final class Kernel {
}
| Attempt | PHP answers |
|---|---|
class Sub extends Kernel {} | Class Sub may not inherit from final class (Kernel) |
overriding dispatch() in a subclass of Router | Cannot override final method Router::dispatch() |
Both are compile-time refusals too. final on a class blocks subclassing; final on a method blocks overriding that one method. Reach for it when a class was not designed to be extended safely.
Common mistakes
- Forgetting
parent::__construct(...)— the parent's typed properties stay uninitialized and the first read is a fatalError. - Expecting a subclass to see the parent's
privatemembers —privatestops at the class boundary. Useprotectedwhen a subclass genuinely needs access. - Reaching for a third level of
extends— an interface plus a trait usually models "these things share a capability" better than a deeper tree.
Your exercise
Vehicle Hierarchy hands you class Vehicle already written: a public int $wheels, a constructor, and describe(). You add class Car extends Vehicle whose constructor takes a string $model, calls parent::__construct(4), and overrides describe() to return the model, a colon and a space, then the parent's sentence.
The mistake the grader catches is skipping parent::__construct(4). $wheels is typed, so it is never initialized and the very first test dies with Typed property Vehicle::$wheels must not be accessed before initialization before printing anything at all. The second trap is writing the parent's wording out by hand inside Car instead of calling parent::describe() — it passes today and goes quietly wrong the moment Vehicle changes. And do not reach for the PHP 8 constructor shorthand: declare private string $model; in the class body. The expected line for input Tesla is Tesla: vehicle with 4 wheels — one colon, one space, and a closing "\n".
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…