---
title: "Групування сусідніх елементів колекції в Laravel за допомогою chunkBy()"
url: https://laravelukraine.com/blog/grupuvannia-susidnix-elementiv-kolekciyi-v-laravel-za-dopomogoiu-chunkby
date: 2026-09-04
source: https://laravel-news.com/laravel-collection-chunk-by?utm_medium=feed&utm_source=feedpress.me&utm_campaign=Feed%3A+laravelnews
---

# Групування сусідніх елементів колекції в Laravel за допомогою chunkBy()

Laravel 13.30 представляє метод `chunkBy()`, який є зручним скороченням для найпоширенішого варіанту використання `chunkWhile()`:

```php
$products->chunkWhile(fn ($value, $key, $chunk) => $value->parent == $chunk->last()->parent);
```

Тепер це можна написати простіше:

```php
$products->chunkBy('parent');
```

## До цього: chunkWhile() та порівняння

Як показано вище, до Laravel 13.30 можна було використовувати `chunkWhile()` для цього завдання. Цей метод приймає callback, який отримує поточне значення, його ключ та chunk, що формується. Він починає новий chunk щоразу, коли callback повертає false:

```php
$lineItems->chunkWhile(
    fn ($value, $key, $chunk) => $value->order_id == $chunk->last()->order_id
);
```

Цікава частина цього рядка - одне слово (`order_id`), приховане в порівнянні між значенням та `$chunk->last()`. Метод `chunkBy()` приймає ключ або callback і будує порівняння за вас:

```php
$lineItems->chunkBy('order_id');
$lineItems->chunkBy(fn ($item) => $item->order_id);
```

Ключ обробляється через `data_get()`, тому точкова нотація дозволяє доступ до вкладених масивів та об'єктів:

```php
$users->chunkBy('address.city');
```

## Сусідні, а не згруповані

Відмінність від `groupBy()` - це ключовий момент для розуміння, оскільки методи повертають однакову структуру і відрізняються лише порядком ваших даних:

```php
collect([1, 1, 2, 2, 1, 1])->chunkBy(fn ($v) => $v);
// [[1, 1], [2, 2], [1, 1]]

collect([1, 1, 2, 2, 1, 1])->groupBy(fn ($v) => $v);
// [1 => [1, 1, 1, 1], 2 => [2, 2]]
```

Метод `chunkBy()` створює три chunks, оскільки дві послідовності `1` не знаходяться поруч. Це не баг, а властивість, яка робить метод ефективним. Якщо несусідні елементи з однаковим значенням мають опинитися разом, дані не відсортовані так, як потрібно `chunkBy()` - спочатку відсортуйте їх або використовуйте `groupBy()`.

Ключі зберігаються всередині кожного chunk:

```php
collect(['a' => 1, 'b' => 1, 'c' => 2])->chunkBy(fn ($v) => $v);
// [['a' => 1, 'b' => 1], ['c' => 2]]
```

Викличте `values()` на chunk, якщо потрібен список.

## Потокова обробка відсортованого запиту

Метод `chunkBy()` додано як до звичайних колекцій, так і до `LazyCollection`. Оскільки `chunkBy()` успадковує ліниве виконання `chunkWhile()`, на ледачій колекції він повертає кожен chunk одразу після зміни значення і ніколи не тримає в пам'яті більше одного поточного chunk.

Розгляньмо експорт CSV для кожного замовлення з таблиці з кількома мільйонами позицій. З `groupBy()` усі рядки зберігаються в пам'яті до запису першого файлу. З cursor та `chunkBy()` найвища точка використання пам'яті припадатиме на найбільше окреме замовлення:

```php
use App\Models\LineItem;
use Illuminate\Support\Facades\Storage;

LineItem::query()
    ->orderBy('order_id')
    ->orderBy('id')
    ->cursor()
    ->chunkBy('order_id')
    ->each(function ($items) {
        $orderId = $items->first()->order_id;
        Storage::disk('exports')->put(
            "orders/{$orderId}.csv",
            $items->map(fn ($item) => implode(',', [
                $item->sku,
                $item->quantity,
                $item->unit_price,
            ]))->implode(PHP_EOL)
        );
    });
```

Важливо: `orderBy('order_id')` - це не прикраса, а контракт, на якому працює `chunkBy()`. База даних виконує сортування за індексом, а PHP - розділення, по одному рядку за раз.

Та сама структура працює з лог-файлом:

```php
use Illuminate\Support\LazyCollection;

LazyCollection::make(function () {
    $handle = fopen(storage_path('logs/laravel.log'), 'r');
    while (($line = fgets($handle)) !== false) {
        yield $line;
    }
})
->chunkBy(fn ($line) => str_contains($line, 'ERROR') ? 'error' : 'other')
->each(function ($block) {
    // Кожен блок — це послідовність рядків з помилками або без них
});
```

Або з API з пагінацією, або з генератора, що читає CSV. Скрізь, де джерело відсортоване і більше за обсяг пам'яті, `chunkBy()` перетворює групування на потокову операцію.

## Дві речі, які варто знати

**Порівняння нестроге.** Реалізація порівнює отримані значення через `==`, а не `===`:

```php
collect(['1', 1, 1.0])->chunkBy(fn ($v) => $v);
// один chunk
```

Для стовпця бази даних, що повертає послідовний тип, це не проблема. Для змішаного вводу це може об'єднати chunks, які ви очікували окремими. За потреби поверніть нормалізоване значення з callback:

```php
$rows->chunkBy(fn ($row) => (string) $row['code']);
```

Два об'єкти порівнюються як рівні, коли вони одного класу з рівними властивостями, що зазвичай є бажаною поведінкою при розбитті за об'єктом-значенням.

**Резолвер виконується двічі на елемент.** Кожна перевірка границі обробляє поточний елемент і повторно обробляє останній елемент chunk. Якщо callback дорогий, наприклад парсинг дати чи хеш, спочатку обчисліть значення:

```php
$entries
    ->map(fn ($entry) => [$entry, Carbon::parse($entry->logged_at)->toDateString()])
    ->chunkBy(fn ($pair) => $pair[1]);
```

Для простого пошуку ключа або властивості це не має значення.

## Крайні випадки

Порожня колекція повертає порожню колекцію. Один елемент повертає один chunk, що містить його. Як eager, так і lazy колекції повертають той самий клас, на якому їх викликано, тому `chunkBy()` на `LazyCollection` дає `LazyCollection` з екземплярів `LazyCollection`.

Метод додано завдяки [@JosephSilber](https://github.com/JosephSilber) у [#61357](https://github.com/laravel/framework/pull/61357).
