Reputation: 178
Why do I receive this error:
Fatal error: Declaration of ConcreteFooMapper::load() must be compatible with that of AbstractFooMapper::load() on line 18
from this code:
<?php
interface Foo {
public function foo();
}
class ConcreteFoo implements Foo {
public function foo() {
}
}
abstract class AbstractFooMapper {
abstract public function load(Foo $entity, array $data);
}
class ConcreteFooMapper extends AbstractFooMapper {
public function load(ConcreteFoo $entity, array $data) {
}
}
?>
My initial thought is that it's a bug; PHP isn't detecting that ConcreteFoo implements Foo when it is evaluating the method declaration it. I think this because when you run this code:
<?php
interface Foo {
public function foo();
}
class ConcreteFoo implements Foo {
public function foo() {
}
}
$foo = new ConcreteFoo();
if ($foo instanceof Foo)
{
print 'w00t!';
}
else
{
print 'FAIL!';
}
?>
it prints w00t! indicating ConcreteFoo is an instance of Foo.
Any insights into whether this behavior is correct or not?
Upvotes: 9
Views: 12346
Reputation: 1162
What's interesting is that you can strengthen the predicate using constructor:
// ABuilder.php
abstract class ABuilder
{
public function __construct(BaseApi $api = null)
{
$this->api = $api;
}
public static function fromArray(array $data, BaseApi $api = null)
{
$entity = new static($api);
// ...
return $entity;
}
}
// ContactBuilder.php
class Contact extends ABuilder
{
// here, you cannot change signature of ::fromArray to require ContactApi
// (which extends/implements BaseApi), like in this example:
public static function fromArray(array $data, ContactApi $api = null) {}
// but you can specify ContactApi in the constructor arguments:
public function __construct(ContactApi $api = null) {}
}
So, when you try to initialise your ContactBuilder
class with wrong implementation of Api (even via ::fromArray()
fabric) — you'll get proper Type error message like:
Argument 1 passed to Builder/Contact::__construct() must be an instance of ContactApi or null, instance of LeadApi given
Upvotes: 0
Reputation: 642
The class implementing the interface must use the exact same method signatures as are defined in the interface. Not doing so will result in a fatal error. And same rules for classes which extends abstract classes.
Please, see in see in details here, see here too
And this is right behaviour \ logic.
check here Abstract types are useful in that they can be used to define and enforce a protocol; a set of operations which all objects that implement the protocol must support.
if we assume that your code will work without exception, then we have following problem: ConcreteFooMapper
can't use instances of some class ConcreteFoo2 implements Foo
as parameter for load
method, but should (by Abstract class definition)
Also, if you use same signature, it not a problem really, cause all class \ type info available. Please, check following code
<?php
interface Foo {
public function foo();
}
class ConcreteFoo implements Foo {
public function foo() {
}
}
abstract class AbstractFooMapper {
abstract public function load(Foo $entity, array $data);
}
class ConcreteFooMapper extends AbstractFooMapper {
public function load(Foo $entity, array $data) {
var_dump($entity instanceof Foo);
var_dump($entity instanceof ConcreteFoo);
}
}
$someConcreteFoo = new ConcreteFoo();
$someFooMapper = new ConcreteFooMapper();
$someFooMapper->load($someConcreteFoo, array('somekey' => 'somevalue'));
// output
// bool(true) bool(true)
?>
Upvotes: 5