Prototypical Inheritance

Christopher Pitt
5 min readJun 4, 2014

--

Inspired by a recent Reddit post, I decided to try and simulate JavaScript-like prototypical inheritance. Not because I think it should ever be used, in production. It’s just something I thought would be fun to try.

Turns out it was! Here’s how I did it:

Construction

Prototypical inheritance is far more fluid than classical Object-oriented design. The way it is implemented (in JavaScript) means you can’t depend on any methods or properties being there all-the-time. This leads to objects being described far more, during the construction phase, than in classical Object-oriented design.

In classical object-oriented design, you’re more likely to see something along the lines of:

class Shape
{
protected $name = "Shape";

public function getType()
{
return $this->name;
}
}

class Square extends Shape
{
protected $name = "Square";
}

$square = new Square();

print $square; // "Square"

Whereas, you’re far more likely to encounter this kind of construction, in prototypical environments:

$shape = new Prototype([
"name" => "Shape",
"getType" => function () {
return $this->name;
}
]);

$square = new Prototype([
"extends" => $shape,
"name" => "Square"
]);

print $square->getType(); // "Square"

Some of you may identify this pseudo-OOP design (applied to a prototypical language) as something out of the camp of MooTools or Prototype (JS). Those were my introduction to building reusable JavaScript components. Their current relevance is a topic for another day!

That’s not to say that prototypical inheritance languages feature an extends keyword or that they implement their inheritance constructs like this. I guess what I am trying to say is that objects (in prototypical inheritance languages) are far more transient or mutable.

The second example is indicative of the direction I decide to take, when building this little class.

Without Inheritance

The simplest way to begin creating this class, is to build it without inheritance. That narrows the requirements to the following:

  • New prototypes can be initialised with an array of members.
  • Needs dynamic members (methods and properties). After initialisation, you should be able to add members and have them returned/invoked when called again.
  • Closure properties (ostensibly methods) need to be bound to the correct context (the prototype instance).

Let’s begin with the first requirement:

class Prototype
{
/**
* @var array
*/
protected $members = [];

/**
* @param array $prototype
*/
public function __construct(array $prototype = [])
{
foreach ($prototype as $key => $value) {
$this->$key = $value;
}
}
}

We can contain the members in a protected property. There’s a disconnect between how we store them ($members) and how we set them ($this->$key). This can be resolved by defining a __set method:

/**
* @param string $property
* @param mixed $value
*/
public function __set($property, $value)
{
$this->members[$property] = $value;
}

Right! Now we can define new prototypes like this:

$shape = new Prototype([
"name" => "Shape",
"getType" => function () {
return $this->name;
}
]);

In order to pull these members out of the prototype, we need to define a __get method:

/**
* @param $property
*
* @return mixed
*/
public function __get($property)
{
if ($this->__isDefined($property)) {
return $this->members[$property];
}
}

/**
* @param string $property
*
* @return bool
*/
public function __isDefined($property)
{
return isset($this->members[$property]);
}

So now we can set and get members out of the prototype. How about being able to invoke member closures?

/**
* @param string $method
* @param mixed $parameters
*
* @return mixed
*/
public function __call($method, $parameters)
{
if ($this->__isDefined($method)
and $this->__isCallable($method)) {
return call_user_func_array(
$this->__getBoundClosure($method),
$parameters
);
}
}

/**
* @param string $method
*
* @return bool
*/
public function __isCallable($method)
{
return $this->members[$method] instanceof Closure;
}

/**
* @param string $method
* @param mixed $context
*
* @return mixed
*/
public function __getBoundClosure($method, $context = null)
{
if ($context === null) {
$context = $this;
}

$closure = $this->members[$method];
return $closure->bindTo($context);
}

We reuse the method for making sure the member is defined, and we add another to check whether it’s callable. If these conditions pass, we get the closure out of the members array and bind it to the provided (default: $this) context. Finally we invoke it.

That allows us do the following:

$shape = new Prototype([
"getType" => function () {
return "Shape";
}
]);

$shape->getType; // Closure

$shape->name = "I am a Shape";
$shape->getType = function () {
return $this->name;
};

$shape->getType(); // "I am a Shape"

With Inheritance

So what needs to change to allow for inheritance? Well (for single-inheritance) we need to get things from the parent prototype, if they weren’t fulfilled by the child prototype:

  • Properties, which aren’t defined for the child prototype, should be brought forward from the parent prototype.
  • Methods, which aren’t defined for the child prototype, should be invoked (in the context of the child prototype) from the parent prototype.

First, we modify the __get method:

/**
* @param $property
*
* @return mixed
*/
public function __get($property)
{
if ($this->__isDefined($property)) {
return $this->members[$property];
}

if ($this->__isDefined("extends")) {
return $this->extends->$property;
}

}

…and then we modify the __call method:

/**
* @param string $method
* @param mixed $parameters
*
* @return mixed
*/
public function __call($method, $parameters)
{
if ($this->__isDefined($method)
and $this->__isCallable($method)) {
return call_user_func_array(
$this->__getBoundClosure($method),
$parameters
);
}

if ($this->__isDefined("extends")) {
return call_user_func_array(
$this->extends->__getBoundClosure($method, $this),
$parameters
);
}

}

This fulfils the requirements of single-inheritance, and allows us to do the following:

$shape = new Prototype([
"name" => "Shape",
"getType" => function () {
return $this->name;
}
]);

print $shape; // "Shape"

$square = new Prototype([
"extends" => $shape,
"name" => "Square"
]);

print $square; // "Square"

$shape->label = "A simple shape";

$square->getLabel = function () {
return $this->label;
};

print $square->getLabel(); // "A simple shape"

Disclaimers

Again; don’t use this in production. It’s an experiment, not your next library/framework/platform.

There are ways in which this implementation falls short of the JavaScript implementation. You should be able to do things like $shape() to make a new instance of the Shape prototype, and yet I have not implemented that. Try clone($shape) if you want, but that won’t transfer prototype changes between clones when they are made on the original prototype (after cloning).

Have fun with this sort of thing! Don’t take it, or yourself so seriously that you forget the motivation behind this (that is to learn/discuss) and start raging me.

--

--