Written by Mauro Chojrin✏️
When discussing PHP, there’s no point in avoiding the elephant in the room; PHP used to be a really mediocre tool, and that’s probably an understatement. In fact, I saw an article recently that addressed the issue of why people don’t like PHP. However, in my personal experience, developers often confuse what they think PHP is with what it actually is.
The fact is that PHP got its reputation from somewhere. But the old, dark days of PHP v4 and earlier are way behind us. If you hadn’t run off screaming in horror, you’d have seen PHP evolve, and did it evolve.
In this article, I’ll cover what a modern PHP development environment looks like, reviewing the tools that are available both within the language itself and as complements. With the complexities involved in web development nowadays, you can’t judge the language in isolation. You need to be aware of all the satellite technologies that help you create high-quality software.
By the end of the article, you’ll hopefully have second thoughts about your certainty of how PHP sucks. However, if you’re a fan of PHP, you’ll have more reasons to defend your choice. Let’s get started!
What is modern PHP?
Before we jump into the details of what makes PHP great, let’s first establish a definition for modern PHP. At the time of writing, PHP v8.1.0 has just seen the light of day, the PHP Foundation is about to become a reality, and PHP v5.6 is reaching its end of life. So, when I refer to modern PHP, I’m referring to v7 and later versions.
Since being rewritten in v5.0, the evolution of the language and its tools has been impressive. PHP v5.0 marked an inflection point in PHP’s history, bringing it to the realm of actual object-oriented languages.
Another discrete leap was the introduction of Composer, a PHP dependency manager, which certainly drew the line between amateur and professional development. But, I’m getting a little ahead of myself, we’ll cover this in-depth later. Let’s review some major improvements made to PHP within the last few versions.
PHP language improvements from ≥v7.x
Since PHP v7.0, which was released on December 3, 2015, several exciting new features have been introduced, like type declarations, built-in cryptography, support for complex data structures, named arguments, and attributes.
The syntax also experienced some powerful improvements, like arrow functions, the spaceship operator, and null coalescing. Each new release came packed with major performance improvements over the previous one.
Each of these new features might be pretty shocking to someone who left PHP three or four versions ago. To get the most of these great features, you’d probably have to be a heavy PHP user, however, for those of us who use PHP more casually, PHP has introduced additional new features tailored towards everyday use cases.
Now that we have a grasp on the features the latest PHP versions have introduced, let’s build our toolbox. In the following sections, I’ll discuss some tools I consider to be indispensable when it comes to professional software development in PHP. They are presented in incremental order, meaning I believe this will be the easiest path to adoption.
Debuggers
Before the introduction of debuggers like XDebug and ZendDebugger, developers were forced to spend excessive time understanding the root cause of an application’s misbehavior.
In practice, debugging involves looking at the contents of variables during a program’s execution. In general, PHP is used in batch mode, meaning that the output is only visible once the script has run to completion, making it hard for developers to guess what the context was when the error occurred.
Additionally, the tools available for this task like var_dump
, echo
, and print_r
pose a high risk of leaving traces behind, potentially exposing sensitive information and lowering the bar for malicious attackers.
Both XDebug and ZendDebugger work well with modern IDEs like PhpStorm and VS Code to solve the issues mentioned above. If you prefer to go straight through the command line, phpdbg
comes bundled with PHP since v5.6.
Dependency management
Importing external libraries as dependencies used to be a real pain in PHP. However, one of the most prominent changes in PHP’s maturity came with the release of Composer. Before Composer, PHP used PEAR, which solved the same problem in a more primitive way.
For example, it’s complex to have individual project dependencies using PEAR. Dependency management with PEAR is an all-or-nothing situation, so running several projects on the same server is difficult, especially if each depends on a different or conflicting set of dependencies.
On the other hand, dependency management is much simpler with Composer. Every project has its own composer.json
and vendor
folders, keeping everything self-contained.
Another great advantage of Composer is its versioning system, which has built-in intelligence to determine the best fit for a dependency tree; think of dependencies that have dependencies of their own. PEAR, on the other hand, does a very poor job in this area.
Nowadays, PHP best practice requires familiarity with Composer. Most of the tools we’ll cover require its availability in your working environment.
MVC frameworks
If you’re building any non-trivial application, chances are you’ll have to create a lot of boilerplate code before you can actually solve your client’s problem. Think of issues like authentication, routing, and database management.
In the older days of PHP, these were a real challenge. Nowadays, there are many MVC frameworks available, most notably Symfony and Laravel, which you can use as a basis for your task. Symfony and Laravel both boast of large community support and widespread usage.
Automated testing
Automated testing tools have become a standard throughout the software development industry. Every language has its own tools, and the biggest player for PHP is definitely phpUnit.
phpUnit was originally designed as a unit testing framework, but other tools have helped expand it to provide other types of testing like end-to-end and integration testing.
Using phpUnit is pretty simple. Say you have a class like the following:
<?php
namespace LeewayAcademy;
class Calculator
{
public function add(int $a, int $b): int
{
return $a + $b;
}
}
Reading the code, you probably assume it will work. But with phpUnit, you can define a set of repeatable tests that will help you build and justify your confidence level. For example, a test case will look like the following:
<?php
use PHPUnit\Framework\TestCase;
use LeewayAcademy\Calculator;
class CalculatorTest extends TestCase
{
public function testAdd()
{
$sut = new Calculator();
$this->assertEquals(3, $sut->add(1, 2));
$this->assertEquals(10, $sut->add(5, 5));
$this->assertEquals(10, $sut->add(0, $sut->add(4, 6)));
}
}
The code above runs the add
method with different sets of inputs, then validates that the output matches what was expected. You can run tests with phpUnit using the following command:
php vendor/phpunit/phpunit/phpunit --no-configuration --filter CalculatorTest --test
The code above will produce an output like the following:
Testing started at 10:07 ...
PHPUnit 9.5.11 by Sebastian Bergmann and contributors.
Time: 00:00.006, Memory: 4.00 MB
OK (1 test, 3 assertions)
You can run this kind of test as many times as you want. If all of them pass, you'll have some actual proof that your application is doing what it’s supposed to do. Of course, these tools are only as good as the tests you write, but that’s another discussion altogether.
Other tools worth mentioning include Codeception and behat. Both use phpUnit underneath, but have different approaches to writing tests.
Static analysis tools
A lack of static analysis used to be a big drawback for PHP and other non-compiled languages. Some bugs were so well-hidden in obscure execution paths, it was very hard to find them under normal testing situations. We now have phpstan, Psalm, and Exakat, just to name a few. For example, consider the following bug:
<?php
function f(int $p): int
{
return $p * 2;
}
$a = 'M';
echo f($a);
With static analysis tools, a type
mismatch bug like the one above can be detected without running the code, simply by issuing a command like the one below:
vendor/bin/phpstan analyse test.php --level 5
The code above will produce the following output:
1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
------ ----------------------------------------------------------
Line test.php
------ ----------------------------------------------------------
10 Parameter #1 $p of function f expects int, string given.
------ ----------------------------------------------------------
[ERROR] Found 1 error
Now, you have very precise information on errors that might have otherwise been overlooked. By including these tools in a continuous integration pipeline or running them as part of Git Hooks, you can more easily improve the quality of your codebase.
Deployment tools
A developer’s job doesn’t end once they’ve written their last line of code. To reach an audience, your application must first reach a production server.
With older versions of PHP, deploying your application required pushing the new files to a remote location. However, nowadays, it’s a bit more complicated. You'll probably have to handle database updates, directory permissions, and an abundance of other small tasks to get everything up and running. Oftentimes, missing one of these actions or running them in a different order makes the whole deployment fail.
Just like automated testing tools, the PHP ecosystem provides fantastic tools for taking your application to production and keeping it updated as needed, preventing tremendous headaches. Some of these include Deployer, Rocketeer, Pomander, and easydeploy. As an example, here’s a configuration for Deployer I used for a client’s project:
<?php
namespace Deployer;
require 'recipe/codeigniter.php';
// Project name
set('application', 'APP');
// Project repository
set('repository', 'git@bitbucket.org:maurochojrin/REPO.git');
set('branch', 'master');
set('default_stage', 'prod');
// [Optional] Allocate tty for git clone. Default value is false.
set('git_tty', true);
// Shared files/dirs between deploys
add('shared_files', [
'application/config/database.php',
'app_env.php',
]);
add('shared_dirs', [
'application/sessions',
'application/logs',
'assets/uploads/excel',
'logs',
]);
// Writable dirs by web server
add('writable_dirs', [
'application/sessions',
'assets/uploads',
'application/logs',
]);
// Hosts
host('THE_HOST')
->stage('prod')
->identityFile('~/.ssh/MauroChojrin.pem')
->set('user', 'ubuntu')
->set('deploy_path', '~/{{application}}');
// Tasks
task('build', function () {
run('cd {{release_path}} && build');
});
task('pwd', function () {
$result = run('pwd');
writeln("Current dir: $result");
});
// [Optional] if deploy fails automatically unlock.
after('deploy:failed', 'deploy:unlock');
With this configuration in place, whenever I push a new version to production, I just have to run the command below:
dep deploy
The script will remotely run every task required to make the app available for users. If you’re still pushing files over FTP, you probably want to check these tools out.
Asynchronous execution
Another common complaint when it comes to PHP is its lack of asynchronous execution support. There are a couple of projects aimed in that direction like Swoole and ReactPHP. Take a look at the following code pulled from the Swoole By Examples repository:
#!/usr/bin/env php
<?php
declare(strict_types=1);
/**
* How to run this script:
* docker exec -t $(docker ps -qf "name=client") bash -c "time ./io/blocking-io.php"
*
* This script takes about 3 seconds to finish, and prints out "12".
*
* Here the PHP function sleep() is used to simulate blocking I/O. The non-blocking version takes about 2 seconds to
* finish, as you can see in script "non-blocking-io.php".
*/
(function () {
sleep(2);
echo '1';
})();
(function () {
sleep(1);
echo '2';
})();
Compare it to its non-blocking counterpart:
#!/usr/bin/env php
<?php
declare(strict_types=1);
/**
* How to run this script:
* docker exec -t $(docker ps -qf "name=client") bash -c "time ./io/non-blocking-io.php"
*
* This script takes about 2 seconds to finish, and prints out "21".
*
* Here the Swoole function co:sleep() is used to simulate non-blocking I/O. If we update the code to make it work in
* blocking mode, it takes about 3 seconds to finish, as you can see in script "blocking-io.php".
*
* To see how the code is executed in order, please check script "non-blocking-io-debug.php".
*/
go(function () {
co::sleep(2);
echo '1';
});
go(function () {
co::sleep(1);
echo '2';
});
Syntactically, they look pretty similar, but underneath, the second version is leveraging Swoole’s power for parallel processing, decreasing the time required to achieve the end result.
In PHP v8.1, Fibers were introduced as an out-of-the-box feature, so if async is your goal, there’s nothing stopping you from achieving it without leaving PHP.
Conclusion
PHP has come a long way. Sadly, not every PHP developer has followed along with these best practices, so you can still find a lot of spaghetti code out there. However, this reflects more of an individual's responsibility rather than a tool's shortcomings.
On the bright side, there are many excellent resources to level up with PHP if you want to. I hope you enjoyed this article. If you weren’t already, I hope you're now a fan of PHP, or at least willing to give it a shot. Let’s turn PHP’s reputation around.
LogRocket: Full visibility into your web apps
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Top comments (0)