Path

ez components / community / implementation guidelines


Implementation guidelines

General

Only use references where it is really needed, references do not increase performance (as one might think). There are many pitfalls with reference usage so stay away.

Don't use @ in front of functions, this makes it much harder to debug. If you have to, a comment before it is required.

Use real static functions, never use fake static (or semi-static) functions. Real static functions will not include the $this variable from the calling function.

Avoid dependencies at all costs, each class should be a small a unit as possible.

  • Unit testing depends on it, the more dependencies involved the harder it is to test. e.g. to reproduce a valid or invalid environment.
  • The components are meant to be used by PEAR and other PHP classes, this means it cannot depend on a given configuration system or debug handler.
  • PHP uses more memory for opcodes for each included class or general code. This means that:
    • Code that is meant to handle non-standard cases, e.g. missing PHP extensions, should be placed in a separate class. This will reduce running code size for most people.
    • defines/constants should be placed as members of a class.
    • Avoid using 0 and 1 or empty strings as boolean values, instead use the real true and false boolean values. Also checking with === instead of == is advised.
    • Use exceptions ONLY for exceptional cases, e.g. running out of disk space.
    • Never hard code file paths or file names.
    • Provide sane default configuration switches, this allows applications to use the classes without providing all kinds of switches and also removes the need to bundle configuration files.
    • Do not use include_once to include class files, rely on PHP 5's autoload function

Use join() function instead of implode(), this makes the code easier to read.

Debugging

Dumping data

Don't use print_r for dumping values of a variable, use var_dump instead. Also var_export might be of use.

Xdebug

At all times have Xdebug installed and use its features, it will dramatically reduce the implementation and debugging time. Some of the interesting features are:

  • Function traces, this can easily provide clues about performance and dependency issues.

Errors and warnings

To avoid dependency on a specific debug class, debugging and error handling must only rely on the internal PHP functions.

To notify a debug message use:

trigger_error( "a debug message" );

Just make sure you NEVER leave debug message in committed code.

To notify a warning message use:

trigger_error( "a warning message", E_USER_WARNING );

To notify an error message use:

trigger_error( "an error message", E_USER_ERROR );

Naming conventions

This document describes the naming conventions for the components (be it classes, variables or functions). By adhering to these guidelines it should be much easier for all developers to use or work with the components. The main idea is to have a set of consistent naming while still allowing for differences in specific contexts.

General guidelines

Avoid names which does not give additional meaning or are unclear. Some examples of such names are:

Quick, Smart, Clever, Simple, Fast, Strange, Stupid

Abbreviations and Acronyms

In general one should not use abbreviations and acronyms unless its known by most programmers. Typical examples for known acronyms are:

HTTP, DAV, URL

Recommended names

To ensure consistency throughout all components the following naming conventions should be used. If the names don't make much sense in a given context other names should be found. These recommendations can also be used as prefix/suffix, e.g. $srcX, $srcY

  • For file or directory paths use path.

  • For filename without a path use file.

  • For directory name without a path use dir.

  • Use load, save for complete operations on the file system, for instance loading an entire INI file.

  • Use read, write for partial operations of data stream, for instance storing 10 bytes to a file.

  • Use fetch, store for remote operations, for instance fetching data from a web server or database.

  • When adding elements the following naming should be used:

    • Use add when adding elements without a specific order.
    • Use insert when adding elements in a specific order, for instance at a given index or position.
    • Use append when adding elements to the end.
    • Use prepend when adding elements to the beginning.
  • Use create for PHP object creation and generate for operations that generate text, code, SQL etc.

  • Use reset for resetting elements in the object.

  • Use getInstance to get an instance in f.e. a singleton pattern.

  • When removing elements the following naming should be used:

    • Use remove when elements are no longer referenced but not actually deleted. For instance removing a file path from a list while still leaving the file on the file system.
    • Use delete when elements are no longer meant to exists. For instance unlinking a file from the file system or deleting a database record.
  • Short names are advised when their context is very clear. An example is a copy() function in a File class which has a source and a destination, it is quite clear that in this context we are working with files and can use abbreviated forms, src and dest. copy( $src, $dest ) The order of source and destination is always source first.

  • Some words are different in British English and American English. It is most common to use the American spelling and so all words should follow this. Some typical names are:

    initialize, finalize, color, grey
    

Specific elements

Each element is explained below:

Class names

Classes are named using UpperCamelCase and are always prefixed with ezc which stands for eZ Components. Do not use more than three words for your names and make sure they are not verbs but nouns.

For exception classes we append "Exception" to the class name, for option classes "Options. We do not add a postfix for abstract classes, interfaces and structs.

All classes in one package should start with the same prefix, unless a class bundles multiple "main" classes into one package. Examples:

ezcDb
ezcDbHandler
ezcDbPostgresqlHandler
ezcDbMysqlHandler

Class names have the form:

"ezc" ([A-Z][a-z]+)+

Other examples:

ezcSystemInformation, ezcTemplate, ezcWebdavServer

Method names

Methods are named using lowerCamelCase and should not use more than three words. Methods which change an internal property should be named setXXX() and in addition the retrieval method must be named getXXX(). All methods must use a verb.

printReport(), validateData(), publish()

Property names

Properties are named using lowerCamelCase and should not use more than two words. Properties must always use nouns and not verbs.

$name, $path, $author

Parameter names

Parameters are named using lowerCamelCase and should not use more than two words. Parameters must always use nouns and not verbs, the exception are booleans which start with is, has etc. and form a question.

$name, $path, $isObject

Constant names

Constant names should follow the UPPER_CASE_WORDS standard, where an underscore separates words.

Special functions

There are a couple of special functions in PHP, which you should not use parenthesis with. These functions are:

  • include, include_once, require, require_once
  • print, echo
  • instanceof
  • break, continue
  • clone, new

Use them without parenthesis, like:

require_once 'file.php';
echo "foo\n";
clone $class;
break 2;

Prefer echo over print, and require_once over include, include_once and require. Although none of the "inclusion" functions should be used at all in normal code.

Directory structure

SVN Structure

The structure in SVN should be as follows:

trunk/PackageName/src/class1.php

For example for the Database package:

trunk/Database/src/db_factory.php
trunk/Database/src/db_handler_interface.php
trunk/Database/src/db_handler/mysql.php
trunk/Database/src/db_handler/postgresql.php
trunk/Database/src/db_handler/oracle.php
trunk/Database/src/db_instance.php

Installed Structure

In the installed structure the "trunk/" and "src/" directories disappear, and the install path is prepended with "ezc/". This makes for the database package (with a default PEAR install path of "/usr/local/lib/php/"):

/usr/local/lib/php/ezc/Database/db_factory.php
/usr/local/lib/php/ezc/Database/db_handler_interface.php
/usr/local/lib/php/ezc/Database/db_handler/mysql.php
/usr/local/lib/php/ezc/Database/db_handler/postgresql.php
/usr/local/lib/php/ezc/Database/db_handler/oracle.php
/usr/local/lib/php/ezc/Database/db_instance.php

Autoload Arrays

Every package should have an "autoload array" that describes how to map a class name to a filename. The format of such an autoload array is:

<?php
    return array(
        'ezcDbFactory' => 'Database/db_factory.php',
        'ezcDbHandlerInterface' => 'Database/db_handler_interface.php',
        'ezcDbHandlerMysql' => 'Database/db_handler/mysql.php',
        ...
        'ezcDbInstance' => 'Database/db_instance.php',
    );
?>

The autoload array files should have an unique name per package, consisting of the first part of the class name after "ezc". This is also the reason why the first part after "ezc" should always be the same in a package, if not, you need two autoload arrays. This can cause problems for some packages, like for the Template package and the TemplateTieInLocale package as classes in both of them start with "ezcTemplate" (ezcTemplate vs. ezcTemplateLocale). In this case you need to make two autoload arrays. For the Template package this will then be "template_autoload.php" and for the TemplateLocale package "template_locale_autoload.php". Another problem is with the ImageAnalysis and ImageConversion packages. There all classes start with ezcImage*. As the ImageConversion package has a second part in the class names, which are not the same (ezcImageConverter and ezcImageFiltersShell f.e.), conflicts with the classes in ImageAnalysis can occur. Luckily there is only one class in there, where the two first parts are unique (ezcImageAnalyzer). Here the autoload file for ImageConversion should be "image_autoload.php" and for ImageAnalysis "image_analysis_autoload.php".

Autoload arrays should be placed into the "root" of a package's source directory, for example:

trunk/Database/src/db_autoload.php
trunk/ImageAnalysis/src/image_analysis_autoload.php
trunk/ImageConversion/src/image_autoload.php

Autoload files installation location

In our Base package we define a small class "ezcBase" which defines a method "autoload" that can be used in an applications __autoload() function. The ezcBase package always gets installed into [installdir]/ezc/Base and should be included with require in all applications that use the components.

Because it is important that the ezcBase::autoload() method can find the autoload files of all the packages, they need to be installed through the package.xml definitions into [installdir]/ezc/autoload/. This means that if the Database and ImageAnalysis packages are installed, it looks like:

[installdir]/ezc/autoload/database_autoload.php
[installdir]/ezc/autoload/image_analysis_autoload.php

Exceptions

One class per error type. Each exception descents from an abstract exception class for the whole component.

Similar errors can be grouped in one abstract exception class:

