Модель ORM

Важно: это лишь предлагаемая мною примерная альфа-версия модели, для ознакомления с идеями, скорее всего она будет отличаться от реальной модели, котораябудет введена в LS.

Синтаксис отношений $aRelations.


Существует 4 типа отношений:

belongs_to — связь 1 к 1, или многие к 1. в таблице обязательно наличие foreign key вида relationalias_id. Примеры:
$aRelations = array('belongs_to' =>
  array(
    'User' => 'autor',
    'Blog',
    'PluginBill_ModuleEvent' => 'event'
  )
);
// поля таблицы prefix_topic
topic_id | topic_title | ... | autor_id | blog_id | event_id
// доступные функции:
$oTopic()->getAutor(); $oTopic()->setAutor();
$oTopic()->getBlog(); $oTopic()->setBlog();
$oTopic->Save();

has_one — связь 1 к 1, обратная к belongs_to. При наличии описанных выше связей для Topic, можно добавить к сущности PluginBill_ModuleEvent_EntityEvent:
$aRelations = array('has_one' => array('Topic'));

// поля таблицы prefix_event не меняются!

// доступные функции:
$oEvent()->getTopic(); $oEvent()->setTopic();
$oEvent()->getTopic()->getBlog(); $oEvent()->getTopic()->setBlog();
$oEvent()->getTopic()->getAutor(); $oEvent()->getTopic()->setAutor();
$oEvent()->getTopic()->Save();


has_many — 1 ко многим: При установленном belongs_to для Topic, можно расширить, например, User:
$aRelations = array('has_many' => array('Topic' => 'owner')); // тут мы указываем алиас 'owner', потому foreign key в таблице prefix_topic был задан как `owner_id`

// поля таблицы prefix_user не меняются!

// доступный функционал:
foreach($oUser->getTopicItems() as $oTopic) {
  $oTopic->getBlog()->setDescription('В этом блоге есть топики самого '.$oUser->getLogin());
  $oTopic->getBlog()->Save();
}


acts_as_tree — возможность создания древовидной структуры. Пример: бесконечно вложенные категорий:
$aRelations = array('acts_as_tree');

// поля таблицы prefix_category
category_id | category_parent_id | category_title | category_url

// доступный функционал:
$oCategory->getParent(); $oCategory->setParent();
$oCategory->getChildren(); $oCategory->setChildren();
foreach($oCategory->getChildren() as $oChildCategory) {
  $oChildCategory->setUrl($oCategory->getUrl().'_'.$oChildCategory->getUrl());
}
$oCategory->getSiblings(); // вывод всех "братьев".


Разрабатываются типы has_and_belongs_to_many (с использованием join-таблиц) и acts_as_list для создания упорядоченных списков.

Описание новых методов


1. Entity.class.php



protected $_aData = array(); — Хранит слепок записи из таблицы.
protected $_aAdditionalData = array(); — Содержит дополнительные поля, такие как 'children', 'parent', а также ссылки на связанные объекты.

public function __construct($oObject)
— При инициализации сущности пробегает по массиву $aRelations и создаёт на его основе $aRelationsAliases (то, что будет доступно по get*()), например:

	protected $aRelations = array(
		'belongs_to' => array('PluginAdvert_Advcategory' => 'cat', 'User' => 'autor'),
		'has_one' => array('User'),
		'has_many' => array('PluginAdvert_Photo' => 'album'),
	);
=>
	protected $aRelationsAliases = array(
		'belongs_to' => array('Cat' => 'PluginAdvert_Advcategory', 'Autor' => 'User'),
		'has_one' => array('User' => 'User'),
		'has_many' => array('Photo' => 'PluginAdvert_Photo'),
	);



public function Save()
public function Add()
public function Update()
public function Delete()

— Вызывают соответствующие функции EntityAdd(), EntityUpdate(), EntityDelete() маппера ORM. Сбрасывают зависимые кэши.

public function getParent()
public function setParent($oEntityParent)
public function getChildren()
public function setChildren($oEntityItems)
public function getSiblings()

— Функции, доступные для деревьев, обозначенных в отношениях как «acts_as_tree».

public function __call($sName,$aArgs)
— Определяет тип запрашиваемого ключа и возвращает или записывает нужное значение или объект.

protected function GetRelationItems($sRelationAlias)
— Возвращает связанный объект или массив связанных объектов согласно типу отношений.

protected function DefineRelationType($sRelationAlias)
— Определяет тип отношений по заданному алиасу.

protected function HasRelationType($sRelationType)
— Проверяет наличие типа отношений.

static function TypeOfKey($oEntity,$sKey)
— Определяет тип ключа. Сначала пытается найти ключ в связанных отношениях, иначе считает что это сокращенное свойство (поле таблицы: `title` вместо `tablename_title`).

2. Module.class.php


public function GetByFilter($aFilter=array())
— Обрабатывает кэширование и выполняет метод ModuleGetByFilter маппера ORM. Возвращает один объект сущности в случае успеха.

public function GetItemsByFilter($aFilter=array())
— Обрабатывает кэширование и выполняет метод ModuleGetItemsByFilter маппера ORM. Возвращает массив сущностей.

public function GetById($iId) — алиас для GetByFilter(array('tablename_id'=>$iId)).
public function GetAll($aFilter = array()) — алиас для GetByFilter(). Вернет массив из всех сущностей записей в таблице.

public function __call($sName,$aArgs)
— «Умное» определение фильтров к методу GetByFilter(). Пример замен:
Test_GetItemsByTitle('Test2')         => Test_GetItemsByFilter(array ( 'test_title' => 'Test2' ) );
Test_GetTestItemsByTitle('Test Title')=> Test_GetItemsByFilter(array ( 'test_title' => 'Test Title' ) );
Test_GetItemsByCategoryId(5)          => Test_GetItemsByFilter(array ( 'category_id' => 5 ) );

Test_GetByTitle('Test1')              => Test_GetByFilter(array ( 'test_title' => 'Test1' ) );
Test_GetByUrl('my_url')               => Test_GetByFilter(array ( 'test_url' => 'my_url' ) );
Test_GetTestById(10)                  => Test_GetByFilter(array ( 'test_id' => 10 ) );


3. ORM.class.php


public function EntityAdd()
public function EntityUpdate()
public function EntityDelete()

— Основые функции для операций с сущностями. Сохраняют содержимое $_aData в таблице.

public function ModuleGetByFilter($aFilter = array())
— Вызывает метод ModuleGetItemsByFilter($aFilter,1) c параметром $bSingle=true

public function ModuleGetItemsByFilter($aFilter = array(),$bSingle = false)
— Основной метод для запросов к таблице класса и получения сущностей объектов из её записей.

Служебные методы:

static function GetPluginName($oModule)
— Пример функционирования:
PluginBill_ModulePlace $oModule => 'Bill'


static function GetPluginPrefix($oModule)
— Пример функционирования:
PluginBill_ModulePlace $oModule => 'PluginBill_'


static function GetModuleName($oModule)
— Пример функционирования:
PluginBill_ModulePlace $oModule => 'Place'


static function GetEntityName($oEntity)
— Пример функционирования:
PluginBill_ModulePlace_EntityPlaceCity $oEntity => 'PlaceCity'


static function GetTableName($oModule)
— Пример функционирования:
PluginBill_ModulePlace $oModule => 'prefix_place'


static function PrefixField($oModule,$sFieldName)
— Пример функционирования:
PluginBill_ModulePlace $oModule, 'id' => 'place_id'


4. Engine.class.php

Алиасы для доступа к основным методам движка:
/**
 * Short aliases for Engine basic methods
 * 
 */
class E extends Engine {

	public static function GI() {
		return parent::GetInstance();
	}
	
	public function GM($sClassName,$sName=null,$oConnect=null) {
		return parent::GetMapper($sClassName,$sName,$oConnect);
	}
	
	public function GE($sName,$aParams=array()) {
		return parent::GetEntity($sName,$aParams);
	}
	
	public function __call($sName,$aArgs=array()) {
		return parent::$sName($aArgs);
	}

}
  • avatar
  • 13
  • +1
    • 1
    • 0
    • 6

59 комментариев

