Zeta Components - high quality PHP components

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.

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:

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

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 Zeta 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:

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 Zeta 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 Zeta 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 * * Licensed to the Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * * @package Mail * @version //autogen// * @license http://www.apache.org/licenses/LICENSE-2.0 License, Version 2.0 */ /** * 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 Zeta 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):

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:

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. In addition, the license information must be available in all files.

The following fields are required:

The following fields are optional:

The following fields may not occur:

Example:

/** * Short description of the contents of the file. * * Licensed to the Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * * @version //autogen// * @package PackageName * @license http://www.apache.org/licenses/LICENSE-2.0 License, Version 2.0 */

Class documentation

Required for all classes.

The following fields are required:

The following fields are optional:

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 * * [… license …] * * @package <packagename> * @version //autogentag// * @license http://www.apache.org/licenses/LICENSE-2.0 License, Version 2.0 */ /** * 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:

The following fields are optional:

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

Example of a hash array:

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.

phpDocumentor tags and required usage
@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://www.apache.org/licenses/LICENSE-2.0 License, Version 2.0
@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//