ruk·si

🐘 PHP
Basics

Updated at 2015-04-01 10:29

PHP is general use scripting and programming language usually used in web development. These notes contain good set of common rules to follow when writing PHP. This guide contains basic syntax and tips how to write PHP.

Quality PHP projects require exceptional amount of discipline. Biggest problem with PHP is that it is too forgiving. You start writing code and suddenly you have working website. After a while, you recruit a new programmer and they do not understand any of it.

Always start your project by using newest PHP possible. Keep an eye out for new PHP versions and always upgrade if possible. PHP has had quite a few security leaks that have been fixed.

Always do the simplest things that works, but understand how it works. Not easiest to code or with least line of code but the simplest thing.

Avoid heavy logic in presentational layer like templates. Even better if you only use HTML templating engine like Mustache.

Use file system to group your code. Create new directories and move related code together.

Use unit testing frameworks:

  • PHPUnit: The most used testing framework.

Use static analysis tools:

  • PHP CodeSniffer: Checks violations of a defined coding standard. Required.
  • PHP Mess Detector: Checks code base for possible bugs, suboptimal code and unused parameters. Good but not required.
  • PHP CopyPasted Detector: Checks code base for identical code snippets. Good but not required.
  • PHP Depend: Checks what parts of your code are overly complex and need refactoring. Good but not required.

Architecture

Always use full-form PHP tags. It is safer to use full-form tags because there are servers that do not support short tags and will cause needless problems.

// bad
<?
// ...
?>

// bad
<?=
// ...
?>

// good
<?php
// ...
?>

Never close PHP tags if the file contains only PHP code. Prevents accidental whitespace injection into HTTP response.

<?php
class Foo {
    // ...
}
?> <- Bad!

Avoid switching between PHP and HMTL as much as possible.

Avoid inlining PHP and HTML.

Prefer generating HTML at the PHP side.

Prefer using require_once and include_once over require and include. They both use same file list, no need to worry about that.

// Failing include_once generates a warning but continues.
// Failing require_once generates a fatal error and does not continue.

include_once 'my-file.php';
require_once 'my-file.php';

Code Layout

The most important code formatting rule is to be consistent. Always prefer using coding style of frameworks and libraries you use.

Be consistent with your indention. I prefer 4 space indention over tabs.

Limit lines to 80 characters in pure PHP files. More in master coding guide.

There should be no character limit in PHP files that mainly contain HTML. HTML markup easily gets past 80 characters if you are not using templating engine.

Align your code to the left. Realigning code after changes is retarded, does not improve readability significantly and in the end developers stop aligning them so the readability benefit is lost. However, you may consider aligning code in configuration files.

// bad
private $project = '';
private $status  = '';
public  $name    = '';

// good
private $project = '';
private $status = '';
public $name = '';

Use parentheses if it shows code structure. Makes slightly complex code easier to read.

$isLoggedIn = ($this->auth->isLoggedIn() === TRUE);

Always include braces when they are optional. Missing braces can cause hard to debug problems, especially if you are developing without a good IDE.

// bad
if (!$isLoggedIn) return;

// good
if (!$isLoggedIn) {
    return;
}

Keep your brace style consistent. When not restricted, I place all opening braces are at the end of the line, regardless of context.

class Foo {
    function bar($baz) {
        if ($baz) {
            // ...
        } elseif ($bar) {
            // ...
        } else {
            // ...
        }

        while ($i <= 10) {
            // ...
        }

        switch ($beer) {
            // ...
        }
    }
}

Add spaces around control structure evaluation block. Control structures are not function calls. Never even consider removing these.

// bad
if($baz){
    // ...
}elseif($bar){
    // ...
}else{
    // ...
}

// good
if ($baz) {
    // ...
} elseif ($bar) {
    // ...
} else {
    // ...
}

Do not add spaces inside control structure evaluation block. Consider adding if it helps readability.

// bad, usually
if ( $baz ) {
    // ...
} elseif ( $bar ) {
    // ...
}

// good
if ($baz) {
    // ...
} elseif ($bar) {
    // ...
}

Do not add space before function declaration parameter block. Then function declaration and call style would be inconsistent.

// bad
function bar ($baz) {
    // ...
}

// good
function bar($baz) {
    // ...
}

Do not add space inside function declaration parameter block. Consider using multiple lines to promote readability.

// bad
function bar( $baz ) {
    // ...
}

// good
function bar($baz) {
    // ...
}

// good if declaration passes 80 characters or is hard to read
function bar(
    $baz,
    $foo,
    $biz
) {
    // ...
}

Naming

The most important naming rule is to be consistent. Always prefer using naming standard of frameworks and libraries you use. The following how I name stuff when nothing is restricted.

$variablesAndKeysAndObjectPropertiesLikeThis
// Standard variable naming in majority of popular programming languages.
// Then your variables look the same as in JavaScript and
// variables passed to JSON look the same on both sides.

NAMED_CONSTANTS_UPPER_SNAKE_CASE
// Native constants are named like this so you should follow it.

ClassesCamelCase
// Standard library classes use this naming.

classMethodsNamesLowerFirstCamelCase();
// Standard library class methods use this naming.

global_function_names_lower_snake_case();
// Most native global functions use this naming.

Namespaces\AreWritten\InCamelCase;
// Namespaces should mimic file path and class name.

Files should be named after their contents. Oh yes, I've seen this rule being broken so many times.

// If file contains a single class, use that for the name.
// Remember that Unix is case sensitive, so reference the file with right
// lettering.
ClassesCamelCase.php

// If a file does not contain a single class, name it with lower snake case.
does_not_contain_a_single_class.php
template_files_do_not_contain_a_class.php

// Directories should also be named in camel case.
DirectoryNamesFollowNamespaceNaming

Understand how PHP namespaces work and use them. Namespaces should contain three parts: vendor, subspace and class. There is a autoloader function for this naming style in my PHP snippets.

namespace foo;
use My\Full\ClassName as Another;

// This is the same as use My\Full\NSname as NSname.
use My\Full\NSname;

// Importing a global class.
use ArrayObject;
$a = new ArrayObject(array(1));
// Without the `use ArrayObject` we would instantiate foo\ArrayObject

// Vendor name should always be top level and same within company or group.
// Vendor can have as many subspaces they wants and you can have multiple
// in once definition. Each subspace should represent a directory.
// Subspace can have as many class namespaces they want.
// When you add `.php` to the end, you should get to the related file.
use \VendorName\Subspace\Subspace\ClassName;

// Underscores should be converted to directory separators on autoloader.
use \Company\Package\Class_Save;
// => /lib/Company/Package/Class/Save.php

Handle filenames as case-sensitive as they are handled as such in Unix. Underscore (_) in class name means directory separator in path so a single huge class can easily be split to multiple files.

// ClassName.php
class ClassName {
    // ...
}

// ClassName/Additional.php
class ClassName_Additional {
    // ...
}

When using abbreviations, do not capitalize the whole abbreviation. Follow naming rules. Most native PHP classes e.g. HttpMessage and JsonSerializable follow this style.

// bad
class DOMDocument {}
class HTTPServerError {}

// good
class DomDocument {}
class HttpServerError {}

Variable name length should depend on the life scope of the variables. $i is a good name for three row loop iterator, but bad for global singleton objects.

Avoid too generic names. Refactor when encountered but be consistent inside the project.

// bad
$x;
$info;
$data;
$result;
$row;

// good
$reservationSql;
$sqlParameters;
$allReservations;
$reservation->id;

Avoid numbers in names. There is usually a better way to name two similar variables.

// bad
$user1;
$user2;

// good
$oldUser;
$newUser;

Do not include underscores _ in private properties. Use private keyword instead. Using underscore is a dated practice and serves no purpose in the modern PHP world.

// bad
var $_variable;
function _getTemplate($name) {
    // ...
}

// good
private $variable;
private function getTemplate($name) {
    // ...
}

Comments

Create one line or small multiline comments with //.

// This is a comment!

// This is also
// acceptable!

Create long multiline comments with /* */.

