PHP Coding Standard

Posted on 7/30/2010 by Andrzej Tucho?ka Lead Code Architect

Notice:

In most of the examples the rule of writing comments has been omitted due to consistency and clarity of this document.

1. General rules

This document includes all rules defined in a General Coding Standard

2. Code layout

All of us highly value our time, so we want the results of your work (especially when confronted with other people) to comply with the accepted quality standards. This is meant to assure that you don't build a castle that is accessible only by yourself. Guides defined in this chapter address not only the quality of the visual side of the source code, but also allow us to avoid common errors that are created by poorly structured source code. It is our intention to create a friendly and healthy work environment, so it is neccessary to communicate with each other in an elegant way.

2.1. Brackets { }

Always include braces in the source code. Even if the body of some construct is only one line long, do not drop the braces. Opening braces never go on their own line and are separated by a single space from previous block. Closing braces always have their own line, except for if..else and try..catch statements where else and catch are preceeded by closing and succeeded by opening brackets.

Examples:


class Parser {

public function execute() { if (...) { foreach (...) {

} } else {

} } }


2.2. Brackets ( )

Direct usage of parentheses is allowed only after methods. Keywords (like if, while...) should be separated with a space from the opening bracket.

Note that when instantiating a class, we are actually calling its constructor method, so this is the only exception from rules defining parentheses usage.

You are not allowed to use parentheses with the "return" statement.

Examples:


class TuentiControl {

// ...

public function draw() { if ($this->isVisible) { foreach ($this->elements as $drawableElement) { $drawableElement->draw(); } } } }


2.3. Logical operators

All operators need to be separated from corresponding values with spaces. Also, the equation defined by usage of every single operator has to be enclosed within parentheses.

Above rules do not apply to operator NOT (!). This operator should be preceded with a space and should be applied directly before the value to be negated. Operator NOT does not have to be preceeded with space if there is an opening parenthesis just before it.

You should only use C-style operators (&&, ||).

Examples:


// ...
    if (($this->isVisible && !$this->isEmpty) || $this->forceDraw) {

} // ...


In case of the need to break the line, the line break should be done on the operator and the operator should start the next line. Next line that continues expression from the previous one, always has to be be indented one more level (with regards to general indenting rules).

2.4. Quotes

You should always use single quotes unless you specifically need variable interpolation to be done on that string. Remember that all literals should be used in conjuction with constants. Double quoted strings will be evaluated/parsed by PHP creating an unnecessary performance decrease.

Examples:


class Image {

const LOG_IMAGE_ERROR = 'Image error'; const LOG_IMAGE_WARNING = 'Image warning'; const LOG_IMAGE_NOT_STORED = "Image could not be stored.\nFull error information:\n%s";

// ... } catch (ImageNotStoredException $e) { $this->logService->noteThis(Image::LOG_IMAGE_ERROR, sprintf(Image::LOG_IMAGE_NOT_STORED, $e->getMessage())); } // ... }


2.5. Shortcuts

