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 ProcessResult
s).
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 **/ }