ezcBaseException
|
+ ezcBaseFileException
| |
| + ezcBaseFileNotWritableException ( $filename, $fileType )
| |
| + ezcBaseFileNotReadableException ( $filename, $fileType )
| |
| + ezcBaseFileCanNotCopyExecption ( $sourceName, $destinationName, $fileType )
|
+ ezcGraphException
  |
  + ezcGraphDatasetAverageInvalidKeysException()

Exceptions are thrown with only their parameters. The exception class is responsible for preparing and formatting the message.

See Exception Class Documentation on how to document exception classes.

Specific Exceptions

There are a number of exceptions in the ezcBase class that provide common exceptions that should be used by all components.

Component configuration

Definition

To ensure minimal set of dependencies on different configuration classes and to keep things consistent a common way of configuring an object is needed.

A class consists of required configuration and optional configuration often called options.

Required configuration
These are configuration settings which the class cannot operate without. Typically this can be file to read or server to connect to.
Optional configuration
These settings never modify the state of the object and are usually read run-time when an operation is executed. How to deal with Options in the implementation can be found in the section Options.

Initialization

The required configuration is passed in the constructor of the class and if possible they should have useful default values. This ensures that the object is in a valid state when it is created.

In addition to the required configuration there should also be an extra parameter for initial options. This makes it possible to configure the object in one expression. This options parameter should always be the last parameter and should default to an empty array.

function __construct( $config1, $config2, array $options = array() )
{
}

Modification

Modifying required configuration must always be done with custom methods. The class and method must be documented so it is clear which methods perform this modification

$csv->openFile( 'myfile.txt' );

Also some configuration may not be allowed to be changed after the object is created, in this case the programmer must initialize a new object with the new configuration. This ensures that the object is at all times in a valid state.

$users = new ezcCsvReader( 'users.txt' );
// $users->openFile( 'groups.txt' ); // Invalid
$groups = new ezcCsvReader( 'groups.txt' );

Modifying the options are done with a common method called setOptions which accepts an associate array of option values keyed with the option name. It is also possible to use the Option Class directly:

$object->setOptions( array( 'eolStyle' => 'native' ) );

$options = new ezcCvsOptions;
$options->eolStyle = 'native';
$object->setOptions( $options );

In case the setOptions method is implemented, it must accept both an associative array of option values keyed by the option name, and an instance of a class that inherits from ezcBaseOptions. See the Options Example.

Inspection

If the programmer wants to inspect the current configuration he must either use specialized methods for the required configuration or getOptions for the options. The getOptions method is not required to be implemented as options can simply be retrieve the option class instance by accessing the options property.

required configuration:

$path = $object->path;

options:

$eolStyle = $object->options->eolStyle;

In case the getOptions method is implemented, it must return an instance of a class that inherits from ezcBaseOptions. See the Options Example.

Standards

To ensure consistency the following should be followed when defining required configuration and options.

  1. Options which behave like a flag (enabled/disabled) should use true and false as the values and not strings or integers.
  2. Required configuration and options follows the naming standard as for properties.

Error handling

If the object failed to initialise to a sane state from the required configuration it must throw an ezcBaseConfigException exception. This ensures that the object will not be used in the invalid state.

If non-existing options are passed then the following exception should be thrown, with $name being the configuration option's name:

throw new ezcBaseConfigException( $name, ezcBaseConfigException::UNKNOWN_CONFIG_SETTING );

Validation

Validation depends a bit on the configuration setting but in general it is recommended that the settings are made sure they are of a given type. This means that in the worst case the values are cast to the given type and in the best case they are validated and proper warning feedback is issued.

Patterns

Dependency Injection

Some components return objects based on parsed information. This includes the Mail and DatabaseSchema components. In some situations it's desirable that the classes of the objects can be modified so that the user of a component can use his own inherited classes instead. This we call "Dependency Injection".

The configuration of which class to return has to be done with Options. In the option class there needs to be a check if the requested class name actually inherits the base class that would be used by default, such as in the following code:

function __set( $propertyName, $propertyValue )
{
    $parentClassMap = array(
        'tableClassName' => 'ezcDbSchemaTable',
        'fieldClassName' => 'ezcDbSchemaField',
        'indexClassName' => 'ezcDbSchemaIndex',
        'indexFieldClassName' => 'ezcDbSchemaIndexField',
    );
    switch ( $propertyName )
    {
        case 'tableClassName':
        case 'fieldClassName':
        case 'indexClassName':
        case 'indexFieldClassName':
            if ( !is_string( $propertyValue ) )
            {
                throw new ezcBaseValueException( $propertyName, $propertyValue, 'string that contains a class name' );
            }

            // Check if the passed classname actually implements the
            // correct parent class. We have to do that with reflection
            // here unfortunately
            $parentClass = new ReflectionClass( $parentClassMap[$propertyName] );
            $handlerClass = new ReflectionClass( $propertyValue );
            if ( $parentClassMap[$propertyName] !== $propertyValue && !$handlerClass->isSubclassOf( $parentClass ) )
            {
                throw new ezcBaseInvalidParentClassException( $parentClassMap[$propertyName], $propertyValue );
            }

            $this->properties[$propertyName] = $propertyValue;
            break;


        ... other options ...
    }
}

