PHP Best Practices

Introduction PHP is a complex language that has suffered years of twists, bends, stretches, and hacks. It’s highly inconsistent and sometimes buggy. Each version has its own unique features, warts, and quirks, and it’s hard to keep track of what version has what problems. It’s easy to see why it gets as much hate as it does sometimes. Despite that, it’s the most popular language on the web today. Because of its long history, you’ll find lots of tutorials on how to do basic things like password hashing and database access. The problem is that out of five tutorials, you have a good chance of finding five totally different ways of doing something. Which way is the “right” way? Do any of the other ways have subtle bugs or gotchas? It’s really hard to find out, and you’ll be bouncing around the internet trying to pin down the right answer. That’s also one of the reasons why new PHP programmers are so frequently blamed for ugly, outdated, or insecure code. They can’t help it if the first Google result was a four-year-old article teaching a five-year-old method! This document tries to address that. It’s an attempt to compile a set of basic instructions for what can be considered best practices for common and confusing issues and tasks in PHP. If a low-level task has multiple and confusing approaches in PHP, it belongs here. What this is It’s a guide suggesting the best direction to take when facing one of the common low-level tasks a PHP programmer might encounter that are unclear because of the many options PHP might offer. For example: connecting to a database is a common task with a large amount of possible solutions in PHP, not all of them good ones—thus, it’s included in this document. It’s a series of short, introductory solutions. Examples should get you up and running in a basic setting, and you should do your own research to flesh them out into something useful to you. It points to what we consider the state-of-the-art of PHP. However, this means that if you’re using an older version of PHP, some of the features required to pull off these solutions might not be available to you. This is a living document that I’ll do my best to keep updated as PHP continues to evolve. What this isn’t This document is not a PHP tutorial. You should learn the basics and syntax of the language elsewhere. It’s not a guide to common web application problems like cookie storage, caching, coding style, documentation, and so on. It’s not a security guide. While it touches upon some security-related issues, you’re expected to do your own research when it comes to securing your PHP apps. In particular, you should carefully review any solution proposed here before implementing it. Your code, and your copy and paste, is your own fault. It’s not an advocate of a certain coding style, pattern, or framework. It’s not an advocate for a certain way of doing high-level tasks like user registration, login systems, etc. This document is strictly for low-level tasks that, because of PHP’s long history, might be confusing or unclear. It’s not a be-all and end-all solution, nor is it the only solution. Some of the methods described below might not be what’s best for your particular situation, and there are lots of different ways of achieving the same ends. In particular, high-load web apps might benefit from more esoteric solutions to some of these problems. Back to top

What PHP version are we using? PHP 7.2.10-0ubuntu​0.18.04.1, installed on Ubuntu 18.04 LTS. PHP is the 100-year-old tortoise of the web world. Its shell is inscribed with a rich, convoluted, and gnarled history. In a shared-hosting environment, its configuration might restrict what you can do. In order to retain a scrap of sanity, we’re going to focus on just one version of PHP: PHP 7.2.10-0ubuntu0.18.04.1. This is the version of PHP you’ll get if you install it using apt-get on an Ubuntu 18.04 LTS server. In other words, it’s the sane default used by many. You might find that some of these solutions work on different or older versions of PHP. If that’s the case, it’s up to you to research the implications of subtle bugs or security issues in these older versions. Back to top

Storing passwords Use the built-in password hashing functions to hash and compare passwords. Hashing is the standard way of protecting a user’s password before it’s stored in a database. Many common hashing algorithms like md5 and even sha1 are unsafe for storing passwords, because hackers can easily crack passwords hashed using those algorithms. PHP provides a built-in password hashing library that uses the bcrypt algorithm, currently considered the best algorithm for password hashing. Example <?php // Hash the password. $hashedPassword will be a 60-character string. $hashedPassword = password_hash('my super cool password', PASSWORD_DEFAULT); // You can now safely store the contents of $hashedPassword in your database! // Check if a user has provided the correct password by comparing what they typed with our hash password_verify('the wrong password', $hashedPassword); // false password_verify('my super cool password', $hashedPassword); // true ?> Gotchas Many sources will recommend that you also “salt” your password before hashing it. That’s a great idea, and password_hash() already salts your password for you. That means that you don’t have to salt it yourself. Further reading How to safely store a password Back to top

