Pull to refresh

Новые функции в репозитории фреймворка: ORM/ActiveRecord

LiveStreet
image
Я хотел бы начать серию статей о развитии движка LiveStreet, а именно его фреймворковой части. LiveStreet получил довольно широкую популярность как блогосоциальный хабраклон, но за 2 года перерос уже в нечто значительно большее. Особенно с выходом версии 0.4.*, когда появились широкие возможности для написания плагинов с функционалом наследования и делегирования.
Достаточно большое количество крупных социальных сетей уже построено на LiveStreet с использованием этих технологий.

В связи с этим у разработчиков появляется необходимость в разработке функционала, модулей и хаков для своих проектов. Внедрение плагинов упростило этот процесс в разы. Мы продолжаем работать в этом направлении: сейчас я расскажу об альфа-версии реализации ORM-подхода на основе паттерна ActiveRecord, который мы разработали (и продолжаем разрабатывать) в LiveStreet.



image
Что такое ORM (в двух словах)

Идея ORM заключаются в представлении, отображении таблиц базы данных как классов (моделей), а записей из них как объектов этого класса. Основная цель ORM подхода: автоматизировать стандартные рутинные функции (CRUD) для работы с данными в базе, избавить от лишней необходимости писать SQL-запросы и формировать ответы.

Как было раньше

Раньше при создании модели (Sample) нужно было выполнить ряд однообразных действий и написать или скопипастить доброе количество кода. Только по структуре LS нужно было создать 3 больших файла:
/modules/sample/Sample.class.php — файл модуля: набор функций для управления моделями: AddSample(), UpdateSample(), GetSampleById(), GetSampleByBlablabla() и т.п., каждая из которых в большинстве своем вызывает синоним из маппера (см. дальше) и обрабатывает кэширование.
/modules/sample/entity/Sample.entity.class.php — сущность модели, имеет список методов а-ля getId(), getBlablabla(), setId(), setBlablabla();
/modules/sample/mapper/Sample.mapper.class.php — sql-маппер модели, который содержит почти те же функции, что и в файле модуля, только с sql-запросами к таблицам.
И все эти методы нужно было прописывать/переписывать вручную каждый раз при создании новой модели. Кроме того нужно было прописывать дополнительные настройки, такие как имя таблицы в базе данных и т.п.

Что можно делать теперь

Что дает нам реализация ORM? Фактически — возможность избавится от ~80% процентов однообразного кода.
Модули, сущности и мапперы, которые наследуют соответствующие ORM-классы автоматически обладают большинством стандартных методов: get*(), Add(), Save(), GetBy*(), GetItemsBy*(), Delete() и т.д.

Приведу более подробный, классический пример. Допустим, мы хотим создать модуль «фотоальбом».
1) Сперва создадим таблицы сущностей «Album» prefix_album и «Photo» prefix_photo в базе данных вида:
album_id | author_id | album_title
и
photo_id | album_id | photo_title | photo_img_src


2) Создаем файл модуля нашей галереи: /classes/modules/gallery/Gallery.class.php:
<?php
class ModuleGallery extends ModuleORM {
  public function Init() {
    parent::Init();
  }
}
?>


3) Создаем файлы сущностей /classes/modules/gallery/entity/Album.entity.class.php и /classes/modules/gallery/entity/Photo.entity.class.php:
<?php
class ModuleGallery_EntityAlbum extends EntityORM {}
?>
и
<?php
class ModuleGallery_EntityPhoto extends EntityORM {}
?>


4) И… на этом все. Теперь мы можем управлять нашими сущностями через 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();
}

// и т.д...


По нашим прикидкам даже этого небольшого набора хватает для реализации 30%-40% методов (читай — строк кода), описанных в стандартных модулях, сущностях и мапперах старым методом.
Мне кажется… это здорово! :)

Что еще?… Отношения (Relations)

Немаловажный элемент в любом веб-приложении это связи (отношения) моделей. Обычно эти связи создаются через базу данных с помощью первичных ключей (primary key) в таблицах.
Вернемся к нашему примеру: очевидно, что фотографии в галерее относятся к альбому, и поэтому не знаю, как вы, а я считаю что было бы круто, чтобы вместо
// выборка коллекции (ключевое слово "Items"):
$aPhotos = LS::E()->Gallery_GetPhotoItemsByAlbumId($oAlbum->getId());

мы могли бы просто написать…
$aPhotos = $oAlbum->getPhotos();

Да, было бы определённо круче! Нет проблем :) Чтобы связать сущности нужно просто настроить их отношения с помощью массива $aRelations в описании класса сущности.
Вернемся к нашему пока пустующему файлу /classes/modules/gallery/entity/Album.entity.class.php и добавим в него такой код:
<?php
class ModuleGallery_EntityAlbum extends EntityORM {
  protected $aRelations = array(
    'photos' =>  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'),
  );
}
?>

А для файла /classes/modules/gallery/entity/Photo.entity.class.php напишем так:
<?php
class ModuleGallery_EntityPhoto extends EntityORM {
  protected $aRelations = array(
    'album' =>  array('belongs_to','Gallery_Album','album_id'),
  );
}
?>

И все. Теперь мы можем еще проще оперировать с сущностями, внимание:
$aPhotos = $oAlbum->getPhotos();
$sUserLogin = $aPhotos[0]->getAlbum()->getAuthor()->getLogin();


Разберемся с синтаксисом массива $aRelations.
Его ключи — имена связанных сущностей, которые потом будет доступны через get*. Значения в большинстве случаев также массивы, состоящие из следующих элементов:
  • Тип отношения. На настоящих момент доступны типы: belongs_to, has_one, has_many, many_to_many и tree
  • Сущность, которую мы цепляем
  • Поле в таблице, которое содержит первичный ключ цепляемой или текущей сущности, в зависимости от типа отношений
  • Название связующей join-таблицы для типа many_to_many

Что дальше?

На первый раз, думаю пока хватит, в следующей статье я подробнее остановлюсь на описании типов отношений, отдельно расскажу о типе tree, который позволяет управлять древовидными структурами; о возможностях автоматической подгрузки связанных сущностей в GetBy*(); а также о дополнительных методах, таких как GetByFilter(), GetCountItemsBy*(), GetByArray*(), Update(), Delete(), Reload(), reload*() и других.

Зачем это все?

Мы зовем разработчиков. Несмотря на успешный старт, комьюнити фреймворка сейчас находится почти в детском возрасте, а мы прекрасно понимаем, что без хороших людей, без настоящей команды, нам будет гораздо сложнее вывести LiveStreet на международный уровень. Да, а мы к этому и стремимся :)
Я искренне надеюсь, что наши нововведения помогут разработчикам (а также всем остальным: пользователям и стартаперам, копирайтерам и PR-менеджерам, специалистам и инвесторам) заинтересоваться действительно замечательным, молодым и перспективным движком LiveStreet.

И, наконец, я хотел бы заранее попросить не затевать холиваров на тему «А у RoR/ZF/Yii ActiveRecord в сторазкруче» и «не парьтесь, поставьте себе Doctrine!» и т.п. Давайте будем оптимистами.

Спасибо за внимание.
С уважением,
Александр Зинчук (Ajaxy).
Tags:livestreetблогосоциальная сетьфреймворкormactiverecordнаследованиеплагиныcmscmf
Hubs: LiveStreet
Total votes 52: ↑39 and ↓13 +26
Views2.3K

Popular right now

Top of the last 24 hours