  • Operators - it is allowed to use a shortcut operator (++, --) only in separate line and for loops.
  • Conditionals - it is allowed to use short form of conditionals only in assignments that are one-liners.
  • PHP tags - it is allowed to use only this form of <?php tag. All other forms are strictly forbidden.
  • strings concatenation - it is allowed to use "." operator for string concatenation. Always add a space before and after the operator. It is allowed to break the lines while concatening strings, but in this case each new line has to start with "." operator (as with all operators).

Examples:


class ChatRoom {

// ...

public function getUserList() { $output = (isset($this->userList)) ? $this->userList->getString() : Lang::EMPTY;

return Lang::USER_LIST_TITLE . Lang::USER_LIST_COLUMNS . $output; } }


2.6. Indenting

Use an indent of 1 tab character. Do not align your code other then adding additional indentation level. It is advised to have editors configured to expand a tab character to the width of 4 spaces. All additional tools displaying source code will have this configured. The target line length is 80 characters; i.e., developers should aim to keep code as close to the 80-column boundary as is practical. However, longer lines are acceptable. The maximum length of any line of PHP code is 120 characters.

In a special case when there is a need to break the line within a logical equation, it should be indented by one more level. In this case each new line should be started by a logical operator that should be applied to the equation.

When passing arguments to a method, they must be separated by comma-space (', ').

Examples:


class Foo {

// ...

public function Bar($name, $surname) { $foo = ''; if ((isset($name) && (count($name) > Foo::MIN_NAME_LENGTH) && (count($name) forceNamePrint && ($name != Foo::EMPTY_NAME))) {

$foo = $this->prefix.$name; } return $foo; } }


2.7. Code blocks

This subchapter defines a standard to write code blocks. It contains mostly self-explenatory examples of specific blocks of code with comments where neccessary.

2.7.1. switch..case

Do not enter too many routines in each case statement. switch..case is a control block, not a group block. Usually we should limit it to 4-5 commands, but it cannot exceed 15 lines per switch. Since this block will be contained inside the methods, see also a point about defining proper method for additional size limits.

The default case has always to be defined and the break statement has to have the same indentation level as the source code.

Examples:


// ...
    switch ($entityType) {
        case Entity::TYPE_LOG:
            $this->logInformation($value);
            break;

case Entity::TYPE_FILE: $this->deleteFile($value); break;

default: throw new EntityTypeNotDefined($entityType); break; }


Falling through case statements is strongly discouraged except where several options share the exact same code. If you have few similarities in your case statements, most of the times an additional method encapsulating these routines should be defined.

Examples:


// ...

case a: case b: case c: doStuff(); break;


2.7.2. if..

Examples:


// ...
    if ($this->isCallable()) {
// ...
    } else {
// ...
    }

// ...

if ($this->getPreviousItem() == $this->currentItem) { // ... } else if ($this->getPreviousItem()->getOrder() currentItem->getOrder()) { // ... } else { // ... }


All comparisons should be positive whenever possible.

Examples:


// Instead of:
    if (!($this->isDisabled())) {
        $this->render();
    } else {
        throw new RenderingDisabledControl($this->getId(), $this->getType());
    }

// Use: if($this->isDisabled()) { throw new RenderingDisabledControl($this->getId(), $this->getType()); } else { $this->render(); }


Do not implement checks like "if ($myVariable)" if $myVariable might not be a boolean value. Also, the variable has to be named with accordance to bool semantics ($is...) to make this even more clear.

It is strongly suggested to use a "else if" form instead of "elseif".

2.7.3. while

Examples:


// ...
    while ($this->getCount() > 0) {
// ...
    }

2.7.4. do..while

Examples:


// ...
    do {
// ...
    } while ($this->getCount() > 0);

2.7.5. foreach

Define iterators for collection classes that are being used in your code.

Examples:


class CommentsIterator implements Iterator
{
    private $commentsCollection;

public function __construct(CommentsCollection $collection) { $this->commentsCollection = $collection; }

public function rewind() { $this->commentsCollection->rewind(); }

public function current() { return $this->commentsCollection->getCurrent(); }

public function key() { return $this->current()->getId(); }

public function next() { $this->commentsCollection->next(); return $this->current(); }

public function valid() { $isValid = ($this->current() !== false); return $isValid; } }

// ...

foreach ($myComments as $id => $comment) { // ... }


2.7.6. class

Do not write code that is not encapsulated within a class.

As a rule each class should be created within a separate file. It is acceptable to create proper Exception classes inside the class "owning" them. See exception handling for more details on exceptions.

2.7.7. method

Do not write routines that are not encapsulated in methods.

A public method should preserve the class invariants of the object it is associated with, and should always assume that they are valid when it commences execution (private methods do not necessarily need to follow this recommendation). To this effect, preconditions are used to constrain the method's parameters, and postconditions to constrain method's output, if it has one. If any one of either the preconditions or postconditions is not met, a method may raise an exception. If the object's state does not satisfy its class invariants on entry to or exit from any method, the program is considered to have a bug.

A size (in lines) of the method content should not exceed 25 lines of code.

Examples:


class Foo {

public function __construct() { // ... }

private function prepareResult() { // ... }

protected function execute() { // ... } }


See Proper method code for more information on methods.

2.7.8. try..catch

If your code can fix the exception, then always encapsulate unsafe code in a try..catch clause and handle all exceptions that can be thrown by enclosed routines. Do not catch base exception class, but provide specific behavior for each type of exception. Exceptions are meant to communicate the developer information about the problem, not to hide information about an error from end-user. This is why we need to be specific when throwing and catching exceptions. Also, if a try..catch clause encloses too much of the code it usually means your code design is probably broken.

Remember that, the catch clause always has to be sorted from most detailed (in terms of inheritance) exception to most general one.

Examples:


// ...

try { // ... } catch (MySqlException $e) { // ... } catch (DbException $e) { // ... } catch (Exception $e) { // ... } // ...

/** * This is a general exception for all mysql related errors * * @author Andrzej Tucholka */ class MysqlException extends DbException { private $errorMessage = 'A general mysql error occurred';

public function __construct($message = NULL) { parent::__construct(($message !== NULL)? $message: $this->errorMessage, ExceptionCodes::STORAGE); } }


2.8. Documentation and comments

It is our intention to create a preconfigured development environment that will help developers in documenting their source code.

Consider your comments a story describing the system. Expect your comments to be extracted by a robot and formed into a manual page. Class comments are one part of the story, method signature comments are another part of the story, method arguments another part, and method implementation yet another part. All these parts should weave together and inform someone else at another point in time just exactly what you did and why. Do not swear in comments or use words that are considered vulgar.

Comments shoud document decisions you made while writing the code. See missing code for a good example of that kind of decisions.

All documentation blocks ("docblocks") must be compatible with the phpDocumentor format. For more information, visit phpDocumentor webpage.

Some general rules about writing comments:

  • The sharp, '#', character should not be used to start comments.
  • Each comment has to be indented to the left.
  • Always write comments before the statement.
  • Code commented out should be deleted before merging back into the main branch.
  • Don't commit commented out code without writing a reason why it shoudl stay there

If a method or a class is deprecated from a specific point in time onwards, a leading comment has to be expanded by @deprecated command containing information about the date from when this code is deprecated, the deprecating reason and should point to the new valid code.

Every file that contains PHP code must have a header block at the top of the file that contains these information (add parameters if they apply):

Examples:


/**
 * <>
 *
 * LICENSE: This file can only be stored on servers belonging to Tuenti Technologies S.L.
 *
 * @copyright 2008, (c) Tuenti Technologies S.L.
 * @author Andrzej Tucholka
 * @package General
 * @subpackage Config
 * @todo <>
 */

If there are multiple people working on the file, list each author in a separate doc-param providing general scope of responsibilities in addition to your name.

Every class must have a docblock that contains these information (add parameters if they apply):

Examples:


// ..

/** * <> * * @author Andrzej Tucholka * @todo <> */


If there are multiple people working on the class, list each author in a separate doc-param providing general scope of responsibilities in addition to your name.

Every method must have a docblock that contains these information (add parameters if they apply):

Examples:


// ..

/** * <> * * @author Andrzej Tucholka * @throws <> * @param TypeOfParameter NameOfTheParameter Comment * @return TypeOfReturn NameOfReturn Comment * @todo <> */


If there are multiple people working on the method, list each author in a separate doc-param providing general scope of responsibilities in addition to your name.

If your method is not returning any result and therefore it's result should be discarded your documentation tag should be @return void

It is possible, that the return values of your methods will not be simple types nor objects. In case of arrays you should apply more detailed rules for describing your possible return values. Below you'll find examples demonstrating how the TypeOfParameter? and Description should be expanded to provide full information for array based results.

Examples:


// ..

/** * Returned value is a array of objects with meaningless keys * @return User[] An array containing a list of banned users * * Returned value is an array of objects with meaningful keys * @return User[int] Format: array($userId => User) * * Returned value is an array of arrays * @return int[int][] Format: array($userId => array($inviteCount)) * * Returned value is an array of arrays * @return string[string][int] Format: array($userName => array($friendId => $friendName)) * * Returned value is an array of arrays with meaningful positions * @return string[string][int] Format: array($userName => array(BEST_FRIEND(0) => $friendName, WORST_FRIEND(1) => $friendName)) */


A special case of the method is one that we consider to be an entry point to the system. These methods need to have an extended description including additional tags documenting an entry point:

  • @epoint-public-action - Defines if the method is an entry point. If it's not, the tag is optional.
  • @epoint-changes-state - Description defines a scope of changes done to the user state
  • @epoint-privacy-control - Description defines a way in which the privacy check is being done on the entry point.
  • @epoint-summary - Describes the risks, how it should be tested and any additional information associated with the entry point.

Examples:


// ..

/** * Shows general canvas * * Three params are available to use by others controllers, in order to reuse * this action (p.e. photo edit) * * @epoint-public-action NO (this doc tag is optional but must go here when an action is public) * * @epoint-changes-state YES * - It deletes notifications when: a) you are viewing a new tagged photo where you appear in, * b) you are viewing a photo where you are tagged in and that photo has new comments * * @epoint-privacy-control YES * Possible cases, a photo can be viewed when: * - if current user is friend with the owner of the photo * - if current user has common friend with the owner and the owner allows friends-of-friends * to see his photos * - if owner has photos as public * - if current user is viewing a photo in a page and the page allows it * * @epoint-summary Displays a photo and receives a key which may be altered by attackers, * tests should pass wrong keys or privacy-closed photo keys. * More information: (you should provide useful information here when necessary) * * @author Mauricio Morales * @param Photo $item * @param boolean $performCleanUp defaults to true * @param boolean $doPreload specifies whether a preload is required or not * @return View */ public function viewPhotoAction($item = NULL, $performCleanUp = TRUE, $doPreload = TRUE) {

}


When commenting imported modules, you should provide a comment block describing the usage scope of that module (see below). In other cases - general imports etc, the comment block is not necessary.

Examples:


// ..

/** * <> * * @see */


Documenting class fields is optional, but should be used in all non-obvious cases - usually with arrays describing the contents of keys and values. In case of documenting the field use a doc block like this: Examples:


// ..

/** * Contains a database configuration $serverIp => $weight; where weight is used for load balancing * * @var array contains database configuration */ private $databaseConfig = array();


2.9. Packages/subpackages structure and usage

The names of packages and subpackages can be only one word containing only letters, digits and "_", "-", "[" or "]" characters.

If below structure is not sufficient, feel free to create new subpackages to address your documentation needs but keep the same naming style and update A-Team on the new packages.

  • Api
    • Exceptions
  • Control
    • Base

    • Interfaces
    • Exceptions
  • Domain
    • Base

    • Interfaces
    • Exceptions
    • ... name of the domain module
  • General
    • Config

    • Exceptions
  • Helpers
    • Domain

    • Storage
    • ... other groups by area
  • Storage
    • Mysql

    • Memcache
    • Sphinx
    • Queues
    • Cached-collections
    • Exceptions
    • ... other storage types
  • Tests
    • Base <-- all framework tests go into this subpackage

    • ... name of the domain/control module.

3. Coding practices

This chapter introduces practices that have to be respected within the source code. They aim in increasing safety of the code and its performance.

We hope to expand this list with time, so do not hesitate to submit new ideas to the content owner (defined on top of this document).

3.1. Lazy initialization

Do not do any real work in an object's constructor. Inside a constructor initialize variables only and only perform actions that can't fail and do not require too much time to compute. It is not our intention to always define open method. This situation should be only happening if the object needs to be prepared by performing some specific routines at runtime. Nevertheless, those routines cannot be put into the constructor.

Create an open() method for an object which completes construction. open() should be called after object instantiation.

Examples:


    class Device
    {
        const UNINITIALIZED_DEVICE_NAME = 'Uninitialized device name';

private $name = ''; private $type = 0;

public function __construct($deviceType) { $this->name = Device::UNINITIALIZED_DEVICE_NAME; $this->type = $deviceType; }

public function open() { // ... } }


3.2. Proper method code

Methods are not designed to remember data. Each of them should work strictly on the arguments passed through parameters. It is acceptable for methods to access state of the object in which they are defined.

Method, being associated with a particular object, may access or modify the data private to that object in a way consistent with the intended behavior of the object. Consequently, rather than thinking "a method is just a sequence of commands", a programmer using an object-oriented language will consider a method to be "an object's way of providing a service" (its "method of doing the job", hence the name); a method call is thus considered to be a request to an object to perform some task.

Always define methods with lowest visibility possible, to ensure the clarity of object's interface. Remember, that after you define a method public, it is probably going to stay that way for a long time.

Try to avoid usage of PHP magic methods (get, set, call) due to performance reasons. It is acceptable to use them only in situations that bring a huge value to the clarity of the code, and where they are not called too frequently.

See method description for more information on methods.

3.3. Usage of continue and break

Continue and break should be used very cautiously.

The two main problems with these commands are:

  • They implicitly bypass the test conditions of the loops
  • They might bypass the increment/decrement expressions
  • They break the default code structure defined by regular expressions

Mixing continue with break in the same loop is a sure way to disaster. In general you are strongly discouraged from using these commands.

3.4. "Missing code"

When writing an empty code block, always insert a comment describing why this block is (and should continue to be) empty. This rule also applies to code that was intentionally omitted. If you see a possibility of the code to execute and decide to ignore it (because of some environment boundaries) write a comment about it so the reader of the code will have the same knowledge as you have.

3.5. Safe code

Communication between classes on the level of sending messages and polymorphic calls, should be properly secured. Each message sent from different class inheritance level or a different object should be checked for validity upon arrival. Also, calls to subclass routines have to be enclosed in exception handling routines to prevent unexpected termination of execution.

Examples:

abstract class BasicObjectFactory {

abstract protected function instantiateObject($className);

protected function getObject($objectType) { $result = FALSE; if ($objectType == BasicObject::PROTOTYPE_OBJECT) { try { $result = instantiateObject(BasicObject::PROTOTYPE_CLASS_NAME); } catch (CreateObjectInstanceException $e) { // If the object doesn't get created, return default value } }

return $result; } }


3.6. Floats

Don't use floating-point variables where discrete values are needed. Using a float for a loop counter is a great way to shoot yourself in the foot. Always test floating-point numbers as =, never use an exact comparison (== or !=).

It is typical that simple decimal fractions like 0.1 or 0.7 cannot be converted into their internal binary counterparts without a small loss of precision. This can lead to confusing results: for example, floor((0.1+0.7)*10) will usually return 7 instead of the expected 8, since the internal representation will be something like 7.9.

This is due to the fact that it is impossible to express some fractions in decimal notation with a finite number of digits. For instance, 1/3 in decimal form becomes 0.3.

If higher precision is necessary, the arbitrary precision math and gmp functions are available.

Also, it is important to remember about locale settings (0.1 vs 0,1) when converting floating values to strings.

Examples:


/*
 * Two floating point numbers should be considered equal if their absolute
 * difference does not exceed a certain value epsilon. Epsilon in this case
 * defines the precision of the comparison
 */
    $epsilon = 0.000001;

if (abs($firstFloat - $secondFloat) < $epsilon) { // It can be assumed that the numbers are equal. }


3.7. Usage of NULL

Remember that NULL is not a value. It should be treated and identified with unknown state rather then empty. This is especially important with SQL queries (check SQL Coding Standard for details).

Handling NULL in your code:

  • when declaring a variable that will be checked against NULL and is not set in the constructor, you should initialize it with NULL
  • when verifing if something isn't NULL always use a strict comparison

Examples:


//  ...
    private $targetFilter;

// ... if ($this->targetFilter !== NULL) { $this->targetFilter = new TargetFilter(); } // ...


3.8. Type hinting

The use of type hinting is encouraged where possible with respect to the module design.

Examples:


    class MyClass {

public function test(OtherClass $otherclass) { echo $otherclass->var; }

public function test_array(array $input_array) { print_r($input_array); } }


3.9. Require / include

If a module uses another module, then the using module is responsible for loading the other one. If the use is conditional, then the loading should also be conditional.

If the file(s) for the other component should always load successfully, regardless of input, then use PHP's require_once statement.

The include, include_once, require, and require_once statements should not use parentheses.

Defined paths for including/requiring modules should always start with LIB_PATH constant to avoid lookups during the execution. If you are including/requiring files that are placed in a different root directory, use different constant to define a full path pointing to the file.

In general you should use only use require_once. Other forms are permitted (with a strong preferrence of require), but only in special cases like in templating, configuration loading etc.

3.10. One statement per line

Each line of the code can contain only one statement. The exception from this rule are logical statements, that contain execution and logic substatements. Still, in this case it is required to preserve as much clarity as possible. Also, the usage of time-consuming execution substatements is highly discouraged in logical statements.

3.11. Execution path breaking

The only acceptable way to break your code execution in the middle of the routine is by throwing an exception. Do not put return in the middle of the method / code.

Examples:


// (BAD) Instead of:
    public function getCoolKids() {
        // ... do some stuff

if ($this->isDisabled()) { return FALSE; }

// ... do some more stuff

return $coolKidsArray; }

// (GOOD) Use: public function getCoolKids() { // ... do some stuff

if ($this->isDisabled()) { throw new CannotRetrieveDataFromDisabledSet(); }

// ... do some more stuff

return $coolKidsArray; }


3.12. Passing one/many items through a parameter

If the method you have just written can accept a parameter to be a single value or an array of these values, you need to assure that the method will perform properly in both cases. To assure that check if the variable that represents the input parameter is an array. If not, wrap in into one and proceed like in the case of an array. This approach is meant to assure that there is only one processing code in your method.

Examples:


//  ...

public function delete($ids) { if (!is_array($ids)) { $ids = array($ids); }

foreach ($ids as $id) { // ... do some stuff } }


3.13. Closing PHP tag

Do not use the closing PHP tag. Ever.

3.14. Profanity

Do not use improper language in your source code. Ever. It is advised to put in funny comments though.

4. Miscellaneous

This chapter describes general rules that didn't fit in previous chapters.

4.1. File extensions

All files containing code or being inserted into PHP files / parsed by the PHP have to have a ".php" extension.

4.2. Globals usage

Introducing global variables is forbidden.

4.3. Optimizations

Always try to optimize your loops if operations are going on at the comparing part, since this part is executed every time the loop is parsed through. For assignments a descriptive name should be chosen.

Examples:


    for($i = 0, $size = count($post_data); $i < $size; $i++) {
// ...
    }

Also, avoid using in_array() on huge arrays, and do not place them into loops if the array to check consist of more than 20 entries. in_array() can be very time consuming and uses a lot of cpu processing time. For little checks it is not noticable, but if checked against a huge array within a loop those checks alone can be a bunch of seconds. If you need this functionality, try using isset() on the arrays keys instead, actually shifting the values into keys and vice versa. A call to isset($array[$var]) is a lot faster than in_array($var, array_keys($array)) for example.

Golden rules of optimization:

  • First make it work, then make it fast. Beware of premature optimization.
  • Optimize only the slow stuff.

4.4. Common mistakes

This is a list of functions that are commonly mistaken to do something else. The list also includes the recommendation for function usage.

  • empty() - commonly mistaken due to different results with different content. Most of the time it will not be neccesary to use this function. Usage of this function is discouraged.
  • isset() - commonly misused to verify if the variable has a value. In case of standard variables check against a NULL should be performed. In case of array values, array_key_exists check should be performed. Do not use this function.
  • unset() - commonly misused to reset a variable. In case of standard variables assign a NULL value rather then unset it. Only use this function to remove an array value.
  • goto - that was introduced in PHP 5.3. Do not use it.
  • is_a - do not use at all. Leaves instances of objects in the process' memory

Follow us