/*
 * This is a multi-line comment giving more
 * detailed context to the next part of the code.
 * After three lines, you should really use these multiline comments,
 * especially if you are going to draw some ASCII charts here ;)
 */

Comments functions and classes using PHPDoc standard /** */.

// http://phpdoc.org

/**
 * @param int $codeId
 * Specific code ID to retrieve.
 * @param string $codeType [optional]
 * Retrieves all codes that are of this type.
 * @param string $codeAbbreviation [optional]
 * Retrieves all codes that are of this abbreviation.
 * If type specicifed, further filters return set.
 * @param string $sortOrder [optional]
 * Optional sort order of result set. Defaults to PK (i.e. id)
 *
 * @return array|string
 * Depending on the parameters supplied, either returns a code row,
 * an array of code rows, or a string.
 */
protected static function getCodes(
    $codeId,
    $codeType = NULL,
    $codeAbbreviation = NULL,
    $sortOrder = NULL
) {
    // ...
}

Include author info. When you release your code to the public, write a header with the license and author to every file.

/**
 * Short description for the file
 *
 * LICENSE: license information.
 *
 * @author First Author <author@example.com>
 * @author Second Author <another@example.com>
 * @license http://www.php.net/license/3_01.txt  PHP License 3.01
 * @link http://pear.php.net/package/PackageName
 */

Variables

Understand how isset() and empty() language constructs work. There is only one place you should use them: to test whether a variable that may or may not exist exists (isset) and optionally whether it's also falsy (empty). For example when accessing GET/POST parameters or associative array items.

// `isset()` returns true if the variable exists and is not NULL.
// Does not trigger an error if the variable does not exist.

// `empty()` returns true if the variable does not exist or its value equals
// false in a loose comparison. Does not trigger an error if the variable
// does not exist.

Null is of type null which can only have one value: null. It has a case-insensitive constant NULL. Null is used to mean the absence of a value, while being a value in itself. Variable is NULL when: 1) it has been declared but not set any value, 2) variable has been defined as NULL or 3) if the variable has been unset().

$hello = 'Hello there!';
unset($hello);
// It is NULL.

$another = NULL;
// It is NULL.

Use associative arrays, they are fast. Avoid using plain stdClass objects if they have no behaviour.

// bad
$person = new stdClass();
$person->id = 'abc-123';
$person->name = 'John Doe';

// good
$person = array(
    'id' => 'abc-123',
    'name' => 'John Doe'
);

Each variable has a primitive type and a class type. Use primitive type checks when you require basic type checking.

is_bool();
is_integer();
is_double();
is_string();
is_object();
is_array();
is_resource();
is_null();

You can hint class type on a function parameter. Hinting of primitive types should be coming in future PHP versions.

class Samurai {
    public function swing() {
        echo 'HYAAA!';
    }
}
class Knight {
    public function swing() {
        echo 'HOOOOO!';
    }
}

function train(Samurai $samurai)  {
    $samurai->swing();
}

$sammy = new Samurai();
train($sammy);

$nigel = new Knight();
train($nigel);
// => Catchable fatal error: ...

Constants should be defined by using const, not define(). Not always possible, but has better performance.

// bad
define('ENVIRONMENT', 'development');

// good
const ENVIRONMENT = 'development';

// checking constant
if (!defined('ENVIRONMENT')) {
    define('ENVIRONMENT', 'development');
}

Strings

Prefer single quotes ' over double quotes ". Single quoted strings are faster because parser does not need to search for variables.

// bad
$name = "Tommi";

// good
$name = 'Tommi';

Use double quotes " to embed variables and special characters. Prefer $var and {$var} over ${var}. For consistency when searching variables.

// good
$name = 'Ruksi';
$aString = "Hello $name!";
$bString = "\r\n"; // a newline

Use double quotes " if the string contains single quotes.

// bad
$aString = 'Dog\'s Life';

// good
$aString = "Dog's Life";

Allow strings to go multiline. If a string goes multiline, make statement continuation obvious by starting newlines with ..

$stringVariable = 'Hello! This variable is a little bit longer than expected '
    . 'so it continues on the next line.';

