Laravel Laravel хорошие практики

Вурдалак

Продвинутый новичок
Каждый класс и метод должны выполнять лишь одно действие.
Некорректная формулировка и искажение изначального смысла. Что значит «действие»? Класс должен всегда иметь ровно один метод?
Пример не имеет отношения к SRP.

Бизнес логика в сервис-классах
...
Хорошо:
$this->articleService->handleUploadedImage($request)
Здесь неясно почему твой «бизнес-сервис» принимает на вход HTTP-request. Бизнес-сервису обязательно знать по HTTP или по какому другому протоколу был загружен файл?

Этот принцип призывает вас переиспользовать код везде, где это возможно.
Это искажение смысла принципа и следование такому совету откровенно вредно. DRY — это не про код, а про недвусмысленность и непротиворечивость. Абсолютно нормально иметь дублирующиеся (и возможно даже copy-pasted) куски кода, если они на самом деле не представляют из себя одно целое, а просто случайно совпадают.
http://verraes.net/2014/08/dry-is-about-knowledge/
https://www.entropywins.wtf/blog/2017/09/06/the-fallacy-of-dry/
http://us3.campaign-archive2.com/?u=1090565ccff48ac602d0a84b4&id=5132867f6e
https://twitter.com/nicolopigna/status/921280768697143296

Внедрение классов через синтаксис new Class создает сильное сопряжение между частями приложения и усложняет тестирование.
...
Плохо:
$user = new User;
...
Хорошо:
public function __construct(User $user)
Откровенная чушь, которая уже обсуждалась на форуме, но ты просто молча слился без каких-то аргументов, а теперь навязываешь свои религиозные догмы новичкам.
 

Alexey Mezenin

Новичок
Спасибо.

Некорректная формулировка и искажение изначального смысла. Что значит «действие»? Класс должен всегда иметь ровно один метод?
Согласен. Можешь предложить что-то лучше, чем "каждый класс и метод должны выполнять лишь одну функцию"?

Здесь неясно почему твой «бизнес-сервис» принимает на вход HTTP-request. Бизнес-сервису обязательно знать по HTTP или по какому другому протоколу был загружен файл?
Соглашусь, но я пытался упростить код, чтобы любой мог его понять. Суть примера не в этом и не в public_path('images') . 'temp', что тоже плохо. Но я все-таки исправил код.

Откровенная чушь, которая уже обсуждалась на форуме, но ты просто молча слился без каких-то аргументов, а теперь навязываешь свои религиозные догмы новичкам.
Перешел по ссылке и я не вижу, чтобы мы это обсуждали. И я не вижу "слива". В теме человек задал вопрос, я и AmdY ему помогли. Остальные вместо помощи обосрали Eloquent, отправив лепить велосипеды и использовать сырые запросы.

Я ни разу не слышал о том, что new Class лучше использования контейнера. Противоположное мнение - десятки раз, в том числе от именитых авторов. Поясни, чем new Class лучше контейнера?
 
Последнее редактирование:

fixxxer

К.О.
Партнер клуба
Согласен. Можешь предложить что-то лучше, чем "каждый класс и метод должны выполнять лишь одну функцию"?
Есть классическое определение про only one reason to change. Да, оно непросто для понимания и его надо осмыслить, но без осмысления все равно ничего не получится.

То же самое касается и DRY. Код может в данный момент совпадать, но если существуют разные причины изменения копии А и копии Б, то DRY к этому не имеет никакого отношения. Именно из-за такого псевдо-dry появляются абстрактные классы с типа-общими методами, когда наследование обусловлено не отношениями "is-a", а машинальными действиями по якобы соблюдению принципа DRY.
 

fixxxer

К.О.
Партнер клуба
Не думаю, что вообще возможно каждому джуну донести эту мысль в одном предложении. Надо долго и тщательно разжевывать на примерах.
 

Вурдалак

Продвинутый новичок
Я ни разу не слышал о том, что new Class лучше использования контейнера. Противоположное мнение - десятки раз, в том числе от именитых авторов. Поясни, чем new Class лучше контейнера?
Ньюкласс ньюклассу рознь.

Во-первых, new class для сущностей и value object'ов — это абсолютно нормально. Сущности начинают свой жизненный цикл не в контейнере и начинают жить они не при каждом запросе. Они начинают жить лишь однажды: пользователь регистрируется однажды, тогда и происходит new User(). VO же можно сравнивать с обычными константами. Ты же не будешь число Пи определять в контейнере и инжектить, ты его сразу подставишь, здесь никакой разницы.

Во-вторых, если рассматривать твой случай с ActiveRecord, у тебя User — это не просто сущность, это сервис + сущность. Но проблема в том, что этот сервис и так singleton. Здесь вообще никак не предполагается возможность инжектить разные инстансы User в разные сервисы и сам сервис получает свои зависимости извне, т.е. это очевидный самообман, никаких плюсов от инъекции тут нет. К примеру, User::find() по-нормальному мокать не получится по очевидным причинам. Да, есть хаки с автолоадингом, но они должны работать и для обычного статического вызова User::find(), т.е. $this->userActiveRecord->find() тут не даёт преимуществ.

Ну и в-третьих, если ты хочешь отдельно инжектить User как сервис, тогда нафиг вообще ActiveRecord-то? В смысле, инжекти явный UserRepository/UserMapper. Тут либо надо трусы надеть, либо пиджак снять.
 

AmdY

Пью пиво
Команда форума
Во-вторых, если рассматривать твой случай с ActiveRecord, у тебя User — это не просто сущность, это сервис + сущность. Но проблема в том, что этот сервис и так singleton. Здесь вообще никак не предполагается возможность инжектить разные инстансы User в разные сервисы и сам сервис получает свои зависимости извне, т.е. это очевидный самообман, никаких плюсов от инъекции тут нет. К примеру, User::find() по-нормальному мокать не получится по очевидным причинам. Да, есть хаки с автолоадингом, но они должны работать и для обычного статического вызова User::find(), т.е. $this->userActiveRecord->find() тут не даёт преимуществ.
.
Вот именно, User это в первую очередь сервис, а не сущность, потому DI нужно. При использование вендоров меня как раз спасала возможность DI подсовывать разные инстасы через when-needs-give.
Даже User::find можно нормлаьно мокать если там фасад. Это удобные костыли от фреймворка.
 

Alexey Mezenin

Новичок
User::find() по-нормальному мокать не получится
Мокается на ура. С DI можно и dummy класс инжектить. Чего нельзя делать с new User.

User чаще и выступает в качестве репозитория. Используется как $this->user->getActiveLawyersWithContracts(). С самых крупных Laravel проектах, которые я видел, тот же подход.
 

fixxxer

К.О.
Партнер клуба
С Eloquent проблема в том, что мы на самом деле хотим инжектить не объект, а класс. Сервис там - "статический", на основе php-шного костыля late static binding. То есть, мы хотим на самом деле что-то такое:

$userImplClass = User::class;
...
$user = $userImplClass::find($id);

Когда мы инжектим User, мы на самом деле получаем полный бред:
1) создается instance of user, который нам нафиг не нужен,
2) вызываем _статический_ метод у _инстанса_.

Если мы хотим инжектить сервис, фактический интерфейс которого - статические методы через LSB, тут единственный нормальный способ - избавиться от статики и объявить явный интерфейс:
PHP:
interface UserRepository {...}

class UserRepository implements UserRepository {
    public function find($id): User {
        return User::findOrFail($id);
    }
}
и инжектить интерфейс UserRepository.
 
Последнее редактирование:

Вурдалак

Продвинутый новичок
2) вызываем _статический_ метод у _инстанса_.
А там точно static? Вон выше рассказывают, что он «мокается на ура».

User чаще и выступает в качестве репозитория.
инжекти явный UserRepository
 

fixxxer

К.О.
Партнер клуба
А там точно static? Вон выше рассказывают, что он «мокается на ура».
Не совсем, конечно. Там немного через задницу:

PHP:
    /**
     * Handle dynamic static method calls into the method.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     */
    public static function __callStatic($method, $parameters)
    {
        return (new static)->$method(...$parameters);
    }

    /**
     * Handle dynamic method calls into the model.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        if (in_array($method, ['increment', 'decrement'])) {
            return $this->$method(...$parameters);
        }
        return $this->newQuery()->$method(...$parameters);
    }
https://github.com/laravel/framework/blob/5.5/src/Illuminate/Database/Eloquent/Model.php#L1441

Но публичный задокументированный интерфейс - статик.
 

Adelf

Administrator
Команда форума
С самых крупных Laravel проектах, которые я видел, тот же подход.
Все крупные проекты на ларавель, которые я видел - авгиевы конюшни.

и инжектить интерфейс UserRepository.
Мы так и делаем. Но с элоквентом очень трудно. Говоришь ему брать эту сущность из Write базы. Все ок. Но пытаешься взять relation - эта скотина его берет из Read базы! Плюс оно не персистит релейшены... надо самому руками все. Я всерьез подумываю начать некоторые write модели переводить на Doctrine.
 

Alexey Mezenin

Новичок
Мы так и делаем.
Поэтому и имеете проблемы, половина SO засрана подобными вопросами. Одна из хороших практик, которую я описал в репо - это забыть о великах и использовать стандартные инструменты и практики. Не нравится Eloquent, почему не использовать Symfony? Такой подход (избегание великов) сэкономит мешок денег клиенту.
 
  • Like
Реакции: AmdY

Adelf

Administrator
Команда форума
Все эти стандартные ларковские практики сыпятся как только проект вырастает из CRUD-подобного. Когда домен вырастает выше некоторого порога.

Не нравится Eloquent, почему не использовать Symfony? Такой подход (избегание великов) сэкономит мешок денег клиенту.
Ну ты конечно много знаешь о сэкономленных мешках денег :) переписывать проект на другой фреймворк никто не станет. А изначально выбрать что-то правильное, часто не хватает квалификации. Люди бегут на хайп.
 

Вурдалак

Продвинутый новичок
Поэтому и имеете проблемы, половина SO засрана подобными вопросами. Одна из хороших практик, которую я описал в репо - это забыть о великах и использовать стандартные инструменты и практики. Не нравится Eloquent, почему не использовать Symfony? Такой подход (избегание великов) сэкономит мешок денег клиенту.
А что это за религиозный подход «принять целиком или отказаться полностью»? Тут выбор не между Библией и Кораном, это два набора отвёрток. Какие-то можно взять отсюда, какие-то — оттуда. А уж смотреть в рот разработчикам Laravel и их приближённым и считать их слова истиной в последней инстанции по тому как пользоваться их набором отвёрток — нет, спасибо, я не чувствую в этом потребности.
 

Alexey Mezenin

Новичок
Ну ты конечно много знаешь о сэкономленных мешках денег
Кое что знаю, по крайней мере нанимают конкретно с целью сэкономить пару мешков. Зачем пытаться унизить?

переписывать проект на другой фреймворк никто не станет
Если Eloquent действительно ставит палки в колеса, это недоработка CTO или лида. Инструмент выбрали неправильно. Но я в этом сомневаюсь, не видел ни одного проекта, где бы Eloquent заменили на что-то другое. Видел десятки коммерческих проектов и больше сотни Laravel проектов, если считать то, что присылают кандидаты для оценки.

А что это за религиозный подход «принять целиком или отказаться полностью»? Тут выбор не между Библией и Кораном, это два набора отвёрток. Какие-то можно взять отсюда, какие-то — оттуда. А уж смотреть в рот разработчикам Laravel и их приближённым и считать их слова истиной в последней инстанции по тому как пользоваться их набором отвёрток — нет, спасибо, я не чувствую в этом потребности.
Мы уже спорили по этому поводу на этом форуме и меня зачморили тогда за мои взгляды. Но я все еще придерживаюсь того же мнения.

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

Приведу пример (паста из группы, недавно писал). Летом меня наняли в австралийский проект, где сроки были профуканы на 7 месяцев. 8-20 баксовые синьоры год назад тоже решили, что нужно сделать все по уму, а стандартные архитектура и инструменты Laravel далеко не самые лучшие. Проработал там 14 часов и решил уйти именно из-за кода. Я был там 24ым по счету разработчиком, если судить по гиту. Меня наняли из 400+ человек (при этом было видно 300+ инвайтов) и работу после перепостили. Это говорит о том, что люди найти кого-то сильно лучше, в плане способности работать с их кодом, уже не могут. Наняли за 50 в час, при том, что начали они с 8-20 баксовых ребят. Это говорит о том, что они готовы вылезти вон из своего изначального бюджета, лишь бы завершить проект. В конце общения CEO обранил такую фразу "никого не можем найти". Ребята были в отчаянии. На данный момент их продукт все еще не завершен. Пример очень яркий.
 
Последнее редактирование:

fixxxer

К.О.
Партнер клуба
А я видел далеко не один проект, который превращался в труднообслуживаемую кашу как раз прежде всего из-за CRUD/ActiveRecord-подхода, когда домен оказывался размыт абы где по всему проекту.

Кстати, не вижу никаких проблем с использованием Doctrine в Laravel. Еще есть analogue, который из eloquent-а и сделан.
 
Сверху