back

Bendegúz Csirmaz

This post is part of a series based on a presentation I gave at Cheppers on March 20, 2019.

PHP quiz #6 - covariance, contravariance and PHP

In the previous post we've seen that constructors can be safely overridden. What about functions? Brace yourselves, this is one of my favorite topics.

Question

Will this code throw a warning (PHP 7.1)?

<?php

class ClassA {
  public function method(array $a) {}
}

class ClassB extends ClassA {
  public function method(iterable $i) {}
}
  • A Yes
  • B
    No

Answer

Show the answer
B No

There are a few cases where functions can be overridden with certain signatures.

Iterable

iterable is a pseudo-type introduced in PHP 7.1.

iterable's inheritance tree

It's like an abstract base class for variables that can be iterated with foreach (arrays, Traversable objects).

Variance (recap)

When a subclass overrides a method of a superclass, it is possible to change its parameter and return types.

Covariance Contravariance Invariance
Covariance Contravariance Invariance

Covariance

Covariance means overriding methods can return more specific types. An array is more specific than an iterable.

<?php

class ClassA {
  public function method(): iterable {
    return [];
  }
}

class ClassB extends ClassA {
  public function method(): array {
    return [];
  }
}

This is type safe! Think about polymorphism to justify why (pseudocode):

ClassA obj = new ClassB();
T retval = obj.method();

Contravariance

Contravariance means overriding methods can accept less specific parameters. An iterable is less specific than an array.

<?php

class ClassA {
  public function method(array $a) {}
}

class ClassB extends ClassA {
  public function method(iterable $i) {}
}

This is type safe too! Again, think about polymorphism (pseudocode):

ClassA obj = new ClassB();
obj.method(new T’());

Invariance

Invariance means the overriding method cannot change the types.

Covariance, contravariance and PHP

PHP does not support covariance/contravariance. It's an invariant language. However, as always, there are a few exceptions:

1. iterable

By now it shouldn't come as a surprise that iterable, the subject of this blog post is indeed covariant/contravariant.

<?php

class ClassA {
  public function method(array $a): iterable {
    return [];
  }
}

class ClassB extends ClassA {
  public function method(iterable $i): array {
    return [];
  }
}

2. parameter type widening

From PHP 7.2 you can omit parameter types in overriding methods. This is an example of contravariance.

<?php

class ClassA {
  public function method(Exception $e) {}
}

class ClassB extends ClassA {
  public function method($e) {}
}

3. return types

If the parent method doesn’t have a return type, it can be specified. This is an example of covariance.

<?php

class ClassA {
  public function method() {}
}

class ClassB extends ClassA {
  public function method(): void {}
}

The future

PHP is mostly invariant... for now. But there is progress!

PHP 7.4 is going to add support for real covariance/contravariance (rfc). It will be a great improvement to PHP's type system.

<< previous