Last active
January 17, 2018 10:34
-
-
Save hack3p/78eeca8f50d3297bf25768a9c72cafeb to your computer and use it in GitHub Desktop.
PHP invariant types. Test script.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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 ... |
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 ...
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
@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
Hi @IMSoP, thanks for responding. You right, the second example is wrong. Ofcourse i'm implied next code:
PHP Warning: Declaration of B::foo(Y $bar) should be compatible with A::foo(X $bar) in ...
Updated second example in gist.