• Изменено

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

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

Итак, у нас есть модель Post. Как нам ее получить из БД:

Просто получаем все существующие посты:

Post::all();

На выходе получаем коллекцию, даже если пост был всего один, а значит доступ к его свойствам (имени и т.д.) получаем внутри цикла.

Post::get();

На выходе получаем коллекцию. Используем вместо all(), когда нужно поставить условия, о которых ниже.

Post::first();

На выходе получаем первую подходящую по условиям модель, а значит можем обращаться к свойствам напрямую. Используем в основном с условиями, о них ниже.

Post::find(1);

На выходе получаем объект модели Post с id == 1.

Условия.

Post::where();

То, чем придется пользоваться чаще всего. Внутри помещаем поле, по которому хотим искать, и значение:

Post::where('name','=','october');

Будем искать все посты с именем october. На данном этапе мы имеем объект класса QueryBuilder. Для того, чтобы получить коллекцию, мы должны воспользоваться одним и способов, описанных в первой части, например:

Post::where('name','=','october')->get();
  • получаем коллекцию всех постов с именем october, даже если пост будет всего один.
    Post::where('name','=','october')->first();
  • получаем первую модель с именем october, всего одну, даже если их в базе несколько.

Вместо знака '=' мы можем ставить мы можем ставить '!=' - неравно, '<' - меньше, '>' - больше. Сам знак равенства можем опустить:

Post::where('name','october');

имеет то же значение, что и Post::where('name','=','october');

Кроме того, мы можем использовать названия столбцов вместе с where:

Post::whereName('october');` то же, что и` Post::where('name','=','october');

Можем использовать для поиска нулевых значений:

Post::whereNull('parent_id);` то же, что и `Post::where('parent_id, '=', NULL);

И наоборот:

Post::whereNotNull('parent_id);` то же, что и `Post::where('parent_id, '!=', NULL);

Мы можем передать несколько фильтров
для поиска:

Post::where([
    ['name', '=', 'october'],
    ['id', '<', 3],
]);

Если первое условие where не нашло ни одной модели, можем использовать дополнительное условие orWhere

Post::where('id',2)->orWhere('id',3)->first();
  • если пост с id 2 не будет найден, будет поиск по id 3;

***В примере выше между первым и вторым условием стоит логический оператор ИЛИ. Бывают ситуации, когда у нас есть, допустим три условия, между вторым и третьим мы хотим иметь ИЛИ, но первое должно быть выполнено обязательно.Если мы просто напишем так:

Post::where('id',2)->where('name','october')->orWhere('name','laravel')->get();
  • то при отсутствии поста с id 2, но при наличии постов c именем laravel, мы все равно получим свою коллекцию. Чтобы поставить нужное нам $a И ($b или $c), мы можем использовать замыкание:
Post::where('id',2)->where(function($query){
    $query->where('name','october')->orWhere('name','laravel');
});

Если нам нужно найти все посты c id, попадающими под значения массива $array = [1,2,3];

Post::whereIn('id',$array);

Отношения.

Получаем только посты с комментариями:

Post::has('comments')->get();

Получаем посты только с комментариями, у которых id больше 10:

Post::whereHas('comments', function($query){
    $query->where('id', '>', 10);
});

Если мы получим все посты так: Post::all(); ,а затем уже на фронте в цикле будем обращаться к комментариям, то каждое обращение будет формировать запрос к базе данных. Чтобы загрузить посты сразу с комментариями:

Post::with('comments')->get();

*** В модели может стоять public $with = ['comments']; ,это значит, что каждый ваш запрос будет автоматически подгружать комментарии, как в примере выше.

Если у комментариев есть свои отношения,например автор, можем подгрузить и его, через точку:

Post::with('comments.author')->get();

Можем получить количество комментариев с помощью:

Post::withCount('comments')->get();

Мы можем 'догрузить' наши отношения уже после получения коллекции с помощью load():

$posts = Post::all();
$posts->load('comments');

Прочие методы Collection.

Если мы хотим получить из коллекции массив:

Post::all()->toArray();

Чаще всего используется вместе с pluck(), чтобы получить массив нужных нам данных, например массив всех id постов:

Post::all()->pluck('id')->toArray; 

pluck() пришел на замену устаревшему lists(), который вы можете встретить в старом коде.

Если мы хотим удалить объект, например с id 2 из коллекции:

Post::all()->reject(function($query){
    return $query->id == 2;
});

Когда мы получили большую коллекцию, и нам нужно провести какую-либо операцию, а памяти не хватает:

