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 ...
@hack3p
Copy link
Author

hack3p commented Jan 16, 2018

It's test example for changing the variability of php types: https://bugs.php.net/bug.php?id=75823

@IMSoP
Copy link

IMSoP commented Jan 16, 2018

Your second example is wrong: class B must be usable wherever class A is usable, so must accept any value that class A accepts. If your code failed, you would not be able to write the following:

function test(A $x, A $y) {
    $x->foo($y);
}
test( new B, new A );

Because it would run (new B)->foo(new A), which your constraint disallowed.

You could instead, if the language allowed, have a wider constraint on the sub-class:

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

function test(A $a) {
    $a->foo(new B);
}
test( new B ); // OK, because B::foo accept B objects as a sub-set of A objects

Or a less circular example:

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

function test(A $a) {
    $a->foo(new Y);
}
test( new B ); // OK, because B::foo accept Y objects as a sub-set of X objects

@hack3p
Copy link
Author

hack3p commented Jan 16, 2018

Hi @IMSoP, thanks for responding. You right, the second example is wrong. Ofcourse i'm implied next code:

class X {}
class Y extends X {}

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

PHP Warning: Declaration of B::foo(Y $bar) should be compatible with A::foo(X $bar) in ...

Updated second example in gist.

@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