In case there is only one class name to modify, this can of course be simplified to something like the following:

public function __set( $propertyName, $propertyValue )
{
    switch ( $propertyName )
    {
        case 'mailClass':
            if ( !is_string( $propertyValue ) )
            {
                throw new ezcBaseValueException( $propertyName, $propertyValue, 'string that contains a class name' );
            }

            // Check if the passed classname actually implements the
            // correct parent class. We have to do that with reflection
            // here unfortunately
            $parentClass = new ReflectionClass( 'ezcMail' );
            $handlerClass = new ReflectionClass( $propertyValue );
            if ( 'ezcMail' !== $propertyValue && !$handlerClass->isSubclassOf( $parentClass ) )
            {
                throw new ezcBaseInvalidParentClassException( 'ezcMail', $propertyValue );
            }
            $this->properties[$propertyName] = $propertyValue;
            break;

        ... other options ...
    }
}

Singletons

Should use the following syntax:

/**
 * @param ezcTranslationBorkFilter Instance
 */
static private $instance = null;

/**
 * Private constructor to prevent non-singleton use
 */
private function __construct()
{
}

/**
 * Returns an instance of the class ezcTranslationBorkFilter
 *
 * @return ezcTranslationBorkFilter Instance of ezcTranslationBorkFilter
 */
public static function getInstance()
{
    if ( is_null( self::$instance ) )
    {
        self::$instance = new ezcTranslationBorkFilter();
    }
    return self::$instance;
}

Properties

Definition

All properties used in a class need to be stored in the $properties array which is defined in the class as follows:

/**
 * Holds the properties of this class.
 *
 * @var array(string=>mixed)
 */
private $properties = array();

Properties also need an __isset() method implemented for them.

Property Set Implementation

The implementation of the properties happens in the __set() and __get() magic methods to allow value bounds checking and access control. The __set() method is called with the $name and $value parameters and the implementation of the method is as follows:

/**
 * Sets the property $name to $value.
 *
 * @throws ezcBasePropertyNotFoundException if the property does not exist.
 * @param string $name
 * @param mixed $value
 * @ignore
 */
public function __set( $name, $value )
{
    switch ( $name )
    {
        // cases to check for properties
        default:
            throw new ezcBasePropertyNotFoundException( $name );
    }
}

For each property that is available, there needs to be a "case" statement in the switch block. Range checking is done like this (including the correct exception).

case 'cols':
    if ( $value < 1 )
    {
        throw new ezcBaseValueException( $name, $value, 'int > 0' );
    }
    $this->properties[$name] = $value;
    break;

The 3rd parameter to the ezcBaseValueException constructor defines which type of value is allowed. This can be either a plain type (int, array, string) or a type combined with a range ("int > 0", "int = 20, 40, 60"). In case such an exception can be thrown by the __set() method, add the following code to your docblock:

* @throws ezcBaseValueException if a the value for a property is out of
*         range.

Read only properties also need to have a "case" statement. In order to signal that a property is read-only, use the following code:

case 'timestamp':
    throw new ezcBasePropertyPermissionException( $name, ezcBasePropertyPermissionException::READ );
    break;

If there is a property that can throw the ezcBasePropertyPermissionException, then you need to add the following to the method docblock as well:

* @throws ezcBasePropertyPermissionException if a read-only property is
*         tried to be modified.

Property Get Implementation

The __get() method is called with the $name parameter and the implementation of the method is as follows:

/**
 * Returns the value of the property $name.
 *
 * @throws ezcBasePropertyNotFoundException if the property does not exist.
 * @param string $name
 * @ignore
 */
public function __get( $name )
{
    switch ( $name )
    {
        // cases to check for properties
    }
    throw new ezcBasePropertyNotFoundException( $name );
}

For each property that is available, there needs to be a "case" statement in the switch block:

case 'cols':
    return $this->properties[$name];

In case a property is an array, you have to cast it to an array like this:

case 'colArray':
    return (array) $this->properties[$name];

There is no value bounds checking here. In case you want to have a write-only property you can use the following code:

case 'timestamp':
    throw new ezcBasePropertyPermissionException( $name, ezcBasePropertyPermissionException::WRITE );
    break;

If there is a property that can throw the ezcBasePropertyPermissionException, then you need to add the following to the method docblock as well:

* @throws ezcBasePropertyPermissionException if a write-only property is
*         tried to be read.

Property Isset Implementation

The __isset() method is called with the $name parameter and the implementation of the method is as follows:

