========================= 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