Comments 29
Нее думали над таким подходом в качестве стандартного?
PS в случае с книгами префикс наверное все же book_?
Кажется, вы начали писать свою ORM. Следующий шаг создание объектов из этого массива?
Кажется, вы начали писать свою ORM
Пожалуй нет.
Следующий шаг создание объектов из этого массива?
Здесь скорее по ситуации. В примере (при замере скорости) я грузил данные из массива в объекты, но лишь для чистоты эксперимента, поскольку от Eloquent, с которым производилось сравнение, мы получаем также коллекцию объектов а не массив.
вашу задачу конвертации результата в дерево я бы решил стандартными способами без сторонних библиотек:
if ($qid = $mysqli->query($sql))
while (list($bookid, $bookname, $authorid, $authorname) = $qid->fetch_row()) {
$books[$bookid]['id'] = $bookid;
$books[$bookid]['name'] = $bookname;
$books[$bookid]['authors'][$authorid] = array('id'=>$authorid, 'name'=>$authorname);
}
чем вам такой метод плох? плюс сохраняется гибкость — т.е. можете как угодно явно указывать вложенность и ключи объектов, для дальнейшего использования
загружай список книжек один раз в кеш в памяти и переиспользуй
В этом случае встает вопрос инвалидации кэша.
вашу задачу конвертации результата в дерево я бы решил стандартными способами без сторонних библиотек:
Пример с книжками — лишь пример.
Полей (столбцов) в выдаче может быть сколь угодно, вложенность (структура) данных может какой угодно, эти параметры могут варьироваться создавая множество вариантов реализации приведенного вами метода.
Также не исключается случай изменения порядка столбцов в выдаче.
В проекте помимо книжек и авторов, например, может быть еще сотни сущностей и для каждой придется писать свой индивидуальный метод обработки массива.
все равно, ваше решение требует использование конфигурации и знание как и чего конфигурировать.
Я предпочитаю всегда использовать стандартные средства языка, благо язык РНР очень богатый.
Если вы заметили, то ваша конфигурация очень похожа на мой код, единственно я использую богатство языка РНР и экспрессивность родных массивов, тогда как вы создаете конфиг, которые куда то передаете дальше и получаете объекты.
Я думаю ваш метод хорошо пойдет тогда, когда у вас полная объекто-ориентированная модель и объекты с методами, интерфейсами и т.д, тогда как у меня просто тупые массивы с данными.
два комментария к вашему решению:
- в развитых ОРМ уже есть ленивая загрузка коллекций — чем ваше решение лучше?
- Если передавать модель в шаблонизатор — зачем ему эта вложеннасть объектной модели? вывод то все равно плоский и там будут те же foreach
2. Во первых можно всю эту структуру кинуть на фронт, да и работать даже на беке с объектной моделью проще.
Не затруднит вас добавить в бенчмарк Doctrine, провести сравнение результатов для данных проецированных в объекты?
Если верно понял, то вы решаете проблему генерации запроса
Нет
получения данных в «сыром» виде
Не совсем понятно что значит в «сыром»
без собственно маппинга в объекты, который и осуществляет ORM
Да, получаете данные в более удобном виде, чем плоский массив, а что делать с ними дальше решаете по ситуации
Не затруднит вас добавить в бенчмарк Doctrine, провести сравнение результатов для данных проецированных в объекты?
Добавлю, но чуть позже (или не чуть). Возможно вот эта страница даст представление об относительном быстродействии различных ОРМ.
Помимо того что в Eloquent немного толще превращение в объект, чеснее было бы сравнивать с Model::hydrate, например.
Второе на что стоит обратить внимание, вы ускорили конкретно ваш юзкейс, у вас один автор — одна книга, как только у вас будет у одной книги больше одного автора (простой пример комментарии и пользователь), то вы получите больше дублей и большей расход памяти, тоже самое если у вас будет куча книг (Стивен Кинг?) и один автор, куча дубляжа. Все в тот же огород камень, к сожалению у сущностней поля далеко не 3, это такой лабораторный пример, и в этом случае все будет еще печальнее с джоинами.
Третье это то, что например таблица с авторами и таблица с книгами могут вообще находится в разных БД или даже серверах.
Четвертое это не удобно, а laravel это больше про удобство чем про погоню за ms (которые можно выиграть другими способами)
Пожалуй последние три причины это то, почему ни один пул-реквест с этой заморочкой так и не был замерджен. Каюсь, когда я только начинал писать на laravel у меня на эту часть доже глаз косился, но ничего переварил. Простые и быстрые запросы наше все, все таки даже дебажить легче.
Помимо того что в Eloquent немного толще превращение в объект, чеснее было бы сравнивать с Model::hydrate, например.
Не понял что именно сравнить с Model::hydrate
вы ускорили конкретно ваш юзкейс
Не совсем так. Да — скорость обработки зависит от структуры данных, и, возможно, существуют ситуации где использование другой ОРМ предпочтительней. Я тестировал скорость работы библиотеки на различных данных, но расписывать их все в одной статье слишком долго (даже короткие статьи не все читают внимательно).
у вас один автор — одна книга
Нет, в примере связь many-to-many
Все в тот же огород камень, к сожалению у сущностней поля далеко не 3
Количество полей и глубина вложенности никак не ограничена
все будет еще печальнее с джоинами.
В статье пример с джойнами
Третье это то, что например таблица с авторами и таблица с книгами могут вообще находится в разных БД или даже серверах.
Я не претендую на создание на 100% универсального решения, эдакой «серебряной пули»
Четвертое это не удобно, а laravel это больше про удобство чем про погоню за ms (которые можно выиграть другими способами)
Удобство — вещь относительная. Laravel — это скорее больше про компромисс между удобством и скоростью.
Кстати, а почему именно Laravel? Да, в примере сравнение производилось с Eloquent, но ведь нигде не сказано, что использование библиотеки ограничивается лишь использованием в проектах на Laravel.
Не понял что именно сравнить с Model::hydrate
Ваша сущность намного проще чем модель Eloqeunt, полагаю ее инициализация так же требует времени, если вы предлагаете пользоваться вашим сущностями, то это отдельная ОРМ, причем крайне простая.
Количество полей и глубина вложенности никак не ограничена
Я не говорил о ее ограничености, в вашем примере структура очень простая, как только структура таблицы будет 10+ полей, то ваши джоины на больших выборках будут терять свое приемущество, на малых же ваше решение просто не удобно.
Eloquent — реализация патерна ActiveRecord, классы сущностей в примере — крайне упрощенная реализация патерна DataMapper, немного разные вещи.
Ускорить инициализацию моделей Eloquent у меня вряд ли получиться (без применения костылей), но если вдруг так вышло что в вашем проекте ну никак не обойтись без Eloquent, то придется либо смириться с его скоростью либо костылить.
На скорость работы библиотеки влияет не количество полей в таблице, а сколько полей указано в секции SELECT запроса. И если вдруг так получилось, что в секции SELECT запроса оказалось 100+ полей, возможно скорость просядет, но не факт (еще раз отмечу — я не претендую на создание на 100% универсального решения). В любом случае стенд для испытаний выложен на гитхаб, каждый может развернуть его у себя и потестить.
При джойнах скорость просядет при большой глубине вложенности данных и при этом высокой уникальности вложенных данных. В этом случае стоит попробовать достать данные несколькими WHERE IN запросами (как это делает тот же Eloquent), но не уверен, что поможет.
В Cycle ORM выборки сделаны похожим образом. Только учитывается отсутствие уникальности названий колонок, дедупликация родительских записей, а также возможность вытягивать часть данных внешним запросом используя ссылочное дерево.
Только мне одному кажется, что в этом случае, раз такая пьянка, лучше использовать dot notation и дальше делать из этого массивы без ограничения вложенности? Благо готовых решений — пруд пруди.
Без проблем: берем, к примеру, adbario/php-dot-notation.
Дальше, как было выше показано, делаем манипуляции:
SELECT
books.id AS id,
books.name AS name,
authors.id AS author.id,
authors.name AS author.name
FROM
books
LEFT JOIN relations ON relations.books_id = books.id
LEFT JOIN authors ON authors.id = relations.authors_id
Ну а дальше перебор (просто как пример, считать псевдоязыком):
$rows = [];
foreach ($databaseRows as $databaseRow) {
$dot = dot([]);
$dot->set($databaseRow);
$rows[$dot->get('id')] = $dot->get();
}
Пример не является точной инструкцией к решению. Я просто попытался показать, что можно обойтись без дополнительного DSL, коим является указание префиксов и/или других путей для парсинга значений массивов.
Я бы с радостью, но для начала нужно исправить Ваш бенчмарк.
Во-первых, в настройках конфигурации (строки 79 и 116), Вы указываете свойство author, а в load()
Вы ожидаете authors. В итоге, Вы тестируете PHP Warning: Invalid argument supplied for foreach()
Во-вторых, в строке 123 Вы указываете, что ожидаете 0-й элемент, но его там никогда не бывает. В итоге, Вы тестируете PHP Notice: Undefined offset: 0 in
Новый результат
+-----------------+------+------+-------------+--------------+
| subject | revs | iter | mem_peak | time_rev |
+-----------------+------+------+-------------+--------------+
| benchEloquent | 1 | 0 | 76,442,992b | 11,806.789ms |
| benchEloquentId | 10 | 0 | 5,122,232b | 20.202ms |
| benchProc | 1 | 0 | 36,368,352b | 1,027.019ms |
| benchProcId | 10 | 0 | 4,462,776b | 9.532ms |
+-----------------+------+------+-------------+--------------+
Заберите пример бенчмарка тут
Эта реализация dot notation не поддерживает группирования, потому проверяется возможность собрать массив результатов, в котором для одной книги ровно один автор. В общем и целом, неполная имплементация Вашей функциональности, но для оценки производительности вполне хватает.
Эта реализация dot notation не поддерживает группирования, потому проверяется возможность собрать массив результатов, в котором для одной книги ровно один автор
В любом случае спасибо за потраченное время. Вот результаты:
+-----------------+-----+------+-------------+--------------+
| subject | set | revs | mem_peak | time_rev |
+-----------------+-----+------+-------------+--------------+
| benchEloquent | 0 | 1 | 76,459,120b | 12,916.083ms |
| benchEloquentId | 0 | 10 | 5,138,912b | 18.136ms |
| benchProc | 0 | 1 | 36,384,480b | 1,069.124ms |
| benchProcId | 0 | 10 | 4,478,904b | 8.863ms |
| benchDot | 0 | 1 | 53,885,656b | 2,476.547ms |
| benchDotId | 0 | 10 | 4,478,904b | 9.217ms |
+-----------------+-----+------+-------------+--------------+
Маппинг данных из реляционной БД