Skip to content

Instantly share code, notes, and snippets.

@vhenzl
Last active June 21, 2022 21:25
Show Gist options
  • Save vhenzl/7da05b17c5f6871e3082e6ce39ef21ee to your computer and use it in GitHub Desktop.
Save vhenzl/7da05b17c5f6871e3082e6ce39ef21ee to your computer and use it in GitHub Desktop.
Covariance and contravariance in PHP & PHPStan

General resources

Other

PHPStan playground

3V4L

<?php
// PHP RFC: Parameter Type Widening (Contravariant argument type)
// https://wiki.php.net/rfc/parameter-no-type-variance
// It's not real contravariance. Type widening doesn't allow to use arbitrary supertype, only mixed (by ommiting type).
// Otherwise, parameter types in PHP are invariant.
// This correspond with behaviour of return types, which are kind of covariant
// https://3v4l.org/jU9IC
class A {}
class B extends A {}
class C extends B {}
class X {}
class Foo {
public function bar(B $b) {}
}
class Woo extends Foo {
public function bar(/*mixed*/ $b) {}
}
$foo = new Foo();
$woo = new Woo();
// $foo->bar(new A);// A is too generic, Foo::bar() expects more specific type B
// TypeError: Argument 1 passed to Foo::bar() must be an instance of B, instance of A given
$foo->bar(new B); // this is ok, B is expected type
$foo->bar(new C); // this is ok, C is subtype of B
// $foo->bar(new X);// X isn't subtype of B
// TypeError: Argument 1 passed to Foo::bar() must be an instance of B, instance of X given
$woo->bar(new A); // this is ok, A is subtype of mixed
$woo->bar(new B); // this is ok, B is subtype of mixed
$woo->bar(new C); // this is ok, C is subtype of mixed
$woo->bar(new X); // this is ok, X is subtype of mixed
$woo->bar([]); // this is ok, array is subtype of mixed
$woo->bar(123); // this is ok, int is subtype of mixed
function baz(Foo $f) {
// Foo:bar() accept only B and its subtypes (ie. B and C)
// and these types are safe also for Woo:bar() if Woo is up-casted to Foo
$f->bar(new C);
}
baz($foo);
baz($woo); // inside of baz function, Woo is up-casted to Foo
<?php
// PHP RFC: Return Type Declarations
// https://wiki.php.net/rfc/return_types
// RFC for return type can be interpreted in the way that PHP has kind of covariation for return types:
// Supertype (top type) mixed can be overidden with more specific type (ie. with any built-in or custom type).
// Otherwise, return types in PHP are invariant.
// This corresponds with behaviour of type widening (contravariance) https://wiki.php.net/rfc/parameter-no-type-variance
// https://3v4l.org/8Rlce
class A {}
class B extends A {}
class C extends B {}
class X {}
class Foo {
public function bar() /*:mixed*/ {
return [];
}
}
class Woo extends Foo {
public function bar() :B {
return new B();
}
}
$foo = new Foo();
$woo = new Woo();
$foo->bar(); // return type is mixed, returned value can be whatever
$woo->bar(); // return type is B, returned value can be of type B or C only
function baz(Foo $f) {
$f->bar(); // return type is mixed,
// code here can't have any expectation for type of returned value, even if $f contains up-casted Woo
}
[true]-:>[bool]
[false]-:>[bool]
[int]-:>[number]
[float]-:>[number]
[number]-:>[scalar]
[bool]-:>[scalar]
[string]-:>[scalar]
[scalar]-:>[mixed]
[object]-:>[mixed]
[array]-:>[mixed]
[iterable]-:>[mixed]
[resource]-:>[mixed]
[callable]-:>[mixed]
[null​]-:>[mixed]
[stdClass]-:>[object]
[DateTime]-:>[object]
[...]-:>[object]
[void]-:>[mixed]
<?php
class A {}
class B extends A {}
class Foo {
public function bar(B $b) {}
}
class Woo extends Foo {
public function bar(A $b) {}
}
$w = new Woo();
<?php
class A {}
class B extends A {}
class Foo {
public function bar() : A {}
}
class Woo extends Foo {
public function bar() : B {}
}
$w = new Woo();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment