By Ryan Parman. Updated August 13th, 2010.
Available under the Creative Commons Attribution 3.0 License.
Back in January 2005, when Geoffrey joined me in building SimplePie, the first thing he did was berate me for my (lack of) coding standards. From there, we came up with a set of coding standards that were readable, concise, fit well with PHP, and attempted to clean up some of PHP’s borked language patterns. As I branched outside of SimplePie and started working on other projects, I’ve developed what I believe is a solid, reasoned coding style that I can recommend to others.
These are the guidelines that I personally choose to follow for the code I write. This is not an indication that any other coding styles, guidelines or standards are wrong. Feel free to use this document as a template to manage your own coding guideline and change whatever you wish as you see fit.
It doesn’t matter what your guidelines are, so long as everyone understands and sticks to them. It cannot be emphasized enough that guidelines are only useful if they are followed. It’s no use having a definitive set of coding guidelines for a joint programming project if everyone continues to write in their own style.
If you are running a joint project, consider putting your foot down and refusing to accept any code that does not conform to your published guidelines. Once everyone begins to code to the guidelines you’ll find it easier to manage the project and you’ll get more work done in the same time. It will require a lot of effort from some of your developers who don’t want to abandon their coding habits, but at the end of the day different coding styles will cause more problems than they’re worth.
We all have IDEs now where we can customize the width of the indentations to our own preferences. Some like a width of 2, some 4, and others 8. Tabs allow us to choose our own preference on the matter while not being forced into someone else’s preference.
That said, if you (optionally) choose to align values, use spaces. Since tabs can be variable width, they can break alignment of things like values (see: TABs vs Spaces. The end of the debate.).
$colors_by_preference = array(
'blue' => 2,
'green' => 1,
'magenta' => 3,
);
Line-endings should use the \n character (LF) used by default on Mac OS X and *nix systems. Windows uses \r\n (CRLF) by default, so make sure that your IDE saves files with Unix-flavored line-endings.
Also, there should not be any trailing whitespace. Lots of IDEs allow you to configure this.
Save files as UTF-8, without the BOM.
Note that UTF-8 doesn’t mean ASCII, ANSI, ISO-8859-1, Windows-1252, MacRoman or even UTF-16.
In general, I believe that following the naming conventions inherent in the language itself is the best way to approach this question. In the cases of languages like Java and JavaScript, everything is an object and all methods are camelCase.
PHP is a different story, however. As previously noted, the PHP language itself seems to follow different standards: CapitalCase for classes, and snake_case or nocase for functions. Since nocase is more difficult to read, we’re going to go with snake_case for all function/method/variable names.
CapitalCase is the same as camelCase, but starts with a capital letter. snake_case is when all letters are lowercase, and words are separated by underscores.
The goal of whitespace is to enhance the readability of the code, while being as concise as possible.
if, for, foreach, do, while, switch, try-catch) should always have one space between the statement and the opening paren.++) and decrement (--) operators should not have a space between itself and the variable it’s incrementing/decrementing (e.g. $i++, --$j).!$condition).case 'x':).:x).-42).if control statement:if ($my_string === 'sample')
{
// code...
}
for control statementfor ($i = 0; $i < 10; $i++)
{
// code...
}
foreach control statement// A foreach block
foreach ($ingredients as $ingredient)
{
// code...
}
// Another foreach block with a single empty line between this and the last
foreach ($ingredients as $ingredient_name => $ingredient_quantity)
{
// code...
}
do control statementdo
{
// code...
}
while ($i = 0; $i < 10; $i++);
while control statementwhile ($i = 0; $i < 10; $i++)
{
// code...
}
switch control statementIn addition to the standard whitespace for switch statements, the contents of each case or default block should be indented for readability. This includes the closing break;. Also, there should always be a default: block.
switch ($method)
{
case 'PUT':
// code...
break;
default:
// code...
}
try-catch control statementtry
{
// code...
}
catch (Exception $e)
{
// code...
}
return constructThere should always be an empty line before an ending return statement.
function do_something()
{
$one = 1;
$two = 2;
$three = 3;
return $one;
}
When working on a shared codebase with other developers, you are encouraged to give increment/decrement operators (++ and --) a line of their own (with the exception of for loops).
$foo[$i++] = $j; // Discouraged: relies on $i being incremented after the expression is evaluated
$foo[--$j] = $i; // Discouraged: relies on $j being decremented before the expression is evaluated
$foo[$i] = $j;
$i++; // Encouraged: obvious when $i is incremented
$j--;
$foo[$j] = $i; // Encouraged: obvious when $j is decremented
Code must not produce any warnings or errors when PHP’s error reporting level is set to E_ALL | E_STRICT. A good tip is to set your development environment to error_reporting(-1).
PHP-only files should have an opening <?php tag, but should not have a closing tag! Adding a closing PHP tag can occasionally cause strange issues when importing. As such, leave out the closing ?> tag.
If you are working with PHP inside an HTML document, it is highly recommended to use the full <?php opening tag and ?> closing tag. Using the shorthand <? tag is not very portable, and should not be used with code that will be shared with the outside world.
If you are working with PHP code that will not be shared with the outside world (your website, perhaps), it is recommended to use the full <?php ?> block to notate multi-line blocks of PHP code inside HTML files. However, if you simply need to output a simple string value to the HTML, you may — at your option — use the shorthand <?= $value ?> format as it’s much less verbose.
Ideally, however, your project should follow a single pattern.
<?php echo $value; ?>
<?= $value ?>
For example:
foreach ($ingredients as $ingredient)
{
if ($ingredient === 'Bell peppers')
{
unset($ingredient);
}
}
The only time you can skip a brace is if you need to conditionally define a single variable that previously had a falsey value.
function my_function($opt = null)
{
if (!$opt) $opt = array();
// The rest of your code
}
The general rule is to use single-quotes for strings. The only time you would use double-quotes is when you need to evaluate a control character such as \n or \t. This is for performance reasons (see PHP: strings).
$string = "Avoid this - it just makes more work for the parser."; // Double quotes
$string = 'This is much better.' // Single quotes
An exception is when you know that there are going to be a lot of single-quotes in a string, and using double-quotes to wrap them would improve readability.
$sql = "SELECT `id`, `name` from `people` "
. "WHERE `name`='Fred' OR `name`='Susan'";
Non-documentation comments are strongly encouraged. A general rule of thumb is that if you look at a section of code and think “Wow, I don’t want to try and describe that”, you need to comment it before you forget how it works.
/* */) and standard C++ comments (//) are both fine.// style comments, and an extra space immediately inside both ends of a /* */ style comment.#) is discouraged.For example:
// Good Comment
/* Good Comment */
# Bad Comment
//Bad Comment
/*Bad Comment*/
If you are making a longer, multi-line comment, use line breaks instead of spaces immediately inside both ends of a /* */ style comment. The content may, at your option, be indented as well.
/*
This is an extra long comment that I'm breaking up
into multiple lines for improved readability.
*/
Parentheses are used to maintain a specific order-of-operations for your logic. Feel free to use parentheses as required by the programming logic, or as it would add clarity to a complex or unreadable bit of code.
if (($class_name instanceof SimpleXMLElement) || ($class_name instanceof CFSimpleXML))
{
// code...
}
That said, if a bit of code is perfectly readable without them, and the parentheses are optional, feel free to exclude them.
function test($options = null)
{
$options = $options ? $options : array();
}
If there is ever a discrepancy between English-speaking dialects, use U.S. English as a baseline for both spelling and word choice.
color (correct)
colour (incorrect)
Also:
spelled (correct)
spelt (incorrect)
The only time semi-colons are optional in PHP is when you embed PHP into HTML as a one-liner. Always use semi-colons, even when they’re optional, when using the full <?php ?> tag notation.
<?php echo $variable; ?>
Most studies claim that 75-85 characters is the optimal line length. Others have suggested up to 120-140 characters is likely fine.
The goal is readability and consistency. Use your best judgement on the matter, and follow the conventions that already exist in the file you’re working on.
There are certain constructs in PHP that are frequently treated as functions: echo, require, include, return, etc. Since they are not functions, you should not use parentheses when using them.
require_once 'simplepie.class.php'; // Good
require_once('simplepie.class.php'); // Bad
echo 'Hello world!'; // Good
echo('Hello world!'); // Bad
return false; // Good
return(false); // Bad
This guideline excludes cases where you are using any kind of logic which uses parentheses, which is perfectly valid.
echo ($condition === 1) ? ('Today is ' . date('F jS, Y')) : '';
See “Naming conventions”, above. Variables should be snake_case.
// Good
$data_type
// Bad
$datatype
$dataType
$DataType
If you ever find yourself needing to define global variables, their name should start with the class or package name and an underscore.
// Good
$myclass_data_type
// Bad
$bob
Constants should follow the same conventions as variables, except use all uppercase letters to distinguish them from variables. If your code is part of a class or package, prefix constant names with the uppercased name of the class/package they are used in.
// Good
SIMPLEPIE_TYPE_ATOM_10
// Bad
SIMPLEPIETYPEATOM10
simplepie_type_atom_10
TYPE_ATOM_10
type_atom_10
Class (or local) constants are the same as global constants, except that that only exist in the scope of the class they’re defined in. Since they only exist inside a class or package, you shouldn’t prefix them with the class name.
// Good
TYPE_ATOM_10
// Bad
TYPEATOM10
type_atom_10
SIMPLEPIE_TYPE_ATOM_10
This is the only occassion where short variable names (as small as one character in length) are permitted, and indeed encouraged. Unless you already have a specific counting variable, use $i as the variable for the outermost loop, then go onto $j for the next most outermost loop, etc. However, do not use the variable $l (lowercase L) in any of your code as it looks too much like the number one.
for ($i = 0; $i < 10; $i++)
{
for ($j = 0; $j < 10; $j++)
{
// Do something
}
}
Another recommendation is that arrays be plural, so that singular instances of that array are singular. This is useful for foreach loops.
foreach ($ingredients as $ingredient)
{
$ingredient->add_to_mixing_bowl();
}
Strings follow the same whitespace rules with regard to single spaces around operators. In the case where the line would end up being very long, feel free to add some indented linebreaks as necessary.
$string_one = 'This is the ' . $number . 'th day of the week';
$string_three = 'There '
. ($number === 1 ? ('is ' . $number . ' item') : ('are ' . $number . ' items'))
. ' in your cart!';
$string_two = 'Today is the ';
if ($number === 1)
{
$string_two .= '1st';
}
elseif ($number === 2)
{
$string_two .= '2nd';
}
elseif ($number === 3)
{
$string_two .= '3rd';
}
else
{
$string_two .= $number . 'th';
}
$string_two .= ' day of the week.';
Although usage of single-quotes is preferred for performance reasons, sometimes using variable substitution (which requires double-quotes) is easier to read. Variable substitution is permitted using either of these forms:
$greeting = "Hello $name, welcome back!";
$greeting = "Hello {$name}, welcome back!";
For consistency, this form is not permitted:
$greeting = "Hello ${name}, welcome back!";
Generally speaking, indexed arrays should be all on the same line, and key-value pairs (associative arrays) should be indented on new lines. The rationale is that a typical indexed array is shorter, and a typical associative array is longer.
There are exceptions of course, as when the values in an indexed array are long and readability would be improved by adding linebreaks. The goal is readability and consistency. Use your best judgement on the matter, and follow the conventions that already exist in the file you’re working on.
For multi-line arrays, the use of a trailing comma for the last item in the array is encouraged. This minimizes the impact of adding new items on successive lines, and helps to ensure no parse errors occur due to a missing comma.
// Good
$indexed_array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Good
$months_english = array(
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
);
If any array elements have their own line, they all should.
// Good
$associative_array = array(
'Key1' => 'Value',
'Key2' => 'Value',
'Key3' => 'Value',
'Key4' => 'Value',
'Key5' => 'Value',
);
// Good
$associative_array = array('Key1' => 'Value', 'Key2' => 'Value', 'Key3' => 'Value');
We want to avoid inconsistency where some are on the same line and others are not (this breaks from the Zend guidelines).
// Bad
$associative_array = array('Key1' => 'Value',
'Key2' => 'Value',
'Key3' => 'Value');
Class/Interface names should always be capitalized. There’s no hard and fast rule when it comes to the length of a name, so just try and be as concise as possible without affecting clarity too much.
/**
* DocBlock goes here
*/
class Utilities
{
// code...
}
In some cases, certain words (or abbreviations) are all-caps. It is recommended to maintain the all-caps pattern of these abbreviations.
/**
* DocBlock goes here
*/
class CacheAPC
{
// code...
}
/**
* DocBlock goes here
*/
class PHPRuntime
{
// code...
}
Class/Interface names may be either CapitalCased or Snake_Cased provided that each word is capitalized. Ideally, however, your project should follow a single pattern.
/**
* DocBlock goes here
*/
class CacheAPC extends CacheCore implements CacheInterface, BaseInterface
{
// code...
}
In some cases, a combination of both makes sense.
/**
* DocBlock goes here
*/
class SimplePie_Unicode
{
// code...
}
Namespacing is a new feature in PHP 5.3 that I’m not going to talk about here. Instead, I’m going to refer to the practice of prefixing a string to the beginning of a class name to avoid collisions with similarly-named classes.
/**
* DocBlock goes here
*/
class SimplePie_Unicode
{
// Project is called "SimplePie" and the class focuses on Unicode support.
}
/**
* DocBlock goes here
*/
class CFRuntime
{
// Classname is prefixed with "CF" for CloudFusion.
}
Function/method names must be snake_case. There’s no hard and fast rule when it comes to the length of a name, so just try and be as concise as possible without affecting clarity too much.
// Good
string_replace()
// Bad
str_replace() // Poor clarity
stringreplace() // nocase
stringReplace() // camelCase
StringReplace() // capitalized
Methods should always define their visibility (e.g. public, private, protected) first, then whether or not they are static.
/**
* DocBlock goes here
*/
public static function get_element()
{
// code...
}
The use of private class methods and properties should be avoided — use protected instead, so that another class could extend your class and change the method if necessary. Protected (and public) methods and properties should not use an underscore prefix, as was common in PHP 4-era code.
Default values for a parameter should follow the previously mentioned rules about spacing around operators and immediately inside parens.
/**
* DocBlock goes here
*/
public function __construct($key, $secret_key, $opt = null)
{
// code...
}
It can be difficult to remember the order of parameters once the list becomes longer than 3 or 4. If some parameters are required and some are optional (or if some parameters can be made optional), consider keeping the required parameters required, and passing all optional parameters as an associative array at the end.
/**
* DocBlock goes here
*/
public function __construct($key, $secret_key, $account_id, $opt = null)
{
// Handle required parameters
$this->key = $key;
$this->secret_key = $secret_key;
$this->account_id = $account_id;
// Handle optional parameters
if (isset($opt['Key1']))
{
// code...
}
return $this;
}
If you’re defining a collection of setter methods for your class, you are highly encouraged to make them chainable. Doing so simplifies the end-user’s ability to set several values at once.
/**
* DocBlock goes here
*/
public function set_cache_location($location = './cache')
{
$this->cache_location = $location;
return $this;
}
By returning $this (a reference to the current class), you can allow people to chain method calls together.
$feed = new SimplePie();
$feed->set_feed_url('http://example.com/feed.rss')
->set_cache_location('./cache')
->set_cache_duration(3600);
Call-time pass-by-reference is discouraged and will throw warnings if used. If you need to pass-by-reference, it should be part of the function signature instead of being determined at call-time.
Here’s an example of a (bad) call-time pass-by-reference:
function test($param)
{
// code...
}
test(&$param);
Here’s an example of a (good) pass-by-reference defined in the function signature:
function test(&$param)
{
// code...
}
test($param);
Function definitions follow the same guidelines as method definitions, except that “Visibility” and “Private Methods” guidelines don’t apply.
When instantiating a class, always use parentheses — even when they’re optional.
// Good
$feed = new SimplePie();
// Bad
$feed = new SimplePie;
These follow the same whitespace rules as the “Control statements & operators” section above.
// 3 required parameters
merge_objects($param1, $param2, $param3);
// 2 required parameters and an optional parameter
merge_objects($param1, $param2, array(
'Key1' => 'Value',
'Key2' => 'Value',
'Key3' => 'Value'
));
Methods follow essentially the same rules. In the case where methods are chainable, you can determine whether or not to break lines based on the line length. In this case, the newlines should be indented (but don’t necessarily need to line up).
// Method call
$feed = new SimplePie();
$feed->set_feed_url('http://example.com/feed.rss')
->set_cache_location('./cache')
->set_cache_duration(3600);
PHP 5.3 introduced real namespacing support (see PHP: namespaces). If your project doesn’t require PHP 5.3 or newer, you can skip this section.
Namespaced code should use braces (as opposed to no braces). Doing so adds clarity to the use of namespaced code.
namespace CloudFusion\AWS
{
class CloudFusion
{
// code...
}
class AmazonS3 extends CloudFusion
{
// code...
}
}
You can alias longer namespaces into something shorter in the local scope of your code (see PHP: Aliasing/Importing Namespaces). Importing is done by using the use keyword, while aliasing is done using the as keyword.
Only alias a namespace or class if you are going to refer to it by a different name than what it’s using; otherwise, simply import it.
namespace CloudFusion\AWS
{
// code...
class AmazonEC2 extends CloudFusion
{
// code...
}
class AmazonS3 extends CloudFusion
{
// code...
}
class AmazonSNS extends CloudFusion
{
// code...
}
}
namespace // Global namespace
{
use CloudFusion\AWS\AmazonS3 as S3; // Good; Used an alias to change the local reference
use CloudFusion\AWS\AmazonSNS; // Good; Didn't unnecessarily alias the class name
use CloudFusion\AWS\AmazonEC2 as AmazonEC2; // Bad; Same classname as what you're importing
$s3 = new S3();
$sns = new AmazonSNS();
$ec2 = new AmazonEC2();
}
If you are importing multiple namespaces, you can either:
use keyword and break them across lines (indented and delimited with a comma).use statements.For example:
// Good
use CloudFusion\AWS\AmazonS3 as S3,
CloudFusion\AWS\S3\Policy as S3Policy,
CloudFusion\Google\Storage,
CloudFusion\OpenStack\Nebula;
// Good
use CloudFusion\AWS\AmazonS3 as S3;
use CloudFusion\AWS\S3\Policy as S3Policy;
use CloudFusion\Google\Storage;
use CloudFusion\OpenStack\Nebula;
Filenames should follow a consistent pattern that more-or-less matches the way you name your classes. That said, files that contain classes should end with .class.php; files that contain interfaces and abstracts should end with .interface.php and .abstract.php, respectively; files that are simply includes (such as configuration files) should end with .inc.php.
The following examples are all potentially correct:
CacheCore.class.phpicachecore.interface.phpConfig.inc.phpsimplepie_unicode.abstract.phpThe important thing is to pick a single standard for the project and stick to it.
This entire section is ripped nearly verbatim from PEAR’s Error Handling Guidelines. Changes were made to the formatting of the code, but the contents of the PEAR Error Handling Guidelines offer sage advice.
An error is defined as an unexpected, invalid program state from which it is impossible to recover. For the sake of definition, recovery scope is defined as the method scope. Incomplete recovery is considered a recovery.
/**
* Connect to Specified Database
*
* @throws Example_Datasource_Exception when it can't connect to specified DSN.
*/
function connect_db($dsn)
{
$this->db =& DB::connect($dsn);
if (DB::isError($this->db))
{
throw new Example_Exception('Unable to connect to ' . $dsn . ': ' . $this->db->getMessage());
}
}
In this example the objective of the method is to connect to the given DSN. Since it can’t do anything but ask PEAR DB to do it, whenever DB returns an error, the only option is to bail out and launch the exception.
/**
* Connect to one of the possible databases
*
* @throws Example_Datasource_Exception when it can't connect to any of the configured databases.
* @throws Example_Config_Exception when it can't find databases in the configuration.
*/
function connect(Config $conf)
{
$dsns =& $conf->searchPath(array('config', 'db'));
if ($dsns === false)
{
throw new Example_Config_Exception('Unable to find config/db section in configuration.');
}
$dsns =& $dsns->toArray();
foreach ($dsns as $dsn)
{
try
{
$this->connectDB($dsn);
return;
}
catch (Example_Datasource_Exception $e)
{
/*
Some warning/logging code recording the failure
to connect to one of the databases.
*/
}
}
throw new Example_Datasource_Exception('Unable to connect to any of the configured databases');
}
This second example shows an exception being caught and recovered from. Although the lower level connect_db() method is unable to do anything but throw an error when one database connection fails, the upper level connect() method knows the object can go by with any one of the configured databases. Since the error was recovered from, the exception is silenced at this level and not rethrown.
/**
* load_config parses the provided configuration. If the configuration
* is invalid, it will set the configuration to the default config.
*/
function load_config(Config $conf)
{
try
{
$this->config = $conf->parse();
}
catch (Config_Parse_Exception $e)
{
// Warn/Log code goes here
// Perform incomplete recovery
$this->config = $this->defaultConfig;
}
}
The recovery produces side effects, so it is considered incomplete. However, the program may proceed, so the exception is considered handled, and must not be rethrown. As in the previous example, when silencing the exception, logging or warning should occur.
Error conditions must be signaled using exceptions. An exception should be thrown whenever an error condition is met, according to the definition provided in the previous section. The thrown exception should contain enough information to debug the error and quickly identify the error cause. Note that, during production runs, no exception should reach the end-user, so there is no need for concern about technical complexity in the exception error messages.
The kind of information to be included in the exception is dependent on the error condition. From the point of view of exception throwing, there are three classes of error conditions:
Errors detected during precondition checks should contain a description of the failed check. If possible, the description should contain the violating value. Naturally, no wrapped exception can be included, as there isn’t a lower level cause of the error. Example:
function divide($x, $y)
{
if ($y == 0)
{
throw new Example_Aritmetic_Exception('Division by zero');
}
}
The error description should try to convey all information contained in the original error. One example, is the connect method previously presented:
/**
* Connect to Specified Database
*
* @throws Example_Datasource_Exception when it can't connect to specified DSN.
*/
function connect_db($dsn)
{
$this->db =& DB::connect($dsn);
if (DB::isError($this->db))
{
throw new Example_Exception('Unable to connect to ' . $dsn . ':' . $this->db->getMessage());
}
}
Lower library exceptions, if they can’t be corrected, should either be rethrown or bubbled up. When rethrowing, the original exception must be wrapped inside the one being thrown. When letting the exception bubble up, the exception just isn’t handled and will continue up the call stack in search of a handler.
function pre_tax_price($retail_price, $tax_rate)
{
try
{
return $this->divide($retail_price, 1 + $tax_rate);
}
catch (Example_Aritmetic_Exception $e)
{
throw new Example_Tax_Exception('Invalid tax rate.', $e);
}
}
function pre_tax_price($retail_price, $tax_rate)
{
return $this->divide($retail_price, 1 + $tax_rate);
}
The case between rethrowing or bubbling up is one of software architecture: Exceptions should be bubbled up, except in these two cases:
Exceptions should never be used as normal program flow. If removing all exception handling logic (try-catch statements) from the program, the remaining code should represent the “One True Path” — the flow that would be executed in the absence of errors.
This requirement is equivalent to requiring that exceptions be thrown only on error conditions, and never in normal program states.
One example of a method that wrongly uses the bubble up capability of exceptions to return a result from a deep recursion:
/**
* Recursively search a tree for string.
* @throws ResultException
*/
public function search(TreeNode $node, $data)
{
if ($node->data === $data)
{
throw new ResultException($node);
}
else
{
search($node->leftChild, $data);
search($node->rightChild, $data);
}
}
In the example the ResultException is simply using the “eject!” qualities of exception handling to jump out of deeply nested recursion. When actually used to signify an error this is a very powerful feature, but in the example above this is simply lazy development.
Because PHP, unlike Java, does not require you to explicitly state which exceptions a method throws in the method signature, it is critical that exceptions be thoroughly documented in your method headers.
/**
* This method searches for aliens.
*
* @return array Array of Aliens objects.
* @throws AntennaBrokenException If the impedence readings indicate that the antenna is broken.
* @throws AntennaInUseException If another process is using the antenna already.
*/
public function find_aliens($color = 'green')
{
// code...
}
In many cases middle layers of an application will rewrap any lower-level exceptions into more meaningful application exceptions. This also needs to be made clear:
/**
* Load session objects into shared memory.
*
* @throws LoadingException Any lower-level IOException will be wrapped and re-thrown as a LoadingException.
*/
public function load_session_objects()
{
// code...
}
In other cases your method may simply be a conduit through which lower level exceptions can pass freely. As challenging as it may be, your method should also document which exceptions it is not catching.
/**
* Performs a batch of database queries (atomically, not in transaction).
*
* @throws SQLException Low-level SQL errors will bubble up through this method.
*/
public function batch_execute()
{
// code...
}
Exceptions play a critical role in the API of your library. Developers using your library depend on accurate descriptions of where and why exceptions might be thrown from your package. Documentation is critical. Also maintaining the types of messages that are thrown is also an important requirement for maintaining backwards-compatibility.
Because Exceptions are critical to the API of your package, you must ensure that you don’t break backwards compatibility by making changes to exceptions.
Things that break backwards compatibility include:
PEAR_Exception rather than a PEAR_IOException, you would be breaking backwards compatibility.Things that do not break backwards compatibility:
PEAR_IOException when before it had been throwing PEAR_Exception would not break backwards compatibility (provided that PEAR_IOException extends PEAR_Exception).Ternary operators may be used for simple boolean conditions.
Never chain more than a single ternary statement together as it makes the code more difficult to read.
echo ($condition === 1) ? 'Hello world!' :
($condition === 0) ? 'I hate the world!' : 'I don\'t care about the world.';
falseNever compare against false as it tends to make the logic more confusing.
echo ($condition === false) ? 'false' : 'true';
echo !$condition ? 'false' : 'true';
Most of the time, ternary statements will function just fine without any kind of parentheses.
echo $condition ? 'true' : 'false';
However, sometimes it’s helpful to add them for clarity. Generally speaking, if there is any kind of computation happening (e.g. string concatenations, mathematical operations) for either the true or false value, or if there are multiple conditions, you should wrap it in a set of parentheses.
$string = '123';
echo ($condition === 1) ? ($string .= '456') : 'false';
Since PHP is a loosely (a.k.a. dynamically, weakly) typed language, you need to pay extra care to understand what types of data you’re working with.
It is recommended that if you don’t know the type, or if the type isn’t exactly what you’re looking for, that you use typecasting to convert the type (see PHP: Type Juggling). This is especially important when an object defines a __toString() method and strings aren’t always really strings.
// Create a SimpleXMLElement object
$xml = simplexml_load_string('<?xml version="1.0" encoding="UTF-8"?><root><inner>value</inner></root>');
echo $xml->inner; // Will display "value"
echo ($xml->inner == 'value') ? 'true' : 'false'; // true
echo ($xml->inner === 'value') ? 'true' : 'false'; // false
Why did the first comparison display true and the second one false? Because the first is a fake string, while the second is a real string. If we use print_r(), we can see what the value really is:
print_r($xml->inner);
/* Displays:
SimpleXMLElement Object
(
[0] => value
)
*/
We can convert the SimpleXMLElement object into a real string with typecasting.
// Create a SimpleXMLElement object
$xml = simplexml_load_string('<?xml version="1.0" encoding="UTF-8"?><root><inner>value</inner></root>');
echo ((string) $xml->inner == 'value') ? 'true' : 'false'; // true
echo ((string) $xml->inner === 'value') ? 'true' : 'false'; // true
In this case, we converted a SimpleXMLElement object, $xml->inner, to an explicit string by typecasting it as such.
It is better to typecast a value when you’re storing into a variable, rather than when you’re trying to pull it from a variable.
Here’s a good example:
$value = (string) $xml->inner;
echo $value;
Here’s a bad example:
$value = $xml->inner;
echo (string) $value;
Typecasting should follow the same whitespace rules listed above. Use full words whenever possible.
// Good
(boolean) $result;
(integer) $value;
// Bad
(bool) $result;
( boolean ) $result;
(boolean)$result;
(boolean) $result;
(int) $value;
For those coming from a strongly-typed language background (e.g. Java, Ruby), the triple-equals (===) might be foreign to you.
Whereas double-equals (==) means equivalent value, triple-equals (===) means exact value. Or to put it another way, triple-equals means same value and type.
That’s why in the preceding example, the following was true — despite the fact that we were comparing a SimpleXMLElement object against a string.
$xml->value == 'value'
They have the same value, but not the same type. Since we were using the double-equals operator (which only compares values), the condition equated to true.
===When it comes to comparing absolute values such as strings and integers, this is a no-brainer. This, when used together with typecasting, will allow your code to execute faster because PHP doesn’t have to do the automatic type juggling at comparison-time.
if ($condition === 'string')
{
// code...
}
However, when it comes to dealing with falsey values (e.g. 0, null, false), it is recommended to use the == operator (or no operator at all) — unless you’re specifically looking for a 0, false or null.
if ($is_defined == true)
{
// code...
}
echo $condition ? 'true' : 'false';
To keep readability in functions and methods, it is wise to return early if simple conditions apply that can be checked at the beginning of a method. Here’s a bad example:
function foo($bar, $baz)
{
if ($foo)
{
// assume
// that
// here
// is
// the
// whole
// logic
// of
// this
// method
return $calculated_value;
}
else
{
return null;
}
}
It’s better to return early, keeping indentation and brain power needed to follow the code low.
function foo($bar, $baz)
{
if (!$foo)
{
return null;
}
// assume
// that
// here
// is
// the
// whole
// logic
// of
// this
// method
return $calculated_value;
}
PHP allows you to do variable assignments as part of a condition.
if ($time = strtotime('tomorrow, 9am') > time())
{
// code that uses $time...
}
Although I’ve certainly seen some clever uses of this feature, it can be very confusing for developers who think you have a typo (i.e. they assume you meant ==, when you intentionally meant =). Because of this, you are discouraged from doing variable assignments as part of conditionals.
Instead, try this approach:
$time = strtotime('tomorrow, 9am')
if ($time > time())
{
// code that uses $time...
}
Learn the difference between include_once and require_once, and use each as appropriate. To quote the PHP manual page on include:
“The two constructs are identical in every way except how they handle failure.
include()produces a Warning whilerequire()results in a Fatal Error.”
Fatal errors stop script execution.
Do not use the @ operator for error supression. It has a high performance cost, and can cause inexplicable errors when used incorrectly. Avoid the need to use this by ensuring that you’ve properly checked values all along the way to avoid errors in the first place (see PHP: Error Control Operators).
For consistency in all use-cases, use elseif instead of else if (see PHP: elseif/else if).
Perl-compatible regular expressions (PCRE, preg_ functions) should be used in preference to their POSIX counterparts. Never use the /e switch, use preg_replace_callback() instead.
Rather than using \n or \r\n as forced line breaks in your files, use the PHP_EOL constant instead. PHP will use whichever end-of-line character is most appropriate for the platform that it’s currently running on.
Use example.com, example.org and example.net for all example URLs and email addresses, per RFC 2606.
Never use extract() or eval(). The world will come to an end if you do.
These are existing coding standards that I reviewed while writing this document. Some ideas I liked, some I hated, and some I ripped-off verbatim.