Skip to content
Advertisement

Can I implement an interface and override the return type of one of the interface’s methods?

I have a Task class that extends an abstract class, TaskBase.

// class Task
class Task extends TaskBase {
   /**
    * @var Processor
    */
    private $processor

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

    public function process() : ProcessResult
    {
        return  $this->processor->execute();
    }
}

// abstract class TaskBase

abstract class TaskBase implements CommanTask {
    protected function getKey(): string
    {
    }
}

This TaskBase implements an interface CommanTask, which contains the following method.

interface CommanTask {
  public function  process(): ProcessResult;
}

Now I need a new task class TaskMultiple, and it’s process() method needs to return an array of ProcessResult instead of one ProcessResult.

How can I extend abstract class TaskBase for this new TaskMultiple class?

Advertisement

Answer

If you implement an interface, you need to actually comply with the contract laid out by the interface.

The interface you propose says that process() returns a ProcessResult. If you could change that in an implementing class to returning an array, then consumers of the class wouldn’t be able to trust the contract specified by the interface.

They would try to use the result of TaskMultiple::process(), and since they interface says it would return a ProcessResult, a fatal error would soon happen when it tried to treat it as such (e.g. by accessing a method for that class), and it wasn’t that.

Your solutions are:

If you are on PHP 8, you could use union types:

interface Task {
  public function  process(): ProcessResult|iterable;
}

It would work, but it’s ugly. Now consumers of the any Task implementing service would need to check on the result of process() to see if it’s a single ProcessResult, or a collection (presumably of ProcessResults).

If you are on PHP < 8, you could make it work simply by removing the type hint:

interface Task {
  public function  process()
}

Now consumers would only know that there is a process() method, but you’d have no type information at all. Uglier still.

Better, simply create different interfaces like Task and MultipleTask:

interface Task {
  public function  process(): ProcessResult;
}


interface MultipleTask {
  public function  process(): iterable;
}

Since your BaseTask includes nothing to help implement the class (only includes an abstract method, making it already very similar to an interface), move the implements to each of the concrete task classes:

class ExampleTask extends BaseTask implements Task
{ /** implementation **/ }

class ExampleMultipleTask extends BaseTask implements MultipleTask
{ /** implementation **/ }
User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement