Welcome, Guest! Log In | Create Account

This page on English


Описание

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

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

LightOrm vs Propel vs Doctrine benchmark

Возможности

Цели проекта

  1. Испытать на практике целесообразность некоторых идей, а именно, кеширования объектов, автоматического управления памятью, использования паттерна "Декоратор", в котором помещается функционал по связи объекта с базой данных.
  2. Найти оптимальное соотношение между функциональностью и производительностью библиотеки. Так как большая функциональность отрицательно сказывается на производительности, то возникает задача выбора между этими двумя показателями.
  3. Постараться избавиться от недостатков, которые присутствуют в других ORM проектах.

Идеология

Автор имеет определённую точку зрения на ORM, которая сказалась на проекте в целом.
По мнению автора:
ORM, в первую очередь, предназначена для устранения дублирования кода при работе с данными, в частности, для устранения дублирования бизнес логики;
ORM не удовлетворяет всех нужд при работе с данными, в частности, по мнению автора, ORM не предназначена для построения сводных таблиц (HTML таблиц), с целью их отображения, для этой цели более практичным будет использование связки DataSet и Grid;
ORM не предназначена для устранения SQL запросов из проекта.

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

Для ORM же, характерно получение коллекции объектов по заданным условиям фильтрации. Особенностью таких запросов является получение всех полей одной таблицы и использование связей для построения более сложных условий фильтрации.

Различие в потребностях приводит к различию самих подходов, и их смешивание приводит к неоптимальному использованию как SQL запросов, так и ORM.

Таким образом, вполне характерно, что в зависимости от потребностей проекта, в нём могут быть использованы оба подхода. Автор не видит в этом проблемы, если использовать чёткие правила по применению обоих подходов.

Присоединяйтесь к команде разработчиков!

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

#objectcache?Кеширование объектов

В библиотеке LigthOrm реализован кеш объектов, или пул объектов, как он называется в Propel и Doctrine. В этих ОРМ пул объектов строится только по первичному ключу объекта. Это означает, что запросы к базе по первичному ключу, могут обрабатываться из кеша. Такие запросы возникают при получении связи многие-к-одному. Например, если у нас есть объект "заказ", и мы хотим получить объект "продавец", то будет выполнен запрос по первичному ключу продавца (ключ которого, естественно, находиться в объекте "заказ"). Если в кеше есть объект "продавец" с таким первичным ключом, то он будет возвращён.

В LightOrm пул объектов строится не только по первичному ключу, но также, по внешним ключам и по SQL запросу. Это означает, что через кеш могут обрабатываться не только связи многие-к-одному, но и один-ко-многим, а также произвольные SQL запросы. Например, если мы хотим получить все объекты "заказ" определённого объекта "продавец", будет выполнен запрос по внешнему ключу (значение которого, естественно, равно значению первичного ключа объекта "продавец"). Если в кеше есть объекты "заказ" с таким значением внешнего ключа, то они будут возвращены.

Необходимо отметить, что в случае запросов один-ко-многим, прежде чем вернуть объекты из кеша, проверяется актуальность кеша. Т.е. сперва выполняется запрос COUNT(*) к базе, и если количество объектов в кеше совпадает с количеством записей в базе, то считается, что кеш актуальный, в противном случае, объекты будут получены из базы данных.

#memoryusage?Автоматическое управление памятью

Как уже отмечалось, в LightOrm, Propel и Doctrine есть кеш, или пул объектов. По мере выполнения запросов, количество объектов в пуле постоянно возрастает, и, естественно занимает оперативную память. Чтобы избежать перерасхода памяти, нужно освобождать пул от объектов, которые стали ненужными.

В Propel нет способа для освобождения памяти занимаемой определённым объектом. Есть только возможность очистить весь пул объектов, вручную вызвав соответствующий метод.

В Doctrine есть возможность удалять и определённый объект, и весь пул объектов целиком, также вручную, вызывая соответствующие методы.

LightOrm - это первый PHP проект, в котором используется оригинальный механизм подсчёта количества ссылок на объект, благодаря чему, освобождение занимаемой памяти происходит автоматически. Признаком для освобождения служит количество ссылок на объект, если оно равно нулю, объект освобождает память. Например:

// в переменную $seller помещаем объект Seller
// пока переменная существует в скрипте, соответсвующий
// объект находится в кеше
$seller = $sellers->firstItem();

// если мы повторно сделаем запрос
// то получим из кеша тот же самый объект
// $seller и $seller2 идентичны
$seller2 = $sellers->firstItem();

// теперь удалим переменную $seller
// количество ссылок на объект уменьшится
// но он по прежнему остаётся в кеше
unset($seller);

// и только после удаления переменной $seller2
// объект удалиться из кеша, так как на него
// больше не осталось ссылок
unset($seller2);

#objectbufer?Буфер объектов

Что произойдёт, если выполнить код для Propel:

foreach (SellerPeer::doSelect(new Criteria) as $seller) {
    print $seller->getName();
}

В память будет загружено всё содержимое таблицы Sellers. Если записей в таблице немного, это не проблема. В портивном случае, придётся ограничивать количество загружаемых данных, например, настраивая критерий выборки:

$criteria = new Criteria();
$criteria->setLimit(100);
$criteria->setOffset(1000);

Это же относится и к выборкам связанных объектов. Например, если получить заказы продавца:

foreach ($seller->getOrders(new Criteria) as $order) {
    print $order->sum;
}

Что бы ограничить количество загружаемых данных, также нужно настраивать объект Criteria.

В LightOrm, чтобы ограничить количество загружаемых данных, достаточно указать необходимый размер буфера (по умолчанию он установлен и равен 100), и дальше проводить итерацию по всей таблице, не беспокоясь о переполнении памяти:

$sellers->buffer = 300;
foreach ($sellers as $seller) {
    print $seller->name;
}

Буферизация, также, работает и в случае выборки связанных объектов:

$orders->buffer = 200;
foreach ($seller->orders as $order) {
    print $order->sum;
}

И в случае валовой загрузки:

$sellers->buffer = 300;
$orders->buffer = 300;
$customers->buffer = 300;

foreach ($sellers->bulkLoad($orders, $customers) as $seller) {
    foreach ($seller->orders as $order) {
        print $order->customer->name;
    }
}

#bulkload?Валовая загрузка

Так же как и другие ОРМ, LightOrm позволяет загружать связанные объекты. Связанные объекты можно загружать двумя способами. Первый - это одним запросом, используя объединение (join) таблиц. Второй - это загрузка несколькими запросами, по количеству загружаемых таблиц.

В LightOrm реализован только второй способ загрузки. На первый взгляд, может показаться, что один запрос должен работать быстрее. Однако, использование объединения таблиц приводит к дополнительным накладным расходам по обработке результата запроса. На небольших объёмах данных, эти накладные расходы невелики, однако, с увеличением объёма данных, они резко возрастают.

При втором способе загрузки, накладные расходы, в виде дополнительных запросов к базе данных, не зависят от объёма получаемых данных, и по производительности опережают вариант с объединением таблиц в одном запросе, что подтверждается сравнительным тестированием производительности.

Обратите внимание, что и при валовой загрузке продолжает работать буферизация объектов, что позволяет эффективно настраивать расход оперативной памяти.

#lazyload?Ленивая загрузка

Ленивая загрузка, это ещё один способ оптимизации работы ОРМ, и означает загрузку некоторых данных модели по необходимости. Например, чтобы сэкономить память и время при создании объекта "новость", свойство "текст новости" можно объявить как "lazy", и не загружать его в объект. При необходимости получить данные этого свойства, при обращении к нему, оно будет подгружено дополнительным запросом.

В LightOrm реализован самый простой механизм "Lazy Load". Есть возможность указать одно или несколько свойств как "lazy", есть возможность автоматической подгрузки свойства при обращении к нему. Нет возможности подгружать все "lazy" свойства по "первому касанию", нет возможности подгружать "lazy" свойства одним запросом, совместно со всеми данными модели. Возможно, это поведение LightOrm будет изменено в будущем.

#sqlquery?Выборка по SQL запросу

Так же, как и большинство других ОРМ, LightOrm позволяет получать объекты по SQL запросу методом Collection::getItemsByQuery(). Построение SQL запроса производится с помощью построителя запросов SqlQuery из фрэймворка PHP_Application (входит в состав LightOrm). LightOrm позволяет, также, получить подготовленный запрос методом Collection::getBlankQuery(), к которому остаётся только добавить условие выборки и объединение с другими таблицами.

При использовании SQL запросов, ответственность за правильность запросов, соответственно, ложиться на самого программиста.

#uow?Hibernate подобные "Unit of Works"

В LightOrm все операции по изменению данных в БД заключаются в так называемые "Unit of Works" блоки, подобные тем, что используются в Java ORM фрэймворке Hibernate. Блоки "Unit of Works" всегда начинаются с запуска транзакции и заканчиваются завершением транзакции или отменой. Любые попытки модифицировать данные за пределами блока "Unit of Works" приведут к исключительной ситуации с соответствующим сообщением.

Непосредственное выполнение всех SQL запросов происходит в конце блока "Unit of Works". Например:

try {
      // начало блока "Unit of Works" - запускаем транзакцию
     $db->beginTransaction();

      // операции модификации данных БД
     $seller = $sellers->createItem();
      $seller->name = 'Seller 1';

      $customer = $customers->createItem();
      $customer->name = 'Customer 1';

      $order = $orders->createItem();
      $order->sum = 50;

      $order->seller = $seller;
      $order->customer = $customer;

      // конец блока "Unit of Works" - завершение транзакции
     // все операции с базой данных происходят в этот момент
     $db->commit();
  } catch (Exception $e) {
      // отмена транзакции в случае исключительной ситации
     $db->rollback();
      throw $e;
  }

#classtreefree?Свободная иерархия классов модели

В отличие от других ОРМ решения для PHP, LightOrm использует паттерн "Декоратор" для размещения всего функционала по работе с базой данных. Реализация паттерна "Декоратор" в PHP позволяет использовать один и тот же декоратор для оборачивания объекта любого класса. Это означает, что нет необходимости наследовать класс модели от базового класса ОРМ. Вы можете строить свою собственную иерархию классов и затем любой из классов подключить к ОРМ.

Замечание: недостатком такого решения является то, что вы не сможете воспользоваться
конструкцией "instanceof" для проверки класса объекта, так как объект модели обёрнут объектом
декоратора. Вы можете воспользоваться методом __instanceOf($class) объекта декоратора, для
проверки класса объекта модели.