/**
 * Returns true if the property $name is set, otherwise false.
 *
 * @param string $name
 * @return bool
 * @ignore
 */
public function __isset( $name )
{
    switch ( $name )
    {
        case 'cols':
        case 'colArray':
        case 'timestamp':
            return isset( $this->properties[$name] );

        default:
            return false;
    }
    // if there is no default case before:
    return parent::__isset( $name );
}

Documentation

See Property Documentation on how to document properties.

Options

Introduction

The ezcBaseOptions class is the base class for all option implementations in eZ components. Every class that utilizes options to configure the behavior of its instances using options must use a derivate of this class to implement the options mechanism.

Implementation

In the following description, a fictional package Foo will be used, which contains a fictional class ezcFooBar. The instances of ezcFooBar can be configured using options.

Option Class

The new option handling introduced in version 1.1 allows a much more convenient handling of options after object instantiation:

$foo->options->foo = 10;

Beside that (because of BC reasons), the following access possibility will also exist for the classes that used options before. This should not be used for new implementations:

$foo->options["foo"] = 10;

This possibility will not be officially documented and its usage will be discouraged in favor of the first one, to keep code using eZ components consistent.

To use the new option handling system, you have to perform the following steps (still using the Foo package example):

  1. Create a class called ezcFooBarOptions, which extends the ezcBaseOptions class.
  2. For each of the options for ezcFooBar create a private property in the ezcFooBarOptions class, and add the default value.
  3. Create validity checks for each of the options in the __set() method of the ezcFooBarOptions.

Options Example

The ezcFooBar class looks now like:

/**
 * ezcFooBar does....
 *
 * @property ezcFooBarOptions $options
 */
class ezcFooBar
{
    /**
     * Options for the foo bar class
     */
    private $options;

    /**
     * ...
     * @param ezcFooBarOptions $options
     */
    public function __construct( ezcFooBarOptions $options = null )
    {
        $this->options = $options === null ? new ezcFooBarOptions() : $options;
    }

    public function setOptions( ezcFooBarOptions $options )
    {
        $this->options = $options;
    }

    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Returns the value of the property $name.
     *
     * @throws ezcBasePropertyNotFoundException
     *         if the property $name does not exist
     * @param string $name
     * @ignore
     */
    public function __get( $name )
    {
        switch ( $name )
        {
            case 'options':
                return $this->options;
                break;
        }
        throw new ezcBasePropertyNotFoundException( $name );
    }

    /**
     * Sets the property $name to $value.
     *
     * @throws ezcBasePropertyNotFoundException
     *         if the property $name does not exist
     * @throws ezcBaseValueException
     *         if $value is not accepted for the property $name
     * @param string $name
     * @param mixed $value
     * @ignore
     */
    public function __set( $name, $value )
    {
        switch ( $name )
        {
            case 'options':
                if ( !( $value instanceof ezcFooBarOptions ) )
                {
                    throw new ezcBaseValueException( 'options', $value, 'instanceof ezcFooBarOptions' );
                }
                $this->options = $value;
                break;

            default:
                throw new ezcBasePropertyNotFoundException( $name );
        }
    }

    /**
     * Returns true if the property $name is set, otherwise false.
     *
     * @param string $name
     * @return bool
     * @ignore
     */
    public function __isset( $name )
    {
        switch ( $name )
        {
            case 'options':
                return true;

            default:
                return false;
        }
    }
}

Option Class Example

The option class itself, could look like the following:

<?php
/**
 * File containing the ezcFooBar class
 *
 * @package Mail
 * @version //autogen//
 * @copyright Copyright (C) 2005-2007 eZ systems as. All rights reserved.
 * @license http://ez.no/licenses/new_bsd New BSD License
 */

/**
 * Class containing the basic options for foo bar things.
 *
 * @property int $timeout
 *           Specifies the time in seconds until the connection is closed if
 *           there is no activity through the connection.
 * @property bool $ssl
 *           Specifies whether to use an SSL connection or not.
 *
 * @package Mail
 * @version //autogen//
 */
class ezcFooBarOptions extends ezcBaseOptions
{
    /**
     * Constructs an object with the specified values.
     *
     * @throws ezcBasePropertyNotFoundException
     *         if $options contains a property not defined
     * @throws ezcBaseValueException
     *         if $options contains a property with a value not allowed
     * @param array(string=>mixed) $options
     */
    public function __construct( array $options = array() )
    {
        $this->timeout = 5; // default value for timeout is 5 seconds
        $this->ssl = false; // default value for ssl is false

        parent::__construct( $options );
    }

