Коллекции Laravel: filter() и pluck() — два метода, которые уберут половину ваших
Зачем это вообще нужно
Почти в каждом проекте есть код, который выглядит так:
$result = [];foreach ($categories as $category) {if ($category->published === 1) {$result[] = $category->title;}}
Это работает. Но занимает пять строк, требует придумать имя для $result и следить, чтобы куда-то не просочился null. Коллекции Laravel позволяют записать то же самое одной строкой, а главное — читается это почти как обычная речь: «возьми категории, оставь опубликованные, вытащи названия».
Разберём оба метода по отдельности, а потом соберём их в связке.
filter() — оставить только нужное
filter() принимает замыкание и возвращает новую коллекцию, в которой остались только элементы, для которых замыкание вернуло true.
Допустим, у нас есть список категорий каталога:
$categories = CatalogCategory::where('published', 1)->orderBy('position')->get();
Хотим оставить только те, у которых задана позиция:
$withPosition = $categories->filter(fn ($cat) => $cat->position > 0);
Обратите внимание: исходная коллекция не меняется. filter() возвращает новую коллекцию, а старая остаётся нетронутой. Это важно — можно спокойно фильтровать одну и ту же коллекцию в разных местах контроллера, не боясь испортить данные для соседнего кода.
Подводный камень: filter() без замыкания
Если вызвать filter() вообще без аргументов, он выкинет все «ложные» значения: null, 0, '', false, пустые массивы. Иногда это удобно:
// убрать пустые элементы из массива$clean = collect(['a', null, 'b', '', 'c'])->filter(); // ['a', 'b', 'c']
Но та же магия может укусить: если у вас есть легитимная цена 0 или позиция 0 — безусловный filter() их удалит. Поэтому в рабочих проектах я советую всегда писать явное замыкание, даже когда условие простое:
// Оставим и нулевые цены тоже — важно для бесплатных позиций$items = $packages->filter(fn ($pkg) => $pkg->price_min !== null);
pluck() — вытащить один столбец
pluck() собирает в новую коллекцию значения одного поля со всех элементов.
$titles = $categories->pluck('title');// Collection: ['Пицца', 'Суши', 'Десерты']
Частый сценарий — получить массив для формы или ответа API:
$titlesArray = $categories->pluck('title')->toArray();
Бонус: pluck по вложенной связи
Малоизвестная фишка — pluck() умеет доставать значения через точку, если связь загружена:
$messages = ChatMessage::with('user')->get();$authorNames = $messages->pluck('user.name');// ['Анна', 'Иван', 'Анна', ...]
Если у какого-то элемента связь не загрузилась (например, пользователь удалён), на его месте будет null — коллекция не упадёт, но учитывайте это при дальнейшей обработке.
Бонус: pluck с ключами
Второй аргумент pluck() — какое поле сделать ключами:
$byId = $categories->pluck('title', 'id');// [5 => 'Пицца', 8 => 'Суши', 12 => 'Десерты']
Удобно, когда нужно быстро проверять наличие категории по ID или строить выпадающий список <select> с осмысленными ключами.
Связка: filter() + pluck() вместе
Теперь самое вкусное. Реальная задача из моего проекта: нужно получить список названий только активных пакетов услуг исполнителя — для выпадающего списка в форме.
Без коллекций это выглядело бы так:
$titles = [];foreach ($performer->packages as $pkg) {if ($pkg->is_active && !empty($pkg->title)) {$titles[$pkg->id] = $pkg->title;}}
А теперь то же самое в коллекционном стиле:
$packageTitles = $performer->packages->filter(fn ($pkg) => $pkg->is_active && filled($pkg->title))->pluck('title', 'id');
Две строки вместо шести, и читается сверху вниз как инструкция: «отфильтруй активные → возьми названия с ID в роли ключей».
Ещё пример — из логики чатов. Нужно узнать, в каких чатах есть непрочитанные сообщения, чтобы показать бейджики:
$chatIdsWithUnread = $messages->filter(fn ($m) => !$m->is_read)->pluck('chat_id')->unique() // убрать повторы->values() // сбросить ключи, чтобы получился простой список [0 => ..., 1 => ...]->toArray();
Здесь стоит заострить внимание на values(). После filter() и unique() коллекция сохраняет исходные ключи, и они могут быть «дырявыми»: [0 => 5, 2 => 12]. Если такой массив уйдёт в JSON-ответ мобильному клиенту, он превратится в объект { "0": 5, "2": 12 }, а не в массив [5, 12] — и Retrofit на стороне Android может недовольно кашлянуть. values() перенумеровывает ключи с нуля и спасает ситуацию.
Мини-шпаргалка
$collection->filter(fn ($item) => условие); // оставить подходящие элементы$collection->pluck('поле'); // вытащить значения одного поля$collection->pluck('поле', 'ключ'); // то же, но с ключами из другого поля$collection->filter(...)->pluck('x')->values(); // классическая связка «отфильтруй и вытащи»
И помните три вещи:
- Оба метода возвращают новую коллекцию — оригинал не трогают.
filter()без замыкания удаляет все «ложные» значения, включая0— будьте внимательны.- После
filter()/unique()вызывайтеvalues(), если отдаёте результат в JSON как список.