• Как правильно получать элемент, который связан с помощью hasMany?

    @Dastan_UI
    Первый вариант самый лучший. Вы используете Implicit Binding. Вы переходите по роуту /my-route/menu/1. Таким образом laravel автоматически ищет Menu::find(1).

    Во втором случае вы выполняете два действия.
    1. То что выше.
    2. Дополнительно ищете Menu::where(relationship)->find(1)

    Минусы 2 варианта - вы два раза ищете одно и то же меню, дополнительно во втором разе добавляете условия Relationship, что более медленнее если не указаны индексы
    Ответ написан
    1 комментарий
  • Как можно объединить условия трех зависимых сущностей в 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 комментарий
  • Как можно объединить условия трех зависимых сущностей в policy?

    iMedved2009
    @iMedved2009
    Не люблю людей
    public function delete(User $user, Menu $menu) {
            // dd($menu->project->id, $menu->project_id);
            return $this->projectPolicy->isAdmin($user, $menu->project);
        }

    Зачем вы так? Ну у вас уже есть пользователь. Есть модель Project. Нахрена вы тащите руками Policy. Ну определите разрешение на модификацию элементов меню в ProjectPolicy и используйте на здоровье.

    public function delete(User $user, Menu $menu) {
            return $user->can('delete-menu', $menu->project);
    }
    Ответ написан
    6 комментариев
  • Как организовать опциональное условие в запросе?

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

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

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

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

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

    iMedved2009
    @iMedved2009
    Не люблю людей
    public function view(User $user, Project $project) {
            return $user->id === $project->user_id || $project->users()->find($user->id);
        }
    Ответ написан
  • Как обрезать/повернуть изображения до того момента, как будет вызван метод контроллера?

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

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

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

    Посмотрите пакет league/glide.
    Ответ написан
    4 комментария
  • Как обрезать/повернуть изображения до того момента, как будет вызван метод контроллера?

    iMedved2009
    @iMedved2009
    Не люблю людей
    В чем вопрос где правильно, если вы уже хотите до вызова контроллера?

    Правильно это делать либо в Модели, либо в отдельном сервисе под сохранение моделей. Неправильно это делать в мидлваре (как вы хотите) или в контроллере.

    image-1.png
    Ответ написан
    3 комментария
  • Как правильно настроить доступность метода контроллера для многоролевой модели?

    iMedved2009
    @iMedved2009
    Не люблю людей
    Route::get('/projects/{project}', 'show')->name('project_show')->can('view', 'project');
    Ответ написан
  • Как обращаться к элементам с указанием строки в css внутри одного блока?

    MrDecoy
    @MrDecoy Куратор тега CSS
    Верставший фронтендер
    Нет селектора по строкам. Только :first-line и относится он ко всей первой строке текста.
    Что у Вас там за вёрстка не очень понятно.
    А лайфхак - делать отступ в данном случае не слева, а справа, тогда отступ убирать у первого элемента второй строки не нужно будет.

    А если поддержка позволяет, то свойство gap для контейнера вместо маргина на элементах, если это на флексах у Вас.
    Ответ написан
    1 комментарий
  • Как написать свою функцию маскирования телефона?

    Ostrovsky_Miroslav
    @Ostrovsky_Miroslav
    Понравился ответ? Отметь решением!
    Написал вам очень простую функцию.

    function getNumberMask (number) {
    var string = number.match(/^(\(?\+?\d{1,2}\)? ?\(?\d{1,3}\)? ?\d+\-? ?\d+\-? ?\d+)$/);
    
    string = string[0];
    
    var result = "";
    
    for (var i =0;i<4;i++) {
    result += string[i];
    }
    
    for (var i = 4; i<string.length;i++){
    result += string[i].replace(string[i], "*");
    }
    
    return result;
    }
    
    console.log(getNumberMask("79502306392"))


    Удачной разработки!
    Ответ написан
    1 комментарий
  • Как правильно выстраивать архитектуру метода контроллера на laravel?

    @vism
    Да всё верно описал Григорий Васильков , согласен.

    lexstile И у вас впринципе то тоже верно

    Насчёт ДТО - согласен, но слишком муторно выходит везде ДТО пихать :(
    Для ДТО - Spatie DataTransferObject

    Я думаю в ПХП 8 простые данные позволительно именованными параметрами передавать
    А если данные требуют доп. преобразования, то ДТО

    ответ обратно либо стандартным
    response()->json()
    либо свой обработчик, как Григорий Васильков предложил

    вот вариант близкий к вашему написанию. В принципе я так и делаю
    public function destroy($id)
        {        
            if (!ProjectPolicy::requestDelete($id)) {
                return response()->json(Response::getErrorMessage('Ошибка доступа'), 403);
            }
    
            $project = Project::find($id);
            if (!$project) {        
                return response()->json(Response::getErrorMessage('Проект не найден'), 404);
            }
    // это вот в сервис класс.
            $project->delete();
            $this->deleteImageProject($project->logo);
    
            return response()->json(Response::getSuccessMessage('Вы успешно удалили проект'), 200);
        }
    Ответ написан
    5 комментариев
  • Как правильно выстраивать архитектуру метода контроллера на laravel?

    iMedved2009
    @iMedved2009
    Не люблю людей
    1.public function destroy($id)
    Используйте model binding - удобно же.

    2. ProjectPolicy::requestDelete($id)
    есть прекрасный вариант расставлять policy на уровне роутера - can:
    Route::delete('/project/{project}', [Controller::class, 'method'])->can('delete', 'project');


    3. $project = Project::find($id);
    FindOrFail - в случае отсутствия модели выбросит ровно 404;

    4. $this->deleteImageProject($project->logo);
    Если вы всегда удаляете лого после удаления модели - то повесьте событие delete и всегда удаляйте лого как только где то и в каком то месте пытаются удалить модель Project. Для случаев mass delete - гляньте в трейт softDeletes, по аналогии можно навешать обработчик.
    Лень - через события? Ну переопределите у модели метод delete.
    public function delete()
    {
        $result = parent::delete();
        if($result){
            $this->deleteImageProject($this->logo);
        }
        return $result;
    }

    У вас логика удаления модели (если нам надо удалить модель - надо удалить лого) - не должна валяться по всему проекту - она должна лежать в одном месте. А судя повсему вы ее вообще в контроллер запихали - ни в какие ворота

    При использовании всего этого у вас метод контроллера приобретет простой и лаконичный вид.

    public function destroy(Project $project)
    {
        $project->delete();
        return response('', 204);
    }


    204 - ибо можем

    If a DELETE method is successfully applied, the origin server SHOULD
    send a 202 (Accepted) status code if the action will likely succeed
    but has not yet been enacted, a 204 (No Content) status code if the
    action has been enacted and no further information is to be supplied,
    or a 200 (OK) status code if the action has been enacted and the
    response message includes a representation describing the status.

    RFC
    Ответ написан
  • Как правильно выстраивать архитектуру метода контроллера на laravel?

    @jazzus
    Не нужно ничего крутить. См Policy, Model Binding и Localization в документации Ларавел.
    Ответ написан
    Комментировать
  • Как правильно выстраивать архитектуру метода контроллера на laravel?

    gzhegow
    @gzhegow
    aka "ОбнимиБизнесмена"
    Знакомая боль давняя.
    Принцип следующий - 1) исходные данные 2) превращение в обьект, 3) проверка (валидация) обьекта, 4) передача обьекта в какое-то действие, 5) проверка результата (если нужна) и 6) генерация респонса.

    Исходные данные. Смотрим что за фрейм. Лара. Умеет в инжекцию в метод контроллера и биндинг обьектов, если написать правильно. Можно сразу на вход писать MyActionRequest (сделать как в документации), в итоге первой строкой будет $myActionRequest->validate(), которая еще и выкинет 422 если что-то не так.

    В чем минус ларавельного реквеста? Он представляет собой как бы ВЕСЬ реквест. Это тоже самое что передавать $_GET по программе, что немного странно.

    Рекомендуется сделать свой обьект ДТО. Это по сути твой же реквест, только в нем поля все заранее описаны, голый класс без методов - одни свойства. После валидации ты свои данные из GET/POST/LaravelRequest ручками втыкаешь в поля ДТО и этот заполненный ДТО можешь дальше кидать как "точно проверенная пачка данных" и не боятся каждый раз "а что если что-то не так" - оно точно так, потому что ты уже проверял.

    Попадать в какой-то другой класс ДТО может целиком если это кусок бизнес-части программы или по одному полю / массиву полей если это какой-то кусок ядра или глобалки. Пример ядро - класс Money, который переводит там валюты знач и позволяет их переключать. Туда ваш MyActionDTO будет странно кидать, а вот string $money или PHPMoney/Money- пжалста. А вот в обьект например MyCatalog кинуть MyActionDto уже не так уже и плохо.

    Вот это ваше действие может быть просто SQL запросом. Category::query()->find() это тоже действие, а почему нет. Рекомендуют конечно запросы выбрасывать в "тип классов" (папка, такая же как "модели") репозиторий чтоб потом когда поломается чего не везде править а в одном месте, но тут тоже, пока помните, что где можно не париться, и потом - увлечетесь обобщением поймаете прикол когда не понятно какую половину запроса в контроллер, а какую - в репозиторий. Стоит помнить что мы пишем задачу под задание, задание представляет собой контроллер, а остальной код ему помогает. Поэтому частности в экшон, похожести - в отдельный класс.

    Ну так вот, это "действие" что-то вам вернет. Может это готовый к выводу контент, а может еще какие-то данные которые для вашего экшена может кривые. Вот проверяете "оно? или нет". Частный случай - ничего в базе не нашлось, это критично, и 404 или просто выплюнуть "нет записей" и 200? Проверили ифами, сделали респонсы - где 404ый, где двухсотый, где джсоновский, где иксмэльный, файл-довнлоадный или стримовый.

    Потом пошли в Handler.php написали отлов исключений в целом по программе и их запись куда-то, в базу, в кеш или на удаленный логгер-анализатор, чтобы везде не писать try catch а просто писать throw и знать что оно "хоть как-то но отрисуется" и куда надо запишется.

    public function destroy(Request $request, $id) // или MyDestroyRequest если юзер что-то еще передает
        {
            $request->validate(); // оно под капотом ещё и авторизацию проверит и 422 само выбросит
    
            // ... верно тут можно проверять политику, но в принципе если реквест наследовать то в ларе помоему их можно и в реквест код перенести и первый метод еще и это сделает
          
            $dto = new MyDestroyDto($request->valid()); // там внутри пишем что куда, хотя можно и здесь
    
            $myDomainService = new MyDomainService(); // или new MyCoreService();
            $status = $myDomainService->doSomething($dto); // или $result = $myCoreService->convert($dto->money);
    
            if ($status === 'ok') return $this->jsonDone('@api.msg.ok_message', 200); // вот метод jsonDone в базовом (абстрактном*) контроллере или, например, в трейте, напиши
            return $this->jsonFail('@api.err.error_message', 500); // собаки в тексте для наглядности, трехуровневый ключ перевода из файла lang, по собаке просто можно определить текст уже проходил через переводчик или нет. три уровня нужны, чтобы когда много переводов скапливается их можно было подключать частями. Первый уровень - где подключать (api, catalog, social), второй - что (msg, err, label, placeholder, title, h1)
        }
    Ответ написан
    1 комментарий
  • Где лучше всего формировать URL для файлов на laravel перед отправкой на клиент?

    @jazzus
    В геттере, как уже написали, который вызывать в Ларавел ресурсе, который отдавать из метода контроллера с названием index. Еще можно убрать из модели лишние методы, чтобы не было каши типа Project::projets и модель не превратилась в простыню.
    Ответ написан
    2 комментария
  • Где лучше всего формировать URL для файлов на laravel перед отправкой на клиент?

    iMedved2009
    @iMedved2009
    Не люблю людей
    Defing accessor
    class Project extends Model{
    ............
    
    public function getFullUrlAttribute(){
               return функция которая делает из атрибута полной путь();
    }
    .........
    }


    во вью {{$project->full_url}}
    Ответ написан
    21 комментарий
  • Как организовать миграцию в laravel на production?

    alexey-m-ukolov
    @alexey-m-ukolov Куратор тега Laravel
    Ну так и делать — миграции и нужны для случаев, когда «в БД меняются поля».
    Ответ написан
    6 комментариев
  • Как организовать миграцию в laravel на production?

    Fragster
    @Fragster
    помогло? отметь решением!
    Не надо менять существующие миграции, надо только добавлять новые. Менять можно только до коммита в cvs.
    Ответ написан
    7 комментариев
  • Как организовать миграцию в laravel на production?

    vfreelancer
    @vfreelancer
    php
    каждое изменение бд - в новой миграции, старые нельзя менять. тогда на проде просто php artisan migrate - выполнятся только новые миграции (laravel ведет учет миграций в таблице migrations в бд)
    Ответ написан
    6 комментариев
  • Как корректно настроить политику CORS на Laravel 9 (localhost)?

    iMedved2009
    @iMedved2009
    Не люблю людей
    В 'allowed_origins' указать те домены с которых будут разрешены запросы. Я так понимаю у вас это admin.site.ru.
    Ответ написан
    Комментировать