Skip to content

Instantly share code, notes, and snippets.

@hack3p
Last active January 17, 2018 10:34
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hack3p/78eeca8f50d3297bf25768a9c72cafeb to your computer and use it in GitHub Desktop.
Save hack3p/78eeca8f50d3297bf25768a9c72cafeb to your computer and use it in GitHub Desktop.
PHP invariant types. Test script.
<?php
// example 1: return type
class A
{
function foo(): A
{
return $this;
}
}
class B extends A
{
function foo(): B
{
return $this;
}
}
// error: PHP Fatal error: Declaration of B::foo(): B must be compatible with A::foo(): A in ...
// example 2: arguments type hinting
class X {}
class Y extends X {}
class A
{
function foo(X $bar) {}
}
class B extends A
{
function foo(Y $bar) {}
}
// error: PHP Warning: Declaration of B::foo(Y $bar) should be compatible with A::foo(X $bar) in ...
@quebits
Copy link

quebits commented Jan 16, 2018

Possibly it would be more real-world example:

interface BarInterface {}

class Bar implements BarInterface {}

interface FooInterface
{
    function baz(BarInterface $bar);
}

class Foo implements FooInterface
{
    function baz(Bar $bar) 
    {
    }
}

(new Foo)->baz(new Bar);

PHP Fatal error: Declaration of Foo::baz(Bar $bar) must be compatible with FooInterface::baz(BarInterface $bar) in ...

@IMSoP
Copy link

IMSoP commented Jan 16, 2018

Hi, you still have the second example wrong I'm afraid - it's still narrowing the typehint when it should only be allowed to widen it.

Consider a different sub-class, Z, which also extends X:

class X {}
class Y extends X {}
class Z extends X {}
class A
{
    function foo(X $bar) {}
}
class B extends A
{
    function foo(Y $bar) {}
}

function test(A $a) {
    $a->foo(new Z);
}
test( new A ); // OK: Calls A::foo( new Z ), and Z instanceof X passes
test( new B ); // ERROR! Calls B::foo( new Z ), but Z instanceof Y fails

@ilya-k-v You've made the same mistake:

// This function should work no matter which implementation of each interface is passed in
function test( FooInterface $someFoo, BarInterface $someBar ) {
    $someFoo->baz( $someBar );
}

// But it doesnt!
class DifferentBar implements BarInterface {}
test( new Foo, new DifferentBar ); // ERROR: Tries to pass DifferentBar to an implementation that only accepts Bar objects

This seems to be something people struggle to intuitively grasp, and I've been playing around with representing it graphically. You may or may not find my attempt with ASCII-art boxes enlightening: https://github.com/IMSoP/variance-pipes

@quebits
Copy link

quebits commented Jan 17, 2018

@IMSoP nice graphics :)

With LSP in mind of course you're right.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment