I’m reading this book called Clean Code, by Robert Martin. In it he talks about the difference between Data Structures and Objects. Data Structures (sometimes called Data Transfer Objects) are described as “a class with public variables and no functions”.

While I was reading this, I was considering an API I am currently building. The endpoints accept a JSON body as input, decoding them and using their various structures to filter, manipulate and generally affect the database and return data.

The problem is that I am constantly passing around hash maps (resembling how one might use Data Structures) but, due to the weakly typed nature of PHP, have to constantly validate the data flying around. Naturally, this leads of often-repeated code or adding behaviour to the Data Structures (thereby making them what Martin calls “Hybrids”).

This got me wondering how I could enforce the types of the data structure properties, so that they could be set once and forever maintain their type. I think I found a solution which is clear in intent:

abstract class AbstractStructure {

public function __set($property, $value) {

$this->throwUnlessPropertyExists($property);

$this->throwUnlessCorrectType($property, $value);

$this->throwUnlessCorrectSubclass($property, $value);



$this->{$property} = $value;

}



public function __get($property) {

$this->throwUnlessPropertyExists($property);



return $this->{$property};

}



private function throwUnlessPropertyExists($property) {

if (property_exists($this, $property) !== true) {

throw new RuntimeException();

}

}



private function throwUnlessCorrectType($property, $value) {

$typeUsedToBe = $this->getTypeUsedToBe($property);



if ($typeUsedToBe !== "null") {

$typeWantsToBe = $this->getTypeWantsToBe($value);



if ($typeUsedToBe !== $typeWantsToBe) {

throw new RuntimeException();

}

}

}



private function getTypeUsedToBe($property) {

return $this->getTypeOf($this->{$property});

}



private function getTypeWantsToBe($value) {

return $this->getTypeOf($value);

}



private function getTypeOf($value) {

return strtolower(gettype($value));

}



private function throwUnlessCorrectSubclass(

$property,

$value

) {

$typeUsedToBe = $this->getTypeUsedToBe($property);



if ($typeUsedToBe === "object") {

$classUsedToBe = $this->getClassUsedToBe($property);



if (!is_subclass_of($value, $classUsedToBe)) {

throw new RuntimeException();

}

}

}



private function getClassUsedToBe($property) {

return get_class($this->{$property});

}

}

Using this class is considerably easier than understanding what it does (not that you should have any trouble in that regard):

class ArticleStructure

extends AbstractStructure

{

protected $id;

protected $title;

protected $subtitle;

protected $body;

protected $person;

}



class PersonStructure

extends AbstractStructure

{

protected $id;

protected $name;

}



$article = new ArticleStructure();

$article->title = "foo";

$article->person = new PersonStructure();

There are two major differences between the Data Transfer Objects, which Martin describes, and the above class(es). The first is that the properties are not public. This is motivated by the second which is that there are a set of underlying methods which allow properties to “gain” a type once, and then enforce that type thereafter.

This is made possible by using __get() to check types and throw RuntimeException’s if the type differs (after initially being set). Properties typed as class instances are also validated, though they can be replaced with subclasses further down the inheritance chain.

The result is a structure which can be used to enforce the types of properties, without the noise of getters and setters. It doesn’t strictly violate the Law of Demeter because these structures aren’t strictly objects in the sense that they have no significant behaviour.

This is further re-enforced by the fact that the validation methods are made private so that they cannot be exposed or used in any other context.

I have a feeling this might reduce the amount of validation I need to do in my API…