Our Blog

0

Being able to extend classes is the key to powerful programming. PHP provides you all the tools you need to obtain rich and extendible classes. But sometimes you find yourself using the wrong tool.. Look at this very simple class:

class Post {
    const TABLE = 'posts';
    const TYPE = 'post';

    static public function all() {
        $select = "SELECT * FROM `%s` WHERE `type` = '%s'\n";
        echo sprintf($select, self::TABLE, self::TYPE);
    }
}

Post::all();
// SELECT * FROM `posts` WHERE `type` = 'post'

Look at the class constants TABLE and TYPE and you can almost feel what our next subclass will look like, and how minimalistic it will be:

class Comment extends Post {
    const TYPE = 'comment';
}

Comment::all();
// SELECT * FROM `posts` WHERE `type` = 'post'
// ^ is not what we meant...

Though the intentions are good, the result is unwanted: we want to select all from posts where type is ‘comment’, not ‘post’.
That is the blame for using ‘self’ to obtain the class constants. ‘self’ Refers to current class it is defined in, which is not the inherited class. But we don’t want to rewrite all the methods – that misses the point of all this. Using ‘static public $TABLE’ as property is an option, but why not use class constants now that PHP provides them.

A lot of people use ‘self’ to refer to class constants or static methods, but as soon as you start inheriting such class (often classes are coded without the intention to inherit them at some stage), this issue comes up.

We will have to use ‘late static binding’ in stead of ‘self’, simply by using ‘static’, which is computed using runtime information. Now take a look at the next example:

class Post {
    const TABLE = 'posts';
    const TYPE = 'post';

    static public function all() {
        $select = "SELECT * FROM `%s` WHERE `type` = '%s'\n";
        echo sprintf($select, static::TABLE, static::TYPE);
    }
}

Post::all();
// SELECT * FROM `posts` WHERE `type` = 'post'


class Comment extends Post {
    const TYPE = 'comment';
}

Comment::all();
// SELECT * FROM `posts` WHERE `type` = 'comment'
// ^ now that's just what we want

Now we’re selecting all from posts where type is ‘comment’!
And if you explicitly want to call the class you inherited from (perhaps in a constructor for doing some logics) you use ‘parent’ in stead, as here on the ‘save’-method:

class Post {
    static public function create() {
        return new static();
    }
    public function save() {
        echo "Inserts into the `posts` table\n";
    }
}

class Chat extends Post {
    public function save() {
        parent::save();
        echo "Inserts into the `post_recipient` table\n";
    }
}

Chat::create()->save();
// Inserts into the `posts` table
// Inserts into the `post_recipient` table