Auto-loading classes Use spl_autoload_register() to register your auto-load function. PHP provides several ways to auto-load files containing classes that haven’t yet been loaded. The older way is to use a magic global function called __autoload() . However you can only have one __autoload() function defined at once, so if you’re including a library that also uses the __autoload() function, then you’ll have a conflict. The correct way to handle this is to name your autoload function something unique, then register it with the spl_autoload_register() function. This function allows more than one __autoload() function to be defined, so you won’t step on any other code’s own __autoload() function. Example <?php // First, define your auto-load function. function MyAutoload($className){ include_once($className . '.php'); } // Next, register it with PHP. spl_autoload_register('MyAutoload'); // Try it out! // Since we haven't included a file defining the MyClass object, our auto-loader will kick in and include MyClass.php. // For this example, assume the MyClass class is defined in the MyClass.php file. $var = new MyClass(); ?> Further reading PHP Manual: spl_autoload_register()

Stack Overflow: Efficient PHP auto-loading and naming strategies Back to top

Single vs. double quotes from a performance perspective It doesn’t really matter. A lot of ink has been spilled about whether to define strings with single quotes (‘) or double quotes (“). Single-quoted strings aren’t parsed, so whatever you’ve put in the string, that’s what will show up. Double-quoted strings are parsed and any PHP variables in the string are evaluated. Additionally, escaped characters like

for newline and \t for tab are not evaluated in single-quoted strings, but are evaluated in double-quoted strings. Because double-quoted strings are evaluated at run time, the theory is that using single-quoted strings will improve performance because PHP won’t have to evaluate every single string. While this might be true on a certain scale, for the average real-life application the difference is so small that it doesn’t really matter. So for an average app, it doesn’t matter what you choose. For extremely high-load apps, it might matter a little. Make a choice depending on what your app needs, but whatever you choose, be consistent. Further reading PHP Manual: Strings

The PHP Benchmark (Scroll down to Quote Types)

Stack Overflow: Is there a performance benefit to single quotes vs double quotes in PHP? Back to top

define() vs. const Use define() unless readability, class constants, or micro-optimization are concerns. Traditionally in PHP you would define constants using the define() function. But at some point PHP gained the ability to also declare constants with the const keyword. Which one should you use when defining your constants? The answer lies in the little differences between the two methods. define() defines constants at run time, while const defines constants at compile time. This gives const a very slight speed edge, but not one worth worrying about unless you’re building large-scale software. define() puts constants in the global scope, although you can include namespaces in your constant name. That means you can’t use define() to define class constants. define() lets you use expressions both in the constant name and in the constant value, unlike const which allows neither. This makes define() much more flexible. define() can be called within an if() block, while const cannot. Example <?php // Let's see how the two methods treat namespaces namespace MiddleEarthCreatures\Dwarves; const GIMLI_ID = 1; define('MiddleEarth\Creatures\Elves\LEGOLAS_ID', 2); print(\MiddleEarth\Creatures\Dwarves\GIMLI_ID); // 1 print(\MiddleEarth\Creatures\Elves\LEGOLAS_ID); // 2; note that we used define(), but the namespace is still recognized // Now let's declare some bit-shifted constants representing ways to enter Mordor. define('TRANSPORT_METHOD_SNEAKING', 1 << 0); // OK! const TRANSPORT_METHOD_WALKING = 1 << 1; // Compile error! const can't use expressions as values // Next, conditional constants. define('HOBBITS_FRODO_ID', 1); if($isGoingToMordor){ define('TRANSPORT_METHOD', TRANSPORT_METHOD_SNEAKING); // OK! const PARTY_LEADER_ID = HOBBITS_FRODO_ID // Compile error: const can't be used in an if block } // Finally, class constants class OneRing{ const MELTING_POINT_CELSIUS = 1000000; // OK! define('MELTING_POINT_ELVISH_DEGREES', 200); // Compile error: can't use define() within a class } ?> Because define() is ultimately more flexible, it’s the one you should use to avoid headaches unless you specifically require class constants. Using const generally results in more readable code, but at the expense of flexibility. Whichever one you use, be consistent! Further reading Stack Overflow: define() vs const

