Lesson 69 of 100
Types15 min5 checksPHP 8.5

Require Multiple Interfaces with Intersection Types

Learn how PHP intersection types such as Named&Priced demand that one object satisfies several interfaces, and how a violation surfaces as a TypeError.

01 · THE IDEAREAD → TRY → LEARN

The problem: one value, two promises

A union type such as A|B says a value may be either kind. Sometimes you need the opposite: a value that is both at once. Imagine a function that prints a line for a shop item. It needs a name and a price, and those two abilities live in two separate interfaces. Typing the parameter as just one interface would let objects through that cannot do the other job.

An intersection type joins class or interface names with &. The value must satisfy every part. The PHP manual lists intersection types as available since PHP 8.1.

A first intersection type

Here Product implements both interfaces, and the function demands both.

Example
<?php
interface Named { public function getName(): string; }
interface Priced { public function getPrice(): int; }
final class Product implements Named, Priced {
    public function __construct(private string $name, private int $price) {}
    public function getName(): string { return $this->name; }
    public function getPrice(): int { return $this->price; }
}
function describeItem(Named&Priced $item): string {
    return $item->getName() . ' costs ' . $item->getPrice();
}
echo describeItem(new Product('Atlas', 1200)), "\n";
Output
Atlas costs 1200

Inside the function, PHP lets you call methods from both interfaces, because the type guarantees they exist.

What happens on a mismatch

Type declarations are checked at call time. If an object satisfies only one interface, PHP throws a TypeError. You can catch it like any other throwable.

Example
<?php
interface Named { public function getName(): string; }
interface Priced { public function getPrice(): int; }
final class Label implements Named {
    public function getName(): string { return 'Sale'; }
}
function describeItem(Named&Priced $item): string {
    return $item->getName() . ' costs ' . $item->getPrice();
}
try {
    echo describeItem(new Label()), "\n";
} catch (TypeError $e) {
    echo get_class($e), "\n";
}
Output
TypeError

Passing null fails the same way, since null is not an instance of either interface.

Return types work too

Intersection types are not limited to parameters. They can describe return values, and the manual notes that type declarations also apply to properties.

Example
<?php
interface Named { public function getName(): string; }
interface Priced { public function getPrice(): int; }
final class Product implements Named, Priced {
    public function __construct(private string $name, private int $price) {}
    public function getName(): string { return $this->name; }
    public function getPrice(): int { return $this->price; }
}
function makePen(): Named&Priced { return new Product('Pen', 150); }
$item = makePen();
echo json_encode(['name' => $item->getName(), 'price' => $item->getPrice()], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
{"name":"Pen","price":150}

The caller sees only the two interface contracts, not the concrete class. That keeps code flexible.

Rules to remember

  • Every part must be a class-type name, meaning a class or an interface. Scalars such as int&Countable are rejected at compile time.
  • self, parent and static cannot appear in an intersection.
  • Repeating a name, even with different letter case, is an error.
  • Since PHP 8.2 you can combine intersections inside union types (DNF types), but this lesson stays with plain intersections.

Exercise contract

You will write summarizeCatalog(array $rows): array. Each row is [kind, name, price]. The starter already defines the interfaces and the classes Product (both interfaces), Label (only Named) and Fee (only Priced).

  1. Define a closure whose parameter is typed Named&Priced and that returns name=price, for example Atlas=1200.
  2. Build an object for each row. Unknown kinds produce null.
  3. Call the closure inside try. On a TypeError, record the string skipped instead.
  4. Return the list of strings in input order.

Boundary cases to think about: an empty list, a product priced 0 (it is valid and must not be skipped), and an unknown kind.

Recap

Named&Priced means both. PHP enforces it when the function is called and throws TypeError otherwise. Use it when a function genuinely needs two capabilities and no single interface describes both.

Before you start

Functions · Arrays · Types

You’ll practice
  • Wrap objects implementing two local interfaces behind a JSON-callable exercise
  • Implement and test a deterministic PHP 8.4 function
Your task

Make summarizeCatalog() pass all 5 checks. Run your code as often as you like, then check your answer.

exercise.phpSaved in this browserPHP 8.5
CONSOLE & CHECKS● Ready

Ready when you are

Your output will appear here.
Make it work. Then make it pass.

PHP loads on your first run. Each check starts fresh. Your code stays in this browser; clearing site data removes your progress. A check or the task looks wrong? Report a problem with this exercise.