Search
Write a publication
Pull to refresh

Comments 29

Любопытно, но раз мы все равно пишем алиасы, можно сразу и писать как 'author.name' etc
Нее думали над таким подходом в качестве стандартного?

PS в случае с книгами префикс наверное все же book_?
… можно сразу и писать как 'author.name' etc

Да, пожалуй можно было и 'author.name'.
PS в случае с книгами префикс наверное все же book_?

опечатка, да в $config 'prefix' => '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);
}

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

В этом случае встает вопрос инвалидации кэша.
вашу задачу конвертации результата в дерево я бы решил стандартными способами без сторонних библиотек:

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

все равно, ваше решение требует использование конфигурации и знание как и чего конфигурировать.
Я предпочитаю всегда использовать стандартные средства языка, благо язык РНР очень богатый.


Если вы заметили, то ваша конфигурация очень похожа на мой код, единственно я использую богатство языка РНР и экспрессивность родных массивов, тогда как вы создаете конфиг, которые куда то передаете дальше и получаете объекты.
Я думаю ваш метод хорошо пойдет тогда, когда у вас полная объекто-ориентированная модель и объекты с методами, интерфейсами и т.д, тогда как у меня просто тупые массивы с данными.
два комментария к вашему решению:


  1. в развитых ОРМ уже есть ленивая загрузка коллекций — чем ваше решение лучше?
  2. Если передавать модель в шаблонизатор — зачем ему эта вложеннасть объектной модели? вывод то все равно плоский и там будут те же foreach
1. Например если наши запросы — вызов процедур, то ORM тут абсолютно неюзабельна. А работать сданными как то надо. Да е мое, мэппинг более-менее сложного запроса в той же доктрине это такое веселье, что не передать.

2. Во первых можно всю эту структуру кинуть на фронт, да и работать даже на беке с объектной моделью проще.
Если верно понял, то вы решаете проблему генерации запроса и получения данных в «сыром» виде, без собственно маппинга в объекты, который и осуществляет ORM.

Не затруднит вас добавить в бенчмарк 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 не поддерживает группирования, потому проверяется возможность собрать массив результатов, в котором для одной книги ровно один автор. В общем и целом, неполная имплементация Вашей функциональности, но для оценки производительности вполне хватает.
Ну вот в этом случае те же яйца только в профиль, только автор предлагает конфигурация мэппинга вместо перебора.

Оно и понятно.
Я за любые начинания, лишь бы меньше было отдельных DSL на каждый чих.
Dot notation вроде как уже давно известная штука. Laravel разработчики точно с ней знакомы и пользуются постоянно. Это просто как пример.
В js вообще весь lodash для объектов на 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      |
+-----------------+-----+------+-------------+--------------+
Лично мне — нраввицо, попробую, хотя писать конфиги мэппинга мне досмерти лень, тут явно требуется стандартный шаблон, через точку.
Sign up to leave a comment.

Articles