PHP Manual: Constants

Stack Overflow: define() vs variable Back to top

Caching PHP opcode Lucky you: PHP has a built-in opcode cache! In older versions of PHP, every time a script was executed it would have to be compiled from scratch, even if it had been compiled before. Opcode caches were additional software that saved previously compiled versions of PHP, speeding things up a bit. There were various flavors of caches you could choose from. Lucky for us, the version of PHP that ships with Ubuntu 18.04 includes a built-in opcode cache that’s turned on by default. So there’s nothing for you to do! Further reading PHP Manual: Opcache Back to top

PHP and Memcached If you need a distributed cache, use the Memcached client library. Otherwise, use APCu. A caching system can often improve your app’s performance. Memcached is a popular choice and it works with many languages, including PHP. However, when it comes to accessing a Memcached server from a PHP script, you have two different and very stupidly named choices of client library: Memcache and Memcached. They’re different libraries with almost the same name, and both are used to access a Memcached instance. It turns out that the Memcache d library is the one that best implements the Memcached protocol. It includes a few useful features that the Memcache library doesn’t, and seems to be the one most actively developed. However if you don’t need to access a Memcached instance from a series of distributed servers, then use APCu instead. APCu is supported by the PHP project and has much of the same functionality as Memcached. Installing the Memached client library After you install the Memcached server, you need to install the Memcached client library. Without the library, your PHP scripts won’t be able to communicate with the Memcached server. You can install the Memcached client library on Ubuntu 16.04 by running this command in your terminal: sudo apt-get install php-memcached Using APCu instead Before Ubuntu 14.04, the APC project was both an opcode cache and a Memcached-like key-value store. Since the version of PHP that ships since Ubuntu 14.04 now includes a built-in opcode cache, APC was split into the APCu project, which is essentially APC’s key-value storage functionality—AKA the “user cache”, or the “u” in APCu—without the opcode-cache parts. Installing APCu You can install APCu on Ubuntu 16.04 by running this command in your terminal: sudo apt-get install php-apcu Example <?php // Store some values in the APCu cache. We can optionally pass a time-to-live, but in this example the values will live forever until they're garbage-collected by APCu. apcu_store('username-1532', 'Frodo Baggins'); apcu_store('username-958', 'Aragorn'); apcu_store('username-6389', 'Gandalf'); // You can store arrays and objects too. apcu_store('creatures', array('ent', 'dwarf', 'elf')); apcu_store('saruman', new Wizard()); // After storing these values, any PHP script can access them, no matter when it's run! $value = apcu_fetch('username-958', $success); if($success === true){ print($value); // Aragorn } $value = apcu_fetch('creatures', $success); if($success === true){ print_r($value); } $value = apcu_fetch('username-1', $success); // $success will be set to boolean false, because this key doesn't exist. if($success !== true){ // Note the !==, this checks for true boolean false, not "falsey" values like 0 or empty string. print('Key not found'); } apcu_delete('username-958'); // This key will no longer be available. ?> Gotchas If you’re migrating APCu code from a version of APUc before 16.04, note that the function names have changed from apc_* to apcu_* . For example, apc_store() became apcu_store() . Further reading PHP Manual: Memcached

