This post is part of The Containerisation Chronicles, a series of posts about Containerization. In them, I write about my experiments and what I’ve learned on Containerization of applications. The contents of this post might make more sense if you read the previous posts in this series.
In order to start making experiments with containerization of applications, I am going to be using the Symfony demo project, which is a small blog where one can view and create some blog posts. Although it is a very small project, and therefore does not reflect the complexity of an enterprise cloud application, I feel it is flexible enough to experiment containerization practices, especially because I want to start small.
However, there are some things I don’t like about the project, so I start by doing some cleanup:
- Add roave/security-advisories
- Remove simple-phpunit, update to phpunit 7 and DAMA bundle 5
- Disable the profiler during test runs, so the tests run a bit faster
- Type hint all the things
- Change namespace from `App` to `Acme\App` to better comply with PSR-4
- Add a Makefile so we abstract our DevEx and automation from the tools
If you want to jump right into the code, this is the tag on GitHub.
1. Add roave/security-advisories
The roave/security-advisories package ensures that your application doesn’t have installed dependencies with known security vulnerabilities.
As mentioned in the project GitHub, the checks are only executed when adding a new dependency via composer require or when running composer update: deploying an application with a valid composer.lock and via composer install won’t trigger any security versions checking. So basically this only makes sense to install in the development requirements of the application, although at the moment PHPStorm still asks us to install that package in the default application dependency requirements.
Of course, this project will never be a real “production” application so maybe this could be skipped, but it is a good practice to do so and my OCD forces me to do it!
So, we add it to composer.json:
And run composer update…
For some reason, it also removed the phpunit.xml.dist, but I rolled it back before merging so we keep the project as unchanged as possible.
2. Remove simple-phpunit, update to phpunit 7 and DAMA bundle 5
This demo project comes with simple-phpunit which, at the time of this writing, gives the following features:
The PHPUnit Bridge provides utilities to report legacy tests and usage of deprecated code and a helper for time-sensitive tests.
It comes with the following features:
- Forces the tests to use a consistent locale (C);
- Auto-register class_exists to load Doctrine annotations (when used);
- It displays the whole list of deprecated features used in the application;
- Displays the stack trace of a deprecation on-demand;
- Provides a ClockMock and DnsMock helper classes for time or network-sensitive tests.
- Provides a modified version of PHPUnit that does not embed symfony/yaml nor prophecy to prevent any conflicts with these dependencies.
However, I see the following disadvantages:
- At the first run, it will start downloading dependencies which is weird because that is the role of composer and it brings us problems later when running inside the container because ideally the user running commands inside the container should only have permissions to change the contents of the
- Will use the phar version of PHPunit, which makes it not be managed by composer;
- Because it is a wrapper around PHPUnit, we can only upgrade to the latest version of PHPUnit is that wrapper supports it;
- I could not integrate it with PHPStorm;
- There is an issue that happens when the code under test shares dependencies with PHPUnit but requires different versions than the ones bundled in the PHAR;
Nevertheless, we really want to get as many of those features as possible and, because the Symfony devs are cool, we can actually get those features (or at least some of them) by using the PHPUnit Bridge without using simple-phpunit!
So we add the PHPUnit v7 dependency and upgrade the dama/doctrine-test-bundle to version 5, but we leave in the PHPUnit Bridge:
To make PHPUnit get the best of the PHPUnit Bridge, we add a listener to phpunit.xml.dist:
We also add the filter section so that we can get the code coverage report from PHPUnit.
After running composer update, we get some extra files, including one in ./bin/phpunit, however, this one is the executable that runs simple-phpunit, so I will not even commit it. What we want to use is the phpunit executable at ./vendor/bin/phpunit.
We also update the README.md file to reflect the executable we should use to run PHPUnit.
3. Disable the profiler during test runs, so the tests run a bit faster
Very recently, at work, we realized that the Symfony Framework Extra Bundle has the profiler enabled by default during test runs. This has a performance impact which is negligible in a small project but it is significant in a big project like the one I am currently working on, which came down to several minutes difference when running the functional and acceptance tests.
These were the changes made to the profiler configuration:
We turn it off altogether and set the
collect setting to
true. This way, if we want to use it during test runs we just need to turn it on, and we don’t need to add any code to start the profiler. Otherwise, we would need to do something like
$profiler->enable(); in our code, to start the profiler.
Looking at the configuration above, one might think that the default configuration (enabled: true; collect: false) ends up being the same since the profiler will only do something if we add some code to start the profiling, however, I tried it a few times and always got some performance improvement.
4. Type hint all the things
Since this project evolved from PHP5, there are plenty of type hints missing so we add them to prevent bugs and satisfy our OCD.
We also prefix the native function calls with the
\ which tells PHP that the function we want to call belongs in the global namespace. This prevents PHP from searching for the function in all namespaces until it finds it, which can give us some minor performance improvements.
We did this two things in only one commit, which is hurting my OCD, but I just wanted to get it done and don’t anticipate a problem coming out of it.
5. Change namespace from `App` to `Acme\App` to better comply with PSR-4
The PSR-4: Autoloader, describes a specification for autoloading classes from file paths. It is fully interoperable and can be used in addition to any other autoloading specification, including PSR-0. This PSR also describes where to place files that will be autoloaded according to the specification.
Part of it is about the classes fully qualified namespace, and it goes like this:
2. A fully qualified class name has the following form:
2.1 – The fully qualified class name MUST have a top-level namespace name,
also known as a “vendor namespace”.
2.2 – The fully qualified class name MAY have one or more sub-namespace names.
2.3 – The fully qualified class name MUST have a terminating class name.
2.4 – Underscores have no special meaning in any portion of the fully qualified class name.
2.5 – Alphabetic characters in the fully qualified class name MAY be any combination of lower case and upper case.
2.6 – All class names MUST be referenced in a case-sensitive fashion.
PHP FIG – PSR-4: Autoloader
In this fourth step, we are concerned with point 2.1, which mentions the “vendor namespace“. This vendor namespace can be a company namespace or our personal namespace so that if there are several projects with the same name their namespace will not clash.
Our project namespace is simply
App\, which is the application name, so we are missing the vendor namespace. So we change the project namespace from
6. Add a Makefile so we abstract our DevEx and automation from the tools
Makefiles were initially created to help compile C++ into executable programs, but they can be used for other purposes as well. In simple terms, it is a file with shell commands that can be called as an argument to make. Nevertheless, it does have more capabilities that we will not need to use, like keeping track of source files changes.
We will use it as:
- An entry point to the application development and automation commands:
We add in it the commands we use during development and in the CI/CD servers setup, for example, the commands to install and update dependencies.
- An abstraction layer over the tools used under the hood:
The commands must not refer to the specific tools used, so that we can switch the tools used and keep using the same commands, both during development and in the automation tools configuration, for example, instead of naming a command
make composer-updatewe should name it something like
make update-dependencies, this way even if we switch from composer to some other tool, the command name will still make sense.
Sure, we could use a shell file for this as well, but the makefile forces a format on us and limits us on the complexity of the logic we can put in it. In other words, it helps us not to get it messy.
It is also important to name and organize the commands according to some convention, as to make them predictable and easy to find.
The convention used in this case is
subject-action-environment, and organize them alphabetically. This will keep commands names meaningful and close together.
At this point, the commands added are as follows:
Fixes the code standards
Creates the database
Removes the dependencies cache
Installs all the dependencies, for development
Installs all the dependencies, for production (optimized and no dev dependencies)
Updates the versions of the dependencies
Tests the code base
Tests the codebase and generates the code coverage report
Starts the application so that it can start receiving requests
6.1 The Makefile.custom.dist file
The resulting command names might at times not be the easiest to remember, and the developers in the team might want to use their own commands, so we also add a Makefile.custom.dist file which the developers can copy into a Makefile.custom file, put there their own commands or aliases and use it as if they were in the main Makefile.
That’s it for today!
Please, feel free to share your thoughts and/or ways to improve this.