17

I'm developing a service that is being injected a Logger object but I can have 2 different kind of loggers, I'm planning on having a syslog logger and a queue message system logger. Is this possible?

The idea is having an interface:

interface Loggable
{
    public function log() ;
}

and 2 classes that implement that interface:

class Syslogger implements Loggable
{
    public function log()
    {
        ...
    }
}

class QMSLogger implements Loggable
{
    public function log($queueName)
    {
        ...
    }
}

The only way I could come with is having an array as a parameter and use it on one class and not using on the other one... but that is a little bit smelly :P

3
  • 7
    If the method accepts a parameter in one case and not in another, it's not the same interface! Commented Dec 20, 2012 at 10:41
  • Yes I suppose this is the more correct way but I wanted to be able to inject either class to be able to change the logging system... I guess I was wrong Commented Dec 20, 2012 at 10:42
  • 2
    Well that's actually possible in case $queueName defaults to NULL (public function log($queueName = null) {...}) but I agree with deceze here. Commented Dec 20, 2012 at 10:44

4 Answers 4

25

You're asking if it's possible: yes it is, but…

If you implement an interface, you must respect its contract.

interface Loggable
{
    public function log();
}

This interface's contract is you can call log() without any parameter.

In order to respect that, you can make the parameter optional:

class QMSLogger implements Loggable
{
    public function log($queueName = null)
    {
        ...
    }
}

This is perfectly valid PHP and it respects the Liskov Substitution Principle. Of course, you must not use that optional parameter when coding against the interface, else you are obviously breaking the interface. Such parameter can be useful only when you are using the implementation (e.g. in some part of the code which is tightly coupled to the QMSLogger).

However this is probably not the solution to your problem as $queueName seems to be a configuration value and it might be better to pass it in the class' constructor (as explained in the other answer).

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

Comments

24

As stated in the comments, that's not the same interface. If you cannot generalize the interface across all possible logger implementations, make the configuration differences part of the instance constructor:

class QMSLogger implements Loggable {

    protected $queueName;

    public function __construct($queueName) {
        $this->queueName = $queueName;
    }

    public function log() {
        ...
    }

}

Comments

14

I came across a similar case where I wanted to create an interface that simply ensure any classes that implemented it would have a method of the same name, but would allow for implementation with different parameters.

/**
 * Interface Loggable
 * @method log
 */
interface Loggable
{

}

Now the Loggable interface can be implemented with different parameters like so.

class Syslogger implements Loggable
{
    public function log($key, $value)
    {
        ...
    }
}

1 Comment

This is actually the best answer (IMHO). This type of pattern is known as duck typing. It's ideally used for "auto-wiring" during instantiation routines in IoC containers and service managers. The empty interface simply tells the container to call a specific method after instantiation, while letting the dependency injector (DI) determine the parameters to inject via type hinting. Technically, the empty interface is optional, but it's good practice to help prevent the DI container from calling like-named methods in third-party libraries.
1

You can also pass the parameters as an array , in this way you respect the contract from one hand and also be flexible to insert any values with any amount inside the array , check this out :

abstract class FeaturesAbstract
{
    /**
     * @param array $paramsArray
     *
     * @return mixed
     */
    abstract public function addExecute($paramsArray);
}

And to actually use this method you could send the parameters like this :

$this->abstract->addExecute(array('paramA' => $paramA, 'paramB' =>  $paramB));

And then inside the concrete implementation you get the parameters like this :

    /**
     * @param array $paramsArray
     *
     * @return void
     */
    public function addExecute($paramsArray) 
    {
        $a = $paramsArray['paramA'];
        $b = $paramsArray['paramB'];
        $c = ...
    }

Good luck :)

Comments

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.