    /**
     * Sets the option $name to $value.
     *
     * @throws ezcBasePropertyNotFoundException
     *         if the property $name is not defined
     * @throws ezcBaseValueException
     *         if $value is not correct for the property $name
     * @param string $name
     * @param mixed $value
     * @ignore
     */
    public function __set( $name, $value )
    {
        switch ( $name )
        {
            case 'timeout':
                if ( !is_numeric( $value ) || ( $value < 1 ) )
                {
                    throw new ezcBaseValueException( $name, $value, 'int >= 1' );
                }
                $this->properties[$name] = (int) $value;
                break;

            case 'ssl':
                if ( !is_bool( $value ) )
                {
                    throw new ezcBaseValueException( $name, $value, 'bool' );
                }
                $this->properties[$name] = $value;
                break;

            default:
                throw new ezcBasePropertyNotFoundException( $name );
        }
    }
}
?>

Options for Static Classes

In case a static class requires options, then it is impossible to have the __set and __get magic methods catch the classname::$options. Because of that, for static classes and options the integration in the class should go like this:

/**
 * ezcFooBar does....
 *
 */
class ezcFooBar
{
    /**
     * Options for the foo bar class
     * @var ezcFooBarOptions
     */
    static private $options;

    /**
     * Associates an option object with this static class.
     *
     * @param ezcFooBarOptions $options
     */
    static public function setOptions( ezcFooBarOptions $options )
    {
        self::$options = $options;
    }
}

Structs

Complex arrays are not used in the eZ Components. In many cases we prefer to use a very lightweight class with a few methods that can be more conveniently used compared to simple arrays. THis is because we can control which "keys" are available in those classes. Those light weigth classes are called "structs" and can be found in the "structs/" directory which is a sub-directory of "src/".

Layout

Each struct class extends from ezcBaseStruct:

class ezcBaseRepositoryDirectory extends ezcBaseStruct

And defines a public property (including documentation) for each element in the "array":

/**
 * The type of repository. Either "ezc" or "external".
 *
 * @var string
 */
public $type;

The constructor of the class accepts all the allowed elements as parameters, sets default values, and assigns the values to the class' properties:

/**
 * Constructs a new ezcMailAddress with the mail address $email and the
 * optional name $name.
 *
 * @param string $email
 * @param string $name
 */
public function __construct( $email, $name = '', $charset = 'us-ascii' )
{
    $this->name = $name;
    $this->email = $email;
    $this->charset = $charset;
}

A __set_state() method is not required, but recommended. The __set_state() method can be used to create an object of this class from a serialized PHP variable. The method's implementation looks like:

static public function __set_state( array $array )
{
    return new ezcMailAddress( $array['email'], $array['name'] );
}

See also Documenting __set_state on how to document this method.

Dealing with Files

Reading files

If possible try to use some of the PHP functions for reading in files, eg. file(), instead of having custom PHP code.

Avoid reading in whole files in memory if this is not needed. In this cases you should not use file() and the likes. Instead read a chunk of the file into a buffer (e.g. 4096 bytes) and work on that.

Writing files

When writing to files never assume that only one process will access the same file at the same time. This means you should create code that does either (or both):

  • File locking, lock the file for reading until the writing is finished, this avoids other processes reading half-finished files.
  • Temporary files, create a new file which is used for writing (locking might be a good idea too). When the file writing is done the original file is backed up (renamed/moved) and the new one copied/moved as the original. (A caveat: using tail -f will not work).

Unicode and UTF-8

All components internally should handle UTF-8, and where possible parsers should always return UTF-8 as well.

Unicode caveats

Case handling in PHP 6 will differ from earlier because it uses the current locale when doing the operation. This means that in some locales you can have non-revertible case changes.

There is no workaround for this at the moment other than checking for the original string and the lowercase string at the same time or using only lowercase characters at all times.

Documentation

This document explains how PHP source and source files should be documented in order to meet the required standards for documentation.

All PHP source should be documented using phpDocumentor syntax. The rest of this document is concerned with:

  • Source that is required to be documented.
  • The tags that are required to be used in the various contexts.
  • Optional documentation.
  • Wording rules.

In general the examples show in what order the various tags should be used.

File documentation

Required in all source files with and without a class. PHPDocumentor will show a warning message otherwise.

The following fields are required:

  • A short (one line) description of the file.
  • @version
  • @package
  • @copyright
  • @license

The following fields are optional:

  • a longer description of the file if the short description does not suffice. Only needed when the file doesn't contain one class.
  • @subpackage Tests To be used for files/classes that make out a part of the test suite.

Example:

/**
 * Short description of the contents of the file.
 *
 * @version //autogen//
 * @package PackageName
 * @copyright Copyright (C) 2005-2007 eZ systems as. All rights reserved.
 * @license http://ez.no/licenses/new_bsd New BSD License
 */

Class documentation

Required for all classes.

The following fields are required:

  • A brief one line description of the class.
  • An extensive description of the class. Use examples unless it is obvious how to use the class.
  • @package
  • @version

