Tuesday, August 9, 2016

Best Practices for Modern PHP Development_part1


1 Setup and configuration

1.1 Keep Current

Let's call this out from the very beginning - a depressingly small number of PHP installs in the wild are current, or kept current. Whether that is due to shared hosting restrictions, defaults that no one thinks to change, or no time/budget for upgrade testing, the humble PHP binaries tend to get left behind. So one clear best practice that needs more emphasis is to always use a current version of PHP (5.6.x as of this article). Furthermore, it's also important to schedule regular upgrades of both PHP itself and any extensions or vendor libraries you may be using. Upgrades get you new language features, improved speed, lower memory usage, and security updates. The more frequently you upgrade, the less painful the process becomes.

1.2 Set sensible defaults

PHP does a decent job of setting good defaults out of the box with its php.ini.development and php.ini.production files, but we can do better. For one, they don't set a date/timezone for us. That makes sense from a distribution perspective, but without one, PHP will throw an E_WARNING error any time we call a date/time related function. Here are some recommended settings:
  • date.timezone - pick from the list of supported timezones
  • session.save_path - if we're using files for sessions and not some other save handler, set this to something outside of /tmp. Leaving this as /tmp can be risky on a shared hosting environment since /tmp is typically wide open permissions-wise. Even with the sticky-bit set, anyone with access to list the contents of this directory can learn all of your active session IDs.
  • session.cookie_secure - no brainer, turn this on if you are serving your PHP code over HTTPS.
  • session.cookie_httponly - set this to prevent PHP session cookies from being accessible via JavaScript
  • More... use a tool like iniscan to test your configuration for common vulnerabilities
1.3 Extensions

It's also a good idea to disable (or at least not enable) extensions that you won't use, like database drivers. To see what's enabled, run the phpinfo() command or go to a command line and run this.
  1. $ php -i
The information is the same, but phpinfo() has HTML formatting added. The CLI version is easier to pipe to grep to find specific information though. Ex.
  1. $ php -i | grep error_log
One caveat of this method though: it's possible to have different PHP settings apply to the web-facing version and the CLI version.

2 Use Composer

This may come as a surprise but one of the best practices for writing modern PHP is to write less of it. While it is true that one of the best ways to get good at programming is to do it, there are a large number of problems that have already been solved in the PHP space, like routing, basic input validation libraries, unit conversion, database abstraction layers, etc... Just go to Packagist and browse around. You'll likely find that significant portions of the problem you're trying to solve have already been written and tested.

While it's tempting to write all the code yourself (and there's nothing wrong with writing your own framework or library as a learning experience) you should fight against those feelings of Not Invented Here and save yourself a lot of time and headache. Follow the doctrine of PIE instead - Proudly Invented Elsewhere. Also, if you do choose to write your own whatever, don't release it unless it does something significantly different or better than existing offerings.

Composer is a package manager for PHP, similar to pip in Python, gem in Ruby, and npm in Node. It lets you define a JSON file that lists your code's dependencies, and it will attempt to resolve those requirements for you by downloading and installing the necessary code bundles.

2.1 Installing Composer

We're assuming that this is a local project, so let's install an instance of Composer just for the current project. Navigate to your project directory and run this:
  1. $ curl -sS https://getcomposer.org/installer | php
Keep in mind that piping any download directly to a script interpreter (sh, ruby, php, etc...) is a security risk, so do read the install code and ensure you're comfortable with it before running any command like this.

