Skip to content
Lesson 14 of 17

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.

Rendering diagram…

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
  • extends names the single parent. Motorbike gets $wheels and describe() 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's readonly makes such a property write-once. Both are parse errors on this course's PHP 7.4 grader — it answers syntax 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 {
}
AttemptPHP answers
class Sub extends Kernel {}Class Sub may not inherit from final class (Kernel)
overriding dispatch() in a subclass of RouterCannot 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 fatal Error.
  • Expecting a subclass to see the parent's private membersprivate stops at the class boundary. Use protected when 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".

Up nextInterfaces and TraitsClasses, Inheritance, Errors

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…