kulishch
kulishch

Reputation: 178

Why are the method declarations not compatible?

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

Answers (3)

pilat
pilat

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

Major Productions
Major Productions

Reputation: 6042

According to the docs, type hints must match exactly.

Upvotes: 7

Vadim
Vadim

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

Related Questions