Введение
Разработчики, работающие с Laravel больше пары недель, уже использовали коллекции Laravel. Каждый вызов User::all() или collect([...]) порождает коллекцию. Но для большинства разработчиков коллекции в Laravel так и остаются «тем самым массивом, с которым можно удобно работать».
На самом деле коллекции Laravel — это не просто удобная обёртка. Это совершенно иной способ мышления об обработке данных.
Сравните два подхода. Допустим, есть список пользователей, из которого нужно оставить только активных, взять их имена и сложить в массив для выпадающего списка. Вот как это часто делают без коллекций:
$users = User::all(); // Вернёт коллекцию, но пока забудем об этом
$result = [];
foreach ($users as $user) {
if ($user->is_active) {
$result[$user->id] = $user->name;
}
}Код рабочий. Но он описывает процесс: «создай пустой массив», «пройди в цикле», «проверь условие», «положи в массив». Через месяц, читая такой код, вам придётся мысленно прокручивать всю эту механику заново.
А вот как та же задача выглядит в мире коллекций Laravel:
$result = User::all()
->where('is_active', true)
->mapWithKeys(fn ($user) => [$user->id => $user->name])
->all();Этот код описывает результат. Он читается как предложение на английском: «взять всех пользователей, где активны, преобразовать в пары ключ-значение, вернуть массив». Вместо цикла и временных переменных — только данные и их преобразования.
В этой статье мы не будем просто перечислять методы коллекций Laravel с сухими примерами. Пойдём другим путём: разберём типовые задачи, с которыми вы сталкиваетесь каждый день, и покажем, как коллекции решают их элегантно и эффективно. Вы узнаете, как фильтровать данные без лишних циклов, как преобразовывать их на лету и, самое главное, как работать с действительно большими объёмами данных, не убивая память сервера. В конце получите не просто шпаргалку, а ментальную модель для работы с коллекциями, которая останется с вами надолго.
Основы работы с коллекциями
Прежде чем погружаться в сложные преобразования, стоит разобраться, как создавать коллекции и какие базовые принципы лежат в их основе. Это та платформа, на которой строится всё остальное.
Создание коллекции
Самый распространённый способ создать коллекцию в Laravel — воспользоваться хелпером collect(). Он принимает массив и возвращает новый экземпляр Illuminate\Support\Collection:
$collection = collect([1, 2, 3, 4, 5]);Можно использовать и фабричный метод make():
$collection = Collection::make([1, 2, 3, 4, 5]);Но на практике collect() встречается чаще — он короче и всегда доступен.
Если данные приходят в формате JSON, пригодится метод fromJson():
$collection = Collection::fromJson('[1, 2, 3, 4, 5]');Однако чаще всего вы будете получать коллекции автоматически — как результат Eloquent-запросов. Метод get() на модели всегда возвращает коллекцию:
$users = User::where('is_active', true)->get(); // Уже коллекцияГлавный принцип: неизменяемость
Это самое важное, что нужно понять о коллекциях Laravel. Большинство методов коллекций не изменяют исходную коллекцию, а возвращают новую.
$original = collect([1, 2, 3]);
$filtered = $original->filter(fn ($item) => $item > 1);
// $original всё ещё [1, 2, 3]
// $filtered - новая коллекция [2, 3]Благодаря неизменяемости (иммутабельности) можно выстраивать длинные цепочки методов, не боясь случайно повредить исходные данные. Каждый метод в цепочке получает текущий набор данных, преобразует его и передаёт дальше новый экземпляр.
$result = collect([1, 2, 3, 4, 5])
->filter(fn ($item) => $item > 2) // [3, 4, 5]
->map(fn ($item) => $item * 2) // [6, 8, 10]
->values(); // [6, 8, 10] (переиндексация)Читается такое выражение как конвейер: данные поступают на вход, проходят через ряд преобразований и дают результат на выходе.
Базовые методы проверки
Прежде чем что-то преобразовывать, часто нужно просто заглянуть в коллекцию и понять, что внутри. Для этого в коллекции встроено несколько простых инструментов.
Превращение обратно в массив или JSON
Если нужно получить обычный PHP-массив, вызывайте all() или toArray():
$array = collect([1, 2, 3])->all(); // [1, 2, 3] - исходные ключи сохраняются
$array = collect([1, 2, 3])->toArray(); // [1, 2, 3] - рекурсивное преобразованиеРазница невелика: all() просто возвращает внутренний массив как есть, а toArray() рекурсивно преобразует вложенные объекты (например, модели Eloquent) в массивы. Для простых данных они работают одинаково.
Для JSON используйте toJson():
$json = collect(['name' => 'John', 'age' => 30])->toJson();
// {"name":"John","age":30}Проверка наличия элементов
Узнать, сколько элементов в коллекции, помогает count():
$count = collect([1, 2, 3])->count(); // 3А проверить, пуста коллекция или нет — isEmpty() и isNotEmpty():
collect([])->isEmpty(); // true
collect([])->isNotEmpty(); // false
collect([1])->isEmpty(); // falseЭти методы часто используют в условных конструкциях, чтобы не писать громоздкие count($collection) > 0.
Итерация
Коллекции реализуют интерфейсы, позволяющие работать с ними в цикле foreach как с обычными массивами:
$collection = collect(['one', 'two', 'three']);
foreach ($collection as $item) {
echo $item; // one, two, three
}Это делает коллекции прозрачной заменой массивов в существующем коде — можно постепенно внедрять методы коллекций там, где это удобно, не ломая старую логику.
Теперь вы умеете создавать коллекции в Laravel, знаете про их неизменяемость и владеете базовыми методами для проверки. Это фундамент. Дальше начинается самое интересное: фильтрация, трансформация и другие операции, которые превращают коллекции из удобной обёртки в настоящую машину обработки данных.
Фильтрация и поиск данных
С фильтрации начинается практически любая работа с данными. Редко когда нужны все пользователи, все заказы или все строки лога. Чаще требуется подмножество: активные пользователи, просроченные задачи, записи за последнюю неделю. Коллекции Laravel дают несколько инструментов для такой выборки, и выбор между ними диктуется конкретной задачей.
Массовая фильтрация: когда нужно оставить много элементов
Если из коллекции Laravel требуется отобрать все элементы, удовлетворяющие условию, используется filter(). Он принимает замыкание, которое получает каждый элемент, и оставляет только те, для которых замыкание вернуло true.
$users = collect([
['name' => 'John', 'is_active' => true],
['name' => 'Mary', 'is_active' => true],
['name' => 'Peter', 'is_active' => false],
]);
$activeUsers = $users->filter(fn ($user) => $user['is_active']);Если вызвать filter() без аргументов, он отбросит все «пустые» значения: null, false, 0, пустые строки и массивы. Это удобно для быстрой очистки:
$clean = collect(['John', '', null, 'Doe', false])->filter();
// ['John', 'Doe']Иногда удобнее мыслить наоборот: не «оставить те, которые подходят», а «убрать те, которые не подходят». Для этого существует reject() — полная противоположность filter().
$inactiveUsers = $users->reject(fn ($user) => $user['is_active']);
// Останется только PeterОба метода возвращают новую коллекцию и не трогают исходную.
Точечный поиск: когда нужен один элемент
Когда требуется не множество, а только первый или последний подходящий элемент, на помощь приходят методы first() и last().
$users = collect([
['name' => 'John', 'age' => 15],
['name' => 'Mary', 'age' => 20],
['name' => 'Peter', 'age' => 30],
]);
$firstAdult = $users->first(fn ($user) => $user['age'] >= 18);
// ['name' => 'Mary', 'age' => 20]
$lastAdult = $users->last(fn ($user) => $user['age'] >= 18);
// ['name' => 'Peter', 'age' => 30]Без аргументов first() и last() возвращают просто первый или последний элемент коллекции — независимо от условий.
Если вы уверены, что элемент должен быть, и его отсутствие — критическая ошибка, используйте firstOrFail(). При пустом результате метод выбросит исключение Illuminate\Support\ItemNotFoundException.
$admin = $users->firstOrFail(fn ($user) => $user['role'] === 'admin');
// Если админа нет - скрипт упадёт с исключениемФильтрация по значениям полей
Для коллекций из однотипных элементов (например, записей из базы) часто нужно фильтровать по конкретному полю. Методы семейства where делают это лаконично.
Простое совпадение:
$filtered = $users->where('age', 20);
// Оставит Mary и George, у которых age = 20С оператором сравнения:
$adults = $users->where('age', '>=', 18);Если значений для фильтрации несколько, пригодится whereIn():
$selectedAges = $users->whereIn('age', [20, 30]);А для диапазона — whereBetween():
$young = $users->whereBetween('age', [18, 25]);Фильтрация по ключам ассоциативных массивов
Когда данные приходят в виде ассоциативного массива (с кучей служебных полей, как у пользователя), часто нужно оставить только определённые ключи или, наоборот, исключить ненужные.
Метод only() оставляет только указанные ключи:
$user = collect([
'id' => 1,
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => 'secret',
'api_token' => 'xxx',
]);
$publicData = $user->only(['name', 'email']);
// ['name' => 'John Doe', 'email' => 'john@example.com']Метод except() делает обратное — удаляет указанные ключи:
$safeData = $user->except(['password', 'api_token']);
// Останутся id, name, emailОба метода незаменимы, когда нужно подготовить данные для отправки в ответе API или передать в представление, случайно не «светя» внутреннюю информацию.
Освоив фильтрацию, переходим к следующему шагу — научиться эти данные преобразовывать.
Трансформация данных
Фильтрация отвечает на вопрос «что оставить». Трансформация — на вопрос «во что превратить». Это, пожалуй, самая востребованная операция после выборки: привести имена к верхнему регистру, сформировать полное имя из частей, превратить массив в коллекцию объектов, извлечь одно поле из каждого элемента.
Простое преобразование каждого элемента: map
Метод map() — главный инструмент трансформации. Он проходит по каждому элементу коллекции, передаёт его в замыкание и собирает результаты в новую коллекцию.
$numbers = collect([1, 2, 3, 4]);
$squared = $numbers->map(fn ($n) => $n * $n);
// [1, 4, 9, 16]Количество элементов сохраняется, но каждый из них преобразован.
Особенно удобен map(), когда нужно превратить сырые данные в объекты:
$userArrays = collect([
['name' => 'John', 'email' => 'john@example.com'],
['name' => 'Mary', 'email' => 'mary@example.com'],
]);
$userObjects = $userArrays->map(fn ($data) => new User($data));
// Коллекция объектов UserПреобразование с изменением структуры
Иногда нужно не просто преобразовать каждое значение, а изменить саму структуру коллекции. Например, из списка пользователей построить ассоциативный массив, где ключом является ID пользователя.
Для этого существует метод mapWithKeys(). Он ожидает, что замыкание вернёт пару [ключ => значение] для каждого элемента.
$users = collect([
['id' => 1, 'name' => 'John'],
['id' => 2, 'name' => 'Mary'],
['id' => 3, 'name' => 'Peter'],
]);
$keyed = $users->mapWithKeys(fn ($user) => [$user['id'] => $user['name']]);
// [1 => 'John', 2 => 'Mary', 3 => 'Peter']Этот приём часто используют для подготовки данных в форму, где нужен массив id => имя для выпадающего списка.
Сведение коллекции в одно значение
Бывает, что из множества элементов нужно получить один итог: сумму всех цен, самый большой элемент, строку, склеенную из кусочков. Для таких задач существует метод reduce(). Он принимает замыкание и начальное значение, а затем «накапливает» результат, проходя по элементам один за другим.
Классический пример — подсчёт общей суммы заказа:
$items = collect([
['product' => 'Ноутбук', 'price' => 1000],
['product' => 'Мышь', 'price' => 50],
['product' => 'Коврик', 'price' => 20],
]);
$total = $items->reduce(fn ($carry, $item) => $carry + $item['price'], 0);
// 1070Здесь $carry — это «накопленное значение». Начинается с 0, на первом элементе становится 1000, на втором — 1050, на третьем — 1070.
reduce() гораздо мощнее, чем может показаться на первый взгляд. С его помощью можно собрать из коллекции массив, отфильтровать элементы (хотя для этого есть специальные методы), найти максимум или минимум. По сути, reduce() — это швейцарский нож для любых операций, где множество элементов нужно свести к единому итогу.
// Поиск максимального значения
$numbers = collect([3, 7, 2, 5]);
$max = $numbers->reduce(fn ($carry, $item) => max($carry, $item), 0);
// 7
// Конкатенация строк
$words = collect(['Hello', 'world', '!']);
$sentence = $words->reduce(fn ($carry, $item) => $carry . ' ' . $item, '');
// " Hello world !" (лишний пробел в начале легко убрать trim'ом)Метод reduce() требует некоторого привыкания, но после освоения многие циклические алгоритмы с ним записываются короче и прозрачнее.
Извлечение значений
Часто требуется собрать значения одного поля со всех элементов коллекции. Например, получить список email-адресов всех пользователей. Для этого идеально подходит метод pluck().
$users = collect([
['name' => 'John', 'email' => 'john@example.com'],
['name' => 'Mary', 'email' => 'mary@example.com'],
]);
$emails = $users->pluck('email');
// ['john@example.com', 'mary@example.com']pluck() умеет работать и с вложенными полями через «точку»:
$users = collect([
['name' => 'John', 'profile' => ['city' => 'New York']],
['name' => 'Mary', 'profile' => ['city' => 'London']],
]);
$cities = $users->pluck('profile.city');
// ['New York', 'London']Если передать второй аргумент, pluck() использует значение указанного поля как ключ результирующей коллекции:
$users->pluck('name', 'id');
// [1 => 'John', 2 => 'Mary', 3 => 'Peter'] (при условии, что id есть в данных)Теперь вы умеете не только отбирать нужные элементы, но и преобразовывать их в любой формат, а также сворачивать множество в итоговое значение. Следующий шаг — научиться делать код надёжнее, проверяя типы данных до того, как они попадут в логику приложения.
Безопасность данных
Когда данные приходят из внешних источников — API, загруженных файлов, пользовательского ввода — на их типы полагаться нельзя. Вчера API присылал цену числом, а сегодня присылает строкой с пробелами. В таком коде рано или поздно что-то падает с невнятной ошибкой.
Метод ensure() появился в коллекциях Laravel версии 10 как раз для таких случаев. Он проверяет, что все элементы коллекции имеют ожидаемый тип, и если нет — выбрасывает понятное исключение.
Проверка примитивов
Допустим, вы ждёте коллекцию идентификаторов, которые должны быть целыми числами. Проверить это проще простого:
$ids = collect([1, 2, 3, 4]);
$ids->ensure('int'); // Ок, всё хорошоЕсли же внутри затесалась строка, метод громко сообщит об ошибке:
$ids = collect([1, 2, '3', 4]);
$ids->ensure('int');
// UnexpectedValueException: Collection should only include 'int' items, but 'string' found.Можно проверять и другие примитивы: string, float, bool, array.
$prices = collect([19.99, 25.00, '29.99']);
$prices->ensure('float'); // Упадёт, потому что третье значение - строкаПроверка объектов и интерфейсов
ensure() работает не только с примитивами, но и с классами. Если коллекция должна содержать только объекты определённого типа — укажите класс:
use App\Models\User;
$users = collect([new User(), new User(), new User()]);
$users->ensure(User::class); // ОкНо если кто-то случайно добавил в коллекцию пост вместо пользователя:
use App\Models\User;
use App\Models\Post;
$mixed = collect([new User(), new Post(), new User()]);
$mixed->ensure(User::class); // Упадёт с исключениемПолезна проверка на интерфейсы. Она гарантирует, что все объекты в коллекции реализуют нужный контракт:
use Illuminate\Contracts\Auth\Authenticatable;
$users = collect([new User(), new User()]); // User implements Authenticatable
$users->ensure(Authenticatable::class); // ОкНесколько допустимых типов
Иногда коллекция может содержать элементы разных, но строго определённых типов. Например, массив, в котором могут быть либо целые числа, либо числа с плавающей точкой. ensure() принимает массив разрешённых типов:
$numbers = collect([1, 2, 3.5, 4, 5.7]);
$numbers->ensure(['int', 'float']); // ОкТо же с объектами:
$items = collect([new User(), new Admin(), new User()]);
$items->ensure([User::class, Admin::class]); // Ок, если Admin тоже допустимВажные ограничения
ensure() проверяет только те элементы, которые уже есть в коллекции на момент вызова. Если после проверки добавить в коллекцию что-то новое, метод не защитит от этого:
$collection = collect([1, 2, 3]);
$collection->ensure('int'); // Проверка пройдена
$collection->push('not a number'); // Упс... теперь в коллекции строка
// Но исключения не будет - ensure() уже отработалПоэтому вызывать ensure() имеет смысл после того, как коллекция полностью сформирована, непосредственно перед тем, как начать её использовать.
Когда это реально нужно
Пример обработки ответа внешнего API:
$response = $api->getProducts();
$products = collect($response['data']);
// До того как начать маппить и фильтровать, проверяем структуру
$products->ensure(fn ($item) =>
isset($item['id']) &&
isset($item['price']) &&
is_int($item['id']) &&
is_numeric($item['price'])
);
$total = $products->sum('price'); // Работаем спокойно - типы гарантированыБез такой проверки ошибка проявится где-то глубоко в логике, и трейсить её придётся долго. С ensure() проблема отлавливается сразу, на входе, и с понятным сообщением.
Конечно, ensure() не заменяет качественных тестов и строгой типизации в коде. Но это отличный защитный слой для пограничных ситуаций, когда данные приходят извне и нужно быть уверенным, что они не поломают логику.
Когда код защищён от неожиданных типов, можно двигаться дальше — к работе с большими объёмами.
Работа с большими данными: Lazy Collection
До сих пор мы говорили об обычных коллекциях Laravel. Они удобны, но у них есть фундаментальное ограничение: все элементы загружаются в память сразу. Для тысячи записей это незаметно. Для ста тысяч — уже проблема. Для миллиона — фатально.
Lazy Collection (ленивые коллекции) решают эту проблему кардинально: они загружают элементы тогда, когда они реально нужны, и по одному.
Обычная коллекция — это как чтение всей книги сразу: вы держите в руках тысячу страниц. Ленивая коллекция — как чтение по одной странице: вы открываете следующую только тогда, когда дошли до неё.
При отсутствии опыта работы с yield пугаться не стоит — в большинстве случаев используются готовые методы Laravel, а не ручное написание генераторов.
use Illuminate\Support\LazyCollection;
LazyCollection::make(function () {
$handle = fopen('very-large-file.csv', 'r');
while (($line = fgets($handle)) !== false) {
yield str_getcsv($line);
}
})->each(function ($row) {
// Обработка каждой строки по очереди
});Этот код читает огромный CSV-файл, но в памяти одновременно находится только одна строка.
Самый частый сценарий: работа с базой данных
В реальных проектах чаще всего ленивые коллекции используют для обработки больших результатов запросов. Вместо привычного get() вызывают cursor():
// Плохо для миллиона записей
$users = User::all(); // Все пользователи в памяти!
// Хорошо
$users = User::cursor(); // LazyCollection
foreach ($users as $user) {
// Здесь обрабатывается один пользователь
// Следующий подгрузится только когда дойдём до него
}cursor() выполняет запрос к базе и возвращает результат порциями, используя курсоры MySQL/PostgreSQL. Память занята ровно настолько, сколько нужно для обработки одной записи.
Комбинация с обычными методами
Ленивая коллекция поддерживает большинство методов, которые есть в обычных коллекциях: map, filter, chunk и другие. Но с одной важной особенностью — преобразования выполняются лениво.
$payments = Payment::where('status', 'accepted')
->cursor()
->map(fn ($payment) => $this->enrichPaymentData($payment))
->filter(fn ($payment) => $payment->amount > 1000);
// На этом этапе **ни одного** запроса к базе ещё не было!
// И ни одного обогащения данных не произошло.
foreach ($payments as $payment) {
// Вот теперь, внутри цикла, данные реально подтягиваются
// и обрабатываются по одному элементу
}Реальный пример: обработка лог-файла
Задача: проанализировать многогигабайтный лог-файл, выбрать все ошибки и сохранить их в базу:
LazyCollection::make(function () {
$handle = fopen('storage/logs/laravel.log', 'r');
while (($line = fgets($handle)) !== false) {
yield $line;
}
})
->map(fn ($line) => json_decode($line, true))
->filter(fn ($log) => $log && ($log['level'] ?? '') === 'error')
->chunk(100)
->each(function ($chunk) {
// Вставляем ошибки в базу пачками по 100
DB::table('parsed_errors')->insert($chunk->toArray());
});Что происходит:
- Файл открывается, но ничего не читается.
- Определяются преобразования:
map(декодинг JSON),filter(только ошибки),chunk(разбить на пачки). - В момент
eachначинается реальное чтение. - Прочиталась одна строка → декодировали → проверили, ошибка ли → если да, положили в текущий чанк.
- Когда чанк набрал 100 элементов — вставили в базу.
- И так до конца файла.
Память при таком подходе практически не растёт, а файл может быть любого размера — хоть 50 гигабайт.
Когда НЕ стоит использовать Lazy Collection
У ленивых коллекций есть обратная сторона — накладные расходы на организацию «ленивости». Для маленьких наборов данных они медленнее обычных коллекций. Если у вас гарантированно не больше пары тысяч записей — берите обычную коллекцию, она будет быстрее.
Простое правило:
- Данные влезают в память с запасом → обычная коллекция.
- Данные большие или могут стать большими в будущем → ленивая коллекция.
Теперь, когда мы умеем обрабатывать любые объёмы данных, остался последний шаг — научиться расширять коллекции своими методами, когда встроенных не хватает.
Расширение возможностей: создание собственных методов коллекций
Коллекции Laravel содержат десятки методов на все случаи жизни. Но рано или поздно возникает ситуация, когда встроенного метода не хватает, а писать одну и ту же цепочку вызовов в каждом месте становится утомительно.
Laravel позволяет добавлять собственные методы в класс коллекции через механизм макросов. Это не наследование, а именно добавление методов во встроенный класс, поэтому ваши методы будут доступны везде, где используются коллекции — и в базовых, и в Eloquent-коллекциях.
Простой макрос
Частая задача — приводить строки в коллекции к «slug»-формату для URL. Вместо того чтобы писать ->map(fn ($item) => Str::slug($item)) каждый раз, можно создать макрос toSlug.
Макросы обычно регистрируются в сервис-провайдере, например в AppServiceProvider:
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
public function boot(): void
{
Collection::macro('toSlug', function () {
return $this->map(fn ($value) => Str::slug($value, '-'));
});
}После этого метод доступен во всех коллекциях:
$titles = collect(['Hello World', 'PHP is great', 'Laravel Collections']);
$slugs = $titles->toSlug();
// ['hello-world', 'php-is-great', 'laravel-collections']Обратите внимание на $this внутри замыкания. Он ссылается на текущий экземпляр коллекции — так же, как если бы метод был написан внутри самого класса.
Макросы с аргументами
Макросы могут принимать аргументы как обычные методы. Например, метод для форматирования цен с разными валютами:
Collection::macro('formatMoney', function (string $currency = '$') {
return $this->map(fn ($amount) => $currency . number_format($amount, 2));
});
// Использование
$prices = collect([19.99, 25.00, 149.50]);
$formatted = $prices->formatMoney('€');
// ['€19.99', '€25.00', '€149.50']Когда создавать макросы
Макросы оправданы, когда:
- Одна и та же цепочка методов повторяется в нескольких местах. Вынесли один раз — забыли о дублировании.
- Операция специфична для вашего проекта. Например, «подготовить данные для экспорта в Excel» или «нормализовать телефонные номера под российский формат».
- Хочется сделать код выразительнее. Иногда собственное имя метода лучше описывает намерение, чем комбинация стандартных.
Но не стоит создавать макросы для одноразовых операций или для того, что можно решить обычной функцией.
Важное ограничение
Макросы добавляются глобально. Если вы определили макрос в провайдере, он будет доступен во всем приложении, включая сторонние пакеты. Это удобно, но требует осторожности: имя макроса не должно конфликтовать с существующими или будущими методами Laravel.
В официальной документации есть полный список доступных методов — сверяйтесь с ним, прежде чем добавить свой.
Теперь вы умеете не только использовать готовые инструменты, но и создавать свои там, где готовых не хватает. Осталось связать всё воедино и поговорить о том, что отличает коллекции Eloquent от обычных.
Надстройка для Eloquent: что умеют только коллекции моделей
В самом начале мы договорились: Illuminate\Database\Eloquent\Collection наследует Illuminate\Support\Collection, поэтому все методы коллекций Laravel, которые мы разобрали, работают и с наборами моделей. Но у Eloquent-коллекций есть собственная надстройка — методы, созданные специально для работы с моделями и их связями.
Если вы работаете с результатами запросов Model::all() или Model::where(...)->get(), вы имеете дело именно с Illuminate\Database\Eloquent\Collection. И следующие методы сделают жизнь заметно проще.
Жадная загрузка на лету: load()
Представьте, что у вас есть коллекция пользователей, и вам нужно вывести их посты. Если связи не были загружены заранее, каждый вызов $user->posts породит отдельный запрос к базе. Это классическая проблема N+1.
Метод load() решает её мгновенно: он подгружает указанные связи для всех моделей в коллекции одним дополнительным запросом.
$users = User::all(); // Пользователи есть, посты не загружены
// Плохо: в цикле будет N+1 запрос
foreach ($users as $user) {
echo $user->posts->count(); // Каждая итерация дёргает базу
}
// Хорошо: грузим посты для всех сразу
$users->load('posts');
foreach ($users as $user) {
echo $user->posts->count(); // Все посты уже здесь
}load() умеет загружать и вложенные связи, и даже применять условия:
$users->load(['posts' => fn ($query) => $query->where('published', true)]);Если нужно загрузить связи только для тех моделей, у которых они ещё не загружены, используйте loadMissing() — он не будет выполнять лишних запросов.
Поиск модели внутри коллекции: find()
У вас уже есть коллекция пользователей. Вам нужно найти в ней конкретного по ID. Вместо того чтобы писать filter() или обходить циклом, используйте find():
$users = User::all();
$user = $users->find(1); // Вернёт модель с ID = 1 или nullЕсли уверены, что модель должна быть, и её отсутствие — ошибка, берите findOrFail():
$user = $users->findOrFail(999); // Выбросит ModelNotFoundExceptionЭти методы работают только с первичным ключом модели. Для поиска по другим полям по-прежнему нужны filter() или firstWhere().
Проверка наличия: contains()
Проверить, есть ли в коллекции модель с определённым ID или конкретный экземпляр модели, можно через contains():
$users = User::all();
if ($users->contains(1)) {
// Пользователь с ID 1 есть в коллекции
}
$specificUser = User::find(1);
if ($users->contains($specificUser)) {
// Этот конкретный экземпляр есть в коллекции
}Получение ключей: modelKeys()
Иногда нужны только ID всех моделей в коллекции. modelKeys() вернёт простой массив первичных ключей:
$users = User::where('active', true)->get();
$activeUserIds = $users->modelKeys(); // [1, 2, 5, 7, ...]Работа со «скрытыми» полями
В моделях Eloquent можно скрывать атрибуты через свойство $hidden. Но иногда в конкретном месте нужно временно показать скрытое или, наоборот, спрятать видимое. Методы makeVisible() и makeHidden() делают это для всей коллекции:
$users = User::all();
// Временно показываем email для всех пользователей в коллекции
$users->makeVisible(['email']);
// Временно прячем пароль
$users->makeHidden(['password']);Если нужно не заменить список видимых атрибутов, а дополнить его, используйте mergeVisible() и mergeHidden().
А setVisible() и setHidden() полностью переопределяют списки видимых/скрытых атрибутов для всех моделей в коллекции. withoutAppends() временно убирает все добавленные (appended) атрибуты.
Превращение коллекции в запрос: toQuery()
Самый мощный метод. Он берет коллекцию моделей и создаёт запрос, который ограничен только этими моделями (через whereIn по первичным ключам).
$vipUsers = User::where('status', 'vip')->get();
// Одним запросом обновить всех VIP-пользователей
$vipUsers->toQuery()->update(['discount' => 20]);Это избавляет от ручного сбора ID и написания whereIn. Особенно полезно, когда коллекция сформирована сложными условиями, и повторять их в запросе на обновление было бы накладно.
Собственные коллекции для моделей
Если стандартных методов Eloquent-коллекций не хватает, можно создать свой класс, унаследованный от Illuminate\Database\Eloquent\Collection, и указать Laravel использовать его для конкретной модели.
Самый простой способ в Laravel 12 — атрибут #[CollectedBy]:
use App\Support\UserCollection;
use Illuminate\Database\Eloquent\Attributes\CollectedBy;
#[CollectedBy(UserCollection::class)]
class User extends Model
{
// ...
}Или, если версия Laravel 11.x и младше, переопределите метод newCollection():
public function newCollection(array $models = [])
{
return new UserCollection($models);
}После этого любые запросы к модели User будут возвращать коллекцию вашего класса, куда можно добавить любую специфическую логику.
Мы прошли путь от создания простых коллекций до работы с миллионами записей и расширения стандартных возможностей. В финале осталось только собрать всё вместе и понять, как этот инструмент меняет стиль программирования.
Заключение
Коллекции в Laravel — не просто удобная обёртка для массивов, а совершенно иной способ мышления об обработке данных. Цепочка ->filter()->map()->reduce() описывает не циклы и временные переменные, а чистое, декларативное и понятное преобразование данных.
В этом руководстве мы прошли путь от основ до продвинутых техник:
- Начали с понимания неизменяемости и базовых методов проверки.
- Научились отбирать нужные элементы разными способами — от массовой фильтрации до точечного поиска.
- Освоили трансформацию данных через
map,mapWithKeysи могучийreduce. - Добавили защитный слой с
ensure(), чтобы отлавливать проблемы с типами на входе. - Разобрались с ленивыми коллекциями, которые позволяют обрабатывать данные любого объёма, не переполняя память.
- Расширили стандартные возможности через макросы.
- И наконец, заглянули в специфические методы Eloquent-коллекций, которые работают с моделями и их связями.
Коллекции — одна из тех тем, где теория без практики быстро выветривается. Попробуйте в ближайших задачах сознательно заменять циклы на методы коллекций. Начните с простого: там, где раньше был foreach с условием, напишите ->filter(). Там, где собирали массив в цикле — используйте map() или reduce().
Через неделю код станет короче, а работа с коллекциями — естественной.
Если хотите по-настоящему прокачаться в работе с коллекциями, вот три статьи, которые уводят вглубь:
- Понимание операции сведения в Коллекциях Laravel —
reduce()заслуживает отдельного разговора. Это швейцарский нож, которым можно заменить половину остальных методов, если понять его философию. - Проверка типов данных в Коллекциях Laravel с помощью метода
ensure()— мы лишь коснулись этой темы. В отдельной статье разобраны граничные случаи, работа с глубокой вложенностью и производительность. - Оптимизация обработки больших массивов данных с Lazy Collection — примеры из реальных проектов, профилирование памяти и сравнение подходов.
Коллекции Laravel — это инструмент, который растёт вместе с вами. Сначала вы просто используете готовые методы. Потом начинаете комбинировать их в цепочки. А затем создаёте свои макросы и пишете код, который читается как спецификация, а не как инструкция для машины.
Именно в этот момент вы перестаёте быть просто пользователем фреймворка и становитесь мастером, который заставляет данные танцевать под свою дудку.