Writing modern, clean and scalable PHP applications doesn’t require the use of a framework. Though there are many frameworks available, many a time, these are unnecessary. The first article in this series on PHP guides readers on how to incorporate clean PHP into their next Web project.
PHP is a popular server side scripting language that powers a significant chunk of the World Wide Web. The early 2000s saw high adoption of the language at major websites; however, in subsequent years, there has been a steady decline in its use as other technologies have come along.
Often, PHP gets the rap because of how it was initially designed. This is because of the developers rather than the language itself. PHP has a relatively gradual learning curve, and so it’s easier for new developers to get started with it.
The language has gone through significant improvements over the years. A major leap was PHP 7.0 which came with a faster engine and paved the way for developers to build modern Web applications with ease.
Do note that in this article, I greatly emphasise writing ‘clean’ code, a term that doesn’t have a formal definition, but most will agree that it possesses the following qualities:
- Readability
- Separation of concerns
- Loose coupling
- High cohesion
- Well structured
Now that we are clear about what we want to accomplish, let’s dive into it. This article assumes that you are using PHP 7+.
Dependency management
Having worked on PHP for quite some time now, it’s not surprising to find PHP based repositories that either do not use proper dependency management, or copy the source files from the vendor into their repositories.
PHP has its own dependency management system! It’s called Composer and it works pretty well. You can easily incorporate it into a project and it will manage your dependencies for you. It will take care of versioning and auto-loading, letting you focus on writing your business logic.
Head over to https://getcomposer.org/ and install Composer following the instructions on the well documented website. You can install it locally to your project or globally. And if you are going to work on more than one PHP project in your life, installing Composer globally will make your life a bit easier.
Installing packages from Composer is as easy as:
composer require twig/twig
Composer will generate a composer.json and a composer.lock file, which will contain all your project configuration and dependency information. A separate directory will be downloaded at your root folder called vendor/, which will contain the actual source files of your dependencies.
To use the dependencies, simply load the autoloader, using the following command:
require_once “vendor/autoload.php”
Then you can use the dependencies by simply importing them in your project files:
use Twig\Environment; $twig = new Environment(...);
As you can see here, we do not include individual source files. We simply import them using their name spaces, and the autoloader includes the necessary files for us.
Structuring and name spacing
Composer allows us to define our own name spaces, so we can import our project files in a clean, uniform way without using include or require statements. In the composer.json file, simply add the following json:
“autoload”: { “psr-4”: { “YourProject\\”: “src/” }, },
This allows the autoloader to point to the src/ folder. So, now you can import the files using the use statement like we saw earlier.
Name spacing is done in a way that is similar to how it is done in Java. A file in src/Controller/Home.php will be in the YourProject\Controller name space. You can import it by using the following command:
namespace YourProject; use YourProject\Controller\Home;
You also have to define the name space of the current scope. In the above snippet, the name space is YourProject, which means that the file is in the src/ directory. Similarly, the Home class will have a name space as shown below:
namespace YourProject\Controller; class Home { … }
It’s a good idea to structure your project in a uniform way that describes its intent and keeps similar things together. Here is a sample directory structure for a good starting point:
app/ • config/ ◦ db_config.xml • cache/ src/ • Model/ • View/ • Controller/ public/ • index.php • css/ • js/ • img/ composer.json composer.lock vendor/
As you can see, the directories are structured according to their purpose. The src/ directory contains only PHP source files relevant to your project, while the public/ directory contains files and assets that are to be accessed by your users.
It’s 2019, and combining HTML and PHP together in the same source file with business logic is a very bad practice. Consider using a templating language like Twig. We will discuss templating in the next article in this series.
Dependency injection
Now that we have understood how to separate your concerns, let’s take a step further and decouple our code to make it adhere to SOLID principles. The D in SOLID stands for the Dependency Inversion Principle.
Details of the principle will not be discussed here, but we will see how we can implement it on our project. Let’s suppose we have a class called Car. A car needs wheels, engines, etc, to work. So those are the dependencies of the car. The Car class can look something like what’s shown below:
namespace YourProject\Model; public class Car { private $engine; private $wheels; public function __construct() { $this->engine = new V8Engine(); $this->wheels = new SteelWheels(); } ... }
What’s the problem here? Well, the car seems to build its own wheels and engine. While in reality, you should be handing over the engine and the wheels to the car to use. And that way you can give it any engine or wheels you want, without always depending on one concrete implementation like the V8Engine or Steel Wheels. So, your car now asks for the engine and the wheels rather than making them.
namespace YourProject\Model; use Engine; use Wheels; public class Car { private $engine; private $wheels; public function __construct(Engine $engine, Wheels $wheels) { $this->engine = $engine; $this->wheels = $wheels; } ... }
To enable this behaviour, you need someone to be able to construct the dependencies and hand it over to the Car class. We make use of a dependency injection container or DIC for this. Some examples are the Auryn and Symfony DIC.
For the purpose of this tutorial, let us use Auryn. You can get it from https://github.com/rdlowrey/auryn. It is not absolutely necessary to have a DIC for implementing dependency injection, but at a certain point, it will make your life easier.
To get started, simply ask it to give you an instance of Car and it will recursively use reflection to build the dependencies. In case your dependencies have a complex construction mechanism, you can delegate the constructor to a factory method.
$injector = new Auryn\Injector; $injector->alias(‘Engine’, ‘V8Engine’); $injector->alias(‘Wheels’, ‘SteelWheels’’); $car = $injector->make(‘Car’);
Here, Engine and Wheels are interfaces and not classes. In the above snippet, I am asking Auryn to build the concrete implementations V8Engine and SteelWheels, where I have specified Engine and Wheels as dependencies. We bind the interfaces to their concrete implementations using the alias method. This way, we can decouple classes from concrete implementations.
A DIC can also enable you to have a single instance of a class to be shared across objects, with ease; for example, a database connection with costly construction.
$injector = new Auryn\Injector; $injector->share(‘Engine’);
Now every time you require Engine as a dependency, you will get the same instance. This is useful only in certain cases; so make sure that you really need one single instance of a class. In most cases, you don’t.
Your code is already scalable! You have decoupled things to a certain extent, and separated the concerns, so you can easily add and delete modules as you need.
In this tutorial, we have laid the foundation for building clean PHP applications and, as a matter of fact, we can apply the same principles to any other language.
In the next article in this series, we will look at how we can break up our project further into independent modules, enable templating and routing, as well as make full use of the powerful language that PHP is.