Here's the situation: I've got several distinct objects, each with their own responsability and (therefore) each their own dependencies. As this is code that will be implemented in 2 existing applications, I've created my own namespaces, interfaces, abstract classes and so on... the works, basically.
IMO, the objects' inheritance chains are pretty tidy, and provide a solid base structure. There is but one thing that's really "pissing me off", though. The fact that abstract methods don't allow for covariance at all. I'm using 2 abstract classes to force certain methods to occur in the child classes (like an init
method, which is called from the abstract class).
Now as I said, each class has its own responsability, and I want to be able to specifically hint for a specific dependency at the childs level, and still enforce that method using the abstract class's restrictions. Especially since some of these methods shouldn't be public (thus ruling out interfaces, at any rate).
Here's a basic example:
abstract class Base
{
protected $foo = null;
final public function __construct(Granny $init = null)
{
return $this->init($init);
}
abstract protected function init(Granny $dependency = null);
}
So I've made the constructor final, and Type-hinted Granny
which, as the name suggest, is the base class for the Base
instance's dependencies.
Suppose Granny
has a child: Dad
, and the classes look like this:
class Granny
{
protected $name = null;
protected $age = null;
public function __construct(array $vals = null)
{
foreach($vals as $name => $val)
{
$name = 'set'.ucfirst($name);
if (method_exists($this, $name))
{
$this->{$name}($val);
}
}
return $this;
}
//basic gettter && setters
public function getAge()
{
return $this->age;
}
public function setAge($age = null)
{
$this->age = $age === null ? null : (int) $age;
return $this;
}
}
class Dad extends Granny
{
//Dad's secret
private $likesMom = null;
public function getLikesMom()
{
return $this->likesMom;
}
public setLikesMom($bool = null)
{
$this->likesMom = $bool === null ? null : !!$bool;
return $this;
}
}
Now I've yet to see anyone play baseball with their grand mother, so The Ball
class, which extends Base
, depends on an instance of Dad
, which I'll pass to the constructor (Base
hints Granny
, so it'll accept Dad
, too). That dependency will be passed on to the init
method, but to be sure the Base::Ball
instance receives the correct dependency, I'd like to declare it like this:
class Ball extends Base
{
protected function init(Dad $dependency = null)
{
$this->foo = $dependency;
return $this;
}
}
Which is where it all comes tumbling down with a fatal error, because the signatures don't match. Which, IMO, is inconsistent behaviour (since the final public function __construct(Granny $foo = null)
doesn't complain, so I can't but pass an instance of Granny
)
Anyway I know the code above is said to "violate the contract", and I still maintain it's utter bull, but I've ended up doing this:
//in Dad:
protected function init(Granny $dad = null)
{
return $this->setDad($dad);
}
//this was already defined => for DI
public function setDad(Dad $dependency = null)
{
$this->foo = $dependency;
return $this;
}
Just because it's more in-tune with how my code works than the, presumably, more performant:
if ($dad !== null && !$dad instanceof Dad)
{
throw new InvalidArgumentException('You\'ll have to ask Dad to play catch');
}
$this->foo = $dad;
return $this;
But what I really want to know is: are there any other options I'm not seeing here? I'd love it if there were a sollution that allowed for covariancy, but after some googling, it's not looking good. I've even come to understand that covariant type-hints are deemed unimportant and won't be supported in the (near) future in PHP. They seem to find it more important to add return-type-hints to the method signatures, though...
Granny
instead of a higher level object/interface that would be common to all dependencies? Why wouldBall
derive from a class that's dependent onGranny
? It seems to me you haven't abstracted it enough. \$\endgroup\$instanceof
to verify, but I dislike that personally. Or don't make the base class abstract, and instead throw an exception ifinit
is called fromBase
. I personally wouldn't worry about someone instantiatingBase
. pastebin.com/6zmUruZm \$\endgroup\$