Post::all()->orderBy('id')->chunk(1000, function($query){
    foreach($query as $queryItem) {
        // тут операции
    }
});

Для chunk() необходимо использовать orderBy(), который упорядочивает коллекцию по указанному столбцу.

  • max ответили на это сообщение.
    • Изменено

    Спасибо! Для меня в этом есть некоторые новинки.

    Ещё дополню.
    Есть удобные для запросов методы skip-take. Применяются они только в паре. В документации называется как Ограничение и смещение

    • skip - сколько элементов пропустить чтобы начать выборку;
    • take - сколько элементов взять в результат выборки.

    Синтаксис:

    Post::skip(0)->take(5)->get();

    Например такой запрос:

    Post::where('published', 1)->orderBy('id', 'desc')->skip(0)->take(5)->get();
    • where - выберет записи по параметру (в данном случае опубликованные)
    • orderBy - отсортирует их по id по убыванию (получится новые вначале)
    • skip(0)->take(5) - получим 5 последних записей

    А вот такой запрос:

    Post::where('published', 1)->orderBy('id', 'desc')->skip(5)->take(15)->get();

    выберет следующие 10 записей.
    Можно первые 5 записей, например, оформить в слайдере, а следующие 10 записей вывести списком.

    В общем новые записи, последние комментарии и т.д.

    • sko6 ответили на это сообщение.

      Koresh про skip не знал, а так то полезная штука. Но take не обязательно применять в паре с ним, можно использовать отдельно, по крайней мере из контекста Коллекции

      Добавлю еще пару “скрытых” механик:

      Как и когда применять with(), а когда whereHas()

      Иногда необходимо вытащить коллекцию записей, у которых есть BelongsToMany связь с другой моделью, и забрать записи только по необходимому условию. На помощь приходит метод with().

      Представим такую ситуацию: У нас есть Посты, со связью МногиеКоМногим с Категориями. У каждого поста есть связь с категориями 1,2,3,4,5.

      Мы хотим забрать посты у которых есть категория 1, и к каждому посту приложить эту категорию в обьект categories. Здесь можно использовать whereHas(), но он не передает в коллекции обьект categories. Если после фильтра whereHas() вставить with('categories') в обьект вставятся ВСЕ связанные категории.

      Так-что шаманим такую конструкцию:

      Post::with(['categories' => function($query){
          $query->where('id', 1);
      }])->get();

      После выполнения этого запроса, мы получим следующее:

      • Список всех постов, у которых есть связь с категорией 1
      • У каждого поста будет обьект categories внутри которой будет лежать эта одна категория

      Для сравнения, давайте посмотрим на пример whereHas()

      Post::whereHas('categories',  function($query){
          $query->where('id', 1);
      })->get();

      После выполнения этого запроса, мы получим следующее:

      • Список всех постов, у которых есть связь с категорией 1

      Если мы, в коде попробуем обратиться к $post->categories то нам будет доступна вся коллекция категорий, когда нам будет нужна только одна.

      • sko6 ответили на это сообщение.
        • Изменено

        reazzon

        $posts = Post::with(['categories' => function($query){
        $query->where('id', 1);
        }])->get();

        Люто полезная инфа, учитывая что у whereHas есть проблемы с производительностью, пошел тестить

        Как работать с Pivot

        Связь Многие ко Многим покрыта многими тайнами, и одна из самых "страшных" по мнению новичков ситуаций в работе с ней, это Pivot данные. Представим для примера что у нас есть модель Post со связью Многие ко Многим с моделью Category.

        Перечислю полезные методы в работе с Pivot данными:

        • Создаем новую связь с Pivot данными:

          $post->category()->attach('id категории', [
              'название столбца Pivot' => 'Значение',
          ]);
        • Добавляем в существующую связь новые Pivot данные:

          $post->category()->attach('id категории', [
              'название столбца Pivot' => 'Значение',
          ]);

          Тоже самое что и создание, вставил для уточнения. Используем этот метод, когда у вас есть связь между моделями, но между ними нет Pivot.

        • Обновляем в cуществующей связи уже существующие Pivot данные:

          $post->category()->updateExistingPivot('id категории', [
               'название столбца Pivot' => 'Значение',
          ]);
        • Отсоединяем связь

          $post->categories()->detach('id категории');
        • Фильтрация записей по Pivot данным

          Post::whereHas('categories', function($query){
              $query->where('octoclub_posts_categories.pivotcolumn', 'значение для фильтрации')
          })->get();

        Если что еще вспомню, буду добавлять сюда.

        • Изменено

        reazzon

        $posts = Post::with(['categories' => function($query){
            $query->where('id', 1);
        }])->get();

        Я что- то не так понял может, но у меня через with и замыкание выводит все продукты

        return Product::with(['categories' => function($query){
            $query->where('category_id', 1328);
        }])->get();
        • reazzon ответили на это сообщение.

          sko6 но у меня через with и замыкание выводит все продукты

          Только что провел тест у себя.

          Есть модель Carrier со связью BelongsToMany с моделью Transfer по ключу transfers.

          Ищем Carrier у которого есть связь с Transfer по ID = 643 :

          $carriers = Carrier::with(['transfers' => function($query){
              $query->where('id', 643);
          }])->first();

          Eloquent находит Carrier с ID = 3, у которого в transfers один обьект Transfer с ID = 643;

          Если запросить модель Carrier с ID = 3 просто с with('transfers'), выводится вся коллекция связи.


          Все работает корректно. На твоей стороне какая-то локальная ошибка.

          • sko6 ответили на это сообщение.

            reazzon Странно, в трех строчках кода заблудился) я еще не пойму, почему у тебя не ругается на неопределенность id в $query->where('id', 643); , там же должно быть типа transfers_id, pivot таблица же

            • reazzon ответили на это сообщение.

              sko6 у тебя не ругается на неопределенность id в $query->where('id', 643);

              Потому-что я общаюсь не с промежуточной таблицой, а с моделью напрямую.

              Есть еще особенность с отложенными связями (deffered binding).
              В документации постоянно предлагают либо attach() или же просто присваивание массива моделей/модель.

              $post->categories()->attach($category);
              // or
              $post->categories = [$category]

              И нужно помнить, что метод attach() не сработает, если мы создаем новую модель, а не обновляем. Чтоб заработал нужно добавить в него вторым аргументом $sessionKey. Хотя на мой взгляд это гемор и легче просто присвоить массив категорий. тогда при сохранении связи не потеряются.

              • reazzon ответили на это сообщение.

                dimensi метод attach() не сработает, если мы создаем новую модель, а не обновляем

                Это при использовании какой связи? У меня есть 2 проекта со связью ManyToMany где при создании новой модели, я делаю attach() и все связывается.

                • Изменено

                Еще прикольная фишка, если вам нужен поиск по базе данных, но, например, удаляя все пробелы в значениях:

                Model::orWhereRaw("REPLACE(your_column_name,' ','') = ?", [$your_search])->get();

                Это полезно, когда клиент ищет товар по артикулу без пробелов, а в бд они хранятся с пробелами.
                Ну и не только для пробелов применимо, само собой.

                • Изменено

                Про оператор LIKE совсем забыли. А он между прочим найдёт совпадения.
                Пример запроса:

                $test = 'значение для поиска';
                Model::where('columnName', 'like', '%' . $test . '%')->get();

                Применение: поиск, похожие материалы и т.д.

                • Linkonoid ответили на это сообщение.
                  9 дней спустя

                  Koresh Есть нюанс при использовании LIKE с Sqlite. Проблема заключается в том, что если полностью не совпадают регистры символов в базе и в строке поиска, то будет ложный результат при использовании UTF-8 кодировки (а она в OctoberCMS по дефолту).

                  Лучше в коде переопределить данную функцию (ссылка). Сам пользуюсь несколько облегчённым вариантом, предполагая, что в верхнем регистре обычно первый символ предложения или слова.
                  Пример:

                  Model::where('columnName', 'like', '%' . mb_substr($test, 1) . '%')->get();
                  • Koresh ответили на это сообщение.

                    Linkonoid У меня MySQL, проблем пока не замечал.

                    год спустя
                    • Изменено

                    sko6 Если нам нужно найти все посты c id, попадающими под значения массива $array = [1,2,3];
                    Post::whereIn('id',$array);

                    А есть аналог, где наоборот нужно найти значение среди массива?
                    что то вроде этого: Post::whereIn($array, 'id');

                    • reazzon ответили на это сообщение.

                      reazzon а при фильтрации коллекции как его использовать?

                      • Koresh ответили на это сообщение.
                        • Изменено

                        max для фильтрации коллекции есть метод filter() который фильтрует коллекцию с помощью данной функции обратного вызова, сохраняя только те элементы , которые проходят тест данной истины:

                        $collection = new Collection([1, 2, 3, 4]);
                        
                        $filtered = $collection->filter(function ($item) {
                            return $item > 2;
                        });
                        
                        $filtered->all();
                        
                        // [3, 4]

                        https://octobercms.com/docs/services/collections#method-filter

                        • max ответили на это сообщение.