avatar
Вдохновлен руби рельсом?
комментарий был удален
комментарий был удален
avatar
Если вы об отношениях моделей, то подобные реализации есть уже практически везде, и в CodeIgniter, и в Kohana, и в Yii Framework, а не только в RoR.
avatar
например, вот — довольно интересная модель:
codeigniter.com/wiki/IgnitedRecord/
avatar
поддерживаются join-ы, что для борцов за производительность с выключенным кэшем может быть приятно:
$this->load->model('ignitedrecord/ignitedrecord');

$this->post = IgnitedRecord::factory('posts');
$this->post->belongs_to('user')->fk('author');

$posts = $this->post->like('title', 'CodeIgniter')
                    ->order_by('date', 'desc')
                    ->join_related('user')
                    ->find_all();

foreach($posts as $post){
    echo $post->title;
    echo $post->user_username;
}  
avatar
А зачем? Когда используется модель с мапперами, к чему здесь ORM?
  • kks
  • 0
avatar
вы серьезно?
это автоматизация 80% всех действий с моделями.
то есть в 80% случаев, у вас будут пустые файлы мапперов, модулей и сущностей.
вам нравится каждый раз вручную писать один и тот же код методов Save, Update, GetById(), GetByTitle(), GetDyUrl() и т.д.?
avatar
Я лишь говорю о том, что подход с мапперами и подход с ORM — два подхода, целью которых является отделение запросов от логики. Если внедрять ORM (что само по себе конечно же принесет разработчикам удобство), то отказываться от мапперов и вместо Mapper_GetObject сразу получать нужный объект в стиле ORM: Object->get(id) например.
avatar
мы попробуем реализовать ORM в стиле стандартного подхода LS: module -> mapper -> entity
avatar
Вы неправы.
ORM и модель с мапперами — разные вещи, и предназначение у них разное, поэтому они совершенно не являются взаимозаменяемыми. Мапперы нужны, как вы правильно сказали, для отделения SQL от логики, а ORM — это модель, с помощью которой записи из таблицы приложения представляются как объекты в ООП, которые можно содавать, изменять и записывать. ORM — более высокий уровень абстракции, и если вы внимательно читали мои сообщения и код, я предлагаю ORM, как надстройку над существующей моделью.

И тем более, где вы видели у меня подобный код:
Object->get(id)
?
И что вообще он должен делать, в вашем понимании?

Предлаголось лишь автоматизировать в самом движке стандартные методы типа Blog_GetBlogById(), чтобы не прописывать их заново каждый раз при создании нового модуля. Вот и все, о чём разговор?
avatar
Насчет создания методов EntityAdd как алиаса для Module_AddEntity — поддерживаю, но это всего лишь еще 10 строчек кода.
avatar
Что то меня удивляет реакция сообщества. Вроде бы материал несложный. ORM по-моему это одна из немногих вещей, которых не хватало движку, после введения плагинов…

В последнее время создается пугающее ощущение, что аудитория этого сайта состоит из пользователей-старшекласников, которые хотят завести себе модный сайт а-ля Хабр и скачать побольше бесплатных хаков.

Ожидалось, что будет больше профессиональных разработчиков, которые рассматривают бы LiveStreet как перспективный фреймворк для создания web-приложений…

Грустно…
avatar
LiveStreet это уже вполне себе CMS, каким образом ее можно рассматривать как перспективный фреймворк? В чем эти перспективы? Вот как большой и разный блоговый социальный движок я перспективы вижу, а как универсальный каркас для любого типа сайтов совершенно не вижу.
avatar
а как универсальный каркас для любого типа сайтов совершенно не вижу.
я вижу и давно уже применяю для этого, примеры сайтов: livestreetcms.com и seogenerator.ru
LS отличный фреймворк, и ORM, как дополнительная возможность, будет не лишним
avatar
У людей LS не ассоциируется с фреймворком, потому что в базовой комплектации это блоговый движок. И в данном варианте лучше бы его и представлять как блоговый движок с гибкими возможностями модификации. Но если оставить один каркас без базовых модулей — да, фреймворк, неплохой, но только нужно тогда предлагать скачать отдельно фреймворк и отдельно блоговый движок на его основе, чтобы эта идея использования его как фреймворка развивалась не только среди покопавшихся в коде людей.
avatar
Фрейморк отличный, и мне с ним легко работать, но я чувствую что через месяц-два напихают всякого Г… из-за которого производительность упадёт в разы, тогда смысл лс как фреймворка? Ведь это каркас, а раз мы берём каркас то мы делаем свой проект, свою идею, которою хотим реализовать быстро, легко и удобно. Идеальный фреймворк ( для меня ) это где пропорции производительности и функциональности максимально равны, смысла использовать каркас на который только уходит более 0,1с я не вижу.
avatar
ORM штука отличная, согласен, но ко всему надо подходить с умом
avatar
В чем конкретно вы видите недостаток «умного» подхода в моем примере? Даже интересно.
комментарий был удален
комментарий был удален
avatar
НЕ использование ORM не приведет к снижению производительности
Разработчик сам сможет выбрать, какой подход использовать. Причем можно использовать оба подхода одновременно.
avatar
Я говорю не только об ORM.

