📜 ⬆️ ⬇️

Subject-oriented design in PHP

The article can be said about sore.
Due to the low threshold of entry, the habit of bundling with MySQL, the lack of need for assembly, the lack of strong typing and other factors, projects written in PHP often do not shine with quality and contain a lot of piled requests to the database instead of beautiful clean code.

PHP is a scripting language, the server responds to the request, and objects die. Yes, this is not a desktop application.
But this does not mean that the objects of the subject area, with which we must work, are not needed at all.
On the contrary! They are needed, they should help us save and restore their state, after their removal from memory.

In PHP, you can and should write high-quality code, otherwise it does not depend on the language at all!
First of all, the article will be useful for beginners, but I think it will not hurt experienced developers. Perhaps, in your project, everything is not as it should be?

3 years engaged in the development of PHP and constantly tormented by one thing. Instead of working with entities in code, we work with the database. Although this is wrong at the root.
I am sure there are many high-quality products, but in most cases the situation is very sad.
')
In all projects with which it was necessary to work, in the code there are no clearly defined models of the domain with which to work and the encapsulation that needs to be followed. Everywhere the same thing.

In projects of low quality, the concept of “Model” is often absent altogether, there is only confusing logic, in the code of which something is pulled out of the database, then a huge heap of cycles and conditions and then the data goes to the template. Moreover, there are still many applications with bare MySQL queries, at best, through the wrapper over PDO.

In higher-quality projects, ORM is used, but this is not something with which I would like to work to implement certain functionality. Anyway, in order to do something, you need to look at the “Model”, see the connections, or execute DESC / EXPLAIN in the console, with the database connected.
As a result, the application code does not hide any operations on the data in the entity methods. And the code where there should be simple (or not) business logic is replete with lines like Orm :: find.
This mess is very upsetting. Especially when the project is large and impossible to rewrite. Maximum - in parallel to lead a new code and work with it, and over time, slowly move away from the old one.

After reading the remarkable book by Erik Evans, "Object-Oriented Design" (link at the end of the article), or Domain-Driven Design, the thought occurred that basically PHP development comes down to pulling lines from the database.

Correctly, in the project there should be objects and their aggregates, through the interface of which you can work with the entities of the domain.
We should not bypass the aggregate and climb into its subordinate objects, and in the same way we should not “write requests” instead of working with objects.

Objects should be able to maintain their state , as well as recover from the database. For complex objects and aggregates, you can use factories. But in no case can you base all logic on mysql queries.

However, all projects in which I had to work maliciously violate this architectural rule.

I will give a very simple example:

Customer - the “Customer” entity object
Order - “Order” aggregate object
The order unit is an immutable object value “OrderItem”

A customer may have many orders, one order may consist of many units.
The order unit is an immutable value, since in the event of a change in the price of the goods (as well as the availability of discounts at the customer), or the removal of goods from the store, the order history must be reliable and store in itself the data that was relevant at the time of purchase. It is wrong to simply refer to the product.

And so, you need to write simple logic in the API method / controller, which should display the user's order.
The code is intentionally simplified and shows only the essence. In this case, it does not matter how the id came, and how we will authorize the user.

Option 1:

 $ order = Orm :: find ('Order', [['id', $ id], ['user_id', $ user-> getId ()]]);
 if (! empty ($ order)) {
   $ items = Orm :: find ('OrderItem', [['order_id', $ id]]); 
   return $ items;
 } else {
   return 'Order was not found';
 }


Option 2:

 $ order = $ user-> getOrder ($ id);
 return $ order-> getItems ();


In the first case, we go to the database and get an order with the requested ID from the current user, so as not to issue someone else's order,
then we go to the base and get records from the OrderItem table that are tied to this order.
Next, you will most likely need to “iterate” the result using foreach / array_walk / array_map to bring the data into the desired form.

This code certainly works, but it quickly grows the same, and it is duplicated in different parts of the project.
Such challenges completely destroy the built architecture of the project and make it simply meaningless.

Accompanying the first version of the code is much more complicated, and it is not read as easily as the second option. Orm :: find c is not always with such obvious parameters and an extra condition.

In the event of some change in the storage of orders in the database, we will have to search and edit all ORM calls, at best.

In the second case, we have pronounced entities with a user-friendly interface.
In the getOrder method, the order-user connection test logic is already embedded , in which case, for example, Exception will be thrown. And the getItems method already has everything you need to simply return a list of positions. Reading this code, it is immediately clear what it actually does. In addition, this code is easier to test. You can even write everything in one line:

  return $ user-> getOrder ($ id) -> getItems (); 


Findings:

In one of his projects, which another programmer was starting to write, in the course of development, introduce models that not only encapsulate work with a database, but also hide the flaws of its architecture and implicit field names.

Write the code so that you enjoy working with it in the future and are easy to maintain.
Since we are writing code using OOP, let's work with objects and use all the advantages of this paradigm.
The business logic we describe in projects is based on entities, and not on mysql samples and arrays. Do not complicate the life of yourself and others!

Do not be lazy to write a class that describes the essence, where necessary, do not be lazy to write a method that will be useful to you again, copy-paste is evil. And with a set of ready-made entities and ready-made methods, the subsequent development, refactoring and testing will be simplified and accelerated!

PS: Article - just food for thought.

Book Reference: Eric Evans - Domain-Based Design

Source: https://habr.com/ru/post/247775/


All Articles