Толстая модель       История разработки ORMШамин МихаилGeometria.ruВедущий разработчик
Geometria.ru• Главный фотохроникер страны• 8 лет на рынке• Представительство в 150 городах России, СНГ и  Прибалтики• Ежед...
Почему понадобился свой ORMБыло•   Наследство в виде залежей кода-лапши•   Практически вся бизнес-логика в контроллерах•  ...
Почему понадобился свой ORMСтало• Стали использовать NoSQL решения, такие как Redis и  Mongo• Понадобилось решение, готово...
Выбор дизайна                 Открываем книгу              Мартина Фаулера                   (Martin Fowler)"Шаблоны корпо...
Выбор дизайна         И находим то что нужно.           Domain Model                  или     Модель предметной области
Поля модели. Как задавать ?• В Zend_Db_Table_Row поля не прописаны явно, а  берутся из схемы таблицы БД• В Doctrine2 через...
Поля модели. Решение:Использовать DocBlockПрофиты:•   Готовый шаблон для типов данных•   Сразу в аннотации класса видны вс...
Zend_Reflection для генерации полей/**  * @property integer $id  * @property string $title  * @property string $body  * @p...
Zend_Reflection для генерации полейПосле $post = new Model_Post();свойство $_data будет выглядеть следующим образом:class ...
Доступ к полям• Внешний доступ к полям обеспечивается через  магические методы __get() и __set()• Можно реализовать методы...
/**  * ...  * @property integer $date Unix timestamp  */class Model_Post extends Geometria_Model...public function setDate...
Установка значения по умолчаниюclass Model_Post extends Geometria_Model...public function getDate($value){    if (null ===...
Как хранить модель?Используем DataMapper• Маппер знает все о модели и о том, как и где еѐ  хранить.• Модель ничего не знае...
Интерфейс маппераinterface Geometria_Model_Mapper_Interface{    public function create(Geometria_Model $model);    public ...
Работа с моделью$post = new Model_Post();$post->title = hello world!;$post->body = foo bar;$postMapper = new Model_Post_Ma...
Выборки• Условие $cond - простой массив  имя поля => значение• Сортировка $sort - тоже просто массив  имя поля => (bool) н...
Делаем ActiveRecordРассказываем модели, что у нее есть маппер.• Делаем статический метод getMapper() который из  специальн...
Теперь создание модели выглядит так:$post = new Model_Post();$post->title = hello world!;$post->body = foo bar;$post->crea...
Что вернет fetchAll()? Коллекцию!• аналог Zend_Db_Table_Rowset• Паттерн Record Set• Позволяет выполнять массовые действия ...
Нужен Paginator?class Geometria_Paginator_Adapter_Mapper  implements Zend_Paginator_Adapter_Interface{  public function __...
Нужен Paginator?class Geometria_Paginator_Adapter_Mapper  ...  public function getItems($offset, $limit)  {    return $thi...
Хотим кешировать, логировать и тд.• Используем декоратор для маппера• Декортатор - это матрешка: в конструктор первого  де...
Примеры декораторовCache• fetchOne(), fetchAll() - на основании переданного условия  берет данные из кеша, или же просит м...
ОтношенияРаз уж строим ORM, то должны быть отношения междусущностями.• Отношения так же, как и поля, задаются в DocBlock• ...
Пример работы с отношениями/**  * ...  * @property integer $authorId  * @property Model_Author $author[relation=belongsTo;...
Виды отношений• hasOne - one-to-one отношение• belongsTo - тоже что и hasOne, но требует  обязательного наличия объекта• h...
Полиморфические связиОбеспечивают связь с несколькими видами сущностей, тона какой тип сущности стоит ссылка определяет па...
Тонкости отношений$posts = Model_Post::getMapper()->fetchAll();foreach ($posts as $post) {    echo $post->title .  by  . $...
Тонкости отношенийРешение:$posts->fetchRelations(author);Просим relation-manager получить всех авторов однимзапросом и про...
Тонкости отношенийА если у автора есть связь с картинкой-аватаркой?$posts->fetchRelations(author, picture);Что означает, ч...
Каскадные операцииУ отношений можно прописать действие, которое будетвыполняться при удалении модели onDelete: • CASCADE -...
Жизнь без JoinовКак сделать выборку постов, написанных женщинами, еслипосты используют одно хранилище, а авторы другое, и ...
Что дало внедрение ORM• Существенное ускорение разработки• Время вхождения в чужой код значительно  уменьшилось• Использов...
Будет ли open source?Будет, но позже )
СпасибоTwitter: @munk13МойКруг: http://munkie.moikrug.ru
ZFConf 2011: Толстая модель: История разработки собственного ORM (Михаил Шамин)
ZFConf 2011: Толстая модель: История разработки собственного ORM (Михаил Шамин)
ZFConf 2011: Толстая модель: История разработки собственного ORM (Михаил Шамин)
Upcoming SlideShare
Loading in …5
×

ZFConf 2011: Толстая модель: История разработки собственного ORM (Михаил Шамин)

1,789 views
1,718 views

Published on

0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,789
On SlideShare
0
From Embeds
0
Number of Embeds
603
Actions
Shares
0
Downloads
0
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

ZFConf 2011: Толстая модель: История разработки собственного ORM (Михаил Шамин)

  1. 1. Толстая модель История разработки ORMШамин МихаилGeometria.ruВедущий разработчик
  2. 2. Geometria.ru• Главный фотохроникер страны• 8 лет на рынке• Представительство в 150 городах России, СНГ и Прибалтики• Ежедневно 80 000 пользователей / 600 000 просмотров• В понедельник 110 000 / 1 000 000• Более 500 000 репортажей• 15 000 000 фотографий• 800 000 зарегистрированных пользователей
  3. 3. Почему понадобился свой ORMБыло• Наследство в виде залежей кода-лапши• Практически вся бизнес-логика в контроллерах• Вплоть до формирования select ов!• Некоторые экшены размером в 200 строк!• В роли модели - Zend_Db_Table
  4. 4. Почему понадобился свой ORMСтало• Стали использовать NoSQL решения, такие как Redis и Mongo• Понадобилось решение, готовое работать с любым хранилищем, а не только SQL• Есть ли что-то на рынке?• Doctrine2 в alphа, еще сырая - страшно.• Что делать?• Пишем свой велосипед!
  5. 5. Выбор дизайна Открываем книгу Мартина Фаулера (Martin Fowler)"Шаблоны корпоративных приложений" ("Patterns of Enterprise Application Architecture")
  6. 6. Выбор дизайна И находим то что нужно. Domain Model или Модель предметной области
  7. 7. Поля модели. Как задавать ?• В Zend_Db_Table_Row поля не прописаны явно, а берутся из схемы таблицы БД• В Doctrine2 через задание private/protected свойств и генерацию getter/setter методов.
  8. 8. Поля модели. Решение:Использовать DocBlockПрофиты:• Готовый шаблон для типов данных• Сразу в аннотации класса видны все поля модели• Автокомплит в IDE (Zend Studio, PhpStorm, NetBeans)• Быстрое создание классов
  9. 9. Zend_Reflection для генерации полей/** * @property integer $id * @property string $title * @property string $body * @property boolean $hidden * @property integer $date **/class Model_Post extends Geometria_Model{}
  10. 10. Zend_Reflection для генерации полейПосле $post = new Model_Post();свойство $_data будет выглядеть следующим образом:class Model_Post extends Geometria_Model{ protected $_data = array( id => null, title => null, body => null, hidden => null, date => null, );}
  11. 11. Доступ к полям• Внешний доступ к полям обеспечивается через магические методы __get() и __set()• Можно реализовать методы get<поле> и set<поле>, чтобы изменить логику установки/получения значения поля.
  12. 12. /** * ... * @property integer $date Unix timestamp */class Model_Post extends Geometria_Model...public function setDate($value){ if ($value instanceof Zend_Date) { $value = $value->getTimestamp(); } $this->_data[date] = $value;}
  13. 13. Установка значения по умолчаниюclass Model_Post extends Geometria_Model...public function getDate($value){ if (null === $this->_data[date]) { $this->_data[date] = time(); } return $this->_data[date];}
  14. 14. Как хранить модель?Используем DataMapper• Маппер знает все о модели и о том, как и где еѐ хранить.• Модель ничего не знает о хранилище.• Логика домена отделена от persist логики• Можно менять структуру бд или даже сменить хранилище, не меняя логику модели, всего лишь изменив маппер.• Маппер выполняет CRUD операции• Можно использовать любое хранилище: MySQL, Mongo, Redis, Config file, RESTApi и др.
  15. 15. Интерфейс маппераinterface Geometria_Model_Mapper_Interface{ public function create(Geometria_Model $model); public function update(Geometria_Model $model); public function delete(Geometria_Model $model); public function fetchOne($cond, $sort); public function fetchAll($cond, $sort, $limit, $skip); public function getCount($cond);}
  16. 16. Работа с моделью$post = new Model_Post();$post->title = hello world!;$post->body = foo bar;$postMapper = new Model_Post_Mapper();$postMapper->create($post);echo $post->id; // 1 маппер сам проставил в моделиid
  17. 17. Выборки• Условие $cond - простой массив имя поля => значение• Сортировка $sort - тоже просто массив имя поля => (bool) направление сортировки• Для более сложных выборок пишем отдельный методВыбрать 10 скрытых постов, начиная с самых новых$mapper->fetchAll( array(hidden => true), array(date => false), 10);
  18. 18. Делаем ActiveRecordРассказываем модели, что у нее есть маппер.• Делаем статический метод getMapper() который из специального контейнера Geometria_Model_Mapper_Manager достает нужный ей маппер• Делаем у модели методы create(), update(), delete()public function create(){ return self::getMapper()->create($this);}
  19. 19. Теперь создание модели выглядит так:$post = new Model_Post();$post->title = hello world!;$post->body = foo bar;$post->create();echo $post->id; // 1А пост можно получить в одну строчку:$post = Model_Post::getMapper()->fetchOne( array(id => 1));или так$post = Model_Post::getMapper()->fetch(1);
  20. 20. Что вернет fetchAll()? Коллекцию!• аналог Zend_Db_Table_Rowset• Паттерн Record Set• Позволяет выполнять массовые действия с набором моделейinterface Geometria_Model_Collection_Interfaceextends Iterator, Countable{ public function append(Geometria_Model $model); public function prepend(Geometria_Model $model); public function populate(array $data); public function clear(); public function toArray();}
  21. 21. Нужен Paginator?class Geometria_Paginator_Adapter_Mapper implements Zend_Paginator_Adapter_Interface{ public function __construct( Geometria_Model_Mapper_Interface $mapper, array $cond = null, array $sort = null ) { $this->_mapper = $mapper; $this->_cond = $cond; $this->_sort = $sort; }}
  22. 22. Нужен Paginator?class Geometria_Paginator_Adapter_Mapper ... public function getItems($offset, $limit) { return $this->_mapper->fetchAll( $this->_cond, $this->_sort, $limit, $offset ); } public function count() { return $this->_mapper->getCount( $this->_cond, $this->_sort ); }
  23. 23. Хотим кешировать, логировать и тд.• Используем декоратор для маппера• Декортатор - это матрешка: в конструктор первого декоратора передаем маппер, в конструктор второго передаем первый декоратор и так далее• Декоратор перехватывает "интересующие" его методы, и изменяет результат на ему угодный, остальные методы просто пропускает дальше.• При инициализации маппера маппер-менеджер спрашивает у маппера, хочет ли он задекорироваться и оборачивает во все указанные декоратры
  24. 24. Примеры декораторовCache• fetchOne(), fetchAll() - на основании переданного условия берет данные из кеша, или же просит маппер выполнить запрос и кеширует его результат.• create(), update(), delete() - сбрасывает соответсвующий кеш.Profiler• Декоратор пропускает все запросы через себя, записывая в лог время выполнения запроса.Identity Map • Кеширует результаты в памяти, чтобы маппер не выполнял одинаковые запросы дважды
  25. 25. ОтношенияРаз уж строим ORM, то должны быть отношения междусущностями.• Отношения так же, как и поля, задаются в DocBlock• Параметры описываются в спец формате• При создании модели, создаются объекты-менеджеры отношений• При обращении к полю, ссылающемуся на внешнюю сущность, объект-менеджер отношения делает запрос к внешнему мапперу и возвращает полученый объект.
  26. 26. Пример работы с отношениями/** * ... * @property integer $authorId * @property Model_Author $author[relation=belongsTo;localKey=authorId] */class Model_Post extends Geometria_Model{...}$post = Model_Post::getMapper()->fetchOne(array(authorId=> 5));$author = $post->author; // Model_AuthorМенеджер отношения в данном случае выполнит запросModel_Author::getMapper()->fetchOne(array(id => 5));
  27. 27. Виды отношений• hasOne - one-to-one отношение• belongsTo - тоже что и hasOne, но требует обязательного наличия объекта• hasMany - one-to-many отношение
  28. 28. Полиморфические связиОбеспечивают связь с несколькими видами сущностей, тона какой тип сущности стоит ссылка определяет параметрownerType, в то время как параметр ownerTypeIdопределяет id сущности./** * @property string $ownerType * @property integer $ownerId * @property Model_User|Model_Post $owner[relation=polymorhic; localKey=ownerType;localTypeKey=ownerId] */class Model_Comment extends Geometria_Model{..}
  29. 29. Тонкости отношений$posts = Model_Post::getMapper()->fetchAll();foreach ($posts as $post) { echo $post->title . by . $post->author;}Автор запрашивается при каждой итерации.Если у нас 10 постов, значит мы сделаем 1 запрос наполучение постов и 10 запросов на получение авторов.Итого 11 запросов - плохо!
  30. 30. Тонкости отношенийРешение:$posts->fetchRelations(author);Просим relation-manager получить всех авторов однимзапросом и проставить во всех постах коллекции.Итого: 2 запроса, независимо от количества постов.
  31. 31. Тонкости отношенийА если у автора есть связь с картинкой-аватаркой?$posts->fetchRelations(author, picture);Что означает, что перед тем, как "распихать" всех авторовпо постам, у полученной коллекции авторов будетвызван метод:$authors->fetchRelations(picture);
  32. 32. Каскадные операцииУ отношений можно прописать действие, которое будетвыполняться при удалении модели onDelete: • CASCADE - удалить все связанные зависимые модели • SET NULL - очистить значения внешних ключейЭто позволяет сохранять целостность связей внутринашей системы.
  33. 33. Жизнь без JoinовКак сделать выборку постов, написанных женщинами, еслипосты используют одно хранилище, а авторы другое, и нетвозможности сделать join?Использовать sphinx.• Создаем индекс в сфинксе для такого рода выборки.• Индексируем данные.• Создаем sphinx декоратор• Декоратор ищет id документов, удовлетворяющих поисковому запросу. И по этом списку id маппер возвращает коллекцию с результатом.
  34. 34. Что дало внедрение ORM• Существенное ускорение разработки• Время вхождения в чужой код значительно уменьшилось• Использование Domain Driven Design позволяет говорить на языке предметной области, что повышает читаемость кода• Логика приложения вынесена в отдельный слой сервисов. Что позволяет использовать ее не только в MVC, но и в CLI, например.• Размер экшенов в контроллерах сократился до 10 строк.
  35. 35. Будет ли open source?Будет, но позже )
  36. 36. СпасибоTwitter: @munk13МойКруг: http://munkie.moikrug.ru

×