через месяц-два напихают
avatar
За счет чего вы предсказываете снижение производительности при модели ORM?
Я уже второй раз слышу недовольство именно этим моментом, может кто-то объяснит чем обоснованы эти беспокойства?
avatar
Просто многие следуют принципу «что слышу, то пою», а те кто им изначально это напел наверное погоняли какой-нить стандартный ORM Framework против «пустных» запросов и решили «что они все такие». Я сам когда-то сравнивал Doctrine и AdoDB — и да, doctrine в 30 раз медленее AdoDB, но всё равно в пределах тысячных секунды. Это всё статичный оверхед, и с ним можно спокойно жить :)
комментарий был удален
комментарий был удален
комментарий был удален
комментарий был удален
avatar
А кто мешает использовать версию без ORM?
avatar
обычное сезонное снижение активности
avatar
К сожалению не могу рассматривать ливстрит, как базу для разработки. Сейчас активно перехожу на Django.
ORM действительно упростила бы разработку.
avatar
В джанге не вся мощь ORM, для относительно простых задач. SQLAlchemy на порядок выше.
комментарий был удален
avatar
главное обратную совместимость обеспечить, а функциональность кода никогда не помешает
avatar
я вот потыкал пальцем в *ORM классы из ядра и мало что понял )
архивчик с примером прилепить бы к топику
avatar
плагин к транковой версии dl.dropbox.com/u/8252571/article_orm.zip
avatar
все здорово, только вот каждый раз писать параметр типа
self::RELATION_TYPE_HAS_MANY

не очень удобно, было бы хорошо как-то сократить эту запись.
avatar
и еще такая проблема: что делать, если мы хотим переопределить модуль User и к нему добавить поддержку Relation-ов?
для переопределения нам нужно писать
PluginTest_ModuleUser extends PluginTest_inherit_ModuleUser

а для поддержки ORM —
PluginTest_ModuleUser extends EntityORM


есть идеи?
avatar
еще 2 проблемы:

1) для старых модулей всегда придется дописывать extends ModuleORM и прописывать parent::Init() в их Init-методе, чтобы заработали функции ModuleORM и MapperORM. Это плохо, лучше бы, чтобы все модули по умолчанию наследовали ORM функции. Или хотя бы, чтобы это можно было удобно настроить через переопределение в плагине.

2) на основании чего ты решил убрать формат полей с 'modulename_id', 'modulename_url' на 'id' и 'url' соответственно?

CREATE TABLE IF NOT EXISTS `prefix_article_category` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
avatar
2) пока не понял как рулить ситуациями, когда разные таблицы по разному оформлены, и как отличать запрос getStatus() для поля module_status от поля status (связь с другой таблицей, но в самой сущности может быть не определена)
avatar
для этого служат согласования имен, то есть необходимо таблицы оформлять по правилам.
в том числе необходимо запрещать использовать собственные поля с таким же именем, как у связи, которая определена в aRelations, вот и все.
avatar
в том числе необходимо запрещать использовать собственные поля с таким же именем, как у связи, которая определена в aRelations
это нужно делать в любом случаи

для этого служат согласования имен, то есть необходимо таблицы оформлять по правилам.
так почему не сделать правилом задавать полям любые имена(кроме регистра и пересечений в aRelations)?

