15

I'm trying to make an generic interface for my service classes. I'm having trouble with a two class making use of the interface. They share a methode called create. The create methode excepts three parameters. I want it to make it so that the third parameter is optional so that both classes can work with it.

interface ServiceInterface{

    public static function create($var1, $var2, $thisOneIsOptional);

}

class ServiceObject1 implements ServiceInterface{

    public static function create($url, $server){
       //....
    }
 }

class ServiceObject2 implements ServiceInterface{

    public static function create($methode, $url, $id){
       //....
    }
}
8
  • 5
    It sounds a bit counter intuitive to have an interface that would allow the method to be used differently. The whole purpose of an interface is to know exactly how to use the implementing classes. If one service doesn't need the $id but the other does, it should still be required. It's then up to the service to just ignore it. Commented May 18, 2017 at 11:28
  • public static function create($var1, $var2, $thisOneIsOptional = ''); or if array public static function create($var1, $var2, $thisOneIsOptional = []); Commented May 18, 2017 at 11:29
  • 1
    You can't have an interface with an optional parameter and an implementation that requires it - that would mean the signature isn't compatible. If you can't generalise your interface enough to suit all possible implementations, an interface isn't what you're looking for. Commented May 18, 2017 at 11:31
  • 1
    Then you have no idea what parameters are required or not for each service without actually looking through the code for each service. Why even bother with an interface at all, then? Commented May 18, 2017 at 11:46
  • 1
    Interfaces for static methods are… odd anyway. Coding against an interface means you can substitute objects for other objects. However, static methods are always called on a specific class, which cannot be injected/replaced. So… why an interface in the first place? Commented May 18, 2017 at 11:57

4 Answers 4

20

Classes implementing a method defined as an abstract signature on a parent class or by an interface must follow certain rules, but they do not have to match exactly.

This is not well documented, for example it's not even mentioned in the PHP documentation of interfaces, but is touched upon by the documentation of abstract classes:

Furthermore the signatures of the methods must match, i.e. the type hints and the number of required arguments must be the same. For example, if the child class defines an optional argument, where the abstract method's signature does not, there is no conflict in the signature.

In other words, the method must be capable of being called as per the signature, but it doesn't rule out:

  1. Using different variable names in the implementation of the method
  2. Declaring additional optional arguments after the arguments declared in the method signature, if any

Taken from the question, this would work:

interface ServiceInterface{

    public static function create($var1, $var2);

}

class ServiceObject1 implements ServiceInterface{

    public static function create($url, $server){
       //....
    }
 }

class ServiceObject2 implements ServiceInterface{

    public static function create($methode, $url, $id = null){
       //....
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

This is really insightful research, thanks! It seems the php docs should be amended. Did you know that you can also add a return type to an implementation which has an interface without a return type? e.g. interface defines public function foo(); and implementation can add a string return type and still be compatible public function foo(): string {return 'bar';}
@KelvinJones, you are right, I take back my comment!
This is still the behaviour in PHP 8.1. It does seem strange to me that a class method can declare, "you may pass in an additional parameter not defined in the interface, sanf I will typehint it" but it cannot say "I am going to ignore the last parameter defined by the interface, and I won't even delcare it to show how serious I am".
15

It's not the proper way to implement an interface.

First of all, an interface defines how a class should be used and an optional parameter could break this reason.

Besides that, even you have exactly two parameters, they should have its own meaning and this meaning should be shared.

There's a huge difference between the interface method signature:

public static function create($var1, $var2);

and the two implemented methods:

public static function create($url, $server)

and:

public static function create($methode, $url)

Also, AFAIK, implementing this way will rise a strict standard violation because you're changing the interface signature.

If you have to create a shared interface that has no meaning, there's no reason to the interface be shared or even created.

Comments

6

In PHP 56+, you can use the ellipsis operator:

interface ServiceInterface {
    public static function create(...$params);
}

class ServiceObject1 implements ServiceInterface
{
    public static function create(...$params)
    {
        $url = $params[0];
        $server = $params[1];
        print "url = $url\n";
        print "server = $server\n";
    }
}

class ServiceObject2 implements ServiceInterface
{
    public static function create(...$params)
    {
        $method = $params[0];
        $url = $params[1];
        $id = $params[1];
        print "method = $method\n";
        print "url = $url\n";
        print "id = $id\n";
    }
}
print "ServiceObject1::create\n";
ServiceObject1::create("url", "server");
print "\nServiceObject2::create\n";
ServiceObject2::create("method", "url", "id");

Output:

ServiceObject1::create
url = url
server = server

ServiceObject2::create
method = method
url = url
id = url

/rant

To users complaining about what the OP want's to do - While general advice is that this is bad idea to do, there exsist many cases in which the program really does not care what the parameters are in advance. An example of this would be if ther function was sumAll instead of create.

1 Comment

I upvoted this because it actually answers the OP's question, while still mentioning that this is not a best-practice by any means. I am working on a legacy codebase where I need to extend a few classes that implement an Interface. This is the least disruptive and fastest way to get there, even if it violates the pure concept of an interface. In my scenario, the original author should have made an abstract class implementing the interface, then have everything inherit from the abstract class. However, ... allows me to modify roughly a dozen classes instead of 50-something. That's a win.
0

You can do:

interface ServiceInterface{

    public static function create($url, $server, $id = null);

}

class ServiceObject1 implements ServiceInterface{

    public static function create($url, $server, $id = null){
       //....
    }
}

class ServiceObject2 implements ServiceInterface{

    public static function create($methode, $url, $id = null){
       //....
    }
}

But wouldn't it be easier and more logical to make ServiceObject2 extend ServiceObject1, by doing this you can overwrite the function with an extra argument. And instead of requiring an object to be an instance of the interface, you require it to be an instance of ServiceObject1.

2 Comments

Yeah, I could do that. I'am planning to make a service class for all the models I'm currently using(6 models), not every model has the same number of parameters for the create methode. I thought it would be nice to have one Contract for all of them. But making a superclass is good solution. It's just that interfaces are structurally more beautiful :)
@melkawakibi - Just a suggestion; Instead of thinking of what's "beautiful", what the "cool kids use" or adding things "just because", you should think about writing intuitive code. In my opinion, that's more beautiful.

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.