Skip to content
Interfaces and Traits
step 1/7

Reading — step 1 of 7

Learn

~5 min readClasses, Inheritance, Errors

Interfaces and Traits

A class extends exactly one parent, which leaves two everyday problems unsolved: how does an unrelated set of classes promise the same capability, and how do two classes share an implementation without inventing a common ancestor?

Interfaces answer the first — a contract with no code in it. Traits answer the second — code with no contract. They are not competing features; most real classes use both.

Interfaces — a contract with no code in it

<?php
interface Priceable {
    public function priceCents(): int;
}

interface Labelled {
    public function label(): string;
}

class Book implements Priceable, Labelled {
    private string $title;
    private int $cents;

    public function __construct(string $title, int $cents) {
        $this->title = $title;
        $this->cents = $cents;
    }

    public function priceCents(): int {
        return $this->cents;
    }

    public function label(): string {
        return $this->title;
    }
}

function receipt(Priceable $item): string {
    return sprintf('%.2f', $item->priceCents() / 100);
}

$b = new Book('Atlas', 1850);
echo $b->label(), "\n";
echo receipt($b), "\n";

//> Atlas
//> 18.50
  • implements takes a comma-separated list, so one class can satisfy as many contracts as it likes.
  • Interface methods have no body and are always public.
  • A class uses implements to reach an interface; one interface uses extends to build on another interface.

The payoff is receipt(). It is typed Priceable, so it accepts anything that implements the contract and never needs to know what a Book is. That is what stands in for the multiple inheritance PHP does not have.

Traits — sharing an implementation sideways

<?php
trait Audited {
    private int $changes = 0;

    public function touch(): void {
        $this->changes++;
    }

    public function changeCount(): int {
        return $this->changes;
    }
}

class Invoice {
    use Audited;
}

class Customer {
    use Audited;
}

$i = new Invoice();
$i->touch();
$i->touch();
echo $i->changeCount(), "\n";

$c = new Customer();
echo $c->changeCount(), "\n";

//> 2
//> 0

use Audited; inside a class body copies the trait's methods and properties in at compile time. It is not inheritance: Invoice and Customer share the code without sharing a parent, each object still gets its own counter, and a class may use several traits. PHP Intermediate covers traits properly — this is the shape you need to recognise.

Late static binding — self:: versus static::

<?php
class Job {
    public static function make(): self {
        return new static();
    }

    public static function makeSelf(): self {
        return new self();
    }
}

class EmailJob extends Job {}

echo get_class(EmailJob::make()), "\n";
echo get_class(EmailJob::makeSelf()), "\n";

//> EmailJob
//> Job

self means "the class this line is written in" and is settled when the file is compiled. static means "the class the call actually went through" and is settled at run time. A factory method almost always wants static.

Grader note: PHP 8.0 lets you spell the return type : static, which describes new static() far better than : self does. On this course's PHP 7.4 grader that is a parse error — syntax error, unexpected 'static' (T_STATIC). Write : self, or leave the return type off. new static() itself has worked since PHP 5.3 and is fine here.

The two together

An interface says what a type promises; a trait supplies an implementation that several unrelated types can reuse. Putting them side by side is the everyday shape:

<?php
interface Shape {
    public function area(): float;
}

trait Describable {
    public function describe(): string {
        $parts = explode("\\", static::class);
        return sprintf('%s area %.2f', end($parts), $this->area());
    }
}

class Square implements Shape {
    use Describable;
    private float $side;
    public function __construct(float $side) { $this->side = $side; }
    public function area(): float { return $this->side * $this->side; }
}

echo (new Square(3.0))->describe(), "\n";

//> Square area 9.00

Square promises area() through the interface and inherits describe() from the trait without either one knowing about the other. Note static::class inside the trait: it resolves to the class that actually used the trait, which is why one describe() serves every shape.

Common mistakes

  • Mixing up extends and implements — a class extends one class and implements interfaces; only an interface extends another interface.
  • Putting a body on an interface method — interface methods declare a signature and nothing else, and they are always public.
  • Two traits defining the same method name — a fatal conflict (Trait method hi has not been applied, because there are collisions) until you pick a winner with insteadof.
  • Assuming self::class in a trait names the trait — it names the class that used the trait. The two only diverge once a subclass appears: given class Sub extends Rect {}, static::class reports Sub while self::class still reports Rect. Prefer static:: so the method keeps working when someone extends your class.

Your exercise

Shape Contract asks for both halves of this lesson at once. Declare interface Shape with a single area(): float. Declare trait Describable whose describe(): string returns the class name, a space, the word area, a space, and the area to two decimals — Rectangle area 12.00. Then write Rectangle and Circle, each implementing Shape and using Describable.

The input is a count, then that many lines: r <width> <height> for a rectangle, c <radius> for a circle. Print one describe() line each.

Three traps the grader catches. The file declares strict_types=1, so the pieces you split off the input line are strings: new Rectangle($parts[1], $parts[2]) dies with Argument 1 passed to Rectangle::__construct() must be of the type float, string given. Cast with (float). Second, build the number with sprintf('%.2f', ...) — a circle of radius 2 must read 12.57, not 12.566370614359; use M_PI for pi. Third, each class needs its own use Describable; line: leave it out and the class has no describe() at all, which ends the run with Call to undefined method Circle::describe().

Discussion

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

Sign in to post a comment or reply.

Loading…