The following fields are optional:

  • @tutorial, if there are relevant tutorials
  • @uses, if this class depends on other packages
  • @see, if this class has related classes
  • @property, @property-read and @property-write are used to document properties of normal and Option Class.

Example:

/**
 * One line description of the class.
 *
 * Extensive documentation of the class. Feel free to use some
 * inline code. For example, the following code is "like this":
 * <code>
 * $archive = new ezcArchive( "/tmp/archive.tar.gz" );
 * $entry = $archive->getEntry();
 * print( "First entry in the archive: " . $entry->getPath() );
 * </code>
 *
 * Continue documentation.
 *
 * @see all_related_classes
 * @uses other_packages
 *
 * @package PackageName
 * @version //autogen//
 */

Property Documentation

This describes a new way of documenting properties, something that phpDocumentor does not yet understand directly. However, our patched version does.

Properties are documented in the class' docblock, and not with the __set() and __get() methods. Documentation of properties goes as follows:

* @property        <type> $name  Description
* @property-read   <type> $name  Description
* @property-write  <type> $name  Description

Examples are:

* @property        string  $pass   password or null
* @property-read   int     $port   port, only values > 1024 are allowed
* @property-write  array   $query  complete query string as an associative array

@property is used for properties that can be read from and written to, @property-read is for read-only properties and @property-write for write-only properties.

Exception Class Documentation

Exception class documentation follows the following template:

<?php
/**
 * File containing the <exception name> class
 *
 * @package <packagename>
 * @version //autogentag//
 * @copyright Copyright (C) 2005-2007 eZ systems as. All rights reserved.
 * @license http://ez.no/licenses/new_bsd New BSD License
 */
/**
 * Exception for <shortdesc>.
 * <longdesc>
 *
 * @package <packagename>
 * @version //autogentag//
 */
class ezcTemplateElementParserException extends ezcTemplateException
{
    /**
     * Creates a new exception.
     *
     * Initializes the exception with the parser error object ($error) and
     * sets the exception message from it.
     *
     * @param ezcTemplateParserError $error The object containing error details.
     */
    public function __construct( ezcTemplateParserError $error )
    {
        $this->parserError = $error;

        parent::__construct( $error->getErrorMessage() );
    }
}
?>

Structured arrays

Due to efficiency reasons we use a lot of "struct" like objects in our Components instead of associative arrays. Those should be documented just like normal classes.

Method documentation

Required for all methods and functions

The following fields are required:

  • A brief (one line) description of what the class does. We use the following wording conventions (snatched from the excellent Qt documentation)

    • Wording: First word of description should always be a verb.
    • Wording: "Constructs a/the" for all constructors
    • Wording: "Returns ...." for all functions returning something except if the returned value is not the significant for the duty of the method.
  • @throws, syntax: @throws ExceptionType if [your reason here], like:

    * @throws ezcConfigurationIniFileNotWritable if the current location values
    * cannot be used for storage.
    
  • @param

  • @return, but only if there is something returned from the method.

The following fields are optional:

  • A longer description of what the function does. If natural mention the various parameters. If used the description field must follow the brief description.
  • @see, if there are other related methods/functions.

Example 1: (With object parameters)

/**
 * Returns the length of the line defined in the two dimensional space
 * between $point1 and $point2.
 *
 * Example:
 * <code>
 * $point1 = new Point( 5, 10 );
 * $point2 = new Point( 15, 42 );
 *
 * $length = getLength( $point1, $point2 );
 * </code>
 *
 * @see getLength3D()
 *
 * @throws PointException if any of the points are imaginary.
 * @param Point $point1
 * @param Point $point2
 * @return int
 */
