HiveBrain v1.2.0
Get Started
← Back to all entries
patternphpMinor

"fake" covariance in PHP type-hinting

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
phphintingtypecovariancefake

Problem

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

Solution

Ok, apparantly you are a professional programmer so let me look at your code first:

the __construct in the class Granny:

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;
}


First of, if another programmer needs to use your class he has NO IDEA how to use it. Yes, i need to add in an array of $vals. But this is just bad. I hope I don't have to explain why.. (code maintainability, readibility, ...)

then, return $this; Seriously? what else would a __construct return? void?

then: setAge($age=null). Why would I call setAge() withou passing in an age? And what is the meaning of 'null' not set?

Why are you using init() instead of simply using __construct()? Because know you are simply calling init() in the __construct() of the parent class. Whereas you could and should simply do it this way:

<?php

class Base {
    public function __construct(Granny $granny) {
        //do stuff
    }
}

Class Ball extends Base {
    public function __construct(Dad $dad) {
        //do stuff
        parent::__construct($dad);//let the parent do his magic
    }
}


Now Ball needs a spcial kind of Granny whereas Base only needs a Granny. If Dad is a Granny nothing is wrong, if Dad isn't a Granny the code will not work because $dad is not a Granny. Problem fixed.

But I bet there is some bewildered reason you didnt think of this easy 101 programming basics...

Maybe now I will have offended you, but be honest, you are doing some really weird things that will not help the guy that comes after you, or joins your project or wants to use your code.
Or maybe even yourself in 4 month when you come back to the Granny class. You then have no idea what the Granny class needs and what is optional. If it is optional leave it out of the construct. Makes testing a lot easier...

So apologies If I offended you, but saying you are a professional because you work 4 years doesnt say much. I know professionals that have been coding for 4 years and still return $this in a __construct()

OLD POST:

Wow, back to the drawing board! I don't think you understand inheritance at all.

Then fun thing about OO code is that Classes can allready tell you a lot by just looking at ho wthey are declared.
For isntance:

class Dog extends Mamal {}


Tells me that Dog is Mamal. And that Dog is more specific then Mamal.
consider the folowing corerct phrase:

A dog can walk, but not all Mamals can (Dolphins are mamals too)


Now, I have a German shepperd class:

class GermanShepperd extends Dog {}


Again this is logical and correct.

Now lets look at your code:

class Granny


By looking at it I would say the Class Granny is some kind of stand-alone or base class. Not really specific but very abstract since it doesn't extend something.

class Dad extends Granny


Aha, Dad is a special case of Granny. Lets look into the class what it can do extra: Ah it can like a Mom. So, a Dad is a Granny that can like a Mom.

Now, you are talking about 'abstract class restrictions'. If the abstract class is giving you restrictions then maybe the abstract class isn't abstract enough.
If my Mamal class needs legs to walk. Then my Dolphin with have some big troubles.

So, back to the drawing board it is. Coding isnt about tapping the keyboard and writing code. Coding is an art, and it requires a lot of think work before you code. Draw it on a piece of paper. A Granny is simply a Women that has grandchildren. Not some kind of exotic object that if it can like a Mom it suddenly becomes a Dad.

Lookup Solid It's a good place to start. Try and stick to the rules. And also, once in a while take a big step back and think 'is the thing I am doing now actually solving my problem?'. Keep it simple. If the problem is not easily solved, chop it into more problems that you can solve. Then past them togeter.

Code Snippets

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;
}
<?php

class Base {
    public function __construct(Granny $granny) {
        //do stuff
    }
}

Class Ball extends Base {
    public function __construct(Dad $dad) {
        //do stuff
        parent::__construct($dad);//let the parent do his magic
    }
}
class Dog extends Mamal {}
A dog can walk, but not all Mamals can (Dolphins are mamals too)
class GermanShepperd extends Dog {}

Context

StackExchange Code Review Q#28921, answer score: 7

Revisions (0)

No revisions yet.