PHP Design by Contract v1.0
Copyright (c) 2012 James Watts (SOLFENIX)
http://www.solfenix.com
This is FREE software, licensed under the GNU/GPL
http://www.gnu.org/licenses/gpl.html
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.
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: https://sourceforge.net/p/php-autoload
Once you've unpacked the files into your include path the package is
ready for use.
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.