Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Practical PHP Patterns: Layer Supertype

DZone 's Guide to

Practical PHP Patterns: Layer Supertype

· Web Dev Zone ·
Free Resource

A common situation where duplication of code seems a solution is when objects in a layer have some common traits of behavior, which should be effectively shared between them.

We are talking for example of duplicated methods and properties, whose physical duplication is not only a waste of space (not important in these days of cheap storage) but also a waste of complexity and developers time. Duplicated code must be kept synchronized in all its copies, and distracts whoever reads a sourcefile from the peculiarity of a class.

One of the simplest, and to a certain extent effective, solution to this issue is to introduce a Layer Supertype to avoid duplication, and move all the shared code in it. Then, having the Layer Supertype subclassed by the various classes to inherit the common members, which remain defined only one time in the Layer Supertype itself. But introducing a base class isn't always a nice solution.

First of all, note that more than one kind of object in a Layer means more than one Layer Supertype. By no means every controller or every helper should inherit from the same class. Due to the dependencies towards frameworks, this principle is not applied very often, and trying to fit every piece of behavior into a single base class leads to the next problem.

In fact, you should beware of always growing base abstract classes, which act as a catch-all for common pieces of functionality. Here is why:

  • you can't test in isolation a base abstract class, without introducing some kind of fake subclass. You can't test any subtype in isolation from its superclass either. However, with respect to composition, there is often no clear contract between a base class and its client subclasses, whereas the composition contract can be defined by a construct naturally supported by the language, an interface.
  • Base classes have the tendency to contain features that are required only by some subclasses: trying to solve this problem leads in turn to more intermediate subclasses and large hierarchies, which render difficult understanding where a called method is defined and what are the relationships between the various members defined in different consanguineous classes. There is some encapsulation available (private scope for fields and methods), but it is usually ignored to favor the protected scope for the sake of extensibility. But here I say it: if you want real extensibility, you should make the effort of thinking about composition of small classes and interfaces, not simply changing the scope of a bunch of methods which were first encapsulated for a reason.

What to factor out in a superclass

I see the Layer Supertype pattern as the ideal place for wiring code and small business logic.

A common example of Layer Supertype you may encounter in my applications is one that contains a constructor that stores the collaborators, when the various subtypes have all the same dependencies. This shared constructor makes building a factory via magic/reflective code very easy.

However, this remains a very basic pattern, often abused as an overuse of inheritance; even if you have only one level of supertypes (one abstract class that is implemented by any concrete one) you're not really safe, since it may grow to a giant superclass like the infamous Doctrine_Record.

Examples

The first code sample is taken example from Scisr, an automated refactoring tool for PHP which I helped refactoring this summer.

In Scisr, different operations are available for automated refactoring, such as Rename Class, Renamed Method, Rename File, and so on. Each of this is modelled as a class.

However, most of the operation classes only track particular tokens encountered by PHP_CodeSniffer when parsing the file, with the goal of inserting in a temporary database. As a result, the classes in this subset take the same collaborators (a DAO for a Sqlite database that caches the various informations).

The abstract class is a simple Layer Supertype that contains the constructor and the protected fields, and makes them available to each Operation class. This solution has provided Dependency Injection, at the same time eliminating duplication, still without introducing a too smart superclass.

<?php

abstract class Scisr_Operations_AbstractVariableTypeOperation
    implements PHP_CodeSniffer_Sniff
{
    protected $_variableTypes;

    public function __construct(Scisr_Operations_VariableTypes $variableTypes)
    {
        $this->_variableTypes = $variableTypes;
    }
}
<?php

/**
 * This class concentrates the wiring code for the ChangeRegistry instance needed 
 * in some Operations.
 *
 * @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
 */
abstract class Scisr_Operations_AbstractChangeOperation
{
    protected $_changeRegistry;

    public function __construct(Scisr_ChangeRegistry $changeRegistry)
    {
        $this->_changeRegistry = $changeRegistry;
    }
}