Prefer built-in escaping functions over escaping strings yourself. Use external libraries if no native function provides what you are looking for.

// HTML
htmlspecialchars($str, ENT_QUOTES);

// URL
urlencode();

// JavaScript
addslashes();
htmlspecialchars();

Learn basic string manipulation functions by heart. You will need them.

// Check if a string is empty.
$var === ''
// or empty($var) if might not exist.

// Comparing two strings
$a == $b

// Check if a string contains another string
strstr( $haystack, $needle )

// Check if a string starts with another string
strpos($haystack, $needle) === 0

// Check if a string ends with another string
substr($haystack, -strlen($needle)) === $needle

// Replace a string inside another string
str_replace($search, $replace, $subject)
// or regex: preg_replace('/search/', $replace, $subject)

// Trim characters from the beginning and end of a string
trim($string, ',');

// Split a string into an array
explode(',', $string)
// or regex: preg_split('/,/', $string)

// Merge an array to a string
implode(' ', $array)

Globals

Prefer class constants over any globals.

// good
class MyClass {
    const MY_CONSTANT = 'constant value';
    function showMyConstant() {
        echo  self::MY_CONSTANT;
    }
}
echo MyClass::MY_CONSTANT;
$class = new MyClass();
$class->showMyConstant();

Prefer superglobals over normal globals. More about superglobals.

// bad
$HTTP_SERVER_VARS

// good
$_SERVER

References

Avoid using references &. You may consider using references if you really understand how they work. They can create hard problems to debug. Good usages of reference are when you are you are passing massive amounts of data or want to modify the original value.

$x = 1;
$y = 2;
$x = $y; // $x now contains the same value as $y
$z = &$y; // $z now contains a reference to $y.
// Changing the value of $z will also change the value of $y and vice-versa.
// But $x will remain unchanged in both cases.

// Function example
function foo(&$var) {
    $var++;
}
$a = 5;
foo($a); // $a is now 6

Control Structures

Multi-part control structures are not separated by a newline.

// bad
if ($baz) {
    // ...
}
else {
    // ...
}

// good
if ($baz) {
    // ...
} else {
    // ...
}
// bad
try {
    // ...
}
catch (Exception $e) {
    // ...
}

// good
try {
    // ...
} catch (Exception $e) {
    // ...
}
// bad
do {
    // ...
}
while ($condition);

// good
do {
    // ...
} while ($condition);

Conditional statements can be preprocessed as variables. Name variable exactly what it tests.

$inGroup = (condition && condition);
$hasProperty = (condition || ! condition);

if ( $inGroup && $hasProperty ) {
    actOnIt();
}

When conditional condition goes multiline, prefer splitting lines on binary operators and parenthesis. Then it is easier to see the logic when operator starts the rows.

if ($condition1
    || $condition2
    && (
        $condition3
        || $condition4
    )
) {
    runUs();
}

Avoid long ternary operators. They can get hard to read really easily.

$a = ($condition && $conditionTwo) ? ($valueOne + 1) : (($valueTwo - 2) / 2);

Start newlines with ? and : if ternary operator goes multiline.

$a = ($conditionOne && $conditionTwo)
    ? $valueOne
    : $valueTwo;

switch-statement cases should be indented. Always end case with break, return or throw.

switch ($condition) {
    case 1:
        actOne();
        break;
    case 2:
        actTwo();
        break;
    default:
        actDefault();
        break;
}

for-loop.

for ($x = 0; $x < 10; $x++) {
    echo $x;
}

while-loop.

$i = 0;
while ($i < 5) {
    echo $i++;
};

do-while-loop.

do {
    echo $i++;
} while ($i < 5);

foreach-loop.

$numbers = ['one' => 1, 'two' => 2, 'three' => 3];
foreach ($numbers as $number) {
    echo $number;
}
foreach ($numbers as $key => $value) {
    echo "$key is same as $value.";
}

Calculate count() before the loops. Has better performance and cleaner code. Not always possible though.

// Loop a numerical a indexed array of strings
$i = count($array);
while ($i--) {
    // ...
}

