There are many ways to define a coding standard. Some like to set some basic guidelines and leave the rest to the developers. Others like to be as explicit as possible. What I am getting at is that there is no correct answer. Any given standard may or may not be appropriate for a given situation or project.
With that in mind, let's be true to the approach of this book and come up with a general-purpose coding standard that you can use as a starting point for your own projects. Our assumption will be that we are developing PHP5+ application, which is why our coding standard will specifically address and forbid some constructs and conventions commonly used in PHP4's object-oriented implementations.
Formatting paints the overall picture of the code. It's the first thing you see when you glance at it. It is also the one chance the developer has to make the code easily readable that does not require understanding of what the code actually does.
All PHP code is to be enclosed by full PHP tags:<?php
and ?>
. Short tags (<? and ?>
) or ASP-style tags (<% and %>
) are not allowed.
Tabs are to be replaced by four space characters. Most IDEs and text editors can be set to do this automatically.
The suggested maximum number of characters per line is 80 characters, although just about all GUI-based editing tools can easily accommodate more characters. This convention takes into consideration that command line interfaces are often limited to 80 characters. The absolute maximum number of characters per line is 120. Lines of code with more characters are not acceptable.
Lines must end only with a standard Unix linefeed (LF). Linefeeds are represented as ordinal 10, or hexadecimal 0x0A.
Carriage returns (CR) (0x0D), commonly used on Macintosh computers, and carriage return/linefeed combinations (CRLF) (0x0D, 0x0A), commonly used on Windows computers, are not allowed.
For readability, spaces are required at the following code sections:
After a comma separating method/function parameter lists or array members
Following control structure keywords, such as
if, else, unless, switch
, and so onBefore curly braces unless they are at the beginning of a line
Before and after logical operators, such as
&&, ||, &, |, ==, !=, ===
, and!==
Before and after arithmetic operators, such as
+, -, *
, and%
Before and after assignment operators, such as
=, +=, -=
, and*=
Put only one statement per line. Multiple statements on a single line are expressly forbidden because they are easily overlooked when reading the code. Spreading a single statement over multiple lines is discouraged unless it clearly improves readability of the code.
<?php // unnecessary and confusing echo ($errorCondition === true) ? "too bad\n" : "nice\n"; // keep ';' on the same line as the statement // don't do this: echo "goodbye!\n" ; // must not put multiple statements on a line echo 'An error has occurred'; exit; ?>
Use only double quotes to define strings if you are taking advantage of variable interpolation or the string contains formatting characters or single quotes, such as', \n
or \t
. In all other instances, single quotes should be used as they result in less work for the parser and faster execution of your script.
Strings exceeding the maximum line length should be broken into smaller segments and concatenated using the dot notation.
Long strings should use the heredoc
notation if possible.
<?php // defining a simple string $myOutput = 'This is an awesome book!'; $adjectives = array('nicer', 'better', 'cleaner'); // string with variable interpolation and formatting characters echo "Reading this book makes a good PHP programer $adjectives[1].\n"; // double quotes containing single quote echo "This book's content will make you a better developer!"; // defining long strings by breaking them up $chapterDesc = 'In this chapter, we are tyring to explore how' 'a thorough and clear common coding standard' 'benefits the project as well as each individual' 'developer.'; // I'm not much of a poet, but this is how you use the heredoc syntax $poem = <<<ENDOFSTRING Roses are red, violets are blue, this is my poem for the code in you. ENDOFSTRING; ?>
Numerically indexed arrays should have base index 0 whenever possible. For numerically indexed array, multiple values per line are allowed, but spaces must be inserted to align the first item of each line.
Associative arrays should be declared such that key-value pairs should be listed one to each line. White space must be inserted so as to align the names, assignment operators, and values of all items. Single quotes are always to be used if the key value is a string.
<?php // simple numerically indexed array $myFruits = array('apples', 'bananas', 'cherries'); // use bracket syntax within string to access array echo "My favorite fruits are {$myFruits[2]}.\n\n"; // longer numerically indexed array $myLongList = array('The', 'quick', 'brown' ,'fox', 'jumped', 'over', 'the', 'lazy', 'fox', '.'); // use bracket syntax with variable as index to access array $listSize = count($myLongList); for ($i = 0; $i < $listSize; $i++) { echo $myLongList[$i]; echo ($i < $listSize - 2) ? ' ' : ''; } echo "\n\n"; PHP coding standardPHP coding standardarrays// associative array; everything lines up $spanAdj = array('green' => 'verde', 'little' => 'poquito', 'big' => 'grande'); // using a foreach construct to access both keys and values foreach ($spanAdj as $english => $spanish) { echo "'" . $spanish . "' means '" . $english . "' in Spanish.\n"; } ?>
The above code fragment outputs the following text when executed:
Conditional tests should be written on one line if possible. For long and complex conditional tests, line breaks should be inserted before each combining operator with the line indented for combining operators to line up. The starting curly brace should appear on the same line as the last conditional test. Control statements should have one space between the control keyword and the opening parenthesis, so as to distinguish them from function calls.
<?php // all conditionals on the same line if ($myValue <= -1 || $myValue > 100) { doSomethingImportant(); } // too many conditional for one line // break up conditionals like this ... if ($myValue > 5 || $myValue < 5 || $myValue == 0 || $myValue == -3) { doSomethingElseImportant(); } ?>
The elseif
and else
keywords are to appear on the same line as the previous block's closing parenthesis:
The body of the switch
statement and the body of each case
statement must be indented. The opening curly bracket should appear on the same line as the test expression. A space character should precede the parentheses of the test expression. Finally, the last case
statement must be followed by a default
statement:
<?php switch ($temperature) { case 50: echo "Let's stay home and write some code.\n"; break; case 80: echo "Let's go to the beach\n"; break; default: echo "Go to jail. Do not pass go.\n"; break; } ?>
Choosing appropriate and descriptive names for your classes, methods, properties, and constants may seem like a trivial task, but it may well be the most important part of your job as a programmer — short of developing functional code. The names you choose will have a significant impact on the readability of your code and the ease with which other developers in your team will be able to follow your design and logic. It is also far from easy because it requires a deep and thorough understanding of the overall structure of your application.
Note
Stringing words together to construct the name of a method, function, variable, property, or class is commonly referred to as a camelCase if each new word starts with a capital letter, but all other letters are lowercase. No other word delimiters are used. Examples of camelCase formatting are: getStackLength(), updateInterval
, and DbLogWriter
.
Classes should be named after the objects they represent. Try to stay away from descriptive phrases (too long), incorporating parent class or interface names, or using verbs.
Examples of bad class names are:
ExcessInventoryProductOrderReceivedAndFilled
(too long and contains verb)IterableWidgetList
(incorporates interface name)
Examples of good names are as follows:
WidgetStack
DbFileCache
Class names should reflect the path relative to the root class directory. Directories are to be delimited by the underscore character ("_"). The first letter of each word/directory name is to be capitalized. No other characters are to be capitalized. In particular, abbreviations should adhere to this convention as well. Furthermore, only alphanumeric characters and the underscore character are allowed. Use of numbers is discouraged.
The following PHP segment takes a class file path and converts it to the corresponding class name:
<?php class ClassNameConverter { public static $classRootDir = array('var', 'www', 'sites', 'my_app', 'includes', 'classes'); public static function makeClassName($absolutePath) { $platformClassRootDir = DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, self::$classRootDir) . DIRECTORY_SEPARATOR; // remove path leading to class root directory $absolutePath = str_replace($platformClassRootDir, '', $absolutePath); // replace directory separators with underscores // and capitalize each word/directory $parts = explode(DIRECTORY_SEPARATOR, $absolutePath); foreach ($parts as $index => $value) { $parts[$index] = ucfirst(strtolower($value)); } // join with underscores $absolutePath = implode('_', $parts); // remove trailing file extension $absolutePath = str_replace('.php', '', $absolutePath); return $absolutePath; } } $classNameExamples = array('/var/www/sites/my_app/includes/classes/logging/db/Mysql.php', '/var/www/sites/my_app/includes/classes/logging/db/MysqlPatched.php', '/var/www/sites/my_app/includes/classes/caching_lib/Memcached.php' ); foreach ($classNameExamples as $path) { echo $path . ' converts to ' . ClassNameConverter::makeClassName($path) . "\n"; } ?>
Properties and variables should start with a lower case letter, contain only alphanumeric characters, and generally follow the "camelCase" convention. Every effort should be made to make property names as descriptive as possible without their length getting excessive. Underscore characters are not allowed in property and variable names.
Short variable names, such as $i
or $cnt
are only allowed in very short looping constructs.
Names of constants should only contain alphanumeric characters. Words are to be separated by the underscore character. Names should be as descriptive as possible, but more than three words are discouraged.
Similar to property names, method and function names should contain only alphanumeric characters and follow the "camel case" convention. They should start with a lower case letter and contain no underscore characters. Class methods must always be preceded by one of the three visibility specifiers: public, protected
, or private
.
Method names should be descriptive as to their purpose. However, excessive length is discouraged.
Accessor methods for object properties should always be named set<PropertyName>
and get<PropertyName>
.
The following listing illustrates proper names for properties, methods, classes, and constants:
<?php class MessageQueue { // upper case constant with underscores const MAX_MESSAGES = 100; // descriptive properties using camelCase private $messageQueue = array("one\ntwo"); private $currentMessageIndex = -1; // setter method for $currentMessageIndex public setCurrentMessageIndex($currentMessageIndex) { $this->currentMessageIndex = (int)$currentMessageIndex; } // getter method for $currentMessageIndex public getCurrentMessageIndex() { return $this->currentMessageIndex; } // is<Attribute> method returns boolean public function isQueueFull() { return count($this->messageQueue) == self::MAX_MESSAGES; } // has<Attribute> method returns boolean public function hasMessages() { return (is_array($this->messageQueue) && count($this->messageQueue) > 0); } // descriptive take action method public function resetQueue() { $this->messageQueue = null; } // descriptive take action method public function convertMessagesToHtml() { // local copy of message queue $myMessages = $this->messageQueue; // $i is acceptable in a short for-loop for ($i = 0; $i < sizeof($myMessages); $i++) { $myMessages[$i] = nl2br($myMessages[$i]); } return $myMessages; } // additional methods to manage message queue ... } ?>
In this section, we will look at some conventions that take advantage of object-oriented features found in PHP5 and later. Some of the recommendations listed as follows are generally considered best practices.
Whenever possible, functions should type-hint by specifying the class of the parameter. Furthermore, as of PHP 5.1, arrays can be type-hinted. Therefore, arrays should always be type hinted—especially when passing a hashed array instead of individual parameters.
<?php class SomeClass { // method requires an object of type MyClass public function doSomething(MyClass $myClass) { echo 'If we made it this far, we know that the name ' . 'of the class in the parameter: ' . get_class($myClass); } // method requires an array public function passMeAnArray(array $myArray) { // we don't need to test is_array($myArr) echo "The parameter array contains the following items:\n"; print_r($myArr); } } ?>
If something goes wrong during object creation, the calling code may be left in a state of uncertainty. For example, throwing an exception in the constructor will prevent the object from being instantiated. Therefore, it is advisable to separate construction and initialization of the object. A short constructor will be responsible for instantiating the object, after which, an init
function is responsible for handling object initialization.
<?php class MyClass { private myAttrib; // short constructor - no parameters this time public function __construct() { // intentionally left blank } // initialization method to be called // immediately after object instantiation public function init($var) { $this->myAttrib = trim$(var); } } // first we instantiate the object $myObject = new MyClass(); // then we initialize the object $myObject->init('a string literal'); ?>
Each class definition should be in a separate source file. And, there should be no additional code (outside the class) in that file. The name of the file should reflect the name of the class. For example, class Message
should be defined in the file Message.php
.
The directory hierarchy in which the class files are organized should be reflected in the name of the class. For example, assume that we decided to put our classes directory inside an includes
directory:
includes/ classes/ Parser/ FileParser/ CommonLog.php
The file CommonLog.php
should contain a class called Parser_FileParser_CommonLog
. This convention will make it easier to comprehend a class's position in the class hierarchy. It also makes it easier for the autoload()
function to locate classes.
All properties and methods of a class must have one of three visibility specifiers: public, protected
, or private
. Direct access to properties is discouraged in favor of corresponding getter and setter methods: get/set<Attribute>()
—even when accessing properties from within a class. Magic methods may be used to provide access to the properties.
<?php class AlwaysUseGetters { // private property not accessible outside this class private $myValue; // setter method public function setMyValue($myValue) { $this->myValue = $myValue; } // getter method public function getMyValue() { return $this->myValue; } public function doSomething($text) { // use getter to retrieve property return $text . $this->getMyValue() . '!'; } } // instantiate object $myAwc = new AlwaysUseGetters(); // use setter to set property $myAwc->setMyValue('book'); // call method to illustrate use of getter echo $myAwc->doSomething('This is an awesome '); ?>
Use require_once
to unconditionally include a PHP file. Analogously, use include_once
to conditionally include a PHP file, for example in a factory method. Since require_once
and include_once
share the same internal file list, a file will never be included more than once even if you're mixing the two constructs.
Never use require
or include
as they provide the same functionality as require_once
and include_once
, but leave open the possibility of unintentionally including the same PHP file multiple times.
<?php // use of require_once require_once('logging/Database/DbLogger.php'); class DbConnector { // these are the RDBMs we support public static $supportedDbVendords = array('mysql', 'oracle', 'mssql'); // factory method using include_once public static function makeDbConnection($dbVendor = 'mysql') { if (in_array($dbVendor, self::$supportedDbVendords)) { // construct the class name from the DB vendor name $className = ucfirst($dbVendor); include_once 'database/drivers/' . $className . '.php'; return new $className(); } else { // unsupported RDBMs -> throw exception throw new Exception('Unsupported RDBSs: ' . $dbVendor); } } } PHP coding standardPHP coding standardsource files, including// use factory method to get DB connection $dbHandle = MakeAnObject::makeDbConnection(); ?>
Developers are encouraged to provide inline comments to clarify logic. Double forward slashes (//) are used to indicate comments. C or Perl-style hash marks (#) to signal comments are not allowed. For readability, all inline comments are to be preceded by a blank line. Here is the previous listing with inline comments. Notice how blank lines were added to make it more readable.
<?php // inline comment preceding if statement if (is_array($myHash)) { // inline comment indented with code throw new Exception("myHash must be an array!"); // inline comment preceding elseif (preceded by blank line) } elseif (array_key_exists('index', $myHash)) { echo "The key 'index' exists.\n"; // inline comment preceding else (preceded by blank line) } else { echo "The key 'index' does NOT exists.\n"; } ?>
The phpDocumentor type documentation blocks are required in four places:
At the beginning of each PHP source code file
Preceding each class definition
Preceding each method definition
Preceding each class-level property definition
Following is a table of required and recommended phpDocumentor tags. Please refer to the chapter on documentation for more details.
Tag name |
File |
Class |
Method |
Property |
Usage |
---|---|---|---|---|---|
abstract |
x |
x |
x | ||
access |
x |
x |
public, private, or protected | ||
author |
x |
x |
[x] |
[x] |
author name <[email protected]> |
copyright |
x |
name date | |||
deprec |
[x] |
[x] | |||
deprecated |
[x] |
[x] | |||
example |
[x] |
[x] |
[x] |
path or url to example | |
extends |
[x] |
class name | |||
global |
[x] |
type $variableName | |||
link |
[x] |
[x] |
[x] |
[x] |
url |
package |
x |
x |
package name | ||
param |
x |
type [$name] description [default] | |||
return |
x |
type description | |||
see |
[x] |
[x] |
[x] |
file, class, or function name | |
since |
x |
date added to class | |||
static |
[x] |
[x] | |||
subpackage |
[x] |
[x] |
sub-package name | ||
throws |
[x] |
exceptionName | |||
todo |
[x] |
[x] |
[x] |
task description | |
var |
x |
type $name description [default] | |||
version |
x |
[x] |
auto-generated by source control |
where x = required [x] = recommended where applicable blank = do not use