SCENARIO
I am implementing "command pattern"-like classes. Since they are so similar to each other in so many sense (e.g. functionally, their relation in domain problem, etc.), it is desirable to enforce them having similar structure.
Let us use an anecdotal example Merger
.
class Merger {
/* ... details are omitted ... */
public function execute (self $rval): self
{
// ... merge $rval to $this
return $this;
}
/* ... details are omitted ... */
}
Then we have another example Filter
class Filter {
/* ... details are omitted ... */
public function execute (self $rval): self
{
// apply filters from $rval to $this
return $this;
}
/* ... details are omitted ... */
}
There are many other class that implements public function execute (self $param): self
.
PROBLEM
I attempt to extract public function execute (self $param): self
to its own structure then have other class which implements that function to somehow derive from such structure.
The self
part from the method signature is slightly problematic: I want self
to refer the class which implements the function execute(self $param)
.
ATTEMPTED SOLUTION
(a) Interface
My attempt of using interface did not work because self
points to the interface e.g.
interface IExecute {
public function execute (self $param): self;
}
class Merger implements IExecute {
/* ... details are omitted ... */
public function execute (self $param): self
{
// ... merge $param to $this
return $this;
}
/* ... details are omitted ... */
}
$merger = new Merger();
/**
* Output:
* <b>Fatal error</b>: Declaration of Merger::execute(Merger $param): Merger must
* be compatible with IExecute::execute(IExecute $param): IExecute
* in <b>[...][...]</b> on line <b>7</b><br />
*
* PHP complains that Merger::execute signature is incorrect because $param
* is of type IExecute + its return type is Merger instead of IExecute.
*
* I want $param to be a Merger
*/
(b) Inheritance
Using inheritance yields the same result as using interface i.e. PHP blasts error complaining how the method signature is incompatible.
class Executor {
public function execute (self $param): self
{}
}
class Merger extends Executor {
/* ... details are omitted ... */
public function execute (self $param): self
{
// ... merge $param to $this
return $this;
}
/* ... details are omitted ... */
}
$merger = new Merger();
/**
* Output:
* <b>Fatal error</b>: Declaration of Merger::execute(Merger $param): Merger must
* be compatible with Executor::execute(Executor $param): Executor
* in <b>[...][...]</b> on line <b>18</b><br />
*
* $param in Merger::execute has type of Merger instead of Executor
*
* I want $param in Merger::execute to be a Merger, not Executor
*/
(c) Trait
One approach that I come up with (and works as I intended to) is by using trait
.
trait Executor {
abstract public function execute (self $self): self;
}
Then have the classes to use that trait.
class Merger {
use Executor;
/* ... details are omitted ... */
public function execute (self $rval): self
{
// ... merge $rval to $this
return $this;
}
/* ... details are omitted ... */
}
This works perfectly well since self
points to the class that uses the trait Executor
.
GOAL
trait
does what I want, though having to use use <trait name here>
is a slight mental overhead i.e. the developer has to know that they have to use trait
to enforce class structure (instead of, for example, have a base class which is more intuitive IMO).
This finally brings to my question: is there a better approach than using trait
to handle this issue? Preferably the one that has more emphasis on enforcing a structure intuitively
I am attracted to the solution provided from this SO post (solution is on poster's question), though it is on C#. Kind of pity that one has to use a work around to do generic typing in PHP
EDIT 16/12/2019: I decided to mark Andy's answer as the correct one, though I'm still hoping another answerer will post another solution (or rather, another take on explaining why no other solution exists at the time of answering)
return $this;
statements, and change those return types tovoid
. You don't need to return the$this
reference to the calling function because the caller already has it. The interface can't specify that the returned value will reference the same object that was called, even if it specifies the exact class.self
type is that the class will act as a builder. Plus the point that many classes share common interface (I'm not saying that the only common thing the classes have is the interface, though. That's why I want to take out the common interface to its own structure) You have a good point, I don't really need toreturn $this
. In fact I can just stash my plan extracting the common interface (which I have considered greatly), however for the sake of the question let us assume that this requirement cannot be compromised.