Мысли об Object-Relational Mapping в LiveStreet
Хочу предложить свои идеи для развития MVC/ORM.
Замечу, что исторически сложилось так, что MVC в LiveStreet весьма отличается от привычного представления в других фреймворках.
Модель здесь заменяется связкой модуль+сущность+маппер, причем, если сущность представляет из себя стандартный ООП-объект с набором свойств и методов, то модули и мапперы, это просто наборы функций для работы с определенными типами данных, что скорее похоже на библиотеки из структурного программирования, чем на стандартный ООП.
Я не возьмусь судить хорошо это или плохо, у меня есть лишь предложения о том, как можно воспользоваться этим для создания эффективных отношений между объектами (модулями).
1. Модули и их сущности
Обычно методы для работы с моделью описаны в одном файле класса модели, причем этот файл совмещает методы для работы и с одним объектом-моделью (записью в контексте БД) и с группой однородных объектов этого класса (таблицей в БД). Разделение же в LS на Entity и Module может быть полезно для того, чтобы в сущности описывались методы как раз объекта-записи:
… а в модуле — методы для работы с целым классом-таблицей.
2. Автоматизация стандартных методов
На самом деле, все методы, которые описаны выше (а также некоторые другие) — довольно стандартны, но сейчас они дублируются во всех модулях, сущностях и мапперах. Есть смысл сделать все это автоматизированным, на уровне движка, чтобы каждый модуль и каждая сущность автоматически имели все эти геттеры, сеттеры, «апдейты» и «сейвы». Многие модели смогут вообще функционировать с пустыми файлами модуля, сущности и маппера.
3. Отношения в ORM
В Ruby on Rails для ActiveRecord для обеспечения связей и зависимостей между моделями и их таблицами в БД существуют замечательные отношения: belongs_to, has_many, has_one и т.п.
Например (в контексте синтаксиса LS) достаточно было бы прописать в prefix_topic поле `blog_id` и добавить по строчке в сущности Topic и Blog и у $oTopic появился бы метод getBlog(), возвращающий сущность Blog, а у $oBlog — getTopics() с массивом сущностей Topic.
Например так:
А дальше движок автоматически на основе согласований имен таблиц и полей добавлял бы новые методы к сущностям моделей.
Есть, правда, одно узкое место — единственные и множественные числа.
Например, если у нас есть модель Topic, которая «принадлежит» модели Blog, то тут надо, чтобы движок умел понимать, что по $oBlog->getTopics() нужно искать по полю `blog_id` в таблице `prefix_topic`, которое указано в единственном числе. В Ruby on Rails есть хороший механизм перевода из ед. во мн. число и обратно с учетом всех правил английского языка.
В принципе, можно создать такой алгоритм, это слегка усложнит задачу, зато добавит изящности в итоге.
Другой вариант — использовать, например постфикс Items для обозначения множественного числа, например так: $oBlog->getTopicItems() и движок уже легко достанет из этого метода имя нужной таблицы: `prefix_topic`.
4. Пример удобства создания новых моделей
Например, если нам нужно создать плагин «Фотогалерея» для организации связи между моделями Album и Photo нам потребуется всего лишь:
a) sql-запрос (или, в идеале, схема или миграции), который описывает поля для обоих моделей (Photo и Album), причем в `prefix_photo` у нас будет дополнительное поле `album_id`.
Примеры полей:
`prefix_album`
`prefix_photo`
b) файлы модулей и мапперов изначально пустые
c) файлы сущностей содержат только по 1 строчке
Вот и все.
А в итоге у нас будут работать следующие методы:
На мой взгляд этот подход на основе согласований имен очень удобен и совмещает в себе практичность, наглядность и удобство проектирования логики приложений.
Что думает уважаемое сообщество по поводу такого возможного развития MVC/ORM в движке LiveStreet?
Замечу, что исторически сложилось так, что MVC в LiveStreet весьма отличается от привычного представления в других фреймворках.
Модель здесь заменяется связкой модуль+сущность+маппер, причем, если сущность представляет из себя стандартный ООП-объект с набором свойств и методов, то модули и мапперы, это просто наборы функций для работы с определенными типами данных, что скорее похоже на библиотеки из структурного программирования, чем на стандартный ООП.
Я не возьмусь судить хорошо это или плохо, у меня есть лишь предложения о том, как можно воспользоваться этим для создания эффективных отношений между объектами (модулями).
1. Модули и их сущности
Обычно методы для работы с моделью описаны в одном файле класса модели, причем этот файл совмещает методы для работы и с одним объектом-моделью (записью в контексте БД) и с группой однородных объектов этого класса (таблицей в БД). Разделение же в LS на Entity и Module может быть полезно для того, чтобы в сущности описывались методы как раз объекта-записи:
getId()
getTitle()
setId()
setTitle()
Add()
Update()
Reload()
… а в модуле — методы для работы с целым классом-таблицей.
GetAll()
GetById()
GetByTitle()
GetPage(30, 2)
2. Автоматизация стандартных методов
На самом деле, все методы, которые описаны выше (а также некоторые другие) — довольно стандартны, но сейчас они дублируются во всех модулях, сущностях и мапперах. Есть смысл сделать все это автоматизированным, на уровне движка, чтобы каждый модуль и каждая сущность автоматически имели все эти геттеры, сеттеры, «апдейты» и «сейвы». Многие модели смогут вообще функционировать с пустыми файлами модуля, сущности и маппера.
3. Отношения в ORM
В Ruby on Rails для ActiveRecord для обеспечения связей и зависимостей между моделями и их таблицами в БД существуют замечательные отношения: belongs_to, has_many, has_one и т.п.
Например (в контексте синтаксиса LS) достаточно было бы прописать в prefix_topic поле `blog_id` и добавить по строчке в сущности Topic и Blog и у $oTopic появился бы метод getBlog(), возвращающий сущность Blog, а у $oBlog — getTopics() с массивом сущностей Topic.
Например так:
class ModuleBlog_EntityBlog extends Entity {
protected $aRelations = array(
'has_many' => array('topic')
);
...
}
class ModuleTopic_EntityTopic extends Entity {
protected $aRelations = array(
'belongs_to' => array('blog')
);
...
}
А дальше движок автоматически на основе согласований имен таблиц и полей добавлял бы новые методы к сущностям моделей.
Есть, правда, одно узкое место — единственные и множественные числа.
Например, если у нас есть модель Topic, которая «принадлежит» модели Blog, то тут надо, чтобы движок умел понимать, что по $oBlog->getTopics() нужно искать по полю `blog_id` в таблице `prefix_topic`, которое указано в единственном числе. В Ruby on Rails есть хороший механизм перевода из ед. во мн. число и обратно с учетом всех правил английского языка.
В принципе, можно создать такой алгоритм, это слегка усложнит задачу, зато добавит изящности в итоге.
Другой вариант — использовать, например постфикс Items для обозначения множественного числа, например так: $oBlog->getTopicItems() и движок уже легко достанет из этого метода имя нужной таблицы: `prefix_topic`.
4. Пример удобства создания новых моделей
Например, если нам нужно создать плагин «Фотогалерея» для организации связи между моделями Album и Photo нам потребуется всего лишь:
a) sql-запрос (или, в идеале, схема или миграции), который описывает поля для обоих моделей (Photo и Album), причем в `prefix_photo` у нас будет дополнительное поле `album_id`.
Примеры полей:
`prefix_album`
album_id | album_title
`prefix_photo`
photo_id | photo_title | photo_img_src | album_id
b) файлы модулей и мапперов изначально пустые
c) файлы сущностей содержат только по 1 строчке
class ModuleAlbum_EntityAlbum extends Entity {
protected $aRelations = array(
'has_many' => array('photo')
);
}
class ModulePhoto_EntityPhoto extends Entity {
protected $aRelations = array(
'belongs_to' => array('album')
);
}
Вот и все.
А в итоге у нас будут работать следующие методы:
$oAlbum = Engine::GetEntity('Album');
$oAlbum->setTitle('Test');
$oAlbum->Add();
$oPhoto = Engine::GetEntity('Photo');
$oPhoto->setImgUrl('my_image.png');
$oPhoto->setAlbumId($oAlbum->getId());
// или еще проще для пользователя:
$oPhoto->setAlbum($oAlbum);
$oPhoto->Add();
// и это ещё не всё:
$this->Album_GetById();
$this->Album_GetByTitle()
// чтобы получить фотки из конкретного альбома можно будет писать
$oAlbum = $this->Album_GetByTitle('Test');
$oAlbumPhotos = $oAlbum->getPhotoItems();
$oAlbumPhotos[0]->getAlbum()->getTitle(); // будет содержать title альбома - 'Test'
На мой взгляд этот подход на основе согласований имен очень удобен и совмещает в себе практичность, наглядность и удобство проектирования логики приложений.
Что думает уважаемое сообщество по поводу такого возможного развития MVC/ORM в движке LiveStreet?
14 комментариев
2. Scaffold? Обязательно нужен… просто убивает написание однотипных запросов, особенно при разработке различных каталогов.
3. Не задумывался, но было бы действительно очень полезно!
2) я говорю не о scaffold`е, а о стандартных методах типа get*(), set*(), Add(), Upload() которые должны быть встроены в движок, а не писаться для каждого нового модуля заново. Scaffold в основном используется для viewer-ов и контроллеров, я же сейчас поднимаю вопрос модели.
3) конечно, не нужно вручную писать запросы, не нужно заботится о кэшировании и т.п.
Абсолютно разумное предложение, тем более что оно принято на обсуждение.
И разработчики модулей — от них напрямую зависит будущее движка, в отличие от пассивных 97% пользователей.
Во-вторых, как я уже не раз говорил, LiveStreet, кроме как движок для создания блогов может представлять из себя самостоятельный фреймворк — каркас для создания веб-приложений. А в настоящее время фреймворк без поддержки ORM мало ценен. Именно эту проблему я и предлагаю решить.
В-третьих, вас никто не обязывает обновлять ядро движка, когда эти изменения будут внесены, их наличие/отсутствие никак не повлияет на существующий в данный момент функционал.
Кроме того, мы уже ведем обсуждение с Максимом по поводу внедрения этого функционала, я думаю, в скором времени он и сам отпишется тут об этом.
Прошу вас высказать своё мнение по этому поводу. Сомневаюсь, что здесь мало разработчиков плагинов или что их не касаются вопросы развития ядра LiveStreet.
В данный момент идет планирование реализации этих концепций, которые будут в ближайшее время включены в ядро LiveStreet, я прошу вас подключаться к обсуждению, высказывать свои идеи и предложения.
У нас все постоянно сетуют, что разработка движка проходит в узком закрытом коллективе, что предложения пользователей принимаются очень редко — сейчас как раз тот момент, когда вы можете поучаствовать в планировании ветки ORM ядра.
Мои пять копеек к «внутренностям»:
1. Вместо предложеного дефолтного метода Add() (и, как я полагаю ещё и Update()) иметь один центральный метод Save(). Если нет ID то будет создаваться новая запись в СУБД, если есть, то будет апдейтиться старая.
2. Очень хотелось бы видеть в какой-нибудь далёкой версии LS дельную имплементацию Unit Of Work — проще говоря, я в скрипте загружаю и меняю объекты, а все эти изменения пишутся в базу «одним махом» в ходе одной транзакции.
3. Мне кажется что так же было бы крайне круто иметь стандартный magic метод для сеттеров и геттеров во всех объектах ORM, что-то типа:
Он есть и сейчас в ядре. Можно использовать get*() и set*() без прописывания в Entity. В моем примере он значительно расширен для работы с отношениями и для автоматического определения соответствующего имени поля в БД,
Например, если сущность Photo относится к модулю Album, то возможен вызов
В сущности можно оставить только алиасы на реальные методы работы с БД, а все действия производить в модуле. Например, вызов является алиасом для
Вообще реализацию всего этого можно начать с работы только одной таблицы, т.е. реализация get* set* save* delete* find(или byFilter)*, а потом уже накручивать реляционные связи