21 May, 2019
It’s high time for the final part of our adventure with the framework-agnostic PHP application. Today’s goal is to prove, that our framework-agnostic app may be used not only with Laravel. To present that, I chose the Symfony framework, which is quite popular amongst PHP developers. This article will only cover differences in infrastructure, so if you want to keep up-to-date, please make sure you have read previous parts about preparing a framework-agnostic part of the application and connecting it with a framework and storage.
Today in our technology stack menu we have:
- Symfony 4.2
- Doctrine (MySQL 5.7)
- Tactician (command bus implementation)
As you can see, it’s not very different from Laravel’s. We’ve only switched framework and database tools. I decided to use Doctrine because this quite mature ORM is very popular amongst developers. Simple as that. So, let’s start.
Firstly, connect framework-agnostic app with storage…
At first glance, you’ll probably notice a big difference between Eloquent and Doctrine. Take one more look at our domain’s Runner model:
…and its Doctrine equivalent,
with the corresponding transformer.
I bet a couple of questions popped into your head: Why not use domain model directly and move doctrine configuration to XML? Why do we need the transformer again? In Eloquent it was definitely needed, but here we have quite a nice and clean class. The answer to both is quite simple: being tools-agnostic!
Unfortunately, Doctrine’s entities aren’t independent at all. To keep relations between classes you are forced to use ArrayCollection class from ORM’s tools. I bet you’d like to operate on simple arrays and definitely don’t want to clutter your domain with external stuff. Hence why you need separate entities and transformers doing a two-way translation. Also, Doctrine may try to force specific things on you (like storing whole related object instead of its id) and transformer may help you keep it under control.
These issues can be easily noticed on the example of RunParticipation entity:
Because of how ORM works, I have to keep the whole Runner entity inside. The domain model needs only runner id, not the whole object. An additional issue is that the Entity Manager needs to track those entities and keep additional information so its “magic” could work. That’s causing a lot of issues when translating from domain to entity. You’d have to fetch all entities from Doctrine’s repository so it can keep track of them. Fortunately, there’s a way to deal with it. Please notice, that I used @Id annotation on both fields in the entity, so we have a compound primary key here. Thanks to that we are being protected from duplicated pairs from both columns.
Let’s take a look at transformer:
Our centre of interest is domainToEntity method. As you can see, I used references from the Entity Manager instead of real entities. I’d have to fetch them from Doctrine’s repository so they could be tracked by ORM. We won’t be using them for anything other than saving data to the database so they’re all that we need. Other than that transformers are quite similar to Laravel ones, they just handle different object representation of tables and columns.
The last thing to see from a storage perspective is the repository. The example below shows read/write repositories.
As you can see, I’m injecting EntityManager to retrieve correct Doctrine repository and transformer. I decided not to use Doctrine repositories directly because I still needed other dependencies.
How would I compare Doctrine and Eloquent from this perspective? Generally, I prefer Doctrine’s ORM, as it has, in my opinion, clearer models and is a bit easier to use. However, in this specific case, its restrictions and rules were more difficult to follow than Eloquent ones.
Now the framework-agnostic app needs a framwork
Well, this part won’t differ a lot from Laravel. Check out this out – I moved the whole Symfony code to the infrastructure part. Again we are doing it in CLI to have a proper comparison. For Symfony thephpleague/tactician-bundle package is available.
Now it’s time to check console command class:
As you can see, the main logic is exactly the same as in Laravel – you fetch console arguments, pass it to command and let command bus handle it. You probably noticed that I’m not connecting command with a bus in this class anymore. It is done automatically by Tactician thanks to its typehint feature (command is resolved based on handler’s argument).
Below you can find the part of the configuration that allowed me to achieve it:
Other than that I’m using autowiring and autoconfiguration, so as many things as possible could be configured automatically. Please check full fa-runner implementation at The Software House’s GitHub.
The final conclusions
As in the last article, we haven’t touched our domain at all, just used different tools around. Thanks to that there were no doubts, that main logic will work fine (although some test wouldn’t hurt – just in case 😉). I’m aware that code I provided you with is not perfect and there is a room for improvement (for example database schema is different for Eloquent/Doctrine, although transition shouldn’t cause any data loss), but I achieved my goal: framework-agnostic application wrapped with completely different tools.
I hope, that this series helped you to understand how separation from external tools can be done in PHP. But! I’m not going to leave you without homework. If you’re interested, please try to take my domain and use Zend Framework and Propel to practice a bit more. Good luck!