The second code sample is taken from Doctrine 2, the object-relational mapper for PHP.

There are various types for object fields metadata in Doctrine 2, that supports the various object fields-database columns couples and provide conversion methods for the contained values. For example, a date column could be converted to a Date object when it is handled in the PHP side, and to a simple string when inserted in the database.

Here the Layer Supertype is shown in an uncommon implementation, since it is aware of all its subclasses and has a static Factory Method that implements a Flyweight pattern. No more than a single object for each of the types is instantiated, and the naturally shared location to place the creation method was in the static scope of the Layer Supertype.

I'm ok with this Singleton-ness since the types have little behavior (two functions for the value conversion back and forth from the database to PHP higher-abstraction variables), no state shared between their clients, and certainly no collaborators.

namespace Doctrine\DBAL\Types;

use Doctrine\DBAL\Platforms\AbstractPlatform,
    Doctrine\DBAL\DBALException;

/**
 * The base class for so-called Doctrine mapping types.
 *
 * A Type object is obtained by calling the static {@link getType()} method.
 *
 * @author Roman Borschel <roman@code-factory.org>
 * @since 2.0
 */
abstract class Type
{
    const TARRAY = 'array';
    const BIGINT = 'bigint';
    const BOOLEAN = 'boolean';
    const DATETIME = 'datetime';
    const DATETIMETZ = 'datetimetz';
    const DATE = 'date';
    const TIME = 'time';
    const DECIMAL = 'decimal';
    const INTEGER = 'integer';
    const OBJECT = 'object';
    const SMALLINT = 'smallint';
    const STRING = 'string';
    const TEXT = 'text';

    /** Map of already instantiated type objects. One instance per type (flyweight). */
    private static $_typeObjects = array();

    /** The map of supported doctrine mapping types. */
    private static $_typesMap = array(
        self::TARRAY => 'Doctrine\DBAL\Types\ArrayType',
        self::OBJECT => 'Doctrine\DBAL\Types\ObjectType',
        self::BOOLEAN => 'Doctrine\DBAL\Types\BooleanType',
        self::INTEGER => 'Doctrine\DBAL\Types\IntegerType',
        self::SMALLINT => 'Doctrine\DBAL\Types\SmallIntType',
        self::BIGINT => 'Doctrine\DBAL\Types\BigIntType',
        self::STRING => 'Doctrine\DBAL\Types\StringType',
        self::TEXT => 'Doctrine\DBAL\Types\TextType',
        self::DATETIME => 'Doctrine\DBAL\Types\DateTimeType',
        self::DATETIMETZ => 'Doctrine\DBAL\Types\DateTimeTzType',
        self::DATE => 'Doctrine\DBAL\Types\DateType',
        self::TIME => 'Doctrine\DBAL\Types\TimeType',
        self::DECIMAL => 'Doctrine\DBAL\Types\DecimalType'
    );

    /* Prevent instantiation and force use of the factory method. */
    final private function __construct() {}

    /**
     * Factory method to create type instances.
     * Type instances are implemented as flyweights.
     *
     * @static
     * @throws DBALException
     * @param string $name The name of the type (as returned by getName()).
     * @return Doctrine\DBAL\Types\Type
     */
    public static function getType($name)
    {
        if ( ! isset(self::$_typeObjects[$name])) {
            if ( ! isset(self::$_typesMap[$name])) {
                throw DBALException::unknownColumnType($name);
            }
            self::$_typeObjects[$name] = new self::$_typesMap[$name]();
        }

        return self::$_typeObjects[$name];
    }

    /**
     * Adds a custom type to the type map.
     *
     * @static
     * @param string $name Name of the type. This should correspond to what getName() returns.
     * @param string $className The class name of the custom type.
     * @throws DBALException
     */
    public static function addType($name, $className)
    {
        if (isset(self::$_typesMap[$name])) {
            throw DBALException::typeExists($name);
        }

        self::$_typesMap[$name] = $className;
    }
}
Topics:

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}