Pull to refresh
Comments 39
Отличная причина создать свой фреймворк, например Maravel )
Давно известно что программист с Тейлора посредственный и он любит велосипеды посредственного качества :D

Чего стоит только противоракетный маневр с местоположением моделей...

Следите за руками (официальная документация):
4.2 — Models typically live in the app/models directory
5.0 — Models typically live in the app directory
8.0 — Models typically live in the app\Models directory.
Загадка от Жака Фреско.

Нахрена?

На размышление даётся 30 секунд.
В 4.2 все модели начали создаваться в app/models, и все их туда пачками ложили, но это не совсем правильно, поэтому в 5.0 сказали — ложите их куда хотите, а если вы сами себе «злобные буратины» вот вам дефолт «app», чтобы вы мучились. Ну и наконец начали как и контролеры, сервисы и всё остальное держать сгруппированными. Но глупого народа много, народ так и продолжал держать всё в корне «app», потому в 8.0 и сделали отдельную папку для таких вот буратин, но никто не запрещает и более того даже нужно, ложить модели туда, где они должны по логике проекта лежать.
Повеяло симфонией с её Controller и AbstractController (как то так) при переходе с 4 на 5.

Для меня этот фрейм умер на 4.2 а для всей команды на 5.* и за постоянных внутренних переделок фреймворка. Симфония будет лучше с цайклс вполне съедобно, если не нравится доктрина как мне.


Сам сижу на yii2 и на nodejs, это позволяет быстрей пробовать прототипы для бизнеса.


Текущий подход yii3 очень нравится, ребята двигаются в верном направлении:
1 симфони консольные команды легкие и элегантные.
2 легкие веб контролеры с di, мы используем то что нам нужно.


Насчет статьи, код отформатирован через phpcs под кодстайл фреймворка, считаю что стандартное форматирования phpcs2 лучшие, так как соответствует пхпшторму.


Спасибо за статью и опыт.

Пару лет работал с Symfony, мне кажется производительность труда (программиста) все-таки выше в Laravel, некоторые вещи слишком усложнены искусственно в Symfony. Одно сохранение модели чего стоит
Сохранение модели к симфони не имеет отношения. Не нравится доктрина — используйте другую ORM.
Вот из официальной доки:

// you can fetch the EntityManager via $this->getDoctrine()
// or you can add an argument to the action: createProduct(EntityManagerInterface $entityManager)
$entityManager = $this->getDoctrine()->getManager();

$product = new Product();
$product->setName('Keyboard');
$product->setPrice(1999);
$product->setDescription('Ergonomic and stylish!');

// tell Doctrine you want to (eventually) save the Product (no queries yet)
$entityManager->persist($product);

// actually executes the queries (i.e. the INSERT query)
$entityManager->flush();
1. Доктрина не является частью фреймворка
2. Дока очень далека от best practice, её основная цель показать что фреймворк может. Да, так тоже можно, но это не значит что так нужно делать
3. Выбирая между Aсtive Record и Data Mapper я выберу второе в большинстве случаев, хотя бы с точки зрения оптимизации работы с БД

А что не так с этим способом? Мы на C# так же делаем...

Вопрос принципиальный
Если придерживаться мнения, что разработка — должна происходить быстро и доставлять удовольствие программисту, то тут — перебор. Программист целых 3 строки будет миллион раз повторять в коде, чтобы сохранить запись. Не говоря уже о читаемости — «persist» переводится как «сохранить», но оно не сохраняет. «flush» переводится как «сбросить», «очистить», но оно сохраняет в базу.
Но это, конечно, holy war. Кому-то нравится программировать, кому-то нравится смотреть на абстракции в коде.

Ну, наименования тут взяты прямиком из Java Persistence Api (JPA), лично мне они тоже кажутся несколько неудачными — я-то привык к Add и SaveChanges :-)