Так как быть с примером выше? status может и не быть в aRelations, т.е. связь только на уровне MySQL InnoDB. Если не использовать префикс для полей, то таких проблем не возникнет.
avatar
но все существующие стандартные модули используют префиксы, значит проблема уже существует, и надо ее как-то решить :)

мне кажется, все связи нужно установит, что все связи должны быть описаны в aRelations, чтобы работали функции ORM.
avatar
еще все вхождения PrimatyKey надо исправить на PrimaryKey
avatar
еще очень странная система работы у ключа #with, он просто по циклу цепляет ко всем сущностям связи, зачем это нужно, если запросы можно делать в момент, когда юзер вызывает getRelation()?

сама по себе идея с #with хорошая, если с помощью нее генерировать запросы с JOIN`ами для тех, кто хочет сэкономить количество запросов (или нет возможности пользоваться кэшированием).

тогда ее можно прикрутить и к GetByFilter, а не только для GetItemsByFilter.
avatar
В момент вызова getRelation() запрос и так делается, #with дает возможность одним запросом подцепить сразу все связи, но не через JOIN, а через IN()
avatar
а, да, это тоже хороший вариант.

кстати, только сейчас заметил, что у тебя в реализации кэширование пока не поддерживается.
avatar
Ребят, а вы не заметили что остались в 2оем? Кому нужен фреймворк без документации? А что у ЛС не будет внятной документации уже давно стало понятно. Это раз. А КМС то вы собираетесь развивать? Если вы не заметили 99.9% сюда пришли именно за ней, если бы лично мне нужен был фреймворк я написал бы свой портал с использованием zend и не тратил время на поиски. Нет, я все прекрасно понимаю, хочется поиграть в серьезные кодерские задачи, а не плагины с хаками писать, но все-таки, вы уверены что выбрали идеальное время для этого?
avatar
Пристыдил )))
КМС это что?
avatar
Кандидат в мастера спорта =D

очевидно же, он имел в виду данную cms
avatar
Кому действительно понравилась данная система уже давно все тонкости и без документации знает. А по поводу развития КМС она и так развивается. А чтобы она развивалась широкими шагами нужны условия для разработчиков. К чему все разработчики и стремятся. Взгляните на версию Livestreet и сделайте выводы. Для 0.4 функционал уже достаточно велик! А плагины с хаками будут писаться, когда все внутренние и структурные работы ядра будут отточены + когда буржунет вкусит Livestreet, ибо зарубежные разработчики любят писать дополнения (видно на примере wordpress, drupal)
avatar
Какой буржунет, какие вкусности, Дмитрий? Рядовому вебмастеру-копирайтеру с ЛС по прежнему делать нечего.
Былая простота (версии 0.3) ушла, вместо этого пришло много интересного функционала, для разработчиков, но разработчиков нет, и плагинов тоже нет. Системе нужно множество простейших плагинов, реализующих элементарные вещи, для которых описываемый функционал не нужен, и нужен не будет.
Той же админки и той в комплекте нет, такие вещи как разграничение прав доступа только сняться. Вы говорите это 0.4? Ок, и сколько ждать версии 1.0? еще лет 6?
Вы скажете на ЛС множество сайтов? Хорошо, но что останется, если убрать все однодневки со стандартной темой? Есть на кого равняться?
Что касается моего ИМХО, то я бы так и сидел на 0.3, если бы не понадеялся на оптимизацию в 0.4 версии и не было так мучительно лень писать самому галерею.
avatar
Спасибо, Максим. наконец то можно снова высказывать свое мнение посредством плюсов
avatar
Не всем к сожалению, ограничего рейтингом.
avatar
Давно использую LS как фреймворк, разрослась куча сущностей и кода и уже тяжко разбираться, поэтому я прикрутил к ЛС YII, все очень приятно интегрировалось и новые сущности создаю только с помощью ORM. Это правильный шаг переход на ОРМ. Вот тока не понимаю зачем изобретать велосипед.
avatar
+100 только я доктрину прикрутил
avatar
ravur, поддерживаю. В LS и так используется много замечательных компонентов, так почему бы не реализовать ORM с помощью добавления готовых и хорошо документированных сторонних библиотек.
avatar
Когда кстати планируется подвязать кэширование?
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.