Ответы пользователя по тегу Laravel
  • При установке проекта laravel, захожу в package.json и там нет изначально laravel-mix, почему?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Они наигрались с миксом, теперь у них Витя. Такой же кривой, но еще и Витя. Vite это оно.
    Ответ написан
  • Как получить данные с помощью Socialite Providers laravel?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Опять новое название из лары. Опять продвижение?

    Ну и вот ты вводишь в гугл. И на первой странице напЫсано
    https://laravel.com/docs/9.x/socialite#routing

    И там есть ->user() такой же как ->redirect()
    Ответ написан
  • Как записать значения через модель в разные таблицы с одним ID?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Вы либо прочитали либо нащупали Single Table Inheritance.

    Его смысл в том, что центральная модель "агрегат" имеет id - AUTOINCREMENT, а зависимые от неё имеют такой же тип, но НЕ_АВТОИНКРЕМЕНТ. При этом центральная модель часто вообще не содержит информации, а является только регистром айдишек для привязки к ней любого числа таблиц завтра.

    Автоинкремент предполагает отсутствие одинаковых id. Если его не поставить - вы сможете это делать, однако вы будете применять "один ко многим", хотя вашей логикой (которую я прочел из названий классов) это не предусмотрено.

    В вашем случае нужно поставить на Category.id
    UNSIGNED INT
    AUTOINCREMENT

    а на CategoryDesc.id
    UNSIGNED INT
    FOREIGN KEY TO Category.id
    UNIQUE

    Потому что описание у одной категории логически одно.

    (формально это можно написать как CategoryDesc.category_id если бы у неё был свой айди, но её отдельно никто никогда не запросит, то есть она "не агрегат", будут запрашивать категорию и всё по ней, а не описание "от чего-то")

    Вы все равно не сможете в этом случае создать две записи с одинаковым айди во второй таблице, но айди вы сможете указать какой хотите, кроме того foreign key проверит - есть ли он в исходной таблице и упадет если нету.

    Сейчас вы получаете ошибку потому, что AUTOINCREMENT не предполагает что вы сами ставите значение id, оно просто берется "следующее".

    Дальше едем. Вы подняли проблему последовательности записи. Её можно делать двумя путями.

    1) После выполнения запроса проверить поле полученной модели. Ларавель записывает айдишку на свое место после того как запрос выполнился.

    $category = Category::create();
    
    $categoryDesc = new CategoryDesc();
    $categoryDesc->category_id = $category->id;
    $categoryDesc->save();


    2) Вы можете сами назначить айди для целой пачки записей ещё до того, как пойдет запрос в базу. Читайте это как "дать своё айди прямо в коде". Его конечно не запишешь потом в INT AUTOINCREMENT, но можно создать поле `uid` и туда его вручную записывать и не использовать автоинкремент совсем. Что бы это дало? Например batch insert (вставку пачками), который запишет сразу целую пачку, если это возможно. Не то чтобы он невозможен и с auto_increment, но вот разделение логики и работы с БД будет невозможно, следующий шаг отталкивается от прошлого, который еще не сделан и не может быть сделан. Без неё - запишет первую половину, потом ляжет на ошибке, а потом надо удалять, и заниматься оборачиванием половины кода в транзакции, хотя они предназначены не для этого. Точнее так, для этого тоже, но они медленны, и сделав их везде - производительность получится нулевая, а ларавель не предоставляя нам удобного пулинга заставляет нас с этим сражаться. Эта проблема появится не сразу, но позже вы поймете, вы нащупали её. То есть транзакция по хорошему одна на вызов скрипта, даже не так - на вызов действия бизнес логики, бывают действия которые многое делают и там будет несколько, но сначала логика, потом запись в бд, потом дальше что-то делаем. А ларавель либо заставит обернуть всю программу в транзакцию, увеличив её время и вызвав блокировки в будущем, либо заставит вас удалять всё руками.

    Кое что о пуллинге (пул - бассейн). Пуллинг - это слежение за несколькими соединениями поочередно переключаясь между ними. Реализация для него чем-то похожа на страшное слово "асинхронка". По сути представляет собой что вы не выполняете команду сразу, а записываете их в массив какой-то, а потом вызываете другую команду flush() и они сохраняя порядок как вызывались в одной транзакции одна за другой выполняются. При этом исходные команды могли быть сделаны в разных соединениях с бд и будут выполнены отдельно в каждом. То есть сначала весь ваш код отработал, сформировал массивы, а потом когда все массивы готовы - выполняются подряд все запросы в БД которые нужно в том порядке как вы их вызывали и откатываются все сразу, если хоть один упал. К сожалению если запустить параллельную запись в разные соединения может случится прикол, когда данные в одно из соединений придут раньше чем в другое (и какой-то системный архитектор ясен хер воткнул в базу триггер, потому что это готовое решение), и какой-то код отработает тогда, когда первый кусок ещё не готов (все ещё идет запись). Поэтому мешать запись в БД с другим кодом нинадо. И триггерами соединять несоединяемое тоже. Нужно дождаться окончания записи (особенно если она параллельная), потом вызвать коммит на всех соединениях, и только потом продолжать дальше.

    Команда flush() встречается в Doctrine, только она там делает лютейшую тонну магии под капотом, чтобы не просто выполнить написанные вами запросы, а еще считает сделанные изменения в оперативке и потом записывает "как можно меньше запросов". Если честно мне не нравится такая магия, учитывая их жесткую привязку к их собственному языку запросов, автоматическим ненастраиваемым связям, графу зависимостей, и валидациям прямо в коде объектов-данных, написанных в комментах. Мне нравится видеть что я вызвал запрос - запрос выполнился, не вызвал - не выполнился, а не я вызвал 100 запросов, выполнилось почему-то два, где-то по дороге половина данных не долетела, но всё работает. Поэтому я предпочитаю написать для лары простенький пулинг, чем долбаться с неизвестными мне ошибками доктрины, которые часто возникают чертигде в этой магии, и только через пару часов удается найти место, где я запятую не там поставил, а ложилось где хотело вообще.

    3) Либо так, как написал выше Дмитрий, используя связи, которые в ларавеле очень читабельны и понятны, но вопрос последовательности и пуллинга все равно открытый.
    Ответ написан
  • Как использовать Vite вместе с sass внутри docker?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Для того чтобы адекватно работать с webpack, стоит попробовать установить symfony/encore, и написать класс из 3-4 методов, чтобы по имени файла находил его же в папке /build/ с помощью генерируемого при сборке manifest.json.

    После этого вы привычным образом пишете "npm install {пакет}" и подключаете его, сборка encore соберет из него папку /build, у которой будут файлы так называемые entrypoints, по факту файлы page-home.js, page-product.js - их вы сами так назовете. В каждом из этих файлов будут работать ваши import/require и даже динамический импорт. Дальше в файлах шаблонов в блоке вы вызовите что-то вроде $encore->styles('page-index') и $encore->scripts('page-index') - их правда придется на пхп написать, но там строчек 20 кода.

    И всё, на любой странице яваскрипт любой сложности просто берет и работает, как раньше, когда вы писали <script src>

    Прикольно играться с Витей в конструктор, или плеваться от Микса, который "пытался но не смог", а можно взять энкор и забыть про этот ад.

    Второй вопрос - что делать с докером.

    Да, нужно Dockerfile написать где подменить CMD на ваш скрипт .sh который выполняет ваши команды, которые вы на локале пишете.

    К сожалению, если вы хотите запускать это всё вместе с веб-сервером, то вам придется писать свой foreground скрипт, который не позволит контейнеру после запуска "отработать и закончится", а повиснет и будет ждать.

    Были б вы в телеге, я б показал как.
    Ответ написан
    1 комментарий
  • Как поменять домен временному URL?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    В ларавель конфиг файлы загружаются до старта приложения и только внутри них можно безопасно брать данные из env() (в теории можно везде конечно, но я как-то ловил с этим проблему либо они пропадают, либо глюк какой-то был, года два назад долбался)

    то есть сначала ты в папке /config/ создаешь любой файл с расширением myfilename.php который возвращает массив, там хаваешь данные:

    # ./.env
    APP_API_URL=http://localhost


    <?php
    // ./config/myfilename.php
    return [ 'api_url' => env('APP_API_URL') ];


    <?php
    // ./src/Commands/MyCommand.php
    
    namespace App\Commands;
    
    class MyCommand {
      public function handle() {
        $apiUrl = config('myfilename.api_url');
      }
    }


    Иногда руководитель не хочет привязываться к ларавель и требует написать код работающий для любого фреймворка, в этом случае на вход исполняющего класса нужно передать array $configFilename, который потом настроить в AppServiceProvider.php (который по сути просто настройщик контейнера-инжектора):

    <?php
    // ./src/Providers/AppServiceProvider
    
    namespace App\Providers;
    
    Class AppServiceProvider {
      public function register() {
        $app->when(\App\Commands\MyCommand::class)
          ->needs('$configFilename')
          ->give(function () { return config('filename'); });
      }
    }


    <?php
    // ./src/Commands/MyCommand.php
    
    namespace App\Commands;
    
    class MyCommand {
      protected $configFilename;
    
      public function __construct(array $configFilename) {
        $this->configFilename = $configFilename;
      }
    
      public function handle() {
        $apiUrl = $this->configFilename['api_url'];
      }
    }


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

    Это что касается как прокинуть свою переменную окружения в любое место.

    Есть еще вопрос как работает фасад Url и его метод ::temporarySignedUrl(). У каждого фасада есть ->getFacadeAccessor() который возвращает класс, занимающийся работой. Фасад это всего лишь "вызывалка" действий, которая вопреки всем законам ООП решили сделать статической.

    Тебе нужно достать оттуда класс в каком-то методе ->handle() (создай себе TestCommand::class), или написать метод ->test() в каком-то из контроллеров (MainController.php или TestController.php), достать акцессор, посмотреть что за класс с помощью dd() или var_dump(get_class()). Потом найди этот класс в папке ./vendor/illuminate и посмотри внутри какую конкретно переменную оно использует. Может быть не APP_API_URL, а просто APP_URL ?

    Если просто APP_URL, то надо подумать можно ли тебе её менять. Ведь скорее всего на эту переменную подвязан какой-нибудь Sitemap Generator, и значит нужно наследовать и подменять фасад для Uri, или писать свой класс декоратор, куда на вход будет исполнитель фасада прилетать, а все что он умеет - единственный метод, который делает то же действие по-другому.

    Ларавель и архитектура это не друзья. Зато ларавель и короткий синтаксис - это работает.

    Способ ларавеля это же сделать чуть другой (я думаю):
    в файле routes.php ты своему роуту "verification.verify" в документации роутера на их сайте ищешь как установить домен. При любой попытке его создать он либо как-то будет использовать твой новый указанный домен, либо "попросит" (упадет в ошибку и не будет работать) его передать вместе с "id" и "hash".
    Ответ написан
  • Как правильно спроектировать функционал для работы с платежной системой под laravel?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Вот здесь про ивент-сорсинг можно поговорить. Где каждое действие программы не трогает юзера там, его баланс или заказы, а создает обьект, наполняемый изменившимися данными и сохраняет их в таблицу истории. А потом когда надо сделать вторую операцию - берем все события, загружаем назад в объект "проигрываем" - и получаем реальную сумму на счете и все остальное.

    Но для платежей кроме лога действий обычно нужен еще номер транзации в платежке, название действия, статус самой транзакции, которая обновляется по мере того как приходит ответ "оттуда", можно конечно всё в лог действий писать, а потом разгребсти что и откуда из этой мусорки, а можно и традиционно - таблица "payment_transactions" и там поле "status: wait/processing/done/fail" и кусочки кода которые сдвигают этот статус.

    Что до верхнего слоя апи - то первые пару методов такие - списать бабло, зачислить бабло, подарить бабло от имени админа, забрать бабло от имени админа.

    Валюты точно потребуются. А значит вводите понятие "базовая валюта", именно в ней будут ваши цены. Можете условно приравнять её к 1 доллару, но не используйте сам доллар/евро/рубль в качестве валюты, сделайте новую. Лучшая аналогия (в том числе юридическая) - какие-нибудь танки. Там могли бы платить баксами, но платят игровым золотом, а золото покупают за баксы.
    Ответ написан
    Комментировать
  • Laravel eloquent. Большое потребление памяти, долгая загрузка и огромное количество моделей. Как оптимизировать?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Там же фильтровать можно:

    $query = Model::query()
      ->with([
        'relation' => function ($query) { $query->where('field', 'value'); },
      ]);


    И потом, на кой вам сдалось выдергивать из базы ВСЕ ОПРОСЫ, на странице поди больше десятка не поместится. А варианты ответов тоже не сразу нужны, вероятно, что можно для одного "открытого" запрашивать, это если за оптимизацию говорить.
    Ответ написан
    Комментировать
  • Как получить GET параметры?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    решетка на сервер не передается, это чисто клиентская хрень.

    нужно на яваскрипте делать.
    Ответ написан
    Комментировать
  • Как можно объединить условия трех зависимых сущностей в policy?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Прежде чем читать ниже, обратите внимание на ответ Дмитрий. Четко. Понятно. По делу. Я дам теорию.

    Контроль доступа тема немножко сложнее, чем просто по документации создать политики.

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

    Чтобы что-то сделать (удалить), вам нужно проверять доступы не к модели, а к действию. Вы их можете положить в модель или в политику, но как вы прикажете базе данных "не возвращать после SELECT записи в которых есть такой-то параметр", когда политика на вход ждет экземпляр модели, который уже получен из базы?

    Политика это про "что можно видеть и что не можно".
    Политика - фильтрует_список ИЛИ запрещает_действие_с_одной_записью (по факту - фильтрует одну запись до пустой переменной и дальнейшее выполнение не имеет смысла).
    Разрешения - запрещают действие, и они отталкиваются часто от довольно сложных запросов, как-то проверить время, рабочий день, параметры среды, параметры десятка моделей и тд.

    Что мы получаем в итоге. Если вы хотите канонично хранить все в политике - создайте внутри политики меню метод, возвращающий Eloquent\Builder или Query\Builder, который нужно выполнить. И не ожидайте что он выполнится "автоматически", вообще в контроле доступа автоматическая магия только вредит. Вызовите его в контроллере так, как вы вызываете $this->validate(), вы же не ожидаете что валидация "выполнится сама", хотя и могла бы, но это неочевидно для тех кто после вас код читать будет.

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

    Вы либо в модели делаете метод newPolicy/getPolicy(), возвращающий политику, в которой есть метод, ожидающий на вход и ту модель и эту, и делающие все действия. В итоге это уже не про "как в ларавеле", это про обычный ООП - написали метод в политике, создали класс, вызвали, и про хранение "где удобно чтоб лежало".

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

    Я обычно пишу свой класс гейта, в который вообще передаю список разрешений (с поддержкой разных ИЛИ даже может быть) из реквеста, который там в статическом методе лежит, прогоняет эти разрешения. Если действие еще и зависит от запросов - я там же и запрос положу в методе, вызову его из реквеста, выполню, проверю результат. И только потом достану модель из БД, и продолжу что-то с ней делать.

    А с помощью политик или обычных ифов я скорее скрою поля в ответе json. И даже тут можно настроить Serializer и группы, которые это будут делать на основе реквеста или же политики.

    ====

    Вообще про контроль доступа много можно начитать, но я здесь вкратце дам то что вы там найдете. Есть подходы ACL, RBAC, ABAC и ещё какие-то менее известные.

    ACL - это про "разрешения". У вас есть список действующих лиц, список ключей (разрешений), список ключниц (ролей), а в коде написаны замки (ифы), которые ключами открываются. В итоге ваш класс берет ключи на ключнице, и пытается открыть замок, если не открылось - бросает 403. Есть ли тут сложности? Есть. Пример был описан на одной из статей на хабре про звездолет. Наняли одного человека - он монтажник двигателя. Потом наняли второго, а первого переназначили на повара. У повара должны быть чистые руки, у механика - грязные. При этом права у них одинаковые (согласно их умений в жизни), но права повара - отключают некоторые права механика и наоборот. А потом мы добавим третью роль, которая например дает права обратно и всё, мы поплыли. Тут и появляется та штука, что мы встречали в apache - deny first / allow first. По мне deny first - безопаснее. Если есть хоть один запрет на полномочие, то можешь потом хоть сто раз его добавить - его не будет. Можно кидать другое исключение что-то вроде "полномочие запрещено".

    RBAC - это придумка Yii (вернее в оригинале это концепт с ролями как выше написал, но реализовали его потом на PHP разработчики Yii, очень своеобразно), который попытался магически сделать ACL. Он основывался на том, что изначально у чего-то (модели, ресурса) есть методы проверки ключей, но чем "круче" роль пользователя (наследование), тем более злой метод применяется. Это жутко неудобно писать на самом деле, потому как наследование начинает путаться с композицией, а иерархия ролей ломаться, потому как постоянно хочется создать роль на основе существующей, и со временем хочется на основе двух существующих и вообще там такой лес начинается что просто беда.

    ABAC - это попытка зайти вообще со стороны ООП. То есть вместо ролей, разрешений и прочего - предлагается использовать обычные функции, в которую кидать на вход всё, что требуется для проверки, а на выход выдавать "можно или нельзя". При этом эти функции могут ходить в базу, что-то там спрашивать, выполнять предварительные действия, то есть как бы "полная свобода", но вот минус - ПОЛНОЕ отсутствие стандарта и невозможность это объяснить. То есть "можно всё" как и в коде. Это и сделано в Симфони с вотерами, но тут нарощен ещё один кусок - вместо deny/allow есть ещё vote, чем больше проголосовало - тем больше вероятность, что "можно" или "нельзя".

    При этом у лары здравая идея с политиками, которые точно нужны например, когда вы выдатаете пользователей и баланс на их кошельках. При всем уважении баланс могут смотреть администраторы, менеджеры и сам юзер, но не его друзья (которые могут получить список юзеров под действием "дай список моих друзей"). И это вопрос даже не ролей, это вопрос наличия ключа "могу смотреть балансы". Обратите внимание, что модель при этом есть, но у нее не видны несколько полей. Технически можно запихнуть все методы ABAC и назвать это политикой, и это будет правильно с точки зрения "все доступы - в политиках" - но вот автоматическое их применение лара не дотянула до приемлемого уровня, а оно и не надо.

    Так что контроль доступа это про "настроить все способы защиты и применять те, которые сейчас нужны по методике ABAC" - то есть в обычных функциях проверять хочешь ключи-разрешения, хочешь - роли, хочешь день недели, а хочешь - поле в модели.

    Разумеется, контроль доступа чаще всего делают "настраиваемым в админке". Но попробуй-ка настроить в админке обычную функцию, которая может делать что-угодно. Вот и пришли к выводу, что в админке настраивают часть ACL, что называется дают разрешения в роли, вяжут роль к юзеру, а потом от этих разрешений зависит какие действия делает обычная функция. Некоторые даже делали flow-контроль, позволяющий чуть ли не в графическом редакторе строить логику добавляя "или", "и", "больше", "меньше" - но этим реально пользуются потом только программисты, никто не хочет ничего сломать, поэтому - в админке дают разрешения, а по ним написан код. Самый правильный способ.
    Ответ написан
    1 комментарий
  • Как испраить ошибку: SQLSTATE: Duplicate alias: имя таблицы указано больше одного раза?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Пожалуйста, называйте связи так, как называется модель, которую вы хотите получить.

    items() ? чего? вы хотите получить CartProduct. назовите cartProducts()

    очень редко, но бывает, когда CartProduct можно получить двумя способами. Тогда cartProductsByModel1(), cartProductsByModel2()

    исключение - абстрактные паттерны, деревья. там можно назвать parent/children/treeParent/treeChild

    <?php
    
    Class Cart {
        // ...
    
        // вот здесь написано, что CartProduct может быть одновременно в двух и более корзинах. не могу представить себе логику, где участник заказывает на сайте товар, а потом этот же товар (который ты заморозил только что) кто-то еще заказывает
        public function items(): BelongsToMany
        {
            return $this->belongsToMany(CartProduct::class, 'cart_products');
            
            // Cart.hasMany(CartProduct) + CartProduct.belongsTo(Cart) + CartProduct.belongsTo(Product)
            // и второе
            // https://github.com/illuminate/database/blob/master/Eloquent/Concerns/HasRelationships.php#L477
            // первым параметром "тип", вторым - "через что". Вы указываете, что "связь много ко многим" должна вернуть "связь много ко многим"
            // при связи Много-Ко-Многим надо Cart.belongsToMany(Product::class, 'cart_products')
        }
    }


    <?php
    
    class CartProduct {
        // ...
    
        // здесь написано, что CartProduct старше чем Product. Это выразится в том, что вы сможете "привязать" CartProduct только из самого Product, причем один раз. Это дважды неверно.
        public function product(): HasOne
        {
            return $this->hasOne(ProductVariant::class, 'id');
    
            // Во первых каждый продукт с его количеством может быть куплен много раз, а вариант всего лишь показывает на его подвид
            // Во вторых модель CartProduct создается позже чем Product, а значит писать $product.addChild($cartProduct) это снова дичь
            // Product.hasMany(ProductVariant::class) + CartProduct.belongsTo(ProductVariant::class) + $cartProduct.associate($productVariant);
        }
    
        // то же самое. CartProduct старше чем Cart, хотя он в ней лежит. BelongsTo задает старшинство и позволяет добавлять родителя в потомок, а hasOne указывает связь, которая нужна только для select, но не годится для insert
        // Потому что hasOne() - это возможность добавить потомка, причем в Eloquent нету метода addChild() его ещё и самому писать надо типа CartProduct.cart().insert($array) и это тут же нарушает идею отложенной записи, т.к. выполняет запрос немедленно и требует транзакции прямо в модели, тогда как транзакция относится к задаче, а не к модели
        public function cart(): HasOne
        {
            return $this->hasOne(Cart::class);
        }
    }
    Ответ написан
    Комментировать
  • Как организовать опциональное условие в запросе?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Не вижу ничего плохого в том, чтобы использовать if () вместо ->when()

    Будьте осторожны с "приходит - фильтруем, не приходит - отдаем все", учитывайте что может прийти только ключ, а еще может прийти пустое значение - пустой массив или пустая строка или цифра 0. А ещё "все" бывает 100 тысяч. А еше база бывает 15 миллионов, и выбрать 12000-ую страницу из 15 миллионов не так то просто, особенно когда у вас есть сортировка. Сортировку внедрять везде - это трудно.

    Лучше всего отталкиваться от array_key_exists(). В вашем случае "если роль находится в допустимом списке то фильтровать" несколько ограничивает вас на пример "роль это константа". В проекте константой является не роль, а разрешение, а роль это для удобства пользователя - группа разрешений. Ролей человек наделает тьму, а вот разрешений сколько кодер накодит столько и будет. Но, да, есть фиксированные роли (они же - базовые) GUEST/USER/BANNED/ROOT. Это не меняет тот факт, что поведение "если присланное клиентом не находится в нашем списке, то программа не работает" - в вашем случае она должна сработать но вернуть пустой список.

    Просто проверяешь запрос на наличие поля (для этого убираешь в ларавеле там мидлвар ConvertEmptyStringsToNull, т.к. это семантически неверное поведение для апи, хотя и рекомендуемое в ларе).

    when и колбэки - это процессорные вызовы, контексты, и лишний расход памяти, ифы это легче. Если б они давали что-то кроме вертикалости написания, но ведь нет. Не то чтобы я призываю писать вас вложенных тысячу ифов, ифы тоже надо уметь упрощать до 1-2 уровня или массива-маппинга.
    Ответ написан
    1 комментарий
  • Как правильно назвать pivot-таблицу при отношении многие ко многим в laravel?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Правильно это делать таблицу под вашу предметную область.

    Если связка "появляется" при разработке функционала шоп - то префикс - шоп. Затем идет таблица, которая имеет "больший охват" и затем таблица которая имеет "меньший охват" (либо если охват туманно звучит - то как читаешь задание - продукты лежат в категории, значит так и есть категория первая, продукт - вторая). Скорее всего в продукт отсутствует префикс "шоп" потому что вы решили, что продукт это глобалка, дескать разрабатываю магазин - значит зачем мне писать ShopProduct, если можно - Product. В то же время в магазине оказалось что есть Category, а потом вы решили сделать ShopCategory, потому что Category уже была, но вы, возможно, задумались что это кусок совершенно другого функционала.

    И правильно задумались, т.к. общий функционал "ядро" не включает ваше поведение проекта, функционал ядра называется ядром потому что его можно использовать в десяти разных проектах почти не изменяя. Если уж совсем следовать стандарту то удобно называть модельки ядра префиксом Info, например InfoDictionary, InfoCurrency, InfoFile, InfoImage - но его можно и опускать, если вы понимаете, что вы специально это сделали. Категория на мой взгляд имеет больший охват ответственностей (она старше, т.к. продукты в ней лежат по задумке), чем продукт.

    То есть правильное название:
    shop_category_product (для таблиц - shop_category и product)
    shop_blog_category_shop_product (для таблиц - blog_category и shop_product, префикс - обозначает кусок техзадания, в котором появилась возможность нахождения продукта в категории блога). В данном случае это исключает, что сами продукты могут быть по категориям, хотя чаще всего это так. Поэтому возможно правильнее было бы иметь shop_category и shop_product, связку shop_category_product, а в shop_category сделать belongsTo на blog_category, показав что есть связь между той категорией и этой.

    Вообще я когда-то раньше делал модели для пивотов и даже наследовал их класс Pivot, но потом понял, что это по большей части ничего не дает, и проще соединить таблицами через связь, чем класс создавать.

    Бывает когда там нужна модель, но почему бы ей не быть просто моделью, если она вам нужна, чем делать для нее специальный тип, который отличается от модели только тем, что не умеет во множественное число и игнорирует установку дат, и чаще всего не имеет id. Когда вы захотите это оптимизировать на работу "по 100 штук" и попробуете применить $model->chunk(100) сие ругнется, что в таблице нет айди и так невозможно. То есть использование собственных пивотов во имя "порядка" и "эстетики" создает проблемы.

    И ради бога, выпилите из ларавельной функции getTable() {} которая в Illuminate\Eloquent\Model преобразование во множественное число. Вы обожжетесь не один раз пытаясь преобразовать "people" в "person" и тратите драгоценное процессорное время на анализ окончаний, который в результате не дает вам ничего, кроме эстетики. Привыкните делать таблицы в единственном числе. С ними проще работать.

    Можно то как хочешь, но нужно так.
    Ответ написан
  • Laravel: почему возвращается 404я страница при обращении к js-файлам в папке /js/chunks?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Опишите ваш сервер. Что там, nginx? Конфиги дайте.

    Скорее всего у вас либо билд косячит и файлов нету (надо npm run dev или типа того запустить), либо билд создает их не там где сервер хочет их видеть.

    В конфиге сервера может стоять даже перенаправление что-то вроде "если начинается на /js" искать вообще в другом месте. По дефолту конечно такого не бывает, но в процессе работы такое могут вкостылить.
    Ответ написан
    Комментировать
  • Не работает whereIn если данных больше 700,почему так?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    В базах данных есть лимиты на число подстановочных элементов.

    Если вы пытаетесь запросить 700 критериев - это значит вы неверно выполняете задачу (хотя кажется, что все верно).

    700 критериев вы откуда-то взяли. Если вы взяли их из базы - используйте ответ Дмитрия, он берет айдишки прямо из базы, в итоге критерий получается вообще один.

    Если вы взяли ваши критерии из файла, то вам следует разбить ваш запрос на повторяющийся с меньшим числом критериев, "делать по 100", например.
    Ответ написан
  • Как обрезать/повернуть изображения до того момента, как будет вызван метод контроллера?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Логотип не всегда нужно пережимать в действии "добавление чего-то". Так можно, но не всегда нужно. (Нужно когда у вас жесткое ограничение на место на диске и вам не хочется хранить гигантские двухметровые оригиналы на амазон, т.к. это платно будет за каждый мегабайт)

    Разумно ещё иметь функцию "пережать несколько", которая вообще вызывается в фоне, если вы загрузили парсером 10000 категорий, и каждую будете в моменте пережимать - долго очень. Грузанули все, сохранили, запустили в фоне "пережималку", она все пережала. Или вообще не запускали и сделали как напишу ниже.

    Размеров может быть совсем не один, а десяток, и поэтому вам нужно загрузить и сохранить оригинал фотки, а затем сделать роут, возвращающий пережатую картинку в нужном размере. Желательно (очень желательно) сделать проверку "секретного ключа" который генерируется под параметры размера, иначе можно ваш сервер напрячь изменением картинки сотни раз, и он будет занят бесполезной работой.

    Посмотрите пакет league/glide.
    Ответ написан
    4 комментария
  • Хелпер Laravel dd() возвращает ошибку 500?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Конкретно по твоему случаю я не знаю, но у меня такое было из-за корсов.

    Запрос для апи должен вернуть заголовки CORS которые не могут быть возвращены, т.к. ты прервал программу с помощью dd(). Браузеры в итоге пишут вроде 500, а вкладки "контент" вообще нет. Причем если напрямую в браузер адрес апи вбить то ответ есть. Но есть же POST запросы, которые надо расширением делать заголовок на POST менять или писать "псевдо-пост" типа "если есть флаг $_GET['method']=POST то воспринимать как пост. Для дебага конечно, на проде нельзя.

    Чтобы их вернуть нужно "как-то" после dd() вернуть ещё респонс и отправить его. И это только пол-проблемы, т.к. запроса будет два. И заголовки нужно вернуть и в OPTIONS запросе (одни) и в обычном запросе (другие), на чем часто горят задницы многих разработчиков типа "задолбался с cors". Ну да, там и вправду творческое задание. Только лучше иметь установленный файрфокс, чтоб дебажить, вслепую быстро доводит до срыва.

    Один из способов - зайти в доку symfony/var-dumper, сделать маленький класс из двух методов и там с помощью varcloner вернуть вывод dd() в переменную. После этого вместо dd() вывести его результат через echo, и позволить запросу завершиться (но это не всегда возможно, если ты поймал критическую ошибку - в этом случае управление перейдет Exceptions\Handler.php (в ларе) или если скрипт очень длинный, тогда будешь ждать минуту каждый раз чтоб дебагнуть.

    Второй способ - вместо dd() бросать исключение и его обрабатывать в Handler.php, который формирует ответ-исключение (может делать, если режим сайта дебаг и тд.) там тоже можно преобразовать в другой вид. Но опять же хотя обработка исключений это стандартный процесс для большинства фреймворков - мне его задумка не очень нравится просто потому, что у нас есть контроллер с респонсами, а тут еще одна приблуда, которая до кучи еще и настроена по-своему во имя возможностей которыми почти никто не пользуется. Но тем не менее код выполнится уже на этапе response->send() а значит отправятся нужные заголовки, а потом выведется dd() который написано выводить если "исключение такое-то".

    Обычный трайкетч в файле index.php часто делает то же самое и более податлив к допилам, чем разбираться как это же делает "настроенный кем-то обработчик" который умеет два метода - render() и notify() который один для отрисовки, второй для отправки куда-то, создается ощущение, что "больше ничего нельзя", а код это все таки свобода мысли.

    Есть ещё совсем уж злой способ - register_shutdown_function(), где ты принудительно вернешь пустой респонс с 500 ошибкой, но вот вывод dd() придется прокидывать через костыль. И потом register_shutdown_function() особенно весело мирить с каким-то шаблонизатором типа Plates, Twig или Blade, которые по дефолту как правило просто не выводят исходник html, если поймают внутри ошибку тупо завершая ob_get_clean() вместо ob_get_flush(), в итоге эксепшен есть, а dd() нету, т.к. он был в хтмл, который остановился и очистился.

    Оба варианта не очень логичны и костыльны, зато позволяют потом в том же постмане или браузере видеть в апи не json, а нашу dd() шку. Там помучаться надо немного, но сильно сократит время на создание полного зеркала апи в постмане или сваггере и слежения за ним. Все-таки сказать человеку "нажми F12" против убить день на то, чтобы он выучил возможности сваггера, а потом ты их выучил, а потом ты их описал, потом понял ограничения - лишние действия под сомнительный итог "у нас документация по канонам". Все равно придется объяснять новым как что работает.
    Ответ написан
    2 комментария
  • Как построить цепочку роута не прибегая использованию модели для алфавитной литеры?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Как я вижу тут два способа.

    Способ первый. Дерево есть дерево. Сначала строится дерево всего что может быть, потом контроллеры работают с деревом. Если нету в дереве - 404. Здесь будет работать механизм SubstituteBindings, когда ты на входе пишешь LiteraController::literaAction(Category $model), которая по твоей букве ищет категорию, у которой slug - твоя литера - и т.к. её нет - автоматически выбрасывается 404. Делать труднее, изменять труднее, зато потом получаешь все плюшки работы с деревом и возможностью по нему всячески бегать, подсчитывать суммы элементов в нем и так далее.

    Способ второй. Твой. Роут работает с любой литерой, а внутри роута pages.books.litera тянет всё что начинается на букву такую-то, и если ничего не вытянул принудительно кидает 404. Это нормальное абсолютно поведение, когда ты бросаешь 404 вручную, а не автоматически. Делать легче, менять легче, плюшек нет. Зато дешевле. Заказчик захочет изменить - надо ПЕРЕ-делывать и наращивать функционал, доделывать - не прокатит.
    Ответ написан
  • Почему не применяются настройки подключения в Laravel на лету?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Там есть приколы связанные с инициализацией и что каждая модель умеет в getConnection() который может быть дефолтный. Надо не конфиг менять, а в самой модельке втыкивать. Не спрашивай почему. Что у Тима в голове сложный вопрос...
    Ответ написан
    1 комментарий
  • Как в Eloquent (или ActiveRecord) получить всех потомков n-ного уровня, в таблице с полями id и parent_id? Нужны ли воспомогательные таблицы?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Нужны ли воспомогательные таблицы?

    Да, желательно.

    Это вообще реально, или нужно raw-запросы писать на SQL?

    Чтобы было еще быстрее - да. А без дополнительной таблицы - тем более да.
    Вот как делать: https://www.percona.com/blog/2011/02/14/moving-sub...

    Ибо я полагаю, что если записей будет over 1M

    Индексы решат проблему с миллионом. А еще проблему миллиона решает взгляд на то, что если ты решил "вывести на страницу миллион" - то ты что-то не так обсудил с заказчиком. Человеку от 20 записей уже становится голове тяжело, а лям - это забей.

    Есть паттерн Closure Table (я рекомендую его), есть паттерн Nested Sets (для меня тяжелый в чтении, и средний в реализации) и есть паттерн Path Enumeration (самый простой при реализации, и без дополнительной таблицы, но в итоге может не всё) и опирается на WHERE LIKE %path/to/parent%.

    Все они реализуют дерево в таблице, позволяя избежать необходимости поддержки рекурсивных запросов, которая в MySQL есть только в более поздних версиях и есть в постгресе. Но честно, я когда разбирался в WITH RECURSIVE понял что с паттернами легче было.
    Ответ написан
    1 комментарий
  • Как правильно получить ответы к комментариям?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    1. Для начала избавьтесь от replies.replies.replies.... Для этого есть паттерн для баз данных Closure Table, которая позволит вам выбрать
    ->with([
      'comments.replies.replies_closure' => function ($q) { $q->where('parent_id', 1)->where('depth', '>', 0); },
      'comments.replies.replies_closure.parent' => function ($q) { $q->where('user_id', 1); },
    ])

    Минус - лишняя таблица, определенный геморой при добавлении комментов, но если гуглить Closure Table на сайте percona.com - там есть три запроса, которые можно скопировать и вставить для "создания", "перемещения в другой родитель" и "отвязки". Бонус - вы получаете неограниченную вложенность комментариев, не требующую рекурсии вообще. Можете хоть всё дерево залпом выводить. Однако стоит обратить внимание, что Ютуб выводит только первый уровень не просто так. Комментов может быть оч. много, и в каждом оч. много ответов, можно просто ушатать лимит памяти сервера на скрипт через полгода использования проектов, когда к какому-то посту будет 5000 комментов уровней на 12. Тостер вот вышел из положения разумнее - он просто сказал, что максимум есть комменты и ответы к ним, и они не вкладываются, зато можно цитировать. Очень правильно и не глючит.

    2. Для вопроса "привязать комментарии" - когда вы сделаете выборку, для вашего ресурса - $this->comments уже будет содержать то что вы выбирали. Метод toArray() рекурсивно пройдет по всему, что попало в ресурс (должен по крайней мере так делать). Если не делает - может стоит написать $this->comments->toArray();
    Если в каждом из $this->comments поле 'replies' пустое, значит что-то не так выбрано.

    3. Деревья при выводе гораздо разумнее выдавать в структуре
    $list[ $id ] = $model;
    $tree[ $parent_id ][ $child_id ] = true;
    // $parents[ $child_id ] = $parent_id; // если есть вероятность, что дерево перестроят на той стороне и вернут обратно новое

    чем в бесконечной вложенности, это минимизирует повторения и уменьшает размер выходного json-а.
    Ответ написан
    Комментировать