
New features in the framework repository: ORM / ActiveRecord

I would like to start a series of articles on the development of the LiveStreet engine, namely its framework part. LiveStreet gained quite wide popularity as a blog-social habraclone , but in 2 years it has already grown into something much larger. Especially with the release of version 0.4. *, When there were ample opportunities for writing plugins with inheritance and delegation functionality .
A fairly large number of large social networks are already built on LiveStreet using these technologies.
In this regard, developers have a need to develop functionality, modules and hacks for their projects. The introduction of plugins has simplified this process at times. We continue to work in this direction: now I will talk about the alpha version of the implementation of the ORM approach based on the ActiveRecord pattern , which we developed (and continue to develop) in LiveStreet.

What is ORM (in a nutshell)
The idea of ORMs is to represent, display database tables as classes (models), and records from them as objects of this class. The main goal of the ORM approach: to automate standard routine functions ( CRUD ) for working with data in the database, to eliminate the unnecessary need to write SQL queries and generate answers.
As it was before
Previously, when creating a model ( Sample ), it was necessary to perform a series of uniform actions and write or copy a good amount of code. Only LS structure needed to create 3 large files:
/modules/sample/Sample.class.php - module file: set of functions for managing models: AddSample (), UpdateSample (), GetSampleById (), GetSampleByBlablabla () , etc. ., each of which for the most part calls a synonym from the mapper (see below) and processes caching.
/modules/sample/entity/Sample.entity.class.php - the essence of the model, has a list of methods a la getId (), getBlablabla (), setId (), setBlablabla () ;
/modules/sample/mapper/Sample.mapper.class.php- sql-mapper of the model, which contains almost the same functions as in the module file, only with sql-queries to the tables.
And all these methods had to be written / rewritten manually each time when creating a new model. In addition, it was necessary to prescribe additional settings, such as the name of the table in the database, etc.
What can be done now
What does the ORM implementation give us? In fact - the ability to get rid of ~ 80% of the uniform code .
Modules, entities and mappers that inherit the corresponding ORM classes automatically have most of the standard methods: get * (), Add (), Save (), GetBy * (), GetItemsBy * (), Delete () , etc.
I will give a more detailed, classic example. Suppose we want to create a “photo album” module.
1) First, create the entity tables “Album” prefix_album and “Photo” prefix_photo in a database of the form:
album_id | author_id | album_title
andphoto_id | album_id | photo_title | photo_img_src
2) Create the module file of our gallery: /classes/modules/gallery/Gallery.class.php :
3) Create the entity files /classes/modules/gallery/entity/Album.entity.class.php and /classes/modules/gallery/entity/Photo.entity.class.php :
and
4) And ... that's all. Now we can manage our entities through a singleton Engine:
// Сущность текущего юзера:
$oUserCurrent = Engine::GetInstance()->User_GetUserCurrent();
// или short-alias:
$oUserCurrent = LS::CurUsr();
// создание пустой сущности:
$oAlbum = Engine::GetEntity('ModuleGallery_EntityAlbum');
// или short-alias:
$oAlbum = LS::Ent('Gallery_Album');
// задание свойств модели
$oAlbum->setAutorId($oUserCurrent->getId());
$oAlbum->setTitle('First Album');
// сохранение сущности в таблице `prefix_album`
$oAlbum->Add();
// или
$oAlbum->Save();
// поиск сущности по ключу:
$oAlbum = Engine::GetInstance()->Gallery_GetAlbumByTitle('First Album');
// или short-alias:
$oAlbum = LS::E()->Gallery_GetAlbumByTitle('First Album');
// выборка коллекции (ключевое слово "Items"):
$aPhotos = LS::E()->Gallery_GetPhotoItemsByAlbumId($oAlbum->getId());
// выборка по нескольким параметрам:
$oPhoto = LS::E()->Gallery_GetPhotoByTitleAndAlbumId('Пейзаж',$oAlbum->getId());
// изменение сущностей:
foreach($aPhotos as $oPhoto) {
$oPhoto->setAlbumId(2);
$oPhoto->Save();
}
// и т.д...
According to our estimates, even this small set is enough to implement 30% -40% of the methods (read - lines of code) described in standard modules, entities and mappers by the old method.
I think ... that's great! :)
What else? ... Relations
An important element in any web application is the connection (relationship) of models. Typically, these relationships are created through the database using primary keys in tables.
Let us return to our example: it is obvious that the photos in the gallery are related to the album, and therefore I don’t know how you are, but I think it would be cool to instead
// выборка коллекции (ключевое слово "Items"):
$aPhotos = LS::E()->Gallery_GetPhotoItemsByAlbumId($oAlbum->getId());
we could just write ...
$aPhotos = $oAlbum->getPhotos();
Yes, it would definitely be cooler! No problem :) To link entities you just need to configure their relationships using the $ aRelations array in the entity class description.
Let's go back to our currently empty file /classes/modules/gallery/entity/Album.entity.class.php and add the following code to it:
array(self::RELATION_TYPE_HAS_MANY,'ModuleGallery_EntityPhoto','album_id'),
'author' => array(self::RELATION_TYPE_BELONGS_TO,'ModuleUser_EntityUser','author_id'),
// или короче:
'photos' => array('has_many','Gallery_Photo','album_id'),
'author' => array('belongs_to','User','author_id'),
);
}
?>
And for the file /classes/modules/gallery/entity/Photo.entity.class.php we write like this:
array('belongs_to','Gallery_Album','album_id'),
);
}
?>
And that’s all. Now we can even more easily operate with entities, attention :
$aPhotos = $oAlbum->getPhotos();
$sUserLogin = $aPhotos[0]->getAlbum()->getAuthor()->getLogin();
Let's deal with the $ aRelations array syntax .
Its keys are the names of related entities, which will then be available through get * . Values in most cases are also arrays consisting of the following elements:
- Type of relationship. Currently available types are: belongs_to, has_one, has_many, many_to_many and tree
- The essence we catch
- A field in a table that contains the primary key of a clinging or current entity, depending on the type of relationship
- The name of the join join table for type many_to_many
What's next?
For the first time, I think so far enough, in the next article I will dwell on the description of the types of relations, I will separately discuss the type of tree , which allows you to manage tree structures; about the capabilities of automatic loading of related entities in GetBy * () ; as well as additional methods, such as GetByFilter (), GetCountItemsBy * (), GetByArray * (), Update (), Delete (), Reload (), reload * () and others.
Why is this all?
We call developers. Despite a successful start, the community of the framework is now almost in childhood, and we are well aware that without good people, without a real team, it will be much more difficult for us to bring LiveStreet to the international level. Yes, and we are striving for this :)
I sincerely hope that our innovations will help developers (as well as everyone else: users and startups, copywriters and PR managers, specialists and investors) to become interested in a really wonderful, young and promising LiveStreet engine .
And, finally, I would like to ask you in advance not to launch holivars on the topic “And RoR / ZF / Yii ActiveRecord is in one hundred percent” and “Don't worry, put Doctrine for yourself!”, Etc. Let's be optimistic.
Thanks for attention.
Sincerely,
Alexander Zinchuk (Ajaxy)