Коллекции 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(); // классическая связка «отфильтруй и вытащи»


И помните три вещи:

  1. Оба метода возвращают новую коллекцию — оригинал не трогают.
  2. filter() без замыкания удаляет все «ложные» значения, включая 0 — будьте внимательны.
  3. После filter()/unique() вызывайте values(), если отдаёте результат в JSON как список.