Что же до числа строчек — то тут всё просто. От первой строчки вы не избавитесь никак, соединение с БД по-любому нужно или получить, или создать. А разделение persist/flush нужно чтобы была возможность сохранять в БД несколько сущностей за раз.

От первой строчки вы не избавитесь никак, соединение с БД по-любому нужно или получить, или создать.

В Ларавеле не так, блин… $product = new Product(); $product->save(); само достанет из кишок фреймворка дефолтное соединение или откроет его, если ещё не открыто...

Обычно для флаша делают мидллварю, персист делают где-то в репо в методе new, который создает инстанс модельки и добавляем в uow

В итоге ваша логике вообще не завязана на работу с entity manager
Этим мне и нравится доктрина: eventually save, а не сразу, в отличие от многих AR.
дело вкуса :) мне просто не хочется каждый раз повторять одни и те же строки. я пишу код, чтобы делать жизнь юзеров лучше, а не чтобы наслаждаться идеальностью абстракций в своем коде

Судя по этому комментарию — вы не работали с доктриной, иначе бы не писали подобного. Ёлка удобнее доктрины только для чтения и только для относительно простых вещей. А работа с ней на запись — это сущий ад, особенно, когда дело касается нормальной транзакционности и отношений.


Вы же понимаете, что в доктрине в идеальном случае достаточно вызывать один раз persist($object) для просчёта отношений + flush() для сохранения состояния и всё сохранится в БД:
1) В одной транзакции (если включено)
2) Исключая дубликаты
3) В нужном порядке
4) Только те данные, что реально изменились
5) Включая все отношения этого объекта, которые были изменены (даже какие-нибудь many2many).


Например
$user = new User();
// В eloquent пришлось бы тут делать save, что б появился PK у модели.

$user->comments = $user->comments->filter(fn ($c) => $c->id === 42);
// eloquent: $user->comments()->detach($user->comments()->first(42));

$user->comments[] = new Comment();
// eloquent: $user->comments()->attach(new Comment());

$user->friends[] = new User();
// eloquent: $user->friends()->associate(new User());

/// ... etc

$em->persist($user);
$em->flush();
// eloquent: $user->save()

В eloquent подобные операции — смерти подобны. А потом это ещё заворачивать в транзакцию…

Запускаем какое-то атомарное действие, которое задействует несколько сервисов. В вашем случае при ошибке в середине или в конце могут остаться хвосты в базе и данные могут стать несогласованными, кроме того, разные сервисы могут работать с разными объектами, которые будут представлять одну и ту же строку в БД. Широкие возможности для выстрела в ногу. И вряд ли это улучшит жизнь обслуживающих код и всех клиентов. В случае с UoW+IdentityMap сохранение будет только там, где вы явно коммитите изменения в БД, когда все этапы работы с представлениями объектов из строк БД уже пройдены.

я пишу код, чтобы делать жизнь юзеров лучше, а не чтобы наслаждаться идеальностью абстракций в своем коде
А паттерны (включая тот же UoW, IM) от сырости или оттого, что коту нечего делать, завелись, видимо?

ORM это всегда оверхэд по производительности, по коду и по логике. То что на SQL делается за 1 логическую операцию, на ORM обрастает абстракцией, которую очень тяжело контролировать. Сегодня борол Laravel: нормализовали клиентов, появился detail к клиентам с аккаунтами. В одном из режимов надо показывать всех клиентов, кроме тех, которые есть только в одном определенном аккаунте. Клиентов пара миллионов. Laravel для педжинейшена делает 2 запроса к БД: один на COUNT(*), один уже на данные с ORDER BY и LIMIT. Оптимизируя один из запросов, второй начинает недопустимо проседать. В ход шли самые современные методы MySQL: WITH, хинты. В общем, на ровном месте, борьба с ветряными абстракциями на несколько часов, которая разрешилась только практически pure sql

Если вас сильно напрягают отдельные persist и flush, то можно создать сервис, объединяющий эти действия.

