0

I got E_COMPILE_ERROR when i'm trying to run the following code:

<?php

interface DataInterface
{
    public function get();
}

interface ServiceInterface
{
    public function save(DataInterface $data);
}

class Data implements DataInterface
{
    public function get()
    {
        return 'data';
    }
}

class Service implements ServiceInterface
{
    public function save(Data $data)
    {//the problem is here^^
        var_dump($data->get());
    }   
}

$service = new Service();
$data = new Data();
$service->save($data);

Data class is implementation of DataInterface interface. I wonder why this code cannot be compiled? Documentation says that valid type must be an instanceof the given class or interface name. (http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration).

$data = new Data();
var_dump($data instanceof DataInterface); //true;

As far as i understand if declared type of method parameter is class which implements expected interface then this type satisfies the needs (implements all methods) and signature should match.

1
  • The documentation you quoted refers to the actual argument passed to the function when it is invoked. Commented Dec 16, 2015 at 14:45

2 Answers 2

2

Service is required to implement ServiceInterface.
ServiceInterface specifies that save must accept a DataInterface.
But Service::save accepts Data instead of DataInterface. That's not the same type, the implementation is incompatible with the interface declaration.

It matters when you call Service::save, that $data is an instanceof DataInterface; not when you declare the method signature.


To get more in-depth on this: interfaces are used this way:

function foo(ServiceInterface $service) {
   $service->save($something);
}

In other words, some other code is going to receive something that implements ServiceInterface. It doesn't know or care what $service is, as long as it implements the known contract specified in ServiceInterface. And ServiceInterface specifies that it's possible to pass any DataInterface to $service::save(), not a Data instance. $something can be any other object which implements DataInterface. Having Service::save only accept Data instances breaks that contract and would lead to runtime errors.

Sign up to request clarification or add additional context in comments.

14 Comments

Let's assume that i have two different implementations of DataInterface: DataSql and DataRedis. And also two implementations of ServiceInterface: ServiceSql and ServiceRedis. I want be sure ServiceSql::save() method expects to receive only instance of DataSql class but not DataRedis. But I cant implement it in this way.
No you can't, because it makes no sense to use an overarching interface declaration if you have two completely incompatible concrete implementations. If you are using a ServiceInterface::save interface at all, that means all implementations of it must be agnostic to concrete differences and must all accept the same arguments. If that's not your reality, then you don't have a situation where a ServiceInterface makes sense. You have two different concrete classes which both accept different argument types.
I can pass object of subclass during runtime, but i can't restrict type of expected parameter even if this type is still implements the same interface . It's quite strange in my opinion.
No, it's not strange. Really try to understand the sample I give. foo doesn't know what it gets, only that it gets some object conforming to ServiceInterface. ServiceInterface::save says it accepts anything implementing DataInterface. It passes $something that implements DataInterface. Your code explodes because your concrete Service::save accepts exclusively Data instances, and $something does not happen to be an instanceof Data. Interface violation. Not allowed. Error caught at compile time thanks to interface declarations.
Take a closer look at the code that I posted. My concrete Service::save accepts Data which is implementation of DataInterface, in other words this method expect to receive instance of DataInterface.
|
0

I'm getting the following exception when running your code:

PHP Fatal error:  Declaration of Service::save() must be compatible with ServiceInterface::save(DataInterface $data) in test.php on line 22
PHP Stack trace:
PHP   1. {main}() test.php:0

Fatal error: Declaration of Service::save() must be compatible with ServiceInterface::save(DataInterface $data) in test.php on line 22

Call Stack:
    0.0002     130808   1. {main}() test.php:0

.. and the exception is very clear about your problem: The declaration of Service::save() is wrong. It's trying to take in a Date whereas the function it's trying to override (ServiceInterface::save) takes a DateInterface.

You should change the signature of Service::save() to take in a DateInterface. You can still pass it a Date object, but you can't force it to be a Date object. If you want a method that takes just Date objects (and their subclasses), you'll need to give it a new name.

4 Comments

I can read error messages and i know how to fix it. I think that this is not my problem, but problem of the PHP.
I beg to differ. PHP has no problem here. It's telling you you're doing something wrong. You are trying to override one function with another function that has a different signature. That's not allowed. What is allowed is passing a subclass of the specified type at runtime, which is what the quoted part of the PHP manual you referenced says.
Right, this is the problem i guess. I can pass subclass object to this method but i cant restrict type of parameter in the implementation of class.
You have 3 options: (1) create a new function name (e.g saveData) which takes a Date, (2) change the base interface to take a Date or (3) in your save implementation use an assert (e.g. assert($data instanceof Data);). Which option is best depends on your circumstances.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.