
Entity “framework” for PHP from one class
Since the development of technology has led to the fact that every programmer now has his own computer, as a side effect we have thousands of various libraries, frameworks, services, APIs, etc. for all occasions. But when this case of life occurs, the problem arises - what to use them and what to do if it does not quite fit - to rewrite, write from scratch or screw several solutions for different use cases.
I think many people noticed that often the creation of a project comes down not so much to programming as to writing integration code for several ready-made solutions. Sometimes such combinations turn into new solutions that can be repeatedly used in subsequent tasks.
Let's move on to a specific "running" task - the object layer for working with databases in PHP. There are a lot of solutions, ranging from PDO to multilevel (and, in my opinion, not quite appropriate in PHP) ORM engines.
Most of these solutions migrated to PHP from other platforms. But often, authors do not take into account the features of PHP, which would dramatically simplify both the writing and use of portable constructs.
One of the common architectures for this class of tasks is the Active Record pattern. In particular, the so-called Entity (entities) are built according to this template, which are used in one form or another on a number of platforms, from persistent beans in EJB3 to EF in .NET.
So, let's build a similar construction for PHP. Let's connect two cool things together - the finished ADODB library and the weak typing and dynamic properties of PHP objects.
One of the many features of ADODB is the so-called auto-generation of SQL queries for inserting (INSERT) and updating (UPDATE) records based on associative arrays with data.
Actually there is nothing military to take an array, where the keys are the names of the fields and the values are the data, respectively, and generate the SQL query string. But ADODB does it more intellectually. The query is based on the structure of the table, which is previously read from the database schema. As a result, firstly, only existing fields get into sql and not everything, secondly, the type of the field is taken into account - quotes are added for strings, date formats can be formed based on timestamp if ADODB sees it instead of a string in the transmitted value, etc. .
Now let's get in with PHP.
Imagine such a class (simplified).
By passing the internal array to the ADODB library, we can automatically generate SQL queries to update the record in the database with this object. Moreover, we do not need cumbersome constructions of mapping the fields of the database tables to the fields of an entity object based on XML and the like. It is only necessary that the field name matches the property of the object. Since the name of the fields in the database and the fields of the object are not important for the computer, there is no reason that they do not coincide.
We show how this works in the final version.
The code for the finished class is located on Gist . This is an abstract class that contains the minimum necessary for working with the database. I note that this class is a simplified version of the solution worked out on several dozen projects.
Imagine that we have such a tablet:
The type of database does not matter - ADODB provides portability for all common database servers.
Create a User entity class based on the Entity class
Actually that's all.
It is used simply:
The table and key field are indicated in pseudo-annotations.
We can also specify a view (for example, view = usersview) if, as it often happens, an entity is selected based on its table with joined or calculated fields. In this case, the data will be selected from the view and the table will be updated. Those who don’t like such annotations can override the getMetatada () method and specify the table parameters in the returned array.
What else is useful about the Entity class in this implementation?
For example, we can override the init () method, which is called after creating an Entity instance, to initialize the default creation date.
Or overload the afterLoad () method, which is automatically called after loading the entity from the database to convert the date to timestamp for further more convenient use.
As a result, we get a not much more complicated design.
You can also overload the beforeSave and beforeDelete methods and other life cycle events where you can, for example, perform validation before saving or some other actions - for example, remove images from the upload when you delete the user.
We load the list of entities by the criterion (in essence, the conditions for WHERE).
The Entity class also allows you to execute an arbitrary, “native” SQL query, so to speak. For example, we want to return a list of users with some groupings by statistics. What specific fields will be returned does not matter (the main thing is that there should be user_id, if there is a need for further manipulation of the entity), you only need to know their names in order to refer to the selected fields. When saving an entity, as is obvious from the above, it is also not necessary to fill in all the fields that will be present in the entity object, they will go to the database. That is, we do not need to create additional classes for arbitrary samples. Something like anonymous structures when fetching in EF, only here it is the same entity class with all the business logic methods.
Strictly speaking, the above list retrieval methods are somewhat outside the scope of the AR pattern. In essence, these are factory methods. But as the old man of Ockham bequeathed, we will not produce entities beyond what is necessary and make a separate Entity Manager or something.
Since loading and saving entities is the basic operation that takes up two-thirds of database calls, we can avoid hundreds of lines of routine coding.
Note that the above is just PHP classes and you can extend and modify them as you like, add properties and methods of business logic to entities (or the base Entity class). That is, we get not just a copy of the row of the database table, but a business entity as part of the object architecture of the application.
Who can benefit from this? Of course, not pumped up developers who believe that using something is simpler than doctrine is not solid, and not perfectionists who are convinced that if a solution does not draw out a billion calls to the database per second, then this is not a solution. Judging by the forums, before many ordinary developers working on ordinary (of which 99.9%) projects, sooner or later, the problem arises of finding a simple and convenient object way to access the database. But they are faced with the fact that most of the solutions are either unreasonably tricked out, or are part of a framework.
PS Made a decision from the framework with a separate GitHub project
I think many people noticed that often the creation of a project comes down not so much to programming as to writing integration code for several ready-made solutions. Sometimes such combinations turn into new solutions that can be repeatedly used in subsequent tasks.
Let's move on to a specific "running" task - the object layer for working with databases in PHP. There are a lot of solutions, ranging from PDO to multilevel (and, in my opinion, not quite appropriate in PHP) ORM engines.
Most of these solutions migrated to PHP from other platforms. But often, authors do not take into account the features of PHP, which would dramatically simplify both the writing and use of portable constructs.
One of the common architectures for this class of tasks is the Active Record pattern. In particular, the so-called Entity (entities) are built according to this template, which are used in one form or another on a number of platforms, from persistent beans in EJB3 to EF in .NET.
So, let's build a similar construction for PHP. Let's connect two cool things together - the finished ADODB library and the weak typing and dynamic properties of PHP objects.
One of the many features of ADODB is the so-called auto-generation of SQL queries for inserting (INSERT) and updating (UPDATE) records based on associative arrays with data.
Actually there is nothing military to take an array, where the keys are the names of the fields and the values are the data, respectively, and generate the SQL query string. But ADODB does it more intellectually. The query is based on the structure of the table, which is previously read from the database schema. As a result, firstly, only existing fields get into sql and not everything, secondly, the type of the field is taken into account - quotes are added for strings, date formats can be formed based on timestamp if ADODB sees it instead of a string in the transmitted value, etc. .
Now let's get in with PHP.
Imagine such a class (simplified).
class Entity{
protected $fields = array();
public final function __set($name, $value) {
$this->fields[$name] = $value;
}
public final function __get($name) {
return $this->fields[$name];
}
}
By passing the internal array to the ADODB library, we can automatically generate SQL queries to update the record in the database with this object. Moreover, we do not need cumbersome constructions of mapping the fields of the database tables to the fields of an entity object based on XML and the like. It is only necessary that the field name matches the property of the object. Since the name of the fields in the database and the fields of the object are not important for the computer, there is no reason that they do not coincide.
We show how this works in the final version.
The code for the finished class is located on Gist . This is an abstract class that contains the minimum necessary for working with the database. I note that this class is a simplified version of the solution worked out on several dozen projects.
Imagine that we have such a tablet:
CREATE TABLE `users` (
`username` varchar(255) ,
`created` date ,
`user_id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`user_id`)
)
The type of database does not matter - ADODB provides portability for all common database servers.
Create a User entity class based on the Entity class
/**
* @table=users
* @keyfield=user_id
*/
class User extends Entity{
}
Actually that's all.
It is used simply:
$user = new User();
$user->username='Вася Пупкин';
$user->created=time();
$user->save(); //сохраняем в хранилище
// загрузим опять
$thesameuser = User::load($user->user_id);
echo $thesameuser ->username;
The table and key field are indicated in pseudo-annotations.
We can also specify a view (for example, view = usersview) if, as it often happens, an entity is selected based on its table with joined or calculated fields. In this case, the data will be selected from the view and the table will be updated. Those who don’t like such annotations can override the getMetatada () method and specify the table parameters in the returned array.
What else is useful about the Entity class in this implementation?
For example, we can override the init () method, which is called after creating an Entity instance, to initialize the default creation date.
Or overload the afterLoad () method, which is automatically called after loading the entity from the database to convert the date to timestamp for further more convenient use.
As a result, we get a not much more complicated design.
/**
* @table=users
* @view=usersview
* @keyfield=user_id
*/
class User extends Entity{
protected function init() {
$this->created = time();
}
protected function afterLoad() {
$this->created = strtotime($this->created);
}
}
You can also overload the beforeSave and beforeDelete methods and other life cycle events where you can, for example, perform validation before saving or some other actions - for example, remove images from the upload when you delete the user.
We load the list of entities by the criterion (in essence, the conditions for WHERE).
$users = User::load("username like 'Пупкин' ");
The Entity class also allows you to execute an arbitrary, “native” SQL query, so to speak. For example, we want to return a list of users with some groupings by statistics. What specific fields will be returned does not matter (the main thing is that there should be user_id, if there is a need for further manipulation of the entity), you only need to know their names in order to refer to the selected fields. When saving an entity, as is obvious from the above, it is also not necessary to fill in all the fields that will be present in the entity object, they will go to the database. That is, we do not need to create additional classes for arbitrary samples. Something like anonymous structures when fetching in EF, only here it is the same entity class with all the business logic methods.
Strictly speaking, the above list retrieval methods are somewhat outside the scope of the AR pattern. In essence, these are factory methods. But as the old man of Ockham bequeathed, we will not produce entities beyond what is necessary and make a separate Entity Manager or something.
Since loading and saving entities is the basic operation that takes up two-thirds of database calls, we can avoid hundreds of lines of routine coding.
Note that the above is just PHP classes and you can extend and modify them as you like, add properties and methods of business logic to entities (or the base Entity class). That is, we get not just a copy of the row of the database table, but a business entity as part of the object architecture of the application.
Who can benefit from this? Of course, not pumped up developers who believe that using something is simpler than doctrine is not solid, and not perfectionists who are convinced that if a solution does not draw out a billion calls to the database per second, then this is not a solution. Judging by the forums, before many ordinary developers working on ordinary (of which 99.9%) projects, sooner or later, the problem arises of finding a simple and convenient object way to access the database. But they are faced with the fact that most of the solutions are either unreasonably tricked out, or are part of a framework.
PS Made a decision from the framework with a separate GitHub project