For convenience sake (if you prefer typing composer install over php composer.phar install, you can use this command to install a single copy of composer globally:
  1. $ mv composer.phar /usr/local/bin/composer
  2. $ chmod +x composer
You may need to run those with sudo depending on your file permissions.

2.2 Using Composer

Composer has two main categories of dependencies that it can manage: "require" and "require-dev". Dependencies listed as "require" are installed everywhere, but "require-dev" dependencies are only installed when specifically requested. Usually these are tools for when the code is under active development, such as PHP_CodeSniffer. The line below shows an example of how to install Guzzle, a popular HTTP library.
  1. $ php composer.phar require guzzle/guzzle
To install a tool just for development purposes, add the --dev flag:
  1. $ php composer.phar require --dev 'sebastian/phpcpd'
This installs PHP Copy-Paste Detector, another code quality tool as a development-only dependency.

2.3 Install vs update

When we first run composer install it will install any libraries and their dependencies we need, based on the composer.json file. When that is done, composer creates a lock file, predictably called composer.lock. This file contains a list of the dependencies composer found for us and their exact versions, with hashes. Then any future time we run composer install, it will look in the lock file and install those exact versions.

composer update is a bit of a different beast. It will ignore the composer.lock file (if present) and try to find the most up to date versions of each of the dependencies that still satisfies the constraints in composer.json. It then writes a new composer.lock file when it's finished.

2.4 Autoloading

Both composer install and composer update will generate an autoloader for us that tells PHP where to find all the necessary files to use the libraries we've just installed. To use it, just add this line (usually to a bootstrap file that gets executed on every request):
  1. require 'vendor/autoload.php';
3 Follow good design principles

3.1 SOLID

SOLID is a mnemonic to remind us of five key principles in good object-oriented software design.

3.1.1 S - Single Responsibility Principle

This states that classes should only have one responsibility, or put another way, they should only have a single reason to change. This fits nicely with the Unix philosophy of lots of small tools, doing one thing well. Classes that only do one thing are much easier to test and debug, and they are less likely to surprise you. You don't want a method call to a Validator class updating db records. Here's an example of an SRP violation, the likes of which you'd commonly see in an application based on the ActiveRecord pattern.
  1. class Person extends Model
  2. {
  3.     public $name;
  4.     public $birthDate;
  5.     protected $preferences;
  6.     public function getPreferences() {}
  7.     public function save() {}
  8. }
So this is a pretty basic entity model. One of these things doesn't belong here though. An entity model's only responsibility should be behavior related to the entity it's representing, it shouldn't be responsible for persisting itself.
  1. class Person extends Model
  2. {
  3.     public $name;
  4.     public $birthDate;
  5.     protected $preferences;
  6.     public function getPreferences() {}
  7. }
  8. class DataStore
  9. {
  10.     public function save(Model $model) {}
  11. }
This is better. The Person model is back to only doing one thing, and the save behavior has been moved to a persistence object instead. Note also that I only type hinted on Model, not Person. We'll come back to that when we get to the L and D parts of SOLID.

3.1.2 O - Open Closed Principle

There's an awesome test for this that pretty well sums up what this principle is about: think of a feature to implement, probably the most recent one you worked on or are working on. Can you implement that feature in your existing codebase SOLELY by adding new classes and not changing any existing classes in your system? Your configuration and wiring code gets a bit of a pass, but in most systems this is surprisingly difficult. You have to rely a lot on polymorphic dispatch and most codebases just aren't set up for that. If you're interested in that there's a good Google talk up on YouTube about polymorphism and writing code without Ifs that digs into it further. As a bonus, the talk is given by Miško Hevery, whom many may know as the creator of AngularJs.

3.1.3 L - Liskov Substitution Principle

This principle is named for Barbara Liskov, and is printed below:
"Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program."
That all sounds well and good, but it's more clearly illustrated with an example.
abstract class Shape
  1. {
  2.     public function getHeight();
  3.     public function setHeight($height);
  4.     public function getLength();
  5.     public function setLength($length);
  6. }
This is going to represent our basic four-sided shape. Nothing fancy here.
  1. class Square extends Shape
  2. {
  3.     protected $size;
  4.     public function getHeight() {
  5.         return $this->size;
  6.     }
  7.     public function setHeight($height) {
  8.         $this->size = $height;
  9.     }
  10.     public function getLength() {
  11.         return $this->size;
  12.     }
  13.     public function setLength($length) {
  14.         $this->size = $length;
  15.     }
  16. }
Here's our first shape, the Square. Pretty straightforward shape, right? You can assume that there's a constructor where we set the dimensions, but you see here from this implementation that the length and height are always going to be the same. Squares are just like that.
  1. class Rectangle extends Shape
  2. {
  3.     protected $height;
  4.     protected $length;
  5.     public function getHeight() {
  6.         return $this->height;
  7.     }
  8.     public function setHeight($height) {
  9.         $this->height = $height;
  10.     }
  11.     public function getLength() {
  12.         return $this->length;
  13.     }
  14.     public function setLength($length) {
  15.         $this->length = $length;
  16.     }
  17. }
So here we have a different shape. Still has the same method signatures, it's still a four sided shape, but what if we start trying to use them in place of one another? Now all of a sudden if we change the height of our Shape, we can no longer assume that the length of our shape will match. We've violated the contract that we had with the user when we gave them our Square shape.
This is a textbook example of a violation of the LSP and we need this type of a principle in place to make the best use of a type system. Even duck typing won't tell us if the underlying behavior is different, and since we can't know that without seeing it break, it's best to make sure that it isn't different in the first place.
Written by Brian Fenton

If you found this post interesting, follow and support us.
Suggest for you:

Learning PHP 7: From the Basics to Application Development

The Complete PHP 7 Guide for Web Developers

Up to Speed with PHP 7

Learn PHP 7 This Way to Rise Above & Beyond Competion!

The Complete PHP with MySQL Developer Course (New)

No comments:

Post a Comment