July 29, 2017

A few thoughts about class properties, constructors, and stuff (Part 1)

Hi everyone. I want to share with you a few ideas I've put together. Let me know what you think about them and if you like the idea of me writing my crazy theories.

In all honesty, I've wrapped my head around some of the following ideas only recently; I have to admit I've never felt the need to actually think through my doing, as PHP fields are almost always only used privately.

But, PHP could soon (hopefully) get typed properties, and with the new, shiny possibility of making some of them public, learning how to design them properly becomes of great importance.

But let's start from the beginning. As you can likely already tell, this is pointless:


class A           { protected $bar; }
class B extends A { protected $bar; }

The field declaration in class B is not an actual declaration; the real declaration is the one in the class that defines the field first, i.e. class A. This sure doesn't hurt anyone, but for sure makes the reader confused. However, the syntax exists for a reason:


class A           { protected $bar; }
class B extends A { public $bar; }

This is a legitimate piece of code. The declaration of the field happens in class A but in class B the field is made more visible.

This leads us to the default values of class properties. Consider the case in which a property is to be made public in a child class, but without changing its initial value:


class A           { protected $bar = "abc"; }
class B extends A { public    $bar = parent::$bar; }
// Make the field public, but keep the value as it's defined in A
// For additional clarity: I'm not interested in changing the
// default value, I only want to make the field public.

This is not possible. The syntax parent::$var is not supported and I personally think is not necessary to have. In order to understand the reason I ask you to notice, if you didn't already in your experience, that fields' default values are effectively another constructor.


class A {
    public    $qux = "phase 1";
    protected $bar = "phase 1";
    protected $foo;

    function __construct(){
        // phase 2
        $this->foo = new Foo1();
    }
}

class B extends A {
    public    $baz = "phase 1";
    public    $bar = "phase 1, overwrites parent";

    function __construct(){
        // phase 2
        parent::__construct();
        $this->foo = new Foo2();
    }
}

When an object of class B is instantiated, two different "construction" phases take place.

The first "construction" phase consists in class A's properties evaluation (e.g. $bar), then the same happens for class B, which may overwrite the values previously set by A.

The second "construction" phase consists in the call of A::__construct and B::__construct.

I think this is bad for several reasons. First of all, the obvious sense of confusion that the two "constructors" bring to the reader. Properties should be all defined in one place, and that place is __construct. Check the much clearer:


class A {
    // declaration only:
    protected $bar;
    protected $foo;
    protected $qux;
    private   $lol;
    function __construct(Foo $foo){
        // definition only:
        $this->bar = "bar";
        $this->foo = $foo;
        $this->qux = new Qux();
        $this->lol = [];
    }
}

Another reason is that with the "additional constructor", __construct loses the role that its very name would suggest. If mixed up with the "additional constructor" its role can be only defined as "a function that does something on a already partly-initialized object".

By keeping declaration and definition separated, __construct could also gain the fully working functionality of reinitialization of an object. Compare this...


class MyArray {
    private $data = [];
    function __construct(Iterable $data){
        foreach($data){
            $this->data[] = $data;
        }
    }
    function reinitialize(Iterable $data){
        $this->data = [];
        $this->__construct($data);
    }
}

... to the nicer:


class MyArray {
    private $data;
    function __construct(Iterable $data){
        $this->data = [];
        foreach($data){
            $this->data[] = $data;
        }
    }
    function reinitialize(Iterable $data){
        $this->__construct($data);
    }
}

Ultimately, the reinitialize() method could be removed altogether, and __construct could be called directly and function perfectly fine:


class MyArray {
    private $data;
    function __construct(Iterable $data){
        $this->data = [];
        foreach($data){
            $this->data[] = $data;
        }
    }
}

$array = new MyArray([1,2,3,4]);
// ...
$array->__construct([5,6,7,8]);
// ...

It's ugly, I know. I'm just trying to demonstrate the advantages of this practice.

[End of Part 1, follow up tomorrow]

2 comments:

  1. Very interesting. Something we should all do.

    ReplyDelete
  2. A good advice. This seems so obvious now that I see it.

    Matt Cohen‏

    ReplyDelete

OOP question #1 (encapsulation, coupling)

Scenario: I have an object A that has some mutator methods, and I have another object B that uses A, and that requires exclusive access to i...