public function getLength2D( Point $point1, Point $point2 )
{

Note how the parameters are not documented since they are already mentioned in the description.

Example 2: (Same as above but with optional extra parameter and array arguments):

/**
 * Returns the length of the line defined in two dimensional space
 * between point1 and point2.
 *
 * @param array $point1     Format array( 'x' => int, 'y' => int )
 * @param array $point2     Format array( 'x' => int, 'y' => int )
 * @param int   $multiplier Multiplies the result by the given factor.
 * @return int
 */

Note how the additional optional parameter is documented since it is not mentioned in the normal description. Of course in this case you could choose to mention it there instead.

Function parameter and return types

  • All parameters must be documented with at least their type, parameter name and short description. All returns should be documented with at least their type. If it is not obvious what the return value/parameter does with the short description, it should be described in the long description of the method, a description must be written. Do not add long descriptions of obvious parameters/return types if they are explained in the description text. This is to avoid cluttering the documentation with obvious stuff.

  • Type names should be written as described on this page http://no.php.net/manual/en/language.types.php. Basically the allowed types are:

    • bool
    • int
    • float
    • string
    • array
    • object (use class name)
    • resource
    • mixed (avoid making functions that use mixed parameters/return types)
  • If the type is array, we must describe the requirements to the contents of the array. This is done by specifying the type for normal arrays or the expected key's and corresponding value type if it is a hash array.

    Example of normal array of integers:

    array(int)
    

Example of a hash array:

array(string=>valueType)
  • Default values are auto documented, never document the default unless it is not obvious what it does.

  • Parameters can be documented in either of the following styles:

    /**
     * @param array(int) $somewhatLongerName A long description of
     *                                       myParameter and it doesn't
     *                                       fit with the 79 characters.
     */
    
    /**
     * @param array(int) $somewhatLongerName
     *        A long description of myParameter and it doesn't fit with the 79
     *        characters.
     */
    

The first format is preferred, but in case the parameters short description does not fit behind the type and parameter name on the line, then the multi-line comment like in format 2 is preferred. Do not use the "short" description for extensive documentation, this should go to the method's long function description.

Documenting set and get

If you use class properties then the __set and __get methods get the following documentation:

/**
 * Sets the property $name to $value.
 *
 * @throws ezcBasePropertyNotFoundException if the property does not exist.
 * @throws ezcBaseFileNotFoundException when setting the property with an invalid filename.
 * @param string $name
 * @param mixed $value
 * @ignore
 */
public function __set( $name, $value )

/**
 * Returns the value of property $value.
 *
 * @throws ezcBasePropertyNotFoundException if the property does not exist.
 * @param string $name
 * @param mixed $value
 * @return mixed
 * @ignore
 */
public function __get( $name )

For documentation how properties really work, please refer to the section Property Documentation.

Documenting __set_state

If you have such a method for your class, it should be documented like this:

/**
 * Returns a new instance of this class with the data specified by $array.
 *
 * $array contains all the data members of this class in the form:
 * array('member_name'=>value).
 *
 * __set_state makes this class exportable with var_export.
 * var_export() generates code, that calls this method when it
 * is parsed with PHP.
 *
 * @param array(string=>mixed)
 * @return ezcPhpGeneratoReturnData
 */

Documenting the properties variable

The properties variable should be documented like this:

/**
 * Holds the properties of this class.
 *
 * @var array(string=>mixed)
 */

Documenting private classes

If you are documenting a private class make sure to mark both the file and the class docblock with @access private. Documentation for these classes will not be generated for the end user documentation.

It is important that private classes are not exposed anywhere within the public classes.

Documenting options

Options should be documented in the class doc block as properties of the option class. It should follow directly after the main description. See Property Documentation for more information.

@apichange

Use this in any form of block level to document something that can be removed or changed when we bump the major version number of a component.

@category

Required in the page level doc blocks in source files of tie-in packages. That is packages directly related to some main package where the separation exists only to avoid dependencies.

@copyright

Required in either the page or class level doc blocks. It should be in the form:

@deprecated

Required to use for everything that is deprecated. If a complete page or class is deprecated you should add this tag only to the page or class level doc block.

@example

Optional usage when making big examples. These can be in source files which we can then actually check for correct behavior.

@filesource

Required in the page level documentation.

@global

Required when creating global variables. I can't think of any reasons why we would want to create that though.

@ignore

Use if needed. __set, __get and __isset method documentation always get this tag.

@internal

Required when documenting public functionality and you want to add information that is developer specific.

@license

Required for the documentation of all files. It should always read:

@license http://ez.no/licenses/new_bsd New BSD License

@link

Required when linking in the documentation.

@package

Required in the page level doc block of all source files. Always use the package name.

@param

Required for all function parameters. The type and variable name parameters are required. The description should be used if the purpose of the parameter is not mentioned in the method description. Documentation of parameters in the description is recommended.

@return

Required for all methods, and the type parameter is required. This tag should not exist for non-returning methods. The description should be used if the purpose of the return value is not mentioned in the method description.

@see

Required to use when documenting methods or classes that have similar purpose.

@since

Required when adding new functionality to a package after the initial release.

@throws

Required for all methods that can throw an exception. You should also mention any exceptions that might bubble up.

@todo

Required to use when functionality is not finished. Packages should never contain TODO items when they are released.

@uses

Required for classes that have dependencies on other packages. The use should display what package you use. Should only be used in class documentation.

@var

Required for all class variables. The only allowed syntax is:

/**
 * Short description
 * Longer description that can also span multiple lines, like
 * this.
 * @var type
 */
private $variableName;

An example:

/**
 * ezcPhpGenerator writes to the file with this name during execution.
 * When {@link finish()} is called this file is moved to
 * $resultFileName.
 * @var string
 */
private $tmpFilename;

@version

Required in all file and class descriptions. The values are auto generated, so just use the format:

@version //autogentag//