PECL: APCu

Stack Overflow: Using Memcache vs Memcached with PHP

Stack Overflow: Memcached vs APC, which one should I choose? Back to top

PHP and regex Use the PCRE ( preg_* ) family of functions. Before PHP 7 came around, PHP had two different ways of using regular expressions: the PCRE (Perl-compatible, preg_* ) functions and the POSIX (POSIX extended, ereg_* ) functions. Each family of functions used a slightly different flavor of regular expression. Luckily for us, the ereg_* functions have been removed in PHP 7, so this source of confusion is past us. Gotchas Remember to use the /u flag when working with regexes, to ensure you’re working in Unicode mode. Further Reading PHP Manual: PCRE

Getting started with PHP regular expressions Back to top

Serving PHP from a web server Use PHP-FPM. There are several ways of configuring a web server to serve PHP. Back in the stone age, we would use Apache’s mod_php. Mod_php attaches PHP to Apache itself, but Apache does a very bad job of managing it. You’ll suffer from severe memory problems as soon as you get any kind of real traffic. Two new options soon became popular: mod_fastcgi and mod_fcgid. Both of these keep a limited number of PHP processes running, and Apache sends requests to these interfaces to handle PHP execution on its behalf. Because these libraries limit how many PHP processes are alive, memory usage is greatly reduced without affecting performance. Some smart people created an implementation of fastcgi that was specially designed to work really well with PHP, and they called it PHP-FPM. This was the standard solution for web servers since Ubuntu 12.04. In the years since Ubuntu 12.04, Apache introduced a new method of interacting with PHP-FPM: mod_proxy_fcgi. We’ll use this module to route PHP requests received by Apache to the FPM instance. The following example is for Apache 2.4.29, but PHP-FPM also works for other web servers like Nginx. Installing PHP-FPM and Apache You can install PHP-FPM and Apache on Ubuntu 18.04 by running these command in your terminal: sudo apt-get install apache2 php-fpm sudo a2enmod proxy_fcgi rewrite First, we’ll create a new PHP FPM pool that will serve our app. Paste the following into /etc/php/7.2/fpm/pool.d/mysite.conf : [mysite] user = www-data group = www-data listen = /run/php/mysite.sock listen.owner = www-data listen.group = www-data pm = ondemand pm.max_children = 10 (Note that you can include many other very interesting options when configuring PHP-FPM pools. Of particular interest is the php_​admin_​value[include_​path] option.) Next, we’ll configure our Apache virtualhost to route PHP requests to the PHP-FPM process. Place the following in your Apache configuration file (in Ubuntu the default one is /etc/apache2/sites-available/000-default.conf ; if you're using the default configuration, paste this into the existing <VirtualHost> directive). <VirtualHost *:80> <Directory /> Require all granted </Directory> RewriteEngine on RewriteCond %{REQUEST_FILENAME} \.php$ RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -f RewriteRule . proxy:unix:/run/php/mysite.sock|fcgi://localhost%{DOCUMENT_ROOT}%{REQUEST_FILENAME} [P] </VirtualHost> Finally, restart Apache and the FPM process: sudo systemctl restart apache2.service php7.2-fpm.service Further reading PHP Manual: PHP-FPM

PHP-FPM homepage

Apache2 and php fpm performance optimization — Step-by-step guide Back to top

Sending email Use PHPMailer. Tested with PHPMailer 6.0.6. PHP provides a mail() function that looks enticingly simple and easy. Unfortunately, like a lot of things in PHP, its simplicity is deceptive and using it at face value can lead to serious security problems. Email is a set of protocols with an even more tortured history than PHP. Suffice it to say that there are so many gotchas in sending email that just being in the same room as PHP’s mail() function should give you the shivers. PHPMailer is a popular and well-aged open-source library that provides an easy interface for sending mail securely. It takes care of the gotchas for you so you can concentrate on more important things. Example <?php // Include the PHPMailer library require_once('phpmailer-5.2.7/PHPMailerAutoload.php'); // Passing 'true' enables exceptions. This is optional and defaults to false. $mailer = new PHPMailer(true); // Send a mail from Bilbo Baggins to Gandalf the Grey // Set up to, from, and the message body. The body doesn't have to be HTML; check the PHPMailer documentation for details. $mailer->Sender = 'bbaggins@example.com'; $mailer->AddReplyTo('bbaggins@example.com', 'Bilbo Baggins'); $mailer->SetFrom('bbaggins@example.com', 'Bilbo Baggins'); $mailer->AddAddress('gandalf@example.com'); $mailer->Subject = 'The finest weed in the South Farthing'; $mailer->MsgHTML('<p>You really must try it, Gandalf!</p><p>-Bilbo</p>'); // Set up our connection information. $mailer->IsSMTP(); $mailer->SMTPAuth = true; $mailer->SMTPSecure = 'ssl'; $mailer->Port = 465; $mailer->Host = 'my smtp host'; $mailer->Username = 'my smtp username'; $mailer->Password = 'my smtp password'; // All done! $mailer->Send(); ?> Further reading PHPMailer at Github Back to top

Validating email addresses Use the filter_var() function. A common task your web app might need to do is to check if a user has entered a valid email address. You’ll no doubt find online a dizzying range of complex regular expressions that all claim to solve this problem, but the easiest way is to use PHP’s built-in filter_var() function, which can validate email addresses. Example <?php filter_var('sgamgee@example.com', FILTER_VALIDATE_EMAIL); // Returns "sgamgee@example.com". This is a valid email address. filter_var('sauron@mordor', FILTER_VALIDATE_EMAIL); // Returns boolean false! This is *not* a valid email address. ?> Further reading PHP Manual: filter_var()

PHP Manual: Types of filters Back to top

Checking if a value is null or false Use the === operator to check for null and boolean false values. PHP’s loose typing system offers many different ways of checking a variable’s value. However it also presents a lot of problems. Using == to check if a value is null or false can return false positives if the value is actually an empty string or 0. isset() checks whether a variable has a value that is not null, but doesn’t check against boolean false . The is_null() function accurately checks if a value is null, and the is_bool() function checks if it’s a boolean value (like false ), but there’s an even better option: the === operator. === checks if the values are identical, which is not the same as equivalent in PHP’s loosely-typed world. It’s also slightly faster than is_null() and is_bool() , and looks nicer than using a function for comparison. Example <?php $x = 0; $y = null; // Is $x null? if($x == null){ print('Oops! $x is 0, not null!'); } // Is $y null? if(is_null($y)){ print('Great, but could be faster.'); } if($y === null){ print('Perfect!'); } // Does the string abc contain the character a? if(strpos('abc', 'a')){ // GOTCHA! strpos returns 0, indicating it wishes to return the position of the first character. // But PHP interpretes 0 as false, so we never reach this print statement! print('Found it!'); } //Solution: use !== (the opposite of ===) to see if strpos() returns 0, or boolean false. if(strpos('abc', 'a') !== false){ print('Found it for real this time!'); } ?> Gotchas When testing the return value of a function that can return either 0 or boolean false , like strpos() , always use === and !== , or you’ll run in to problems. Further reading PHP Manual: Comparison operators

Stack Overflow: is_null() vs === Back to top

Removing accent marks (diacritics) Most web guides will suggest using PHP’s iconv() function to remove diacritics. However iconv() often has trouble with UTF-8 input and will sometimes produce surprising errors. A better way is to use PHP’s intl library. It can be installed with: sudo apt install php-intl Once you have it installed, use the Transliterator class to remove diacritics from text: <?php $transliterator = Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', Transliterator::FORWARD); print($transliterator->transliterate('Êl síla erin lû e-govaned vîn.')); ?> Back to top