$product = new Product();
$entityPersister->save($product);
мне кажется производительность труда (программиста) все-таки выше в Laravel

Она выше только на начальных этапах жизни проекта, в первые несколько человекомесяцев. При этом она пологая и очень медленно растет. В отличие от сложности Laravel проекта, которая растет очень существенно.

Ну я знаю как минимум 3 случая ещё, когда feature PR отклонялся, а потом дядя Ти выкатывал какой-то лютый **** со словами "смотрите, какую я фичу запилил". Так что ничего удивительного. Вы не первый — вы не последний.

То, что идею себе присвоил — не так обидно, как то, что он такой плохой код добавил :)
Вообще Laravel довольно неоднозначный фреймворк. На первый взгляд он очень функционален и в нем реализовано много полезных штук, но как только приходится решать нетривиальную задачу с помощью этих самых штук — начинаешь вспоминать разработчиков матерным словом и переделывать их работу. То же самое касается производительности — очень многое реализовано крайне неэффективно и начинает дико тормозить с ростом посещаемости.
Поделитесь подробностями, не хотелось бы нарваться на дикие тормоза в самый ответственный момент.
Пожалуй самый главный тормоз это модели, а точнее доступ к атрибутам. Они реализованы в корне неверно, и вот я посмотрел в 8 версии ларавела — все то же самое. А проблема вот в чем: модель внутри хранит сырые данные из базы, и при каждом обращении к атрибуту вызываются касты и мутаторы, и это никак даже не кэшируется. Соотвественно запись в атрибут сразу конвертирует значение в понятное базе. А должно быть наоборот — конвертация один раз при чтении или записи в базу.
В нашем проекте есть json поля в базе, и много бизнес логики для них, соответственно обращение к таким атрибутам очень частое, и вот при каждом обращении под капотом вызывается json_decode/json_encode. Я был разочарован когда раскопал откуда тормоза пошли.

Второй пример это функция Arr::get(), а точнее ее использование. Она позволяет получить значение из вложенного массива передавая путь вида «path.to.value», и соответственно реализация содержит explode() и цикл по сегментам пути. Так вот очень важная функция config() под капотом вызывает ту самую Arr::get() при каждом обращении, никак не кэшируя результат. И если у вас где-то в большом цикле вызов config() — прощай производительность. Эта Arr::get() используется и некоторых в других местах аналогичным способом.

Это два ярких примера которые пришли в голову сразу, на самом деле там еще найдется. Вполне возможно что в вашем проекте это не актуально, но эти примеры отлично иллюстрируют подход разработчиков к вопросам производительности.
Интересно. А замеряли эффект на производительности?

Когда я обнаружил проблему с моделями (профайлером), исправление ускорило код примерно на 30%. Пришлось сделать самодельный кэш атрибутов и использовать в нагруженных местах.

Понятно. Тем, кто как я, не пользуется ихним ORM, а по старинке делает sql руками это не грозит. И да, чем сложнее задача, тем их модели справляются хуже. Ну это и понятно, абсолютно универсальных решений нет.
Тейлор классно умеет продвигать. Любыми способами)
и у него получается. Пипл хавает.
Пиплов так много набралось, что совместными усилиями все таки смогли сделать приличный феймворк, хоть и с достаточным количеством оверинженеринга
Сам буквально пару недель назад попал в такую же ситуацию, расшарил в ишьюсах свое виденье АПИ для DB::afterCommit хуков, получил в ответ: «мы не будем пока это реализовывать». Через 2 дня такая же реализация с небольшм переименованием была влита в мастер
Тайлер быстро говнокодит фичу, потом приходит community и спустя пару версий делает код более качественным. Нормальный процесс, зато на McLaren накопил выступлениями.

В целом было бы чхать на это, если бы он на некоторые больные места не накладывал свое вето, как например composite primary keys.
Only those users with full accounts are able to leave comments. Log in, please.