=========================
Implementation guidelines
=========================
.. contents:: Table of Contents
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
.. _lowerCamelCase: http://en.wikipedia.org/wiki/CamelCase
.. _UpperCamelCase: http://en.wikipedia.org/wiki/CamelCase
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::
'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::
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.
Coding standard
===============
The Zeta coding standard is derived from the coding guidelines used in eZ
Publish. It may feel uncommon to you, if you coded in the manner of PEAR
before, but it feels a lot more readable, once you get used to it. Please stick
to the following rules, when you provide code to Apache Zeta Components.
Names
-----
All names are in nerdCase (headless camel case). This applies to variable,
property, function method and also class names. Examples are::
class ezcWebdavTransport
static public $parsingMap = array();
public final function parseRequest( $uri )
$response->validateHeaders();
Note that class names always start with the prefix ``ezc``, so the actual name
of the class is in CamelCase. The only exception for this naming rule are class
constants, which are written fully in UPPERCASE and, if they consist of
multiple words, are devided by underscores (``_``). For example::
const VERSION = '//autogentag//';
const XML_DEFAULT_NAMESPACE = 'DAV:';
For conventions on names, please rever to the `Naming conventions`_ section.
Brackets
--------
There are three simple rules on how you need to handle brackets:
Parenthesis
~~~~~~~~~~~
After every opening parenthesis and before every closing parenthesis there is a
whitespace::
if ( ( $someThing === 'some' || $anotherThing === 'another' ) )
The whitespace is typically a simple space character, but might also be a
newline, if you need to indent code in parenthesis.
Square brackets
~~~~~~~~~~~~~~~
There is no space after an opening square bracket and none before the closing
conterpart::
$this->properties['namespaceRegistry'] = null;
Curly brackets
~~~~~~~~~~~~~~
Every curly bracket is preceeded and followed by a newline::
public final function parseRequest( $uri )
{
// …
if ( isset( self::$parsingMap[$_SERVER['REQUEST_METHOD']] ) )
{
try
{
// …
}
catch ( Exception $e )
{
// …
}
}
// …
}
The only exception for this rule are curly brackets inside double quoted
strings, which are used to mark a variable replacement::
$foo "This is a {$foo} string with {$bar[23]}";
Line length
-----------
A code line should not be longer than 80 characters. In case a line in your
source code becomes longer, you need to wrap it and use proper `Indentation`_.
Proper wrapping and indentation depends on the situation. For example, for a
method signature, a proper wrapping would be::
public function doSomething(
ezcSomeClass $foo,
ezcAnotherClass $bar,
$doItRight = true,
$someIntValue = 10
)
In case of a long condition, you should try to group the condition elements
semantically, as good as possible::
if ( ( $someThing === 'some' || $anotherThing === 'another' )
&& ( $foo !== $bar ) )
Indentation
-----------
Indentation is performed by **four spaces**, tabs or a different ammount of
spaces for indentation is not allowed. Code blocks are indented in two
different cases:
1. After an opening curly bracket
2. In case a code line becomes to long
Properly indented code looks like this::
public final function parseRequest( $uri )
{
$body = $this->retrieveBody();
$path = $this->retrievePath( $uri );
if ( isset( self::$parsingMap[$_SERVER['REQUEST_METHOD']] ) )
{
try
{
// Plugin hook beforeParseRequest
ezcWebdavServer::getInstance()->pluginRegistry->announceHook(
__CLASS__,
'beforeParseRequest',
new ezcWebdavPluginParameters(
array(
'path' => &$path,
'body' => &$body,
)
)
);
$request = call_user_func(
array( $this, self::$parsingMap[$_SERVER['REQUEST_METHOD']] ),
$path,
$body
);
}
catch ( Exception $e )
{
return $this->handleException( $e, $uri );
}
}
// …
Operators
---------
Before and after any operator there must be a space::
$foo = 23 * 42;
$bar = 'Some' . ' ' . 'thing';
There is no space before a comma (``,``)::
$this->doSomething( $foo, $bar, $baz );
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":
*
* $archive = new ezcArchive( "/tmp/archive.tar.gz" );
* $entry = $archive->getEntry();
* print( "First entry in the archive: " . $entry->getPath() );
*
*
* 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 $name Description
* @property-read $name Description
* @property-write $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::
class
*
* @package
* @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 .
*
*
* @package
* @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:
*
* $point1 = new Point( 5, 10 );
* $point2 = new Point( 15, 42 );
*
* $length = getLength( $point1, $point2 );
*
*
* @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.
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://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//
..
Local Variables:
mode: rst
fill-column: 79
End:
vim: et syn=rst tw=79