Functions

Function parameters can go on multiple lines. When parameters go multiline, place each on newline.

function functionName($value, $valueWithDefault = NULL) {
    // ...
}

function anotherFunctionName(
    $valueOne,
    $valueTwo,
    $valueThree = NULL
) {
    // ...
}

PHP 5.3+ has first-class functions. Utilize anonymous functions.

$greet = function($name)
{
    printf("Hello %s\r\n", $name);
};
$greet('World'); // Hello World
$greet('PHP');   // Hello PHP
$cameled = preg_replace_callback('~[\\W]([\\w])~', function ($match) {
    return strtoupper($match[1]);
}, "Yello-world-boy-Dont What what");
echo $cameled; // YelloWorldBoyDontWhatWhat

Functions can inherit variables. You inherit variables from parent's scope with use.

$message = 'first';
$example1 = function () { var_dump($message); };
$example2 = function ($message) { var_dump($message); };
$example3 = function () use ($message) { var_dump($message); };  // inherited
$example4 = function () use (&$message) { var_dump($message); }; // reference
$example1();            // NULL
$example2($message);    // first
$example3();            // first
$example4();            // first
$message = 'second';
$example1();            // NULL
$example2($message);    // second
$example3();            // first
$example4();            // second
$create_logger = function($name) {
    return function($message) use ($name) {
        echo $name . ': ' . $message;
    };
};
$log = $create_logger('Controller');
$log('SNAFU!');
// => Controller: SNAFU!

Optional parameters always come last. Otherwise they are not really optional.

// bad
public function getKnight($kingdomId = 0, $knightId) {
    // ...
}

// good
public function getKnight($knightId, $kingdomId = 0) {
    // ...
}

Use func_ functions to create variable parameter functions. To receive call parameters without specifying them in function declaration, use func_num_args(), func_get_arg() and func_get_args().

function foo() {
    $argumentCount = func_num_args();
    $arguments = func_get_args();
    for ( $i = 0; $i < $argumentCount; $i++ ) {
        echo "Argument $i is: " . $arguments[$i] . "<br>\n";
    }
}

Function calls can go multiline. Function calls should go to multiple lines if you hit line character limit. Each parameter should go to a newline.

$this->myVeryOwnFunction(
    $value,
    array(
        $value,
        $value
    )
);

Do not use language constructs like functions. echo, print, require_once, include_once, return etc. They are not functions and should not look like functions.

// bad
$foo = 'foobar';
echo("foo is $foo");

// good
$foo = 'foobar';
echo "foo is $foo";

Return early by using guard statements. Makes code nicer to read.

// bad
public function getCarModel( $carModelId ) {
    if (is_int($carModelId)) {
        // ...
    }
    else {
        return NULL;
    }
}

// good
public function getCarModel( $carModelId ) {
    if ( ! is_int($carModelId) ) {
        return NULL;
    }
    // ...
}

Avoid silence operator @ if you do not know what you are doing. It slows down your code.

// bad, most of the time
@fopen('http://example.com/another-not-existing-file', 'r');
// -> Nothing.

// better
fopen('http://example.com/another-not-existing-file', 'r');
// -> Warning: fopen() ...

Parameters are passed by reference or value. Objects are passed by reference. Value types are passed by value, meaning they are copied.

// Is $a modified by the function or not?
// Even the compiler does not know it.
foo($a);
function amyfy($person) {
    $person->name = "Amy";
}
$jack = new StdClass();
$jack->name = "Jack";
amyfy($jack);
echo $jack->name; // => Amy

Classes

// Basic class usage.
$my_class = new MyClass('This is very secret!');
echo $my_class->myPublicProperty;
echo $my_class->myMethod();

// Static method usage.
echo MyClass::CONTAINED_CONSTANT_VALUE;
echo MyClass::myStaticMethod();

Class declaration.

class MyClass {

    public $myPublic = 'public';
    protected $myProtected = 'protected';
    private $myPrivate = 'private';

    const MY_CONSTANT = 'constant';
    public static $myStatic = 'static';

