За кулисами: Как ОРМ «думает»?

Меня интересует дизайн Rails ActiveRecord, Doctrine for PHP (и аналогичные ORM).

  • Как ORM удается реализовать такие функции, как прикованные аксессуры и насколько они, как ожидается, будут работать?
  • Как ORM строит запросы внутри?
  • Как ORM управляет запросами, поддерживая произвольный характер всего, что от него ожидается?

Очевидно, это академический вопрос, но все натуры ответов приветствуются!

(Мой язык выбора – OO PHP5.3!)

Цепочки вызовов метода ортогональны к вопросу ORM, они используются повсеместно в ООП. Цепочный метод просто возвращает ссылку на текущий объект, позволяя вызывать возвращаемое значение. В PHP

class A { public function b() { ... return $this; } public function c($param) { ... return $this; } } $foo = new A(); $foo->b()->c('one'); // chaining is equivilant to // $foo = $foo->b(); // $foo = $foo->c(); 

Что касается построения запросов, существует два метода. В ActiveRecord, таком как ORM, есть код, который анализирует метаданные базы данных. Большинство баз данных имеют какие-то SQL или SQL-подобные команды для просмотра этих метаданных. (MySQL DESCRIBE TABLE , таблица USER_TAB_COLUMNS Oracle и т. Д.)

В некоторых ORM вы описываете свои таблицы базы данных на нейтральном языке, таком как YAML. Другие могут вывести структуру базы данных из того, как вы создали свои модели объектов (я хочу сказать, что Django делает это, но прошло некоторое время, так как я посмотрел на нее). Наконец, существует гибридный подход, в котором используется любой из двух предыдущих методов, но предоставляется отдельный инструмент для автоматического создания YAML / etc. или файлы классов.

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

Что касается вашего последнего вопроса,

Как ORM управляет запросами, поддерживая произвольный характер всего, что от него ожидается?

Я бы сказал, что ответ «не очень хорошо». После того, как вы перейдете за рамки одной таблицы, метафоры с одним объектом, у каждого ORM есть другой подход и философия относительно того, как SQL-запросы должны использоваться для моделирования объектов. В абстрактном, однако, это так же просто, как добавление новых методов, которые строят запросы на основе предположений ORM (т. Е. Метода Zend_Db_Table «findManyToManyRowset»)

Как ORM удается реализовать такие функции, как прикованные аксессуры и насколько они, как ожидается, будут работать?

Кажется, никто не ответил на это. Я могу быстро описать, как Doctrine делает это в PHP.

В Доктрине ни одно из полей, которые вы видите в объектной модели, на самом деле не определено для этого класса. Таким образом, в вашем примере, владельцы $ car->, в поле $ car нет реального поля, называемого «владельцем».

Вместо этого ORM использует магические методы, такие как __get и __set . Поэтому, когда вы используете выражение типа $ car-> color, внутри PHP вызывает Doctrine_Record #__ get ('color').

На этом этапе ORM может удовлетворить это в любом случае. Здесь много вариантов. Он может хранить эти значения в массиве с именем $ _values, а затем возвращать $ this -> _ values ​​['color']. Doctrine, в частности, отслеживает не только значения для каждой записи, но и ее статус относительно сохранения в базе данных.

Одним из примеров этого, который не является интуитивным, является отношение Доктрины. Когда вы получаете ссылку на $ car, у нее есть отношение к таблице People, которая называется «владельцы». Таким образом, данные для владельцев $ car-> фактически хранятся в отдельной таблице из данных для самого автомобиля. Таким образом, ORM имеет два варианта:

  1. Каждый раз, когда вы загружаете $ user, ORM автоматически объединяет все связанные таблицы и заполняет эту информацию в объекте. Теперь, когда вы делаете $ car-> владельцев, эти данные уже есть. Этот метод медленный, однако, потому что объекты могут иметь много отношений, и эти отношения могут иметь отношения сами. Таким образом, вы бы добавили много соединений и не обязательно использовали эту информацию.
  2. Каждый раз, когда вы загружаете $ user, ORM замечает, какие поля загружаются из таблицы User, и он заполняет их, но любые поля, которые загружаются из связанных таблиц, не загружаются. Вместо этого некоторые метаданные привязаны к этим полям, чтобы пометить их как «не загруженные, а доступные». Теперь, когда вы пишете выражение $ car-> owner, ORM видит, что отношения «владельцы» не были загружены, и он выдает отдельный запрос, чтобы получить эту информацию, добавить ее в объект и затем вернуть эти данные. Все это происходит прозрачно, если вам не нужно это осознавать.

Конечно, Doctrine использует # 2, так как №1 становится громоздким для любого реального сайта с умеренной сложностью. Но это также имеет побочные эффекты. Если вы используете несколько отношений на $ car, то Doctrine будет загружать каждый отдельно, когда вы обращаетесь к нему. Таким образом, вы получаете 5-6 запросов, когда требуется только 1.

Doctrine позволяет вам оптимизировать эту ситуацию с помощью Doctrine Query Language. Вы скажете DQL, что хотите загрузить объект автомобиля, но также присоедините его к его владельцам, производителю, названиям, записям и т. Д., И он будет загружать все эти данные в объекты.

Уф! Длительный отклик. В принципе, однако, вы получили в основе «Что такое ORM?» и «Почему мы должны использовать его?» ORM позволяет нам продолжать думать в объектном режиме в большинстве случаев, но абстракция не идеальна, и утечки в абстракции, как правило, выходят за пределы производительности.

Я создал презентацию на тему создания PHP DataMapper, которая может быть вам интересна. Он был записан на видео в Collaborative Collaborative Oklahoma City, когда я представил его там для группы пользователей PHP:

Видео: http://blip.tv/file/2249586/

Презентационные слайды: http://www.slideshare.net/vlucas/building-data-mapper-php5-представление

Презентация была в основном ранней концепцией phpDataMapper , хотя с тех пор многое изменилось.

Надеюсь, они помогут вам лучше понять внутреннюю работу ORM.

Прикомандированные аксессоры не очень важны: вы return $this из метода setter. Бум, сделанный, работает на столько уровней, сколько вам нравится.