This blog post has first been published in the Qafoo blog and is duplicated here since I wrote it or participated in writing it.
Cover photo for post Struct classes in PHP

Struct classes in PHP

PHP arrays are a wonderful tool and one of the reasons I like PHP. Their versatility makes it possible to easily set up proof of concepts (POC), either used as hash maps storing multiple keys, or as lists, stacks, trees or whatever you like.

But once you are past the phase of the initial POC, the excessive usage of arrays and exactly their versatility has some drawbacks: If you see an array type hint or return documentation, you know nearly nothing about the data structure. Using arrays as key-value hash maps for storing configuration keys or data sets you also know nearly nothing about the expected contents of the array.

This is no problem during the initial implementation, but can become a problem during maintenance - it might not be trivial to find out what the array contains or is supposed to contain (without dumping it). There are no common ways to document such array structures nor you get auto-completion from common IDEs. If such a hash map is filled with data in different locations in your application it even gets worse. Also, mistyping a key - wether on read or write - creates a serious debugging hell.

In Apache Zeta Components and in several of my own projects we are using - so called - struct classes to solve this issue: The struct classes do not define any methods but just contain documented properties. They just deal as a data container, similar to a hash map.

There are several benefits and one drawback using this approach. The benefits:

  • Struct classes are far easier to document

  • Your IDE can provide you with correct auto-completion

  • Your IDE even knows the type of each child in a struct allowing you to create and process deeply nested structures correctly

  • You can be sure which properties a passed struct has - no need to check the availability of each property on access

  • Structs can throw exceptions access to non-existent properties

The drawback:

  • The structs are objects, which means they are passed by reference. This can be an issue if you are operating on those structs. I will show an example later.

Implementation

To see what I am talking about let's take a look at a example base class for structs:

<?php abstract class Struct { public function __get( $property ) { throw new RuntimeException( 'Trying to get non-existing property ' . $property ); } public function __set( $property, $value ) { throw new RuntimeException( 'Trying to set non-existing property ' . $property ); } }

In a struct base class you can implement __get() and __set() so they throw an exception if an unknown property is accessed. For me PHPs behavior of silently creating public properties on property write access caused quite some irritations over time. A typo in a property name and your code does strange things. I like to get a warning or (even better) an exception for that. Now, let's take a look at a concrete struct:

<?php class LocationStruct extends Struct { /** * @var string */ public $city; /** * @var string */ public $country; public function __construct( $city = null, $country = null ) { $this->city = $city; $this->country = $country; } }

The LocationStruct has two documented, public properties. Each one, of course, could be a struct again. If the LocationStruct is used as a type hint somewhere in your application or library you now know exactly what data is expected and can create a it comfortable, supported by your favorite IDE. The definition of a constructor is really helpful to easily create new struct instances.

Extending the base struct

There are some sensible extension you probably want to use for the base struct: As mentioned before the structs are passed by reference, which is not always what you want. You therefore probably want to implement __clone() in a sensible way, generically for all your structs:

<?php abstract class Struct { // … public function __clone() { foreach ( $this as $property => $value ) { if ( is_object( $value ) ) { $this->$property = clone $value; } } } }

Another functionality you might want to implement, and a good use case of late static binding (LSB) in PHP 5.3, is the __set_state() method, so you can export your struct using var_export() just like arrays:

<?php abstract class Struct { // … public static function __set_state( array $properties ) { $struct = new static(); foreach ( $properties as $property => $value ) { $this->$property = $value; } return $struct; } }

If you are using __set_state() to ex- and import structs in your application, this is a good reason to define sensible default values for all constructor arguments.

Copy on write

As mentioned before, one problem with this usage of struct classes is that they are always passed by reference. It is not entirely obvious why this would be a problem, but it already caught me some times, so here is a example.

In the Graph component from the Apache Zeta Components we, for example, use a struct class to represent coordinates (ezcGraphCoordinate). Obviously there are quite some calculations to perform when rendering (beautiful) charts.

Now imagine you want to draw a set of circles at increasing offsets:

$offset = new ezcGraphCoordinate( 42, 23 ); for ( $i = 0; $i < $shapeCount; ++$i ) { $driver->drawCircle( $coordinate, 10 ); $offset->x += 15; }

The drawCircle() method now might perform additional calculation on the passed coordinate, for example, because the currently used driver does not use the center point, but the top left edge of the circle as a drawing offset. In this case the method might internally modify the coordinate and thus the offset in the shown loop would also be modified. Hopefully you got tests for this in place and therefor add a $offset = clone $offset in the drawCircle() method. This hit me very seldomly until now, but it might be an issue you should be aware of when using struct classes.

Summary

Even requiring slightly more work when writing software, the benefit of struct classes during the maintenance phase of projects makes them a true winner - in my personal opinion.

For POCs I tend to still use arrays for structs, but once the software reaches production quality I tend to convert array structs into struct classes since some time in the software I write / maintain.

In C#, for example, such struct classes are a language element and differ from common object exactly in the copy-on-write vs. pass-by-reference behaviour mentioned in this post. I would love to see that in PHP but my knowledge of the Zend Engine is limited and maybe I should bribe a more experienced PHP internals developer…

Final note

There are other ways to implement struct classes, like using a properties array instead of public properties, which enable you to perform type checks on property write access. Those might be discussed in another blog post but would exceed the purpose of this blog post.