    // Constructor
    public function __construct($newValue) {
        $this->myPrivate = $newValue;
    }

    // Destructor.
    // Called when moved from memory.
    // When `unset()` is called or no more references exist.
    public function __destruct() {
        // ...
    }

    // Method
    public function myMethod() {
        return 'I am an instance of MyClass!';
    }

    // Static method
    public static function myStaticMethod() {
        return 'I am so static!';
    }
}
class MyOtherClass extends MyClass {
    // You can access superclass with `parent::`
}

Prefer composite classes and strategy pattern over inheritance.

// good
class Lesson {
    private $duration;
    private $costStrategy;
    public function __construct($duration, CostStrategy $strategy) {
        $this->duration = $duration;
        $this->costStrategy = $strategy;
    }
    public function cost() {
        return $this->costStrategy->cost($this);
    }
}

Use accessor methods to hide internal properties. Allows extending accessing the object e.g. logging.

// bad
$item = new Item();
$item->name = 'Potion';
echo $item->name;

// good
$item = new Item();
$item->setName('Potion');
echo $item->getName();

Understand what are abstract classes and interfaces. Prefer using interfaces over abstract classes. Interfaces are defined using interface and used with implements.

// good
interface Chargeable {
    public function getPrice();
}
class Product implements Chargeable {
    private $price;
    private $discount;
    public function getPrice() {
        return ($this->price - $this->discount);
    }
}
class Shelf {
    private $items = array();
    public function add(Chargeable $item) {
        $this->items[] = $item;
    }
}

Property visibility should always start as private. Change it to protected or public only if you need to.

Avoid interceptors that are called when accessing undefined properties. Makes code hard to follow. You may use them when building a framework but clearly document their use in the system.

// `__get`, `__set`, `__isset`, `__unset` and `__call`

Use __autoload only in well documented places and if project wide. It can greatly simplify dependency issues with sessions and reduces the i/o of requires, but can make code harder to understand since you do not know what is being included until runtime.

Define and use __clone to copy objects.

class Person {
    private $id;
    private $name;
    public function __construct($name) {
        $this->name = $name;
    }
    public function setId($id) {
        $this->id = $id;
    }
    public function __clone() {
        $this->id = 0;
        // If any of the properties are objects, clone them too,
        // e.g. $this->account = clone $this->account;
    }
}

// bad
$first = new Person("John");
$second = $first; // NONONO!

// good
$first = new Person("John");
$second = clone $first;

Understand how static works. You can use class context in place of object instance context.

ClassName::$variableSharedToAllInTheClass;
ClassName::functionSharedToAllInTheClass();

// Inside the class, you can just use self::

Late static binding is done using static().

abstract class DomainObject {
    public static function create() {
        // calling "self()" would call DomainObject, not an extending class.
        return new static();
    }
}
class User extends DomainObject {}
class Document extends DomainObject {}
Document::create(); // calls Document's create() method if it exists.

Error Handling

Always set error reporting to E_ALL|E_NOTICE in development. Use E_STRICT if possible, but turned off on live production servers

Prefer throwing exceptions over returning error codes.

When creating own exceptions, always inherit base PHP Exception class and subclass name should always end in Exception.

class MyClassNameException extends Exception {}

Catch exceptions and try to recover from them, throw new exception if cannot, but also deliver information about the lower level exception.

Comment functions that throw exceptions with @throws and tell why.

Exceptions should never get to output on production.

Important Native Functions

// Prints information about a variable to output.
var_dump($var);
print_r($var);

// Returns information about a variable.
var_export($var, TRUE);

// Variable to and from JSON.
json_encode($var)
json_decode($var)

// Return all classes in array defined when this function is ran.
get_declared_classes();

// Returns name of the variable class type.
// Returns false if only has primitive type e.g. string.
get_class($var);

// Returns names of public methods in a class.
get_class_methods('ClassName');

// PHP native functions assume one byte is one character.
// Use multibyte variants if you are using e.g. UTF-8 because
// some character like ä are composed of multiple bytes.
substr();
mb_substr($string, 0, 1, 'UTF-8');

Sources