Задать вопрос

Правильная реализация ActiveRecord в PHP на манер Rails?

Добрый день!

Реализую на PHP ActiveRecord, на манер десятка аналогов (Rails, Yii и так далее).

Допустим, есть следующий код, получающий список объектов:

$users = Users::model()->findAll()->all;

Возник вопрос о правильности реализации. Дело в том, что по википедии «каждый экземпляр данного класса соответствует одной записи таблицы;». Вроде как идеологически правильно возвращать массив объектов.

Да и например запись

foreach ($users as $user){<br/>
 print $user-&gt;name;<br/>
}<br/>


тоже выглядит логичной. Rails вроде тоже так делает.

С другой стороны, $users = Users::model()->findAll()->all; может быть не массивом объектов, а просто массивом массивов с данными.

Я понял, что вроде правильным является первый вариант (при запросе списка возвращать массив объектов), но тут и возник вопрос:

Если у нас на странице 800 комментариев (или других сущностей), то действительно где-то хранить 800 экземпляров объектов? И ->all где то в цикле создаёт объекты, привязывает к таблице и заполняет их полученными данными?
  • Вопрос задан
  • 5112 просмотров
Подписаться 5 Оценить Комментировать
Пригласить эксперта
Ответы на вопрос 7
anaximen
@anaximen
Все правильно пишете, просто редко когда необходимо получить сразу все 800 записей. Что в рельсах, что в yii такая команда съест много ресурсов. Обычно применяют пагинатор и запрашивают 10-20 записей со смещением по странице.
Ответ написан
Если у нас на странице 800 комментариев (или других сущностей), то действительно где-то хранить 800 экземпляров объектов? И ->all где то в цикле создаёт объекты, привязывает к таблице и заполняет их полученными данными?

Если использовать наследника от ArrayObject, то необязательно хранить все 800 в памяти. Например,

UserCollection extends ArrayObject {
  private $ids;

  function __construct($where) {
     $ids = db::query("SELECT id FROM Users WHERE $where")->getColumnValues('id');
  }

  public function offset($index) {
    return new User(db::query("SELECT * FROM users WHERE id={$ids[$index]}"));
  }
}


Пример грубый, небезопасный, фактически псевдокод. Но с 800 записями возникет проблема, известная как «1+N» — foreach в шаблоне вызовет 801 запрос к БД. Хранить 800 не хорошо, 801 запрос к БД ещё хуже. Выход — «страничный» кэш. При запросе, например, $users[73] выбираем (если ещё не выбрано) первые сто записей (при [173] вторые сто), инстанцируем 100 объектов в кэше (массив объектов), затирая те, что были раньше (вторая сотня затирает первую) и возвращаем 73-й (в обоих случаях). Особенно эффективен если вся обработка идёт последовательно (foreach и т. п.) — на 800 записей 9 запросов и максимум 100 объектов в памяти (вернее от работы сборщика мусора зависит, можно форсировать его работу при затирании ненужной сотни через gc_collect_cycles()), главное их в другом массиве не сохранять :)
Ответ написан
Можно наворотить делов или классов:
класс таблицы
класс записи
класс поля таблицы
класс запроса select
класс ответа select

Потом это всё дело замешиваем и вуаля, нет никаких противоречий.
Ответ написан
@LastDragon
Рекомендую к прочтению: «Архитектура корпоративных программных приложение» Мартин Фаулер — там очень подробно рассказано о нескольких подходах для получения записей из БД.

Ну и как вариант решения вашей проблемы — при выборе многих записей реализовать обертку для массива (коллекцию, практически идентичную обычному массиву), которая будет по мере необходимости автоматически получать новые строки из БД (реализация примерно на порядок сложнее).
Ответ написан
Комментировать
Tucker56
@Tucker56
Я обычно так делаю. Если мне нужно отобразить на странице много объектов, то я делаю просто селект из бд по нужным критерия и вывожу результат. Если мне нужно изменить объект или сдеалть массовое изменение объетов, то я из бд выбираю спискок идишников, по каждому из них в цикле создаю экземпляр класса и дергаю нужные методы с последющим сохранением.
Ответ написан
Комментировать
GearHead
@GearHead
Fullstack разработчик и предприниматель
> Если у нас на странице 800 комментариев (или других сущностей), то действительно где-то хранить 800 экземпляров объектов?
по поводу ActiveRecord уже не помню, но вот в Mongoid в Rails есть хороший пример, как такое преодолеть. Пока не вызван специальный метод to_a, любой запрос типа User.where(rights: 'admin').limit(200).offset(200) вернёт не массив строк или объектов, а объект типа MongoID::Criteria, у которого определён метод each (аналог foreach) таким способом, что он постепенно берёт от базы по строке и оборачивает в объект. Таким образом, garbage collector довольно быстро успевает хавать ненужные объекты.
Ответ написан
dag
@dag
Rails developer
посмотри как тут сделано laravel.com/docs/eloquent
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы