---
title: "Laravel 13.30: новий метод chunkBy() для колекцій та посилення безпеки Storage::path()"
url: https://laravelukraine.com/blog/laravel-1330-novii-metod-chunkby-dlia-kolekcii-ta-posilennia-bezpeki-storagepath
date: 2026-09-03
source: https://laravel-news.com/laravel-13-30-0?utm_medium=feed&utm_source=feedpress.me&utm_campaign=Feed%3A+laravelnews
---

# Laravel 13.30: новий метод chunkBy() для колекцій та посилення безпеки Storage::path()

Реліз Laravel 13.30 приніс кілька важливих нововведень: метод `chunkBy()` для групування сусідніх елементів колекцій, посилення безпеки `Storage::path()` через нормалізацію шляхів, виведення причини зупинки queue worker у консолі, а також низку інших покращень та виправлень помилок.

## Метод chunkBy() для колекцій

Раніше для розбиття колекції на частини за певною ознакою використовувався метод `chunkWhile()`, де найчастіше доводилося порівнювати поточний елемент з останнім елементом поточного chunk:

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

Новий метод `chunkBy()` спрощує цей паттерн. Він приймає ключ або callback і починає новий chunk щоразу, коли значення змінюється:

```php
$products->chunkBy('parent');
collect([1, 1, 2, 2, 1, 1])->chunkBy(fn ($value) => $value);
// [[1, 1], [2, 2], [1, 1]]
```

Ключ обробляється через `data_get()`, тому працює dot-нотація (наприклад, `chunkBy('address.city')`), а оригінальні ключі зберігаються всередині кожного chunk. Метод доступний як для звичайних, так і для lazy колекцій.

## Посилення безпеки Storage::path()

Виявлено серйозну вразливість: усі методи файлової системи, крім одного, проходили через Flysystem драйвер, який нормалізує шлях і викидає `PathTraversalDetected` при спробі вийти за межі кореневої директорії диска. Метод `Storage::path()` пропускав цей крок:

```php
Storage::get('../../../.env');  // відхилено
Storage::path('../../../.env'); // повертав шлях за межами диска
```

На стандартному local диску `path()` повертав нативний шлях до `.env` додатка, тоді як `get()`, `delete()` та `readStream()` відхиляли той самий аргумент. Це створювало проблему безпеки, особливо коли шлях надходив від користувача:

```php
response()->download(Storage::path($request->query('path')));
```

Тепер `path()` пропускає аргумент через `WhitespacePathNormalizer` - той самий нормалізатор, що використовується для всіх інших викликів. Відносні сегменти розв'язуються так, як їх розв'язує драйвер, а все, що виходить за межі кореня, викидає `PathTraversalDetected`. Код, що покладався на прийняття `..` сегментів методом `path()`, тепер викидатиме виключення.

## Виведення причини зупинки queue worker

Раніше worker, що завершував роботу, не залишав жодних слідів у консолі про причину виходу. Подія `WorkerStopping` містила `WorkerStopReason`, але для його читання потрібно було реєструвати listener, що вимагало багато налаштувань для питання, яке зазвичай виникає при перегляді терміналу чи логів.

Тепер `queue:work` виводить причину як останній рядок:

```
2026-09-01 13:20:40 Worker STOPPED Memory limit exceeded
```

З прапорцем `--json` інформація виводиться як структурований запис поряд із виводом для кожного job:

```json
{"level":"warning","status":"stopped","reason":"memory","exit_code":12,"jobs_processed":2,"memory":1.2,"timestamp":"2026-09-01T13:20:40.118273+00:00"}
```

Enum `WorkerStopReason` отримав метод `description()` для людиночитаних рядків, що охоплює дев'ять сценаріїв виходу: перервано, втрачено з'єднання, перевищено максимум jobs, перевищено ліміт пам'яті, перевищено максимальний час роботи, черга порожня, черга порожня протягом налаштованого часу, отримано сигнал перезапуску та час виконання job вичерпано. При використанні `--quiet` або `--silent` нічого не виводиться.

## Відключення vendor та стандартних dev команд

`DevCommands` реєструє все, про що знає: стандартні команди фреймворку (`serve`, `queue:listen`, `pail` та Vite скрипт), будь-що з пакетів у `vendor` та команди додатка. Для звуження цього списку потрібно було вказувати всі потрібні команди через `only()` або небажані через `except()`. Перший підхід ламався при додаванні черги, другий - коли залежність починала реєструвати щось нове.

Два нові методи фільтрують за походженням команди:

```php
use Illuminate\Foundation\DevCommands;
DevCommands::withoutVendorCommands();  // прибрати все з vendor/
DevCommands::withoutDefaultCommands(); // прибрати стандартні команди фреймворку
```

Обидва методи є opt-in і залишають команди додатка недоторканими. `DevCommands` уже відстежував походження кожної команди як пріоритет (default, vendor або userland) для вирішення конфліктів імен; ці методи фільтрують за тим самим значенням.

## Нативні DSN рядки підключення SQL Server

Парсер database URL у Laravel припускав URL. Нативний рядок підключення PDO_SQLSRV не є URL:

```
sqlsrv:Server=127.0.0.1,1433;Database=example;Encrypt=true;TrustServerCertificate=true
```

`parse_url()` читав все це як шлях, Laravel видаляв перший символ, і назва бази повертались як `erver=127.0.0.1,1433`. Результатом було підключення з пошкодженою конфігурацією.

Тепер нативні `sqlsrv:` DSN виявляються перед парсингом URL і передаються спеціалізованому парсеру, який витягує хост, порт, базу даних та підтримувані опції PDO_SQLSRV (`Encrypt`, `TrustServerCertificate`, `MultiSubnetFailover`, `ApplicationIntent`, `LoginTimeout` тощо), відображаючи їх на ключі конфігурації Laravel. Значення, що містять крапки з комою, закриваючі дужки або пробіли, екрануються при відновленні рядка підключення. Власна конвенція Laravel `sqlsrv://user:pass@host:port/database` продовжує працювати через існуючий URL парсер.

## Отримання однієї Artisan команди

Artisan команди розв'язуються ліниво, але не було способу отримати одну за іменем без втрати цієї переваги. `Artisan::all()` конструює всі зареєстровані команди для побудови масиву:

```php
// Конструює всі команди
$command = Artisan::all()['app:my-command'] ?? null;
// Конструює лише MyCommand, якщо вона зареєстрована
$command = Artisan::commandNamed('app:my-command');
```

Метод `commandNamed()` повертає екземпляр команди або `null`.

## Підсумки для Cloud Queue

Методи `totalPendingSize()`, `totalDelayedSize()` та `totalReservedSize()`, додані в 13.27, повертають `0` на SQS, оскільки SQS не може перелічити черги, які використовує підключення. Laravel Cloud queue може це робити: він уже знає, якими чергами управляє, тому три методи тепер реалізовані там шляхом підсумовування через `managedQueues()`:

```php
use Illuminate\Support\Facades\Cloud;
Cloud::queue()->totalReservedSize();
```

## Інші виправлення та покращення

- `route:cache` завантажував другий, тимчасовий додаток для збору чистої копії маршрутів, і bootstrapping переспрямовував глобальний facade application на той контейнер без відновлення. Під `php artisan optimize` це призводило до `LogicException: Route is not bound.` Тепер команда відновлює facade application одразу після bootstrap.

- `Request::clamp()` повертав 500 для введення, яке не міг обмежити. Нечислові значення тепер проходять до значення за замовчуванням, яке потім обмежується min та max.

- `db:seed --database=mysql` перемикає стандартне підключення і повертає його після завершення seeder. Виключення пропускало відновлення. Тепер перемикання обгорнуте в `try`/`finally`.

- `setVisibility()` на read-through диску торкався лише primary. Тепер розв'язується через `readerFor()`, як `getVisibility()`, `mimeType()` та `fileSize()`.

- `Queue::createPayloadUsing()` проходив через `QueueManager::__call()`, що викликало помилку при реєстрації callback у service provider. Метод тепер визначений безпосередньо на manager.

- Tooltips на сторінці виключень рендеряться з `allowHTML: false` за замовчуванням. Власний syntax-highlighted tooltip фреймворку вмикає HTML через окремий атрибут.

- JSON:API ресурси відстежують відносини, запитані відносно кожного включеного ресурсу, тому вкладені `include` шляхи розв'язуються на правильному рівні.

- `spl_object_id()` замінює `spl_object_hash()` у контейнері, Eloquent builder, global scopes та `Onceable`, портовано з 14.x для очищення deprecation warnings.

- `Handler::contextForException()` публічно експонує контекст логування виключення, а `BladeMapper::findCompiledView()` та `detectLineNumber()` стали публічними.

- Cloud events transport повертає boolean з `emit()` та `emitMany()`, отримав `$socketFactory` хук для тестування та реєструється під час bootstrap exception-handler.

- Виправлення квадратичної експансії wildcard правил у `ValidationRuleParser::explodeWildcardRules()` портовано до 12.x.
