How To Build Maintainable PHP Applications
Have you struggled with the system because it was hard to change?
Have you wanted to upgrade Framework or PHP version, however it required to many changes to make it?
Or maybe you are starting new project and you would like avoid such situations?
In this article we will be dealing with external Frameworks and Libraries, as it often happens that the way we use them is blocking us from achieving maintainable code which can follow up on the newest upgrades.
* For the need of the article, I will be referencing package as synonym to library or framework.
Framework, Libraries pitfalls
Coupling with the Framework
When we are start using Framework related classes, we become depended on it.
The problem starts to become real, when the Framework gets major version upgrade. The breaking changes, require us fix our code in order to follow up.
The problems starts to reveal like moles from holes, when we combine it with external libraries or framework modules. If they do the breaking changes too, then the need for a change is multiplied. Or if they do not follow our newest framework version, we will need to replace them or stay on old version of the framework.
Overloaded with Configuration
Configuration, that targets name of the classes, methods, namespaces or paths, block us from freely moving classes and methods and increase the refactor time.
Whenever we change method name, move the class to different namespace which is referenced in configuration files, we are obligated to change the configuration too.
Somebody's else abstraction
The code that is specific to your business and solves it's problems is business related code.
If we will pull solution from outside by using some external package to solve business related problems, sooner or later we may get into situation, that it will not be enough.
It also adds additional packages, that we will need to carry with next upgrades.
Business evolves and changes and so do the system requirements.
If we solve our business related code by using external package, we are put on the mercy of it's owners.
Do we need to use external packages for development?
Let's consider the situation, we are not using any Framework at all.
We would first need to create some Request object, that will handle our incoming HTTP Request.
Then we will need to build Routing system, that will map our Request to specific action.
What about dependency injection, we need to wire our classes together too, right?
You have probably heard the scary stories of developers, that went no framework, ending up building own framework.
The Frameworks and Libraries are here to help us. They solve a lot of problems for us, so we can stay productive and more secure in the code we write.
We can not escape external packages.
The question is how to use it, so we can benefit and avoid the pitfalls?
How to use Framework, Libraries and avoid the pitfalls
So how can we use Framework and Libraries in a way that it can help us in long term maintainability and possibility to upgrade?
Isolate Business Oriented Code
Whenever we implement core business functionality, we can move it away of the framework and libraries.
This means to implement classes clean out of external packages,
so we can extend and evolve the system on our terms.
In case the system is big, we will need to decide what belongs to Core business functionality as it may be not beneficial for the business to invest the time and resources into Supporting functionality.
If we would implement Room Renting Service, we could focus on Appointment and Scheduling as the Core and use 3rd party solution for Authorization and Authentication.
Allow Framework to glue your code
If we decide to use the Framework, then let's allow it to do it's job.
And in most of the cases, it's about taking care of repetitive tasks to make our life easier.
When using Web Framework like Symfony and Laravel, we will make use of Controllers and Routing, so we don't need to spend time on building and connecting HTTP Request to specific Class and Action.
Another example are Message Handlers.
A Message Handler is responsible for handling specific Message (class).
Thanks to it, we can build decoupled solution where top layers like HTTP Controller or Console Line Command, can construct the Message and send it, without referencing specific business related code.
The glue code responsible for moving the Message to specific Message Handler will be cared by the Framework.
By letting Framework do the glue code, we can focus and invest more time in Core Business Functionality.
Make Use Of Easy Configuration
Suppose we are using framework for our Message Handlers.
And we want to register our class:
We want to register class OrderHandler and method handle as our Message Handler that will handle PlaceOrderMessage message class.
So what options do we have for registering this Handler?
1. XML or YAML
The first solution would be to use XML or YAML.
Let's check how such configuration could look like:
We would achieve nice separation by splitting the business code from glue code.
However it has drawback, in case of any refactor, we will need to edit those files and this can be really time consuming.
If you are familiar with setting up Dependency Injection using configuration files I am sure you know the pain.
2. Implementing Framework specific interface
The second solution would be to implement an interface provided by framework.
Easy to implement, as we just need to implement interface or extend specific class.
And it's also easy to see, that this class is Message Handler.
However it creates direct coupling with the Framework.
This is also limiting, as we would need to follow up on convention defined by the Framework and also we would not be able to register more than one Message Handlers in same class.
3. PHP Configuration
The third solution would be to provide configuration using PHP.
This allow us to keep refactoring tools automatic for the class name.
We would also keep separation by splitting the business code from the glue code.
However the method name, will still need to be changed manually in case of modification.
And from the inside of the class, we can't clearly see, that it was registered as Message Handler.
4. PHP Attributes
The last solution would be to provide configuration using Attributes.
Framework will find all the classes and method annotated with #[MessageHandler].
This allows us, to change the method and class name and the configuration will stay untouched.
We also provide exact information inside the class, that it's registered as Message Handler.
We can have multiple Message Handlers within one class.
And as Attributes are just comments to our code (like DocBlocks), it keeps the separation of the business code and the framework code.
Making use of PHP Attributes, will make your code self-descriptive and easy to refactor and change.
If you want to build maintainable code, you should make clean distinction between what is your core business functionality and separate it from external packages.
This allow you to evolve on your own terms and will be decrease amount of used libraries so you can upgrade your application easier.
Push the glue code to the framework, so you can drop repetitive tasks and focus on the business code.