Menu

Home

James Watts

PHP Design by Contract

PHP Design by Contract provides a basic implementation of contract programming for PHP 5.3+ with namespaces.

The base Contract class allows new or existing classes to define properties as protected Attributes and methods as Routines, which require argument type/class validation, aswell as PreCondition and PostCondition checks. Instances can also test for state consistency with an invariant check.

Installation

To use the package it's highly recommended to use a PSR-0 compatible autoloader for required classes.

You can find a fully compatible autoloader here: PHP AutoLoad

Once you've unpacked the files into your include path the package is ready for use.

Configuration

This package does not require any pre-configuration.

Implementation

To use the base Contract class simply extend it with a new or existing class, for example:

class Person extends Contract {

    // class members are defined here
}



When extending the Contract class certain magic methods become available. The first of these methods is __create( void ). This method behaves as a constructor function for the instance object.

public function __create()
{
    // object is defined here
}



To define the Attributes (properties) and Routines (methods) available for this object there are 2 additional magic methods, __attribute( Attribute $attribute ) and __routine( Routine $routine ).

The Attribute class defines a property for the object, using setName( string $name ) for the name of the property, setType( int $type ) for the data type stored in the property, and setValue( mixed $value ) for the default value of the property, for example:

$attribute = new Attribute();
$attribute->setName( 'example' );
$attribute->setType( DataTypes::TYPE_STRING );
$attribute->setValue( 'Hello World' );



Only the routine of the same object can modify an attribute. If a routine of another object attempts to modify an attribute an IllegalAttributeAccessException will be thrown. Additionally, if the data type of the value being set to the attribute is not the same as the type defined by the attribute an InvalidAttributeTypeException will be thrown.

Once the attribute has been defined it can be registered on the object.

$this->__attribute( $attribute );



The DataTypes class defines the data types available as the following constants:

  • TYPE_NULL: A discriminated null value
  • TYPE_BOOLEAN: A boolean logical value
  • TYPE_NUMERIC: A valid numeric value
  • TYPE_INTEGER: A whole number
  • TYPE_DOUBLE: A double precision floating point number
  • TYPE_STRING: A string of characters
  • TYPE_ARRAY: An array of values (keys permitted)
  • TYPE_OBJECT: An object
  • TYPE_LAMBDA: An instance of Closure
  • TYPE_RESOURCE: An external PHP resource

A routine can be either a procedure or a function. The difference between these types is that a procedure is used to modify the state of an object, but does not return a value, whilst a function is used to access the object and return a value, but not modify it's state. This concept is called command/query spearation.

The Routine class defines a method for the object, using setName( string $name ) for the name of the method, setType( int $type ) for the type of the routine, setArguments( array $arguments ) for the validation of the data types of the arguments expected and recieved by the method, and setHandler( callable $callback ) for the callback to execute when called, for example:

$routine = new Routine();
$routine->setName( 'test' );
$routine->setType( Routine::TYPE_PROCEDURE );
$routine->setArguments( array(
    DataTypes::TYPE_NULL,
    DataTypes::TYPE_BOOLEAN,
    DataTypes::TYPE_NUMERIC,
    DataTypes::TYPE_INTEGER,
    DataTypes::TYPE_DOUBLE,
    DataTypes::TYPE_STRING,
    DataTypes::TYPE_ARRAY,
    DataTypes::TYPE_OBJECT,
    DataTypes::TYPE_LAMBDA,
    DataTypes::TYPE_RESOURCE,
    'Test\MyContract'
) );
$routine->setHandler( array( $this, 'testRoutine' ) );



If an argument is passed to the routine but is not of the data type specified by the routine in the arguments array, an InvalidArgumentTypeException will be thrown. Also, if a number of arguments is passed to the routine which is less than the number expected by the routine, defined by the length of the arguments array, a MissingRequiredArgumentsException will be thrown.

The callback for a routine can be any value considered by PHP as callable. The callback for the Routine receives the arguments passed to the method.

A routine can also optionally define a PreCondition and a PostCondition. These are executed before and after the routine's callback. These validators use setHandler( callable $callback ) for the callback to execute when called, which should return a boolean value. In the case when false is returned by the PreCondition callback the routine will not execute, and a PreConditionException will be thrown. And in the case when false is returned by the PostCondition callback, the object will be returned to it's last state before the execution of the routine, and a PostConditionException will be thrown.

$preCondition = new PreCondition();
$preCondition->setHandler( function( $object, $arguments ) {
    return true;
} );

$postCondition = new PostCondition();
$postCondition->setHandler( array( $this, 'testPostCondition' ) );

$routine->setPreCondition( $preCondition );
$routine->setPostCondition( $postCondition );



The callbacks for a conditions can be any value considered by PHP as callable. The callback for the PreCondition receives 2 arguments, the object and the arguments passed to the routine, whilst the callback for the PostCondition callback also receives 2 arguments, except the second argument is the return value of the routine.

Once the routine has been defined it can be registered on the object.

$this->__routine( $routine );



Finally, the Contract class provides an __invariant( void ) magic method. This method is called internally after every routine execution. The purpose of this method is to ensure the object is in a coherent state, and has not been corrupted.

public function __invariant()
{
    // check object state
}



This method should return a boolean value. In the case when false is returned, the object will be returned to it's last state before the execution of the routine, and an InvariantException will be thrown.

All exceptions related to the PHP Design by Contract package extend the ContractException class.