# Laravel Ukraine - документація Laravel українською

> Повний текст українського перекладу офіційної документації Laravel.
> Джерело: https://laravelukraine.com/docs - огляд сайту: https://laravelukraine.com/llms.txt

---
Джерело: https://laravelukraine.com/docs/13.x/releases

# Нотатки релізу

- [Схема версіонування](#versioning-scheme)
- [Політика підтримки](#support-policy)
- [Laravel 13](#laravel-13)

<a name="versioning-scheme"></a>
## Схема версіонування

Laravel та інші його офіційні пакети дотримуються [семантичного версіонування](https://semver.org). Мажорні релізи фреймворку виходять щороку (приблизно в першому кварталі), тоді як мінорні та патч-релізи можуть виходити навіть щотижня. Мінорні та патч-релізи **ніколи** не повинні містити змін, що порушують сумісність.

Посилаючись на фреймворк Laravel або його компоненти зі свого застосунку чи пакета, завжди використовуйте обмеження версії на кшталт `^13.0`, оскільки мажорні релізи Laravel таки містять зміни, що порушують сумісність. Утім, ми прагнемо завжди забезпечити можливість оновитися до нового мажорного релізу за день або швидше.

<a name="named-arguments"></a>
#### Іменовані аргументи

[Іменовані аргументи](https://www.php.net/manual/en/functions.arguments.php#functions.named-arguments) не підпадають під настанови Laravel щодо зворотної сумісності. За потреби ми можемо перейменовувати аргументи функцій, щоб покращити кодову базу Laravel. Тому використовувати іменовані аргументи, викликаючи методи Laravel, слід обачно й з розумінням, що імена параметрів можуть змінитися в майбутньому.

<a name="support-policy"></a>
## Політика підтримки

Для всіх релізів Laravel виправлення помилок надаються протягом 18 місяців, а виправлення безпеки - протягом 2 років. Для всіх додаткових бібліотек виправлення помилок отримує лише останній мажорний реліз. Крім того, перегляньте версії баз даних, [які підтримує Laravel](/docs/{{version}}/database#introduction).

<div class="overflow-auto">

| Версія  | PHP (*)   | Реліз               | Виправлення помилок до | Виправлення безпеки до |
| ------- |-----------| ------------------- | ---------------------- | ---------------------- |
| 10      | 8.1 - 8.3 | 14 лютого 2023      | 6 серпня 2024          | 4 лютого 2025          |
| 11      | 8.2 - 8.4 | 12 березня 2024     | 3 вересня 2025         | 12 березня 2026        |
| 12      | 8.2 - 8.5 | 24 лютого 2025      | 13 серпня 2026         | 24 лютого 2027         |
| 13      | 8.3 - 8.5 | 17 березня 2026     | 3 квартал 2027         | 17 березня 2028        |

</div>

<div class="version-colors">
    <div class="end-of-life">
        <div class="color-box"></div>
        <div>Кінець підтримки</div>
    </div>
    <div class="security-fixes">
        <div class="color-box"></div>
        <div>Лише виправлення безпеки</div>
    </div>
</div>

(*) Підтримувані версії PHP

<a name="laravel-13"></a>
## Laravel 13

Laravel 13 продовжує щорічний ритм релізів Laravel, зосереджуючись на робочих процесах, орієнтованих на AI, надійніших налаштуваннях за замовчуванням і виразніших API для розробників. Цей реліз містить офіційні AI-примітиви, ресурси JSON:API, можливості семантичного та векторного пошуку, а також поступові покращення в чергах, кеші й безпеці.

<a name="minimal-breaking-changes"></a>
### Мінімум змін, що порушують сумісність

Значну частину зусиль у цьому циклі релізу ми присвятили тому, щоб звести до мінімуму зміни, які порушують сумісність. Натомість ми зосередилися на тому, щоб протягом року постійно постачати покращення, які не ламають наявні застосунки.

Тому реліз Laravel 13 - відносно незначне оновлення з погляду зусиль, хоча й дає суттєві нові можливості. З огляду на це більшість застосунків Laravel можуть оновитися до Laravel 13, майже не змінюючи код застосунку.

<a name="php-8"></a>
### PHP 8.3

Laravel 13.x потребує щонайменше PHP версії 8.3.

<a name="ai-sdk"></a>
### Laravel AI SDK

Laravel 13 представляє офіційний [Laravel AI SDK](https://laravel.com/ai), що дає уніфікований API для генерації тексту, агентів із викликом інструментів, ембедингів, аудіо, зображень та інтеграцій із векторними сховищами.

За допомогою AI SDK ви можете створювати AI-можливості, незалежні від конкретного провайдера, зберігаючи водночас послідовний і природний для Laravel досвід розробки.

Наприклад, базового агента можна запитати одним викликом:

```php
use App\Ai\Agents\SalesCoach;

$response = SalesCoach::make()->prompt('Analyze this sales transcript...');

return (string) $response;
```

Laravel AI SDK також може генерувати зображення, аудіо та ембединги.

Для сценаріїв візуальної генерації SDK пропонує зрозумілий API для створення зображень із запитів звичайною мовою:

```php
use Laravel\Ai\Image;

$image = Image::of('A donut sitting on the kitchen counter')->generate();

$rawContent = (string) $image;
```

Для голосових сценаріїв ви можете синтезувати природне звучання з тексту для асистентів, начитки та можливостей доступності:

```php
use Laravel\Ai\Audio;

$audio = Audio::of('I love coding with Laravel.')->generate();

$rawContent = (string) $audio;
```

А для семантичного пошуку та сценаріїв пошуку інформації ви можете генерувати ембединги прямо з рядків:

```php
use Illuminate\Support\Str;

$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings();
```

<a name="json-api"></a>
### Ресурси JSON:API

Laravel тепер містить офіційні [ресурси JSON:API](/docs/{{version}}/eloquent-resources#jsonapi-resources), що спрощує повернення відповідей, сумісних зі специфікацією JSON:API.

Ресурси JSON:API беруть на себе серіалізацію об'єктів ресурсу, долучення зв'язків, розріджені набори полів (sparse fieldsets), посилання та заголовки відповідей, сумісні з JSON:API.

<a name="request-forgery-protection"></a>
### Захист від підробки запитів

Задля безпеки `middleware` [захисту від підробки запитів](/docs/{{version}}/csrf#preventing-csrf-requests) було вдосконалено та формалізовано як `PreventRequestForgery`, додавши перевірку запитів з урахуванням джерела (origin) і зберігши сумісність із захистом CSRF на основі токенів.

<a name="queue-routing"></a>
### Маршрутизація черг

Laravel 13 додає [маршрутизацію черг за класом](/docs/{{version}}/queues#queue-routing) через `Queue::route(...)`, що дозволяє визначити в одному місці правила маршрутизації черги чи підключення за замовчуванням для конкретних завдань:

```php
Queue::route(ProcessPodcast::class, connection: 'redis', queue: 'podcasts');
```

<a name="php-attributes"></a>
### Розширені атрибути PHP

Laravel 13 продовжує розширювати офіційну підтримку атрибутів PHP у фреймворку, роблячи типові питання конфігурації та поведінки декларативнішими й розміщеними поруч із вашими класами та методами.

Серед помітних доповнень - атрибути контролерів і авторизації, як-от [`#[Middleware]`](/docs/{{version}}/controllers#controller-middleware) та [`#[Authorize]`](/docs/{{version}}/controllers#authorization-attributes), а також засоби керування завданнями черг: [`#[Tries]`](/docs/{{version}}/queues#max-job-attempts-and-timeout), [`#[Backoff]`](/docs/{{version}}/queues#dealing-with-failed-jobs), [`#[Timeout]`](/docs/{{version}}/queues#max-job-attempts-and-timeout) і [`#[FailOnTimeout]`](/docs/{{version}}/queues#failing-on-timeout).

Наприклад, `middleware` контролера та перевірки політик тепер можна оголошувати прямо на класах і методах:

```php
<?php

namespace App\Http\Controllers;

use App\Models\Comment;
use App\Models\Post;
use Illuminate\Routing\Attributes\Controllers\Authorize;
use Illuminate\Routing\Attributes\Controllers\Middleware;

#[Middleware('auth')]
class CommentController
{
    #[Middleware('subscribed')]
    #[Authorize('create', [Comment::class, 'post'])]
    public function store(Post $post)
    {
        // ...
    }
}
```

Додаткові атрибути з'явилися також в API Eloquent, подій, сповіщень, валідації, тестування та серіалізації ресурсів, даючи вам послідовний підхід «спочатку атрибути» в більшій кількості областей фреймворку.

<a name="cache-touch"></a>
### Продовження TTL кешу

Laravel тепер містить [`Cache::touch(...)`](/docs/{{version}}/cache), що дозволяє продовжити TTL наявного елемента кешу, не отримуючи та не зберігаючи його значення заново.

<a name="semantic-search"></a>
### Семантичний та векторний пошук

Laravel 13 поглиблює підтримку семантичного пошуку завдяки нативним векторним запитам, робочим процесам з ембедингами та відповідним API, описаним у розділах [пошуку](/docs/{{version}}/search#semantic-vector-search), [запитів](/docs/{{version}}/queries#vector-similarity-clauses) та [AI SDK](/docs/{{version}}/ai-sdk#embeddings).

Ці можливості спрощують створення пошуку на основі AI за допомогою PostgreSQL і `pgvector`, зокрема пошук за схожістю щодо ембедингів, згенерованих безпосередньо з рядків.

Наприклад, ви можете виконувати пошук за семантичною схожістю прямо з конструктора запитів:

```php
$documents = DB::table('documents')
    ->whereVectorSimilarTo('embedding', 'Best wineries in Napa Valley')
    ->limit(10)
    ->get();
```

---

Джерело: https://laravelukraine.com/docs/13.x/upgrade

# Посібник з оновлення

- [Оновлення з 12.x до 13.0](#upgrade-13.0)
    - [Оновлення за допомогою AI](#upgrading-using-ai)

<a name="high-impact-changes"></a>
## Зміни з високим впливом

<div class="content-list" markdown="1">

- [Оновлення залежностей](#updating-dependencies)
- [Оновлення інсталятора Laravel](#updating-the-laravel-installer)
- [Захист від підробки запитів](#request-forgery-protection)

</div>

<a name="medium-impact-changes"></a>
## Зміни з середнім впливом

<div class="content-list" markdown="1">

- [Конфігурація кешу `serializable_classes`](#cache-serializable_classes-configuration)
- [`upsert` бази даних з MySQL або MariaDB](#database-upsert-mariadb-mysql)

</div>

<a name="low-impact-changes"></a>
## Зміни з низьким впливом

<div class="content-list" markdown="1">

- [Префікси кешу та імена cookie сесії](#cache-prefixes-and-session-cookie-names)
- [Серіалізація колекцій моделей відновлює жадібно завантажені зв'язки](#collection-model-serialization-restores-eager-loaded-relations)
- [`Container::call` і значення за замовчуванням для nullable-класів](#containercall-and-nullable-class-defaults)
- [Пріоритет реєстрації доменних маршрутів](#domain-route-registration-precedence)
- [Дані винятку в події `JobAttempted`](#jobattempted-event-exception-payload)
- [Прив'язка колбека `extend` у менеджерах](#manager-extend-callback-binding)
- [Запити `DELETE` у MySQL із `JOIN`, `ORDER BY` та `LIMIT`](#mysql-delete-queries-with-join-order-by-and-limit)
- [Імена представлень пагінації для Bootstrap](#pagination-bootstrap-view-names)
- [Генерація імен поліморфних зведених таблиць](#polymorphic-pivot-table-name-generation)
- [Перейменування властивості події `QueueBusy`](#queuebusy-event-property-rename)
- [Конфігурація `serialization` сесії](#session-serialization-configuration)
- [Фабрики `Str` скидаються між тестами](#str-factories-reset-between-tests)

</div>

<a name="upgrade-13.0"></a>
## Оновлення з 12.x до 13.0

#### Орієнтовний час оновлення: 10 хвилин

> [!NOTE]
> Ми намагаємося задокументувати кожну можливу зміну, що порушує сумісність. Оскільки частина цих змін стосується маловживаних частин фреймворку, реально вплинути на ваш застосунок може лише частина з них. Щоб заощадити час, скористайтеся [Shift](https://laravelshift.com) - сервісом, який підтримує спільнота і який автоматизує оновлення Laravel.

<a name="upgrading-using-ai"></a>
### Оновлення за допомогою AI

Ви можете автоматизувати оновлення за допомогою [Laravel Boost](https://github.com/laravel/boost). Boost - це офіційний MCP-сервер, який дає вашому AI-асистенту покрокові підказки для оновлення. Щойно його встановлено в будь-якому застосунку Laravel 12, скористайтеся slash-командою `/upgrade-laravel-v13` у Claude Code, Cursor, OpenCode, Gemini чи VS Code, щоб почати оновлення до Laravel 13. Ця команда потребує Laravel Boost `^2.0`.

<a name="updating-dependencies"></a>
### Оновлення залежностей

**Імовірність впливу: висока**

Вам слід оновити такі залежності у файлі `composer.json` вашого застосунку:

<div class="content-list" markdown="1">

- `laravel/framework` до `^13.0`
- `laravel/boost` до `^2.0`
- `laravel/tinker` до `^3.0`
- `phpunit/phpunit` до `^12.0`
- `pestphp/pest` до `^4.0`

</div>

<a name="updating-the-laravel-installer"></a>
### Оновлення інсталятора Laravel

Якщо ви користуєтеся CLI-інструментом інсталятора Laravel для створення нових застосунків, оновіть його для сумісності з Laravel 13.x.

Якщо ви встановили інсталятор Laravel через `composer global require`, оновіть його командою `composer global update`:

```shell
composer global update laravel/installer
```

Або, якщо ви користуєтеся копією інсталятора Laravel, що постачається з [Laravel Herd](https://herd.laravel.com), оновіть свою інсталяцію Herd до найновішого релізу.

<a name="cache"></a>
### Кеш

<a name="cache-prefixes-and-session-cookie-names"></a>
#### Префікси кешу та імена cookie сесії

**Імовірність впливу: низька**

Префікси ключів кешу та Redis за замовчуванням тепер використовують суфікси з дефісами.

У більшості застосунків ця зміна не діятиме, бо конфігураційні файли рівня застосунку вже визначають ці значення. Насамперед вона стосується застосунків, що покладаються на резервну конфігурацію рівня фреймворку, коли відповідних значень у конфігурації застосунку немає.

Якщо ваш застосунок покладається на ці згенеровані значення за замовчуванням, ключі кешу та імена cookie сесії після оновлення можуть змінитися:

```php
// Laravel <= 12.x
Str::slug((string) env('APP_NAME', 'laravel'), '_').'_cache_';
Str::slug((string) env('APP_NAME', 'laravel'), '_').'_database_';
Str::slug((string) env('APP_NAME', 'laravel'), '_').'_session';

// Laravel >= 13.x
Str::slug((string) env('APP_NAME', 'laravel')).'-cache-';
Str::slug((string) env('APP_NAME', 'laravel')).'-database-';
Str::slug((string) env('APP_NAME', 'laravel')).'-session';
```

Щоб зберегти попередню поведінку, явно задайте `CACHE_PREFIX`, `REDIS_PREFIX` і `SESSION_COOKIE` у своєму середовищі.

<a name="store-and-repository-contracts-touch"></a>
#### Контракти `Store` і `Repository`: `touch`

**Імовірність впливу: дуже низька**

Контракти кешу тепер містять метод `touch` для продовження TTL елементів. Якщо ви підтримуєте власні реалізації сховищ кешу, додайте цей метод:

```php
// Illuminate\Contracts\Cache\Store
public function touch($key, $seconds);
```

<a name="cache-serializable_classes-configuration"></a>
#### Конфігурація кешу `serializable_classes`

**Імовірність впливу: середня**

Конфігурація `cache` застосунку за замовчуванням тепер містить опцію `serializable_classes` зі значенням `false`. Це посилює поведінку десеріалізації кешу, щоб запобігти атакам через ланцюжки гаджетів десеріалізації PHP, якщо `APP_KEY` вашого застосунку витече. Якщо ваш застосунок навмисне зберігає PHP-об'єкти в кеші, явно перелічіть класи, які дозволено десеріалізувати:

```php
'serializable_classes' => [
    App\Data\CachedDashboardStats::class,
    App\Support\CachedPricingSnapshot::class,
],
```

Якщо раніше ваш застосунок покладався на десеріалізацію довільних закешованих об'єктів, вам доведеться перевести це на явні списки дозволених класів або на дані кешу без об'єктів (наприклад, масиви).

<a name="container"></a>
### Контейнер

<a name="containercall-and-nullable-class-defaults"></a>
#### `Container::call` і значення за замовчуванням для nullable-класів

**Імовірність впливу: низька**

`Container::call` тепер враховує значення за замовчуванням для nullable-параметрів класів, коли прив'язки не існує, що відповідає поведінці впровадження через конструктор, запровадженій у Laravel 12:

```php
$container->call(function (?Carbon $date = null) {
    return $date;
});

// Laravel <= 12.x: екземпляр Carbon
// Laravel >= 13.x: null
```

Якщо ваша логіка впровадження при виклику методів залежала від попередньої поведінки, її може знадобитися оновити.

<a name="contracts"></a>
### Контракти

<a name="dispatcher-contract-dispatchafterresponse"></a>
#### Контракт `Dispatcher`: `dispatchAfterResponse`

**Імовірність впливу: дуже низька**

Контракт `Illuminate\Contracts\Bus\Dispatcher` тепер містить метод `dispatchAfterResponse($command, $handler = null)`.

Якщо ви підтримуєте власну реалізацію диспетчера, додайте цей метод до свого класу.

<a name="responsefactory-contract-eventstream"></a>
#### Контракт `ResponseFactory`: `eventStream`

**Імовірність впливу: дуже низька**

Контракт `Illuminate\Contracts\Routing\ResponseFactory` тепер містить сигнатуру `eventStream`.

Якщо ви підтримуєте власну реалізацію цього контракту, додайте цей метод.

<a name="mustverifyemail-contract-markemailasunverified"></a>
#### Контракт `MustVerifyEmail`: `markEmailAsUnverified`

**Імовірність впливу: дуже низька**

Контракт `Illuminate\Contracts\Auth\MustVerifyEmail` тепер містить `markEmailAsUnverified()`.

Якщо ви надаєте власну реалізацію цього контракту, додайте цей метод, щоб лишитися сумісними.

<a name="database"></a>
### База даних

<a name="database-upsert-mariadb-mysql"></a>
#### `upsert` бази даних з MySQL або MariaDB

**Імовірність впливу: середня**

Laravel тепер перевіряє, що той, хто викликає метод, передає непорожнє значення для `uniqueBy`, і викидає `InvalidArgumentException` замість генерації некоректного SQL.

Хоча драйвери MariaDB та MySQL ігнорують значення `uniqueBy` і завжди використовують первинні та унікальні індекси таблиці для виявлення наявних записів, перевірка все одно застосовується. Якщо `uniqueBy` порожній, буде викинуто `InvalidArgumentException`.

<a name="mysql-delete-queries-with-join-order-by-and-limit"></a>
#### Запити `DELETE` у MySQL із `JOIN`, `ORDER BY` та `LIMIT`

**Імовірність впливу: низька**

Laravel тепер компілює повні запити `DELETE ... JOIN`, включно з `ORDER BY` та `LIMIT`, для граматики MySQL.

У попередніх версіях речення `ORDER BY` / `LIMIT` могли мовчки ігноруватися при видаленні з приєднанням. У Laravel 13 ці речення потрапляють до згенерованого SQL. Як наслідок, рушії баз даних, що не підтримують цей синтаксис (MySQL і MariaDB до версії 11.8.1), тепер можуть викидати `QueryException` замість виконання необмеженого видалення.

<a name="eloquent"></a>
### Eloquent

<a name="model-booting-and-nested-instantiation"></a>
#### Завантаження моделі та вкладене створення екземплярів

**Імовірність впливу: дуже низька**

Створення нового екземпляра моделі, поки ця модель ще завантажується, тепер заборонено й викидає `LogicException`.

Це стосується коду, який створює екземпляри моделей усередині методів `boot` моделі або методів `boot*` трейтів:

```php
protected static function boot()
{
    parent::boot();

    // No longer allowed during booting...
    (new static())->getTable();
}
```

Винесіть цю логіку за межі циклу завантаження, щоб уникнути вкладеного завантаження.

<a name="polymorphic-pivot-table-name-generation"></a>
#### Генерація імен поліморфних зведених таблиць

**Імовірність впливу: низька**

Коли імена таблиць виводяться для поліморфних зведених моделей із власними класами зведених моделей, Laravel тепер генерує імена у множині.

Якщо ваш застосунок покладався на попередні виведені імена в однині для морф-зведених таблиць і використовував власні класи зведених моделей, явно визначте ім'я таблиці у своїй зведеній моделі.

<a name="collection-model-serialization-restores-eager-loaded-relations"></a>
#### Серіалізація колекцій моделей відновлює жадібно завантажені зв'язки

**Імовірність впливу: низька**

Коли колекції моделей Eloquent серіалізуються та відновлюються (наприклад, у завданнях черги), жадібно завантажені (eager loading) зв'язки тепер відновлюються для моделей колекції.

Якщо ваш код покладався на те, що після десеріалізації зв'язків немає, цю логіку може знадобитися скоригувати.

<a name="http-client"></a>
### HTTP-клієнт

<a name="http-client-response-throw-and-throwif-signatures"></a>
#### Сигнатури `Response::throw` і `throwIf` HTTP-клієнта

**Імовірність впливу: дуже низька**

Методи відповіді HTTP-клієнта тепер оголошують параметри колбеків у сигнатурах:

```php
public function throw($callback = null);
public function throwIf($condition, $callback = null);
```

Якщо ви перевизначаєте ці методи у власних класах відповідей, переконайтеся, що сигнатури ваших методів сумісні.

<a name="notifications"></a>
### Сповіщення

<a name="default-password-reset-subject"></a>
#### Тема листа скидання пароля за замовчуванням

**Імовірність впливу: дуже низька**

Тема листа скидання пароля Laravel за замовчуванням змінилася:

```text
// Laravel <= 12.x
Reset Password Notification

// Laravel >= 13.x
Reset your password
```

Якщо ваші тести, перевірки чи перевизначення перекладів залежать від попереднього рядка за замовчуванням, оновіть їх відповідно.

<a name="queued-notifications-and-missing-models"></a>
#### Сповіщення в черзі та відсутні моделі

**Імовірність впливу: дуже низька**

Сповіщення в черзі тепер враховують атрибут `#[DeleteWhenMissingModels]` і властивість `$deleteWhenMissingModels`, визначені в класі сповіщення.

У попередніх версіях відсутні моделі все одно могли призводити до провалу завдань черги зі сповіщеннями в тих випадках, коли ви очікували, що їх буде видалено.

<a name="queue"></a>
### Черги

<a name="jobattempted-event-exception-payload"></a>
#### Дані винятку в події `JobAttempted`

**Імовірність впливу: низька**

Подія `Illuminate\Queue\Events\JobAttempted` тепер надає об'єкт винятку (або `null`) через `$exception`, замінюючи попередню булеву властивість `$exceptionOccurred`:

```php
// Laravel <= 12.x
$event->exceptionOccurred;

// Laravel >= 13.x
$event->exception;
```

Якщо ви слухаєте цю подію, оновіть код свого слухача відповідно.

<a name="queuebusy-event-property-rename"></a>
#### Перейменування властивості події `QueueBusy`

**Імовірність впливу: низька**

Властивість `$connection` події `Illuminate\Queue\Events\QueueBusy` перейменовано на `$connectionName` задля узгодженості з іншими подіями черг.

Якщо ваші слухачі звертаються до `$connection`, оновіть їх на `$connectionName`.

<a name="queue-contract-method-additions"></a>
#### Додані методи контракту `Queue`

**Імовірність впливу: дуже низька**

Контракт `Illuminate\Contracts\Queue\Queue` тепер містить методи перевірки розміру черги, які раніше були оголошені лише в блоках docblock.

Якщо ви підтримуєте власні реалізації драйверів черг за цим контрактом, додайте реалізації для:

<div class="content-list" markdown="1">

- `pendingSize`
- `delayedSize`
- `reservedSize`
- `creationTimeOfOldestPendingJob`

</div>

<a name="routing"></a>
### Маршрутизація

<a name="domain-route-registration-precedence"></a>
#### Пріоритет реєстрації доменних маршрутів

**Імовірність впливу: низька**

Маршрути з явно вказаним доменом тепер мають пріоритет над недоменними маршрутами під час зіставлення маршрутів.

Це дозволяє маршрутам-перехоплювачам піддоменів поводитися послідовно навіть тоді, коли недоменні маршрути зареєстровані раніше. Якщо ваш застосунок покладався на попередній пріоритет реєстрації між доменними та недоменними маршрутами, перевірте поведінку зіставлення маршрутів.

<a name="session"></a>
### Session

<a name="session-serialization-configuration"></a>
#### Конфігурація `serialization` сесії

**Ймовірність впливу: низька**

Щоб допомогти запобігти атакам через ланцюги десеріалізації PHP, стандартний каркас застосунку тепер встановлює параметр `serialization` сесії в значення `json` у файлі `config/session.php`.

Якщо ви оновлюєте існуючий застосунок і синхронізуєте файли конфігурації з каркасом Laravel 13, зміна цього значення з `php` на `json` анулює всі активні сесії користувачів.

Якщо ви бажаєте безперебійно зберегти активні сесії під час оновлення, ви повинні залишити це значення встановленим на `php`. Однак, якщо ваш застосунок не зберігає PHP-об'єкти в сесії і ви готові вимагати від користувачів повторної автентифікації, ми рекомендуємо оновити це значення до `json` для покращеної безпеки.

<a name="scheduling"></a>
### Планування

<a name="withscheduling-registration-timing"></a>
#### Момент реєстрації `withScheduling`

**Імовірність впливу: дуже низька**

Розклади, зареєстровані через `ApplicationBuilder::withScheduling()`, тепер відкладаються до моменту, коли розв'язується `Schedule`.

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

<a name="security"></a>
### Безпека

<a name="request-forgery-protection"></a>
#### Захист від підробки запитів

**Імовірність впливу: висока**

CSRF-`middleware` Laravel перейменовано з `VerifyCsrfToken` на `PreventRequestForgery`, і тепер він містить перевірку джерела запиту за допомогою заголовка `Sec-Fetch-Site`.

`VerifyCsrfToken` і `ValidateCsrfToken` лишаються як застарілі псевдоніми, але прямі посилання слід оновити на `PreventRequestForgery`, особливо коли ви виключаєте `middleware` у тестах чи визначеннях маршрутів:

```php
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;

// Laravel <= 12.x
->withoutMiddleware([VerifyCsrfToken::class]);

// Laravel >= 13.x
->withoutMiddleware([PreventRequestForgery::class]);
```

API конфігурації `middleware` тепер також надає `preventRequestForgery(...)`.

<a name="support"></a>
### Support

<a name="manager-extend-callback-binding"></a>
#### Прив'язка колбека `extend` у менеджерах

**Імовірність впливу: низька**

Замикання власних драйверів, зареєстровані через методи `extend` менеджерів, тепер прив'язуються до екземпляра менеджера.

Якщо раніше ви покладалися на інший прив'язаний об'єкт (наприклад, екземпляр сервіс-провайдера) як `$this` усередині цих колбеків, перенесіть ці значення до замикання через `use (...)`.

<a name="str-factories-reset-between-tests"></a>
#### Фабрики `Str` скидаються між тестами

**Імовірність впливу: низька**

Laravel тепер скидає власні фабрики `Str` під час завершення тесту.

Якщо ваші тести покладалися на те, що власні фабрики UUID / ULID / випадкових рядків зберігаються між тестовими методами, задавайте їх у кожному відповідному тесті або в хуку налаштування.

<a name="jsfrom-uses-unescaped-unicode-by-default"></a>
#### `Js::from` за замовчуванням використовує неекранований Unicode

**Імовірність впливу: дуже низька**

`Illuminate\Support\Js::from` тепер за замовчуванням використовує `JSON_UNESCAPED_UNICODE`.

Якщо ваші тести чи порівняння виводу фронтенду залежали від екранованих послідовностей Unicode (наприклад, `è`), оновіть свої очікування.

<a name="utilities"></a>
### Утиліти

<a name="symfony-polyfill"></a>
#### Поліфіл Symfony для PHP 8.5 і конфлікти глобальних функцій

**Імовірність впливу: низька**

Laravel 13 додає залежність від `symfony/polyfill-php85`. На версіях PHP нижче 8.5 цей поліфіл визначає глобальні функції на кшталт `array_first()` та `array_last()`, якщо їх не було визначено раніше під час завантаження.

Ці функції можуть конфліктувати зі старими пакетами хелперів, як-от `laravel/helpers`, або з власними глобальними хелперами з такими самими іменами. Наприклад, історичний хелпер `array_first()` приймав колбек і повертав перший відповідний елемент, тоді як версія з поліфілу повертає лише перший елемент масиву.

Щоб уникнути конфліктів і забезпечити послідовну поведінку на різних версіях PHP, надавайте перевагу методам `Illuminate\Support\Arr`:

```php
use Illuminate\Support\Arr;

Arr::first($array, function ($value) {
  return /* condition */;
});
```

<a name="views"></a>
### Представлення

<a name="pagination-bootstrap-view-names"></a>
#### Імена представлень пагінації для Bootstrap

**Імовірність впливу: низька**

Внутрішні імена представлень пагінації для типових налаштувань Bootstrap 3 тепер явні:

```text
// Laravel <= 12.x
pagination::default
pagination::simple-default

// Laravel >= 13.x
pagination::bootstrap-3
pagination::simple-bootstrap-3
```

Якщо ваш застосунок посилається безпосередньо на старі імена представлень пагінації, оновіть ці посилання.

<a name="miscellaneous"></a>
### Різне

Радимо також переглянути зміни в [репозиторії GitHub](https://github.com/laravel/laravel) `laravel/laravel`. Хоча багато з цих змін не є обов'язковими, ви можете захотіти тримати ці файли синхронізованими зі своїм застосунком. Частину змін розглянуто в цьому посібнику з оновлення, але інші - як-от зміни в конфігураційних файлах чи коментарях - ні. Ви можете легко переглянути зміни за допомогою [інструмента порівняння GitHub](https://github.com/laravel/laravel/compare/12.x...13.x) і вибрати, які оновлення для вас важливі.

---

Джерело: https://laravelukraine.com/docs/13.x/contributions

# Посібник з внеску

- [Повідомлення про помилки](#bug-reports)
- [Питання щодо підтримки](#support-questions)
- [Яку гілку обрати?](#which-branch)
- [Скомпільовані ресурси](#compiled-assets)
- [Внески, згенеровані AI](#ai-generated-contributions)
- [Вразливості безпеки](#security-vulnerabilities)
- [Стиль коду](#coding-style)
    - [PHPDoc](#phpdoc)
    - [StyleCI](#styleci)
- [Кодекс поведінки](#code-of-conduct)

<a name="bug-reports"></a>
## Повідомлення про помилки

Щоб заохотити активну співпрацю, Laravel наполегливо радить надсилати pull request, які розв'язують проблеми, а не створювати issue на GitHub. Issue на GitHub вимкнено для більшості наших офіційних пакетів.

Якщо ви виявили проблему, створіть pull request, який її вирішує. Ваш pull request має містити заголовок і чіткий опис проблеми та її розв'язання. Також варто додати якомога більше доречної інформації та приклад коду, що демонструє проблему. Мета pull request - полегшити вам і іншим розуміння проблеми та перевірку виправлення.

Якщо ви не знаєте, як виправити проблему, опишіть її AI-агенту й скористайтеся ним, щоб створити pull request.

Кожен pull request розглядається лише тоді, коли його позначено як «ready for review» (тобто не в стані «draft») і всі тести для нових можливостей проходять. Застарілий неактивний pull request, залишений у стані «draft», буде закрито за кілька днів.

Вихідний код Laravel розміщено на GitHub, і для кожного з проєктів Laravel є свій репозиторій:

<div class="content-list" markdown="1">

- [Laravel AI SDK](https://github.com/laravel/ai)
- [Laravel Application](https://github.com/laravel/laravel)
- [Laravel Art](https://github.com/laravel/art)
- [Laravel Boost](https://github.com/laravel/boost)
- [Laravel Documentation](https://github.com/laravel/docs)
- [Laravel Dusk](https://github.com/laravel/dusk)
- [Laravel Cashier Stripe](https://github.com/laravel/cashier)
- [Laravel Cashier Paddle](https://github.com/laravel/cashier-paddle)
- [Laravel Echo](https://github.com/laravel/echo)
- [Laravel Envoy](https://github.com/laravel/envoy)
- [Laravel Folio](https://github.com/laravel/folio)
- [Laravel Framework](https://github.com/laravel/framework)
- [Laravel Horizon](https://github.com/laravel/horizon)
- [Laravel Passport](https://github.com/laravel/passport)
- [Laravel Pennant](https://github.com/laravel/pennant)
- [Laravel Pint](https://github.com/laravel/pint)
- [Laravel Prompts](https://github.com/laravel/prompts)
- [Laravel Reverb](https://github.com/laravel/reverb)
- [Laravel Sail](https://github.com/laravel/sail)
- [Laravel Sanctum](https://github.com/laravel/sanctum)
- [Laravel Scout](https://github.com/laravel/scout)
- [Laravel Socialite](https://github.com/laravel/socialite)
- [Laravel Telescope](https://github.com/laravel/telescope)
- [Laravel Livewire Starter Kit](https://github.com/laravel/livewire-starter-kit)
- [Laravel React Starter Kit](https://github.com/laravel/react-starter-kit)
- [Laravel Svelte Starter Kit](https://github.com/laravel/svelte-starter-kit)
- [Laravel Vue Starter Kit](https://github.com/laravel/vue-starter-kit)

</div>

<a name="support-questions"></a>
## Питання щодо підтримки

Трекери issue на GitHub не призначені для надання допомоги чи підтримки щодо Laravel. Натомість скористайтеся одним із таких каналів:

<div class="content-list" markdown="1">

- [GitHub Discussions](https://github.com/laravel/framework/discussions)
- [Laracasts Forums](https://laracasts.com/discuss)
- [Laravel.io Forums](https://laravel.io/forum)
- [StackOverflow](https://stackoverflow.com/questions/tagged/laravel)
- [Discord](https://discord.gg/laravel)
- [Larachat](https://larachat.co)
- [IRC](https://web.libera.chat/?nick=artisan&channels=#laravel)

</div>

<a name="which-branch"></a>
## Яку гілку обрати?

**Усі** виправлення помилок слід надсилати до найновішої версії, яка підтримує виправлення помилок (наразі `13.x`). Виправлення помилок **ніколи** не слід надсилати до гілки `master`, окрім випадків, коли вони виправляють можливості, що існують лише в майбутньому релізі.

**Незначні** можливості, **повністю зворотно сумісні** з поточним релізом, можна надсилати до останньої стабільної гілки (наразі `13.x`).

**Великі** нові можливості або можливості зі змінами, що порушують сумісність, завжди слід надсилати до гілки `master`, яка містить майбутній реліз.

<a name="compiled-assets"></a>
## Скомпільовані ресурси

Якщо ви надсилаєте зміну, яка вплине на скомпільований файл - як-от більшість файлів у `resources/css` чи `resources/js` репозиторію `laravel/laravel`, - не комітьте скомпільовані файли. Через їхній великий розмір супровідник не зможе реально їх перевірити. Цим можна скористатися, щоб впровадити зловмисний код у Laravel. Щоб запобігти цьому, усі скомпільовані файли генеруються й комітяться супровідниками Laravel.

<a name="ai-generated-contributions"></a>
## Внески, згенеровані AI

Ми цінуємо кожен pull request, надісланий до Laravel. Однак значні внески, здебільшого згенеровані AI без вдумливого перегляду й осмислення людиною, неприйнятні.

Якщо ви вирішили скористатися AI-інструментами для підготовки великих або складних внесків до фреймворку, отриманий код **обов'язково** має бути ретельно переглянутий, протестований і зрозумілий вам перед надсиланням.

Опис pull request **обов'язково** має бути написаний цілком контриб'ютором. Pull request з описом, згенерованим AI, буде закрито.

**Масового створення issue чи pull request, повністю згенерованих AI, ми не терпітимемо.** Такий pull request буде закрито без розгляду, а користувача, який його надіслав, може бути заблоковано в репозиторії.

Ми заохочуємо контриб'юторів ознайомлюватися з наявною кодовою базою, взаємодіяти зі спільнотою та надсилати такий pull request, який відображає їхнє власне розуміння й уважне осмислення проблеми, яку вони розв'язують.

<a name="security-vulnerabilities"></a>
## Вразливості безпеки

Якщо ви виявили вразливість безпеки в Laravel, надішліть листа нашій команді безпеки на <a href="mailto:security@laravel.com">security@laravel.com</a>. Усі вразливості безпеки буде оперативно опрацьовано.

<a name="coding-style"></a>
## Стиль коду

Laravel дотримується стандарту кодування [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) та стандарту автозавантаження [PSR-4](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md).

<a name="phpdoc"></a>
### PHPDoc

Нижче наведено приклад коректного блоку документації Laravel. Зверніть увагу, що після атрибута `@param` іде два пробіли, тип аргументу, ще два пробіли й нарешті ім'я змінної:

```php
/**
 * Register a binding with the container.
 *
 * @param  string|array  $abstract
 * @param  \Closure|string|null  $concrete
 * @param  bool  $shared
 * @return void
 *
 * @throws \Exception
 */
public function bind($abstract, $concrete = null, $shared = false)
{
    // ...
}
```

Коли атрибути `@param` чи `@return` є надлишковими через використання нативних типів, їх можна прибрати:

```php
/**
 * Execute the job.
 * [tl! remove]
 * @return void [tl! remove]
 */
public function handle(AudioProcessor $processor): void
{
    // ...
}
```

Однак якщо нативний тип є узагальненим (generic), вкажіть узагальнений тип за допомогою атрибутів `@param` чи `@return`:

```php
/**
 * Get the attachments for the message.
 * [tl! add]
 * @return array<int, \Illuminate\Mail\Mailables\Attachment> [tl! add]
 */
public function attachments(): array
{
    return [
        Attachment::fromStorage('/path/to/file'),
    ];
}
```

<a name="styleci"></a>
### StyleCI

Не хвилюйтеся, якщо стиль вашого коду не бездоганний! [StyleCI](https://styleci.io/) автоматично зіллє всі виправлення стилю до репозиторію Laravel після того, як ваш pull request буде злито. Це дозволяє нам зосередитися на змісті внеску, а не на стилі коду.

<a name="code-of-conduct"></a>
## Кодекс поведінки

Кодекс поведінки Laravel походить від кодексу поведінки Ruby. Про будь-які порушення кодексу поведінки можна повідомити Тейлору Отвеллу (taylor@laravel.com):

<div class="content-list" markdown="1">

- Учасники мають бути толерантними до протилежних поглядів.
- Учасники повинні стежити, щоб їхні висловлювання та дії були вільними від особистих нападів і принизливих зауважень на адресу інших.
- Тлумачачи слова й дії інших, учасники завжди мають припускати добрі наміри.
- Поведінки, яку можна обґрунтовано вважати домаганням, ми не терпітимемо.

</div>

---

Джерело: https://laravelukraine.com/docs/13.x/installation

# Встановлення

- [Знайомство з Laravel](#meet-laravel)
    - [Чому Laravel?](#why-laravel)
- [Створення застосунку Laravel](#creating-a-laravel-project)
    - [Початок роботи за допомогою AI](#getting-started-using-ai)
    - [Встановлення PHP та інсталятора Laravel](#installing-php)
    - [Створення застосунку](#creating-an-application)
- [Початкова конфігурація](#initial-configuration)
    - [Конфігурація на основі середовища](#environment-based-configuration)
    - [Бази даних і міграції](#databases-and-migrations)
    - [Конфігурація каталогів](#directory-configuration)
- [Встановлення за допомогою Herd](#installation-using-herd)
    - [Herd на macOS](#herd-on-macos)
    - [Herd на Windows](#herd-on-windows)
- [Підтримка IDE](#ide-support)
- [Laravel та AI](#laravel-and-ai)
    - [Встановлення Laravel Boost](#installing-laravel-boost)
- [Наступні кроки](#next-steps)
    - [Laravel як повностековий фреймворк](#laravel-the-fullstack-framework)
    - [Laravel як бекенд для API](#laravel-the-api-backend)

<a name="meet-laravel"></a>
## Знайомство з Laravel

Laravel - це фреймворк для веб-застосунків з виразним, елегантним синтаксисом. Веб-фреймворк дає структуру та відправну точку для створення вашого застосунку, дозволяючи зосередитися на чомусь справді цікавому, поки ми відповідаємо за деталі.

Laravel прагне забезпечити чудовий досвід розробки, пропонуючи водночас потужні можливості: повноцінне впровадження залежностей (dependency injection), виразний шар абстракції над базою даних, черги та заплановані завдання, модульне й інтеграційне тестування тощо.

Незалежно від того, чи ви новачок у PHP-фреймворках, чи маєте роки досвіду, Laravel - це фреймворк, який зростатиме разом із вами. Ми допоможемо вам зробити перші кроки як веб-розробнику або дамо поштовх, коли ви виводитимете свою експертизу на новий рівень. Нам не терпиться побачити, що ви створите.

<a name="why-laravel"></a>
### Чому Laravel?

Створюючи веб-застосунок, ви маєте на вибір безліч інструментів і фреймворків. Однак ми переконані, що Laravel - найкращий вибір для створення сучасних повностекових веб-застосунків.

#### Прогресивний фреймворк

Ми любимо називати Laravel «прогресивним» фреймворком. Під цим ми маємо на увазі, що Laravel зростає разом із вами. Якщо ви робите перші кроки у веб-розробці, велика бібліотека документації, посібників і [відеоуроків](https://laracasts.com) допоможе вам опанувати основи, не перевантажуючи інформацією.

Якщо ж ви досвідчений розробник, Laravel дає надійні інструменти для [впровадження залежностей](/docs/{{version}}/container), [модульного тестування](/docs/{{version}}/testing), [черг](/docs/{{version}}/queues), [подій у реальному часі](/docs/{{version}}/broadcasting) тощо. Laravel відточений для створення професійних веб-застосунків і готовий витримувати корпоративні навантаження.

#### Масштабований фреймворк

Laravel неймовірно масштабований. Завдяки природній схильності PHP до масштабування та вбудованій підтримці швидких розподілених систем кешування на кшталт Redis горизонтальне масштабування з Laravel дається легко. Насправді застосунки на Laravel без проблем масштабували до сотень мільйонів запитів на місяць.

Потрібне екстремальне масштабування? Платформи на кшталт [Laravel Cloud](https://cloud.laravel.com) дозволяють запускати ваш застосунок Laravel практично з необмеженим масштабом.

#### Фреймворк, готовий до роботи з агентами

Чіткі домовленості та добре визначена структура роблять Laravel ідеальним фреймворком для [розробки за допомогою AI](/docs/{{version}}/ai) з такими інструментами, як Cursor і Claude Code. Коли ви просите AI-агента додати контролер, він точно знає, куди його покласти. Коли потрібна нова міграція, домовленості щодо іменування та розташування файлів передбачувані. Ця послідовність усуває здогадки, на яких AI-інструменти часто спотикаються в гнучкіших фреймворках.

Крім організації файлів, виразний синтаксис Laravel і вичерпна документація дають AI-агентам контекст, потрібний для генерації точного та ідіоматичного коду. Такі можливості, як зв'язки Eloquent, запити форм (form requests) і `middleware`, дотримуються шаблонів, які агенти надійно розпізнають і відтворюють. Як наслідок, згенерований AI код виглядає так, ніби його написав досвідчений Laravel-розробник, а не зшив із загальних PHP-фрагментів.

Щоб дізнатися більше про те, чому Laravel - ідеальний вибір для розробки за допомогою AI, перегляньте нашу документацію про [агентну розробку](/docs/{{version}}/ai).

#### Фреймворк спільноти

Laravel поєднує найкращі пакети екосистеми PHP, пропонуючи найнадійніший і найзручніший для розробника фреймворк. До того ж тисячі талановитих розробників з усього світу [зробили внесок у фреймворк](https://github.com/laravel/framework). Хтозна, можливо, і ви станете контриб'ютором Laravel.

<a name="creating-a-laravel-project"></a>
## Створення застосунку Laravel

<a name="getting-started-using-ai"></a>
### Початок роботи за допомогою AI

Якщо ви користуєтеся AI-агентом для написання коду, як-от [Claude Code](https://docs.anthropic.com/en/docs/claude-code) чи [OpenCode](https://opencode.ai), можете почати з запиту, який дасть агентові конкретний план дій для Laravel ще до того, як він торкнеться вашого проєкту.

Запит нижче каже агентові, де шукати настанови зі встановлення Laravel, на чому зосередитися і як обирати розумні значення за замовчуванням, поки ви ще не визначилися. Вставте його у свого агента, щоб почати:

```text
I'm building a new Laravel application.

Fetch and follow the instructions from https://laravel.com/for/agents. Treat the returned Markdown as the source of truth for how to install and set up Laravel in this session.
```

Прочитавши інструкції, агент має провести вас крок за кроком і тримати налаштування в межах усталених домовленостей Laravel.

<a name="installing-php"></a>
### Встановлення PHP та інсталятора Laravel

Перш ніж створювати свій перший застосунок Laravel, переконайтеся, що на вашій локальній машині встановлено [PHP](https://php.net), [Composer](https://getcomposer.org) та [інсталятор Laravel](https://github.com/laravel/installer). Крім того, варто встановити [Node і NPM](https://nodejs.org) або [Bun](https://bun.sh/), щоб мати змогу збирати фронтенд-ресурси застосунку.

Якщо PHP і Composer ще не встановлені на вашій локальній машині, наведені нижче команди встановлять PHP, Composer та інсталятор Laravel у macOS, Windows або Linux:

```shell tab=macOS
/bin/bash -c "$(curl -fsSL https://php.new/install/mac/8.5)"
```

```shell tab=Windows PowerShell
# Run as administrator...
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://php.new/install/windows/8.5'))
```

```shell tab=Linux
/bin/bash -c "$(curl -fsSL https://php.new/install/linux/8.5)"
```

Після виконання однієї з наведених вище команд перезапустіть сеанс термінала. Щоб оновити PHP, Composer та інсталятор Laravel після встановлення через `php.new`, повторно виконайте ту саму команду в терміналі.

Якщо PHP і Composer уже встановлені, ви можете встановити інсталятор Laravel через Composer:

```shell
composer global require laravel/installer
```

> [!NOTE]
> Щоб отримати повнофункціональний графічний досвід встановлення PHP і керування ним, перегляньте [Laravel Herd](#installation-using-herd).

<a name="creating-an-application"></a>
### Створення застосунку

Після встановлення PHP, Composer та інсталятора Laravel ви готові створити новий застосунок Laravel:

```shell
laravel new example-app
```

Коли застосунок створено, ви можете запустити локальний сервер розробки Laravel, воркер черги та сервер розробки Vite за допомогою Composer-скрипта `dev`:

```shell
cd example-app
npm install && npm run build
composer run dev
```

Після запуску сервера розробки ваш застосунок буде доступний у браузері за адресою [http://localhost:8000](http://localhost:8000). Далі ви готові [зробити наступні кроки в екосистемі Laravel](#next-steps). Звісно, ви також можете [налаштувати базу даних](#databases-and-migrations) і виконати потрібні міграції.

> [!NOTE]
> Якщо хочете отримати фору в розробці свого застосунку Laravel, скористайтеся одним із наших [стартових наборів](/docs/{{version}}/starter-kits). Стартові набори Laravel дають готовий каркас автентифікації для бекенду та фронтенду вашого нового застосунку.

<a name="initial-configuration"></a>
## Початкова конфігурація

Усі конфігураційні файли фреймворку Laravel зберігаються в каталозі `config`. Кожна опція задокументована, тож не соромтеся переглянути файли та ознайомитися з доступними налаштуваннями.

Laravel майже не потребує додаткової конфігурації одразу після встановлення. Можете вільно братися до розробки! Утім, варто переглянути файл `config/app.php` та його документацію. Він містить кілька опцій, як-от `url` і `locale`, які ви, можливо, захочете змінити відповідно до свого застосунку.

<a name="environment-based-configuration"></a>
### Конфігурація на основі середовища

Оскільки значення багатьох конфігураційних опцій Laravel можуть відрізнятися залежно від того, чи працює застосунок на вашій локальній машині, чи на робочому сервері, багато важливих значень визначаються у файлі `.env`, що лежить у корені застосунку.

Ваш файл `.env` не слід додавати до системи контролю версій, оскільки кожен розробник чи сервер, що використовує ваш застосунок, може потребувати іншої конфігурації середовища. Ба більше, це створило б загрозу безпеці, якби зловмисник отримав доступ до вашого репозиторію, адже всі конфіденційні облікові дані опинилися б на видноті.

> [!NOTE]
> Докладніше про файл `.env` і конфігурацію на основі середовища читайте в повній [документації з конфігурації](/docs/{{version}}/configuration#environment-configuration).

<a name="databases-and-migrations"></a>
### Бази даних і міграції

Тепер, коли ви створили застосунок Laravel, вам, імовірно, потрібно зберігати дані в базі. За замовчуванням конфігураційний файл `.env` вашого застосунку вказує, що Laravel працюватиме з базою даних SQLite.

Під час створення застосунку Laravel створив для вас файл `database/database.sqlite` і виконав потрібні міграції, щоб створити таблиці бази даних.

Якщо ви віддаєте перевагу іншому драйверу бази даних, як-от MySQL чи PostgreSQL, оновіть конфігураційний файл `.env`, вказавши відповідну базу. Наприклад, щоб використовувати MySQL, оновіть змінні `DB_*` у файлі `.env` так:

```ini
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
```

Якщо ви обрали базу даних, відмінну від SQLite, вам потрібно створити базу та виконати [міграції бази даних](/docs/{{version}}/migrations) вашого застосунку:

```shell
php artisan migrate
```

> [!NOTE]
> Якщо ви розробляєте на macOS або Windows і вам потрібно встановити MySQL, PostgreSQL чи Redis локально, скористайтеся [Herd Pro](https://herd.laravel.com/#plans) або [DBngin](https://dbngin.com/).

<a name="directory-configuration"></a>
### Конфігурація каталогів

Laravel завжди слід віддавати з кореня «веб-каталогу», налаштованого для вашого веб-сервера. Не намагайтеся віддавати застосунок Laravel із підкаталогу «веб-каталогу». Спроба зробити це може відкрити доступ до конфіденційних файлів вашого застосунку.

<a name="installation-using-herd"></a>
## Встановлення за допомогою Herd

[Laravel Herd](https://herd.laravel.com) - це блискавично швидке нативне середовище розробки для Laravel і PHP під macOS та Windows. Herd містить усе потрібне, щоб почати розробку на Laravel, зокрема PHP і Nginx.

Щойно ви встановите Herd, можете братися до розробки на Laravel. Herd містить інструменти командного рядка для `php`, `composer`, `laravel`, `expose`, `node`, `npm` і `nvm`.

> [!NOTE]
> [Herd Pro](https://herd.laravel.com/#plans) доповнює Herd додатковими потужними можливостями, як-от створення локальних баз даних MySQL, Postgres і Redis та керування ними, а також локальний перегляд пошти й моніторинг логів.

<a name="herd-on-macos"></a>
### Herd на macOS

Якщо ви розробляєте на macOS, можете завантажити інсталятор Herd із [сайту Herd](https://herd.laravel.com). Інсталятор автоматично завантажує найновішу версію PHP і налаштовує ваш Mac так, щоб [Nginx](https://www.nginx.com/) завжди працював у фоновому режимі.

Herd для macOS використовує [dnsmasq](https://en.wikipedia.org/wiki/Dnsmasq) для підтримки «припаркованих» каталогів. Будь-який застосунок Laravel у припаркованому каталозі автоматично обслуговуватиметься Herd. За замовчуванням Herd створює припаркований каталог `~/Herd`, і ви можете звертатися до будь-якого застосунку Laravel у цьому каталозі за доменом `.test`, використовуючи ім'я його каталогу.

Після встановлення Herd найшвидший спосіб створити новий застосунок Laravel - скористатися Laravel CLI, який постачається разом із Herd:

```shell
cd ~/Herd
laravel new my-app
cd my-app
herd open
```

Звісно, ви завжди можете керувати припаркованими каталогами та іншими налаштуваннями PHP через інтерфейс Herd, який відкривається з меню Herd у системному треї.

Докладніше про Herd читайте в [документації Herd](https://herd.laravel.com/docs).

<a name="herd-on-windows"></a>
### Herd на Windows

Інсталятор Herd для Windows можна завантажити на [сайті Herd](https://herd.laravel.com/windows). Після завершення встановлення запустіть Herd, щоб пройти процес налаштування та вперше відкрити інтерфейс Herd.

Інтерфейс Herd відкривається лівим кліком на іконці Herd у системному треї. Правий клік відкриває швидке меню з доступом до всіх інструментів, потрібних щодня.

Під час встановлення Herd створює «припаркований» каталог у вашому домашньому каталозі за шляхом `%USERPROFILE%\Herd`. Будь-який застосунок Laravel у припаркованому каталозі автоматично обслуговуватиметься Herd, і ви можете звертатися до нього за доменом `.test`, використовуючи ім'я його каталогу.

Після встановлення Herd найшвидший спосіб створити новий застосунок Laravel - скористатися Laravel CLI, який постачається разом із Herd. Щоб почати, відкрийте Powershell і виконайте такі команди:

```shell
cd ~\Herd
laravel new my-app
cd my-app
herd open
```

Докладніше про Herd читайте в [документації Herd для Windows](https://herd.laravel.com/docs/windows).

<a name="ide-support"></a>
## Підтримка IDE

Розробляючи застосунки Laravel, ви вільні користуватися будь-яким редактором коду. [Laravel LSP](https://github.com/laravel/lsp) забезпечує підтримку редактора з урахуванням особливостей фреймворку: автодоповнення коду, інформацію при наведенні, діагностику, посилання в документі, перехід до визначення та швидкі виправлення для коду Laravel і Blade.

Щоб установити Laravel LSP, установіть його глобально через Composer. Переконайтеся, що глобальний каталог `vendor/bin` Composer є у вашому `PATH`:

```shell
composer global require laravel/lsp
```

Якщо шукаєте легкі та розширювані редактори, [VS Code](https://code.visualstudio.com) чи [Cursor](https://cursor.com) у поєднанні з офіційним [розширенням Laravel для VS Code](https://marketplace.visualstudio.com/items?itemName=laravel.vscode-laravel) дають підсвічування синтаксису, сніпети, інтеграцію з командами Artisan і автоматичну підтримку Laravel LSP. Офіційні розширення Laravel також доступні для [Sublime Text](https://github.com/laravel/sublime-extension) і [Zed](https://github.com/laravel/zed-extension). Інструкції з налаштування для інших редакторів, сумісних із мовним сервером, зокрема Neovim і OpenCode, дивіться в [репозиторії Laravel LSP](https://github.com/laravel/lsp).

Щоб отримати ґрунтовну та надійну підтримку Laravel, зверніть увагу на [PhpStorm](https://www.jetbrains.com/phpstorm/laravel/?utm_source=laravel.com&utm_medium=link&utm_campaign=laravel-2025&utm_content=partner&ref=laravel-2025) - IDE від JetBrains. Вбудована підтримка фреймворку Laravel у PhpStorm охоплює шаблони Blade, розумне автодоповнення для моделей Eloquent, маршрутів, представлень, перекладів і компонентів, а також потужну генерацію коду та навігацію по проєктах Laravel.

Для тих, хто шукає хмарний досвід розробки, [Firebase Studio](https://firebase.studio/) дає миттєвий доступ до розробки на Laravel просто у браузері. Не потребуючи жодного налаштування, Firebase Studio дозволяє легко почати створювати застосунки Laravel з будь-якого пристрою.

<a name="laravel-and-ai"></a>
## Laravel та AI

[Laravel Boost](https://github.com/laravel/boost) - це потужний інструмент, який долає розрив між AI-агентами для написання коду та застосунками Laravel. Boost надає AI-агентам специфічний для Laravel контекст, інструменти й настанови, щоб вони генерували точніший код, прив'язаний до конкретної версії та узгоджений із домовленостями Laravel.

Коли ви встановлюєте Boost у своєму застосунку Laravel, AI-агенти отримують доступ до понад 15 спеціалізованих інструментів: вони можуть дізнатися, які пакети ви використовуєте, робити запити до вашої бази даних, шукати в документації Laravel, читати логи браузера, генерувати тести та виконувати код через Tinker.

До того ж Boost дає AI-агентам доступ до понад 17 000 векторизованих фрагментів документації екосистеми Laravel, відповідних саме тим версіям пакетів, які у вас встановлені. Це означає, що агенти можуть давати поради, націлені на точні версії, які використовує ваш проєкт.

Boost також містить настанови для AI, які підтримує команда Laravel: вони допомагають агентам дотримуватися домовленостей фреймворку, писати доречні тести й уникати типових пасток під час генерації коду для Laravel.

<a name="installing-laravel-boost"></a>
### Встановлення Laravel Boost

Boost можна встановити в застосунках Laravel 10, 11, 12 і 13, що працюють на PHP 8.1 або новішому. Щоб почати, встановіть Boost як залежність для розробки:

```shell
composer require laravel/boost --dev
```

Після встановлення запустіть інтерактивний інсталятор:

```shell
php artisan boost:install
```

Інсталятор автоматично визначить вашу IDE та AI-агентів, дозволивши увімкнути ті можливості, які мають сенс для вашого проєкту. Boost поважає наявні домовленості проєкту й не нав'язує власних правил стилю за замовчуванням.

> [!NOTE]
> Щоб дізнатися більше про Boost, перегляньте [репозиторій Laravel Boost на GitHub](https://github.com/laravel/boost).

<a name="adding-custom-ai-guidelines"></a>
#### Додавання власних настанов для AI

Щоб доповнити Laravel Boost власними настановами для AI, додайте файли `.blade.php` або `.md` до каталогу `.ai/guidelines/*` вашого застосунку. Ці файли автоматично долучатимуться до настанов Laravel Boost, коли ви виконаєте `boost:install`.

<a name="next-steps"></a>
## Наступні кроки

Тепер, коли ви створили свій застосунок Laravel, вам, можливо, цікаво, що вивчати далі. Насамперед ми наполегливо радимо ознайомитися з тим, як працює Laravel, прочитавши таку документацію:

<div class="content-list" markdown="1">

- [Життєвий цикл запиту](/docs/{{version}}/lifecycle)
- [Конфігурація](/docs/{{version}}/configuration)
- [Структура каталогів](/docs/{{version}}/structure)
- [Фронтенд](/docs/{{version}}/frontend)
- [Сервіс-контейнер](/docs/{{version}}/container)
- [Фасади](/docs/{{version}}/facades)

</div>

Те, як саме ви плануєте використовувати Laravel, теж визначить ваші наступні кроки. Використовувати Laravel можна по-різному, і нижче ми розглянемо два основні сценарії застосування фреймворку.

<a name="laravel-the-fullstack-framework"></a>
### Laravel як повностековий фреймворк

Laravel може працювати як повностековий фреймворк. Під «повностековим» ми маємо на увазі, що ви використовуватимете Laravel для маршрутизації запитів до вашого застосунку та рендерингу фронтенду через [шаблони Blade](/docs/{{version}}/blade) або гібридну технологію односторінкових застосунків на кшталт [Inertia](https://inertiajs.com). Це найпоширеніший спосіб використання фреймворку Laravel і, на нашу думку, найпродуктивніший.

Якщо ви плануєте використовувати Laravel саме так, перегляньте нашу документацію про [фронтенд-розробку](/docs/{{version}}/frontend), [маршрутизацію](/docs/{{version}}/routing), [представлення](/docs/{{version}}/views) чи [Eloquent ORM](/docs/{{version}}/eloquent). Крім того, вам може бути цікаво дізнатися про пакети спільноти, як-от [Livewire](https://livewire.laravel.com) та [Inertia](https://inertiajs.com). Ці пакети дозволяють використовувати Laravel як повностековий фреймворк, користуючись водночас багатьма перевагами інтерфейсу односторінкових JavaScript-застосунків.

Якщо ви використовуєте Laravel як повностековий фреймворк, ми також наполегливо радимо навчитися збирати CSS і JavaScript вашого застосунку за допомогою [Vite](/docs/{{version}}/vite).

> [!NOTE]
> Якщо хочете отримати фору в розробці застосунку, перегляньте один із наших офіційних [стартових наборів застосунків](/docs/{{version}}/starter-kits).

<a name="laravel-the-api-backend"></a>
### Laravel як бекенд для API

Laravel також може працювати як бекенд для API для односторінкового JavaScript-застосунку чи мобільного застосунку. Наприклад, ви можете використовувати Laravel як бекенд для API вашого застосунку на [Next.js](https://nextjs.org). У такому разі ви можете покластися на Laravel щодо [автентифікації](/docs/{{version}}/sanctum) та зберігання й отримання даних, користуючись водночас потужними сервісами Laravel: чергами, поштою, сповіщеннями тощо.

Якщо ви плануєте використовувати Laravel саме так, перегляньте нашу документацію про [маршрутизацію](/docs/{{version}}/routing), [Laravel Sanctum](/docs/{{version}}/sanctum) та [Eloquent ORM](/docs/{{version}}/eloquent).

---

Джерело: https://laravelukraine.com/docs/13.x/configuration

# Конфігурація

- [Вступ](#introduction)
- [Конфігурація середовища](#environment-configuration)
    - [Типи змінних середовища](#environment-variable-types)
    - [Отримання конфігурації середовища](#retrieving-environment-configuration)
    - [Визначення поточного середовища](#determining-the-current-environment)
    - [Шифрування файлів середовища](#encrypting-environment-files)
- [Доступ до значень конфігурації](#accessing-configuration-values)
- [Кешування конфігурації](#configuration-caching)
- [Публікація конфігурації](#configuration-publishing)
- [Режим налагодження](#debug-mode)
- [Режим обслуговування](#maintenance-mode)

<a name="introduction"></a>
## Вступ

Усі конфігураційні файли фреймворку Laravel зберігаються в каталозі `config`. Кожна опція задокументована, тож не соромтеся переглянути файли та ознайомитися з доступними вам налаштуваннями.

Ці конфігураційні файли дозволяють налаштувати такі речі, як дані підключення до бази даних, дані поштового сервера, а також різні інші базові значення конфігурації - як-от URL застосунку та ключ шифрування.

<a name="the-about-command"></a>
#### Команда `about`

Laravel може показати огляд конфігурації, драйверів і середовища вашого застосунку за допомогою команди Artisan `about`.

```shell
php artisan about
```

Якщо вас цікавить лише певний розділ цього огляду, ви можете відфільтрувати його опцією `--only`:

```shell
php artisan about --only=environment
```

Або, щоб докладно дослідити значення конкретного конфігураційного файлу, скористайтеся командою Artisan `config:show`:

```shell
php artisan config:show database
```

<a name="environment-configuration"></a>
## Конфігурація середовища

Часто буває корисно мати різні значення конфігурації залежно від середовища, у якому працює застосунок. Наприклад, локально ви можете захотіти використовувати інший драйвер кешу, ніж на робочому сервері.

Щоб зробити це простим, Laravel використовує PHP-бібліотеку [DotEnv](https://github.com/vlucas/phpdotenv). У щойно встановленому Laravel кореневий каталог застосунку містить файл `.env.example`, який визначає багато поширених змінних середовища. Під час встановлення Laravel цей файл автоматично копіюється в `.env`.

Типовий файл `.env` Laravel містить деякі поширені значення конфігурації, які можуть відрізнятися залежно від того, чи працює ваш застосунок локально, чи на робочому веб-сервері. Далі ці значення зчитуються конфігураційними файлами в каталозі `config` за допомогою функції Laravel `env`.

Якщо ви розробляєте в команді, варто й далі тримати у проєкті файл `.env.example` та оновлювати його. Розмістивши в цьому прикладі конфігурації значення-заповнювачі, ви дасте іншим розробникам вашої команди чітко побачити, які змінні середовища потрібні для запуску застосунку.

> [!NOTE]
> Будь-яку змінну у вашому файлі `.env` можуть перевизначити зовнішні змінні середовища - наприклад, задані на рівні сервера чи системи.

<a name="environment-file-security"></a>
#### Безпека файлу середовища

Ваш файл `.env` не слід додавати до системи контролю версій, оскільки кожен розробник чи сервер, що використовує ваш застосунок, може потребувати іншої конфігурації середовища. Ба більше, це створило б загрозу безпеці, якби зловмисник отримав доступ до вашого репозиторію, адже всі конфіденційні облікові дані опинилися б на видноті.

Утім, файл середовища можна зашифрувати вбудованим [шифруванням середовища](#encrypting-environment-files) Laravel. Зашифровані файли середовища можна безпечно додавати до контролю версій.

<a name="additional-environment-files"></a>
#### Додаткові файли середовища

Перш ніж завантажити змінні середовища вашого застосунку, Laravel визначає, чи задано ззовні змінну середовища `APP_ENV`, чи вказано аргумент CLI `--env`. Якщо так, Laravel спробує завантажити файл `.env.[APP_ENV]`, якщо той існує. Якщо ні - буде завантажено типовий файл `.env`.

<a name="environment-variable-types"></a>
### Типи змінних середовища

Усі змінні у ваших файлах `.env` зазвичай розбираються як рядки, тому було створено кілька зарезервованих значень, щоб функція `env()` могла повертати ширший діапазон типів:

<div class="overflow-auto">

| Значення в `.env` | Значення `env()` |
| ----------------- | ---------------- |
| true              | (bool) true      |
| (true)            | (bool) true      |
| false             | (bool) false     |
| (false)           | (bool) false     |
| empty             | (string) ''      |
| (empty)           | (string) ''      |
| null              | (null) null      |
| (null)            | (null) null      |

</div>

Якщо вам потрібно визначити змінну середовища зі значенням, що містить пробіли, візьміть це значення в подвійні лапки:

```ini
APP_NAME="My Application"
```

<a name="retrieving-environment-configuration"></a>
### Отримання конфігурації середовища

Усі змінні, перелічені у файлі `.env`, завантажуються в суперглобальний масив PHP `$_ENV`, коли ваш застосунок отримує запит. Однак у конфігураційних файлах для отримання значень цих змінних ви можете скористатися функцією `env`. Насправді, якщо переглянути конфігураційні файли Laravel, ви помітите, що багато опцій уже використовують цю функцію:

```php
'debug' => (bool) env('APP_DEBUG', false),
```

Друге значення, передане функції `env`, - це «значення за замовчуванням». Воно повернеться, якщо для вказаного ключа змінної середовища не існує.

<a name="determining-the-current-environment"></a>
### Визначення поточного середовища

Поточне середовище застосунку визначається змінною `APP_ENV` із вашого файлу `.env`. Отримати це значення можна методом `environment` [фасаду](/docs/{{version}}/facades) `App`:

```php
use Illuminate\Support\Facades\App;

$environment = App::environment();
```

Ви також можете передати методу `environment` аргументи, щоб перевірити, чи відповідає середовище певному значенню. Метод поверне `true`, якщо середовище збігається з будь-яким із переданих значень:

```php
if (App::environment('local')) {
    // The environment is local
}

if (App::environment(['local', 'staging'])) {
    // The environment is either local OR staging...
}
```

> [!NOTE]
> Визначення поточного середовища застосунку можна перевизначити, задавши змінну середовища `APP_ENV` на рівні сервера.

<a name="encrypting-environment-files"></a>
### Шифрування файлів середовища

Незашифровані файли середовища ніколи не слід зберігати в контролі версій. Однак Laravel дозволяє зашифрувати їх, щоб їх можна було безпечно додати до контролю версій разом із рештою застосунку.

<a name="encryption"></a>
#### Шифрування

Щоб зашифрувати файл середовища, скористайтеся командою `env:encrypt`:

```shell
php artisan env:encrypt
```

Виконання команди `env:encrypt` зашифрує ваш файл `.env` і помістить зашифрований вміст у файл `.env.encrypted`. Ключ розшифрування виводиться у результаті виконання команди, і його слід зберігати в надійному менеджері паролів. Якщо ви хочете надати власний ключ шифрування, скористайтеся опцією `--key` під час виклику команди:

```shell
php artisan env:encrypt --key=3UVsEgGVK36XN82KKeyLFMhvosbZN1aF
```

> [!NOTE]
> Довжина наданого ключа має відповідати довжині, якої потребує використовуваний шифр. За замовчуванням Laravel використовує шифр `AES-256-CBC`, який потребує ключа з 32 символів. Ви вільні використовувати будь-який шифр, що його підтримує [шифрувальник](/docs/{{version}}/encryption) Laravel, передавши опцію `--cipher` під час виклику команди.

Якщо ваш застосунок має кілька файлів середовища, як-от `.env` і `.env.staging`, ви можете вказати файл, який слід зашифрувати, передавши ім'я середовища через опцію `--env`:

```shell
php artisan env:encrypt --env=staging
```

<a name="readable-variable-names"></a>
#### Читабельні імена змінних

Шифруючи файл середовища, ви можете скористатися опцією `--readable`, щоб зберегти видимими імена змінних, зашифрувавши лише їхні значення:

```shell
php artisan env:encrypt --readable
```

Це створить зашифрований файл такого формату:

```ini
APP_NAME=eyJpdiI6...
APP_ENV=eyJpdiI6...
APP_KEY=eyJpdiI6...
APP_DEBUG=eyJpdiI6...
APP_URL=eyJpdiI6...
```

Читабельний формат дозволяє бачити, які змінні середовища існують, не розкриваючи конфіденційних даних. Він також значно спрощує перегляд змін у pull request, адже ви бачите, які змінні було додано, вилучено чи перейменовано, не розшифровуючи файл.

Розшифровуючи файли середовища, Laravel автоматично визначає використаний формат, тож для команди `env:decrypt` жодних додаткових опцій не потрібно.

> [!NOTE]
> Коли використовується опція `--readable`, коментарі та порожні рядки з вихідного файлу середовища не потрапляють до зашифрованого результату.

<a name="updating-readable-environment-files"></a>
#### Оновлення читабельних файлів середовища

Під час оновлення читабельного зашифрованого файлу середовища Laravel зберігає незмінені значення та видаляє змінні, яких більше немає у вихідному файлі середовища:

```shell
php artisan env:encrypt --readable --key=3UVsEgGVK36XN82KKeyLFMhvosbZN1aF
```

Зашифрований файл буде створено, якщо він не існує. Під час оновлення наявного файлу надайте той самий ключ шифрування та шифр, які використовувалися для його створення. Якщо файл не вдається розшифрувати, команда завершиться з помилкою, не перезаписуючи його.

Щоб знову зашифрувати всі значення, використовуйте опцію `--force`. Це дозволяє змінити ключ шифрування або замінити недійсний зашифрований файл:

```shell
php artisan env:encrypt --readable --force --key=3UVsEgGVK36XN82KKeyLFMhvosbZN1aF
```

<a name="decryption"></a>
#### Розшифрування

Щоб розшифрувати файл середовища, скористайтеся командою `env:decrypt`. Ця команда потребує ключа розшифрування, який Laravel візьме зі змінної середовища `LARAVEL_ENV_ENCRYPTION_KEY`:

```shell
php artisan env:decrypt
```

Або ж ключ можна передати команді напряму через опцію `--key`:

```shell
php artisan env:decrypt --key=3UVsEgGVK36XN82KKeyLFMhvosbZN1aF
```

Коли викликано команду `env:decrypt`, Laravel розшифрує вміст файлу `.env.encrypted` і помістить розшифрований вміст у файл `.env`.

Команді `env:decrypt` можна передати опцію `--cipher`, щоб використати власний шифр:

```shell
php artisan env:decrypt --key=qUWuNRdfuImXcKxZ --cipher=AES-128-CBC
```

Якщо ваш застосунок має кілька файлів середовища, як-от `.env` і `.env.staging`, ви можете вказати файл, який слід розшифрувати, передавши ім'я середовища через опцію `--env`:

```shell
php artisan env:decrypt --env=staging
```

Щоб перезаписати наявний файл середовища, передайте команді `env:decrypt` опцію `--force`:

```shell
php artisan env:decrypt --force
```

<a name="accessing-configuration-values"></a>
## Доступ до значень конфігурації

Ви можете легко отримати значення конфігурації за допомогою фасаду `Config` або глобальної функції `config` із будь-якого місця вашого застосунку. Доступ до значень конфігурації здійснюється «крапковим» синтаксисом, що містить ім'я файлу та потрібної опції. Можна також вказати значення за замовчуванням, яке повернеться, якщо опції конфігурації не існує:

```php
use Illuminate\Support\Facades\Config;

$value = Config::get('app.timezone');

$value = config('app.timezone');

// Retrieve a default value if the configuration value does not exist...
$value = config('app.timezone', 'Asia/Seoul');
```

Щоб задати значення конфігурації під час виконання, викличте метод `set` фасаду `Config` або передайте масив функції `config`:

```php
Config::set('app.timezone', 'America/Chicago');

config(['app.timezone' => 'America/Chicago']);
```

Щоб полегшити статичний аналіз, фасад `Config` також надає типізовані методи отримання конфігурації. Якщо отримане значення не відповідає очікуваному типу, буде викинуто виняток:

```php
Config::string('config-key');
Config::integer('config-key');
Config::float('config-key');
Config::boolean('config-key');
Config::array('config-key');
Config::collection('config-key');
```

<a name="configuration-caching"></a>
## Кешування конфігурації

Щоб пришвидшити ваш застосунок, слід закешувати всі конфігураційні файли в один файл командою Artisan `config:cache`. Вона об'єднає всі опції конфігурації вашого застосунку в єдиний файл, який фреймворк зможе швидко завантажити.

Зазвичай команду `php artisan config:cache` варто виконувати як частину процесу розгортання в продакшені. Її не слід виконувати під час локальної розробки, оскільки опції конфігурації доведеться часто змінювати в процесі роботи над застосунком.

Щойно конфігурацію закешовано, фреймворк не завантажуватиме файл `.env` вашого застосунку під час запитів чи команд Artisan; отже, функція `env` повертатиме лише зовнішні змінні середовища системного рівня.

Тому переконайтеся, що викликаєте функцію `env` лише всередині конфігураційних (`config`) файлів вашого застосунку. Багато прикладів цього можна побачити в типових конфігураційних файлах Laravel. Доступ до значень конфігурації з будь-якого місця застосунку можна отримати функцією `config`, [описаною вище](#accessing-configuration-values).

Команда `config:clear` дозволяє очистити закешовану конфігурацію:

```shell
php artisan config:clear
```

> [!WARNING]
> Якщо ви виконуєте команду `config:cache` під час розгортання, переконайтеся, що викликаєте функцію `env` лише всередині конфігураційних файлів. Щойно конфігурацію закешовано, файл `.env` не завантажуватиметься; отже, функція `env` повертатиме лише зовнішні змінні середовища системного рівня.

<a name="configuration-publishing"></a>
## Публікація конфігурації

Більшість конфігураційних файлів Laravel уже опубліковано в каталозі `config` вашого застосунку; однак деякі файли, як-от `cors.php` і `view.php`, за замовчуванням не публікуються, адже більшості застосунків ніколи не знадобиться їх змінювати.

Утім, ви можете скористатися командою Artisan `config:publish`, щоб опублікувати будь-які конфігураційні файли, які не публікуються за замовчуванням:

```shell
php artisan config:publish

php artisan config:publish --all
```

<a name="debug-mode"></a>
## Режим налагодження

Опція `debug` у вашому конфігураційному файлі `config/app.php` визначає, скільки інформації про помилку насправді показується користувачеві. За замовчуванням ця опція налаштована на значення змінної середовища `APP_DEBUG`, яка зберігається у вашому файлі `.env`.

> [!WARNING]
> Для локальної розробки змінній середовища `APP_DEBUG` варто задати значення `true`. **У продакшен-середовищі це значення завжди має бути `false`. Якщо в продакшені змінна матиме значення `true`, ви ризикуєте розкрити конфіденційні значення конфігурації кінцевим користувачам вашого застосунку.**

<a name="maintenance-mode"></a>
## Режим обслуговування

Коли ваш застосунок перебуває в режимі обслуговування, для всіх запитів до нього показуватиметься спеціальне представлення. Це дозволяє легко «вимкнути» застосунок під час оновлення чи технічних робіт. Перевірка режиму обслуговування входить до типового стека `middleware` вашого застосунку. Якщо застосунок у режимі обслуговування, буде викинуто екземпляр `Symfony\Component\HttpKernel\Exception\HttpException` зі статус-кодом 503.

Щоб увімкнути режим обслуговування, виконайте команду Artisan `down`:

```shell
php artisan down
```

Якщо ви хочете, щоб з усіма відповідями в режимі обслуговування надсилався HTTP-заголовок `Refresh`, передайте опцію `refresh` під час виклику команди `down`. Заголовок `Refresh` вкаже браузеру автоматично оновити сторінку через задану кількість секунд:

```shell
php artisan down --refresh=15
```

Команді `down` можна також передати опцію `retry`, значення якої буде встановлено як HTTP-заголовок `Retry-After`, хоча браузери зазвичай його ігнорують:

```shell
php artisan down --retry=60
```

<a name="bypassing-maintenance-mode"></a>
#### Обхід режиму обслуговування

Щоб дозволити обхід режиму обслуговування за секретним токеном, скористайтеся опцією `secret` і вкажіть токен обходу:

```shell
php artisan down --secret="1630542a-246b-4b66-afa1-dd72a4c43515"
```

Перевівши застосунок у режим обслуговування, ви можете перейти за URL застосунку, що відповідає цьому токену, і Laravel видасть вашому браузеру cookie обходу режиму обслуговування:

```shell
https://example.com/1630542a-246b-4b66-afa1-dd72a4c43515
```

Якщо ви хочете, щоб Laravel згенерував секретний токен за вас, скористайтеся опцією `with-secret`. Секрет буде показано вам, щойно застосунок перейде в режим обслуговування:

```shell
php artisan down --with-secret
```

Звернувшись до цього прихованого маршруту, ви будете перенаправлені на маршрут `/` застосунку. Щойно cookie видано вашому браузеру, ви зможете переглядати застосунок як звичайно, ніби він і не в режимі обслуговування.

> [!NOTE]
> Ваш секрет режиму обслуговування зазвичай має складатися з літер і цифр та, за бажанням, дефісів. Уникайте символів, що мають спеціальне значення в URL, як-от `?` чи `&`.

<a name="maintenance-mode-on-multiple-servers"></a>
#### Режим обслуговування на кількох серверах

За замовчуванням Laravel визначає, чи перебуває застосунок у режимі обслуговування, за допомогою файлової системи. Це означає, що для активації режиму обслуговування команду `php artisan down` доведеться виконати на кожному сервері, де розміщено ваш застосунок.

Як альтернативу Laravel пропонує спосіб обробки режиму обслуговування на основі кешу. Цей спосіб потребує виконання команди `php artisan down` лише на одному сервері. Щоб скористатися ним, змініть змінні режиму обслуговування у файлі `.env` вашого застосунку. Оберіть сховище кешу (`store`), доступне всім вашим серверам. Це гарантує, що стан режиму обслуговування узгоджено підтримуватиметься на кожному сервері:

```ini
APP_MAINTENANCE_DRIVER=cache
APP_MAINTENANCE_STORE=database
```

<a name="pre-rendering-the-maintenance-mode-view"></a>
#### Попередній рендеринг представлення режиму обслуговування

Якщо ви використовуєте команду `php artisan down` під час розгортання, ваші користувачі все одно можуть іноді натрапляти на помилки, звертаючись до застосунку в момент, коли оновлюються залежності Composer чи інші компоненти інфраструктури. Це стається тому, що для визначення, що застосунок у режимі обслуговування, і рендерингу відповідного представлення шаблонізатором має завантажитися значна частина фреймворку Laravel.

Тому Laravel дозволяє попередньо відрендерити представлення режиму обслуговування, яке повертатиметься на самому початку циклу запиту. Це представлення рендериться до завантаження будь-яких залежностей вашого застосунку. Попередньо відрендерити потрібний шаблон можна опцією `render` команди `down`:

```shell
php artisan down --render="errors::503"
```

<a name="redirecting-maintenance-mode-requests"></a>
#### Перенаправлення запитів у режимі обслуговування

У режимі обслуговування Laravel показуватиме відповідне представлення для всіх URL застосунку, до яких намагається звернутися користувач. За бажанням ви можете вказати Laravel перенаправляти всі запити на конкретний URL. Це робиться опцією `redirect`. Наприклад, ви можете захотіти перенаправляти всі запити на URI `/`:

```shell
php artisan down --redirect=/
```

<a name="disabling-maintenance-mode"></a>
#### Вимкнення режиму обслуговування

Щоб вимкнути режим обслуговування, скористайтеся командою `up`:

```shell
php artisan up
```

> [!NOTE]
> Ви можете налаштувати типовий шаблон режиму обслуговування, визначивши власний у файлі `resources/views/errors/503.blade.php`.

<a name="maintenance-mode-queues"></a>
#### Режим обслуговування та черги

Поки ваш застосунок у режимі обслуговування, жодні [завдання з черг](/docs/{{version}}/queues) не оброблятимуться. Обробка завдань відновиться як звичайно, щойно застосунок вийде з режиму обслуговування.

<a name="alternatives-to-maintenance-mode"></a>
#### Альтернативи режиму обслуговування

Оскільки режим обслуговування потребує кількох секунд простою вашого застосунку, розгляньте можливість запускати застосунки на повністю керованій платформі на кшталт [Laravel Cloud](https://cloud.laravel.com), щоб досягти розгортання без простою.

---

Джерело: https://laravelukraine.com/docs/13.x/ai

# Розробка за допомогою AI

 - [Вступ](#introduction)
     - [Чому Laravel для розробки з AI?](#why-laravel-for-ai-development)
 - [Laravel Boost](#laravel-boost)
     - [Встановлення](#installation)
     - [Доступні інструменти](#available-tools)
     - [Настанови для AI](#ai-guidelines)
     - [Навички агентів](#agent-skills)
     - [Пошук у документації](#documentation-search)
     - [Інтеграція з агентами](#agents-integration)

<a name="introduction"></a>
## Вступ

Laravel має унікальні передумови, щоб бути найкращим фреймворком для розробки за допомогою AI та агентів. Поява AI-агентів для написання коду - [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [OpenCode](https://opencode.ai), [Cursor](https://cursor.com) і [GitHub Copilot](https://github.com/features/copilot) - змінила те, як розробники пишуть код. Ці інструменти можуть створювати цілі можливості, налагоджувати складні проблеми та рефакторити код із небаченою швидкістю, але їхня ефективність значною мірою залежить від того, наскільки добре вони розуміють вашу кодову базу.

<a name="why-laravel-for-ai-development"></a>
### Чому Laravel для розробки з AI?

Чіткі домовленості та добре визначена структура роблять Laravel ідеальним фреймворком для розробки за допомогою AI. Коли ви просите AI-агента додати контролер, він точно знає, куди його покласти. Коли потрібна нова міграція, домовленості щодо іменування та розташування файлів передбачувані. Ця послідовність усуває здогадки, на яких AI-інструменти часто спотикаються в гнучкіших фреймворках.

Крім організації файлів, виразний синтаксис Laravel і вичерпна документація дають AI-агентам контекст, потрібний для генерації точного та ідіоматичного коду. Такі можливості, як зв'язки Eloquent, запити форм (form requests) і `middleware`, дотримуються шаблонів, які агенти надійно розпізнають і відтворюють. Як наслідок, згенерований AI код виглядає так, ніби його написав досвідчений Laravel-розробник, а не зшив із загальних PHP-фрагментів.

<a name="laravel-boost"></a>
## Laravel Boost

[Laravel Boost](https://github.com/laravel/boost) долає розрив між AI-агентами для написання коду та вашим застосунком Laravel. Boost - це MCP-сервер (Model Context Protocol) із понад 15 спеціалізованими інструментами, які дають AI-агентам глибоке розуміння структури вашого застосунку, бази даних, маршрутів тощо. Коли ви встановлюєте Boost, ваш AI-агент перетворюється із загального помічника з написання коду на експерта з Laravel, який розуміє саме ваш застосунок.

Boost надає три основні можливості: набір MCP-інструментів для інспекції вашого застосунку та взаємодії з ним, композиційні настанови для AI, створені спеціально для екосистеми Laravel, і потужний API документації, що містить понад 17 000 фрагментів знань про Laravel.

<a name="installation"></a>
### Встановлення

Boost можна встановити в застосунках Laravel 10, 11, 12 і 13, що працюють на PHP 8.1 або новішому. Щоб почати, встановіть Boost як залежність для розробки:

```shell
composer require laravel/boost --dev
```

Після встановлення запустіть інтерактивний інсталятор:

```shell
php artisan boost:install
```

Інсталятор автоматично визначить вашу IDE та AI-агентів, дозволивши обрати інтеграції, які мають сенс для вашого проєкту. Boost згенерує потрібні конфігураційні файли, як-от `.mcp.json` для редакторів, сумісних з MCP, і файли настанов для контексту AI.

> [!NOTE]
> Згенеровані конфігураційні файли на кшталт `.mcp.json`, `CLAUDE.md` і `boost.json` можна безпечно додати до `.gitignore`, якщо ви віддаєте перевагу тому, щоб кожен розробник налаштовував власне середовище.

<a name="available-tools"></a>
### Доступні інструменти

Boost надає AI-агентам вичерпний набір інструментів через Model Context Protocol. Ці інструменти дозволяють агентам глибоко розуміти ваш застосунок Laravel і взаємодіяти з ним:

<div class="content-list" markdown="1">

- **Інспекція застосунку** - дізнаватися версії PHP і Laravel, переглядати список встановлених пакетів, а також конфігурацію застосунку та змінні середовища.
- **Інструменти бази даних** - переглядати схему бази даних, виконувати запити лише на читання й розуміти структуру ваших даних, не полишаючи діалогу.
- **Інспекція маршрутів** - переглядати всі зареєстровані маршрути разом із їхніми `middleware`, контролерами та параметрами.
- **Команди Artisan** - знаходити доступні команди Artisan та їхні аргументи, щоб агенти могли пропонувати й виконувати потрібні для вашого завдання команди.
- **Аналіз логів** - читати й аналізувати файли логів вашого застосунку, щоб допомогти в налагодженні.
- **Логи браузера** - отримувати доступ до логів і помилок консолі браузера під час розробки з фронтенд-інструментами Laravel.
- **Інтеграція з Tinker** - виконувати PHP-код у контексті вашого застосунку через Laravel Tinker, що дозволяє агентам перевіряти гіпотези та підтверджувати поведінку.
- **Пошук у документації** - шукати в документації екосистеми Laravel з результатами, підібраними під версії ваших встановлених пакетів.

</div>

<a name="ai-guidelines"></a>
### Настанови для AI

Boost містить вичерпний набір настанов для AI, створених спеціально для екосистеми Laravel. Ці настанови вчать AI-агентів писати ідіоматичний код Laravel, дотримуватися домовленостей фреймворку й уникати типових пасток. Настанови композиційні та враховують версії, тобто агенти отримують інструкції, доречні саме для ваших версій пакетів.

Настанови доступні для самого Laravel і понад 16 пакетів екосистеми Laravel, зокрема:

<div class="content-list" markdown="1">

- Livewire (2.x, 3.x і 4.x)
- Inertia.js (варіанти для React, Svelte і Vue)
- Tailwind CSS (3.x і 4.x)
- Filament (3.x і 4.x)
- PHPUnit
- Pest PHP
- Laravel Pint
- І багато інших

</div>

Коли ви виконуєте `boost:install`, Boost автоматично визначає, які пакети використовує ваш застосунок, і збирає відповідні настанови у файли AI-контексту вашого проєкту.

<a name="agent-skills"></a>
### Навички агентів

[Навички агентів](https://agentskills.io/home) - це легкі, цільові модулі знань, які агенти можуть активувати на вимогу, працюючи в конкретній предметній області. На відміну від настанов, які завантажуються наперед, навички дозволяють підвантажувати докладні шаблони й найкращі практики лише тоді, коли вони доречні, зменшуючи роздування контексту та підвищуючи релевантність згенерованого AI коду.

Навички доступні для популярних пакетів Laravel, як-от Livewire, Inertia, Tailwind CSS, Pest тощо. Коли ви виконуєте `boost:install` і обираєте навички як одну з можливостей, вони встановлюються автоматично на основі пакетів, виявлених у вашому `composer.json`.

<a name="documentation-search"></a>
### Пошук у документації

Boost містить потужний API документації, який дає AI-агентам доступ до понад 17 000 фрагментів документації екосистеми Laravel. На відміну від звичайного пошуку в інтернеті, ця документація проіндексована, векторизована та відфільтрована відповідно до ваших версій пакетів.

Коли агентові потрібно зрозуміти, як працює якась можливість, він може виконати пошук через API документації Boost і отримати точну інформацію, прив'язану до конкретної версії. Це усуває поширену проблему, коли AI-агенти пропонують застарілі методи чи синтаксис зі старіших версій фреймворку.

<a name="agents-integration"></a>
### Інтеграція з агентами

Boost інтегрується з популярними IDE та AI-інструментами, які підтримують Model Context Protocol. Докладні інструкції з налаштування для Cursor, Claude Code, Codex, Gemini CLI, GitHub Copilot і Junie дивіться в розділі [Налаштування ваших агентів](/docs/{{version}}/boost#set-up-your-agents) документації Boost.

---

Джерело: https://laravelukraine.com/docs/13.x/structure

# Структура каталогів

- [Вступ](#introduction)
- [Кореневий каталог](#the-root-directory)
    - [Каталог `app`](#the-root-app-directory)
    - [Каталог `bootstrap`](#the-bootstrap-directory)
    - [Каталог `config`](#the-config-directory)
    - [Каталог `database`](#the-database-directory)
    - [Каталог `public`](#the-public-directory)
    - [Каталог `resources`](#the-resources-directory)
    - [Каталог `routes`](#the-routes-directory)
    - [Каталог `storage`](#the-storage-directory)
    - [Каталог `tests`](#the-tests-directory)
    - [Каталог `vendor`](#the-vendor-directory)
- [Каталог App](#the-app-directory)
    - [Каталог `Broadcasting`](#the-broadcasting-directory)
    - [Каталог `Console`](#the-console-directory)
    - [Каталог `Events`](#the-events-directory)
    - [Каталог `Exceptions`](#the-exceptions-directory)
    - [Каталог `Http`](#the-http-directory)
    - [Каталог `Jobs`](#the-jobs-directory)
    - [Каталог `Listeners`](#the-listeners-directory)
    - [Каталог `Mail`](#the-mail-directory)
    - [Каталог `Models`](#the-models-directory)
    - [Каталог `Notifications`](#the-notifications-directory)
    - [Каталог `Policies`](#the-policies-directory)
    - [Каталог `Providers`](#the-providers-directory)
    - [Каталог `Rules`](#the-rules-directory)

<a name="introduction"></a>
## Вступ

Типова структура застосунку Laravel має бути гарною відправною точкою як для великих, так і для малих застосунків. Але ви вільні організовувати свій застосунок так, як вам зручно. Laravel майже не накладає обмежень на те, де розташований той чи інший клас, - аби Composer міг його автозавантажити.

<a name="the-root-directory"></a>
## Кореневий каталог

<a name="the-root-app-directory"></a>
### Каталог App

Каталог `app` містить основний код вашого застосунку. Незабаром ми розглянемо його докладніше, але майже всі класи вашого застосунку будуть саме тут.

<a name="the-bootstrap-directory"></a>
### Каталог Bootstrap

Каталог `bootstrap` містить файл `app.php`, який завантажує фреймворк. Тут також є каталог `cache` із файлами, згенерованими фреймворком для оптимізації швидкодії, як-от файли кешу маршрутів і сервісів.

<a name="the-config-directory"></a>
### Каталог Config

Каталог `config`, як випливає з назви, містить усі конфігураційні файли вашого застосунку. Варто прочитати всі ці файли й ознайомитися з доступними вам опціями.

<a name="the-database-directory"></a>
### Каталог Database

Каталог `database` містить міграції бази даних, фабрики моделей і наповнювачі. За бажанням ви можете зберігати тут і базу даних SQLite.

<a name="the-public-directory"></a>
### Каталог Public

Каталог `public` містить файл `index.php` - точку входу для всіх запитів, що надходять до вашого застосунку, - і налаштовує автозавантаження. Тут також лежать ваші ресурси: зображення, JavaScript і CSS.

<a name="the-resources-directory"></a>
### Каталог Resources

Каталог `resources` містить ваші [представлення](/docs/{{version}}/views), а також вихідні, нескомпільовані ресурси на кшталт CSS чи JavaScript.

<a name="the-routes-directory"></a>
### Каталог Routes

Каталог `routes` містить усі визначення маршрутів вашого застосунку. За замовчуванням Laravel постачається з двома файлами маршрутів: `web.php` і `console.php`.

Файл `web.php` містить маршрути, які Laravel поміщає у групу `middleware` під назвою `web`, що забезпечує стан сесії, захист від CSRF і шифрування cookie. Якщо ваш застосунок не надає безстанового RESTful API, то, найімовірніше, усі ваші маршрути будуть визначені саме у файлі `web.php`.

У файлі `console.php` ви можете визначати консольні команди на основі замикань. Кожне замикання прив'язується до екземпляра команди, що дає простий підхід до роботи з методами введення-виведення кожної команди. Хоча цей файл не визначає HTTP-маршрутів, він визначає консольні точки входу (маршрути) до вашого застосунку. У файлі `console.php` ви також можете [планувати](/docs/{{version}}/scheduling) завдання.

За бажанням ви можете встановити додаткові файли маршрутів для API (`api.php`) і каналів бродкастингу (`channels.php`) за допомогою команд Artisan `install:api` та `install:broadcasting`.

Файл `api.php` містить маршрути, які мають бути безстановими, тож запити, що надходять до застосунку через них, мають автентифікуватися [за допомогою токенів](/docs/{{version}}/sanctum) і не матимуть доступу до стану сесії.

У файлі `channels.php` ви можете зареєструвати всі канали [бродкастингу подій](/docs/{{version}}/broadcasting), які підтримує ваш застосунок.

<a name="the-storage-directory"></a>
### Каталог Storage

Каталог `storage` містить ваші логи, скомпільовані шаблони Blade, файлові сесії, файлові кеші та інші файли, згенеровані фреймворком. Він поділений на каталоги `app`, `framework` і `logs`. Каталог `app` можна використовувати для зберігання будь-яких файлів, згенерованих вашим застосунком. Каталог `framework` слугує для зберігання файлів і кешів, згенерованих фреймворком. Нарешті, каталог `logs` містить файли логів вашого застосунку.

Каталог `storage/app/public` можна використовувати для зберігання створених користувачами файлів, як-от аватари профілів, які мають бути загальнодоступними. Вам слід створити символічне посилання `public/storage`, що вказує на цей каталог. Створити його можна командою Artisan `php artisan storage:link`.

<a name="the-tests-directory"></a>
### Каталог Tests

Каталог `tests` містить ваші автоматизовані тести. Приклади модульних і функціональних тестів [Pest](https://pestphp.com) чи [PHPUnit](https://phpunit.de/) постачаються одразу. Кожен клас тесту має закінчуватися словом `Test`. Запускати тести можна командами `/vendor/bin/pest` або `/vendor/bin/phpunit`. Або ж, якщо вам потрібне докладніше й гарніше подання результатів, запускайте тести командою Artisan `php artisan test`.

<a name="the-vendor-directory"></a>
### Каталог Vendor

Каталог `vendor` містить ваші залежності [Composer](https://getcomposer.org).

<a name="the-app-directory"></a>
## Каталог App

Більша частина вашого застосунку розміщується в каталозі `app`. За замовчуванням цей каталог має простір імен `App` і автозавантажується Composer за [стандартом автозавантаження PSR-4](https://www.php-fig.org/psr/psr-4/).

За замовчуванням каталог `app` містить каталоги `Http`, `Models` і `Providers`. Однак із часом усередині нього з'являтимуться й інші каталоги, у міру того як ви використовуватимете команди Artisan `make` для генерації класів. Наприклад, каталог `app/Console` не існуватиме, доки ви не виконаєте команду Artisan `make:command` для створення класу команди.

Каталоги `Console` і `Http` докладніше описані у відповідних розділах нижче, але сприймайте їх як такі, що надають API до ядра вашого застосунку. Протокол HTTP і CLI - це механізми взаємодії з вашим застосунком, які самі по собі не містять логіки застосунку. Інакше кажучи, це два способи віддавати команди вашому застосунку. Каталог `Console` містить усі ваші команди Artisan, а каталог `Http` - ваші контролери, `middleware` та запити.

> [!NOTE]
> Багато класів у каталозі `app` можна згенерувати за допомогою команд Artisan. Щоб переглянути доступні команди, виконайте в терміналі `php artisan list make`.

<a name="the-broadcasting-directory"></a>
### Каталог Broadcasting

Каталог `Broadcasting` містить усі класи каналів бродкастингу вашого застосунку. Ці класи генеруються командою `make:channel`. За замовчуванням цього каталогу немає, але його буде створено, коли ви створите свій перший канал. Щоб дізнатися більше про канали, перегляньте документацію з [бродкастингу подій](/docs/{{version}}/broadcasting).

<a name="the-console-directory"></a>
### Каталог Console

Каталог `Console` містить усі власні команди Artisan вашого застосунку. Ці команди можна генерувати командою `make:command`.

<a name="the-events-directory"></a>
### Каталог Events

За замовчуванням цього каталогу немає, але його створять команди Artisan `event:generate` та `make:event`. Каталог `Events` містить [класи подій](/docs/{{version}}/events). Події можна використовувати, щоб сповіщати інші частини вашого застосунку про те, що сталася певна дія, - це дає значну гнучкість і послаблює зв'язність.

<a name="the-exceptions-directory"></a>
### Каталог Exceptions

Каталог `Exceptions` містить усі власні винятки вашого застосунку. Ці винятки можна генерувати командою `make:exception`.

<a name="the-http-directory"></a>
### Каталог Http

Каталог `Http` містить ваші контролери, `middleware` та запити форм. Майже вся логіка обробки запитів, що надходять до вашого застосунку, розміщується саме тут.

<a name="the-jobs-directory"></a>
### Каталог Jobs

За замовчуванням цього каталогу немає, але його буде створено, якщо ви виконаєте команду Artisan `make:job`. Каталог `Jobs` містить [завдання для черг](/docs/{{version}}/queues) вашого застосунку. Завдання можуть ставитися вашим застосунком у чергу або виконуватися синхронно в межах поточного життєвого циклу запиту. Завдання, що виконуються синхронно під час поточного запиту, іноді називають «командами», оскільки вони є реалізацією [шаблону «команда»](https://en.wikipedia.org/wiki/Command_pattern).

<a name="the-listeners-directory"></a>
### Каталог Listeners

За замовчуванням цього каталогу немає, але його буде створено, якщо ви виконаєте команди Artisan `event:generate` чи `make:listener`. Каталог `Listeners` містить класи, які обробляють ваші [події](/docs/{{version}}/events). Слухачі подій отримують екземпляр події й виконують логіку у відповідь на її спрацювання. Наприклад, подію `UserRegistered` може обробляти слухач `SendWelcomeEmail`.

<a name="the-mail-directory"></a>
### Каталог Mail

За замовчуванням цього каталогу немає, але його буде створено, якщо ви виконаєте команду Artisan `make:mail`. Каталог `Mail` містить усі ваші [класи, що представляють електронні листи](/docs/{{version}}/mail), які надсилає ваш застосунок. Об'єкти пошти дозволяють інкапсулювати всю логіку створення листа в одному простому класі, який можна надіслати методом `Mail::send`.

<a name="the-models-directory"></a>
### Каталог Models

Каталог `Models` містить усі ваші [класи моделей Eloquent](/docs/{{version}}/eloquent). Eloquent ORM, що постачається з Laravel, дає гарну й просту реалізацію ActiveRecord для роботи з базою даних. Кожній таблиці бази даних відповідає своя «модель», через яку ви взаємодієте з цією таблицею. Моделі дозволяють робити запити до даних у ваших таблицях, а також вставляти нові записи.

<a name="the-notifications-directory"></a>
### Каталог Notifications

За замовчуванням цього каталогу немає, але його буде створено, якщо ви виконаєте команду Artisan `make:notification`. Каталог `Notifications` містить усі «транзакційні» [сповіщення](/docs/{{version}}/notifications), які надсилає ваш застосунок, як-от прості повідомлення про події, що відбуваються всередині нього. Механізм сповіщень Laravel абстрагує надсилання сповіщень через різні драйвери: електронну пошту, Slack, SMS чи збереження в базі даних.

<a name="the-policies-directory"></a>
### Каталог Policies

За замовчуванням цього каталогу немає, але його буде створено, якщо ви виконаєте команду Artisan `make:policy`. Каталог `Policies` містить [класи політик авторизації](/docs/{{version}}/authorization) вашого застосунку. Політики визначають, чи може користувач виконати певну дію щодо ресурсу.

<a name="the-providers-directory"></a>
### Каталог Providers

Каталог `Providers` містить усі [сервіс-провайдери](/docs/{{version}}/providers) вашого застосунку. Сервіс-провайдери завантажують ваш застосунок, прив'язуючи сервіси в сервіс-контейнері, реєструючи події та виконуючи інші завдання, щоб підготувати застосунок до вхідних запитів.

У новому застосунку Laravel цей каталог уже міститиме `AppServiceProvider`. Ви вільні додавати сюди власні провайдери за потреби.

<a name="the-rules-directory"></a>
### Каталог Rules

За замовчуванням цього каталогу немає, але його буде створено, якщо ви виконаєте команду Artisan `make:rule`. Каталог `Rules` містить об'єкти власних правил валідації вашого застосунку. Правила слугують для інкапсуляції складної логіки валідації в простому об'єкті. Докладніше дивіться в [документації з валідації](/docs/{{version}}/validation).

---

Джерело: https://laravelukraine.com/docs/13.x/frontend

# Фронтенд

- [Вступ](#introduction)
- [Використання PHP](#using-php)
    - [PHP і Blade](#php-and-blade)
    - [Livewire](#livewire)
    - [Стартові набори](#php-starter-kits)
- [Використання React, Svelte чи Vue](#using-react-svelte-or-vue)
    - [Inertia](#inertia)
    - [Стартові набори](#inertia-starter-kits)
- [Складання ресурсів](#bundling-assets)

<a name="introduction"></a>
## Вступ

Laravel - це бекенд-фреймворк, який дає всі можливості, потрібні для створення сучасних веб-застосунків: [маршрутизацію](/docs/{{version}}/routing), [валідацію](/docs/{{version}}/validation), [кешування](/docs/{{version}}/cache), [черги](/docs/{{version}}/queues), [файлове сховище](/docs/{{version}}/filesystem) тощо. Утім, ми вважаємо важливим давати розробникам приємний повностековий досвід, зокрема потужні підходи до створення фронтенду вашого застосунку.

Є два основні способи підійти до фронтенд-розробки, створюючи застосунок на Laravel, і вибір залежить від того, чи хочете ви будувати фронтенд засобами PHP, чи за допомогою JavaScript-фреймворків на кшталт React, Svelte і Vue. Нижче ми розглянемо обидва варіанти, щоб ви могли зважено обрати найкращий підхід до фронтенду для свого застосунку.

<a name="using-php"></a>
## Використання PHP

<a name="php-and-blade"></a>
### PHP і Blade

Колись більшість PHP-застосунків віддавали HTML у браузер за допомогою простих HTML-шаблонів, перемежованих інструкціями `echo`, які виводили дані, отримані з бази під час запиту:

```blade
<div>
    <?php foreach ($users as $user): ?>
        Hello, <?php echo $user->name; ?> <br />
    <?php endforeach; ?>
</div>
```

У Laravel такий підхід до рендерингу HTML усе ще доступний завдяки [представленням](/docs/{{version}}/views) і [Blade](/docs/{{version}}/blade). Blade - надзвичайно легка мова шаблонів, що дає зручний короткий синтаксис для виведення даних, ітерування по них тощо:

```blade
<div>
    @foreach ($users as $user)
        Hello, {{ $user->name }} <br />
    @endforeach
</div>
```

Коли застосунки будуються в такий спосіб, надсилання форм та інші взаємодії зі сторінкою зазвичай отримують із сервера цілком новий HTML-документ, і браузер перерендерює всю сторінку. Навіть сьогодні багатьом застосункам чудово підходить фронтенд, побудований саме так - на простих шаблонах Blade.

<a name="growing-expectations"></a>
#### Зростання очікувань

Однак у міру того, як очікування користувачів від веб-застосунків зростали, багато розробників відчули потребу створювати динамічніші фронтенди з відточенішими взаємодіями. З огляду на це частина розробників обирає будувати фронтенд свого застосунку за допомогою JavaScript-фреймворків, як-от React, Svelte і Vue.

Інші, віддаючи перевагу звичній для себе бекенд-мові, розробили рішення, що дозволяють створювати сучасні інтерфейси веб-застосунків, залишаючись переважно в межах обраної бекенд-мови. Наприклад, в екосистемі [Rails](https://rubyonrails.org/) це спонукало появу бібліотек на кшталт [Turbo](https://turbo.hotwired.dev/), [Hotwire](https://hotwired.dev/) і [Stimulus](https://stimulus.hotwired.dev/).

В екосистемі Laravel потреба створювати сучасні динамічні фронтенди переважно засобами PHP привела до появи [Laravel Livewire](https://livewire.laravel.com) та [Alpine.js](https://alpinejs.dev/).

<a name="livewire"></a>
### Livewire

[Laravel Livewire](https://livewire.laravel.com) - це фреймворк для створення фронтендів на базі Laravel, які відчуваються динамічними, сучасними й живими - так само, як фронтенди, побудовані на сучасних JavaScript-фреймворках на кшталт React, Svelte і Vue.

Використовуючи Livewire, ви створюватимете «компоненти» Livewire, які рендерять окрему частину вашого інтерфейсу та надають методи й дані, що їх можна викликати й з якими можна взаємодіяти з фронтенду вашого застосунку. Наприклад, простий компонент «лічильник» може виглядати так:

```php
<?php

use Livewire\Component;

new class extends Component
{
    public $count = 0;

    public function increment()
    {
        $this->count++;
    }
};
?>

<div>
    <button wire:click="increment">+</button>
    <h1>{{ $count }}</h1>
</div>

```

Як бачите, Livewire дозволяє писати нові HTML-атрибути на кшталт `wire:click`, які з'єднують фронтенд і бекенд вашого застосунку Laravel. Крім того, ви можете виводити поточний стан компонента звичайними виразами Blade.

Для багатьох Livewire став революцією у фронтенд-розробці з Laravel, дозволивши залишатися в комфорті Laravel, створюючи водночас сучасні динамічні веб-застосунки. Зазвичай розробники, які використовують Livewire, також беруть [Alpine.js](https://alpinejs.dev/), щоб «присипати» фронтенд JavaScript лише там, де це потрібно - наприклад, щоб показати діалогове вікно.

Якщо ви новачок у Laravel, радимо спершу ознайомитися з базовим використанням [представлень](/docs/{{version}}/views) і [Blade](/docs/{{version}}/blade). Далі зверніться до офіційної [документації Laravel Livewire](https://livewire.laravel.com/docs), щоб дізнатися, як вивести застосунок на новий рівень за допомогою інтерактивних компонентів Livewire.

<a name="php-starter-kits"></a>
### Стартові набори

Якщо ви хочете будувати фронтенд засобами PHP і Livewire, скористайтеся нашим [стартовим набором Livewire](/docs/{{version}}/starter-kits), щоб пришвидшити початок розробки застосунку.

<a name="using-react-svelte-or-vue"></a>
## Використання React, Svelte чи Vue

Хоча сучасні фронтенди цілком можна створювати за допомогою Laravel і Livewire, багато розробників усе ж віддають перевагу потужності JavaScript-фреймворків на кшталт React, Svelte чи Vue. Це дозволяє скористатися багатою екосистемою JavaScript-пакетів та інструментів, доступних через NPM.

Однак без додаткових інструментів поєднання Laravel із React, Svelte чи Vue залишило б нам купу складних проблем: маршрутизацію на боці клієнта, гідратацію даних і автентифікацію. Маршрутизацію на боці клієнта часто спрощують фреймворки з чіткими домовленостями, як-от [Next](https://nextjs.org/) і [Nuxt](https://nuxt.com/), але гідратація даних і автентифікація лишаються складними й марудними задачами, коли ви поєднуєте бекенд-фреймворк на кшталт Laravel із цими фронтенд-фреймворками.

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

<a name="inertia"></a>
### Inertia

На щастя, Laravel пропонує найкраще з обох світів. [Inertia](https://inertiajs.com) долає розрив між вашим застосунком Laravel і сучасним фронтендом на React, Svelte чи Vue, дозволяючи створювати повноцінні сучасні фронтенди на React, Svelte чи Vue, використовуючи водночас маршрути й контролери Laravel для маршрутизації, гідратації даних і автентифікації - і все це в одному репозиторії. За такого підходу ви отримуєте всю потужність і Laravel, і React / Svelte / Vue, не обмежуючи можливостей жодного з інструментів.

Після встановлення Inertia у ваш застосунок Laravel ви писатимете маршрути й контролери як зазвичай. Однак замість повернення шаблону Blade із контролера ви повертатимете сторінку Inertia:

```php
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Inertia\Inertia;
use Inertia\Response;

class UserController extends Controller
{
    /**
     * Show the profile for a given user.
     */
    public function show(string $id): Response
    {
        return Inertia::render('users/show', [
            'user' => User::findOrFail($id)
        ]);
    }
}
```

Сторінці Inertia відповідає компонент React, Svelte чи Vue, який зазвичай зберігається в каталозі `resources/js/pages` вашого застосунку. Дані, передані сторінці через метод `Inertia::render`, використовуються для гідратації «props» компонента сторінки:

```jsx
import Layout from '@/layouts/authenticated';
import { Head } from '@inertiajs/react';

export default function Show({ user }) {
    return (
        <Layout>
            <Head title="Welcome" />
            <h1>Welcome</h1>
            <p>Hello {user.name}, welcome to Inertia.</p>
        </Layout>
    )
}
```

Як бачите, Inertia дозволяє скористатися всією потужністю React, Svelte чи Vue під час створення фронтенду, даючи водночас легкий місток між вашим бекендом на Laravel і фронтендом на JavaScript.

#### Рендеринг на боці сервера

Якщо ви вагаєтеся братися за Inertia, бо ваш застосунок потребує рендерингу на боці сервера, не хвилюйтеся. Inertia пропонує [підтримку рендерингу на боці сервера](https://inertiajs.com/server-side-rendering). А розгортаючи застосунок через [Laravel Cloud](https://cloud.laravel.com) чи [Laravel Forge](https://forge.laravel.com), ви легко забезпечите, щоб процес серверного рендерингу Inertia працював завжди.

<a name="inertia-starter-kits"></a>
### Стартові набори

Якщо ви хочете будувати фронтенд за допомогою Inertia і React / Svelte / Vue, скористайтеся нашими [стартовими наборами застосунків для React, Svelte чи Vue](/docs/{{version}}/starter-kits), щоб пришвидшити початок розробки. Усі ці набори створюють каркас автентифікації для бекенду та фронтенду вашого застосунку за допомогою Inertia, React / Svelte / Vue, [Tailwind](https://tailwindcss.com) і [Vite](https://vitejs.dev), тож ви можете братися до втілення своєї наступної великої ідеї.

<a name="bundling-assets"></a>
## Складання ресурсів

Незалежно від того, чи розробляєте ви фронтенд на Blade і Livewire, чи на React / Svelte / Vue та Inertia, вам, найімовірніше, знадобиться зібрати CSS вашого застосунку у готові до продакшену ресурси. Звісно, якщо ви будуєте фронтенд на React, Svelte чи Vue, вам також потрібно буде зібрати компоненти в готові для браузера JavaScript-ресурси.

За замовчуванням Laravel використовує [Vite](https://vitejs.dev) для складання ваших ресурсів. Vite забезпечує блискавичну швидкість збірки та майже миттєву гарячу заміну модулів (HMR) під час локальної розробки. У всіх нових застосунках Laravel, зокрема тих, що використовують наші [стартові набори](/docs/{{version}}/starter-kits), ви знайдете файл `vite.config.js`, який завантажує наш легкий плагін Laravel Vite, завдяки чому користуватися Vite із застосунками Laravel - справжнє задоволення.

Найшвидший спосіб почати з Laravel і Vite - розпочати розробку застосунку з [наших стартових наборів](/docs/{{version}}/starter-kits), які дають фору, надаючи готовий каркас автентифікації для фронтенду й бекенду.

> [!NOTE]
> Докладнішу документацію щодо використання Vite з Laravel дивіться в нашій [окремій документації про складання та компіляцію ресурсів](/docs/{{version}}/vite).

---

Джерело: https://laravelukraine.com/docs/13.x/starter-kits

# Стартові набори

- [Вступ](#introduction)
- [Створення застосунку за допомогою стартового набору](#creating-an-application)
- [Доступні стартові набори](#available-starter-kits)
    - [React](#react)
    - [Svelte](#svelte)
    - [Vue](#vue)
    - [Livewire](#livewire)
- [Налаштування стартового набору](#starter-kit-customization)
    - [React](#react-customization)
    - [Svelte](#svelte-customization)
    - [Vue](#vue-customization)
    - [Livewire](#livewire-customization)
- [Автентифікація](#authentication)
    - [Увімкнення та вимкнення можливостей](#enabling-and-disabling-features)
    - [Налаштування створення користувача та скидання пароля](#customizing-actions)
    - [Двофакторна автентифікація](#two-factor-authentication)
    - [Обмеження частоти](#rate-limiting)
- [Команди](#teams)
- [Автентифікація через WorkOS AuthKit](#workos)
    - [Налаштування вашого стартового набору WorkOS](#configuring-your-workos-starter-kit)
- [Inertia SSR](#inertia-ssr)
- [Стартові набори від спільноти](#community-maintained-starter-kits)
- [Часті запитання](#faqs)

<a name="introduction"></a>
## Вступ

Щоб дати вам фору в розробці нового застосунку Laravel, ми з радістю пропонуємо [стартові набори застосунків](https://laravel.com/starter-kits). Ці набори дають фору в створенні вашого наступного застосунку Laravel і містять маршрути, контролери та представлення, потрібні для реєстрації та автентифікації користувачів. Для автентифікації стартові набори використовують [Laravel Fortify](/docs/{{version}}/fortify).

Хоча ви можете вільно користуватися цими наборами, вони не обов'язкові. Ви можете створити власний застосунок з нуля, просто встановивши свіжу копію Laravel. Хай там як, ми знаємо, що ви створите щось чудове!

<a name="creating-an-application"></a>
## Створення застосунку за допомогою стартового набору

Щоб створити новий застосунок Laravel за допомогою одного з наших стартових наборів, спершу [встановіть PHP та CLI-інструмент Laravel](/docs/{{version}}/installation#installing-php). Якщо PHP і Composer уже встановлені, ви можете встановити CLI-інструмент інсталятора Laravel через Composer:

```shell
composer global require laravel/installer
```

Далі створіть новий застосунок Laravel за допомогою CLI інсталятора Laravel. Інсталятор запропонує обрати бажаний стартовий набір:

```shell
laravel new my-app
```

Після створення застосунку Laravel вам залишиться лише встановити фронтенд-залежності через NPM і запустити сервер розробки Laravel:

```shell
cd my-app
npm install && npm run build
composer run dev
```

Щойно ви запустите сервер розробки Laravel, ваш застосунок буде доступний у браузері за адресою [http://localhost:8000](http://localhost:8000).

<a name="available-starter-kits"></a>
## Доступні стартові набори

<a name="react"></a>
### React

Наш стартовий набір React дає надійну сучасну відправну точку для створення застосунків Laravel із фронтендом на React за допомогою [Inertia](https://inertiajs.com).

Inertia дозволяє створювати сучасні односторінкові застосунки на React, використовуючи класичну серверну маршрутизацію та контролери. Це дає змогу поєднати потужність React на фронтенді з неймовірною продуктивністю Laravel на бекенді та блискавичною компіляцією Vite.

Стартовий набір React використовує React 19, TypeScript, Tailwind і бібліотеку компонентів [shadcn/ui](https://ui.shadcn.com).

<a name="svelte"></a>
### Svelte

Наш стартовий набір Svelte дає надійну сучасну відправну точку для створення застосунків Laravel із фронтендом на Svelte за допомогою [Inertia](https://inertiajs.com).

Inertia дозволяє створювати сучасні односторінкові застосунки на Svelte, використовуючи класичну серверну маршрутизацію та контролери. Це дає змогу поєднати потужність Svelte на фронтенді з неймовірною продуктивністю Laravel на бекенді та блискавичною компіляцією Vite.

Стартовий набір Svelte використовує Svelte 5, TypeScript, Tailwind і бібліотеку компонентів [shadcn-svelte](https://www.shadcn-svelte.com/).

<a name="vue"></a>
### Vue

Наш стартовий набір Vue дає чудову відправну точку для створення застосунків Laravel із фронтендом на Vue за допомогою [Inertia](https://inertiajs.com).

Inertia дозволяє створювати сучасні односторінкові застосунки на Vue, використовуючи класичну серверну маршрутизацію та контролери. Це дає змогу поєднати потужність Vue на фронтенді з неймовірною продуктивністю Laravel на бекенді та блискавичною компіляцією Vite.

Стартовий набір Vue використовує Vue Composition API, TypeScript, Tailwind і бібліотеку компонентів [shadcn-vue](https://www.shadcn-vue.com/).

<a name="livewire"></a>
### Livewire

Наш стартовий набір Livewire дає ідеальну відправну точку для створення застосунків Laravel із фронтендом на [Laravel Livewire](https://livewire.laravel.com).

Livewire - це потужний спосіб створювати динамічні реактивні інтерфейси, використовуючи лише PHP. Він чудово підходить командам, які здебільшого працюють із шаблонами Blade і шукають простішу альтернативу SPA-фреймворкам на JavaScript, як-от React, Svelte і Vue.

Стартовий набір Livewire використовує Livewire, Tailwind і бібліотеку компонентів [Flux UI](https://fluxui.dev).

<a name="starter-kit-customization"></a>
## Налаштування стартового набору

<a name="react-customization"></a>
### React

Наш стартовий набір React побудований на Inertia 3, React 19, Tailwind 4 і [shadcn/ui](https://ui.shadcn.com). Як і в усіх наших наборах, увесь код бекенду та фронтенду міститься у вашому застосунку, що дозволяє повністю його налаштувати.

Більшість коду фронтенду розташована в каталозі `resources/js`. Ви вільні змінювати будь-який код, щоб налаштувати вигляд і поведінку свого застосунку:

```text
resources/js/
├── components/    # Reusable React components
├── hooks/         # React hooks
├── layouts/       # Application layouts
├── lib/           # Utility functions and configuration
├── pages/         # Page components
└── types/         # TypeScript definitions
```

Щоб опублікувати додаткові компоненти shadcn, спершу [знайдіть потрібний компонент](https://ui.shadcn.com). Далі опублікуйте його за допомогою `npx`:

```shell
npx shadcn@latest add switch
```

У цьому прикладі команда опублікує компонент Switch у `resources/js/components/ui/switch.tsx`. Щойно компонент опубліковано, ви можете використовувати його на будь-якій зі своїх сторінок:

```jsx
import { Switch } from "@/components/ui/switch"

const MyPage = () => {
  return (
    <div>
      <Switch />
    </div>
  );
};

export default MyPage;
```

<a name="react-available-layouts"></a>
#### Доступні макети

Стартовий набір React містить два основні макети на вибір: макет із бічною панеллю («sidebar») і макет із шапкою («header»). За замовчуванням використовується макет із бічною панеллю, але ви можете перемкнутися на макет із шапкою, змінивши макет, що імпортується на початку файлу `resources/js/layouts/app-layout.tsx` вашого застосунку:

```js
import AppLayoutTemplate from '@/layouts/app/app-sidebar-layout'; // [tl! remove]
import AppLayoutTemplate from '@/layouts/app/app-header-layout'; // [tl! add]
```

<a name="react-sidebar-variants"></a>
#### Варіанти бічної панелі

Макет із бічною панеллю має три варіанти: типовий, «inset» і «floating». Ви можете обрати той, що подобається найбільше, змінивши компонент `resources/js/components/app-sidebar.tsx`:

```text
<Sidebar collapsible="icon" variant="sidebar"> [tl! remove]
<Sidebar collapsible="icon" variant="inset"> [tl! add]
```

<a name="react-authentication-page-layout-variants"></a>
#### Варіанти макета сторінок автентифікації

Сторінки автентифікації, що входять до стартового набору React, - як-от сторінки входу та реєстрації, - також мають три варіанти макета: «simple», «card» і «split».

Щоб змінити макет автентифікації, змініть макет, що імпортується на початку файлу `resources/js/layouts/auth-layout.tsx` вашого застосунку:

```js
import AuthLayoutTemplate from '@/layouts/auth/auth-simple-layout'; // [tl! remove]
import AuthLayoutTemplate from '@/layouts/auth/auth-split-layout'; // [tl! add]
```

<a name="svelte-customization"></a>
### Svelte

Наш стартовий набір Svelte побудований на Inertia 3, Svelte 5, Tailwind і [shadcn-svelte](https://www.shadcn-svelte.com/). Як і в усіх наших наборах, увесь код бекенду та фронтенду міститься у вашому застосунку, що дозволяє повністю його налаштувати.

Більшість коду фронтенду розташована в каталозі `resources/js`. Ви вільні змінювати будь-який код, щоб налаштувати вигляд і поведінку свого застосунку:

```text
resources/js/
├── components/    # Reusable Svelte components
├── layouts/       # Application layouts
├── lib/           # Utility functions and configuration and Svelte rune modules
├── pages/         # Page components
└── types/         # TypeScript definitions
```

Щоб опублікувати додаткові компоненти shadcn-svelte, спершу [знайдіть потрібний компонент](https://www.shadcn-svelte.com). Далі опублікуйте його за допомогою `npx`:

```shell
npx shadcn-svelte@latest add switch
```

У цьому прикладі команда опублікує компонент Switch у `resources/js/components/ui/switch/switch.svelte`. Щойно компонент опубліковано, ви можете використовувати його на будь-якій зі своїх сторінок:

```svelte
<script lang="ts">
    import { Switch } from '@/components/ui/switch'
</script>

<div>
    <Switch />
</div>
```

<a name="svelte-available-layouts"></a>
#### Доступні макети

Стартовий набір Svelte містить два основні макети на вибір: макет із бічною панеллю («sidebar») і макет із шапкою («header»). За замовчуванням використовується макет із бічною панеллю, але ви можете перемкнутися на макет із шапкою, змінивши макет, що імпортується на початку файлу `resources/js/layouts/AppLayout.svelte` вашого застосунку:

```js
import AppLayout from '@/layouts/app/AppSidebarLayout.svelte'; // [tl! remove]
import AppLayout from '@/layouts/app/AppHeaderLayout.svelte'; // [tl! add]
```

<a name="svelte-sidebar-variants"></a>
#### Варіанти бічної панелі

Макет із бічною панеллю має три варіанти: типовий, «inset» і «floating». Ви можете обрати той, що подобається найбільше, змінивши компонент `resources/js/components/AppSidebar.svelte`:

```text
<Sidebar collapsible="icon" variant="sidebar"> [tl! remove]
<Sidebar collapsible="icon" variant="inset"> [tl! add]
```

<a name="svelte-authentication-page-layout-variants"></a>
#### Варіанти макета сторінок автентифікації

Сторінки автентифікації, що входять до стартового набору Svelte, - як-от сторінки входу та реєстрації, - також мають три варіанти макета: «simple», «card» і «split».

Щоб змінити макет автентифікації, змініть макет, що імпортується на початку файлу `resources/js/layouts/AuthLayout.svelte` вашого застосунку:

```js
import AuthLayout from '@/layouts/auth/AuthSimpleLayout.svelte'; // [tl! remove]
import AuthLayout from '@/layouts/auth/AuthSplitLayout.svelte'; // [tl! add]
```

<a name="vue-customization"></a>
### Vue

Наш стартовий набір Vue побудований на Inertia 3, Vue 3 Composition API, Tailwind і [shadcn-vue](https://www.shadcn-vue.com/). Як і в усіх наших наборах, увесь код бекенду та фронтенду міститься у вашому застосунку, що дозволяє повністю його налаштувати.

Більшість коду фронтенду розташована в каталозі `resources/js`. Ви вільні змінювати будь-який код, щоб налаштувати вигляд і поведінку свого застосунку:

```text
resources/js/
├── components/    # Reusable Vue components
├── composables/   # Vue composables / hooks
├── layouts/       # Application layouts
├── lib/           # Utility functions and configuration
├── pages/         # Page components
└── types/         # TypeScript definitions
```

Щоб опублікувати додаткові компоненти shadcn-vue, спершу [знайдіть потрібний компонент](https://www.shadcn-vue.com). Далі опублікуйте його за допомогою `npx`:

```shell
npx shadcn-vue@latest add switch
```

У цьому прикладі команда опублікує компонент Switch у `resources/js/components/ui/Switch.vue`. Щойно компонент опубліковано, ви можете використовувати його на будь-якій зі своїх сторінок:

```vue
<script setup lang="ts">
import { Switch } from '@/components/ui/switch'
</script>

<template>
    <div>
        <Switch />
    </div>
</template>
```

<a name="vue-available-layouts"></a>
#### Доступні макети

Стартовий набір Vue містить два основні макети на вибір: макет із бічною панеллю («sidebar») і макет із шапкою («header»). За замовчуванням використовується макет із бічною панеллю, але ви можете перемкнутися на макет із шапкою, змінивши макет, що імпортується на початку файлу `resources/js/layouts/AppLayout.vue` вашого застосунку:

```js
import AppLayout from '@/layouts/app/AppSidebarLayout.vue'; // [tl! remove]
import AppLayout from '@/layouts/app/AppHeaderLayout.vue'; // [tl! add]
```

<a name="vue-sidebar-variants"></a>
#### Варіанти бічної панелі

Макет із бічною панеллю має три варіанти: типовий, «inset» і «floating». Ви можете обрати той, що подобається найбільше, змінивши компонент `resources/js/components/AppSidebar.vue`:

```text
<Sidebar collapsible="icon" variant="sidebar"> [tl! remove]
<Sidebar collapsible="icon" variant="inset"> [tl! add]
```

<a name="vue-authentication-page-layout-variants"></a>
#### Варіанти макета сторінок автентифікації

Сторінки автентифікації, що входять до стартового набору Vue, - як-от сторінки входу та реєстрації, - також мають три варіанти макета: «simple», «card» і «split».

Щоб змінити макет автентифікації, змініть макет, що імпортується на початку файлу `resources/js/layouts/AuthLayout.vue` вашого застосунку:

```js
import AuthLayout from '@/layouts/auth/AuthSimpleLayout.vue'; // [tl! remove]
import AuthLayout from '@/layouts/auth/AuthSplitLayout.vue'; // [tl! add]
```

<a name="livewire-customization"></a>
### Livewire

Наш стартовий набір Livewire побудований на Livewire 4, Tailwind і [Flux UI](https://fluxui.dev/). Як і в усіх наших наборах, увесь код бекенду та фронтенду міститься у вашому застосунку, що дозволяє повністю його налаштувати.

Більшість коду фронтенду розташована в каталозі `resources/views`. Ви вільні змінювати будь-який код, щоб налаштувати вигляд і поведінку свого застосунку:

```text
resources/views
├── components            # Reusable components
├── flux                  # Customized Flux components
├── layouts               # Application layouts
├── pages                 # Livewire pages
├── partials              # Reusable Blade partials
├── dashboard.blade.php   # Authenticated user dashboard
├── welcome.blade.php     # Guest user welcome page
```

<a name="livewire-available-layouts"></a>
#### Доступні макети

Стартовий набір Livewire містить два основні макети на вибір: макет із бічною панеллю («sidebar») і макет із шапкою («header»). За замовчуванням використовується макет із бічною панеллю, але ви можете перемкнутися на макет із шапкою, змінивши макет, який використовує файл `resources/views/layouts/app.blade.php` вашого застосунку. Крім того, вам слід додати атрибут `container` до головного компонента Flux:

```blade
<x-layouts::app.header>
    <flux:main container>
        {{ $slot }}
    </flux:main>
</x-layouts::app.header>
```

<a name="livewire-authentication-page-layout-variants"></a>
#### Варіанти макета сторінок автентифікації

Сторінки автентифікації, що входять до стартового набору Livewire, - як-от сторінки входу та реєстрації, - також мають три варіанти макета: «simple», «card» і «split».

Щоб змінити макет автентифікації, змініть макет, який використовує файл `resources/views/layouts/auth.blade.php` вашого застосунку:

```blade
<x-layouts::auth.split>
    {{ $slot }}
</x-layouts::auth.split>
```

<a name="authentication"></a>
## Автентифікація

Усі стартові набори використовують [Laravel Fortify](/docs/{{version}}/fortify) для обробки автентифікації. Fortify надає маршрути, контролери та логіку для входу, реєстрації, скидання пароля, підтвердження електронної пошти тощо.

Fortify автоматично реєструє наведені нижче маршрути автентифікації залежно від того, які можливості увімкнено у вашому конфігураційному файлі `config/fortify.php`:

<div class="overflow-auto">

| Маршрут                            | Метод  | Опис                                    |
| ---------------------------------- | ------ | --------------------------------------- |
| `/login`                           | `GET`    | Показати форму входу                  |
| `/login`                           | `POST`   | Автентифікувати користувача           |
| `/logout`                          | `POST`   | Вийти з облікового запису             |
| `/register`                        | `GET`    | Показати форму реєстрації             |
| `/register`                        | `POST`   | Створити нового користувача           |
| `/forgot-password`                 | `GET`    | Показати форму запиту скидання пароля |
| `/forgot-password`                 | `POST`   | Надіслати посилання для скидання      |
| `/reset-password/{token}`          | `GET`    | Показати форму скидання пароля        |
| `/reset-password`                  | `POST`   | Оновити пароль                        |
| `/email/verify`                    | `GET`    | Показати повідомлення про підтвердження |
| `/email/verify/{id}/{hash}`        | `GET`    | Підтвердити адресу електронної пошти  |
| `/email/verification-notification` | `POST`   | Надіслати лист підтвердження ще раз   |
| `/user/confirm-password`           | `GET`    | Показати форму підтвердження пароля   |
| `/user/confirm-password`           | `POST`   | Підтвердити пароль                    |
| `/two-factor-challenge`            | `GET`    | Показати форму перевірки 2FA          |
| `/two-factor-challenge`            | `POST`   | Перевірити код 2FA                    |

</div>

Команда Artisan `php artisan route:list` дозволяє переглянути всі маршрути вашого застосунку.

<a name="enabling-and-disabling-features"></a>
### Увімкнення та вимкнення можливостей

Ви можете керувати тим, які можливості Fortify увімкнено, у конфігураційному файлі `config/fortify.php` вашого застосунку:

```php
use Laravel\Fortify\Features;

'features' => [
    Features::registration(),
    Features::resetPasswords(),
    Features::emailVerification(),
    Features::twoFactorAuthentication([
        'confirm' => true,
        'confirmPassword' => true,
    ]),
],
```

Щоб вимкнути можливість, закоментуйте або вилучіть відповідний запис із масиву `features`. Наприклад, вилучіть `Features::registration()`, щоб вимкнути публічну реєстрацію.

Використовуючи стартові набори [React](#react), [Svelte](#svelte) чи [Vue](#vue), вам також потрібно буде прибрати всі згадки маршрутів вимкненої можливості у коді фронтенду. Наприклад, якщо ви вимикаєте підтвердження електронної пошти, слід прибрати імпорти та звернення до маршрутів `verification` у ваших компонентах React, Svelte чи Vue. Це потрібно, бо ці набори використовують Wayfinder для типобезпечної маршрутизації, який генерує визначення маршрутів під час збірки. Якщо ви посилатиметеся на маршрути, яких більше немає, збірка вашого застосунку завершиться помилкою.

<a name="customizing-actions"></a>
### Налаштування створення користувача та скидання пароля

Коли користувач реєструється або скидає пароль, Fortify викликає класи дій, розташовані в каталозі `app/Actions/Fortify` вашого застосунку:

<div class="overflow-auto">

| Файл                          | Опис                                       |
| ----------------------------- | ------------------------------------------ |
| `CreateNewUser.php`           | Валідує та створює нових користувачів      |
| `ResetUserPassword.php`       | Валідує та оновлює паролі користувачів     |
| `PasswordValidationRules.php` | Визначає правила валідації паролів         |

</div>

Наприклад, щоб налаштувати логіку реєстрації вашого застосунку, відредагуйте дію `CreateNewUser`:

```php
public function create(array $input): User
{
    Validator::make($input, [
        'name' => ['required', 'string', 'max:255'],
        'email' => ['required', 'email', 'max:255', 'unique:users'],
        'phone' => ['required', 'string', 'max:20'], // [tl! add]
        'password' => $this->passwordRules(),
    ])->validate();

    return User::create([
        'name' => $input['name'],
        'email' => $input['email'],
        'phone' => $input['phone'], // [tl! add]
        'password' => Hash::make($input['password']),
    ]);
}
```

<a name="two-factor-authentication"></a>
### Двофакторна автентифікація

Стартові набори містять вбудовану двофакторну автентифікацію (2FA), що дозволяє користувачам захистити свої облікові записи будь-яким TOTP-сумісним застосунком-автентифікатором. 2FA увімкнено за замовчуванням через `Features::twoFactorAuthentication()` у конфігураційному файлі `config/fortify.php` вашого застосунку.

Опція `confirm` вимагає від користувачів підтвердити код, перш ніж 2FA буде повністю увімкнено, а `confirmPassword` вимагає підтвердження пароля перед увімкненням чи вимкненням 2FA. Докладніше дивіться в [документації Fortify щодо двофакторної автентифікації](/docs/{{version}}/fortify#two-factor-authentication).

<a name="rate-limiting"></a>
### Обмеження частоти

Обмеження частоти запобігає перебору паролів і повторним спробам входу, які могли б перевантажити ваші точки автентифікації. Ви можете налаштувати поведінку обмеження частоти Fortify у `FortifyServiceProvider` вашого застосунку:

```php
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Cache\RateLimiting\Limit;

RateLimiter::for('login', function ($request) {
    return Limit::perMinute(5)->by($request->email.$request->ip());
});
```

<a name="teams"></a>
## Команди

Стартові набори React, Svelte, Vue і Livewire можна також згенерувати з підтримкою команд. Коли можливість команд увімкнено, кожен користувач належить до однієї чи кількох команд і має поточну команду. Під час реєстрації нові користувачі автоматично отримують особисту команду. Набори також містять екрани керування командами: створення команд, перемикання між ними, запрошення учасників і оновлення даних команди.

Коли маршрут прив'язаний до поточної команди, її slug входить до URL. Наприклад, маршрут панелі керування стає `/{current_team}/dashboard`, а сторінки керування командами використовують маршрути на кшталт `settings/teams/{team}`. Використовуючи параметри маршруту `{current_team}` і `{team}`, стартові набори автоматично перевіряють, що автентифікований користувач належить до запитаної команди, перш ніж надати доступ до маршруту.

Щоб зручніше генерувати URL з урахуванням команди, стартові набори реєструють значення URL за замовчуванням для поточної команди автентифікованого користувача. Це дозволяє викликам хелперів на кшталт `route('dashboard')` автоматично включати slug поточної команди. Коли користувач входить, реєструється чи перемикає команди, набори оновлюють поточну команду та ці значення за замовчуванням, тож згенеровані посилання й далі використовують правильний контекст команди.

Створюючи чи перейменовуючи команду, стартові набори також не дають користувачам обирати зарезервовані імена, які могли б утворити небезпечні чи конфліктні сегменти маршрутів. Наприклад, не можна використовувати імена, що збігалися б із префіксами маршрутів на кшталт `settings`, `login` чи `dashboard`.

<a name="workos"></a>
## Автентифікація через WorkOS AuthKit

За замовчуванням стартові набори React, Svelte, Vue і Livewire використовують вбудовану систему автентифікації Laravel, що пропонує вхід, реєстрацію, скидання пароля, підтвердження електронної пошти тощо. Крім того, ми пропонуємо варіант кожного набору на основі [WorkOS AuthKit](https://authkit.com), який дає:

<div class="content-list" markdown="1">

- Соціальну автентифікацію (Google, Microsoft, GitHub та Apple)
- Автентифікацію за допомогою passkey
- «Magic Auth» на основі електронної пошти
- SSO

</div>

Використання WorkOS як провайдера автентифікації [потребує облікового запису WorkOS](https://workos.com). WorkOS пропонує безкоштовну автентифікацію для застосунків із до 1 мільйона активних користувачів на місяць.

Щоб використовувати WorkOS AuthKit як провайдера автентифікації вашого застосунку, оберіть варіант WorkOS під час створення нового застосунку зі стартовим набором через `laravel new`.

<a name="configuring-your-workos-starter-kit"></a>
### Налаштування вашого стартового набору WorkOS

Після створення нового застосунку зі стартовим набором на основі WorkOS задайте змінні середовища `WORKOS_CLIENT_ID`, `WORKOS_API_KEY` і `WORKOS_REDIRECT_URL` у файлі `.env` вашого застосунку. Ці значення мають збігатися з тими, що надані вам у панелі WorkOS для вашого застосунку:

```ini
WORKOS_CLIENT_ID=your-client-id
WORKOS_API_KEY=your-api-key
WORKOS_REDIRECT_URL="${APP_URL}/authenticate"
```

Крім того, налаштуйте URL головної сторінки застосунку в панелі WorkOS. Саме на цей URL перенаправлятимуться користувачі після виходу з вашого застосунку.

<a name="configuring-authkit-authentication-methods"></a>
#### Налаштування методів автентифікації AuthKit

Коли ви використовуєте стартовий набір на основі WorkOS, ми радимо вимкнути автентифікацію «Email + Password» у налаштуваннях WorkOS AuthKit вашого застосунку, дозволивши користувачам автентифікуватися лише через провайдерів соціальної автентифікації, passkey, «Magic Auth» і SSO. Це дозволяє вашому застосунку взагалі не мати справи з паролями користувачів.

<a name="configuring-authkit-session-timeouts"></a>
#### Налаштування тайм-аутів сесії AuthKit

Крім того, ми радимо налаштувати тайм-аут неактивності сесії WorkOS AuthKit так, щоб він відповідав налаштованому порогу тайм-ауту сесії вашого застосунку Laravel, який зазвичай становить дві години.

<a name="inertia-ssr"></a>
### Inertia SSR

Стартові набори React, Svelte і Vue сумісні з можливостями [рендерингу на боці сервера](https://inertiajs.com/server-side-rendering) від Inertia. Щоб зібрати сумісний з Inertia SSR бандл для вашого застосунку, виконайте команду `build:ssr`:

```shell
npm run build:ssr
```

Для зручності також доступна команда `composer dev:ssr`. Вона запустить сервер розробки Laravel і сервер Inertia SSR після збірки SSR-сумісного бандла, дозволяючи протестувати застосунок локально з рушієм серверного рендерингу Inertia:

```shell
composer dev:ssr
```

<a name="community-maintained-starter-kits"></a>
### Стартові набори від спільноти

Створюючи новий застосунок Laravel за допомогою інсталятора Laravel, ви можете передати прапорцю `--using` будь-який стартовий набір від спільноти, доступний на Packagist:

```shell
laravel new my-app --using=example/starter-kit
```

<a name="creating-starter-kits"></a>
#### Створення стартових наборів

Щоб ваш стартовий набір був доступний іншим, вам потрібно опублікувати його на [Packagist](https://packagist.org). Ваш набір має визначати потрібні йому змінні середовища у файлі `.env.example`, а всі потрібні команди після встановлення слід перелічити в масиві `post-create-project-cmd` файлу `composer.json` набору.

<a name="faqs"></a>
### Часті запитання

<a name="faq-upgrade"></a>
#### Як мені оновитися?

Кожен стартовий набір дає вам надійну відправну точку для наступного застосунку. Маючи повне право власності на код, ви можете доопрацьовувати, налаштовувати й будувати свій застосунок саме так, як задумали. Утім, оновлювати сам стартовий набір не потрібно.

<a name="faq-enable-email-verification"></a>
#### Як увімкнути підтвердження електронної пошти?

Підтвердження електронної пошти можна додати, розкоментувавши імпорт `MustVerifyEmail` у вашій моделі `App/Models/User.php` і переконавшись, що модель реалізує інтерфейс `MustVerifyEmail`:

```php
<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
// ...

class User extends Authenticatable implements MustVerifyEmail
{
    // ...
}
```

Після реєстрації користувачі отримуватимуть лист підтвердження. Щоб обмежити доступ до певних маршрутів, доки адресу користувача не підтверджено, додайте до цих маршрутів `middleware` `verified`:

```php
Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('dashboard', function () {
        return Inertia::render('dashboard');
    })->name('dashboard');
});
```

> [!NOTE]
> Підтвердження електронної пошти не потрібне, коли ви використовуєте варіант стартових наборів на основі [WorkOS](#workos).

<a name="faq-modify-email-template"></a>
#### Як змінити типовий шаблон листа?

Можливо, ви захочете налаштувати типовий шаблон листа, щоб він краще відповідав брендингу вашого застосунку. Щоб змінити цей шаблон, опублікуйте представлення листів у своєму застосунку такою командою:

```shell
php artisan vendor:publish --tag=laravel-mail
```

Це створить кілька файлів у `resources/views/vendor/mail`. Ви можете змінювати будь-який із них, а також файл `resources/views/vendor/mail/themes/default.css`, щоб змінити вигляд типового шаблону листа.

---

Джерело: https://laravelukraine.com/docs/13.x/deployment

# Розгортання

- [Вступ](#introduction)
- [Вимоги до сервера](#server-requirements)
- [Конфігурація сервера](#server-configuration)
    - [Nginx](#nginx)
    - [FrankenPHP](#frankenphp)
    - [Права на каталоги](#directory-permissions)
- [Оптимізація](#optimization)
    - [Кешування конфігурації](#optimizing-configuration-loading)
    - [Кешування подій](#caching-events)
    - [Кешування маршрутів](#optimizing-route-loading)
    - [Кешування представлень](#optimizing-view-loading)
- [Перезавантаження сервісів](#reloading-services)
- [Режим налагодження](#debug-mode)
- [Маршрут перевірки стану](#the-health-route)
- [Розгортання за допомогою Laravel Cloud чи Forge](#deploying-with-cloud-or-forge)

<a name="introduction"></a>
## Вступ

Коли ви готові розгорнути свій застосунок Laravel у продакшені, є кілька важливих речей, які варто зробити, щоб застосунок працював якомога ефективніше. У цьому документі ми розглянемо гарні відправні точки для правильного розгортання вашого застосунку Laravel.

<a name="server-requirements"></a>
## Вимоги до сервера

Фреймворк Laravel має кілька системних вимог. Переконайтеся, що ваш веб-сервер має таку мінімальну версію PHP і розширення:

<div class="content-list" markdown="1">

- PHP >= 8.3
- Розширення PHP Ctype
- Розширення PHP cURL
- Розширення PHP DOM
- Розширення PHP Fileinfo
- Розширення PHP Filter
- Розширення PHP Hash
- Розширення PHP Mbstring
- Розширення PHP OpenSSL
- Розширення PHP PCRE
- Розширення PHP PDO
- Розширення PHP Session
- Розширення PHP Tokenizer
- Розширення PHP XML

</div>

<a name="server-configuration"></a>
## Конфігурація сервера

<a name="nginx"></a>
### Nginx

Якщо ви розгортаєте застосунок на сервері з Nginx, можете взяти наведений нижче конфігураційний файл за відправну точку для налаштування веб-сервера. Найімовірніше, цей файл доведеться адаптувати під конфігурацію вашого сервера. **Якщо вам потрібна допомога в керуванні сервером, розгляньте повністю керовану платформу для Laravel, як-от [Laravel Cloud](https://cloud.laravel.com).**

Переконайтеся, що ваш веб-сервер спрямовує всі запити до файлу `public/index.php` вашого застосунку, як у конфігурації нижче. Ніколи не намагайтеся перенести файл `index.php` до кореня проєкту: віддавання застосунку з кореня проєкту відкриє публічному інтернету багато конфіденційних конфігураційних файлів:

```nginx
server {
    listen 80;
    listen [::]:80;
    server_name example.com;
    root /srv/example.com/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";

    index index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_buffer_size 32k;
        fastcgi_buffers 8 32k;
        fastcgi_busy_buffers_size 64k;
        fastcgi_hide_header X-Powered-By;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}
```

<a name="frankenphp"></a>
### FrankenPHP

[FrankenPHP](https://frankenphp.dev/) також можна використовувати для віддавання ваших застосунків Laravel. FrankenPHP - це сучасний сервер PHP-застосунків, написаний мовою Go. Щоб віддавати PHP-застосунок Laravel через FrankenPHP, просто викличте його команду `php-server`:

```shell
frankenphp php-server -r public/
```

Щоб скористатися потужнішими можливостями FrankenPHP - як-от інтеграцією з [Laravel Octane](/docs/{{version}}/octane), HTTP/3, сучасним стисненням чи можливістю пакувати застосунки Laravel в окремі виконувані файли, - зверніться до [документації FrankenPHP щодо Laravel](https://frankenphp.dev/docs/laravel/).

<a name="directory-permissions"></a>
### Права на каталоги

Laravel потребує запису в каталоги `bootstrap/cache` і `storage`, тож переконайтеся, що власник процесу веб-сервера має права на запис до цих каталогів.

<a name="optimization"></a>
## Оптимізація

Розгортаючи застосунок у продакшені, слід закешувати низку файлів: конфігурацію, події, маршрути та представлення. Laravel надає одну зручну команду Artisan `optimize`, яка закешує всі ці файли. Зазвичай її варто викликати як частину процесу розгортання вашого застосунку:

```shell
php artisan optimize
```

Метод `optimize:clear` дозволяє видалити всі файли кешу, згенеровані командою `optimize`, а також усі ключі в драйвері кешу за замовчуванням:

```shell
php artisan optimize:clear
```

Далі в документації ми розглянемо кожну з окремих команд оптимізації, які виконує команда `optimize`.

<a name="optimizing-configuration-loading"></a>
### Кешування конфігурації

Розгортаючи застосунок у продакшені, переконайтеся, що під час розгортання виконується команда Artisan `config:cache`:

```shell
php artisan config:cache
```

Ця команда об'єднає всі конфігураційні файли Laravel в один закешований файл, що значно зменшує кількість звернень фреймворку до файлової системи під час завантаження значень конфігурації.

> [!WARNING]
> Якщо ви виконуєте команду `config:cache` під час розгортання, переконайтеся, що викликаєте функцію `env` лише всередині конфігураційних файлів. Щойно конфігурацію закешовано, файл `.env` не завантажуватиметься, і всі виклики функції `env` для змінних із `.env` повертатимуть `null`.

<a name="caching-events"></a>
### Кешування подій

Під час розгортання варто закешувати автоматично виявлені зіставлення подій зі слухачами. Це робиться викликом команди Artisan `event:cache`:

```shell
php artisan event:cache
```

<a name="optimizing-route-loading"></a>
### Кешування маршрутів

Якщо ви створюєте великий застосунок із багатьма маршрутами, переконайтеся, що під час розгортання виконується команда Artisan `route:cache`:

```shell
php artisan route:cache
```

Ця команда зводить усі реєстрації маршрутів до одного виклику методу в закешованому файлі, що покращує швидкодію реєстрації, коли маршрутів сотні.

<a name="optimizing-view-loading"></a>
### Кешування представлень

Розгортаючи застосунок у продакшені, переконайтеся, що під час розгортання виконується команда Artisan `view:cache`:

```shell
php artisan view:cache
```

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

<a name="reloading-services"></a>
## Перезавантаження сервісів

> [!NOTE]
> Розгортаючись у [Laravel Cloud](https://cloud.laravel.com), використовувати команду `reload` не потрібно - коректне перезавантаження всіх сервісів відбувається автоматично.

Після розгортання нової версії застосунку всі довготривалі сервіси - як-от воркери черг, Laravel Reverb чи Laravel Octane - слід перезавантажити чи перезапустити, щоб вони використовували новий код. Laravel надає одну команду Artisan `reload`, яка завершує роботу цих сервісів:

```shell
php artisan reload
```

Якщо ви не користуєтеся [Laravel Cloud](https://cloud.laravel.com), вам слід самостійно налаштувати монітор процесів, який виявлятиме завершення ваших перезавантажуваних процесів і автоматично перезапускатиме їх.

<a name="debug-mode"></a>
## Режим налагодження

Опція debug у вашому конфігураційному файлі `config/app.php` визначає, скільки інформації про помилку насправді показується користувачеві. За замовчуванням ця опція налаштована на значення змінної середовища `APP_DEBUG`, яка зберігається у файлі `.env` вашого застосунку.

> [!WARNING]
> **У продакшен-середовищі це значення завжди має бути `false`. Якщо в продакшені змінна `APP_DEBUG` матиме значення `true`, ви ризикуєте розкрити конфіденційні значення конфігурації кінцевим користувачам вашого застосунку.**

<a name="the-health-route"></a>
## Маршрут перевірки стану

Laravel містить вбудований маршрут перевірки стану, за яким можна стежити за станом вашого застосунку. У продакшені цей маршрут можна використовувати, щоб повідомляти про стан застосунку монітору доступності, балансувальнику навантаження чи системі оркестрації на кшталт Kubernetes.

За замовчуванням маршрут перевірки стану доступний за адресою `/up` і повертає HTTP-відповідь 200, якщо застосунок завантажився без винятків. Інакше повертається HTTP-відповідь 500. Ви можете налаштувати URI цього маршруту у файлі `bootstrap/app` вашого застосунку:

```php
->withRouting(
    web: __DIR__.'/../routes/web.php',
    commands: __DIR__.'/../routes/console.php',
    health: '/up', // [tl! remove]
    health: '/status', // [tl! add]
)
```

Коли до цього маршруту надходять HTTP-запити, Laravel також надсилає подію `Illuminate\Foundation\Events\DiagnosingHealth`, що дозволяє виконати додаткові перевірки стану, доречні для вашого застосунку. У [слухачі](/docs/{{version}}/events) цієї події ви можете перевірити стан бази даних чи кешу вашого застосунку. Якщо ви виявите проблему, просто викиньте виняток зі слухача.

<a name="deploying-with-cloud-or-forge"></a>
## Розгортання за допомогою Laravel Cloud чи Forge

<a name="laravel-cloud"></a>
#### Laravel Cloud

Якщо вам потрібна повністю керована платформа розгортання з автомасштабуванням, налаштована під Laravel, перегляньте [Laravel Cloud](https://cloud.laravel.com). Laravel Cloud - це надійна платформа розгортання для Laravel, що пропонує керовані обчислення, бази даних, кеші та об'єктне сховище.

Запустіть свій застосунок Laravel у Cloud і закохайтеся в масштабовану простоту. Laravel Cloud відточений творцями Laravel так, щоб бездоганно працювати з фреймворком, тож ви можете й далі писати свої застосунки Laravel звичним для себе способом.

<a name="laravel-forge"></a>
#### Laravel Forge

Якщо ви віддаєте перевагу керуванню власними серверами, але не почуваєтеся впевнено, налаштовуючи всі потрібні сервіси для роботи надійного застосунку Laravel, [Laravel Forge](https://forge.laravel.com) - це платформа керування VPS-серверами для застосунків Laravel.

Laravel Forge може створювати сервери в різних інфраструктурних провайдерів: DigitalOcean, Linode, AWS тощо. Крім того, Forge встановлює всі потрібні для створення надійних застосунків Laravel інструменти - Nginx, MySQL, Redis, Memcached, Beanstalk тощо - і керує ними.

---

Джерело: https://laravelukraine.com/docs/13.x/lifecycle

# Життєвий цикл запиту

- [Вступ](#introduction)
- [Огляд життєвого циклу](#lifecycle-overview)
    - [Перші кроки](#first-steps)
    - [Ядра HTTP і консолі](#http-console-kernels)
    - [Сервіс-провайдери](#service-providers)
    - [Маршрутизація](#routing)
    - [Завершення](#finishing-up)
- [Зосередимося на сервіс-провайдерах](#focus-on-service-providers)

<a name="introduction"></a>
## Вступ

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

Мета цього документа - дати вам добрий загальний огляд того, як працює фреймворк Laravel. Що краще ви знатимете фреймворк загалом, то менше «магії» в ньому лишатиметься і то впевненіше ви створюватимете свої застосунки. Якщо ви не зрозумієте всіх термінів одразу, не занепадайте духом! Просто спробуйте вловити базову картину того, що відбувається, а ваші знання зростатимуть у міру вивчення інших розділів документації.

<a name="lifecycle-overview"></a>
## Огляд життєвого циклу

<a name="first-steps"></a>
### Перші кроки

Точка входу для всіх запитів до застосунку Laravel - файл `public/index.php`. Усі запити спрямовуються до нього конфігурацією вашого веб-сервера (Apache / Nginx). Файл `index.php` містить небагато коду: він радше є відправною точкою для завантаження решти фреймворку.

Файл `index.php` завантажує визначення автозавантажувача, згенероване Composer, а потім отримує екземпляр застосунку Laravel із `bootstrap/app.php`. Перша дія самого Laravel - створення екземпляра застосунку, тобто [сервіс-контейнера](/docs/{{version}}/container).

<a name="http-console-kernels"></a>
### Ядра HTTP і консолі

Далі вхідний запит надсилається або до ядра HTTP, або до ядра консолі - методами `handleRequest` чи `handleCommand` екземпляра застосунку, залежно від типу запиту, що надходить. Ці два ядра є центральним місцем, через яке проходять усі запити. Наразі зосередимося на ядрі HTTP, яке є екземпляром `Illuminate\Foundation\Http\Kernel`.

Ядро HTTP визначає масив `bootstrappers`, які виконуються перед обробкою запиту. Ці завантажувачі налаштовують обробку помилок, логування, [визначають середовище застосунку](/docs/{{version}}/configuration#environment-configuration) та виконують інші завдання, які потрібно зробити до фактичної обробки запиту. Зазвичай ці класи опікуються внутрішньою конфігурацією Laravel, про яку вам не потрібно турбуватися.

Ядро HTTP також відповідає за проведення запиту через стек `middleware` вашого застосунку. Ці `middleware` читають і записують [сесію HTTP](/docs/{{version}}/session), визначають, чи перебуває застосунок у режимі обслуговування, [перевіряють токен CSRF](/docs/{{version}}/csrf) тощо. Про них ми поговоримо докладніше згодом.

Сигнатура методу `handle` ядра HTTP доволі проста: він отримує `Request` і повертає `Response`. Уявляйте ядро як велику чорну скриньку, що представляє весь ваш застосунок. Подаєте йому HTTP-запити - і він повертає HTTP-відповіді.

<a name="service-providers"></a>
### Сервіс-провайдери

Одна з найважливіших дій завантаження ядра - завантаження [сервіс-провайдерів](/docs/{{version}}/providers) вашого застосунку. Сервіс-провайдери відповідають за завантаження всіх різноманітних компонентів фреймворку: бази даних, черг, валідації, маршрутизації тощо.

Laravel пройде цим списком провайдерів і створить екземпляр кожного з них. Після створення екземплярів на всіх провайдерах буде викликано метод `register`. Далі, коли всі провайдери зареєстровано, на кожному буде викликано метод `boot`. Так зроблено для того, щоб сервіс-провайдери могли покладатися на те, що на момент виконання їхнього методу `boot` усі прив'язки контейнера вже зареєстровані й доступні.

Фактично кожну важливу можливість Laravel завантажує й налаштовує якийсь сервіс-провайдер. Оскільки вони завантажують і налаштовують стільки можливостей фреймворку, сервіс-провайдери - найважливіший аспект усього процесу завантаження Laravel.

Хоча внутрішньо фреймворк використовує десятки сервіс-провайдерів, ви також можете створювати власні. Список визначених вами чи сторонніх сервіс-провайдерів, які використовує ваш застосунок, можна знайти у файлі `bootstrap/providers.php`.

<a name="routing"></a>
### Маршрутизація

Щойно застосунок завантажено й усі сервіс-провайдери зареєстровано, `Request` передається маршрутизатору для диспетчеризації. Маршрутизатор направить запит до маршруту чи контролера, а також виконає всі `middleware`, специфічні для цього маршруту.

`Middleware` дають зручний механізм для фільтрації чи перевірки HTTP-запитів, що надходять до вашого застосунку. Наприклад, Laravel містить `middleware`, який перевіряє, чи автентифікований користувач вашого застосунку. Якщо ні - `middleware` перенаправить користувача на екран входу. Якщо ж користувач автентифікований, `middleware` дозволить запиту рухатися далі вглиб застосунку. Деякі `middleware` призначені всім маршрутам застосунку - як-от `PreventRequestsDuringMaintenance`, - а деякі лише конкретним маршрутам чи групам маршрутів. Дізнатися більше можна в повній [документації з `middleware`](/docs/{{version}}/middleware).

Якщо запит проходить крізь усі `middleware`, призначені відповідному маршруту, буде виконано метод маршруту чи контролера, і повернута ним відповідь піде назад крізь ланцюжок `middleware` цього маршруту.

<a name="finishing-up"></a>
### Завершення

Щойно метод маршруту чи контролера повертає відповідь, вона рухається назад назовні крізь `middleware` маршруту, даючи застосунку змогу змінити чи перевірити вихідну відповідь.

Нарешті, коли відповідь пройшла назад крізь `middleware`, метод `handle` ядра HTTP повертає об'єкт відповіді до `handleRequest` екземпляра застосунку, а той викликає на поверненій відповіді метод `send`. Метод `send` надсилає вміст відповіді до браузера користувача. На цьому нашу подорож повним життєвим циклом запиту Laravel завершено!

<a name="focus-on-service-providers"></a>
## Зосередимося на сервіс-провайдерах

Сервіс-провайдери - справді ключ до завантаження застосунку Laravel. Створюється екземпляр застосунку, реєструються сервіс-провайдери, і запит передається завантаженому застосунку. Ось так просто!

Добре розуміти, як застосунок Laravel будується й завантажується через сервіс-провайдери, дуже корисно. Визначені вами сервіс-провайдери зберігаються в каталозі `app/Providers` вашого застосунку.

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

---

Джерело: https://laravelukraine.com/docs/13.x/container

# Сервіс-контейнер

- [Вступ](#introduction)
    - [Розв'язання без конфігурації](#zero-configuration-resolution)
    - [Коли використовувати контейнер](#when-to-use-the-container)
- [Прив'язка](#binding)
    - [Основи прив'язки](#binding-basics)
    - [Прив'язка інтерфейсів до реалізацій](#binding-interfaces-to-implementations)
    - [Контекстна прив'язка](#contextual-binding)
    - [Контекстні атрибути](#contextual-attributes)
    - [Прив'язка примітивів](#binding-primitives)
    - [Прив'язка типізованих варіативних параметрів](#binding-typed-variadics)
    - [Теги](#tagging)
    - [Розширення прив'язок](#extending-bindings)
- [Розв'язання](#resolving)
    - [Метод make](#the-make-method)
    - [Автоматичне впровадження](#automatic-injection)
- [Виклик методів і впровадження](#method-invocation-and-injection)
- [Події контейнера](#container-events)
    - [Повторна прив'язка](#rebinding)
- [PSR-11](#psr-11)

<a name="introduction"></a>
## Вступ

Сервіс-контейнер Laravel - потужний інструмент для керування залежностями класів і виконання впровадження залежностей (dependency injection). Впровадження залежностей - гучна назва, що по суті означає таке: залежності класу «впроваджуються» в клас через конструктор або, подекуди, через методи-сетери.

Погляньмо на простий приклад:

```php
<?php

namespace App\Http\Controllers;

use App\Services\AppleMusic;
use Illuminate\View\View;

class PodcastController extends Controller
{
    /**
     * Create a new controller instance.
     */
    public function __construct(
        protected AppleMusic $apple,
    ) {}

    /**
     * Show information about the given podcast.
     */
    public function show(string $id): View
    {
        return view('podcasts.show', [
            'podcast' => $this->apple->findPodcast($id)
        ]);
    }
}
```

У цьому прикладі `PodcastController` має отримувати подкасти з джерела даних - наприклад, Apple Music. Тож ми **впровадимо** сервіс, здатний отримувати подкасти. Оскільки сервіс впроваджено, ми можемо легко «замокати» чи створити фіктивну реалізацію сервісу `AppleMusic` під час тестування застосунку.

Глибоке розуміння сервіс-контейнера Laravel - необхідна умова для створення потужного великого застосунку, а також для внеску в саме ядро Laravel.

<a name="zero-configuration-resolution"></a>
### Розв'язання без конфігурації

Якщо клас не має залежностей або залежить лише від інших конкретних класів (не інтерфейсів), контейнеру не потрібно пояснювати, як розв'язувати цей клас. Наприклад, ви можете розмістити такий код у файлі `routes/web.php`:

```php
<?php

class Service
{
    // ...
}

Route::get('/', function (Service $service) {
    dd($service::class);
});
```

У цьому прикладі звернення до маршруту `/` вашого застосунку автоматично розв'яже клас `Service` і впровадить його в обробник маршруту. Це змінює правила гри: ви можете розробляти застосунок і користуватися впровадженням залежностей, не турбуючись про роздуті конфігураційні файли.

На щастя, багато класів, які ви писатимете, створюючи застосунок Laravel, автоматично отримують свої залежності через контейнер - зокрема [контролери](/docs/{{version}}/controllers), [слухачі подій](/docs/{{version}}/events), [`middleware`](/docs/{{version}}/middleware) тощо. Крім того, ви можете вказувати типи залежностей у методі `handle` [завдань у черзі](/docs/{{version}}/queues). Щойно ви відчуєте смак автоматичного впровадження залежностей без конфігурації, розробляти без нього здаватиметься неможливим.

<a name="when-to-use-the-container"></a>
### Коли використовувати контейнер

Завдяки розв'язанню без конфігурації ви часто вказуватимете типи залежностей у маршрутах, контролерах, слухачах подій та інших місцях, жодного разу не звертаючись до контейнера вручну. Наприклад, ви можете вказати тип `Illuminate\Http\Request` у визначенні маршруту, щоб мати легкий доступ до поточного запиту. Хоча для написання цього коду нам не доводиться взаємодіяти з контейнером, саме він за лаштунками керує впровадженням цих залежностей:

```php
use Illuminate\Http\Request;

Route::get('/', function (Request $request) {
    // ...
});
```

У багатьох випадках завдяки автоматичному впровадженню залежностей і [фасадам](/docs/{{version}}/facades) ви можете створювати застосунки Laravel, **жодного разу** не прив'язуючи й не розв'язуючи нічого з контейнера вручну. **То коли ж вам доведеться взаємодіяти з контейнером вручну?** Розгляньмо дві ситуації.

По-перше, якщо ви пишете клас, що реалізує інтерфейс, і хочете вказати цей інтерфейс як тип у маршруті чи конструкторі класу, вам потрібно [пояснити контейнеру, як розв'язувати цей інтерфейс](#binding-interfaces-to-implementations). По-друге, якщо ви [пишете пакет Laravel](/docs/{{version}}/packages), яким плануєте поділитися з іншими розробниками, вам може знадобитися прив'язати сервіси свого пакета до контейнера.

<a name="binding"></a>
## Прив'язка

<a name="binding-basics"></a>
### Основи прив'язки

<a name="simple-bindings"></a>
#### Прості прив'язки

Майже всі прив'язки сервіс-контейнера реєструються в [сервіс-провайдерах](/docs/{{version}}/providers), тож більшість цих прикладів демонструватимуть використання контейнера саме в такому контексті.

У сервіс-провайдері вам завжди доступний контейнер через властивість `$this->app`. Зареєструвати прив'язку можна методом `bind`, передавши ім'я класу чи інтерфейсу, який ви хочете зареєструвати, разом із замиканням, що повертає екземпляр класу:

```php
use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->bind(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});
```

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

Як згадувалося, зазвичай ви взаємодієте з контейнером усередині сервіс-провайдерів; однак якщо вам потрібно звернутися до контейнера поза сервіс-провайдером, це можна зробити через [фасад](/docs/{{version}}/facades) `App`:

```php
use App\Services\Transistor;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\App;

App::bind(Transistor::class, function (Application $app) {
    // ...
});
```

Метод `bindIf` дозволяє зареєструвати прив'язку контейнера лише тоді, коли для цього типу її ще не зареєстровано:

```php
$this->app->bindIf(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});
```

Для зручності ви можете не передавати ім'я класу чи інтерфейсу окремим аргументом, а дозволити Laravel вивести тип із типу повернення замикання, яке ви передаєте методу `bind`:

```php
App::bind(function (Application $app): Transistor {
    return new Transistor($app->make(PodcastParser::class));
});
```

> [!NOTE]
> Немає потреби прив'язувати класи до контейнера, якщо вони не залежать від жодних інтерфейсів. Контейнеру не потрібно пояснювати, як будувати такі об'єкти, адже він може розв'язати їх автоматично за допомогою рефлексії.

<a name="binding-a-singleton"></a>
#### Прив'язка синглтона

Метод `singleton` прив'язує до контейнера клас чи інтерфейс, який має розв'язуватися лише один раз. Щойно прив'язку-синглтон розв'язано, на подальші звернення до контейнера повертатиметься той самий екземпляр об'єкта:

```php
use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->singleton(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});
```

Метод `singletonIf` дозволяє зареєструвати прив'язку-синглтон лише тоді, коли для цього типу її ще не зареєстровано:

```php
$this->app->singletonIf(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});
```

<a name="singleton-attribute"></a>
#### Атрибут Singleton

Як альтернативу ви можете позначити інтерфейс чи клас атрибутом `#[Singleton]`, вказавши контейнеру, що його слід розв'язувати один раз:

```php
<?php

namespace App\Services;

use Illuminate\Container\Attributes\Singleton;

#[Singleton]
class Transistor
{
    // ...
}
```

<a name="binding-scoped"></a>
#### Прив'язка scoped-синглтонів

Метод `scoped` прив'язує до контейнера клас чи інтерфейс, який має розв'язуватися лише один раз у межах певного життєвого циклу запиту чи завдання Laravel. Хоча цей метод схожий на `singleton`, екземпляри, зареєстровані через `scoped`, скидатимуться щоразу, коли застосунок Laravel починає новий «життєвий цикл» - наприклад, коли воркер [Laravel Octane](/docs/{{version}}/octane) обробляє новий запит або коли [воркер черги](/docs/{{version}}/queues) Laravel обробляє нове завдання:

```php
use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->scoped(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});
```

Метод `scopedIf` дозволяє зареєструвати scoped-прив'язку лише тоді, коли для цього типу її ще не зареєстровано:

```php
$this->app->scopedIf(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});
```

<a name="scoped-attribute"></a>
#### Атрибут Scoped

Як альтернативу ви можете позначити інтерфейс чи клас атрибутом `#[Scoped]`, вказавши контейнеру, що його слід розв'язувати один раз у межах певного життєвого циклу запиту чи завдання Laravel:

```php
<?php

namespace App\Services;

use Illuminate\Container\Attributes\Scoped;

#[Scoped]
class Transistor
{
    // ...
}
```

<a name="binding-instances"></a>
#### Прив'язка екземплярів

Ви також можете прив'язати до контейнера наявний екземпляр об'єкта методом `instance`. Переданий екземпляр завжди повертатиметься на подальші звернення до контейнера:

```php
use App\Services\Transistor;
use App\Services\PodcastParser;

$service = new Transistor(new PodcastParser);

$this->app->instance(Transistor::class, $service);
```

<a name="binding-interfaces-to-implementations"></a>
### Прив'язка інтерфейсів до реалізацій

Дуже потужна можливість сервіс-контейнера - здатність прив'язати інтерфейс до певної реалізації. Наприклад, припустімо, що ми маємо інтерфейс `EventPusher` і реалізацію `RedisEventPusher`. Написавши реалізацію `RedisEventPusher` цього інтерфейсу, ми можемо зареєструвати її в сервіс-контейнері так:

```php
use App\Contracts\EventPusher;
use App\Services\RedisEventPusher;

$this->app->bind(EventPusher::class, RedisEventPusher::class);
```

Ця інструкція каже контейнеру впроваджувати `RedisEventPusher`, коли класу потрібна реалізація `EventPusher`. Тепер ми можемо вказати інтерфейс `EventPusher` як тип у конструкторі класу, який розв'язує контейнер. Пам'ятайте: контролери, слухачі подій, `middleware` та різні інші типи класів у застосунках Laravel завжди розв'язуються через контейнер:

```php
use App\Contracts\EventPusher;

/**
 * Create a new class instance.
 */
public function __construct(
    protected EventPusher $pusher,
) {}
```

<a name="bind-attribute"></a>
#### Атрибут Bind

Для більшої зручності Laravel також надає атрибут `Bind`. Ви можете застосувати його до будь-якого інтерфейсу, щоб вказати Laravel, яку реалізацію слід автоматично впроваджувати щоразу, коли цей інтерфейс запитують. Використовуючи атрибут `Bind`, вам не потрібно додатково реєструвати сервіс у сервіс-провайдерах застосунку.

Ба більше, на інтерфейс можна розмістити кілька атрибутів `Bind`, щоб налаштувати різні реалізації для різних наборів середовищ:

```php
<?php

namespace App\Contracts;

use App\Services\FakeEventPusher;
use App\Services\RedisEventPusher;
use Illuminate\Container\Attributes\Bind;

#[Bind(RedisEventPusher::class)]
#[Bind(FakeEventPusher::class, environments: ['local', 'testing'])]
interface EventPusher
{
    // ...
}
```

Крім того, можна застосувати атрибути [Singleton](#singleton-attribute) і [Scoped](#scoped-attribute), щоб вказати, чи мають прив'язки контейнера розв'язуватися один раз, чи один раз на життєвий цикл запиту або завдання:

```php
use App\Services\RedisEventPusher;
use Illuminate\Container\Attributes\Bind;
use Illuminate\Container\Attributes\Singleton;

#[Bind(RedisEventPusher::class)]
#[Singleton]
interface EventPusher
{
    // ...
}
```

Для прив'язок, які залежать від довільної умови, можна використовувати атрибут `BindWhen`. Замикання може отримувати контейнер і має повертати `true`, коли прив'язку слід застосувати. Атрибути `Bind` і `BindWhen` оцінюються в порядку їх оголошення:

```php
use App\Services\BetaEventPusher;
use Illuminate\Container\Attributes\BindWhen;
use Laravel\Pennant\Feature;

#[BindWhen(BetaEventPusher::class, static fn () => Feature::active('beta-events'))]
interface EventPusher
{
    // ...
}
```

> [!NOTE]
> Атрибут `BindWhen` вимагає PHP 8.5 або вище.

<a name="contextual-binding"></a>
### Контекстна прив'язка

Іноді ви можете мати два класи, що використовують той самий інтерфейс, але хочете впровадити в кожен із них різні реалізації. Наприклад, два контролери можуть залежати від різних реалізацій [контракту](/docs/{{version}}/contracts) `Illuminate\Contracts\Filesystem\Filesystem`. Laravel надає простий плинний інтерфейс для визначення такої поведінки:

```php
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\UploadController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Facades\Storage;

$this->app->when(PhotoController::class)
    ->needs(Filesystem::class)
    ->give(function () {
        return Storage::disk('local');
    });

$this->app->when([VideoController::class, UploadController::class])
    ->needs(Filesystem::class)
    ->give(function () {
        return Storage::disk('s3');
    });
```

<a name="contextual-attributes"></a>
### Контекстні атрибути

Оскільки контекстну прив'язку часто використовують для впровадження реалізацій драйверів чи значень конфігурації, Laravel пропонує різноманітні атрибути контекстної прив'язки, які дозволяють впроваджувати такі значення, не визначаючи контекстних прив'язок у сервіс-провайдерах вручну.

Наприклад, атрибут `Storage` дозволяє впровадити конкретний [диск сховища](/docs/{{version}}/filesystem):

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Container\Attributes\Storage;
use Illuminate\Contracts\Filesystem\Filesystem;

class PhotoController extends Controller
{
    public function __construct(
        #[Storage('local')] protected Filesystem $filesystem
    ) {
        // ...
    }
}
```

Крім атрибута `Storage`, Laravel пропонує атрибути `Auth`, `Cache`, `Config`, `Context`, `DB`, `Give`, `Log`, `RequestAttribute`, `RouteParameter` і [Tag](#tagging):

```php
<?php

namespace App\Http\Controllers;

use App\Contracts\UserRepository;
use App\Models\Organization;
use App\Models\Photo;
use App\Repositories\DatabaseRepository;
use Illuminate\Container\Attributes\Auth;
use Illuminate\Container\Attributes\Cache;
use Illuminate\Container\Attributes\Config;
use Illuminate\Container\Attributes\Context;
use Illuminate\Container\Attributes\DB;
use Illuminate\Container\Attributes\Give;
use Illuminate\Container\Attributes\Log;
use Illuminate\Container\Attributes\RequestAttribute;
use Illuminate\Container\Attributes\RouteParameter;
use Illuminate\Container\Attributes\Tag;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Database\Connection;
use Psr\Log\LoggerInterface;

class PhotoController extends Controller
{
    public function __construct(
        #[Auth('web')] protected Guard $auth,
        #[Cache('redis')] protected Repository $cache,
        #[Config('app.timezone')] protected string $timezone,
        #[Context('uuid')] protected string $uuid,
        #[Context('ulid', hidden: true)] protected string $ulid,
        #[DB('mysql')] protected Connection $connection,
        #[Give(DatabaseRepository::class)] protected UserRepository $users,
        #[Log('daily')] protected LoggerInterface $log,
        #[RequestAttribute('organization')] protected Organization $organization,
        #[RouteParameter] protected Photo $photo,
        #[Tag('reports')] protected iterable $reports,
    ) {
        // ...
    }
}
```

Атрибут `RouteParameter` розв'яже параметр маршруту, що відповідає імені змінної. За потреби ви можете вказати ім'я параметра явно: `#[RouteParameter('photo')]`.

Атрибут `RequestAttribute` розв'яже значення, збережене за вказаним ключем у [наборі атрибутів](https://symfony.com/doc/current/components/http_foundation.html#accessing-request-data) поточного запиту: `#[RequestAttribute('organization')]`.

Крім того, Laravel надає атрибут `CurrentUser` для впровадження поточного автентифікованого користувача в маршрут чи клас:

```php
use App\Models\User;
use Illuminate\Container\Attributes\CurrentUser;

Route::get('/user', function (#[CurrentUser] User $user) {
    return $user;
})->middleware('auth');
```

<a name="defining-custom-attributes"></a>
#### Визначення власних атрибутів

Ви можете створювати власні контекстні атрибути, реалізувавши контракт `Illuminate\Contracts\Container\ContextualAttribute`. Контейнер викличе метод `resolve` вашого атрибута, який має розв'язати значення для впровадження в клас, що використовує цей атрибут. У прикладі нижче ми заново реалізуємо вбудований атрибут `Config` від Laravel:

```php
<?php

namespace App\Attributes;

use Attribute;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualAttribute;
use ReflectionParameter;

#[Attribute(Attribute::TARGET_PARAMETER)]
class Config implements ContextualAttribute
{
    /**
     * Create a new attribute instance.
     */
    public function __construct(public string $key, public mixed $default = null)
    {
    }

    /**
     * Resolve the configuration value.
     *
     * @param  self  $attribute
     * @param  \Illuminate\Contracts\Container\Container  $container
     * @param  \ReflectionParameter  $parameter
     * @return mixed
     */
    public static function resolve(self $attribute, Container $container, ReflectionParameter $parameter)
    {
        return $container->make('config')->get($attribute->key, $attribute->default);
    }
}
```

<a name="binding-primitives"></a>
### Прив'язка примітивів

Іноді ви можете мати клас, який отримує кілька впроваджених класів, але також потребує впровадженого примітивного значення - наприклад, цілого числа. За допомогою контекстної прив'язки ви можете легко впровадити будь-яке потрібне класу значення:

```php
use App\Http\Controllers\UserController;

$this->app->when(UserController::class)
    ->needs('$variableName')
    ->give($value);
```

Іноді клас може залежати від масиву [позначених тегом](#tagging) екземплярів. За допомогою методу `giveTagged` ви можете легко впровадити всі прив'язки контейнера з цим тегом:

```php
$this->app->when(ReportAggregator::class)
    ->needs('$reports')
    ->giveTagged('reports');
```

Якщо вам потрібно впровадити значення з одного з конфігураційних файлів застосунку, скористайтеся методом `giveConfig`:

```php
$this->app->when(ReportAggregator::class)
    ->needs('$timezone')
    ->giveConfig('app.timezone');
```

<a name="binding-typed-variadics"></a>
### Прив'язка типізованих варіативних параметрів

Подекуди ви можете мати клас, що приймає масив типізованих об'єктів через варіативний аргумент конструктора:

```php
<?php

use App\Models\Filter;
use App\Services\Logger;

class Firewall
{
    /**
     * The filter instances.
     *
     * @var array
     */
    protected $filters;

    /**
     * Create a new class instance.
     */
    public function __construct(
        protected Logger $logger,
        Filter ...$filters,
    ) {
        $this->filters = $filters;
    }
}
```

За допомогою контекстної прив'язки ви можете розв'язати цю залежність, передавши методу `give` замикання, що повертає масив розв'язаних екземплярів `Filter`:

```php
$this->app->when(Firewall::class)
    ->needs(Filter::class)
    ->give(function (Application $app) {
          return [
              $app->make(NullFilter::class),
              $app->make(ProfanityFilter::class),
              $app->make(TooLongFilter::class),
          ];
    });
```

Для зручності ви можете просто передати масив імен класів, які контейнер розв'язуватиме щоразу, коли `Firewall` потребуватиме екземплярів `Filter`:

```php
$this->app->when(Firewall::class)
    ->needs(Filter::class)
    ->give([
        NullFilter::class,
        ProfanityFilter::class,
        TooLongFilter::class,
    ]);
```

<a name="variadic-tag-dependencies"></a>
#### Варіативні залежності з тегом

Іноді клас може мати варіативну залежність, тип якої вказано як певний клас (`Report ...$reports`). За допомогою методів `needs` і `giveTagged` ви можете легко впровадити для цієї залежності всі прив'язки контейнера з відповідним [тегом](#tagging):

```php
$this->app->when(ReportAggregator::class)
    ->needs(Report::class)
    ->giveTagged('reports');
```

<a name="tagging"></a>
### Теги

Подекуди вам може знадобитися розв'язати всі прив'язки певної «категорії». Наприклад, ви створюєте аналізатор звітів, що приймає масив із багатьма різними реалізаціями інтерфейсу `Report`. Зареєструвавши реалізації `Report`, ви можете призначити їм тег методом `tag`:

```php
$this->app->bind(CpuReport::class, function () {
    // ...
});

$this->app->bind(MemoryReport::class, function () {
    // ...
});

$this->app->tag([CpuReport::class, MemoryReport::class], 'reports');
```

Щойно сервіси позначено тегом, ви можете легко розв'язати їх усі методом `tagged` контейнера:

```php
$this->app->bind(ReportAnalyzer::class, function (Application $app) {
    return new ReportAnalyzer($app->tagged('reports'));
});
```

<a name="extending-bindings"></a>
### Розширення прив'язок

Метод `extend` дозволяє змінювати розв'язані сервіси. Наприклад, коли сервіс розв'язано, ви можете виконати додатковий код, щоб декорувати чи налаштувати його. Метод `extend` приймає два аргументи: клас сервісу, який ви розширюєте, і замикання, що має повернути змінений сервіс. Замикання отримує сервіс, який розв'язується, та екземпляр контейнера:

```php
$this->app->extend(Service::class, function (Service $service, Application $app) {
    return new DecoratedService($service);
});
```

<a name="resolving"></a>
## Розв'язання

<a name="the-make-method"></a>
### Метод `make`

Ви можете скористатися методом `make`, щоб розв'язати екземпляр класу з контейнера. Метод `make` приймає ім'я класу чи інтерфейсу, який ви хочете розв'язати:

```php
use App\Services\Transistor;

$transistor = $this->app->make(Transistor::class);
```

Якщо деякі залежності вашого класу не можна розв'язати через контейнер, ви можете впровадити їх, передавши асоціативним масивом у метод `makeWith`. Наприклад, ми можемо вручну передати аргумент конструктора `$id`, потрібний сервісу `Transistor`:

```php
use App\Services\Transistor;

$transistor = $this->app->makeWith(Transistor::class, ['id' => 1]);
```

Метод `bound` дозволяє визначити, чи було явно прив'язано клас або інтерфейс у контейнері:

```php
if ($this->app->bound(Transistor::class)) {
    // ...
}
```

Якщо ви перебуваєте поза сервіс-провайдером у місці коду, де немає доступу до змінної `$app`, ви можете скористатися [фасадом](/docs/{{version}}/facades) `App` або [хелпером](/docs/{{version}}/helpers#method-app) `app`, щоб розв'язати екземпляр класу з контейнера:

```php
use App\Services\Transistor;
use Illuminate\Support\Facades\App;

$transistor = App::make(Transistor::class);

$transistor = app(Transistor::class);
```

Якщо ви хочете, щоб у клас, який розв'язує контейнер, було впроваджено сам екземпляр контейнера Laravel, вкажіть у конструкторі свого класу тип `Illuminate\Container\Container`:

```php
use Illuminate\Container\Container;

/**
 * Create a new class instance.
 */
public function __construct(
    protected Container $container,
) {}
```

<a name="automatic-injection"></a>
### Автоматичне впровадження

Як альтернативу - і це важливо - ви можете вказати тип залежності в конструкторі класу, який розв'язує контейнер, зокрема [контролерів](/docs/{{version}}/controllers), [слухачів подій](/docs/{{version}}/events), [`middleware`](/docs/{{version}}/middleware) тощо. Крім того, ви можете вказувати типи залежностей у методі `handle` [завдань у черзі](/docs/{{version}}/queues). На практиці саме так контейнер і має розв'язувати більшість ваших об'єктів.

Наприклад, ви можете вказати в конструкторі контролера тип сервісу, визначеного вашим застосунком. Сервіс буде автоматично розв'язано й впроваджено в клас:

```php
<?php

namespace App\Http\Controllers;

use App\Services\AppleMusic;

class PodcastController extends Controller
{
    /**
     * Create a new controller instance.
     */
    public function __construct(
        protected AppleMusic $apple,
    ) {}

    /**
     * Show information about the given podcast.
     */
    public function show(string $id): Podcast
    {
        return $this->apple->findPodcast($id);
    }
}
```

<a name="method-invocation-and-injection"></a>
## Виклик методів і впровадження

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

```php
<?php

namespace App;

use App\Services\AppleMusic;

class PodcastStats
{
    /**
     * Generate a new podcast stats report.
     */
    public function generate(AppleMusic $apple): array
    {
        return [
            // ...
        ];
    }
}
```

Ви можете викликати метод `generate` через контейнер так:

```php
use App\PodcastStats;
use Illuminate\Support\Facades\App;

$stats = App::call([new PodcastStats, 'generate']);
```

Метод `call` приймає будь-який PHP callable. Метод `call` контейнера можна навіть використати для виклику замикання з автоматичним впровадженням його залежностей:

```php
use App\Services\AppleMusic;
use Illuminate\Support\Facades\App;

$result = App::call(function (AppleMusic $apple) {
    // ...
});
```

<a name="container-events"></a>
## Події контейнера

Сервіс-контейнер запускає подію щоразу, коли розв'язує об'єкт. Ви можете слухати цю подію методом `resolving`:

```php
use App\Services\Transistor;
use Illuminate\Contracts\Foundation\Application;

$this->app->resolving(Transistor::class, function (Transistor $transistor, Application $app) {
    // Called when container resolves objects of type "Transistor"...
});

$this->app->resolving(function (mixed $object, Application $app) {
    // Called when container resolves object of any type...
});
```

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

<a name="rebinding"></a>
### Повторна прив'язка

Метод `rebinding` дозволяє слухати момент, коли сервіс повторно прив'язується до контейнера, тобто реєструється знову чи перевизначається після початкової прив'язки. Це може бути корисно, коли вам потрібно оновлювати залежності чи змінювати поведінку щоразу, коли конкретну прив'язку оновлено:

```php
use App\Contracts\PodcastPublisher;
use App\Services\SpotifyPublisher;
use App\Services\TransistorPublisher;
use Illuminate\Contracts\Foundation\Application;

$this->app->bind(PodcastPublisher::class, SpotifyPublisher::class);

$this->app->rebinding(
    PodcastPublisher::class,
    function (Application $app, PodcastPublisher $newInstance) {
        //
    },
);

// New binding will trigger rebinding closure...
$this->app->bind(PodcastPublisher::class, TransistorPublisher::class);
```

<a name="psr-11"></a>
## PSR-11

Сервіс-контейнер Laravel реалізує інтерфейс [PSR-11](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-11-container.md). Тому ви можете вказати тип контейнера PSR-11, щоб отримати екземпляр контейнера Laravel:

```php
use App\Services\Transistor;
use Psr\Container\ContainerInterface;

Route::get('/', function (ContainerInterface $container) {
    $service = $container->get(Transistor::class);

    // ...
});
```

Якщо переданий ідентифікатор не вдається розв'язати, буде викинуто виняток. Це буде екземпляр `Psr\Container\NotFoundExceptionInterface`, якщо ідентифікатор ніколи не прив'язували. Якщо ідентифікатор було прив'язано, але розв'язати його не вдалося, буде викинуто екземпляр `Psr\Container\ContainerExceptionInterface`.

---

Джерело: https://laravelukraine.com/docs/13.x/providers

# Сервіс-провайдери

- [Вступ](#introduction)
- [Написання сервіс-провайдерів](#writing-service-providers)
    - [Метод register](#the-register-method)
    - [Метод boot](#the-boot-method)
- [Реєстрація провайдерів](#registering-providers)
- [Відкладені провайдери](#deferred-providers)

<a name="introduction"></a>
## Вступ

Сервіс-провайдери - центральне місце завантаження всього застосунку Laravel. І ваш власний застосунок, і всі базові сервіси Laravel завантажуються саме через сервіс-провайдери.

Але що ми маємо на увазі під «завантаженням»? Загалом - **реєстрацію** речей: прив'язок сервіс-контейнера, слухачів подій, `middleware` і навіть маршрутів. Сервіс-провайдери є центральним місцем налаштування вашого застосунку.

Внутрішньо Laravel використовує десятки сервіс-провайдерів, щоб завантажити свої базові сервіси - поштовий модуль, черги, кеш тощо. Багато з цих провайдерів «відкладені», тобто вони завантажуються не на кожному запиті, а лише тоді, коли сервіси, які вони надають, справді потрібні.

Усі визначені вами сервіс-провайдери реєструються у файлі `bootstrap/providers.php`. Далі в документації ви дізнаєтеся, як писати власні сервіс-провайдери та реєструвати їх у своєму застосунку Laravel.

> [!NOTE]
> Якщо ви хочете дізнатися більше про те, як Laravel обробляє запити й працює всередині, перегляньте нашу документацію про [життєвий цикл запиту](/docs/{{version}}/lifecycle).

<a name="writing-service-providers"></a>
## Написання сервіс-провайдерів

Усі сервіс-провайдери успадковують клас `Illuminate\Support\ServiceProvider`. Більшість сервіс-провайдерів містять методи `register` і `boot`. У методі `register` слід **лише прив'язувати речі до [сервіс-контейнера](/docs/{{version}}/container)**. Ніколи не намагайтеся реєструвати в методі `register` слухачів подій, маршрути чи будь-яку іншу функціональність.

Artisan CLI може згенерувати новий провайдер командою `make:provider`. Laravel автоматично зареєструє ваш новий провайдер у файлі `bootstrap/providers.php` вашого застосунку:

```shell
php artisan make:provider RiakServiceProvider
```

<a name="the-register-method"></a>
### Метод register

Як згадувалося раніше, у методі `register` слід лише прив'язувати речі до [сервіс-контейнера](/docs/{{version}}/container). Ніколи не намагайтеся реєструвати там слухачів подій, маршрути чи будь-яку іншу функціональність. Інакше ви можете випадково скористатися сервісом, який надає ще не завантажений сервіс-провайдер.

Погляньмо на простий сервіс-провайдер. У будь-якому з його методів вам завжди доступна властивість `$app`, що дає доступ до сервіс-контейнера:

```php
<?php

namespace App\Providers;

use App\Services\Riak\Connection;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ServiceProvider;

class RiakServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        $this->app->singleton(Connection::class, function (Application $app) {
            return new Connection(config('riak'));
        });
    }
}
```

Цей сервіс-провайдер визначає лише метод `register` і використовує його, щоб визначити реалізацію `App\Services\Riak\Connection` у сервіс-контейнері. Якщо ви ще не знайомі із сервіс-контейнером Laravel, перегляньте [його документацію](/docs/{{version}}/container).

<a name="the-bindings-and-singletons-properties"></a>
#### Властивості `bindings` і `singletons`

Якщо ваш сервіс-провайдер реєструє багато простих прив'язок, ви можете скористатися властивостями `bindings` і `singletons` замість того, щоб реєструвати кожну прив'язку контейнера вручну. Коли фреймворк завантажує сервіс-провайдер, він автоматично перевіряє ці властивості й реєструє їхні прив'язки:

```php
<?php

namespace App\Providers;

use App\Contracts\DowntimeNotifier;
use App\Contracts\ServerProvider;
use App\Services\DigitalOceanServerProvider;
use App\Services\PingdomDowntimeNotifier;
use App\Services\ServerToolsProvider;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * All of the container bindings that should be registered.
     *
     * @var array
     */
    public $bindings = [
        ServerProvider::class => DigitalOceanServerProvider::class,
    ];

    /**
     * All of the container singletons that should be registered.
     *
     * @var array
     */
    public $singletons = [
        DowntimeNotifier::class => PingdomDowntimeNotifier::class,
        ServerProvider::class => ServerToolsProvider::class,
    ];
}
```

<a name="the-boot-method"></a>
### Метод boot

А що, якщо нам потрібно зареєструвати в сервіс-провайдері [компоновник представлень](/docs/{{version}}/views#view-composers)? Це слід робити в методі `boot`. **Цей метод викликається після реєстрації всіх інших сервіс-провайдерів**, тобто вам доступні всі інші сервіси, зареєстровані фреймворком:

```php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;

class ComposerServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        View::composer('view', function () {
            // ...
        });
    }
}
```

<a name="boot-method-dependency-injection"></a>
#### Впровадження залежностей у методі boot

Ви можете вказати типи залежностей для методу `boot` вашого сервіс-провайдера. [Сервіс-контейнер](/docs/{{version}}/container) автоматично впровадить усі потрібні вам залежності:

```php
use Illuminate\Contracts\Routing\ResponseFactory;

/**
 * Bootstrap any application services.
 */
public function boot(ResponseFactory $response): void
{
    $response->macro('serialized', function (mixed $value) {
        // ...
    });
}
```

<a name="registering-providers"></a>
## Реєстрація провайдерів

Усі сервіс-провайдери реєструються в конфігураційному файлі `bootstrap/providers.php`. Цей файл повертає масив, що містить імена класів сервіс-провайдерів вашого застосунку:

```php
<?php

return [
    App\Providers\AppServiceProvider::class,
];
```

Коли ви викликаєте команду Artisan `make:provider`, Laravel автоматично додає згенерований провайдер до файлу `bootstrap/providers.php`. Однак якщо ви створили клас провайдера вручну, вам слід самостійно додати його до масиву:

```php
<?php

return [
    App\Providers\AppServiceProvider::class,
    App\Providers\ComposerServiceProvider::class, // [tl! add]
];
```

<a name="deferred-providers"></a>
## Відкладені провайдери

Якщо ваш провайдер **лише** реєструє прив'язки в [сервіс-контейнері](/docs/{{version}}/container), ви можете відкласти його реєстрацію до моменту, коли одна із зареєстрованих прив'язок справді знадобиться. Відкладене завантаження такого провайдера покращить швидкодію вашого застосунку, адже його не доведеться завантажувати з файлової системи на кожному запиті.

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

Щоб відкласти завантаження провайдера, реалізуйте інтерфейс `\Illuminate\Contracts\Support\DeferrableProvider` і визначте метод `provides`. Метод `provides` має повертати прив'язки сервіс-контейнера, зареєстровані провайдером:

```php
<?php

namespace App\Providers;

use App\Services\Riak\Connection;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;

class RiakServiceProvider extends ServiceProvider implements DeferrableProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        $this->app->singleton(Connection::class, function (Application $app) {
            return new Connection($app['config']['riak']);
        });
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array<int, string>
     */
    public function provides(): array
    {
        return [Connection::class];
    }
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/facades

# Фасади

- [Вступ](#introduction)
- [Коли використовувати фасади](#when-to-use-facades)
    - [Фасади проти впровадження залежностей](#facades-vs-dependency-injection)
    - [Фасади проти функцій-хелперів](#facades-vs-helper-functions)
- [Як працюють фасади](#how-facades-work)
- [Фасади в реальному часі](#real-time-facades)
- [Довідник класів фасадів](#facade-class-reference)

<a name="introduction"></a>
## Вступ

У всій документації Laravel ви бачитимете приклади коду, що взаємодіє з можливостями Laravel через «фасади». Фасади дають «статичний» інтерфейс до класів, доступних у [сервіс-контейнері](/docs/{{version}}/container) застосунку. Laravel постачається з багатьма фасадами, які дають доступ майже до всіх можливостей фреймворку.

Фасади Laravel слугують «статичними проксі» до класів у сервіс-контейнері, даючи стислий виразний синтаксис і зберігаючи водночас кращу тестованість і гнучкість, ніж традиційні статичні методи. Цілком нормально, якщо ви не до кінця розумієте, як працюють фасади, - просто рухайтеся далі й вивчайте Laravel.

Усі фасади Laravel визначені у просторі імен `Illuminate\Support\Facades`. Тож звернутися до фасаду можна легко:

```php
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;

Route::get('/cache', function () {
    return Cache::get('key');
});
```

У всій документації Laravel багато прикладів використовують фасади, щоб продемонструвати різні можливості фреймворку.

<a name="helper-functions"></a>
#### Функції-хелпери

На додачу до фасадів Laravel пропонує різноманітні глобальні «функції-хелпери», які ще більше спрощують роботу з поширеними можливостями Laravel. Серед хелперів, з якими ви можете стикатися, - `view`, `response`, `url`, `config` тощо. Кожна функція-хелпер Laravel задокументована разом із відповідною можливістю, а повний список доступний в окремій [документації з хелперів](/docs/{{version}}/helpers).

Наприклад, замість фасаду `Illuminate\Support\Facades\Response` для генерації JSON-відповіді можна просто скористатися функцією `response`. Оскільки функції-хелпери доступні глобально, для їх використання не потрібно імпортувати жодних класів:

```php
use Illuminate\Support\Facades\Response;

Route::get('/users', function () {
    return Response::json([
        // ...
    ]);
});

Route::get('/users', function () {
    return response()->json([
        // ...
    ]);
});
```

<a name="when-to-use-facades"></a>
## Коли використовувати фасади

Фасади мають багато переваг. Вони дають стислий, легкий для запам'ятовування синтаксис, що дозволяє користуватися можливостями Laravel, не пам'ятаючи довгих імен класів, які треба впроваджувати чи налаштовувати вручну. До того ж завдяки своєму особливому використанню динамічних методів PHP їх легко тестувати.

Однак із фасадами слід бути обачними. Головна їхня небезпека - «розповзання» відповідальності класу. Оскільки фасадами дуже просто користуватися і вони не потребують впровадження, легко дозволити своїм класам розростатися й використовувати багато фасадів в одному класі. При впровадженні залежностей цього ризику менше завдяки візуальному сигналу: великий конструктор одразу показує, що клас стає завеликим. Тож, використовуючи фасади, особливо уважно стежте за розміром класу, щоб його зона відповідальності лишалася вузькою. Якщо клас стає завеликим, подумайте про поділ його на кілька менших.

<a name="facades-vs-dependency-injection"></a>
### Фасади проти впровадження залежностей

Одна з головних переваг впровадження залежностей (dependency injection) - можливість підміняти реалізації впровадженого класу. Це корисно під час тестування, адже ви можете впровадити мок чи стаб і перевірити, що на ньому були викликані певні методи.

Зазвичай замокати чи застабити справді статичний метод класу неможливо. Однак оскільки фасади використовують динамічні методи, щоб проксіювати виклики до об'єктів, розв'язаних із сервіс-контейнера, ми фактично можемо тестувати фасади так само, як тестували б впроваджений екземпляр класу. Наприклад, маючи такий маршрут:

```php
use Illuminate\Support\Facades\Cache;

Route::get('/cache', function () {
    return Cache::get('key');
});
```

За допомогою методів тестування фасадів Laravel ми можемо написати такий тест, щоб переконатися, що метод `Cache::get` було викликано з очікуваним аргументом:

```php tab=Pest
use Illuminate\Support\Facades\Cache;

test('basic example', function () {
    Cache::shouldReceive('get')
        ->with('key')
        ->andReturn('value');

    $response = $this->get('/cache');

    $response->assertSee('value');
});
```

```php tab=PHPUnit
use Illuminate\Support\Facades\Cache;

/**
 * A basic functional test example.
 */
public function test_basic_example(): void
{
    Cache::shouldReceive('get')
        ->with('key')
        ->andReturn('value');

    $response = $this->get('/cache');

    $response->assertSee('value');
}
```

<a name="facades-vs-helper-functions"></a>
### Фасади проти функцій-хелперів

Крім фасадів, Laravel містить різноманітні функції-хелпери, які виконують типові завдання: генерують представлення, запускають події, диспетчеризують завдання чи надсилають HTTP-відповіді. Багато з них виконують те саме, що й відповідний фасад. Наприклад, цей виклик фасаду й виклик хелпера рівнозначні:

```php
return Illuminate\Support\Facades\View::make('profile');

return view('profile');
```

Між фасадами та функціями-хелперами немає абсолютно жодної практичної різниці. Використовуючи хелпери, ви можете тестувати їх точно так само, як відповідний фасад. Наприклад, маючи такий маршрут:

```php
Route::get('/cache', function () {
    return cache('key');
});
```

Хелпер `cache` викличе метод `get` на класі, що лежить в основі фасаду `Cache`. Тож навіть використовуючи функцію-хелпер, ми можемо написати такий тест, щоб переконатися, що метод було викликано з очікуваним аргументом:

```php
use Illuminate\Support\Facades\Cache;

/**
 * A basic functional test example.
 */
public function test_basic_example(): void
{
    Cache::shouldReceive('get')
        ->with('key')
        ->andReturn('value');

    $response = $this->get('/cache');

    $response->assertSee('value');
}
```

<a name="how-facades-work"></a>
## Як працюють фасади

У застосунку Laravel фасад - це клас, що дає доступ до об'єкта з контейнера. Механізм, який це забезпечує, міститься в класі `Facade`. Фасади Laravel, як і будь-які створені вами власні фасади, успадковують базовий клас `Illuminate\Support\Facades\Facade`.

Базовий клас `Facade` використовує магічний метод `__callStatic()`, щоб перенаправляти виклики з вашого фасаду до об'єкта, розв'язаного з контейнера. У прикладі нижче виконується звернення до системи кешу Laravel. Побіжно глянувши на цей код, можна припустити, що на класі `Cache` викликається статичний метод `get`:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Cache;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * Show the profile for the given user.
     */
    public function showProfile(string $id): View
    {
        $user = Cache::get('user:'.$id);

        return view('profile', ['user' => $user]);
    }
}
```

Зверніть увагу, що ближче до початку файлу ми «імпортуємо» фасад `Cache`. Цей фасад слугує проксі для доступу до реалізації інтерфейсу `Illuminate\Contracts\Cache\Factory`. Усі виклики, зроблені через фасад, буде передано до відповідного екземпляра сервісу кешу Laravel.

Якщо ми зазирнемо до класу `Illuminate\Support\Facades\Cache`, то побачимо, що статичного методу `get` там немає:

```php
class Cache extends Facade
{
    /**
     * Get the registered name of the component.
     */
    protected static function getFacadeAccessor(): string
    {
        return 'cache';
    }
}
```

Натомість фасад `Cache` успадковує базовий клас `Facade` і визначає метод `getFacadeAccessor()`. Завдання цього методу - повернути ім'я прив'язки сервіс-контейнера. Коли користувач звертається до будь-якого статичного методу фасаду `Cache`, Laravel розв'язує прив'язку `cache` із [сервіс-контейнера](/docs/{{version}}/container) і виконує на цьому об'єкті запитаний метод (у цьому випадку - `get`).

<a name="real-time-facades"></a>
## Фасади в реальному часі

За допомогою фасадів у реальному часі ви можете поводитися з будь-яким класом свого застосунку так, ніби це фасад. Щоб проілюструвати, як це працює, спершу розгляньмо код, що їх не використовує. Припустімо, наша модель `Podcast` має метод `publish`. Однак, щоб опублікувати подкаст, нам потрібно впровадити екземпляр `Publisher`:

```php
<?php

namespace App\Models;

use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;

class Podcast extends Model
{
    /**
     * Publish the podcast.
     */
    public function publish(Publisher $publisher): void
    {
        $this->update(['publishing' => now()]);

        $publisher->publish($this);
    }
}
```

Впровадження реалізації publisher у метод дозволяє легко тестувати його ізольовано, адже ми можемо замокати впроваджений publisher. Однак це вимагає щоразу передавати екземпляр publisher під час виклику методу `publish`. Із фасадами в реальному часі ми зберігаємо ту саму тестованість, не будучи зобов'язаними явно передавати екземпляр `Publisher`. Щоб створити фасад у реальному часі, додайте до простору імен імпортованого класу префікс `Facades`:

```php
<?php

namespace App\Models;

use App\Contracts\Publisher; // [tl! remove]
use Facades\App\Contracts\Publisher; // [tl! add]
use Illuminate\Database\Eloquent\Model;

class Podcast extends Model
{
    /**
     * Publish the podcast.
     */
    public function publish(Publisher $publisher): void // [tl! remove]
    public function publish(): void // [tl! add]
    {
        $this->update(['publishing' => now()]);

        $publisher->publish($this); // [tl! remove]
        Publisher::publish($this); // [tl! add]
    }
}
```

Коли використовується фасад у реальному часі, реалізацію publisher буде розв'язано із сервіс-контейнера за тією частиною імені інтерфейсу чи класу, що йде після префікса `Facades`. Під час тестування ми можемо скористатися вбудованими хелперами тестування фасадів Laravel, щоб замокати цей виклик методу:

```php tab=Pest
<?php

use App\Models\Podcast;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;

pest()->use(RefreshDatabase::class);

test('podcast can be published', function () {
    $podcast = Podcast::factory()->create();

    Publisher::shouldReceive('publish')->once()->with($podcast);

    $podcast->publish();
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use App\Models\Podcast;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class PodcastTest extends TestCase
{
    use RefreshDatabase;

    /**
     * A test example.
     */
    public function test_podcast_can_be_published(): void
    {
        $podcast = Podcast::factory()->create();

        Publisher::shouldReceive('publish')->once()->with($podcast);

        $podcast->publish();
    }
}
```

<a name="facade-class-reference"></a>
## Довідник класів фасадів

Нижче наведено кожен фасад і клас, що лежить у його основі. Це зручний інструмент, щоб швидко зазирнути в документацію API для конкретного фасаду. Де це доречно, також вказано ключ [прив'язки сервіс-контейнера](/docs/{{version}}/container).

<div class="overflow-auto">

| Фасад | Клас | Прив'язка сервіс-контейнера |
| --- | --- | --- |
| App | [Illuminate\Foundation\Application](https://api.laravel.com/docs/{{version}}/Illuminate/Foundation/Application.html) | `app` |
| Artisan | [Illuminate\Contracts\Console\Kernel](https://api.laravel.com/docs/{{version}}/Illuminate/Contracts/Console/Kernel.html) | &nbsp; |
| Auth (Instance) | [Illuminate\Contracts\Auth\Guard](https://api.laravel.com/docs/{{version}}/Illuminate/Contracts/Auth/Guard.html) | `auth.driver` |
| Auth | [Illuminate\Auth\AuthManager](https://api.laravel.com/docs/{{version}}/Illuminate/Auth/AuthManager.html) | `auth` |
| Blade | [Illuminate\View\Compilers\BladeCompiler](https://api.laravel.com/docs/{{version}}/Illuminate/View/Compilers/BladeCompiler.html) | `blade.compiler` |
| Broadcast (Instance) | [Illuminate\Contracts\Broadcasting\Broadcaster](https://api.laravel.com/docs/{{version}}/Illuminate/Contracts/Broadcasting/Broadcaster.html) | &nbsp; |
| Broadcast | [Illuminate\Contracts\Broadcasting\Factory](https://api.laravel.com/docs/{{version}}/Illuminate/Contracts/Broadcasting/Factory.html) | &nbsp; |
| Bus | [Illuminate\Contracts\Bus\Dispatcher](https://api.laravel.com/docs/{{version}}/Illuminate/Contracts/Bus/Dispatcher.html) | &nbsp; |
| Cache (Instance) | [Illuminate\Cache\Repository](https://api.laravel.com/docs/{{version}}/Illuminate/Cache/Repository.html) | `cache.store` |
| Cache | [Illuminate\Cache\CacheManager](https://api.laravel.com/docs/{{version}}/Illuminate/Cache/CacheManager.html) | `cache` |
| Cloud | [Illuminate\Foundation\Cloud\CloudManager](https://api.laravel.com/docs/{{version}}/Illuminate/Foundation/Cloud/CloudManager.html) | &nbsp; |
| Config | [Illuminate\Config\Repository](https://api.laravel.com/docs/{{version}}/Illuminate/Config/Repository.html) | `config` |
| Context | [Illuminate\Log\Context\Repository](https://api.laravel.com/docs/{{version}}/Illuminate/Log/Context/Repository.html) | &nbsp; |
| Cookie | [Illuminate\Cookie\CookieJar](https://api.laravel.com/docs/{{version}}/Illuminate/Cookie/CookieJar.html) | `cookie` |
| Crypt | [Illuminate\Encryption\Encrypter](https://api.laravel.com/docs/{{version}}/Illuminate/Encryption/Encrypter.html) | `encrypter` |
| Date | [Illuminate\Support\DateFactory](https://api.laravel.com/docs/{{version}}/Illuminate/Support/DateFactory.html) | `date` |
| DB (Instance) | [Illuminate\Database\Connection](https://api.laravel.com/docs/{{version}}/Illuminate/Database/Connection.html) | `db.connection` |
| DB | [Illuminate\Database\DatabaseManager](https://api.laravel.com/docs/{{version}}/Illuminate/Database/DatabaseManager.html) | `db` |
| Event | [Illuminate\Events\Dispatcher](https://api.laravel.com/docs/{{version}}/Illuminate/Events/Dispatcher.html) | `events` |
| Exceptions (Instance) | [Illuminate\Contracts\Debug\ExceptionHandler](https://api.laravel.com/docs/{{version}}/Illuminate/Contracts/Debug/ExceptionHandler.html) | &nbsp; |
| Exceptions | [Illuminate\Foundation\Exceptions\Handler](https://api.laravel.com/docs/{{version}}/Illuminate/Foundation/Exceptions/Handler.html) | &nbsp; |
| File | [Illuminate\Filesystem\Filesystem](https://api.laravel.com/docs/{{version}}/Illuminate/Filesystem/Filesystem.html) | `files` |
| Gate | [Illuminate\Contracts\Auth\Access\Gate](https://api.laravel.com/docs/{{version}}/Illuminate/Contracts/Auth/Access/Gate.html) | &nbsp; |
| Hash | [Illuminate\Contracts\Hashing\Hasher](https://api.laravel.com/docs/{{version}}/Illuminate/Contracts/Hashing/Hasher.html) | `hash` |
| Http | [Illuminate\Http\Client\Factory](https://api.laravel.com/docs/{{version}}/Illuminate/Http/Client/Factory.html) | &nbsp; |
| Lang | [Illuminate\Translation\Translator](https://api.laravel.com/docs/{{version}}/Illuminate/Translation/Translator.html) | `translator` |
| Log | [Illuminate\Log\LogManager](https://api.laravel.com/docs/{{version}}/Illuminate/Log/LogManager.html) | `log` |
| Mail (Instance) | [Illuminate\Mail\Mailer](https://api.laravel.com/docs/{{version}}/Illuminate/Mail/Mailer.html) | `mailer` |
| Mail | [Illuminate\Mail\MailManager](https://api.laravel.com/docs/{{version}}/Illuminate/Mail/MailManager.html) | `mail.manager` |
| Notification | [Illuminate\Notifications\ChannelManager](https://api.laravel.com/docs/{{version}}/Illuminate/Notifications/ChannelManager.html) | &nbsp; |
| Password (Instance) | [Illuminate\Auth\Passwords\PasswordBroker](https://api.laravel.com/docs/{{version}}/Illuminate/Auth/Passwords/PasswordBroker.html) | `auth.password.broker` |
| Password | [Illuminate\Auth\Passwords\PasswordBrokerManager](https://api.laravel.com/docs/{{version}}/Illuminate/Auth/Passwords/PasswordBrokerManager.html) | `auth.password` |
| Pipeline (Instance) | [Illuminate\Pipeline\Pipeline](https://api.laravel.com/docs/{{version}}/Illuminate/Pipeline/Pipeline.html) | &nbsp; |
| Process | [Illuminate\Process\Factory](https://api.laravel.com/docs/{{version}}/Illuminate/Process/Factory.html) | &nbsp; |
| Queue (Base Class) | [Illuminate\Queue\Queue](https://api.laravel.com/docs/{{version}}/Illuminate/Queue/Queue.html) | &nbsp; |
| Queue (Instance) | [Illuminate\Contracts\Queue\Queue](https://api.laravel.com/docs/{{version}}/Illuminate/Contracts/Queue/Queue.html) | `queue.connection` |
| Queue | [Illuminate\Queue\QueueManager](https://api.laravel.com/docs/{{version}}/Illuminate/Queue/QueueManager.html) | `queue` |
| RateLimiter | [Illuminate\Cache\RateLimiter](https://api.laravel.com/docs/{{version}}/Illuminate/Cache/RateLimiter.html) | &nbsp; |
| Redirect | [Illuminate\Routing\Redirector](https://api.laravel.com/docs/{{version}}/Illuminate/Routing/Redirector.html) | `redirect` |
| Redis (Instance) | [Illuminate\Redis\Connections\Connection](https://api.laravel.com/docs/{{version}}/Illuminate/Redis/Connections/Connection.html) | `redis.connection` |
| Redis | [Illuminate\Redis\RedisManager](https://api.laravel.com/docs/{{version}}/Illuminate/Redis/RedisManager.html) | `redis` |
| Request | [Illuminate\Http\Request](https://api.laravel.com/docs/{{version}}/Illuminate/Http/Request.html) | `request` |
| Response (Instance) | [Illuminate\Http\Response](https://api.laravel.com/docs/{{version}}/Illuminate/Http/Response.html) | &nbsp; |
| Response | [Illuminate\Contracts\Routing\ResponseFactory](https://api.laravel.com/docs/{{version}}/Illuminate/Contracts/Routing/ResponseFactory.html) | &nbsp; |
| Route | [Illuminate\Routing\Router](https://api.laravel.com/docs/{{version}}/Illuminate/Routing/Router.html) | `router` |
| Schedule | [Illuminate\Console\Scheduling\Schedule](https://api.laravel.com/docs/{{version}}/Illuminate/Console/Scheduling/Schedule.html) | &nbsp; |
| Schema | [Illuminate\Database\Schema\Builder](https://api.laravel.com/docs/{{version}}/Illuminate/Database/Schema/Builder.html) | &nbsp; |
| Session (Instance) | [Illuminate\Session\Store](https://api.laravel.com/docs/{{version}}/Illuminate/Session/Store.html) | `session.store` |
| Session | [Illuminate\Session\SessionManager](https://api.laravel.com/docs/{{version}}/Illuminate/Session/SessionManager.html) | `session` |
| Storage (Instance) | [Illuminate\Contracts\Filesystem\Filesystem](https://api.laravel.com/docs/{{version}}/Illuminate/Contracts/Filesystem/Filesystem.html) | `filesystem.disk` |
| Storage | [Illuminate\Filesystem\FilesystemManager](https://api.laravel.com/docs/{{version}}/Illuminate/Filesystem/FilesystemManager.html) | `filesystem` |
| URL | [Illuminate\Routing\UrlGenerator](https://api.laravel.com/docs/{{version}}/Illuminate/Routing/UrlGenerator.html) | `url` |
| Validator (Instance) | [Illuminate\Validation\Validator](https://api.laravel.com/docs/{{version}}/Illuminate/Validation/Validator.html) | &nbsp; |
| Validator | [Illuminate\Validation\Factory](https://api.laravel.com/docs/{{version}}/Illuminate/Validation/Factory.html) | `validator` |
| View (Instance) | [Illuminate\View\View](https://api.laravel.com/docs/{{version}}/Illuminate/View/View.html) | &nbsp; |
| View | [Illuminate\View\Factory](https://api.laravel.com/docs/{{version}}/Illuminate/View/Factory.html) | `view` |
| Vite | [Illuminate\Foundation\Vite](https://api.laravel.com/docs/{{version}}/Illuminate/Foundation/Vite.html) | &nbsp; |

</div>

---

Джерело: https://laravelukraine.com/docs/13.x/routing

# Маршрутизація

- [Базова маршрутизація](#basic-routing)
    - [Типові файли маршрутів](#the-default-route-files)
    - [Маршрути-перенаправлення](#redirect-routes)
    - [Маршрути-представлення](#view-routes)
    - [Перегляд списку маршрутів](#listing-your-routes)
    - [Налаштування маршрутизації](#routing-customization)
- [Параметри маршруту](#route-parameters)
    - [Обов'язкові параметри](#required-parameters)
    - [Необов'язкові параметри](#parameters-optional-parameters)
    - [Обмеження регулярними виразами](#parameters-regular-expression-constraints)
- [Іменовані маршрути](#named-routes)
- [Групи маршрутів](#route-groups)
    - [Middleware](#route-group-middleware)
    - [Контролери](#route-group-controllers)
    - [Маршрутизація піддоменів](#route-group-subdomain-routing)
    - [Префікси маршрутів](#route-group-prefixes)
    - [Префікси імен маршрутів](#route-group-name-prefixes)
- [Прив'язка моделей до маршрутів](#route-model-binding)
    - [Неявна прив'язка](#implicit-binding)
    - [Неявна прив'язка enum](#implicit-enum-binding)
    - [Явна прив'язка](#explicit-binding)
- [Резервні маршрути](#fallback-routes)
- [Обмеження частоти](#rate-limiting)
    - [Визначення обмежувачів частоти](#defining-rate-limiters)
    - [Призначення обмежувачів маршрутам](#attaching-rate-limiters-to-routes)
- [Підміна методу форми](#form-method-spoofing)
- [Доступ до поточного маршруту](#accessing-the-current-route)
- [Спільне використання ресурсів між джерелами (CORS)](#cors)
- [Кешування маршрутів](#route-caching)

<a name="basic-routing"></a>
## Базова маршрутизація

Найпростіші маршрути Laravel приймають URI та замикання, даючи дуже простий і виразний спосіб визначати маршрути й поведінку без складних конфігураційних файлів:

```php
use Illuminate\Support\Facades\Route;

Route::get('/greeting', function () {
    return 'Hello World';
});
```

<a name="the-default-route-files"></a>
### Типові файли маршрутів

Усі маршрути Laravel визначаються у файлах маршрутів, розташованих у каталозі `routes`. Ці файли автоматично завантажуються Laravel за конфігурацією, вказаною у файлі `bootstrap/app.php` вашого застосунку. Файл `routes/web.php` визначає маршрути для вашого веб-інтерфейсу. Цим маршрутам призначається [група `middleware`](/docs/{{version}}/middleware#laravels-default-middleware-groups) `web`, що дає такі можливості, як стан сесії та захист від CSRF.

Для більшості застосунків ви почнете з визначення маршрутів у файлі `routes/web.php`. До маршрутів, визначених у `routes/web.php`, можна звертатися, ввівши URL маршруту в браузері. Наприклад, до наведеного нижче маршруту можна звернутися, перейшовши на `http://example.com/user`:

```php
use App\Http\Controllers\UserController;

Route::get('/user', [UserController::class, 'index']);
```

<a name="api-routes"></a>
#### Маршрути API

Якщо ваш застосунок також надаватиме безстанове API, ви можете увімкнути маршрутизацію API командою Artisan `install:api`:

```shell
php artisan install:api
```

Команда `install:api` встановлює [Laravel Sanctum](/docs/{{version}}/sanctum), який надає надійний і водночас простий гард автентифікації за API-токенами - його можна використовувати для автентифікації сторонніх споживачів API, SPA чи мобільних застосунків. Крім того, команда `install:api` створює файл `routes/api.php`:

```php
Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:sanctum');
```

Звісно, ви вільні не додавати `middleware` `auth:sanctum` до маршрутів, які мають бути загальнодоступними.

Маршрути у `routes/api.php` є безстановими й належать до [групи `middleware`](/docs/{{version}}/middleware#laravels-default-middleware-groups) `api`. Крім того, до цих маршрутів автоматично застосовується префікс URI `/api`, тож вам не потрібно додавати його вручну до кожного маршруту у файлі. Змінити префікс можна у файлі `bootstrap/app.php`:

```php
->withRouting(
    api: __DIR__.'/../routes/api.php',
    apiPrefix: 'api/admin',
    // ...
)
```

<a name="available-router-methods"></a>
#### Доступні методи маршрутизатора

Маршрутизатор дозволяє реєструвати маршрути, що відповідають на будь-який HTTP-метод:

```php
Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::patch($uri, $callback);
Route::delete($uri, $callback);
Route::options($uri, $callback);
Route::query($uri, $callback);
```

Метод `query` реєструє маршрут, що відповідає на HTTP-метод `QUERY`, що дає змогу клієнтам передавати умови вибірки в тілі запиту. Маршрути `QUERY` у групі `middleware` `web` підлягають [CSRF-захисту](/docs/{{version}}/csrf).

Іноді вам може знадобитися зареєструвати маршрут, що відповідає на кілька HTTP-методів. Це робиться методом `match`. Або ж ви можете зареєструвати маршрут, що відповідає на всі HTTP-методи, включно з `QUERY`, методом `any`:

```php
Route::match(['get', 'post'], '/', function () {
    // ...
});

Route::any('/', function () {
    // ...
});
```

> [!NOTE]
> Визначаючи кілька маршрутів зі спільним URI, маршрути з методами `get`, `post`, `put`, `patch`, `delete`, `options` та `query` слід визначати перед маршрутами з методами `any`, `match` і `redirect`. Це гарантує, що вхідний запит буде зіставлено з правильним маршрутом.

<a name="dependency-injection"></a>
#### Впровадження залежностей

Ви можете вказати типи будь-яких потрібних маршруту залежностей у сигнатурі його колбека. Оголошені залежності буде автоматично розв'язано й впроваджено в колбек [сервіс-контейнером](/docs/{{version}}/container) Laravel. Наприклад, ви можете вказати тип `Illuminate\Http\Request`, щоб поточний HTTP-запит автоматично впроваджувався у колбек маршруту:

```php
use Illuminate\Http\Request;

Route::get('/users', function (Request $request) {
    // ...
});
```

<a name="csrf-protection"></a>
#### Захист від CSRF

Пам'ятайте: будь-які HTML-форми, що вказують на маршрути `POST`, `PUT`, `PATCH` чи `DELETE`, визначені у файлі маршрутів `web`, мають містити поле з CSRF-токеном. Інакше запит буде відхилено. Докладніше про захист від CSRF читайте в [документації з CSRF](/docs/{{version}}/csrf):

```blade
<form method="POST" action="/profile">
    @csrf
    ...
</form>
```

<a name="redirect-routes"></a>
### Маршрути-перенаправлення

Якщо ви визначаєте маршрут, який перенаправляє на інший URI, скористайтеся методом `Route::redirect`. Він дає зручне скорочення, тож вам не доведеться визначати повноцінний маршрут чи контролер заради простого перенаправлення:

```php
Route::redirect('/here', '/there');
```

За замовчуванням `Route::redirect` повертає статус-код `302`. Ви можете змінити його необов'язковим третім параметром:

```php
Route::redirect('/here', '/there', 301);
```

Або ж скористайтеся методом `Route::permanentRedirect`, щоб повернути статус-код `301`:

```php
Route::permanentRedirect('/here', '/there');
```

> [!WARNING]
> Використовуючи параметри маршруту в маршрутах-перенаправленнях, пам'ятайте: параметри `destination` і `status` зарезервовані Laravel і не можуть бути використані.

<a name="view-routes"></a>
### Маршрути-представлення

Якщо ваш маршрут має лише повернути [представлення](/docs/{{version}}/views), скористайтеся методом `Route::view`. Як і метод `redirect`, він дає просте скорочення, тож вам не доведеться визначати повноцінний маршрут чи контролер. Метод `view` приймає URI першим аргументом та ім'я представлення другим. Крім того, ви можете передати масив даних для представлення необов'язковим третім аргументом:

```php
Route::view('/welcome', 'welcome');

Route::view('/welcome', 'welcome', ['name' => 'Taylor']);
```

> [!WARNING]
> Використовуючи параметри маршруту в маршрутах-представленнях, пам'ятайте: параметри `view`, `data`, `status` і `headers` зарезервовані Laravel і не можуть бути використані.

<a name="listing-your-routes"></a>
### Перегляд списку маршрутів

Команда Artisan `route:list` дає зручний огляд усіх маршрутів, визначених вашим застосунком:

```shell
php artisan route:list
```

За замовчуванням `middleware`, призначені кожному маршруту, у виводі `route:list` не показуються; однак ви можете вказати Laravel показувати `middleware` маршрутів та імена їхніх груп, додавши до команди опцію `-v`:

```shell
php artisan route:list -v

# Expand middleware groups...
php artisan route:list -vv
```

Ви також можете вказати Laravel показувати лише маршрути, чий URI містить певний рядок:

```shell
php artisan route:list --path=api
```

Крім того, ви можете сховати маршрути, визначені сторонніми пакетами, передавши опцію `--except-vendor` під час виконання команди `route:list`:

```shell
php artisan route:list --except-vendor
```

І навпаки, ви можете показати лише маршрути, визначені сторонніми пакетами, передавши опцію `--only-vendor`:

```shell
php artisan route:list --only-vendor
```

<a name="routing-customization"></a>
### Налаштування маршрутизації

За замовчуванням маршрути вашого застосунку налаштовуються й завантажуються файлом `bootstrap/app.php`:

```php
<?php

use Illuminate\Foundation\Application;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )->create();
```

Утім, іноді вам може знадобитися визначити цілком новий файл для частини маршрутів застосунку. Для цього передайте замикання `then` методу `withRouting`. У цьому замиканні ви можете зареєструвати будь-які додаткові маршрути, потрібні вашому застосунку:

```php
use Illuminate\Support\Facades\Route;

->withRouting(
    web: __DIR__.'/../routes/web.php',
    commands: __DIR__.'/../routes/console.php',
    health: '/up',
    then: function () {
        Route::middleware('api')
            ->prefix('webhooks')
            ->name('webhooks.')
            ->group(base_path('routes/webhooks.php'));
    },
)
```

Або ж ви можете взяти повний контроль над реєстрацією маршрутів, передавши замикання `using` методу `withRouting`. Коли передано цей аргумент, фреймворк не зареєструє жодного HTTP-маршруту, і ви відповідаєте за реєстрацію всіх маршрутів вручну:

```php
use Illuminate\Support\Facades\Route;

->withRouting(
    commands: __DIR__.'/../routes/console.php',
    using: function () {
        Route::middleware('api')
            ->prefix('api')
            ->group(base_path('routes/api.php'));

        Route::middleware('web')
            ->group(base_path('routes/web.php'));
    },
)
```

<a name="route-parameters"></a>
## Параметри маршруту

<a name="required-parameters"></a>
### Обов'язкові параметри

Іноді вам знадобиться захопити сегменти URI у своєму маршруті. Наприклад, вам може знадобитися взяти з URL ідентифікатор користувача. Це робиться визначенням параметрів маршруту:

```php
Route::get('/user/{id}', function (string $id) {
    return 'User '.$id;
});
```

Ви можете визначити стільки параметрів маршруту, скільки потрібно:

```php
Route::get('/posts/{post}/comments/{comment}', function (string $postId, string $commentId) {
    // ...
});
```

Параметри маршруту завжди беруться у фігурні дужки `{}` і мають складатися з літер. Символи підкреслення (`_`) в іменах параметрів теж допустимі. Параметри впроваджуються в колбеки чи контролери маршрутів за їхнім порядком - імена аргументів колбека чи контролера значення не мають.

<a name="parameters-and-dependency-injection"></a>
#### Параметри та впровадження залежностей

Якщо ваш маршрут має залежності, які сервіс-контейнер Laravel має автоматично впровадити в колбек, перелічуйте параметри маршруту після залежностей:

```php
use Illuminate\Http\Request;

Route::get('/user/{id}', function (Request $request, string $id) {
    return 'User '.$id;
});
```

<a name="parameters-optional-parameters"></a>
### Необов'язкові параметри

Подекуди вам може знадобитися параметр маршруту, який не завжди присутній в URI. Для цього поставте `?` після імені параметра. Обов'язково задайте відповідній змінній маршруту значення за замовчуванням:

```php
Route::get('/user/{name?}', function (?string $name = null) {
    return $name;
});

Route::get('/user/{name?}', function (?string $name = 'John') {
    return $name;
});
```

<a name="parameters-regular-expression-constraints"></a>
### Обмеження регулярними виразами

Ви можете обмежити формат параметрів маршруту методом `where` на екземплярі маршруту. Метод `where` приймає ім'я параметра та регулярний вираз, що визначає обмеження:

```php
Route::get('/user/{name}', function (string $name) {
    // ...
})->where('name', '[A-Za-z]+');

Route::get('/user/{id}', function (string $id) {
    // ...
})->where('id', '[0-9]+');

Route::get('/user/{id}/{name}', function (string $id, string $name) {
    // ...
})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);
```

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

```php
Route::get('/user/{id}/{name}', function (string $id, string $name) {
    // ...
})->whereNumber('id')->whereAlpha('name');

Route::get('/user/{name}', function (string $name) {
    // ...
})->whereAlphaNumeric('name');

Route::get('/user/{id}', function (string $id) {
    // ...
})->whereUuid('id');

Route::get('/user/{id}', function (string $id) {
    // ...
})->whereUlid('id');

Route::get('/category/{category}', function (string $category) {
    // ...
})->whereIn('category', ['movie', 'song', 'painting']);

Route::get('/category/{category}', function (string $category) {
    // ...
})->whereIn('category', CategoryEnum::cases());
```

Якщо вхідний запит не відповідає обмеженням маршруту, буде повернуто HTTP-відповідь 404.

<a name="parameters-global-constraints"></a>
#### Глобальні обмеження

Якщо ви хочете, щоб параметр маршруту завжди був обмежений певним регулярним виразом, скористайтеся методом `pattern`. Ці шаблони слід визначати в методі `boot` класу `App\Providers\AppServiceProvider` вашого застосунку:

```php
use Illuminate\Support\Facades\Route;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Route::pattern('id', '[0-9]+');
}
```

Щойно шаблон визначено, він автоматично застосовується до всіх маршрутів із цим іменем параметра:

```php
Route::get('/user/{id}', function (string $id) {
    // Only executed if {id} is numeric...
});
```

<a name="parameters-encoded-forward-slashes"></a>
#### Закодовані прямі скісні риски

Компонент маршрутизації Laravel дозволяє будь-які символи, окрім `/`, у значеннях параметрів маршруту. Щоб `/` міг бути частиною вашого заповнювача, потрібно явно дозволити це регулярним виразом в умові `where`:

```php
Route::get('/search/{search}', function (string $search) {
    return $search;
})->where('search', '.*');
```

> [!WARNING]
> Закодовані прямі скісні риски підтримуються лише в останньому сегменті маршруту.

<a name="named-routes"></a>
## Іменовані маршрути

Іменовані маршрути дозволяють зручно генерувати URL чи перенаправлення для конкретних маршрутів. Задати ім'я маршруту можна, приєднавши метод `name` до його визначення:

```php
Route::get('/user/profile', function () {
    // ...
})->name('profile');
```

Ви також можете задавати імена маршрутів для дій контролерів:

```php
Route::get(
    '/user/profile',
    [UserProfileController::class, 'show']
)->name('profile');
```

> [!WARNING]
> Імена маршрутів завжди мають бути унікальними.

<a name="generating-urls-to-named-routes"></a>
#### Генерація URL до іменованих маршрутів

Щойно ви призначили маршруту ім'я, ви можете використовувати його під час генерації URL чи перенаправлень через функції-хелпери `route` і `redirect`:

```php
// Generating URLs...
$url = route('profile');

// Generating Redirects...
return redirect()->route('profile');

return to_route('profile');
```

Якщо іменований маршрут визначає параметри, ви можете передати їх другим аргументом функції `route`. Передані параметри буде автоматично вставлено в згенерований URL у правильних позиціях:

```php
Route::get('/user/{id}/profile', function (string $id) {
    // ...
})->name('profile');

$url = route('profile', ['id' => 1]);
```

Якщо ви передасте в масиві додаткові параметри, ці пари «ключ - значення» буде автоматично додано до рядка запиту згенерованого URL:

```php
Route::get('/user/{id}/profile', function (string $id) {
    // ...
})->name('profile');

$url = route('profile', ['id' => 1, 'photos' => 'yes']);

// http://example.com/user/1/profile?photos=yes
```

> [!NOTE]
> Іноді вам може знадобитися задати значення за замовчуванням для параметрів URL у межах усього запиту - наприклад, поточну локаль. Для цього скористайтеся [методом URL::defaults](/docs/{{version}}/urls#default-values).

<a name="inspecting-the-current-route"></a>
#### Перевірка поточного маршруту

Якщо ви хочете визначити, чи спрямовано поточний запит до певного іменованого маршруту, скористайтеся методом `named` на екземплярі Route. Наприклад, ви можете перевірити ім'я поточного маршруту з `middleware` маршруту:

```php
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

/**
 * Handle an incoming request.
 *
 * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
 */
public function handle(Request $request, Closure $next): Response
{
    if ($request->route()->named('profile')) {
        // ...
    }

    return $next($request);
}
```

<a name="route-groups"></a>
## Групи маршрутів

Групи маршрутів дозволяють спільно використовувати атрибути маршрутів - як-от `middleware` - для великої кількості маршрутів, не визначаючи ці атрибути для кожного окремо.

Вкладені групи намагаються розумно «об'єднати» атрибути з батьківською групою. `Middleware` та умови `where` об'єднуються, а імена й префікси додаються в кінець. Роздільники просторів імен і скісні риски в префіксах URI додаються автоматично там, де це доречно.

<a name="route-group-middleware"></a>
### Middleware

Щоб призначити [`middleware`](/docs/{{version}}/middleware) усім маршрутам групи, скористайтеся методом `middleware` перед визначенням групи. `Middleware` виконуються в тому порядку, у якому їх перелічено в масиві:

```php
Route::middleware(['first', 'second'])->group(function () {
    Route::get('/', function () {
        // Uses first & second middleware...
    });

    Route::get('/user/profile', function () {
        // Uses first & second middleware...
    });
});
```

<a name="route-group-controllers"></a>
### Контролери

Якщо група маршрутів використовує той самий [контролер](/docs/{{version}}/controllers), ви можете скористатися методом `controller`, щоб визначити спільний контролер для всіх маршрутів групи. Тоді, визначаючи маршрути, вам потрібно вказати лише метод контролера, який вони викликають:

```php
use App\Http\Controllers\OrderController;

Route::controller(OrderController::class)->group(function () {
    Route::get('/orders/{id}', 'show');
    Route::post('/orders', 'store');
});
```

<a name="route-group-subdomain-routing"></a>
### Маршрутизація піддоменів

Групи маршрутів також можна використовувати для маршрутизації піддоменів. Піддоменам можна призначати параметри маршруту так само, як і URI, що дозволяє захопити частину піддомену для використання у вашому маршруті чи контролері. Піддомен вказується викликом методу `domain` перед визначенням групи:

```php
Route::domain('{account}.example.com')->group(function () {
    Route::get('/user/{id}', function (string $account, string $id) {
        // ...
    });
});
```

<a name="route-group-prefixes"></a>
### Префікси маршрутів

Метод `prefix` дозволяє додати кожному маршруту групи вказаний префікс URI. Наприклад, ви можете захотіти додати всім URI маршрутів групи префікс `admin`:

```php
Route::prefix('admin')->group(function () {
    Route::get('/users', function () {
        // Matches The "/admin/users" URL
    });
});
```

<a name="route-group-name-prefixes"></a>
### Префікси імен маршрутів

Метод `name` дозволяє додати кожному імені маршруту в групі вказаний рядок-префікс. Наприклад, ви можете захотіти додати іменам усіх маршрутів групи префікс `admin`. Переданий рядок додається до імені маршруту точно так, як його вказано, тож не забудьте про крапку `.` наприкінці префікса:

```php
Route::name('admin.')->group(function () {
    Route::get('/users', function () {
        // Route assigned name "admin.users"...
    })->name('users');
});
```

<a name="route-model-binding"></a>
## Прив'язка моделей до маршрутів

Впроваджуючи ідентифікатор моделі в маршрут чи дію контролера, ви часто робите запит до бази даних, щоб отримати модель, яка відповідає цьому ідентифікатору. Прив'язка моделей до маршрутів у Laravel дає зручний спосіб автоматично впроваджувати екземпляри моделей просто у ваші маршрути. Наприклад, замість впроваджувати ідентифікатор користувача, ви можете впровадити цілий екземпляр моделі `User`, що відповідає цьому ідентифікатору.

<a name="implicit-binding"></a>
### Неявна прив'язка

Laravel автоматично розв'язує моделі Eloquent, визначені в маршрутах чи діях контролерів, коли імена їхніх типізованих змінних збігаються з іменем сегмента маршруту. Наприклад:

```php
use App\Models\User;

Route::get('/users/{user}', function (User $user) {
    return $user->email;
});
```

Оскільки змінна `$user` має тип моделі Eloquent `App\Models\User`, а ім'я змінної збігається із сегментом URI `{user}`, Laravel автоматично впровадить екземпляр моделі з ідентифікатором, що відповідає значенню з URI запиту. Якщо відповідного екземпляра в базі даних не знайдено, автоматично буде згенеровано HTTP-відповідь 404.

Звісно, неявна прив'язка можлива й у методах контролерів. Знову ж таки, зверніть увагу: сегмент URI `{user}` збігається зі змінною `$user` у контролері, яка має тип `App\Models\User`:

```php
use App\Http\Controllers\UserController;
use App\Models\User;

// Route definition...
Route::get('/users/{user}', [UserController::class, 'show']);

// Controller method definition...
public function show(User $user)
{
    return view('user.profile', ['user' => $user]);
}
```

<a name="implicit-soft-deleted-models"></a>
#### М'яко видалені моделі

Зазвичай неявна прив'язка не отримує моделі, які було [м'яко видалено](/docs/{{version}}/eloquent#soft-deleting). Утім, ви можете вказати неявній прив'язці отримувати такі моделі, приєднавши метод `withTrashed` до визначення маршруту:

```php
use App\Models\User;

Route::get('/users/{user}', function (User $user) {
    return $user->email;
})->withTrashed();
```

<a name="customizing-the-default-key-name"></a>
#### Налаштування ключа

Іноді ви можете захотіти розв'язувати моделі Eloquent за колонкою, відмінною від `id`. Для цього вкажіть колонку у визначенні параметра маршруту:

```php
use App\Models\Post;

Route::get('/posts/{post:slug}', function (Post $post) {
    return $post;
});
```

Якщо ви хочете, щоб прив'язка моделей завжди використовувала колонку, відмінну від `id`, під час отримання певного класу моделі, застосуйте до моделі Eloquent атрибут `RouteKey`:

```php
use Illuminate\Database\Eloquent\Attributes\RouteKey;
use Illuminate\Database\Eloquent\Model;

#[RouteKey('slug')]
class Post extends Model
{
    // ...
}
```

<a name="implicit-model-binding-scoping"></a>
#### Власні ключі та обмеження області

Неявно прив'язуючи кілька моделей Eloquent в одному визначенні маршруту, ви можете захотіти обмежити другу модель так, щоб вона обов'язково була дочірньою щодо попередньої. Наприклад, розгляньмо визначення маршруту, що отримує допис блогу за slug для конкретного користувача:

```php
use App\Models\Post;
use App\Models\User;

Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
    return $post;
});
```

Коли ви використовуєте неявну прив'язку з власним ключем як параметр вкладеного маршруту, Laravel автоматично обмежить запит так, щоб отримати вкладену модель через її батька, використовуючи домовленості для вгадування імені зв'язку. У цьому випадку припускатиметься, що модель `User` має зв'язок `posts` (множина від імені параметра маршруту), через який можна отримати модель `Post`.

За бажанням ви можете вказати Laravel обмежувати «дочірні» прив'язки навіть тоді, коли власного ключа не передано. Для цього викличте метод `scopeBindings` під час визначення маршруту:

```php
use App\Models\Post;
use App\Models\User;

Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
    return $post;
})->scopeBindings();
```

Або ж ви можете вказати цілій групі визначень маршрутів використовувати прив'язки з обмеженою областю:

```php
Route::scopeBindings()->group(function () {
    Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
        return $post;
    });
});
```

Так само ви можете явно вказати Laravel не обмежувати прив'язки, викликавши метод `withoutScopedBindings`:

```php
Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
    return $post;
})->withoutScopedBindings();
```

<a name="customizing-missing-model-behavior"></a>
#### Налаштування поведінки для відсутньої моделі

Зазвичай, якщо неявно прив'язану модель не знайдено, генерується HTTP-відповідь 404. Утім, ви можете налаштувати цю поведінку, викликавши метод `missing` під час визначення маршруту. Метод `missing` приймає замикання, яке буде викликано, якщо неявно прив'язану модель не вдалося знайти:

```php
use App\Http\Controllers\LocationsController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;

Route::get('/locations/{location:slug}', [LocationsController::class, 'show'])
    ->name('locations.view')
    ->missing(function (Request $request) {
        return Redirect::route('locations.index');
    });
```

<a name="implicit-enum-binding"></a>
### Неявна прив'язка enum

PHP 8.1 запровадив підтримку [enum](https://www.php.net/manual/en/language.enumerations.backed.php). Доповнюючи цю можливість, Laravel дозволяє вказати у визначенні маршруту тип [enum на основі рядка](https://www.php.net/manual/en/language.enumerations.backed.php), і Laravel викличе маршрут лише тоді, коли сегмент маршруту відповідає дійсному значенню enum. Інакше автоматично буде повернуто HTTP-відповідь 404. Наприклад, маючи такий enum:

```php
<?php

namespace App\Enums;

enum Category: string
{
    case Fruits = 'fruits';
    case People = 'people';
}
```

Ви можете визначити маршрут, який буде викликано лише тоді, коли сегмент `{category}` дорівнює `fruits` чи `people`. Інакше Laravel поверне HTTP-відповідь 404:

```php
use App\Enums\Category;
use Illuminate\Support\Facades\Route;

Route::get('/categories/{category}', function (Category $category) {
    return $category->value;
});
```

<a name="explicit-binding"></a>
### Явна прив'язка

Ви не зобов'язані використовувати неявне розв'язання моделей за домовленостями, щоб користуватися прив'язкою моделей. Ви також можете явно визначити, як параметри маршруту відповідають моделям. Щоб зареєструвати явну прив'язку, скористайтеся методом `model` маршрутизатора, вказавши клас для певного параметра. Явні прив'язки моделей слід визначати на початку методу `boot` вашого класу `AppServiceProvider`:

```php
use App\Models\User;
use Illuminate\Support\Facades\Route;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Route::model('user', User::class);
}
```

Далі визначте маршрут, що містить параметр `{user}`:

```php
use App\Models\User;

Route::get('/users/{user}', function (User $user) {
    // ...
});
```

Оскільки ми прив'язали всі параметри `{user}` до моделі `App\Models\User`, у маршрут буде впроваджено екземпляр цього класу. Тож, наприклад, запит до `users/1` впровадить екземпляр `User` із бази даних, що має ідентифікатор `1`.

Якщо відповідного екземпляра в базі даних не знайдено, автоматично буде згенеровано HTTP-відповідь 404.

<a name="customizing-the-resolution-logic"></a>
#### Налаштування логіки розв'язання

Якщо ви хочете визначити власну логіку розв'язання прив'язок моделей, скористайтеся методом `Route::bind`. Замикання, передане методу `bind`, отримає значення сегмента URI й має повернути екземпляр класу, який слід впровадити в маршрут. Знову ж таки, це налаштування має відбуватися в методі `boot` вашого `AppServiceProvider`:

```php
use App\Models\User;
use Illuminate\Support\Facades\Route;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Route::bind('user', function (string $value) {
        return User::where('name', $value)->firstOrFail();
    });
}
```

Як альтернативу ви можете перевизначити метод `resolveRouteBinding` у своїй моделі Eloquent. Він отримає значення сегмента URI й має повернути екземпляр класу для впровадження в маршрут:

```php
/**
 * Retrieve the model for a bound value.
 *
 * @param  mixed  $value
 * @param  string|null  $field
 * @return \Illuminate\Database\Eloquent\Model|null
 */
public function resolveRouteBinding($value, $field = null)
{
    return $this->where('name', $value)->firstOrFail();
}
```

Якщо маршрут використовує [обмеження області неявних прив'язок](#implicit-model-binding-scoping), для розв'язання дочірньої прив'язки батьківської моделі буде використано метод `resolveChildRouteBinding`:

```php
/**
 * Retrieve the child model for a bound value.
 *
 * @param  string  $childType
 * @param  mixed  $value
 * @param  string|null  $field
 * @return \Illuminate\Database\Eloquent\Model|null
 */
public function resolveChildRouteBinding($childType, $value, $field)
{
    return parent::resolveChildRouteBinding($childType, $value, $field);
}
```

<a name="fallback-routes"></a>
## Резервні маршрути

За допомогою методу `Route::fallback` ви можете визначити маршрут, який виконуватиметься, коли жоден інший маршрут не збігається з вхідним запитом. Зазвичай необроблені запити автоматично рендерять сторінку «404» через обробник винятків вашого застосунку. Однак оскільки резервний маршрут ви зазвичай визначаєте у файлі `routes/web.php`, до нього застосовуватимуться всі `middleware` групи `web`. Ви вільні додавати до цього маршруту й інші `middleware` за потреби:

```php
Route::fallback(function () {
    // ...
});
```

<a name="rate-limiting"></a>
## Обмеження частоти

<a name="defining-rate-limiters"></a>
### Визначення обмежувачів частоти

Laravel містить потужні й гнучкі сервіси обмеження частоти, якими можна скористатися, щоб обмежити обсяг трафіку для певного маршруту чи групи маршрутів. Щоб почати, визначте конфігурації обмежувачів, що відповідають потребам вашого застосунку.

Обмежувачі частоти можна визначати в методі `boot` класу `App\Providers\AppServiceProvider` вашого застосунку:

```php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
    });
}
```

Обмежувачі визначаються методом `for` фасаду `RateLimiter`. Метод `for` приймає ім'я обмежувача й замикання, що повертає конфігурацію обмеження для маршрутів, яким призначено цей обмежувач. Конфігурації обмежень є екземплярами класу `Illuminate\Cache\RateLimiting\Limit`. Цей клас містить корисні методи-будівельники, щоб ви могли швидко визначити своє обмеження. Ім'ям обмежувача може бути будь-який рядок:

```php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    RateLimiter::for('global', function (Request $request) {
        return Limit::perMinute(1000);
    });
}
```

Якщо вхідний запит перевищує вказане обмеження, Laravel автоматично поверне відповідь зі статус-кодом 429. Якщо ви хочете визначити власну відповідь для перевищення обмеження, скористайтеся методом `response`:

```php
RateLimiter::for('global', function (Request $request) {
    return Limit::perMinute(1000)->response(function (Request $request, array $headers) {
        return response('Custom response...', 429, $headers);
    });
});
```

Оскільки колбеки обмежувачів отримують екземпляр вхідного HTTP-запиту, ви можете будувати відповідне обмеження динамічно на основі запиту чи автентифікованого користувача:

```php
RateLimiter::for('uploads', function (Request $request) {
    return $request->user()?->vipCustomer()
        ? Limit::none()
        : Limit::perHour(10);
});
```

<a name="segmenting-rate-limits"></a>
#### Сегментація обмежень частоти

Іноді ви можете захотіти сегментувати обмеження за якимось довільним значенням. Наприклад, ви можете дозволити користувачам звертатися до маршруту 100 разів на хвилину з однієї IP-адреси. Для цього скористайтеся методом `by` під час побудови обмеження:

```php
RateLimiter::for('uploads', function (Request $request) {
    return $request->user()->vipCustomer()
        ? Limit::none()
        : Limit::perMinute(100)->by($request->ip());
});
```

Проілюструймо цю можливість ще одним прикладом: ми можемо обмежити доступ до маршруту до 100 разів на хвилину для автентифікованого користувача або 10 разів на хвилину з IP-адреси для гостей:

```php
RateLimiter::for('uploads', function (Request $request) {
    return $request->user()
        ? Limit::perMinute(100)->by($request->user()->id)
        : Limit::perMinute(10)->by($request->ip());
});
```

<a name="multiple-rate-limits"></a>
#### Кілька обмежень частоти

За потреби ви можете повернути масив обмежень для конфігурації обмежувача. Кожне обмеження оцінюватиметься для маршруту в тому порядку, у якому їх розміщено в масиві:

```php
RateLimiter::for('login', function (Request $request) {
    return [
        Limit::perMinute(500),
        Limit::perMinute(3)->by($request->input('email')),
    ];
});
```

Якщо ви призначаєте кілька обмежень, сегментованих однаковими значеннями `by`, переконайтеся, що кожне значення `by` унікальне. Найпростіший спосіб цього досягти - додати до переданих методу `by` значень префікси:

```php
RateLimiter::for('uploads', function (Request $request) {
    return [
        Limit::perMinute(10)->by('minute:'.$request->user()->id),
        Limit::perDay(1000)->by('day:'.$request->user()->id),
    ];
});
```

<a name="response-base-rate-limiting"></a>
#### Обмеження частоти на основі відповіді

Окрім обмеження вхідних запитів, Laravel дозволяє обмежувати частоту на основі відповіді за допомогою методу `after`. Це корисно, коли ви хочете зараховувати до обмеження лише певні відповіді - як-от помилки валідації, відповіді 404 чи інші конкретні HTTP-статуси.

Метод `after` приймає замикання, яке отримує відповідь і має повернути `true`, якщо цю відповідь слід зарахувати до обмеження, або `false`, якщо її слід проігнорувати. Це особливо корисно, щоб запобігти атакам перебору, обмеживши послідовні відповіді 404, або щоб дозволити користувачам повторювати запити, які не пройшли валідацію, не вичерпуючи ліміт на точці, що має обмежувати лише успішні операції:

```php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Symfony\Component\HttpFoundation\Response;

RateLimiter::for('resource-not-found', function (Request $request) {
    return Limit::perMinute(10)
        ->by($request->user()?->id ?: $request->ip())
        ->after(function (Response $response) {
            // Only count 404 responses toward the rate limit to prevent enumeration...
            return $response->status() === 404;
        });
});
```

<a name="attaching-rate-limiters-to-routes"></a>
### Призначення обмежувачів маршрутам

Обмежувачі частоти можна призначати маршрутам чи групам маршрутів за допомогою [`middleware`](/docs/{{version}}/middleware) `throttle`. Цей `middleware` приймає ім'я обмежувача, який ви хочете призначити маршруту:

```php
Route::middleware(['throttle:uploads'])->group(function () {
    Route::post('/audio', function () {
        // ...
    });

    Route::post('/video', function () {
        // ...
    });
});
```

<a name="throttling-with-redis"></a>
#### Обмеження частоти з Redis

За замовчуванням `middleware` `throttle` зіставлено з класом `Illuminate\Routing\Middleware\ThrottleRequests`. Однак якщо ви використовуєте Redis як драйвер кешу застосунку, ви можете захотіти вказати Laravel керувати обмеженням частоти через Redis. Для цього скористайтеся методом `throttleWithRedis` у файлі `bootstrap/app.php`. Він зіставляє `middleware` `throttle` із класом `Illuminate\Routing\Middleware\ThrottleRequestsWithRedis`:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->throttleWithRedis();
    // ...
})
```

<a name="form-method-spoofing"></a>
## Підміна методу форми

HTML-форми не підтримують дій `PUT`, `PATCH` чи `DELETE`. Тож, визначаючи маршрути `PUT`, `PATCH` чи `DELETE`, які викликаються з HTML-форми, вам потрібно додати до форми приховане поле `_method`. Значення, надіслане з полем `_method`, буде використано як метод HTTP-запиту:

```blade
<form action="/example" method="POST">
    <input type="hidden" name="_method" value="PUT">
    <input type="hidden" name="_token" value="{{ csrf_token() }}">
</form>
```

Для зручності ви можете скористатися [директивою Blade](/docs/{{version}}/blade) `@method`, щоб згенерувати поле `_method`:

```blade
<form action="/example" method="POST">
    @method('PUT')
    @csrf
</form>
```

<a name="accessing-the-current-route"></a>
## Доступ до поточного маршруту

Ви можете скористатися методами `current`, `currentRouteName` і `currentRouteAction` фасаду `Route`, щоб отримати інформацію про маршрут, який обробляє вхідний запит:

```php
use Illuminate\Support\Facades\Route;

$route = Route::current(); // Illuminate\Routing\Route
$name = Route::currentRouteName(); // string
$action = Route::currentRouteAction(); // string
```

Щоб переглянути всі доступні методи маршрутизатора та класів маршрутів, зверніться до документації API [базового класу фасаду Route](https://api.laravel.com/docs/{{version}}/Illuminate/Routing/Router.html) та [екземпляра Route](https://api.laravel.com/docs/{{version}}/Illuminate/Routing/Route.html).

<a name="cors"></a>
## Спільне використання ресурсів між джерелами (CORS)

Laravel може автоматично відповідати на HTTP-запити CORS `OPTIONS` значеннями, які ви налаштуєте. Запити `OPTIONS` автоматично обробляє [`middleware`](/docs/{{version}}/middleware) `HandleCors`, який автоматично входить до глобального стека вашого застосунку.

Іноді вам може знадобитися налаштувати значення конфігурації CORS. Це можна зробити, опублікувавши конфігураційний файл `cors` командою Artisan `config:publish`:

```shell
php artisan config:publish cors
```

Ця команда розмістить конфігураційний файл `cors.php` у каталозі `config` вашого застосунку.

> [!NOTE]
> Докладніше про CORS і заголовки CORS читайте у [веб-документації MDN щодо CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#The_HTTP_response_headers).

<a name="route-caching"></a>
## Кешування маршрутів

Розгортаючи застосунок у продакшені, скористайтеся кешем маршрутів Laravel. Використання кешу різко зменшить час, потрібний для реєстрації всіх маршрутів вашого застосунку. Щоб згенерувати кеш маршрутів, виконайте команду Artisan `route:cache`:

```shell
php artisan route:cache
```

Після виконання цієї команди закешований файл маршрутів завантажуватиметься під час кожного запиту. Пам'ятайте: якщо ви додасте нові маршрути, вам потрібно буде згенерувати новий кеш. Через це команду `route:cache` варто виконувати лише під час розгортання проєкту.

Щоб очистити кеш маршрутів, скористайтеся командою `route:clear`:

```shell
php artisan route:clear
```

---

Джерело: https://laravelukraine.com/docs/13.x/middleware

# Middleware

- [Вступ](#introduction)
- [Визначення middleware](#defining-middleware)
- [Реєстрація middleware](#registering-middleware)
    - [Глобальні middleware](#global-middleware)
    - [Призначення middleware маршрутам](#assigning-middleware-to-routes)
    - [Групи middleware](#middleware-groups)
    - [Псевдоніми middleware](#middleware-aliases)
    - [Впорядкування middleware](#sorting-middleware)
- [Параметри middleware](#middleware-parameters)
- [Завершувані middleware](#terminable-middleware)

<a name="introduction"></a>
## Вступ

`Middleware` дають зручний механізм для перевірки та фільтрації HTTP-запитів, що надходять до вашого застосунку. Наприклад, Laravel містить `middleware`, який перевіряє, чи автентифікований користувач вашого застосунку. Якщо ні - `middleware` перенаправить користувача на екран входу. Якщо ж користувач автентифікований, `middleware` дозволить запиту рухатися далі вглиб застосунку.

Додаткові `middleware` можна писати для найрізноманітніших завдань, окрім автентифікації. Наприклад, `middleware` логування може записувати всі вхідні запити до вашого застосунку. Laravel містить чимало `middleware`, зокрема для автентифікації та захисту від CSRF; утім, усі визначені вами `middleware` зазвичай розташовані в каталозі `app/Http/Middleware` вашого застосунку.

<a name="defining-middleware"></a>
## Визначення middleware

Щоб створити новий `middleware`, скористайтеся командою Artisan `make:middleware`:

```shell
php artisan make:middleware EnsureTokenIsValid
```

Ця команда помістить новий клас `EnsureTokenIsValid` до каталогу `app/Http/Middleware`. У цьому `middleware` ми дозволимо доступ до маршруту лише тоді, коли переданий вхідний параметр `token` збігається з визначеним значенням. Інакше ми перенаправимо користувачів назад на URI `/home`:

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class EnsureTokenIsValid
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        if ($request->input('token') !== 'my-secret-token') {
            return redirect('/home');
        }

        return $next($request);
    }
}
```

Як бачите, якщо переданий `token` не збігається з нашим секретним токеном, `middleware` поверне клієнту HTTP-перенаправлення; інакше запит буде передано далі вглиб застосунку. Щоб передати запит глибше (тобто дозволити `middleware` «пропустити» його), викличте колбек `$next` із `$request`.

Найкраще уявляти `middleware` як низку «шарів», крізь які HTTP-запити мають пройти, перш ніж потраплять до вашого застосунку. Кожен шар може перевірити запит і навіть цілком його відхилити.

> [!NOTE]
> Усі `middleware` розв'язуються через [сервіс-контейнер](/docs/{{version}}/container), тож ви можете вказати типи будь-яких потрібних залежностей у конструкторі `middleware`.

<a name="middleware-and-responses"></a>
#### Middleware та відповіді

Звісно, `middleware` може виконувати завдання до або після передавання запиту глибше в застосунок. Наприклад, наведений нижче `middleware` виконає певне завдання **перед** тим, як застосунок обробить запит:

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class BeforeMiddleware
{
    public function handle(Request $request, Closure $next): Response
    {
        // Perform action

        return $next($request);
    }
}
```

Натомість цей `middleware` виконає своє завдання **після** того, як застосунок обробить запит:

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class AfterMiddleware
{
    public function handle(Request $request, Closure $next): Response
    {
        $response = $next($request);

        // Perform action

        return $response;
    }
}
```

<a name="registering-middleware"></a>
## Реєстрація middleware

<a name="global-middleware"></a>
### Глобальні middleware

Якщо ви хочете, щоб `middleware` виконувався під час кожного HTTP-запиту до вашого застосунку, додайте його до глобального стека `middleware` у файлі `bootstrap/app.php`:

```php
use App\Http\Middleware\EnsureTokenIsValid;

->withMiddleware(function (Middleware $middleware): void {
     $middleware->append(EnsureTokenIsValid::class);
})
```

Об'єкт `$middleware`, переданий замиканню `withMiddleware`, є екземпляром `Illuminate\Foundation\Configuration\Middleware` і відповідає за керування `middleware`, призначеними маршрутам вашого застосунку. Метод `append` додає `middleware` у кінець списку глобальних. Якщо ви хочете додати `middleware` на початок списку, скористайтеся методом `prepend`.

<a name="manually-managing-laravels-default-global-middleware"></a>
#### Ручне керування типовими глобальними middleware Laravel

Якщо ви хочете керувати глобальним стеком `middleware` Laravel вручну, передайте типовий стек методу `use`. Далі ви можете коригувати його за потреби:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->use([
        \Illuminate\Http\Middleware\ValidatePathEncoding::class,
        \Illuminate\Foundation\Http\Middleware\InvokeDeferredCallbacks::class,
        // \Illuminate\Http\Middleware\TrustHosts::class,
        \Illuminate\Http\Middleware\TrustProxies::class,
        \Illuminate\Http\Middleware\HandleCors::class,
        \Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Http\Middleware\ValidatePostSize::class,
        \Illuminate\Foundation\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ]);
})
```

<a name="assigning-middleware-to-routes"></a>
### Призначення middleware маршрутам

Якщо ви хочете призначити `middleware` конкретним маршрутам, викличте метод `middleware` під час визначення маршруту:

```php
use App\Http\Middleware\EnsureTokenIsValid;

Route::get('/profile', function () {
    // ...
})->middleware(EnsureTokenIsValid::class);
```

Ви можете призначити маршруту кілька `middleware`, передавши методу `middleware` масив їхніх імен:

```php
Route::get('/', function () {
    // ...
})->middleware([First::class, Second::class]);
```

<a name="excluding-middleware"></a>
#### Виключення middleware

Призначаючи `middleware` групі маршрутів, ви подекуди можете захотіти не застосовувати його до окремого маршруту в межах цієї групи. Це робиться методом `withoutMiddleware`:

```php
use App\Http\Middleware\EnsureTokenIsValid;

Route::middleware([EnsureTokenIsValid::class])->group(function () {
    Route::get('/', function () {
        // ...
    });

    Route::get('/profile', function () {
        // ...
    })->withoutMiddleware([EnsureTokenIsValid::class]);
});
```

Ви також можете виключити певний набір `middleware` для цілої [групи](/docs/{{version}}/routing#route-groups) визначень маршрутів:

```php
use App\Http\Middleware\EnsureTokenIsValid;

Route::withoutMiddleware([EnsureTokenIsValid::class])->group(function () {
    Route::get('/profile', function () {
        // ...
    });
});
```

Метод `withoutMiddleware` може прибирати лише `middleware` маршрутів і не діє на [глобальні `middleware`](#global-middleware).

<a name="middleware-groups"></a>
### Групи middleware

Іноді вам може знадобитися згрупувати кілька `middleware` під одним ключем, щоб їх було зручніше призначати маршрутам. Це робиться методом `appendToGroup` у файлі `bootstrap/app.php` вашого застосунку:

```php
use App\Http\Middleware\First;
use App\Http\Middleware\Second;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->appendToGroup('group-name', [
        First::class,
        Second::class,
    ]);

    $middleware->prependToGroup('group-name', [
        First::class,
        Second::class,
    ]);
})
```

Групи `middleware` можна призначати маршрутам і діям контролерів тим самим синтаксисом, що й окремі `middleware`:

```php
Route::get('/', function () {
    // ...
})->middleware('group-name');

Route::middleware(['group-name'])->group(function () {
    // ...
});
```

<a name="laravels-default-middleware-groups"></a>
#### Типові групи middleware Laravel

Laravel містить наперед визначені групи `middleware` `web` та `api` із поширеними `middleware`, які ви можете захотіти застосувати до своїх веб- та API-маршрутів. Пам'ятайте: Laravel автоматично застосовує ці групи до відповідних файлів `routes/web.php` і `routes/api.php`:

<div class="overflow-auto">

| Група `middleware` `web`                                  |
| --------------------------------------------------------- |
| `Illuminate\Cookie\Middleware\EncryptCookies`             |
| `Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse` |
| `Illuminate\Session\Middleware\StartSession`              |
| `Illuminate\View\Middleware\ShareErrorsFromSession`       |
| `Illuminate\Foundation\Http\Middleware\PreventRequestForgery` |
| `Illuminate\Routing\Middleware\SubstituteBindings`        |

</div>

<div class="overflow-auto">

| Група `middleware` `api`                           |
| -------------------------------------------------- |
| `Illuminate\Routing\Middleware\SubstituteBindings` |

</div>

Якщо ви хочете додати `middleware` в кінець чи на початок цих груп, скористайтеся методами `web` та `api` у файлі `bootstrap/app.php`. Вони є зручною альтернативою методу `appendToGroup`:

```php
use App\Http\Middleware\EnsureTokenIsValid;
use App\Http\Middleware\EnsureUserIsSubscribed;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->web(append: [
        EnsureUserIsSubscribed::class,
    ]);

    $middleware->api(prepend: [
        EnsureTokenIsValid::class,
    ]);
})
```

Ви можете навіть замінити один із записів типової групи `middleware` Laravel власним:

```php
use App\Http\Middleware\StartCustomSession;
use Illuminate\Session\Middleware\StartSession;

$middleware->web(replace: [
    StartSession::class => StartCustomSession::class,
]);
```

Або ж цілком прибрати `middleware`:

```php
$middleware->web(remove: [
    StartSession::class,
]);
```

<a name="manually-managing-laravels-default-middleware-groups"></a>
#### Ручне керування типовими групами middleware Laravel

Якщо ви хочете вручну керувати всіма `middleware` у типових групах `web` та `api`, ви можете повністю перевизначити ці групи. Приклад нижче визначає групи `web` та `api` з їхніми типовими `middleware`, дозволяючи налаштувати їх за потреби:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->group('web', [
        \Illuminate\Cookie\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \Illuminate\Foundation\Http\Middleware\PreventRequestForgery::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        // \Illuminate\Session\Middleware\AuthenticateSession::class,
    ]);

    $middleware->group('api', [
        // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
        // 'throttle:api',
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ]);
})
```

> [!NOTE]
> За замовчуванням групи `middleware` `web` та `api` автоматично застосовуються до відповідних файлів `routes/web.php` і `routes/api.php` вашого застосунку через файл `bootstrap/app.php`.

<a name="middleware-aliases"></a>
### Псевдоніми middleware

Ви можете призначати `middleware` псевдоніми у файлі `bootstrap/app.php` вашого застосунку. Псевдоніми дозволяють визначити коротку назву для класу `middleware`, що особливо корисно для `middleware` з довгими іменами класів:

```php
use App\Http\Middleware\EnsureUserIsSubscribed;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->alias([
        'subscribed' => EnsureUserIsSubscribed::class
    ]);
})
```

Щойно псевдонім визначено у файлі `bootstrap/app.php`, ви можете використовувати його, призначаючи `middleware` маршрутам:

```php
Route::get('/profile', function () {
    // ...
})->middleware('subscribed');
```

Для зручності деякі вбудовані `middleware` Laravel мають псевдоніми за замовчуванням. Наприклад, `auth` - це псевдонім для `middleware` `Illuminate\Auth\Middleware\Authenticate`. Нижче наведено список типових псевдонімів:

<div class="overflow-auto">

| Псевдонім          | Middleware                                                                                                    |
| ------------------ | ------------------------------------------------------------------------------------------------------------- |
| `auth`             | `Illuminate\Auth\Middleware\Authenticate`                                                                     |
| `auth.basic`       | `Illuminate\Auth\Middleware\AuthenticateWithBasicAuth`                                                        |
| `auth.session`     | `Illuminate\Session\Middleware\AuthenticateSession`                                                           |
| `cache.headers`    | `Illuminate\Http\Middleware\SetCacheHeaders`                                                                  |
| `can`              | `Illuminate\Auth\Middleware\Authorize`                                                                        |
| `guest`            | `Illuminate\Auth\Middleware\RedirectIfAuthenticated`                                                          |
| `password.confirm` | `Illuminate\Auth\Middleware\RequirePassword`                                                                  |
| `precognitive`     | `Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests`                                            |
| `signed`           | `Illuminate\Routing\Middleware\ValidateSignature`                                                             |
| `subscribed`       | `\Spark\Http\Middleware\VerifyBillableIsSubscribed`                                                           |
| `throttle`         | `Illuminate\Routing\Middleware\ThrottleRequests` or `Illuminate\Routing\Middleware\ThrottleRequestsWithRedis` |
| `verified`         | `Illuminate\Auth\Middleware\EnsureEmailIsVerified`                                                            |

</div>

<a name="sorting-middleware"></a>
### Впорядкування middleware

Зрідка вам може знадобитися, щоб ваші `middleware` виконувалися в певному порядку, але ви не маєте контролю над їхнім порядком під час призначення маршруту. У таких випадках ви можете задати пріоритет `middleware` методом `priority` у файлі `bootstrap/app.php` вашого застосунку:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->priority([
        \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
        \Illuminate\Cookie\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \Illuminate\Foundation\Http\Middleware\PreventRequestForgery::class,
        \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
        \Illuminate\Routing\Middleware\ThrottleRequests::class,
        \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
        \Illuminate\Auth\Middleware\Authorize::class,
    ]);
})
```

Якщо ви хочете додати `middleware` до наявного списку пріоритетів без його заміни, можете використати методи `prependToPriorityList` або `appendToPriorityList`. Метод `prependToPriorityList` вставляє заданий `middleware` перед іншим `middleware`, а `appendToPriorityList` вставляє його після:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->prependToPriorityList(
        before: \Illuminate\Routing\Middleware\SubstituteBindings::class,
        prepend: \App\Http\Middleware\EnsureTokenIsValid::class,
    );

    $middleware->appendToPriorityList(
        after: \Illuminate\Routing\Middleware\SubstituteBindings::class,
        append: \App\Http\Middleware\EnsureUserIsSubscribed::class,
    );
})
```

Аргументи `before` і `after` також можуть бути масивом класів `middleware`.

<a name="middleware-parameters"></a>
## Параметри middleware

`Middleware` можуть також отримувати додаткові параметри. Наприклад, якщо вашому застосунку потрібно перевірити, чи має автентифікований користувач певну «роль», перш ніж виконати дію, ви можете створити `middleware` `EnsureUserHasRole`, який отримує ім'я ролі як додатковий аргумент.

Додаткові параметри передаються `middleware` після аргументу `$next`:

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class EnsureUserHasRole
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next, string $role): Response
    {
        if (! $request->user()->hasRole($role)) {
            // Redirect...
        }

        return $next($request);
    }
}
```

Параметри `middleware` можна вказати під час визначення маршруту, відокремивши ім'я `middleware` від параметрів двокрапкою `:`:

```php
use App\Http\Middleware\EnsureUserHasRole;

Route::put('/post/{id}', function (string $id) {
    // ...
})->middleware(EnsureUserHasRole::class.':editor');
```

Кілька параметрів розділяються комами:

```php
Route::put('/post/{id}', function (string $id) {
    // ...
})->middleware(EnsureUserHasRole::class.':editor,publisher');
```

<a name="terminable-middleware"></a>
## Завершувані middleware

Іноді `middleware` може знадобитися виконати певну роботу після того, як HTTP-відповідь надіслано браузеру. Якщо ви визначите у своєму `middleware` метод `terminate`, а ваш веб-сервер використовує [FastCGI](https://www.php.net/manual/en/install.fpm.php), метод `terminate` буде автоматично викликано після надсилання відповіді браузеру:

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class TerminatingMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        return $next($request);
    }

    /**
     * Handle tasks after the response has been sent to the browser.
     */
    public function terminate(Request $request, Response $response): void
    {
        // ...
    }
}
```

Метод `terminate` має отримувати і запит, і відповідь. Визначивши завершуваний `middleware`, додайте його до списку маршрутних чи глобальних `middleware` у файлі `bootstrap/app.php` вашого застосунку.

Викликаючи метод `terminate` вашого `middleware`, Laravel розв'яже з [сервіс-контейнера](/docs/{{version}}/container) новий екземпляр `middleware`. Якщо ви хочете використовувати той самий екземпляр під час викликів методів `handle` і `terminate`, зареєструйте `middleware` в контейнері методом `singleton`. Зазвичай це варто робити в методі `register` вашого `AppServiceProvider`:

```php
use App\Http\Middleware\TerminatingMiddleware;

/**
 * Register any application services.
 */
public function register(): void
{
    $this->app->singleton(TerminatingMiddleware::class);
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/csrf

# Захист від CSRF

- [Вступ](#csrf-introduction)
- [Запобігання CSRF-запитам](#preventing-csrf-requests)
    - [Перевірка джерела](#origin-verification)
    - [Виключення URI](#csrf-excluding-uris)
- [X-CSRF-Token](#csrf-x-csrf-token)
- [X-XSRF-Token](#csrf-x-xsrf-token)

<a name="csrf-introduction"></a>
## Вступ

Міжсайтова підробка запитів - це різновид зловмисної атаки, за якої від імені автентифікованого користувача виконуються несанкціоновані команди. На щастя, Laravel спрощує захист вашого застосунку від атак [міжсайтової підробки запитів](https://en.wikipedia.org/wiki/Cross-site_request_forgery) (CSRF).

<a name="csrf-explanation"></a>
#### Пояснення вразливості

Якщо ви не знайомі з міжсайтовою підробкою запитів, розгляньмо приклад того, як цю вразливість можна використати. Уявіть, що ваш застосунок має маршрут `/user/email`, який приймає `POST`-запит для зміни адреси електронної пошти автентифікованого користувача. Найімовірніше, цей маршрут очікує поле `email` із адресою, яку користувач хоче почати використовувати.

Без захисту від CSRF зловмисний сайт міг би створити HTML-форму, що вказує на маршрут `/user/email` вашого застосунку й надсилає власну адресу зловмисника:

```blade
<form action="https://your-application.com/user/email" method="POST">
    <input type="email" value="malicious-email@example.com">
</form>

<script>
    document.forms[0].submit();
</script>
```

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

Щоб запобігти цій вразливості, нам потрібно перевіряти кожен вхідний запит `POST`, `PUT`, `PATCH`, `DELETE` чи `QUERY` на наявність секретного значення сесії, доступу до якого зловмисний застосунок не має.

<a name="preventing-csrf-requests"></a>
## Запобігання CSRF-запитам

[`Middleware`](/docs/{{version}}/middleware) `Illuminate\Foundation\Http\Middleware\PreventRequestForgery`, який за замовчуванням входить до групи `web`, захищає ваш застосунок від міжсайтової підробки запитів двошаровим підходом.

Спершу `middleware` перевіряє заголовок браузера `Sec-Fetch-Site`. Сучасні браузери автоматично встановлюють цей заголовок для кожного запиту, вказуючи, чи походить він із того самого джерела (origin), того самого сайту, чи з міжсайтового джерела. Якщо заголовок свідчить, що запит надійшов із того самого джерела, його одразу пропускають без жодної перевірки токена.

Якщо перевірка джерела не проходить - наприклад, тому що запит надходить зі старішого браузера, який не надсилає заголовок `Sec-Fetch-Site`, або тому що з'єднання не є захищеним, - `middleware` повертається до традиційної перевірки CSRF-токена.

Laravel автоматично генерує CSRF-«токен» для кожної активної [сесії користувача](/docs/{{version}}/session), якою керує застосунок. Цей токен використовується, щоб перевірити, що запити до застосунку робить саме автентифікований користувач. Оскільки токен зберігається в сесії користувача й змінюється щоразу, коли сесію регенеровано, зловмисний застосунок не може отримати до нього доступ.

Доступ до CSRF-токена поточної сесії можна отримати через сесію запиту або через функцію-хелпер `csrf_token`:

```php
use Illuminate\Http\Request;

Route::get('/token', function (Request $request) {
    $token = $request->session()->token();

    $token = csrf_token();

    // ...
});
```

Щоразу, визначаючи у своєму застосунку HTML-форму з методом «POST», «PUT», «PATCH» чи «DELETE», додавайте до неї приховане поле CSRF `_token`, щоб `middleware` захисту від CSRF міг перевірити запит. Для зручності ви можете скористатися директивою Blade `@csrf`, яка згенерує приховане поле з токеном:

```blade
<form method="POST" action="/profile">
    @csrf

    <!-- Equivalent to... -->
    <input type="hidden" name="_token" value="{{ csrf_token() }}" />
</form>
```

<a name="csrf-tokens-and-spas"></a>
#### CSRF-токени та SPA

Якщо ви створюєте SPA, що використовує Laravel як бекенд для API, зверніться до [документації Laravel Sanctum](/docs/{{version}}/sanctum) по інформацію про автентифікацію з вашим API та захист від CSRF-вразливостей.

<a name="origin-verification"></a>
### Перевірка джерела

Як зазначено вище, `middleware` захисту від підробки запитів спершу перевіряє заголовок `Sec-Fetch-Site`, щоб визначити, чи надійшов запит із того самого джерела. За замовчуванням, якщо ця перевірка не проходить, `middleware` повертається до перевірки CSRF-токена.

Однак якщо ви хочете покладатися виключно на перевірку джерела й повністю вимкнути запасну перевірку токена, скористайтеся методом `preventRequestForgery` у файлі `bootstrap/app.php` вашого застосунку:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->preventRequestForgery(originOnly: true);
})
```

У режимі перевірки лише за джерелом запити, які не пройшли цю перевірку, отримають HTTP-відповідь `403` замість відповіді `419`, яку зазвичай пов'язують із розбіжністю CSRF-токенів.

> [!WARNING]
> Заголовок `Sec-Fetch-Site` браузери надсилають лише через захищені (HTTPS) з'єднання. Якщо ваш застосунок працює не через HTTPS, перевірка джерела буде недоступна, і `middleware` повернеться до перевірки CSRF-токена.

Якщо ваш застосунок має приймати запити з піддоменів (наприклад, `dashboard.example.com` приймає запити від `example.com`), ви можете дозволити запити з того самого сайту на додачу до запитів із того самого джерела:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->preventRequestForgery(allowSameSite: true);
})
```

<a name="csrf-excluding-uris"></a>
### Виключення URI із захисту від CSRF

Іноді вам може знадобитися виключити набір URI із захисту від CSRF. Наприклад, якщо ви використовуєте [Stripe](https://stripe.com) для обробки платежів і користуєтеся їхньою системою вебхуків, вам доведеться виключити маршрут обробника вебхуків Stripe із захисту від CSRF, адже Stripe не знатиме, який CSRF-токен надсилати до ваших маршрутів.

Зазвичай такі маршрути варто розміщувати поза групою `middleware` `web`, яку Laravel застосовує до всіх маршрутів у файлі `routes/web.php`. Утім, ви також можете виключити конкретні маршрути, передавши їхні URI методу `preventRequestForgery` у файлі `bootstrap/app.php` вашого застосунку:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->preventRequestForgery(except: [
        'stripe/*',
        'http://example.com/foo/bar',
        'http://example.com/foo/*',
    ]);
})
```

> [!NOTE]
> Для зручності CSRF-`middleware` автоматично вимикається для всіх маршрутів під час [запуску тестів](/docs/{{version}}/testing).

<a name="csrf-x-csrf-token"></a>
## X-CSRF-TOKEN

Крім перевірки CSRF-токена як POST-параметра, `middleware` `PreventRequestForgery` також перевіряє заголовок запиту `X-CSRF-TOKEN`. Ви можете, наприклад, зберігати токен у HTML-тегу `meta`:

```blade
<meta name="csrf-token" content="{{ csrf_token() }}">
```

Далі ви можете вказати бібліотеці на кшталт jQuery автоматично додавати токен до всіх заголовків запитів. Це дає простий і зручний захист від CSRF для ваших AJAX-застосунків, що використовують застарілі JavaScript-технології:

```js
$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});
```

<a name="csrf-x-xsrf-token"></a>
## X-XSRF-TOKEN

Laravel зберігає поточний CSRF-токен у зашифрованій cookie `XSRF-TOKEN`, яка додається до кожної відповіді, згенерованої фреймворком. Ви можете використати значення цієї cookie, щоб задати заголовок запиту `X-XSRF-TOKEN`.

Цю cookie надсилають насамперед задля зручності розробників, оскільки деякі JavaScript-фреймворки та бібліотеки - як-от Angular і Axios - автоматично підставляють її значення в заголовок `X-XSRF-TOKEN` для запитів із того самого джерела.

---

Джерело: https://laravelukraine.com/docs/13.x/controllers

# Контролери

- [Вступ](#introduction)
- [Написання контролерів](#writing-controllers)
    - [Базові контролери](#basic-controllers)
    - [Контролери однієї дії](#single-action-controllers)
- [Middleware контролерів](#controller-middleware)
    - [Атрибути middleware](#middleware-attributes)
    - [Атрибути авторизації](#authorization-attributes)
- [Ресурсні контролери](#resource-controllers)
    - [Часткові ресурсні маршрути](#restful-partial-resource-routes)
    - [Вкладені ресурси](#restful-nested-resources)
    - [Іменування ресурсних маршрутів](#restful-naming-resource-routes)
    - [Іменування параметрів ресурсних маршрутів](#restful-naming-resource-route-parameters)
    - [Обмеження області ресурсних маршрутів](#restful-scoping-resource-routes)
    - [Локалізація URI ресурсів](#restful-localizing-resource-uris)
    - [Доповнення ресурсних контролерів](#restful-supplementing-resource-controllers)
    - [Синглтон-ресурсні контролери](#singleton-resource-controllers)
    - [Middleware та ресурсні контролери](#middleware-and-resource-controllers)
- [Впровадження залежностей і контролери](#dependency-injection-and-controllers)

<a name="introduction"></a>
## Вступ

Замість визначати всю логіку обробки запитів замиканнями у файлах маршрутів, ви можете організувати цю поведінку класами-«контролерами». Контролери групують пов'язану логіку обробки запитів в одному класі. Наприклад, клас `UserController` може обробляти всі вхідні запити, пов'язані з користувачами: показ, створення, оновлення та видалення. За замовчуванням контролери зберігаються в каталозі `app/Http/Controllers`.

<a name="writing-controllers"></a>
## Написання контролерів

<a name="basic-controllers"></a>
### Базові контролери

Щоб швидко згенерувати новий контролер, виконайте команду Artisan `make:controller`. За замовчуванням усі контролери вашого застосунку зберігаються в каталозі `app/Http/Controllers`:

```shell
php artisan make:controller UserController
```

Погляньмо на приклад базового контролера. Контролер може мати будь-яку кількість публічних методів, які відповідатимуть на вхідні HTTP-запити:

```php
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * Show the profile for a given user.
     */
    public function show(string $id): View
    {
        return view('user.profile', [
            'user' => User::findOrFail($id)
        ]);
    }
}
```

Написавши клас контролера та метод, ви можете визначити маршрут до методу контролера так:

```php
use App\Http\Controllers\UserController;

Route::get('/user/{id}', [UserController::class, 'show']);
```

Коли вхідний запит збігається з указаним URI маршруту, буде викликано метод `show` класу `App\Http\Controllers\UserController`, а параметри маршруту буде передано методу.

> [!NOTE]
> Контролери **не зобов'язані** успадковувати базовий клас. Утім, іноді зручно успадкувати базовий контролер із методами, спільними для всіх ваших контролерів.

<a name="single-action-controllers"></a>
### Контролери однієї дії

Якщо дія контролера особливо складна, вам може бути зручно виділити для неї цілий клас контролера. Для цього визначте в контролері єдиний метод `__invoke`:

```php
<?php

namespace App\Http\Controllers;

class ProvisionServer extends Controller
{
    /**
     * Provision a new web server.
     */
    public function __invoke()
    {
        // ...
    }
}
```

Реєструючи маршрути для контролерів однієї дії, вам не потрібно вказувати метод контролера. Натомість просто передайте маршрутизатору ім'я контролера:

```php
use App\Http\Controllers\ProvisionServer;

Route::post('/server', ProvisionServer::class);
```

Ви можете згенерувати викликаний контролер за допомогою опції `--invokable` команди Artisan `make:controller`:

```shell
php artisan make:controller ProvisionServer --invokable
```

> [!NOTE]
> Заготовки контролерів можна налаштувати через [публікацію заготовок](/docs/{{version}}/artisan#stub-customization).

<a name="controller-middleware"></a>
## Middleware контролерів

[`Middleware`](/docs/{{version}}/middleware) можна призначати маршрутам контролера у файлах маршрутів:

```php
Route::get('/profile', [UserController::class, 'show'])->middleware('auth');
```

Або ж вам може бути зручно вказати `middleware` у самому класі контролера. Для цього ваш контролер має реалізувати інтерфейс `HasMiddleware`, який вимагає наявності статичного методу `middleware`. З цього методу ви можете повернути масив `middleware`, які слід застосувати до дій контролера:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;

class UserController implements HasMiddleware
{
    /**
     * Get the middleware that should be assigned to the controller.
     */
    public static function middleware(): array
    {
        return [
            'auth',
            new Middleware('log', only: ['index']),
            new Middleware('subscribed', except: ['store']),
        ];
    }

    // ...
}
```

Ви також можете визначати `middleware` контролера як замикання - це зручний спосіб задати вбудований `middleware`, не пишучи цілого класу:

```php
use Closure;
use Illuminate\Http\Request;

/**
 * Get the middleware that should be assigned to the controller.
 */
public static function middleware(): array
{
    return [
        function (Request $request, Closure $next) {
            return $next($request);
        },
    ];
}
```

<a name="middleware-attributes"></a>
### Атрибути middleware

Ви також можете призначати `middleware` контролерам за допомогою атрибутів PHP:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Routing\Attributes\Controllers\Middleware;

#[Middleware('auth')]
#[Middleware('log', only: ['index'])]
#[Middleware('subscribed', except: ['store'])]
class UserController
{
    // ...
}
```

Атрибути `middleware` можна розміщувати й на окремих методах контролера. `Middleware`, призначені методам, буде об'єднано з тими, що призначені на рівні класу:

```php
<?php

namespace App\Http\Controllers;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Routing\Attributes\Controllers\Middleware;

#[Middleware('auth')]
class UserController
{
    #[Middleware('log')]
    #[Middleware('subscribed')]
    public function index()
    {
        // ...
    }

    #[Middleware(static function (Request $request, Closure $next) {
        // ...

        return $next($request);
    })]
    public function store()
    {
        // ...
    }
}
```

Щоб виключити `middleware` з контролера чи окремих його методів, скористайтеся атрибутом `WithoutMiddleware`. Аргументи `only` та `except` дозволяють обмежити атрибут рівня класу конкретними методами:

```php
<?php

namespace App\Http\Controllers;

use App\Http\Middleware\EnsureTokenIsValid;
use Illuminate\Routing\Attributes\Controllers\WithoutMiddleware;

#[WithoutMiddleware('subscribed', except: ['index'])]
class UserController
{
    #[WithoutMiddleware(EnsureTokenIsValid::class)]
    public function index()
    {
        // ...
    }

    public function show()
    {
        // ...
    }
}
```

Атрибути `WithoutMiddleware` рівня класу успадковуються дочірніми контролерами. Атрибут може прибирати лише `middleware` маршрутів і не діє на [глобальні `middleware`](/docs/{{version}}/middleware#global-middleware).

<a name="authorization-attributes"></a>
### Атрибути авторизації

Якщо ви авторизуєте дії контролера через політики, скористайтеся атрибутом `Authorize` як зручним скороченням для `middleware` `can`:

```php
<?php

namespace App\Http\Controllers;

use App\Models\Comment;
use App\Models\Post;
use Illuminate\Routing\Attributes\Controllers\Authorize;

class CommentController
{
    #[Authorize('create', [Comment::class, 'post'])]
    public function store(Post $post)
    {
        // ...
    }

    #[Authorize('delete', 'comment')]
    public function destroy(Comment $comment)
    {
        // ...
    }
}
```

Перший аргумент - здатність (ability), яку ви хочете авторизувати. Другий - клас моделі, параметр маршруту чи параметри, які слід передати політиці.

<a name="resource-controllers"></a>
## Ресурсні контролери

Якщо уявляти кожну модель Eloquent у вашому застосунку як «ресурс», то типово над кожним ресурсом виконуються одні й ті самі набори дій. Наприклад, уявіть, що ваш застосунок містить моделі `Photo` і `Movie`. Найімовірніше, користувачі можуть створювати, читати, оновлювати чи видаляти ці ресурси.

Через цей поширений сценарій ресурсна маршрутизація Laravel призначає контролеру типові маршрути створення, читання, оновлення та видалення («CRUD») одним рядком коду. Щоб почати, скористаймося опцією `--resource` команди Artisan `make:controller`, аби швидко створити контролер для цих дій:

```shell
php artisan make:controller PhotoController --resource
```

Ця команда згенерує контролер за шляхом `app/Http/Controllers/PhotoController.php`. Контролер міститиме метод для кожної доступної операції над ресурсом. Далі ви можете зареєструвати ресурсний маршрут, що вказує на контролер:

```php
use App\Http\Controllers\PhotoController;

Route::resource('photos', PhotoController::class);
```

Це єдине оголошення маршруту створює кілька маршрутів для різних дій над ресурсом. Згенерований контролер уже матиме заготовки методів для кожної з цих дій. Пам'ятайте: ви завжди можете швидко переглянути маршрути свого застосунку командою Artisan `route:list`.

Ви навіть можете зареєструвати багато ресурсних контролерів одразу, передавши масив методу `resources`:

```php
Route::resources([
    'photos' => PhotoController::class,
    'posts' => PostController::class,
]);
```

Метод `softDeletableResources` реєструє багато ресурсних контролерів, які всі використовують метод `withTrashed`:

```php
Route::softDeletableResources([
    'photos' => PhotoController::class,
    'posts' => PostController::class,
]);
```

<a name="actions-handled-by-resource-controllers"></a>
#### Дії, які обробляють ресурсні контролери

<div class="overflow-auto">

| Метод     | URI                    | Дія     | Ім'я маршруту  |
| --------- | ---------------------- | ------- | -------------- |
| GET       | `/photos`              | index   | photos.index   |
| GET       | `/photos/create`       | create  | photos.create  |
| POST      | `/photos`              | store   | photos.store   |
| GET       | `/photos/{photo}`      | show    | photos.show    |
| GET       | `/photos/{photo}/edit` | edit    | photos.edit    |
| PUT/PATCH | `/photos/{photo}`      | update  | photos.update  |
| DELETE    | `/photos/{photo}`      | destroy | photos.destroy |

</div>

<a name="customizing-missing-model-behavior"></a>
#### Налаштування поведінки для відсутньої моделі

Зазвичай, якщо неявно прив'язану модель ресурсу не знайдено, генерується HTTP-відповідь 404. Утім, ви можете налаштувати цю поведінку, викликавши метод `missing` під час визначення ресурсного маршруту. Метод `missing` приймає замикання, яке буде викликано, якщо неявно прив'язану модель не вдалося знайти для будь-якого з маршрутів ресурсу:

```php
use App\Http\Controllers\PhotoController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;

Route::resource('photos', PhotoController::class)
    ->missing(function (Request $request) {
        return Redirect::route('photos.index');
    });
```

<a name="soft-deleted-models"></a>
#### М'яко видалені моделі

Зазвичай неявна прив'язка моделей не отримує [м'яко видалені](/docs/{{version}}/eloquent#soft-deleting) моделі, а натомість повертає HTTP-відповідь 404. Однак ви можете вказати фреймворку дозволити м'яко видалені моделі, викликавши метод `withTrashed` під час визначення ресурсного маршруту:

```php
use App\Http\Controllers\PhotoController;

Route::resource('photos', PhotoController::class)->withTrashed();
```

Виклик `withTrashed` без аргументів дозволить м'яко видалені моделі для ресурсних маршрутів `show`, `edit` та `update`. Ви можете вказати підмножину цих маршрутів, передавши масив методу `withTrashed`:

```php
Route::resource('photos', PhotoController::class)->withTrashed(['show']);
```

<a name="specifying-the-resource-model"></a>
#### Вказання моделі ресурсу

Якщо ви використовуєте [прив'язку моделей до маршрутів](/docs/{{version}}/routing#route-model-binding) і хочете, щоб методи ресурсного контролера вказували тип екземпляра моделі, скористайтеся опцією `--model` під час генерації контролера:

```shell
php artisan make:controller PhotoController --model=Photo --resource
```

<a name="generating-form-requests"></a>
#### Генерація запитів форм

Ви можете передати опцію `--requests` під час генерації ресурсного контролера, щоб Artisan згенерував [класи запитів форм](/docs/{{version}}/validation#form-request-validation) для методів збереження та оновлення:

```shell
php artisan make:controller PhotoController --model=Photo --resource --requests
```

<a name="restful-partial-resource-routes"></a>
### Часткові ресурсні маршрути

Оголошуючи ресурсний маршрут, ви можете вказати підмножину дій, які має обробляти контролер, замість повного набору типових дій:

```php
use App\Http\Controllers\PhotoController;

Route::resource('photos', PhotoController::class)->only([
    'index', 'show'
]);

Route::resource('photos', PhotoController::class)->except([
    'create', 'store', 'update', 'destroy'
]);
```

<a name="api-resource-routes"></a>
#### Ресурсні маршрути API

Оголошуючи ресурсні маршрути, які споживатимуть API, ви зазвичай захочете виключити маршрути, що показують HTML-шаблони, - як-от `create` та `edit`. Для зручності скористайтеся методом `apiResource`, який автоматично виключає ці два маршрути:

```php
use App\Http\Controllers\PhotoController;

Route::apiResource('photos', PhotoController::class);
```

Ви можете зареєструвати багато ресурсних контролерів API одразу, передавши масив методу `apiResources`:

```php
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\PostController;

Route::apiResources([
    'photos' => PhotoController::class,
    'posts' => PostController::class,
]);
```

Щоб швидко згенерувати ресурсний контролер API без методів `create` та `edit`, скористайтеся перемикачем `--api` під час виконання команди `make:controller`:

```shell
php artisan make:controller PhotoController --api
```

<a name="restful-nested-resources"></a>
### Вкладені ресурси

Іноді вам може знадобитися визначити маршрути до вкладеного ресурсу. Наприклад, ресурс фотографії може мати кілька коментарів, прикріплених до неї. Щоб вкласти ресурсні контролери, скористайтеся «крапковою» нотацією в оголошенні маршруту:

```php
use App\Http\Controllers\PhotoCommentController;

Route::resource('photos.comments', PhotoCommentController::class);
```

Цей маршрут зареєструє вкладений ресурс, доступний за URI на кшталт такого:

```text
/photos/{photo}/comments/{comment}
```

<a name="scoping-nested-resources"></a>
#### Обмеження області вкладених ресурсів

Можливість [неявної прив'язки моделей](/docs/{{version}}/routing#implicit-model-binding-scoping) Laravel може автоматично обмежувати область вкладених прив'язок так, щоб розв'язана дочірня модель гарантовано належала батьківській. Скориставшись методом `scoped` під час визначення вкладеного ресурсу, ви можете увімкнути автоматичне обмеження області, а також вказати Laravel, за яким полем слід отримувати дочірній ресурс. Докладніше про це дивіться в документації з [обмеження області ресурсних маршрутів](#restful-scoping-resource-routes).

<a name="shallow-nesting"></a>
#### Поверхневе вкладення

Часто мати в URI одночасно ідентифікатори батька й дитини зовсім не обов'язково, адже ідентифікатор дитини вже унікальний. Використовуючи унікальні ідентифікатори на кшталт автоінкрементних первинних ключів для позначення моделей у сегментах URI, ви можете обрати «поверхневе вкладення»:

```php
use App\Http\Controllers\CommentController;

Route::resource('photos.comments', CommentController::class)->shallow();
```

Це визначення маршруту створить такі маршрути:

<div class="overflow-auto">

| Метод     | URI                               | Дія     | Ім'я маршруту          |
| --------- | --------------------------------- | ------- | ---------------------- |
| GET       | `/photos/{photo}/comments`        | index   | photos.comments.index  |
| GET       | `/photos/{photo}/comments/create` | create  | photos.comments.create |
| POST      | `/photos/{photo}/comments`        | store   | photos.comments.store  |
| GET       | `/comments/{comment}`             | show    | comments.show          |
| GET       | `/comments/{comment}/edit`        | edit    | comments.edit          |
| PUT/PATCH | `/comments/{comment}`             | update  | comments.update        |
| DELETE    | `/comments/{comment}`             | destroy | comments.destroy       |

</div>

<a name="restful-naming-resource-routes"></a>
### Іменування ресурсних маршрутів

За замовчуванням усі дії ресурсного контролера мають ім'я маршруту; утім, ви можете перевизначити ці імена, передавши масив `names` із бажаними іменами:

```php
use App\Http\Controllers\PhotoController;

Route::resource('photos', PhotoController::class)->names([
    'create' => 'photos.build'
]);
```

<a name="restful-naming-resource-route-parameters"></a>
### Іменування параметрів ресурсних маршрутів

За замовчуванням `Route::resource` створює параметри ваших ресурсних маршрутів на основі форми однини від імені ресурсу. Ви можете легко перевизначити це для кожного ресурсу окремо методом `parameters`. Масив, переданий методу `parameters`, має бути асоціативним масивом імен ресурсів та імен параметрів:

```php
use App\Http\Controllers\AdminUserController;

Route::resource('users', AdminUserController::class)->parameters([
    'users' => 'admin_user'
]);
```

Наведений вище приклад генерує такий URI для маршруту `show` цього ресурсу:

```text
/users/{admin_user}
```

<a name="restful-scoping-resource-routes"></a>
### Обмеження області ресурсних маршрутів

Можливість [неявної прив'язки моделей з обмеженням області](/docs/{{version}}/routing#implicit-model-binding-scoping) Laravel може автоматично обмежувати область вкладених прив'язок так, щоб розв'язана дочірня модель гарантовано належала батьківській. Скориставшись методом `scoped` під час визначення вкладеного ресурсу, ви можете увімкнути автоматичне обмеження області, а також вказати Laravel, за яким полем слід отримувати дочірній ресурс:

```php
use App\Http\Controllers\PhotoCommentController;

Route::resource('photos.comments', PhotoCommentController::class)->scoped([
    'comment' => 'slug',
]);
```

Цей маршрут зареєструє вкладений ресурс з обмеженою областю, доступний за URI на кшталт такого:

```text
/photos/{photo}/comments/{comment:slug}
```

Коли ви використовуєте неявну прив'язку з власним ключем як параметр вкладеного маршруту, Laravel автоматично обмежить запит так, щоб отримати вкладену модель через її батька, використовуючи домовленості для вгадування імені зв'язку на батьківській моделі. У цьому випадку припускатиметься, що модель `Photo` має зв'язок `comments` (множина від імені параметра маршруту), через який можна отримати модель `Comment`.

<a name="restful-localizing-resource-uris"></a>
### Локалізація URI ресурсів

За замовчуванням `Route::resource` створює URI ресурсів, використовуючи англійські дієслова та правила множини. Якщо вам потрібно локалізувати дієслова дій `create` та `edit`, скористайтеся методом `Route::resourceVerbs`. Це можна зробити на початку методу `boot` у `App\Providers\AppServiceProvider` вашого застосунку:

```php
/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Route::resourceVerbs([
        'create' => 'crear',
        'edit' => 'editar',
    ]);
}
```

Плюралізатор Laravel підтримує [кілька різних мов, які ви можете налаштувати за потреби](/docs/{{version}}/localization#pluralization-language). Щойно дієслова та мову множини налаштовано, реєстрація ресурсного маршруту на кшталт `Route::resource('publicacion', PublicacionController::class)` створить такі URI:

```text
/publicacion/crear

/publicacion/{publicacion}/editar
```

<a name="restful-supplementing-resource-controllers"></a>
### Доповнення ресурсних контролерів

Якщо вам потрібно додати до ресурсного контролера маршрути поза типовим набором ресурсних маршрутів, визначайте їх **до** виклику методу `Route::resource`; інакше маршрути, визначені методом `resource`, можуть ненавмисно взяти гору над вашими додатковими маршрутами:

```php
use App\Http\Controllers\PhotoController;

Route::get('/photos/popular', [PhotoController::class, 'popular']);
Route::resource('photos', PhotoController::class);
```

> [!NOTE]
> Пам'ятайте: тримайте свої контролери зосередженими. Якщо ви регулярно потребуєте методів поза типовим набором ресурсних дій, подумайте про поділ контролера на два менші.

<a name="singleton-resource-controllers"></a>
### Синглтон-ресурсні контролери

Іноді ваш застосунок матиме ресурси, які можуть існувати лише в одному екземплярі. Наприклад, «профіль» користувача можна редагувати чи оновлювати, але користувач не може мати більш ніж один «профіль». Так само зображення може мати одну «мініатюру». Такі ресурси називають «синглтон-ресурсами»: існує один і тільки один екземпляр ресурсу. У цих сценаріях ви можете зареєструвати «синглтон»-ресурсний контролер:

```php
use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;

Route::singleton('profile', ProfileController::class);
```

Наведене вище визначення синглтон-ресурсу зареєструє такі маршрути. Як бачите, маршрути «створення» для синглтон-ресурсів не реєструються, а зареєстровані маршрути не приймають ідентифікатора, адже може існувати лише один екземпляр ресурсу:

<div class="overflow-auto">

| Метод     | URI             | Дія    | Ім'я маршруту  |
| --------- | --------------- | ------ | -------------- |
| GET       | `/profile`      | show   | profile.show   |
| GET       | `/profile/edit` | edit   | profile.edit   |
| PUT/PATCH | `/profile`      | update | profile.update |

</div>

Синглтон-ресурси можна також вкладати у стандартний ресурс:

```php
Route::singleton('photos.thumbnail', ThumbnailController::class);
```

У цьому прикладі ресурс `photos` отримає всі [стандартні ресурсні маршрути](#actions-handled-by-resource-controllers); однак ресурс `thumbnail` буде синглтон-ресурсом із такими маршрутами:

<div class="overflow-auto">

| Метод     | URI                              | Дія    | Ім'я маршруту           |
| --------- | -------------------------------- | ------ | ----------------------- |
| GET       | `/photos/{photo}/thumbnail`      | show   | photos.thumbnail.show   |
| GET       | `/photos/{photo}/thumbnail/edit` | edit   | photos.thumbnail.edit   |
| PUT/PATCH | `/photos/{photo}/thumbnail`      | update | photos.thumbnail.update |

</div>

<a name="creatable-singleton-resources"></a>
#### Синглтон-ресурси зі створенням

Подекуди ви можете захотіти визначити маршрути створення та збереження для синглтон-ресурсу. Для цього викличте метод `creatable` під час реєстрації маршруту синглтон-ресурсу:

```php
Route::singleton('photos.thumbnail', ThumbnailController::class)->creatable();
```

У цьому прикладі буде зареєстровано такі маршрути. Як бачите, для синглтон-ресурсів зі створенням реєструється також маршрут `DELETE`:

<div class="overflow-auto">

| Метод     | URI                                | Дія     | Ім'я маршруту            |
| --------- | ---------------------------------- | ------- | ------------------------ |
| GET       | `/photos/{photo}/thumbnail/create` | create  | photos.thumbnail.create  |
| POST      | `/photos/{photo}/thumbnail`        | store   | photos.thumbnail.store   |
| GET       | `/photos/{photo}/thumbnail`        | show    | photos.thumbnail.show    |
| GET       | `/photos/{photo}/thumbnail/edit`   | edit    | photos.thumbnail.edit    |
| PUT/PATCH | `/photos/{photo}/thumbnail`        | update  | photos.thumbnail.update  |
| DELETE    | `/photos/{photo}/thumbnail`        | destroy | photos.thumbnail.destroy |

</div>

Якщо ви хочете, щоб Laravel зареєстрував для синглтон-ресурсу маршрут `DELETE`, але не реєстрував маршрутів створення чи збереження, скористайтеся методом `destroyable`:

```php
Route::singleton(...)->destroyable();
```

<a name="api-singleton-resources"></a>
#### Синглтон-ресурси API

Метод `apiSingleton` дозволяє зареєструвати синглтон-ресурс, яким керуватимуть через API, роблячи маршрути `create` та `edit` непотрібними:

```php
Route::apiSingleton('profile', ProfileController::class);
```

Звісно, синглтон-ресурси API також можуть бути `creatable`, що зареєструє для ресурсу маршрути `store` та `destroy`:

```php
Route::apiSingleton('photos.thumbnail', ProfileController::class)->creatable();
```
<a name="middleware-and-resource-controllers"></a>
### Middleware та ресурсні контролери

Laravel дозволяє призначати `middleware` всім або лише певним методам ресурсних маршрутів за допомогою методів `middleware`, `middlewareFor` і `withoutMiddlewareFor`. Ці методи дають тонкий контроль над тим, який `middleware` застосовується до кожної ресурсної дії.

#### Застосування middleware до всіх методів

Скористайтеся методом `middleware`, щоб призначити `middleware` всім маршрутам, згенерованим ресурсним чи синглтон-ресурсним маршрутом:

```php
Route::resource('users', UserController::class)
    ->middleware(['auth', 'verified']);

Route::singleton('profile', ProfileController::class)
    ->middleware('auth');
```

#### Застосування middleware до конкретних методів

Скористайтеся методом `middlewareFor`, щоб призначити `middleware` одному чи кільком конкретним методам ресурсного контролера:

```php
Route::resource('users', UserController::class)
    ->middlewareFor('show', 'auth');

Route::apiResource('users', UserController::class)
    ->middlewareFor(['show', 'update'], 'auth');

Route::resource('users', UserController::class)
    ->middlewareFor('show', 'auth')
    ->middlewareFor('update', 'auth');

Route::apiResource('users', UserController::class)
    ->middlewareFor(['show', 'update'], ['auth', 'verified']);
```

Метод `middlewareFor` можна також використовувати разом із синглтон-ресурсними контролерами та їхніми API-варіантами:

```php
Route::singleton('profile', ProfileController::class)
    ->middlewareFor('show', 'auth');

Route::apiSingleton('profile', ProfileController::class)
    ->middlewareFor(['show', 'update'], 'auth');
```

#### Виключення middleware з конкретних методів

Скористайтеся методом `withoutMiddlewareFor`, щоб виключити `middleware` з конкретних методів ресурсного контролера:

```php
Route::middleware(['auth', 'verified', 'subscribed'])->group(function () {
    Route::resource('users', UserController::class)
        ->withoutMiddlewareFor('index', ['auth', 'verified'])
        ->withoutMiddlewareFor(['create', 'store'], 'verified')
        ->withoutMiddlewareFor('destroy', 'subscribed');
});
```

<a name="dependency-injection-and-controllers"></a>
## Впровадження залежностей і контролери

<a name="constructor-injection"></a>
#### Впровадження через конструктор

[Сервіс-контейнер](/docs/{{version}}/container) Laravel використовується для розв'язання всіх контролерів Laravel. Завдяки цьому ви можете вказати типи будь-яких потрібних контролеру залежностей у його конструкторі. Оголошені залежності буде автоматично розв'язано й впроваджено в екземпляр контролера:

```php
<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController extends Controller
{
    /**
     * Create a new controller instance.
     */
    public function __construct(
        protected UserRepository $users,
    ) {}
}
```

<a name="method-injection"></a>
#### Впровадження через метод

Крім впровадження через конструктор, ви можете вказувати типи залежностей у методах контролера. Поширений сценарій - впровадження екземпляра `Illuminate\Http\Request` у методи контролера:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * Store a new user.
     */
    public function store(Request $request): RedirectResponse
    {
        $name = $request->name;

        // Store the user...

        return redirect('/users');
    }
}
```

Якщо ваш метод контролера також очікує вхідні дані з параметра маршруту, перелічуйте аргументи маршруту після інших залежностей. Наприклад, якщо ваш маршрут визначено так:

```php
use App\Http\Controllers\UserController;

Route::put('/user/{id}', [UserController::class, 'update']);
```

Ви все одно можете вказати тип `Illuminate\Http\Request` і звертатися до параметра `id`, визначивши метод контролера так:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * Update the given user.
     */
    public function update(Request $request, string $id): RedirectResponse
    {
        // Update the user...

        return redirect('/users');
    }
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/requests

# HTTP-запити

- [Вступ](#introduction)
- [Робота із запитом](#interacting-with-the-request)
    - [Доступ до запиту](#accessing-the-request)
    - [Шлях, хост і метод запиту](#request-path-and-method)
    - [Заголовки запиту](#request-headers)
    - [IP-адреса запиту](#request-ip-address)
    - [Узгодження вмісту](#content-negotiation)
    - [Запити PSR-7](#psr7-requests)
- [Вхідні дані](#input)
    - [Отримання вхідних даних](#retrieving-input)
    - [Наявність вхідних даних](#input-presence)
    - [Додавання вхідних даних](#merging-additional-input)
    - [Попередні вхідні дані](#old-input)
    - [Cookie](#cookies)
    - [Обрізання та нормалізація вхідних даних](#input-trimming-and-normalization)
- [Файли](#files)
    - [Отримання завантажених файлів](#retrieving-uploaded-files)
    - [Збереження завантажених файлів](#storing-uploaded-files)
- [Налаштування довірених проксі](#configuring-trusted-proxies)
- [Налаштування довірених хостів](#configuring-trusted-hosts)

<a name="introduction"></a>
## Вступ

Клас `Illuminate\Http\Request` у Laravel дає об'єктно-орієнтований спосіб працювати з поточним HTTP-запитом, який обробляє ваш застосунок, а також отримувати вхідні дані, cookie й файли, надіслані разом із запитом.

<a name="interacting-with-the-request"></a>
## Робота із запитом

<a name="accessing-the-request"></a>
### Доступ до запиту

Щоб отримати екземпляр поточного HTTP-запиту через впровадження залежностей, вкажіть тип `Illuminate\Http\Request` у замиканні маршруту чи методі контролера. [Сервіс-контейнер](/docs/{{version}}/container) Laravel автоматично впровадить екземпляр вхідного запиту:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * Store a new user.
     */
    public function store(Request $request): RedirectResponse
    {
        $name = $request->input('name');

        // Store the user...

        return redirect('/users');
    }
}
```

Як згадувалося, ви також можете вказати тип `Illuminate\Http\Request` у замиканні маршруту. Сервіс-контейнер автоматично впровадить вхідний запит у замикання під час його виконання:

```php
use Illuminate\Http\Request;

Route::get('/', function (Request $request) {
    // ...
});
```

<a name="dependency-injection-route-parameters"></a>
#### Впровадження залежностей і параметри маршруту

Якщо ваш метод контролера також очікує вхідні дані з параметра маршруту, перелічуйте параметри маршруту після інших залежностей. Наприклад, якщо ваш маршрут визначено так:

```php
use App\Http\Controllers\UserController;

Route::put('/user/{id}', [UserController::class, 'update']);
```

Ви все одно можете вказати тип `Illuminate\Http\Request` і звертатися до параметра маршруту `id`, визначивши метод контролера так:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * Update the specified user.
     */
    public function update(Request $request, string $id): RedirectResponse
    {
        // Update the user...

        return redirect('/users');
    }
}
```

<a name="request-path-and-method"></a>
### Шлях, хост і метод запиту

Екземпляр `Illuminate\Http\Request` надає різноманітні методи для дослідження вхідного HTTP-запиту й успадковує клас `Symfony\Component\HttpFoundation\Request`. Нижче ми розглянемо кілька найважливіших методів.

<a name="retrieving-the-request-path"></a>
#### Отримання шляху запиту

Метод `path` повертає інформацію про шлях запиту. Тож якщо вхідний запит спрямовано на `http://example.com/foo/bar`, метод `path` поверне `foo/bar`:

```php
$uri = $request->path();
```

<a name="inspecting-the-request-path"></a>
#### Перевірка шляху чи маршруту запиту

Метод `is` дозволяє перевірити, чи збігається шлях вхідного запиту із заданим шаблоном. У цьому методі можна використовувати символ `*` як підстановочний:

```php
if ($request->is('admin/*')) {
    // ...
}
```

Методом `routeIs` ви можете визначити, чи збігся вхідний запит із [іменованим маршрутом](/docs/{{version}}/routing#named-routes):

```php
if ($request->routeIs('admin.*')) {
    // ...
}
```

<a name="retrieving-the-request-url"></a>
#### Отримання URL запиту

Щоб отримати повний URL вхідного запиту, скористайтеся методами `url` чи `fullUrl`. Метод `url` поверне URL без рядка запиту, а `fullUrl` - разом із ним:

```php
$url = $request->url();

$urlWithQueryString = $request->fullUrl();
```

Якщо ви хочете додати дані рядка запиту до поточного URL, викличте метод `fullUrlWithQuery`. Він об'єднає переданий масив змінних рядка запиту з поточним рядком запиту:

```php
$request->fullUrlWithQuery(['type' => 'phone']);
```

Якщо ви хочете отримати поточний URL без певного параметра рядка запиту, скористайтеся методом `fullUrlWithoutQuery`:

```php
$request->fullUrlWithoutQuery(['type']);
```

<a name="retrieving-the-request-host"></a>
#### Отримання хоста запиту

Отримати «хост» вхідного запиту можна методами `host`, `httpHost` і `schemeAndHttpHost`:

```php
// http://localhost:8000
$request->host(); // localhost
$request->httpHost(); // localhost:8000
$request->schemeAndHttpHost(); // http://localhost:8000
```

<a name="retrieving-the-request-method"></a>
#### Отримання методу запиту

Метод `method` поверне HTTP-метод запиту. Ви можете скористатися методом `isMethod`, щоб перевірити, чи збігається HTTP-метод із заданим рядком:

```php
$method = $request->method();

if ($request->isMethod('post')) {
    // ...
}
```

<a name="request-headers"></a>
### Заголовки запиту

Отримати заголовок запиту з екземпляра `Illuminate\Http\Request` можна методом `header`. Якщо заголовка в запиті немає, буде повернуто `null`. Утім, метод `header` приймає необов'язковий другий аргумент, який буде повернуто за відсутності заголовка:

```php
$value = $request->header('X-Header-Name');

$value = $request->header('X-Header-Name', 'default');
```

Метод `hasHeader` дозволяє визначити, чи містить запит певний заголовок:

```php
if ($request->hasHeader('X-Header-Name')) {
    // ...
}
```

Для зручності метод `bearerToken` дозволяє отримати bearer-токен із заголовка `Authorization`. Якщо такого заголовка немає, буде повернуто порожній рядок:

```php
$token = $request->bearerToken();
```

<a name="request-ip-address"></a>
### IP-адреса запиту

Метод `ip` дозволяє отримати IP-адресу клієнта, який зробив запит до вашого застосунку:

```php
$ipAddress = $request->ip();
```

Якщо ви хочете отримати масив IP-адрес, зокрема всі адреси клієнта, переслані проксі-серверами, скористайтеся методом `ips`. «Початкова» IP-адреса клієнта буде в кінці масиву:

```php
$ipAddresses = $request->ips();
```

Загалом IP-адреси слід вважати недовіреними даними, які контролює користувач, і використовувати лише в інформаційних цілях.

<a name="content-negotiation"></a>
### Узгодження вмісту

Laravel надає кілька методів для дослідження типів вмісту, які запитує вхідний запит, через заголовок `Accept`. Спершу метод `getAcceptableContentTypes` поверне масив усіх типів вмісту, прийнятних для запиту:

```php
$contentTypes = $request->getAcceptableContentTypes();
```

Метод `accepts` приймає масив типів вмісту й повертає `true`, якщо хоч один із них прийнятний для запиту. Інакше буде повернуто `false`:

```php
if ($request->accepts(['text/html', 'application/json'])) {
    // ...
}
```

Метод `prefers` дозволяє визначити, який тип вмісту з переданого масиву є найбажанішим для запиту. Якщо жоден із наданих типів не прийнятний, буде повернуто `null`:

```php
$preferred = $request->prefers(['text/html', 'application/json']);
```

Оскільки багато застосунків віддають лише HTML чи JSON, метод `expectsJson` дозволяє швидко визначити, чи очікує вхідний запит JSON-відповідь:

```php
if ($request->expectsJson()) {
    // ...
}
```

Якщо вам потрібно визначити, чи запит саме віддає перевагу Markdown або чи прийме його серед інших типів вмісту - наприклад, коли ви обслуговуєте AI-агентів чи інших клієнтів, що споживають Markdown-відповіді, - скористайтеся методами `wantsMarkdown` і `acceptsMarkdown`:

```php
if ($request->wantsMarkdown()) {
    // The client's most preferred content type is text/markdown...
}

if ($request->acceptsMarkdown()) {
    // The client accepts Markdown responses...
}
```

<a name="psr7-requests"></a>
### Запити PSR-7

[Стандарт PSR-7](https://www.php-fig.org/psr/psr-7/) визначає інтерфейси для HTTP-повідомлень, зокрема запитів і відповідей. Якщо ви хочете отримати екземпляр запиту PSR-7 замість запиту Laravel, спершу потрібно встановити кілька бібліотек. Laravel використовує компонент *Symfony HTTP Message Bridge* для перетворення типових запитів і відповідей Laravel на сумісні з PSR-7 реалізації:

```shell
composer require symfony/psr-http-message-bridge
composer require nyholm/psr7
```

Встановивши ці бібліотеки, ви можете отримати запит PSR-7, вказавши тип інтерфейсу запиту в замиканні маршруту чи методі контролера:

```php
use Psr\Http\Message\ServerRequestInterface;

Route::get('/', function (ServerRequestInterface $request) {
    // ...
});
```

> [!NOTE]
> Якщо ви повернете з маршруту чи контролера екземпляр відповіді PSR-7, його буде автоматично перетворено назад на екземпляр відповіді Laravel і показано фреймворком.

<a name="input"></a>
## Вхідні дані

<a name="retrieving-input"></a>
### Отримання вхідних даних

<a name="retrieving-all-input-data"></a>
#### Отримання всіх вхідних даних

Ви можете отримати всі вхідні дані запиту як `array` методом `all`. Цей метод працює незалежно від того, надійшов запит із HTML-форми чи є XHR-запитом:

```php
$input = $request->all();
```

Методом `collect` ви можете отримати всі вхідні дані запиту як [колекцію](/docs/{{version}}/collections):

```php
$input = $request->collect();
```

Метод `collect` також дозволяє отримати як колекцію лише частину вхідних даних:

```php
$request->collect('users')->each(function (string $user) {
    // ...
});
```

<a name="retrieving-an-input-value"></a>
#### Отримання окремого значення

За допомогою кількох простих методів ви можете звертатися до всіх вхідних даних користувача з екземпляра `Illuminate\Http\Request`, не переймаючись тим, який HTTP-метод було використано. Незалежно від методу, отримати дані користувача можна методом `input`:

```php
$name = $request->input('name');
```

Ви можете передати значення за замовчуванням другим аргументом методу `input`. Воно повернеться, якщо запитаного значення в запиті немає:

```php
$name = $request->input('name', 'Sally');
```

Працюючи з формами, що містять масиви, використовуйте «крапкову» нотацію для доступу до них:

```php
$name = $request->input('products.0.name');

$names = $request->input('products.*.name');
```

Ви можете викликати метод `input` без аргументів, щоб отримати всі вхідні значення як асоціативний масив:

```php
$input = $request->input();
```

<a name="retrieving-input-from-the-query-string"></a>
#### Отримання даних із рядка запиту

Тоді як метод `input` отримує значення з усього тіла запиту (включно з рядком запиту), метод `query` бере значення лише з рядка запиту:

```php
$name = $request->query('name');
```

Якщо запитаного значення в рядку запиту немає, буде повернуто другий аргумент цього методу:

```php
$name = $request->query('name', 'Helen');
```

Ви можете викликати метод `query` без аргументів, щоб отримати всі значення рядка запиту як асоціативний масив:

```php
$query = $request->query();
```

<a name="retrieving-json-input-values"></a>
#### Отримання значень із JSON

Надсилаючи JSON-запити до вашого застосунку, ви можете звертатися до JSON-даних методом `input`, якщо заголовок `Content-Type` запиту правильно встановлено в `application/json`. Ви навіть можете скористатися «крапковим» синтаксисом, щоб отримати значення, вкладені в JSON-масиви чи об'єкти:

```php
$name = $request->input('user.name');
```

<a name="retrieving-stringable-input-values"></a>
#### Отримання значень як Stringable

Замість отримувати вхідні дані запиту як примітивний `string`, ви можете скористатися методом `string`, щоб отримати їх як екземпляр [Illuminate\Support\Stringable](/docs/{{version}}/strings):

```php
$name = $request->string('name')->trim();
```

<a name="retrieving-integer-input-values"></a>
#### Отримання цілочисельних значень

Щоб отримати вхідні значення як цілі числа, скористайтеся методом `integer`. Він спробує привести значення до цілого. Якщо значення відсутнє, буде повернуто вказане вами значення за замовчуванням. Це особливо корисно для пагінації чи інших числових даних:

```php
$perPage = $request->integer('per_page');
```

<a name="retrieving-boolean-input-values"></a>
#### Отримання булевих значень

Працюючи з HTML-елементами на кшталт чекбоксів, ваш застосунок може отримувати «істинні» значення, які насправді є рядками - наприклад, «true» чи «on». Для зручності скористайтеся методом `boolean`, щоб отримати ці значення як булеві. Метод `boolean` повертає `true` для 1, "1", true, "true", "on" та "yes". Усі інші значення дадуть `false`:

```php
$archived = $request->boolean('archived');
```

<a name="retrieving-array-input-values"></a>
#### Отримання масивів

Вхідні значення, що містять масиви, можна отримати методом `array`. Він завжди приведе значення до масиву. Якщо запит не містить значення з указаним іменем, буде повернуто порожній масив:

```php
$versions = $request->array('versions');
```

<a name="retrieving-date-input-values"></a>
#### Отримання дат

Для зручності вхідні значення з датами чи часом можна отримати як екземпляри Carbon методом `date`. Якщо запит не містить значення з указаним іменем, буде повернуто `null`:

```php
$birthday = $request->date('birthday');
```

Другий і третій аргументи методу `date` дозволяють указати формат дати та часовий пояс відповідно:

```php
$elapsed = $request->date('elapsed', '!H:i', 'Europe/Madrid');
```

Якщо значення присутнє, але має недійсний формат, буде викинуто `InvalidArgumentException`; тому рекомендуємо валідувати дані перед викликом методу `date`.

<a name="retrieving-interval-input-values"></a>
#### Отримання інтервалів

Вхідні значення, що містять тривалості, можна отримати як екземпляри `CarbonInterval` методом `interval`. Якщо запит не містить значення з указаним іменем, буде повернуто `null`:

```php
$duration = $request->interval('duration');
```

Якщо значення числове, ви можете передати одиницю виміру другим аргументом. Це може бути рядок на кшталт `second`, `minute` чи `day`, або екземпляр enum `Carbon\Unit`:

```php
use Carbon\Unit;

$timeout = $request->interval('timeout', 'second');

$delay = $request->interval('delay', Unit::Minute);
```

Якщо значення присутнє, але має недійсний формат, буде викинуто `InvalidArgumentException`; тому рекомендуємо валідувати дані перед викликом методу `interval`.

<a name="retrieving-enum-input-values"></a>
#### Отримання значень enum

Вхідні значення, що відповідають [enum PHP](https://www.php.net/manual/en/language.types.enumerations.php), також можна отримати із запиту. Якщо запит не містить значення з указаним іменем або enum не має відповідного значення, буде повернуто `null`. Метод `enum` приймає ім'я вхідного значення та клас enum першим і другим аргументами:

```php
use App\Enums\Status;

$status = $request->enum('status', Status::class);
```

Ви також можете передати значення за замовчуванням, яке повернеться, якщо значення відсутнє чи недійсне:

```php
$status = $request->enum('status', Status::class, Status::Pending);
```

Якщо вхідне значення є масивом значень, що відповідають enum PHP, скористайтеся методом `enums`, щоб отримати масив значень як екземпляри enum:

```php
use App\Enums\Product;

$products = $request->enums('products', Product::class);
```

<a name="retrieving-input-via-dynamic-properties"></a>
#### Отримання даних через динамічні властивості

Ви також можете звертатися до вхідних даних користувача через динамічні властивості екземпляра `Illuminate\Http\Request`. Наприклад, якщо одна з форм вашого застосунку містить поле `name`, ви можете звернутися до його значення так:

```php
$name = $request->name;
```

Використовуючи динамічні властивості, Laravel спершу шукатиме значення параметра в тілі запиту. Якщо його там немає, Laravel шукатиме поле серед параметрів відповідного маршруту.

<a name="retrieving-a-portion-of-the-input-data"></a>
#### Отримання частини вхідних даних

Якщо вам потрібно отримати підмножину вхідних даних, скористайтеся методами `only` та `except`. Обидва приймають один `array` або динамічний список аргументів:

```php
$input = $request->only(['username', 'password']);

$input = $request->only('username', 'password');

$input = $request->except(['credit_card']);

$input = $request->except('credit_card');
```

> [!WARNING]
> Метод `only` повертає всі запитані пари «ключ - значення»; однак він не поверне пар, яких немає в запиті.

<a name="input-presence"></a>
### Наявність вхідних даних

Метод `has` дозволяє визначити, чи присутнє значення в запиті. Він повертає `true`, якщо значення присутнє:

```php
if ($request->has('name')) {
    // ...
}
```

Якщо передати масив, метод `has` визначить, чи присутні всі вказані значення:

```php
if ($request->has(['name', 'email'])) {
    // ...
}
```

Метод `hasAny` повертає `true`, якщо присутнє хоч одне з указаних значень:

```php
if ($request->hasAny(['name', 'email'])) {
    // ...
}
```

Метод `whenHas` виконає передане замикання, якщо значення присутнє в запиті:

```php
$request->whenHas('name', function (string $input) {
    // ...
});
```

Методу `whenHas` можна передати друге замикання, яке виконається, якщо вказаного значення в запиті немає:

```php
$request->whenHas('name', function (string $input) {
    // The "name" value is present...
}, function () {
    // The "name" value is not present...
});
```

Якщо ви хочете визначити, що значення присутнє в запиті й не є порожнім рядком, скористайтеся методом `filled`:

```php
if ($request->filled('name')) {
    // ...
}
```

Якщо ви хочете визначити, що значення відсутнє в запиті або є порожнім рядком, скористайтеся методом `isNotFilled`:

```php
if ($request->isNotFilled('name')) {
    // ...
}
```

Якщо передати масив, метод `isNotFilled` визначить, чи всі вказані значення відсутні або порожні:

```php
if ($request->isNotFilled(['name', 'email'])) {
    // ...
}
```

Метод `anyFilled` повертає `true`, якщо хоч одне з указаних значень не є порожнім рядком:

```php
if ($request->anyFilled(['name', 'email'])) {
    // ...
}
```

Метод `whenFilled` виконає передане замикання, якщо значення присутнє в запиті й не є порожнім рядком:

```php
$request->whenFilled('name', function (string $input) {
    // ...
});
```

Методу `whenFilled` можна передати друге замикання, яке виконається, якщо вказане значення не «заповнене»:

```php
$request->whenFilled('name', function (string $input) {
    // The "name" value is filled...
}, function () {
    // The "name" value is not filled...
});
```

Щоб визначити, що певного ключа в запиті немає, скористайтеся методами `missing` і `whenMissing`:

```php
if ($request->missing('name')) {
    // ...
}

$request->whenMissing('name', function () {
    // The "name" value is missing...
}, function () {
    // The "name" value is present...
});
```

<a name="merging-additional-input"></a>
### Додавання вхідних даних

Іноді вам може знадобитися вручну додати додаткові дані до наявних вхідних даних запиту. Це робиться методом `merge`. Якщо переданий ключ уже існує в запиті, його буде перезаписано даними, переданими методу `merge`:

```php
$request->merge(['votes' => 0]);
```

Метод `mergeIfMissing` дозволяє додати дані до запиту, якщо відповідних ключів у ньому ще немає:

```php
$request->mergeIfMissing(['votes' => 0]);
```

<a name="old-input"></a>
### Попередні вхідні дані

Laravel дозволяє зберігати вхідні дані одного запиту для наступного. Ця можливість особливо корисна для повторного заповнення форм після виявлення помилок валідації. Утім, якщо ви користуєтеся вбудованими [можливостями валідації](/docs/{{version}}/validation) Laravel, вам, імовірно, не доведеться викликати ці методи вручну, адже частина вбудованих засобів валідації робить це автоматично.

<a name="flashing-input-to-the-session"></a>
#### Запис вхідних даних до сесії

Метод `flash` класу `Illuminate\Http\Request` запише поточні вхідні дані до [сесії](/docs/{{version}}/session), щоб вони були доступні під час наступного запиту користувача:

```php
$request->flash();
```

Ви також можете скористатися методами `flashOnly` та `flashExcept`, щоб записати до сесії лише частину даних запиту. Ці методи корисні, щоб не зберігати в сесії конфіденційну інформацію на кшталт паролів:

```php
$request->flashOnly(['username', 'email']);

$request->flashExcept('password');
```

<a name="flashing-input-then-redirecting"></a>
#### Запис даних із подальшим перенаправленням

Оскільки часто потрібно записати дані до сесії й одразу перенаправити на попередню сторінку, ви можете легко приєднати запис даних до перенаправлення методом `withInput`:

```php
return redirect('/form')->withInput();

return redirect()->route('user.create')->withInput();

return redirect('/form')->withInput(
    $request->except('password')
);
```

<a name="retrieving-old-input"></a>
#### Отримання попередніх даних

Щоб отримати збережені дані попереднього запиту, викличте метод `old` на екземплярі `Illuminate\Http\Request`. Метод `old` візьме раніше збережені дані із [сесії](/docs/{{version}}/session):

```php
$username = $request->old('username');
```

Laravel також надає глобальний хелпер `old`. Якщо ви показуєте попередні дані в [шаблоні Blade](/docs/{{version}}/blade), зручніше скористатися хелпером `old`, щоб заново заповнити форму. Якщо попередніх даних для вказаного поля немає, буде повернуто `null`:

```blade
<input type="text" name="username" value="{{ old('username') }}">
```

<a name="cookies"></a>
### Cookie

<a name="retrieving-cookies-from-requests"></a>
#### Отримання cookie із запитів

Усі cookie, створені фреймворком Laravel, зашифровані й підписані кодом автентифікації, тобто вважатимуться недійсними, якщо клієнт їх змінив. Щоб отримати значення cookie із запиту, скористайтеся методом `cookie` на екземплярі `Illuminate\Http\Request`:

```php
$value = $request->cookie('name');
```

<a name="input-trimming-and-normalization"></a>
## Обрізання та нормалізація вхідних даних

За замовчуванням Laravel додає `middleware` `Illuminate\Foundation\Http\Middleware\TrimStrings` та `Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull` до глобального стека вашого застосунку. Ці `middleware` автоматично обрізають усі вхідні рядкові поля запиту, а також перетворюють порожні рядки на `null`. Завдяки цьому вам не потрібно перейматися нормалізацією в маршрутах і контролерах.

#### Вимкнення нормалізації вхідних даних

Якщо ви хочете вимкнути цю поведінку для всіх запитів, вилучіть ці два `middleware` зі стека вашого застосунку, викликавши метод `$middleware->remove` у файлі `bootstrap/app.php`:

```php
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
use Illuminate\Foundation\Http\Middleware\TrimStrings;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->remove([
        ConvertEmptyStringsToNull::class,
        TrimStrings::class,
    ]);
})
```

Якщо ви хочете вимкнути обрізання рядків і перетворення порожніх рядків лише для частини запитів, скористайтеся методами `trimStrings` і `convertEmptyStringsToNull` у файлі `bootstrap/app.php`. Обидва приймають масив замикань, які мають повертати `true` чи `false`, вказуючи, чи слід пропустити нормалізацію:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->convertEmptyStringsToNull(except: [
        fn (Request $request) => $request->is('admin/*'),
    ]);

    $middleware->trimStrings(except: [
        fn (Request $request) => $request->is('admin/*'),
    ]);
})
```

<a name="files"></a>
## Файли

<a name="retrieving-uploaded-files"></a>
### Отримання завантажених файлів

Отримати завантажені файли з екземпляра `Illuminate\Http\Request` можна методом `file` або через динамічні властивості. Метод `file` повертає екземпляр класу `Illuminate\Http\UploadedFile`, який успадковує PHP-клас `SplFileInfo` і надає різноманітні методи для роботи з файлом:

```php
$file = $request->file('photo');

$file = $request->photo;
```

Визначити, чи присутній файл у запиті, можна методом `hasFile`:

```php
if ($request->hasFile('photo')) {
    // ...
}
```

Якщо завантажений файл є зображенням, яке потрібно обробити перед збереженням, скористайтеся методом `image`, щоб отримати екземпляр `Illuminate\Image\Image`, або `null`, якщо файла немає:

```php
$image = $request->image('photo');
```

Докладніше про обробку зображень читайте в повній [документації з обробки зображень](/docs/{{version}}/images).

<a name="validating-successful-uploads"></a>
#### Перевірка успішності завантаження

Окрім перевірки наявності файлу, ви можете переконатися, що під час завантаження не було проблем, методом `isValid`:

```php
if ($request->file('photo')->isValid()) {
    // ...
}
```

<a name="file-paths-extensions"></a>
#### Шляхи та розширення файлів

Клас `UploadedFile` також містить методи для доступу до повного шляху файлу та його розширення. Метод `extension` спробує вгадати розширення на основі вмісту файлу. Це розширення може відрізнятися від того, що надав клієнт:

```php
$path = $request->photo->path();

$extension = $request->photo->extension();
```

<a name="other-file-methods"></a>
#### Інші методи файлів

Екземпляри `UploadedFile` мають чимало інших методів. Докладніше про них дивіться в [документації API цього класу](https://github.com/symfony/symfony/blob/6.0/src/Symfony/Component/HttpFoundation/File/UploadedFile.php).

<a name="storing-uploaded-files"></a>
### Збереження завантажених файлів

Щоб зберегти завантажений файл, ви зазвичай скористаєтеся однією з налаштованих [файлових систем](/docs/{{version}}/filesystem). Клас `UploadedFile` має метод `store`, який перемістить завантажений файл на один із ваших дисків - це може бути розташування в локальній файловій системі чи хмарне сховище на кшталт Amazon S3.

Метод `store` приймає шлях, за яким слід зберегти файл, відносно кореневого каталогу файлової системи. Цей шлях не має містити імені файлу, адже як ім'я автоматично буде згенеровано унікальний ідентифікатор.

Метод `store` також приймає необов'язковий другий аргумент - ім'я диска, на якому слід зберегти файл. Метод поверне шлях до файлу відносно кореня диска:

```php
$path = $request->photo->store('images');

$path = $request->photo->store('images', 's3');
```

Якщо ви не хочете, щоб ім'я файлу генерувалося автоматично, скористайтеся методом `storeAs`, який приймає шлях, ім'я файлу та ім'я диска:

```php
$path = $request->photo->storeAs('images', 'filename.jpg');

$path = $request->photo->storeAs('images', 'filename.jpg', 's3');
```

> [!NOTE]
> Докладніше про зберігання файлів у Laravel читайте в повній [документації з файлового сховища](/docs/{{version}}/filesystem).

<a name="configuring-trusted-proxies"></a>
## Налаштування довірених проксі

Коли ваші застосунки працюють за балансувальником навантаження, що завершує TLS/SSL-з'єднання, ви можете помітити, що застосунок іноді не генерує HTTPS-посилання через хелпер `url`. Зазвичай це тому, що трафік надходить від балансувальника на порт 80, і застосунок не знає, що має генерувати захищені посилання.

Щоб це виправити, увімкніть `middleware` `Illuminate\Http\Middleware\TrustProxies`, який входить до вашого застосунку Laravel і дозволяє швидко вказати балансувальники чи проксі, яким має довіряти застосунок. Довірені проксі задаються методом `trustProxies` у файлі `bootstrap/app.php`:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->trustProxies(at: [
        '192.168.1.1',
        '10.0.0.0/8',
    ]);
})
```

Окрім налаштування довірених проксі, ви можете також налаштувати заголовки проксі, яким слід довіряти:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->trustProxies(headers: Request::HEADER_X_FORWARDED_FOR |
        Request::HEADER_X_FORWARDED_HOST |
        Request::HEADER_X_FORWARDED_PORT |
        Request::HEADER_X_FORWARDED_PROTO |
        Request::HEADER_X_FORWARDED_AWS_ELB
    );
})
```

> [!NOTE]
> Якщо ви використовуєте AWS Elastic Load Balancing, значенням `headers` має бути `Request::HEADER_X_FORWARDED_AWS_ELB`. Якщо ваш балансувальник використовує стандартний заголовок `Forwarded` із [RFC 7239](https://www.rfc-editor.org/rfc/rfc7239#section-4), значенням `headers` має бути `Request::HEADER_FORWARDED`. Докладніше про константи, які можна використовувати у значенні `headers`, дивіться в документації Symfony щодо [довіри проксі](https://symfony.com/doc/current/deployment/proxies.html).

<a name="trusting-all-proxies"></a>
#### Довіра всім проксі

Якщо ви користуєтеся Amazon AWS чи іншим «хмарним» провайдером балансувальників, ви можете не знати IP-адрес своїх балансувальників. У такому разі скористайтеся `*`, щоб довіряти всім проксі:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->trustProxies(at: '*');
})
```

<a name="configuring-trusted-hosts"></a>
## Налаштування довірених хостів

За замовчуванням Laravel відповідатиме на всі отримані запити незалежно від вмісту заголовка `Host`. Крім того, значення заголовка `Host` використовуватиметься під час генерації абсолютних URL до вашого застосунку.

Зазвичай варто налаштувати ваш веб-сервер - Nginx чи Apache - так, щоб він надсилав до застосунку лише запити, що відповідають певному імені хоста. Утім, якщо ви не можете налаштувати веб-сервер напряму й хочете вказати Laravel відповідати лише певним іменам хостів, увімкніть для свого застосунку `middleware` `Illuminate\Http\Middleware\TrustHosts`.

Щоб увімкнути `middleware` `TrustHosts`, викличте метод `trustHosts` у файлі `bootstrap/app.php`. За допомогою аргументу `at` ви можете вказати імена хостів, на які має відповідати ваш застосунок. Рядок імені хоста трактується як регулярний вираз. Вхідні запити з іншими заголовками `Host` буде відхилено:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->trustHosts(at: ['^laravel\.test$']);
})
```

За замовчуванням запити з піддоменів URL застосунку теж автоматично вважаються довіреними. Якщо ви хочете вимкнути цю поведінку, скористайтеся аргументом `subdomains`:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->trustHosts(at: ['^laravel\.test$'], subdomains: false);
})
```

Якщо для визначення довірених хостів вам потрібен доступ до конфігураційних файлів чи бази даних застосунку, передайте аргументу `at` замикання:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->trustHosts(at: fn () => config('app.trusted_hosts'));
})
```

---

Джерело: https://laravelukraine.com/docs/13.x/responses

# HTTP-відповіді

- [Створення відповідей](#creating-responses)
    - [Додавання заголовків до відповідей](#attaching-headers-to-responses)
    - [Додавання cookie до відповідей](#attaching-cookies-to-responses)
    - [Cookie та шифрування](#cookies-and-encryption)
- [Перенаправлення](#redirects)
    - [Перенаправлення до іменованих маршрутів](#redirecting-named-routes)
    - [Перенаправлення до дій контролерів](#redirecting-controller-actions)
    - [Перенаправлення до зовнішніх доменів](#redirecting-external-domains)
    - [Перенаправлення з флеш-даними сесії](#redirecting-with-flashed-session-data)
- [Інші типи відповідей](#other-response-types)
    - [Відповіді-представлення](#view-responses)
    - [JSON-відповіді](#json-responses)
    - [Markdown-відповіді](#markdown-responses)
    - [Завантаження файлів](#file-downloads)
    - [Файлові відповіді](#file-responses)
- [Потокові відповіді](#streamed-responses)
    - [Споживання потокових відповідей](#consuming-streamed-responses)
    - [Потокові JSON-відповіді](#streamed-json-responses)
    - [Потоки подій (SSE)](#event-streams)
    - [Потокові завантаження](#streamed-downloads)
- [Макроси відповідей](#response-macros)

<a name="creating-responses"></a>
## Створення відповідей

<a name="strings-arrays"></a>
#### Рядки та масиви

Усі маршрути й контролери мають повертати відповідь, яку буде надіслано до браузера користувача. Laravel пропонує кілька різних способів повертати відповіді. Найпростіша відповідь - це рядок, повернений із маршруту чи контролера. Фреймворк автоматично перетворить його на повноцінну HTTP-відповідь:

```php
Route::get('/', function () {
    return 'Hello World';
});
```

Окрім рядків, ви можете повертати з маршрутів і контролерів масиви. Фреймворк автоматично перетворить масив на JSON-відповідь:

```php
Route::get('/', function () {
    return [1, 2, 3];
});
```

> [!NOTE]
> А ви знали, що з маршрутів чи контролерів можна також повертати [колекції Eloquent](/docs/{{version}}/eloquent-collections)? Вони автоматично перетворяться на JSON. Спробуйте!

<a name="response-objects"></a>
#### Об'єкти відповідей

Зазвичай ви не повертатимете з дій маршрутів лише прості рядки чи масиви. Натомість ви повертатимете повноцінні екземпляри `Illuminate\Http\Response` або [представлення](/docs/{{version}}/views).

Повернення повного екземпляра `Response` дозволяє налаштувати HTTP-статус і заголовки відповіді. Екземпляр `Response` успадковує клас `Symfony\Component\HttpFoundation\Response`, що надає різноманітні методи для побудови HTTP-відповідей:

```php
Route::get('/home', function () {
    return response('Hello World', 200)
        ->header('Content-Type', 'text/plain');
});
```

<a name="eloquent-models-and-collections"></a>
#### Моделі та колекції Eloquent

Ви також можете повертати моделі й колекції [Eloquent ORM](/docs/{{version}}/eloquent) безпосередньо з маршрутів і контролерів. Тоді Laravel автоматично перетворить їх на JSON-відповіді, враховуючи [приховані атрибути](/docs/{{version}}/eloquent-serialization#hiding-attributes-from-json) моделі:

```php
use App\Models\User;

Route::get('/user/{user}', function (User $user) {
    return $user;
});
```

<a name="attaching-headers-to-responses"></a>
### Додавання заголовків до відповідей

Пам'ятайте, що більшість методів відповіді можна об'єднувати в ланцюжок, що дозволяє плинно будувати екземпляри відповідей. Наприклад, метод `header` дозволяє додати до відповіді низку заголовків перед надсиланням її користувачеві:

```php
return response($content)
    ->header('Content-Type', $type)
    ->header('X-Header-One', 'Header Value')
    ->header('X-Header-Two', 'Header Value');
```

Або ж скористайтеся методом `withHeaders`, щоб указати масив заголовків, які слід додати до відповіді:

```php
return response($content)
    ->withHeaders([
        'Content-Type' => $type,
        'X-Header-One' => 'Header Value',
        'X-Header-Two' => 'Header Value',
    ]);
```

Ви можете вилучити конкретні заголовки з вихідної відповіді методом `withoutHeader`:

```php
return response($content)->withoutHeader('X-Debug');

return response($content)->withoutHeader(['X-Debug', 'X-Powered-By']);
```

<a name="cache-control-middleware"></a>
#### Middleware керування кешем

Laravel містить `middleware` `cache.headers`, який дозволяє швидко задати заголовок `Cache-Control` для групи маршрутів. Директиви слід передавати у вигляді «snake case» відповідної директиви cache-control, розділяючи їх крапкою з комою. Якщо в списку директив указано `etag`, як ідентифікатор ETag автоматично буде встановлено xxh128-хеш вмісту відповіді:

```php
Route::middleware('cache.headers:public;max_age=30;s_maxage=300;stale_while_revalidate=600;etag')->group(function () {
    Route::get('/privacy', function () {
        // ...
    });

    Route::get('/terms', function () {
        // ...
    });
});
```

<a name="attaching-cookies-to-responses"></a>
### Додавання cookie до відповідей

Ви можете додати cookie до вихідного екземпляра `Illuminate\Http\Response` методом `cookie`. Передайте цьому методу ім'я, значення та кількість хвилин, протягом яких cookie вважатиметься дійсною:

```php
return response('Hello World')->cookie(
    'name', 'value', $minutes
);
```

Метод `cookie` приймає ще кілька аргументів, які використовують рідше. Загалом вони мають те саме призначення й значення, що й аргументи нативного PHP-методу [setcookie](https://secure.php.net/manual/en/function.setcookie.php):

```php
return response('Hello World')->cookie(
    'name', 'value', $minutes, $path, $domain, $secure, $httpOnly
);
```

Якщо ви хочете гарантувати, що cookie буде надіслано з вихідною відповіддю, але ще не маєте екземпляра цієї відповіді, скористайтеся фасадом `Cookie`, щоб поставити cookie в «чергу» на прикріплення до відповіді під час надсилання. Метод `queue` приймає аргументи, потрібні для створення екземпляра cookie. Ці cookie буде прикріплено до вихідної відповіді перед надсиланням її до браузера:

```php
use Illuminate\Support\Facades\Cookie;

Cookie::queue('name', 'value', $minutes);
```

<a name="generating-cookie-instances"></a>
#### Створення екземплярів cookie

Якщо ви хочете створити екземпляр `Symfony\Component\HttpFoundation\Cookie`, який згодом можна буде прикріпити до екземпляра відповіді, скористайтеся глобальним хелпером `cookie`. Ця cookie не буде надіслана клієнту, доки її не прикріплено до екземпляра відповіді:

```php
$cookie = cookie('name', 'value', $minutes);

return response('Hello World')->cookie($cookie);
```

<a name="expiring-cookies-early"></a>
#### Дострокове завершення дії cookie

Ви можете вилучити cookie, завершивши її дію методом `withoutCookie` або `withoutCookies` вихідної відповіді:

```php
return response('Hello World')->withoutCookie('name');

return response('Hello World')->withoutCookies([
    'name',
    'email',
    'preferences',
]);
```

Якщо ви ще не маєте екземпляра вихідної відповіді, скористайтеся методом `expire` фасаду `Cookie`:

```php
Cookie::expire('name');
```

<a name="cookies-and-encryption"></a>
### Cookie та шифрування

За замовчуванням, завдяки `middleware` `Illuminate\Cookie\Middleware\EncryptCookies`, усі cookie, згенеровані Laravel, шифруються й підписуються, тож клієнт не може їх змінити чи прочитати. Якщо ви хочете вимкнути шифрування для частини cookie вашого застосунку, скористайтеся методом `encryptCookies` у файлі `bootstrap/app.php`:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->encryptCookies(except: [
        'cookie_name',
    ]);
})
```

> [!NOTE]
> Загалом шифрування cookie ніколи не слід вимикати, адже це наражає ваші cookie на потенційне розкриття даних і підробку на боці клієнта.

<a name="redirects"></a>
## Перенаправлення

Відповіді-перенаправлення є екземплярами класу `Illuminate\Http\RedirectResponse` і містять потрібні заголовки для перенаправлення користувача на іншу адресу. Створити екземпляр `RedirectResponse` можна кількома способами. Найпростіший - скористатися глобальним хелпером `redirect`:

```php
Route::get('/dashboard', function () {
    return redirect('/home/dashboard');
});
```

Іноді вам може знадобитися перенаправити користувача на попередню сторінку - наприклад, коли надіслана форма виявилася недійсною. Це робиться глобальною функцією-хелпером `back`. Оскільки ця можливість використовує [сесію](/docs/{{version}}/session), переконайтеся, що маршрут, який викликає `back`, входить до групи `middleware` `web`:

```php
Route::post('/user/profile', function () {
    // Validate the request...

    return back()->withInput();
});
```

<a name="redirecting-named-routes"></a>
### Перенаправлення до іменованих маршрутів

Коли ви викликаєте хелпер `redirect` без параметрів, повертається екземпляр `Illuminate\Routing\Redirector`, що дозволяє викликати на ньому будь-який метод. Наприклад, щоб згенерувати `RedirectResponse` до іменованого маршруту, скористайтеся методом `route`:

```php
return redirect()->route('login');
```

Якщо ваш маршрут має параметри, передайте їх другим аргументом методу `route`:

```php
// For a route with the following URI: /profile/{id}

return redirect()->route('profile', ['id' => 1]);
```

<a name="populating-parameters-via-eloquent-models"></a>
#### Заповнення параметрів через моделі Eloquent

Якщо ви перенаправляєте до маршруту з параметром «ID», який заповнюється з моделі Eloquent, ви можете передати саму модель. Ідентифікатор буде вилучено автоматично:

```php
// For a route with the following URI: /profile/{id}

return redirect()->route('profile', [$user]);
```

Якщо ви хочете налаштувати значення, що потрапляє в параметр маршруту, вкажіть колонку у визначенні параметра (`/profile/{id:slug}`) або перевизначте метод `getRouteKey` у своїй моделі Eloquent:

```php
/**
 * Get the value of the model's route key.
 */
public function getRouteKey(): mixed
{
    return $this->slug;
}
```

<a name="redirecting-controller-actions"></a>
### Перенаправлення до дій контролерів

Ви також можете генерувати перенаправлення до [дій контролерів](/docs/{{version}}/controllers). Для цього передайте методу `action` контролер та ім'я дії:

```php
use App\Http\Controllers\UserController;

return redirect()->action([UserController::class, 'index']);
```

Якщо маршрут вашого контролера потребує параметрів, передайте їх другим аргументом методу `action`:

```php
return redirect()->action(
    [UserController::class, 'profile'], ['id' => 1]
);
```

<a name="redirecting-external-domains"></a>
### Перенаправлення до зовнішніх доменів

Іноді вам може знадобитися перенаправлення на домен поза вашим застосунком. Це робиться методом `away`, який створює `RedirectResponse` без додаткового кодування, валідації чи перевірки URL:

```php
return redirect()->away('https://www.google.com');
```

<a name="redirecting-with-flashed-session-data"></a>
### Перенаправлення з флеш-даними сесії

Перенаправлення на нову адресу та [запис флеш-даних до сесії](/docs/{{version}}/session#flash-data) зазвичай виконуються одночасно. Типово це роблять після успішного виконання дії, записуючи до сесії повідомлення про успіх. Для зручності ви можете створити екземпляр `RedirectResponse` і записати дані до сесії одним плинним ланцюжком методів:

```php
Route::post('/user/profile', function () {
    // ...

    return redirect('/dashboard')->with('status', 'Profile updated!');
});
```

Після перенаправлення користувача ви можете показати збережене повідомлення із [сесії](/docs/{{version}}/session). Наприклад, за допомогою [синтаксису Blade](/docs/{{version}}/blade):

```blade
@if (session('status'))
    <div class="alert alert-success">
        {{ session('status') }}
    </div>
@endif
```

<a name="redirecting-with-input"></a>
#### Перенаправлення з вхідними даними

Метод `withInput` екземпляра `RedirectResponse` дозволяє записати вхідні дані поточного запиту до сесії перед перенаправленням користувача. Зазвичай це роблять, коли користувач натрапив на помилку валідації. Щойно вхідні дані записано до сесії, ви можете легко [отримати їх](/docs/{{version}}/requests#retrieving-old-input) під час наступного запиту, щоб заново заповнити форму:

```php
return back()->withInput();
```

<a name="other-response-types"></a>
## Інші типи відповідей

Хелпер `response` дозволяє генерувати інші типи екземплярів відповідей. Коли його викликають без аргументів, повертається реалізація [контракту](/docs/{{version}}/contracts) `Illuminate\Contracts\Routing\ResponseFactory`. Цей контракт надає кілька корисних методів для генерації відповідей.

<a name="view-responses"></a>
### Відповіді-представлення

Якщо вам потрібен контроль над статусом і заголовками відповіді, але водночас потрібно повернути [представлення](/docs/{{version}}/views) як її вміст, скористайтеся методом `view`:

```php
return response()
    ->view('hello', $data, 200)
    ->header('Content-Type', $type);
```

Звісно, якщо вам не потрібно передавати власний HTTP-статус чи заголовки, ви можете скористатися глобальною функцією-хелпером `view`.

<a name="json-responses"></a>
### JSON-відповіді

Метод `json` автоматично встановить заголовок `Content-Type` у значення `application/json`, а також перетворить переданий масив на JSON PHP-функцією `json_encode`:

```php
return response()->json([
    'name' => 'Abigail',
    'state' => 'CA',
]);
```

Якщо ви хочете створити JSONP-відповідь, скористайтеся методом `json` у поєднанні з методом `withCallback`:

```php
return response()
    ->json(['name' => 'Abigail', 'state' => 'CA'])
    ->withCallback($request->input('callback'));
```

<a name="markdown-responses"></a>
### Markdown-відповіді

Метод `markdown` можна використовувати для повернення Markdown-вмісту з заголовком `Content-Type`, встановленим на `text/markdown`:

```php
return response()->markdown("# Hello\n\nWorld");
```

Ви можете передати власний HTTP-код статусу та масив додаткових заголовків як другий і третій аргументи:

```php
return response()->markdown('# Not Found', 404, ['X-Custom-Header' => 'Value']);
```

<a name="file-downloads"></a>
### Завантаження файлів

Метод `download` дозволяє згенерувати відповідь, що змусить браузер користувача завантажити файл за вказаним шляхом. Метод `download` приймає ім'я файлу другим аргументом - саме його побачить користувач, який завантажує файл. Нарешті, третім аргументом можна передати масив HTTP-заголовків:

```php
return response()->download($pathToFile);

return response()->download($pathToFile, $name, $headers);
```

> [!WARNING]
> Symfony HttpFoundation, який керує завантаженням файлів, вимагає, щоб файл мав ім'я з символів ASCII.

<a name="file-responses"></a>
### Файлові відповіді

Метод `file` дозволяє показати файл - як-от зображення чи PDF - безпосередньо в браузері користувача замість того, щоб починати завантаження. Цей метод приймає абсолютний шлях до файлу першим аргументом і масив заголовків другим:

```php
return response()->file($pathToFile);

return response()->file($pathToFile, $headers);
```

<a name="streamed-responses"></a>
## Потокові відповіді

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

```php
Route::get('/stream', function () {
    return response()->stream(function (): void {
        foreach (['developer', 'admin'] as $string) {
            echo $string;
            ob_flush();
            flush();
            sleep(2); // Simulate delay between chunks...
        }
    }, 200, ['X-Accel-Buffering' => 'no']);
});
```

Для зручності, якщо замикання, передане методу `stream`, повертає [генератор](https://www.php.net/manual/en/language.generators.overview.php), Laravel автоматично скидатиме буфер виводу між рядками, які повертає генератор, а також вимкне буферизацію виводу Nginx:

```php
Route::post('/chat', function () {
    return response()->stream(function (): Generator {
        $stream = OpenAI::client()->chat()->createStreamed(...);

        foreach ($stream as $response) {
            yield $response->choices[0];
        }
    });
});
```

<a name="consuming-streamed-responses"></a>
### Споживання потокових відповідей

Потокові відповіді можна споживати за допомогою npm-пакета `stream` від Laravel, який надає зручний API для роботи з відповідями та потоками подій Laravel. Щоб почати, встановіть пакет `@laravel/stream-react`, `@laravel/stream-vue` або `@laravel/stream-svelte`:

```shell tab=React
npm install @laravel/stream-react
```

```shell tab=Vue
npm install @laravel/stream-vue
```

```shell tab=Svelte
npm install @laravel/stream-svelte
```

Далі скористайтеся `useStream`, щоб споживати потік подій. Після вказання URL потоку хук автоматично оновлюватиме `data` об'єднаною відповіддю в міру її надходження від вашого застосунку Laravel:

```tsx tab=React
import { useStream } from "@laravel/stream-react";

function App() {
    const { data, isFetching, isStreaming, send } = useStream("chat");

    const sendMessage = () => {
        send({
            message: `Current timestamp: ${Date.now()}`,
        });
    };

    return (
        <div>
            <div>{data}</div>
            {isFetching && <div>Connecting...</div>}
            {isStreaming && <div>Generating...</div>}
            <button onClick={sendMessage}>Send Message</button>
        </div>
    );
}
```

```vue tab=Vue
<script setup lang="ts">
import { useStream } from "@laravel/stream-vue";

const { data, isFetching, isStreaming, send } = useStream("chat");

const sendMessage = () => {
    send({
        message: `Current timestamp: ${Date.now()}`,
    });
};
</script>

<template>
    <div>
        <div>{{ data }}</div>
        <div v-if="isFetching">Connecting...</div>
        <div v-if="isStreaming">Generating...</div>
        <button @click="sendMessage">Send Message</button>
    </div>
</template>
```

```svelte tab=Svelte
<script>
import { useStream } from "@laravel/stream-svelte";

const stream = useStream("chat");

const sendMessage = () => {
    stream.send({
        message: `Current timestamp: ${Date.now()}`,
    });
};
</script>

<div>
    <div>{$stream.data}</div>
    {#if $stream.isFetching}
        <div>Connecting...</div>
    {/if}
    {#if $stream.isStreaming}
        <div>Generating...</div>
    {/if}
    <button onclick={sendMessage}>Send Message</button>
</div>
```

Коли ви надсилаєте дані назад у потік через `send`, активне з'єднання з потоком скасовується перед надсиланням нових даних. Усі запити надсилаються як JSON-запити `POST`.

> [!WARNING]
> Оскільки хук `useStream` робить `POST`-запит до вашого застосунку, потрібен дійсний CSRF-токен. Найпростіший спосіб його надати - [додати його через meta-тег у head макета вашого застосунку](/docs/{{version}}/csrf#csrf-x-csrf-token).

Другий аргумент, переданий `useStream`, - це об'єкт опцій, яким можна налаштувати поведінку споживання потоку. Типові значення цього об'єкта наведено нижче:

```tsx tab=React
import { useStream } from "@laravel/stream-react";

function App() {
    const { data } = useStream("chat", {
        id: undefined,
        initialInput: undefined,
        headers: undefined,
        csrfToken: undefined,
        onResponse: (response: Response) => void,
        onData: (data: string) => void,
        onCancel: () => void,
        onFinish: () => void,
        onError: (error: Error) => void,
    });

    return <div>{data}</div>;
}
```

```vue tab=Vue
<script setup lang="ts">
import { useStream } from "@laravel/stream-vue";

const { data } = useStream("chat", {
    id: undefined,
    initialInput: undefined,
    headers: undefined,
    csrfToken: undefined,
    onResponse: (response: Response) => void,
    onData: (data: string) => void,
    onCancel: () => void,
    onFinish: () => void,
    onError: (error: Error) => void,
});
</script>

<template>
    <div>{{ data }}</div>
</template>
```

```svelte tab=Svelte
<script>
import { useStream } from "@laravel/stream-svelte";

const stream = useStream("chat", {
    id: undefined,
    initialInput: undefined,
    headers: undefined,
    csrfToken: undefined,
    onResponse: (response) => {},
    onData: (data) => {},
    onCancel: () => {},
    onFinish: () => {},
    onError: (error) => {},
});
</script>

<div>{$stream.data}</div>
```

`onResponse` спрацьовує після успішної початкової відповіді з потоку, і в колбек передається сирий [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response). `onData` викликається для кожного отриманого фрагмента - у колбек передається поточний фрагмент. `onFinish` викликається, коли потік завершився, а також коли під час циклу отримання чи читання виникає помилка.

За замовчуванням під час ініціалізації запит до потоку не робиться. Ви можете передати потоку початкові дані опцією `initialInput`:

```tsx tab=React
import { useStream } from "@laravel/stream-react";

function App() {
    const { data } = useStream("chat", {
        initialInput: {
            message: "Introduce yourself.",
        },
    });

    return <div>{data}</div>;
}
```

```vue tab=Vue
<script setup lang="ts">
import { useStream } from "@laravel/stream-vue";

const { data } = useStream("chat", {
    initialInput: {
        message: "Introduce yourself.",
    },
});
</script>

<template>
    <div>{{ data }}</div>
</template>
```

```svelte tab=Svelte
<script>
import { useStream } from "@laravel/stream-svelte";

const stream = useStream("chat", {
    initialInput: {
        message: "Introduce yourself.",
    },
});
</script>

<div>{$stream.data}</div>
```

Щоб скасувати потік вручну, скористайтеся методом `cancel`, який повертає хук:

```tsx tab=React
import { useStream } from "@laravel/stream-react";

function App() {
    const { data, cancel } = useStream("chat");

    return (
        <div>
            <div>{data}</div>
            <button onClick={cancel}>Cancel</button>
        </div>
    );
}
```

```vue tab=Vue
<script setup lang="ts">
import { useStream } from "@laravel/stream-vue";

const { data, cancel } = useStream("chat");
</script>

<template>
    <div>
        <div>{{ data }}</div>
        <button @click="cancel">Cancel</button>
    </div>
</template>
```

```svelte tab=Svelte
<script>
import { useStream } from "@laravel/stream-svelte";

const stream = useStream("chat");
</script>

<div>
    <div>{$stream.data}</div>
    <button onclick={() => stream.cancel()}>Cancel</button>
</div>
```

Щоразу, коли використовується хук `useStream`, генерується випадковий `id` для ідентифікації потоку. Він надсилається на сервер із кожним запитом у заголовку `X-STREAM-ID`. Коли ви споживаєте той самий потік із кількох компонентів, ви можете читати з нього й писати до нього, надавши власний `id`:

```tsx tab=React
// App.tsx
import { useStream } from "@laravel/stream-react";

function App() {
    const { data, id } = useStream("chat");

    return (
        <div>
            <div>{data}</div>
            <StreamStatus id={id} />
        </div>
    );
}

// StreamStatus.tsx
import { useStream } from "@laravel/stream-react";

function StreamStatus({ id }) {
    const { isFetching, isStreaming } = useStream("chat", { id });

    return (
        <div>
            {isFetching && <div>Connecting...</div>}
            {isStreaming && <div>Generating...</div>}
        </div>
    );
}
```

```vue tab=Vue
<!-- App.vue -->
<script setup lang="ts">
import { useStream } from "@laravel/stream-vue";
import StreamStatus from "./StreamStatus.vue";

const { data, id } = useStream("chat");
</script>

<template>
    <div>
        <div>{{ data }}</div>
        <StreamStatus :id="id" />
    </div>
</template>

<!-- StreamStatus.vue -->
<script setup lang="ts">
import { useStream } from "@laravel/stream-vue";

const props = defineProps<{
    id: string;
}>();

const { isFetching, isStreaming } = useStream("chat", { id: props.id });
</script>

<template>
    <div>
        <div v-if="isFetching">Connecting...</div>
        <div v-if="isStreaming">Generating...</div>
    </div>
</template>
```

```svelte tab=Svelte
<!-- App.svelte -->
<script>
import { useStream } from "@laravel/stream-svelte";
import StreamStatus from "./StreamStatus.svelte";

const stream = useStream("chat");
</script>

<div>
    <div>{$stream.data}</div>
    <StreamStatus id={stream.id} />
</div>

<!-- StreamStatus.svelte -->
<script>
import { useStream } from "@laravel/stream-svelte";

let { id } = $props();

const stream = useStream("chat", { id });
</script>

<div>
    {#if $stream.isFetching}
        <div>Connecting...</div>
    {/if}
    {#if $stream.isStreaming}
        <div>Generating...</div>
    {/if}
</div>
```

<a name="streamed-json-responses"></a>
### Потокові JSON-відповіді

Якщо вам потрібно передавати JSON-дані поступово, скористайтеся методом `streamJson`. Він особливо корисний для великих наборів даних, які треба поступово надсилати браузеру у форматі, що легко розбирається JavaScript:

```php
use App\Models\User;

Route::get('/users.json', function () {
    return response()->streamJson([
        'users' => User::cursor(),
    ]);
});
```

Хук `useJsonStream` ідентичний [хуку useStream](#consuming-streamed-responses), за винятком того, що він намагатиметься розібрати дані як JSON після завершення потоку:

```tsx tab=React
import { useJsonStream } from "@laravel/stream-react";

type User = {
    id: number;
    name: string;
    email: string;
};

function App() {
    const { data, send } = useJsonStream<{ users: User[] }>("users");

    const loadUsers = () => {
        send({
            query: "taylor",
        });
    };

    return (
        <div>
            <ul>
                {data?.users.map((user) => (
                    <li>
                        {user.id}: {user.name}
                    </li>
                ))}
            </ul>
            <button onClick={loadUsers}>Load Users</button>
        </div>
    );
}
```

```vue tab=Vue
<script setup lang="ts">
import { useJsonStream } from "@laravel/stream-vue";

type User = {
    id: number;
    name: string;
    email: string;
};

const { data, send } = useJsonStream<{ users: User[] }>("users");

const loadUsers = () => {
    send({
        query: "taylor",
    });
};
</script>

<template>
    <div>
        <ul>
            <li v-for="user in data?.users" :key="user.id">
                {{ user.id }}: {{ user.name }}
            </li>
        </ul>
        <button @click="loadUsers">Load Users</button>
    </div>
</template>
```

```svelte tab=Svelte
<script>
import { useJsonStream } from "@laravel/stream-svelte";

const stream = useJsonStream("users");

const loadUsers = () => {
    stream.send({
        query: "taylor",
    });
};
</script>

<div>
    <ul>
        {#if $stream.data?.users}
            {#each $stream.data.users as user (user.id)}
                <li>{user.id}: {user.name}</li>
            {/each}
        {/if}
    </ul>
    <button onclick={loadUsers}>Load Users</button>
</div>
```

<a name="event-streams"></a>
### Потоки подій (SSE)

Метод `eventStream` дозволяє повернути потокову відповідь із подіями, надісланими сервером (SSE), із типом вмісту `text/event-stream`. Метод `eventStream` приймає замикання, яке має [віддавати](https://www.php.net/manual/en/language.generators.overview.php) відповіді в потік у міру їх появи:

```php
Route::get('/chat', function () {
    return response()->eventStream(function () {
        $stream = OpenAI::client()->chat()->createStreamed(...);

        foreach ($stream as $response) {
            yield $response->choices[0];
        }
    });
});
```

Якщо ви хочете налаштувати ім'я події, віддавайте екземпляр класу `StreamedEvent`:

```php
use Illuminate\Http\StreamedEvent;

yield new StreamedEvent(
    event: 'update',
    data: $response->choices[0],
);
```

<a name="consuming-event-streams"></a>
#### Споживання потоків подій

Потоки подій можна споживати за допомогою npm-пакета `stream` від Laravel, який надає зручний API для роботи з потоками подій Laravel. Щоб почати, встановіть пакет `@laravel/stream-react`, `@laravel/stream-vue` або `@laravel/stream-svelte`:

```shell tab=React
npm install @laravel/stream-react
```

```shell tab=Vue
npm install @laravel/stream-vue
```

```shell tab=Svelte
npm install @laravel/stream-svelte
```

Далі скористайтеся `useEventStream`, щоб споживати потік подій. Після вказання URL потоку хук автоматично оновлюватиме `message` об'єднаною відповіддю в міру надходження повідомлень від вашого застосунку Laravel:

```jsx tab=React
import { useEventStream } from "@laravel/stream-react";

function App() {
  const { message } = useEventStream("/chat");

  return <div>{message}</div>;
}
```

```vue tab=Vue
<script setup lang="ts">
import { useEventStream } from "@laravel/stream-vue";

const { message } = useEventStream("/chat");
</script>

<template>
  <div>{{ message }}</div>
</template>
```

```svelte tab=Svelte
<script>
import { useEventStream } from "@laravel/stream-svelte";

const eventStream = useEventStream("/chat");
</script>

<div>{$eventStream.message}</div>
```

Другий аргумент, переданий `useEventStream`, - це об'єкт опцій, яким можна налаштувати поведінку споживання потоку. Типові значення цього об'єкта наведено нижче:

```jsx tab=React
import { useEventStream } from "@laravel/stream-react";

function App() {
  const { message } = useEventStream("/stream", {
    eventName: "update",
    onMessage: (message) => {
      //
    },
    onError: (error) => {
      //
    },
    onComplete: () => {
      //
    },
    endSignal: "</stream>",
    glue: " ",
  });

  return <div>{message}</div>;
}
```

```vue tab=Vue
<script setup lang="ts">
import { useEventStream } from "@laravel/stream-vue";

const { message } = useEventStream("/chat", {
  eventName: "update",
  onMessage: (message) => {
    // ...
  },
  onError: (error) => {
    // ...
  },
  onComplete: () => {
    // ...
  },
  endSignal: "</stream>",
  glue: " ",
});
</script>
```

```svelte tab=Svelte
<script>
import { useEventStream } from "@laravel/stream-svelte";

const eventStream = useEventStream("/chat", {
    eventName: "update",
    onMessage: (event) => {
        //
    },
    onError: (error) => {
        //
    },
    onComplete: () => {
        //
    },
    endSignal: "</stream>",
    glue: " ",
    replace: false,
});
</script>
```

Потоки подій можна також споживати вручну через об'єкт [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) на фронтенді вашого застосунку. Метод `eventStream` автоматично надішле в потік оновлення `</stream>`, коли потік завершиться:

```js
const source = new EventSource('/chat');

source.addEventListener('update', (event) => {
    if (event.data === '</stream>') {
        source.close();

        return;
    }

    console.log(event.data);
});
```

Щоб налаштувати останню подію, яку надсилають у потік, передайте екземпляр `StreamedEvent` в аргумент `endStreamWith` методу `eventStream`:

```php
return response()->eventStream(function () {
    // ...
}, endStreamWith: new StreamedEvent(event: 'update', data: '</stream>'));
```

<a name="streamed-downloads"></a>
### Потокові завантаження

Іноді ви можете захотіти перетворити рядкову відповідь певної операції на завантажуваний файл, не записуючи її вміст на диск. У такому разі скористайтеся методом `streamDownload`. Він приймає колбек, ім'я файлу та необов'язковий масив заголовків:

```php
use App\Services\GitHub;

return response()->streamDownload(function () {
    echo GitHub::api('repo')
        ->contents()
        ->readme('laravel', 'laravel')['contents'];
}, 'laravel-readme.md');
```

<a name="response-macros"></a>
## Макроси відповідей

Якщо ви хочете визначити власну відповідь, яку можна повторно використовувати в різних маршрутах і контролерах, скористайтеся методом `macro` фасаду `Response`. Зазвичай цей метод варто викликати з методу `boot` одного із [сервіс-провайдерів](/docs/{{version}}/providers) вашого застосунку - наприклад, `App\Providers\AppServiceProvider`:

```php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Response;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Response::macro('caps', function (string $value) {
            return Response::make(strtoupper($value));
        });
    }
}
```

Функція `macro` приймає ім'я першим аргументом і замикання другим. Замикання макроса виконуватиметься під час виклику імені макроса на реалізації `ResponseFactory` чи хелпері `response`:

```php
return response()->caps('foo');
```

---

Джерело: https://laravelukraine.com/docs/13.x/views

# Представлення

- [Вступ](#introduction)
    - [Написання представлень на React / Svelte / Vue](#writing-views-in-react-svelte-or-vue)
- [Створення та рендеринг представлень](#creating-and-rendering-views)
    - [Вкладені каталоги представлень](#nested-view-directories)
    - [Створення першого доступного представлення](#creating-the-first-available-view)
    - [Визначення наявності представлення](#determining-if-a-view-exists)
- [Передавання даних до представлень](#passing-data-to-views)
    - [Спільні дані для всіх представлень](#sharing-data-with-all-views)
- [Компоновники представлень](#view-composers)
    - [Творці представлень](#view-creators)
- [Оптимізація представлень](#optimizing-views)

<a name="introduction"></a>
## Вступ

Звісно, повертати цілі рядки HTML-документів безпосередньо з маршрутів і контролерів непрактично. На щастя, представлення дають зручний спосіб розмістити весь наш HTML в окремих файлах.

Представлення відокремлюють логіку контролера чи застосунку від логіки відображення і зберігаються в каталозі `resources/views`. У Laravel шаблони представлень зазвичай пишуть [мовою шаблонів Blade](/docs/{{version}}/blade). Просте представлення може виглядати так:

```blade
<!-- View stored in resources/views/greeting.blade.php -->

<html>
    <body>
        <h1>Hello, {{ $name }}</h1>
    </body>
</html>
```

Оскільки це представлення зберігається за шляхом `resources/views/greeting.blade.php`, ми можемо повернути його за допомогою глобального хелпера `view`:

```php
Route::get('/', function () {
    return view('greeting', ['name' => 'James']);
});
```

> [!NOTE]
> Шукаєте докладнішу інформацію про написання шаблонів Blade? Перегляньте повну [документацію Blade](/docs/{{version}}/blade), щоб почати.

<a name="writing-views-in-react-svelte-or-vue"></a>
### Написання представлень на React / Svelte / Vue

Замість писати шаблони фронтенду на PHP через Blade, багато розробників почали віддавати перевагу React, Svelte чи Vue. Laravel робить це безболісним завдяки [Inertia](https://inertiajs.com/) - бібліотеці, яка легко пов'язує ваш фронтенд на React / Svelte / Vue із бекендом на Laravel без типових складнощів створення SPA.

Наші [стартові набори застосунків для React, Svelte і Vue](/docs/{{version}}/starter-kits) дають чудову відправну точку для вашого наступного застосунку Laravel на основі Inertia.

<a name="creating-and-rendering-views"></a>
## Створення та рендеринг представлень

Ви можете створити представлення, розмістивши файл із розширенням `.blade.php` у каталозі `resources/views` вашого застосунку або скориставшись командою Artisan `make:view`:

```shell
php artisan make:view greeting
```

Розширення `.blade.php` повідомляє фреймворку, що файл містить [шаблон Blade](/docs/{{version}}/blade). Шаблони Blade містять HTML, а також директиви Blade, які дозволяють легко виводити значення, створювати умови «if», ітерувати дані тощо.

Створивши представлення, ви можете повернути його з одного з маршрутів чи контролерів свого застосунку за допомогою глобального хелпера `view`:

```php
Route::get('/', function () {
    return view('greeting', ['name' => 'James']);
});
```

Представлення також можна повертати через фасад `View`:

```php
use Illuminate\Support\Facades\View;

return View::make('greeting', ['name' => 'James']);
```

Як бачите, перший аргумент, переданий хелперу `view`, відповідає імені файлу представлення в каталозі `resources/views`. Другий аргумент - масив даних, які мають бути доступні представленню. У цьому випадку ми передаємо змінну `name`, яка виводиться в представленні за допомогою [синтаксису Blade](/docs/{{version}}/blade).

<a name="nested-view-directories"></a>
### Вкладені каталоги представлень

Представлення також можуть бути вкладені в підкаталоги каталогу `resources/views`. Для звернення до вкладених представлень можна використовувати «крапкову» нотацію. Наприклад, якщо ваше представлення зберігається за шляхом `resources/views/admin/profile.blade.php`, ви можете повернути його з маршруту чи контролера так:

```php
return view('admin.profile', $data);
```

> [!WARNING]
> Імена каталогів представлень не повинні містити символ `.`.

<a name="creating-the-first-available-view"></a>
### Створення першого доступного представлення

За допомогою методу `first` фасаду `View` ви можете створити перше представлення, що існує в переданому масиві представлень. Це може бути корисно, якщо ваш застосунок чи пакет дозволяє налаштовувати або перевизначати представлення:

```php
use Illuminate\Support\Facades\View;

return View::first(['custom.admin', 'admin'], $data);
```

<a name="determining-if-a-view-exists"></a>
### Визначення наявності представлення

Якщо вам потрібно визначити, чи існує представлення, скористайтеся фасадом `View`. Метод `exists` поверне `true`, якщо представлення існує:

```php
use Illuminate\Support\Facades\View;

if (View::exists('admin.profile')) {
    // ...
}
```

<a name="passing-data-to-views"></a>
## Передавання даних до представлень

Як ви бачили в попередніх прикладах, ви можете передати представленню масив даних, щоб ці дані стали йому доступні:

```php
return view('greetings', ['name' => 'Victoria']);
```

Передаючи інформацію в такий спосіб, дані мають бути масивом пар «ключ - значення». Надавши дані представленню, ви можете звертатися до кожного значення в ньому за ключами - наприклад, `<?php echo $name; ?>`.

Як альтернативу передаванню повного масиву даних функції-хелперу `view`, ви можете скористатися методом `with`, щоб додавати до представлення окремі фрагменти даних. Метод `with` повертає екземпляр об'єкта представлення, тож ви можете продовжувати ланцюжок методів перед поверненням представлення:

```php
return view('greeting')
    ->with('name', 'Victoria')
    ->with('occupation', 'Astronaut');
```

<a name="sharing-data-with-all-views"></a>
### Спільні дані для всіх представлень

Подекуди вам може знадобитися надати дані всім представленням, які рендерить ваш застосунок. Це можна зробити методом `share` фасаду `View`. Зазвичай виклики `share` варто розміщувати в методі `boot` сервіс-провайдера. Ви вільні додати їх до класу `App\Providers\AppServiceProvider` або створити для цього окремий сервіс-провайдер:

```php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        View::share('key', 'value');
    }
}
```

<a name="view-composers"></a>
## Компоновники представлень

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

Зазвичай компоновники представлень реєструються в одному із [сервіс-провайдерів](/docs/{{version}}/providers) вашого застосунку. У цьому прикладі ми припустимо, що цю логіку міститиме `App\Providers\AppServiceProvider`.

Ми скористаємося методом `composer` фасаду `View`, щоб зареєструвати компоновник. Laravel не має типового каталогу для компоновників на основі класів, тож ви вільні організувати їх як завгодно. Наприклад, ви можете створити каталог `app/View/Composers` для всіх компоновників вашого застосунку:

```php
<?php

namespace App\Providers;

use App\View\Composers\ProfileComposer;
use Illuminate\Support\Facades;
use Illuminate\Support\ServiceProvider;
use Illuminate\View\View;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        // Using class-based composers...
        Facades\View::composer('profile', ProfileComposer::class);

        // Using closure-based composers...
        Facades\View::composer('welcome', function (View $view) {
            // ...
        });

        Facades\View::composer('dashboard', function (View $view) {
            // ...
        });
    }
}
```

Тепер, коли ми зареєстрували компоновник, метод `compose` класу `App\View\Composers\ProfileComposer` виконуватиметься щоразу, коли рендериться представлення `profile`. Погляньмо на приклад класу компоновника:

```php
<?php

namespace App\View\Composers;

use App\Repositories\UserRepository;
use Illuminate\View\View;

class ProfileComposer
{
    /**
     * Create a new profile composer.
     */
    public function __construct(
        protected UserRepository $users,
    ) {}

    /**
     * Bind data to the view.
     */
    public function compose(View $view): void
    {
        $view->with('count', $this->users->count());
    }
}
```

Як бачите, усі компоновники представлень розв'язуються через [сервіс-контейнер](/docs/{{version}}/container), тож ви можете вказати типи будь-яких потрібних залежностей у конструкторі компоновника.

<a name="attaching-a-composer-to-multiple-views"></a>
#### Прикріплення компоновника до кількох представлень

Ви можете прикріпити компоновник одразу до кількох представлень, передавши масив представлень першим аргументом методу `composer`:

```php
use App\Views\Composers\MultiComposer;
use Illuminate\Support\Facades\View;

View::composer(
    ['profile', 'dashboard'],
    MultiComposer::class
);
```

Метод `composer` також приймає символ `*` як шаблон, що дозволяє прикріпити компоновник до всіх представлень:

```php
use Illuminate\Support\Facades;
use Illuminate\View\View;

Facades\View::composer('*', function (View $view) {
    // ...
});
```

<a name="view-creators"></a>
### Творці представлень

«Творці» представлень дуже схожі на компоновників, однак виконуються одразу після створення екземпляра представлення, а не чекають моменту рендерингу. Щоб зареєструвати творця представлення, скористайтеся методом `creator`:

```php
use App\View\Creators\ProfileCreator;
use Illuminate\Support\Facades\View;

View::creator('profile', ProfileCreator::class);
```

<a name="optimizing-views"></a>
## Оптимізація представлень

За замовчуванням представлення на шаблонах Blade компілюються на вимогу. Коли виконується запит, що рендерить представлення, Laravel визначає, чи існує скомпільована версія. Якщо файл існує, Laravel перевіряє, чи змінювалося нескомпільоване представлення пізніше за скомпільоване. Якщо скомпільованого представлення немає або нескомпільоване було змінено, Laravel перекомпілює його.

Компіляція представлень під час запиту може трохи негативно вплинути на швидкодію, тож Laravel надає команду Artisan `view:cache` для попередньої компіляції всіх представлень, які використовує ваш застосунок. Задля кращої швидкодії варто виконувати цю команду як частину процесу розгортання:

```shell
php artisan view:cache
```

Щоб очистити кеш представлень, скористайтеся командою `view:clear`:

```shell
php artisan view:clear
```

---

Джерело: https://laravelukraine.com/docs/13.x/blade

# Шаблони Blade

- [Вступ](#introduction)
    - [Підсилення Blade за допомогою Livewire](#supercharging-blade-with-livewire)
- [Виведення даних](#displaying-data)
    - [Кодування HTML-сутностей](#html-entity-encoding)
    - [Blade та JavaScript-фреймворки](#blade-and-javascript-frameworks)
- [Директиви Blade](#blade-directives)
    - [Умовні конструкції](#if-statements)
    - [Конструкції switch](#switch-statements)
    - [Цикли](#loops)
    - [Змінна циклу](#the-loop-variable)
    - [Умовні класи](#conditional-classes)
    - [Додаткові атрибути](#additional-attributes)
    - [Включення підпредставлень](#including-subviews)
    - [Директива `@once`](#the-once-directive)
    - [Чистий PHP](#raw-php)
    - [Шрифти](#fonts)
    - [Коментарі](#comments)
- [Компоненти](#components)
    - [Рендеринг компонентів](#rendering-components)
    - [Індексні компоненти](#index-components)
    - [Передавання даних до компонентів](#passing-data-to-components)
    - [Атрибути компонентів](#component-attributes)
    - [Зарезервовані ключові слова](#reserved-keywords)
    - [Слоти](#slots)
    - [Вбудовані представлення компонентів](#inline-component-views)
    - [Динамічні компоненти](#dynamic-components)
    - [Ручна реєстрація компонентів](#manually-registering-components)
- [Анонімні компоненти](#anonymous-components)
    - [Анонімні індексні компоненти](#anonymous-index-components)
    - [Властивості даних та атрибути](#data-properties-attributes)
    - [Доступ до даних батьківського компонента](#accessing-parent-data)
    - [Шляхи анонімних компонентів](#anonymous-component-paths)
- [Створення макетів](#building-layouts)
    - [Макети на компонентах](#layouts-using-components)
    - [Макети на успадкуванні шаблонів](#layouts-using-template-inheritance)
- [Форми](#forms)
    - [Поле CSRF](#csrf-field)
    - [Поле методу](#method-field)
    - [Помилки валідації](#validation-errors)
- [Стеки](#stacks)
- [Впровадження сервісів](#service-injection)
- [Рендеринг вбудованих шаблонів Blade](#rendering-inline-blade-templates)
- [Рендеринг фрагментів Blade](#rendering-blade-fragments)
- [Розширення Blade](#extending-blade)
    - [Власні обробники виведення](#custom-echo-handlers)
    - [Власні умовні конструкції](#custom-if-statements)

<a name="introduction"></a>
## Вступ

Blade - це простий, але потужний шаблонізатор, що входить до складу Laravel. На відміну від деяких PHP-шаблонізаторів, Blade не забороняє використовувати звичайний PHP-код у ваших шаблонах. Насправді всі шаблони Blade компілюються у звичайний PHP-код і кешуються, доки їх не змінено, тобто Blade практично не додає накладних витрат вашому застосунку. Файли шаблонів Blade мають розширення `.blade.php` і зазвичай зберігаються в каталозі `resources/views`.

Представлення Blade можна повертати з маршрутів чи контролерів за допомогою глобального хелпера `view`. Звісно, як зазначено в документації з [представлень](/docs/{{version}}/views), дані можна передати представленню Blade другим аргументом хелпера `view`:

```php
Route::get('/', function () {
    return view('greeting', ['name' => 'Finn']);
});
```

<a name="supercharging-blade-with-livewire"></a>
### Підсилення Blade за допомогою Livewire

Хочете вивести свої шаблони Blade на новий рівень і легко створювати динамічні інтерфейси? Перегляньте [Laravel Livewire](https://livewire.laravel.com). Livewire дозволяє писати компоненти Blade, доповнені динамічною функціональністю, яка зазвичай можлива лише через фронтенд-фреймворки на кшталт React, Svelte чи Vue, - це чудовий підхід до створення сучасних реактивних інтерфейсів без складнощів, клієнтського рендерингу чи етапів збірки, властивих багатьом JavaScript-фреймворкам.

<a name="displaying-data"></a>
## Виведення даних

Ви можете виводити дані, передані вашим представленням Blade, узявши змінну у фігурні дужки. Наприклад, маючи такий маршрут:

```php
Route::get('/', function () {
    return view('welcome', ['name' => 'Samantha']);
});
```

Ви можете вивести вміст змінної `name` так:

```blade
Hello, {{ $name }}.
```

> [!NOTE]
> Інструкції виведення `{{ }}` у Blade автоматично проходять через PHP-функцію `htmlspecialchars`, щоб запобігти XSS-атакам.

Ви не обмежені виведенням лише переданих представленню змінних. Ви також можете вивести результат будь-якої PHP-функції. Насправді ви можете розмістити всередині інструкції виведення Blade будь-який PHP-код:

```blade
The current UNIX timestamp is {{ time() }}.
```

<a name="html-entity-encoding"></a>
### Кодування HTML-сутностей

За замовчуванням Blade (і функція Laravel `e`) виконує подвійне кодування HTML-сутностей. Якщо ви хочете вимкнути подвійне кодування, викличте метод `Blade::withoutDoubleEncoding` у методі `boot` вашого `AppServiceProvider`:

```php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Blade::withoutDoubleEncoding();
    }
}
```

<a name="displaying-unescaped-data"></a>
#### Виведення неекранованих даних

За замовчуванням інструкції `{{ }}` у Blade автоматично проходять через PHP-функцію `htmlspecialchars`, щоб запобігти XSS-атакам. Якщо ви не хочете, щоб ваші дані екранувалися, скористайтеся таким синтаксисом:

```blade
Hello, {!! $name !!}.
```

> [!WARNING]
> Будьте дуже обережні, виводячи вміст, наданий користувачами вашого застосунку. Зазвичай, показуючи надані користувачами дані, слід використовувати екранований синтаксис із подвійними фігурними дужками, щоб запобігти XSS-атакам.

<a name="blade-and-javascript-frameworks"></a>
### Blade та JavaScript-фреймворки

Оскільки багато JavaScript-фреймворків теж використовують фігурні дужки, щоб позначити вираз для виведення в браузері, ви можете скористатися символом `@`, щоб повідомити рушій Blade, що вираз слід залишити недоторканим. Наприклад:

```blade
<h1>Laravel</h1>

Hello, @{{ name }}.
```

У цьому прикладі Blade прибере символ `@`; однак вираз `{{ name }}` лишиться недоторканим рушієм Blade, що дозволить вашому JavaScript-фреймворку відрендерити його.

Символ `@` можна також використовувати, щоб екранувати директиви Blade:

```blade
{{-- Blade template --}}
@@if()

<!-- HTML output -->
@if()
```

<a name="rendering-json"></a>
#### Рендеринг JSON

Іноді ви можете передавати представленню масив, щоб відрендерити його як JSON і ініціалізувати JavaScript-змінну. Наприклад:

```blade
<script>
    var app = <?php echo json_encode($array); ?>;
</script>
```

Однак замість викликати `json_encode` вручну, ви можете скористатися методом `Illuminate\Support\Js::from`. Метод `from` приймає ті самі аргументи, що й PHP-функція `json_encode`; однак він гарантує, що отриманий JSON правильно екрановано для вставки в HTML-лапки. Метод `from` поверне рядок з інструкцією JavaScript `JSON.parse`, яка перетворить переданий об'єкт чи масив на дійсний об'єкт JavaScript:

```blade
<script>
    var app = {{ Illuminate\Support\Js::from($array) }};
</script>
```

Найновіші версії каркаса застосунку Laravel містять фасад `Js`, що дає зручний доступ до цієї функціональності у ваших шаблонах Blade:

```blade
<script>
    var app = {{ Js::from($array) }};
</script>
```

> [!WARNING]
> Метод `Js::from` слід використовувати лише для рендерингу наявних змінних як JSON. Шаблонізатор Blade побудований на регулярних виразах, і спроби передати директиві складний вираз можуть спричинити несподівані збої.

<a name="the-at-verbatim-directive"></a>
#### Директива `@verbatim`

Якщо ви виводите JavaScript-змінні у великій частині свого шаблону, ви можете обгорнути HTML директивою `@verbatim`, щоб не додавати символ `@` до кожної інструкції виведення Blade:

```blade
@verbatim
    <div class="container">
        Hello, {{ name }}.
    </div>
@endverbatim
```

<a name="blade-directives"></a>
## Директиви Blade

Окрім успадкування шаблонів і виведення даних, Blade також надає зручні скорочення для поширених керуючих конструкцій PHP - як-от умовних конструкцій і циклів. Ці скорочення дають дуже чистий і стислий спосіб працювати з керуючими конструкціями PHP, залишаючись водночас звичними для їхніх PHP-відповідників.

<a name="if-statements"></a>
### Умовні конструкції

Ви можете створювати конструкції `if` за допомогою директив `@if`, `@elseif`, `@else` та `@endif`. Вони працюють ідентично до своїх PHP-відповідників:

```blade
@if (count($records) === 1)
    I have one record!
@elseif (count($records) > 1)
    I have multiple records!
@else
    I don't have any records!
@endif
```

Для зручності Blade також надає директиву `@unless`:

```blade
@unless (Auth::check())
    You are not signed in.
@endunless
```

Окрім уже розглянутих умовних директив, директиви `@isset` та `@empty` можна використовувати як зручні скорочення для відповідних PHP-функцій:

```blade
@isset($records)
    // $records is defined and is not null...
@endisset

@empty($records)
    // $records is "empty"...
@endempty
```

<a name="authentication-directives"></a>
#### Директиви автентифікації

Директиви `@auth` та `@guest` дозволяють швидко визначити, чи є поточний користувач [автентифікованим](/docs/{{version}}/authentication), чи гостем:

```blade
@auth
    // The user is authenticated...
@endauth

@guest
    // The user is not authenticated...
@endguest
```

За потреби ви можете вказати гард автентифікації, який слід перевіряти, використовуючи директиви `@auth` та `@guest`:

```blade
@auth('admin')
    // The user is authenticated...
@endauth

@guest('admin')
    // The user is not authenticated...
@endguest
```

<a name="environment-directives"></a>
#### Директиви середовища

Перевірити, чи працює застосунок у продакшен-середовищі, можна директивою `@production`:

```blade
@production
    // Production specific content...
@endproduction
```

Або ж ви можете визначити, чи працює застосунок у конкретному середовищі, директивою `@env`:

```blade
@env('staging')
    // The application is running in "staging"...
@endenv

@env(['staging', 'production'])
    // The application is running in "staging" or "production"...
@endenv
```

<a name="section-directives"></a>
#### Директиви секцій

Ви можете визначити, чи має секція успадкування шаблонів вміст, директивою `@hasSection`:

```blade
@hasSection('navigation')
    <div class="pull-right">
        @yield('navigation')
    </div>

    <div class="clearfix"></div>
@endif
```

Директива `sectionMissing` дозволяє визначити, що секція не має вмісту:

```blade
@sectionMissing('navigation')
    <div class="pull-right">
        @include('default-navigation')
    </div>
@endif
```

<a name="session-directives"></a>
#### Директиви сесії

Директива `@session` дозволяє визначити, чи існує значення [сесії](/docs/{{version}}/session). Якщо воно існує, вміст шаблону між директивами `@session` та `@endsession` буде обчислено. Усередині директиви `@session` ви можете вивести змінну `$value`, щоб показати значення сесії:

```blade
@session('status')
    <div class="p-4 bg-green-100">
        {{ $value }}
    </div>
@endsession
```

<a name="context-directives"></a>
#### Директиви контексту

Директива `@context` дозволяє визначити, чи існує значення [контексту](/docs/{{version}}/context). Якщо воно існує, вміст шаблону між директивами `@context` та `@endcontext` буде обчислено. Усередині директиви `@context` ви можете вивести змінну `$value`, щоб показати значення контексту:

```blade
@context('canonical')
    <link href="{{ $value }}" rel="canonical">
@endcontext
```

<a name="switch-statements"></a>
### Конструкції switch

Конструкції switch можна створювати за допомогою директив `@switch`, `@case`, `@break`, `@default` та `@endswitch`:

```blade
@switch($i)
    @case(1)
        First case...
        @break

    @case(2)
        Second case...
        @break

    @default
        Default case...
@endswitch
```

<a name="loops"></a>
### Цикли

Окрім умовних конструкцій, Blade надає прості директиви для роботи з циклами PHP. Знову ж таки, кожна з них працює ідентично до свого PHP-відповідника:

```blade
@for ($i = 0; $i < 10; $i++)
    The current value is {{ $i }}
@endfor

@foreach ($users as $user)
    <p>This is user {{ $user->id }}</p>
@endforeach

@forelse ($users as $user)
    <li>{{ $user->name }}</li>
@empty
    <p>No users</p>
@endforelse

@while (true)
    <p>I'm looping forever.</p>
@endwhile
```

> [!NOTE]
> Ітеруючи в циклі `foreach`, ви можете скористатися [змінною циклу](#the-loop-variable), щоб отримати корисну інформацію про цикл - наприклад, чи це перша або остання ітерація.

Використовуючи цикли, ви також можете пропустити поточну ітерацію чи завершити цикл директивами `@continue` та `@break`:

```blade
@foreach ($users as $user)
    @if ($user->type == 1)
        @continue
    @endif

    <li>{{ $user->name }}</li>

    @if ($user->number == 5)
        @break
    @endif
@endforeach
```

Ви також можете вказати умову продовження чи переривання просто в оголошенні директиви:

```blade
@foreach ($users as $user)
    @continue($user->type == 1)

    <li>{{ $user->name }}</li>

    @break($user->number == 5)
@endforeach
```

<a name="the-loop-variable"></a>
### Змінна циклу

Ітеруючи в циклі `foreach`, усередині циклу вам буде доступна змінна `$loop`. Вона дає доступ до корисної інформації - як-от поточний індекс циклу та чи є ця ітерація першою або останньою:

```blade
@foreach ($users as $user)
    @if ($loop->first)
        This is the first iteration.
    @endif

    @if ($loop->last)
        This is the last iteration.
    @endif

    <p>This is user {{ $user->id }}</p>
@endforeach
```

Якщо ви у вкладеному циклі, ви можете звернутися до змінної `$loop` батьківського циклу через властивість `parent`:

```blade
@foreach ($users as $user)
    @foreach ($user->posts as $post)
        @if ($loop->parent->first)
            This is the first iteration of the parent loop.
        @endif
    @endforeach
@endforeach
```

Змінна `$loop` також містить низку інших корисних властивостей:

<div class="overflow-auto">

| Властивість        | Опис                                                   |
| ------------------ | ------------------------------------------------------ |
| `$loop->index`     | Індекс поточної ітерації циклу (починається з 0).      |
| `$loop->iteration` | Поточна ітерація циклу (починається з 1).              |
| `$loop->remaining` | Скільки ітерацій лишилося в циклі.                     |
| `$loop->count`     | Загальна кількість елементів у масиві, який ітерується.|
| `$loop->first`     | Чи є ця ітерація першою в циклі.                       |
| `$loop->last`      | Чи є ця ітерація останньою в циклі.                    |
| `$loop->even`      | Чи є ця ітерація парною.                               |
| `$loop->odd`       | Чи є ця ітерація непарною.                             |
| `$loop->depth`     | Рівень вкладеності поточного циклу.                    |
| `$loop->parent`    | У вкладеному циклі - змінна циклу батька.              |

</div>

<a name="conditional-classes"></a>
### Умовні класи та стилі

Директива `@class` умовно компілює рядок CSS-класів. Вона приймає масив класів, де ключ містить клас або класи, які ви хочете додати, а значення є булевим виразом. Якщо елемент масиву має числовий ключ, його буде включено завжди:

```blade
@php
    $isActive = false;
    $hasError = true;
@endphp

<span @class([
    'p-4',
    'font-bold' => $isActive,
    'text-gray-500' => ! $isActive,
    'bg-red' => $hasError,
])></span>

<span class="p-4 text-gray-500 bg-red"></span>
```

Так само директива `@style` дозволяє умовно додавати вбудовані CSS-стилі до HTML-елемента:

```blade
@php
    $isActive = true;
@endphp

<span @style([
    'background-color: red',
    'font-weight: bold' => $isActive,
])></span>

<span style="background-color: red; font-weight: bold;"></span>
```

<a name="additional-attributes"></a>
### Додаткові атрибути

Для зручності ви можете скористатися директивою `@checked`, щоб легко вказати, чи є певний HTML-чекбокс «позначеним». Ця директива виведе `checked`, якщо передана умова дає `true`:

```blade
<input
    type="checkbox"
    name="active"
    value="active"
    @checked(old('active', $user->active))
/>
```

Так само директива `@selected` дозволяє вказати, чи має бути обраний певний варіант у списку:

```blade
<select name="version">
    @foreach ($product->versions as $version)
        <option value="{{ $version }}" @selected(old('version') == $version)>
            {{ $version }}
        </option>
    @endforeach
</select>
```

Крім того, директива `@disabled` дозволяє вказати, чи має елемент бути вимкненим:

```blade
<button type="submit" @disabled($errors->isNotEmpty())>Submit</button>
```

Ба більше, директива `@readonly` дозволяє вказати, чи має елемент бути лише для читання:

```blade
<input
    type="email"
    name="email"
    value="email@laravel.com"
    @readonly($user->isNotAdmin())
/>
```

Крім того, директива `@required` дозволяє вказати, чи є елемент обов'язковим:

```blade
<input
    type="text"
    name="title"
    value="title"
    @required($user->isAdmin())
/>
```

<a name="including-subviews"></a>
### Включення підпредставлень

> [!NOTE]
> Хоча ви вільні використовувати директиву `@include`, [компоненти](#components) Blade дають схожу функціональність і кілька переваг над `@include` - як-от прив'язку даних і атрибутів.

Директива `@include` у Blade дозволяє включити одне представлення Blade всередину іншого. Усі змінні, доступні батьківському представленню, будуть доступні й включеному:

```blade
<div>
    @include('shared.errors')

    <form>
        <!-- Form Contents -->
    </form>
</div>
```

Хоча включене представлення успадкує всі дані батьківського, ви також можете передати масив додаткових даних:

```blade
@include('view.name', ['status' => 'complete'])
```

Якщо ви спробуєте включити (`@include`) представлення, якого не існує, Laravel викине помилку. Якщо ви хочете включити представлення, яке може існувати, а може й ні, скористайтеся директивою `@includeIf`:

```blade
@includeIf('view.name', ['status' => 'complete'])
```

Якщо ви хочете включити представлення, коли булевий вираз дає `true` чи `false`, скористайтеся директивами `@includeWhen` та `@includeUnless`:

```blade
@includeWhen($boolean, 'view.name', ['status' => 'complete'])

@includeUnless($boolean, 'view.name', ['status' => 'complete'])
```

Щоб включити перше наявне представлення з переданого масиву, скористайтеся директивою `includeFirst`:

```blade
@includeFirst(['custom.admin', 'admin'], ['status' => 'complete'])
```

Якщо ви хочете включити представлення, не успадковуючи змінних батьківського, скористайтеся директивою `@includeIsolated`. Включене представлення матиме доступ лише до змінних, які ви передасте явно:

```blade
@includeIsolated('view.name', ['user' => $user])
```

> [!WARNING]
> Уникайте використання констант `__DIR__` і `__FILE__` у своїх представленнях Blade, адже вони вказуватимуть на розташування закешованого скомпільованого представлення.

<a name="rendering-views-for-collections"></a>
#### Рендеринг представлень для колекцій

Ви можете поєднати цикли та включення в один рядок директивою `@each`:

```blade
@each('view.name', $jobs, 'job')
```

Перший аргумент директиви `@each` - представлення для рендерингу кожного елемента масиву чи колекції. Другий - масив чи колекція, яку ви хочете ітерувати, а третій - ім'я змінної, яку буде призначено поточній ітерації всередині представлення. Тож, наприклад, якщо ви ітеруєте масив `jobs`, зазвичай ви захочете звертатися до кожного завдання як до змінної `job`. Ключ масиву для поточної ітерації буде доступний у представленні як змінна `key`.

Директиві `@each` можна також передати четвертий аргумент. Він визначає представлення, яке буде відрендерено, якщо переданий масив порожній.

```blade
@each('view.name', $jobs, 'job', 'view.empty')
```

> [!WARNING]
> Представлення, відрендерені через `@each`, не успадковують змінних батьківського представлення. Якщо дочірньому представленню потрібні ці змінні, скористайтеся натомість директивами `@foreach` та `@include`.

<a name="the-once-directive"></a>
### Директива `@once`

Директива `@once` дозволяє визначити частину шаблону, яка буде обчислена лише один раз за цикл рендерингу. Це може бути корисно, щоб додати певний JavaScript у шапку сторінки через [стеки](#stacks). Наприклад, якщо ви рендерите [компонент](#components) у циклі, ви можете захотіти додати JavaScript у шапку лише під час першого рендерингу компонента:

```blade
@once
    @push('scripts')
        <script>
            // Your custom JavaScript...
        </script>
    @endpush
@endonce
```

Оскільки директиву `@once` часто використовують разом із `@push` чи `@prepend`, для зручності доступні директиви `@pushOnce` та `@prependOnce`:

```blade
@pushOnce('scripts')
    <script>
        // Your custom JavaScript...
    </script>
@endPushOnce
```

Якщо ви додаєте однаковий вміст із двох різних шаблонів Blade, передайте унікальний ідентифікатор другим аргументом директиви `@pushOnce`, щоб вміст відрендерився лише раз:

```blade
<!-- pie-chart.blade.php -->
@pushOnce('scripts', 'chart.js')
    <script src="/chart.js"></script>
@endPushOnce

<!-- line-chart.blade.php -->
@pushOnce('scripts', 'chart.js')
    <script src="/chart.js"></script>
@endPushOnce
```

<a name="raw-php"></a>
### Чистий PHP

У деяких ситуаціях корисно вставити PHP-код у свої представлення. Ви можете скористатися директивою Blade `@php`, щоб виконати блок звичайного PHP у своєму шаблоні:

```blade
@php
    $counter = 1;
@endphp
```

Або, якщо PHP потрібен вам лише для імпорту класу, скористайтеся директивою `@use`:

```blade
@use('App\Models\Flight')
```

Директиві `@use` можна передати другий аргумент, щоб задати псевдонім імпортованому класу:

```blade
@use('App\Models\Flight', 'FlightModel')
```

Якщо у вас кілька класів в одному просторі імен, ви можете згрупувати їхні імпорти:

```blade
@use('App\Models\{Flight, Airport}')
```

Директива `@use` також підтримує імпорт PHP-функцій і констант - для цього додайте до шляху імпорту модифікатор `function` чи `const`:

```blade
@use(function App\Helpers\format_currency)
@use(const App\Constants\MAX_ATTEMPTS)
```

Як і для класів, для функцій і констант підтримуються псевдоніми:

```blade
@use(function App\Helpers\format_currency, 'formatMoney')
@use(const App\Constants\MAX_ATTEMPTS, 'MAX_TRIES')
```

Згруповані імпорти теж підтримуються з модифікаторами `function` і `const`, що дозволяє імпортувати кілька символів з одного простору імен однією директивою:

```blade
@use(function App\Helpers\{format_currency, format_date})
@use(const App\Constants\{MAX_ATTEMPTS, DEFAULT_TIMEOUT})
```

<a name="fonts"></a>
### Шрифти

Використовуючи [оптимізацію шрифтів через Vite](/docs/{{version}}/vite#working-with-fonts) у Laravel, ви можете скористатися директивою `@fonts`, щоб відрендерити налаштовані посилання попереднього завантаження шрифтів і вбудований CSS шрифтів у макеті вашого застосунку:

```blade
<!doctype html>
<head>
    {{-- ... --}}

    @fonts
    @vite('resources/js/app.js')
</head>
```

Директива `@fonts` рендерить усі родини шрифтів, налаштовані у вашому файлі `vite.config.js`. Зазвичай її слід розміщувати в `<head>` кореневого макета вашого застосунку перед будь-яким вмістом, що використовує ці шрифти.

Якщо сторінці потрібні лише деякі з налаштованих шрифтів, ви можете передати директиві один чи кілька псевдонімів:

```blade
{{-- Load a single font alias... --}}
@fonts('sans')

{{-- Load multiple font aliases... --}}
@fonts(['sans', 'mono'])
```

Псевдоніми шрифтів налаштовуються опцією `alias` під час визначення шрифтів у вашій конфігурації Vite. Директива `@fonts` викликає метод `fonts` фасаду `Vite`, який можна викликати й напряму:

```blade
{{ Vite::fonts(['sans', 'mono']) }}
```

<a name="comments"></a>
### Коментарі

Blade також дозволяє визначати коментарі у ваших представленнях. Однак, на відміну від HTML-коментарів, коментарі Blade не потрапляють до HTML, який повертає ваш застосунок:

```blade
{{-- This comment will not be present in the rendered HTML --}}
```

<a name="components"></a>
## Компоненти

Компоненти та слоти дають переваги, схожі на секції, макети та включення; утім, декому ментальна модель компонентів і слотів може здатися зрозумілішою. Є два підходи до написання компонентів: компоненти на основі класів та анонімні компоненти.

Щоб створити компонент на основі класу, скористайтеся командою Artisan `make:component`. Щоб проілюструвати використання компонентів, ми створимо простий компонент `Alert`. Команда `make:component` помістить компонент у каталог `app/View/Components`:

```shell
php artisan make:component Alert
```

Команда `make:component` також створить шаблон представлення для компонента. Представлення буде розміщено в каталозі `resources/views/components`. Пишучи компоненти для власного застосунку, ви не потребуєте додаткової реєстрації: компоненти автоматично виявляються в каталогах `app/View/Components` і `resources/views/components`.

Ви також можете створювати компоненти в підкаталогах:

```shell
php artisan make:component Forms/Input
```

Наведена вище команда створить компонент `Input` у каталозі `app/View/Components/Forms`, а представлення буде розміщено в `resources/views/components/forms`.

<a name="manually-registering-package-components"></a>
#### Ручна реєстрація компонентів пакета

Пишучи компоненти для власного застосунку, ви не потребуєте додаткової реєстрації: вони автоматично виявляються в каталогах `app/View/Components` і `resources/views/components`.

Однак якщо ви створюєте пакет, що використовує компоненти Blade, вам доведеться вручну зареєструвати клас компонента та його псевдонім HTML-тега. Зазвичай компоненти слід реєструвати в методі `boot` сервіс-провайдера вашого пакета:

```php
use Illuminate\Support\Facades\Blade;

/**
 * Bootstrap your package's services.
 */
public function boot(): void
{
    Blade::component('package-alert', Alert::class);
}
```

Щойно компонент зареєстровано, його можна відрендерити за псевдонімом тега:

```blade
<x-package-alert/>
```

Як альтернативу ви можете скористатися методом `componentNamespace`, щоб автозавантажувати класи компонентів за домовленостями. Наприклад, пакет `Nightshade` може мати компоненти `Calendar` і `ColorPicker`, розташовані у просторі імен `Nightshade\Views\Components`:

```php
use Illuminate\Support\Facades\Blade;

/**
 * Bootstrap your package's services.
 */
public function boot(): void
{
    Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}
```

Це дозволить використовувати компоненти пакета через простір імен постачальника за синтаксисом `package-name::`:

```blade
<x-nightshade::calendar />
<x-nightshade::color-picker />
```

Blade автоматично визначить клас, пов'язаний із цим компонентом, перетворивши ім'я компонента на PascalCase. Підкаталоги теж підтримуються через «крапкову» нотацію.

<a name="rendering-components"></a>
### Рендеринг компонентів

Щоб показати компонент, скористайтеся тегом компонента Blade в одному зі своїх шаблонів. Теги компонентів Blade починаються з рядка `x-`, за яким іде ім'я класу компонента в kebab-case:

```blade
<x-alert/>

<x-user-profile/>
```

Якщо клас компонента вкладено глибше в каталозі `app/View/Components`, ви можете скористатися символом `.`, щоб позначити вкладеність каталогів. Наприклад, якщо компонент розташовано в `app/View/Components/Inputs/Button.php`, ми можемо відрендерити його так:

```blade
<x-inputs.button/>
```

Якщо ви хочете рендерити компонент умовно, визначте в його класі метод `shouldRender`. Якщо `shouldRender` поверне `false`, компонент не буде відрендерено:

```php
use Illuminate\Support\Str;

/**
 * Whether the component should be rendered
 */
public function shouldRender(): bool
{
    return Str::length($this->message) > 0;
}
```

<a name="index-components"></a>
### Індексні компоненти

Іноді компоненти є частиною групи, і ви можете захотіти згрупувати пов'язані компоненти в одному каталозі. Наприклад, уявіть компонент «card» із такою структурою класів:

```text
App\View\Components\Card\Card
App\View\Components\Card\Header
App\View\Components\Card\Body
```

Оскільки кореневий компонент `Card` вкладено в каталог `Card`, можна було б очікувати, що рендерити його доведеться через `<x-card.card>`. Однак коли ім'я файлу компонента збігається з іменем його каталогу, Laravel автоматично вважає цей компонент «кореневим» і дозволяє рендерити його без повторення імені каталогу:

```blade
<x-card>
    <x-card.header>...</x-card.header>
    <x-card.body>...</x-card.body>
</x-card>
```

<a name="passing-data-to-components"></a>
### Передавання даних до компонентів

Ви можете передавати дані компонентам Blade через HTML-атрибути. Жорстко задані примітивні значення передаються звичайними рядковими атрибутами HTML. PHP-вирази та змінні слід передавати через атрибути з префіксом `:`:

```blade
<x-alert type="error" :message="$message"/>
```

Усі атрибути даних компонента слід визначити в конструкторі його класу. Усі публічні властивості компонента автоматично стануть доступними його представленню. Передавати дані представленню з методу `render` компонента не потрібно:

```php
<?php

namespace App\View\Components;

use Illuminate\View\Component;
use Illuminate\View\View;

class Alert extends Component
{
    /**
     * Create the component instance.
     */
    public function __construct(
        public string $type,
        public string $message,
    ) {}

    /**
     * Get the view / contents that represent the component.
     */
    public function render(): View
    {
        return view('components.alert');
    }
}
```

Коли ваш компонент рендериться, ви можете вивести вміст його публічних змінних, звернувшись до них за іменем:

```blade
<div class="alert alert-{{ $type }}">
    {{ $message }}
</div>
```

<a name="casing"></a>
#### Регістр

Аргументи конструктора компонента слід записувати в `camelCase`, тоді як у HTML-атрибутах на них посилаються в `kebab-case`. Наприклад, маючи такий конструктор компонента:

```php
/**
 * Create the component instance.
 */
public function __construct(
    public string $alertType,
) {}
```

Аргумент `$alertType` можна передати компоненту так:

```blade
<x-alert alert-type="danger" />
```

<a name="short-attribute-syntax"></a>
#### Короткий синтаксис атрибутів

Передаючи атрибути компонентам, ви можете скористатися «коротким синтаксисом атрибутів». Це часто зручно, адже імена атрибутів нерідко збігаються з іменами відповідних змінних:

```blade
{{-- Short attribute syntax... --}}
<x-profile :$userId :$name />

{{-- Is equivalent to... --}}
<x-profile :user-id="$userId" :name="$name" />
```

<a name="escaping-attribute-rendering"></a>
#### Екранування рендерингу атрибутів

Оскільки деякі JavaScript-фреймворки на кшталт Alpine.js теж використовують атрибути з двокрапкою, ви можете скористатися префіксом із подвійною двокрапкою (`::`), щоб повідомити Blade, що атрибут не є PHP-виразом. Наприклад, маючи такий компонент:

```blade
<x-button ::class="{ danger: isDeleting }">
    Submit
</x-button>
```

Blade відрендерить такий HTML:

```blade
<button :class="{ danger: isDeleting }">
    Submit
</button>
```

<a name="component-methods"></a>
#### Методи компонента

Окрім публічних змінних, доступних шаблону компонента, можна викликати будь-які його публічні методи. Наприклад, уявіть компонент із методом `isSelected`:

```php
/**
 * Determine if the given option is the currently selected option.
 */
public function isSelected(string $option): bool
{
    return $option === $this->selected;
}
```

Ви можете виконати цей метод із шаблону компонента, звернувшись до змінної з іменем цього методу:

```blade
<option {{ $isSelected($value) ? 'selected' : '' }} value="{{ $value }}">
    {{ $label }}
</option>
```

<a name="using-attributes-slots-within-component-class"></a>
#### Доступ до атрибутів і слотів усередині класів компонентів

Компоненти Blade також дозволяють звертатися до імені компонента, його атрибутів і слота всередині методу `render` класу. Однак щоб отримати доступ до цих даних, метод `render` має повертати замикання:

```php
use Closure;

/**
 * Get the view / contents that represent the component.
 */
public function render(): Closure
{
    return function () {
        return '<div {{ $attributes }}>Components content</div>';
    };
}
```

Замикання, повернене методом `render`, може також отримувати масив `$data` як єдиний аргумент. Цей масив міститиме кілька елементів з інформацією про компонент:

```php
return function (array $data) {
    // $data['componentName'];
    // $data['attributes'];
    // $data['slot'];

    return '<div {{ $attributes }}>Components content</div>';
}
```

> [!WARNING]
> Елементи масиву `$data` ніколи не слід вставляти безпосередньо в рядок Blade, який повертає ваш метод `render`, адже це може дозволити віддалене виконання коду через зловмисний вміст атрибутів.

`componentName` дорівнює імені, використаному в HTML-тегу після префікса `x-`. Тож для `<x-alert />` `componentName` буде `alert`. Елемент `attributes` міститиме всі атрибути, присутні в HTML-тегу. Елемент `slot` є екземпляром `Illuminate\View\ComponentSlot` із вмістом слота компонента.

Замикання має повертати рядок. Якщо повернений рядок відповідає наявному представленню, буде відрендерено це представлення; інакше рядок буде обчислено як вбудоване представлення Blade.

<a name="additional-dependencies"></a>
#### Додаткові залежності

Якщо вашому компоненту потрібні залежності із [сервіс-контейнера](/docs/{{version}}/container) Laravel, перелічіть їх перед будь-якими атрибутами даних компонента, і контейнер автоматично впровадить їх:

```php
use App\Services\AlertCreator;

/**
 * Create the component instance.
 */
public function __construct(
    public AlertCreator $creator,
    public string $type,
    public string $message,
) {}
```

<a name="hiding-attributes-and-methods"></a>
#### Приховування атрибутів і методів

Якщо ви хочете, щоб деякі публічні методи чи властивості не потрапляли до шаблону компонента як змінні, додайте їх до масиву `$except` у своєму компоненті:

```php
<?php

namespace App\View\Components;

use Illuminate\View\Component;

class Alert extends Component
{
    /**
     * The properties / methods that should not be exposed to the component template.
     *
     * @var array
     */
    protected $except = ['type'];

    /**
     * Create the component instance.
     */
    public function __construct(
        public string $type,
    ) {}
}
```

<a name="component-attributes"></a>
### Атрибути компонентів

Ми вже розглянули, як передавати компоненту атрибути даних; однак іноді вам може знадобитися вказати додаткові HTML-атрибути - як-от `class`, - які не є частиною даних, потрібних компоненту для роботи. Зазвичай ви захочете передати ці додаткові атрибути кореневому елементу шаблону компонента. Наприклад, уявіть, що ми хочемо відрендерити компонент `alert` так:

```blade
<x-alert type="error" :message="$message" class="mt-4"/>
```

Усі атрибути, які не є частиною конструктора компонента, автоматично потраплять до «набору атрибутів» компонента. Цей набір автоматично доступний компоненту через змінну `$attributes`. Усі атрибути можна відрендерити в компоненті, вивівши цю змінну:

```blade
<div {{ $attributes }}>
    <!-- Component content -->
</div>
```

> [!WARNING]
> Використання директив на кшталт `@env` усередині тегів компонентів наразі не підтримується. Наприклад, `<x-alert :live="@env('production')"/>` не буде скомпільовано.

<a name="default-merged-attributes"></a>
#### Типові та об'єднані атрибути

Іноді вам може знадобитися задати типові значення атрибутів або додати значення до наявних атрибутів компонента. Для цього скористайтеся методом `merge` набору атрибутів. Цей метод особливо корисний, щоб визначити набір типових CSS-класів, які завжди мають застосовуватися до компонента:

```blade
<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
    {{ $message }}
</div>
```

Якщо припустити, що цей компонент використовується так:

```blade
<x-alert type="error" :message="$message" class="mb-4"/>
```

Кінцевий відрендерений HTML компонента виглядатиме так:

```blade
<div class="alert alert-error mb-4">
    <!-- Contents of the $message variable -->
</div>
```

<a name="conditionally-merge-classes"></a>
#### Умовне об'єднання класів

Іноді ви можете захотіти об'єднати класи, якщо певна умова дає `true`. Це робиться методом `class`, який приймає масив класів, де ключ містить клас або класи, які ви хочете додати, а значення є булевим виразом. Якщо елемент масиву має числовий ключ, його буде включено завжди:

```blade
<div {{ $attributes->class(['p-4', 'bg-red' => $hasError]) }}>
    {{ $message }}
</div>
```

Якщо вам потрібно об'єднати інші атрибути компонента, ви можете приєднати метод `merge` до методу `class`:

```blade
<button {{ $attributes->class(['p-4'])->merge(['type' => 'button']) }}>
    {{ $slot }}
</button>
```

> [!NOTE]
> Якщо вам потрібно умовно компілювати класи для інших HTML-елементів, які не мають отримувати об'єднані атрибути, скористайтеся [директивою @class](#conditional-classes).

<a name="non-class-attribute-merging"></a>
#### Об'єднання атрибутів, відмінних від class

Об'єднуючи атрибути, відмінні від `class`, значення, передані методу `merge`, вважатимуться «типовими» значеннями атрибута. Однак, на відміну від `class`, ці атрибути не об'єднуватимуться з переданими значеннями, а будуть перезаписані. Наприклад, реалізація компонента `button` може виглядати так:

```blade
<button {{ $attributes->merge(['type' => 'button']) }}>
    {{ $slot }}
</button>
```

Щоб відрендерити компонент кнопки з власним `type`, його можна вказати під час використання компонента. Якщо тип не вказано, буде використано `button`:

```blade
<x-button type="submit">
    Submit
</x-button>
```

Відрендерений HTML компонента `button` у цьому прикладі буде таким:

```blade
<button type="submit">
    Submit
</button>
```

Якщо ви хочете, щоб типове та передане значення атрибута, відмінного від `class`, об'єднувалися, скористайтеся методом `prepends`. У цьому прикладі атрибут `data-controller` завжди починатиметься з `profile-controller`, а будь-які додаткові передані значення `data-controller` розміщуватимуться після цього типового значення:

```blade
<div {{ $attributes->merge(['data-controller' => $attributes->prepends('profile-controller')]) }}>
    {{ $slot }}
</div>
```

<a name="filtering-attributes"></a>
#### Отримання та фільтрування атрибутів

Ви можете фільтрувати атрибути методом `filter`. Він приймає замикання, яке має повертати `true`, якщо ви хочете залишити атрибут у наборі:

```blade
{{ $attributes->filter(fn (string $value, string $key) => $key == 'foo') }}
```

Для зручності ви можете скористатися методом `whereStartsWith`, щоб отримати всі атрибути, чиї ключі починаються з певного рядка:

```blade
{{ $attributes->whereStartsWith('wire:model') }}
```

І навпаки, метод `whereDoesntStartWith` дозволяє виключити всі атрибути, чиї ключі починаються з певного рядка:

```blade
{{ $attributes->whereDoesntStartWith('wire:model') }}
```

Методом `first` ви можете відрендерити перший атрибут у наборі:

```blade
{{ $attributes->whereStartsWith('wire:model')->first() }}
```

Якщо ви хочете перевірити наявність атрибута в компоненті, скористайтеся методом `has`. Він приймає ім'я атрибута як єдиний аргумент і повертає булеве значення, що вказує на його наявність:

```blade
@if ($attributes->has('class'))
    <div>Class attribute is present</div>
@endif
```

Якщо методу `has` передано масив, він визначить, чи присутні в компоненті всі вказані атрибути:

```blade
@if ($attributes->has(['name', 'class']))
    <div>All of the attributes are present</div>
@endif
```

Метод `hasAny` дозволяє визначити, чи присутній у компоненті хоч один із указаних атрибутів:

```blade
@if ($attributes->hasAny(['href', ':href', 'v-bind:href']))
    <div>One of the attributes is present</div>
@endif
```

Отримати значення конкретного атрибута можна методом `get`:

```blade
{{ $attributes->get('class') }}
```

Метод `only` дозволяє отримати лише атрибути з указаними ключами:

```blade
{{ $attributes->only(['class']) }}
```

Метод `except` дозволяє отримати всі атрибути, крім тих, що мають указані ключі:

```blade
{{ $attributes->except(['class']) }}
```

<a name="reserved-keywords"></a>
### Зарезервовані ключові слова

За замовчуванням деякі ключові слова зарезервовано для внутрішнього використання Blade під час рендерингу компонентів. Наведені нижче слова не можна визначати як публічні властивості чи імена методів у ваших компонентах:

<div class="content-list" markdown="1">

- `data`
- `render`
- `resolve`
- `resolveView`
- `shouldRender`
- `view`
- `withAttributes`
- `withName`

</div>

<a name="slots"></a>
### Слоти

Часто вам потрібно передати компоненту додатковий вміст через «слоти». Слоти компонента рендеряться виведенням змінної `$slot`. Щоб дослідити цю концепцію, уявімо, що компонент `alert` має таку розмітку:

```blade
<!-- /resources/views/components/alert.blade.php -->

<div class="alert alert-danger">
    {{ $slot }}
</div>
```

Ми можемо передати вміст у `slot`, вставивши його в компонент:

```blade
<x-alert>
    <strong>Whoops!</strong> Something went wrong!
</x-alert>
```

Іноді компоненту може знадобитися відрендерити кілька різних слотів у різних місцях. Змінімо наш компонент alert, щоб дозволити вставку слота «title»:

```blade
<!-- /resources/views/components/alert.blade.php -->

<span class="alert-title">{{ $title }}</span>

<div class="alert alert-danger">
    {{ $slot }}
</div>
```

Вміст іменованого слота визначається тегом `x-slot`. Будь-який вміст поза явним тегом `x-slot` буде передано компоненту у змінній `$slot`:

```xml
<x-alert>
    <x-slot:title>
        Server Error
    </x-slot>

    <strong>Whoops!</strong> Something went wrong!
</x-alert>
```

Ви можете викликати метод `isEmpty` слота, щоб визначити, чи містить він вміст:

```blade
<span class="alert-title">{{ $title }}</span>

<div class="alert alert-danger">
    @if ($slot->isEmpty())
        This is default content if the slot is empty.
    @else
        {{ $slot }}
    @endif
</div>
```

Крім того, метод `hasActualContent` дозволяє визначити, чи містить слот «справжній» вміст, що не є HTML-коментарем:

```blade
@if ($slot->hasActualContent())
    The scope has non-comment content.
@endif
```

<a name="scoped-slots"></a>
#### Слоти з областю видимості

Якщо ви користувалися JavaScript-фреймворком на кшталт Vue, вам можуть бути знайомі «scoped slots», які дозволяють звертатися до даних чи методів компонента всередині слота. Схожої поведінки в Laravel можна досягти, визначивши публічні методи чи властивості компонента й звертаючись до компонента всередині слота через змінну `$component`. У цьому прикладі ми припустимо, що компонент `x-alert` має публічний метод `formatAlert`, визначений у його класі:

```blade
<x-alert>
    <x-slot:title>
        {{ $component->formatAlert('Server Error') }}
    </x-slot>

    <strong>Whoops!</strong> Something went wrong!
</x-alert>
```

<a name="slot-attributes"></a>
#### Атрибути слотів

Як і компонентам Blade, слотам можна призначати додаткові [атрибути](#component-attributes) - як-от імена CSS-класів:

```xml
<x-card class="shadow-sm">
    <x-slot:heading class="font-bold">
        Heading
    </x-slot>

    Content

    <x-slot:footer class="text-sm">
        Footer
    </x-slot>
</x-card>
```

Щоб працювати з атрибутами слота, звертайтеся до властивості `attributes` змінної слота. Докладніше про роботу з атрибутами дивіться в документації з [атрибутів компонентів](#component-attributes):

```blade
@props([
    'heading',
    'footer',
])

<div {{ $attributes->class(['border']) }}>
    <h1 {{ $heading->attributes->class(['text-lg']) }}>
        {{ $heading }}
    </h1>

    {{ $slot }}

    <footer {{ $footer->attributes->class(['text-gray-700']) }}>
        {{ $footer }}
    </footer>
</div>
```

<a name="inline-component-views"></a>
### Вбудовані представлення компонентів

Для дуже маленьких компонентів керувати і класом компонента, і його шаблоном може здаватися марудним. Тому ви можете повертати розмітку компонента просто з методу `render`:

```php
/**
 * Get the view / contents that represent the component.
 */
public function render(): string
{
    return <<<'blade'
        <div class="alert alert-danger">
            {{ $slot }}
        </div>
    blade;
}
```

<a name="generating-inline-view-components"></a>
#### Генерація компонентів із вбудованим представленням

Щоб створити компонент, який рендерить вбудоване представлення, скористайтеся опцією `inline` під час виконання команди `make:component`:

```shell
php artisan make:component Alert --inline
```

<a name="dynamic-components"></a>
### Динамічні компоненти

Іноді вам може знадобитися відрендерити компонент, не знаючи до моменту виконання, який саме компонент слід рендерити. У такій ситуації скористайтеся вбудованим компонентом `dynamic-component`, щоб відрендерити компонент на основі значення чи змінної під час виконання:

```blade
// $componentName = "secondary-button";

<x-dynamic-component :component="$componentName" class="mt-4" />
```

<a name="manually-registering-components"></a>
### Ручна реєстрація компонентів

> [!WARNING]
> Наведена нижче документація про ручну реєстрацію компонентів стосується насамперед тих, хто пише пакети Laravel із компонентами представлень. Якщо ви не пишете пакет, ця частина документації може бути для вас неактуальною.

Пишучи компоненти для власного застосунку, ви не потребуєте додаткової реєстрації: вони автоматично виявляються в каталогах `app/View/Components` і `resources/views/components`.

Однак якщо ви створюєте пакет, що використовує компоненти Blade, або розміщуєте компоненти в нетипових каталогах, вам доведеться вручну зареєструвати клас компонента та його псевдонім HTML-тега, щоб Laravel знав, де шукати компонент. Зазвичай компоненти слід реєструвати в методі `boot` сервіс-провайдера вашого пакета:

```php
use Illuminate\Support\Facades\Blade;
use VendorPackage\View\Components\AlertComponent;

/**
 * Bootstrap your package's services.
 */
public function boot(): void
{
    Blade::component('package-alert', AlertComponent::class);
}
```

Щойно компонент зареєстровано, його можна відрендерити за псевдонімом тега:

```blade
<x-package-alert/>
```

#### Автозавантаження компонентів пакета

Як альтернативу ви можете скористатися методом `componentNamespace`, щоб автозавантажувати класи компонентів за домовленостями. Наприклад, пакет `Nightshade` може мати компоненти `Calendar` і `ColorPicker`, розташовані у просторі імен `Nightshade\Views\Components`:

```php
use Illuminate\Support\Facades\Blade;

/**
 * Bootstrap your package's services.
 */
public function boot(): void
{
    Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}
```

Це дозволить використовувати компоненти пакета через простір імен постачальника за синтаксисом `package-name::`:

```blade
<x-nightshade::calendar />
<x-nightshade::color-picker />
```

Blade автоматично визначить клас, пов'язаний із цим компонентом, перетворивши ім'я компонента на PascalCase. Підкаталоги теж підтримуються через «крапкову» нотацію.

<a name="anonymous-components"></a>
## Анонімні компоненти

Подібно до вбудованих компонентів, анонімні компоненти дають механізм керування компонентом через один файл. Однак анонімні компоненти використовують лише файл представлення й не мають пов'язаного класу. Щоб визначити анонімний компонент, достатньо розмістити шаблон Blade у каталозі `resources/views/components`. Наприклад, якщо ви визначили компонент у `resources/views/components/alert.blade.php`, ви можете відрендерити його так:

```blade
<x-alert/>
```

Ви можете скористатися символом `.`, щоб позначити вкладеність компонента глибше в каталозі `components`. Наприклад, якщо компонент визначено в `resources/views/components/inputs/button.blade.php`, відрендерити його можна так:

```blade
<x-inputs.button/>
```

Щоб створити анонімний компонент через Artisan, скористайтеся прапорцем `--view` під час виклику команди `make:component`:

```shell
php artisan make:component forms.input --view
```

Наведена вище команда створить файл Blade у `resources/views/components/forms/input.blade.php`, який можна відрендерити як компонент через `<x-forms.input />`.

<a name="anonymous-index-components"></a>
### Анонімні індексні компоненти

Іноді, коли компонент складається з багатьох шаблонів Blade, ви можете захотіти згрупувати їх в одному каталозі. Наприклад, уявіть компонент «accordion» із такою структурою каталогів:

```text
/resources/views/components/accordion.blade.php
/resources/views/components/accordion/item.blade.php
```

Ця структура дозволяє рендерити компонент accordion та його елемент так:

```blade
<x-accordion>
    <x-accordion.item>
        ...
    </x-accordion.item>
</x-accordion>
```

Однак щоб відрендерити компонент accordion через `x-accordion`, нам довелося розмістити «індексний» шаблон accordion у каталозі `resources/views/components`, а не вкладати його в каталог `accordion` разом з іншими пов'язаними шаблонами.

На щастя, Blade дозволяє розмістити файл, ім'я якого збігається з іменем каталогу компонента, усередині самого цього каталогу. Коли такий шаблон існує, його можна рендерити як «кореневий» елемент компонента, навіть якщо він вкладений у каталог. Тож ми можемо й далі використовувати той самий синтаксис Blade, наведений у прикладі вище, але змінимо структуру каталогів так:

```text
/resources/views/components/accordion/accordion.blade.php
/resources/views/components/accordion/item.blade.php
```

<a name="data-properties-attributes"></a>
### Властивості даних та атрибути

Оскільки анонімні компоненти не мають пов'язаного класу, ви можете замислитися, як розрізнити, які дані слід передавати компоненту як змінні, а які атрибути мають потрапити до [набору атрибутів](#component-attributes) компонента.

Ви можете вказати, які атрибути слід вважати змінними даних, директивою `@props` на початку шаблону вашого компонента. Усі інші атрибути компонента будуть доступні через набір атрибутів. Якщо ви хочете задати змінній даних типове значення, вкажіть ім'я змінної як ключ масиву, а типове значення - як значення:

```blade
<!-- /resources/views/components/alert.blade.php -->

@props(['type' => 'info', 'message'])

<div {{ $attributes->merge(['class' => 'alert alert-'.$type]) }}>
    {{ $message }}
</div>
```

З наведеним вище визначенням компонента ми можемо відрендерити його так:

```blade
<x-alert type="error" :message="$message" class="mb-4"/>
```

<a name="accessing-parent-data"></a>
### Доступ до даних батьківського компонента

Іноді вам може знадобитися звернутися до даних батьківського компонента всередині дочірнього. У таких випадках скористайтеся директивою `@aware`. Наприклад, уявімо, що ми створюємо складний компонент меню, який складається з батьківського `<x-menu>` і дочірнього `<x-menu.item>`:

```blade
<x-menu color="purple">
    <x-menu.item>...</x-menu.item>
    <x-menu.item>...</x-menu.item>
</x-menu>
```

Компонент `<x-menu>` може мати таку реалізацію:

```blade
<!-- /resources/views/components/menu/index.blade.php -->

@props(['color' => 'gray'])

<ul {{ $attributes->merge(['class' => 'bg-'.$color.'-200']) }}>
    {{ $slot }}
</ul>
```

Оскільки проп `color` було передано лише батьківському компоненту (`<x-menu>`), він не буде доступний усередині `<x-menu.item>`. Однак якщо ми скористаємося директивою `@aware`, ми можемо зробити його доступним і там:

```blade
<!-- /resources/views/components/menu/item.blade.php -->

@aware(['color' => 'gray'])

<li {{ $attributes->merge(['class' => 'text-'.$color.'-800']) }}>
    {{ $slot }}
</li>
```

> [!WARNING]
> Директива `@aware` не може звертатися до даних батька, які не передано батьківському компоненту явно через HTML-атрибути. Типові значення `@props`, не передані батьківському компоненту явно, недоступні директиві `@aware`.

<a name="anonymous-component-paths"></a>
### Шляхи анонімних компонентів

Як зазначалося раніше, анонімні компоненти зазвичай визначаються розміщенням шаблону Blade у каталозі `resources/views/components`. Утім, подекуди ви можете захотіти зареєструвати в Laravel інші шляхи анонімних компонентів на додачу до типового.

Метод `anonymousComponentPath` приймає «шлях» до розташування анонімних компонентів першим аргументом і необов'язковий «простір імен», під яким слід розміщувати компоненти, - другим. Зазвичай цей метод слід викликати з методу `boot` одного із [сервіс-провайдерів](/docs/{{version}}/providers) вашого застосунку:

```php
/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Blade::anonymousComponentPath(__DIR__.'/../components');
}
```

Коли шляхи компонентів зареєстровано без указаного префікса, як у прикладі вище, їх можна рендерити у ваших компонентах Blade теж без префікса. Наприклад, якщо за зареєстрованим вище шляхом існує компонент `panel.blade.php`, його можна відрендерити так:

```blade
<x-panel />
```

Префікси-«простори імен» можна передати другим аргументом методу `anonymousComponentPath`:

```php
Blade::anonymousComponentPath(__DIR__.'/../components', 'dashboard');
```

Коли префікс указано, компоненти в цьому «просторі імен» рендеряться додаванням простору імен до імені компонента:

```blade
<x-dashboard::panel />
```

<a name="building-layouts"></a>
## Створення макетів

<a name="layouts-using-components"></a>
### Макети на компонентах

Більшість веб-застосунків мають однаковий загальний макет на різних сторінках. Було б неймовірно марудно й важко підтримувати застосунок, якби нам доводилося повторювати весь HTML макета в кожному створеному представленні. На щастя, зручно визначити цей макет як єдиний [компонент Blade](#components) і використовувати його в усьому застосунку.

<a name="defining-the-layout-component"></a>
#### Визначення компонента макета

Наприклад, уявімо, що ми створюємо застосунок зі списком справ. Ми можемо визначити компонент `layout`, який виглядає так:

```blade
<!-- resources/views/components/layout.blade.php -->

<html>
    <head>
        <title>{{ $title ?? 'Todo Manager' }}</title>
    </head>
    <body>
        <h1>Todos</h1>
        <hr/>
        {{ $slot }}
    </body>
</html>
```

<a name="applying-the-layout-component"></a>
#### Застосування компонента макета

Щойно компонент `layout` визначено, ми можемо створити представлення Blade, що його використовує. У цьому прикладі ми визначимо просте представлення, яке показує наш список завдань:

```blade
<!-- resources/views/tasks.blade.php -->

<x-layout>
    @foreach ($tasks as $task)
        <div>{{ $task }}</div>
    @endforeach
</x-layout>
```

Пам'ятайте: вміст, вставлений у компонент, потрапить до типової змінної `$slot` усередині нашого компонента `layout`. Як ви могли помітити, наш `layout` також враховує слот `$title`, якщо його передано; інакше показується типовий заголовок. Ми можемо передати власний заголовок із представлення списку завдань, використовуючи стандартний синтаксис слотів, розглянутий у [документації з компонентів](#components):

```blade
<!-- resources/views/tasks.blade.php -->

<x-layout>
    <x-slot:title>
        Custom Title
    </x-slot>

    @foreach ($tasks as $task)
        <div>{{ $task }}</div>
    @endforeach
</x-layout>
```

Тепер, коли ми визначили макет і представлення списку завдань, залишається лише повернути представлення `task` із маршруту:

```php
use App\Models\Task;

Route::get('/tasks', function () {
    return view('tasks', ['tasks' => Task::all()]);
});
```

<a name="layouts-using-template-inheritance"></a>
### Макети на успадкуванні шаблонів

<a name="defining-a-layout"></a>
#### Визначення макета

Макети можна також створювати через «успадкування шаблонів». Це був основний спосіб створення застосунків до появи [компонентів](#components).

Щоб почати, погляньмо на простий приклад. Спершу розглянемо макет сторінки. Оскільки більшість веб-застосунків мають однаковий загальний макет на різних сторінках, зручно визначити цей макет як єдине представлення Blade:

```blade
<!-- resources/views/layouts/app.blade.php -->

<html>
    <head>
        <title>App Name - @yield('title')</title>
    </head>
    <body>
        @section('sidebar')
            This is the master sidebar.
        @show

        <div class="container">
            @yield('content')
        </div>
    </body>
</html>
```

Як бачите, цей файл містить типову HTML-розмітку. Однак зверніть увагу на директиви `@section` і `@yield`. Директива `@section`, як випливає з назви, визначає секцію вмісту, а директива `@yield` слугує для виведення вмісту певної секції.

Тепер, коли ми визначили макет для нашого застосунку, визначмо дочірню сторінку, що успадковує цей макет.

<a name="extending-a-layout"></a>
#### Розширення макета

Визначаючи дочірнє представлення, скористайтеся директивою Blade `@extends`, щоб указати, який макет має «успадкувати» це представлення. Представлення, що розширюють макет Blade, можуть вставляти вміст у секції макета через директиви `@section`. Пам'ятайте: як показано в прикладі вище, вміст цих секцій буде показано в макеті через `@yield`:

```blade
<!-- resources/views/child.blade.php -->

@extends('layouts.app')

@section('title', 'Page Title')

@section('sidebar')
    @@parent

    <p>This is appended to the master sidebar.</p>
@endsection

@section('content')
    <p>This is my body content.</p>
@endsection
```

У цьому прикладі секція `sidebar` використовує директиву `@@parent`, щоб додати вміст до бічної панелі макета, а не перезаписати його. Директиву `@@parent` буде замінено вмістом макета під час рендерингу представлення.

> [!NOTE]
> На відміну від попереднього прикладу, ця секція `sidebar` закінчується `@endsection`, а не `@show`. Директива `@endsection` лише визначає секцію, тоді як `@show` визначає і **одразу виводить** її.

Директива `@yield` також приймає типове значення другим параметром. Воно буде відрендерено, якщо секція, яку виводять, не визначена:

```blade
@yield('content', 'Default content')
```

<a name="forms"></a>
## Форми

<a name="csrf-field"></a>
### Поле CSRF

Щоразу, визначаючи HTML-форму у своєму застосунку, додавайте до неї приховане поле з CSRF-токеном, щоб `middleware` [захисту від CSRF](/docs/{{version}}/csrf) міг перевірити запит. Згенерувати поле з токеном можна директивою Blade `@csrf`:

```blade
<form method="POST" action="/profile">
    @csrf

    ...
</form>
```

<a name="method-field"></a>
### Поле методу

Оскільки HTML-форми не можуть робити запити `PUT`, `PATCH` чи `DELETE`, вам потрібно додати приховане поле `_method`, щоб підмінити ці HTTP-методи. Створити це поле за вас може директива Blade `@method`:

```blade
<form action="/foo/bar" method="POST">
    @method('PUT')

    ...
</form>
```

<a name="validation-errors"></a>
### Помилки валідації

Директива `@error` дозволяє швидко перевірити, чи існують [повідомлення про помилки валідації](/docs/{{version}}/validation#quick-displaying-the-validation-errors) для певного атрибута. Усередині директиви `@error` ви можете вивести змінну `$message`, щоб показати повідомлення про помилку:

```blade
<!-- /resources/views/post/create.blade.php -->

<label for="title">Post Title</label>

<input
    id="title"
    type="text"
    class="@error('title') is-invalid @enderror"
/>

@error('title')
    <div class="alert alert-danger">{{ $message }}</div>
@enderror
```

Оскільки директива `@error` компілюється в конструкцію «if», ви можете скористатися директивою `@else`, щоб відрендерити вміст, коли помилки для атрибута немає:

```blade
<!-- /resources/views/auth.blade.php -->

<label for="email">Email address</label>

<input
    id="email"
    type="email"
    class="@error('email') is-invalid @else is-valid @enderror"
/>
```

Ви можете передати [ім'я конкретного набору помилок](/docs/{{version}}/validation#named-error-bags) другим параметром директиви `@error`, щоб отримати повідомлення про помилки валідації на сторінках із кількома формами:

```blade
<!-- /resources/views/auth.blade.php -->

<label for="email">Email address</label>

<input
    id="email"
    type="email"
    class="@error('email', 'login') is-invalid @enderror"
/>

@error('email', 'login')
    <div class="alert alert-danger">{{ $message }}</div>
@enderror
```

<a name="stacks"></a>
## Стеки

Blade дозволяє додавати вміст до іменованих стеків, які можна відрендерити деінде - в іншому представленні чи макеті. Це особливо корисно, щоб указати JavaScript-бібліотеки, потрібні вашим дочірнім представленням:

```blade
@push('scripts')
    <script src="/example.js"></script>
@endpush
```

Якщо ви хочете додати вміст до стека, коли булевий вираз дає `true`, скористайтеся директивою `@pushIf`:

```blade
@pushIf($shouldPush, 'scripts')
    <script src="/example.js"></script>
@endPushIf
```

Ви можете додавати до стека скільки завгодно разів. Щоб відрендерити повний вміст стека, передайте його ім'я директиві `@stack`:

```blade
<head>
    <!-- Head Contents -->

    @stack('scripts')
</head>
```

Якщо ви хочете додати вміст на початок стека, скористайтеся директивою `@prepend`:

```blade
@push('scripts')
    This will be second...
@endpush

// Later...

@prepend('scripts')
    This will be first...
@endprepend
```

Директива `@hasstack` дозволяє визначити, чи стек порожній:

```blade
@hasstack('list')
    <ul>
        @stack('list')
    </ul>
@endif
```

<a name="service-injection"></a>
## Впровадження сервісів

Директива `@inject` дозволяє отримати сервіс із [сервіс-контейнера](/docs/{{version}}/container) Laravel. Перший аргумент, переданий `@inject`, - ім'я змінної, у яку буде поміщено сервіс, а другий - ім'я класу чи інтерфейсу сервісу, який ви хочете розв'язати:

```blade
@inject('metrics', 'App\Services\MetricsService')

<div>
    Monthly Revenue: {{ $metrics->monthlyRevenue() }}.
</div>
```

<a name="rendering-inline-blade-templates"></a>
## Рендеринг вбудованих шаблонів Blade

Іноді вам може знадобитися перетворити сирий рядок шаблону Blade на дійсний HTML. Це робиться методом `render` фасаду `Blade`. Метод `render` приймає рядок шаблону Blade і необов'язковий масив даних для шаблону:

```php
use Illuminate\Support\Facades\Blade;

return Blade::render('Hello, {{ $name }}', ['name' => 'Julian Bashir']);
```

Laravel рендерить вбудовані шаблони Blade, записуючи їх у каталог `storage/framework/views`. Якщо ви хочете, щоб Laravel видаляв ці тимчасові файли після рендерингу, передайте методу аргумент `deleteCachedView`:

```php
return Blade::render(
    'Hello, {{ $name }}',
    ['name' => 'Julian Bashir'],
    deleteCachedView: true
);
```

<a name="rendering-blade-fragments"></a>
## Рендеринг фрагментів Blade

Використовуючи фронтенд-фреймворки на кшталт [Turbo](https://turbo.hotwired.dev/) чи [htmx](https://htmx.org/), ви можете подекуди потребувати повернення лише частини шаблону Blade у HTTP-відповіді. «Фрагменти» Blade дозволяють саме це. Щоб почати, розмістіть частину свого шаблону Blade між директивами `@fragment` та `@endfragment`:

```blade
@fragment('user-list')
    <ul>
        @foreach ($users as $user)
            <li>{{ $user->name }}</li>
        @endforeach
    </ul>
@endfragment
```

Далі, рендерячи представлення, що використовує цей шаблон, викличте метод `fragment`, щоб указати, що до вихідної HTTP-відповіді слід включити лише вказаний фрагмент:

```php
return view('dashboard', ['users' => $users])->fragment('user-list');
```

Метод `fragmentIf` дозволяє умовно повернути фрагмент представлення залежно від певної умови. Інакше буде повернуто все представлення:

```php
return view('dashboard', ['users' => $users])
    ->fragmentIf($request->hasHeader('HX-Request'), 'user-list');
```

Методи `fragments` і `fragmentsIf` дозволяють повернути у відповіді кілька фрагментів представлення. Фрагменти буде з'єднано разом:

```php
view('dashboard', ['users' => $users])
    ->fragments(['user-list', 'comment-list']);

view('dashboard', ['users' => $users])
    ->fragmentsIf(
        $request->hasHeader('HX-Request'),
        ['user-list', 'comment-list']
    );
```

<a name="extending-blade"></a>
## Розширення Blade

Blade дозволяє визначати власні директиви методом `directive`. Коли компілятор Blade натрапляє на власну директиву, він викликає наданий колбек із виразом, який містить директива.

Наведений нижче приклад створює директиву `@datetime($var)`, яка форматує переданий `$var`, що має бути екземпляром `DateTime`:

```php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Blade::directive('datetime', function (string $expression) {
            return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";
        });
    }
}
```

Як бачите, ми приєднаємо метод `format` до будь-якого виразу, переданого директиві. Тож у цьому прикладі кінцевий PHP, згенерований директивою, буде таким:

```php
<?php echo ($var)->format('m/d/Y H:i'); ?>
```

> [!WARNING]
> Після оновлення логіки директиви Blade вам потрібно буде видалити всі закешовані представлення Blade. Зробити це можна командою Artisan `view:clear`.

<a name="custom-echo-handlers"></a>
### Власні обробники виведення

Якщо ви спробуєте вивести об'єкт через Blade, буде викликано магічний метод `__toString` цього об'єкта. [__toString](https://www.php.net/manual/en/language.oop5.magic.php#object.tostring) - один із вбудованих «магічних методів» PHP. Однак іноді ви не маєте контролю над методом `__toString` певного класу - наприклад, коли клас належить сторонній бібліотеці.

У таких випадках Blade дозволяє зареєструвати власний обробник виведення для конкретного типу об'єктів. Для цього викличте метод `stringable` у Blade. Метод `stringable` приймає замикання, яке має вказувати тип об'єкта, за рендеринг якого воно відповідає. Зазвичай метод `stringable` слід викликати в методі `boot` класу `AppServiceProvider` вашого застосунку:

```php
use Illuminate\Support\Facades\Blade;
use Money\Money;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Blade::stringable(function (Money $money) {
        return $money->formatTo('en_GB');
    });
}
```

Щойно ваш обробник виведення визначено, ви можете просто вивести об'єкт у своєму шаблоні Blade:

```blade
Cost: {{ $money }}
```

<a name="custom-if-statements"></a>
### Власні умовні конструкції

Програмування власної директиви іноді складніше, ніж потрібно, коли йдеться про прості власні умовні конструкції. Тому Blade надає метод `Blade::if`, який дозволяє швидко визначати власні умовні директиви через замикання. Наприклад, визначмо власну умову, що перевіряє налаштований типовий «диск» застосунку. Зробимо це в методі `boot` нашого `AppServiceProvider`:

```php
use Illuminate\Support\Facades\Blade;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Blade::if('disk', function (string $value) {
        return config('filesystems.default') === $value;
    });
}
```

Щойно власну умову визначено, ви можете використовувати її у своїх шаблонах:

```blade
@disk('local')
    <!-- The application is using the local disk... -->
@elsedisk('s3')
    <!-- The application is using the s3 disk... -->
@else
    <!-- The application is using some other disk... -->
@enddisk

@unlessdisk('local')
    <!-- The application is not using the local disk... -->
@enddisk
```

---

Джерело: https://laravelukraine.com/docs/13.x/vite

# Складання ресурсів (Vite)

- [Вступ](#introduction)
- [Встановлення та налаштування](#installation)
  - [Встановлення Node](#installing-node)
  - [Встановлення Vite та плагіна Laravel](#installing-vite-and-laravel-plugin)
  - [Налаштування Vite](#configuring-vite)
  - [Завантаження скриптів і стилів](#loading-your-scripts-and-styles)
- [Запуск Vite](#running-vite)
- [Робота з JavaScript](#working-with-scripts)
  - [Псевдоніми](#aliases)
  - [Vue](#vue)
  - [React](#react)
  - [Svelte](#svelte)
  - [Inertia](#inertia)
  - [Обробка URL](#url-processing)
- [Робота зі стилями](#working-with-stylesheets)
- [Робота зі шрифтами](#working-with-fonts)
  - [Провайдери шрифтів](#font-providers)
  - [Локальні шрифти](#local-fonts)
  - [Опції шрифтів](#font-options)
- [Робота з Blade і маршрутами](#working-with-blade-and-routes)
  - [Обробка статичних ресурсів через Vite](#blade-processing-static-assets)
  - [Оновлення під час збереження](#blade-refreshing-on-save)
  - [Псевдоніми](#blade-aliases)
- [Попереднє завантаження ресурсів](#asset-prefetching)
- [Власні базові URL](#custom-base-urls)
- [Змінні середовища](#environment-variables)
- [Вимкнення Vite у тестах](#disabling-vite-in-tests)
- [Рендеринг на боці сервера (SSR)](#ssr)
- [Атрибути тегів script і style](#script-and-style-attributes)
  - [Nonce для політики безпеки вмісту (CSP)](#content-security-policy-csp-nonce)
  - [Цілісність підресурсів (SRI)](#subresource-integrity-sri)
  - [Довільні атрибути](#arbitrary-attributes)
- [Розширене налаштування](#advanced-customization)
  - [CORS сервера розробки](#cors)
  - [Виправлення URL сервера розробки](#correcting-dev-server-urls)

<a name="introduction"></a>
## Вступ

[Vite](https://vitejs.dev) - це сучасний інструмент збірки фронтенду, що дає надзвичайно швидке середовище розробки й пакує ваш код для продакшену. Створюючи застосунки на Laravel, ви зазвичай використовуватимете Vite, щоб зібрати файли CSS і JavaScript у готові до продакшену ресурси.

Laravel бездоганно інтегрується з Vite завдяки офіційному плагіну та директиві Blade, які завантажують ваші ресурси для розробки й продакшену.

<a name="installation"></a>
## Встановлення та налаштування

> [!NOTE]
> Наведена нижче документація описує, як встановити й налаштувати плагін Laravel Vite вручну. Утім, [стартові набори](/docs/{{version}}/starter-kits) Laravel уже містять увесь цей каркас і є найшвидшим способом почати роботу з Laravel і Vite.

<a name="installing-node"></a>
### Встановлення Node

Перш ніж запускати Vite та плагін Laravel, переконайтеся, що встановлено Node.js (16+) і NPM:

```shell
node -v
npm -v
```

Ви можете легко встановити найновішу версію Node і NPM за допомогою простих графічних інсталяторів з [офіційного сайту Node](https://nodejs.org/en/download/). Або, якщо ви користуєтеся [Laravel Sail](https://laravel.com/docs/{{version}}/sail), викликайте Node і NPM через Sail:

```shell
./vendor/bin/sail node -v
./vendor/bin/sail npm -v
```

<a name="installing-vite-and-laravel-plugin"></a>
### Встановлення Vite та плагіна Laravel

У щойно встановленому Laravel ви знайдете файл `package.json` у корені каталогу застосунку. Типовий `package.json` уже містить усе потрібне, щоб почати роботу з Vite і плагіном Laravel. Встановити фронтенд-залежності застосунку можна через NPM:

```shell
npm install
```

<a name="configuring-vite"></a>
### Налаштування Vite

Vite налаштовується через файл `vite.config.js` у корені вашого проєкту. Ви вільні змінювати цей файл під свої потреби, а також встановлювати будь-які інші потрібні застосунку плагіни - як-от `@vitejs/plugin-react`, `@sveltejs/vite-plugin-svelte` чи `@vitejs/plugin-vue`.

Плагін Laravel Vite вимагає вказати точки входу вашого застосунку. Це можуть бути файли JavaScript чи CSS, зокрема мови з попередньою обробкою - TypeScript, JSX, TSX і Sass.

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel([
            'resources/css/app.css',
            'resources/js/app.js',
        ]),
    ],
});
```

Якщо ви створюєте SPA, зокрема застосунки на Inertia, Vite найкраще працює без CSS-точок входу:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel([
            'resources/css/app.css', // [tl! remove]
            'resources/js/app.js',
        ]),
    ],
});
```

Натомість вам слід імпортувати CSS через JavaScript. Зазвичай це роблять у файлі `resources/js/app.js` вашого застосунку:

```js
import './bootstrap';
import '../css/app.css'; // [tl! add]
```

Плагін Laravel також підтримує кілька точок входу та розширені опції конфігурації - як-от [точки входу SSR](#ssr).

<a name="working-with-a-secure-development-server"></a>
#### Робота із захищеним сервером розробки

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

Якщо ви користуєтеся [Laravel Herd](https://herd.laravel.com) і захистили сайт, або користуєтеся [Laravel Valet](/docs/{{version}}/valet) і виконали [команду secure](/docs/{{version}}/valet#securing-sites) для свого застосунку, плагін Laravel Vite автоматично виявить і використає згенерований TLS-сертифікат.

Якщо ви захистили сайт хостом, що не збігається з іменем каталогу застосунку, ви можете вказати хост вручну у файлі `vite.config.js`:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            // ...
            detectTls: 'my-app.test', // [tl! add]
        }),
    ],
});
```

Використовуючи інший веб-сервер, вам слід згенерувати довірений сертифікат і вручну налаштувати Vite на його використання:

```js
// ...
import fs from 'fs'; // [tl! add]

const host = 'my-app.test'; // [tl! add]

export default defineConfig({
    // ...
    server: { // [tl! add]
        host, // [tl! add]
        hmr: { host }, // [tl! add]
        https: { // [tl! add]
            key: fs.readFileSync(`/path/to/${host}.key`), // [tl! add]
            cert: fs.readFileSync(`/path/to/${host}.crt`), // [tl! add]
        }, // [tl! add]
    }, // [tl! add]
});
```

Якщо ви не можете згенерувати довірений сертифікат для своєї системи, встановіть і налаштуйте [плагін @vitejs/plugin-basic-ssl](https://github.com/vitejs/vite-plugin-basic-ssl). Використовуючи недовірені сертифікати, вам доведеться прийняти попередження про сертифікат для сервера розробки Vite у браузері, перейшовши за посиланням «Local» у консолі під час виконання команди `npm run dev`.

<a name="configuring-hmr-in-sail-on-wsl2"></a>
#### Запуск сервера розробки в Sail на WSL2

Запускаючи сервер розробки Vite у [Laravel Sail](/docs/{{version}}/sail) на Windows Subsystem for Linux 2 (WSL2), додайте до файлу `vite.config.js` таку конфігурацію, щоб браузер міг спілкуватися із сервером розробки:

```js
// ...

export default defineConfig({
    // ...
    server: { // [tl! add:start]
        hmr: {
            host: 'localhost',
        },
    }, // [tl! add:end]
});
```

Якщо зміни у ваших файлах не відображаються в браузері під час роботи сервера розробки, вам також може знадобитися налаштувати [опцію server.watch.usePolling](https://vitejs.dev/config/server-options.html#server-watch) у Vite.

<a name="loading-your-scripts-and-styles"></a>
### Завантаження скриптів і стилів

Налаштувавши точки входу Vite, ви можете посилатися на них у директиві Blade `@vite()`, яку додаєте до `<head>` кореневого шаблону вашого застосунку:

```blade
<!DOCTYPE html>
<head>
    {{-- ... --}}

    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
```

Якщо ви імпортуєте CSS через JavaScript, вам потрібно вказати лише точку входу JavaScript:

```blade
<!DOCTYPE html>
<head>
    {{-- ... --}}

    @vite('resources/js/app.js')
</head>
```

Директива `@vite` автоматично виявить сервер розробки Vite і вставить клієнт Vite, щоб увімкнути гарячу заміну модулів. У режимі збірки директива завантажить ваші скомпільовані версіоновані ресурси, зокрема будь-який імпортований CSS.

За потреби ви також можете вказати шлях збірки ваших скомпільованих ресурсів під час виклику директиви `@vite`:

```blade
<!doctype html>
<head>
    {{-- Given build path is relative to public path. --}}

    @vite('resources/js/app.js', 'vendor/courier/build')
</head>
```

<a name="inline-assets"></a>
#### Вбудовані ресурси

Іноді може знадобитися включити сирий вміст ресурсів замість посилання на їхню версіоновану адресу. Наприклад, вам може знадобитися вставити вміст ресурсу просто в сторінку, передаючи HTML генератору PDF. Вивести вміст ресурсів Vite можна методом `content` фасаду `Vite`:

```blade
@use('Illuminate\Support\Facades\Vite')

<!doctype html>
<head>
    {{-- ... --}}

    <style>
        {!! Vite::content('resources/css/app.css') !!}
    </style>
    <script>
        {!! Vite::content('resources/js/app.js') !!}
    </script>
</head>
```

<a name="running-vite"></a>
## Запуск Vite

Запустити Vite можна двома способами. Ви можете запустити сервер розробки командою `dev` - це корисно під час локальної розробки. Сервер автоматично виявлятиме зміни у ваших файлах і миттєво відображатиме їх у відкритих вікнах браузера.

Або ж команда `build` версіонує й запакує ресурси вашого застосунку, підготувавши їх до розгортання в продакшені:

```shell
# Run the Vite development server...
npm run dev

# Build and version the assets for production...
npm run build
```

Якщо ви запускаєте сервер розробки в [Sail](/docs/{{version}}/sail) на WSL2, вам можуть знадобитися [додаткові опції конфігурації](#configuring-hmr-in-sail-on-wsl2).

<a name="working-with-scripts"></a>
## Робота з JavaScript

<a name="aliases"></a>
### Псевдоніми

За замовчуванням плагін Laravel надає загальний псевдонім, щоб ви могли одразу братися до справи й зручно імпортувати ресурси свого застосунку:

```js
{
    '@' => '/resources/js'
}
```

Ви можете перевизначити псевдонім `'@'`, додавши власний до конфігураційного файлу `vite.config.js`:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel(['resources/ts/app.tsx']),
    ],
    resolve: {
        alias: {
            '@': '/resources/ts',
        },
    },
});
```

<a name="vue"></a>
### Vue

Якщо ви хочете створювати фронтенд за допомогою фреймворку [Vue](https://vuejs.org/), вам також потрібно встановити плагін `@vitejs/plugin-vue`:

```shell
npm install --save-dev @vitejs/plugin-vue
```

Далі ви можете додати плагін до конфігураційного файлу `vite.config.js`. Використовуючи плагін Vue з Laravel, вам знадобиться кілька додаткових опцій:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
    plugins: [
        laravel(['resources/js/app.js']),
        vue({
            template: {
                transformAssetUrls: {
                    // The Vue plugin will re-write asset URLs, when referenced
                    // in Single File Components, to point to the Laravel web
                    // server. Setting this to `null` allows the Laravel plugin
                    // to instead re-write asset URLs to point to the Vite
                    // server instead.
                    base: null,

                    // The Vue plugin will parse absolute URLs and treat them
                    // as absolute paths to files on disk. Setting this to
                    // `false` will leave absolute URLs un-touched so they can
                    // reference assets in the public directory as expected.
                    includeAbsolute: false,
                },
            },
        }),
    ],
});
```

> [!NOTE]
> [Стартові набори](/docs/{{version}}/starter-kits) Laravel уже містять правильну конфігурацію Laravel, Vue і Vite. Вони є найшвидшим способом почати роботу з Laravel, Vue і Vite.

<a name="react"></a>
### React

Якщо ви хочете створювати фронтенд за допомогою фреймворку [React](https://reactjs.org/), вам також потрібно встановити плагін `@vitejs/plugin-react`:

```shell
npm install --save-dev @vitejs/plugin-react
```

Далі ви можете додати плагін до конфігураційного файлу `vite.config.js`:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import react from '@vitejs/plugin-react';

export default defineConfig({
    plugins: [
        laravel(['resources/js/app.jsx']),
        react(),
    ],
});
```

Переконайтеся, що всі файли з JSX мають розширення `.jsx` чи `.tsx`, і за потреби не забудьте оновити свою точку входу, [як показано вище](#configuring-vite).

Вам також потрібно буде додати додаткову директиву Blade `@viteReactRefresh` поряд із наявною директивою `@vite`.

```blade
@viteReactRefresh
@vite('resources/js/app.jsx')
```

Директиву `@viteReactRefresh` слід викликати перед директивою `@vite`.

> [!NOTE]
> [Стартові набори](/docs/{{version}}/starter-kits) Laravel уже містять правильну конфігурацію Laravel, React і Vite. Вони є найшвидшим способом почати роботу з Laravel, React і Vite.

<a name="svelte"></a>
### Svelte

Якщо ви хочете створювати фронтенд за допомогою фреймворку [Svelte](https://svelte.dev/), вам також потрібно встановити плагін `@sveltejs/vite-plugin-svelte`:

```shell
npm install --save-dev @sveltejs/vite-plugin-svelte
```

Далі ви можете додати плагін до конфігураційного файлу `vite.config.js`.

```js
import { svelte } from '@sveltejs/vite-plugin-svelte';
import laravel from 'laravel-vite-plugin';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    laravel({
      input: ['resources/js/app.ts'],
      ssr: 'resources/js/ssr.ts',
      refresh: true,
    }),
    svelte(),
  ],
});
```

> [!NOTE]
> [Стартові набори](/docs/{{version}}/starter-kits) Laravel уже містять правильну конфігурацію Laravel, Svelte і Vite. Вони є найшвидшим способом почати роботу з Laravel, Svelte і Vite.

<a name="inertia"></a>
### Inertia

Плагін Laravel Vite надає зручну функцію `resolvePageComponent`, яка допомагає розв'язувати компоненти сторінок Inertia. Нижче наведено приклад використання цього хелпера з Vue 3; утім, ви можете застосовувати цю функцію і в інших фреймворках - як-от React чи Svelte:

```js
import { createApp, h } from 'vue';
import { createInertiaApp } from '@inertiajs/vue3';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';

createInertiaApp({
  resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
  setup({ el, App, props, plugin }) {
    createApp({ render: () => h(App, props) })
      .use(plugin)
      .mount(el)
  },
});
```

Якщо ви використовуєте можливість розділення коду Vite разом з Inertia, радимо налаштувати [попереднє завантаження ресурсів](#asset-prefetching).

> [!NOTE]
> [Стартові набори](/docs/{{version}}/starter-kits) Laravel уже містять правильну конфігурацію Laravel, Inertia і Vite. Вони є найшвидшим способом почати роботу з Laravel, Inertia і Vite.

<a name="url-processing"></a>
### Обробка URL

Використовуючи Vite й посилаючись на ресурси в HTML, CSS чи JS вашого застосунку, варто врахувати кілька застережень. По-перше, якщо ви посилаєтеся на ресурс за абсолютним шляхом, Vite не включить його до збірки; тому переконайтеся, що ресурс доступний у вашому каталозі `public`. Уникайте абсолютних шляхів, коли використовуєте [окрему точку входу CSS](#configuring-vite), адже під час розробки браузери намагатимуться завантажити ці шляхи із сервера розробки Vite, де розміщено CSS, а не з вашого каталогу `public`.

Посилаючись на відносні шляхи ресурсів, пам'ятайте, що вони відносні до файлу, у якому їх зазначено. Будь-які ресурси, на які посилаються за відносним шляхом, буде переписано, версіоновано та запаковано Vite.

Розгляньмо таку структуру проєкту:

```text
public/
  taylor.png
resources/
  js/
    Pages/
      Welcome.vue
  images/
    abigail.png
```

Наступний приклад демонструє, як Vite поводитиметься з відносними та абсолютними URL:

```html
<!-- This asset is not handled by Vite and will not be included in the build -->
<img src="/taylor.png">

<!-- This asset will be re-written, versioned, and bundled by Vite -->
<img src="../../images/abigail.png">
```

<a name="working-with-stylesheets"></a>
## Робота зі стилями

> [!NOTE]
> [Стартові набори](/docs/{{version}}/starter-kits) Laravel уже містять правильну конфігурацію Tailwind і Vite. Або, якщо ви хочете використовувати Tailwind із Laravel без наших стартових наборів, перегляньте [посібник Tailwind зі встановлення для Laravel](https://tailwindcss.com/docs/guides/laravel).

Усі застосунки Laravel уже містять Tailwind і правильно налаштований файл `vite.config.js`. Тож вам залишається лише запустити сервер розробки Vite або виконати команду Composer `dev`, яка запустить і сервер Laravel, і сервер Vite:

```shell
composer run dev
```

CSS вашого застосунку можна розмістити у файлі `resources/css/app.css`.

<a name="working-with-fonts"></a>
## Робота зі шрифтами

Плагін Laravel Vite може віддавати оптимізовані шрифти з вашого власного хостингу. Коли шрифти налаштовано, плагін розв'язує запитані файли шрифтів, віддає їх як ресурси Vite, генерує CSS шрифтів і записує маніфест, який може споживати [директива `@fonts`](/docs/{{version}}/blade#fonts) у Blade.

Щоб налаштувати шрифти, імпортуйте один чи кілька хелперів провайдерів із `laravel-vite-plugin/fonts` і додайте їх до опції `fonts` плагіна Laravel:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import { google } from 'laravel-vite-plugin/fonts';

export default defineConfig({
    plugins: [
        laravel({
            input: 'resources/js/app.js',
            fonts: [
                google('Inter', {
                    alias: 'sans',
                    weights: [400, 500, 600, 700],
                    styles: ['normal', 'italic'],
                    subsets: ['latin'],
                    display: 'swap',
                    preload: [
                        { weight: 400 },
                        { weight: 700 },
                    ],
                    fallbacks: ['system-ui', 'sans-serif'],
                }),
            ],
        }),
    ],
});
```

У цьому прикладі шрифт `Inter` буде доступний через псевдонім `sans`. Плагін згенерує CSS-змінну `--font-sans` і клас-утиліту `.font-sans`, що застосовує згенерований набір шрифтів.

<a name="font-providers"></a>
### Провайдери шрифтів

Плагін Laravel Vite містить хелпери провайдерів для Google Fonts, Bunny Fonts, Fontsource та локальних шрифтів:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import { bunny, fontsource, google, local } from 'laravel-vite-plugin/fonts';

export default defineConfig({
    plugins: [
        laravel({
            input: 'resources/js/app.js',
            fonts: [
                google('Inter', { alias: 'sans' }),
                bunny('Figtree', { alias: 'body' }),
                fontsource('JetBrains Mono', { alias: 'mono' }),
                local('Brand Sans', {
                    alias: 'brand',
                    src: 'resources/fonts/brand-sans',
                }),
            ],
        }),
    ],
});
```

Провайдер `fontsource` читає шрифти зі встановленого пакета Fontsource. За замовчуванням ім'я пакета виводиться з родини шрифтів - наприклад, `@fontsource/jetbrains-mono`. Якщо ваш застосунок використовує інше ім'я пакета, вкажіть його опцією `package`.

<a name="local-fonts"></a>
### Локальні шрифти

Використовуючи локальні шрифти, опція `src` може вказувати на один файл шрифту, каталог або шаблон glob. Плагін знайде підтримувані файли шрифтів і визначить їхню товщину та стиль з імен файлів:

```js
local('Brand Sans', {
    alias: 'brand',
    src: 'resources/fonts/brand-sans/*.woff2',
})
```

Якщо вам потрібен повний контроль над доступними варіантами, визначте їх явно опцією `variants`:

```js
local('Brand Sans', {
    alias: 'brand',
    variants: [
        { src: 'resources/fonts/BrandSans-Regular.woff2', weight: 400 },
        { src: 'resources/fonts/BrandSans-Italic.woff2', weight: 400, style: 'italic' },
        { src: ['resources/fonts/BrandSans-Bold.woff2', 'resources/fonts/BrandSans-Bold.ttf'], weight: 700 },
    ],
})
```

<a name="font-options"></a>
### Опції шрифтів

Залежно від провайдера, визначення шрифтів можуть приймати кілька опцій, що дозволяють налаштувати згенерований CSS:

<div class="content-list" markdown="1">

- `alias` визначає ім'я, яке використовує директива `@fonts` у Blade; за замовчуванням це slug родини шрифтів.
- `variable` визначає згенеровану CSS-змінну; за замовчуванням `--font-{alias}`.
- `weights` визначає віддалені товщини шрифтів чи товщини Fontsource, які слід розв'язати; за замовчуванням `[400]`.
- `styles` визначає віддалені стилі шрифтів чи стилі Fontsource, які слід розв'язати; за замовчуванням `['normal']`.
- `subsets` визначає віддалені підмножини шрифтів чи підмножини Fontsource, які слід розв'язати; за замовчуванням `['latin']`.
- `display` визначає значення `font-display`; за замовчуванням `swap`.
- `preload` керує тим, які варіанти WOFF2 слід завантажувати наперед. Ця опція може бути `true`, `false` або масивом селекторів `{ weight, style }`.
- `fallbacks` визначає додаткові резервні шрифти, які слід додати до згенерованого набору.
- `optimizedFallbacks` намагається згенерувати резервні шрифти з підлаштованими метриками за допомогою необов'язкового пакета `fontaine`; за замовчуванням `true`.

</div>

Оптимізовані резервні шрифти потребують пакета `fontaine`, який не встановлюється за замовчуванням. Якщо ви хочете, щоб Laravel генерував резервні шрифти з підлаштованими метриками, встановіть `fontaine` як залежність для розробки:

```shell
npm install --save-dev fontaine
```

Якщо `fontaine` не встановлено або він не може прочитати файл шрифту, Laravel пропустить оптимізований резервний шрифт для цього шрифту й далі використовуватиме шрифти, налаштовані опцією `fallbacks`.

Локальні шрифти розв'язуються з описаних вище опцій `src` чи `variants`, а не через `weights`, `styles` і `subsets`.

<a name="working-with-blade-and-routes"></a>
## Робота з Blade і маршрутами

<a name="blade-processing-static-assets"></a>
### Обробка статичних ресурсів через Vite

Коли ви посилаєтеся на ресурси у своєму JavaScript чи CSS, Vite автоматично обробляє й версіонує їх. Крім того, створюючи застосунки на Blade, Vite може обробляти й версіонувати статичні ресурси, на які ви посилаєтеся лише в шаблонах Blade.

Однак для цього потрібно повідомити Vite про ваші ресурси, вказавши їх в опції `assets` плагіна. Ця опція призначена для статичних файлів, на які ви хочете посилатися безпосередньо через `Vite::asset`. Якщо ви хочете, щоб Laravel генерував CSS шрифтів і посилання попереднього завантаження, скористайтеся натомість [опцією `fonts`](#working-with-fonts).

Наприклад, якщо ви хочете обробити й версіонувати всі зображення в `resources/images` та всі шрифти в `resources/fonts`, додайте до конфігурації Vite таке:

```js
laravel({
    input: 'resources/js/app.js',
    assets: ['resources/images/**', 'resources/fonts/**'],
})
```

Тепер ці ресурси оброблятиме Vite під час виконання `npm run build`. Далі ви можете посилатися на них у шаблонах Blade методом `Vite::asset`, який поверне версіоновану адресу ресурсу:

```blade
<img src="{{ Vite::asset('resources/images/logo.png') }}">
```

> [!NOTE]
> До версії 3 плагіна Laravel Vite статичні ресурси доводилося імпортувати в точці входу застосунку через `import.meta.glob`. Опцію `assets` запроваджено через зміни у Vite 8.

<a name="blade-refreshing-on-save"></a>
### Оновлення під час збереження

Коли ваш застосунок побудовано на традиційному серверному рендерингу з Blade, Vite може покращити ваш робочий процес, автоматично оновлюючи браузер, коли ви змінюєте файли представлень. Щоб почати, просто вкажіть опцію `refresh` зі значенням `true`.

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            // ...
            refresh: true,
        }),
    ],
});
```

Коли опція `refresh` має значення `true`, збереження файлів у таких каталогах змусить браузер повністю оновити сторінку, поки виконується `npm run dev`:

- `app/Livewire/**`
- `app/View/Components/**`
- `lang/**`
- `resources/lang/**`
- `resources/views/**`
- `routes/**`

Стеження за каталогом `routes/**` корисне, якщо ви використовуєте [Ziggy](https://github.com/tighten/ziggy) для генерації посилань на маршрути у фронтенді вашого застосунку.

Якщо типові шляхи вам не підходять, ви можете вказати власний список шляхів для стеження:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            // ...
            refresh: ['resources/views/**'],
        }),
    ],
});
```

Під капотом плагін Laravel Vite використовує пакет [vite-plugin-full-reload](https://github.com/ElMassimo/vite-plugin-full-reload), який пропонує розширені опції конфігурації для тонкого налаштування цієї можливості. Якщо вам потрібен такий рівень контролю, передайте визначення `config`:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            // ...
            refresh: [{
                paths: ['path/to/watch/**'],
                config: { delay: 300 }
            }],
        }),
    ],
});
```

<a name="blade-aliases"></a>
### Псевдоніми

У JavaScript-застосунках поширено [створювати псевдоніми](#aliases) для каталогів, на які часто посилаються. Але ви також можете створювати псевдоніми для використання в Blade методом `macro` класу `Illuminate\Support\Facades\Vite`. Зазвичай «макроси» слід визначати в методі `boot` [сервіс-провайдера](/docs/{{version}}/providers):

```php
/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Vite::macro('image', fn (string $asset) => $this->asset("resources/images/{$asset}"));
}
```

Щойно макрос визначено, його можна викликати у ваших шаблонах. Наприклад, ми можемо скористатися визначеним вище макросом `image`, щоб послатися на ресурс `resources/images/logo.png`:

```blade
<img src="{{ Vite::image('logo.png') }}" alt="Laravel Logo">
```

<a name="asset-prefetching"></a>
## Попереднє завантаження ресурсів

Створюючи SPA з можливістю розділення коду Vite, потрібні ресурси завантажуються під час кожного переходу між сторінками. Це може призводити до затримок у рендерингу інтерфейсу. Якщо для обраного вами фронтенд-фреймворку це проблема, Laravel дозволяє завчасно завантажувати JavaScript- і CSS-ресурси застосунку під час першого завантаження сторінки.

Вказати Laravel завчасно завантажувати ресурси можна викликом методу `Vite::prefetch` у методі `boot` [сервіс-провайдера](/docs/{{version}}/providers):

```php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Vite;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Vite::prefetch(concurrency: 3);
    }
}
```

У прикладі вище ресурси завантажуватимуться завчасно щонайбільше в `3` паралельні потоки під час кожного завантаження сторінки. Ви можете змінити паралельність під потреби свого застосунку або не вказувати обмеження взагалі, якщо застосунок має завантажувати всі ресурси одразу:

```php
/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Vite::prefetch();
}
```

За замовчуванням попереднє завантаження почнеться, коли спрацює [подія _load_ сторінки](https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event). Якщо ви хочете налаштувати момент початку, вкажіть подію, яку слухатиме Vite:

```php
/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Vite::prefetch(event: 'vite:prefetch');
}
```

З наведеним вище кодом попереднє завантаження починатиметься, коли ви вручну надішлете подію `vite:prefetch` на об'єкті `window`. Наприклад, ви можете почати завантаження через три секунди після завантаження сторінки:

```html
<script>
    addEventListener('load', () => setTimeout(() => {
        dispatchEvent(new Event('vite:prefetch'))
    }, 3000))
</script>
```

<a name="custom-base-urls"></a>
## Власні базові URL

Якщо ваші скомпільовані Vite ресурси розгорнуто на домені, відмінному від застосунку - наприклад, через CDN, - вам потрібно вказати змінну середовища `ASSET_URL` у файлі `.env` вашого застосунку:

```env
ASSET_URL=https://cdn.example.com
```

Після налаштування адреси ресурсів усі переписані URL до ваших ресурсів матимуть цей префікс:

```text
https://cdn.example.com/build/assets/app.9dce8d17.js
```

Пам'ятайте, що [абсолютні URL не переписуються Vite](#url-processing), тож префікса вони не отримають.

<a name="environment-variables"></a>
## Змінні середовища

Ви можете впроваджувати змінні середовища у свій JavaScript, додавши до них префікс `VITE_` у файлі `.env` вашого застосунку:

```env
VITE_SENTRY_DSN_PUBLIC=http://example.com
```

Звертатися до впроваджених змінних середовища можна через об'єкт `import.meta.env`:

```js
import.meta.env.VITE_SENTRY_DSN_PUBLIC
```

<a name="disabling-vite-in-tests"></a>
## Вимкнення Vite у тестах

Інтеграція Vite в Laravel намагатиметься розв'язати ваші ресурси під час виконання тестів, а це вимагає або запущеного сервера розробки Vite, або зібраних ресурсів.

Якщо ви віддаєте перевагу мокуванню Vite під час тестування, викличте метод `withoutVite`, доступний у будь-яких тестах, що успадковують клас `TestCase` від Laravel:

```php tab=Pest
test('without vite example', function () {
    $this->withoutVite();

    // ...
});
```

```php tab=PHPUnit
use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_without_vite_example(): void
    {
        $this->withoutVite();

        // ...
    }
}
```

Якщо ви хочете вимкнути Vite для всіх тестів, викличте метод `withoutVite` у методі `setUp` вашого базового класу `TestCase`:

```php
<?php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    protected function setUp(): void// [tl! add:start]
    {
        parent::setUp();

        $this->withoutVite();
    }// [tl! add:end]
}
```

<a name="ssr"></a>
## Рендеринг на боці сервера (SSR)

Плагін Laravel Vite робить налаштування серверного рендерингу з Vite безболісним. Щоб почати, створіть точку входу SSR у `resources/js/ssr.js` і вкажіть її, передавши опцію конфігурації плагіну Laravel:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            input: 'resources/js/app.js',
            ssr: 'resources/js/ssr.js',
        }),
    ],
});
```

Щоб не забути перезібрати точку входу SSR, радимо доповнити скрипт «build» у файлі `package.json` вашого застосунку:

```json
"scripts": {
     "dev": "vite",
     "build": "vite build" // [tl! remove]
     "build": "vite build && vite build --ssr" // [tl! add]
}
```

Далі, щоб зібрати й запустити SSR-сервер, виконайте такі команди:

```shell
npm run build
node bootstrap/ssr/ssr.js
```

Якщо ви використовуєте [SSR з Inertia](https://inertiajs.com/server-side-rendering), ви можете натомість запустити SSR-сервер командою Artisan `inertia:start-ssr`:

```shell
php artisan inertia:start-ssr
```

> [!NOTE]
> [Стартові набори](/docs/{{version}}/starter-kits) Laravel уже містять правильну конфігурацію Laravel, Inertia SSR і Vite. Вони є найшвидшим способом почати роботу з Laravel, Inertia SSR і Vite.

<a name="script-and-style-attributes"></a>
## Атрибути тегів script і style

<a name="content-security-policy-csp-nonce"></a>
### Nonce для політики безпеки вмісту (CSP)

Якщо ви хочете додати [атрибут nonce](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce) до своїх тегів script і style у межах [політики безпеки вмісту](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP), ви можете згенерувати чи вказати nonce методом `useCspNonce` у власному [`middleware`](/docs/{{version}}/middleware):

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Vite;
use Symfony\Component\HttpFoundation\Response;

class AddContentSecurityPolicyHeaders
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        Vite::useCspNonce();

        return $next($request)->withHeaders([
            'Content-Security-Policy' => "script-src 'nonce-".Vite::cspNonce()."'",
        ]);
    }
}
```

Після виклику методу `useCspNonce` Laravel автоматично додаватиме атрибути `nonce` до всіх згенерованих тегів script і style.

Якщо вам потрібно вказати nonce деінде - зокрема в [директиві Ziggy `@route`](https://github.com/tighten/ziggy#using-routes-with-a-content-security-policy), що входить до [стартових наборів](/docs/{{version}}/starter-kits) Laravel, - ви можете отримати його методом `cspNonce`:

```blade
@routes(nonce: Vite::cspNonce())
```

Якщо ви вже маєте nonce, який хочете передати Laravel, передайте його методу `useCspNonce`:

```php
Vite::useCspNonce($nonce);
```

<a name="subresource-integrity-sri"></a>
### Цілісність підресурсів (SRI)

Якщо ваш маніфест Vite містить хеші `integrity` для ресурсів, Laravel автоматично додасть атрибут `integrity` до всіх згенерованих тегів script і style, щоб забезпечити [цілісність підресурсів](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity). За замовчуванням Vite не додає хеш `integrity` до свого маніфесту, але ви можете увімкнути це, встановивши NPM-плагін [vite-plugin-manifest-sri](https://www.npmjs.com/package/vite-plugin-manifest-sri):

```shell
npm install --save-dev vite-plugin-manifest-sri
```

Далі ви можете увімкнути цей плагін у файлі `vite.config.js`:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import manifestSRI from 'vite-plugin-manifest-sri';// [tl! add]

export default defineConfig({
    plugins: [
        laravel({
            // ...
        }),
        manifestSRI(),// [tl! add]
    ],
});
```

За потреби ви також можете налаштувати ключ маніфесту, за яким знаходиться хеш цілісності:

```php
use Illuminate\Support\Facades\Vite;

Vite::useIntegrityKey('custom-integrity-key');
```

Якщо ви хочете цілком вимкнути це автоматичне визначення, передайте `false` методу `useIntegrityKey`:

```php
Vite::useIntegrityKey(false);
```

<a name="arbitrary-attributes"></a>
### Довільні атрибути

Якщо вам потрібно додати до тегів script і style додаткові атрибути - як-от [data-turbo-track](https://turbo.hotwired.dev/handbook/drive#reloading-when-assets-change), - ви можете вказати їх методами `useScriptTagAttributes` і `useStyleTagAttributes`. Зазвичай ці методи слід викликати із [сервіс-провайдера](/docs/{{version}}/providers):

```php
use Illuminate\Support\Facades\Vite;

Vite::useScriptTagAttributes([
    'data-turbo-track' => 'reload', // Specify a value for the attribute...
    'async' => true, // Specify an attribute without a value...
    'integrity' => false, // Exclude an attribute that would otherwise be included...
]);

Vite::useStyleTagAttributes([
    'data-turbo-track' => 'reload',
]);
```

Якщо вам потрібно додавати атрибути умовно, передайте колбек, який отримає шлях до джерела ресурсу, його URL, його фрагмент маніфесту та весь маніфест:

```php
use Illuminate\Support\Facades\Vite;

Vite::useScriptTagAttributes(fn (string $src, string $url, array|null $chunk, array|null $manifest) => [
    'data-turbo-track' => $src === 'resources/js/app.js' ? 'reload' : false,
]);

Vite::useStyleTagAttributes(fn (string $src, string $url, array|null $chunk, array|null $manifest) => [
    'data-turbo-track' => $chunk && $chunk['isEntry'] ? 'reload' : false,
]);
```

> [!WARNING]
> Аргументи `$chunk` і `$manifest` будуть `null`, поки працює сервер розробки Vite.

<a name="advanced-customization"></a>
## Розширене налаштування

Одразу після встановлення плагін Vite від Laravel використовує розумні домовленості, які підійдуть більшості застосунків; утім, іноді вам може знадобитися налаштувати поведінку Vite. Щоб увімкнути додаткові можливості налаштування, ми пропонуємо такі методи й опції, які можна використати замість директиви Blade `@vite`:

```blade
<!doctype html>
<head>
    {{-- ... --}}

    {{
        Vite::useHotFile(storage_path('vite.hot')) // Customize the "hot" file...
            ->useBuildDirectory('bundle') // Customize the build directory...
            ->useManifestFilename('assets.json') // Customize the manifest filename...
            ->withEntryPoints(['resources/js/app.js']) // Specify the entry points...
            ->createAssetPathsUsing(function (string $path, ?bool $secure) { // Customize the backend path generation for built assets...
                return "https://cdn.example.com/{$path}";
            })
    }}
</head>
```

Далі у файлі `vite.config.js` вам слід указати ту саму конфігурацію:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            hotFile: 'storage/vite.hot', // Customize the "hot" file...
            buildDirectory: 'bundle', // Customize the build directory...
            input: ['resources/js/app.js'], // Specify the entry points...
        }),
    ],
    build: {
      manifest: 'assets.json', // Customize the manifest filename...
    },
});
```

<a name="cors"></a>
### CORS сервера розробки

Якщо ви натрапляєте на проблеми зі спільним використанням ресурсів між джерелами (CORS) у браузері під час отримання ресурсів із сервера розробки Vite, вам може знадобитися надати вашому власному джерелу доступ до сервера розробки. Vite у поєднанні з плагіном Laravel дозволяє такі джерела без додаткової конфігурації:

- `::1`
- `127.0.0.1`
- `localhost`
- `*.test`
- `*.localhost`
- `APP_URL` у файлі `.env` проєкту

Найпростіший спосіб дозволити власне джерело для вашого проєкту - переконатися, що змінна середовища `APP_URL` збігається з джерелом, яке ви відкриваєте в браузері. Наприклад, якщо ви відвідуєте `https://my-app.laravel`, оновіть свій `.env` відповідно:

```env
APP_URL=https://my-app.laravel
```

Якщо вам потрібен тонший контроль над джерелами - наприклад, підтримка кількох джерел, - скористайтеся [вичерпною та гнучкою вбудованою конфігурацією CORS-сервера Vite](https://vite.dev/config/server-options.html#server-cors). Наприклад, ви можете вказати кілька джерел в опції `server.cors.origin` у файлі `vite.config.js` вашого проєкту:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            input: 'resources/js/app.js',
            refresh: true,
        }),
    ],
    server: {  // [tl! add]
        cors: {  // [tl! add]
            origin: [  // [tl! add]
                'https://backend.laravel',  // [tl! add]
                'http://admin.laravel:8566',  // [tl! add]
            ],  // [tl! add]
        },  // [tl! add]
    },  // [tl! add]
});
```

Ви також можете використовувати регулярні вирази - це стане в пригоді, якщо ви хочете дозволити всі джерела для певного домену верхнього рівня, як-от `*.laravel`:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            input: 'resources/js/app.js',
            refresh: true,
        }),
    ],
    server: {  // [tl! add]
        cors: {  // [tl! add]
            origin: [ // [tl! add]
                // Supports: SCHEME://DOMAIN.laravel[:PORT] [tl! add]
                /^https?:\/\/.*\.laravel(:\d+)?$/, //[tl! add]
            ], // [tl! add]
        }, // [tl! add]
    }, // [tl! add]
});
```

<a name="correcting-dev-server-urls"></a>
### Виправлення URL сервера розробки

Деякі плагіни в екосистемі Vite припускають, що URL, які починаються з прямої скісної риски, завжди вказують на сервер розробки Vite. Однак через природу інтеграції з Laravel це не так.

Наприклад, плагін `vite-imagetools` виводить такі URL, поки Vite віддає ваші ресурси:

```html
<img src="/@imagetools/f0b2f404b13f052c604e632f2fb60381bf61a520">
```

Плагін `vite-imagetools` очікує, що вихідний URL перехопить Vite, і тоді плагін зможе обробити всі URL, що починаються з `/@imagetools`. Якщо ви використовуєте плагіни, які очікують такої поведінки, вам доведеться виправляти URL вручну. Це можна зробити у файлі `vite.config.js` за допомогою опції `transformOnServe`.

У цьому конкретному прикладі ми додамо URL сервера розробки до всіх входжень `/@imagetools` у згенерованому коді:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import { imagetools } from 'vite-imagetools';

export default defineConfig({
    plugins: [
        laravel({
            // ...
            transformOnServe: (code, devServerUrl) => code.replaceAll('/@imagetools', devServerUrl+'/@imagetools'),
        }),
        imagetools(),
    ],
});
```

Тепер, поки Vite віддає ресурси, він виводитиме URL, що вказують на сервер розробки Vite:

```html
- <img src="/@imagetools/f0b2f404b13f052c604e632f2fb60381bf61a520"><!-- [tl! remove] -->
+ <img src="http://[::1]:5173/@imagetools/f0b2f404b13f052c604e632f2fb60381bf61a520"><!-- [tl! add] -->
```

---

Джерело: https://laravelukraine.com/docs/13.x/urls

# Генерація URL

- [Вступ](#introduction)
- [Основи](#the-basics)
    - [Генерація URL](#generating-urls)
    - [Доступ до поточного URL](#accessing-the-current-url)
- [URL для іменованих маршрутів](#urls-for-named-routes)
    - [Підписані URL](#signed-urls)
- [URL для дій контролерів](#urls-for-controller-actions)
- [Плинні об'єкти URI](#fluent-uri-objects)
- [Значення за замовчуванням](#default-values)

<a name="introduction"></a>
## Вступ

Laravel надає кілька хелперів, які допомагають генерувати URL для вашого застосунку. Вони насамперед корисні, коли ви створюєте посилання у шаблонах і відповідях API або генеруєте відповіді-перенаправлення до іншої частини застосунку.

<a name="the-basics"></a>
## Основи

<a name="generating-urls"></a>
### Генерація URL

Хелпер `url` дозволяє генерувати довільні URL для вашого застосунку. Згенерований URL автоматично використає схему (HTTP чи HTTPS) і хост із поточного запиту, який обробляє застосунок:

```php
$post = App\Models\Post::find(1);

echo url("/posts/{$post->id}");

// http://example.com/posts/1
```

Щоб згенерувати URL із параметрами рядка запиту, скористайтеся методом `query`:

```php
echo url()->query('/posts', ['search' => 'Laravel']);

// https://example.com/posts?search=Laravel

echo url()->query('/posts?sort=latest', ['search' => 'Laravel']);

// http://example.com/posts?sort=latest&search=Laravel
```

Передавання параметрів рядка запиту, які вже є у шляху, перезапише їхні наявні значення:

```php
echo url()->query('/posts?sort=latest', ['sort' => 'oldest']);

// http://example.com/posts?sort=oldest
```

Як параметри запиту можна також передавати масиви значень. Ці значення отримають коректні ключі й будуть закодовані у згенерованому URL:

```php
echo $url = url()->query('/posts', ['columns' => ['title', 'body']]);

// http://example.com/posts?columns%5B0%5D=title&columns%5B1%5D=body

echo urldecode($url);

// http://example.com/posts?columns[0]=title&columns[1]=body
```

<a name="accessing-the-current-url"></a>
### Доступ до поточного URL

Якщо хелперу `url` не передано шляху, повертається екземпляр `Illuminate\Routing\UrlGenerator`, що дає доступ до інформації про поточний URL:

```php
// Get the current URL without the query string...
echo url()->current();

// Get the current URL including the query string...
echo url()->full();
```

До кожного з цих методів можна також звернутися через [фасад](/docs/{{version}}/facades) `URL`:

```php
use Illuminate\Support\Facades\URL;

echo URL::current();
```

<a name="accessing-the-previous-url"></a>
#### Доступ до попереднього URL

Іноді корисно знати попередній URL, з якого прийшов користувач. Отримати його можна методами `previous` і `previousPath` хелпера `url`:

```php
// Get the full URL for the previous request...
echo url()->previous();

// Get the path for the previous request...
echo url()->previousPath();
```

Або ж через [сесію](/docs/{{version}}/session) ви можете отримати попередній URL як екземпляр [плинного URI](#fluent-uri-objects):

```php
use Illuminate\Http\Request;

Route::post('/users', function (Request $request) {
    $previousUri = $request->session()->previousUri();

    // ...
});
```

Через сесію також можна отримати ім'я маршруту попередньо відвіданого URL:

```php
$previousRoute = $request->session()->previousRoute();
```

<a name="urls-for-named-routes"></a>
## URL для іменованих маршрутів

Хелпер `route` дозволяє генерувати URL до [іменованих маршрутів](/docs/{{version}}/routing#named-routes). Іменовані маршрути дають змогу генерувати URL, не прив'язуючись до фактичної адреси, визначеної в маршруті. Тому якщо URL маршруту зміниться, ваші виклики функції `route` змінювати не доведеться. Наприклад, уявіть, що ваш застосунок містить такий маршрут:

```php
Route::get('/post/{post}', function (Post $post) {
    // ...
})->name('post.show');
```

Щоб згенерувати URL до цього маршруту, скористайтеся хелпером `route` так:

```php
echo route('post.show', ['post' => 1]);

// http://example.com/post/1
```

Звісно, хелпер `route` можна використовувати й для маршрутів із кількома параметрами:

```php
Route::get('/post/{post}/comment/{comment}', function (Post $post, Comment $comment) {
    // ...
})->name('comment.show');

echo route('comment.show', ['post' => 1, 'comment' => 3]);

// http://example.com/post/1/comment/3
```

Будь-які додаткові елементи масиву, що не відповідають параметрам у визначенні маршруту, буде додано до рядка запиту URL:

```php
echo route('post.show', ['post' => 1, 'search' => 'rocket']);

// http://example.com/post/1?search=rocket
```

<a name="eloquent-models"></a>
#### Моделі Eloquent

Часто ви генеруватимете URL за ключем маршруту (зазвичай первинним ключем) [моделей Eloquent](/docs/{{version}}/eloquent). Тому ви можете передавати моделі Eloquent як значення параметрів - хелпер `route` автоматично візьме ключ маршруту моделі:

```php
echo route('post.show', ['post' => $post]);
```

<a name="signed-urls"></a>
### Підписані URL

Laravel дозволяє легко створювати «підписані» URL до іменованих маршрутів. До рядка запиту таких URL додається хеш-«підпис», що дозволяє Laravel перевірити, чи не змінювався URL після створення. Підписані URL особливо корисні для маршрутів, які є загальнодоступними, але потребують шару захисту від маніпуляцій з адресою.

Наприклад, підписані URL можна використати для публічного посилання «відписатися», яке надсилають клієнтам електронною поштою. Щоб створити підписаний URL до іменованого маршруту, скористайтеся методом `signedRoute` фасаду `URL`:

```php
use Illuminate\Support\Facades\URL;

return URL::signedRoute('unsubscribe', ['user' => 1]);
```

Ви можете виключити домен із хешу підписаного URL, передавши методу `signedRoute` аргумент `absolute`:

```php
return URL::signedRoute('unsubscribe', ['user' => 1], absolute: false);
```

Якщо ви хочете згенерувати тимчасовий підписаний URL маршруту, що спливає через певний час, скористайтеся методом `temporarySignedRoute`. Перевіряючи тимчасовий підписаний URL, Laravel переконається, що закодована в ньому мітка часу спливання ще не минула:

```php
use Illuminate\Support\Facades\URL;

return URL::temporarySignedRoute(
    'unsubscribe', now()->plus(minutes: 30), ['user' => 1]
);
```

<a name="validating-signed-route-requests"></a>
#### Перевірка запитів до підписаних маршрутів

Щоб перевірити, чи має вхідний запит дійсний підпис, викличте метод `hasValidSignature` на екземплярі `Illuminate\Http\Request`:

```php
use Illuminate\Http\Request;

Route::get('/unsubscribe/{user}', function (Request $request) {
    if (! $request->hasValidSignature()) {
        abort(401);
    }

    // ...
})->name('unsubscribe');
```

Іноді фронтенду вашого застосунку потрібно додавати дані до підписаного URL - наприклад, під час пагінації на боці клієнта. Тому ви можете вказати параметри рядка запиту, які слід ігнорувати під час перевірки підписаного URL, методом `hasValidSignatureWhileIgnoring`. Пам'ятайте: ігнорування параметрів дозволяє будь-кому змінювати їх у запиті:

```php
if (! $request->hasValidSignatureWhileIgnoring(['page', 'order'])) {
    abort(401);
}
```

Замість перевіряти підписані URL через екземпляр вхідного запиту, ви можете призначити маршруту [`middleware`](/docs/{{version}}/middleware) `signed` (`Illuminate\Routing\Middleware\ValidateSignature`). Якщо вхідний запит не має дійсного підпису, `middleware` автоматично поверне HTTP-відповідь `403`:

```php
Route::post('/unsubscribe/{user}', function (Request $request) {
    // ...
})->name('unsubscribe')->middleware('signed');
```

Якщо ваші підписані URL не містять домену в хеші, передайте `middleware` аргумент `relative`:

```php
Route::post('/unsubscribe/{user}', function (Request $request) {
    // ...
})->name('unsubscribe')->middleware('signed:relative');
```

<a name="responding-to-invalid-signed-routes"></a>
#### Реакція на недійсні підписані маршрути

Коли хтось відвідує підписаний URL, термін дії якого сплив, він отримає загальну сторінку помилки для HTTP-статусу `403`. Однак ви можете налаштувати цю поведінку, визначивши власне замикання «render» для винятку `InvalidSignatureException` у файлі `bootstrap/app.php` вашого застосунку:

```php
use Illuminate\Routing\Exceptions\InvalidSignatureException;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->render(function (InvalidSignatureException $e) {
        return response()->view('errors.link-expired', status: 403);
    });
})
```

<a name="urls-for-controller-actions"></a>
## URL для дій контролерів

Функція `action` генерує URL для вказаної дії контролера:

```php
use App\Http\Controllers\HomeController;

$url = action([HomeController::class, 'index']);
```

Якщо метод контролера приймає параметри маршруту, ви можете передати асоціативний масив параметрів другим аргументом функції:

```php
$url = action([UserController::class, 'profile'], ['id' => 1]);
```

<a name="fluent-uri-objects"></a>
## Плинні об'єкти URI

Клас `Uri` Laravel надає зручний плинний інтерфейс для створення URI та роботи з ними через об'єкти. Цей клас обгортає функціональність пакета League URI й бездоганно інтегрується із системою маршрутизації Laravel.

Створити екземпляр `Uri` легко за допомогою статичних методів:

```php
use App\Http\Controllers\UserController;
use App\Http\Controllers\InvokableController;
use Illuminate\Support\Uri;

// Generate a URI instance from the given string...
$uri = Uri::of('https://example.com/path');

// Generate URI instances to paths, named routes, or controller actions...
$uri = Uri::to('/dashboard');
$uri = Uri::route('users.show', ['user' => 1]);
$uri = Uri::signedRoute('users.show', ['user' => 1]);
$uri = Uri::temporarySignedRoute('user.index', now()->plus(minutes: 5));
$uri = Uri::action([UserController::class, 'index']);
$uri = Uri::action(InvokableController::class);

// Generate a URI instance from the current request URL...
$uri = $request->uri();

// Generate a URI instance from the previous request URL...
$uri = $request->session()->previousUri();
```

Маючи екземпляр URI, ви можете плинно його змінювати:

```php
$uri = Uri::of('https://example.com')
    ->withScheme('http')
    ->withHost('test.com')
    ->withPort(8000)
    ->withPath('/users')
    ->withQuery(['page' => 2])
    ->withFragment('section-1');
```

Докладніше про роботу з плинними об'єктами URI дивіться в [документації URI](/docs/{{version}}/helpers#uri).

<a name="default-values"></a>
## Значення за замовчуванням

У деяких застосунках вам може знадобитися задати значення за замовчуванням для певних параметрів URL у межах усього запиту. Наприклад, уявіть, що багато ваших маршрутів визначають параметр `{locale}`:

```php
Route::get('/{locale}/posts', function () {
    // ...
})->name('post.index');
```

Марудно щоразу передавати `locale`, викликаючи хелпер `route`. Тож ви можете скористатися методом `URL::defaults`, щоб задати значення за замовчуванням для цього параметра, яке застосовуватиметься протягом поточного запиту. Цей метод варто викликати з [`middleware` маршруту](/docs/{{version}}/middleware#assigning-middleware-to-routes), щоб мати доступ до поточного запиту:

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\URL;
use Symfony\Component\HttpFoundation\Response;

class SetDefaultLocaleForUrls
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        URL::defaults(['locale' => $request->user()->locale]);

        return $next($request);
    }
}
```

Щойно значення за замовчуванням для параметра `locale` задано, вам більше не потрібно передавати його, генеруючи URL хелпером `route`.

<a name="url-defaults-middleware-priority"></a>
#### Значення URL за замовчуванням і пріоритет middleware

Задавання значень URL за замовчуванням може заважати обробці неявних прив'язок моделей у Laravel. Тому вам слід [задати пріоритет своєму `middleware`](/docs/{{version}}/middleware#sorting-middleware), який встановлює ці значення, щоб він виконувався перед власним `middleware` `SubstituteBindings` від Laravel. Це можна зробити методом `priority` у файлі `bootstrap/app.php` вашого застосунку:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->prependToPriorityList(
        before: \Illuminate\Routing\Middleware\SubstituteBindings::class,
        prepend: \App\Http\Middleware\SetDefaultLocaleForUrls::class,
    );
})
```

---

Джерело: https://laravelukraine.com/docs/13.x/session

# HTTP-сесія

- [Вступ](#introduction)
    - [Конфігурація](#configuration)
    - [Передумови драйверів](#driver-prerequisites)
- [Робота із сесією](#interacting-with-the-session)
    - [Отримання даних](#retrieving-data)
    - [Збереження даних](#storing-data)
    - [Флеш-дані](#flash-data)
    - [Видалення даних](#deleting-data)
    - [Регенерація ідентифікатора сесії](#regenerating-the-session-id)
- [Кеш сесії](#session-cache)
- [Блокування сесії](#session-blocking)
- [Додавання власних драйверів сесії](#adding-custom-session-drivers)
    - [Реалізація драйвера](#implementing-the-driver)
    - [Реєстрація драйвера](#registering-the-driver)

<a name="introduction"></a>
## Вступ

Оскільки застосунки на основі HTTP є безстановими, сесії дають спосіб зберігати інформацію про користувача між кількома запитами. Зазвичай ця інформація розміщується в постійному сховищі, доступному з наступних запитів.

Laravel постачається з різноманітними бекендами сесій, доступними через виразний уніфікований API. Підтримку популярних бекендів, як-от [Memcached](https://memcached.org), [Redis](https://redis.io) і баз даних, уже вбудовано.

<a name="configuration"></a>
### Конфігурація

Конфігураційний файл сесій вашого застосунку зберігається за шляхом `config/session.php`. Обов'язково перегляньте доступні в ньому опції. За замовчуванням Laravel налаштовано на драйвер сесій `database`.

Опція конфігурації `driver` визначає, де зберігатимуться дані сесії для кожного запиту. Laravel містить кілька драйверів:

<div class="content-list" markdown="1">

- `file` - сесії зберігаються в `storage/framework/sessions`.
- `cookie` - сесії зберігаються в захищених зашифрованих cookie.
- `database` - сесії зберігаються в реляційній базі даних.
- `memcached` / `redis` - сесії зберігаються в одному з цих швидких сховищ на основі кешу.
- `dynamodb` - сесії зберігаються в AWS DynamoDB.
- `array` - сесії зберігаються в PHP-масиві і не є постійними.

</div>

> [!NOTE]
> Драйвер array використовується насамперед під час [тестування](/docs/{{version}}/testing) і не дає даним сесії зберігатися постійно.

<a name="driver-prerequisites"></a>
### Передумови драйверів

<a name="database"></a>
#### База даних

Використовуючи драйвер сесій `database`, вам потрібно переконатися, що у вас є таблиця бази даних для зберігання даних сесії. Зазвичай її створює типова [міграція](/docs/{{version}}/migrations) Laravel `0001_01_01_000000_create_users_table.php`; однак якщо з якоїсь причини таблиці `sessions` у вас немає, ви можете згенерувати цю міграцію командою Artisan `make:session-table`:

```shell
php artisan make:session-table

php artisan migrate
```

<a name="redis"></a>
#### Redis

Перш ніж використовувати сесії Redis із Laravel, вам потрібно встановити PHP-розширення PhpRedis через PECL або пакет `predis/predis` через Composer. Докладніше про налаштування Redis дивіться в [документації Laravel щодо Redis](/docs/{{version}}/redis#configuration).

> [!NOTE]
> Змінна середовища `SESSION_CONNECTION` або опція `connection` у конфігураційному файлі `session.php` дозволяють вказати, яке підключення Redis використовується для зберігання сесій.

<a name="interacting-with-the-session"></a>
## Робота із сесією

<a name="retrieving-data"></a>
### Отримання даних

Є два основні способи роботи з даними сесії в Laravel: глобальний хелпер `session` і екземпляр `Request`. Спершу розгляньмо доступ до сесії через екземпляр `Request`, тип якого можна вказати в замиканні маршруту чи методі контролера. Пам'ятайте: залежності методів контролера автоматично впроваджуються через [сервіс-контейнер](/docs/{{version}}/container) Laravel:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * Show the profile for the given user.
     */
    public function show(Request $request, string $id): View
    {
        $value = $request->session()->get('key');

        // ...

        $user = $this->users->find($id);

        return view('user.profile', ['user' => $user]);
    }
}
```

Отримуючи елемент із сесії, ви також можете передати значення за замовчуванням другим аргументом методу `get`. Воно повернеться, якщо вказаного ключа в сесії немає. Якщо ви передасте методу `get` замикання як значення за замовчуванням і запитаного ключа не існує, замикання буде виконано, а його результат повернуто:

```php
$value = $request->session()->get('key', 'default');

$value = $request->session()->get('key', function () {
    return 'default';
});
```

<a name="the-global-session-helper"></a>
#### Глобальний хелпер session

Ви також можете скористатися глобальною PHP-функцією `session`, щоб отримувати та зберігати дані в сесії. Коли хелпер `session` викликається з одним рядковим аргументом, він повертає значення відповідного ключа сесії. Коли ж його викликають із масивом пар «ключ - значення», ці значення зберігаються в сесії:

```php
Route::get('/home', function () {
    // Retrieve a piece of data from the session...
    $value = session('key');

    // Specifying a default value...
    $value = session('key', 'default');

    // Store a piece of data in the session...
    session(['key' => 'value']);
});
```

> [!NOTE]
> Практичної різниці між використанням сесії через екземпляр HTTP-запиту та через глобальний хелпер `session` майже немає. Обидва підходи можна [тестувати](/docs/{{version}}/testing) методом `assertSessionHas`, доступним у всіх ваших тестах.

<a name="retrieving-all-session-data"></a>
#### Отримання всіх даних сесії

Якщо ви хочете отримати всі дані сесії, скористайтеся методом `all`:

```php
$data = $request->session()->all();
```

<a name="retrieving-a-portion-of-the-session-data"></a>
#### Отримання частини даних сесії

Методи `only` та `except` дозволяють отримати підмножину даних сесії:

```php
$data = $request->session()->only(['username', 'email']);

$data = $request->session()->except(['username', 'email']);
```

<a name="determining-if-an-item-exists-in-the-session"></a>
#### Визначення наявності елемента в сесії

Щоб визначити, чи присутній елемент у сесії, скористайтеся методом `has`. Він повертає `true`, якщо елемент присутній і не дорівнює `null`:

```php
if ($request->session()->has('users')) {
    // ...
}
```

Щоб визначити, чи присутній елемент у сесії, навіть якщо його значення `null`, скористайтеся методом `exists`:

```php
if ($request->session()->exists('users')) {
    // ...
}
```

Щоб визначити, що елемента в сесії немає, скористайтеся методом `missing`. Він повертає `true`, якщо елемент відсутній:

```php
if ($request->session()->missing('users')) {
    // ...
}
```

<a name="storing-data"></a>
### Збереження даних

Щоб зберегти дані в сесії, ви зазвичай використовуватимете метод `put` екземпляра запиту або глобальний хелпер `session`:

```php
// Via a request instance...
$request->session()->put('key', 'value');

// Via the global "session" helper...
session(['key' => 'value']);
```

<a name="pushing-to-array-session-values"></a>
#### Додавання до значень-масивів у сесії

Метод `push` дозволяє додати нове значення до значення сесії, що є масивом. Наприклад, якщо ключ `user.teams` містить масив назв команд, ви можете додати до нього нове значення так:

```php
$request->session()->push('user.teams', 'developers');
```

<a name="retrieving-deleting-an-item"></a>
#### Отримання та видалення елемента

Метод `pull` отримає й видалить елемент із сесії однією інструкцією:

```php
$value = $request->session()->pull('key', 'default');
```

<a name="incrementing-and-decrementing-session-values"></a>
#### Збільшення та зменшення значень сесії

Якщо дані вашої сесії містять ціле число, яке ви хочете збільшити чи зменшити, скористайтеся методами `increment` і `decrement`:

```php
$request->session()->increment('count');

$request->session()->increment('count', $incrementBy = 2);

$request->session()->decrement('count');

$request->session()->decrement('count', $decrementBy = 2);
```

<a name="flash-data"></a>
### Флеш-дані

Іноді вам може знадобитися зберегти елементи в сесії лише для наступного запиту. Це робиться методом `flash`. Дані, збережені в сесії цим методом, будуть доступні одразу й протягом наступного HTTP-запиту. Після нього флеш-дані буде видалено. Флеш-дані насамперед корисні для короткочасних повідомлень про стан:

```php
$request->session()->flash('status', 'Task was successful!');
```

Якщо вам потрібно зберегти флеш-дані на кілька запитів, скористайтеся методом `reflash`, який залишить усі флеш-дані ще на один запит. Якщо ж потрібно зберегти лише певні дані, скористайтеся методом `keep`:

```php
$request->session()->reflash();

$request->session()->keep(['username', 'email']);
```

Щоб зберегти флеш-дані лише для поточного запиту, скористайтеся методом `now`:

```php
$request->session()->now('status', 'Task was successful!');
```

<a name="deleting-data"></a>
### Видалення даних

Метод `forget` вилучить фрагмент даних із сесії. Якщо ви хочете вилучити всі дані, скористайтеся методом `flush`:

```php
// Forget a single key...
$request->session()->forget('name');

// Forget multiple keys...
$request->session()->forget(['name', 'status']);

$request->session()->flush();
```

<a name="regenerating-the-session-id"></a>
### Регенерація ідентифікатора сесії

Регенерацію ідентифікатора сесії часто виконують, щоб завадити зловмисникам скористатися атакою [фіксації сесії](https://owasp.org/www-community/attacks/Session_fixation) на ваш застосунок.

Laravel автоматично регенерує ідентифікатор сесії під час автентифікації, якщо ви використовуєте один зі [стартових наборів](/docs/{{version}}/starter-kits) чи [Laravel Fortify](/docs/{{version}}/fortify); однак якщо вам потрібно зробити це вручну, скористайтеся методом `regenerate`:

```php
$request->session()->regenerate();
```

Якщо вам потрібно регенерувати ідентифікатор сесії й вилучити всі дані однією інструкцією, скористайтеся методом `invalidate`:

```php
$request->session()->invalidate();
```

<a name="session-cache"></a>
## Кеш сесії

Кеш сесії Laravel дає зручний спосіб кешувати дані в межах окремої сесії користувача. На відміну від глобального кешу застосунку, дані кешу сесії автоматично ізольовані для кожної сесії й очищаються, коли сесія спливає або знищується. Кеш сесії підтримує всі звичні [методи кешу Laravel](/docs/{{version}}/cache) - `get`, `put`, `remember`, `forget` тощо, - але в межах поточної сесії.

Кеш сесії чудово підходить для зберігання тимчасових даних, специфічних для користувача, які ви хочете зберегти між кількома запитами в межах однієї сесії, але не потребуєте зберігати назавжди. Це можуть бути дані форм, тимчасові обчислення, відповіді API чи будь-які інші ефемерні дані, прив'язані до сесії конкретного користувача.

Доступ до кешу сесії можна отримати методом `cache` на сесії:

```php
$discount = $request->session()->cache()->get('discount');

$request->session()->cache()->put(
    'discount', 10, now()->plus(minutes: 5)
);
```

Докладніше про методи кешу Laravel дивіться в [документації з кешу](/docs/{{version}}/cache).

<a name="session-blocking"></a>
## Блокування сесії

> [!WARNING]
> Щоб скористатися блокуванням сесії, ваш застосунок має використовувати драйвер кешу з підтримкою [атомарних блокувань](/docs/{{version}}/cache#atomic-locks). Наразі це драйвери `memcached`, `dynamodb`, `redis`, `mongodb` (входить до офіційного пакета `mongodb/laravel-mongodb`), `database`, `file` та `array`. Крім того, ви не можете використовувати драйвер сесій `cookie`.

За замовчуванням Laravel дозволяє запитам однієї сесії виконуватися паралельно. Тож, наприклад, якщо ви використовуєте JavaScript-бібліотеку HTTP, щоб зробити два запити до вашого застосунку, вони виконуватимуться одночасно. Для багатьох застосунків це не проблема; однак втрата даних сесії може статися в невеликій частині застосунків, які роблять паралельні запити до двох різних точок, що обидві пишуть у сесію.

Щоб цьому запобігти, Laravel надає можливість обмежити паралельні запити для певної сесії. Щоб почати, просто додайте метод `block` до визначення маршруту. У цьому прикладі вхідний запит до точки `/profile` отримає блокування сесії. Поки воно триває, будь-які вхідні запити до `/profile` чи `/order` із тим самим ідентифікатором сесії чекатимуть завершення першого запиту, перш ніж продовжити виконання:

```php
Route::post('/profile', function () {
    // ...
})->block($lockSeconds = 10, $waitSeconds = 10);

Route::post('/order', function () {
    // ...
})->block($lockSeconds = 10, $waitSeconds = 10);
```

Метод `block` приймає два необов'язкові аргументи. Перший - максимальна кількість секунд, протягом яких блокування сесії має утримуватися перед звільненням. Звісно, якщо запит завершиться раніше, блокування буде знято раніше.

Другий аргумент - кількість секунд, які запит має чекати, намагаючись отримати блокування сесії. Якщо запит не зможе отримати блокування за вказану кількість секунд, буде викинуто `Illuminate\Contracts\Cache\LockTimeoutException`.

Якщо не передати жодного з цих аргументів, блокування отримають максимум на 10 секунд, і запити чекатимуть на нього максимум 10 секунд:

```php
Route::post('/profile', function () {
    // ...
})->block();
```

<a name="adding-custom-session-drivers"></a>
## Додавання власних драйверів сесії

<a name="implementing-the-driver"></a>
### Реалізація драйвера

Якщо жоден із наявних драйверів сесій не відповідає потребам вашого застосунку, Laravel дозволяє написати власний обробник сесій. Ваш драйвер має реалізувати вбудований у PHP інтерфейс `SessionHandlerInterface`, що містить лише кілька простих методів. Заготовка реалізації для MongoDB виглядає так:

```php
<?php

namespace App\Extensions;

class MongoSessionHandler implements \SessionHandlerInterface
{
    public function open($savePath, $sessionName) {}
    public function close() {}
    public function read($sessionId) {}
    public function write($sessionId, $data) {}
    public function destroy($sessionId) {}
    public function gc($lifetime) {}
}
```

Оскільки Laravel не має типового каталогу для ваших розширень, ви вільні розміщувати їх де завгодно. У цьому прикладі ми створили каталог `Extensions` для `MongoSessionHandler`.

Оскільки призначення цих методів не є очевидним, ось огляд кожного з них:

<div class="content-list" markdown="1">

- Метод `open` зазвичай використовується у файлових системах зберігання сесій. Оскільки Laravel постачається з драйвером сесій `file`, вам рідко доведеться щось у нього додавати. Ви можете просто залишити його порожнім.
- Метод `close`, як і `open`, зазвичай теж можна не враховувати. Для більшості драйверів він не потрібен.
- Метод `read` має повертати рядкову версію даних сесії, пов'язаних із переданим `$sessionId`. Виконувати серіалізацію чи інше кодування під час отримання чи збереження даних сесії у вашому драйвері не потрібно - Laravel зробить це за вас.
- Метод `write` має записати переданий рядок `$data`, пов'язаний із `$sessionId`, до якогось постійного сховища - MongoDB чи іншого на ваш вибір. Знову ж таки, серіалізацію виконувати не потрібно: Laravel уже подбав про це.
- Метод `destroy` має вилучити з постійного сховища дані, пов'язані з `$sessionId`.
- Метод `gc` має знищити всі дані сесій, старші за переданий `$lifetime`, що є кількістю секунд. Для систем із самостійним спливанням, як-от Memcached і Redis, цей метод можна лишити порожнім.

</div>

<a name="registering-the-driver"></a>
### Реєстрація драйвера

Щойно ваш драйвер реалізовано, ви готові зареєструвати його в Laravel. Щоб додати драйвери до бекенду сесій Laravel, скористайтеся методом `extend` [фасаду](/docs/{{version}}/facades) `Session`. Викликати `extend` слід із методу `boot` [сервіс-провайдера](/docs/{{version}}/providers). Ви можете зробити це з наявного `App\Providers\AppServiceProvider` або створити цілком новий провайдер:

```php
<?php

namespace App\Providers;

use App\Extensions\MongoSessionHandler;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\ServiceProvider;

class SessionServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Session::extend('mongo', function (Application $app) {
            // Return an implementation of SessionHandlerInterface...
            return new MongoSessionHandler;
        });
    }
}
```

Щойно драйвер сесій зареєстровано, ви можете вказати драйвер `mongo` як драйвер сесій вашого застосунку через змінну середовища `SESSION_DRIVER` або в конфігураційному файлі `config/session.php`.

---

Джерело: https://laravelukraine.com/docs/13.x/validation

# Валідація

- [Вступ](#introduction)
- [Швидкий старт](#validation-quickstart)
    - [Визначення маршрутів](#quick-defining-the-routes)
    - [Створення контролера](#quick-creating-the-controller)
    - [Написання логіки валідації](#quick-writing-the-validation-logic)
    - [Виведення помилок валідації](#quick-displaying-the-validation-errors)
    - [Повторне заповнення форм](#repopulating-forms)
    - [Зауваження про необов'язкові поля](#a-note-on-optional-fields)
    - [Формат відповіді з помилками валідації](#validation-error-response-format)
- [Валідація через запити форм](#form-request-validation)
    - [Створення запитів форм](#creating-form-requests)
    - [Авторизація запитів форм](#authorizing-form-requests)
    - [Налаштування повідомлень про помилки](#customizing-the-error-messages)
    - [Підготовка даних до валідації](#preparing-input-for-validation)
- [Ручне створення валідаторів](#manually-creating-validators)
    - [Автоматичне перенаправлення](#automatic-redirection)
    - [Іменовані набори помилок](#named-error-bags)
    - [Налаштування повідомлень про помилки](#manual-customizing-the-error-messages)
    - [Додаткова валідація](#performing-additional-validation)
- [Робота з валідованими даними](#working-with-validated-input)
- [Робота з повідомленнями про помилки](#working-with-error-messages)
    - [Власні повідомлення у мовних файлах](#specifying-custom-messages-in-language-files)
    - [Атрибути у мовних файлах](#specifying-attribute-in-language-files)
    - [Значення у мовних файлах](#specifying-values-in-language-files)
- [Доступні правила валідації](#available-validation-rules)
- [Умовне додавання правил](#conditionally-adding-rules)
- [Валідація масивів](#validating-arrays)
    - [Валідація вкладених масивів](#validating-nested-array-input)
    - [Індекси та позиції в повідомленнях про помилки](#error-message-indexes-and-positions)
- [Валідація файлів](#validating-files)
- [Валідація паролів](#validating-passwords)
- [Власні правила валідації](#custom-validation-rules)
    - [Використання об'єктів правил](#using-rule-objects)
    - [Використання замикань](#using-closures)
    - [Неявні правила](#implicit-rules)

<a name="introduction"></a>
## Вступ

Laravel пропонує кілька різних підходів до валідації вхідних даних вашого застосунку. Найпоширеніший - скористатися методом `validate`, доступним для всіх вхідних HTTP-запитів. Утім, ми розглянемо й інші підходи до валідації.

Laravel містить широкий набір зручних правил валідації, які ви можете застосовувати до даних, - зокрема можливість перевірити унікальність значення в певній таблиці бази даних. Ми детально розглянемо кожне з цих правил, щоб ви ознайомилися з усіма можливостями валідації в Laravel.

<a name="validation-quickstart"></a>
## Швидкий старт

Щоб дізнатися про потужні можливості валідації в Laravel, розгляньмо повний приклад валідації форми та показу повідомлень про помилки користувачеві. Прочитавши цей загальний огляд, ви добре зрозумієте, як валідувати вхідні дані запиту засобами Laravel:

<a name="quick-defining-the-routes"></a>
### Визначення маршрутів

Спершу припустімо, що ми маємо такі маршрути у файлі `routes/web.php`:

```php
use App\Http\Controllers\PostController;

Route::get('/post/create', [PostController::class, 'create']);
Route::post('/post', [PostController::class, 'store']);
```

Маршрут `GET` показуватиме форму створення нового допису блогу, а маршрут `POST` зберігатиме допис у базі даних.

<a name="quick-creating-the-controller"></a>
### Створення контролера

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

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

class PostController extends Controller
{
    /**
     * Show the form to create a new blog post.
     */
    public function create(): View
    {
        return view('post.create');
    }

    /**
     * Store a new blog post.
     */
    public function store(Request $request): RedirectResponse
    {
        // Validate and store the blog post...

        $post = /** ... */

        return to_route('post.show', ['post' => $post->id]);
    }
}
```

<a name="quick-writing-the-validation-logic"></a>
### Написання логіки валідації

Тепер ми готові заповнити метод `store` логікою валідації нового допису. Для цього скористаємося методом `validate`, який надає об'єкт `Illuminate\Http\Request`. Якщо правила валідації пройдено, ваш код виконуватиметься далі як звичайно; однак якщо валідація не пройшла, буде викинуто виняток `Illuminate\Validation\ValidationException`, і користувачеві автоматично буде надіслано відповідну відповідь із помилкою.

Якщо валідація не пройшла під час традиційного HTTP-запиту, буде згенеровано відповідь-перенаправлення на попередню адресу. Якщо вхідний запит є XHR-запитом, буде повернуто [JSON-відповідь із повідомленнями про помилки валідації](#validation-error-response-format).

Щоб краще зрозуміти метод `validate`, повернімося до методу `store`:

```php
/**
 * Store a new blog post.
 */
public function store(Request $request): RedirectResponse
{
    $validated = $request->validate([
        'title' => ['required', 'unique:posts', 'max:255'],
        'body' => ['required'],
    ]);

    // The blog post is valid...

    return redirect('/posts');
}
```

Як бачите, правила валідації передаються методу `validate`. Не хвилюйтеся - усі доступні правила [задокументовано](#available-validation-rules). Знову ж таки, якщо валідація не пройде, відповідну відповідь буде згенеровано автоматично. Якщо валідація пройшла, наш контролер продовжить виконуватися як звичайно.

Крім того, ви можете скористатися методом `validateWithBag`, щоб валідувати запит і зберегти повідомлення про помилки в [іменованому наборі помилок](#named-error-bags):

```php
$validated = $request->validateWithBag('post', [
    'title' => ['required', 'unique:posts', 'max:255'],
    'body' => ['required'],
]);
```

<a name="stopping-on-first-validation-failure"></a>
#### Зупинка на першій невдалій перевірці

Іноді ви можете захотіти припинити виконання правил валідації для атрибута після першої невдачі. Для цього призначте атрибуту правило `bail`:

```php
$request->validate([
    'title' => ['bail', 'required', 'unique:posts', 'max:255'],
    'body' => ['required'],
]);
```

У цьому прикладі, якщо правило `unique` для атрибута `title` не пройде, правило `max` не перевірятиметься. Правила перевіряються в порядку їх призначення.

<a name="a-note-on-nested-attributes"></a>
#### Зауваження про вкладені атрибути

Якщо вхідний HTTP-запит містить «вкладені» дані полів, ви можете вказати ці поля у правилах валідації через «крапковий» синтаксис:

```php
$request->validate([
    'title' => ['required', 'unique:posts', 'max:255'],
    'author.name' => ['required'],
    'author.description' => ['required'],
]);
```

З іншого боку, якщо ім'я вашого поля містить справжню крапку, ви можете явно запобігти її трактуванню як «крапкового» синтаксису, екранувавши її зворотним слешем:

```php
$request->validate([
    'title' => ['required', 'unique:posts', 'max:255'],
    'v1\.0' => ['required'],
]);
```

<a name="quick-displaying-the-validation-errors"></a>
### Виведення помилок валідації

Отже, що станеться, якщо поля вхідного запиту не пройдуть указаних правил валідації? Як згадувалося раніше, Laravel автоматично перенаправить користувача на попередню сторінку. Крім того, усі помилки валідації та [вхідні дані запиту](/docs/{{version}}/requests#retrieving-old-input) буде автоматично [записано до сесії](/docs/{{version}}/session#flash-data).

Змінна `$errors` доступна всім представленням вашого застосунку завдяки `middleware` `Illuminate\View\Middleware\ShareErrorsFromSession`, який входить до групи `web`. Коли цей `middleware` застосовано, змінна `$errors` завжди доступна у ваших представленнях, тож ви можете спокійно вважати, що вона завжди визначена. Змінна `$errors` є екземпляром `Illuminate\Support\MessageBag`. Докладніше про роботу з цим об'єктом читайте в [його документації](#working-with-error-messages).

Тож у нашому прикладі, коли валідація не пройде, користувача буде перенаправлено до методу `create` нашого контролера, що дозволить показати повідомлення про помилки в представленні:

```blade
<!-- /resources/views/post/create.blade.php -->

<h1>Create Post</h1>

@if ($errors->any())
    <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif

<!-- Create Post Form -->
```

<a name="quick-customizing-the-error-messages"></a>
#### Налаштування повідомлень про помилки

Кожне вбудоване правило валідації Laravel має повідомлення про помилку, розташоване у файлі `lang/en/validation.php` вашого застосунку. Якщо ваш застосунок не має каталогу `lang`, ви можете створити його командою Artisan `lang:publish`.

У файлі `lang/en/validation.php` ви знайдете запис перекладу для кожного правила валідації. Ви вільні змінювати ці повідомлення відповідно до потреб свого застосунку.

Крім того, ви можете скопіювати цей файл до каталогу іншої мови, щоб перекласти повідомлення мовою вашого застосунку. Докладніше про локалізацію в Laravel читайте в повній [документації з локалізації](/docs/{{version}}/localization).

> [!WARNING]
> За замовчуванням каркас застосунку Laravel не містить каталогу `lang`. Якщо ви хочете налаштувати мовні файли Laravel, опублікуйте їх командою Artisan `lang:publish`.

<a name="quick-xhr-requests-and-validation"></a>
#### XHR-запити та валідація

У цьому прикладі ми використали традиційну форму для надсилання даних застосунку. Однак багато застосунків отримують XHR-запити від фронтенду на JavaScript. Використовуючи метод `validate` під час XHR-запиту, Laravel не генеруватиме відповідь-перенаправлення. Натомість Laravel згенерує [JSON-відповідь з усіма помилками валідації](#validation-error-response-format). Цю JSON-відповідь буде надіслано зі статус-кодом 422.

<a name="the-at-error-directive"></a>
#### Директива `@error`

Ви можете скористатися директивою [Blade](/docs/{{version}}/blade) `@error`, щоб швидко визначити, чи існують повідомлення про помилки валідації для певного атрибута. Усередині директиви `@error` ви можете вивести змінну `$message`, щоб показати повідомлення про помилку:

```blade
<!-- /resources/views/post/create.blade.php -->

<label for="title">Post Title</label>

<input
    id="title"
    type="text"
    name="title"
    class="@error('title') is-invalid @enderror"
/>

@error('title')
    <div class="alert alert-danger">{{ $message }}</div>
@enderror
```

Якщо ви користуєтеся [іменованими наборами помилок](#named-error-bags), передайте ім'я набору другим аргументом директиви `@error`:

```blade
<input ... class="@error('title', 'post') is-invalid @enderror">
```

<a name="repopulating-forms"></a>
### Повторне заповнення форм

Коли Laravel генерує відповідь-перенаправлення через помилку валідації, фреймворк автоматично [записує всі вхідні дані запиту до сесії](/docs/{{version}}/session#flash-data). Це робиться для того, щоб ви могли зручно звернутися до цих даних під час наступного запиту й заново заповнити форму, яку намагався надіслати користувач.

Щоб отримати збережені дані попереднього запиту, викличте метод `old` на екземплярі `Illuminate\Http\Request`. Метод `old` візьме раніше збережені дані із [сесії](/docs/{{version}}/session):

```php
$title = $request->old('title');
```

Laravel також надає глобальний хелпер `old`. Якщо ви показуєте попередні дані в [шаблоні Blade](/docs/{{version}}/blade), зручніше скористатися хелпером `old`, щоб заново заповнити форму. Якщо попередніх даних для вказаного поля немає, буде повернуто `null`:

```blade
<input type="text" name="title" value="{{ old('title') }}">
```

<a name="a-note-on-optional-fields"></a>
### Зауваження про необов'язкові поля

За замовчуванням Laravel додає `middleware` `TrimStrings` та `ConvertEmptyStringsToNull` до глобального стека вашого застосунку. Через це вам часто доведеться позначати «необов'язкові» поля запиту як `nullable`, якщо ви не хочете, щоб валідатор вважав значення `null` недійсними. Наприклад:

```php
$request->validate([
    'title' => ['required', 'unique:posts', 'max:255'],
    'body' => ['required'],
    'publish_at' => ['nullable', 'date'],
]);
```

У цьому прикладі ми вказуємо, що поле `publish_at` може бути або `null`, або дійсним поданням дати. Якщо модифікатор `nullable` не додати до визначення правила, валідатор вважатиме `null` недійсною датою.

<a name="validation-error-response-format"></a>
### Формат відповіді з помилками валідації

Коли ваш застосунок викидає виняток `Illuminate\Validation\ValidationException`, а вхідний HTTP-запит очікує JSON-відповідь, Laravel автоматично відформатує повідомлення про помилки й поверне HTTP-відповідь `422 Unprocessable Entity`.

Нижче наведено приклад формату JSON-відповіді для помилок валідації. Зверніть увагу: вкладені ключі помилок зводяться до «крапкової» нотації:

```json
{
    "message": "The team name field must be a string. (and 4 more errors)",
    "errors": {
        "team_name": [
            "The team name field must be a string.",
            "The team name field must be at least 1 characters."
        ],
        "authorization.role": [
            "The selected authorization.role is invalid."
        ],
        "users.0.email": [
            "The users.0.email field is required."
        ],
        "users.2.email": [
            "The users.2.email field must be a valid email address."
        ]
    }
}
```

<a name="form-request-validation"></a>
## Валідація через запити форм

<a name="creating-form-requests"></a>
### Створення запитів форм

Для складніших сценаріїв валідації ви можете створити «запит форми» (form request). Запити форм - це власні класи запитів, що інкапсулюють власну логіку валідації та авторизації. Щоб створити клас запиту форми, скористайтеся командою Artisan `make:request`:

```shell
php artisan make:request StorePostRequest
```

Згенерований клас запиту форми буде розміщено в каталозі `app/Http/Requests`. Якщо цього каталогу немає, його буде створено під час виконання команди `make:request`. Кожен згенерований Laravel запит форми має два методи: `authorize` і `rules`.

Як ви могли здогадатися, метод `authorize` відповідає за визначення того, чи може поточний автентифікований користувач виконати дію, яку представляє запит, а метод `rules` повертає правила валідації, що мають застосовуватися до даних запиту:

```php
/**
 * Get the validation rules that apply to the request.
 *
 * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
 */
public function rules(): array
{
    return [
        'title' => ['required', 'unique:posts', 'max:255'],
        'body' => ['required'],
    ];
}
```

> [!NOTE]
> Ви можете вказати типи будь-яких потрібних залежностей у сигнатурі методу `rules`. Їх буде автоматично розв'язано через [сервіс-контейнер](/docs/{{version}}/container) Laravel.

Отже, як обчислюються правила валідації? Вам потрібно лише вказати тип запиту в методі контролера. Вхідний запит форми валідується до виклику методу контролера, тобто вам не потрібно захаращувати контролер логікою валідації:

```php
/**
 * Store a new blog post.
 */
public function store(StorePostRequest $request): RedirectResponse
{
    // The incoming request is valid...

    // Retrieve the validated input data...
    $validated = $request->validated();

    // Retrieve a portion of the validated input data...
    $validated = $request->safe()->only(['name', 'email']);
    $validated = $request->safe()->except(['name', 'email']);

    // Store the blog post...

    return redirect('/posts');
}
```

Якщо валідація не пройде, буде згенеровано відповідь-перенаправлення, щоб повернути користувача на попередню сторінку. Помилки також буде записано до сесії, щоб їх можна було показати. Якщо запит був XHR-запитом, користувачеві буде повернуто HTTP-відповідь зі статус-кодом 422, що містить [JSON-подання помилок валідації](#validation-error-response-format).

> [!NOTE]
> Потрібно додати валідацію запитів форм у реальному часі до вашого фронтенду на Inertia? Перегляньте [Laravel Precognition](/docs/{{version}}/precognition).

<a name="performing-additional-validation-on-form-requests"></a>
#### Додаткова валідація

Іноді вам потрібно виконати додаткову валідацію після завершення початкової. Це робиться методом `after` запиту форми.

Метод `after` має повертати масив викликаних об'єктів чи замикань, які буде виконано після завершення валідації. Передані об'єкти отримають екземпляр `Illuminate\Validation\Validator`, що дозволить за потреби додати нові повідомлення про помилки:

```php
use Illuminate\Validation\Validator;

/**
 * Get the "after" validation callables for the request.
 */
public function after(): array
{
    return [
        function (Validator $validator) {
            if ($this->somethingElseIsInvalid()) {
                $validator->errors()->add(
                    'field',
                    'Something is wrong with this field!'
                );
            }
        }
    ];
}
```

Як зазначено, масив, повернений методом `after`, може також містити викликані класи. Метод `__invoke` цих класів отримає екземпляр `Illuminate\Validation\Validator`:

```php
use App\Validation\ValidateShippingTime;
use App\Validation\ValidateUserStatus;
use Illuminate\Validation\Validator;

/**
 * Get the "after" validation callables for the request.
 */
public function after(): array
{
    return [
        new ValidateUserStatus,
        new ValidateShippingTime,
        function (Validator $validator) {
            //
        }
    ];
}
```

<a name="request-stopping-on-first-validation-rule-failure"></a>
#### Зупинка на першій невдалій перевірці

Додавши до класу запиту атрибут `StopOnFirstFailure`, ви можете повідомити валідатор, що йому слід припинити валідацію всіх атрибутів після першої ж невдачі:

```php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\Attributes\StopOnFirstFailure;
use Illuminate\Foundation\Http\FormRequest;

#[StopOnFirstFailure]
class StorePostRequest extends FormRequest
{
    // ...
}
```

<a name="request-failing-on-unknown-fields"></a>
#### Відхилення невідомих полів

Додавши до класу запиту атрибут `FailOnUnknownFields`, ви можете вказати Laravel відхиляти будь-які вхідні поля, не визначені правилами валідації вашого запиту:

```php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\Attributes\FailOnUnknownFields;
use Illuminate\Foundation\Http\FormRequest;

#[FailOnUnknownFields]
class StorePostRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'title' => ['required', 'string'],
            'body' => ['required', 'string'],
        ];
    }
}
```

Ви також можете увімкнути цю поведінку глобально для всіх запитів форм зі свого `AppServiceProvider`:

```php
use Illuminate\Foundation\Http\FormRequest;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    FormRequest::failOnUnknownFields();
}
```

За потреби ви можете вимкнути цю поведінку для конкретного запиту, передавши атрибуту `false`:

```php
#[FailOnUnknownFields(false)]
class PublicWebhookRequest extends FormRequest
{
    // ...
}
```

Відхилення невідомих полів дає додатковий захист від проблем на кшталт mass-assignment, не даючи неочікуваним ключам потрапляти глибше у ваш застосунок. Утім, вам усе одно слід налаштувати властивості `$fillable` / `$guarded` своєї моделі та зберігати лише довірені валідовані дані.

<a name="customizing-the-redirect-location"></a>
#### Налаштування адреси перенаправлення

Коли валідація запиту форми не проходить, генерується відповідь-перенаправлення, що повертає користувача на попередню сторінку. Утім, ви вільні налаштувати цю поведінку. Для цього скористайтеся атрибутом `RedirectTo` у своєму запиті форми:

```php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\Attributes\RedirectTo;
use Illuminate\Foundation\Http\FormRequest;

#[RedirectTo('/dashboard')]
class StorePostRequest extends FormRequest
{
    // ...
}
```

Або, якщо ви хочете перенаправляти користувачів до іменованого маршруту, скористайтеся натомість атрибутом `RedirectToRoute`:

```php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\Attributes\RedirectToRoute;
use Illuminate\Foundation\Http\FormRequest;

#[RedirectToRoute('dashboard')]
class StorePostRequest extends FormRequest
{
    // ...
}
```

<a name="customizing-the-error-bag"></a>
#### Налаштування набору помилок

Коли валідація запиту форми не проходить, помилки записуються до набору `default`. Якщо вам потрібно зберігати їх в іншому [іменованому наборі помилок](#named-error-bags), скористайтеся атрибутом `ErrorBag` у своєму запиті форми:

```php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\Attributes\ErrorBag;
use Illuminate\Foundation\Http\FormRequest;

#[ErrorBag('login')]
class LoginRequest extends FormRequest
{
    // ...
}
```

<a name="authorizing-form-requests"></a>
### Авторизація запитів форм

Клас запиту форми також містить метод `authorize`. У ньому ви можете визначити, чи справді автентифікований користувач має право оновити певний ресурс. Наприклад, ви можете визначити, чи справді користувач володіє коментарем у блозі, який намагається оновити. Найімовірніше, у цьому методі ви взаємодіятимете зі своїми [гейтами та політиками авторизації](/docs/{{version}}/authorization):

```php
use App\Models\Comment;

/**
 * Determine if the user is authorized to make this request.
 */
public function authorize(): bool
{
    $comment = Comment::find($this->route('comment'));

    return $comment && $this->user()->can('update', $comment);
}
```

Оскільки всі запити форм успадковують базовий клас запиту Laravel, ми можемо скористатися методом `user`, щоб отримати поточного автентифікованого користувача. Також зверніть увагу на виклик методу `route` у прикладі вище. Він дає доступ до параметрів URI, визначених у маршруті, який викликається, - як-от параметр `{comment}` у прикладі нижче:

```php
Route::post('/comment/{comment}');
```

Тому, якщо ваш застосунок використовує [прив'язку моделей до маршрутів](/docs/{{version}}/routing#route-model-binding), ваш код можна зробити ще стислішим, звернувшись до розв'язаної моделі як до властивості запиту:

```php
return $this->user()->can('update', $this->comment);
```

Якщо метод `authorize` поверне `false`, автоматично буде повернуто HTTP-відповідь зі статус-кодом 403, а метод вашого контролера не виконається.

Якщо ви плануєте обробляти логіку авторизації запиту в іншій частині застосунку, ви можете цілком прибрати метод `authorize` або просто повертати `true`:

```php
/**
 * Determine if the user is authorized to make this request.
 */
public function authorize(): bool
{
    return true;
}
```

> [!NOTE]
> Ви можете вказати типи будь-яких потрібних залежностей у сигнатурі методу `authorize`. Їх буде автоматично розв'язано через [сервіс-контейнер](/docs/{{version}}/container) Laravel.

<a name="customizing-the-error-messages"></a>
### Налаштування повідомлень про помилки

Ви можете налаштувати повідомлення про помилки, які використовує запит форми, перевизначивши метод `messages`. Цей метод має повертати масив пар «атрибут / правило» та відповідних повідомлень про помилки:

```php
/**
 * Get the error messages for the defined validation rules.
 *
 * @return array<string, string>
 */
public function messages(): array
{
    return [
        'title.required' => 'A title is required',
        'body.required' => 'A message is required',
    ];
}
```

<a name="customizing-the-validation-attributes"></a>
#### Налаштування атрибутів валідації

Багато вбудованих повідомлень про помилки валідації в Laravel містять заповнювач `:attribute`. Якщо ви хочете, щоб заповнювач `:attribute` у вашому повідомленні замінювався власним іменем атрибута, вкажіть власні імена, перевизначивши метод `attributes`. Цей метод має повертати масив пар «атрибут / ім'я»:

```php
/**
 * Get custom attributes for validator errors.
 *
 * @return array<string, string>
 */
public function attributes(): array
{
    return [
        'email' => 'email address',
    ];
}
```

<a name="preparing-input-for-validation"></a>
### Підготовка даних до валідації

Якщо вам потрібно підготувати чи очистити дані запиту перед застосуванням правил валідації, скористайтеся методом `prepareForValidation`:

```php
use Illuminate\Support\Str;

/**
 * Prepare the data for validation.
 */
protected function prepareForValidation(): void
{
    $this->merge([
        'slug' => Str::slug($this->slug),
    ]);
}
```

Так само, якщо вам потрібно нормалізувати дані запиту після завершення валідації, скористайтеся методом `passedValidation`:

```php
/**
 * Handle a passed validation attempt.
 */
protected function passedValidation(): void
{
    $this->replace(['name' => 'Taylor']);
}
```

<a name="manually-creating-validators"></a>
## Ручне створення валідаторів

Якщо ви не хочете використовувати метод `validate` на запиті, ви можете створити екземпляр валідатора вручну через [фасад](/docs/{{version}}/facades) `Validator`. Метод `make` фасаду створює новий екземпляр валідатора:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

class PostController extends Controller
{
    /**
     * Store a new blog post.
     */
    public function store(Request $request): RedirectResponse
    {
        $validator = Validator::make($request->all(), [
            'title' => ['required', 'unique:posts', 'max:255'],
            'body' => ['required'],
        ]);

        if ($validator->fails()) {
            return redirect('/post/create')
                ->withErrors($validator)
                ->withInput();
        }

        // Retrieve the validated input...
        $validated = $validator->validated();

        // Retrieve a portion of the validated input...
        $validated = $validator->safe()->only(['name', 'email']);
        $validated = $validator->safe()->except(['name', 'email']);

        // Store the blog post...

        return redirect('/posts');
    }
}
```

Перший аргумент, переданий методу `make`, - дані, що підлягають валідації. Другий - масив правил валідації, які слід застосувати до цих даних.

Визначивши, що валідація запиту не пройшла, ви можете скористатися методом `withErrors`, щоб записати повідомлення про помилки до сесії. Використовуючи цей метод, змінна `$errors` автоматично стане доступною вашим представленням після перенаправлення, що дозволить легко показати помилки користувачеві. Метод `withErrors` приймає валідатор, `MessageBag` або PHP-масив.

#### Зупинка на першій невдалій перевірці

Метод `stopOnFirstFailure` повідомить валідатор, що йому слід припинити валідацію всіх атрибутів після першої ж невдачі:

```php
if ($validator->stopOnFirstFailure()->fails()) {
    // ...
}
```

<a name="automatic-redirection"></a>
### Автоматичне перенаправлення

Якщо ви хочете створити екземпляр валідатора вручну, але водночас скористатися автоматичним перенаправленням, яке дає метод `validate` HTTP-запиту, викличте метод `validate` на наявному екземплярі валідатора. Якщо валідація не пройде, користувача буде автоматично перенаправлено або, у випадку XHR-запиту, [повернено JSON-відповідь](#validation-error-response-format):

```php
Validator::make($request->all(), [
    'title' => ['required', 'unique:posts', 'max:255'],
    'body' => ['required'],
])->validate();
```

Ви можете скористатися методом `validateWithBag`, щоб зберегти повідомлення про помилки в [іменованому наборі помилок](#named-error-bags), якщо валідація не пройде:

```php
Validator::make($request->all(), [
    'title' => ['required', 'unique:posts', 'max:255'],
    'body' => ['required'],
])->validateWithBag('post');
```

<a name="named-error-bags"></a>
### Іменовані набори помилок

Якщо на одній сторінці у вас кілька форм, ви можете захотіти дати ім'я `MessageBag`, що містить помилки валідації, аби отримувати повідомлення для конкретної форми. Для цього передайте ім'я другим аргументом `withErrors`:

```php
return redirect('/register')->withErrors($validator, 'login');
```

Далі ви можете звернутися до іменованого екземпляра `MessageBag` через змінну `$errors`:

```blade
{{ $errors->login->first('email') }}
```

<a name="manual-customizing-the-error-messages"></a>
### Налаштування повідомлень про помилки

За потреби ви можете надати власні повідомлення про помилки, які валідатор використовуватиме замість типових повідомлень Laravel. Указати власні повідомлення можна кількома способами. По-перше, ви можете передати їх третім аргументом методу `Validator::make`:

```php
$validator = Validator::make($input, $rules, $messages = [
    'required' => 'The :attribute field is required.',
]);
```

У цьому прикладі заповнювач `:attribute` буде замінено фактичним іменем поля, що валідується. Ви можете використовувати й інші заповнювачі в повідомленнях валідації. Наприклад:

```php
$messages = [
    'same' => 'The :attribute and :other must match.',
    'size' => 'The :attribute must be exactly :size.',
    'between' => 'The :attribute value :input is not between :min - :max.',
    'in' => 'The :attribute must be one of the following types: :values',
];
```

<a name="specifying-a-custom-message-for-a-given-attribute"></a>
#### Власне повідомлення для конкретного атрибута

Іноді ви можете захотіти вказати власне повідомлення лише для конкретного атрибута. Це робиться через «крапкову» нотацію: спершу ім'я атрибута, потім правило:

```php
$messages = [
    'email.required' => 'We need to know your email address!',
];
```

<a name="specifying-custom-attribute-values"></a>
#### Власні значення атрибутів

Багато вбудованих повідомлень про помилки Laravel містять заповнювач `:attribute`, який замінюється іменем поля чи атрибута, що валідується. Щоб налаштувати значення для заміни цих заповнювачів у конкретних полях, передайте масив власних атрибутів четвертим аргументом методу `Validator::make`:

```php
$validator = Validator::make($input, $rules, $messages, [
    'email' => 'email address',
]);
```

<a name="performing-additional-validation"></a>
### Додаткова валідація

Іноді вам потрібно виконати додаткову валідацію після завершення початкової. Це робиться методом `after` валідатора. Метод `after` приймає замикання або масив викликаних об'єктів, які буде виконано після завершення валідації. Вони отримають екземпляр `Illuminate\Validation\Validator`, що дозволить за потреби додати нові повідомлення про помилки:

```php
use Illuminate\Support\Facades\Validator;

$validator = Validator::make(/* ... */);

$validator->after(function ($validator) {
    if ($this->somethingElseIsInvalid()) {
        $validator->errors()->add(
            'field', 'Something is wrong with this field!'
        );
    }
});

if ($validator->fails()) {
    // ...
}
```

Як зазначено, метод `after` також приймає масив викликаних об'єктів - це особливо зручно, якщо ваша логіка «після валідації» інкапсульована у викликаних класах, які отримають екземпляр `Illuminate\Validation\Validator` через метод `__invoke`:

```php
use App\Validation\ValidateShippingTime;
use App\Validation\ValidateUserStatus;

$validator->after([
    new ValidateUserStatus,
    new ValidateShippingTime,
    function ($validator) {
        // ...
    },
]);
```

<a name="working-with-validated-input"></a>
## Робота з валідованими даними

Провалідувавши вхідні дані запиту через запит форми чи створений вручну валідатор, ви можете захотіти отримати саме ті дані, які пройшли валідацію. Це можна зробити кількома способами. По-перше, ви можете викликати метод `validated` на запиті форми чи екземплярі валідатора. Цей метод повертає масив валідованих даних:

```php
$validated = $request->validated();

$validated = $validator->validated();
```

Як альтернативу ви можете викликати метод `safe` на запиті форми чи екземплярі валідатора. Він повертає екземпляр `Illuminate\Support\ValidatedInput`. Цей об'єкт має методи `only`, `except` і `all`, щоб отримати підмножину валідованих даних або весь їх масив:

```php
$validated = $request->safe()->only(['name', 'email']);

$validated = $request->safe()->except(['name', 'email']);

$validated = $request->safe()->all();
```

Крім того, екземпляр `Illuminate\Support\ValidatedInput` можна ітерувати й звертатися до нього як до масиву:

```php
// Validated data may be iterated...
foreach ($request->safe() as $key => $value) {
    // ...
}

// Validated data may be accessed as an array...
$validated = $request->safe();

$email = $validated['email'];
```

Якщо ви хочете додати до валідованих даних додаткові поля, викличте метод `merge`:

```php
$validated = $request->safe()->merge(['name' => 'Taylor Otwell']);
```

Якщо ви хочете отримати валідовані дані як [колекцію](/docs/{{version}}/collections), викличте метод `collect`:

```php
$collection = $request->safe()->collect();
```

<a name="working-with-error-messages"></a>
## Робота з повідомленнями про помилки

Викликавши метод `errors` на екземплярі `Validator`, ви отримаєте екземпляр `Illuminate\Support\MessageBag`, що має низку зручних методів для роботи з повідомленнями про помилки. Змінна `$errors`, автоматично доступна всім представленням, теж є екземпляром класу `MessageBag`.

<a name="retrieving-the-first-error-message-for-a-field"></a>
#### Отримання першого повідомлення для поля

Щоб отримати перше повідомлення про помилку для певного поля, скористайтеся методом `first`:

```php
$errors = $validator->errors();

echo $errors->first('email');
```

<a name="retrieving-all-error-messages-for-a-field"></a>
#### Отримання всіх повідомлень для поля

Якщо вам потрібен масив усіх повідомлень для певного поля, скористайтеся методом `get`:

```php
foreach ($errors->get('email') as $message) {
    // ...
}
```

Якщо ви валідуєте поле форми, що є масивом, ви можете отримати всі повідомлення для кожного елемента масиву за допомогою символу `*`:

```php
foreach ($errors->get('attachments.*') as $message) {
    // ...
}
```

<a name="retrieving-all-error-messages-for-all-fields"></a>
#### Отримання всіх повідомлень для всіх полів

Щоб отримати масив усіх повідомлень для всіх полів, скористайтеся методом `all`:

```php
foreach ($errors->all() as $message) {
    // ...
}
```

<a name="determining-if-messages-exist-for-a-field"></a>
#### Визначення наявності повідомлень для поля

Метод `has` дозволяє визначити, чи існують повідомлення про помилки для певного поля:

```php
if ($errors->has('email')) {
    // ...
}
```

<a name="specifying-custom-messages-in-language-files"></a>
### Власні повідомлення у мовних файлах

Кожне вбудоване правило валідації Laravel має повідомлення про помилку, розташоване у файлі `lang/en/validation.php` вашого застосунку. Якщо ваш застосунок не має каталогу `lang`, ви можете створити його командою Artisan `lang:publish`.

У файлі `lang/en/validation.php` ви знайдете запис перекладу для кожного правила валідації. Ви вільні змінювати ці повідомлення відповідно до потреб свого застосунку.

Крім того, ви можете скопіювати цей файл до каталогу іншої мови, щоб перекласти повідомлення мовою вашого застосунку. Докладніше про локалізацію в Laravel читайте в повній [документації з локалізації](/docs/{{version}}/localization).

> [!WARNING]
> За замовчуванням каркас застосунку Laravel не містить каталогу `lang`. Якщо ви хочете налаштувати мовні файли Laravel, опублікуйте їх командою Artisan `lang:publish`.

<a name="custom-messages-for-specific-attributes"></a>
#### Власні повідомлення для конкретних атрибутів

Ви можете налаштувати повідомлення про помилки для вказаних комбінацій атрибута й правила у мовних файлах валідації вашого застосунку. Для цього додайте свої налаштування до масиву `custom` у файлі `lang/xx/validation.php`:

```php
'custom' => [
    'email' => [
        'required' => 'We need to know your email address!',
        'max' => 'Your email address is too long!'
    ],
],
```

<a name="specifying-attribute-in-language-files"></a>
### Атрибути у мовних файлах

Багато вбудованих повідомлень про помилки Laravel містять заповнювач `:attribute`, який замінюється іменем поля чи атрибута, що валідується. Якщо ви хочете, щоб частину `:attribute` вашого повідомлення було замінено власним значенням, вкажіть власне ім'я атрибута в масиві `attributes` вашого файлу `lang/xx/validation.php`:

```php
'attributes' => [
    'email' => 'email address',
],
```

> [!WARNING]
> За замовчуванням каркас застосунку Laravel не містить каталогу `lang`. Якщо ви хочете налаштувати мовні файли Laravel, опублікуйте їх командою Artisan `lang:publish`.

<a name="specifying-values-in-language-files"></a>
### Значення у мовних файлах

Деякі вбудовані повідомлення про помилки валідації в Laravel містять заповнювач `:value`, який замінюється поточним значенням атрибута запиту. Однак подекуди вам може знадобитися, щоб частину `:value` вашого повідомлення було замінено зрозумілішим поданням значення. Наприклад, розгляньмо правило, яке вказує, що номер кредитної картки обов'язковий, якщо `payment_type` має значення `cc`:

```php
Validator::make($request->all(), [
    'credit_card_number' => ['required_if:payment_type,cc']
]);
```

Якщо це правило валідації не пройде, воно створить таке повідомлення про помилку:

```text
The credit card number field is required when payment type is cc.
```

Замість показувати `cc` як значення типу платежу, ви можете вказати зрозуміліше подання у своєму файлі `lang/xx/validation.php`, визначивши масив `values`:

```php
'values' => [
    'payment_type' => [
        'cc' => 'credit card'
    ],
],
```

> [!WARNING]
> За замовчуванням каркас застосунку Laravel не містить каталогу `lang`. Якщо ви хочете налаштувати мовні файли Laravel, опублікуйте їх командою Artisan `lang:publish`.

Після визначення цього значення правило валідації створить таке повідомлення про помилку:

```text
The credit card number field is required when payment type is credit card.
```

<a name="available-validation-rules"></a>
## Доступні правила валідації

Нижче наведено список усіх доступних правил валідації та їхнє призначення:

<style>
    .collection-method-list > p {
        columns: 10.8em 3; -moz-columns: 10.8em 3; -webkit-columns: 10.8em 3;
    }

    .collection-method-list a {
        display: block;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
</style>

#### Booleans

<div class="collection-method-list" markdown="1">

[Accepted](#rule-accepted)
[Accepted If](#rule-accepted-if)
[Boolean](#rule-boolean)
[Declined](#rule-declined)
[Declined If](#rule-declined-if)

</div>

#### Strings

<div class="collection-method-list" markdown="1">

[Active URL](#rule-active-url)
[Alpha](#rule-alpha)
[Alpha Dash](#rule-alpha-dash)
[Alpha Numeric](#rule-alpha-num)
[Ascii](#rule-ascii)
[Confirmed](#rule-confirmed)
[Current Password](#rule-current-password)
[Different](#rule-different)
[Doesnt Start With](#rule-doesnt-start-with)
[Doesnt End With](#rule-doesnt-end-with)
[Email](#rule-email)
[Ends With](#rule-ends-with)
[Enum](#rule-enum)
[Hex Color](#rule-hex-color)
[In](#rule-in)
[IP Address](#rule-ip)
[JSON](#rule-json)
[Lowercase](#rule-lowercase)
[MAC Address](#rule-mac)
[Max](#rule-max)
[Min](#rule-min)
[Not In](#rule-not-in)
[Regular Expression](#rule-regex)
[Not Regular Expression](#rule-not-regex)
[Same](#rule-same)
[Size](#rule-size)
[Starts With](#rule-starts-with)
[String](#rule-string)
[Uppercase](#rule-uppercase)
[URL](#rule-url)
[ULID](#rule-ulid)
[UUID](#rule-uuid)

</div>

#### Numbers

<div class="collection-method-list" markdown="1">

[Between](#rule-between)
[Decimal](#rule-decimal)
[Different](#rule-different)
[Digits](#rule-digits)
[Digits Between](#rule-digits-between)
[Greater Than](#rule-gt)
[Greater Than Or Equal](#rule-gte)
[Integer](#rule-integer)
[Less Than](#rule-lt)
[Less Than Or Equal](#rule-lte)
[Max](#rule-max)
[Max Digits](#rule-max-digits)
[Min](#rule-min)
[Min Digits](#rule-min-digits)
[Multiple Of](#rule-multiple-of)
[Numeric](#rule-numeric)
[Same](#rule-same)
[Size](#rule-size)

</div>

#### Arrays

<div class="collection-method-list" markdown="1">

[Array](#rule-array)
[Array Keys](#rule-array-keys)
[Between](#rule-between)
[Contains](#rule-contains)
[Doesnt Contain](#rule-doesnt-contain)
[Distinct](#rule-distinct)
[In Array](#rule-in-array)
[In Array Keys](#rule-in-array-keys)
[List](#rule-list)
[Max](#rule-max)
[Min](#rule-min)
[Size](#rule-size)

</div>

#### Dates

<div class="collection-method-list" markdown="1">

[After](#rule-after)
[After Or Equal](#rule-after-or-equal)
[Before](#rule-before)
[Before Or Equal](#rule-before-or-equal)
[Date](#rule-date)
[Date Equals](#rule-date-equals)
[Date Format](#rule-date-format)
[Different](#rule-different)
[Timezone](#rule-timezone)

</div>

#### Files

<div class="collection-method-list" markdown="1">

[Between](#rule-between)
[Dimensions](#rule-dimensions)
[Encoding](#rule-encoding)
[Extensions](#rule-extensions)
[File](#rule-file)
[Image](#rule-image)
[Max](#rule-max)
[Min](#rule-min)
[MIME Types](#rule-mimetypes)
[MIME Type By File Extension](#rule-mimes)
[Size](#rule-size)

</div>

#### Database

<div class="collection-method-list" markdown="1">

[Exists](#rule-exists)
[Unique](#rule-unique)

</div>

#### Utilities

<div class="collection-method-list" markdown="1">

[Any Of](#rule-anyof)
[Bail](#rule-bail)
[Exclude](#rule-exclude)
[Exclude If](#rule-exclude-if)
[Exclude Unless](#rule-exclude-unless)
[Exclude With](#rule-exclude-with)
[Exclude Without](#rule-exclude-without)
[Filled](#rule-filled)
[Missing](#rule-missing)
[Missing If](#rule-missing-if)
[Missing Unless](#rule-missing-unless)
[Missing With](#rule-missing-with)
[Missing With All](#rule-missing-with-all)
[Nullable](#rule-nullable)
[Present](#rule-present)
[Present If](#rule-present-if)
[Present Unless](#rule-present-unless)
[Present With](#rule-present-with)
[Present With All](#rule-present-with-all)
[Prohibited](#rule-prohibited)
[Prohibited If](#rule-prohibited-if)
[Prohibited If Accepted](#rule-prohibited-if-accepted)
[Prohibited If Declined](#rule-prohibited-if-declined)
[Prohibited Unless](#rule-prohibited-unless)
[Prohibits](#rule-prohibits)
[Required](#rule-required)
[Required If](#rule-required-if)
[Required If Accepted](#rule-required-if-accepted)
[Required If Declined](#rule-required-if-declined)
[Required Unless](#rule-required-unless)
[Required With](#rule-required-with)
[Required With All](#rule-required-with-all)
[Required Without](#rule-required-without)
[Required Without All](#rule-required-without-all)
[Required Array Keys](#rule-required-array-keys)
[Sometimes](#validating-when-present)

</div>

<a name="rule-accepted"></a>
#### accepted

Поле, що валідується, має бути `"yes"`, `"on"`, `1`, `"1"`, `true` чи `"true"`. Це корисно для перевірки прийняття «Умов використання» та подібних полів.

<a name="rule-accepted-if"></a>
#### accepted_if:anotherfield,value,...

Поле, що валідується, має бути `"yes"`, `"on"`, `1`, `"1"`, `true` чи `"true"`, якщо інше поле дорівнює вказаному значенню. Це корисно для перевірки прийняття «Умов використання» та подібних полів.

<a name="rule-active-url"></a>
#### active_url

Поле, що валідується, має мати дійсний запис A чи AAAA згідно з PHP-функцією `dns_get_record`. Ім'я хоста з наданого URL витягується PHP-функцією `parse_url` перед передаванням до `dns_get_record`.

Під час тестування правил валідації, які виконують DNS-запити, як-от `active_url` та `email:dns`, ви можете скористатися методом `Validator::fakeDnsLookups`. Він імітує DNS-запити, зберігаючи решту валідаційної поведінки правил:

```php
use Illuminate\Support\Facades\Validator;

Validator::fakeDnsLookups();
```

<a name="rule-after"></a>
#### after:_date_

Поле, що валідується, має бути значенням після вказаної дати. Дати передаються до PHP-функції `strtotime`, щоб перетворити їх на дійсний екземпляр `DateTime`:

```php
'start_date' => ['required', 'date', 'after:tomorrow']
```

Замість передавати рядок дати для обчислення через `strtotime`, ви можете вказати інше поле для порівняння:

```php
'finish_date' => ['required', 'date', 'after:start_date']
```

Для зручності правила на основі дат можна будувати плинним конструктором `date`:

```php
use Illuminate\Validation\Rule;

'start_date' => [
    'required',
    Rule::date()->after(today()->addDays(7)),
],
```

Методи `afterToday` і `todayOrAfter` дозволяють плинно виразити, що дата має бути після сьогодні або сьогодні чи пізніше відповідно:

```php
'start_date' => [
    'required',
    Rule::date()->afterToday(),
],
```

<a name="rule-after-or-equal"></a>
#### after\_or\_equal:_date_

Поле, що валідується, має бути значенням після вказаної дати або дорівнювати їй. Докладніше дивіться правило [after](#rule-after).

Для зручності правила на основі дат можна будувати плинним конструктором `date`:

```php
use Illuminate\Validation\Rule;

'start_date' => [
    'required',
    Rule::date()->afterOrEqual(today()->addDays(7)),
],
```

<a name="rule-anyof"></a>
#### anyOf

Правило валідації `Rule::anyOf` дозволяє вказати, що поле має задовольняти будь-який із наведених наборів правил. Наприклад, наступне правило перевірить, що поле `username` є або адресою електронної пошти, або буквено-цифровим рядком (із дефісами) щонайменше з 6 символів:

```php
use Illuminate\Validation\Rule;

'username' => [
    'required',
    Rule::anyOf([
        ['string', 'email'],
        ['string', 'alpha_dash', 'min:6'],
    ]),
],
```

<a name="rule-alpha"></a>
#### alpha

Поле, що валідується, має складатися виключно з літер Unicode, що входять до [\p{L}](https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%3AL%3A%5D&g=&i=) та [\p{M}](https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%3AM%3A%5D&g=&i=).

Щоб обмежити це правило символами діапазону ASCII (`a-z` та `A-Z`), передайте правилу опцію `ascii`:

```php
'username' => ['alpha:ascii'],
```

<a name="rule-alpha-dash"></a>
#### alpha_dash

Поле, що валідується, має складатися виключно з буквено-цифрових символів Unicode, що входять до [\p{L}](https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%3AL%3A%5D&g=&i=), [\p{M}](https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%3AM%3A%5D&g=&i=), [\p{N}](https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%3AN%3A%5D&g=&i=), а також ASCII-дефісів (`-`) та ASCII-підкреслень (`_`).

Щоб обмежити це правило символами діапазону ASCII (`a-z`, `A-Z` та `0-9`), передайте правилу опцію `ascii`:

```php
'username' => ['alpha_dash:ascii'],
```

<a name="rule-alpha-num"></a>
#### alpha_num

Поле, що валідується, має складатися виключно з буквено-цифрових символів Unicode, що входять до [\p{L}](https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%3AL%3A%5D&g=&i=), [\p{M}](https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%3AM%3A%5D&g=&i=) та [\p{N}](https://util.unicode.org/UnicodeJsps/list-unicodeset.jsp?a=%5B%3AN%3A%5D&g=&i=).

Щоб обмежити це правило символами діапазону ASCII (`a-z`, `A-Z` та `0-9`), передайте правилу опцію `ascii`:

```php
'username' => ['alpha_num:ascii'],
```

<a name="rule-array"></a>
#### array

Поле, що валідується, має бути PHP-масивом (`array`).

Коли правилу `array` передано додаткові значення, кожен ключ вхідного масиву має бути присутнім у переданому списку значень. У прикладі нижче ключ `admin` у вхідному масиві недійсний, бо його немає в списку значень, переданих правилу `array`:

```php
use Illuminate\Support\Facades\Validator;

$input = [
    'user' => [
        'name' => 'Taylor Otwell',
        'username' => 'taylorotwell',
        'admin' => true,
    ],
];

Validator::make($input, [
    'user' => ['array:name,username'],
]);
```

Загалом вам слід завжди вказувати ключі масиву, які дозволено в ньому мати.

<a name="rule-array-keys"></a>
#### array_keys:_foo_,_bar_,...

Поле, що перевіряється, має бути PHP-масивом (`array`), усі ключі якого входять до наведеного списку. Потрібно вказати принаймні один ключ:

```php
'user' => ['array_keys:name,username'],
```

Для зручності ви можете скористатися методом `Rule::arrayKeys`:

```php
'user' => [Rule::arrayKeys('name', 'username')],
```

<a name="rule-ascii"></a>
#### ascii

Поле, що валідується, має складатися виключно із 7-бітних символів ASCII.

<a name="rule-bail"></a>
#### bail

Припинити виконання правил валідації для поля після першої невдалої перевірки.

Тоді як правило `bail` припиняє валідацію лише конкретного поля, метод `stopOnFirstFailure` повідомить валідатор, що йому слід припинити валідацію всіх атрибутів після першої ж невдачі:

```php
if ($validator->stopOnFirstFailure()->fails()) {
    // ...
}
```

<a name="rule-before"></a>
#### before:_date_

Поле, що валідується, має бути значенням перед указаною датою. Дати передаються до PHP-функції `strtotime`, щоб перетворити їх на дійсний екземпляр `DateTime`. Крім того, як і в правилі [after](#rule-after), як значення `date` можна передати ім'я іншого поля.

Для зручності правила на основі дат можна також будувати плинним конструктором `date`:

```php
use Illuminate\Validation\Rule;

'start_date' => [
    'required',
    Rule::date()->before(today()->subDays(7)),
],
```

Методи `beforeToday` і `todayOrBefore` дозволяють плинно виразити, що дата має бути до сьогодні або сьогодні чи раніше відповідно:

```php
'start_date' => [
    'required',
    Rule::date()->beforeToday(),
],
```

<a name="rule-before-or-equal"></a>
#### before\_or\_equal:_date_

Поле, що валідується, має бути значенням перед указаною датою або дорівнювати їй. Дати передаються до PHP-функції `strtotime`, щоб перетворити їх на дійсний екземпляр `DateTime`. Крім того, як і в правилі [after](#rule-after), як значення `date` можна передати ім'я іншого поля.

Для зручності правила на основі дат можна також будувати плинним конструктором `date`:

```php
use Illuminate\Validation\Rule;

'start_date' => [
    'required',
    Rule::date()->beforeOrEqual(today()->subDays(7)),
],
```

<a name="rule-between"></a>
#### between:_min_,_max_

Поле, що валідується, має мати розмір між указаними _min_ і _max_ (включно). Рядки, числа, масиви та файли оцінюються так само, як у правилі [size](#rule-size).

<a name="rule-boolean"></a>
#### boolean

Поле, що валідується, має піддаватися приведенню до булевого типу. Прийнятні значення: `true`, `false`, `1`, `0`, `"1"` та `"0"`.

Ви можете скористатися параметром `strict`, щоб вважати поле дійсним лише тоді, коли його значення є `true` чи `false`:

```php
'foo' => ['boolean:strict']
```

<a name="rule-confirmed"></a>
#### confirmed

Поле, що валідується, має мати відповідне поле `{field}_confirmation`. Наприклад, якщо валідується поле `password`, у вхідних даних має бути присутнє поле `password_confirmation`.

Ви також можете передати власне ім'я поля підтвердження. Наприклад, `confirmed:repeat_username` очікуватиме, що поле `repeat_username` збігатиметься з полем, що валідується.

<a name="rule-contains"></a>
#### contains:_foo_,_bar_,...

Поле, що валідується, має бути масивом, який містить усі передані значення. Оскільки це правило часто потребує `implode` масиву, для плинної побудови правила можна скористатися методом `Rule::contains`:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make($data, [
    'roles' => [
        'required',
        'array',
        Rule::contains(['admin', 'editor']),
    ],
]);
```

<a name="rule-doesnt-contain"></a>
#### doesnt_contain:_foo_,_bar_,...

Поле, що валідується, має бути масивом, який не містить жодного з переданих значень. Оскільки це правило часто потребує `implode` масиву, для плинної побудови правила можна скористатися методом `Rule::doesntContain`:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make($data, [
    'roles' => [
        'required',
        'array',
        Rule::doesntContain(['admin', 'editor']),
    ],
]);
```

<a name="rule-current-password"></a>
#### current_password

Поле, що валідується, має збігатися з паролем автентифікованого користувача. Ви можете вказати [гард автентифікації](/docs/{{version}}/authentication) першим параметром правила:

```php
'password' => ['current_password:api']
```

<a name="rule-date"></a>
#### date

Поле, що валідується, має бути дійсною невідносною датою згідно з PHP-функцією `strtotime`.

<a name="rule-date-equals"></a>
#### date_equals:_date_

Поле, що валідується, має дорівнювати вказаній даті. Дати передаються до PHP-функції `strtotime`, щоб перетворити їх на дійсний екземпляр `DateTime`.

<a name="rule-date-format"></a>
#### date_format:_format_,...

Поле, що валідується, має відповідати одному з указаних _форматів_. Валідуючи поле, слід використовувати **або** `date`, **або** `date_format`, але не обидва. Це правило підтримує всі формати, які підтримує PHP-клас [DateTime](https://www.php.net/manual/en/class.datetime.php).

Для зручності правила на основі дат можна будувати плинним конструктором `date`:

```php
use Illuminate\Validation\Rule;

'start_date' => [
    'required',
    Rule::date()->format('Y-m-d'),
],
```

<a name="rule-decimal"></a>
#### decimal:_min_,_max_

Поле, що валідується, має бути числовим і містити вказану кількість десяткових знаків:

```php
// Must have exactly two decimal places (9.99)...
'price' => ['decimal:2']

// Must have between 2 and 4 decimal places...
'price' => ['decimal:2,4']
```

<a name="rule-declined"></a>
#### declined

Поле, що валідується, має бути `"no"`, `"off"`, `0`, `"0"`, `false` чи `"false"`.

<a name="rule-declined-if"></a>
#### declined_if:anotherfield,value,...

Поле, що валідується, має бути `"no"`, `"off"`, `0`, `"0"`, `false` чи `"false"`, якщо інше поле дорівнює вказаному значенню.

<a name="rule-different"></a>
#### different:_field_

Поле, що валідується, має мати значення, відмінне від _field_.

<a name="rule-digits"></a>
#### digits:_value_

Ціле число, що валідується, має мати точну довжину _value_.

<a name="rule-digits-between"></a>
#### digits_between:_min_,_max_

Ціле число, що валідується, має мати довжину між указаними _min_ і _max_.

<a name="rule-dimensions"></a>
#### dimensions

Файл, що валідується, має бути зображенням, яке відповідає обмеженням розмірів, указаним у параметрах правила:

```php
'avatar' => ['dimensions:min_width=100,min_height=200']
```

Доступні обмеження: _min\_width_, _max\_width_, _min\_height_, _max\_height_, _width_, _height_, _ratio_, _min\_ratio_, _max\_ratio_.

Обмеження _ratio_ слід подавати як ширину, поділену на висоту. Це можна вказати або дробом на кшталт `3/2`, або числом із рухомою комою на кшталт `1.5`:

```php
'avatar' => ['dimensions:ratio=3/2']
```

Обмеження _min\_ratio_ та _max\_ratio_ дозволяють задати діапазон прийнятних співвідношень сторін:

```php
'avatar' => ['dimensions:min_ratio=1/2,max_ratio=3/2']
```

Оскільки це правило потребує кількох аргументів, часто зручніше скористатися методом `Rule::dimensions` для плинної побудови правила:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make($data, [
    'avatar' => [
        'required',
        Rule::dimensions()
            ->maxWidth(1000)
            ->maxHeight(500)
            ->ratio(3 / 2),
    ],
]);
```

Ви також можете скористатися методами `minRatio`, `maxRatio` та `ratioBetween`, щоб плинно задати обмеження співвідношення:

```php
Rule::dimensions()->ratioBetween(min: 1 / 2, max: 3 / 2);
```

<a name="rule-distinct"></a>
#### distinct

Валідуючи масиви, поле, що валідується, не має містити повторюваних значень:

```php
'foo.*.id' => ['distinct']
```

За замовчуванням `distinct` використовує нестроге порівняння змінних. Щоб застосувати строге порівняння, додайте до визначення правила параметр `strict`:

```php
'foo.*.id' => ['distinct:strict']
```

Ви можете додати до аргументів правила `ignore_case`, щоб воно ігнорувало відмінності в регістрі:

```php
'foo.*.id' => ['distinct:ignore_case']
```

<a name="rule-doesnt-start-with"></a>
#### doesnt_start_with:_foo_,_bar_,...

Поле, що валідується, не має починатися з жодного з переданих значень.

<a name="rule-doesnt-end-with"></a>
#### doesnt_end_with:_foo_,_bar_,...

Поле, що валідується, не має закінчуватися жодним із переданих значень.

<a name="rule-email"></a>
#### email

Поле, що валідується, має бути відформатоване як адреса електронної пошти. Це правило використовує пакет [egulias/email-validator](https://github.com/egulias/EmailValidator). За замовчуванням застосовується валідатор `RFCValidation`, але ви можете застосувати й інші стилі валідації:

```php
'email' => ['email:rfc,dns']
```

Наведений вище приклад застосує валідації `RFCValidation` та `DNSCheckValidation`. Ось повний список стилів валідації, які можна застосувати:

<div class="content-list" markdown="1">

- `rfc`: `RFCValidation` - валідувати адресу згідно з [підтримуваними RFC](https://github.com/egulias/EmailValidator?tab=readme-ov-file#supported-rfcs).
- `strict`: `NoRFCWarningsValidation` - валідувати адресу згідно з [підтримуваними RFC](https://github.com/egulias/EmailValidator?tab=readme-ov-file#supported-rfcs), відхиляючи її за наявності попереджень (наприклад, крапка наприкінці чи кілька крапок поспіль).
- `dns`: `DNSCheckValidation` - переконатися, що домен адреси має дійсний MX-запис.
- `spoof`: `SpoofCheckValidation` - переконатися, що адреса не містить гомогліфів чи оманливих символів Unicode.
- `filter`: `FilterEmailValidation` - переконатися, що адреса дійсна згідно з PHP-функцією `filter_var`.
- `filter_unicode`: `FilterEmailValidation::unicode()` - переконатися, що адреса дійсна згідно з PHP-функцією `filter_var`, дозволяючи деякі символи Unicode.

</div>

Для зручності правила валідації електронної пошти можна будувати плинним конструктором:

```php
use Illuminate\Validation\Rule;

$request->validate([
    'email' => [
        'required',
        Rule::email()
            ->rfcCompliant(strict: false)
            ->validateMxRecord()
            ->preventSpoofing()
    ],
]);
```

Валідатор `dns` виконує справжній DNS-запит, щоб підтвердити наявність дійсного MX-запису для домену адреси. Він не визначає, чи існує конкретна поштова скринька.

Оскільки ваші тести не повинні залежати від реальних DNS-запитів, ви можете скористатися методом `Validator::fakeDnsLookups`, щоб [імітувати DNS-запити](#rule-active-url), поки інші необхідні валідації, як-от `rfc`, продовжують виконуватися:

```php
use Illuminate\Support\Facades\Validator;

Validator::fakeDnsLookups();
```

Це дозволяє вашому застосунку продовжувати використовувати наявні правила валідації під час тестування:

```php
'email' => ['required', 'email:rfc,dns'],
```

> [!WARNING]
> Валідатори `dns` і `spoof` потребують PHP-розширення `intl`.

<a name="rule-encoding"></a>
#### encoding:*encoding_type*

Поле, що валідується, має відповідати вказаному кодуванню символів. Це правило використовує PHP-функцію `mb_check_encoding`, щоб перевірити кодування переданого файлу чи рядка. Для зручності правило `encoding` можна будувати плинним конструктором файлових правил Laravel:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\File;

Validator::validate($input, [
    'attachment' => [
        'required',
        File::types(['csv'])
            ->encoding('utf-8'),
    ],
]);
```

<a name="rule-ends-with"></a>
#### ends_with:_foo_,_bar_,...

Поле, що валідується, має закінчуватися одним із переданих значень.

<a name="rule-enum"></a>
#### enum

Правило `Enum` - це правило на основі класу, що перевіряє, чи містить поле дійсне значення enum. Правило `Enum` приймає ім'я enum як єдиний аргумент конструктора. Валідуючи примітивні значення, правилу `Enum` слід передавати enum на основі значень (backed enum):

```php
use App\Enums\ServerStatus;
use Illuminate\Validation\Rule;

$request->validate([
    'status' => [Rule::enum(ServerStatus::class)],
]);
```

Методи `only` та `except` правила `Enum` дозволяють обмежити, які випадки enum вважати дійсними:

```php
Rule::enum(ServerStatus::class)
    ->only([ServerStatus::Pending, ServerStatus::Active]);

Rule::enum(ServerStatus::class)
    ->except([ServerStatus::Pending, ServerStatus::Active]);
```

Метод `when` дозволяє умовно змінювати правило `Enum`:

```php
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;

Rule::enum(ServerStatus::class)
    ->when(
        Auth::user()->isAdmin(),
        fn ($rule) => $rule->only(...),
        fn ($rule) => $rule->only(...),
    );
```

<a name="rule-exclude"></a>
#### exclude

Поле, що валідується, буде виключено з даних запиту, які повертають методи `validate` та `validated`.

<a name="rule-exclude-if"></a>
#### exclude_if:_anotherfield_,_value_

Поле, що валідується, буде виключено з даних запиту, які повертають методи `validate` та `validated`, якщо поле _anotherfield_ дорівнює _value_.

Якщо потрібна складна умовна логіка виключення, скористайтеся методом `Rule::excludeIf`. Він приймає булеве значення або замикання. Замикання має повертати `true` чи `false`, вказуючи, чи слід виключити поле:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make($request->all(), [
    'role_id' => [Rule::excludeIf($request->user()->is_admin)],
]);

Validator::make($request->all(), [
    'role_id' => [Rule::excludeIf(fn () => $request->user()->is_admin)],
]);
```

<a name="rule-exclude-unless"></a>
#### exclude_unless:_anotherfield_,_value_

Поле, що валідується, буде виключено з даних запиту, які повертають методи `validate` та `validated`, якщо тільки поле _anotherfield_ не дорівнює _value_. Якщо _value_ є `null` (`exclude_unless:name,null`), поле буде виключено, якщо тільки поле для порівняння не є `null` або відсутнє в даних запиту.

Якщо потрібна складна умовна логіка виключення, скористайтеся методом `Rule::excludeUnless`. Він приймає булеве значення або замикання. Замикання має повертати `true` чи `false`, вказуючи, чи не слід виключати поле:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make($request->all(), [
    'role_id' => [Rule::excludeUnless($request->user()->is_admin)],
]);

Validator::make($request->all(), [
    'role_id' => [Rule::excludeUnless(fn () => $request->user()->is_admin)],
]);
```

<a name="rule-exclude-with"></a>
#### exclude_with:_anotherfield_

Поле, що валідується, буде виключено з даних запиту, які повертають методи `validate` та `validated`, якщо поле _anotherfield_ присутнє.

<a name="rule-exclude-without"></a>
#### exclude_without:_anotherfield_

Поле, що валідується, буде виключено з даних запиту, які повертають методи `validate` та `validated`, якщо поле _anotherfield_ відсутнє.

<a name="rule-exists"></a>
#### exists:_table_,_column_

Поле, що валідується, має існувати у вказаній таблиці бази даних.

<a name="basic-usage-of-exists-rule"></a>
#### Базове використання правила Exists

```php
'state' => ['exists:states']
```

Якщо опцію `column` не вказано, буде використано ім'я поля. Тож у цьому випадку правило перевірить, що таблиця `states` містить запис зі значенням колонки `state`, що збігається зі значенням атрибута `state` у запиті.

<a name="specifying-a-custom-column-name"></a>
#### Указання власного імені колонки

Ви можете явно вказати ім'я колонки бази даних, яку має використовувати правило валідації, розмістивши його після імені таблиці:

```php
'state' => ['exists:states,abbreviation']
```

Подекуди вам може знадобитися вказати конкретне підключення до бази даних для запиту `exists`. Це робиться додаванням імені підключення перед іменем таблиці:

```php
'email' => ['exists:connection.staff,email']
```

Замість указувати ім'я таблиці напряму, ви можете вказати модель Eloquent, за якою буде визначено ім'я таблиці:

```php
'user_id' => ['exists:App\Models\User,id']
```

Якщо ви хочете налаштувати запит, який виконує правило валідації, скористайтеся класом `Rule` для плинного визначення правила.

```php
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make($data, [
    'email' => [
        'required',
        Rule::exists('staff')->where(function (Builder $query) {
            $query->where('account_id', 1);
        }),
    ],
]);
```

Ви можете явно вказати ім'я колонки для правила `exists`, згенерованого методом `Rule::exists`, передавши його другим аргументом методу `exists`:

```php
'state' => [Rule::exists('states', 'abbreviation')],
```

Іноді ви можете захотіти перевірити, чи існує в базі даних масив значень. Це робиться додаванням до поля обох правил - `exists` та [array](#rule-array):

```php
'states' => ['array', Rule::exists('states', 'abbreviation')],
```

Коли полю призначено обидва ці правила, Laravel автоматично побудує єдиний запит, щоб визначити, чи всі передані значення існують у вказаній таблиці.

<a name="rule-extensions"></a>
#### extensions:_foo_,_bar_,...

Файл, що валідується, має мати призначене користувачем розширення, що відповідає одному з перелічених:

```php
'photo' => ['required', 'extensions:jpg,png'],
```

> [!WARNING]
> Ніколи не покладайтеся на перевірку файлу лише за призначеним користувачем розширенням. Це правило зазвичай слід використовувати в поєднанні з правилами [mimes](#rule-mimes) чи [mimetypes](#rule-mimetypes).

<a name="rule-file"></a>
#### file

Поле, що валідується, має бути успішно завантаженим файлом.

<a name="rule-filled"></a>
#### filled

Поле, що валідується, не має бути порожнім, коли воно присутнє.

<a name="rule-gt"></a>
#### gt:_field_

Поле, що валідується, має бути більшим за вказане _field_ чи _value_. Обидва поля мають бути одного типу. Рядки, числа, масиви та файли оцінюються за тими самими домовленостями, що й у правилі [size](#rule-size).

<a name="rule-gte"></a>
#### gte:_field_

Поле, що валідується, має бути більшим за вказане _field_ чи _value_ або дорівнювати йому. Обидва поля мають бути одного типу. Рядки, числа, масиви та файли оцінюються за тими самими домовленостями, що й у правилі [size](#rule-size).

<a name="rule-hex-color"></a>
#### hex_color

Поле, що валідується, має містити дійсне значення кольору у [шістнадцятковому](https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color) форматі.

<a name="rule-image"></a>
#### image

Файл, що валідується, має бути зображенням (jpg, jpeg, png, bmp, gif, webp, avif, heic чи heif).

> [!WARNING]
> За замовчуванням правило `image` не дозволяє файли SVG через можливість XSS-вразливостей. Якщо вам потрібно дозволити SVG, передайте правилу `image` директиву `allow_svg` (`image:allow_svg`).

<a name="rule-in"></a>
#### in:_foo_,_bar_,...

Поле, що валідується, має входити до переданого списку значень. Оскільки це правило часто потребує `implode` масиву, для плинної побудови правила можна скористатися методом `Rule::in`:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make($data, [
    'zones' => [
        'required',
        Rule::in(['first-zone', 'second-zone']),
    ],
]);
```

Коли правило `in` поєднано з правилом `array`, кожне значення вхідного масиву має бути присутнім у списку значень, переданих правилу `in`. У прикладі нижче код аеропорту `LAS` у вхідному масиві недійсний, бо його немає в переданому списку:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

$input = [
    'airports' => ['NYC', 'LAS'],
];

Validator::make($input, [
    'airports' => [
        'required',
        'array',
    ],
    'airports.*' => Rule::in(['NYC', 'LIT']),
]);
```

<a name="rule-in-array"></a>
#### in_array:_anotherfield_.*

Поле, що валідується, має існувати серед значень _anotherfield_.

<a name="rule-in-array-keys"></a>
#### in_array_keys:_value_.*

Поле, що валідується, має бути масивом, який містить принаймні одне з переданих _значень_ як ключ:

```php
'config' => ['array', 'in_array_keys:timezone']
```

<a name="rule-integer"></a>
#### integer

Поле, що валідується, має бути цілим числом.

Ви можете скористатися параметром `strict`, щоб вважати поле дійсним лише тоді, коли його тип - `integer`. Рядки з цілими значеннями вважатимуться недійсними:

```php
'age' => ['integer:strict']
```

> [!WARNING]
> Це правило не перевіряє, що вхідні дані мають тип змінної «integer», а лише те, що вони мають тип, прийнятний для PHP-правила `FILTER_VALIDATE_INT`. Якщо вам потрібно перевірити, що вхідні дані є числом, використовуйте це правило в поєднанні з [правилом `numeric`](#rule-numeric).

<a name="rule-ip"></a>
#### ip

Поле, що валідується, має бути IP-адресою.

<a name="ipv4"></a>
#### ipv4

Поле, що валідується, має бути адресою IPv4.

<a name="ipv6"></a>
#### ipv6

Поле, що валідується, має бути адресою IPv6.

<a name="rule-json"></a>
#### json

Поле, що валідується, має бути дійсним рядком JSON.

<a name="rule-lt"></a>
#### lt:_field_

Поле, що валідується, має бути меншим за вказане _field_. Обидва поля мають бути одного типу. Рядки, числа, масиви та файли оцінюються за тими самими домовленостями, що й у правилі [size](#rule-size).

<a name="rule-lte"></a>
#### lte:_field_

Поле, що валідується, має бути меншим за вказане _field_ або дорівнювати йому. Обидва поля мають бути одного типу. Рядки, числа, масиви та файли оцінюються за тими самими домовленостями, що й у правилі [size](#rule-size).

<a name="rule-lowercase"></a>
#### lowercase

Поле, що валідується, має бути в нижньому регістрі.

<a name="rule-list"></a>
#### list

Поле, що валідується, має бути масивом-списком. Масив вважається списком, якщо його ключі є послідовними числами від 0 до `count($array) - 1`.

<a name="rule-mac"></a>
#### mac_address

Поле, що валідується, має бути MAC-адресою.

<a name="rule-max"></a>
#### max:_value_

Поле, що валідується, має бути меншим за максимальне _value_ або дорівнювати йому. Рядки, числа, масиви та файли оцінюються так само, як у правилі [size](#rule-size).

<a name="rule-max-digits"></a>
#### max_digits:_value_

Ціле число, що валідується, має мати максимальну довжину _value_.

<a name="rule-mimetypes"></a>
#### mimetypes:_text/plain_,...

Файл, що валідується, має відповідати одному з указаних MIME-типів:

```php
'video' => ['mimetypes:video/avi,video/mpeg,video/quicktime'],

'media' => ['mimetypes:image/*,video/*'],
```

Щоб визначити MIME-тип завантаженого файлу, буде прочитано його вміст, і фреймворк спробує вгадати тип, який може відрізнятися від наданого клієнтом.

<a name="rule-mimes"></a>
#### mimes:_foo_,_bar_,...

Файл, що валідується, має мати MIME-тип, що відповідає одному з перелічених розширень:

```php
'photo' => ['mimes:jpg,bmp,png']
```

Хоча вам потрібно вказати лише розширення, це правило насправді перевіряє MIME-тип файлу, читаючи його вміст і вгадуючи тип. Повний перелік MIME-типів та відповідних їм розширень можна знайти тут:

[https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types](https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types)

<a name="mime-types-and-extensions"></a>
#### MIME-типи та розширення

Це правило не перевіряє відповідність між MIME-типом і розширенням, яке користувач призначив файлу. Наприклад, правило `mimes:png` вважатиме файл із дійсним вмістом PNG дійсним зображенням PNG, навіть якщо файл названо `photo.txt`. Якщо ви хочете перевірити призначене користувачем розширення, скористайтеся правилом [extensions](#rule-extensions).

<a name="rule-min"></a>
#### min:_value_

Поле, що валідується, має мати мінімальне значення _value_. Рядки, числа, масиви та файли оцінюються так само, як у правилі [size](#rule-size).

<a name="rule-min-digits"></a>
#### min_digits:_value_

Ціле число, що валідується, має мати мінімальну довжину _value_.

<a name="rule-multiple-of"></a>
#### multiple_of:_value_

Поле, що валідується, має бути кратним _value_.

<a name="rule-missing"></a>
#### missing

Поле, що валідується, не має бути присутнім у вхідних даних.

<a name="rule-missing-if"></a>
#### missing_if:_anotherfield_,_value_,...

Поле, що валідується, не має бути присутнім, якщо поле _anotherfield_ дорівнює будь-якому _value_.

<a name="rule-missing-unless"></a>
#### missing_unless:_anotherfield_,_value_

Поле, що валідується, не має бути присутнім, якщо тільки поле _anotherfield_ не дорівнює будь-якому _value_.

<a name="rule-missing-with"></a>
#### missing_with:_foo_,_bar_,...

Поле, що валідується, не має бути присутнім, _лише якщо_ присутнє будь-яке з інших указаних полів.

<a name="rule-missing-with-all"></a>
#### missing_with_all:_foo_,_bar_,...

Поле, що валідується, не має бути присутнім, _лише якщо_ присутні всі інші вказані поля.

<a name="rule-not-in"></a>
#### not_in:_foo_,_bar_,...

Поле, що валідується, не має входити до переданого списку значень. Для плинної побудови правила можна скористатися методом `Rule::notIn`:

```php
use Illuminate\Validation\Rule;

Validator::make($data, [
    'toppings' => [
        'required',
        Rule::notIn(['sprinkles', 'cherries']),
    ],
]);
```

<a name="rule-not-regex"></a>
#### not_regex:_pattern_

Поле, що валідується, не має збігатися з указаним регулярним виразом.

Внутрішньо це правило використовує PHP-функцію `preg_match`. Указаний шаблон має відповідати тому самому форматуванню, якого потребує `preg_match`, тобто містити й дійсні роздільники. Наприклад: `'email' => ['not_regex:/^.+$/i']`.

<a name="rule-nullable"></a>
#### nullable

Поле, що валідується, може бути `null`.

<a name="rule-numeric"></a>
#### numeric

Поле, що валідується, має бути [числовим](https://www.php.net/manual/en/function.is-numeric.php).

Ви можете скористатися параметром `strict`, щоб вважати поле дійсним лише тоді, коли його значення має тип integer чи float. Числові рядки вважатимуться недійсними:

```php
'amount' => ['numeric:strict']
```

<a name="rule-present"></a>
#### present

Поле, що валідується, має існувати у вхідних даних.

<a name="rule-present-if"></a>
#### present_if:_anotherfield_,_value_,...

Поле, що валідується, має бути присутнім, якщо поле _anotherfield_ дорівнює будь-якому _value_.

<a name="rule-present-unless"></a>
#### present_unless:_anotherfield_,_value_

Поле, що валідується, має бути присутнім, якщо тільки поле _anotherfield_ не дорівнює будь-якому _value_.

<a name="rule-present-with"></a>
#### present_with:_foo_,_bar_,...

Поле, що валідується, має бути присутнім, _лише якщо_ присутнє будь-яке з інших указаних полів.

<a name="rule-present-with-all"></a>
#### present_with_all:_foo_,_bar_,...

Поле, що валідується, має бути присутнім, _лише якщо_ присутні всі інші вказані поля.

<a name="rule-prohibited"></a>
#### prohibited

Поле, що валідується, має бути відсутнім або порожнім. Поле є «порожнім», якщо воно відповідає одному з таких критеріїв:

<div class="content-list" markdown="1">

- Значення дорівнює `null`.
- Значення є порожнім рядком.
- Значення є порожнім масивом або порожнім об'єктом `Countable`.
- Значення є завантаженим файлом із порожнім шляхом.

</div>

<a name="rule-prohibited-if"></a>
#### prohibited_if:_anotherfield_,_value_,...

Поле, що валідується, має бути відсутнім або порожнім, якщо поле _anotherfield_ дорівнює будь-якому _value_. Поле є «порожнім», якщо воно відповідає одному з таких критеріїв:

<div class="content-list" markdown="1">

- Значення дорівнює `null`.
- Значення є порожнім рядком.
- Значення є порожнім масивом або порожнім об'єктом `Countable`.
- Значення є завантаженим файлом із порожнім шляхом.

</div>

Якщо потрібна складна умовна логіка заборони, скористайтеся методом `Rule::prohibitedIf`. Він приймає булеве значення або замикання. Замикання має повертати `true` чи `false`, вказуючи, чи слід заборонити поле:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make($request->all(), [
    'role_id' => [Rule::prohibitedIf($request->user()->is_admin)],
]);

Validator::make($request->all(), [
    'role_id' => [Rule::prohibitedIf(fn () => $request->user()->is_admin)],
]);
```
<a name="rule-prohibited-if-accepted"></a>
#### prohibited_if_accepted:_anotherfield_,...

Поле, що валідується, має бути відсутнім або порожнім, якщо поле _anotherfield_ дорівнює `"yes"`, `"on"`, `1`, `"1"`, `true` чи `"true"`.

<a name="rule-prohibited-if-declined"></a>
#### prohibited_if_declined:_anotherfield_,...

Поле, що валідується, має бути відсутнім або порожнім, якщо поле _anotherfield_ дорівнює `"no"`, `"off"`, `0`, `"0"`, `false` чи `"false"`.

<a name="rule-prohibited-unless"></a>
#### prohibited_unless:_anotherfield_,_value_,...

Поле, що валідується, має бути відсутнім або порожнім, якщо тільки поле _anotherfield_ не дорівнює будь-якому _value_. Поле є «порожнім», якщо воно відповідає одному з таких критеріїв:

<div class="content-list" markdown="1">

- Значення дорівнює `null`.
- Значення є порожнім рядком.
- Значення є порожнім масивом або порожнім об'єктом `Countable`.
- Значення є завантаженим файлом із порожнім шляхом.

</div>

Якщо потрібна складна умовна логіка заборони, скористайтеся методом `Rule::prohibitedUnless`. Він приймає булеве значення або замикання. Замикання має повертати `true` чи `false`, вказуючи, чи не слід забороняти поле:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make($request->all(), [
    'role_id' => [Rule::prohibitedUnless($request->user()->is_admin)],
]);

Validator::make($request->all(), [
    'role_id' => [Rule::prohibitedUnless(fn () => $request->user()->is_admin)],
]);
```

<a name="rule-prohibits"></a>
#### prohibits:_anotherfield_,...

Якщо поле, що валідується, не є відсутнім чи порожнім, усі поля в _anotherfield_ мають бути відсутніми або порожніми. Поле є «порожнім», якщо воно відповідає одному з таких критеріїв:

<div class="content-list" markdown="1">

- Значення дорівнює `null`.
- Значення є порожнім рядком.
- Значення є порожнім масивом або порожнім об'єктом `Countable`.
- Значення є завантаженим файлом із порожнім шляхом.

</div>

<a name="rule-regex"></a>
#### regex:_pattern_

Поле, що валідується, має збігатися з указаним регулярним виразом.

Внутрішньо це правило використовує PHP-функцію `preg_match`. Указаний шаблон має відповідати тому самому форматуванню, якого потребує `preg_match`, тобто містити й дійсні роздільники. Наприклад: `'email' => ['regex:/^.+@.+$/i']`.

<a name="rule-required"></a>
#### required

Поле, що валідується, має бути присутнім у вхідних даних і не бути порожнім. Поле є «порожнім», якщо воно відповідає одному з таких критеріїв:

<div class="content-list" markdown="1">

- Значення дорівнює `null`.
- Значення є порожнім рядком.
- Значення є порожнім масивом або порожнім об'єктом `Countable`.
- Значення є завантаженим файлом без шляху.

</div>

<a name="rule-required-if"></a>
#### required_if:_anotherfield_,_value_,...

Поле, що валідується, має бути присутнім і не порожнім, якщо поле _anotherfield_ дорівнює будь-якому _value_.

Якщо ви хочете побудувати складнішу умову для правила `required_if`, скористайтеся методом `Rule::requiredIf`. Він приймає булеве значення або замикання. Замикання має повертати `true` чи `false`, вказуючи, чи є поле обов'язковим:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make($request->all(), [
    'role_id' => [Rule::requiredIf($request->user()->is_admin)],
]);

Validator::make($request->all(), [
    'role_id' => [Rule::requiredIf(fn () => $request->user()->is_admin)],
]);
```

<a name="rule-required-if-accepted"></a>
#### required_if_accepted:_anotherfield_,...

Поле, що валідується, має бути присутнім і не порожнім, якщо поле _anotherfield_ дорівнює `"yes"`, `"on"`, `1`, `"1"`, `true` чи `"true"`.

<a name="rule-required-if-declined"></a>
#### required_if_declined:_anotherfield_,...

Поле, що валідується, має бути присутнім і не порожнім, якщо поле _anotherfield_ дорівнює `"no"`, `"off"`, `0`, `"0"`, `false` чи `"false"`.

<a name="rule-required-unless"></a>
#### required_unless:_anotherfield_,_value_,...

Поле, що валідується, має бути присутнім і не порожнім, якщо тільки поле _anotherfield_ не дорівнює будь-якому _value_. Це також означає, що _anotherfield_ має бути присутнім у даних запиту, якщо тільки _value_ не є `null`. Якщо _value_ є `null` (`required_unless:name,null`), поле буде обов'язковим, якщо тільки поле для порівняння не є `null` або відсутнє в даних запиту.

Якщо ви хочете побудувати складнішу умову для правила `required_unless`, скористайтеся методом `Rule::requiredUnless`. Він приймає булеве значення або замикання. Замикання має повертати `true` чи `false`, вказуючи, чи не є поле обов'язковим:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make($request->all(), [
    'role_id' => [Rule::requiredUnless($request->user()->is_admin)],
]);

Validator::make($request->all(), [
    'role_id' => [Rule::requiredUnless(fn () => $request->user()->is_admin)],
]);
```

<a name="rule-required-with"></a>
#### required_with:_foo_,_bar_,...

Поле, що валідується, має бути присутнім і не порожнім, _лише якщо_ будь-яке з інших указаних полів присутнє й не порожнє.

<a name="rule-required-with-all"></a>
#### required_with_all:_foo_,_bar_,...

Поле, що валідується, має бути присутнім і не порожнім, _лише якщо_ всі інші вказані поля присутні й не порожні.

<a name="rule-required-without"></a>
#### required_without:_foo_,_bar_,...

Поле, що валідується, має бути присутнім і не порожнім, _лише коли_ будь-яке з інших указаних полів порожнє чи відсутнє.

<a name="rule-required-without-all"></a>
#### required_without_all:_foo_,_bar_,...

Поле, що валідується, має бути присутнім і не порожнім, _лише коли_ всі інші вказані поля порожні чи відсутні.

<a name="rule-required-array-keys"></a>
#### required_array_keys:_foo_,_bar_,...

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

<a name="rule-same"></a>
#### same:_field_

Указане _field_ має збігатися з полем, що валідується.

<a name="rule-size"></a>
#### size:_value_

Поле, що валідується, має мати розмір, що відповідає вказаному _value_. Для рядкових даних _value_ відповідає кількості символів. Для числових - вказаному цілому значенню (атрибут також має мати правило `numeric` чи `integer`). Для масиву _size_ відповідає результату `count`. Для файлів _size_ відповідає розміру файлу в кілобайтах. Погляньмо на приклади:

```php
// Validate that a string is exactly 12 characters long...
'title' => ['size:12'];

// Validate that a provided integer equals 10...
'seats' => ['integer', 'size:10'];

// Validate that an array has exactly 5 elements...
'tags' => ['array', 'size:5'];

// Validate that an uploaded file is exactly 512 kilobytes...
'image' => ['file', 'size:512'];
```

<a name="rule-starts-with"></a>
#### starts_with:_foo_,_bar_,...

Поле, що валідується, має починатися з одного з переданих значень.

<a name="rule-string"></a>
#### string

Поле, що валідується, має бути рядком. Якщо ви хочете дозволити полю бути також `null`, призначте йому правило `nullable`.

Для зручності правила валідації рядків можна також будувати плинним конструктором `Rule::string()`:

```php
use Illuminate\Validation\Rule;

'title' => [
    'required',
    Rule::string()
        ->min(3)
        ->max(255)
        ->alphaDash(ascii: true),
],
```

Конструктор рядкових правил надає методи для поширених обмежень, зокрема `alpha`, `alphaDash`, `alphaNumeric`, `ascii`, `between`, `doesntEndWith`, `doesntStartWith`, `endsWith`, `exactly`, `lowercase`, `max`, `min`, `startsWith` та `uppercase`. Оскільки конструктор підтримує умови, ви можете також скористатися методами `when` та `unless`, щоб застосовувати обмеження умовно.

<a name="rule-timezone"></a>
#### timezone

Поле, що валідується, має бути дійсним ідентифікатором часового поясу згідно з методом `DateTimeZone::listIdentifiers`.

Аргументи, [які приймає метод `DateTimeZone::listIdentifiers`](https://www.php.net/manual/en/datetimezone.listidentifiers.php), можна також передати цьому правилу валідації:

```php
'timezone' => ['required', 'timezone:all'];

'timezone' => ['required', 'timezone:Africa'];

'timezone' => ['required', 'timezone:per_country,US'];
```

<a name="rule-unique"></a>
#### unique:_table_,_column_

Поле, що валідується, не має існувати у вказаній таблиці бази даних.

**Указання власного імені таблиці чи колонки:**

Замість указувати ім'я таблиці напряму, ви можете вказати модель Eloquent, за якою буде визначено ім'я таблиці:

```php
'email' => ['unique:App\Models\User,email_address']
```

Опція `column` дозволяє вказати відповідну колонку бази даних для поля. Якщо її не вказано, буде використано ім'я поля, що валідується.

```php
'email' => ['unique:users,email_address']
```

**Указання власного підключення до бази даних**

Подекуди вам може знадобитися задати власне підключення для запитів, які робить валідатор. Це робиться додаванням імені підключення перед іменем таблиці:

```php
'email' => ['unique:connection.users,email_address']
```

**Змушення правила Unique ігнорувати певний ID:**

Іноді ви можете захотіти ігнорувати певний ідентифікатор під час перевірки унікальності. Наприклад, розгляньмо екран «оновлення профілю», що містить ім'я користувача, адресу електронної пошти та місцезнаходження. Ви, імовірно, захочете перевірити унікальність адреси. Однак якщо користувач змінює лише поле імені, а не адреси, ви не хочете, щоб виникала помилка валідації, адже користувач уже є власником цієї адреси.

Щоб вказати валідатору ігнорувати ідентифікатор користувача, скористаємося класом `Rule` для плинного визначення правила.

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make($data, [
    'email' => [
        'required',
        Rule::unique('users')->ignore($user->id),
    ],
]);
```

> [!WARNING]
> Ніколи не передавайте методу `ignore` вхідні дані запиту, які контролює користувач. Натомість передавайте лише згенерований системою унікальний ідентифікатор - як-от автоінкрементний ID чи UUID з екземпляра моделі Eloquent. Інакше ваш застосунок стане вразливим до SQL-ін'єкцій.

Замість передавати методу `ignore` значення ключа моделі, ви можете передати весь екземпляр моделі. Laravel автоматично витягне з неї ключ:

```php
Rule::unique('users')->ignore($user);
```

Якщо ваша таблиця використовує ім'я колонки первинного ключа, відмінне від `id`, ви можете вказати його під час виклику методу `ignore`:

```php
Rule::unique('users')->ignore($user->id, 'user_id');
```

За замовчуванням правило `unique` перевіряє унікальність колонки, ім'я якої збігається з іменем атрибута, що валідується. Утім, ви можете передати інше ім'я колонки другим аргументом методу `unique`:

```php
Rule::unique('users', 'email_address')->ignore($user->id);
```

**Додавання додаткових умов Where:**

Ви можете вказати додаткові умови запиту, налаштувавши його методом `where`. Наприклад, додаймо умову, що обмежує запит записами зі значенням колонки `account_id`, рівним `1`:

```php
'email' => Rule::unique('users')->where(fn (Builder $query) => $query->where('account_id', 1))
```

**Ігнорування м'яко видалених записів під час перевірки унікальності:**

За замовчуванням правило `unique` враховує м'яко видалені записи, визначаючи унікальність. Щоб виключити їх із перевірки, викличте метод `withoutTrashed`:

```php
Rule::unique('users')->withoutTrashed();
```

Якщо ваша модель використовує для м'яко видалених записів колонку з іменем, відмінним від `deleted_at`, вкажіть її під час виклику `withoutTrashed`:

```php
Rule::unique('users')->withoutTrashed('was_deleted_at');
```

<a name="rule-uppercase"></a>
#### uppercase

Поле, що валідується, має бути у верхньому регістрі.

<a name="rule-url"></a>
#### url

Поле, що валідується, має бути дійсним URL.

Якщо ви хочете вказати протоколи URL, які слід вважати дійсними, передайте їх як параметри правила валідації:

```php
'url' => ['url:http,https'],

'game' => ['url:minecraft,steam'],
```

<a name="rule-ulid"></a>
#### ulid

Поле, що валідується, має бути дійсним [універсально унікальним лексикографічно сортованим ідентифікатором](https://github.com/ulid/spec) (ULID).

<a name="rule-uuid"></a>
#### uuid

Поле, що валідується, має бути дійсним універсально унікальним ідентифікатором (UUID) за RFC 9562 (версії 1, 3, 4, 5, 6, 7 чи 8).

Ви також можете перевірити, що переданий UUID відповідає специфікації UUID за версією:

```php
'uuid' => ['uuid:4']
```

<a name="conditionally-adding-rules"></a>
## Умовне додавання правил

<a name="skipping-validation-when-fields-have-certain-values"></a>
#### Пропуск валідації, коли поля мають певні значення

Подекуди ви можете захотіти не валідувати певне поле, якщо інше поле має певне значення. Це робиться правилом `exclude_if`. У цьому прикладі поля `appointment_date` та `doctor_name` не валідуватимуться, якщо поле `has_appointment` має значення `false`:

```php
use Illuminate\Support\Facades\Validator;

$validator = Validator::make($data, [
    'has_appointment' => ['required', 'boolean'],
    'appointment_date' => ['exclude_if:has_appointment,false', 'required', 'date'],
    'doctor_name' => ['exclude_if:has_appointment,false', 'required', 'string'],
]);
```

Як альтернативу ви можете скористатися правилом `exclude_unless`, щоб не валідувати поле, якщо тільки інше поле не має певного значення:

```php
$validator = Validator::make($data, [
    'has_appointment' => ['required', 'boolean'],
    'appointment_date' => ['exclude_unless:has_appointment,true', 'required', 'date'],
    'doctor_name' => ['exclude_unless:has_appointment,true', 'required', 'string'],
]);
```

<a name="validating-when-present"></a>
#### Валідація за наявності

У деяких ситуаціях ви можете захотіти виконувати перевірки для поля **лише** тоді, коли воно присутнє у даних, що валідуються. Щоб швидко цього досягти, додайте до списку правило `sometimes`:

```php
$validator = Validator::make($data, [
    'email' => ['sometimes', 'required', 'email'],
]);
```

У прикладі вище поле `email` валідуватиметься лише тоді, коли воно присутнє в масиві `$data`.

> [!NOTE]
> Якщо ви намагаєтеся валідувати поле, яке має бути присутнім завжди, але може бути порожнім, перегляньте [зауваження про необов'язкові поля](#a-note-on-optional-fields).

<a name="complex-conditional-validation"></a>
#### Складна умовна валідація

Іноді ви можете захотіти додавати правила валідації на основі складнішої умовної логіки. Наприклад, ви можете зробити поле обов'язковим лише тоді, коли інше поле має значення більше за 100. Або вам можуть знадобитися два поля з певним значенням лише тоді, коли присутнє інше поле. Додавати такі правила не обов'язково болісно. Спершу створіть екземпляр `Validator` зі _статичними правилами_, які ніколи не змінюються:

```php
use Illuminate\Support\Facades\Validator;

$validator = Validator::make($request->all(), [
    'email' => ['required', 'email'],
    'games' => ['required', 'integer', 'min:0'],
]);
```

Припустімо, наш веб-застосунок призначений для колекціонерів ігор. Якщо колекціонер реєструється в нашому застосунку і має понад 100 ігор, ми хочемо, щоб він пояснив, чому їх так багато. Наприклад, можливо, він тримає магазин перепродажу ігор, а може, просто любить їх колекціонувати. Щоб додати цю вимогу умовно, ми можемо скористатися методом `sometimes` екземпляра `Validator`.

```php
use Illuminate\Support\Fluent;

$validator->sometimes('reason', ['required', 'max:500'], function (Fluent $input) {
    return $input->games >= 100;
});
```

Перший аргумент, переданий методу `sometimes`, - ім'я поля, яке ми валідуємо умовно. Другий - список правил, які хочемо додати. Якщо замикання, передане третім аргументом, повертає `true`, правила буде додано. Цей метод дозволяє легко будувати складні умовні перевірки. Ви можете навіть додавати умовні перевірки одразу для кількох полів:

```php
$validator->sometimes(['reason', 'cost'], 'required', function (Fluent $input) {
    return $input->games >= 100;
});
```

> [!NOTE]
> Параметр `$input`, переданий вашому замиканню, буде екземпляром `Illuminate\Support\Fluent` і дозволить звертатися до ваших вхідних даних і файлів, що валідуються.

<a name="complex-conditional-array-validation"></a>
#### Складна умовна валідація масивів

Іноді ви можете захотіти валідувати поле на основі іншого поля в тому самому вкладеному масиві, індексу якого не знаєте. У таких ситуаціях ви можете дозволити своєму замиканню приймати другий аргумент - поточний елемент масиву, що валідується:

```php
$input = [
    'channels' => [
        [
            'type' => 'email',
            'address' => 'abigail@example.com',
        ],
        [
            'type' => 'url',
            'address' => 'https://example.com',
        ],
    ],
];

$validator->sometimes('channels.*.address', 'email', function (Fluent $input, Fluent $item) {
    return $item->type === 'email';
});

$validator->sometimes('channels.*.address', 'url', function (Fluent $input, Fluent $item) {
    return $item->type !== 'email';
});
```

Як і параметр `$input`, параметр `$item` є екземпляром `Illuminate\Support\Fluent`, коли дані атрибута є масивом; інакше це рядок.

<a name="validating-arrays"></a>
## Валідація масивів

Як зазначено в [документації правила array](#rule-array), правило `array` приймає список дозволених ключів масиву. Якщо в масиві присутні будь-які додаткові ключі, валідація не пройде:

```php
use Illuminate\Support\Facades\Validator;

$input = [
    'user' => [
        'name' => 'Taylor Otwell',
        'username' => 'taylorotwell',
        'admin' => true,
    ],
];

Validator::make($input, [
    'user' => ['array:name,username'],
]);
```

Загалом вам слід завжди вказувати ключі масиву, які дозволено в ньому мати. Інакше методи `validate` та `validated` валідатора повернуть усі валідовані дані, зокрема масив і всі його ключі, навіть якщо ці ключі не валідувалися іншими правилами.

<a name="validating-nested-array-input"></a>
### Валідація вкладених масивів

Валідація вкладених полів форми на основі масивів не обов'язково болісна. Ви можете скористатися «крапковою нотацією», щоб валідувати атрибути всередині масиву. Наприклад, якщо вхідний HTTP-запит містить поле `photos[profile]`, ви можете валідувати його так:

```php
use Illuminate\Support\Facades\Validator;

$validator = Validator::make($request->all(), [
    'photos.profile' => ['required', 'image'],
]);
```

Ви також можете валідувати кожен елемент масиву. Наприклад, щоб перевірити унікальність кожної адреси електронної пошти в масиві, зробіть так:

```php
$validator = Validator::make($request->all(), [
    'users.*.email' => ['email', 'unique:users'],
    'users.*.first_name' => ['required_with:users.*.last_name'],
]);
```

Так само ви можете використовувати символ `*`, указуючи [власні повідомлення валідації у мовних файлах](#custom-messages-for-specific-attributes), що дозволяє легко застосувати одне повідомлення до полів-масивів:

```php
'custom' => [
    'users.*.email' => [
        'unique' => 'Each user must have a unique email address',
    ]
],
```

<a name="accessing-nested-array-data"></a>
#### Доступ до даних вкладеного масиву

Іноді вам може знадобитися звернутися до значення певного елемента вкладеного масиву, призначаючи атрибуту правила валідації. Це робиться методом `Rule::forEach`. Метод `forEach` приймає замикання, яке буде викликано для кожної ітерації атрибута-масиву й отримає значення атрибута та його явне повне ім'я. Замикання має повертати масив правил для цього елемента:

```php
use App\Rules\HasPermission;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

$validator = Validator::make($request->all(), [
    'companies.*.id' => Rule::forEach(function (string|null $value, string $attribute) {
        return [
            Rule::exists(Company::class, 'id'),
            new HasPermission('manage-company', $value),
        ];
    }),
]);
```

<a name="error-message-indexes-and-positions"></a>
### Індекси та позиції в повідомленнях про помилки

Валідуючи масиви, ви можете захотіти послатися в повідомленні про помилку на індекс чи позицію конкретного елемента, який не пройшов валідацію. Для цього включіть у своє [власне повідомлення](#manual-customizing-the-error-messages) заповнювачі `:index` (починається з `0`), `:position` (починається з `1`) чи `:ordinal-position` (починається з `1st`):

```php
use Illuminate\Support\Facades\Validator;

$input = [
    'photos' => [
        [
            'name' => 'BeachVacation.jpg',
            'description' => 'A photo of my beach vacation!',
        ],
        [
            'name' => 'GrandCanyon.jpg',
            'description' => '',
        ],
    ],
];

Validator::validate($input, [
    'photos.*.description' => ['required'],
], [
    'photos.*.description.required' => 'Please describe photo #:position.',
]);
```

З наведеним вище прикладом валідація не пройде, і користувач побачить помилку _«Please describe photo #2.»_

За потреби ви можете посилатися на глибше вкладені індекси та позиції через `second-index`, `second-position`, `third-index`, `third-position` тощо.

```php
'photos.*.attributes.*.string' => 'Invalid attribute for photo #:second-position.',
```

<a name="validating-files"></a>
## Валідація файлів

Laravel надає різноманітні правила валідації для завантажених файлів - як-от `mimes`, `image`, `min` і `max`. Хоча ви вільні вказувати ці правила окремо, Laravel також пропонує плинний конструктор правил валідації файлів, який може здатися вам зручним:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\File;

Validator::validate($input, [
    'attachment' => [
        'required',
        File::types(['mp3', 'wav'])
            ->min(1024)
            ->max(12 * 1024),
    ],
]);
```

<a name="validating-files-file-types"></a>
#### Валідація типів файлів

Хоча під час виклику методу `types` вам потрібно вказати лише розширення, цей метод насправді перевіряє MIME-тип файлу, читаючи його вміст і вгадуючи тип. Повний перелік MIME-типів та відповідних їм розширень можна знайти тут:

[https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types](https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types)

<a name="validating-files-file-sizes"></a>
#### Валідація розмірів файлів

Для зручності мінімальний і максимальний розміри файлу можна вказати рядком із суфіксом одиниць. Підтримуються суфікси `kb`, `mb`, `gb` і `tb`:

```php
File::types(['mp3', 'wav'])
    ->min('1kb')
    ->max('10mb');
```

<a name="validating-files-image-files"></a>
#### Валідація файлів зображень

Якщо ваш застосунок приймає зображення, завантажені користувачами, ви можете скористатися конструктором `image` правила `File`, щоб переконатися, що файл є зображенням (jpg, jpeg, png, bmp, gif, webp, avif, heic чи heif).

Крім того, правило `dimensions` дозволяє обмежити розміри зображення:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\File;

Validator::validate($input, [
    'photo' => [
        'required',
        File::image()
            ->min(1024)
            ->max(12 * 1024)
            ->dimensions(Rule::dimensions()->maxWidth(1000)->maxHeight(500)),
    ],
]);
```

> [!NOTE]
> Докладніше про валідацію розмірів зображень читайте в [документації правила dimensions](#rule-dimensions).

> [!WARNING]
> За замовчуванням правило `image` не дозволяє файли SVG через можливість XSS-вразливостей. Якщо вам потрібно дозволити SVG, передайте правилу `image` параметр `allowSvg: true`: `File::image(allowSvg: true)`.

<a name="validating-files-image-dimensions"></a>
#### Валідація розмірів зображень

Ви також можете валідувати розміри зображення. Наприклад, щоб перевірити, що завантажене зображення має ширину щонайменше 1000 пікселів і висоту 500 пікселів, скористайтеся правилом `dimensions`:

```php
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\File;

File::image()->dimensions(
    Rule::dimensions()
        ->maxWidth(1000)
        ->maxHeight(500)
)
```

> [!NOTE]
> Докладніше про валідацію розмірів зображень читайте в [документації правила dimensions](#rule-dimensions).

<a name="validating-passwords"></a>
## Валідація паролів

Щоб переконатися, що паролі мають достатній рівень складності, скористайтеся об'єктом правила `Password` від Laravel:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\Password;

$validator = Validator::make($request->all(), [
    'password' => ['required', 'confirmed', Password::min(8)],
]);
```

Об'єкт правила `Password` дозволяє легко налаштувати вимоги до складності паролів у вашому застосунку - наприклад, вказати, що пароль має містити щонайменше одну літеру, цифру, символ чи літери різного регістру:

```php
// Require at least 8 characters...
Password::min(8);

// Require at most 256 characters...
Password::min(16)->max(256);

// Require at least one letter...
Password::min(8)->letters();

// Require at least one uppercase and one lowercase letter...
Password::min(8)->mixedCase();

// Require at least one number...
Password::min(8)->numbers();

// Require at least one symbol...
Password::min(8)->symbols();
```

Крім того, ви можете переконатися, що пароль не було скомпрометовано в публічному витоку даних, за допомогою методу `uncompromised`:

```php
Password::min(8)->uncompromised();
```

Внутрішньо об'єкт правила `Password` використовує модель [k-анонімності](https://en.wikipedia.org/wiki/K-anonymity), щоб визначити, чи пароль витік, через сервіс [haveibeenpwned.com](https://haveibeenpwned.com), не жертвуючи приватністю чи безпекою користувача.

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

```php
// Ensure the password appears no more than 3 times in the same data leak...
Password::min(8)->uncompromised(3);
```

Звісно, ви можете об'єднати всі методи з наведених вище прикладів у ланцюжок:

```php
Password::min(8)
    ->max(256)
    ->letters()
    ->mixedCase()
    ->numbers()
    ->symbols()
    ->uncompromised();
```

Ви можете перетворити об'єкт правила `Password` на рядок, придатний для HTML-атрибута `passwordrules`, методом `toPasswordRulesString`:

```blade
<input
    type="password"
    name="password"
    autocomplete="new-password"
    passwordrules="{{ Password::defaults()->toPasswordRulesString() }}"
/>
```

<a name="defining-default-password-rules"></a>
#### Визначення типових правил для паролів

Вам може бути зручно вказати типові правила валідації паролів в одному місці застосунку. Це легко зробити методом `Password::defaults`, який приймає замикання. Замикання, передане методу `defaults`, має повертати типову конфігурацію правила Password. Зазвичай `defaults` слід викликати в методі `boot` одного із сервіс-провайдерів вашого застосунку:

```php
use Illuminate\Validation\Rules\Password;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Password::defaults(function () {
        $rule = Password::min(8);

        return $this->app->isProduction()
            ? $rule->mixedCase()->uncompromised()
            : $rule;
    });
}
```

Далі, коли ви захочете застосувати типові правила до конкретного пароля, викличте метод `defaults` без аргументів:

```php
'password' => ['required', Password::defaults()],
```

Подекуди ви можете захотіти приєднати до типових правил валідації паролів додаткові правила. Це робиться методом `rules`:

```php
use App\Rules\ZxcvbnRule;

Password::defaults(function () {
    $rule = Password::min(8)->rules([new ZxcvbnRule]);

    // ...
});
```

<a name="custom-validation-rules"></a>
## Власні правила валідації

<a name="using-rule-objects"></a>
### Використання об'єктів правил

Laravel надає різноманітні корисні правила валідації; утім, ви можете захотіти визначити власні. Один зі способів зареєструвати власні правила - використати об'єкти правил. Щоб згенерувати новий об'єкт правила, скористайтеся командою Artisan `make:rule`. Скористаймося цією командою, щоб згенерувати правило, яке перевіряє, що рядок записано у верхньому регістрі. Laravel помістить нове правило в каталог `app/Rules`. Якщо цього каталогу немає, Laravel створить його під час виконання команди:

```shell
php artisan make:rule Uppercase
```

Щойно правило створено, ми готові визначити його поведінку. Об'єкт правила містить єдиний метод `validate`. Він отримує ім'я атрибута, його значення та колбек, який слід викликати в разі невдачі з повідомленням про помилку:

```php
<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class Uppercase implements ValidationRule
{
    /**
     * Run the validation rule.
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (strtoupper($value) !== $value) {
            $fail('The :attribute must be uppercase.');
        }
    }
}
```

Щойно правило визначено, ви можете приєднати його до валідатора, передавши екземпляр об'єкта правила разом з іншими правилами:

```php
use App\Rules\Uppercase;

$request->validate([
    'name' => ['required', 'string', new Uppercase],
]);
```

#### Переклад повідомлень валідації

Замість передавати замиканню `$fail` буквальне повідомлення про помилку, ви можете передати [ключ рядка перекладу](/docs/{{version}}/localization) і вказати Laravel перекласти повідомлення:

```php
if (strtoupper($value) !== $value) {
    $fail('validation.uppercase')->translate();
}
```

За потреби ви можете передати заміни заповнювачів і бажану мову першим і другим аргументами методу `translate`:

```php
$fail('validation.location')->translate([
    'value' => $this->value,
], 'fr');
```

#### Доступ до додаткових даних

Якщо вашому класу власного правила потрібен доступ до всіх інших даних, що валідуються, він може реалізувати інтерфейс `Illuminate\Contracts\Validation\DataAwareRule`. Цей інтерфейс вимагає, щоб ваш клас визначив метод `setData`. Laravel автоматично викличе його (перед початком валідації) з усіма даними, що валідуються:

```php
<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\DataAwareRule;
use Illuminate\Contracts\Validation\ValidationRule;

class Uppercase implements DataAwareRule, ValidationRule
{
    /**
     * All of the data under validation.
     *
     * @var array<string, mixed>
     */
    protected $data = [];

    // ...

    /**
     * Set the data under validation.
     *
     * @param  array<string, mixed>  $data
     */
    public function setData(array $data): static
    {
        $this->data = $data;

        return $this;
    }
}
```

Або, якщо вашому правилу потрібен доступ до екземпляра валідатора, що виконує валідацію, реалізуйте інтерфейс `ValidatorAwareRule`:

```php
<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Contracts\Validation\ValidatorAwareRule;
use Illuminate\Validation\Validator;

class Uppercase implements ValidationRule, ValidatorAwareRule
{
    /**
     * The validator instance.
     *
     * @var \Illuminate\Validation\Validator
     */
    protected $validator;

    // ...

    /**
     * Set the current validator.
     */
    public function setValidator(Validator $validator): static
    {
        $this->validator = $validator;

        return $this;
    }
}
```

<a name="using-closures"></a>
### Використання замикань

Якщо функціональність власного правила потрібна вам у застосунку лише раз, ви можете скористатися замиканням замість об'єкта правила. Замикання отримує ім'я атрибута, його значення та колбек `$fail`, який слід викликати, якщо валідація не пройшла:

```php
use Illuminate\Support\Facades\Validator;
use Closure;

$validator = Validator::make($request->all(), [
    'title' => [
        'required',
        'max:255',
        function (string $attribute, mixed $value, Closure $fail) {
            if ($value === 'foo') {
                $fail("The {$attribute} is invalid.");
            }
        },
    ],
]);
```

<a name="implicit-rules"></a>
### Неявні правила

За замовчуванням, коли атрибут, що валідується, відсутній або містить порожній рядок, звичайні правила валідації - зокрема власні - не виконуються. Наприклад, правило [unique](#rule-unique) не виконуватиметься для порожнього рядка:

```php
use Illuminate\Support\Facades\Validator;

$rules = ['name' => ['unique:users,name']];

$input = ['name' => ''];

Validator::make($input, $rules)->passes(); // true
```

Щоб власне правило виконувалося навіть тоді, коли атрибут порожній, правило має неявно вказувати, що атрибут обов'язковий. Щоб швидко згенерувати новий об'єкт неявного правила, скористайтеся командою Artisan `make:rule` з опцією `--implicit`:

```shell
php artisan make:rule Uppercase --implicit
```

> [!WARNING]
> «Неявне» правило лише _натякає_, що атрибут обов'язковий. Чи справді воно відхилятиме відсутній чи порожній атрибут - вирішувати вам.

---

Джерело: https://laravelukraine.com/docs/13.x/errors

# Обробка помилок

- [Вступ](#introduction)
- [Конфігурація](#configuration)
- [Обробка винятків](#handling-exceptions)
    - [Звітування про винятки](#reporting-exceptions)
    - [Рівні логування винятків](#exception-log-levels)
    - [Ігнорування винятків за типом](#ignoring-exceptions-by-type)
    - [Рендеринг винятків](#rendering-exceptions)
    - [Винятки зі звітуванням і рендерингом](#renderable-exceptions)
- [Обмеження частоти звітування про винятки](#throttling-reported-exceptions)
- [HTTP-винятки](#http-exceptions)
    - [Власні сторінки HTTP-помилок](#custom-http-error-pages)

<a name="introduction"></a>
## Вступ

Коли ви розпочинаєте новий проєкт Laravel, обробку помилок і винятків уже налаштовано за вас; утім, будь-коли ви можете скористатися методом `withExceptions` у файлі `bootstrap/app.php` вашого застосунку, щоб керувати тим, як застосунок звітує про винятки та рендерить їх.

Об'єкт `$exceptions`, переданий замиканню `withExceptions`, є екземпляром `Illuminate\Foundation\Configuration\Exceptions` і відповідає за керування обробкою винятків у вашому застосунку. Ми глибше розглянемо цей об'єкт далі в документації.

<a name="configuration"></a>
## Конфігурація

Опція `debug` у вашому конфігураційному файлі `config/app.php` визначає, скільки інформації про помилку насправді показується користувачеві. За замовчуванням ця опція налаштована на значення змінної середовища `APP_DEBUG`, яка зберігається у вашому файлі `.env`.

Під час локальної розробки змінній середовища `APP_DEBUG` варто задати значення `true`.

> [!WARNING]
> У продакшен-середовищі значення `APP_DEBUG` завжди має бути `false`. Якщо в продакшені воно матиме значення `true`, ви ризикуєте розкрити конфіденційні значення конфігурації кінцевим користувачам вашого застосунку.

<a name="handling-exceptions"></a>
## Обробка винятків

<a name="reporting-exceptions"></a>
### Звітування про винятки

У Laravel звітування про винятки використовується, щоб логувати їх або надсилати до зовнішнього сервісу на кшталт [Laravel Nightwatch](https://nightwatch.laravel.com), [Sentry](https://github.com/getsentry/sentry-laravel) чи [Flare](https://flareapp.io). За замовчуванням винятки логуються відповідно до вашої конфігурації [логування](/docs/{{version}}/logging). Утім, ви вільні логувати їх як завгодно.

Якщо вам потрібно звітувати про різні типи винятків по-різному, скористайтеся методом винятків `report` у файлі `bootstrap/app.php`, щоб зареєструвати замикання, яке має виконуватися, коли потрібно відзвітувати про виняток певного типу. Laravel визначить тип винятку, про який звітує замикання, за вказаним у ньому типом:

```php
use App\Exceptions\InvalidOrderException;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->report(function (InvalidOrderException $e) {
        // ...
    });
})
```

Коли ви реєструєте власний колбек звітування методом `report`, Laravel усе одно логуватиме виняток за типовою конфігурацією логування застосунку. Якщо ви хочете зупинити поширення винятку до типового стека логування, скористайтеся методом `stop` під час визначення колбека або поверніть із нього `false`:

```php
use App\Exceptions\InvalidOrderException;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->report(function (InvalidOrderException $e) {
        // ...
    })->stop();

    $exceptions->report(function (InvalidOrderException $e) {
        return false;
    });
})
```

> [!NOTE]
> Щоб налаштувати звітування для конкретного винятку, ви також можете скористатися [винятками зі звітуванням](/docs/{{version}}/errors#renderable-exceptions).

<a name="global-log-context"></a>
#### Глобальний контекст логів

Якщо він доступний, Laravel автоматично додає ідентифікатор поточного користувача до повідомлення логу кожного винятку як контекстні дані. Ви можете визначити власні глобальні контекстні дані методом винятків `context` у файлі `bootstrap/app.php`. Цю інформацію буде додано до кожного повідомлення логу про виняток, що його записує ваш застосунок:

```php
->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->context(fn () => [
        'foo' => 'bar',
    ]);
})
```

<a name="exception-log-context"></a>
#### Контекст логу винятку

Хоча додавати контекст до кожного повідомлення логу корисно, іноді конкретний виняток має унікальний контекст, який ви хотіли б бачити у логах. Визначивши метод `context` в одному з винятків вашого застосунку, ви можете вказати будь-які дані, доречні для цього винятку, які слід додати до його запису в логу:

```php
<?php

namespace App\Exceptions;

use Exception;

class InvalidOrderException extends Exception
{
    // ...

    /**
     * Get the exception's context information.
     *
     * @return array<string, mixed>
     */
    public function context(): array
    {
        return ['order_id' => $this->orderId];
    }
}
```

<a name="the-report-helper"></a>
#### Хелпер `report`

Іноді вам може знадобитися відзвітувати про виняток, але продовжити обробку поточного запиту. Функція-хелпер `report` дозволяє швидко відзвітувати про виняток, не рендерячи користувачеві сторінку помилки:

```php
public function isValid(string $value): bool
{
    try {
        // Validate the value...
    } catch (Throwable $e) {
        report($e);

        return false;
    }
}
```

<a name="deduplicating-reported-exceptions"></a>
#### Усунення дублікатів у звітах про винятки

Якщо ви використовуєте функцію `report` у всьому застосунку, ви можете подекуди відзвітувати про той самий виняток кілька разів, створивши дублікати записів у логах.

Якщо ви хочете, щоб про кожен окремий екземпляр винятку звітувалося лише раз, викличте метод винятків `dontReportDuplicates` у файлі `bootstrap/app.php` вашого застосунку:

```php
->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontReportDuplicates();
})
```

Тепер, коли хелпер `report` викликається з тим самим екземпляром винятку, буде відзвітовано лише перший виклик:

```php
$original = new RuntimeException('Whoops!');

report($original); // reported

try {
    throw $original;
} catch (Throwable $caught) {
    report($caught); // ignored
}

report($original); // ignored
report($caught); // ignored
```

<a name="exception-log-levels"></a>
### Рівні логування винятків

Коли повідомлення записуються до [логів](/docs/{{version}}/logging) вашого застосунку, вони записуються на визначеному [рівні логування](/docs/{{version}}/logging#log-levels), що вказує на серйозність чи важливість повідомлення.

Як зазначено вище, навіть коли ви реєструєте власний колбек звітування методом `report`, Laravel усе одно логуватиме виняток за типовою конфігурацією логування; однак оскільки рівень логування іноді впливає на канали, до яких потрапляє повідомлення, ви можете захотіти налаштувати рівень, на якому логуються певні винятки.

Для цього скористайтеся методом винятків `level` у файлі `bootstrap/app.php`. Він приймає тип винятку першим аргументом і рівень логування другим:

```php
use PDOException;
use Psr\Log\LogLevel;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->level(PDOException::class, LogLevel::CRITICAL);
})
```

<a name="ignoring-exceptions-by-type"></a>
### Ігнорування винятків за типом

Створюючи застосунок, ви матимете типи винятків, про які ніколи не захочете звітувати. Щоб ігнорувати їх, скористайтеся методом винятків `dontReport` у файлі `bootstrap/app.php`. Про будь-який клас, переданий цьому методу, ніколи не звітуватиметься; утім, він усе ще може мати власну логіку рендерингу:

```php
use App\Exceptions\InvalidOrderException;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontReport([
        InvalidOrderException::class,
    ]);
})
```

Як альтернативу ви можете просто «позначити» клас винятку інтерфейсом `Illuminate\Contracts\Debug\ShouldntReport`. Коли виняток позначено цим інтерфейсом, обробник винятків Laravel ніколи про нього не звітуватиме:

```php
<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Contracts\Debug\ShouldntReport;

class PodcastProcessingException extends Exception implements ShouldntReport
{
    //
}
```

Якщо вам потрібен ще більший контроль над тим, коли ігнорується певний тип винятку, передайте замикання методу `dontReportWhen`:

```php
use App\Exceptions\InvalidOrderException;
use Throwable;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontReportWhen(function (Throwable $e) {
        return $e instanceof PodcastProcessingException &&
               $e->reason() === 'Subscription expired';
    });
})
```

Внутрішньо Laravel уже ігнорує деякі типи помилок за вас - як-от винятки від HTTP-помилок 404, відповідей 403 через розбіжність джерела чи відповідей 419 через недійсні CSRF-токени. Якщо ви хочете вказати Laravel припинити ігнорувати певний тип винятку, скористайтеся методом винятків `stopIgnoring` у файлі `bootstrap/app.php`:

```php
use Symfony\Component\HttpKernel\Exception\HttpException;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->stopIgnoring(HttpException::class);
})
```

<a name="rendering-exceptions"></a>
### Рендеринг винятків

За замовчуванням обробник винятків Laravel перетворює винятки на HTTP-відповідь за вас. Однак ви вільні зареєструвати власне замикання рендерингу для винятків певного типу. Це робиться методом винятків `render` у файлі `bootstrap/app.php` вашого застосунку.

Замикання, передане методу `render`, має повертати екземпляр `Illuminate\Http\Response`, який можна створити хелпером `response`. Laravel визначить тип винятку, який рендерить замикання, за вказаним у ньому типом:

```php
use App\Exceptions\InvalidOrderException;
use Illuminate\Http\Request;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->render(function (InvalidOrderException $e, Request $request) {
        return response()->view('errors.invalid-order', status: 500);
    });
})
```

Ви також можете скористатися методом `render`, щоб перевизначити поведінку рендерингу вбудованих винятків Laravel чи Symfony - як-от `NotFoundHttpException`. Якщо передане методу `render` замикання не повертає значення, буде використано типовий рендеринг винятків Laravel:

```php
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->render(function (NotFoundHttpException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'message' => 'Record not found.'
            ], 404);
        }
    });
})
```

<a name="rendering-exceptions-as-json"></a>
#### Рендеринг винятків у форматі JSON

Рендерячи виняток, Laravel автоматично визначає, чи слід віддати його як HTML- чи JSON-відповідь, спираючись на заголовок `Accept` запиту. Якщо ви хочете налаштувати, як саме Laravel це вирішує, скористайтеся методом `shouldRenderJsonWhen`:

```php
use Illuminate\Http\Request;
use Throwable;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $e) {
        if ($request->is('admin/*')) {
            return true;
        }

        return $request->expectsJson();
    });
})
```

<a name="customizing-the-exception-response"></a>
#### Налаштування відповіді для винятку

Зрідка вам може знадобитися налаштувати всю HTTP-відповідь, яку рендерить обробник винятків Laravel. Для цього зареєструйте замикання налаштування відповіді методом `respond`:

```php
use Symfony\Component\HttpFoundation\Response;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->respond(function (Response $response) {
        if ($response->getStatusCode() === 419) {
            return back()->with([
                'message' => 'The page expired, please try again.',
            ]);
        }

        return $response;
    });
})
```

<a name="renderable-exceptions"></a>
### Винятки зі звітуванням і рендерингом

Замість визначати власну поведінку звітування та рендерингу у файлі `bootstrap/app.php`, ви можете визначити методи `report` і `render` безпосередньо у винятках свого застосунку. Коли ці методи існують, фреймворк викликатиме їх автоматично:

```php
<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class InvalidOrderException extends Exception
{
    /**
     * Report the exception.
     */
    public function report(): void
    {
        // ...
    }

    /**
     * Render the exception as an HTTP response.
     */
    public function render(Request $request): Response
    {
        return response(/* ... */);
    }
}
```

Якщо ваш виняток успадковує виняток, який уже підлягає рендерингу - як-от вбудований виняток Laravel чи Symfony, - ви можете повернути `false` з методу `render`, щоб відрендерити типову HTTP-відповідь цього винятку:

```php
/**
 * Render the exception as an HTTP response.
 */
public function render(Request $request): Response|bool
{
    if (/** Determine if the exception needs custom rendering */) {

        return response(/* ... */);
    }

    return false;
}
```

Якщо ваш виняток містить власну логіку звітування, потрібну лише за певних умов, вам може знадобитися вказати Laravel іноді звітувати про виняток за типовою конфігурацією обробки. Для цього поверніть `false` з методу `report` винятку:

```php
/**
 * Report the exception.
 */
public function report(): bool
{
    if (/** Determine if the exception needs custom reporting */) {

        // ...

        return true;
    }

    return false;
}
```

> [!NOTE]
> Ви можете вказати типи будь-яких потрібних залежностей методу `report`, і [сервіс-контейнер](/docs/{{version}}/container) Laravel автоматично впровадить їх у метод.

<a name="throttling-reported-exceptions"></a>
### Обмеження частоти звітування про винятки

Якщо ваш застосунок звітує про дуже велику кількість винятків, ви можете захотіти обмежити, скільки з них справді логується чи надсилається до зовнішнього сервісу відстеження помилок.

Щоб брати випадкову вибірку винятків, скористайтеся методом винятків `throttle` у файлі `bootstrap/app.php`. Метод `throttle` приймає замикання, яке має повертати екземпляр `Lottery`:

```php
use Illuminate\Support\Lottery;
use Throwable;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->throttle(function (Throwable $e) {
        return Lottery::odds(1, 1000);
    });
})
```

Вибірку можна робити й умовно, залежно від типу винятку. Якщо ви хочете брати вибірку лише для екземплярів конкретного класу, повертайте `Lottery` тільки для нього:

```php
use App\Exceptions\ApiMonitoringException;
use Illuminate\Support\Lottery;
use Throwable;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->throttle(function (Throwable $e) {
        if ($e instanceof ApiMonitoringException) {
            return Lottery::odds(1, 1000);
        }
    });
})
```

Ви також можете обмежити частоту винятків, які логуються чи надсилаються до зовнішнього сервісу відстеження помилок, повернувши екземпляр `Limit` замість `Lottery`. Це корисно, якщо ви хочете захиститися від раптових сплесків винятків, що заповнюють ваші логи, - наприклад, коли сторонній сервіс, який використовує ваш застосунок, недоступний:

```php
use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Throwable;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->throttle(function (Throwable $e) {
        if ($e instanceof BroadcastException) {
            return Limit::perMinute(300);
        }
    });
})
```

За замовчуванням обмеження використовують клас винятку як ключ. Ви можете налаштувати це, вказавши власний ключ методом `by` на `Limit`:

```php
use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Throwable;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->throttle(function (Throwable $e) {
        if ($e instanceof BroadcastException) {
            return Limit::perMinute(300)->by($e->getMessage());
        }
    });
})
```

Звісно, ви можете повертати поєднання екземплярів `Lottery` та `Limit` для різних винятків:

```php
use App\Exceptions\ApiMonitoringException;
use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Lottery;
use Throwable;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->throttle(function (Throwable $e) {
        return match (true) {
            $e instanceof BroadcastException => Limit::perMinute(300),
            $e instanceof ApiMonitoringException => Lottery::odds(1, 1000),
            default => Limit::none(),
        };
    });
})
```

<a name="http-exceptions"></a>
## HTTP-винятки

Деякі винятки описують коди HTTP-помилок від сервера. Наприклад, це може бути помилка «сторінку не знайдено» (404), «неавторизовано» (401) чи навіть згенерована розробником помилка 500. Щоб згенерувати таку відповідь із будь-якого місця застосунку, скористайтеся хелпером `abort`:

```php
abort(404);
```

<a name="custom-http-error-pages"></a>
### Власні сторінки HTTP-помилок

Laravel спрощує показ власних сторінок помилок для різних HTTP-статусів. Наприклад, щоб налаштувати сторінку помилки для статусу 404, створіть шаблон представлення `resources/views/errors/404.blade.php`. Це представлення рендеритиметься для всіх помилок 404, згенерованих вашим застосунком. Представлення в цьому каталозі мають називатися відповідно до HTTP-статусу, якому вони відповідають. Екземпляр `Symfony\Component\HttpKernel\Exception\HttpException`, створений функцією `abort`, буде передано представленню як змінну `$exception`:

```blade
<h2>{{ $exception->getMessage() }}</h2>
```

Ви можете опублікувати типові шаблони сторінок помилок Laravel командою Artisan `vendor:publish`. Опублікувавши шаблони, ви можете налаштувати їх на свій смак:

```shell
php artisan vendor:publish --tag=laravel-errors
```

<a name="fallback-http-error-pages"></a>
#### Резервні сторінки HTTP-помилок

Ви також можете визначити «резервну» сторінку помилки для певної серії HTTP-статусів. Вона рендеритиметься, якщо для конкретного статусу немає відповідної сторінки. Для цього визначте шаблони `4xx.blade.php` і `5xx.blade.php` у каталозі `resources/views/errors` вашого застосунку.

Резервні сторінки не впливатимуть на відповіді з помилками `401`, `402`, `403`, `404`, `419`, `429`, `500` і `503`, оскільки Laravel має внутрішні спеціальні сторінки для цих статусів. Щоб налаштувати сторінки для них, визначте власну сторінку помилки для кожного окремо.

---

Джерело: https://laravelukraine.com/docs/13.x/logging

# Логування

- [Вступ](#introduction)
- [Конфігурація](#configuration)
    - [Доступні драйвери каналів](#available-channel-drivers)
    - [Передумови каналів](#channel-prerequisites)
    - [Логування попереджень про застарілість](#logging-deprecation-warnings)
- [Створення стеків логів](#building-log-stacks)
- [Запис повідомлень до логу](#writing-log-messages)
    - [Контекстна інформація](#contextual-information)
    - [Запис до конкретних каналів](#writing-to-specific-channels)
- [Налаштування каналів Monolog](#monolog-channel-customization)
    - [Налаштування Monolog для каналів](#customizing-monolog-for-channels)
    - [Створення каналів з обробниками Monolog](#creating-monolog-handler-channels)
    - [Створення власних каналів через фабрики](#creating-custom-channels-via-factories)
- [Стеження за логами за допомогою Pail](#tailing-log-messages-using-pail)
    - [Встановлення](#pail-installation)
    - [Використання](#pail-usage)
    - [Фільтрування логів](#pail-filtering-logs)

<a name="introduction"></a>
## Вступ

Щоб допомогти вам дізнатися більше про те, що відбувається всередині вашого застосунку, Laravel надає надійні сервіси логування, які дозволяють записувати повідомлення у файли, системний лог помилок і навіть у Slack, щоб сповістити всю вашу команду.

Логування Laravel побудоване на «каналах». Кожен канал представляє конкретний спосіб запису інформації логу. Наприклад, канал `single` пише логи в один файл, тоді як канал `slack` надсилає повідомлення до Slack. Повідомлення можуть записуватися до кількох каналів залежно від їхньої серйозності.

Під капотом Laravel використовує бібліотеку [Monolog](https://github.com/Seldaek/monolog), яка підтримує різноманітні потужні обробники логів. Laravel робить налаштування цих обробників дуже простим, дозволяючи комбінувати їх, щоб налаштувати обробку логів вашого застосунку.

<a name="configuration"></a>
## Конфігурація

Усі опції конфігурації, що керують поведінкою логування вашого застосунку, містяться у файлі `config/logging.php`. Цей файл дозволяє налаштувати канали логування, тож обов'язково перегляньте кожен доступний канал і його опції. Кілька поширених опцій ми розглянемо нижче.

За замовчуванням Laravel використовує канал `stack` для запису повідомлень. Канал `stack` слугує для об'єднання кількох каналів логування в один. Докладніше про створення стеків читайте в [документації нижче](#building-log-stacks).

<a name="available-channel-drivers"></a>
### Доступні драйвери каналів

Кожен канал логування працює на «драйвері». Драйвер визначає, як і де насправді записується повідомлення. Наведені нижче драйвери каналів доступні в кожному застосунку Laravel. Запис для більшості з них уже присутній у вашому файлі `config/logging.php`, тож обов'язково перегляньте його вміст:

<div class="overflow-auto">

| Назва        | Опис                                                                 |
| ------------ | -------------------------------------------------------------------- |
| `custom`     | Драйвер, що викликає вказану фабрику для створення каналу.           |
| `daily`      | Драйвер Monolog на основі `RotatingFileHandler` із щоденною ротацією. |
| `monthly`    | Драйвер Monolog на основі `RotatingFileHandler` із щомісячною ротацією. |
| `errorlog`   | Драйвер Monolog на основі `ErrorLogHandler`.                         |
| `monolog`    | Фабричний драйвер Monolog, що може використовувати будь-який підтримуваний обробник. |
| `single`     | Канал логера на основі одного файлу чи шляху (`StreamHandler`).      |
| `slack`      | Драйвер Monolog на основі `SlackWebhookHandler`.                     |
| `stack`      | Обгортка для створення «багатоканальних» каналів.                    |
| `syslog`     | Драйвер Monolog на основі `SyslogHandler`.                           |

</div>

> [!NOTE]
> Перегляньте документацію з [розширеного налаштування каналів](#monolog-channel-customization), щоб дізнатися більше про драйвери `monolog` і `custom`.

<a name="configuring-the-channel-name"></a>
#### Налаштування імені каналу

За замовчуванням Monolog створюється з «іменем каналу», що відповідає поточному середовищу - наприклад, `production` чи `local`. Щоб змінити це значення, додайте до конфігурації каналу опцію `name`:

```php
'stack' => [
    'driver' => 'stack',
    'name' => 'channel-name',
    'channels' => ['single', 'slack'],
],
```

<a name="channel-prerequisites"></a>
### Передумови каналів

<a name="configuring-the-single-daily-and-monthly-channels"></a>
#### Налаштування каналів Single, Daily та Monthly

Канали `single`, `daily` та `monthly` мають три необов'язкові опції конфігурації: `bubble`, `permission` та `locking`.

<div class="overflow-auto">

| Назва        | Опис                                                                          | За замовчуванням |
| ------------ | ----------------------------------------------------------------------------- | ---------------- |
| `bubble`     | Визначає, чи повідомлення мають спливати до інших каналів після обробки.     | `true`           |
| `locking`    | Спроба заблокувати файл журналу перед записом до нього.                      | `false`          |
| `permission` | Права доступу до файлу журналу.                                               | `0644`           |

</div>

Крім того, політику зберігання для каналів `daily` та `monthly` можна налаштувати через опцію конфігурації `max_files`. Змінна оточення `LOG_DAILY_DAYS` також може використовуватися для налаштування зберігання для каналу `daily`.

<a name="configuring-the-papertrail-channel"></a>
#### Налаштування каналу Papertrail

Канал `papertrail` потребує опцій конфігурації `host` і `port`. Їх можна задати змінними середовища `PAPERTRAIL_URL` і `PAPERTRAIL_PORT`. Ці значення можна отримати в [Papertrail](https://help.papertrailapp.com/kb/configuration/configuring-centralized-logging-from-php-apps/#send-events-from-php-app).

<a name="configuring-the-slack-channel"></a>
#### Налаштування каналу Slack

Канал `slack` потребує опції конфігурації `url`. Це значення можна задати змінною середовища `LOG_SLACK_WEBHOOK_URL`. Ця адреса має відповідати URL [вхідного вебхука](https://slack.com/apps/A0F7XDUAZ-incoming-webhooks), налаштованого для вашої команди в Slack.

За замовчуванням Slack отримуватиме лише логи рівня `critical` і вище; утім, ви можете змінити це змінною середовища `LOG_LEVEL` або опцією `level` у масиві конфігурації вашого каналу Slack.

<a name="logging-deprecation-warnings"></a>
### Логування попереджень про застарілість

PHP, Laravel та інші бібліотеки часто сповіщають користувачів, що деякі їхні можливості застаріли й будуть вилучені в майбутній версії. Якщо ви хочете логувати ці попередження, вкажіть бажаний канал `deprecations` змінною середовища `LOG_DEPRECATIONS_CHANNEL` або у файлі `config/logging.php` вашого застосунку:

```php
'deprecations' => [
    'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
    'trace' => env('LOG_DEPRECATIONS_TRACE', false),
],

'channels' => [
    // ...
]
```

Або ж ви можете визначити канал логування з іменем `deprecations`. Якщо канал із таким іменем існує, він завжди використовуватиметься для логування попереджень про застарілість:

```php
'channels' => [
    'deprecations' => [
        'driver' => 'single',
        'path' => storage_path('logs/php-deprecation-warnings.log'),
    ],
],
```

<a name="building-log-stacks"></a>
## Створення стеків логів

Як згадувалося раніше, драйвер `stack` дозволяє для зручності поєднати кілька каналів в один. Щоб проілюструвати використання стеків, погляньмо на приклад конфігурації, яку ви можете побачити в продакшен-застосунку:

```php
'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['syslog', 'slack'], // [tl! add]
        'ignore_exceptions' => false,
    ],

    'syslog' => [
        'driver' => 'syslog',
        'level' => env('LOG_LEVEL', 'debug'),
        'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER),
        'replace_placeholders' => true,
    ],

    'slack' => [
        'driver' => 'slack',
        'url' => env('LOG_SLACK_WEBHOOK_URL'),
        'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'),
        'emoji' => env('LOG_SLACK_EMOJI', ':boom:'),
        'level' => env('LOG_LEVEL', 'critical'),
        'replace_placeholders' => true,
    ],
],
```

Розберімо цю конфігурацію. Спершу зверніть увагу, що наш канал `stack` об'єднує два інші канали через опцію `channels`: `syslog` і `slack`. Тож під час запису повідомлень обидва ці канали матимуть змогу його залогувати. Однак, як ми побачимо нижче, чи справді вони це зроблять, залежить від серйозності повідомлення, тобто його «рівня».

<a name="log-levels"></a>
#### Рівні логування

Зверніть увагу на опцію `level` у конфігураціях каналів `syslog` і `slack` у прикладі вище. Вона визначає мінімальний «рівень», якого має досягти повідомлення, щоб канал його залогував. Monolog, на якому працюють сервіси логування Laravel, пропонує всі рівні, визначені у [специфікації RFC 5424](https://tools.ietf.org/html/rfc5424). У порядку спадання серйозності це: **emergency**, **alert**, **critical**, **error**, **warning**, **notice**, **info** і **debug**.

Тож уявімо, що ми логуємо повідомлення методом `debug`:

```php
Log::debug('An informational message.');
```

За нашої конфігурації канал `syslog` запише повідомлення до системного логу; однак оскільки це повідомлення не має рівня `critical` чи вищого, до Slack воно не потрапить. Натомість якщо ми залогуємо повідомлення рівня `emergency`, воно потрапить і до системного логу, і до Slack, адже рівень `emergency` вищий за мінімальний поріг обох каналів:

```php
Log::emergency('The system is down!');
```

<a name="writing-log-messages"></a>
## Запис повідомлень до логу

Ви можете записувати інформацію до логів за допомогою [фасаду](/docs/{{version}}/facades) `Log`. Як згадувалося раніше, логер надає вісім рівнів логування, визначених у [специфікації RFC 5424](https://tools.ietf.org/html/rfc5424): **emergency**, **alert**, **critical**, **error**, **warning**, **notice**, **info** і **debug**:

```php
use Illuminate\Support\Facades\Log;

Log::emergency($message);
Log::alert($message);
Log::critical($message);
Log::error($message);
Log::warning($message);
Log::notice($message);
Log::info($message);
Log::debug($message);
```

Ви можете викликати будь-який із цих методів, щоб залогувати повідомлення відповідного рівня. За замовчуванням повідомлення буде записано до типового каналу логування, налаштованого у вашому конфігураційному файлі `logging`:

```php
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * Show the profile for the given user.
     */
    public function show(string $id): View
    {
        Log::info('Showing the user profile for user: {id}', ['id' => $id]);

        return view('user.profile', [
            'user' => User::findOrFail($id)
        ]);
    }
}
```

<a name="contextual-information"></a>
### Контекстна інформація

Методам логування можна передати масив контекстних даних. Ці дані буде відформатовано й показано разом із повідомленням логу:

```php
use Illuminate\Support\Facades\Log;

Log::info('User {id} failed to login.', ['id' => $user->id]);
```

Подекуди вам може знадобитися вказати контекстну інформацію, яку слід додавати до всіх наступних записів логу в певному каналі. Наприклад, ви можете захотіти логувати ідентифікатор запиту, пов'язаний із кожним вхідним запитом до вашого застосунку. Для цього викличте метод `withContext` фасаду `Log`:

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;

class AssignRequestId
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $requestId = (string) Str::uuid();

        Log::withContext([
            'request-id' => $requestId
        ]);

        $response = $next($request);

        $response->headers->set('Request-Id', $requestId);

        return $response;
    }
}
```

Якщо ви хочете надати контекстну інформацію _всім_ каналам логування, викличте метод `Log::shareContext()`. Він передасть контекстну інформацію всім створеним каналам, а також усім каналам, створеним згодом:

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;

class AssignRequestId
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $requestId = (string) Str::uuid();

        Log::shareContext([
            'request-id' => $requestId
        ]);

        // ...
    }
}
```

> [!NOTE]
> Якщо вам потрібно ділитися контекстом логу під час обробки завдань у черзі, скористайтеся [`middleware` завдань](/docs/{{version}}/queues#job-middleware).

<a name="writing-to-specific-channels"></a>
### Запис до конкретних каналів

Іноді ви можете захотіти залогувати повідомлення до каналу, відмінного від типового каналу вашого застосунку. Скористайтеся методом `channel` фасаду `Log`, щоб отримати будь-який визначений у конфігурації канал і залогувати до нього:

```php
use Illuminate\Support\Facades\Log;

Log::channel('slack')->info('Something happened!');
```

Якщо ви хочете створити стек логування на вимогу з кількох каналів, скористайтеся методом `stack`:

```php
Log::stack(['single', 'slack'])->info('Something happened!');
```

<a name="on-demand-channels"></a>
#### Канали на вимогу

Також можна створити канал на вимогу, надавши конфігурацію під час виконання, без її присутності у файлі `logging` вашого застосунку. Для цього передайте масив конфігурації методу `build` фасаду `Log`:

```php
use Illuminate\Support\Facades\Log;

Log::build([
  'driver' => 'single',
  'path' => storage_path('logs/custom.log'),
])->info('Something happened!');
```

Ви також можете захотіти включити канал на вимогу до стека логування на вимогу. Це робиться додаванням екземпляра вашого каналу до масиву, переданого методу `stack`:

```php
use Illuminate\Support\Facades\Log;

$channel = Log::build([
  'driver' => 'single',
  'path' => storage_path('logs/custom.log'),
]);

Log::stack(['slack', $channel])->info('Something happened!');
```

<a name="monolog-channel-customization"></a>
## Налаштування каналів Monolog

<a name="customizing-monolog-for-channels"></a>
### Налаштування Monolog для каналів

Іноді вам може знадобитися повний контроль над тим, як Monolog налаштовано для наявного каналу. Наприклад, ви можете захотіти налаштувати власну реалізацію `FormatterInterface` для вбудованого каналу `single` від Laravel.

Щоб почати, визначте в конфігурації каналу масив `tap`. Він має містити список класів, які матимуть змогу налаштувати (тобто «підключитися» до) екземпляр Monolog після його створення. Загальноприйнятого місця для цих класів немає, тож ви вільні створити для них каталог у своєму застосунку:

```php
'single' => [
    'driver' => 'single',
    'tap' => [App\Logging\CustomizeFormatter::class],
    'path' => storage_path('logs/laravel.log'),
    'level' => env('LOG_LEVEL', 'debug'),
    'replace_placeholders' => true,
],
```

Налаштувавши опцію `tap` у своєму каналі, ви готові визначити клас, який налаштує ваш екземпляр Monolog. Цей клас потребує лише одного методу - `__invoke`, який отримує екземпляр `Illuminate\Log\Logger`. Цей екземпляр проксіює всі виклики методів до відповідного екземпляра Monolog:

```php
<?php

namespace App\Logging;

use Illuminate\Log\Logger;
use Monolog\Formatter\LineFormatter;

class CustomizeFormatter
{
    /**
     * Customize the given logger instance.
     */
    public function __invoke(Logger $logger): void
    {
        foreach ($logger->getHandlers() as $handler) {
            $handler->setFormatter(new LineFormatter(
                '[%datetime%] %channel%.%level_name%: %message% %context% %extra%'
            ));
        }
    }
}
```

> [!NOTE]
> Усі ваші класи «tap» розв'язуються через [сервіс-контейнер](/docs/{{version}}/container), тож будь-які потрібні їм залежності конструктора буде впроваджено автоматично.

<a name="creating-monolog-handler-channels"></a>
### Створення каналів з обробниками Monolog

Monolog має чимало [доступних обробників](https://github.com/Seldaek/monolog/tree/main/src/Monolog/Handler), і Laravel не містить вбудованого каналу для кожного з них. У деяких випадках ви можете захотіти створити власний канал, який є просто екземпляром конкретного обробника Monolog без відповідного драйвера логування Laravel. Такі канали легко створити драйвером `monolog`.

Використовуючи драйвер `monolog`, опція конфігурації `handler` вказує, який обробник буде створено. За бажанням будь-які потрібні обробнику параметри конструктора можна задати опцією `handler_with`:

```php
'logentries' => [
    'driver'  => 'monolog',
    'handler' => Monolog\Handler\SyslogUdpHandler::class,
    'handler_with' => [
        'host' => 'my.logentries.internal.datahubhost.company.com',
        'port' => '10000',
    ],
],
```

<a name="monolog-formatters"></a>
#### Форматувальники Monolog

Використовуючи драйвер `monolog`, типовим форматувальником буде `LineFormatter` від Monolog. Утім, ви можете налаштувати тип форматувальника, переданого обробнику, опціями `formatter` і `formatter_with`:

```php
'browser' => [
    'driver' => 'monolog',
    'handler' => Monolog\Handler\BrowserConsoleHandler::class,
    'formatter' => Monolog\Formatter\HtmlFormatter::class,
    'formatter_with' => [
        'dateFormat' => 'Y-m-d',
    ],
],
```

Якщо ви використовуєте обробник Monolog, здатний надати власний форматувальник, ви можете задати опції `formatter` значення `default`:

```php
'newrelic' => [
    'driver' => 'monolog',
    'handler' => Monolog\Handler\NewRelicHandler::class,
    'formatter' => 'default',
],
```

<a name="monolog-processors"></a>
#### Процесори Monolog

Monolog також може обробляти повідомлення перед їх логуванням. Ви можете створити власні процесори або скористатися [наявними процесорами Monolog](https://github.com/Seldaek/monolog/tree/main/src/Monolog/Processor).

Якщо ви хочете налаштувати процесори для драйвера `monolog`, додайте до конфігурації каналу значення `processors`:

```php
'memory' => [
    'driver' => 'monolog',
    'handler' => Monolog\Handler\StreamHandler::class,
    'handler_with' => [
        'stream' => 'php://stderr',
    ],
    'processors' => [
        // Simple syntax...
        Monolog\Processor\MemoryUsageProcessor::class,

        // With options...
        [
            'processor' => Monolog\Processor\PsrLogMessageProcessor::class,
            'with' => ['removeUsedContextFields' => true],
        ],
    ],
],
```

<a name="creating-custom-channels-via-factories"></a>
### Створення власних каналів через фабрики

Якщо ви хочете визначити цілком власний канал, у якому маєте повний контроль над створенням і конфігурацією Monolog, вкажіть тип драйвера `custom` у файлі `config/logging.php`. Ваша конфігурація має містити опцію `via` з іменем класу фабрики, який буде викликано для створення екземпляра Monolog:

```php
'channels' => [
    'example-custom-channel' => [
        'driver' => 'custom',
        'via' => App\Logging\CreateCustomLogger::class,
    ],
],
```

Налаштувавши канал із драйвером `custom`, ви готові визначити клас, який створюватиме ваш екземпляр Monolog. Цей клас потребує лише одного методу `__invoke`, який має повертати екземпляр логера Monolog. Метод отримає масив конфігурації каналу як єдиний аргумент:

```php
<?php

namespace App\Logging;

use Monolog\Logger;

class CreateCustomLogger
{
    /**
     * Create a custom Monolog instance.
     */
    public function __invoke(array $config): Logger
    {
        return new Logger(/* ... */);
    }
}
```

<a name="tailing-log-messages-using-pail"></a>
## Стеження за логами за допомогою Pail

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

Laravel Pail - це пакет, який дозволяє легко зазирнути у файли логів вашого застосунку Laravel просто з командного рядка. На відміну від стандартної команди `tail`, Pail працює з будь-яким драйвером логування, зокрема [Laravel Nightwatch](https://nightwatch.laravel.com), Sentry чи Flare. Крім того, Pail надає набір корисних фільтрів, щоб швидко знайти те, що ви шукаєте.

<img src="https://laravel.com/img/docs/pail-example.png">

<a name="pail-installation"></a>
### Встановлення

> [!WARNING]
> Laravel Pail потребує PHP-розширення [PCNTL](https://www.php.net/manual/en/book.pcntl.php).

Щоб почати, встановіть Pail у свій проєкт менеджером пакетів Composer:

```shell
composer require --dev laravel/pail
```

<a name="pail-usage"></a>
### Використання

Щоб почати стежити за логами, виконайте команду `pail`:

```shell
php artisan pail
```

Щоб збільшити докладність виводу й уникнути скорочення (…), скористайтеся опцією `-v`:

```shell
php artisan pail -v
```

Для максимальної докладності та показу стеків викликів винятків скористайтеся опцією `-vv`:

```shell
php artisan pail -vv
```

Щоб припинити стеження за логами, будь-коли натисніть `Ctrl+C`.

<a name="pail-filtering-logs"></a>
### Фільтрування логів

<a name="pail-filtering-logs-filter-option"></a>
#### `--filter`

Опція `--filter` дозволяє фільтрувати логи за типом, файлом, повідомленням і вмістом стека викликів:

```shell
php artisan pail --filter="QueryException"
```

<a name="pail-filtering-logs-message-option"></a>
#### `--message`

Щоб фільтрувати логи лише за повідомленням, скористайтеся опцією `--message`:

```shell
php artisan pail --message="User created"
```

<a name="pail-filtering-logs-level-option"></a>
#### `--level`

Опція `--level` дозволяє фільтрувати логи за їхнім [рівнем](#log-levels):

```shell
php artisan pail --level=error
```

<a name="pail-filtering-logs-user-option"></a>
#### `--user`

Щоб показувати лише логи, записані під час автентифікації певного користувача, передайте ідентифікатор цього користувача опції `--user`:

```shell
php artisan pail --user=1
```

---

Джерело: https://laravelukraine.com/docs/13.x/artisan

# Консоль Artisan

- [Вступ](#introduction)
    - [Tinker (REPL)](#tinker)
- [Написання команд](#writing-commands)
    - [Генерація команд](#generating-commands)
    - [Структура команди](#command-structure)
    - [Команди на замиканнях](#closure-commands)
    - [Ізольовані команди](#isolatable-commands)
- [Опис очікуваного вводу](#defining-input-expectations)
    - [Аргументи](#arguments)
    - [Опції](#options)
    - [Масиви вводу](#input-arrays)
    - [Описи вводу](#input-descriptions)
    - [Запит відсутнього вводу](#prompting-for-missing-input)
- [Ввід і вивід команди](#command-io)
    - [Отримання вводу](#retrieving-input)
    - [Запит вводу в користувача](#prompting-for-input)
    - [Виведення](#writing-output)
- [Реєстрація команд](#registering-commands)
- [Програмний запуск команд](#programmatically-executing-commands)
    - [Виклик команд з інших команд](#calling-commands-from-other-commands)
- [Обробка сигналів](#signal-handling)
- [Команда dev](#the-dev-command)
    - [Налаштування процесів dev](#customizing-dev-processes)
    - [Фільтрування процесів dev](#filtering-dev-processes)
- [Налаштування стабів](#stub-customization)
- [Події](#events)

<a name="introduction"></a>
## Вступ

Artisan - це інтерфейс командного рядка, що входить до складу Laravel. Artisan лежить у корені вашого застосунку як скрипт `artisan` і надає чимало корисних команд, які можна використовувати під час розробки. Щоб побачити список усіх доступних команд Artisan, скористайтеся командою `list`:

```shell
php artisan list
```

Кожна команда має також екран довідки, який показує та описує доступні аргументи й опції команди. Щоб побачити довідку, поставте перед назвою команди слово `help`:

```shell
php artisan help migrate
```

<a name="laravel-sail"></a>
#### Laravel Sail

Якщо ви використовуєте [Laravel Sail](/docs/{{version}}/sail) як локальне середовище розробки, не забувайте викликати команди Artisan через командний рядок `sail`. Sail виконає ваші команди Artisan усередині Docker-контейнерів застосунку:

```shell
./vendor/bin/sail artisan list
```

<a name="tinker"></a>
### Tinker (REPL)

[Laravel Tinker](https://github.com/laravel/tinker) - це потужний REPL для фреймворку Laravel, побудований на пакеті [PsySH](https://github.com/bobthecow/psysh).

<a name="installation"></a>
#### Встановлення

Усі застосунки Laravel містять Tinker за замовчуванням. Проте, якщо ви раніше видалили його з застосунку, встановити Tinker можна через Composer:

```shell
composer require laravel/tinker
```

> [!NOTE]
> Шукаєте гаряче перезавантаження, багаторядкове редагування коду й автодоповнення під час роботи з застосунком Laravel? Погляньте на [Tinkerwell](https://tinkerwell.app)!

<a name="usage"></a>
#### Використання

Tinker дозволяє взаємодіяти з усім вашим застосунком Laravel у командному рядку, включно з моделями Eloquent, завданнями, подіями тощо. Щоб увійти в середовище Tinker, виконайте команду Artisan `tinker`:

```shell
php artisan tinker
```

Опублікувати файл конфігурації Tinker можна командою `vendor:publish`:

```shell
php artisan vendor:publish --provider="Laravel\Tinker\TinkerServiceProvider"
```

> [!WARNING]
> Функція-хелпер `dispatch` і метод `dispatch` класу `Dispatchable` покладаються на збирач сміття, щоб покласти завдання в чергу. Тому в Tinker для диспетчеризації завдань слід використовувати `Bus::dispatch` або `Queue::push`.

<a name="command-allow-list"></a>
#### Список дозволених команд

Tinker використовує список дозволених команд, щоб визначити, які команди Artisan можна запускати в його оболонці. За замовчуванням доступні команди `clear-compiled`, `down`, `env`, `inspire`, `migrate`, `migrate:install`, `up` та `optimize`. Якщо ви хочете дозволити більше команд, додайте їх до масиву `commands` у файлі конфігурації `tinker.php`:

```php
'commands' => [
    // App\Console\Commands\ExampleCommand::class,
],
```

<a name="classes-that-should-not-be-aliased"></a>
#### Класи, для яких не слід створювати аліаси

Зазвичай Tinker автоматично створює аліаси для класів, з якими ви взаємодієте. Проте для деяких класів ви можете цього не хотіти. Перелічіть такі класи в масиві `dont_alias` файлу конфігурації `tinker.php`:

```php
'dont_alias' => [
    App\Models\User::class,
],
```

<a name="writing-commands"></a>
## Написання команд

Окрім команд, які постачаються з Artisan, ви можете створювати власні. Команди зазвичай зберігаються в каталозі `app/Console/Commands`; втім, ви вільні обрати інше місце, доки вказуєте Laravel [сканувати інші каталоги в пошуках команд Artisan](#registering-commands).

<a name="generating-commands"></a>
### Генерація команд

Щоб створити нову команду, скористайтеся командою Artisan `make:command`. Вона створить новий клас команди в каталозі `app/Console/Commands`. Не переймайтеся, якщо цього каталогу у вашому застосунку немає - його буде створено під час першого запуску `make:command`:

```shell
php artisan make:command SendEmails
```

<a name="command-structure"></a>
### Структура команди

Після генерації команди опишіть її сигнатуру та опис за допомогою атрибутів `Signature` і `Description`. Атрибут `Signature` також дозволяє описати [очікуваний командою ввід](#defining-input-expectations). Метод `handle` викликається під час виконання команди - саме в ньому розміщується логіка.

Погляньмо на приклад команди. Зверніть увагу, що ми можемо запросити будь-які потрібні залежності через метод `handle`. [Сервіс-контейнер](/docs/{{version}}/container) Laravel автоматично впровадить усі залежності, типи яких вказано в сигнатурі цього методу:

```php
<?php

namespace App\Console\Commands;

use App\Models\User;
use App\Support\DripEmailer;
use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Command;

#[Signature('mail:send {user}')]
#[Description('Send a marketing email to a user')]
class SendEmails extends Command
{
    /**
     * Execute the console command.
     */
    public function handle(DripEmailer $drip): void
    {
        $drip->send(User::find($this->argument('user')));
    }
}
```

> [!NOTE]
> Задля кращого повторного використання коду варто тримати консольні команди легкими й доручати роботу сервісам застосунку. У прикладі вище ми впроваджуємо сервісний клас, який бере на себе «важку працю» з надсилання листів.

<a name="exit-codes"></a>
#### Коди виходу

Якщо метод `handle` нічого не повертає і команда виконалася успішно, вона завершиться з кодом виходу `0`, що означає успіх. Проте `handle` може повернути ціле число, щоб задати код виходу вручну:

```php
$this->error('Something went wrong.');

return 1;
```

Якщо ви хочете «провалити» команду з будь-якого її методу, скористайтеся методом `fail`. Він негайно припинить виконання команди й поверне код виходу `1`:

```php
$this->fail('Something went wrong.');
```

<a name="closure-commands"></a>
### Команди на замиканнях

Команди на замиканнях - це альтернатива описові консольних команд у вигляді класів. Так само, як замикання маршрутів є альтернативою контролерам, замикання команд є альтернативою класам команд.

Хоч файл `routes/console.php` і не описує HTTP-маршрути, він задає консольні точки входу (маршрути) до застосунку. У цьому файлі ви можете описати всі свої консольні команди на замиканнях за допомогою методу `Artisan::command`. Метод `command` приймає два аргументи: [сигнатуру команди](#defining-input-expectations) і замикання, яке отримує аргументи та опції команди:

```php
Artisan::command('mail:send {user}', function (string $user) {
    $this->info("Sending email to: {$user}!");
});
```

Замикання прив'язане до екземпляра команди, тож вам доступні всі допоміжні методи, які ви зазвичай маєте в повноцінному класі команди.

<a name="type-hinting-dependencies"></a>
#### Типізація залежностей

Окрім аргументів і опцій команди, замикання можуть приймати типізовані додаткові залежності, які потрібно розв'язати із [сервіс-контейнера](/docs/{{version}}/container):

```php
use App\Models\User;
use App\Support\DripEmailer;
use Illuminate\Support\Facades\Artisan;

Artisan::command('mail:send {user}', function (DripEmailer $drip, string $user) {
    $drip->send(User::find($user));
});
```

<a name="closure-command-descriptions"></a>
#### Описи команд на замиканнях

Описуючи команду на замиканні, ви можете додати їй опис методом `purpose`. Цей опис показуватиметься під час виконання команд `php artisan list` або `php artisan help`:

```php
Artisan::command('mail:send {user}', function (string $user) {
    // ...
})->purpose('Send a marketing email to a user');
```

<a name="isolatable-commands"></a>
### Ізольовані команди

> [!WARNING]
> Щоб скористатися цією можливістю, ваш застосунок має використовувати драйвер кешу `memcached`, `redis`, `dynamodb`, `database`, `file` або `array` як драйвер за замовчуванням. Крім того, усі сервери мають спілкуватися з одним центральним сервером кешу.

Інколи потрібно гарантувати, що одночасно виконується лише один екземпляр команди. Для цього реалізуйте у класі команди інтерфейс `Illuminate\Contracts\Console\Isolatable`:

```php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Contracts\Console\Isolatable;

class SendEmails extends Command implements Isolatable
{
    // ...
}
```

Коли ви позначаєте команду як `Isolatable`, Laravel автоматично робить доступною опцію `--isolated` без потреби описувати її в опціях команди. Коли команду викликано з цією опцією, Laravel переконається, що інші екземпляри цієї команди ще не виконуються. Для цього він намагається отримати атомарне блокування через драйвер кешу за замовчуванням. Якщо інші екземпляри команди вже працюють, команда не виконається, проте все одно завершиться з успішним кодом виходу:

```shell
php artisan mail:send 1 --isolated
```

Якщо ви хочете задати код виходу, який команда має повертати, коли не змогла виконатися, передайте потрібний код через опцію `isolated`:

```shell
php artisan mail:send 1 --isolated=12
```

<a name="lock-id"></a>
#### Ідентифікатор блокування

За замовчуванням Laravel використовує назву команди, щоб згенерувати рядковий ключ для атомарного блокування в кеші застосунку. Проте ви можете налаштувати цей ключ, визначивши метод `isolatableId` у класі команди Artisan, - це дозволяє вплести в ключ аргументи чи опції команди:

```php
/**
 * Get the isolatable ID for the command.
 */
public function isolatableId(): string
{
    return $this->argument('user');
}
```

<a name="lock-expiration-time"></a>
#### Час дії блокування

За замовчуванням блокування ізоляції спливає після завершення команди. Якщо ж команду перервано і вона не змогла завершитися, блокування спливе за годину. Втім, ви можете змінити час дії блокування, визначивши в команді метод `isolationLockExpiresAt`:

```php
use DateTimeInterface;
use DateInterval;

/**
 * Determine when an isolation lock expires for the command.
 */
public function isolationLockExpiresAt(): DateTimeInterface|DateInterval
{
    return now()->plus(minutes: 5);
}
```

<a name="defining-input-expectations"></a>
## Опис очікуваного вводу

Пишучи консольні команди, часто доводиться збирати ввід від користувача через аргументи або опції. Laravel робить опис очікуваного вводу дуже зручним завдяки властивості `signature`. Вона дозволяє описати назву, аргументи та опції команди в одному виразному синтаксисі, схожому на маршрути.

<a name="arguments"></a>
### Аргументи

Усі аргументи та опції, які надає користувач, беруться у фігурні дужки. У прикладі нижче команда описує один обов'язковий аргумент `user`:

```php
/**
 * The name and signature of the console command.
 *
 * @var string
 */
protected $signature = 'mail:send {user}';
```

Аргументи можна також робити необов'язковими або задавати їм значення за замовчуванням:

```php
// Optional argument...
'mail:send {user?}'

// Optional argument with default value...
'mail:send {user=foo}'
```

<a name="options"></a>
### Опції

Опції, як і аргументи, є ще однією формою користувацького вводу. У командному рядку опції записуються з двома дефісами (`--`). Є два типи опцій: ті, що приймають значення, і ті, що не приймають. Опції без значення слугують булевим «перемикачем». Погляньмо на приклад такої опції:

```php
/**
 * The name and signature of the console command.
 *
 * @var string
 */
protected $signature = 'mail:send {user} {--queue}';
```

У цьому прикладі перемикач `--queue` можна вказати під час виклику команди Artisan. Якщо його передано, значенням опції буде `true`. Інакше - `false`:

```shell
php artisan mail:send 1 --queue
```

<a name="options-with-values"></a>
#### Опції зі значеннями

Тепер погляньмо на опцію, яка очікує значення. Якщо користувач має вказати значення для опції, додайте до назви опції знак `=`:

```php
/**
 * The name and signature of the console command.
 *
 * @var string
 */
protected $signature = 'mail:send {user} {--queue=}';
```

У цьому прикладі користувач може передати значення опції ось так. Якщо опцію не вказано під час виклику команди, її значенням буде `null`:

```shell
php artisan mail:send 1 --queue=default
```

Задати опціям значення за замовчуванням можна, вказавши його після назви опції. Якщо користувач не передасть значення, буде використано значення за замовчуванням:

```php
'mail:send {user} {--queue=default}'
```

<a name="option-shortcuts"></a>
#### Скорочення опцій

Щоб призначити опції скорочення, вкажіть його перед назвою опції та відділіть символом `|`:

```php
'mail:send {user} {--Q|queue=}'
```

Викликаючи команду в терміналі, скорочення опцій пишуться з одним дефісом, а символ `=` під час передавання значення не використовується:

```shell
php artisan mail:send 1 -Qdefault
```

<a name="input-arrays"></a>
### Масиви вводу

Якщо ви хочете описати аргументи чи опції, які очікують кілька значень, скористайтеся символом `*`. Спершу погляньмо на приклад такого аргументу:

```php
'mail:send {user*}'
```

Під час запуску цієї команди аргументи `user` можна передати в командному рядку один за одним. Наприклад, наступна команда встановить значенням `user` масив зі значеннями `1` і `2`:

```shell
php artisan mail:send 1 2
```

Символ `*` можна поєднати з описом необов'язкового аргументу, щоб дозволити нуль або більше екземплярів аргументу:

```php
'mail:send {user?*}'
```

<a name="option-arrays"></a>
#### Масиви опцій

Коли ви описуєте опцію, що очікує кілька значень, кожне передане команді значення опції має мати префікс із назвою опції:

```php
'mail:send {--id=*}'
```

Таку команду можна викликати, передавши кілька аргументів `--id`:

```shell
php artisan mail:send --id=1 --id=2
```

<a name="input-descriptions"></a>
### Описи вводу

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

```php
/**
 * The name and signature of the console command.
 *
 * @var string
 */
protected $signature = 'mail:send
                        {user : The ID of the user}
                        {--queue : Whether the job should be queued}';
```

<a name="prompting-for-missing-input"></a>
### Запит відсутнього вводу

Якщо ваша команда містить обов'язкові аргументи, користувач отримає повідомлення про помилку, коли їх не передано. Натомість ви можете налаштувати команду так, щоб вона автоматично запитувала користувача про відсутні обов'язкові аргументи, - для цього реалізуйте інтерфейс `PromptsForMissingInput`:

```php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Contracts\Console\PromptsForMissingInput;

class SendEmails extends Command implements PromptsForMissingInput
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'mail:send {user}';

    // ...
}
```

Якщо Laravel потрібно отримати від користувача обов'язковий аргумент, він автоматично запитає його, розумно сформулювавши питання на основі назви або опису аргументу. Якщо ви хочете змінити питання, яким збирається обов'язковий аргумент, реалізуйте метод `promptForMissingArgumentsUsing`, повернувши масив питань із ключами за назвами аргументів:

```php
/**
 * Prompt for missing input arguments using the returned questions.
 *
 * @return array<string, string>
 */
protected function promptForMissingArgumentsUsing(): array
{
    return [
        'user' => 'Which user ID should receive the mail?',
    ];
}
```

Ви також можете додати текст-підказку, скориставшись кортежем із питання та підказки:

```php
return [
    'user' => ['Which user ID should receive the mail?', 'E.g. 123'],
];
```

Якщо вам потрібен повний контроль над запитом, передайте замикання, яке має запитати користувача й повернути його відповідь:

```php
use App\Models\User;
use function Laravel\Prompts\search;

// ...

return [
    'user' => fn () => search(
        label: 'Search for a user:',
        placeholder: 'E.g. Taylor Otwell',
        options: fn ($value) => strlen($value) > 0
            ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
            : []
    ),
];
```

> [!NOTE]
Вичерпна документація [Laravel Prompts](/docs/{{version}}/prompts) містить додаткову інформацію про доступні запити та їх використання.

Якщо ви хочете запропонувати користувачеві обрати чи ввести [опції](#options), додайте запити в метод `handle` команди. Проте, якщо ви хочете запитувати користувача лише тоді, коли його вже автоматично запитали про відсутні аргументи, реалізуйте метод `afterPromptingForMissingArguments`:

```php
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use function Laravel\Prompts\confirm;

// ...

/**
 * Perform actions after the user was prompted for missing arguments.
 */
protected function afterPromptingForMissingArguments(InputInterface $input, OutputInterface $output): void
{
    $input->setOption('queue', confirm(
        label: 'Would you like to queue the mail?',
        default: $this->option('queue')
    ));
}
```

<a name="command-io"></a>
## Ввід і вивід команди

<a name="retrieving-input"></a>
### Отримання вводу

Під час виконання команди вам, найімовірніше, знадобиться доступ до значень аргументів і опцій, які вона приймає. Для цього скористайтеся методами `argument` і `option`. Якщо аргументу чи опції не існує, буде повернуто `null`:

```php
/**
 * Execute the console command.
 */
public function handle(): void
{
    $userId = $this->argument('user');
}
```

Якщо потрібно отримати всі аргументи як `array`, викличте метод `arguments`:

```php
$arguments = $this->arguments();
```

Опції отримуються так само просто, як аргументи, - методом `option`. Щоб отримати всі опції як масив, викличте метод `options`:

```php
// Retrieve a specific option...
$queueName = $this->option('queue');

// Retrieve all options as an array...
$options = $this->options();
```

Метод `input` дозволяє отримати аргументи й опції команди як екземпляр `Illuminate\Console\CommandInput`, який надає ті самі типізовані аксесори, що доступні для HTTP-запитів та інших контейнерів даних:

```php
use App\Enums\ReportType;

/**
 * Execute the console command.
 */
public function handle(): void
{
    $input = $this->input()->date('from');

    // ...
}
```

Метод `input` також можна використати, щоб отримати одне значення з аргументів або опцій:

```php
$queue = $this->input('queue', 'default');
```

<a name="prompting-for-input"></a>
### Запит вводу в користувача

> [!NOTE]
> [Laravel Prompts](/docs/{{version}}/prompts) - це PHP-пакет для додавання гарних і зручних форм до ваших консольних застосунків, з можливостями на кшталт браузерних: текстом-підказкою та валідацією.

Окрім виведення інформації, ви можете просити користувача ввести дані під час виконання команди. Метод `ask` покаже користувачеві задане питання, прийме його ввід і поверне його вашій команді:

```php
/**
 * Execute the console command.
 */
public function handle(): void
{
    $name = $this->ask('What is your name?');

    // ...
}
```

Метод `ask` також приймає необов'язковий другий аргумент - значення за замовчуванням, яке буде повернуто, якщо користувач нічого не ввів:

```php
$name = $this->ask('What is your name?', 'Taylor');
```

Метод `secret` схожий на `ask`, але ввід користувача не відображатиметься в консолі під час набору. Цей метод стане в пригоді, коли ви запитуєте чутливу інформацію на кшталт паролів:

```php
$password = $this->secret('What is the password?');
```

<a name="asking-for-confirmation"></a>
#### Запит підтвердження

Якщо потрібно спитати користувача про просте підтвердження «так чи ні», скористайтеся методом `confirm`. За замовчуванням цей метод повертає `false`. Проте, якщо користувач введе `y` або `yes`, метод поверне `true`.

```php
if ($this->confirm('Do you wish to continue?')) {
    // ...
}
```

За потреби ви можете вказати, що запит підтвердження має за замовчуванням повертати `true`, передавши `true` другим аргументом методу `confirm`:

```php
if ($this->confirm('Do you wish to continue?', true)) {
    // ...
}
```

<a name="auto-completion"></a>
#### Автодоповнення

Метод `anticipate` можна використати, щоб надати автодоповнення для можливих варіантів. Користувач усе одно може ввести будь-яку відповідь, незалежно від підказок автодоповнення:

```php
$name = $this->anticipate('What is your name?', ['Taylor', 'Dayle']);
```

Як варіант, ви можете передати другим аргументом методу `anticipate` замикання. Воно викликатиметься щоразу, коли користувач вводить символ. Замикання має приймати рядковий параметр із поточним вводом користувача й повертати масив варіантів для автодоповнення:

```php
use App\Models\Address;

$name = $this->anticipate('What is your address?', function (string $input) {
    return Address::whereLike('name', "{$input}%")
        ->limit(5)
        ->pluck('name')
        ->all();
});
```

<a name="multiple-choice-questions"></a>
#### Питання з кількома варіантами

Якщо потрібно запропонувати користувачеві наперед визначений набір варіантів, скористайтеся методом `choice`. Індекс масиву зі значенням за замовчуванням, яке буде повернуто, якщо користувач нічого не обрав, передається третім аргументом методу:

```php
$name = $this->choice(
    'What is your name?',
    ['Taylor', 'Dayle'],
    $defaultIndex
);
```

Крім того, метод `choice` приймає необов'язкові четвертий і п'ятий аргументи: максимальну кількість спроб обрати коректну відповідь і чи дозволено кілька варіантів вибору:

```php
$name = $this->choice(
    'What is your name?',
    ['Taylor', 'Dayle'],
    $defaultIndex,
    $maxAttempts = null,
    $allowMultipleSelections = false
);
```

<a name="writing-output"></a>
### Виведення

Щоб вивести щось у консоль, скористайтеся методами `line`, `newLine`, `info`, `comment`, `question`, `warn`, `alert` та `error`. Кожен із них використовує відповідні ANSI-кольори. Наприклад, покажемо користувачеві загальну інформацію. Зазвичай метод `info` виводить у консоль текст зеленого кольору:

```php
/**
 * Execute the console command.
 */
public function handle(): void
{
    // ...

    $this->info('The command was successful!');
}
```

Щоб показати повідомлення про помилку, скористайтеся методом `error`. Текст помилки зазвичай виводиться червоним:

```php
$this->error('Something went wrong!');
```

Метод `line` виводить простий неколірний текст:

```php
$this->line('Display this on the screen');
```

Метод `newLine` виводить порожній рядок:

```php
// Write a single blank line...
$this->newLine();

// Write three blank lines...
$this->newLine(3);
```

<a name="tables"></a>
#### Таблиці

Метод `table` спрощує правильне форматування кількох рядків / стовпців даних. Усе, що вам потрібно, - передати назви стовпців і дані таблиці, а Laravel автоматично обчислить відповідну ширину й висоту таблиці:

```php
use App\Models\User;

$this->table(
    ['Name', 'Email'],
    User::all(['name', 'email'])->toArray()
);
```

<a name="progress-bars"></a>
#### Індикатори прогресу

Для тривалих завдань корисно показувати індикатор прогресу, який повідомляє користувачам, наскільки завдання виконане. З методом `withProgressBar` Laravel покаже індикатор прогресу й просуватиме його на кожній ітерації по заданому ітерабельному значенню:

```php
use App\Models\User;

$users = $this->withProgressBar(User::all(), function (User $user) {
    $this->performTask($user);
});
```

Інколи потрібен більший контроль над тим, як просувається індикатор прогресу. Спершу задайте загальну кількість кроків, які пройде процес. Потім просувайте індикатор після обробки кожного елемента:

```php
$users = App\Models\User::all();

$bar = $this->output->createProgressBar(count($users));

$bar->start();

foreach ($users as $user) {
    $this->performTask($user);

    $bar->advance();
}

$bar->finish();
```

> [!NOTE]
> Про більш просунуті можливості читайте в [документації компонента Symfony Progress Bar](https://symfony.com/doc/current/components/console/helpers/progressbar.html).

<a name="registering-commands"></a>
## Реєстрація команд

За замовчуванням Laravel автоматично реєструє всі команди в каталозі `app/Console/Commands`. Проте ви можете вказати Laravel сканувати інші каталоги в пошуках команд Artisan за допомогою методу `withCommands` у файлі `bootstrap/app.php` вашого застосунку:

```php
->withCommands([
    __DIR__.'/../app/Domain/Orders/Commands',
])
```

За потреби ви можете зареєструвати команди й вручну, передавши методу `withCommands` назву класу команди:

```php
use App\Domain\Orders\Commands\SendEmails;

->withCommands([
    SendEmails::class,
])
```

Коли Artisan завантажується, усі команди застосунку будуть розв'язані через [сервіс-контейнер](/docs/{{version}}/container) і зареєстровані в Artisan.

<a name="programmatically-executing-commands"></a>
## Програмний запуск команд

Інколи потрібно виконати команду Artisan поза межами CLI. Наприклад, вам може знадобитися виконати команду Artisan з маршруту чи контролера. Для цього скористайтеся методом `call` фасаду `Artisan`. Метод `call` приймає першим аргументом назву сигнатури команди або назву класу, а другим - масив параметрів команди. Він повертає код виходу:

```php
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Route;

Route::post('/user/{user}/mail', function (string $user) {
    $exitCode = Artisan::call('mail:send', [
        'user' => $user, '--queue' => 'default'
    ]);

    // ...
});
```

Як варіант, ви можете передати методу `call` цілу команду Artisan рядком:

```php
Artisan::call('mail:send 1 --queue=default');
```

<a name="passing-array-values"></a>
#### Передавання масивів значень

Якщо ваша команда описує опцію, що приймає масив, ви можете передати цій опції масив значень:

```php
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Route;

Route::post('/mail', function () {
    $exitCode = Artisan::call('mail:send', [
        '--id' => [5, 13]
    ]);
});
```

<a name="passing-boolean-values"></a>
#### Передавання булевих значень

Якщо потрібно задати значення опції, яка не приймає рядкових значень, - наприклад, прапорець `--force` команди `migrate:refresh`, - передайте як значення опції `true` або `false`:

```php
$exitCode = Artisan::call('migrate:refresh', [
    '--force' => true,
]);
```

<a name="queueing-artisan-commands"></a>
#### Постановка команд Artisan у чергу

За допомогою методу `queue` фасаду `Artisan` ви можете навіть ставити команди Artisan у чергу, щоб їх у фоні обробляли [воркери черги](/docs/{{version}}/queues). Перш ніж користуватися цим методом, переконайтеся, що ви налаштували чергу й запустили слухача черги:

```php
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Route;

Route::post('/user/{user}/mail', function (string $user) {
    Artisan::queue('mail:send', [
        'user' => $user, '--queue' => 'default'
    ]);

    // ...
});
```

Методами `onConnection` та `onQueue` можна вказати підключення або чергу, до якої слід відправити команду Artisan:

```php
Artisan::queue('mail:send', [
    'user' => 1, '--queue' => 'default'
])->onConnection('redis')->onQueue('commands');
```

<a name="calling-commands-from-other-commands"></a>
### Виклик команд з інших команд

Інколи потрібно викликати інші команди з наявної команди Artisan. Це робиться методом `call`. Він приймає назву команди та масив аргументів / опцій:

```php
/**
 * Execute the console command.
 */
public function handle(): void
{
    $this->call('mail:send', [
        'user' => 1, '--queue' => 'default'
    ]);

    // ...
}
```

Якщо ви хочете викликати іншу консольну команду й приховати весь її вивід, скористайтеся методом `callSilently`. Він має ту саму сигнатуру, що й метод `call`:

```php
$this->callSilently('mail:send', [
    'user' => 1, '--queue' => 'default'
]);
```

<a name="signal-handling"></a>
## Обробка сигналів

Як ви, можливо, знаєте, операційні системи дозволяють надсилати сигнали запущеним процесам. Наприклад, сигналом `SIGTERM` операційна система просить програму коректно завершити роботу. Якщо ви хочете слухати сигнали у своїх консольних командах Artisan і виконувати код, коли вони надходять, скористайтеся методом `trap`:

```php
/**
 * Execute the console command.
 */
public function handle(): void
{
    $this->trap(SIGTERM, fn () => $this->shouldKeepRunning = false);

    while ($this->shouldKeepRunning) {
        // ...
    }
}
```

Щоб слухати кілька сигналів одразу, передайте методу `trap` масив сигналів:

```php
$this->trap([SIGTERM, SIGQUIT], function (int $signal) {
    $this->shouldKeepRunning = false;

    dump($signal); // SIGTERM / SIGQUIT
});
```

<a name="the-dev-command"></a>
## Команда dev

Команда Artisan `dev` запускає в одному вікні термінала всі процеси, потрібні для локальної розробки. За замовчуванням вона паралельно запускає сервер розробки PHP, воркер черги, стеження за логами через [Pail](/docs/{{version}}/logging#tailing-log-messages-using-pail) і компіляцію ресурсів Vite:

```shell
php artisan dev
```

Під капотом команда `dev` керує процесами через npm-пакет `@laravel/multiplex`, відкриваючи для кожного процесу власну вкладку з виводом, який можна прокручувати і в якому можна шукати. Кожен процес має свою мітку й колір, тож їх легко розрізнити. Якщо якийсь процес зазнає краху, його буде автоматично перезапущено, а коли ви завершите роботу, весь вивід буде записано назад у термінал, тож нічого не втрачається.

> [!NOTE]
> Команда `dev` вимагає Node 22.13 або новішої версії. На Windows вона повертається до npm-пакета `concurrently`, і інтерфейс із вкладками недоступний.

Процеси за замовчуванням:

| Назва | Команда |
| --- | --- |
| `server` | `php artisan serve` |
| `queue` | `php artisan queue:listen --tries=1 --timeout=0` |
| `logs` | `php artisan pail --timeout=0` |
| `vite` | `npm run dev` |

> [!NOTE]
> Процес `vite` автоматично визначає ваш менеджер пакетів Node (npm, pnpm, Yarn чи Bun) і використовує відповідну команду запуску.

<a name="customizing-dev-processes"></a>
### Налаштування процесів dev

Ви можете налаштувати процеси, які запускає команда `dev`, за допомогою класу `DevCommands` - зазвичай у методі `boot` вашого `AppServiceProvider`. Метод `register` приймає рядок команди й необов'язкову назву:

```php
use Illuminate\Foundation\DevCommands;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    DevCommands::register('some-command --flag', 'my-process');
}
```

Реєструючи команду Artisan, ви можете скористатися методом `artisan`, який автоматично додає до команди префікс `php artisan`:

```php
DevCommands::artisan('horizon', 'horizon');
```

Так само метод `node` додає префікс із командою запуску вашого менеджера пакетів (наприклад, `npm run`), а метод `nodeExec` - префікс із командою виконання менеджера пакетів (наприклад, `npx`):

```php
DevCommands::node('storybook', 'storybook');

DevCommands::nodeExec('tailwindcss -i resources/css/app.css -o public/css/app.css --watch', 'tailwind');
```

Якщо ви зареєструєте процес із такою самою назвою, як у процесу за замовчуванням, ваш процес замінить стандартний. Наприклад, ви можете налаштувати процес сервера на інший порт:

```php
DevCommands::artisan('serve --host=localhost --port=9000', 'server');
```

Ви також можете змінити колір мітки процесу в терміналі. Доступні методи кольорів: `blue`, `purple`, `pink`, `orange`, `green` та `yellow`. Крім того, методу `color` можна передати власний hex-колір:

```php
DevCommands::register('my-command', 'my-process')->green();

DevCommands::register('my-command', 'my-process')->color('#ff6347');
```

Щоб побачити всі зареєстровані процеси dev, не запускаючи їх, скористайтеся командою `dev:list`:

```shell
php artisan dev:list
```

<a name="restarting-failed-processes"></a>
#### Перезапуск процесів, що завершилися з помилкою

Якщо процес аварійно завершується, Laravel перезапустить його після короткої затримки, до п'яти разів, перш ніж позначити його як невдалий. Процес, який припиняється протягом секунди після запуску, не перезапускається, оскільки ймовірно він ніколи не запускався успішно з самого початку. Ручний перезапуск процесу через `r` скидає лічильник.

Ви можете вимкнути цю поведінку для одного запуску, використовуючи опцію `--no-restart`:

```shell
php artisan dev --no-restart
```

Або ви можете вимкнути її для всього застосунку, використовуючи метод `disableAutoRestart`:

```php
DevCommands::disableAutoRestart();
```

<a name="filtering-dev-processes"></a>
### Фільтрування процесів dev

Ви можете вказати команді `dev` запускати лише певні процеси за допомогою методу `only`. Так само можна виключити певні процеси методом `except`:

```php
// Only run the server and vite processes...
DevCommands::only('server', 'vite');

// Run all processes except the queue worker...
DevCommands::except('queue');
```

Ви можете виключити команди, зареєстровані пакетами, або стандартні команди Laravel за допомогою методів `withoutVendorCommands` і `withoutDefaultCommands`:

```php
DevCommands::withoutVendorCommands();

DevCommands::withoutDefaultCommands();
```

<a name="stub-customization"></a>
## Налаштування стабів

Команди `make` консолі Artisan створюють різноманітні класи: контролери, завдання, міграції, тести. Ці класи генеруються з файлів-«стабів», які заповнюються значеннями на основі вашого вводу. Проте вам може захотітися внести невеликі зміни у файли, які генерує Artisan. Для цього скористайтеся командою `stub:publish`, щоб опублікувати найпоширеніші стаби у ваш застосунок і налаштувати їх:

```shell
php artisan stub:publish
```

Опубліковані стаби опиняться в каталозі `stubs` у корені застосунку. Усі зміни, які ви внесете в ці стаби, відображатимуться під час генерації відповідних класів командами `make` Artisan.

<a name="events"></a>
## Події

Під час виконання команд Artisan диспетчеризує три події: `Illuminate\Console\Events\ArtisanStarting`, `Illuminate\Console\Events\CommandStarting` і `Illuminate\Console\Events\CommandFinished`. Подія `ArtisanStarting` диспетчеризується одразу, щойно Artisan починає роботу. Далі, безпосередньо перед виконанням команди, диспетчеризується подія `CommandStarting`. Нарешті, після завершення виконання команди диспетчеризується подія `CommandFinished`.

---

Джерело: https://laravelukraine.com/docs/13.x/broadcasting

# Бродкастинг

- [Вступ](#introduction)
- [Швидкий старт](#quickstart)
- [Встановлення на боці сервера](#server-side-installation)
    - [Reverb](#reverb)
    - [Pusher Channels](#pusher-channels)
    - [Ably](#ably)
    - [Mercure](#mercure)
- [Встановлення на боці клієнта](#client-side-installation)
    - [Reverb](#client-reverb)
    - [Pusher Channels](#client-pusher-channels)
    - [Ably](#client-ably)
    - [Mercure](#client-mercure)
- [Огляд концепції](#concept-overview)
    - [На прикладі застосунку](#using-example-application)
- [Опис подій для бродкастингу](#defining-broadcast-events)
    - [Ім'я бродкасту](#broadcast-name)
    - [Дані бродкасту](#broadcast-data)
    - [Черга бродкасту](#broadcast-queue)
    - [Умови бродкасту](#broadcast-conditions)
    - [Бродкастинг і транзакції бази даних](#broadcasting-and-database-transactions)
- [Авторизація каналів](#authorizing-channels)
    - [Опис колбеків авторизації](#defining-authorization-callbacks)
    - [Класи каналів](#defining-channel-classes)
- [Бродкастинг подій](#broadcasting-events)
    - [Лише іншим](#only-to-others)
    - [Налаштування підключення](#customizing-the-connection)
    - [Анонімні події](#anonymous-events)
    - [Убезпечення бродкастів](#rescuing-broadcasts)
- [Отримання бродкастів](#receiving-broadcasts)
    - [Прослуховування подій](#listening-for-events)
    - [Вихід з каналу](#leaving-a-channel)
    - [Простори імен](#namespaces)
    - [Використання React, Vue чи Svelte](#using-react-or-vue)
- [Канали присутності](#presence-channels)
    - [Авторизація каналів присутності](#authorizing-presence-channels)
    - [Приєднання до каналів присутності](#joining-presence-channels)
    - [Бродкастинг у канали присутності](#broadcasting-to-presence-channels)
- [Зашифровані приватні канали](#encrypted-private-channels)
- [Бродкастинг моделей](#model-broadcasting)
    - [Домовленості бродкастингу моделей](#model-broadcasting-conventions)
    - [Прослуховування бродкастів моделей](#listening-for-model-broadcasts)
- [Клієнтські події](#client-events)
- [Сповіщення](#notifications)

<a name="introduction"></a>
## Вступ

У багатьох сучасних вебзастосунках WebSocket використовують, щоб будувати інтерфейси, які оновлюються в реальному часі. Коли на сервері оновлюються якісь дані, через WebSocket-з'єднання зазвичай надсилається повідомлення, яке обробляє клієнт. WebSocket - ефективніша альтернатива постійному опитуванню сервера на предмет змін, які мають відобразитися в інтерфейсі.

Наприклад, уявіть, що ваш застосунок уміє експортувати дані користувача у CSV-файл і надсилати його поштою. Проте створення цього CSV-файлу займає кілька хвилин, тож ви створюєте й надсилаєте CSV у [завданні в черзі](/docs/{{version}}/queues). Коли CSV створено й надіслано користувачеві, ми можемо скористатися бродкастингом подій, щоб диспетчеризувати подію `App\Events\UserDataExported`, яку отримає JavaScript нашого застосунку. Отримавши подію, ми можемо показати користувачеві повідомлення, що його CSV надіслано поштою, - і йому не доведеться оновлювати сторінку.

Щоб допомогти вам будувати такі можливості, Laravel спрощує «бродкастинг» серверних [подій](/docs/{{version}}/events) Laravel через WebSocket-з'єднання. Бродкастинг подій Laravel дозволяє мати спільні імена подій і дані між серверним застосунком Laravel і клієнтським застосунком на JavaScript.

Основні ідеї бродкастингу прості: на фронтенді клієнти підключаються до іменованих каналів, а ваш застосунок Laravel на бекенді надсилає події в ці канали. Ці події можуть містити будь-які додаткові дані, які ви хочете зробити доступними на фронтенді.

<a name="supported-drivers"></a>
#### Підтримувані драйвери

За замовчуванням Laravel містить чотири серверні драйвери бродкастингу на вибір: [Laravel Reverb](https://reverb.laravel.com), [Pusher Channels](https://pusher.com/channels), [Ably](https://ably.com) та [Mercure](https://mercure.rocks).

> [!NOTE]
> Перш ніж занурюватися в бродкастинг подій, обов'язково прочитайте документацію Laravel про [події та слухачів](/docs/{{version}}/events).

<a name="quickstart"></a>
## Швидкий старт

За замовчуванням у нових застосунках Laravel бродкастинг вимкнено. Увімкнути його можна командою Artisan `install:broadcasting`:

```shell
php artisan install:broadcasting
```

Команда `install:broadcasting` запитає, який сервіс бродкастингу подій ви хочете використовувати. Крім того, вона створить файл конфігурації `config/broadcasting.php` і файл `routes/channels.php`, де ви можете реєструвати маршрути та колбеки авторизації бродкастингу вашого застосунку.

Laravel «з коробки» підтримує кілька драйверів бродкастингу: [Laravel Reverb](/docs/{{version}}/reverb), [Pusher Channels](https://pusher.com/channels), [Ably](https://ably.com), [Mercure](https://mercure.rocks), а також драйвер `log` для локальної розробки й налагодження. Крім того, є драйвер `null`, який дозволяє вимкнути бродкастинг під час тестування. Приклад конфігурації для кожного з цих драйверів є у файлі `config/broadcasting.php`.

Уся конфігурація бродкастингу подій вашого застосунку зберігається у файлі `config/broadcasting.php`. Не переймайтеся, якщо цього файлу у вашому застосунку немає, - його буде створено, коли ви виконаєте команду Artisan `install:broadcasting`.

<a name="quickstart-next-steps"></a>
#### Наступні кроки

Щойно ви увімкнули бродкастинг подій, можна вивчати [опис подій для бродкастингу](#defining-broadcast-events) і [прослуховування подій](#listening-for-events). Якщо ви користуєтеся [стартовими наборами](/docs/{{version}}/starter-kits) Laravel для React, Vue чи Svelte, слухати події можна через [хук useEcho](#using-react-or-vue) з Echo.

> [!NOTE]
> Перш ніж надсилати будь-які події, вам слід налаштувати й запустити [воркер черги](/docs/{{version}}/queues). Увесь бродкастинг подій відбувається через завдання в черзі, щоб час відповіді вашого застосунку суттєво не страждав від надсилання подій.

<a name="server-side-installation"></a>
## Встановлення на боці сервера

Щоб почати користуватися бродкастингом подій у Laravel, нам потрібно дещо налаштувати в застосунку Laravel, а також встановити кілька пакетів.

Бродкастинг подій виконує серверний драйвер бродкастингу, який надсилає ваші події Laravel так, щоб Laravel Echo (бібліотека на JavaScript) могла отримати їх у браузері. Не хвилюйтеся - ми пройдемо кожен крок встановлення по черзі.

<a name="reverb"></a>
### Reverb

Щоб швидко увімкнути підтримку бродкастингу в Laravel із Reverb як бродкастером подій, виконайте команду Artisan `install:broadcasting` з опцією `--reverb`. Ця команда встановить потрібні Reverb пакети Composer і NPM та оновить файл `.env` вашого застосунку відповідними змінними:

```shell
php artisan install:broadcasting --reverb
```

<a name="reverb-manual-installation"></a>
#### Встановлення вручну

Під час виконання команди `install:broadcasting` вам запропонують встановити [Laravel Reverb](/docs/{{version}}/reverb). Звісно, ви можете встановити Reverb і вручну через менеджер пакетів Composer:

```shell
composer require laravel/reverb
```

Щойно пакет встановлено, ви можете виконати команду встановлення Reverb, щоб опублікувати конфігурацію, додати потрібні Reverb змінні середовища й увімкнути бродкастинг подій у застосунку:

```shell
php artisan reverb:install
```

Детальні інструкції зі встановлення та використання Reverb ви знайдете в [документації Reverb](/docs/{{version}}/reverb).

<a name="pusher-channels"></a>
### Pusher Channels

Щоб швидко увімкнути підтримку бродкастингу в Laravel із Pusher як бродкастером подій, виконайте команду Artisan `install:broadcasting` з опцією `--pusher`. Ця команда запитає ваші облікові дані Pusher, встановить PHP- та JavaScript-SDK Pusher і оновить файл `.env` вашого застосунку відповідними змінними:

```shell
php artisan install:broadcasting --pusher
```

<a name="pusher-manual-installation"></a>
#### Встановлення вручну

Щоб встановити підтримку Pusher вручну, встановіть PHP SDK Pusher Channels через менеджер пакетів Composer:

```shell
composer require pusher/pusher-php-server
```

Далі налаштуйте облікові дані Pusher Channels у файлі конфігурації `config/broadcasting.php`. Приклад конфігурації Pusher Channels уже є в цьому файлі, тож ви можете швидко вказати свої ключ, секрет та ID застосунку. Зазвичай облікові дані Pusher Channels налаштовують у файлі `.env` вашого застосунку:

```ini
PUSHER_APP_ID="your-pusher-app-id"
PUSHER_APP_KEY="your-pusher-key"
PUSHER_APP_SECRET="your-pusher-secret"
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME="https"
PUSHER_APP_CLUSTER="mt1"
```

Конфігурація `pusher` у файлі `config/broadcasting.php` також дозволяє вказати додаткові `options`, які підтримує Channels, - наприклад, кластер.

Далі задайте змінній середовища `BROADCAST_CONNECTION` значення `pusher` у файлі `.env` вашого застосунку:

```ini
BROADCAST_CONNECTION=pusher
```

Нарешті, ви готові встановити й налаштувати [Laravel Echo](#client-side-installation), яка отримуватиме події бродкастингу на боці клієнта.

<a name="pusher-manual-installation-encrypted-private-channels"></a>
#### Зашифровані приватні канали

Якщо ви плануєте використовувати [наскрізно зашифровані приватні канали](#encrypted-private-channels), вам слід додати опцію `encryption_master_key_base64`, що містить закодований у base64 32-байтний ключ, до масиву `options` підключення `pusher`:

```php
'options' => [
    // ...
    'encryption_master_key_base64' => env('PUSHER_ENCRYPTION_MASTER_KEY'),
],
```

Ви можете згенерувати відповідний ключ за допомогою команди `openssl`:

```shell
openssl rand -base64 32
```

<a name="ably"></a>
### Ably

> [!NOTE]
> Документація нижче описує використання Ably в режимі «сумісності з Pusher». Проте команда Ably рекомендує й підтримує власні бродкастер та клієнт Echo, які вміють користуватися унікальними можливостями Ably. Докладніше про драйвери від Ably читайте в [документації бродкастера Ably для Laravel](https://github.com/ably/laravel-broadcaster).

Щоб швидко увімкнути підтримку бродкастингу в Laravel з [Ably](https://ably.com) як бродкастером подій, виконайте команду Artisan `install:broadcasting` з опцією `--ably`. Ця команда запитає ваші облікові дані Ably, встановить PHP- та JavaScript-SDK Ably і оновить файл `.env` вашого застосунку відповідними змінними:

```shell
php artisan install:broadcasting --ably
```

**Перш ніж продовжувати, увімкніть підтримку протоколу Pusher у налаштуваннях вашого застосунку Ably. Зробити це можна в розділі «Protocol Adapter Settings» панелі налаштувань застосунку Ably.**

<a name="ably-manual-installation"></a>
#### Встановлення вручну

Щоб встановити підтримку Ably вручну, встановіть PHP SDK Ably через менеджер пакетів Composer:

```shell
composer require ably/ably-php
```

Далі налаштуйте облікові дані Ably у файлі конфігурації `config/broadcasting.php`. Приклад конфігурації Ably уже є в цьому файлі, тож ви можете швидко вказати свій ключ. Зазвичай це значення задають через [змінну середовища](/docs/{{version}}/configuration#environment-configuration) `ABLY_KEY`:

```ini
ABLY_KEY=your-ably-key
```

Далі задайте змінній середовища `BROADCAST_CONNECTION` значення `ably` у файлі `.env` вашого застосунку:

```ini
BROADCAST_CONNECTION=ably
```

Нарешті, ви готові встановити й налаштувати [Laravel Echo](#client-side-installation), яка отримуватиме події бродкастингу на боці клієнта.

<a name="mercure"></a>
### Mercure

Щоб швидко увімкнути підтримку бродкастингу в Laravel із Mercure як бродкастером подій, виконайте команду Artisan `install:broadcasting` з опцією `--mercure`. Ця команда запитає ваші облікові дані Mercure, встановить PHP і JavaScript SDK для Mercure та оновить файл `.env` вашого застосунку відповідними змінними:

```shell
php artisan install:broadcasting --mercure
```

<a name="mercure-manual-installation"></a>
#### Встановлення вручну

Щоб встановити підтримку Mercure вручну, ви повинні встановити компонент Symfony Mercure і бібліотеку JWT:

```shell
composer require symfony/mercure:^0.8 web-token/jwt-library:^4.1
```

Далі налаштуйте підключення Mercure у файлі `.env` вашого застосунку:

```ini
BROADCAST_CONNECTION=mercure

MERCURE_URL=https://mercure.example.com/.well-known/mercure
MERCURE_PUBLIC_URL=https://mercure.example.com/.well-known/mercure
MERCURE_JWT_SECRET=<your-mercure-jwt-secret>
```

Значення `MERCURE_URL` - це URL, який Laravel використовує для публікації оновлень, тоді як `MERCURE_PUBLIC_URL` - це URL, який браузерні клієнти використовують для підписки. Ваш Mercure-хаб має бути налаштований з тим самим JWT-секретом.

Щоб використовувати [наскрізно зашифровані приватні канали](#encrypted-private-channels), налаштуйте 32-байтову змінну оточення `MERCURE_ENCRYPTION_KEY`:

```ini
MERCURE_ENCRYPTION_KEY=<your-32-byte-encryption-key>
```

Нарешті, ви готові встановити й налаштувати [Laravel Echo](#client-side-installation), який буде отримувати події бродкастингу на стороні клієнта.

<a name="client-side-installation"></a>
## Встановлення на боці клієнта

<a name="client-reverb"></a>
### Reverb

[Laravel Echo](https://github.com/laravel/echo) - це бібліотека на JavaScript, яка робить підписку на канали й прослуховування подій, надісланих вашим серверним драйвером бродкастингу, безболісними.

Коли ви встановлюєте Laravel Reverb командою Artisan `install:broadcasting`, каркас і конфігурацію Reverb та Echo буде додано до вашого застосунку автоматично. Проте, якщо ви хочете налаштувати Laravel Echo вручну, скористайтеся інструкціями нижче.

<a name="reverb-client-manual-installation"></a>
#### Встановлення вручну

Щоб налаштувати Laravel Echo для фронтенду вашого застосунку вручну, спершу встановіть пакет `pusher-js`, оскільки Reverb використовує протокол Pusher для WebSocket-підписок, каналів і повідомлень:

```shell
npm install --save-dev laravel-echo pusher-js
```

Щойно Echo встановлено, ви можете створити свіжий екземпляр Echo у JavaScript вашого застосунку. Чудове місце для цього - кінець файлу `resources/js/app.js`, який входить до складу фреймворку Laravel:

```js tab=JavaScript
import Echo from 'laravel-echo';

import Pusher from 'pusher-js';
window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'reverb',
    key: import.meta.env.VITE_REVERB_APP_KEY,
    wsHost: import.meta.env.VITE_REVERB_HOST,
    wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
    wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
    forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
});
```

```js tab=React
import { configureEcho } from "@laravel/echo-react";

configureEcho({
    broadcaster: "reverb",
    // key: import.meta.env.VITE_REVERB_APP_KEY,
    // wsHost: import.meta.env.VITE_REVERB_HOST,
    // wsPort: import.meta.env.VITE_REVERB_PORT,
    // wssPort: import.meta.env.VITE_REVERB_PORT,
    // forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    // enabledTransports: ['ws', 'wss'],
});
```

```js tab=Vue
import { configureEcho } from "@laravel/echo-vue";

configureEcho({
    broadcaster: "reverb",
    // key: import.meta.env.VITE_REVERB_APP_KEY,
    // wsHost: import.meta.env.VITE_REVERB_HOST,
    // wsPort: import.meta.env.VITE_REVERB_PORT,
    // wssPort: import.meta.env.VITE_REVERB_PORT,
    // forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    // enabledTransports: ['ws', 'wss'],
});
```

```js tab=Svelte
import { configureEcho } from "@laravel/echo-svelte";

configureEcho({
    broadcaster: "reverb",
    // key: import.meta.env.VITE_REVERB_APP_KEY,
    // wsHost: import.meta.env.VITE_REVERB_HOST,
    // wsPort: import.meta.env.VITE_REVERB_PORT,
    // wssPort: import.meta.env.VITE_REVERB_PORT,
    // forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    // enabledTransports: ['ws', 'wss'],
});
```

Далі скомпілюйте ресурси вашого застосунку:

```shell
npm run build
```

> [!WARNING]
> Бродкастер `reverb` для Laravel Echo потребує laravel-echo v1.16.0+.

<a name="client-pusher-channels"></a>
### Pusher Channels

[Laravel Echo](https://github.com/laravel/echo) - це бібліотека на JavaScript, яка робить підписку на канали й прослуховування подій, надісланих вашим серверним драйвером бродкастингу, безболісними.

Коли ви встановлюєте підтримку бродкастингу командою Artisan `install:broadcasting --pusher`, каркас і конфігурацію Pusher та Echo буде додано до вашого застосунку автоматично. Проте, якщо ви хочете налаштувати Laravel Echo вручну, скористайтеся інструкціями нижче.

<a name="pusher-client-manual-installation"></a>
#### Встановлення вручну

Щоб налаштувати Laravel Echo для фронтенду вашого застосунку вручну, спершу встановіть пакети `laravel-echo` та `pusher-js`, які використовують протокол Pusher для WebSocket-підписок, каналів і повідомлень:

```shell
npm install --save-dev laravel-echo pusher-js
```

Щойно Echo встановлено, ви можете створити свіжий екземпляр Echo у файлі `resources/js/app.js` вашого застосунку:

```js tab=JavaScript
import Echo from 'laravel-echo';

import Pusher from 'pusher-js';
window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: import.meta.env.VITE_PUSHER_APP_KEY,
    cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
    forceTLS: true
});
```

```js tab=React
import { configureEcho } from "@laravel/echo-react";

configureEcho({
    broadcaster: "pusher",
    // key: import.meta.env.VITE_PUSHER_APP_KEY,
    // cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
    // forceTLS: true,
    // wsHost: import.meta.env.VITE_PUSHER_HOST,
    // wsPort: import.meta.env.VITE_PUSHER_PORT,
    // wssPort: import.meta.env.VITE_PUSHER_PORT,
    // enabledTransports: ["ws", "wss"],
});
```

```js tab=Vue
import { configureEcho } from "@laravel/echo-vue";

configureEcho({
    broadcaster: "pusher",
    // key: import.meta.env.VITE_PUSHER_APP_KEY,
    // cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
    // forceTLS: true,
    // wsHost: import.meta.env.VITE_PUSHER_HOST,
    // wsPort: import.meta.env.VITE_PUSHER_PORT,
    // wssPort: import.meta.env.VITE_PUSHER_PORT,
    // enabledTransports: ["ws", "wss"],
});
```

```js tab=Svelte
import { configureEcho } from "@laravel/echo-svelte";

configureEcho({
    broadcaster: "pusher",
    // key: import.meta.env.VITE_PUSHER_APP_KEY,
    // cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
    // forceTLS: true,
    // wsHost: import.meta.env.VITE_PUSHER_HOST,
    // wsPort: import.meta.env.VITE_PUSHER_PORT,
    // wssPort: import.meta.env.VITE_PUSHER_PORT,
    // enabledTransports: ["ws", "wss"],
});
```

Далі задайте відповідні значення змінних середовища Pusher у файлі `.env` вашого застосунку. Якщо цих змінних у файлі `.env` ще немає, додайте їх:

```ini
PUSHER_APP_ID="your-pusher-app-id"
PUSHER_APP_KEY="your-pusher-key"
PUSHER_APP_SECRET="your-pusher-secret"
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME="https"
PUSHER_APP_CLUSTER="mt1"

VITE_APP_NAME="${APP_NAME}"
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
```

Щойно ви скоригували конфігурацію Echo під потреби свого застосунку, можете скомпілювати його ресурси:

```shell
npm run build
```

> [!NOTE]
> Щоб дізнатися більше про компіляцію JavaScript-ресурсів вашого застосунку, зверніться до документації про [Vite](/docs/{{version}}/vite).

<a name="using-an-existing-client-instance"></a>
#### Використання наявного екземпляра клієнта

Якщо у вас уже є попередньо налаштований екземпляр клієнта Pusher Channels, який ви хочете віддати Echo, передайте його через опцію конфігурації `client`:

```js
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

const options = {
    broadcaster: 'pusher',
    key: import.meta.env.VITE_PUSHER_APP_KEY
}

window.Echo = new Echo({
    ...options,
    client: new Pusher(options.key, options)
});
```

<a name="client-ably"></a>
### Ably

> [!NOTE]
> Документація нижче описує використання Ably в режимі «сумісності з Pusher». Проте команда Ably рекомендує й підтримує власні бродкастер та клієнт Echo, які вміють користуватися унікальними можливостями Ably. Докладніше про драйвери від Ably читайте в [документації бродкастера Ably для Laravel](https://github.com/ably/laravel-broadcaster).

[Laravel Echo](https://github.com/laravel/echo) - це бібліотека на JavaScript, яка робить підписку на канали й прослуховування подій, надісланих вашим серверним драйвером бродкастингу, безболісними.

Коли ви встановлюєте підтримку бродкастингу командою Artisan `install:broadcasting --ably`, каркас і конфігурацію Ably та Echo буде додано до вашого застосунку автоматично. Проте, якщо ви хочете налаштувати Laravel Echo вручну, скористайтеся інструкціями нижче.

<a name="ably-client-manual-installation"></a>
#### Встановлення вручну

Щоб налаштувати Laravel Echo для фронтенду вашого застосунку вручну, спершу встановіть пакети `laravel-echo` та `pusher-js`, які використовують протокол Pusher для WebSocket-підписок, каналів і повідомлень:

```shell
npm install --save-dev laravel-echo pusher-js
```

**Перш ніж продовжувати, увімкніть підтримку протоколу Pusher у налаштуваннях вашого застосунку Ably. Зробити це можна в розділі «Protocol Adapter Settings» панелі налаштувань застосунку Ably.**

Щойно Echo встановлено, ви можете створити свіжий екземпляр Echo у файлі `resources/js/app.js` вашого застосунку:

```js tab=JavaScript
import Echo from 'laravel-echo';

import Pusher from 'pusher-js';
window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: import.meta.env.VITE_ABLY_PUBLIC_KEY,
    wsHost: 'realtime-pusher.ably.io',
    wsPort: 443,
    disableStats: true,
    encrypted: true,
});
```

```js tab=React
import { configureEcho } from "@laravel/echo-react";

configureEcho({
    broadcaster: "ably",
    // key: import.meta.env.VITE_ABLY_PUBLIC_KEY,
    // wsHost: "realtime-pusher.ably.io",
    // wsPort: 443,
    // disableStats: true,
    // encrypted: true,
});
```

```js tab=Vue
import { configureEcho } from "@laravel/echo-vue";

configureEcho({
    broadcaster: "ably",
    // key: import.meta.env.VITE_ABLY_PUBLIC_KEY,
    // wsHost: "realtime-pusher.ably.io",
    // wsPort: 443,
    // disableStats: true,
    // encrypted: true,
});
```

```js tab=Svelte
import { configureEcho } from "@laravel/echo-svelte";

configureEcho({
    broadcaster: "ably",
    // key: import.meta.env.VITE_ABLY_PUBLIC_KEY,
    // wsHost: "realtime-pusher.ably.io",
    // wsPort: 443,
    // disableStats: true,
    // encrypted: true,
});
```

Ви могли помітити, що наша конфігурація Echo для Ably посилається на змінну середовища `VITE_ABLY_PUBLIC_KEY`. Значенням цієї змінної має бути ваш публічний ключ Ably. Публічний ключ - це частина вашого ключа Ably до символу `:`.

Щойно ви скоригували конфігурацію Echo під свої потреби, можете скомпілювати ресурси застосунку:

```shell
npm run dev
```

> [!NOTE]
> Щоб дізнатися більше про компіляцію JavaScript-ресурсів вашого застосунку, зверніться до документації про [Vite](/docs/{{version}}/vite).

<a name="client-mercure"></a>
### Mercure

Щоб використовувати Mercure з Laravel Echo, встановіть пакет `laravel-echo`:

```shell
npm install --save-dev laravel-echo
```

Далі створіть екземпляр Echo з бродкастером `mercure`. Опція `host` за замовчуванням вказує на `/.well-known/mercure` на поточному origin:

```js tab=JavaScript
import Echo from 'laravel-echo';

window.Echo = new Echo({
    broadcaster: 'mercure',
    host: import.meta.env.VITE_MERCURE_HUB_URL,
});
```

```js tab=React
import { configureEcho } from "@laravel/echo-react";

configureEcho({
    broadcaster: "mercure",
});
```

```js tab=Vue
import { configureEcho } from "@laravel/echo-vue";

configureEcho({
    broadcaster: "mercure",
});
```

```js tab=Svelte
import { configureEcho } from "@laravel/echo-svelte";

configureEcho({
    broadcaster: "mercure",
});
```

Визначте URL хабу у вашому файлі `.env`:

```ini
VITE_MERCURE_HUB_URL="${MERCURE_PUBLIC_URL}"
```

<a name="concept-overview"></a>
## Огляд концепції

Бродкастинг подій у Laravel дозволяє надсилати серверні події Laravel до клієнтського застосунку на JavaScript, використовуючи драйверний підхід. Наразі Laravel постачається з драйверами [Laravel Reverb](https://reverb.laravel.com), [Pusher Channels](https://pusher.com/channels), [Ably](https://ably.com) та [Mercure](https://mercure.rocks). Події легко спожити на боці клієнта за допомогою JavaScript-пакета [Laravel Echo](#client-side-installation).

Події надсилаються через «канали», які можуть бути публічними або приватними. Будь-який відвідувач вашого застосунку може підписатися на публічний канал без автентифікації чи авторизації; натомість, щоб підписатися на приватний канал, користувач має бути автентифікований і авторизований слухати цей канал.

<a name="using-example-application"></a>
### На прикладі застосунку

Перш ніж заглиблюватися в кожен компонент бродкастингу подій, розгляньмо все з висоти пташиного польоту на прикладі інтернет-магазину.

Припустімо, у нашому застосунку є сторінка, на якій користувачі бачать статус доставки своїх замовлень. Припустімо також, що коли застосунок обробляє оновлення статусу доставки, спрацьовує подія `OrderShipmentStatusUpdated`:

```php
use App\Events\OrderShipmentStatusUpdated;

OrderShipmentStatusUpdated::dispatch($order);
```

<a name="the-shouldbroadcast-interface"></a>
#### Інтерфейс `ShouldBroadcast`

Коли користувач переглядає одне зі своїх замовлень, ми не хочемо, щоб йому доводилося оновлювати сторінку заради оновлень статусу. Натомість ми хочемо надсилати оновлення в застосунок щойно вони з'являються. Отже, нам потрібно позначити подію `OrderShipmentStatusUpdated` інтерфейсом `ShouldBroadcast`. Це скаже Laravel надсилати подію, коли вона спрацьовує:

```php
<?php

namespace App\Events;

use App\Models\Order;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;

class OrderShipmentStatusUpdated implements ShouldBroadcast
{
    /**
     * The order instance.
     *
     * @var \App\Models\Order
     */
    public $order;
}
```

Інтерфейс `ShouldBroadcast` вимагає, щоб наша подія описала метод `broadcastOn`. Цей метод відповідає за повернення каналів, у які має надсилатися подія. Порожня заготовка цього методу вже є у згенерованих класах подій, тож нам залишається лише заповнити її. Ми хочемо, щоб оновлення статусу бачив лише автор замовлення, тому надсилатимемо подію в приватний канал, прив'язаний до замовлення:

```php
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\PrivateChannel;

/**
 * Get the channel the event should broadcast on.
 */
public function broadcastOn(): Channel
{
    return new PrivateChannel('orders.'.$this->order->id);
}
```

Якщо ви хочете надсилати подію в кілька каналів, поверніть замість цього `array`:

```php
use Illuminate\Broadcasting\PrivateChannel;

/**
 * Get the channels the event should broadcast on.
 *
 * @return array<int, \Illuminate\Broadcasting\Channel>
 */
public function broadcastOn(): array
{
    return [
        new PrivateChannel('orders.'.$this->order->id),
        // ...
    ];
}
```

<a name="example-application-authorizing-channels"></a>
#### Авторизація каналів

Пам'ятайте: щоб слухати приватні канали, користувачі мають бути авторизовані. Правила авторизації каналів ми можемо описати у файлі `routes/channels.php` нашого застосунку. У цьому прикладі нам потрібно перевірити, що будь-який користувач, який намагається слухати приватний канал `orders.1`, справді є автором замовлення:

```php
use App\Models\Order;
use App\Models\User;

Broadcast::channel('orders.{orderId}', function (User $user, int $orderId) {
    return $user->id === Order::findOrNew($orderId)->user_id;
});
```

Метод `channel` приймає два аргументи: ім'я каналу й колбек, який повертає `true` або `false` залежно від того, чи авторизований користувач слухати цей канал.

Усі колбеки авторизації першим аргументом отримують поточного автентифікованого користувача, а наступними - будь-які додаткові підстановочні параметри. У цьому прикладі ми використовуємо плейсхолдер `{orderId}`, щоб позначити, що частина імені каналу з «ID» є підстановкою.

<a name="listening-for-event-broadcasts"></a>
#### Прослуховування бродкастів подій

Далі лишається тільки послухати подію в нашому застосунку на JavaScript. Зробити це можна за допомогою [Laravel Echo](#client-side-installation). Вбудовані в Laravel Echo хуки для React, Vue та Svelte роблять старт простим, і за замовчуванням усі публічні властивості події потраплять до надісланої події:

```js tab=React
import { useEcho } from "@laravel/echo-react";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);
```

```vue tab=Vue
<script setup lang="ts">
import { useEcho } from "@laravel/echo-vue";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);
</script>
```

```svelte tab=Svelte
<script>
import { useEcho } from "@laravel/echo-svelte";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);
</script>
```

<a name="defining-broadcast-events"></a>
## Опис подій для бродкастингу

Щоб повідомити Laravel, що певну подію слід надсилати, реалізуйте в класі події інтерфейс `Illuminate\Contracts\Broadcasting\ShouldBroadcast`. Цей інтерфейс уже імпортовано в усі класи подій, згенеровані фреймворком, тож ви легко можете додати його до будь-якої своєї події.

Інтерфейс `ShouldBroadcast` вимагає реалізувати єдиний метод - `broadcastOn`. Метод `broadcastOn` має повернути канал або масив каналів, у які слід надсилати подію. Канали мають бути екземплярами `Channel`, `PrivateChannel` чи `PresenceChannel`. Екземпляри `Channel` представляють публічні канали, на які може підписатися будь-який користувач, а `PrivateChannels` і `PresenceChannels` - приватні канали, які потребують [авторизації каналу](#authorizing-channels):

```php
<?php

namespace App\Events;

use App\Models\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;

class ServerCreated implements ShouldBroadcast
{
    use SerializesModels;

    /**
     * Create a new event instance.
     */
    public function __construct(
        public User $user,
    ) {}

    /**
     * Get the channels the event should broadcast on.
     *
     * @return array<int, \Illuminate\Broadcasting\Channel>
     */
    public function broadcastOn(): array
    {
        return [
            new PrivateChannel('user.'.$this->user->id),
        ];
    }
}
```

Реалізувавши інтерфейс `ShouldBroadcast`, вам залишається лише [запустити подію](/docs/{{version}}/events) як зазвичай. Щойно подію запущено, [завдання в черзі](/docs/{{version}}/queues) автоматично надішле її через вказаний драйвер бродкастингу.

<a name="broadcast-name"></a>
### Ім'я бродкасту

За замовчуванням Laravel надсилає подію під іменем її класу. Проте ви можете змінити ім'я бродкасту, описавши в події метод `broadcastAs`:

```php
/**
 * The event's broadcast name.
 */
public function broadcastAs(): string
{
    return 'server.created';
}
```

Якщо ви змінюєте ім'я бродкасту методом `broadcastAs`, обов'язково реєструйте слухача з провідною крапкою `.`. Це скаже Echo не додавати до події простір імен застосунку:

```js
.listen('.server.created', function (e) {
    // ...
});
```

<a name="broadcast-data"></a>
### Дані бродкасту

Коли подію надіслано, усі її `public` властивості автоматично серіалізуються й передаються як дані події, тож ви маєте доступ до будь-яких її публічних даних із застосунку на JavaScript. Наприклад, якщо ваша подія має єдину публічну властивість `$user` з моделлю Eloquent, дані бродкасту події будуть такими:

```json
{
    "user": {
        "id": 1,
        "name": "Patrick Stewart"
        ...
    }
}
```

Проте, якщо ви хочете тонше контролювати дані бродкасту, додайте до події метод `broadcastWith`. Цей метод має повернути масив даних, які ви хочете надіслати як дані події:

```php
/**
 * Get the data to broadcast.
 *
 * @return array<string, mixed>
 */
public function broadcastWith(): array
{
    return ['id' => $this->user->id];
}
```

<a name="broadcast-queue"></a>
### Черга бродкасту

За замовчуванням кожна подія бродкастингу потрапляє до черги за замовчуванням для підключення черги за замовчуванням, вказаного у файлі конфігурації `queue.php`. Ви можете змінити підключення й ім'я черги, які використовує бродкастер, за допомогою атрибутів `Connection` та `Queue` у класі події:

```php
use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\Queue;

#[Connection('redis')]
#[Queue('default')]
class ServerCreated implements ShouldBroadcast
{
    // ...
}
```

Як варіант, ви можете змінити ім'я черги, описавши в події метод `broadcastQueue`:

```php
/**
 * The name of the queue on which to place the broadcasting job.
 */
public function broadcastQueue(): string
{
    return 'default';
}
```

Якщо ви хочете, щоб усі події бродкастингу використовували ту саму чергу без налаштування кожного класу події, можете натомість [спрямувати контракт `ShouldBroadcast` у чергу](/docs/{{version}}/queues#queue-routing).

Якщо ви хочете надсилати подію через чергу `sync` замість драйвера черги за замовчуванням, реалізуйте інтерфейс `ShouldBroadcastNow` замість `ShouldBroadcast`:

```php
<?php

namespace App\Events;

use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;

class OrderShipmentStatusUpdated implements ShouldBroadcastNow
{
    // ...
}
```

<a name="broadcast-conditions"></a>
### Умови бродкасту

Інколи потрібно надсилати подію лише за певної умови. Описати такі умови можна, додавши до класу події метод `broadcastWhen`:

```php
/**
 * Determine if this event should broadcast.
 */
public function broadcastWhen(): bool
{
    return $this->order->value > 100;
}
```

<a name="broadcasting-and-database-transactions"></a>
#### Бродкастинг і транзакції бази даних

Коли події бродкастингу диспетчеризуються всередині транзакцій бази даних, черга може обробити їх ще до того, як транзакцію буде зафіксовано. Коли таке трапляється, будь-які зміни, які ви внесли до моделей чи записів у базі під час транзакції, ще можуть не бути в базі. Ба більше, будь-які моделі чи записи, створені всередині транзакції, можуть у базі не існувати. Якщо ваша подія залежить від цих моделей, під час обробки завдання, яке надсилає подію, можуть виникнути несподівані помилки.

Якщо опція конфігурації `after_commit` вашого підключення черги має значення `false`, ви все одно можете вказати, що конкретну подію бродкастингу слід диспетчеризувати після фіксації всіх відкритих транзакцій бази даних, - реалізуйте в класі події інтерфейс `ShouldDispatchAfterCommit`:

```php
<?php

namespace App\Events;

use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Queue\SerializesModels;

class ServerCreated implements ShouldBroadcast, ShouldDispatchAfterCommit
{
    use SerializesModels;
}
```

> [!NOTE]
> Щоб дізнатися більше про обхід цих проблем, перегляньте документацію про [завдання в черзі та транзакції бази даних](/docs/{{version}}/queues#jobs-and-database-transactions).

<a name="authorizing-channels"></a>
## Авторизація каналів

Приватні канали вимагають перевірити, що поточний автентифікований користувач справді може слухати канал. Це робиться через HTTP-запит до вашого застосунку Laravel з іменем каналу, і застосунок вирішує, чи може користувач слухати цей канал. Коли ви користуєтеся [Laravel Echo](#client-side-installation), HTTP-запит на авторизацію підписок на приватні канали виконується автоматично.

Коли бродкастинг встановлено, Laravel намагається автоматично зареєструвати маршрут `/broadcasting/auth` для обробки запитів авторизації. Якщо Laravel не вдасться зареєструвати ці маршрути автоматично, ви можете зареєструвати їх вручну у файлі `/bootstrap/app.php` вашого застосунку:

```php
->withRouting(
    web: __DIR__.'/../routes/web.php',
    channels: __DIR__.'/../routes/channels.php',
    health: '/up',
)
```

<a name="defining-authorization-callbacks"></a>
### Опис колбеків авторизації

Далі нам потрібно описати логіку, яка визначатиме, чи може поточний автентифікований користувач слухати певний канал. Це робиться у файлі `routes/channels.php`, який створює команда Artisan `install:broadcasting`. У цьому файлі ви можете реєструвати колбеки авторизації каналів методом `Broadcast::channel`:

```php
use App\Models\User;

Broadcast::channel('orders.{orderId}', function (User $user, int $orderId) {
    return $user->id === Order::findOrNew($orderId)->user_id;
});
```

Метод `channel` приймає два аргументи: ім'я каналу й колбек, який повертає `true` або `false` залежно від того, чи авторизований користувач слухати цей канал.

Усі колбеки авторизації першим аргументом отримують поточного автентифікованого користувача, а наступними - будь-які додаткові підстановочні параметри. У цьому прикладі ми використовуємо плейсхолдер `{orderId}`, щоб позначити, що частина імені каналу з «ID» є підстановкою.

Переглянути список колбеків авторизації бродкастингу вашого застосунку можна командою Artisan `channel:list`:

```shell
php artisan channel:list
```

<a name="authorization-callback-model-binding"></a>
#### Прив'язка моделей у колбеках авторизації

Так само як HTTP-маршрути, маршрути каналів можуть користуватися неявною та явною [прив'язкою моделей до маршрутів](/docs/{{version}}/routing#route-model-binding). Наприклад, замість рядкового чи числового ID замовлення ви можете запросити справжній екземпляр моделі `Order`:

```php
use App\Models\Order;
use App\Models\User;

Broadcast::channel('orders.{order}', function (User $user, Order $order) {
    return $user->id === $order->user_id;
});
```

> [!WARNING]
> На відміну від прив'язки моделей у HTTP-маршрутах, прив'язка моделей у каналах не підтримує автоматичне [скопування неявної прив'язки моделей](/docs/{{version}}/routing#implicit-model-binding-scoping). Утім, це рідко стає проблемою, бо більшість каналів можна скопувати за унікальним первинним ключем однієї моделі.

<a name="authorization-callback-authentication"></a>
#### Автентифікація в колбеках авторизації

Приватні канали та канали присутності автентифікують поточного користувача через гард автентифікації за замовчуванням вашого застосунку. Якщо користувач не автентифікований, авторизацію каналу буде автоматично відхилено, а колбек авторизації ніколи не виконається. Проте за потреби ви можете призначити кілька власних гардів, які мають автентифікувати вхідний запит:

```php
Broadcast::channel('channel', function () {
    // ...
}, ['guards' => ['web', 'admin']]);
```

<a name="defining-channel-classes"></a>
### Класи каналів

Якщо ваш застосунок споживає багато різних каналів, файл `routes/channels.php` може розростися. Тож замість замикань для авторизації каналів ви можете скористатися класами каналів. Щоб згенерувати клас каналу, скористайтеся командою Artisan `make:channel`. Вона покладе новий клас каналу в каталог `App/Broadcasting`.

```shell
php artisan make:channel OrderChannel
```

Далі зареєструйте свій канал у файлі `routes/channels.php`:

```php
use App\Broadcasting\OrderChannel;

Broadcast::channel('orders.{order}', OrderChannel::class);
```

Нарешті, ви можете розмістити логіку авторизації каналу в методі `join` класу каналу. Цей метод `join` міститиме ту саму логіку, яку ви зазвичай розмістили б у замиканні авторизації каналу. Ви так само можете користуватися прив'язкою моделей у каналах:

```php
<?php

namespace App\Broadcasting;

use App\Models\Order;
use App\Models\User;

class OrderChannel
{
    /**
     * Create a new channel instance.
     */
    public function __construct() {}

    /**
     * Authenticate the user's access to the channel.
     */
    public function join(User $user, Order $order): array|bool
    {
        return $user->id === $order->user_id;
    }
}
```

> [!NOTE]
> Як і багато інших класів у Laravel, класи каналів автоматично розв'язуються [сервіс-контейнером](/docs/{{version}}/container). Тож ви можете вказати типи будь-яких залежностей, потрібних вашому каналу, у його конструкторі.

<a name="broadcasting-events"></a>
## Бродкастинг подій

Щойно ви описали подію й позначили її інтерфейсом `ShouldBroadcast`, вам залишається лише запустити подію її методом диспетчеризації. Диспетчер подій помітить, що подію позначено інтерфейсом `ShouldBroadcast`, і поставить її в чергу на бродкастинг:

```php
use App\Events\OrderShipmentStatusUpdated;

OrderShipmentStatusUpdated::dispatch($order);
```

<a name="only-to-others"></a>
### Лише іншим

Будуючи застосунок з бродкастингом подій, ви інколи можете потребувати надіслати подію всім підписникам каналу, окрім поточного користувача. Зробити це можна за допомогою хелпера `broadcast` і методу `toOthers`:

```php
use App\Events\OrderShipmentStatusUpdated;

broadcast(new OrderShipmentStatusUpdated($update))->toOthers();
```

Щоб краще зрозуміти, коли вам знадобиться метод `toOthers`, уявімо застосунок зі списком завдань, у якому користувач створює нове завдання, вводячи його назву. Щоб створити завдання, ваш застосунок може надіслати запит на URL `/task`, який надсилає подію про створення завдання й повертає JSON-представлення нового завдання. Коли ваш застосунок на JavaScript отримує відповідь від точки входу, він може одразу вставити нове завдання до свого списку ось так:

```js
axios.post('/task', task)
    .then((response) => {
        this.tasks.push(response.data);
    });
```

Проте пам'ятайте, що ми також надсилаємо подію про створення завдання. Якщо ваш застосунок на JavaScript теж слухає цю подію, щоб додавати завдання до списку, у списку з'являться дублікати: один із точки входу, другий - з бродкасту. Розв'язати це можна методом `toOthers`, який скаже бродкастеру не надсилати подію поточному користувачеві.

> [!WARNING]
> Щоб викликати метод `toOthers`, ваша подія має використовувати трейт `Illuminate\Broadcasting\InteractsWithSockets`.

<a name="only-to-others-configuration"></a>
#### Конфігурація

Коли ви ініціалізуєте екземпляр Laravel Echo, з'єднанню призначається ID сокета. Якщо ви користуєтеся глобальним екземпляром [Axios](https://github.com/axios/axios) для HTTP-запитів із застосунку на JavaScript, ID сокета автоматично додається до кожного вихідного запиту заголовком `X-Socket-ID`. Тоді, коли ви викликаєте метод `toOthers`, Laravel дістане ID сокета із заголовка й скаже бродкастеру не надсилати подію жодному з'єднанню з цим ID сокета.

Якщо ви не користуєтеся глобальним екземпляром Axios, вам доведеться вручну налаштувати ваш застосунок на JavaScript надсилати заголовок `X-Socket-ID` з усіма вихідними запитами. Отримати ID сокета можна методом `Echo.socketId`:

```js
var socketId = Echo.socketId();
```

<a name="customizing-the-connection"></a>
### Налаштування підключення

Якщо ваш застосунок працює з кількома підключеннями бродкастингу і ви хочете надіслати подію не через бродкастер за замовчуванням, вказати підключення, до якого слід віддати подію, можна методом `via`:

```php
use App\Events\OrderShipmentStatusUpdated;

broadcast(new OrderShipmentStatusUpdated($update))->via('pusher');
```

Як варіант, ви можете вказати підключення бродкастингу події, викликавши метод `broadcastVia` у її конструкторі. Проте перед цим переконайтеся, що клас події використовує трейт `InteractsWithBroadcasting`:

```php
<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithBroadcasting;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;

class OrderShipmentStatusUpdated implements ShouldBroadcast
{
    use InteractsWithBroadcasting;

    /**
     * Create a new event instance.
     */
    public function __construct()
    {
        $this->broadcastVia('pusher');
    }
}
```

<a name="anonymous-events"></a>
### Анонімні події

Інколи вам може захотітися надіслати просту подію на фронтенд застосунку, не створюючи окремого класу події. Для цього фасад `Broadcast` дозволяє надсилати «анонімні події»:

```php
Broadcast::on('orders.'.$order->id)->send();
```

Приклад вище надішле таку подію:

```json
{
    "event": "AnonymousEvent",
    "data": "[]",
    "channel": "orders.1"
}
```

Методами `as` і `with` ви можете змінити ім'я та дані події:

```php
Broadcast::on('orders.'.$order->id)
    ->as('OrderPlaced')
    ->with($order)
    ->send();
```

Приклад вище надішле подію на кшталт такої:

```json
{
    "event": "OrderPlaced",
    "data": "{ id: 1, total: 100 }",
    "channel": "orders.1"
}
```

Якщо ви хочете надіслати анонімну подію в приватний канал або канал присутності, скористайтеся методами `private` та `presence`:

```php
Broadcast::private('orders.'.$order->id)->send();
Broadcast::presence('channels.'.$channel->id)->send();
```

Надсилання анонімної події методом `send` диспетчеризує подію до [черги](/docs/{{version}}/queues) вашого застосунку на обробку. Проте, якщо ви хочете надіслати подію негайно, скористайтеся методом `sendNow`:

```php
Broadcast::on('orders.'.$order->id)->sendNow();
```

Щоб надіслати подію всім підписникам каналу, окрім поточного автентифікованого користувача, викличте метод `toOthers`:

```php
Broadcast::on('orders.'.$order->id)
    ->toOthers()
    ->send();
```

<a name="rescuing-broadcasts"></a>
### Убезпечення бродкастів

Коли сервер черги вашого застосунку недоступний або Laravel натрапляє на помилку під час надсилання події, викидається виняток, через який кінцевий користувач зазвичай бачить помилку застосунку. Оскільки бродкастинг подій часто є доповненням до основної функціональності застосунку, ви можете не дати цим винятками псувати користувацький досвід, - реалізуйте у своїх подіях інтерфейс `ShouldRescue`.

Події, які реалізують інтерфейс `ShouldRescue`, під час спроб бродкастингу автоматично використовують [функцію-хелпер rescue](/docs/{{version}}/helpers#method-rescue) Laravel. Цей хелпер ловить будь-які винятки, повідомляє про них обробнику винятків вашого застосунку для логування й дозволяє застосунку працювати далі, не перериваючи роботу користувача:

```php
<?php

namespace App\Events;

use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Broadcasting\ShouldRescue;

class ServerCreated implements ShouldBroadcast, ShouldRescue
{
    // ...
}
```

<a name="receiving-broadcasts"></a>
## Отримання бродкастів

<a name="listening-for-events"></a>
### Прослуховування подій

Щойно ви [встановили та створили екземпляр Laravel Echo](#client-side-installation), можна починати слухати події, які надсилає ваш застосунок Laravel. Спершу отримайте екземпляр каналу методом `channel`, а потім викличте метод `listen`, щоб слухати вказану подію:

```js
Echo.channel(`orders.${this.order.id}`)
    .listen('OrderShipmentStatusUpdated', (e) => {
        console.log(e.order.name);
    });
```

Якщо ви хочете слухати події в приватному каналі, скористайтеся методом `private`. Ви можете й далі ланцюжком викликати метод `listen`, щоб слухати кілька подій в одному каналі:

```js
Echo.private(`orders.${this.order.id}`)
    .listen(/* ... */)
    .listen(/* ... */)
    .listen(/* ... */);
```

<a name="stop-listening-for-events"></a>
#### Припинення прослуховування подій

Якщо ви хочете перестати слухати певну подію, не [виходячи з каналу](#leaving-a-channel), скористайтеся методом `stopListening`:

```js
Echo.private(`orders.${this.order.id}`)
    .stopListening('OrderShipmentStatusUpdated');
```

<a name="leaving-a-channel"></a>
### Вихід з каналу

Щоб вийти з каналу, викличте метод `leaveChannel` вашого екземпляра Echo:

```js
Echo.leaveChannel(`orders.${this.order.id}`);
```

Якщо ви хочете вийти з каналу разом із пов'язаними з ним приватним каналом і каналом присутності, викличте метод `leave`:

```js
Echo.leave(`orders.${this.order.id}`);
```
<a name="namespaces"></a>
### Простори імен

Ви могли помітити в прикладах вище, що ми не вказували повний простір імен `App\Events` для класів подій. Це тому, що Echo автоматично вважає, що події лежать у просторі імен `App\Events`. Проте ви можете налаштувати кореневий простір імен під час створення екземпляра Echo, передавши опцію конфігурації `namespace`:

```js
window.Echo = new Echo({
    broadcaster: 'pusher',
    // ...
    namespace: 'App.Other.Namespace'
});
```

Як варіант, ви можете додавати до класів подій префікс `.`, підписуючись на них через Echo. Це дозволить завжди вказувати повне ім'я класу:

```js
Echo.channel('orders')
    .listen('.Namespace\\Event\\Class', (e) => {
        // ...
    });
```

<a name="using-react-or-vue"></a>
### Використання React, Vue чи Svelte

Laravel Echo містить хуки для React, Vue та Svelte, які роблять прослуховування подій безболісним. Для початку викличте хук `useEcho`, який слухає приватні події. Хук `useEcho` автоматично виходить з каналів, коли компонент, який його використовує, демонтується:

```js tab=React
import { useEcho } from "@laravel/echo-react";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);
```

```vue tab=Vue
<script setup lang="ts">
import { useEcho } from "@laravel/echo-vue";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);
</script>
```

```svelte tab=Svelte
<script>
import { useEcho } from "@laravel/echo-svelte";

useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);
</script>
```

Ви можете слухати кілька подій, передавши до `useEcho` масив подій:

```js
useEcho(
    `orders.${orderId}`,
    ["OrderShipmentStatusUpdated", "OrderShipped"],
    (e) => {
        console.log(e.order);
    },
);
```

Ви також можете описати форму даних події бродкасту, отримавши кращу типобезпеку й зручність редагування:

```ts
type OrderData = {
    order: {
        id: number;
        user: {
            id: number;
            name: string;
        };
        created_at: string;
    };
};

useEcho<OrderData>(`orders.${orderId}`, "OrderShipmentStatusUpdated", (e) => {
    console.log(e.order.id);
    console.log(e.order.user.id);
});
```

Хук `useEcho` автоматично виходить з каналів, коли компонент, який його використовує, демонтується; проте за потреби ви можете скористатися поверненими функціями, щоб програмно зупиняти / поновлювати прослуховування каналів вручну:

```js tab=React
import { useEcho } from "@laravel/echo-react";

const { leaveChannel, leave, stopListening, listen } = useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);

// Stop listening without leaving channel...
stopListening();

// Start listening again...
listen();

// Leave channel...
leaveChannel();

// Leave a channel and also its associated private and presence channels...
leave();
```

```vue tab=Vue
<script setup lang="ts">
import { useEcho } from "@laravel/echo-vue";

const { leaveChannel, leave, stopListening, listen } = useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);

// Stop listening without leaving channel...
stopListening();

// Start listening again...
listen();

// Leave channel...
leaveChannel();

// Leave a channel and also its associated private and presence channels...
leave();
</script>
```

```svelte tab=Svelte
<script>
import { useEcho } from "@laravel/echo-svelte";

const { leaveChannel, leave, stopListening, listen } = useEcho(
    `orders.${orderId}`,
    "OrderShipmentStatusUpdated",
    (e) => {
        console.log(e.order);
    },
);

// Stop listening without leaving channel...
stopListening();

// Start listening again...
listen();

// Leave channel...
leaveChannel();

// Leave a channel and also its associated private and presence channels...
leave();
</script>
```

<a name="react-vue-connecting-to-public-channels"></a>
#### Підключення до публічних каналів

Щоб підключитися до публічного каналу, скористайтеся хуком `useEchoPublic`:

```js tab=React
import { useEchoPublic } from "@laravel/echo-react";

useEchoPublic("posts", "PostPublished", (e) => {
    console.log(e.post);
});
```

```vue tab=Vue
<script setup lang="ts">
import { useEchoPublic } from "@laravel/echo-vue";

useEchoPublic("posts", "PostPublished", (e) => {
    console.log(e.post);
});
</script>
```

```svelte tab=Svelte
<script>
import { useEchoPublic } from "@laravel/echo-svelte";

useEchoPublic("posts", "PostPublished", (e) => {
    console.log(e.post);
});
</script>
```

<a name="react-vue-connecting-to-presence-channels"></a>
#### Підключення до каналів присутності

Щоб підключитися до каналу присутності, скористайтеся хуком `useEchoPresence`:

```js tab=React
import { useEchoPresence } from "@laravel/echo-react";

useEchoPresence("posts", "PostPublished", (e) => {
    console.log(e.post);
});
```

```vue tab=Vue
<script setup lang="ts">
import { useEchoPresence } from "@laravel/echo-vue";

useEchoPresence("posts", "PostPublished", (e) => {
    console.log(e.post);
});
</script>
```

```svelte tab=Svelte
<script>
import { useEchoPresence } from "@laravel/echo-svelte";

useEchoPresence("posts", "PostPublished", (e) => {
    console.log(e.post);
});
</script>
```

<a name="react-vue-connection-status"></a>
#### Статус з'єднання

Отримати поточний статус WebSocket-з'єднання можна хуком `useConnectionStatus`, який надає реактивний статус, що автоматично оновлюється зі зміною стану з'єднання:

```js tab=React
import { useConnectionStatus } from "@laravel/echo-react";

function ConnectionIndicator() {
    const status = useConnectionStatus();

    return <div>Connection: {status}</div>;
}
```

```vue tab=Vue
<script setup lang="ts">
import { useConnectionStatus } from "@laravel/echo-vue";

const status = useConnectionStatus();
</script>

<template>
    <div>Connection: {{ status }}</div>
</template>
```

```svelte tab=Svelte
<script>
import { useConnectionStatus } from "@laravel/echo-svelte";

const status = useConnectionStatus();
</script>

<div>Connection: {status()}</div>
```

Можливі значення статусу:

<div class="content-list" markdown="1">

- `connected` - успішно підключено до WebSocket-сервера.
- `connecting` - триває початкова спроба підключення.
- `reconnecting` - триває спроба перепідключитися після розриву.
- `disconnected` - не підключено й спроб перепідключитися немає.
- `failed` - підключення не вдалося, повторів не буде.

</div>

<a name="react-vue-socket-id"></a>
#### ID сокета

Отримати поточний ID WebSocket-сокета можна хуком `useSocketId`, який надає реактивне значення, що автоматично оновлюється, коли з'єднання перепідключається з новим ID сокета:

```js tab=React
import { useSocketId } from "@laravel/echo-react";

function SocketIndicator() {
    const socketId = useSocketId();

    return <div>Socket ID: {socketId}</div>;
}
```

```vue tab=Vue
<script setup lang="ts">
import { useSocketId } from "@laravel/echo-vue";

const socketId = useSocketId();
</script>

<template>
    <div>Socket ID: {{ socketId }}</div>
</template>
```

```svelte tab=Svelte
<script>
import { useSocketId } from "@laravel/echo-svelte";

const socketId = useSocketId();
</script>

<div>Socket ID: {socketId()}</div>
```

<a name="presence-channels"></a>
## Канали присутності

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

<a name="authorizing-presence-channels"></a>
### Авторизація каналів присутності

Усі канали присутності є також приватними, тому користувачі мають бути [авторизовані для доступу до них](#authorizing-channels). Проте, описуючи колбеки авторизації для каналів присутності, ви не повертаєте `true`, коли користувач має право приєднатися до каналу. Натомість вам слід повернути масив даних про користувача.

Дані, які повернув колбек авторизації, стануть доступні слухачам подій каналу присутності у вашому застосунку на JavaScript. Якщо користувач не має права приєднатися до каналу присутності, поверніть `false` або `null`:

```php
use App\Models\User;

Broadcast::channel('chat.{roomId}', function (User $user, int $roomId) {
    if ($user->canJoinRoom($roomId)) {
        return ['id' => $user->id, 'name' => $user->name];
    }
});
```

<a name="joining-presence-channels"></a>
### Приєднання до каналів присутності

Щоб приєднатися до каналу присутності, скористайтеся методом `join` з Echo. Метод `join` поверне реалізацію `PresenceChannel`, яка, окрім методу `listen`, дозволяє підписатися на події `here`, `joining` та `leaving`.

```js
Echo.join(`chat.${roomId}`)
    .here((users) => {
        // ...
    })
    .joining((user) => {
        console.log(user.name);
    })
    .leaving((user) => {
        console.log(user.name);
    })
    .error((error) => {
        console.error(error);
    });
```

Колбек `here` виконається одразу після успішного приєднання до каналу й отримає масив з інформацією про всіх інших користувачів, які наразі підписані на канал. Метод `joining` виконається, коли до каналу приєднається новий користувач, а метод `leaving` - коли користувач залишить канал. Метод `error` виконається, коли точка автентифікації поверне HTTP-статус, відмінний від 200, або якщо виникне проблема з розбором повернутого JSON.

<a name="broadcasting-to-presence-channels"></a>
### Бродкастинг у канали присутності

Канали присутності можуть отримувати події так само, як публічні чи приватні канали. На прикладі чат-кімнати ми можемо захотіти надсилати події `NewMessage` до каналу присутності кімнати. Для цього повернемо екземпляр `PresenceChannel` з методу `broadcastOn` події:

```php
/**
 * Get the channels the event should broadcast on.
 *
 * @return array<int, \Illuminate\Broadcasting\Channel>
 */
public function broadcastOn(): array
{
    return [
        new PresenceChannel('chat.'.$this->message->room_id),
    ];
}
```

Як і з іншими подіями, ви можете скористатися хелпером `broadcast` і методом `toOthers`, щоб виключити поточного користувача з отримувачів бродкасту:

```php
broadcast(new NewMessage($message));

broadcast(new NewMessage($message))->toOthers();
```

Як і для інших типів подій, слухати події, надіслані до каналів присутності, можна методом `listen` з Echo:

```js
Echo.join(`chat.${roomId}`)
    .here(/* ... */)
    .joining(/* ... */)
    .leaving(/* ... */)
    .listen('NewMessage', (e) => {
        // ...
    });
```

<a name="encrypted-private-channels"></a>
## Зашифровані приватні канали

Приватні канали гарантують, що слухати канал можуть лише авторизовані користувачі. Проте самі дані події все одно проходять через ваш сервіс бродкастингу у відкритому вигляді. При використанні Pusher Channels або Mercure ви можете застосувати наскрізно зашифровані приватні канали, щоб тільки ваш застосунок та його авторизовані клієнти могли прочитати дані події.

Щоб почати, налаштуйте ключ шифрування для [Pusher Channels](#pusher-manual-installation) або [Mercure](#mercure-manual-installation). Потім поверніть екземпляр `EncryptedPrivateChannel` з методу `broadcastOn` вашої події:

```php
use Illuminate\Broadcasting\EncryptedPrivateChannel;

/**
 * Get the channels the event should broadcast on.
 *
 * @return array<int, \Illuminate\Broadcasting\Channel>
 */
public function broadcastOn(): array
{
    return [
        new EncryptedPrivateChannel('orders.'.$this->order->id),
    ];
}
```

Зашифровані приватні канали авторизуються точно так само, як приватні канали, тому колбек авторизації `orders.{orderId}` у файлі `routes/channels.php` вашого застосунку також авторизуватиме зашифрований канал `orders.1`.

У вашому JavaScript-застосунку ви можете підписатися на канал, використовуючи метод `encryptedPrivate` Echo:

```js
Echo.encryptedPrivate(`orders.${orderId}`)
    .listen('OrderShipmentStatusUpdated', (e) => {
        console.log(e.order);
    });
```

При використанні Pusher Channels типова збірка `pusher-js` не містить коду, потрібного для розшифрування повідомлень. Натомість ви маєте імпортувати збірку `with-encryption` при [налаштуванні Echo](#pusher-client-manual-installation):

```js
import Pusher from 'pusher-js/with-encryption';
window.Pusher = Pusher;
```

<a name="model-broadcasting"></a>
## Бродкастинг моделей

> [!WARNING]
> Перш ніж читати документацію нижче про бродкастинг моделей, радимо ознайомитися із загальними концепціями сервісів бродкастингу моделей у Laravel, а також із тим, як вручну створювати й слухати події бродкастингу.

Часто буває потрібно надсилати події, коли [моделі Eloquent](/docs/{{version}}/eloquent) вашого застосунку створюються, оновлюються чи видаляються. Звісно, це легко зробити, вручну [описавши власні події для змін стану моделі Eloquent](/docs/{{version}}/eloquent#events) і позначивши ці події інтерфейсом `ShouldBroadcast`.

Проте, якщо ви не використовуєте ці події для чогось іще у своєму застосунку, створювати класи подій лише заради бродкастингу може бути обтяжливо. Щоб зарадити цьому, Laravel дозволяє вказати, що модель Eloquent має автоматично надсилати свої зміни стану.

Для початку ваша модель Eloquent має використовувати трейт `Illuminate\Database\Eloquent\BroadcastsEvents`. Крім того, модель має описати метод `broadcastOn`, який поверне масив каналів, у які слід надсилати події моделі:

```php
<?php

namespace App\Models;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Database\Eloquent\BroadcastsEvents;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Post extends Model
{
    use BroadcastsEvents, HasFactory;

    /**
     * Get the user that the post belongs to.
     */
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    /**
     * Get the channels that model events should broadcast on.
     *
     * @return array<int, \Illuminate\Broadcasting\Channel|\Illuminate\Database\Eloquent\Model>
     */
    public function broadcastOn(string $event): array
    {
        return [$this, $this->user];
    }
}
```

Щойно ваша модель містить цей трейт і описує свої канали бродкастингу, вона почне автоматично надсилати події, коли екземпляр моделі створюється, оновлюється, видаляється, потрапляє в кошик чи відновлюється.

Крім того, ви могли помітити, що метод `broadcastOn` отримує рядковий аргумент `$event`. Цей аргумент містить тип події, що сталася з моделлю, і матиме значення `created`, `updated`, `deleted`, `trashed` або `restored`. Перевіряючи значення цієї змінної, ви можете визначити, у які канали (якщо взагалі в якісь) модель має надсилати конкретну подію:

```php
/**
 * Get the channels that model events should broadcast on.
 *
 * @return array<string, array<int, \Illuminate\Broadcasting\Channel|\Illuminate\Database\Eloquent\Model>>
 */
public function broadcastOn(string $event): array
{
    return match ($event) {
        'deleted' => [],
        default => [$this, $this->user],
    };
}
```

<a name="customizing-model-broadcasting-event-creation"></a>
#### Налаштування створення події бродкастингу моделі

Інколи вам може захотітися змінити те, як Laravel створює подію бродкастингу моделі під капотом. Зробити це можна, описавши в моделі Eloquent метод `newBroadcastableEvent`. Цей метод має повернути екземпляр `Illuminate\Database\Eloquent\BroadcastableModelEventOccurred`:

```php
use Illuminate\Database\Eloquent\BroadcastableModelEventOccurred;

/**
 * Create a new broadcastable model event for the model.
 */
protected function newBroadcastableEvent(string $event): BroadcastableModelEventOccurred
{
    return (new BroadcastableModelEventOccurred(
        $this, $event
    ))->dontBroadcastToCurrentUser();
}
```

<a name="model-broadcasting-conventions"></a>
### Домовленості бродкастингу моделей

<a name="model-broadcasting-channel-conventions"></a>
#### Домовленості щодо каналів

Як ви могли помітити, метод `broadcastOn` у прикладі моделі вище повертав не екземпляри `Channel`. Натомість поверталися самі моделі Eloquent. Якщо метод `broadcastOn` вашої моделі повертає екземпляр моделі Eloquent (або містить його в поверненому масиві), Laravel автоматично створить екземпляр приватного каналу для моделі, використавши як ім'я каналу назву класу моделі та її первинний ключ.

Тож модель `App\Models\User` з `id` `1` перетвориться на екземпляр `Illuminate\Broadcasting\PrivateChannel` з іменем `App.Models.User.1`. Звісно, окрім повернення екземплярів моделей Eloquent із методу `broadcastOn`, ви можете повертати повноцінні екземпляри `Channel`, щоб повністю контролювати імена каналів моделі:

```php
use Illuminate\Broadcasting\PrivateChannel;

/**
 * Get the channels that model events should broadcast on.
 *
 * @return array<int, \Illuminate\Broadcasting\Channel>
 */
public function broadcastOn(string $event): array
{
    return [
        new PrivateChannel('user.'.$this->id)
    ];
}
```

Якщо ви плануєте явно повертати екземпляр каналу з методу `broadcastOn` моделі, ви можете передати екземпляр моделі Eloquent у конструктор каналу. У такому разі Laravel скористається домовленостями щодо каналів моделей, описаними вище, щоб перетворити модель Eloquent на рядкове ім'я каналу:

```php
return [new Channel($this->user)];
```

Якщо вам потрібно дізнатися ім'я каналу моделі, викличте метод `broadcastChannel` на будь-якому екземплярі моделі. Наприклад, для моделі `App\Models\User` з `id` `1` цей метод поверне рядок `App.Models.User.1`:

```php
$user->broadcastChannel();
```

<a name="model-broadcasting-event-conventions"></a>
#### Домовленості щодо подій

Оскільки події бродкастингу моделей не пов'язані зі «справжньою» подією в каталозі `App\Events` вашого застосунку, ім'я та дані їм призначаються за домовленостями. Домовленість Laravel така: подія надсилається під назвою класу моделі (без простору імен) і назвою події моделі, яка спричинила бродкаст.

Тож, наприклад, оновлення моделі `App\Models\Post` надішле до вашого клієнтського застосунку подію `PostUpdated` з такими даними:

```json
{
    "model": {
        "id": 1,
        "title": "My first post"
        ...
    },
    ...
    "socket": "someSocketId"
}
```

Видалення моделі `App\Models\User` надішле подію з назвою `UserDeleted`.

За бажанням ви можете описати власні ім'я бродкасту й дані, додавши до моделі методи `broadcastAs` і `broadcastWith`. Ці методи отримують назву події / операції моделі, що відбувається, дозволяючи налаштувати ім'я та дані події для кожної операції з моделлю. Якщо метод `broadcastAs` повертає `null`, надсилаючи подію, Laravel скористається домовленостями щодо імен подій бродкастингу моделей, описаними вище:

```php
/**
 * The model event's broadcast name.
 */
public function broadcastAs(string $event): string|null
{
    return match ($event) {
        'created' => 'post.created',
        default => null,
    };
}

/**
 * Get the data to broadcast for the model.
 *
 * @return array<string, mixed>
 */
public function broadcastWith(string $event): array
{
    return match ($event) {
        'created' => ['title' => $this->title],
        default => ['model' => $this],
    };
}
```

<a name="listening-for-model-broadcasts"></a>
### Прослуховування бродкастів моделей

Щойно ви додали до моделі трейт `BroadcastsEvents` і описали її метод `broadcastOn`, можна починати слухати надіслані події моделі у клієнтському застосунку. Перш ніж почати, вам може бути корисно переглянути повну документацію про [прослуховування подій](#listening-for-events).

Спершу отримайте екземпляр каналу методом `private`, а потім викличте метод `listen`, щоб слухати вказану подію. Зазвичай ім'я каналу, передане методу `private`, має відповідати [домовленостям бродкастингу моделей](#model-broadcasting-conventions) у Laravel.

Отримавши екземпляр каналу, ви можете слухати конкретну подію методом `listen`. Оскільки події бродкастингу моделей не пов'язані зі «справжньою» подією в каталозі `App\Events` вашого застосунку, [ім'я події](#model-broadcasting-event-conventions) має мати префікс `.`, який вказує, що вона не належить до жодного простору імен. Кожна подія бродкастингу моделі має властивість `model`, яка містить усі властивості моделі, придатні для бродкастингу:

```js
Echo.private(`App.Models.User.${this.user.id}`)
    .listen('.UserUpdated', (e) => {
        console.log(e.model);
    });
```

<a name="model-broadcasts-with-react-or-vue"></a>
#### Використання React, Vue чи Svelte

Якщо ви користуєтеся React, Vue чи Svelte, для зручного прослуховування бродкастів моделей скористайтеся хуком `useEchoModel`, який входить до Laravel Echo:

```js tab=React
import { useEchoModel } from "@laravel/echo-react";

useEchoModel("App.Models.User", userId, ["UserUpdated"], (e) => {
    console.log(e.model);
});
```

```vue tab=Vue
<script setup lang="ts">
import { useEchoModel } from "@laravel/echo-vue";

useEchoModel("App.Models.User", userId, ["UserUpdated"], (e) => {
    console.log(e.model);
});
</script>
```

```svelte tab=Svelte
<script>
import { useEchoModel } from "@laravel/echo-svelte";

useEchoModel("App.Models.User", userId, ["UserUpdated"], (e) => {
    console.log(e.model);
});
</script>
```

Ви також можете описати форму даних події моделі, отримавши кращу типобезпеку й зручність редагування:

```ts
type User = {
    id: number;
    name: string;
    email: string;
};

useEchoModel<User, "App.Models.User">("App.Models.User", userId, ["UserUpdated"], (e) => {
    console.log(e.model.id);
    console.log(e.model.name);
});
```

<a name="client-events"></a>
## Клієнтські події

> [!NOTE]
> Користуючись [Pusher Channels](https://pusher.com/channels), ви маєте увімкнути опцію «Client Events» у розділі «App Settings» вашої [панелі застосунку](https://dashboard.pusher.com/), щоб надсилати клієнтські події.

Інколи вам може захотітися надіслати подію іншим підключеним клієнтам, узагалі не звертаючись до застосунку Laravel. Це особливо корисно для речей на кшталт сповіщень «набирає повідомлення», коли ви хочете попередити користувачів застосунку, що інший користувач набирає повідомлення на певному екрані.

Щоб надсилати клієнтські події, скористайтеся методом `whisper` з Echo:

```js tab=JavaScript
Echo.private(`chat.${roomId}`)
    .whisper('typing', {
        name: this.user.name
    });
```

```js tab=React
import { useEcho } from "@laravel/echo-react";

const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => {
    console.log('Chat event received:', e);
});

channel().whisper('typing', { name: user.name });
```

```vue tab=Vue
<script setup lang="ts">
import { useEcho } from "@laravel/echo-vue";

const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => {
    console.log('Chat event received:', e);
});

channel().whisper('typing', { name: user.name });
</script>
```

```svelte tab=Svelte
<script>
import { useEcho } from "@laravel/echo-svelte";

const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => {
    console.log('Chat event received:', e);
});

channel().whisper('typing', { name: user.name });
</script>
```

Щоб слухати клієнтські події, скористайтеся методом `listenForWhisper`:

```js tab=JavaScript
Echo.private(`chat.${roomId}`)
    .listenForWhisper('typing', (e) => {
        console.log(e.name);
    });
```

```js tab=React
import { useEcho } from "@laravel/echo-react";

const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => {
    console.log('Chat event received:', e);
});

channel().listenForWhisper('typing', (e) => {
    console.log(e.name);
});
```

```vue tab=Vue
<script setup lang="ts">
import { useEcho } from "@laravel/echo-vue";

const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => {
    console.log('Chat event received:', e);
});

channel().listenForWhisper('typing', (e) => {
    console.log(e.name);
});
</script>
```

```svelte tab=Svelte
<script>
import { useEcho } from "@laravel/echo-svelte";

const { channel } = useEcho(`chat.${roomId}`, ['update'], (e) => {
    console.log('Chat event received:', e);
});

channel().listenForWhisper('typing', (e) => {
    console.log(e.name);
});
</script>
```

<a name="notifications"></a>
## Сповіщення

Поєднавши бродкастинг подій зі [сповіщеннями](/docs/{{version}}/notifications), ваш застосунок на JavaScript зможе отримувати нові сповіщення щойно вони з'являються, без оновлення сторінки. Перш ніж почати, обов'язково прочитайте документацію про [канал сповіщень broadcast](/docs/{{version}}/notifications#broadcast-notifications).

Щойно ви налаштували сповіщення на використання каналу broadcast, слухати події бродкастингу можна методом `notification` з Echo. Пам'ятайте: ім'я каналу має збігатися з назвою класу сутності, яка отримує сповіщення:

```js tab=JavaScript
Echo.private(`App.Models.User.${userId}`)
    .notification((notification) => {
        console.log(notification.type);
    });
```

```js tab=React
import { useEchoModel } from "@laravel/echo-react";

const { channel } = useEchoModel('App.Models.User', userId);

channel().notification((notification) => {
    console.log(notification.type);
});
```

```vue tab=Vue
<script setup lang="ts">
import { useEchoModel } from "@laravel/echo-vue";

const { channel } = useEchoModel('App.Models.User', userId);

channel().notification((notification) => {
    console.log(notification.type);
});
</script>
```

```svelte tab=Svelte
<script>
import { useEchoModel } from "@laravel/echo-svelte";

const { channel } = useEchoModel('App.Models.User', userId);

channel().notification((notification) => {
    console.log(notification.type);
});
</script>
```

У цьому прикладі всі сповіщення, надіслані екземплярам `App\Models\User` через канал `broadcast`, отримуватиме колбек. Колбек авторизації каналу `App.Models.User.{id}` уже є у файлі `routes/channels.php` вашого застосунку.

<a name="stop-listening-for-notifications"></a>
#### Припинення прослуховування сповіщень

Якщо ви хочете перестати слухати сповіщення, не [виходячи з каналу](#leaving-a-channel), скористайтеся методом `stopListeningForNotification`:

```js
const callback = (notification) => {
    console.log(notification.type);
}

// Start listening...
Echo.private(`App.Models.User.${userId}`)
    .notification(callback);

// Stop listening (callback must be the same)...
Echo.private(`App.Models.User.${userId}`)
    .stopListeningForNotification(callback);
```

---

Джерело: https://laravelukraine.com/docs/13.x/cache

# Кеш

- [Вступ](#introduction)
- [Конфігурація](#configuration)
    - [Передумови драйверів](#driver-prerequisites)
- [Використання кешу](#cache-usage)
    - [Отримання екземпляра кешу](#obtaining-a-cache-instance)
    - [Отримання елементів із кешу](#retrieving-items-from-the-cache)
    - [Збереження елементів у кеші](#storing-items-in-the-cache)
    - [Продовження часу життя елемента](#extending-item-lifetime)
    - [Видалення елементів із кешу](#removing-items-from-the-cache)
    - [Мемоізація кешу](#cache-memoization)
    - [Хелпер кешу](#the-cache-helper)
- [Теги кешу](#cache-tags)
    - [Збереження елементів кешу з тегами](#storing-tagged-cache-items)
    - [Доступ до елементів кешу з тегами](#accessing-tagged-cache-items)
    - [Видалення елементів кешу з тегами](#removing-tagged-cache-items)
- [Атомарні блокування](#atomic-locks)
    - [Керування блокуваннями](#managing-locks)
    - [Керування блокуваннями між процесами](#managing-locks-across-processes)
    - [Оновлення блокувань](#refreshing-locks)
    - [Обмеження паралельності](#concurrency-limiting)
- [Резервування кешу](#cache-failover)
- [Додавання власних драйверів кешу](#adding-custom-cache-drivers)
    - [Написання драйвера](#writing-the-driver)
    - [Реєстрація драйвера](#registering-the-driver)
- [Події](#events)

<a name="introduction"></a>
## Вступ

Деякі завдання з отримання чи обробки даних у вашому застосунку можуть інтенсивно навантажувати процесор або тривати кілька секунд. У такому разі отримані дані зазвичай кешують на певний час, щоб швидко віддавати їх на наступні запити тих самих даних. Закешовані дані зазвичай зберігають у дуже швидкому сховищі на кшталт [Memcached](https://memcached.org) чи [Redis](https://redis.io).

На щастя, Laravel надає виразний уніфікований API для різних бекендів кешу, що дозволяє скористатися їхньою блискавичною швидкістю та пришвидшити ваш веб-застосунок.

<a name="configuration"></a>
## Конфігурація

Конфігураційний файл кешу вашого застосунку розташовано за шляхом `config/cache.php`. У ньому ви можете вказати, яке сховище кешу використовуватиметься за замовчуванням у всьому застосунку. Laravel одразу підтримує популярні бекенди кешування: [Memcached](https://memcached.org), [Redis](https://redis.io), [DynamoDB](https://aws.amazon.com/dynamodb), реляційні бази даних і диски файлової системи. Крім того, доступний файловий драйвер кешу, а драйвери `array` і `null` дають зручні бекенди для ваших автоматизованих тестів.

Конфігураційний файл кешу містить і низку інших опцій, які варто переглянути. За замовчуванням Laravel налаштовано на драйвер кешу `database`, що зберігає серіалізовані закешовані об'єкти в базі даних вашого застосунку.

<a name="driver-prerequisites"></a>
### Передумови драйверів

<a name="prerequisites-database"></a>
#### База даних

Використовуючи драйвер кешу `database`, вам знадобиться таблиця бази даних для зберігання даних кешу. Зазвичай її створює типова [міграція](/docs/{{version}}/migrations) Laravel `0001_01_01_000001_create_cache_table.php`; однак якщо ваш застосунок не містить цієї міграції, ви можете створити її командою Artisan `make:cache-table`:

```shell
php artisan make:cache-table

php artisan migrate
```

<a name="memcached"></a>
#### Memcached

Використання драйвера Memcached потребує встановленого [пакета Memcached PECL](https://pecl.php.net/package/memcached). Ви можете перелічити всі свої сервери Memcached у конфігураційному файлі `config/cache.php`. Цей файл уже містить запис `memcached.servers`, щоб ви могли почати:

```php
'memcached' => [
    // ...

    'servers' => [
        [
            'host' => env('MEMCACHED_HOST', '127.0.0.1'),
            'port' => env('MEMCACHED_PORT', 11211),
            'weight' => 100,
        ],
    ],
],
```

За потреби ви можете задати опції `host` шлях до UNIX-сокета. Якщо ви це зробите, опції `port` слід задати значення `0`:

```php
'memcached' => [
    // ...

    'servers' => [
        [
            'host' => '/var/run/memcached/memcached.sock',
            'port' => 0,
            'weight' => 100
        ],
    ],
],
```

<a name="redis"></a>
#### Redis

Перш ніж використовувати кеш Redis із Laravel, вам потрібно встановити PHP-розширення PhpRedis через PECL або пакет `predis/predis` через Composer. [Laravel Sail](/docs/{{version}}/sail) уже містить це розширення. Крім того, офіційні платформи для застосунків Laravel - як-от [Laravel Cloud](https://cloud.laravel.com) і [Laravel Forge](https://forge.laravel.com) - мають PhpRedis встановленим за замовчуванням.

Докладніше про налаштування Redis читайте на [сторінці документації Laravel](/docs/{{version}}/redis#configuration).

<a name="storage"></a>
#### Storage

Драйвер кешу `storage` дозволяє зберігати закешовані значення на будь-якому налаштованому [диску файлової системи](/docs/{{version}}/filesystem). Це може бути корисно, коли ви хочете використати наявний диск - наприклад, диск S3 - як сховище кешу «ключ-значення»:

```php
'storage' => [
    'driver' => 'storage',
    'disk' => env('CACHE_STORAGE_DISK'),
    'path' => env('CACHE_STORAGE_PATH', 'framework/cache/data'),
],
```

<a name="dynamodb"></a>
#### DynamoDB

Перш ніж використовувати драйвер кешу [DynamoDB](https://aws.amazon.com/dynamodb), вам потрібно створити таблицю DynamoDB для зберігання всіх закешованих даних. Зазвичай її називають `cache`. Утім, ім'я таблиці слід обирати відповідно до значення конфігурації `stores.dynamodb.table` у файлі `cache`. Ім'я таблиці можна також задати змінною середовища `DYNAMODB_CACHE_TABLE`.

Ця таблиця також має мати рядковий ключ розділу з іменем, що відповідає значенню конфігурації `stores.dynamodb.attributes.key` у файлі `cache` вашого застосунку. За замовчуванням ключ розділу має називатися `key`.

Зазвичай DynamoDB не вилучає з таблиці елементи, термін дії яких сплив. Тому вам слід [увімкнути Time to Live (TTL)](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html) для таблиці. Налаштовуючи TTL, задайте ім'я атрибута TTL як `expires_at`.

Далі встановіть AWS SDK, щоб ваш застосунок Laravel міг спілкуватися з DynamoDB:

```shell
composer require aws/aws-sdk-php
```

Крім того, переконайтеся, що для опцій конфігурації сховища кешу DynamoDB надано значення. Зазвичай ці опції - як-от `AWS_ACCESS_KEY_ID` та `AWS_SECRET_ACCESS_KEY` - визначають у файлі `.env` вашого застосунку:

```php
'dynamodb' => [
    'driver' => 'dynamodb',
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
    'endpoint' => env('DYNAMODB_ENDPOINT'),
],
```

<a name="mongodb"></a>
#### MongoDB

Якщо ви використовуєте MongoDB, драйвер кешу `mongodb` надає офіційний пакет `mongodb/laravel-mongodb`, і його можна налаштувати через підключення до бази даних `mongodb`. MongoDB підтримує TTL-індекси, якими можна автоматично очищати застарілі елементи кешу.

Докладніше про налаштування MongoDB читайте в [документації MongoDB щодо кешу та блокувань](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/cache/).

<a name="cache-usage"></a>
## Використання кешу

<a name="obtaining-a-cache-instance"></a>
### Отримання екземпляра кешу

Щоб отримати екземпляр сховища кешу, скористайтеся фасадом `Cache` - саме його ми використовуватимемо в цій документації. Фасад `Cache` дає зручний і стислий доступ до реалізацій контрактів кешу Laravel:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Cache;

class UserController extends Controller
{
    /**
     * Show a list of all users of the application.
     */
    public function index(): array
    {
        $value = Cache::get('key');

        return [
            // ...
        ];
    }
}
```

<a name="accessing-multiple-cache-stores"></a>
#### Доступ до кількох сховищ кешу

За допомогою фасаду `Cache` ви можете звертатися до різних сховищ методом `store`. Ключ, переданий методу `store`, має відповідати одному зі сховищ, перелічених у масиві конфігурації `stores` вашого файлу `cache`:

```php
$value = Cache::store('file')->get('foo');

Cache::store('redis')->put('bar', 'baz', 600); // 10 Minutes
```

<a name="retrieving-items-from-the-cache"></a>
### Отримання елементів із кешу

Метод `get` фасаду `Cache` слугує для отримання елементів із кешу. Якщо елемента в кеші немає, буде повернуто `null`. За бажанням ви можете передати методу `get` другий аргумент, указавши значення за замовчуванням, яке слід повернути за відсутності елемента:

```php
$value = Cache::get('key');

$value = Cache::get('key', 'default');
```

Ви можете навіть передати замикання як значення за замовчуванням. Його результат буде повернуто, якщо вказаного елемента в кеші немає. Передавання замикання дозволяє відкласти отримання значень за замовчуванням із бази даних чи іншого зовнішнього сервісу:

```php
$value = Cache::get('key', function () {
    return DB::table(/* ... */)->get();
});
```

<a name="determining-item-existence"></a>
#### Перевірка наявності елемента

Метод `has` дозволяє визначити, чи існує елемент у кеші. Цей метод також поверне `false`, якщо елемент існує, але його значення - `null`:

```php
if (Cache::has('key')) {
    // ...
}
```

<a name="incrementing-decrementing-values"></a>
#### Збільшення та зменшення значень

Методи `increment` і `decrement` дозволяють коригувати значення цілочисельних елементів у кеші. Обидва приймають необов'язковий другий аргумент - величину, на яку слід змінити значення:

```php
// Initialize the value if it does not exist...
Cache::add('key', 0, now()->plus(hours: 4));

// Increment or decrement the value...
Cache::increment('key');
Cache::increment('key', $amount);
Cache::decrement('key');
Cache::decrement('key', $amount);
```

<a name="retrieve-store"></a>
#### Отримати й зберегти

Іноді ви можете захотіти отримати елемент із кешу, але водночас зберегти значення за замовчуванням, якщо запитаного елемента немає. Наприклад, ви можете захотіти отримати всіх користувачів із кешу або, якщо їх там немає, узяти їх з бази даних і додати до кешу. Це робиться методом `Cache::remember`:

```php
$value = Cache::remember('users', $seconds, function () {
    return DB::table('users')->get();
});
```

Якщо елемента в кеші немає, буде виконано замикання, передане методу `remember`, а його результат буде поміщено в кеш.

Якщо вам потрібно знати, чи елемент отримано з кешу, а не через виконання замикання, скористайтеся методом `rememberWithWarmth`. Він повертає масив із закешованим значенням і булевим прапорцем, що вказує, чи був елемент «теплим», тобто отриманим із кешу, а не розв'язаним із замикання:

```php
[$value, $warm] = Cache::rememberWithWarmth('users', $seconds, function () {
    return DB::table('users')->get();
});
```

Метод `rememberForever` дозволяє отримати елемент із кешу або зберегти його назавжди, якщо його немає:

```php
$value = Cache::rememberForever('users', function () {
    return DB::table('users')->get();
});
```

<a name="swr"></a>
#### Stale While Revalidate

Використовуючи метод `Cache::remember`, частина користувачів може стикатися з повільними відповідями, коли термін дії закешованого значення сплив. Для певних типів даних буває корисно віддавати частково застарілі дані, поки значення перераховується у фоні, - так деякі користувачі не чекатимуть на обчислення. Цей підхід часто називають «stale-while-revalidate», і метод `Cache::flexible` є його реалізацією.

Метод `flexible` приймає масив, що вказує, як довго закешоване значення вважається «свіжим» і коли воно стає «застарілим». Перше значення масиву - кількість секунд, протягом яких кеш вважається свіжим, друге - як довго його можна віддавати як застарілі дані до того, як перерахунок стане необхідним.

Якщо запит надходить у межах свіжого періоду (до першого значення), кеш повертається одразу без перерахунку. Якщо запит надходить у застарілий період (між двома значеннями), користувачеві віддається застаріле значення, а для оновлення кешу після надсилання відповіді реєструється [відкладена функція](/docs/{{version}}/helpers#deferred-functions). Якщо запит надходить після другого значення, кеш вважається простроченим, і значення перераховується одразу, що може призвести до повільнішої відповіді:

```php
$value = Cache::flexible('users', [5, 10], function () {
    return DB::table('users')->get();
});
```

<a name="retrieve-delete"></a>
#### Отримати й видалити

Якщо вам потрібно отримати елемент із кешу й одразу його видалити, скористайтеся методом `pull`. Як і метод `get`, він поверне `null`, якщо елемента в кеші немає:

```php
$value = Cache::pull('key');

$value = Cache::pull('key', 'default');
```

<a name="storing-items-in-the-cache"></a>
### Збереження елементів у кеші

Щоб зберегти елементи в кеші, скористайтеся методом `put` фасаду `Cache`:

```php
Cache::put('key', 'value', $seconds = 10);
```

Якщо методу `put` не передано час зберігання, елемент зберігатиметься безстроково:

```php
Cache::put('key', 'value');
```

Замість передавати кількість секунд цілим числом, ви можете передати екземпляр `DateTime`, що представляє бажаний час спливання закешованого елемента:

```php
Cache::put('key', 'value', now()->plus(minutes: 10));
```

<a name="store-if-not-present"></a>
#### Зберегти, якщо відсутній

Метод `add` додасть елемент до кешу лише тоді, коли його там ще немає. Метод поверне `true`, якщо елемент справді додано, інакше - `false`. Метод `add` є атомарною операцією:

```php
Cache::add('key', 'value', $seconds);
```

<a name="extending-item-lifetime"></a>
### Продовження часу життя елемента

Метод `touch` дозволяє продовжити час життя (TTL) наявного елемента кешу. Метод `touch` поверне `true`, якщо елемент кешу існує і час його спливання успішно продовжено. Якщо елемента в кеші немає, метод поверне `false`:

```php
Cache::touch('key', 3600);
```

Ви можете передати екземпляр `DateTimeInterface`, `DateInterval` чи `Carbon`, щоб указати точний час спливання:

```php
Cache::touch('key', now()->addHours(2));
```

<a name="storing-items-forever"></a>
#### Безстрокове зберігання елементів

Метод `forever` дозволяє зберегти елемент у кеші назавжди. Оскільки термін дії таких елементів не спливає, їх доведеться вилучати вручну методом `forget`:

```php
Cache::forever('key', 'value');
```

> [!NOTE]
> Якщо ви використовуєте драйвер Memcached, елементи, збережені «назавжди», можуть бути вилучені, коли кеш досягне граничного розміру.

<a name="removing-items-from-the-cache"></a>
### Видалення елементів із кешу

Вилучити елементи з кешу можна методом `forget`:

```php
Cache::forget('key');
```

Ви також можете вилучити елементи, передавши нульову чи від'ємну кількість секунд:

```php
Cache::put('key', 'value', 0);

Cache::put('key', 'value', -5);
```

Очистити весь кеш можна методом `flush`:

```php
Cache::flush();
```

Очистити всі атомарні блокування в кеші можна методом `flushLocks`:

```php
Cache::flushLocks();
```

> [!WARNING]
> Очищення кешу не враховує налаштований «префікс» і вилучить із кешу всі записи. Добре зважте це, очищаючи кеш, який спільно використовують інші застосунки.

<a name="cache-memoization"></a>
### Мемоізація кешу

Драйвер кешу `memo` в Laravel дозволяє тимчасово зберігати розв'язані значення кешу в пам'яті протягом одного запиту чи виконання завдання. Це запобігає повторним зверненням до кешу в межах одного виконання й помітно покращує швидкодію.

Щоб скористатися мемоізованим кешем, викличте метод `memo`:

```php
use Illuminate\Support\Facades\Cache;

$value = Cache::memo()->get('key');
```

Метод `memo` за бажанням приймає ім'я сховища кешу, яке вказує, яке саме сховище декоруватиме мемоізований драйвер:

```php
// Using the default cache store...
$value = Cache::memo()->get('key');

// Using the Redis cache store...
$value = Cache::memo('redis')->get('key');
```

Перший виклик `get` для певного ключа отримує значення з вашого сховища кешу, але наступні виклики в межах того самого запиту чи завдання братимуть значення з пам'яті:

```php
// Hits the cache...
$value = Cache::memo()->get('key');

// Does not hit the cache, returns memoized value...
$value = Cache::memo()->get('key');
```

Коли викликаються методи, що змінюють значення кешу (як-от `put`, `increment`, `remember` тощо), мемоізований кеш автоматично забуває збережене значення й делегує виклик основному сховищу кешу:

```php
Cache::memo()->put('name', 'Taylor'); // Writes to underlying cache...
Cache::memo()->get('name');           // Hits underlying cache...
Cache::memo()->get('name');           // Memoized, does not hit cache...

Cache::memo()->put('name', 'Tim');    // Forgets memoized value, writes new value...
Cache::memo()->get('name');           // Hits underlying cache again...
```

<a name="the-cache-helper"></a>
### Хелпер кешу

Окрім фасаду `Cache`, ви можете скористатися глобальною функцією `cache`, щоб отримувати та зберігати дані в кеші. Коли функцію `cache` викликано з одним рядковим аргументом, вона поверне значення за вказаним ключем:

```php
$value = cache('key');
```

Якщо ви передасте функції масив пар «ключ-значення» та час спливання, вона збереже значення в кеші на вказаний час:

```php
cache(['key' => 'value'], $seconds);

cache(['key' => 'value'], now()->plus(minutes: 10));
```

Коли функцію `cache` викликано без аргументів, вона повертає екземпляр реалізації `Illuminate\Contracts\Cache\Factory`, що дозволяє викликати інші методи кешування:

```php
cache()->remember('users', $seconds, function () {
    return DB::table('users')->get();
});
```

> [!NOTE]
> Тестуючи виклики глобальної функції `cache`, ви можете скористатися методом `Cache::shouldReceive` так само, як під час [тестування фасаду](/docs/{{version}}/mocking#mocking-facades).

<a name="cache-tags"></a>
## Теги кешу

> [!WARNING]
> Теги кешу не підтримуються драйверами `file`, `dynamodb`, `database` та `storage`.

<a name="storing-tagged-cache-items"></a>
### Збереження елементів кешу з тегами

Теги кешу дозволяють позначати пов'язані елементи в кеші, а потім скидати всі значення, яким призначено певний тег. Отримати доступ до кешу з тегами можна, передавши впорядкований масив імен тегів. Наприклад, звернімося до кешу з тегами й помістімо в нього значення методом `put`:

```php
use Illuminate\Support\Facades\Cache;

Cache::tags(['people', 'artists'])->put('John', $john, $seconds);
Cache::tags(['people', 'authors'])->put('Anne', $anne, $seconds);
```

<a name="accessing-tagged-cache-items"></a>
### Доступ до елементів кешу з тегами

До елементів, збережених із тегами, не можна звернутися, не вказавши тих самих тегів. Щоб отримати елемент кешу з тегами, передайте методу `tags` той самий упорядкований список тегів, а потім викличте метод `get` із потрібним ключем:

```php
$john = Cache::tags(['people', 'artists'])->get('John');

$anne = Cache::tags(['people', 'authors'])->get('Anne');
```

<a name="removing-tagged-cache-items"></a>
### Видалення елементів кешу з тегами

Ви можете скинути всі елементи, яким призначено тег чи список тегів. Наприклад, наведений нижче код вилучить усі кеші, позначені `people`, `authors` або обома тегами. Тож із кешу буде вилучено і `Anne`, і `John`:

```php
Cache::tags(['people', 'authors'])->flush();
```

Натомість наведений нижче код вилучить лише значення, позначені тегом `authors`, - тож `Anne` буде вилучено, а `John` ні:

```php
Cache::tags('authors')->flush();
```

<a name="atomic-locks"></a>
## Атомарні блокування

> [!WARNING]
> Щоб скористатися цією можливістю, ваш застосунок має використовувати як типовий драйвер кешу `memcached`, `redis`, `dynamodb`, `database`, `file` чи `array`. Крім того, усі сервери мають спілкуватися з одним центральним сервером кешу.

<a name="managing-locks"></a>
### Керування блокуваннями

Атомарні блокування дозволяють працювати з розподіленими блокуваннями, не турбуючись про стани гонитви. Наприклад, [Laravel Cloud](https://cloud.laravel.com) використовує атомарні блокування, щоб гарантувати виконання лише одного віддаленого завдання на сервері за раз. Створювати блокування та керувати ними можна методом `Cache::lock`:

```php
use Illuminate\Support\Facades\Cache;

$lock = Cache::lock('foo', 10);

if ($lock->get()) {
    // Lock acquired for 10 seconds...

    $lock->release();
}
```

Метод `get` також приймає замикання. Після його виконання Laravel автоматично зніме блокування:

```php
Cache::lock('foo', 10)->get(function () {
    // Lock acquired for 10 seconds and automatically released...
});
```

Якщо блокування недоступне в момент запиту, ви можете вказати Laravel зачекати визначену кількість секунд. Якщо блокування не вдасться отримати за цей час, буде викинуто `Illuminate\Contracts\Cache\LockTimeoutException`:

```php
use Illuminate\Contracts\Cache\LockTimeoutException;

$lock = Cache::lock('foo', 10);

try {
    $lock->block(5);

    // Lock acquired after waiting a maximum of 5 seconds...
} catch (LockTimeoutException $e) {
    // Unable to acquire lock...
} finally {
    $lock->release();
}
```

Наведений вище приклад можна спростити, передавши замикання методу `block`. Коли методу передано замикання, Laravel намагатиметься отримати блокування протягом указаної кількості секунд і автоматично зніме його після виконання замикання:

```php
Cache::lock('foo', 10)->block(5, function () {
    // Lock acquired for 10 seconds after waiting a maximum of 5 seconds...
});
```

<a name="managing-locks-across-processes"></a>
### Керування блокуваннями між процесами

Іноді ви можете захотіти отримати блокування в одному процесі, а зняти його в іншому. Наприклад, ви можете отримати блокування під час веб-запиту й зняти його наприкінці завдання з черги, запущеного цим запитом. У такому сценарії вам слід передати завданню «токен власника» блокування, щоб завдання могло відтворити блокування за цим токеном.

У прикладі нижче ми надішлемо завдання до черги, якщо блокування успішно отримано. Крім того, ми передамо завданню токен власника через метод `owner`:

```php
$podcast = Podcast::find($id);

$lock = Cache::lock('processing', 120);

if ($lock->get()) {
    ProcessPodcast::dispatch($podcast, $lock->owner());
}
```

У завданні `ProcessPodcast` нашого застосунку ми можемо відновити й зняти блокування за токеном власника:

```php
Cache::restoreLock('processing', $this->owner)->release();
```

Якщо ви хочете зняти блокування, не зважаючи на його поточного власника, скористайтеся методом `forceRelease`:

```php
Cache::lock('processing')->forceRelease();
```

<a name="refreshing-locks"></a>
### Оновлення блокувань

Якщо вам потрібно продовжити час дії блокування, яким ви наразі володієте, скористайтеся методом `refresh`. Якщо кількість секунд не передано, буде використано початкову тривалість блокування. Це корисно для тривалих операцій, коли ви віддаєте перевагу короткому блокуванню з періодичним продовженням замість блокування з дуже довгим часом спливання:

```php
$lock = Cache::lock('generate-reports', 60);

if ($lock->get()) {
    foreach ($reports as $report) {
        $report->generate();

        // Extend the lock for another 60 seconds...
        $lock->refresh();
    }

    $lock->release();
}
```

<a name="concurrency-limiting"></a>
### Обмеження паралельності

Атомарні блокування в Laravel також дають кілька способів обмежити паралельне виконання замикань. Скористайтеся `withoutOverlapping`, коли хочете дозволити лише один запущений екземпляр у всій вашій інфраструктурі:

```php
Cache::withoutOverlapping('foo', function () {
    // Lock acquired after waiting a maximum of 10 seconds...
});
```

За замовчуванням блокування утримується, доки замикання не завершить виконання, а метод чекає на його отримання до 10 секунд. Ви можете змінити ці значення додатковими аргументами:

```php
Cache::withoutOverlapping('foo', function () {
    // Lock acquired for 120 seconds after waiting a maximum of 5 seconds...
}, lockFor: 120, waitFor: 5);
```

Якщо блокування не вдасться отримати за вказаний час очікування, буде викинуто `Illuminate\Contracts\Cache\LockTimeoutException`.

Якщо вам потрібен контрольований паралелізм, скористайтеся методом `funnel`, щоб задати максимальну кількість одночасних виконань. Метод `funnel` працює з будь-яким драйвером кешу, що підтримує блокування:

```php
Cache::funnel('foo')
    ->limit(3)
    ->releaseAfter(60)
    ->block(10)
    ->then(function () {
        // Concurrency lock acquired...
    }, function () {
        // Could not acquire concurrency lock...
    });
```

Ключ `funnel` ідентифікує ресурс, який обмежують. Метод `limit` визначає максимальну кількість одночасних виконань. Метод `releaseAfter` задає запобіжний тайм-аут у секундах, після якого зайнятий слот автоматично звільняється. Метод `block` задає, скільки секунд чекати на вільний слот.

Якщо ви віддаєте перевагу обробці тайм-ауту через винятки замість замикання на випадок невдачі, друге замикання можна не передавати. Якщо блокування не вдасться отримати за вказаний час очікування, буде викинуто `Illuminate\Cache\Limiters\LimiterTimeoutException`:

```php
use Illuminate\Cache\Limiters\LimiterTimeoutException;

try {
    Cache::funnel('foo')
        ->limit(3)
        ->releaseAfter(60)
        ->block(10)
        ->then(function () {
            // Concurrency lock acquired...
        });
} catch (LimiterTimeoutException $e) {
    // Unable to acquire concurrency lock...
}
```

Якщо ви хочете використати для обмежувача паралельності конкретне сховище кешу, викличте метод `funnel` на потрібному сховищі:

```php
Cache::store('redis')->funnel('foo')
    ->limit(3)
    ->block(10)
    ->then(function () {
        // Concurrency lock acquired using the "redis" store...
    });
```

> [!NOTE]
> Метод `funnel` потребує, щоб сховище кешу реалізовувало інтерфейс `Illuminate\Contracts\Cache\LockProvider`. Якщо ви спробуєте скористатися `funnel` зі сховищем, що не підтримує блокувань, буде викинуто `BadMethodCallException`.

<a name="cache-failover"></a>
## Резервування кешу

Драйвер кешу `failover` забезпечує автоматичне перемикання на резерв під час роботи з кешем. Якщо основне сховище кешу з якоїсь причини відмовить, Laravel автоматично спробує наступне сховище зі списку. Це особливо корисно для високої доступності в продакшені, де надійність кешу критична.

Щоб налаштувати резервне сховище кешу, вкажіть драйвер `failover` і передайте масив імен сховищ у порядку спроб. За замовчуванням Laravel містить приклад такої конфігурації у файлі `config/cache.php` вашого застосунку:

```php
'failover' => [
    'driver' => 'failover',
    'stores' => [
        'database',
        'array',
    ],
],
```

Налаштувавши сховище з драйвером `failover`, вам потрібно задати його як типове сховище кешу у файлі `.env`, щоб скористатися резервуванням:

```ini
CACHE_STORE=failover
```

Коли операція зі сховищем кешу зазнає невдачі й активується резервування, Laravel надішле подію `Illuminate\Cache\Events\CacheFailedOver`, що дозволить вам відзвітувати чи залогувати відмову сховища.

<a name="adding-custom-cache-drivers"></a>
## Додавання власних драйверів кешу

<a name="writing-the-driver"></a>
### Написання драйвера

Щоб створити власний драйвер кешу, спершу потрібно реалізувати [контракт](/docs/{{version}}/contracts) `Illuminate\Contracts\Cache\Store`. Тож реалізація кешу для MongoDB може виглядати так:

```php
<?php

namespace App\Extensions;

use Illuminate\Contracts\Cache\Store;

class MongoStore implements Store
{
    public function get($key) {}
    public function many(array $keys) {}
    public function put($key, $value, $seconds) {}
    public function putMany(array $values, $seconds) {}
    public function increment($key, $value = 1) {}
    public function decrement($key, $value = 1) {}
    public function forever($key, $value) {}
    public function touch($key, $seconds) {}
    public function forget($key) {}
    public function flush() {}
    public function getPrefix() {}
}
```

Нам залишається реалізувати кожен із цих методів через підключення до MongoDB. Приклад реалізації кожного методу можна побачити в класі `Illuminate\Cache\MemcachedStore` у [вихідному коді фреймворку Laravel](https://github.com/laravel/framework). Коли наша реалізація готова, ми можемо завершити реєстрацію власного драйвера викликом методу `extend` фасаду `Cache`:

```php
Cache::extend('mongo', function (Application $app) {
    return Cache::repository(new MongoStore);
});
```

> [!NOTE]
> Якщо вам цікаво, куди покласти код власного драйвера кешу, можете створити простір імен `Extensions` у каталозі `app`. Утім, пам'ятайте: Laravel не має жорсткої структури застосунку, і ви вільні організовувати його на власний розсуд.

<a name="registering-the-driver"></a>
### Реєстрація драйвера

Щоб зареєструвати власний драйвер кешу в Laravel, ми скористаємося методом `extend` фасаду `Cache`. Оскільки інші сервіс-провайдери можуть намагатися читати закешовані значення у своєму методі `boot`, ми зареєструємо драйвер у колбеку `booting`. Це гарантує, що власний драйвер буде зареєстровано саме перед викликом методу `boot` сервіс-провайдерів застосунку, але після виклику методу `register` на всіх провайдерах. Колбек `booting` ми зареєструємо в методі `register` класу `App\Providers\AppServiceProvider`:

```php
<?php

namespace App\Providers;

use App\Extensions\MongoStore;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        $this->app->booting(function () {
             Cache::extend('mongo', function (Application $app) {
                 return Cache::repository(new MongoStore);
             });
         });
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        // ...
    }
}
```

Перший аргумент, переданий методу `extend`, - ім'я драйвера. Воно відповідатиме вашій опції `driver` у конфігураційному файлі `config/cache.php`. Другий аргумент - замикання, що має повертати екземпляр `Illuminate\Cache\Repository`. Замиканню буде передано екземпляр `$app` - екземпляр [сервіс-контейнера](/docs/{{version}}/container).

Щойно ваше розширення зареєстровано, оновіть змінну середовища `CACHE_STORE` чи опцію `default` у файлі `config/cache.php` вашого застосунку, вказавши ім'я вашого розширення.

<a name="events"></a>
## Події

Щоб виконувати код на кожній операції з кешем, ви можете слухати різні [події](/docs/{{version}}/events), які надсилає кеш:

<div class="overflow-auto">

| Ім'я події                                      |
|-------------------------------------------------|
| `Illuminate\Cache\Events\CacheFlushed`          |
| `Illuminate\Cache\Events\CacheFlushing`         |
| `Illuminate\Cache\Events\CacheFlushFailed`      |
| `Illuminate\Cache\Events\CacheLocksFlushed`     |
| `Illuminate\Cache\Events\CacheLocksFlushing`    |
| `Illuminate\Cache\Events\CacheLocksFlushFailed` |
| `Illuminate\Cache\Events\CacheHit`              |
| `Illuminate\Cache\Events\CacheMissed`           |
| `Illuminate\Cache\Events\ForgettingKey`         |
| `Illuminate\Cache\Events\KeyForgetFailed`       |
| `Illuminate\Cache\Events\KeyForgotten`          |
| `Illuminate\Cache\Events\KeyWriteFailed`        |
| `Illuminate\Cache\Events\KeyWritten`            |
| `Illuminate\Cache\Events\RetrievingKey`         |
| `Illuminate\Cache\Events\RetrievingManyKeys`    |
| `Illuminate\Cache\Events\WritingKey`            |
| `Illuminate\Cache\Events\WritingManyKeys`       |

</div>

Щоб підвищити швидкодію, ви можете вимкнути події кешу, задавши опції конфігурації `events` значення `false` для потрібного сховища у файлі `config/cache.php` вашого застосунку:

```php
'database' => [
    'driver' => 'database',
    // ...
    'events' => false,
],
```

---

Джерело: https://laravelukraine.com/docs/13.x/collections

# Колекції

- [Вступ](#introduction)
    - [Створення колекцій](#creating-collections)
    - [Розширення колекцій](#extending-collections)
- [Доступні методи](#available-methods)
- [Повідомлення вищого порядку](#higher-order-messages)
- [Ліниві колекції](#lazy-collections)
    - [Вступ](#lazy-collection-introduction)
    - [Створення лінивих колекцій](#creating-lazy-collections)
    - [Контракт Enumerable](#the-enumerable-contract)
    - [Методи лінивих колекцій](#lazy-collection-methods)

<a name="introduction"></a>
## Вступ

Клас `Illuminate\Support\Collection` надає зручну плавну обгортку для роботи з масивами даних. Наприклад, погляньте на код нижче. Ми скористаємося хелпером `collect`, щоб створити новий екземпляр колекції з масиву, застосуємо до кожного елемента функцію `strtoupper`, а потім приберемо всі порожні елементи:

```php
$collection = collect(['Taylor', 'Abigail', null])->map(function (?string $name) {
    return strtoupper($name);
})->reject(function (string $name) {
    return empty($name);
});
```

Як бачите, клас `Collection` дозволяє ланцюжком викликати свої методи, щоб плавно виконувати map і reduce над масивом під капотом. Загалом колекції незмінні: кожен метод `Collection` повертає цілком новий екземпляр `Collection`.

<a name="creating-collections"></a>
### Створення колекцій

Як згадано вище, хелпер `collect` повертає новий екземпляр `Illuminate\Support\Collection` для заданого масиву. Тож створити колекцію - простіше простого:

```php
$collection = collect([1, 2, 3]);
```

Ви також можете створити колекцію методами [make](#method-make) та [fromJson](#method-fromjson).

> [!NOTE]
> Результати запитів [Eloquent](/docs/{{version}}/eloquent) завжди повертаються як екземпляри `Collection`.

<a name="extending-collections"></a>
### Розширення колекцій

Колекції «макрові»: це дозволяє додавати до класу `Collection` додаткові методи під час виконання. Метод `macro` класу `Illuminate\Support\Collection` приймає замикання, яке виконається, коли ваш макрос буде викликано. Замикання макросу має доступ до інших методів колекції через `$this`, ніби це справжній метод класу колекції. Наприклад, код нижче додає до класу `Collection` метод `toUpper`:

```php
use Illuminate\Support\Collection;
use Illuminate\Support\Str;

Collection::macro('toUpper', function () {
    return $this->map(function (string $value) {
        return Str::upper($value);
    });
});

$collection = collect(['first', 'second']);

$upper = $collection->toUpper();

// ['FIRST', 'SECOND']
```

Зазвичай макроси колекцій оголошують у методі `boot` [сервіс-провайдера](/docs/{{version}}/providers).

<a name="macro-arguments"></a>
#### Аргументи макросів

За потреби ви можете описувати макроси, які приймають додаткові аргументи:

```php
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Lang;

Collection::macro('toLocale', function (string $locale) {
    return $this->map(function (string $value) use ($locale) {
        return Lang::get($value, [], $locale);
    });
});

$collection = collect(['first', 'second']);

$translated = $collection->toLocale('es');

// ['primero', 'segundo'];
```

<a name="available-methods"></a>
## Доступні методи

Більшу частину решти документації про колекції ми присвятимо кожному методу, доступному в класі `Collection`. Пам'ятайте: усі ці методи можна ланцюжком поєднувати, щоб плавно маніпулювати масивом під капотом. Ба більше, майже кожен метод повертає новий екземпляр `Collection`, тож за потреби ви можете зберегти оригінальну копію колекції:

<style>
    .collection-method-list > p {
        columns: 10.8em 3; -moz-columns: 10.8em 3; -webkit-columns: 10.8em 3;
    }

    .collection-method-list a {
        display: block;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
</style>

<div class="collection-method-list" markdown="1">

[after](#method-after)
[all](#method-all)
[average](#method-average)
[avg](#method-avg)
[before](#method-before)
[chunk](#method-chunk)
[chunkBy](#method-chunkby)
[chunkWhile](#method-chunkwhile)
[collapse](#method-collapse)
[collapseWithKeys](#method-collapsewithkeys)
[collect](#method-collect)
[combine](#method-combine)
[concat](#method-concat)
[contains](#method-contains)
[containsStrict](#method-containsstrict)
[count](#method-count)
[countBy](#method-countBy)
[crossJoin](#method-crossjoin)
[dd](#method-dd)
[diff](#method-diff)
[diffAssoc](#method-diffassoc)
[diffAssocUsing](#method-diffassocusing)
[diffKeys](#method-diffkeys)
[doesntContain](#method-doesntcontain)
[doesntContainStrict](#method-doesntcontainstrict)
[dot](#method-dot)
[dump](#method-dump)
[duplicates](#method-duplicates)
[duplicatesStrict](#method-duplicatesstrict)
[each](#method-each)
[eachSpread](#method-eachspread)
[ensure](#method-ensure)
[every](#method-every)
[except](#method-except)
[filter](#method-filter)
[first](#method-first)
[firstOrFail](#method-first-or-fail)
[firstWhere](#method-first-where)
[flatMap](#method-flatmap)
[flatten](#method-flatten)
[flip](#method-flip)
[forget](#method-forget)
[forPage](#method-forpage)
[fromJson](#method-fromjson)
[get](#method-get)
[groupBy](#method-groupby)
[has](#method-has)
[hasAny](#method-hasany)
[hasMany](#method-hasmany)
[hasSole](#method-hassole)
[implode](#method-implode)
[intersect](#method-intersect)
[intersectUsing](#method-intersectusing)
[intersectAssoc](#method-intersectAssoc)
[intersectAssocUsing](#method-intersectassocusing)
[intersectByKeys](#method-intersectbykeys)
[isEmpty](#method-isempty)
[isNotEmpty](#method-isnotempty)
[join](#method-join)
[keyBy](#method-keyby)
[keys](#method-keys)
[last](#method-last)
[lazy](#method-lazy)
[macro](#method-macro)
[make](#method-make)
[map](#method-map)
[mapInto](#method-mapinto)
[mapSpread](#method-mapspread)
[mapToGroups](#method-maptogroups)
[mapWithKeys](#method-mapwithkeys)
[max](#method-max)
[median](#method-median)
[merge](#method-merge)
[mergeRecursive](#method-mergerecursive)
[min](#method-min)
[mode](#method-mode)
[multiply](#method-multiply)
[nth](#method-nth)
[only](#method-only)
[pad](#method-pad)
[partition](#method-partition)
[percentage](#method-percentage)
[pipe](#method-pipe)
[pipeInto](#method-pipeinto)
[pipeThrough](#method-pipethrough)
[pluck](#method-pluck)
[pop](#method-pop)
[prepend](#method-prepend)
[pull](#method-pull)
[push](#method-push)
[put](#method-put)
[random](#method-random)
[range](#method-range)
[reduce](#method-reduce)
[reduceInto](#method-reduce-into)
[reduceSpread](#method-reduce-spread)
[reject](#method-reject)
[replace](#method-replace)
[replaceRecursive](#method-replacerecursive)
[reverse](#method-reverse)
[search](#method-search)
[select](#method-select)
[shift](#method-shift)
[shuffle](#method-shuffle)
[skip](#method-skip)
[skipUntil](#method-skipuntil)
[skipWhile](#method-skipwhile)
[slice](#method-slice)
[sliding](#method-sliding)
[sole](#method-sole)
[some](#method-some)
[sort](#method-sort)
[sortBy](#method-sortby)
[sortByDesc](#method-sortbydesc)
[sortDesc](#method-sortdesc)
[sortKeys](#method-sortkeys)
[sortKeysDesc](#method-sortkeysdesc)
[sortKeysUsing](#method-sortkeysusing)
[splice](#method-splice)
[split](#method-split)
[splitIn](#method-splitin)
[sum](#method-sum)
[take](#method-take)
[takeUntil](#method-takeuntil)
[takeWhile](#method-takewhile)
[tap](#method-tap)
[times](#method-times)
[toArray](#method-toarray)
[toJson](#method-tojson)
[toPrettyJson](#method-to-pretty-json)
[transform](#method-transform)
[undot](#method-undot)
[union](#method-union)
[unique](#method-unique)
[uniqueStrict](#method-uniquestrict)
[unless](#method-unless)
[unlessEmpty](#method-unlessempty)
[unlessNotEmpty](#method-unlessnotempty)
[unwrap](#method-unwrap)
[value](#method-value)
[values](#method-values)
[when](#method-when)
[whenEmpty](#method-whenempty)
[whenNotEmpty](#method-whennotempty)
[where](#method-where)
[whereStrict](#method-wherestrict)
[whereBetween](#method-wherebetween)
[whereIn](#method-wherein)
[whereInStrict](#method-whereinstrict)
[whereInstanceOf](#method-whereinstanceof)
[whereNotBetween](#method-wherenotbetween)
[whereNotIn](#method-wherenotin)
[whereNotInStrict](#method-wherenotinstrict)
[whereNotNull](#method-wherenotnull)
[whereNull](#method-wherenull)
[wrap](#method-wrap)
[zip](#method-zip)

</div>

<a name="method-listing"></a>
## Перелік методів

<style>
    .collection-method code {
        font-size: 14px;
    }

    .collection-method:not(.first-collection-method) {
        margin-top: 50px;
    }
</style>

<a name="method-after"></a>
#### `after()` {.collection-method .first-collection-method}

Метод `after` повертає елемент після заданого. Якщо заданий елемент не знайдено або він останній, повертається `null`:

```php
$collection = collect([1, 2, 3, 4, 5]);

$collection->after(3);

// 4

$collection->after(5);

// null
```

Цей метод шукає заданий елемент за «нестрогим» порівнянням: рядок із цілим значенням вважається рівним цілому числу з тим самим значенням. Щоб скористатися «строгим» порівнянням, передайте методу аргумент `strict`:

```php
collect([2, 4, 6, 8])->after('4', strict: true);

// null
```

Як варіант, ви можете передати власне замикання, щоб знайти перший елемент, який проходить заданий тест:

```php
collect([2, 4, 6, 8])->after(function (int $item, int $key) {
    return $item > 5;
});

// 8
```

<a name="method-all"></a>
#### `all()` {.collection-method}

Метод `all` повертає масив, який лежить в основі колекції:

```php
collect([1, 2, 3])->all();

// [1, 2, 3]
```

<a name="method-average"></a>
#### `average()` {.collection-method}

Аліас методу [avg](#method-avg).

<a name="method-avg"></a>
#### `avg()` {.collection-method}

Метод `avg` повертає [середнє значення](https://en.wikipedia.org/wiki/Average) заданого ключа:

```php
$average = collect([
    ['foo' => 10],
    ['foo' => 10],
    ['foo' => 20],
    ['foo' => 40]
])->avg('foo');

// 20

$average = collect([1, 1, 2, 4])->avg();

// 2
```

<a name="method-before"></a>
#### `before()` {.collection-method}

Метод `before` протилежний методу [after](#method-after). Він повертає елемент перед заданим. Якщо заданий елемент не знайдено або він перший, повертається `null`:

```php
$collection = collect([1, 2, 3, 4, 5]);

$collection->before(3);

// 2

$collection->before(1);

// null

collect([2, 4, 6, 8])->before('4', strict: true);

// null

collect([2, 4, 6, 8])->before(function (int $item, int $key) {
    return $item > 5;
});

// 4
```

<a name="method-chunk"></a>
#### `chunk()` {.collection-method}

Метод `chunk` розбиває колекцію на кілька менших колекцій заданого розміру:

```php
$collection = collect([1, 2, 3, 4, 5, 6, 7]);

$chunks = $collection->chunk(4);

$chunks->all();

// [[1, 2, 3, 4], [5, 6, 7]]
```

Цей метод особливо корисний у [представленнях](/docs/{{version}}/views) під час роботи з сітковими системами на кшталт [Bootstrap](https://getbootstrap.com/docs/5.3/layout/grid/). Наприклад, уявіть колекцію моделей [Eloquent](/docs/{{version}}/eloquent), яку ви хочете показати сіткою:

```blade
@foreach ($products->chunk(3) as $chunk)
    <div class="row">
        @foreach ($chunk as $product)
            <div class="col-xs-4">{{ $product->name }}</div>
        @endforeach
    </div>
@endforeach
```

<a name="method-chunkby"></a>
#### `chunkBy()` {.collection-method}

Метод `chunkBy` розбиває колекцію на кілька менших колекцій, групуючи сусідні елементи, які мають однакове значення для заданого ключа або колбека. Наприклад, ви можете згрупувати сусідні товари, які мають однакового батька:

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

На відміну від методу `groupBy`, елементи з однаковим значенням, які не є сусідніми, розміщуються в окремі фрагменти:

```php
$collection = collect([1, 1, 2, 2, 1]);

$chunks = $collection->chunkBy(fn (int $value) => $value);

$chunks->all();

// [[1, 1], [2, 2], [1]]
```

<a name="method-chunkwhile"></a>
#### `chunkWhile()` {.collection-method}

Метод `chunkWhile` розбиває колекцію на кілька менших колекцій на основі результату заданого колбека. Змінна `$chunk`, яку передано до замикання, дозволяє перевірити попередній елемент:

```php
$collection = collect(str_split('AABBCCCD'));

$chunks = $collection->chunkWhile(function (string $value, int $key, Collection $chunk) {
    return $value === $chunk->last();
});

$chunks->all();

// [['A', 'A'], ['B', 'B'], ['C', 'C', 'C'], ['D']]
```

<a name="method-collapse"></a>
#### `collapse()` {.collection-method}

Метод `collapse` згортає колекцію масивів або колекцій в одну плоску колекцію:

```php
$collection = collect([
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
]);

$collapsed = $collection->collapse();

$collapsed->all();

// [1, 2, 3, 4, 5, 6, 7, 8, 9]
```

<a name="method-collapsewithkeys"></a>
#### `collapseWithKeys()` {.collection-method}

Метод `collapseWithKeys` сплющує колекцію масивів або колекцій в одну колекцію, зберігаючи оригінальні ключі. Якщо колекція вже плоска, він поверне порожню колекцію:

```php
$collection = collect([
    ['first'  => collect([1, 2, 3])],
    ['second' => [4, 5, 6]],
    ['third'  => collect([7, 8, 9])]
]);

$collapsed = $collection->collapseWithKeys();

$collapsed->all();

// [
//     'first'  => [1, 2, 3],
//     'second' => [4, 5, 6],
//     'third'  => [7, 8, 9],
// ]
```

<a name="method-collect"></a>
#### `collect()` {.collection-method}

Метод `collect` повертає новий екземпляр `Collection` з елементами, які наразі є в колекції:

```php
$collectionA = collect([1, 2, 3]);

$collectionB = $collectionA->collect();

$collectionB->all();

// [1, 2, 3]
```

Метод `collect` насамперед корисний для перетворення [лінивих колекцій](#lazy-collections) на звичайні екземпляри `Collection`:

```php
$lazyCollection = LazyCollection::make(function () {
    yield 1;
    yield 2;
    yield 3;
});

$collection = $lazyCollection->collect();

$collection::class;

// 'Illuminate\Support\Collection'

$collection->all();

// [1, 2, 3]
```

> [!NOTE]
> Метод `collect` особливо корисний, коли ви маєте екземпляр `Enumerable` і потребуєте неліниву колекцію. Оскільки `collect()` є частиною контракту `Enumerable`, ви можете сміливо користуватися ним, щоб отримати екземпляр `Collection`.

<a name="method-combine"></a>
#### `combine()` {.collection-method}

Метод `combine` поєднує значення колекції як ключі зі значеннями іншого масиву або колекції:

```php
$collection = collect(['name', 'age']);

$combined = $collection->combine(['George', 29]);

$combined->all();

// ['name' => 'George', 'age' => 29]
```

<a name="method-concat"></a>
#### `concat()` {.collection-method}

Метод `concat` додає значення заданого масиву або колекції в кінець іншої колекції:

```php
$collection = collect(['John Doe']);

$concatenated = $collection->concat(['Jane Doe'])->concat(['name' => 'Johnny Doe']);

$concatenated->all();

// ['John Doe', 'Jane Doe', 'Johnny Doe']
```

Метод `concat` числово переіндексовує ключі елементів, доданих до оригінальної колекції. Щоб зберегти ключі в асоціативних колекціях, дивіться метод [merge](#method-merge).

<a name="method-contains"></a>
#### `contains()` {.collection-method}

Метод `contains` визначає, чи містить колекція заданий елемент. Ви можете передати методу `contains` замикання, щоб визначити, чи існує в колекції елемент, який проходить заданий тест:

```php
$collection = collect([1, 2, 3, 4, 5]);

$collection->contains(function (int $value, int $key) {
    return $value > 5;
});

// false
```

Як варіант, ви можете передати методу `contains` рядок, щоб визначити, чи містить колекція задане значення елемента:

```php
$collection = collect(['name' => 'Desk', 'price' => 100]);

$collection->contains('Desk');

// true

$collection->contains('New York');

// false
```

Ви також можете передати методу `contains` пару ключ / значення, і він визначить, чи існує задана пара в колекції:

```php
$collection = collect([
    ['product' => 'Desk', 'price' => 200],
    ['product' => 'Chair', 'price' => 100],
]);

$collection->contains('product', 'Bookcase');

// false
```

Перевіряючи значення елементів, метод `contains` використовує «нестрогі» порівняння: рядок із цілим значенням вважається рівним цілому числу з тим самим значенням. Щоб фільтрувати зі «строгими» порівняннями, скористайтеся методом [containsStrict](#method-containsstrict).

Протилежність `contains` - метод [doesntContain](#method-doesntcontain).

<a name="method-containsstrict"></a>
#### `containsStrict()` {.collection-method}

Цей метод має ту саму сигнатуру, що й метод [contains](#method-contains); проте всі значення порівнюються «строго».

> [!NOTE]
> Поведінка цього методу змінюється під час роботи з [колекціями Eloquent](/docs/{{version}}/eloquent-collections#method-contains).

<a name="method-count"></a>
#### `count()` {.collection-method}

Метод `count` повертає загальну кількість елементів у колекції:

```php
$collection = collect([1, 2, 3, 4]);

$collection->count();

// 4
```

<a name="method-countBy"></a>
#### `countBy()` {.collection-method}

Метод `countBy` рахує входження значень у колекції. За замовчуванням метод рахує входження кожного елемента, що дозволяє порахувати певні «типи» елементів у колекції:

```php
$collection = collect([1, 2, 2, 2, 3]);

$counted = $collection->countBy();

$counted->all();

// [1 => 1, 2 => 3, 3 => 1]
```

Ви можете передати методу `countBy` замикання, щоб порахувати всі елементи за власним значенням:

```php
$collection = collect(['alice@gmail.com', 'bob@yahoo.com', 'carlos@gmail.com']);

$counted = $collection->countBy(function (string $email) {
    return substr(strrchr($email, '@'), 1);
});

$counted->all();

// ['gmail.com' => 2, 'yahoo.com' => 1]
```

<a name="method-crossjoin"></a>
#### `crossJoin()` {.collection-method}

Метод `crossJoin` перехресно поєднує значення колекції із заданими масивами чи колекціями, повертаючи декартів добуток з усіма можливими комбінаціями:

```php
$collection = collect([1, 2]);

$matrix = $collection->crossJoin(['a', 'b']);

$matrix->all();

/*
    [
        [1, 'a'],
        [1, 'b'],
        [2, 'a'],
        [2, 'b'],
    ]
*/

$collection = collect([1, 2]);

$matrix = $collection->crossJoin(['a', 'b'], ['I', 'II']);

$matrix->all();

/*
    [
        [1, 'a', 'I'],
        [1, 'a', 'II'],
        [1, 'b', 'I'],
        [1, 'b', 'II'],
        [2, 'a', 'I'],
        [2, 'a', 'II'],
        [2, 'b', 'I'],
        [2, 'b', 'II'],
    ]
*/
```

<a name="method-dd"></a>
#### `dd()` {.collection-method}

Метод `dd` виводить елементи колекції та припиняє виконання скрипта:

```php
$collection = collect(['John Doe', 'Jane Doe']);

$collection->dd();

/*
    array:2 [
        0 => "John Doe"
        1 => "Jane Doe"
    ]
*/
```

Якщо ви не хочете зупиняти виконання скрипта, скористайтеся натомість методом [dump](#method-dump).

<a name="method-diff"></a>
#### `diff()` {.collection-method}

Метод `diff` порівнює колекцію з іншою колекцією або звичайним PHP-масивом `array` за значеннями. Цей метод поверне значення оригінальної колекції, яких немає в заданій колекції:

```php
$collection = collect([1, 2, 3, 4, 5]);

$diff = $collection->diff([2, 4, 6, 8]);

$diff->all();

// [1, 3, 5]
```

> [!NOTE]
> Поведінка цього методу змінюється під час роботи з [колекціями Eloquent](/docs/{{version}}/eloquent-collections#method-diff).

<a name="method-diffassoc"></a>
#### `diffAssoc()` {.collection-method}

Метод `diffAssoc` порівнює колекцію з іншою колекцією або звичайним PHP-масивом `array` за ключами та значеннями. Цей метод поверне пари ключ / значення оригінальної колекції, яких немає в заданій колекції:

```php
$collection = collect([
    'color' => 'orange',
    'type' => 'fruit',
    'remain' => 6,
]);

$diff = $collection->diffAssoc([
    'color' => 'yellow',
    'type' => 'fruit',
    'remain' => 3,
    'used' => 6,
]);

$diff->all();

// ['color' => 'orange', 'remain' => 6]
```

<a name="method-diffassocusing"></a>
#### `diffAssocUsing()` {.collection-method}

На відміну від `diffAssoc`, `diffAssocUsing` приймає задану користувачем функцію-колбек для порівняння індексів:

```php
$collection = collect([
    'color' => 'orange',
    'type' => 'fruit',
    'remain' => 6,
]);

$diff = $collection->diffAssocUsing([
    'Color' => 'yellow',
    'Type' => 'fruit',
    'Remain' => 3,
], 'strnatcasecmp');

$diff->all();

// ['color' => 'orange', 'remain' => 6]
```

Колбек має бути функцією порівняння, яка повертає ціле число менше, рівне або більше за нуль. Докладніше дивіться в документації PHP про [array_diff_uassoc](https://www.php.net/array_diff_uassoc#refsect1-function.array-diff-uassoc-parameters) - саме цю функцію PHP метод `diffAssocUsing` використовує під капотом.

<a name="method-diffkeys"></a>
#### `diffKeys()` {.collection-method}

Метод `diffKeys` порівнює колекцію з іншою колекцією або звичайним PHP-масивом `array` за ключами. Цей метод поверне пари ключ / значення оригінальної колекції, яких немає в заданій колекції:

```php
$collection = collect([
    'one' => 10,
    'two' => 20,
    'three' => 30,
    'four' => 40,
    'five' => 50,
]);

$diff = $collection->diffKeys([
    'two' => 2,
    'four' => 4,
    'six' => 6,
    'eight' => 8,
]);

$diff->all();

// ['one' => 10, 'three' => 30, 'five' => 50]
```

<a name="method-doesntcontain"></a>
#### `doesntContain()` {.collection-method}

Метод `doesntContain` визначає, чи не містить колекція заданий елемент. Ви можете передати методу `doesntContain` замикання, щоб визначити, чи не існує в колекції елемента, який проходить заданий тест:

```php
$collection = collect([1, 2, 3, 4, 5]);

$collection->doesntContain(function (int $value, int $key) {
    return $value < 5;
});

// false
```

Як варіант, ви можете передати методу `doesntContain` рядок, щоб визначити, чи не містить колекція задане значення елемента:

```php
$collection = collect(['name' => 'Desk', 'price' => 100]);

$collection->doesntContain('Table');

// true

$collection->doesntContain('Desk');

// false
```

Ви також можете передати методу `doesntContain` пару ключ / значення, і він визначить, чи не існує задана пара в колекції:

```php
$collection = collect([
    ['product' => 'Desk', 'price' => 200],
    ['product' => 'Chair', 'price' => 100],
]);

$collection->doesntContain('product', 'Bookcase');

// true
```

Перевіряючи значення елементів, метод `doesntContain` використовує «нестрогі» порівняння: рядок із цілим значенням вважається рівним цілому числу з тим самим значенням.

<a name="method-doesntcontainstrict"></a>
#### `doesntContainStrict()` {.collection-method}

Цей метод має ту саму сигнатуру, що й метод [doesntContain](#method-doesntcontain); проте всі значення порівнюються «строго».

<a name="method-dot"></a>
#### `dot()` {.collection-method}

Метод `dot` сплющує багатовимірну колекцію в одновимірну, використовуючи «крапкову» нотацію для позначення вкладеності:

```php
$collection = collect(['products' => ['desk' => ['price' => 100]]]);

$flattened = $collection->dot();

$flattened->all();

// ['products.desk.price' => 100]
```

<a name="method-dump"></a>
#### `dump()` {.collection-method}

Метод `dump` виводить елементи колекції:

```php
$collection = collect(['John Doe', 'Jane Doe']);

$collection->dump();

/*
    array:2 [
        0 => "John Doe"
        1 => "Jane Doe"
    ]
*/
```

Якщо ви хочете припинити виконання скрипта після виведення колекції, скористайтеся натомість методом [dd](#method-dd).

<a name="method-duplicates"></a>
#### `duplicates()` {.collection-method}

Метод `duplicates` дістає й повертає значення-дублікати з колекції:

```php
$collection = collect(['a', 'b', 'a', 'c', 'b']);

$collection->duplicates();

// [2 => 'a', 4 => 'b']
```

Якщо колекція містить масиви або об'єкти, ви можете передати ключ атрибутів, які треба перевірити на дублікати:

```php
$employees = collect([
    ['email' => 'abigail@example.com', 'position' => 'Developer'],
    ['email' => 'james@example.com', 'position' => 'Designer'],
    ['email' => 'victoria@example.com', 'position' => 'Developer'],
]);

$employees->duplicates('position');

// [2 => 'Developer']
```

<a name="method-duplicatesstrict"></a>
#### `duplicatesStrict()` {.collection-method}

Цей метод має ту саму сигнатуру, що й метод [duplicates](#method-duplicates); проте всі значення порівнюються «строго».

<a name="method-each"></a>
#### `each()` {.collection-method}

Метод `each` проходить елементи колекції й передає кожен елемент до замикання:

```php
$collection = collect([1, 2, 3, 4]);

$collection->each(function (int $item, int $key) {
    // ...
});
```

Якщо ви хочете припинити обхід елементів, поверніть із замикання `false`:

```php
$collection->each(function (int $item, int $key) {
    if (/* condition */) {
        return false;
    }
});
```

<a name="method-eachspread"></a>
#### `eachSpread()` {.collection-method}

Метод `eachSpread` проходить елементи колекції, передаючи значення кожного вкладеного елемента до заданого колбека:

```php
$collection = collect([['John Doe', 35], ['Jane Doe', 33]]);

$collection->eachSpread(function (string $name, int $age) {
    // ...
});
```

Ви можете припинити обхід елементів, повернувши з колбека `false`:

```php
$collection->eachSpread(function (string $name, int $age) {
    return false;
});
```

<a name="method-ensure"></a>
#### `ensure()` {.collection-method}

Метод `ensure` дозволяє перевірити, що всі елементи колекції належать до заданого типу чи списку типів. Інакше буде викинуто `UnexpectedValueException`:

```php
return $collection->ensure(User::class);

return $collection->ensure([User::class, Customer::class]);
```

Можна вказати й примітивні типи на кшталт `string`, `int`, `float`, `bool` та `array`:

```php
return $collection->ensure('int');
```

> [!WARNING]
> Метод `ensure` не гарантує, що елементи інших типів не буде додано до колекції пізніше.

<a name="method-every"></a>
#### `every()` {.collection-method}

Метод `every` дозволяє перевірити, що всі елементи колекції проходять заданий тест:

```php
collect([1, 2, 3, 4])->every(function (int $value, int $key) {
    return $value > 2;
});

// false
```

Якщо колекція порожня, метод `every` поверне true:

```php
$collection = collect([]);

$collection->every(function (int $value, int $key) {
    return $value > 2;
});

// true
```

<a name="method-except"></a>
#### `except()` {.collection-method}

Метод `except` повертає всі елементи колекції, окрім тих, що мають вказані ключі:

```php
$collection = collect(['product_id' => 1, 'price' => 100, 'discount' => false]);

$filtered = $collection->except(['price', 'discount']);

$filtered->all();

// ['product_id' => 1]
```

Протилежність `except` - метод [only](#method-only).

> [!NOTE]
> Поведінка цього методу змінюється під час роботи з [колекціями Eloquent](/docs/{{version}}/eloquent-collections#method-except).

<a name="method-filter"></a>
#### `filter()` {.collection-method}

Метод `filter` фільтрує колекцію заданим колбеком, лишаючи тільки ті елементи, які проходять заданий тест:

```php
$collection = collect([1, 2, 3, 4]);

$filtered = $collection->filter(function (int $value, int $key) {
    return $value > 2;
});

$filtered->all();

// [3, 4]
```

Якщо колбек не передано, буде вилучено всі елементи колекції, еквівалентні `false`:

```php
$collection = collect([1, 2, 3, null, false, '', 0, []]);

$collection->filter()->all();

// [1, 2, 3]
```

Протилежність `filter` - метод [reject](#method-reject).

<a name="method-first"></a>
#### `first()` {.collection-method}

Метод `first` повертає перший елемент колекції, який проходить заданий тест:

```php
collect([1, 2, 3, 4])->first(function (int $value, int $key) {
    return $value > 2;
});

// 3
```

Ви також можете викликати метод `first` без аргументів, щоб отримати перший елемент колекції. Якщо колекція порожня, повертається `null`:

```php
collect([1, 2, 3, 4])->first();

// 1
```

<a name="method-first-or-fail"></a>
#### `firstOrFail()` {.collection-method}

Метод `firstOrFail` ідентичний методу `first`; проте, якщо результату не знайдено, буде викинуто виняток `Illuminate\Support\ItemNotFoundException`:

```php
collect([1, 2, 3, 4])->firstOrFail(function (int $value, int $key) {
    return $value > 5;
});

// Throws ItemNotFoundException...
```

Ви також можете викликати метод `firstOrFail` без аргументів, щоб отримати перший елемент колекції. Якщо колекція порожня, буде викинуто виняток `Illuminate\Support\ItemNotFoundException`:

```php
collect([])->firstOrFail();

// Throws ItemNotFoundException...
```

<a name="method-first-where"></a>
#### `firstWhere()` {.collection-method}

Метод `firstWhere` повертає перший елемент колекції із заданою парою ключ / значення:

```php
$collection = collect([
    ['name' => 'Regena', 'age' => null],
    ['name' => 'Linda', 'age' => 14],
    ['name' => 'Diego', 'age' => 23],
    ['name' => 'Linda', 'age' => 84],
]);

$collection->firstWhere('name', 'Linda');

// ['name' => 'Linda', 'age' => 14]
```

Ви також можете викликати метод `firstWhere` з оператором порівняння:

```php
$collection->firstWhere('age', '>=', 18);

// ['name' => 'Diego', 'age' => 23]
```

Як і методу [where](#method-where), методу `firstWhere` можна передати один аргумент. У такому разі `firstWhere` поверне перший елемент, у якого значення заданого ключа є «істинним»:

```php
$collection->firstWhere('age');

// ['name' => 'Linda', 'age' => 14]
```

<a name="method-flatmap"></a>
#### `flatMap()` {.collection-method}

Метод `flatMap` проходить колекцію й передає кожне значення до заданого замикання. Замикання вільне змінити елемент і повернути його, утворюючи так нову колекцію змінених елементів. Потім масив сплющується на один рівень:

```php
$collection = collect([
    ['name' => 'Sally'],
    ['school' => 'Arkansas'],
    ['age' => 28]
]);

$flattened = $collection->flatMap(function (array $values) {
    return array_map('strtoupper', $values);
});

$flattened->all();

// ['name' => 'SALLY', 'school' => 'ARKANSAS', 'age' => '28'];
```

<a name="method-flatten"></a>
#### `flatten()` {.collection-method}

Метод `flatten` сплющує багатовимірну колекцію в одновимірну:

```php
$collection = collect([
    'name' => 'Taylor',
    'languages' => [
        'PHP', 'JavaScript'
    ]
]);

$flattened = $collection->flatten();

$flattened->all();

// ['Taylor', 'PHP', 'JavaScript'];
```

За потреби ви можете передати методу `flatten` аргумент «глибини»:

```php
$collection = collect([
    'Apple' => [
        [
            'name' => 'iPhone 6S',
            'brand' => 'Apple'
        ],
    ],
    'Samsung' => [
        [
            'name' => 'Galaxy S7',
            'brand' => 'Samsung'
        ],
    ],
]);

$products = $collection->flatten(1);

$products->values()->all();

/*
    [
        ['name' => 'iPhone 6S', 'brand' => 'Apple'],
        ['name' => 'Galaxy S7', 'brand' => 'Samsung'],
    ]
*/
```

У цьому прикладі виклик `flatten` без глибини сплющив би й вкладені масиви, давши `['iPhone 6S', 'Apple', 'Galaxy S7', 'Samsung']`. Глибина дозволяє вказати, на скільки рівнів сплющувати вкладені масиви.

<a name="method-flip"></a>
#### `flip()` {.collection-method}

Метод `flip` міняє місцями ключі колекції з відповідними значеннями:

```php
$collection = collect(['name' => 'Taylor', 'framework' => 'Laravel']);

$flipped = $collection->flip();

$flipped->all();

// ['Taylor' => 'name', 'Laravel' => 'framework']
```

<a name="method-forget"></a>
#### `forget()` {.collection-method}

Метод `forget` вилучає елемент з колекції за його ключем:

```php
$collection = collect(['name' => 'Taylor', 'framework' => 'Laravel']);

// Forget a single key...
$collection->forget('name');

// ['framework' => 'Laravel']

// Forget multiple keys...
$collection->forget(['name', 'framework']);

// []
```

> [!WARNING]
> На відміну від більшості інших методів колекції, `forget` не повертає нову змінену колекцію: він змінює й повертає ту колекцію, на якій його викликано.

<a name="method-forpage"></a>
#### `forPage()` {.collection-method}

Метод `forPage` повертає нову колекцію з елементами, які були б на заданій сторінці. Першим аргументом метод приймає номер сторінки, другим - кількість елементів на сторінці:

```php
$collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9]);

$chunk = $collection->forPage(2, 3);

$chunk->all();

// [4, 5, 6]
```

<a name="method-fromjson"></a>
#### `fromJson()` {.collection-method}

Статичний метод `fromJson` створює новий екземпляр колекції, декодуючи заданий JSON-рядок PHP-функцією `json_decode`:

```php
use Illuminate\Support\Collection;

$json = json_encode([
    'name' => 'Taylor Otwell',
    'role' => 'Developer',
    'status' => 'Active',
]);

$collection = Collection::fromJson($json);
```

<a name="method-get"></a>
#### `get()` {.collection-method}

Метод `get` повертає елемент за заданим ключем. Якщо ключа не існує, повертається `null`:

```php
$collection = collect(['name' => 'Taylor', 'framework' => 'Laravel']);

$value = $collection->get('name');

// Taylor
```

Другим аргументом ви можете передати значення за замовчуванням:

```php
$collection = collect(['name' => 'Taylor', 'framework' => 'Laravel']);

$value = $collection->get('age', 34);

// 34
```

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

```php
$collection->get('email', function () {
    return 'taylor@example.com';
});

// taylor@example.com
```

<a name="method-groupby"></a>
#### `groupBy()` {.collection-method}

Метод `groupBy` групує елементи колекції за заданим ключем:

```php
$collection = collect([
    ['account_id' => 'account-x10', 'product' => 'Chair'],
    ['account_id' => 'account-x10', 'product' => 'Bookcase'],
    ['account_id' => 'account-x11', 'product' => 'Desk'],
]);

$grouped = $collection->groupBy('account_id');

$grouped->all();

/*
    [
        'account-x10' => [
            ['account_id' => 'account-x10', 'product' => 'Chair'],
            ['account_id' => 'account-x10', 'product' => 'Bookcase'],
        ],
        'account-x11' => [
            ['account_id' => 'account-x11', 'product' => 'Desk'],
        ],
    ]
*/
```

Замість рядкового `key` ви можете передати колбек. Колбек має повернути значення, за яким ви хочете згрупувати:

```php
$grouped = $collection->groupBy(function (array $item, int $key) {
    return substr($item['account_id'], -3);
});

$grouped->all();

/*
    [
        'x10' => [
            ['account_id' => 'account-x10', 'product' => 'Chair'],
            ['account_id' => 'account-x10', 'product' => 'Bookcase'],
        ],
        'x11' => [
            ['account_id' => 'account-x11', 'product' => 'Desk'],
        ],
    ]
*/
```

Кілька критеріїв групування можна передати масивом. Кожен елемент масиву буде застосовано до відповідного рівня багатовимірного масиву:

```php
$data = new Collection([
    10 => ['user' => 1, 'skill' => 1, 'roles' => ['Role_1', 'Role_3']],
    20 => ['user' => 2, 'skill' => 1, 'roles' => ['Role_1', 'Role_2']],
    30 => ['user' => 3, 'skill' => 2, 'roles' => ['Role_1']],
    40 => ['user' => 4, 'skill' => 2, 'roles' => ['Role_2']],
]);

$result = $data->groupBy(['skill', function (array $item) {
    return $item['roles'];
}], preserveKeys: true);

/*
[
    1 => [
        'Role_1' => [
            10 => ['user' => 1, 'skill' => 1, 'roles' => ['Role_1', 'Role_3']],
            20 => ['user' => 2, 'skill' => 1, 'roles' => ['Role_1', 'Role_2']],
        ],
        'Role_3' => [
            10 => ['user' => 1, 'skill' => 1, 'roles' => ['Role_1', 'Role_3']],
        ],
        'Role_2' => [
            20 => ['user' => 2, 'skill' => 1, 'roles' => ['Role_1', 'Role_2']],
        ],
    ],
    2 => [
        'Role_1' => [
            30 => ['user' => 3, 'skill' => 2, 'roles' => ['Role_1']],
        ],
        'Role_2' => [
            40 => ['user' => 4, 'skill' => 2, 'roles' => ['Role_2']],
        ],
    ],
];
*/
```

<a name="method-has"></a>
#### `has()` {.collection-method}

Метод `has` визначає, чи існує заданий ключ у колекції:

```php
$collection = collect(['account_id' => 1, 'product' => 'Desk', 'amount' => 5]);

$collection->has('product');

// true

$collection->has(['product', 'amount']);

// true

$collection->has(['amount', 'price']);

// false
```

<a name="method-hasany"></a>
#### `hasAny()` {.collection-method}

Метод `hasAny` визначає, чи існує в колекції хоч один із заданих ключів:

```php
$collection = collect(['account_id' => 1, 'product' => 'Desk', 'amount' => 5]);

$collection->hasAny(['product', 'price']);

// true

$collection->hasAny(['name', 'price']);

// false
```

<a name="method-hasmany"></a>
#### `hasMany()` {.collection-method}

Метод `hasMany` визначає, чи містить колекція кілька елементів:

```php
collect([])->hasMany();

// false

collect(['1'])->hasMany();

// false

collect([1, 2, 3])->hasMany();

// true

collect([
    ['age' => 2],
    ['age' => 3],
])->hasMany(fn ($item) => $item['age'] === 2);

// false
```

<a name="method-hassole"></a>
#### `hasSole()` {.collection-method}

Метод `hasSole` визначає, чи містить колекція єдиний елемент - за потреби той, що відповідає заданим критеріям:

```php
collect([])->hasSole();

// false

collect(['1'])->hasSole();

// true

collect([1, 2, 3])->hasSole(fn (int $item) => $item === 2);

// true
```

<a name="method-implode"></a>
#### `implode()` {.collection-method}

Метод `implode` склеює елементи колекції. Його аргументи залежать від типу елементів у колекції. Якщо колекція містить масиви або об'єкти, передайте ключ атрибутів, які треба склеїти, і рядок-«клей», який слід поставити між значеннями:

```php
$collection = collect([
    ['account_id' => 1, 'product' => 'Desk'],
    ['account_id' => 2, 'product' => 'Chair'],
]);

$collection->implode('product', ', ');

// 'Desk, Chair'
```

Якщо колекція містить прості рядки чи числові значення, передайте методу «клей» єдиним аргументом:

```php
collect([1, 2, 3, 4, 5])->implode('-');

// '1-2-3-4-5'
```

Якщо ви хочете відформатувати значення, які склеюються, передайте методу `implode` замикання:

```php
$collection->implode(function (array $item, int $key) {
    return strtoupper($item['product']);
}, ', ');

// 'DESK, CHAIR'
```

<a name="method-intersect"></a>
#### `intersect()` {.collection-method}

Метод `intersect` вилучає з оригінальної колекції всі значення, яких немає в заданому масиві чи колекції. Отримана колекція збереже ключі оригінальної:

```php
$collection = collect(['Desk', 'Sofa', 'Chair']);

$intersect = $collection->intersect(['Desk', 'Chair', 'Bookcase']);

$intersect->all();

// [0 => 'Desk', 2 => 'Chair']
```

> [!NOTE]
> Поведінка цього методу змінюється під час роботи з [колекціями Eloquent](/docs/{{version}}/eloquent-collections#method-intersect).

<a name="method-intersectusing"></a>
#### `intersectUsing()` {.collection-method}

Метод `intersectUsing` вилучає з оригінальної колекції всі значення, яких немає в заданому масиві чи колекції, порівнюючи значення власним колбеком. Отримана колекція збереже ключі оригінальної:

```php
$collection = collect(['Desk', 'Sofa', 'Chair']);

$intersect = $collection->intersectUsing(['desk', 'chair', 'bookcase'], function (string $a, string $b) {
    return strcasecmp($a, $b);
});

$intersect->all();

// [0 => 'Desk', 2 => 'Chair']
```

<a name="method-intersectAssoc"></a>
#### `intersectAssoc()` {.collection-method}

Метод `intersectAssoc` порівнює оригінальну колекцію з іншою колекцією чи масивом, повертаючи пари ключ / значення, які є в усіх заданих колекціях:

```php
$collection = collect([
    'color' => 'red',
    'size' => 'M',
    'material' => 'cotton'
]);

$intersect = $collection->intersectAssoc([
    'color' => 'blue',
    'size' => 'M',
    'material' => 'polyester'
]);

$intersect->all();

// ['size' => 'M']
```

<a name="method-intersectassocusing"></a>
#### `intersectAssocUsing()` {.collection-method}

Метод `intersectAssocUsing` порівнює оригінальну колекцію з іншою колекцією чи масивом, повертаючи пари ключ / значення, які є в обох, і порівнює ключі власним колбеком:

```php
$collection = collect([
    'color' => 'red',
    'Size' => 'M',
    'material' => 'cotton',
]);

$intersect = $collection->intersectAssocUsing([
    'color' => 'blue',
    'size' => 'M',
    'material' => 'polyester',
], function (string $a, string $b) {
    return strcasecmp($a, $b);
});

$intersect->all();

// ['Size' => 'M']
```

<a name="method-intersectbykeys"></a>
#### `intersectByKeys()` {.collection-method}

Метод `intersectByKeys` вилучає з оригінальної колекції всі ключі та їхні значення, яких немає в заданому масиві чи колекції:

```php
$collection = collect([
    'serial' => 'UX301', 'type' => 'screen', 'year' => 2009,
]);

$intersect = $collection->intersectByKeys([
    'reference' => 'UX404', 'type' => 'tab', 'year' => 2011,
]);

$intersect->all();

// ['type' => 'screen', 'year' => 2009]
```

<a name="method-isempty"></a>
#### `isEmpty()` {.collection-method}

Метод `isEmpty` повертає `true`, якщо колекція порожня; інакше повертається `false`:

```php
collect([])->isEmpty();

// true
```

<a name="method-isnotempty"></a>
#### `isNotEmpty()` {.collection-method}

Метод `isNotEmpty` повертає `true`, якщо колекція не порожня; інакше повертається `false`:

```php
collect([])->isNotEmpty();

// false
```

<a name="method-join"></a>
#### `join()` {.collection-method}

Метод `join` склеює значення колекції рядком. Другим аргументом цього методу ви можете вказати, як до рядка слід додати останній елемент:

```php
collect(['a', 'b', 'c'])->join(', '); // 'a, b, c'
collect(['a', 'b', 'c'])->join(', ', ', and '); // 'a, b, and c'
collect(['a', 'b'])->join(', ', ' and '); // 'a and b'
collect(['a'])->join(', ', ' and '); // 'a'
collect([])->join(', ', ' and '); // ''
```

<a name="method-keyby"></a>
#### `keyBy()` {.collection-method}

Метод `keyBy` робить заданий ключ ключем колекції. Якщо кілька елементів мають однаковий ключ, у новій колекції залишиться тільки останній:

```php
$collection = collect([
    ['product_id' => 'prod-100', 'name' => 'Desk'],
    ['product_id' => 'prod-200', 'name' => 'Chair'],
]);

$keyed = $collection->keyBy('product_id');

$keyed->all();

/*
    [
        'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'],
        'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'],
    ]
*/
```

Ви також можете передати методу колбек. Колбек має повернути значення, яке стане ключем колекції:

```php
$keyed = $collection->keyBy(function (array $item, int $key) {
    return strtoupper($item['product_id']);
});

$keyed->all();

/*
    [
        'PROD-100' => ['product_id' => 'prod-100', 'name' => 'Desk'],
        'PROD-200' => ['product_id' => 'prod-200', 'name' => 'Chair'],
    ]
*/
```

<a name="method-keys"></a>
#### `keys()` {.collection-method}

Метод `keys` повертає всі ключі колекції:

```php
$collection = collect([
    'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'],
    'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'],
]);

$keys = $collection->keys();

$keys->all();

// ['prod-100', 'prod-200']
```

<a name="method-last"></a>
#### `last()` {.collection-method}

Метод `last` повертає останній елемент колекції, який проходить заданий тест:

```php
collect([1, 2, 3, 4])->last(function (int $value, int $key) {
    return $value < 3;
});

// 2
```

Ви також можете викликати метод `last` без аргументів, щоб отримати останній елемент колекції. Якщо колекція порожня, повертається `null`:

```php
collect([1, 2, 3, 4])->last();

// 4
```

<a name="method-lazy"></a>
#### `lazy()` {.collection-method}

Метод `lazy` повертає новий екземпляр [LazyCollection](#lazy-collections) з масиву елементів під капотом:

```php
$lazyCollection = collect([1, 2, 3, 4])->lazy();

$lazyCollection::class;

// Illuminate\Support\LazyCollection

$lazyCollection->all();

// [1, 2, 3, 4]
```

Це особливо корисно, коли вам потрібно виконати перетворення над величезною `Collection` з великою кількістю елементів:

```php
$count = $hugeCollection
    ->lazy()
    ->where('country', 'FR')
    ->where('balance', '>', '100')
    ->count();
```

Перетворивши колекцію на `LazyCollection`, ми уникаємо виділення купи додаткової пам'яті. Хоч оригінальна колекція й далі тримає _свої_ значення в пам'яті, наступні фільтри цього вже не роблять. Тож під час фільтрування результатів колекції додаткова пам'ять практично не виділяється.

<a name="method-macro"></a>
#### `macro()` {.collection-method}

Статичний метод `macro` дозволяє додавати методи до класу `Collection` під час виконання. Докладніше дивіться в документації про [розширення колекцій](#extending-collections).

<a name="method-make"></a>
#### `make()` {.collection-method}

Статичний метод `make` створює новий екземпляр колекції. Дивіться розділ [Створення колекцій](#creating-collections).

```php
use Illuminate\Support\Collection;

$collection = Collection::make([1, 2, 3]);
```

<a name="method-map"></a>
#### `map()` {.collection-method}

Метод `map` проходить колекцію й передає кожне значення до заданого колбека. Колбек вільний змінити елемент і повернути його, утворюючи так нову колекцію змінених елементів:

```php
$collection = collect([1, 2, 3, 4, 5]);

$multiplied = $collection->map(function (int $item, int $key) {
    return $item * 2;
});

$multiplied->all();

// [2, 4, 6, 8, 10]
```

> [!WARNING]
> Як і більшість інших методів колекції, `map` повертає новий екземпляр колекції: він не змінює ту колекцію, на якій його викликано. Якщо ви хочете перетворити оригінальну колекцію, скористайтеся методом [transform](#method-transform).

<a name="method-mapinto"></a>
#### `mapInto()` {.collection-method}

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

```php
class Currency
{
    /**
     * Create a new currency instance.
     */
    function __construct(
        public string $code,
    ) {}
}

$collection = collect(['USD', 'EUR', 'GBP']);

$currencies = $collection->mapInto(Currency::class);

$currencies->all();

// [Currency('USD'), Currency('EUR'), Currency('GBP')]
```

<a name="method-mapspread"></a>
#### `mapSpread()` {.collection-method}

Метод `mapSpread` проходить елементи колекції, передаючи значення кожного вкладеного елемента до заданого замикання. Замикання вільне змінити елемент і повернути його, утворюючи так нову колекцію змінених елементів:

```php
$collection = collect([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);

$chunks = $collection->chunk(2);

$sequence = $chunks->mapSpread(function (int $even, int $odd) {
    return $even + $odd;
});

$sequence->all();

// [1, 5, 9, 13, 17]
```

<a name="method-maptogroups"></a>
#### `mapToGroups()` {.collection-method}

Метод `mapToGroups` групує елементи колекції заданим замиканням. Замикання має повернути асоціативний масив з єдиною парою ключ / значення, утворюючи так нову колекцію згрупованих значень:

```php
$collection = collect([
    [
        'name' => 'John Doe',
        'department' => 'Sales',
    ],
    [
        'name' => 'Jane Doe',
        'department' => 'Sales',
    ],
    [
        'name' => 'Johnny Doe',
        'department' => 'Marketing',
    ]
]);

$grouped = $collection->mapToGroups(function (array $item, int $key) {
    return [$item['department'] => $item['name']];
});

$grouped->all();

/*
    [
        'Sales' => ['John Doe', 'Jane Doe'],
        'Marketing' => ['Johnny Doe'],
    ]
*/

$grouped->get('Sales')->all();

// ['John Doe', 'Jane Doe']
```

<a name="method-mapwithkeys"></a>
#### `mapWithKeys()` {.collection-method}

Метод `mapWithKeys` проходить колекцію й передає кожне значення до заданого колбека. Колбек має повернути асоціативний масив з єдиною парою ключ / значення:

```php
$collection = collect([
    [
        'name' => 'John',
        'department' => 'Sales',
        'email' => 'john@example.com',
    ],
    [
        'name' => 'Jane',
        'department' => 'Marketing',
        'email' => 'jane@example.com',
    ]
]);

$keyed = $collection->mapWithKeys(function (array $item, int $key) {
    return [$item['email'] => $item['name']];
});

$keyed->all();

/*
    [
        'john@example.com' => 'John',
        'jane@example.com' => 'Jane',
    ]
*/
```

<a name="method-max"></a>
#### `max()` {.collection-method}

Метод `max` повертає максимальне значення заданого ключа:

```php
$max = collect([
    ['foo' => 10],
    ['foo' => 20]
])->max('foo');

// 20

$max = collect([1, 2, 3, 4, 5])->max();

// 5
```

<a name="method-median"></a>
#### `median()` {.collection-method}

Метод `median` повертає [медіанне значення](https://en.wikipedia.org/wiki/Median) заданого ключа:

```php
$median = collect([
    ['foo' => 10],
    ['foo' => 10],
    ['foo' => 20],
    ['foo' => 40]
])->median('foo');

// 15

$median = collect([1, 1, 2, 4])->median();

// 1.5
```

<a name="method-merge"></a>
#### `merge()` {.collection-method}

Метод `merge` зливає заданий масив чи колекцію з оригінальною колекцією. Якщо рядковий ключ у заданих елементах збігається з рядковим ключем в оригінальній колекції, значення заданого елемента перезапише значення в оригінальній колекції:

```php
$collection = collect(['product_id' => 1, 'price' => 100]);

$merged = $collection->merge(['price' => 200, 'discount' => false]);

$merged->all();

// ['product_id' => 1, 'price' => 200, 'discount' => false]
```

Якщо ключі заданого елемента числові, значення буде додано в кінець колекції:

```php
$collection = collect(['Desk', 'Chair']);

$merged = $collection->merge(['Bookcase', 'Door']);

$merged->all();

// ['Desk', 'Chair', 'Bookcase', 'Door']
```

<a name="method-mergerecursive"></a>
#### `mergeRecursive()` {.collection-method}

Метод `mergeRecursive` рекурсивно зливає заданий масив чи колекцію з оригінальною колекцією. Якщо рядковий ключ у заданих елементах збігається з рядковим ключем в оригінальній колекції, значення цих ключів зливаються в масив - і так рекурсивно:

```php
$collection = collect(['product_id' => 1, 'price' => 100]);

$merged = $collection->mergeRecursive([
    'product_id' => 2,
    'price' => 200,
    'discount' => false
]);

$merged->all();

// ['product_id' => [1, 2], 'price' => [100, 200], 'discount' => false]
```

<a name="method-min"></a>
#### `min()` {.collection-method}

Метод `min` повертає мінімальне значення заданого ключа:

```php
$min = collect([
    ['foo' => 10],
    ['foo' => 20]
])->min('foo');

// 10

$min = collect([1, 2, 3, 4, 5])->min();

// 1
```

<a name="method-mode"></a>
#### `mode()` {.collection-method}

Метод `mode` повертає [моду](https://en.wikipedia.org/wiki/Mode_(statistics)) заданого ключа:

```php
$mode = collect([
    ['foo' => 10],
    ['foo' => 10],
    ['foo' => 20],
    ['foo' => 40]
])->mode('foo');

// [10]

$mode = collect([1, 1, 2, 4])->mode();

// [1]

$mode = collect([1, 1, 2, 2])->mode();

// [1, 2]
```

<a name="method-multiply"></a>
#### `multiply()` {.collection-method}

Метод `multiply` створює вказану кількість копій усіх елементів колекції:

```php
$users = collect([
    ['name' => 'User #1', 'email' => 'user1@example.com'],
    ['name' => 'User #2', 'email' => 'user2@example.com'],
])->multiply(3);

/*
    [
        ['name' => 'User #1', 'email' => 'user1@example.com'],
        ['name' => 'User #2', 'email' => 'user2@example.com'],
        ['name' => 'User #1', 'email' => 'user1@example.com'],
        ['name' => 'User #2', 'email' => 'user2@example.com'],
        ['name' => 'User #1', 'email' => 'user1@example.com'],
        ['name' => 'User #2', 'email' => 'user2@example.com'],
    ]
*/
```

<a name="method-nth"></a>
#### `nth()` {.collection-method}

Метод `nth` створює нову колекцію з кожного n-го елемента:

```php
$collection = collect(['a', 'b', 'c', 'd', 'e', 'f']);

$collection->nth(4);

// ['a', 'e']
```

Другим аргументом ви можете передати початковий зсув:

```php
$collection->nth(4, 1);

// ['b', 'f']
```

<a name="method-only"></a>
#### `only()` {.collection-method}

Метод `only` повертає елементи колекції з вказаними ключами:

```php
$collection = collect([
    'product_id' => 1,
    'name' => 'Desk',
    'price' => 100,
    'discount' => false
]);

$filtered = $collection->only(['product_id', 'name']);

$filtered->all();

// ['product_id' => 1, 'name' => 'Desk']
```

Протилежність `only` - метод [except](#method-except).

> [!NOTE]
> Поведінка цього методу змінюється під час роботи з [колекціями Eloquent](/docs/{{version}}/eloquent-collections#method-only).

<a name="method-pad"></a>
#### `pad()` {.collection-method}

Метод `pad` заповнює масив заданим значенням, доки той не досягне вказаного розміру. Цей метод поводиться як PHP-функція [array_pad](https://secure.php.net/manual/en/function.array-pad.php).

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

```php
$collection = collect(['A', 'B', 'C']);

$filtered = $collection->pad(5, 0);

$filtered->all();

// ['A', 'B', 'C', 0, 0]

$filtered = $collection->pad(-5, 0);

$filtered->all();

// [0, 0, 'A', 'B', 'C']
```

<a name="method-partition"></a>
#### `partition()` {.collection-method}

Метод `partition` можна поєднати з деструктуризацією масивів PHP, щоб відділити елементи, які проходять заданий тест, від тих, які його не проходять:

```php
$collection = collect([1, 2, 3, 4, 5, 6]);

[$underThree, $equalOrAboveThree] = $collection->partition(function (int $i) {
    return $i < 3;
});

$underThree->all();

// [1, 2]

$equalOrAboveThree->all();

// [3, 4, 5, 6]
```

> [!NOTE]
> Поведінка цього методу змінюється під час роботи з [колекціями Eloquent](/docs/{{version}}/eloquent-collections#method-partition).

<a name="method-percentage"></a>
#### `percentage()` {.collection-method}

Метод `percentage` дозволяє швидко визначити відсоток елементів колекції, які проходять заданий тест:

```php
$collection = collect([1, 1, 2, 2, 2, 3]);

$percentage = $collection->percentage(fn (int $value) => $value === 1);

// 33.33
```

За замовчуванням відсоток округлюється до двох знаків після коми. Проте ви можете змінити цю поведінку, передавши методу другий аргумент:

```php
$percentage = $collection->percentage(fn (int $value) => $value === 1, precision: 3);

// 33.333
```

<a name="method-pipe"></a>
#### `pipe()` {.collection-method}

Метод `pipe` передає колекцію до заданого замикання й повертає результат його виконання:

```php
$collection = collect([1, 2, 3]);

$piped = $collection->pipe(function (Collection $collection) {
    return $collection->sum();
});

// 6
```

<a name="method-pipeinto"></a>
#### `pipeInto()` {.collection-method}

Метод `pipeInto` створює новий екземпляр заданого класу й передає колекцію в конструктор:

```php
class ResourceCollection
{
    /**
     * Create a new ResourceCollection instance.
     */
    public function __construct(
        public Collection $collection,
    ) {}
}

$collection = collect([1, 2, 3]);

$resource = $collection->pipeInto(ResourceCollection::class);

$resource->collection->all();

// [1, 2, 3]
```

<a name="method-pipethrough"></a>
#### `pipeThrough()` {.collection-method}

Метод `pipeThrough` передає колекцію до заданого масиву замикань і повертає результат їх виконання:

```php
use Illuminate\Support\Collection;

$collection = collect([1, 2, 3]);

$result = $collection->pipeThrough([
    function (Collection $collection) {
        return $collection->merge([4, 5]);
    },
    function (Collection $collection) {
        return $collection->sum();
    },
]);

// 15
```

<a name="method-pluck"></a>
#### `pluck()` {.collection-method}

Метод `pluck` дістає всі значення за заданим ключем:

```php
$collection = collect([
    ['product_id' => 'prod-100', 'name' => 'Desk'],
    ['product_id' => 'prod-200', 'name' => 'Chair'],
]);

$plucked = $collection->pluck('name');

$plucked->all();

// ['Desk', 'Chair']
```

Ви також можете вказати, за яким ключем має будуватися отримана колекція:

```php
$plucked = $collection->pluck('name', 'product_id');

$plucked->all();

// ['prod-100' => 'Desk', 'prod-200' => 'Chair']
```

Метод `pluck` також підтримує отримання вкладених значень за «крапковою» нотацією:

```php
$collection = collect([
    [
        'name' => 'Laracon',
        'speakers' => [
            'first_day' => ['Rosa', 'Judith'],
        ],
    ],
    [
        'name' => 'VueConf',
        'speakers' => [
            'first_day' => ['Abigail', 'Joey'],
        ],
    ],
]);

$plucked = $collection->pluck('speakers.first_day');

$plucked->all();

// [['Rosa', 'Judith'], ['Abigail', 'Joey']]
```

Якщо трапляються однакові ключі, до отриманої колекції потрапить останній відповідний елемент:

```php
$collection = collect([
    ['brand' => 'Tesla',  'color' => 'red'],
    ['brand' => 'Pagani', 'color' => 'white'],
    ['brand' => 'Tesla',  'color' => 'black'],
    ['brand' => 'Pagani', 'color' => 'orange'],
]);

$plucked = $collection->pluck('color', 'brand');

$plucked->all();

// ['Tesla' => 'black', 'Pagani' => 'orange']
```

<a name="method-pop"></a>
#### `pop()` {.collection-method}

Метод `pop` вилучає й повертає останній елемент колекції. Якщо колекція порожня, буде повернуто `null`:

```php
$collection = collect([1, 2, 3, 4, 5]);

$collection->pop();

// 5

$collection->all();

// [1, 2, 3, 4]
```

Ви можете передати методу `pop` ціле число, щоб вилучити й повернути кілька елементів з кінця колекції:

```php
$collection = collect([1, 2, 3, 4, 5]);

$collection->pop(3);

// collect([5, 4, 3])

$collection->all();

// [1, 2]
```

<a name="method-prepend"></a>
#### `prepend()` {.collection-method}

Метод `prepend` додає елемент на початок колекції:

```php
$collection = collect([1, 2, 3, 4, 5]);

$collection->prepend(0);

$collection->all();

// [0, 1, 2, 3, 4, 5]
```

Другим аргументом ви можете вказати ключ доданого елемента:

```php
$collection = collect(['one' => 1, 'two' => 2]);

$collection->prepend(0, 'zero');

$collection->all();

// ['zero' => 0, 'one' => 1, 'two' => 2]
```

<a name="method-pull"></a>
#### `pull()` {.collection-method}

Метод `pull` вилучає й повертає елемент колекції за його ключем:

```php
$collection = collect(['product_id' => 'prod-100', 'name' => 'Desk']);

$collection->pull('name');

// 'Desk'

$collection->all();

// ['product_id' => 'prod-100']
```

<a name="method-push"></a>
#### `push()` {.collection-method}

Метод `push` додає елемент у кінець колекції:

```php
$collection = collect([1, 2, 3, 4]);

$collection->push(5);

$collection->all();

// [1, 2, 3, 4, 5]
```

Ви також можете передати кілька елементів, щоб додати їх у кінець колекції:

```php
$collection = collect([1, 2, 3, 4]);

$collection->push(5, 6, 7);
 
$collection->all();
 
// [1, 2, 3, 4, 5, 6, 7]
```

<a name="method-put"></a>
#### `put()` {.collection-method}

Метод `put` встановлює в колекції заданий ключ і значення:

```php
$collection = collect(['product_id' => 1, 'name' => 'Desk']);

$collection->put('price', 100);

$collection->all();

// ['product_id' => 1, 'name' => 'Desk', 'price' => 100]
```

<a name="method-random"></a>
#### `random()` {.collection-method}

Метод `random` повертає випадковий елемент колекції:

```php
$collection = collect([1, 2, 3, 4, 5]);

$collection->random();

// 4 - (retrieved randomly)
```

Ви можете передати `random` ціле число, щоб вказати, скільки елементів дістати випадково. Коли ви явно передаєте кількість потрібних елементів, завжди повертається колекція:

```php
$random = $collection->random(3);

$random->all();

// [2, 4, 5] - (retrieved randomly)
```

Якщо в екземплярі колекції менше елементів, ніж запитано, метод `random` викине `InvalidArgumentException`.

Метод `random` також приймає замикання, яке отримає поточний екземпляр колекції:

```php
use Illuminate\Support\Collection;

$random = $collection->random(fn (Collection $items) => min(10, count($items)));

$random->all();

// [1, 2, 3, 4, 5] - (retrieved randomly)
```

<a name="method-range"></a>
#### `range()` {.collection-method}

Метод `range` повертає колекцію з цілими числами у вказаному діапазоні:

```php
$collection = collect()->range(3, 6);

$collection->all();

// [3, 4, 5, 6]
```

<a name="method-reduce"></a>
#### `reduce()` {.collection-method}

Метод `reduce` зводить колекцію до єдиного значення, передаючи результат кожної ітерації до наступної:

```php
$collection = collect([1, 2, 3]);

$total = $collection->reduce(function (?int $carry, int $item) {
    return $carry + $item;
});

// 6
```

На першій ітерації значенням `$carry` є `null`; проте ви можете задати його початкове значення, передавши `reduce` другий аргумент:

```php
$collection->reduce(function (int $carry, int $item) {
    return $carry + $item;
}, 4);

// 10
```

Метод `reduce` також передає до заданого колбека ключі масиву:

```php
$collection = collect([
    'usd' => 1400,
    'gbp' => 1200,
    'eur' => 1000,
]);

$ratio = [
    'usd' => 1,
    'gbp' => 1.37,
    'eur' => 1.22,
];

$collection->reduce(function (int $carry, int $value, string $key) use ($ratio) {
    return $carry + ($value * $ratio[$key]);
}, 0);

// 4264
```

<a name="method-reduce-into"></a>
#### `reduceInto()` {.collection-method}

Метод `reduceInto` зводить колекцію до єдиного значення, змінюючи задане початкове значення. На відміну від методу `reduce`, заданий колбек не мусить повертати накопичене значення:

```php
class OrderStats
{
    public int $total = 0;

    public int $count = 0;
}

$orders = collect([
    ['amount' => 100],
    ['amount' => 250],
    ['amount' => 50],
]);

$stats = $orders->reduceInto(new OrderStats, function (OrderStats $stats, array $order) {
    $stats->total += $order['amount'];
    $stats->count++;
});

$stats->total;

// 400
```

Коли ви зводите до скаляра чи масиву, приймайте його в колбеку за посиланням, щоб ваші зміни застосовувалися до оригінального значення:

```php
$collection = collect([1, 2, 3, 4, 5]);

$even = $collection->reduceInto([], function (array &$result, int $value) {
    if ($value % 2 === 0) {
        $result[] = $value;
    }
});

// [2, 4]
```

<a name="method-reduce-spread"></a>
#### `reduceSpread()` {.collection-method}

Метод `reduceSpread` зводить колекцію до масиву значень, передаючи результати кожної ітерації до наступної. Цей метод схожий на `reduce`; проте він може приймати кілька початкових значень:

```php
[$creditsRemaining, $batch] = Image::where('status', 'unprocessed')
    ->get()
    ->reduceSpread(function (int $creditsRemaining, Collection $batch, Image $image) {
        if ($creditsRemaining >= $image->creditsRequired()) {
            $batch->push($image);

            $creditsRemaining -= $image->creditsRequired();
        }

        return [$creditsRemaining, $batch];
    }, $creditsAvailable, collect());
```

<a name="method-reject"></a>
#### `reject()` {.collection-method}

Метод `reject` фільтрує колекцію заданим замиканням. Замикання має повернути `true`, якщо елемент слід вилучити з отриманої колекції:

```php
$collection = collect([1, 2, 3, 4]);

$filtered = $collection->reject(function (int $value, int $key) {
    return $value > 2;
});

$filtered->all();

// [1, 2]
```

Протилежність методу `reject` - метод [filter](#method-filter).

<a name="method-replace"></a>
#### `replace()` {.collection-method}

Метод `replace` поводиться подібно до `merge`; проте, окрім перезапису відповідних елементів із рядковими ключами, `replace` перезапише й елементи колекції з відповідними числовими ключами:

```php
$collection = collect(['Taylor', 'Abigail', 'James']);

$replaced = $collection->replace([1 => 'Victoria', 3 => 'Finn']);

$replaced->all();

// ['Taylor', 'Victoria', 'James', 'Finn']
```

<a name="method-replacerecursive"></a>
#### `replaceRecursive()` {.collection-method}

Метод `replaceRecursive` поводиться подібно до `replace`, але заходить у масиви й застосовує той самий процес заміни до внутрішніх значень:

```php
$collection = collect([
    'Taylor',
    'Abigail',
    [
        'James',
        'Victoria',
        'Finn'
    ]
]);

$replaced = $collection->replaceRecursive([
    'Charlie',
    2 => [1 => 'King']
]);

$replaced->all();

// ['Charlie', 'Abigail', ['James', 'King', 'Finn']]
```

<a name="method-reverse"></a>
#### `reverse()` {.collection-method}

Метод `reverse` обертає порядок елементів колекції, зберігаючи оригінальні ключі:

```php
$collection = collect(['a', 'b', 'c', 'd', 'e']);

$reversed = $collection->reverse();

$reversed->all();

/*
    [
        4 => 'e',
        3 => 'd',
        2 => 'c',
        1 => 'b',
        0 => 'a',
    ]
*/
```

<a name="method-search"></a>
#### `search()` {.collection-method}

Метод `search` шукає в колекції задане значення й повертає його ключ, якщо знайшов. Якщо елемент не знайдено, повертається `false`:

```php
$collection = collect([2, 4, 6, 8]);

$collection->search(4);

// 1
```

Пошук виконується «нестрогим» порівнянням: рядок із цілим значенням вважається рівним цілому числу з тим самим значенням. Щоб скористатися «строгим» порівнянням, передайте другим аргументом методу `true`:

```php
collect([2, 4, 6, 8])->search('4', strict: true);

// false
```

Як варіант, ви можете передати власне замикання, щоб знайти перший елемент, який проходить заданий тест:

```php
collect([2, 4, 6, 8])->search(function (int $item, int $key) {
    return $item > 5;
});

// 2
```

<a name="method-select"></a>
#### `select()` {.collection-method}

Метод `select` вибирає з колекції задані ключі - подібно до інструкції `SELECT` в SQL:

```php
$users = collect([
    ['name' => 'Taylor Otwell', 'role' => 'Developer', 'status' => 'active'],
    ['name' => 'Victoria Faith', 'role' => 'Researcher', 'status' => 'active'],
]);

$users->select(['name', 'role']);

/*
    [
        ['name' => 'Taylor Otwell', 'role' => 'Developer'],
        ['name' => 'Victoria Faith', 'role' => 'Researcher'],
    ],
*/
```

<a name="method-shift"></a>
#### `shift()` {.collection-method}

Метод `shift` вилучає й повертає перший елемент колекції:

```php
$collection = collect([1, 2, 3, 4, 5]);

$collection->shift();

// 1

$collection->all();

// [2, 3, 4, 5]
```

Ви можете передати методу `shift` ціле число, щоб вилучити й повернути кілька елементів з початку колекції:

```php
$collection = collect([1, 2, 3, 4, 5]);

$collection->shift(3);

// collect([1, 2, 3])

$collection->all();

// [4, 5]
```

<a name="method-shuffle"></a>
#### `shuffle()` {.collection-method}

Метод `shuffle` випадково перемішує елементи колекції:

```php
$collection = collect([1, 2, 3, 4, 5]);

$shuffled = $collection->shuffle();

$shuffled->all();

// [3, 2, 5, 1, 4] - (generated randomly)
```

<a name="method-skip"></a>
#### `skip()` {.collection-method}

Метод `skip` повертає нову колекцію, з початку якої вилучено задану кількість елементів:

```php
$collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

$collection = $collection->skip(4);

$collection->all();

// [5, 6, 7, 8, 9, 10]
```

<a name="method-skipuntil"></a>
#### `skipUntil()` {.collection-method}

Метод `skipUntil` пропускає елементи колекції, доки заданий колбек повертає `false`. Щойно колбек поверне `true`, усі решта елементів колекції буде повернуто як нову колекцію:

```php
$collection = collect([1, 2, 3, 4]);

$subset = $collection->skipUntil(function (int $item) {
    return $item >= 3;
});

$subset->all();

// [3, 4]
```

Ви також можете передати методу `skipUntil` просте значення, щоб пропускати всі елементи, доки не знайдеться задане значення:

```php
$collection = collect([1, 2, 3, 4]);

$subset = $collection->skipUntil(3);

$subset->all();

// [3, 4]
```

> [!WARNING]
> Якщо заданого значення не знайдено або колбек ніколи не повертає `true`, метод `skipUntil` поверне порожню колекцію.

<a name="method-skipwhile"></a>
#### `skipWhile()` {.collection-method}

Метод `skipWhile` пропускає елементи колекції, доки заданий колбек повертає `true`. Щойно колбек поверне `false`, усі решта елементів колекції буде повернуто як нову колекцію:

```php
$collection = collect([1, 2, 3, 4]);

$subset = $collection->skipWhile(function (int $item) {
    return $item <= 3;
});

$subset->all();

// [4]
```

> [!WARNING]
> Якщо колбек ніколи не повертає `false`, метод `skipWhile` поверне порожню колекцію.

<a name="method-slice"></a>
#### `slice()` {.collection-method}

Метод `slice` повертає зріз колекції, що починається із заданого індексу:

```php
$collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

$slice = $collection->slice(4);

$slice->all();

// [5, 6, 7, 8, 9, 10]
```

Якщо ви хочете обмежити розмір поверненого зрізу, передайте потрібний розмір другим аргументом методу:

```php
$slice = $collection->slice(4, 2);

$slice->all();

// [5, 6]
```

За замовчуванням повернений зріз збереже ключі. Якщо ви не хочете зберігати оригінальні ключі, переіндексувати їх можна методом [values](#method-values).

<a name="method-sliding"></a>
#### `sliding()` {.collection-method}

Метод `sliding` повертає нову колекцію фрагментів, що дають вигляд елементів колекції через «ковзне вікно»:

```php
$collection = collect([1, 2, 3, 4, 5]);

$chunks = $collection->sliding(2);

$chunks->toArray();

// [[1, 2], [2, 3], [3, 4], [4, 5]]
```

Це особливо корисно в поєднанні з методом [eachSpread](#method-eachspread):

```php
$transactions->sliding(2)->eachSpread(function ($previous, $current) {
    $current->total = $previous->total + $current->amount;
});
```

Другим аргументом ви можете передати «крок», який визначає відстань між першими елементами сусідніх фрагментів:

```php
$collection = collect([1, 2, 3, 4, 5]);

$chunks = $collection->sliding(3, step: 2);

$chunks->toArray();

// [[1, 2, 3], [3, 4, 5]]
```

<a name="method-sole"></a>
#### `sole()` {.collection-method}

Метод `sole` повертає перший елемент колекції, який проходить заданий тест, - але тільки якщо тест проходить рівно один елемент:

```php
collect([1, 2, 3, 4])->sole(function (int $value, int $key) {
    return $value === 2;
});

// 2
```

Ви також можете передати методу `sole` пару ключ / значення, і він поверне перший елемент колекції, який відповідає заданій парі, - але тільки якщо їй відповідає рівно один елемент:

```php
$collection = collect([
    ['product' => 'Desk', 'price' => 200],
    ['product' => 'Chair', 'price' => 100],
]);

$collection->sole('product', 'Chair');

// ['product' => 'Chair', 'price' => 100]
```

Як варіант, ви можете викликати метод `sole` без аргументів, щоб отримати перший елемент колекції, якщо в ній лише один елемент:

```php
$collection = collect([
    ['product' => 'Desk', 'price' => 200],
]);

$collection->sole();

// ['product' => 'Desk', 'price' => 200]
```

Якщо в колекції немає елементів, які має повернути метод `sole`, буде викинуто виняток `\Illuminate\Support\ItemNotFoundException`. Якщо повернути слід більше ніж один елемент, буде викинуто `\Illuminate\Support\MultipleItemsFoundException`.

<a name="method-some"></a>
#### `some()` {.collection-method}

Аліас методу [contains](#method-contains).

<a name="method-sort"></a>
#### `sort()` {.collection-method}

Метод `sort` сортує колекцію. Відсортована колекція зберігає оригінальні ключі масиву, тож у прикладі нижче ми скористаємося методом [values](#method-values), щоб скинути ключі до послідовних числових індексів:

```php
$collection = collect([5, 3, 1, 2, 4]);

$sorted = $collection->sort();

$sorted->values()->all();

// [1, 2, 3, 4, 5]
```

Якщо ваші потреби в сортуванні складніші, передайте `sort` колбек із власним алгоритмом. Зверніться до документації PHP про [uasort](https://secure.php.net/manual/en/function.uasort.php#refsect1-function.uasort-parameters) - саме її метод `sort` колекції використовує під капотом.

> [!NOTE]
> Якщо вам потрібно відсортувати колекцію вкладених масивів чи об'єктів, дивіться методи [sortBy](#method-sortby) та [sortByDesc](#method-sortbydesc).

<a name="method-sortby"></a>
#### `sortBy()` {.collection-method}

Метод `sortBy` сортує колекцію за заданим ключем. Відсортована колекція зберігає оригінальні ключі масиву, тож у прикладі нижче ми скористаємося методом [values](#method-values), щоб скинути ключі до послідовних числових індексів:

```php
$collection = collect([
    ['name' => 'Desk', 'price' => 200],
    ['name' => 'Chair', 'price' => 100],
    ['name' => 'Bookcase', 'price' => 150],
]);

$sorted = $collection->sortBy('price');

$sorted->values()->all();

/*
    [
        ['name' => 'Chair', 'price' => 100],
        ['name' => 'Bookcase', 'price' => 150],
        ['name' => 'Desk', 'price' => 200],
    ]
*/
```

Другим аргументом метод `sortBy` приймає [прапорці сортування](https://www.php.net/manual/en/function.sort.php):

```php
$collection = collect([
    ['title' => 'Item 1'],
    ['title' => 'Item 12'],
    ['title' => 'Item 3'],
]);

$sorted = $collection->sortBy('title', SORT_NATURAL);

$sorted->values()->all();

/*
    [
        ['title' => 'Item 1'],
        ['title' => 'Item 3'],
        ['title' => 'Item 12'],
    ]
*/
```

Як варіант, ви можете передати власне замикання, щоб визначити, як сортувати значення колекції:

```php
$collection = collect([
    ['name' => 'Desk', 'colors' => ['Black', 'Mahogany']],
    ['name' => 'Chair', 'colors' => ['Black']],
    ['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']],
]);

$sorted = $collection->sortBy(function (array $product, int $key) {
    return count($product['colors']);
});

$sorted->values()->all();

/*
    [
        ['name' => 'Chair', 'colors' => ['Black']],
        ['name' => 'Desk', 'colors' => ['Black', 'Mahogany']],
        ['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']],
    ]
*/
```

Якщо ви хочете відсортувати колекцію за кількома атрибутами, передайте методу `sortBy` масив операцій сортування. Кожна операція має бути масивом з атрибута, за яким ви хочете сортувати, і напрямку сортування:

```php
$collection = collect([
    ['name' => 'Taylor Otwell', 'age' => 34],
    ['name' => 'Abigail Otwell', 'age' => 30],
    ['name' => 'Taylor Otwell', 'age' => 36],
    ['name' => 'Abigail Otwell', 'age' => 32],
]);

$sorted = $collection->sortBy([
    ['name', 'asc'],
    ['age', 'desc'],
]);

$sorted->values()->all();

/*
    [
        ['name' => 'Abigail Otwell', 'age' => 32],
        ['name' => 'Abigail Otwell', 'age' => 30],
        ['name' => 'Taylor Otwell', 'age' => 36],
        ['name' => 'Taylor Otwell', 'age' => 34],
    ]
*/
```

Сортуючи колекцію за кількома атрибутами, ви також можете передати замикання, які описують кожну операцію сортування:

```php
$collection = collect([
    ['name' => 'Taylor Otwell', 'age' => 34],
    ['name' => 'Abigail Otwell', 'age' => 30],
    ['name' => 'Taylor Otwell', 'age' => 36],
    ['name' => 'Abigail Otwell', 'age' => 32],
]);

$sorted = $collection->sortBy([
    fn (array $a, array $b) => $a['name'] <=> $b['name'],
    fn (array $a, array $b) => $b['age'] <=> $a['age'],
]);

$sorted->values()->all();

/*
    [
        ['name' => 'Abigail Otwell', 'age' => 32],
        ['name' => 'Abigail Otwell', 'age' => 30],
        ['name' => 'Taylor Otwell', 'age' => 36],
        ['name' => 'Taylor Otwell', 'age' => 34],
    ]
*/
```

<a name="method-sortbydesc"></a>
#### `sortByDesc()` {.collection-method}

Цей метод має ту саму сигнатуру, що й метод [sortBy](#method-sortby), але сортує колекцію у зворотному порядку.

<a name="method-sortdesc"></a>
#### `sortDesc()` {.collection-method}

Цей метод сортує колекцію у зворотному порядку відносно методу [sort](#method-sort):

```php
$collection = collect([5, 3, 1, 2, 4]);

$sorted = $collection->sortDesc();

$sorted->values()->all();

// [5, 4, 3, 2, 1]
```

На відміну від `sort`, передати замикання до `sortDesc` не можна. Натомість скористайтеся методом [sort](#method-sort) і оберніть своє порівняння.

<a name="method-sortkeys"></a>
#### `sortKeys()` {.collection-method}

Метод `sortKeys` сортує колекцію за ключами асоціативного масиву під капотом:

```php
$collection = collect([
    'id' => 22345,
    'first' => 'John',
    'last' => 'Doe',
]);

$sorted = $collection->sortKeys();

$sorted->all();

/*
    [
        'first' => 'John',
        'id' => 22345,
        'last' => 'Doe',
    ]
*/
```

<a name="method-sortkeysdesc"></a>
#### `sortKeysDesc()` {.collection-method}

Цей метод має ту саму сигнатуру, що й метод [sortKeys](#method-sortkeys), але сортує колекцію у зворотному порядку.

<a name="method-sortkeysusing"></a>
#### `sortKeysUsing()` {.collection-method}

Метод `sortKeysUsing` сортує колекцію за ключами асоціативного масиву під капотом за допомогою колбека:

```php
$collection = collect([
    'ID' => 22345,
    'first' => 'John',
    'last' => 'Doe',
]);

$sorted = $collection->sortKeysUsing('strnatcasecmp');

$sorted->all();

/*
    [
        'first' => 'John',
        'ID' => 22345,
        'last' => 'Doe',
    ]
*/
```

Колбек має бути функцією порівняння, яка повертає ціле число менше, рівне або більше за нуль. Докладніше дивіться в документації PHP про [uksort](https://www.php.net/manual/en/function.uksort.php#refsect1-function.uksort-parameters) - саме цю функцію PHP метод `sortKeysUsing` використовує під капотом.

<a name="method-splice"></a>
#### `splice()` {.collection-method}

Метод `splice` вилучає й повертає зріз елементів, що починається із вказаного індексу:

```php
$collection = collect([1, 2, 3, 4, 5]);

$chunk = $collection->splice(2);

$chunk->all();

// [3, 4, 5]

$collection->all();

// [1, 2]
```

Другим аргументом ви можете обмежити розмір отриманої колекції:

```php
$collection = collect([1, 2, 3, 4, 5]);

$chunk = $collection->splice(2, 1);

$chunk->all();

// [3]

$collection->all();

// [1, 2, 4, 5]
```

Крім того, третім аргументом ви можете передати нові елементи, які замінять вилучені з колекції:

```php
$collection = collect([1, 2, 3, 4, 5]);

$chunk = $collection->splice(2, 1, [10, 11]);

$chunk->all();

// [3]

$collection->all();

// [1, 2, 10, 11, 4, 5]
```

<a name="method-split"></a>
#### `split()` {.collection-method}

Метод `split` розбиває колекцію на задану кількість груп:

```php
$collection = collect([1, 2, 3, 4, 5]);

$groups = $collection->split(3);

$groups->all();

// [[1, 2], [3, 4], [5]]
```

<a name="method-splitin"></a>
#### `splitIn()` {.collection-method}

Метод `splitIn` розбиває колекцію на задану кількість груп, повністю заповнюючи всі групи, крім останньої, а решту віддаючи останній:

```php
$collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

$groups = $collection->splitIn(3);

$groups->all();

// [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]
```

<a name="method-sum"></a>
#### `sum()` {.collection-method}

Метод `sum` повертає суму всіх елементів колекції:

```php
collect([1, 2, 3, 4, 5])->sum();

// 15
```

Якщо колекція містить вкладені масиви чи об'єкти, передайте ключ, за яким визначатиметься, які значення підсумовувати:

```php
$collection = collect([
    ['name' => 'JavaScript: The Good Parts', 'pages' => 176],
    ['name' => 'JavaScript: The Definitive Guide', 'pages' => 1096],
]);

$collection->sum('pages');

// 1272
```

Крім того, ви можете передати власне замикання, щоб визначити, які значення колекції підсумовувати:

```php
$collection = collect([
    ['name' => 'Chair', 'colors' => ['Black']],
    ['name' => 'Desk', 'colors' => ['Black', 'Mahogany']],
    ['name' => 'Bookcase', 'colors' => ['Red', 'Beige', 'Brown']],
]);

$collection->sum(function (array $product) {
    return count($product['colors']);
});

// 6
```

<a name="method-take"></a>
#### `take()` {.collection-method}

Метод `take` повертає нову колекцію із вказаною кількістю елементів:

```php
$collection = collect([0, 1, 2, 3, 4, 5]);

$chunk = $collection->take(3);

$chunk->all();

// [0, 1, 2]
```

Ви також можете передати від'ємне ціле число, щоб узяти вказану кількість елементів з кінця колекції:

```php
$collection = collect([0, 1, 2, 3, 4, 5]);

$chunk = $collection->take(-2);

$chunk->all();

// [4, 5]
```

<a name="method-takeuntil"></a>
#### `takeUntil()` {.collection-method}

Метод `takeUntil` повертає елементи колекції, доки заданий колбек не поверне `true`:

```php
$collection = collect([1, 2, 3, 4]);

$subset = $collection->takeUntil(function (int $item) {
    return $item >= 3;
});

$subset->all();

// [1, 2]
```

Ви також можете передати методу `takeUntil` просте значення, щоб отримати елементи, доки не знайдеться задане значення:

```php
$collection = collect([1, 2, 3, 4]);

$subset = $collection->takeUntil(3);

$subset->all();

// [1, 2]
```

> [!WARNING]
> Якщо заданого значення не знайдено або колбек ніколи не повертає `true`, метод `takeUntil` поверне всі елементи колекції.

<a name="method-takewhile"></a>
#### `takeWhile()` {.collection-method}

Метод `takeWhile` повертає елементи колекції, доки заданий колбек не поверне `false`:

```php
$collection = collect([1, 2, 3, 4]);

$subset = $collection->takeWhile(function (int $item) {
    return $item < 3;
});

$subset->all();

// [1, 2]
```

> [!WARNING]
> Якщо колбек ніколи не повертає `false`, метод `takeWhile` поверне всі елементи колекції.

<a name="method-tap"></a>
#### `tap()` {.collection-method}

Метод `tap` передає колекцію до заданого колбека, дозволяючи «вклинитися» в колекцію в певній точці й щось зробити з елементами, не впливаючи на саму колекцію. Далі метод `tap` повертає колекцію:

```php
collect([2, 4, 3, 1, 5])
    ->sort()
    ->tap(function (Collection $collection) {
        Log::debug('Values after sorting', $collection->values()->all());
    })
    ->shift();

// 1
```

<a name="method-times"></a>
#### `times()` {.collection-method}

Статичний метод `times` створює нову колекцію, викликаючи задане замикання вказану кількість разів:

```php
$collection = Collection::times(10, function (int $number) {
    return $number * 9;
});

$collection->all();

// [9, 18, 27, 36, 45, 54, 63, 72, 81, 90]
```

<a name="method-toarray"></a>
#### `toArray()` {.collection-method}

Метод `toArray` перетворює колекцію на звичайний PHP-масив `array`. Якщо значення колекції - моделі [Eloquent](/docs/{{version}}/eloquent), моделі також буде перетворено на масиви:

```php
$collection = collect(['name' => 'Desk', 'price' => 200]);

$collection->toArray();

// ['name' => 'Desk', 'price' => 200]
```

> [!WARNING]
> `toArray` також перетворює на масив усі вкладені об'єкти колекції, які є екземплярами `Arrayable`. Якщо ви хочете отримати сирий масив під капотом колекції, скористайтеся натомість методом [all](#method-all).

<a name="method-tojson"></a>
#### `toJson()` {.collection-method}

Метод `toJson` перетворює колекцію на серіалізований JSON-рядок:

```php
$collection = collect(['name' => 'Desk', 'price' => 200]);

$collection->toJson();

// '{"name":"Desk","price":200}'
```

<a name="method-to-pretty-json"></a>
#### `toPrettyJson()` {.collection-method}

Метод `toPrettyJson` перетворює колекцію на відформатований JSON-рядок з опцією `JSON_PRETTY_PRINT`:

```php
$collection = collect(['name' => 'Desk', 'price' => 200]);

$collection->toPrettyJson();
```

<a name="method-transform"></a>
#### `transform()` {.collection-method}

Метод `transform` проходить колекцію й викликає заданий колбек з кожним її елементом. Елементи колекції буде замінено значеннями, які повернув колбек:

```php
$collection = collect([1, 2, 3, 4, 5]);

$collection->transform(function (int $item, int $key) {
    return $item * 2;
});

$collection->all();

// [2, 4, 6, 8, 10]
```

> [!WARNING]
> На відміну від більшості інших методів колекції, `transform` змінює саму колекцію. Якщо ви натомість хочете створити нову колекцію, скористайтеся методом [map](#method-map).

<a name="method-undot"></a>
#### `undot()` {.collection-method}

Метод `undot` розгортає одновимірну колекцію з «крапковою» нотацією на багатовимірну:

```php
$person = collect([
    'name.first_name' => 'Marie',
    'name.last_name' => 'Valentine',
    'address.line_1' => '2992 Eagle Drive',
    'address.line_2' => '',
    'address.suburb' => 'Detroit',
    'address.state' => 'MI',
    'address.postcode' => '48219'
]);

$person = $person->undot();

$person->toArray();

/*
    [
        "name" => [
            "first_name" => "Marie",
            "last_name" => "Valentine",
        ],
        "address" => [
            "line_1" => "2992 Eagle Drive",
            "line_2" => "",
            "suburb" => "Detroit",
            "state" => "MI",
            "postcode" => "48219",
        ],
    ]
*/
```

<a name="method-union"></a>
#### `union()` {.collection-method}

Метод `union` додає заданий масив до колекції. Якщо заданий масив містить ключі, які вже є в оригінальній колекції, перевагу матимуть значення оригінальної колекції:

```php
$collection = collect([1 => ['a'], 2 => ['b']]);

$union = $collection->union([3 => ['c'], 1 => ['d']]);

$union->all();

// [1 => ['a'], 2 => ['b'], 3 => ['c']]
```

<a name="method-unique"></a>
#### `unique()` {.collection-method}

Метод `unique` повертає всі унікальні елементи колекції. Повернена колекція зберігає оригінальні ключі масиву, тож у прикладі нижче ми скористаємося методом [values](#method-values), щоб скинути ключі до послідовних числових індексів:

```php
$collection = collect([1, 1, 2, 2, 3, 4, 2]);

$unique = $collection->unique();

$unique->values()->all();

// [1, 2, 3, 4]
```

Працюючи з вкладеними масивами чи об'єктами, ви можете вказати ключ, за яким визначається унікальність:

```php
$collection = collect([
    ['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'],
    ['name' => 'iPhone 5', 'brand' => 'Apple', 'type' => 'phone'],
    ['name' => 'Apple Watch', 'brand' => 'Apple', 'type' => 'watch'],
    ['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'],
    ['name' => 'Galaxy Gear', 'brand' => 'Samsung', 'type' => 'watch'],
]);

$unique = $collection->unique('brand');

$unique->values()->all();

/*
    [
        ['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'],
        ['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'],
    ]
*/
```

Нарешті, ви можете передати методу `unique` власне замикання, щоб вказати, яке значення визначає унікальність елемента:

```php
$unique = $collection->unique(function (array $item) {
    return $item['brand'].$item['type'];
});

$unique->values()->all();

/*
    [
        ['name' => 'iPhone 6', 'brand' => 'Apple', 'type' => 'phone'],
        ['name' => 'Apple Watch', 'brand' => 'Apple', 'type' => 'watch'],
        ['name' => 'Galaxy S6', 'brand' => 'Samsung', 'type' => 'phone'],
        ['name' => 'Galaxy Gear', 'brand' => 'Samsung', 'type' => 'watch'],
    ]
*/
```

Перевіряючи значення елементів, метод `unique` використовує «нестрогі» порівняння: рядок із цілим значенням вважається рівним цілому числу з тим самим значенням. Щоб фільтрувати зі «строгими» порівняннями, скористайтеся методом [uniqueStrict](#method-uniquestrict).

> [!NOTE]
> Поведінка цього методу змінюється під час роботи з [колекціями Eloquent](/docs/{{version}}/eloquent-collections#method-unique).

<a name="method-uniquestrict"></a>
#### `uniqueStrict()` {.collection-method}

Цей метод має ту саму сигнатуру, що й метод [unique](#method-unique); проте всі значення порівнюються «строго».

<a name="method-unless"></a>
#### `unless()` {.collection-method}

Метод `unless` виконає заданий колбек, якщо перший переданий методу аргумент не є `true`. До замикання буде передано екземпляр колекції та перший аргумент, переданий методу `unless`:

```php
$collection = collect([1, 2, 3]);

$collection->unless(true, function (Collection $collection, bool $value) {
    return $collection->push(4);
});

$collection->unless(false, function (Collection $collection, bool $value) {
    return $collection->push(5);
});

$collection->all();

// [1, 2, 3, 5]
```

Методу `unless` можна передати другий колбек. Він виконається, коли перший аргумент, переданий методу `unless`, є `true`:

```php
$collection = collect([1, 2, 3]);

$collection->unless(true, function (Collection $collection, bool $value) {
    return $collection->push(4);
}, function (Collection $collection, bool $value) {
    return $collection->push(5);
});

$collection->all();

// [1, 2, 3, 5]
```

Протилежність `unless` - метод [when](#method-when).

<a name="method-unlessempty"></a>
#### `unlessEmpty()` {.collection-method}

Аліас методу [whenNotEmpty](#method-whennotempty).

<a name="method-unlessnotempty"></a>
#### `unlessNotEmpty()` {.collection-method}

Аліас методу [whenEmpty](#method-whenempty).

<a name="method-unwrap"></a>
#### `unwrap()` {.collection-method}

Статичний метод `unwrap` повертає елементи колекції із заданого значення, якщо це доречно:

```php
Collection::unwrap(collect('John Doe'));

// ['John Doe']

Collection::unwrap(['John Doe']);

// ['John Doe']

Collection::unwrap('John Doe');

// 'John Doe'
```

<a name="method-value"></a>
#### `value()` {.collection-method}

Метод `value` дістає задане значення з першого елемента колекції:

```php
$collection = collect([
    ['product' => 'Desk', 'price' => 200],
    ['product' => 'Speaker', 'price' => 400],
]);

$value = $collection->value('price');

// 200
```

<a name="method-values"></a>
#### `values()` {.collection-method}

Метод `values` повертає нову колекцію з ключами, скинутими до послідовних цілих чисел:

```php
$collection = collect([
    10 => ['product' => 'Desk', 'price' => 200],
    11 => ['product' => 'Speaker', 'price' => 400],
]);

$values = $collection->values();

$values->all();

/*
    [
        0 => ['product' => 'Desk', 'price' => 200],
        1 => ['product' => 'Speaker', 'price' => 400],
    ]
*/
```

<a name="method-when"></a>
#### `when()` {.collection-method}

Метод `when` виконає заданий колбек, коли перший переданий методу аргумент є `true`. До замикання буде передано екземпляр колекції та перший аргумент, переданий методу `when`:

```php
$collection = collect([1, 2, 3]);

$collection->when(true, function (Collection $collection, bool $value) {
    return $collection->push(4);
});

$collection->when(false, function (Collection $collection, bool $value) {
    return $collection->push(5);
});

$collection->all();

// [1, 2, 3, 4]
```

Методу `when` можна передати другий колбек. Він виконається, коли перший аргумент, переданий методу `when`, є `false`:

```php
$collection = collect([1, 2, 3]);

$collection->when(false, function (Collection $collection, bool $value) {
    return $collection->push(4);
}, function (Collection $collection, bool $value) {
    return $collection->push(5);
});

$collection->all();

// [1, 2, 3, 5]
```

Протилежність `when` - метод [unless](#method-unless).

<a name="method-whenempty"></a>
#### `whenEmpty()` {.collection-method}

Метод `whenEmpty` виконає заданий колбек, коли колекція порожня:

```php
$collection = collect(['Michael', 'Tom']);

$collection->whenEmpty(function (Collection $collection) {
    return $collection->push('Adam');
});

$collection->all();

// ['Michael', 'Tom']

$collection = collect();

$collection->whenEmpty(function (Collection $collection) {
    return $collection->push('Adam');
});

$collection->all();

// ['Adam']
```

Методу `whenEmpty` можна передати друге замикання, яке виконається, коли колекція не порожня:

```php
$collection = collect(['Michael', 'Tom']);

$collection->whenEmpty(function (Collection $collection) {
    return $collection->push('Adam');
}, function (Collection $collection) {
    return $collection->push('Taylor');
});

$collection->all();

// ['Michael', 'Tom', 'Taylor']
```

Протилежність `whenEmpty` - метод [whenNotEmpty](#method-whennotempty).

<a name="method-whennotempty"></a>
#### `whenNotEmpty()` {.collection-method}

Метод `whenNotEmpty` виконає заданий колбек, коли колекція не порожня:

```php
$collection = collect(['Michael', 'Tom']);

$collection->whenNotEmpty(function (Collection $collection) {
    return $collection->push('Adam');
});

$collection->all();

// ['Michael', 'Tom', 'Adam']

$collection = collect();

$collection->whenNotEmpty(function (Collection $collection) {
    return $collection->push('Adam');
});

$collection->all();

// []
```

Методу `whenNotEmpty` можна передати друге замикання, яке виконається, коли колекція порожня:

```php
$collection = collect();

$collection->whenNotEmpty(function (Collection $collection) {
    return $collection->push('Adam');
}, function (Collection $collection) {
    return $collection->push('Taylor');
});

$collection->all();

// ['Taylor']
```

Протилежність `whenNotEmpty` - метод [whenEmpty](#method-whenempty).

<a name="method-where"></a>
#### `where()` {.collection-method}

Метод `where` фільтрує колекцію за заданою парою ключ / значення:

```php
$collection = collect([
    ['product' => 'Desk', 'price' => 200],
    ['product' => 'Chair', 'price' => 100],
    ['product' => 'Bookcase', 'price' => 150],
    ['product' => 'Door', 'price' => 100],
]);

$filtered = $collection->where('price', 100);

$filtered->all();

/*
    [
        ['product' => 'Chair', 'price' => 100],
        ['product' => 'Door', 'price' => 100],
    ]
*/
```

Перевіряючи значення елементів, метод `where` використовує «нестрогі» порівняння: рядок із цілим значенням вважається рівним цілому числу з тим самим значенням. Щоб фільтрувати зі «строгими» порівняннями, скористайтеся методом [whereStrict](#method-wherestrict), а щоб фільтрувати за значеннями `null` - методами [whereNull](#method-wherenull) та [whereNotNull](#method-wherenotnull).

За бажанням другим параметром ви можете передати оператор порівняння. Підтримуються оператори: '===', '!==', '!=', '==', '=', '<>', '>', '<', '>=' та '<=':

```php
$collection = collect([
    ['name' => 'Jim', 'platform' => 'Mac'],
    ['name' => 'Sally', 'platform' => 'Mac'],
    ['name' => 'Sue', 'platform' => 'Linux'],
]);

$filtered = $collection->where('platform', '!=', 'Linux');

$filtered->all();

/*
    [
        ['name' => 'Jim', 'platform' => 'Mac'],
        ['name' => 'Sally', 'platform' => 'Mac'],
    ]
*/
```

<a name="method-wherestrict"></a>
#### `whereStrict()` {.collection-method}

Цей метод має ту саму сигнатуру, що й метод [where](#method-where); проте всі значення порівнюються «строго».

<a name="method-wherebetween"></a>
#### `whereBetween()` {.collection-method}

Метод `whereBetween` фільтрує колекцію, перевіряючи, чи потрапляє значення вказаного елемента в заданий діапазон:

```php
$collection = collect([
    ['product' => 'Desk', 'price' => 200],
    ['product' => 'Chair', 'price' => 80],
    ['product' => 'Bookcase', 'price' => 150],
    ['product' => 'Pencil', 'price' => 30],
    ['product' => 'Door', 'price' => 100],
]);

$filtered = $collection->whereBetween('price', [100, 200]);

$filtered->all();

/*
    [
        ['product' => 'Desk', 'price' => 200],
        ['product' => 'Bookcase', 'price' => 150],
        ['product' => 'Door', 'price' => 100],
    ]
*/
```

<a name="method-wherein"></a>
#### `whereIn()` {.collection-method}

Метод `whereIn` вилучає з колекції елементи, значення яких за вказаним ключем не міститься в заданому масиві:

```php
$collection = collect([
    ['product' => 'Desk', 'price' => 200],
    ['product' => 'Chair', 'price' => 100],
    ['product' => 'Bookcase', 'price' => 150],
    ['product' => 'Door', 'price' => 100],
]);

$filtered = $collection->whereIn('price', [150, 200]);

$filtered->all();

/*
    [
        ['product' => 'Desk', 'price' => 200],
        ['product' => 'Bookcase', 'price' => 150],
    ]
*/
```

Перевіряючи значення елементів, метод `whereIn` використовує «нестрогі» порівняння: рядок із цілим значенням вважається рівним цілому числу з тим самим значенням. Щоб фільтрувати зі «строгими» порівняннями, скористайтеся методом [whereInStrict](#method-whereinstrict).

<a name="method-whereinstrict"></a>
#### `whereInStrict()` {.collection-method}

Цей метод має ту саму сигнатуру, що й метод [whereIn](#method-wherein); проте всі значення порівнюються «строго».

<a name="method-whereinstanceof"></a>
#### `whereInstanceOf()` {.collection-method}

Метод `whereInstanceOf` фільтрує колекцію за заданим типом класу:

```php
use App\Models\User;
use App\Models\Post;

$collection = collect([
    new User,
    new User,
    new Post,
]);

$filtered = $collection->whereInstanceOf(User::class);

$filtered->all();

// [App\Models\User, App\Models\User]
```

<a name="method-wherenotbetween"></a>
#### `whereNotBetween()` {.collection-method}

Метод `whereNotBetween` фільтрує колекцію, перевіряючи, чи лежить значення вказаного елемента поза заданим діапазоном:

```php
$collection = collect([
    ['product' => 'Desk', 'price' => 200],
    ['product' => 'Chair', 'price' => 80],
    ['product' => 'Bookcase', 'price' => 150],
    ['product' => 'Pencil', 'price' => 30],
    ['product' => 'Door', 'price' => 100],
]);

$filtered = $collection->whereNotBetween('price', [100, 200]);

$filtered->all();

/*
    [
        ['product' => 'Chair', 'price' => 80],
        ['product' => 'Pencil', 'price' => 30],
    ]
*/
```

<a name="method-wherenotin"></a>
#### `whereNotIn()` {.collection-method}

Метод `whereNotIn` вилучає з колекції елементи, значення яких за вказаним ключем міститься в заданому масиві:

```php
$collection = collect([
    ['product' => 'Desk', 'price' => 200],
    ['product' => 'Chair', 'price' => 100],
    ['product' => 'Bookcase', 'price' => 150],
    ['product' => 'Door', 'price' => 100],
]);

$filtered = $collection->whereNotIn('price', [150, 200]);

$filtered->all();

/*
    [
        ['product' => 'Chair', 'price' => 100],
        ['product' => 'Door', 'price' => 100],
    ]
*/
```

Перевіряючи значення елементів, метод `whereNotIn` використовує «нестрогі» порівняння: рядок із цілим значенням вважається рівним цілому числу з тим самим значенням. Щоб фільтрувати зі «строгими» порівняннями, скористайтеся методом [whereNotInStrict](#method-wherenotinstrict).

<a name="method-wherenotinstrict"></a>
#### `whereNotInStrict()` {.collection-method}

Цей метод має ту саму сигнатуру, що й метод [whereNotIn](#method-wherenotin); проте всі значення порівнюються «строго».

<a name="method-wherenotnull"></a>
#### `whereNotNull()` {.collection-method}

Метод `whereNotNull` повертає елементи колекції, у яких заданий ключ не є `null`:

```php
$collection = collect([
    ['name' => 'Desk'],
    ['name' => null],
    ['name' => 'Bookcase'],
    ['name' => 0],
    ['name' => ''],
]);

$filtered = $collection->whereNotNull('name');

$filtered->all();

/*
    [
        ['name' => 'Desk'],
        ['name' => 'Bookcase'],
        ['name' => 0],
        ['name' => ''],
    ]
*/
```

<a name="method-wherenull"></a>
#### `whereNull()` {.collection-method}

Метод `whereNull` повертає елементи колекції, у яких заданий ключ є `null`:

```php
$collection = collect([
    ['name' => 'Desk'],
    ['name' => null],
    ['name' => 'Bookcase'],
    ['name' => 0],
    ['name' => ''],
]);

$filtered = $collection->whereNull('name');

$filtered->all();

/*
    [
        ['name' => null],
    ]
*/
```

<a name="method-wrap"></a>
#### `wrap()` {.collection-method}

Статичний метод `wrap` загортає задане значення в колекцію, якщо це доречно:

```php
use Illuminate\Support\Collection;

$collection = Collection::wrap('John Doe');

$collection->all();

// ['John Doe']

$collection = Collection::wrap(['John Doe']);

$collection->all();

// ['John Doe']

$collection = Collection::wrap(collect('John Doe'));

$collection->all();

// ['John Doe']
```

<a name="method-zip"></a>
#### `zip()` {.collection-method}

Метод `zip` зливає значення заданого масиву зі значеннями оригінальної колекції за відповідними індексами:

```php
$collection = collect(['Chair', 'Desk']);

$zipped = $collection->zip([100, 200]);

$zipped->all();

// [['Chair', 100], ['Desk', 200]]
```

<a name="higher-order-messages"></a>
## Повідомлення вищого порядку

Колекції також підтримують «повідомлення вищого порядку» - скорочення для поширених дій над колекціями. Повідомлення вищого порядку надають такі методи колекції: [average](#method-average), [avg](#method-avg), [contains](#method-contains), [each](#method-each), [every](#method-every), [filter](#method-filter), [first](#method-first), [flatMap](#method-flatmap), [groupBy](#method-groupby), [keyBy](#method-keyby), [map](#method-map), [max](#method-max), [min](#method-min), [partition](#method-partition), [reject](#method-reject), [skipUntil](#method-skipuntil), [skipWhile](#method-skipwhile), [some](#method-some), [sortBy](#method-sortby), [sortByDesc](#method-sortbydesc), [sum](#method-sum), [takeUntil](#method-takeuntil), [takeWhile](#method-takewhile) та [unique](#method-unique).

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

```php
use App\Models\User;

$users = User::where('votes', '>', 500)->get();

$users->each->markAsVip();
```

Так само ми можемо скористатися повідомленням вищого порядку `sum`, щоб зібрати загальну кількість «голосів» для колекції користувачів:

```php
$users = User::where('group', 'Development')->get();

return $users->sum->votes;
```

<a name="lazy-collections"></a>
## Ліниві колекції

<a name="lazy-collection-introduction"></a>
### Вступ

> [!WARNING]
> Перш ніж вивчати ліниві колекції Laravel, приділіть трохи часу знайомству з [генераторами PHP](https://www.php.net/manual/en/language.generators.overview.php).

Щоб доповнити й без того потужний клас `Collection`, клас `LazyCollection` використовує [генератори](https://www.php.net/manual/en/language.generators.overview.php) PHP, які дозволяють працювати з дуже великими наборами даних, тримаючи споживання пам'яті низьким.

Наприклад, уявіть, що вашому застосунку потрібно обробити багатогігабайтний файл логу, скориставшись методами колекцій Laravel для розбору логів. Замість читати весь файл у пам'ять одразу ліниві колекції дозволяють тримати в пам'яті лише невелику частину файлу за раз:

```php
use App\Models\LogEntry;
use Illuminate\Support\LazyCollection;

LazyCollection::make(function () {
    $handle = fopen('log.txt', 'r');

    while (($line = fgets($handle)) !== false) {
        yield $line;
    }

    fclose($handle);
})->chunk(4)->map(function (LazyCollection $lines) {
    return LogEntry::fromLines($lines);
})->each(function (LogEntry $logEntry) {
    // Process the log entry...
});
```

Або уявіть, що вам потрібно пройти 10 000 моделей Eloquent. Зі звичайними колекціями Laravel усі 10 000 моделей Eloquent доведеться завантажити в пам'ять одночасно:

```php
use App\Models\User;

$users = User::all()->filter(function (User $user) {
    return $user->id > 500;
});
```

Проте метод `cursor` конструктора запитів повертає екземпляр `LazyCollection`. Це дозволяє й далі виконувати один-єдиний запит до бази, але тримати в пам'яті лише одну модель Eloquent за раз. У цьому прикладі колбек `filter` не виконується, доки ми справді не пройдемо кожного користувача окремо, - і це різко знижує споживання пам'яті:

```php
use App\Models\User;

$users = User::cursor()->filter(function (User $user) {
    return $user->id > 500;
});

foreach ($users as $user) {
    echo $user->id;
}
```

<a name="creating-lazy-collections"></a>
### Створення лінивих колекцій

Щоб створити екземпляр лінивої колекції, передайте методу `make` колекції функцію-генератор PHP:

```php
use Illuminate\Support\LazyCollection;

LazyCollection::make(function () {
    $handle = fopen('log.txt', 'r');

    while (($line = fgets($handle)) !== false) {
        yield $line;
    }

    fclose($handle);
});
```

<a name="the-enumerable-contract"></a>
### Контракт Enumerable

Майже всі методи, доступні в класі `Collection`, доступні й у класі `LazyCollection`. Обидва ці класи реалізують контракт `Illuminate\Support\Enumerable`, який описує такі методи:

<style>
    .collection-method-list > p {
        columns: 10.8em 3; -moz-columns: 10.8em 3; -webkit-columns: 10.8em 3;
    }

    .collection-method-list a {
        display: block;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
</style>

<div class="collection-method-list" markdown="1">

[all](#method-all)
[average](#method-average)
[avg](#method-avg)
[chunk](#method-chunk)
[chunkBy](#method-chunkby)
[chunkWhile](#method-chunkwhile)
[collapse](#method-collapse)
[collect](#method-collect)
[combine](#method-combine)
[concat](#method-concat)
[contains](#method-contains)
[containsStrict](#method-containsstrict)
[count](#method-count)
[countBy](#method-countBy)
[crossJoin](#method-crossjoin)
[dd](#method-dd)
[diff](#method-diff)
[diffAssoc](#method-diffassoc)
[diffKeys](#method-diffkeys)
[dump](#method-dump)
[duplicates](#method-duplicates)
[duplicatesStrict](#method-duplicatesstrict)
[each](#method-each)
[eachSpread](#method-eachspread)
[every](#method-every)
[except](#method-except)
[filter](#method-filter)
[first](#method-first)
[firstOrFail](#method-first-or-fail)
[firstWhere](#method-first-where)
[flatMap](#method-flatmap)
[flatten](#method-flatten)
[flip](#method-flip)
[forPage](#method-forpage)
[get](#method-get)
[groupBy](#method-groupby)
[has](#method-has)
[implode](#method-implode)
[intersect](#method-intersect)
[intersectAssoc](#method-intersectAssoc)
[intersectByKeys](#method-intersectbykeys)
[isEmpty](#method-isempty)
[isNotEmpty](#method-isnotempty)
[join](#method-join)
[keyBy](#method-keyby)
[keys](#method-keys)
[last](#method-last)
[macro](#method-macro)
[make](#method-make)
[map](#method-map)
[mapInto](#method-mapinto)
[mapSpread](#method-mapspread)
[mapToGroups](#method-maptogroups)
[mapWithKeys](#method-mapwithkeys)
[max](#method-max)
[median](#method-median)
[merge](#method-merge)
[mergeRecursive](#method-mergerecursive)
[min](#method-min)
[mode](#method-mode)
[nth](#method-nth)
[only](#method-only)
[pad](#method-pad)
[partition](#method-partition)
[pipe](#method-pipe)
[pluck](#method-pluck)
[random](#method-random)
[reduce](#method-reduce)
[reduceInto](#method-reduce-into)
[reject](#method-reject)
[replace](#method-replace)
[replaceRecursive](#method-replacerecursive)
[reverse](#method-reverse)
[search](#method-search)
[shuffle](#method-shuffle)
[skip](#method-skip)
[slice](#method-slice)
[sole](#method-sole)
[some](#method-some)
[sort](#method-sort)
[sortBy](#method-sortby)
[sortByDesc](#method-sortbydesc)
[sortKeys](#method-sortkeys)
[sortKeysDesc](#method-sortkeysdesc)
[split](#method-split)
[sum](#method-sum)
[take](#method-take)
[tap](#method-tap)
[times](#method-times)
[toArray](#method-toarray)
[toJson](#method-tojson)
[union](#method-union)
[unique](#method-unique)
[uniqueStrict](#method-uniquestrict)
[unless](#method-unless)
[unlessEmpty](#method-unlessempty)
[unlessNotEmpty](#method-unlessnotempty)
[unwrap](#method-unwrap)
[values](#method-values)
[when](#method-when)
[whenEmpty](#method-whenempty)
[whenNotEmpty](#method-whennotempty)
[where](#method-where)
[whereStrict](#method-wherestrict)
[whereBetween](#method-wherebetween)
[whereIn](#method-wherein)
[whereInStrict](#method-whereinstrict)
[whereInstanceOf](#method-whereinstanceof)
[whereNotBetween](#method-wherenotbetween)
[whereNotIn](#method-wherenotin)
[whereNotInStrict](#method-wherenotinstrict)
[wrap](#method-wrap)
[zip](#method-zip)

</div>

> [!WARNING]
> Методи, які змінюють колекцію (такі як `shift`, `pop`, `prepend` тощо), у класі `LazyCollection` **недоступні**.

<a name="lazy-collection-methods"></a>
### Методи лінивих колекцій

Окрім методів, описаних у контракті `Enumerable`, клас `LazyCollection` містить такі методи:

<a name="method-takeUntilTimeout"></a>
#### `takeUntilTimeout()` {.collection-method}

Метод `takeUntilTimeout` повертає нову ліниву колекцію, яка перебиратиме значення до вказаного моменту. Після цього моменту колекція припинить перебір:

```php
$lazyCollection = LazyCollection::times(INF)
    ->takeUntilTimeout(now()->plus(minutes: 1));

$lazyCollection->each(function (int $number) {
    dump($number);

    sleep(1);
});

// 1
// 2
// ...
// 58
// 59
```

Щоб проілюструвати використання цього методу, уявіть застосунок, який надсилає рахунки з бази даних через курсор. Ви могли б описати [заплановане завдання](/docs/{{version}}/scheduling), яке запускається кожні 15 хвилин і обробляє рахунки щонайбільше 14 хвилин:

```php
use App\Models\Invoice;
use Illuminate\Support\Carbon;

Invoice::pending()->cursor()
    ->takeUntilTimeout(
        Carbon::createFromTimestamp(LARAVEL_START)->add(14, 'minutes')
    )
    ->each(fn (Invoice $invoice) => $invoice->submit());
```

<a name="method-tapEach"></a>
#### `tapEach()` {.collection-method}

Якщо метод `each` викликає заданий колбек для кожного елемента колекції одразу, то метод `tapEach` викликає заданий колбек лише тоді, коли елементи по одному дістаються зі списку:

```php
// Nothing has been dumped so far...
$lazyCollection = LazyCollection::times(INF)->tapEach(function (int $value) {
    dump($value);
});

// Three items are dumped...
$array = $lazyCollection->take(3)->all();

// 1
// 2
// 3
```

<a name="method-throttle"></a>
#### `throttle()` {.collection-method}

Метод `throttle` пригальмовує ліниву колекцію так, що кожне значення повертається через вказану кількість секунд. Цей метод особливо корисний у ситуаціях, коли ви працюєте із зовнішніми API, які обмежують частоту вхідних запитів:

```php
use App\Models\User;

User::where('vip', true)
    ->cursor()
    ->throttle(seconds: 1)
    ->each(function (User $user) {
        // Call external API...
    });
```

<a name="method-remember"></a>
#### `remember()` {.collection-method}

Метод `remember` повертає нову ліниву колекцію, яка запам'ятає вже перебрані значення й не діставатиме їх знову під час наступних перебирань колекції:

```php
// No query has been executed yet...
$users = User::cursor()->remember();

// The query is executed...
// The first 5 users are hydrated from the database...
$users->take(5)->all();

// First 5 users come from the collection's cache...
// The rest are hydrated from the database...
$users->take(20)->all();
```

<a name="method-with-heartbeat"></a>
#### `withHeartbeat()` {.collection-method}

Метод `withHeartbeat` дозволяє виконувати колбек через регулярні проміжки часу, доки перебирається лінива колекція. Це особливо корисно для тривалих операцій, які потребують періодичних службових дій - наприклад, продовження блокувань чи надсилання оновлень прогресу:

```php
use Carbon\CarbonInterval;
use Illuminate\Support\Facades\Cache;

$lock = Cache::lock('generate-reports', seconds: 60 * 5);

if ($lock->get()) {
    try {
        Report::where('status', 'pending')
            ->lazy()
            ->withHeartbeat(
                CarbonInterval::minutes(4),
                fn () => $lock->refresh()
            )
            ->each(fn ($report) => $report->process());
    } finally {
        $lock->release();
    }
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/concurrency

# Паралельність

- [Вступ](#introduction)
- [Запуск паралельних завдань](#running-concurrent-tasks)
    - [Іменовані результати](#named-results)
    - [Тайм-аути завдань](#task-timeouts)
- [Відкладені паралельні завдання](#deferring-concurrent-tasks)

<a name="introduction"></a>
## Вступ

Іноді вам може знадобитися виконати кілька повільних завдань, які не залежать одне від одного. У багатьох випадках паралельне виконання дає значний приріст швидкодії. Фасад `Concurrency` у Laravel надає простий і зручний API для паралельного виконання замикань.

<a name="how-it-works"></a>
#### Як це працює

Laravel досягає паралельності, серіалізуючи передані замикання й надсилаючи їх до прихованої команди Artisan CLI, яка десеріалізує замикання та виконує їх у власному процесі PHP. Після виконання замикання отриманий результат серіалізується назад до батьківського процесу.

Фасад `Concurrency` підтримує три драйвери: `process` (типовий), `fork` і `sync`.

Драйвер `fork` дає кращу швидкодію порівняно з типовим `process`, але його можна використовувати лише в контексті PHP CLI, адже PHP не підтримує форкування під час веб-запитів. Перш ніж використовувати драйвер `fork`, потрібно встановити пакет `spatie/fork`:

```shell
composer require spatie/fork
```

Драйвер `sync` насамперед корисний під час тестування, коли ви хочете вимкнути будь-яку паралельність і просто виконати передані замикання послідовно в батьківському процесі.

<a name="running-concurrent-tasks"></a>
## Запуск паралельних завдань

Щоб запустити паралельні завдання, викличте метод `run` фасаду `Concurrency`. Метод `run` приймає масив замикань, які слід виконати одночасно в дочірніх процесах PHP:

```php
use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\DB;

[$userCount, $orderCount] = Concurrency::run([
    fn () => DB::table('users')->count(),
    fn () => DB::table('orders')->count(),
]);
```

Щоб скористатися конкретним драйвером, застосуйте метод `driver`:

```php
$results = Concurrency::driver('fork')->run(...);
```

Або, щоб змінити типовий драйвер паралельності, опублікуйте конфігураційний файл `concurrency` командою Artisan `config:publish` і оновіть у ньому опцію `default`:

```shell
php artisan config:publish concurrency
```

<a name="named-results"></a>
### Іменовані результати

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

```php
use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\DB;

$results = Concurrency::run([
    'users' => fn () => DB::table('users')->count(),
    'orders' => fn () => DB::table('orders')->count(),
]);

$userCount = $results['users'];
$orderCount = $results['orders'];
```

<a name="task-timeouts"></a>
### Тайм-аути завдань

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

```php
use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\DB;

[$userCount, $orderCount] = Concurrency::run([
    fn () => DB::table('users')->count(),
    fn () => DB::table('orders')->count(),
], timeout: 30);
```

Ви також можете передати екземпляр `CarbonInterval`, якщо вам більше до вподоби виразніше визначення тайм-ауту:

```php
use Illuminate\Support\Facades\Concurrency;

use function Illuminate\Support\seconds;

Concurrency::run([...], timeout: seconds(30));
```

<a name="deferring-concurrent-tasks"></a>
## Відкладені паралельні завдання

Якщо ви хочете виконати масив замикань паралельно, але вас не цікавлять їхні результати, скористайтеся методом `defer`. Коли викликано метод `defer`, передані замикання не виконуються одразу. Натомість Laravel виконає їх паралельно після того, як HTTP-відповідь буде надіслано користувачеві:

```php
use App\Services\Metrics;
use Illuminate\Support\Facades\Concurrency;

Concurrency::defer([
    fn () => Metrics::report('users'),
    fn () => Metrics::report('orders'),
]);
```

---

Джерело: https://laravelukraine.com/docs/13.x/context

# Контекст

- [Вступ](#introduction)
    - [Як це працює](#how-it-works)
- [Збереження контексту](#capturing-context)
    - [Стеки](#stacks)
- [Отримання контексту](#retrieving-context)
    - [Перевірка наявності елемента](#determining-item-existence)
- [Видалення контексту](#removing-context)
- [Прихований контекст](#hidden-context)
- [Події](#events)
    - [Дегідратація](#dehydrating)
    - [Гідратація](#hydrated)

<a name="introduction"></a>
## Вступ

Можливості «контексту» в Laravel дозволяють зберігати, отримувати та передавати інформацію крізь запити, завдання й команди, що виконуються у вашому застосунку. Ця збережена інформація також потрапляє до логів, які пише ваш застосунок, даючи глибше розуміння історії виконання коду перед появою запису в логу й дозволяючи простежити потоки виконання в розподіленій системі.

<a name="how-it-works"></a>
### Як це працює

Найкращий спосіб зрозуміти можливості контексту в Laravel - побачити їх у дії разом із вбудованими можливостями логування. Щоб почати, [додайте інформацію до контексту](#capturing-context) через фасад `Context`. У цьому прикладі ми скористаємося [`middleware`](/docs/{{version}}/middleware), щоб додавати URL запиту та унікальний ідентифікатор трасування до контексту для кожного вхідного запиту:

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;

class AddContext
{
    /**
     * Handle an incoming request.
     */
    public function handle(Request $request, Closure $next): Response
    {
        Context::add('url', $request->url());
        Context::add('trace_id', Str::uuid()->toString());

        return $next($request);
    }
}
```

Інформація, додана до контексту, автоматично додається як метадані до всіх [записів логу](/docs/{{version}}/logging), що пишуться протягом запиту. Додавання контексту саме як метаданих дозволяє відрізнити інформацію, передану окремим записам логу, від інформації, спільної через `Context`. Наприклад, уявімо, що ми пишемо такий запис:

```php
Log::info('User authenticated.', ['auth_id' => Auth::id()]);
```

Записаний лог міститиме `auth_id`, переданий запису, а також `url` і `trace_id` із контексту як метадані:

```text
User authenticated. {"auth_id":27} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}
```

Інформація, додана до контексту, також стає доступною завданням, надісланим до черги. Наприклад, уявімо, що ми надсилаємо до черги завдання `ProcessPodcast` після додавання певної інформації до контексту:

```php
// In our middleware...
Context::add('url', $request->url());
Context::add('trace_id', Str::uuid()->toString());

// In our controller...
ProcessPodcast::dispatch($podcast);
```

Коли завдання надсилається, уся інформація, що зараз зберігається в контексті, захоплюється й передається завданню. Далі, поки завдання виконується, її гідратують назад у поточний контекст. Тож якби метод `handle` нашого завдання писав до логу:

```php
class ProcessPodcast implements ShouldQueue
{
    use Queueable;

    // ...

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        Log::info('Processing podcast.', [
            'podcast_id' => $this->podcast->id,
        ]);

        // ...
    }
}
```

Отриманий запис логу міститиме інформацію, додану до контексту під час запиту, який спочатку надіслав це завдання:

```text
Processing podcast. {"podcast_id":95} {"url":"https://example.com/login","trace_id":"e04e1a11-e75c-4db3-b5b5-cfef4ef56697"}
```

Хоча ми зосередилися на вбудованих можливостях логування, наведена далі документація покаже, як контекст дозволяє передавати інформацію через межу «HTTP-запит - завдання в черзі», а також як додавати [приховані дані контексту](#hidden-context), які не потрапляють до записів логу.

<a name="capturing-context"></a>
## Збереження контексту

Ви можете зберегти інформацію в поточному контексті методом `add` фасаду `Context`:

```php
use Illuminate\Support\Facades\Context;

Context::add('key', 'value');
```

Щоб додати кілька елементів одразу, передайте методу `add` асоціативний масив:

```php
Context::add([
    'first_key' => 'value',
    'second_key' => 'value',
]);
```

Метод `add` перезапише будь-яке наявне значення з тим самим ключем. Якщо ви хочете додати інформацію лише тоді, коли ключа ще немає, скористайтеся методом `addIf`:

```php
Context::add('key', 'first');

Context::get('key');
// "first"

Context::addIf('key', 'second');

Context::get('key');
// "first"
```

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

```php
Context::increment('records_added');
Context::increment('records_added', 5);

Context::decrement('records_added');
Context::decrement('records_added', 5);
```

<a name="conditional-context"></a>
#### Умовний контекст

Метод `when` дозволяє додавати дані до контексту залежно від певної умови. Перше замикання, передане методу `when`, буде викликано, якщо умова дає `true`, а друге - якщо `false`:

```php
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Context;

Context::when(
    Auth::user()->isAdmin(),
    fn ($context) => $context->add('permissions', Auth::user()->permissions),
    fn ($context) => $context->add('permissions', []),
);
```

<a name="scoped-context"></a>
#### Контекст з обмеженою областю

Метод `scope` дає спосіб тимчасово змінити контекст під час виконання переданого колбека й відновити його початковий стан після завершення. Крім того, ви можете передати додаткові дані (другим і третім аргументами), які буде об'єднано з контекстом на час виконання замикання.

```php
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Facades\Log;

Context::add('trace_id', 'abc-999');
Context::addHidden('user_id', 123);

Context::scope(
    function () {
        Context::add('action', 'adding_friend');

        $userId = Context::getHidden('user_id');

        Log::debug("Adding user [{$userId}] to friends list.");
        // Adding user [987] to friends list.  {"trace_id":"abc-999","user_name":"taylor_otwell","action":"adding_friend"}
    },
    data: ['user_name' => 'taylor_otwell'],
    hidden: ['user_id' => 987],
);

Context::all();
// [
//     'trace_id' => 'abc-999',
// ]

Context::allHidden();
// [
//     'user_id' => 123,
// ]
```

> [!WARNING]
> Якщо об'єкт усередині контексту змінено всередині замикання зі scope, ця зміна відобразиться й поза межами області.

<a name="stacks"></a>
### Стеки

Контекст дозволяє створювати «стеки» - списки даних, збережених у порядку додавання. Додати інформацію до стека можна методом `push`:

```php
use Illuminate\Support\Facades\Context;

Context::push('breadcrumbs', 'first_value');

Context::push('breadcrumbs', 'second_value', 'third_value');

Context::get('breadcrumbs');
// [
//     'first_value',
//     'second_value',
//     'third_value',
// ]
```

Стеки корисні для збереження історичної інформації про запит - наприклад, подій, що відбуваються у вашому застосунку. Скажімо, ви можете створити слухач подій, який додаватиме до стека запис щоразу, коли виконується запит до бази, зберігаючи SQL і тривалість як кортеж:

```php
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Facades\DB;

// In AppServiceProvider.php...
DB::listen(function ($event) {
    Context::push('queries', [$event->time, $event->sql]);
});
```

Визначити, чи є значення в стеці, можна методами `stackContains` і `hiddenStackContains`:

```php
if (Context::stackContains('breadcrumbs', 'first_value')) {
    //
}

if (Context::hiddenStackContains('secrets', 'first_value')) {
    //
}
```

Методи `stackContains` і `hiddenStackContains` також приймають замикання другим аргументом, що дає більший контроль над порівнянням значень:

```php
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Str;

return Context::stackContains('breadcrumbs', function ($value) {
    return Str::startsWith($value, 'query_');
});
```

<a name="retrieving-context"></a>
## Отримання контексту

Отримати інформацію з контексту можна методом `get` фасаду `Context`:

```php
use Illuminate\Support\Facades\Context;

$value = Context::get('key');
```

Методи `only` та `except` дозволяють отримати підмножину інформації з контексту:

```php
$data = Context::only(['first_key', 'second_key']);

$data = Context::except(['first_key']);
```

Метод `pull` дозволяє отримати інформацію з контексту й одразу вилучити її звідти:

```php
$value = Context::pull('key');
```

Якщо дані контексту зберігаються у [стеці](#stacks), ви можете дістати елементи зі стека методом `pop`:

```php
Context::push('breadcrumbs', 'first_value', 'second_value');

Context::pop('breadcrumbs');
// second_value

Context::get('breadcrumbs');
// ['first_value']
```

Методи `remember` і `rememberHidden` дозволяють отримати інформацію з контексту, водночас задавши значення контексту тим, що повертає передане замикання, якщо запитаної інформації немає:

```php
$permissions = Context::remember(
    'user-permissions',
    fn () => $user->permissions,
);
```

Якщо ви хочете отримати всю збережену в контексті інформацію, викличте метод `all`:

```php
$data = Context::all();
```

<a name="determining-item-existence"></a>
### Перевірка наявності елемента

Методи `has` і `missing` дозволяють визначити, чи має контекст будь-яке значення для вказаного ключа:

```php
use Illuminate\Support\Facades\Context;

if (Context::has('key')) {
    // ...
}

if (Context::missing('key')) {
    // ...
}
```

Метод `has` поверне `true` незалежно від збереженого значення. Тож, наприклад, ключ зі значенням `null` вважатиметься присутнім:

```php
Context::add('key', null);

Context::has('key');
// true
```

<a name="removing-context"></a>
## Видалення контексту

Метод `forget` дозволяє вилучити ключ та його значення з поточного контексту:

```php
use Illuminate\Support\Facades\Context;

Context::add(['first_key' => 1, 'second_key' => 2]);

Context::forget('first_key');

Context::all();

// ['second_key' => 2]
```

Ви можете вилучити кілька ключів одразу, передавши методу `forget` масив:

```php
Context::forget(['first_key', 'second_key']);
```

<a name="hidden-context"></a>
## Прихований контекст

Контекст дозволяє зберігати «приховані» дані. Ця інформація не додається до логів і недоступна через описані вище методи отримання даних. Для роботи з прихованим контекстом контекст надає окремий набір методів:

```php
use Illuminate\Support\Facades\Context;

Context::addHidden('key', 'value');

Context::getHidden('key');
// 'value'

Context::get('key');
// null
```

«Приховані» методи дзеркалять функціональність звичайних методів, описаних вище:

```php
Context::addHidden(/* ... */);
Context::addHiddenIf(/* ... */);
Context::pushHidden(/* ... */);
Context::getHidden(/* ... */);
Context::pullHidden(/* ... */);
Context::popHidden(/* ... */);
Context::onlyHidden(/* ... */);
Context::exceptHidden(/* ... */);
Context::allHidden(/* ... */);
Context::hasHidden(/* ... */);
Context::missingHidden(/* ... */);
Context::forgetHidden(/* ... */);
```

<a name="events"></a>
## Події

Контекст надсилає дві події, що дозволяють підключитися до процесів гідратації та дегідратації контексту.

Щоб проілюструвати їх використання, уявіть, що в `middleware` вашого застосунку ви задаєте значення конфігурації `app.locale` на основі заголовка `Accept-Language` вхідного HTTP-запиту. Події контексту дозволяють зберегти це значення під час запиту й відновити його в черзі, гарантуючи, що сповіщення, надіслані з черги, матимуть правильне значення `app.locale`. Досягти цього можна за допомогою подій контексту та [прихованих](#hidden-context) даних, що й показує наведена далі документація.

<a name="dehydrating"></a>
### Дегідратація

Щоразу, коли завдання надсилається до черги, дані контексту «дегідратуються» й зберігаються разом із даними завдання. Метод `Context::dehydrating` дозволяє зареєструвати замикання, яке буде викликано під час дегідратації. У цьому замиканні ви можете змінити дані, які буде передано завданню в черзі.

Зазвичай колбеки `dehydrating` слід реєструвати в методі `boot` класу `AppServiceProvider` вашого застосунку:

```php
use Illuminate\Log\Context\Repository;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Context;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Context::dehydrating(function (Repository $context) {
        $context->addHidden('locale', Config::get('app.locale'));
    });
}
```

> [!NOTE]
> Не використовуйте фасад `Context` усередині колбека `dehydrating`, адже це змінить контекст поточного процесу. Переконайтеся, що вносите зміни лише до репозиторію, переданого колбеку.

<a name="hydrated"></a>
### Гідратація

Щоразу, коли завдання з черги починає виконуватися, будь-який контекст, переданий разом із ним, «гідратується» назад у поточний контекст. Метод `Context::hydrated` дозволяє зареєструвати замикання, яке буде викликано під час гідратації.

Зазвичай колбеки `hydrated` слід реєструвати в методі `boot` класу `AppServiceProvider` вашого застосунку:

```php
use Illuminate\Log\Context\Repository;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Context;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Context::hydrated(function (Repository $context) {
        if ($context->hasHidden('locale')) {
            Config::set('app.locale', $context->getHidden('locale'));
        }
    });
}
```

> [!NOTE]
> Не використовуйте фасад `Context` усередині колбека `hydrated` - натомість вносьте зміни лише до репозиторію, переданого колбеку.

---

Джерело: https://laravelukraine.com/docs/13.x/contracts

# Контракти

- [Вступ](#introduction)
    - [Контракти проти фасадів](#contracts-vs-facades)
- [Коли використовувати контракти](#when-to-use-contracts)
- [Як використовувати контракти](#how-to-use-contracts)
- [Довідник контрактів](#contract-reference)

<a name="introduction"></a>
## Вступ

«Контракти» Laravel - це набір інтерфейсів, що визначають основні сервіси фреймворку. Наприклад, контракт `Illuminate\Contracts\Queue\Queue` визначає методи, потрібні для постановки завдань у чергу, а контракт `Illuminate\Contracts\Mail\Mailer` - методи, потрібні для надсилання електронної пошти.

Кожен контракт має відповідну реалізацію, яку надає фреймворк. Наприклад, Laravel надає реалізацію черг із різноманітними драйверами та реалізацію поштового модуля на основі [Symfony Mailer](https://symfony.com/doc/current/mailer.html).

Усі контракти Laravel живуть у [власному репозиторії на GitHub](https://github.com/illuminate/contracts). Це дає швидкий довідник усіх доступних контрактів, а також єдиний незалежний пакет, який можна використовувати, створюючи пакети, що взаємодіють із сервісами Laravel.

<a name="contracts-vs-facades"></a>
### Контракти проти фасадів

[Фасади](/docs/{{version}}/facades) та функції-хелпери Laravel дають простий спосіб користуватися сервісами Laravel, не вказуючи типів і не розв'язуючи контракти із сервіс-контейнера. У більшості випадків кожен фасад має рівнозначний контракт.

На відміну від фасадів, які не потребують оголошення в конструкторі вашого класу, контракти дозволяють визначати явні залежності класів. Одні розробники віддають перевагу такому явному визначенню залежностей і тому обирають контракти, інші ж цінують зручність фасадів. **Загалом більшість застосунків може без проблем користуватися фасадами під час розробки.**

<a name="when-to-use-contracts"></a>
## Коли використовувати контракти

Вибір між контрактами й фасадами залежить від особистих уподобань ваших і вашої команди розробки. І контракти, і фасади дозволяють створювати надійні, добре протестовані застосунки Laravel. Вони не є взаємовиключними. Одні частини вашого застосунку можуть використовувати фасади, тоді як інші залежатимуть від контрактів. Доки ви тримаєте відповідальність класів зосередженою, практичної різниці між контрактами й фасадами майже не помітите.

Загалом більшість застосунків може без проблем користуватися фасадами під час розробки. Якщо ж ви створюєте пакет, що інтегрується з кількома PHP-фреймворками, вам може знадобитися пакет `illuminate/contracts`, щоб визначити свою інтеграцію із сервісами Laravel, не додаючи конкретних реалізацій Laravel до файлу `composer.json` вашого пакета.

<a name="how-to-use-contracts"></a>
## Як використовувати контракти

Отже, як отримати реалізацію контракту? Насправді це доволі просто.

Багато типів класів у Laravel розв'язуються через [сервіс-контейнер](/docs/{{version}}/container) - зокрема контролери, слухачі подій, `middleware`, завдання в чергах і навіть замикання маршрутів. Тож щоб отримати реалізацію контракту, достатньо вказати тип інтерфейсу в конструкторі класу, який розв'язується.

Наприклад, погляньте на цей слухач подій:

```php
<?php

namespace App\Listeners;

use App\Events\OrderWasPlaced;
use App\Models\User;
use Illuminate\Contracts\Redis\Factory;

class CacheOrderInformation
{
    /**
     * Create the event listener.
     */
    public function __construct(
        protected Factory $redis,
    ) {}

    /**
     * Handle the event.
     */
    public function handle(OrderWasPlaced $event): void
    {
        // ...
    }
}
```

Коли слухач подій розв'язується, сервіс-контейнер читає вказані типи в конструкторі класу й впроваджує відповідне значення. Щоб дізнатися більше про реєстрацію в сервіс-контейнері, перегляньте [його документацію](/docs/{{version}}/container).

<a name="contract-reference"></a>
## Довідник контрактів

Ця таблиця дає швидкий довідник усіх контрактів Laravel та рівнозначних їм фасадів:

<div class="overflow-auto">

| Контракт | Відповідний фасад |
| --- | --- |
| [Illuminate\Contracts\Auth\Access\Authorizable](https://github.com/illuminate/contracts/blob/{{version}}/Auth/Access/Authorizable.php) | &nbsp; |
| [Illuminate\Contracts\Auth\Access\Gate](https://github.com/illuminate/contracts/blob/{{version}}/Auth/Access/Gate.php) | `Gate` |
| [Illuminate\Contracts\Auth\Authenticatable](https://github.com/illuminate/contracts/blob/{{version}}/Auth/Authenticatable.php) | &nbsp; |
| [Illuminate\Contracts\Auth\CanResetPassword](https://github.com/illuminate/contracts/blob/{{version}}/Auth/CanResetPassword.php) | &nbsp; |
| [Illuminate\Contracts\Auth\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Auth/Factory.php) | `Auth` |
| [Illuminate\Contracts\Auth\Guard](https://github.com/illuminate/contracts/blob/{{version}}/Auth/Guard.php) | `Auth::guard()` |
| [Illuminate\Contracts\Auth\PasswordBroker](https://github.com/illuminate/contracts/blob/{{version}}/Auth/PasswordBroker.php) | `Password::broker()` |
| [Illuminate\Contracts\Auth\PasswordBrokerFactory](https://github.com/illuminate/contracts/blob/{{version}}/Auth/PasswordBrokerFactory.php) | `Password` |
| [Illuminate\Contracts\Auth\StatefulGuard](https://github.com/illuminate/contracts/blob/{{version}}/Auth/StatefulGuard.php) | &nbsp; |
| [Illuminate\Contracts\Auth\SupportsBasicAuth](https://github.com/illuminate/contracts/blob/{{version}}/Auth/SupportsBasicAuth.php) | &nbsp; |
| [Illuminate\Contracts\Auth\UserProvider](https://github.com/illuminate/contracts/blob/{{version}}/Auth/UserProvider.php) | &nbsp; |
| [Illuminate\Contracts\Broadcasting\Broadcaster](https://github.com/illuminate/contracts/blob/{{version}}/Broadcasting/Broadcaster.php) | `Broadcast::connection()` |
| [Illuminate\Contracts\Broadcasting\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Broadcasting/Factory.php) | `Broadcast` |
| [Illuminate\Contracts\Broadcasting\ShouldBroadcast](https://github.com/illuminate/contracts/blob/{{version}}/Broadcasting/ShouldBroadcast.php) | &nbsp; |
| [Illuminate\Contracts\Broadcasting\ShouldBroadcastNow](https://github.com/illuminate/contracts/blob/{{version}}/Broadcasting/ShouldBroadcastNow.php) | &nbsp; |
| [Illuminate\Contracts\Bus\Dispatcher](https://github.com/illuminate/contracts/blob/{{version}}/Bus/Dispatcher.php) | `Bus` |
| [Illuminate\Contracts\Bus\QueueingDispatcher](https://github.com/illuminate/contracts/blob/{{version}}/Bus/QueueingDispatcher.php) | `Bus::dispatchToQueue()` |
| [Illuminate\Contracts\Cache\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Cache/Factory.php) | `Cache` |
| [Illuminate\Contracts\Cache\Lock](https://github.com/illuminate/contracts/blob/{{version}}/Cache/Lock.php) | &nbsp; |
| [Illuminate\Contracts\Cache\LockProvider](https://github.com/illuminate/contracts/blob/{{version}}/Cache/LockProvider.php) | &nbsp; |
| [Illuminate\Contracts\Cache\Repository](https://github.com/illuminate/contracts/blob/{{version}}/Cache/Repository.php) | `Cache::driver()` |
| [Illuminate\Contracts\Cache\Store](https://github.com/illuminate/contracts/blob/{{version}}/Cache/Store.php) | &nbsp; |
| [Illuminate\Contracts\Config\Repository](https://github.com/illuminate/contracts/blob/{{version}}/Config/Repository.php) | `Config` |
| [Illuminate\Contracts\Console\Application](https://github.com/illuminate/contracts/blob/{{version}}/Console/Application.php) | &nbsp; |
| [Illuminate\Contracts\Console\Kernel](https://github.com/illuminate/contracts/blob/{{version}}/Console/Kernel.php) | `Artisan` |
| [Illuminate\Contracts\Container\Container](https://github.com/illuminate/contracts/blob/{{version}}/Container/Container.php) | `App` |
| [Illuminate\Contracts\Cookie\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Cookie/Factory.php) | `Cookie` |
| [Illuminate\Contracts\Cookie\QueueingFactory](https://github.com/illuminate/contracts/blob/{{version}}/Cookie/QueueingFactory.php) | `Cookie::queue()` |
| [Illuminate\Contracts\Database\ModelIdentifier](https://github.com/illuminate/contracts/blob/{{version}}/Database/ModelIdentifier.php) | &nbsp; |
| [Illuminate\Contracts\Debug\ExceptionHandler](https://github.com/illuminate/contracts/blob/{{version}}/Debug/ExceptionHandler.php) | &nbsp; |
| [Illuminate\Contracts\Encryption\Encrypter](https://github.com/illuminate/contracts/blob/{{version}}/Encryption/Encrypter.php) | `Crypt` |
| [Illuminate\Contracts\Events\Dispatcher](https://github.com/illuminate/contracts/blob/{{version}}/Events/Dispatcher.php) | `Event` |
| [Illuminate\Contracts\Filesystem\Cloud](https://github.com/illuminate/contracts/blob/{{version}}/Filesystem/Cloud.php) | `Storage::cloud()` |
| [Illuminate\Contracts\Filesystem\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Filesystem/Factory.php) | `Storage` |
| [Illuminate\Contracts\Filesystem\Filesystem](https://github.com/illuminate/contracts/blob/{{version}}/Filesystem/Filesystem.php) | `Storage::disk()` |
| [Illuminate\Contracts\Foundation\Application](https://github.com/illuminate/contracts/blob/{{version}}/Foundation/Application.php) | `App` |
| [Illuminate\Contracts\Hashing\Hasher](https://github.com/illuminate/contracts/blob/{{version}}/Hashing/Hasher.php) | `Hash` |
| [Illuminate\Contracts\Http\Kernel](https://github.com/illuminate/contracts/blob/{{version}}/Http/Kernel.php) | &nbsp; |
| [Illuminate\Contracts\Mail\Mailable](https://github.com/illuminate/contracts/blob/{{version}}/Mail/Mailable.php) | &nbsp; |
| [Illuminate\Contracts\Mail\Mailer](https://github.com/illuminate/contracts/blob/{{version}}/Mail/Mailer.php) | `Mail` |
| [Illuminate\Contracts\Mail\MailQueue](https://github.com/illuminate/contracts/blob/{{version}}/Mail/MailQueue.php) | `Mail::queue()` |
| [Illuminate\Contracts\Notifications\Dispatcher](https://github.com/illuminate/contracts/blob/{{version}}/Notifications/Dispatcher.php) | `Notification`|
| [Illuminate\Contracts\Notifications\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Notifications/Factory.php) | `Notification` |
| [Illuminate\Contracts\Pagination\LengthAwarePaginator](https://github.com/illuminate/contracts/blob/{{version}}/Pagination/LengthAwarePaginator.php) | &nbsp; |
| [Illuminate\Contracts\Pagination\Paginator](https://github.com/illuminate/contracts/blob/{{version}}/Pagination/Paginator.php) | &nbsp; |
| [Illuminate\Contracts\Pipeline\Hub](https://github.com/illuminate/contracts/blob/{{version}}/Pipeline/Hub.php) | &nbsp; |
| [Illuminate\Contracts\Pipeline\Pipeline](https://github.com/illuminate/contracts/blob/{{version}}/Pipeline/Pipeline.php) | `Pipeline` |
| [Illuminate\Contracts\Queue\EntityResolver](https://github.com/illuminate/contracts/blob/{{version}}/Queue/EntityResolver.php) | &nbsp; |
| [Illuminate\Contracts\Queue\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Queue/Factory.php) | `Queue` |
| [Illuminate\Contracts\Queue\Job](https://github.com/illuminate/contracts/blob/{{version}}/Queue/Job.php) | &nbsp; |
| [Illuminate\Contracts\Queue\Monitor](https://github.com/illuminate/contracts/blob/{{version}}/Queue/Monitor.php) | `Queue` |
| [Illuminate\Contracts\Queue\Queue](https://github.com/illuminate/contracts/blob/{{version}}/Queue/Queue.php) | `Queue::connection()` |
| [Illuminate\Contracts\Queue\QueueableCollection](https://github.com/illuminate/contracts/blob/{{version}}/Queue/QueueableCollection.php) | &nbsp; |
| [Illuminate\Contracts\Queue\QueueableEntity](https://github.com/illuminate/contracts/blob/{{version}}/Queue/QueueableEntity.php) | &nbsp; |
| [Illuminate\Contracts\Queue\ShouldQueue](https://github.com/illuminate/contracts/blob/{{version}}/Queue/ShouldQueue.php) | &nbsp; |
| [Illuminate\Contracts\Redis\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Redis/Factory.php) | `Redis` |
| [Illuminate\Contracts\Routing\BindingRegistrar](https://github.com/illuminate/contracts/blob/{{version}}/Routing/BindingRegistrar.php) | `Route` |
| [Illuminate\Contracts\Routing\Registrar](https://github.com/illuminate/contracts/blob/{{version}}/Routing/Registrar.php) | `Route` |
| [Illuminate\Contracts\Routing\ResponseFactory](https://github.com/illuminate/contracts/blob/{{version}}/Routing/ResponseFactory.php) | `Response` |
| [Illuminate\Contracts\Routing\UrlGenerator](https://github.com/illuminate/contracts/blob/{{version}}/Routing/UrlGenerator.php) | `URL` |
| [Illuminate\Contracts\Routing\UrlRoutable](https://github.com/illuminate/contracts/blob/{{version}}/Routing/UrlRoutable.php) | &nbsp; |
| [Illuminate\Contracts\Session\Session](https://github.com/illuminate/contracts/blob/{{version}}/Session/Session.php) | `Session::driver()` |
| [Illuminate\Contracts\Support\Arrayable](https://github.com/illuminate/contracts/blob/{{version}}/Support/Arrayable.php) | &nbsp; |
| [Illuminate\Contracts\Support\Htmlable](https://github.com/illuminate/contracts/blob/{{version}}/Support/Htmlable.php) | &nbsp; |
| [Illuminate\Contracts\Support\Jsonable](https://github.com/illuminate/contracts/blob/{{version}}/Support/Jsonable.php) | &nbsp; |
| [Illuminate\Contracts\Support\MessageBag](https://github.com/illuminate/contracts/blob/{{version}}/Support/MessageBag.php) | &nbsp; |
| [Illuminate\Contracts\Support\MessageProvider](https://github.com/illuminate/contracts/blob/{{version}}/Support/MessageProvider.php) | &nbsp; |
| [Illuminate\Contracts\Support\Renderable](https://github.com/illuminate/contracts/blob/{{version}}/Support/Renderable.php) | &nbsp; |
| [Illuminate\Contracts\Support\Responsable](https://github.com/illuminate/contracts/blob/{{version}}/Support/Responsable.php) | &nbsp; |
| [Illuminate\Contracts\Translation\Loader](https://github.com/illuminate/contracts/blob/{{version}}/Translation/Loader.php) | &nbsp; |
| [Illuminate\Contracts\Translation\Translator](https://github.com/illuminate/contracts/blob/{{version}}/Translation/Translator.php) | `Lang` |
| [Illuminate\Contracts\Validation\Factory](https://github.com/illuminate/contracts/blob/{{version}}/Validation/Factory.php) | `Validator` |
| [Illuminate\Contracts\Validation\ValidatesWhenResolved](https://github.com/illuminate/contracts/blob/{{version}}/Validation/ValidatesWhenResolved.php) | &nbsp; |
| [Illuminate\Contracts\Validation\ValidationRule](https://github.com/illuminate/contracts/blob/{{version}}/Validation/ValidationRule.php) | &nbsp; |
| [Illuminate\Contracts\Validation\Validator](https://github.com/illuminate/contracts/blob/{{version}}/Validation/Validator.php) | `Validator::make()` |
| [Illuminate\Contracts\View\Engine](https://github.com/illuminate/contracts/blob/{{version}}/View/Engine.php) | &nbsp; |
| [Illuminate\Contracts\View\Factory](https://github.com/illuminate/contracts/blob/{{version}}/View/Factory.php) | `View` |
| [Illuminate\Contracts\View\View](https://github.com/illuminate/contracts/blob/{{version}}/View/View.php) | `View::make()` |

</div>

---

Джерело: https://laravelukraine.com/docs/13.x/events

# Події

- [Вступ](#introduction)
- [Генерація подій і слухачів](#generating-events-and-listeners)
- [Реєстрація подій і слухачів](#registering-events-and-listeners)
    - [Виявлення подій](#event-discovery)
    - [Реєстрація подій вручну](#manually-registering-events)
    - [Слухачі на замиканнях](#closure-listeners)
- [Опис подій](#defining-events)
- [Опис слухачів](#defining-listeners)
- [Слухачі подій у черзі](#queued-event-listeners)
    - [Ручна взаємодія з чергою](#manually-interacting-with-the-queue)
    - [Слухачі в черзі та транзакції бази даних](#queued-event-listeners-and-database-transactions)
    - [Middleware слухачів у черзі](#queued-listener-middleware)
    - [Зашифровані слухачі в черзі](#encrypted-queued-listeners)
    - [Унікальні слухачі подій](#unique-event-listeners)
        - [Тримати слухачів унікальними до початку обробки](#keeping-listeners-unique-until-processing-begins)
        - [Блокування унікальних слухачів](#unique-listener-locks)
    - [Слухачі подій з дебаунсом](#debounced-event-listeners)
    - [Обробка невдалих завдань](#handling-failed-jobs)
- [Диспетчеризація подій](#dispatching-events)
    - [Диспетчеризація подій після транзакцій бази даних](#dispatching-events-after-database-transactions)
    - [Відкладення подій](#deferring-events)
- [Підписники подій](#event-subscribers)
    - [Написання підписників подій](#writing-event-subscribers)
    - [Реєстрація підписників подій](#registering-event-subscribers)
- [Тестування](#testing)
    - [Підміна частини подій](#faking-a-subset-of-events)
    - [Обмежені підміни подій](#scoped-event-fakes)

<a name="introduction"></a>
## Вступ

Події Laravel надають просту реалізацію патерну «спостерігач», яка дозволяє підписуватися на різні події вашого застосунку й слухати їх. Класи подій зазвичай зберігаються в каталозі `app/Events`, а їхні слухачі - в `app/Listeners`. Не переймайтеся, якщо цих каталогів у вашому застосунку немає: їх буде створено, коли ви генеруватимете події та слухачів консольними командами Artisan.

Події - чудовий спосіб розчепити різні частини застосунку, адже одна подія може мати кілька слухачів, які не залежать одне від одного. Наприклад, ви можете хотіти надсилати користувачеві сповіщення у Slack щоразу, коли замовлення відправлено. Замість зчіплювати код обробки замовлень із кодом сповіщень у Slack, ви можете підняти подію `App\Events\OrderShipped`, яку отримає слухач і надішле сповіщення у Slack.

<a name="generating-events-and-listeners"></a>
## Генерація подій і слухачів

Щоб швидко згенерувати події та слухачів, скористайтеся командами Artisan `make:event` і `make:listener`:

```shell
php artisan make:event PodcastProcessed

php artisan make:listener SendPodcastNotification --event=PodcastProcessed
```

Для зручності ви можете викликати команди Artisan `make:event` і `make:listener` і без додаткових аргументів. Тоді Laravel сам запитає назву класу, а для слухача - подію, яку той має слухати:

```shell
php artisan make:event

php artisan make:listener
```

<a name="registering-events-and-listeners"></a>
## Реєстрація подій і слухачів

<a name="event-discovery"></a>
### Виявлення подій

За замовчуванням Laravel автоматично знаходить і реєструє ваших слухачів подій, скануючи каталог `Listeners` вашого застосунку. Коли Laravel знаходить у класі слухача метод, назва якого починається з `handle` або `__invoke`, він реєструє ці методи як слухачів події, тип якої вказано в сигнатурі методу:

```php
use App\Events\PodcastProcessed;

class SendPodcastNotification
{
    /**
     * Handle the event.
     */
    public function handle(PodcastProcessed $event): void
    {
        // ...
    }
}
```

Ви можете слухати кілька подій за допомогою об'єднаних типів PHP:

```php
/**
 * Handle the event.
 */
public function handle(PodcastProcessed|PodcastPublished $event): void
{
    // ...
}
```

Якщо ви плануєте зберігати слухачів в іншому каталозі чи в кількох каталогах, вкажіть Laravel сканувати ці каталоги методом `withEvents` у файлі `bootstrap/app.php` вашого застосунку:

```php
->withEvents(discover: [
    __DIR__.'/../app/Domain/Orders/Listeners',
])
```

Ви можете шукати слухачів у кількох схожих каталогах, скориставшись символом `*` як підстановкою:

```php
->withEvents(discover: [
    __DIR__.'/../app/Domain/*/Listeners',
])
```

Команда `event:list` дозволяє переглянути всіх слухачів, зареєстрованих у вашому застосунку:

```shell
php artisan event:list
```

<a name="event-discovery-in-production"></a>
#### Виявлення подій у продакшені

Щоб пришвидшити застосунок, вам варто закешувати маніфест усіх слухачів командами Artisan `optimize` або `event:cache`. Зазвичай цю команду виконують у межах [процесу розгортання](/docs/{{version}}/deployment#optimization) вашого застосунку. Фреймворк використовуватиме цей маніфест, щоб пришвидшити реєстрацію подій. Команда `event:clear` дозволяє знищити кеш подій.

<a name="dynamic-event-discovery"></a>
#### Динамічне виявлення подій

Щоб динамічно керувати тим, чи буде виявлено конкретного слухача, реалізуйте в класі слухача інтерфейс `ShouldBeDiscovered` і опишіть метод `shouldBeDiscovered`, який повертає булеве значення. Якщо метод поверне `false`, слухача не буде зареєстровано під час виявлення подій:

```php
use Illuminate\Contracts\Events\ShouldBeDiscovered;

class SendPodcastNotification implements ShouldBeDiscovered
{
    /**
     * Handle the event.
     */
    public function handle(PodcastProcessed $event): void
    {
        // ...
    }

    /**
     * Determine if the listener should be discovered.
     */
    public static function shouldBeDiscovered(): bool
    {
        return app()->environment('production');
    }
}
```

<a name="manually-registering-events"></a>
### Реєстрація подій вручну

За допомогою фасаду `Event` ви можете вручну реєструвати події та відповідних слухачів у методі `boot` вашого `AppServiceProvider`:

```php
use App\Domain\Orders\Events\PodcastProcessed;
use App\Domain\Orders\Listeners\SendPodcastNotification;
use Illuminate\Support\Facades\Event;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Event::listen(
        PodcastProcessed::class,
        SendPodcastNotification::class,
    );
}
```

Команда `event:list` дозволяє переглянути всіх слухачів, зареєстрованих у вашому застосунку:

```shell
php artisan event:list
```

<a name="closure-listeners"></a>
### Слухачі на замиканнях

Зазвичай слухачів описують класами; проте ви можете вручну зареєструвати й слухачів подій на замиканнях у методі `boot` вашого `AppServiceProvider`:

```php
use App\Events\PodcastProcessed;
use Illuminate\Support\Facades\Event;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Event::listen(function (PodcastProcessed $event) {
        // ...
    });
}
```

<a name="queueable-anonymous-event-listeners"></a>
#### Анонімні слухачі подій у черзі

Реєструючи слухачів подій на замиканнях, ви можете загорнути замикання слухача у функцію `Illuminate\Events\queueable`, щоб Laravel виконував слухача через [чергу](/docs/{{version}}/queues):

```php
use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Event::listen(queueable(function (PodcastProcessed $event) {
        // ...
    }));
}
```

Як і для завдань у черзі, ви можете скористатися методами `onConnection`, `onQueue` та `delay`, щоб налаштувати виконання слухача в черзі:

```php
Event::listen(queueable(function (PodcastProcessed $event) {
    // ...
})->onConnection('redis')->onQueue('podcasts')->delay(now()->plus(seconds: 10)));
```

Якщо ви хочете обробляти невдачі анонімних слухачів у черзі, передайте замикання методу `catch` під час опису слухача `queueable`. Це замикання отримає екземпляр події та екземпляр `Throwable`, який спричинив невдачу слухача:

```php
use App\Events\PodcastProcessed;
use function Illuminate\Events\queueable;
use Illuminate\Support\Facades\Event;
use Throwable;

Event::listen(queueable(function (PodcastProcessed $event) {
    // ...
})->catch(function (PodcastProcessed $event, Throwable $e) {
    // The queued listener failed...
}));
```

<a name="wildcard-event-listeners"></a>
#### Слухачі подій з підстановкою

Ви також можете реєструвати слухачів, використовуючи символ `*` як підстановочний параметр, - це дозволяє ловити кілька подій одним слухачем. Слухачі з підстановкою першим аргументом отримують ім'я події, а другим - увесь масив даних події:

```php
Event::listen('event.*', function (string $eventName, array $data) {
    // ...
});
```

<a name="defining-events"></a>
## Опис подій

Клас події - це, по суті, контейнер даних, який містить інформацію, пов'язану з подією. Наприклад, припустімо, що подія `App\Events\OrderShipped` отримує об'єкт [Eloquent ORM](/docs/{{version}}/eloquent):

```php
<?php

namespace App\Events;

use App\Models\Order;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderShipped
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     */
    public function __construct(
        public Order $order,
    ) {}
}
```

Як бачите, цей клас події не містить логіки. Це контейнер для екземпляра `App\Models\Order`, який було куплено. Трейт `SerializesModels`, який використовує подія, коректно серіалізує будь-які моделі Eloquent, якщо об'єкт події серіалізується PHP-функцією `serialize`, - наприклад, коли ви користуєтеся [слухачами в черзі](#queued-event-listeners).

<a name="defining-listeners"></a>
## Опис слухачів

Далі погляньмо на слухача для нашої прикладної події. Слухачі подій отримують екземпляри подій у своєму методі `handle`. Команда Artisan `make:listener`, викликана з опцією `--event`, автоматично імпортує потрібний клас події й вкаже тип події в методі `handle`. У методі `handle` ви можете виконати будь-які дії, потрібні для реакції на подію:

```php
<?php

namespace App\Listeners;

use App\Events\OrderShipped;

class SendShipmentNotification
{
    /**
     * Create the event listener.
     */
    public function __construct() {}

    /**
     * Handle the event.
     */
    public function handle(OrderShipped $event): void
    {
        // Access the order using $event->order...
    }
}
```

> [!NOTE]
> Ваші слухачі подій можуть також вказувати типи потрібних їм залежностей у конструкторах. Усі слухачі подій розв'язуються через [сервіс-контейнер](/docs/{{version}}/container) Laravel, тож залежності буде впроваджено автоматично.

<a name="stopping-the-propagation-of-an-event"></a>
#### Припинення поширення події

Інколи вам може захотітися припинити поширення події до інших слухачів. Для цього поверніть `false` з методу `handle` вашого слухача.

<a name="queued-event-listeners"></a>
## Слухачі подій у черзі

Ставити слухачів у чергу корисно, якщо слухач виконуватиме повільну задачу - наприклад, надсилатиме лист чи робитиме HTTP-запит. Перш ніж користуватися слухачами в черзі, обов'язково [налаштуйте чергу](/docs/{{version}}/queues) і запустіть воркер черги на сервері чи в локальному середовищі розробки.

Щоб вказати, що слухача слід ставити в чергу, додайте до класу слухача інтерфейс `ShouldQueue`. Слухачі, згенеровані командами Artisan `make:listener`, уже мають цей інтерфейс імпортованим у поточний простір імен, тож ви можете одразу ним скористатися:

```php
<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendShipmentNotification implements ShouldQueue
{
    // ...
}
```

Ось і все! Тепер, коли диспетчеризується подія, яку обробляє цей слухач, диспетчер подій автоматично поставить слухача в чергу через [систему черг](/docs/{{version}}/queues) Laravel. Якщо під час виконання слухача чергою не викинуто винятків, завдання в черзі буде автоматично видалено після завершення обробки.

<a name="customizing-the-queue-connection-queue-name"></a>
#### Налаштування підключення, імені та затримки черги

Якщо ви хочете змінити підключення черги, ім'я черги чи час затримки для слухача подій, скористайтеся атрибутами `Connection`, `Queue` та `Delay` у класі слухача:

```php
<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\Delay;
use Illuminate\Queue\Attributes\Queue;

#[Connection('sqs')]
#[Queue('listeners')]
#[Delay(60)]
class SendShipmentNotification implements ShouldQueue
{
    // ...
}
```
Якщо ви хочете задати підключення черги, ім'я черги чи затримку слухача під час виконання, опишіть у слухачі методи `viaConnection`, `viaQueue` або `withDelay`:

```php
/**
 * Get the name of the listener's queue connection.
 */
public function viaConnection(): string
{
    return 'sqs';
}

/**
 * Get the name of the listener's queue.
 */
public function viaQueue(): string
{
    return 'listeners';
}

/**
 * Get the number of seconds before the job should be processed.
 */
public function withDelay(OrderShipped $event): int
{
    return $event->highPriority ? 0 : 60;
}
```

Якщо ви хочете, щоб усі ваші слухачі у черзі використовували ту саму чергу без налаштування кожного класу слухача окремо, можете натомість [спрямувати контракт `ShouldQueue` у чергу](/docs/{{version}}/queues#queue-routing).

<a name="conditionally-queueing-listeners"></a>
#### Умовна постановка слухачів у чергу

Інколи вам може знадобитися визначити, чи слід ставити слухача в чергу, на основі даних, доступних лише під час виконання. Для цього до слухача можна додати метод `shouldQueue`, який визначатиме, чи ставити слухача в чергу. Якщо метод `shouldQueue` поверне `false`, слухача в чергу не поставлять:

```php
<?php

namespace App\Listeners;

use App\Events\OrderCreated;
use Illuminate\Contracts\Queue\ShouldQueue;

class RewardGiftCard implements ShouldQueue
{
    /**
     * Reward a gift card to the customer.
     */
    public function handle(OrderCreated $event): void
    {
        // ...
    }

    /**
     * Determine whether the listener should be queued.
     */
    public function shouldQueue(OrderCreated $event): bool
    {
        return $event->order->subtotal >= 5000;
    }
}
```

<a name="manually-interacting-with-the-queue"></a>
### Ручна взаємодія з чергою

Якщо вам потрібен ручний доступ до методів `delete` і `release` завдання черги, що стоїть за слухачем, скористайтеся трейтом `Illuminate\Queue\InteractsWithQueue`. Цей трейт імпортується у згенерованих слухачах за замовчуванням і надає доступ до цих методів:

```php
<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;

class SendShipmentNotification implements ShouldQueue
{
    use InteractsWithQueue;

    /**
     * Handle the event.
     */
    public function handle(OrderShipped $event): void
    {
        if ($condition) {
            $this->release(30);
        }
    }
}
```

<a name="queued-event-listeners-and-database-transactions"></a>
### Слухачі в черзі та транзакції бази даних

Коли слухачі в черзі диспетчеризуються всередині транзакцій бази даних, черга може обробити їх ще до того, як транзакцію буде зафіксовано. Коли таке трапляється, будь-які зміни, які ви внесли до моделей чи записів у базі під час транзакції, ще можуть не бути в базі. Ба більше, будь-які моделі чи записи, створені всередині транзакції, можуть у базі не існувати. Якщо ваш слухач залежить від цих моделей, під час обробки завдання, яке диспетчеризує слухача в черзі, можуть виникнути несподівані помилки.

Якщо опція конфігурації `after_commit` вашого підключення черги має значення `false`, ви все одно можете вказати, що конкретного слухача в черзі слід диспетчеризувати після фіксації всіх відкритих транзакцій бази даних, - реалізуйте в класі слухача інтерфейс `ShouldQueueAfterCommit`:

```php
<?php

namespace App\Listeners;

use Illuminate\Contracts\Queue\ShouldQueueAfterCommit;
use Illuminate\Queue\InteractsWithQueue;

class SendShipmentNotification implements ShouldQueueAfterCommit
{
    use InteractsWithQueue;
}
```

> [!NOTE]
> Щоб дізнатися більше про обхід цих проблем, перегляньте документацію про [завдання в черзі та транзакції бази даних](/docs/{{version}}/queues#jobs-and-database-transactions).

<a name="queued-listener-middleware"></a>
### Middleware слухачів у черзі

Слухачі в черзі можуть також користуватися [middleware завдань](/docs/{{version}}/queues#job-middleware). Middleware завдань дозволяє огорнути виконання слухачів у черзі власною логікою, зменшивши кількість шаблонного коду в самих слухачах. Створивши middleware завдання, ви можете причепити його до слухача, повернувши з методу `middleware` слухача:

```php
<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use App\Jobs\Middleware\RateLimited;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendShipmentNotification implements ShouldQueue
{
    /**
     * Handle the event.
     */
    public function handle(OrderShipped $event): void
    {
        // Process the event...
    }

    /**
     * Get the middleware the listener should pass through.
     *
     * @return array<int, object>
     */
    public function middleware(OrderShipped $event): array
    {
        return [new RateLimited];
    }
}
```

<a name="encrypted-queued-listeners"></a>
#### Зашифровані слухачі в черзі

Laravel дозволяє забезпечити приватність і цілісність даних слухача в черзі за допомогою [шифрування](/docs/{{version}}/encryption). Для початку просто додайте до класу слухача інтерфейс `ShouldBeEncrypted`. Щойно цей інтерфейс додано до класу, Laravel автоматично зашифрує вашого слухача перед тим, як покласти його в чергу:

```php
<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;

class SendShipmentNotification implements ShouldQueue, ShouldBeEncrypted
{
    // ...
}
```

<a name="unique-event-listeners"></a>
### Унікальні слухачі подій

> [!WARNING]
> Унікальні слухачі потребують драйвера кешу, який підтримує [блокування](/docs/{{version}}/cache#atomic-locks). Наразі атомарні блокування підтримують драйвери кешу `memcached`, `redis`, `dynamodb`, `database`, `file` та `array`.

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

```php
<?php

namespace App\Listeners;

use App\Events\LicenseSaved;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;

class AcquireProductKey implements ShouldQueue, ShouldBeUnique
{
    public function __invoke(LicenseSaved $event): void
    {
        // ...
    }
}
```

У прикладі вище слухач `AcquireProductKey` є унікальним. Тож слухача не поставлять у чергу, якщо інший його екземпляр уже в черзі й не завершив обробку. Це гарантує, що для кожної ліцензії буде отримано лише один ключ продукту, навіть якщо ліцензію збережено кілька разів поспіль.

У певних випадках вам може знадобитися задати конкретний «ключ», який робить слухача унікальним, або вказати таймаут, після якого слухач перестає бути унікальним. Для цього опишіть у класі слухача властивості або методи `uniqueId` та `uniqueFor`. Методи отримують екземпляр події, тож ви можете скористатися даними події, щоб побудувати повернене значення:

```php
<?php

namespace App\Listeners;

use App\Events\LicenseSaved;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;

class AcquireProductKey implements ShouldQueue, ShouldBeUnique
{
    /**
     * The number of seconds after which the listener's unique lock will be released.
     *
     * @var int
     */
    public $uniqueFor = 3600;

    public function __invoke(LicenseSaved $event): void
    {
        // ...
    }

    /**
     * Get the unique ID for the listener.
     */
    public function uniqueId(LicenseSaved $event): string
    {
        return 'listener:'.$event->license->id;
    }
}
```

У прикладі вище слухач `AcquireProductKey` унікальний за ID ліцензії. Тож будь-які нові диспетчеризації слухача для тієї самої ліцензії ігноруватимуться, доки наявний слухач не завершить обробку. Це не дає отримати дублікати ключів продукту для однієї ліцензії. Крім того, якщо наявного слухача не оброблено протягом години, унікальне блокування буде звільнено, і в чергу можна буде поставити іншого слухача з тим самим унікальним ключем.

> [!WARNING]
> Якщо ваш застосунок диспетчеризує події з кількох вебсерверів чи контейнерів, подбайте, щоб усі сервери спілкувалися з одним центральним сервером кешу, - тоді Laravel зможе точно визначити, чи є слухач унікальним.

<a name="keeping-listeners-unique-until-processing-begins"></a>
#### Тримати слухачів унікальними до початку обробки

За замовчуванням унікальні слухачі «розблоковуються» після того, як слухач завершить обробку або вичерпає всі спроби. Проте бувають ситуації, коли ви хочете, щоб слухач розблоковувався безпосередньо перед обробкою. Для цього ваш слухач має реалізувати контракт `ShouldBeUniqueUntilProcessing` замість `ShouldBeUnique`:

```php
<?php

namespace App\Listeners;

use App\Events\LicenseSaved;
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
use Illuminate\Contracts\Queue\ShouldQueue;

class AcquireProductKey implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
    // ...
}
```

<a name="unique-listener-locks"></a>
#### Блокування унікальних слухачів

Під капотом, коли диспетчеризується слухач `ShouldBeUnique`, Laravel намагається отримати [блокування](/docs/{{version}}/cache#atomic-locks) з ключем `uniqueId`. Якщо блокування вже утримується, слухача не диспетчеризують. Це блокування звільняється, коли слухач завершує обробку або вичерпує всі спроби. За замовчуванням Laravel використовує для цього блокування драйвер кешу за замовчуванням. Проте, якщо ви хочете отримувати блокування іншим драйвером, опишіть метод `uniqueVia`, який поверне потрібний драйвер кешу:

```php
<?php

namespace App\Listeners;

use App\Events\LicenseSaved;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Facades\Cache;

class AcquireProductKey implements ShouldQueue, ShouldBeUnique
{
    // ...

    /**
     * Get the cache driver for the unique listener lock.
     */
    public function uniqueVia(LicenseSaved $event): Repository
    {
        return Cache::driver('redis');
    }
}
```

> [!NOTE]
> Якщо вам потрібно лише обмежити одночасну обробку слухача, скористайтеся натомість middleware завдання [WithoutOverlapping](/docs/{{version}}/queues#preventing-job-overlaps).

<a name="debounced-event-listeners"></a>
### Слухачі подій з дебаунсом

Іноді вам може знадобитися обробити лише останній екземпляр події, яка надсилається повторно протягом короткого періоду. Ви можете зробити це, додавши атрибут `DebounceFor` до слухача черги:

```php
<?php

namespace App\Listeners;

use App\Events\ProductUpdated;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\DebounceFor;

#[DebounceFor(30)]
class UpdateProductSearchIndex implements ShouldQueue
{
    /**
     * Handle the event.
     */
    public function handle(ProductUpdated $event): void
    {
        // Update the product's search index...
    }

    /**
     * Get the debounce ID for the listener.
     */
    public function debounceId(ProductUpdated $event): string
    {
        return (string) $event->product->getKey();
    }
}
```

У наведеному прикладі повторне надсилання подій `ProductUpdated` для того самого продукту протягом `30` секунд призведе до дебаунсу слухача, щоб обробити лише останню подію. Різні ідентифікатори дебаунсу обробляються незалежно.

Щоб обмежити, як довго часто надсилана подія може відкладати слухача, передайте атрибуту `DebounceFor` аргумент `maxWait`:

```php
#[DebounceFor(30, maxWait: 120)]
class UpdateProductSearchIndex implements ShouldQueue
{
    // ...
}
```

Сховище кешу, у якому відстежується дебаунс, налаштовується методом `debounceVia` у слухачі. Метод отримує екземпляр події і має повернути репозиторій кешу:

```php
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Facades\Cache;

public function debounceVia(ProductUpdated $event): Repository
{
    return Cache::driver('redis');
}
```

Слухачі з дебаунсом та унікальні слухачі є взаємовиключними. Слухач, який використовує атрибут `DebounceFor`, не повинен реалізовувати `ShouldBeUnique`.

> [!WARNING]
> Якщо ваш застосунок надсилає події з кількох веб-серверів або контейнерів, ви маєте переконатися, що всі ваші сервери взаємодіють з одним і тим самим центральним сервером кешу.

<a name="handling-failed-jobs"></a>
### Обробка невдалих завдань

Інколи ваші слухачі подій у черзі можуть зазнавати невдачі. Якщо слухач у черзі перевищить максимальну кількість спроб, задану вашим воркером черги, у слухачі буде викликано метод `failed`. Метод `failed` отримує екземпляр події та `Throwable`, який спричинив невдачу:

```php
<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Throwable;

class SendShipmentNotification implements ShouldQueue
{
    use InteractsWithQueue;

    /**
     * Handle the event.
     */
    public function handle(OrderShipped $event): void
    {
        // ...
    }

    /**
     * Handle a job failure.
     */
    public function failed(OrderShipped $event, Throwable $exception): void
    {
        // ...
    }
}
```

<a name="specifying-queued-listener-maximum-attempts"></a>
#### Задання максимальної кількості спроб для слухача в черзі

Якщо один з ваших слухачів у черзі натрапляє на помилку, ви навряд чи хочете, щоб він повторював спроби нескінченно. Тому Laravel надає різні способи вказати, скільки разів або як довго можна намагатися виконати слухача.

Ви можете скористатися атрибутом `Tries` у класі слухача, щоб вказати, скільки разів можна намагатися виконати слухача, перш ніж він вважатиметься невдалим:

```php
<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\InteractsWithQueue;

#[Tries(5)]
class SendShipmentNotification implements ShouldQueue
{
    use InteractsWithQueue;

    // ...
}
```

Як альтернативу заданню кількості спроб перед невдачею ви можете вказати момент, після якого спроби виконати слухача припиняються. Це дозволяє виконувати слухача будь-яку кількість разів у межах заданого проміжку часу. Щоб задати момент, після якого слухача більше не намагатимуться виконати, додайте до класу слухача метод `retryUntil`. Цей метод має повернути екземпляр `DateTimeInterface`:

```php
use DateTimeInterface;

/**
 * Determine the time at which the listener should timeout.
 */
public function retryUntil(): DateTimeInterface
{
    return now()->plus(minutes: 5);
}
```

Якщо описано і `retryUntil`, і `tries`, Laravel віддає перевагу методу `retryUntil`.

<a name="specifying-queued-listener-backoff"></a>
#### Задання відступу для слухача в черзі

Якщо ви хочете налаштувати, скільки секунд Laravel має чекати перед повторною спробою виконати слухача, який натрапив на виняток, скористайтеся атрибутом `Backoff` у класі слухача:

```php
<?php

namespace App\Listeners;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\Backoff;

#[Backoff(3)]
class SendShipmentNotification implements ShouldQueue
{
    // ...
}
```

Якщо для визначення часу відступу слухача вам потрібна складніша логіка, опишіть у класі слухача метод `backoff`:

```php
/**
 * Calculate the number of seconds to wait before retrying the queued listener.
 */
public function backoff(OrderShipped $event): int
{
    return 3;
}
```

Ви легко можете налаштувати «експоненційні» відступи, повернувши з методу `backoff` масив значень. У цьому прикладі затримка перед повтором становитиме 1 секунду для першого повтору, 5 секунд для другого, 10 секунд для третього і 10 секунд для кожного наступного, якщо спроби ще лишилися:

```php
/**
 * Calculate the number of seconds to wait before retrying the queued listener.
 *
 * @return list<int>
 */
public function backoff(OrderShipped $event): array
{
    return [1, 5, 10];
}
```

<a name="specifying-queued-listener-max-exceptions"></a>
#### Задання максимальної кількості винятків для слухача в черзі

Інколи вам може знадобитися вказати, що слухача в черзі можна намагатися виконати багато разів, але він має зазнати невдачі, якщо повтори спричинено заданою кількістю необроблених винятків (на відміну від звільнення методом `release` напряму). Для цього скористайтеся атрибутами `Tries` і `MaxExceptions` у класі слухача:

```php
<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\MaxExceptions;
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\InteractsWithQueue;

#[Tries(25)]
#[MaxExceptions(3)]
class SendShipmentNotification implements ShouldQueue
{
    use InteractsWithQueue;

    /**
     * Handle the event.
     */
    public function handle(OrderShipped $event): void
    {
        // Process the event...
    }
}
```

У цьому прикладі слухача буде повторено до 25 разів. Проте слухач зазнає невдачі, якщо викине три необроблені винятки.

<a name="specifying-queued-listener-timeout"></a>
#### Задання таймауту для слухача в черзі

Часто ви приблизно знаєте, скільки часу мають виконуватися ваші слухачі в черзі. Тому Laravel дозволяє задати значення «таймауту». Якщо слухач обробляється довше за вказану кількість секунд, воркер, який його обробляє, завершиться з помилкою. Задати максимальну кількість секунд, яку дозволено виконуватися слухачеві, можна атрибутом `Timeout` у класі слухача:

```php
<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\Timeout;

#[Timeout(120)]
class SendShipmentNotification implements ShouldQueue
{
    // ...
}
```

Якщо ви хочете, щоб слухача позначало як невдалого після таймауту, скористайтеся атрибутом `FailOnTimeout` у класі слухача:

```php
<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\Attributes\FailOnTimeout;

#[FailOnTimeout]
class SendShipmentNotification implements ShouldQueue
{
    // ...
}
```

<a name="dispatching-events"></a>
## Диспетчеризація подій

Щоб диспетчеризувати подію, викличте на ній статичний метод `dispatch`. Цей метод стає доступним у події завдяки трейту `Illuminate\Foundation\Events\Dispatchable`. Будь-які аргументи, передані методу `dispatch`, буде передано в конструктор події:

```php
<?php

namespace App\Http\Controllers;

use App\Events\OrderShipped;
use App\Models\Order;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class OrderShipmentController extends Controller
{
    /**
     * Ship the given order.
     */
    public function store(Request $request): RedirectResponse
    {
        $order = Order::findOrFail($request->order_id);

        // Order shipment logic...

        OrderShipped::dispatch($order);

        return redirect('/orders');
    }
}
```

Якщо ви хочете диспетчеризувати подію умовно, скористайтеся методами `dispatchIf` та `dispatchUnless`:

```php
OrderShipped::dispatchIf($condition, $order);

OrderShipped::dispatchUnless($condition, $order);
```

> [!NOTE]
> Під час тестування буває корисно перевірити, що певні події було диспетчеризовано, не запускаючи їхніх слухачів. [Вбудовані тестові хелпери](#testing) Laravel роблять це елементарним.

<a name="dispatching-events-after-database-transactions"></a>
### Диспетчеризація подій після транзакцій бази даних

Інколи вам може захотітися сказати Laravel диспетчеризувати подію лише після фіксації активної транзакції бази даних. Для цього реалізуйте в класі події інтерфейс `ShouldDispatchAfterCommit`.

Цей інтерфейс каже Laravel не диспетчеризувати подію, доки поточну транзакцію бази даних не зафіксовано. Якщо транзакція провалиться, подію буде відкинуто. Якщо на момент диспетчеризації події жодної транзакції не відкрито, подію буде диспетчеризовано негайно:

```php
<?php

namespace App\Events;

use App\Models\Order;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class OrderShipped implements ShouldDispatchAfterCommit
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     */
    public function __construct(
        public Order $order,
    ) {}
}
```

<a name="deferring-events"></a>
### Відкладення подій

Відкладені події дозволяють затримати диспетчеризацію подій моделей і виконання слухачів подій до завершення певного блоку коду. Це особливо корисно, коли вам потрібно переконатися, що всі пов'язані записи створено, перш ніж спрацюють слухачі подій.

Щоб відкласти події, передайте замикання методу `Event::defer()`:

```php
use App\Models\User;
use Illuminate\Support\Facades\Event;

Event::defer(function () {
    $user = User::create(['name' => 'Victoria Otwell']);

    $user->posts()->create(['title' => 'My first post!']);
});
```

Усі події, спричинені всередині замикання, буде диспетчеризовано після його виконання. Це гарантує, що слухачі подій матимуть доступ до всіх пов'язаних записів, створених під час відкладеного виконання. Якщо всередині замикання станеться виняток, відкладені події не буде диспетчеризовано.

Щоб відкласти лише певні події, передайте масив подій другим аргументом методу `defer`:

```php
use App\Models\User;
use Illuminate\Support\Facades\Event;

Event::defer(function () {
    $user = User::create(['name' => 'Victoria Otwell']);

    $user->posts()->create(['title' => 'My first post!']);
}, ['eloquent.created: '.User::class]);
```

<a name="event-subscribers"></a>
## Підписники подій

<a name="writing-event-subscribers"></a>
### Написання підписників подій

Підписники подій - це класи, які можуть підписуватися на кілька подій зсередини самого класу підписника, дозволяючи описати кілька обробників подій в одному класі. Підписники мають описувати метод `subscribe`, який отримує екземпляр диспетчера подій. Щоб зареєструвати слухачів подій, викличте на переданому диспетчері метод `listen`:

```php
<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Events\Dispatcher;

class UserEventSubscriber
{
    /**
     * Handle user login events.
     */
    public function handleUserLogin(Login $event): void {}

    /**
     * Handle user logout events.
     */
    public function handleUserLogout(Logout $event): void {}

    /**
     * Register the listeners for the subscriber.
     */
    public function subscribe(Dispatcher $events): void
    {
        $events->listen(
            Login::class,
            [UserEventSubscriber::class, 'handleUserLogin']
        );

        $events->listen(
            Logout::class,
            [UserEventSubscriber::class, 'handleUserLogout']
        );
    }
}
```

Якщо методи-слухачі подій описано в самому підписнику, вам може бути зручніше повернути з методу `subscribe` масив подій і назв методів. Реєструючи слухачів подій, Laravel автоматично визначить назву класу підписника:

```php
<?php

namespace App\Listeners;

use Illuminate\Auth\Events\Login;
use Illuminate\Auth\Events\Logout;
use Illuminate\Events\Dispatcher;

class UserEventSubscriber
{
    /**
     * Handle user login events.
     */
    public function handleUserLogin(Login $event): void {}

    /**
     * Handle user logout events.
     */
    public function handleUserLogout(Logout $event): void {}

    /**
     * Register the listeners for the subscriber.
     *
     * @return array<string, string>
     */
    public function subscribe(Dispatcher $events): array
    {
        return [
            Login::class => 'handleUserLogin',
            Logout::class => 'handleUserLogout',
        ];
    }
}
```

<a name="registering-event-subscribers"></a>
### Реєстрація підписників подій

Коли підписника написано, Laravel автоматично зареєструє його методи-обробники, якщо ті відповідають [домовленостям виявлення подій](#event-discovery) Laravel. Інакше ви можете зареєструвати підписника вручну методом `subscribe` фасаду `Event`. Зазвичай це роблять у методі `boot` вашого `AppServiceProvider`:

```php
<?php

namespace App\Providers;

use App\Listeners\UserEventSubscriber;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Event::subscribe(UserEventSubscriber::class);
    }
}
```

<a name="testing"></a>
## Тестування

Тестуючи код, який диспетчеризує події, ви можете захотіти сказати Laravel не виконувати слухачів події, адже код слухача можна протестувати напряму й окремо від коду, який диспетчеризує відповідну подію. Звісно, щоб протестувати самого слухача, ви можете створити його екземпляр і викликати метод `handle` напряму у вашому тесті.

За допомогою методу `fake` фасаду `Event` ви можете завадити виконанню слухачів, виконати тестований код, а потім перевірити, які події диспетчеризував ваш застосунок, методами `assertDispatched`, `assertNotDispatched` та `assertNothingDispatched`:

```php tab=Pest
<?php

use App\Events\OrderFailedToShip;
use App\Events\OrderShipped;
use Illuminate\Support\Facades\Event;

test('orders can be shipped', function () {
    Event::fake();

    // Perform order shipping...

    // Assert that an event was dispatched...
    Event::assertDispatched(OrderShipped::class);

    // Assert an event was dispatched twice...
    Event::assertDispatched(OrderShipped::class, 2);

    // Assert an event was dispatched once...
    Event::assertDispatchedOnce(OrderShipped::class);

    // Assert an event was not dispatched...
    Event::assertNotDispatched(OrderFailedToShip::class);

    // Assert that no events were dispatched...
    Event::assertNothingDispatched();
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use App\Events\OrderFailedToShip;
use App\Events\OrderShipped;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * Test order shipping.
     */
    public function test_orders_can_be_shipped(): void
    {
        Event::fake();

        // Perform order shipping...

        // Assert that an event was dispatched...
        Event::assertDispatched(OrderShipped::class);

        // Assert an event was dispatched twice...
        Event::assertDispatched(OrderShipped::class, 2);

        // Assert an event was dispatched once...
        Event::assertDispatchedOnce(OrderShipped::class);

        // Assert an event was not dispatched...
        Event::assertNotDispatched(OrderFailedToShip::class);

        // Assert that no events were dispatched...
        Event::assertNothingDispatched();
    }
}
```

Ви можете передати замикання методам `assertDispatched` чи `assertNotDispatched`, щоб перевірити, що було диспетчеризовано подію, яка проходить заданий тест. Якщо диспетчеризовано щонайменше одну подію, яка проходить заданий тест, перевірка буде успішною:

```php
Event::assertDispatched(function (OrderShipped $event) use ($order) {
    return $event->order->id === $order->id;
});
```

Як альтернативу, ви можете передати масив очікуваних значень властивостей другим аргументом методам `assertDispatched` чи `assertNotDispatched`:

```php
Event::assertDispatched(OrderShipped::class, ['order' => $order]);
```

Усі вказані властивості мають збігатися. Значення порівнюються за строгою рівністю, а Eloquent-моделі порівнюються за допомогою методу `is`.

Якщо ви просто хочете перевірити, що слухач подій слухає задану подію, скористайтеся методом `assertListening`:

```php
Event::assertListening(
    OrderShipped::class,
    SendShipmentNotification::class
);
```

> [!WARNING]
> Після виклику `Event::fake()` жоден слухач подій не виконуватиметься. Тож, якщо ваші тести використовують фабрики моделей, які покладаються на події - наприклад, створення UUID під час події `creating` моделі, - викликайте `Event::fake()` **після** використання фабрик.

<a name="faking-a-subset-of-events"></a>
### Підміна частини подій

Якщо ви хочете підмінити слухачів подій лише для певного набору подій, передайте їх методу `fake` або `fakeFor`:

```php tab=Pest
test('orders can be processed', function () {
    Event::fake([
        OrderCreated::class,
    ]);

    $order = Order::factory()->create();

    Event::assertDispatched(OrderCreated::class);

    // Other events are dispatched as normal...
    $order->update([
        // ...
    ]);
});
```

```php tab=PHPUnit
/**
 * Test order process.
 */
public function test_orders_can_be_processed(): void
{
    Event::fake([
        OrderCreated::class,
    ]);

    $order = Order::factory()->create();

    Event::assertDispatched(OrderCreated::class);

    // Other events are dispatched as normal...
    $order->update([
        // ...
    ]);
}
```

Ви можете підмінити всі події, окрім заданого набору, методом `except`:

```php
Event::fake()->except([
    OrderCreated::class,
]);
```

<a name="scoped-event-fakes"></a>
### Обмежені підміни подій

Якщо ви хочете підмінити слухачів подій лише для частини вашого тесту, скористайтеся методом `fakeFor`:

```php tab=Pest
<?php

use App\Events\OrderCreated;
use App\Models\Order;
use Illuminate\Support\Facades\Event;

test('orders can be processed', function () {
    $order = Event::fakeFor(function () {
        $order = Order::factory()->create();

        Event::assertDispatched(OrderCreated::class);

        return $order;
    });

    // Events are dispatched as normal and observers will run...
    $order->update([
        // ...
    ]);
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use App\Events\OrderCreated;
use App\Models\Order;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * Test order process.
     */
    public function test_orders_can_be_processed(): void
    {
        $order = Event::fakeFor(function () {
            $order = Order::factory()->create();

            Event::assertDispatched(OrderCreated::class);

            return $order;
        });

        // Events are dispatched as normal and observers will run...
        $order->update([
            // ...
        ]);
    }
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/filesystem

# Файлове сховище

- [Вступ](#introduction)
- [Конфігурація](#configuration)
    - [Драйвер local](#the-local-driver)
    - [Публічний диск](#the-public-disk)
    - [Передумови драйверів](#driver-prerequisites)
    - [Скоповані файлові системи, системи лише для читання та системи з наскрізним читанням](#scoped-and-read-only-filesystems)
    - [Файлові системи, сумісні з Amazon S3](#amazon-s3-compatible-filesystems)
- [Отримання екземплярів диска](#obtaining-disk-instances)
    - [Диски на льоту](#on-demand-disks)
- [Отримання файлів](#retrieving-files)
    - [Завантаження файлів](#downloading-files)
    - [URL файлів](#file-urls)
    - [Тимчасові URL](#temporary-urls)
    - [Метадані файлів](#file-metadata)
- [Збереження файлів](#storing-files)
    - [Додавання на початок і в кінець файлів](#prepending-appending-to-files)
    - [Копіювання та переміщення файлів](#copying-moving-files)
    - [Автоматичний потік](#automatic-streaming)
    - [Завантаження файлів на сервер](#file-uploads)
    - [Видимість файлів](#file-visibility)
    - [Обробка зображень](#image-manipulation)
- [Видалення файлів](#deleting-files)
- [Каталоги](#directories)
- [Тестування](#testing)
- [Власні файлові системи](#custom-filesystems)

<a name="introduction"></a>
## Вступ

Laravel надає потужну абстракцію файлової системи завдяки чудовому PHP-пакету [Flysystem](https://github.com/thephpleague/flysystem) від Френка де Йонге. Інтеграція Laravel із Flysystem надає прості драйвери для роботи з локальними файловими системами, SFTP та Amazon S3. Ще краще те, що перемикатися між цими сховищами на локальній машині розробки й на продакшн-сервері напрочуд просто, адже API для кожної системи однаковий.

<a name="configuration"></a>
## Конфігурація

Файл конфігурації файлової системи Laravel лежить у `config/filesystems.php`. У цьому файлі ви можете налаштувати всі свої «диски» файлової системи. Кожен диск представляє певний драйвер сховища й місце зберігання. Приклади конфігурації для кожного підтримуваного драйвера є у файлі конфігурації, тож ви можете змінити її під свої вподобання й облікові дані.

Драйвер `local` працює з файлами, які зберігаються локально на сервері із застосунком Laravel, а драйвер `sftp` використовується для FTP на основі SSH-ключів. Драйвер `s3` використовується для запису в хмарне сховище Amazon S3.

> [!NOTE]
> Ви можете налаштувати скільки завгодно дисків і навіть мати кілька дисків з одним драйвером.

<a name="the-local-driver"></a>
### Драйвер local

Коли ви користуєтеся драйвером `local`, усі операції з файлами відносні до каталогу `root`, заданого у файлі конфігурації `filesystems`. За замовчуванням це значення - каталог `storage/app/private`. Тому метод нижче запише у `storage/app/private/example.txt`:

```php
use Illuminate\Support\Facades\Storage;

Storage::disk('local')->put('example.txt', 'Contents');
```

<a name="the-public-disk"></a>
### Публічний диск

Диск `public`, який є у файлі конфігурації `filesystems` вашого застосунку, призначений для файлів, що мають бути загальнодоступними. За замовчуванням диск `public` використовує драйвер `local` і зберігає файли в `storage/app/public`.

Якщо ваш диск `public` використовує драйвер `local` і ви хочете зробити ці файли доступними з вебу, створіть символічне посилання з вихідного каталогу `storage/app/public` до цільового каталогу `public/storage`:

Щоб створити символічне посилання, скористайтеся командою Artisan `storage:link`:

```shell
php artisan storage:link
```

Щойно файл збережено, а символічне посилання створено, ви можете згенерувати URL до файлів хелпером `asset`:

```php
echo asset('storage/file.txt');
```

Ви можете налаштувати додаткові символічні посилання у файлі конфігурації `filesystems`. Кожне налаштоване посилання буде створено, коли ви виконаєте команду `storage:link`:

```php
'links' => [
    public_path('storage') => storage_path('app/public'),
    public_path('images') => storage_path('app/images'),
],
```

Команда `storage:unlink` дозволяє знищити налаштовані символічні посилання:

```shell
php artisan storage:unlink
```

<a name="driver-prerequisites"></a>
### Передумови драйверів

<a name="s3-driver-configuration"></a>
#### Конфігурація драйвера S3

Перш ніж користуватися драйвером S3, вам потрібно встановити пакет Flysystem S3 через менеджер пакетів Composer:

```shell
composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies
```

Масив конфігурації диска S3 лежить у файлі конфігурації `config/filesystems.php`. Зазвичай інформацію й облікові дані S3 налаштовують такими змінними середовища, на які посилається файл `config/filesystems.php`:

```ini
AWS_ACCESS_KEY_ID=<your-key-id>
AWS_SECRET_ACCESS_KEY=<your-secret-access-key>
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=<your-bucket-name>
AWS_USE_PATH_STYLE_ENDPOINT=false
```

Для зручності ці змінні середовища відповідають домовленостям іменування, які використовує AWS CLI.

<a name="ftp-driver-configuration"></a>
#### Конфігурація драйвера FTP

Перш ніж користуватися драйвером FTP, вам потрібно встановити пакет Flysystem FTP через менеджер пакетів Composer:

```shell
composer require league/flysystem-ftp "^3.0"
```

Інтеграції Laravel із Flysystem чудово працюють з FTP; проте приклад конфігурації не входить до стандартного файлу `config/filesystems.php` фреймворку. Якщо вам потрібно налаштувати файлову систему FTP, скористайтеся прикладом конфігурації нижче:

```php
'ftp' => [
    'driver' => 'ftp',
    'host' => env('FTP_HOST'),
    'username' => env('FTP_USERNAME'),
    'password' => env('FTP_PASSWORD'),

    // Optional FTP Settings...
    // 'port' => env('FTP_PORT', 21),
    // 'root' => env('FTP_ROOT'),
    // 'passive' => true,
    // 'ssl' => true,
    // 'timeout' => 30,
],
```

<a name="sftp-driver-configuration"></a>
#### Конфігурація драйвера SFTP

Перш ніж користуватися драйвером SFTP, вам потрібно встановити пакет Flysystem SFTP через менеджер пакетів Composer:

```shell
composer require league/flysystem-sftp-v3 "^3.0"
```

Інтеграції Laravel із Flysystem чудово працюють з SFTP; проте приклад конфігурації не входить до стандартного файлу `config/filesystems.php` фреймворку. Якщо вам потрібно налаштувати файлову систему SFTP, скористайтеся прикладом конфігурації нижче:

```php
'sftp' => [
    'driver' => 'sftp',
    'host' => env('SFTP_HOST'),

    // Settings for basic authentication...
    'username' => env('SFTP_USERNAME'),
    'password' => env('SFTP_PASSWORD'),

    // Settings for SSH key-based authentication with encryption password...
    'privateKey' => env('SFTP_PRIVATE_KEY'),
    'passphrase' => env('SFTP_PASSPHRASE'),

    // Settings for file / directory permissions...
    'visibility' => 'private', // `private` = 0600, `public` = 0644
    'directory_visibility' => 'private', // `private` = 0700, `public` = 0755

    // Optional SFTP Settings...
    // 'hostFingerprint' => env('SFTP_HOST_FINGERPRINT'),
    // 'maxTries' => 4,
    // 'passphrase' => env('SFTP_PASSPHRASE'),
    // 'port' => env('SFTP_PORT', 22),
    // 'root' => env('SFTP_ROOT', ''),
    // 'timeout' => 30,
    // 'useAgent' => true,
],
```

<a name="scoped-and-read-only-filesystems"></a>
### Скоповані файлові системи, системи лише для читання та системи з наскрізним читанням

Скоповані диски дозволяють описати файлову систему, у якій усі шляхи автоматично отримують заданий префікс. Перш ніж створювати скопований диск, вам потрібно встановити додатковий пакет Flysystem через менеджер пакетів Composer:

```shell
composer require league/flysystem-path-prefixing "^3.0"
```

Ви можете створити скопований за шляхом екземпляр будь-якого наявного диска, описавши диск із драйвером `scoped`. Наприклад, ви можете створити диск, який скопує ваш наявний диск `s3` до певного префікса шляху, - і тоді кожна операція з файлами через скопований диск використовуватиме вказаний префікс:

```php
's3-videos' => [
    'driver' => 'scoped',
    'disk' => 's3',
    'prefix' => 'path/to/videos',
],
```

Диски «лише для читання» дозволяють створювати диски, які не дозволяють операцій запису. Перш ніж користуватися опцією конфігурації `read-only`, вам потрібно встановити додатковий пакет Flysystem через менеджер пакетів Composer:

```shell
composer require league/flysystem-read-only "^3.0"
```

Далі ви можете додати опцію конфігурації `read-only` до масивів конфігурації одного чи кількох ваших дисків:

```php
's3-videos' => [
    'driver' => 's3',
    // ...
    'read-only' => true,
],
```

Диски з наскрізним читанням дозволяють мігрувати файли між дисками без простою. Читаючи файл, Laravel спершу шукає його на основному диску. Якщо файл є лише на резервному, Laravel віддає його звідти й одразу копіює на основний - для наступних запитів:

```php
'assets' => [
    'driver' => 'read-through',
    'primary' => 's3',
    'fallback' => 'legacy-s3',
],
```

Запис і перегляд каталогів завжди йдуть на основний диск. Перевірка наявності файлу та його метаданих працює з обома дисками й нічого не копіює. Якщо ж копіювання не вдалося, читання за замовчуванням усе одно вважається успішним. Щоб натомість отримати виняток, встановіть опцію конфігурації `throw_on_promotion_failure` у `true`.

<a name="amazon-s3-compatible-filesystems"></a>
### Файлові системи, сумісні з Amazon S3

За замовчуванням файл конфігурації `filesystems` вашого застосунку містить конфігурацію диска `s3`. Окрім роботи з [Amazon S3](https://aws.amazon.com/s3/), ви можете скористатися цим диском для будь-якого сумісного з S3 сховища - наприклад, [RustFS](https://github.com/rustfs/rustfs), [DigitalOcean Spaces](https://www.digitalocean.com/products/spaces/), [Vultr Object Storage](https://www.vultr.com/products/object-storage/), [Cloudflare R2](https://www.cloudflare.com/developer-platform/products/r2/) чи [Hetzner Cloud Storage](https://www.hetzner.com/storage/object-storage/).

Зазвичай, оновивши облікові дані диска відповідно до сервісу, яким плануєте користуватися, вам залишається лише оновити значення опції конфігурації `endpoint`. Значення цієї опції зазвичай задають через змінну середовища `AWS_ENDPOINT`:

```php
'endpoint' => env('AWS_ENDPOINT', 'https://rustfs:9000'),
```

<a name="obtaining-disk-instances"></a>
## Отримання екземплярів диска

Фасад `Storage` дозволяє працювати з будь-яким із налаштованих дисків. Наприклад, ви можете скористатися методом `put` фасаду, щоб зберегти аватар на диску за замовчуванням. Якщо ви викликаєте методи фасаду `Storage`, не викликавши спершу метод `disk`, метод автоматично буде передано диску за замовчуванням:

```php
use Illuminate\Support\Facades\Storage;

Storage::put('avatars/1', $content);
```

Якщо ваш застосунок працює з кількома дисками, скористайтеся методом `disk` фасаду `Storage`, щоб працювати з файлами на конкретному диску:

```php
Storage::disk('s3')->put('avatars/1', $content);
```

<a name="on-demand-disks"></a>
### Диски на льоту

Інколи вам може захотітися створити диск під час виконання із заданою конфігурацією, якої немає у файлі конфігурації `filesystems` вашого застосунку. Для цього передайте масив конфігурації методу `build` фасаду `Storage`:

```php
use Illuminate\Support\Facades\Storage;

$disk = Storage::build([
    'driver' => 'local',
    'root' => '/path/to/root',
]);

$disk->put('image.jpg', $content);
```

<a name="retrieving-files"></a>
## Отримання файлів

Метод `get` дозволяє отримати вміст файлу. Метод поверне сирий рядковий вміст файлу. Пам'ятайте: усі шляхи до файлів вказуються відносно кореня («root») диска:

```php
$contents = Storage::get('file.jpg');
```

Якщо файл, який ви отримуєте, містить JSON, скористайтеся методом `json`, щоб отримати файл і декодувати його вміст:

```php
$orders = Storage::json('orders.json');
```

Метод `exists` дозволяє визначити, чи існує файл на диску:

```php
if (Storage::disk('s3')->exists('file.jpg')) {
    // ...
}
```

Метод `missing` дозволяє визначити, чи файлу на диску немає:

```php
if (Storage::disk('s3')->missing('file.jpg')) {
    // ...
}
```

<a name="downloading-files"></a>
### Завантаження файлів

Метод `download` дозволяє згенерувати відповідь, яка змусить браузер користувача завантажити файл за заданим шляхом. Другим аргументом метод `download` приймає ім'я файлу, яке побачить користувач, що його завантажує. Нарешті, третім аргументом методу можна передати масив HTTP-заголовків:

```php
return Storage::download('file.jpg');

return Storage::download('file.jpg', $name, $headers);
```

<a name="file-urls"></a>
### URL файлів

Метод `url` дозволяє отримати URL заданого файлу. Якщо ви користуєтеся драйвером `local`, він зазвичай просто додасть до заданого шляху `/storage` і поверне відносний URL до файлу. Якщо ви користуєтеся драйвером `s3`, буде повернено повний віддалений URL:

```php
use Illuminate\Support\Facades\Storage;

$url = Storage::url('file.jpg');
```

Коли ви користуєтеся драйвером `local`, усі файли, які мають бути загальнодоступними, слід класти в каталог `storage/app/public`. Крім того, вам слід [створити символічне посилання](#the-public-disk) `public/storage`, яке вказує на каталог `storage/app/public`.

> [!WARNING]
> Коли ви користуєтеся драйвером `local`, повернене значення `url` не є URL-кодованим. Тому радимо завжди зберігати файли з іменами, які утворюють коректні URL.

<a name="url-host-customization"></a>
#### Налаштування хоста URL

Якщо ви хочете змінити хост для URL, згенерованих фасадом `Storage`, додайте або змініть опцію `url` у масиві конфігурації диска:

```php
'public' => [
    'driver' => 'local',
    'root' => storage_path('app/public'),
    'url' => env('APP_URL').'/storage',
    'visibility' => 'public',
    'throw' => false,
],
```

<a name="temporary-urls"></a>
### Тимчасові URL

Методом `temporaryUrl` ви можете створювати тимчасові URL до файлів, збережених драйверами `local` та `s3`. Цей метод приймає шлях і екземпляр `DateTime`, який вказує, коли URL має спливти:

```php
use Illuminate\Support\Facades\Storage;

$url = Storage::temporaryUrl(
    'file.jpg', now()->plus(minutes: 5)
);
```

<a name="enabling-local-temporary-urls"></a>
#### Увімкнення локальних тимчасових URL

Якщо ви почали розробляти застосунок до того, як у драйвері `local` з'явилася підтримка тимчасових URL, вам може знадобитися увімкнути локальні тимчасові URL. Для цього додайте опцію `serve` до масиву конфігурації вашого диска `local` у файлі `config/filesystems.php`:

```php
'local' => [
    'driver' => 'local',
    'root' => storage_path('app/private'),
    'serve' => true, // [tl! add]
    'throw' => false,
],
```

<a name="s3-request-parameters"></a>
#### Параметри запиту S3

Якщо вам потрібно вказати додаткові [параметри запиту S3](https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectGET.html#RESTObjectGET-requests), передайте масив параметрів запиту третім аргументом методу `temporaryUrl`:

```php
$url = Storage::temporaryUrl(
    'file.jpg',
    now()->plus(minutes: 5),
    [
        'ResponseContentType' => 'application/octet-stream',
        'ResponseContentDisposition' => 'attachment; filename=file2.jpg',
    ]
);
```

<a name="customizing-temporary-urls"></a>
#### Налаштування тимчасових URL

Якщо вам потрібно змінити те, як створюються тимчасові URL для конкретного диска, скористайтеся методом `buildTemporaryUrlsUsing`. Наприклад, це може стати в пригоді, якщо у вас є контролер, який дозволяє завантажувати файли з диска, що зазвичай не підтримує тимчасових URL. Зазвичай цей метод викликають у методі `boot` сервіс-провайдера:

```php
<?php

namespace App\Providers;

use DateTime;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Storage::disk('local')->buildTemporaryUrlsUsing(
            function (string $path, DateTime $expiration, array $options) {
                return URL::temporarySignedRoute(
                    'files.download',
                    $expiration,
                    array_merge($options, ['path' => $path])
                );
            }
        );
    }
}
```

<a name="temporary-upload-urls"></a>
#### Тимчасові URL для завантаження на сервер

> [!WARNING]
> Можливість генерувати тимчасові URL для завантаження на сервер підтримують лише драйвери `s3` та `local`.

Якщо вам потрібно згенерувати тимчасовий URL, яким можна завантажити файл напряму з вашого клієнтського застосунку, скористайтеся методом `temporaryUploadUrl`. Цей метод приймає шлях і екземпляр `DateTime`, який вказує, коли URL має спливти. Метод `temporaryUploadUrl` повертає асоціативний масив, який можна деструктурувати на URL завантаження та заголовки, які слід додати до запиту:

```php
use Illuminate\Support\Facades\Storage;

['url' => $url, 'headers' => $headers] = Storage::temporaryUploadUrl(
    'file.jpg', now()->plus(minutes: 5)
);
```

Цей метод насамперед корисний у безсерверних середовищах, де клієнтський застосунок має завантажувати файли напряму до хмарного сховища на кшталт Amazon S3.

<a name="file-metadata"></a>
### Метадані файлів

Окрім читання та запису файлів, Laravel може надати інформацію й про самі файли. Наприклад, метод `size` дозволяє отримати розмір файлу в байтах:

```php
use Illuminate\Support\Facades\Storage;

$size = Storage::size('file.jpg');
```

Метод `lastModified` повертає UNIX-мітку часу останньої зміни файлу:

```php
$time = Storage::lastModified('file.jpg');
```

MIME-тип заданого файлу можна отримати методом `mimeType`:

```php
$mime = Storage::mimeType('file.jpg');
```

<a name="file-paths"></a>
#### Шляхи до файлів

Метод `path` дозволяє отримати шлях до заданого файлу. Якщо ви користуєтеся драйвером `local`, він поверне абсолютний шлях до файлу. Якщо ви користуєтеся драйвером `s3`, цей метод поверне відносний шлях до файлу в бакеті S3:

```php
use Illuminate\Support\Facades\Storage;

$path = Storage::path('file.jpg');
```

<a name="storing-files"></a>
## Збереження файлів

Метод `put` дозволяє зберегти вміст файлу на диску. Ви також можете передати методу `put` PHP-ресурс `resource`, і тоді буде використано підтримку потоків із Flysystem. Пам'ятайте: усі шляхи до файлів вказуються відносно кореня («root»), налаштованого для диска:

```php
use Illuminate\Support\Facades\Storage;

Storage::put('file.jpg', $contents);

Storage::put('file.jpg', $resource);
```

<a name="failed-writes"></a>
#### Невдалі записи

Якщо метод `put` (чи інші операції запису) не зможе записати файл на диск, буде повернуто `false`:

```php
if (! Storage::put('file.jpg', $contents)) {
    // The file could not be written to disk...
}
```

За бажанням ви можете описати опцію `throw` у масиві конфігурації вашого диска. Коли ця опція має значення `true`, методи запису на кшталт `put` викидатимуть екземпляр `League\Flysystem\UnableToWriteFile`, якщо операція запису провалиться:

```php
'public' => [
    'driver' => 'local',
    // ...
    'throw' => true,
],
```

Альтернативно ви можете описати опцію `report` у масиві конфігурації вашого диска. Коли ця опція має значення `true`, Laravel залогує базовий виняток через обробник винятків вашого застосунку, якщо операція запису провалиться, не викидаючи виняток і не змінюючи значення, яке повертає операція запису:

```php
'public' => [
    'driver' => 'local',
    // ...
    'report' => true,
],
```

Якщо жодна з опцій `throw` чи `report` не описана, диск мовчки поверне `false` у разі невдачі, і базовий виняток не буде викинуто чи залоговано.

<a name="prepending-appending-to-files"></a>
### Додавання на початок і в кінець файлів

Методи `prepend` і `append` дозволяють писати на початок або в кінець файлу:

```php
Storage::prepend('file.log', 'Prepended Text');

Storage::append('file.log', 'Appended Text');
```

<a name="copying-moving-files"></a>
### Копіювання та переміщення файлів

Метод `copy` дозволяє скопіювати наявний файл у нове місце на диску, а метод `move` - перейменувати чи перемістити наявний файл у нове місце:

```php
Storage::copy('old/file.jpg', 'new/file.jpg');

Storage::move('old/file.jpg', 'new/file.jpg');
```

Ви можете використовувати методи `copyToDisk` і `moveToDisk`, щоб скопіювати чи перемістити файл на інший диск. Шлях вихідного файлу використовується на цільовому диску, якщо ви не передасте третій аргумент:

```php
Storage::disk('local')->copyToDisk('s3', 'reports/report.csv');

Storage::disk('local')->moveToDisk(
    's3', 'reports/report.csv', 'archive/report.csv'
);
```

<a name="automatic-streaming"></a>
### Автоматичний потік

Передавання файлів у сховище потоком значно знижує споживання пам'яті. Якщо ви хочете, щоб Laravel автоматично керував передаванням заданого файлу до вашого сховища, скористайтеся методом `putFile` чи `putFileAs`. Цей метод приймає екземпляр `Illuminate\Http\File` або `Illuminate\Http\UploadedFile` і автоматично передасть файл потоком у потрібне місце:

```php
use Illuminate\Http\File;
use Illuminate\Support\Facades\Storage;

// Automatically generate a unique ID for filename...
$path = Storage::putFile('photos', new File('/path/to/photo'));

// Manually specify a filename...
$path = Storage::putFileAs('photos', new File('/path/to/photo'), 'photo.jpg');
```

Щодо методу `putFile` варто зауважити кілька важливих речей. Зверніть увагу, що ми вказали лише назву каталогу, а не ім'я файлу. За замовчуванням метод `putFile` згенерує унікальний ID, який стане іменем файлу. Розширення файлу буде визначено за його MIME-типом. Метод `putFile` поверне шлях до файлу, тож ви можете зберегти цей шлях разом зі згенерованим іменем у своїй базі даних.

Методи `putFile` і `putFileAs` також приймають аргумент, який задає «видимість» збереженого файлу. Це особливо корисно, якщо ви зберігаєте файл на хмарному диску на кшталт Amazon S3 і хочете, щоб файл був загальнодоступним за згенерованими URL:

```php
Storage::putFile('photos', new File('/path/to/photo'), 'public');
```

<a name="file-uploads"></a>
### Завантаження файлів на сервер

У вебзастосунках один із найпоширеніших сценаріїв збереження файлів - зберігати файли, завантажені користувачами: фото й документи. Laravel дуже спрощує збереження завантажених файлів методом `store` на екземплярі завантаженого файлу. Викличте метод `store` зі шляхом, за яким ви хочете зберегти завантажений файл:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class UserAvatarController extends Controller
{
    /**
     * Update the avatar for the user.
     */
    public function update(Request $request): string
    {
        $path = $request->file('avatar')->store('avatars');

        return $path;
    }
}
```

Щодо цього прикладу варто зауважити кілька важливих речей. Зверніть увагу, що ми вказали лише назву каталогу, а не ім'я файлу. За замовчуванням метод `store` згенерує унікальний ID, який стане іменем файлу. Розширення файлу буде визначено за його MIME-типом. Метод `store` поверне шлях до файлу, тож ви можете зберегти цей шлях разом зі згенерованим іменем у своїй базі даних.

Ви також можете викликати метод `putFile` фасаду `Storage`, щоб виконати ту саму операцію збереження файлу, що й у прикладі вище:

```php
$path = Storage::putFile('avatars', $request->file('avatar'));
```

<a name="specifying-a-file-name"></a>
#### Задання імені файлу

Якщо ви не хочете, щоб збереженому файлу автоматично призначалося ім'я, скористайтеся методом `storeAs`, який приймає аргументами шлях, ім'я файлу і (необов'язково) диск:

```php
$path = $request->file('avatar')->storeAs(
    'avatars', $request->user()->id
);
```

Ви також можете скористатися методом `putFileAs` фасаду `Storage`, який виконає ту саму операцію збереження файлу, що й у прикладі вище:

```php
$path = Storage::putFileAs(
    'avatars', $request->file('avatar'), $request->user()->id
);
```

> [!WARNING]
> Недруковані та некоректні символи Unicode буде автоматично вилучено зі шляхів до файлів. Тому вам, можливо, варто санітизувати шляхи, перш ніж передавати їх методам збереження файлів у Laravel. Шляхи нормалізуються методом `League\Flysystem\WhitespacePathNormalizer::normalizePath`.

<a name="specifying-a-disk"></a>
#### Задання диска

За замовчуванням метод `store` завантаженого файлу використовує ваш диск за замовчуванням. Якщо ви хочете вказати інший диск, передайте його назву другим аргументом методу `store`:

```php
$path = $request->file('avatar')->store(
    'avatars/'.$request->user()->id, 's3'
);
```

Якщо ви користуєтеся методом `storeAs`, назву диска можна передати третім аргументом методу:

```php
$path = $request->file('avatar')->storeAs(
    'avatars',
    $request->user()->id,
    's3'
);
```

<a name="other-uploaded-file-information"></a>
#### Інша інформація про завантажений файл

Якщо ви хочете отримати оригінальні ім'я та розширення завантаженого файлу, скористайтеся методами `getClientOriginalName` і `getClientOriginalExtension`:

```php
$file = $request->file('avatar');

$name = $file->getClientOriginalName();
$extension = $file->getClientOriginalExtension();
```

Проте пам'ятайте, що методи `getClientOriginalName` і `getClientOriginalExtension` вважаються небезпечними, адже зловмисник може підробити ім'я й розширення файлу. Тому зазвичай варто віддавати перевагу методам `hashName` та `extension`, щоб отримати ім'я й розширення для завантаженого файлу:

```php
$file = $request->file('avatar');

$name = $file->hashName(); // Generate a unique, random name...
$extension = $file->extension(); // Determine the file's extension based on the file's MIME type...
```

<a name="file-visibility"></a>
### Видимість файлів

В інтеграції Laravel із Flysystem «видимість» - це абстракція прав доступу до файлів на різних платформах. Файли можуть бути оголошені як `public` або `private`. Коли файл оголошено `public`, ви вказуєте, що він загалом має бути доступним іншим. Наприклад, з драйвером S3 ви можете отримувати URL для файлів `public`.

Задати видимість можна під час запису файлу методом `put`:

```php
use Illuminate\Support\Facades\Storage;

Storage::put('file.jpg', $contents, 'public');
```

Якщо файл уже збережено, його видимість можна отримати й задати методами `getVisibility` та `setVisibility`:

```php
$visibility = Storage::getVisibility('file.jpg');

Storage::setVisibility('file.jpg', 'public');
```

Працюючи із завантаженими файлами, ви можете скористатися методами `storePublicly` та `storePubliclyAs`, щоб зберегти завантажений файл з видимістю `public`:

```php
$path = $request->file('avatar')->storePublicly('avatars', 's3');

$path = $request->file('avatar')->storePubliclyAs(
    'avatars',
    $request->user()->id,
    's3'
);
```

<a name="image-manipulation"></a>
### Обробка зображень

Якщо вам потрібно змінити розмір, обрізати чи конвертувати завантажене зображення перед збереженням, скористайтеся [можливостями обробки зображень](/docs/{{version}}/images) Laravel:

```php
$path = $request->image('avatar')
    ->cover(400, 400)
    ->toWebp()
    ->storePublicly('avatars', 'public');
```

Ви також можете створити екземпляр зображення з файлу, який уже зберігається на одному з ваших дисків:

```php
$image = Storage::disk('public')->image('avatars/photo.jpg');
```

<a name="local-files-and-visibility"></a>
#### Локальні файли та видимість

Коли ви користуєтеся драйвером `local`, [видимість](#file-visibility) `public` перетворюється на права `0755` для каталогів і `0644` для файлів. Ви можете змінити відповідність прав у файлі конфігурації `filesystems` вашого застосунку:

```php
'local' => [
    'driver' => 'local',
    'root' => storage_path('app'),
    'permissions' => [
        'file' => [
            'public' => 0644,
            'private' => 0600,
        ],
        'dir' => [
            'public' => 0755,
            'private' => 0700,
        ],
    ],
    'throw' => false,
],
```

<a name="deleting-files"></a>
## Видалення файлів

Метод `delete` приймає одне ім'я файлу або масив файлів для видалення:

```php
use Illuminate\Support\Facades\Storage;

Storage::delete('file.jpg');

Storage::delete(['file.jpg', 'file2.jpg']);
```

За потреби ви можете вказати диск, з якого слід видалити файл:

```php
use Illuminate\Support\Facades\Storage;

Storage::disk('s3')->delete('path/file.jpg');
```

<a name="directories"></a>
## Каталоги

<a name="get-all-files-within-a-directory"></a>
#### Отримати всі файли в каталозі

Метод `files` повертає масив усіх файлів у заданому каталозі. Якщо ви хочете отримати список усіх файлів у заданому каталозі разом із підкаталогами, скористайтеся методом `allFiles`:

```php
use Illuminate\Support\Facades\Storage;

$files = Storage::files($directory);

$files = Storage::allFiles($directory);
```

<a name="get-all-directories-within-a-directory"></a>
#### Отримати всі каталоги в каталозі

Метод `directories` повертає масив усіх каталогів у заданому каталозі. Якщо ви хочете отримати список усіх каталогів у заданому каталозі разом із підкаталогами, скористайтеся методом `allDirectories`:

```php
$directories = Storage::directories($directory);

$directories = Storage::allDirectories($directory);
```

<a name="create-a-directory"></a>
#### Створити каталог

Метод `makeDirectory` створить заданий каталог разом з усіма потрібними підкаталогами:

```php
Storage::makeDirectory($directory);
```

<a name="delete-a-directory"></a>
#### Видалити каталог

Нарешті, метод `deleteDirectory` дозволяє видалити каталог і всі його файли:

```php
Storage::deleteDirectory($directory);
```

<a name="testing"></a>
## Тестування

Метод `fake` фасаду `Storage` дозволяє легко згенерувати фейковий диск, який у поєднанні з утилітами генерації файлів класу `Illuminate\Http\UploadedFile` значно спрощує тестування завантаження файлів. Наприклад:

```php tab=Pest
<?php

use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;

test('albums can be uploaded', function () {
    Storage::fake('photos');

    $response = $this->json('POST', '/photos', [
        UploadedFile::fake()->image('photo1.jpg'),
        UploadedFile::fake()->image('photo2.jpg')
    ]);

    // Assert one or more files were stored...
    Storage::disk('photos')->assertExists('photo1.jpg');
    Storage::disk('photos')->assertExists(['photo1.jpg', 'photo2.jpg']);

    // Assert one or more files were not stored...
    Storage::disk('photos')->assertMissing('missing.jpg');
    Storage::disk('photos')->assertMissing(['missing.jpg', 'non-existing.jpg']);

    // Assert that the number of files in a given directory matches the expected count...
    Storage::disk('photos')->assertCount('/wallpapers', 2);

    // Assert that a given directory is empty...
    Storage::disk('photos')->assertDirectoryEmpty('/wallpapers');

    // Assert that the disk contains no files...
    Storage::disk('photos')->assertEmpty();
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_albums_can_be_uploaded(): void
    {
        Storage::fake('photos');

        $response = $this->json('POST', '/photos', [
            UploadedFile::fake()->image('photo1.jpg'),
            UploadedFile::fake()->image('photo2.jpg')
        ]);

        // Assert one or more files were stored...
        Storage::disk('photos')->assertExists('photo1.jpg');
        Storage::disk('photos')->assertExists(['photo1.jpg', 'photo2.jpg']);

        // Assert one or more files were not stored...
        Storage::disk('photos')->assertMissing('missing.jpg');
        Storage::disk('photos')->assertMissing(['missing.jpg', 'non-existing.jpg']);

        // Assert that the number of files in a given directory matches the expected count...
        Storage::disk('photos')->assertCount('/wallpapers', 2);

        // Assert that a given directory is empty...
        Storage::disk('photos')->assertDirectoryEmpty('/wallpapers');

        // Assert that the disk contains no files...
        Storage::disk('photos')->assertEmpty();
    }
}
```

За замовчуванням метод `fake` видаляє всі файли у своєму тимчасовому каталозі. Якщо ви хочете зберегти ці файли, скористайтеся натомість методом «persistentFake». Докладніше про тестування завантаження файлів читайте в [документації з HTTP-тестування, розділ про завантаження файлів](/docs/{{version}}/http-tests#testing-file-uploads).

> [!WARNING]
> Метод `image` потребує [розширення GD](https://www.php.net/manual/en/book.image.php).

<a name="custom-filesystems"></a>
## Власні файлові системи

Інтеграція Laravel із Flysystem «з коробки» підтримує кілька «драйверів»; проте Flysystem ними не обмежується й має адаптери для багатьох інших сховищ. Ви можете створити власний драйвер, якщо хочете скористатися одним із цих додаткових адаптерів у своєму застосунку Laravel.

Щоб описати власну файлову систему, вам знадобиться адаптер Flysystem. Додаймо до нашого проєкту адаптер Dropbox, який підтримує спільнота:

```shell
composer require spatie/flysystem-dropbox
```

Далі ви можете зареєструвати драйвер у методі `boot` одного з [сервіс-провайдерів](/docs/{{version}}/providers) вашого застосунку. Для цього скористайтеся методом `extend` фасаду `Storage`:

```php
<?php

namespace App\Providers;

use Illuminate\Contracts\Foundation\Application;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider;
use League\Flysystem\Filesystem;
use Spatie\Dropbox\Client as DropboxClient;
use Spatie\FlysystemDropbox\DropboxAdapter;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Storage::extend('dropbox', function (Application $app, array $config) {
            $adapter = new DropboxAdapter(new DropboxClient(
                $config['authorization_token']
            ));

            return new FilesystemAdapter(
                new Filesystem($adapter, $config),
                $adapter,
                $config
            );
        });
    }
}
```

Перший аргумент методу `extend` - назва драйвера, другий - замикання, яке отримує змінні `$app` і `$config`. Замикання має повернути екземпляр `Illuminate\Filesystem\FilesystemAdapter`. Змінна `$config` містить значення, задані в `config/filesystems.php` для вказаного диска.

Щойно ви створили й зареєстрували сервіс-провайдер розширення, ви можете користуватися драйвером `dropbox` у файлі конфігурації `config/filesystems.php`.

---

Джерело: https://laravelukraine.com/docs/13.x/helpers

# Хелпери

- [Вступ](#introduction)
- [Доступні методи](#available-methods)
- [Інші утиліти](#other-utilities)
    - [Бенчмаркінг](#benchmarking)
    - [Дата й час](#dates)
    - [Відкладені функції](#deferred-functions)
    - [Лотерея](#lottery)
    - [Конвеєр](#pipeline)
    - [Sleep](#sleep)
    - [Timebox](#timebox)
    - [URI](#uri)

<a name="introduction"></a>
## Вступ

Laravel містить чимало глобальних «хелперів» - PHP-функцій. Багато з них використовує сам фреймворк; проте ви вільні користуватися ними і у власних застосунках, якщо вважаєте їх зручними.

<a name="available-methods"></a>
## Доступні методи

<style>
    .collection-method-list > p {
        columns: 10.8em 3; -moz-columns: 10.8em 3; -webkit-columns: 10.8em 3;
    }

    .collection-method-list a {
        display: block;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
</style>

<a name="arrays-and-objects-method-list"></a>
### Масиви та об'єкти

<div class="collection-method-list" markdown="1">

[Arr::accessible](#method-array-accessible)
[Arr::add](#method-array-add)
[Arr::array](#method-array-array)
[Arr::boolean](#method-array-boolean)
[Arr::collapse](#method-array-collapse)
[Arr::crossJoin](#method-array-crossjoin)
[Arr::divide](#method-array-divide)
[Arr::dot](#method-array-dot)
[Arr::every](#method-array-every)
[Arr::except](#method-array-except)
[Arr::exceptValues](#method-array-except-values)
[Arr::exists](#method-array-exists)
[Arr::first](#method-array-first)
[Arr::flatten](#method-array-flatten)
[Arr::float](#method-array-float)
[Arr::forget](#method-array-forget)
[Arr::from](#method-array-from)
[Arr::get](#method-array-get)
[Arr::has](#method-array-has)
[Arr::hasAll](#method-array-hasall)
[Arr::hasAny](#method-array-hasany)
[Arr::integer](#method-array-integer)
[Arr::isAssoc](#method-array-isassoc)
[Arr::isList](#method-array-islist)
[Arr::join](#method-array-join)
[Arr::keyBy](#method-array-keyby)
[Arr::last](#method-array-last)
[Arr::map](#method-array-map)
[Arr::mapSpread](#method-array-map-spread)
[Arr::mapWithKeys](#method-array-map-with-keys)
[Arr::only](#method-array-only)
[Arr::onlyValues](#method-array-only-values)
[Arr::partition](#method-array-partition)
[Arr::pluck](#method-array-pluck)
[Arr::prepend](#method-array-prepend)
[Arr::prependKeysWith](#method-array-prependkeyswith)
[Arr::pull](#method-array-pull)
[Arr::push](#method-array-push)
[Arr::query](#method-array-query)
[Arr::random](#method-array-random)
[Arr::reject](#method-array-reject)
[Arr::select](#method-array-select)
[Arr::set](#method-array-set)
[Arr::shuffle](#method-array-shuffle)
[Arr::sole](#method-array-sole)
[Arr::some](#method-array-some)
[Arr::sort](#method-array-sort)
[Arr::sortDesc](#method-array-sort-desc)
[Arr::sortRecursive](#method-array-sort-recursive)
[Arr::string](#method-array-string)
[Arr::take](#method-array-take)
[Arr::toCssClasses](#method-array-to-css-classes)
[Arr::toCssStyles](#method-array-to-css-styles)
[Arr::undot](#method-array-undot)
[Arr::where](#method-array-where)
[Arr::whereNotNull](#method-array-where-not-null)
[Arr::wrap](#method-array-wrap)
[data_fill](#method-data-fill)
[data_get](#method-data-get)
[data_set](#method-data-set)
[data_forget](#method-data-forget)
[head](#method-head)
[last](#method-last)
</div>

<a name="numbers-method-list"></a>
### Числа

<div class="collection-method-list" markdown="1">

[Number::abbreviate](#method-number-abbreviate)
[Number::clamp](#method-number-clamp)
[Number::currency](#method-number-currency)
[Number::defaultCurrency](#method-default-currency)
[Number::defaultLocale](#method-default-locale)
[Number::fileSize](#method-number-file-size)
[Number::forHumans](#method-number-for-humans)
[Number::format](#method-number-format)
[Number::ordinal](#method-number-ordinal)
[Number::pairs](#method-number-pairs)
[Number::parse](#method-number-parse)
[Number::parseInt](#method-number-parse-int)
[Number::parseFloat](#method-number-parse-float)
[Number::percentage](#method-number-percentage)
[Number::spell](#method-number-spell)
[Number::spellOrdinal](#method-number-spell-ordinal)
[Number::trim](#method-number-trim)
[Number::useLocale](#method-number-use-locale)
[Number::withLocale](#method-number-with-locale)
[Number::useCurrency](#method-number-use-currency)
[Number::withCurrency](#method-number-with-currency)

</div>

<a name="paths-method-list"></a>
### Шляхи

<div class="collection-method-list" markdown="1">

[app_path](#method-app-path)
[base_path](#method-base-path)
[config_path](#method-config-path)
[database_path](#method-database-path)
[lang_path](#method-lang-path)
[public_path](#method-public-path)
[resource_path](#method-resource-path)
[storage_path](#method-storage-path)

</div>

<a name="urls-method-list"></a>
### URL

<div class="collection-method-list" markdown="1">

[action](#method-action)
[asset](#method-asset)
[route](#method-route)
[secure_asset](#method-secure-asset)
[secure_url](#method-secure-url)
[to_action](#method-to-action)
[to_route](#method-to-route)
[uri](#method-uri)
[url](#method-url)

</div>

<a name="miscellaneous-method-list"></a>
### Різне

<div class="collection-method-list" markdown="1">

[abort](#method-abort)
[abort_if](#method-abort-if)
[abort_unless](#method-abort-unless)
[app](#method-app)
[auth](#method-auth)
[back](#method-back)
[bcrypt](#method-bcrypt)
[blank](#method-blank)
[broadcast](#method-broadcast)
[broadcast_if](#method-broadcast-if)
[broadcast_unless](#method-broadcast-unless)
[cache](#method-cache)
[class_uses_recursive](#method-class-uses-recursive)
[collect](#method-collect)
[config](#method-config)
[context](#method-context)
[cookie](#method-cookie)
[csrf_field](#method-csrf-field)
[csrf_token](#method-csrf-token)
[decrypt](#method-decrypt)
[dd](#method-dd)
[dispatch](#method-dispatch)
[dispatch_sync](#method-dispatch-sync)
[dump](#method-dump)
[encrypt](#method-encrypt)
[env](#method-env)
[event](#method-event)
[fake](#method-fake)
[filled](#method-filled)
[info](#method-info)
[literal](#method-literal)
[logger](#method-logger)
[method_field](#method-method-field)
[now](#method-now)
[old](#method-old)
[once](#method-once)
[optional](#method-optional)
[policy](#method-policy)
[redirect](#method-redirect)
[report](#method-report)
[report_if](#method-report-if)
[report_unless](#method-report-unless)
[request](#method-request)
[rescue](#method-rescue)
[resolve](#method-resolve)
[response](#method-response)
[retry](#method-retry)
[session](#method-session)
[tap](#method-tap)
[throw_if](#method-throw-if)
[throw_unless](#method-throw-unless)
[today](#method-today)
[trait_uses_recursive](#method-trait-uses-recursive)
[transform](#method-transform)
[validator](#method-validator)
[value](#method-value)
[view](#method-view)
[with](#method-with)
[when](#method-when)

</div>

<a name="arrays"></a>
## Масиви та об'єкти

<a name="method-array-accessible"></a>
#### `Arr::accessible()` {.collection-method .first-collection-method}

Метод `Arr::accessible` визначає, чи є задане значення доступним як масив:

```php
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;

$isAccessible = Arr::accessible(['a' => 1, 'b' => 2]);

// true

$isAccessible = Arr::accessible(new Collection);

// true

$isAccessible = Arr::accessible('abc');

// false

$isAccessible = Arr::accessible(new stdClass);

// false
```

<a name="method-array-add"></a>
#### `Arr::add()` {.collection-method}

Метод `Arr::add` додає до масиву задану пару ключ / значення, якщо такого ключа в масиві ще немає або він має значення `null`:

```php
use Illuminate\Support\Arr;

$array = Arr::add(['name' => 'Desk'], 'price', 100);

// ['name' => 'Desk', 'price' => 100]

$array = Arr::add(['name' => 'Desk', 'price' => null], 'price', 100);

// ['name' => 'Desk', 'price' => 100]
```

<a name="method-array-array"></a>
#### `Arr::array()` {.collection-method}

Метод `Arr::array` дістає значення з глибоко вкладеного масиву за «крапковою» нотацією (так само, як [Arr::get()](#method-array-get)), але викидає `InvalidArgumentException`, якщо запитане значення не є `array`:

```php
use Illuminate\Support\Arr;

$array = ['name' => 'Joe', 'languages' => ['PHP', 'Ruby']];

$value = Arr::array($array, 'languages');

// ['PHP', 'Ruby']

$value = Arr::array($array, 'name');

// throws InvalidArgumentException
```

<a name="method-array-boolean"></a>
#### `Arr::boolean()` {.collection-method}

Метод `Arr::boolean` дістає значення з глибоко вкладеного масиву за «крапковою» нотацією (так само, як [Arr::get()](#method-array-get)), але викидає `InvalidArgumentException`, якщо запитане значення не є `boolean`:

```php
use Illuminate\Support\Arr;

$array = ['name' => 'Joe', 'available' => true];

$value = Arr::boolean($array, 'available');

// true

$value = Arr::boolean($array, 'name');

// throws InvalidArgumentException
```


<a name="method-array-collapse"></a>
#### `Arr::collapse()` {.collection-method}

Метод `Arr::collapse` згортає масив масивів або колекцій в один масив:

```php
use Illuminate\Support\Arr;

$array = Arr::collapse([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);

// [1, 2, 3, 4, 5, 6, 7, 8, 9]
```

<a name="method-array-crossjoin"></a>
#### `Arr::crossJoin()` {.collection-method}

Метод `Arr::crossJoin` перехресно поєднує задані масиви, повертаючи декартів добуток з усіма можливими комбінаціями:

```php
use Illuminate\Support\Arr;

$matrix = Arr::crossJoin([1, 2], ['a', 'b']);

/*
    [
        [1, 'a'],
        [1, 'b'],
        [2, 'a'],
        [2, 'b'],
    ]
*/

$matrix = Arr::crossJoin([1, 2], ['a', 'b'], ['I', 'II']);

/*
    [
        [1, 'a', 'I'],
        [1, 'a', 'II'],
        [1, 'b', 'I'],
        [1, 'b', 'II'],
        [2, 'a', 'I'],
        [2, 'a', 'II'],
        [2, 'b', 'I'],
        [2, 'b', 'II'],
    ]
*/
```

<a name="method-array-divide"></a>
#### `Arr::divide()` {.collection-method}

Метод `Arr::divide` повертає два масиви: один з ключами, другий зі значеннями заданого масиву:

```php
use Illuminate\Support\Arr;

[$keys, $values] = Arr::divide(['name' => 'Desk']);

// $keys: ['name']

// $values: ['Desk']
```

<a name="method-array-dot"></a>
#### `Arr::dot()` {.collection-method}

Метод `Arr::dot` сплющує багатовимірний масив в одновимірний, використовуючи «крапкову» нотацію для позначення вкладеності:

```php
use Illuminate\Support\Arr;

$array = ['products' => ['desk' => ['price' => 100]]];

$flattened = Arr::dot($array);

// ['products.desk.price' => 100]
```

<a name="method-array-every"></a>
#### `Arr::every()` {.collection-method}

Метод `Arr::every` переконується, що всі значення масиву проходять заданий тест:

```php
use Illuminate\Support\Arr;

$array = [1, 2, 3];

Arr::every($array, fn ($i) => $i > 0);

// true

Arr::every($array, fn ($i) => $i > 2);

// false
```

<a name="method-array-except"></a>
#### `Arr::except()` {.collection-method}

Метод `Arr::except` вилучає з масиву задані пари ключ / значення:

```php
use Illuminate\Support\Arr;

$array = ['name' => 'Desk', 'price' => 100];

$filtered = Arr::except($array, ['price']);

// ['name' => 'Desk']
```

<a name="method-array-except-values"></a>
#### `Arr::exceptValues()` {.collection-method}

Метод `Arr::exceptValues` вилучає з масиву вказані значення:

```php
use Illuminate\Support\Arr;

$array = ['foo', 'bar', 'baz', 'qux'];

$filtered = Arr::exceptValues($array, ['foo', 'baz']);

// ['bar', 'qux']
```

Ви також можете передати `true` в аргумент `strict`, щоб під час фільтрування використовувати строге порівняння типів:

```php
use Illuminate\Support\Arr;

$array = [1, '1', 2, '2'];

$filtered = Arr::exceptValues($array, [1, 2], strict: true);

// ['1', '2']
```

<a name="method-array-exists"></a>
#### `Arr::exists()` {.collection-method}

Метод `Arr::exists` перевіряє, чи існує заданий ключ у переданому масиві:

```php
use Illuminate\Support\Arr;

$array = ['name' => 'John Doe', 'age' => 17];

$exists = Arr::exists($array, 'name');

// true

$exists = Arr::exists($array, 'salary');

// false
```

<a name="method-array-first"></a>
#### `Arr::first()` {.collection-method}

Метод `Arr::first` повертає перший елемент масиву, який проходить заданий тест:

```php
use Illuminate\Support\Arr;

$array = [100, 200, 300];

$first = Arr::first($array, function (int $value, int $key) {
    return $value >= 150;
});

// 200
```

Третім параметром методу можна передати значення за замовчуванням. Його буде повернуто, якщо жодне значення не пройде тест:

```php
use Illuminate\Support\Arr;

$first = Arr::first($array, $callback, $default);
```

<a name="method-array-flatten"></a>
#### `Arr::flatten()` {.collection-method}

Метод `Arr::flatten` сплющує багатовимірний масив в одновимірний:

```php
use Illuminate\Support\Arr;

$array = ['name' => 'Joe', 'languages' => ['PHP', 'Ruby']];

$flattened = Arr::flatten($array);

// ['Joe', 'PHP', 'Ruby']
```

<a name="method-array-float"></a>
#### `Arr::float()` {.collection-method}

Метод `Arr::float` дістає значення з глибоко вкладеного масиву за «крапковою» нотацією (так само, як [Arr::get()](#method-array-get)), але викидає `InvalidArgumentException`, якщо запитане значення не є `float`:

```php
use Illuminate\Support\Arr;

$array = ['name' => 'Joe', 'balance' => 123.45];

$value = Arr::float($array, 'balance');

// 123.45

$value = Arr::float($array, 'name');

// throws InvalidArgumentException
```

<a name="method-array-forget"></a>
#### `Arr::forget()` {.collection-method}

Метод `Arr::forget` вилучає задані пари ключ / значення з глибоко вкладеного масиву за «крапковою» нотацією:

```php
use Illuminate\Support\Arr;

$array = ['products' => ['desk' => ['price' => 100]]];

Arr::forget($array, 'products.desk');

// ['products' => []]
```

<a name="method-array-from"></a>
#### `Arr::from()` {.collection-method}

Метод `Arr::from` перетворює різні типи вводу на звичайний PHP-масив. Він підтримує низку типів, зокрема масиви, об'єкти та кілька поширених інтерфейсів Laravel - `Arrayable`, `Enumerable`, `Jsonable` та `JsonSerializable`. Крім того, він обробляє екземпляри `Traversable` і `WeakMap`:

```php
use Illuminate\Support\Arr;

Arr::from((object) ['foo' => 'bar']); // ['foo' => 'bar']

class TestJsonableObject implements Jsonable
{
    public function toJson($options = 0)
    {
        return json_encode(['foo' => 'bar']);
    }
}

Arr::from(new TestJsonableObject); // ['foo' => 'bar']
```

<a name="method-array-get"></a>
#### `Arr::get()` {.collection-method}

Метод `Arr::get` дістає значення з глибоко вкладеного масиву за «крапковою» нотацією:

```php
use Illuminate\Support\Arr;

$array = ['products' => ['desk' => ['price' => 100]]];

$price = Arr::get($array, 'products.desk.price');

// 100
```

Метод `Arr::get` також приймає значення за замовчуванням, яке буде повернуто, якщо вказаного ключа в масиві немає:

```php
use Illuminate\Support\Arr;

$discount = Arr::get($array, 'products.desk.discount', 0);

// 0
```

<a name="method-array-has"></a>
#### `Arr::has()` {.collection-method}

Метод `Arr::has` перевіряє, чи існує в масиві заданий елемент або елементи, за «крапковою» нотацією:

```php
use Illuminate\Support\Arr;

$array = ['product' => ['name' => 'Desk', 'price' => 100]];

$contains = Arr::has($array, 'product.name');

// true

$contains = Arr::has($array, ['product.price', 'product.discount']);

// false
```

<a name="method-array-hasall"></a>
#### `Arr::hasAll()` {.collection-method}

Метод `Arr::hasAll` визначає, чи існують у заданому масиві всі вказані ключі, за «крапковою» нотацією:

```php
use Illuminate\Support\Arr;

$array = ['name' => 'Taylor', 'language' => 'PHP'];

Arr::hasAll($array, ['name']); // true
Arr::hasAll($array, ['name', 'language']); // true
Arr::hasAll($array, ['name', 'IDE']); // false
```

<a name="method-array-hasany"></a>
#### `Arr::hasAny()` {.collection-method}

Метод `Arr::hasAny` перевіряє, чи існує в масиві хоч один елемент із заданого набору, за «крапковою» нотацією:

```php
use Illuminate\Support\Arr;

$array = ['product' => ['name' => 'Desk', 'price' => 100]];

$contains = Arr::hasAny($array, 'product.name');

// true

$contains = Arr::hasAny($array, ['product.name', 'product.discount']);

// true

$contains = Arr::hasAny($array, ['category', 'product.discount']);

// false
```

<a name="method-array-integer"></a>
#### `Arr::integer()` {.collection-method}

Метод `Arr::integer` дістає значення з глибоко вкладеного масиву за «крапковою» нотацією (так само, як [Arr::get()](#method-array-get)), але викидає `InvalidArgumentException`, якщо запитане значення не є `int`:

```php
use Illuminate\Support\Arr;

$array = ['name' => 'Joe', 'age' => 42];

$value = Arr::integer($array, 'age');

// 42

$value = Arr::integer($array, 'name');

// throws InvalidArgumentException
```

<a name="method-array-isassoc"></a>
#### `Arr::isAssoc()` {.collection-method}

Метод `Arr::isAssoc` повертає `true`, якщо заданий масив є асоціативним. Масив вважається «асоціативним», якщо він не має послідовних числових ключів, що починаються з нуля:

```php
use Illuminate\Support\Arr;

$isAssoc = Arr::isAssoc(['product' => ['name' => 'Desk', 'price' => 100]]);

// true

$isAssoc = Arr::isAssoc([1, 2, 3]);

// false
```

<a name="method-array-islist"></a>
#### `Arr::isList()` {.collection-method}

Метод `Arr::isList` повертає `true`, якщо ключі заданого масиву - послідовні цілі числа, що починаються з нуля:

```php
use Illuminate\Support\Arr;

$isList = Arr::isList(['foo', 'bar', 'baz']);

// true

$isList = Arr::isList(['product' => ['name' => 'Desk', 'price' => 100]]);

// false
```

<a name="method-array-join"></a>
#### `Arr::join()` {.collection-method}

Метод `Arr::join` склеює елементи масиву рядком. Третім аргументом цього методу ви можете вказати рядок, яким приєднується останній елемент масиву:

```php
use Illuminate\Support\Arr;

$array = ['Tailwind', 'Alpine', 'Laravel', 'Livewire'];

$joined = Arr::join($array, ', ');

// Tailwind, Alpine, Laravel, Livewire

$joined = Arr::join($array, ', ', ', and ');

// Tailwind, Alpine, Laravel, and Livewire
```

<a name="method-array-keyby"></a>
#### `Arr::keyBy()` {.collection-method}

Метод `Arr::keyBy` робить заданий ключ ключем масиву. Якщо кілька елементів мають однаковий ключ, у новому масиві залишиться тільки останній:

```php
use Illuminate\Support\Arr;

$array = [
    ['product_id' => 'prod-100', 'name' => 'Desk'],
    ['product_id' => 'prod-200', 'name' => 'Chair'],
];

$keyed = Arr::keyBy($array, 'product_id');

/*
    [
        'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'],
        'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'],
    ]
*/
```

<a name="method-array-last"></a>
#### `Arr::last()` {.collection-method}

Метод `Arr::last` повертає останній елемент масиву, який проходить заданий тест:

```php
use Illuminate\Support\Arr;

$array = [100, 200, 300, 110];

$last = Arr::last($array, function (int $value, int $key) {
    return $value >= 150;
});

// 300
```

Третім аргументом методу можна передати значення за замовчуванням. Його буде повернуто, якщо жодне значення не пройде тест:

```php
use Illuminate\Support\Arr;

$last = Arr::last($array, $callback, $default);
```

<a name="method-array-map"></a>
#### `Arr::map()` {.collection-method}

Метод `Arr::map` проходить масив і передає кожне значення та ключ до заданого колбека. Значення масиву замінюється тим, що повернув колбек:

```php
use Illuminate\Support\Arr;

$array = ['first' => 'james', 'last' => 'kirk'];

$mapped = Arr::map($array, function (string $value, string $key) {
    return ucfirst($value);
});

// ['first' => 'James', 'last' => 'Kirk']
```

<a name="method-array-map-spread"></a>
#### `Arr::mapSpread()` {.collection-method}

Метод `Arr::mapSpread` проходить масив, передаючи значення кожного вкладеного елемента до заданого замикання. Замикання вільне змінити елемент і повернути його, утворюючи так новий масив змінених елементів:

```php
use Illuminate\Support\Arr;

$array = [
    [0, 1],
    [2, 3],
    [4, 5],
    [6, 7],
    [8, 9],
];

$mapped = Arr::mapSpread($array, function (int $even, int $odd) {
    return $even + $odd;
});

/*
    [1, 5, 9, 13, 17]
*/
```

<a name="method-array-map-with-keys"></a>
#### `Arr::mapWithKeys()` {.collection-method}

Метод `Arr::mapWithKeys` проходить масив і передає кожне значення до заданого колбека. Колбек має повернути асоціативний масив з єдиною парою ключ / значення:

```php
use Illuminate\Support\Arr;

$array = [
    [
        'name' => 'John',
        'department' => 'Sales',
        'email' => 'john@example.com',
    ],
    [
        'name' => 'Jane',
        'department' => 'Marketing',
        'email' => 'jane@example.com',
    ]
];

$mapped = Arr::mapWithKeys($array, function (array $item, int $key) {
    return [$item['email'] => $item['name']];
});

/*
    [
        'john@example.com' => 'John',
        'jane@example.com' => 'Jane',
    ]
*/
```

<a name="method-array-only"></a>
#### `Arr::only()` {.collection-method}

Метод `Arr::only` повертає із заданого масиву лише вказані пари ключ / значення:

```php
use Illuminate\Support\Arr;

$array = ['name' => 'Desk', 'price' => 100, 'orders' => 10];

$slice = Arr::only($array, ['name', 'price']);

// ['name' => 'Desk', 'price' => 100]
```

<a name="method-array-only-values"></a>
#### `Arr::onlyValues()` {.collection-method}

Метод `Arr::onlyValues` повертає з масиву лише вказані значення:

```php
use Illuminate\Support\Arr;

$array = ['foo', 'bar', 'baz', 'qux'];

$filtered = Arr::onlyValues($array, ['foo', 'baz']);

// ['foo', 'baz']
```

Ви також можете передати `true` в аргумент `strict`, щоб під час фільтрування використовувати строге порівняння типів:

```php
use Illuminate\Support\Arr;

$array = [1, '1', 2, '2'];

$filtered = Arr::onlyValues($array, [1, 2], strict: true);

// [1, 2]
```

<a name="method-array-partition"></a>
#### `Arr::partition()` {.collection-method}

Метод `Arr::partition` можна поєднати з деструктуризацією масивів PHP, щоб відділити елементи, які проходять заданий тест, від тих, які його не проходять:

```php
<?php

use Illuminate\Support\Arr;

$numbers = [1, 2, 3, 4, 5, 6];

[$underThree, $equalOrAboveThree] = Arr::partition($numbers, function (int $i) {
    return $i < 3;
});

dump($underThree);

// [1, 2]

dump($equalOrAboveThree);

// [3, 4, 5, 6]
```

<a name="method-array-pluck"></a>
#### `Arr::pluck()` {.collection-method}

Метод `Arr::pluck` дістає з масиву всі значення за заданим ключем:

```php
use Illuminate\Support\Arr;

$array = [
    ['developer' => ['id' => 1, 'name' => 'Taylor']],
    ['developer' => ['id' => 2, 'name' => 'Abigail']],
];

$names = Arr::pluck($array, 'developer.name');

// ['Taylor', 'Abigail']
```

Ви також можете вказати, за яким ключем має будуватися отриманий список:

```php
use Illuminate\Support\Arr;

$names = Arr::pluck($array, 'developer.name', 'developer.id');

// [1 => 'Taylor', 2 => 'Abigail']
```

<a name="method-array-prepend"></a>
#### `Arr::prepend()` {.collection-method}

Метод `Arr::prepend` додає елемент на початок масиву:

```php
use Illuminate\Support\Arr;

$array = ['one', 'two', 'three', 'four'];

$array = Arr::prepend($array, 'zero');

// ['zero', 'one', 'two', 'three', 'four']
```

За потреби ви можете вказати ключ, який слід використати для значення:

```php
use Illuminate\Support\Arr;

$array = ['price' => 100];

$array = Arr::prepend($array, 'Desk', 'name');

// ['name' => 'Desk', 'price' => 100]
```

<a name="method-array-prependkeyswith"></a>
#### `Arr::prependKeysWith()` {.collection-method}

Метод `Arr::prependKeysWith` додає заданий префікс до всіх назв ключів асоціативного масиву:

```php
use Illuminate\Support\Arr;

$array = [
    'name' => 'Desk',
    'price' => 100,
];

$keyed = Arr::prependKeysWith($array, 'product.');

/*
    [
        'product.name' => 'Desk',
        'product.price' => 100,
    ]
*/
```

<a name="method-array-pull"></a>
#### `Arr::pull()` {.collection-method}

Метод `Arr::pull` повертає й вилучає з масиву пару ключ / значення:

```php
use Illuminate\Support\Arr;

$array = ['name' => 'Desk', 'price' => 100];

$name = Arr::pull($array, 'name');

// $name: Desk

// $array: ['price' => 100]
```

Третім аргументом методу можна передати значення за замовчуванням. Його буде повернуто, якщо ключа не існує:

```php
use Illuminate\Support\Arr;

$value = Arr::pull($array, $key, $default);
```

<a name="method-array-push"></a>
#### `Arr::push()` {.collection-method}

Метод `Arr::push` додає елемент до масиву за «крапковою» нотацією. Якщо за заданим ключем масиву немає, його буде створено:

```php
use Illuminate\Support\Arr;

$array = [];

Arr::push($array, 'office.furniture', 'Desk');

// $array: ['office' => ['furniture' => ['Desk']]]
```

<a name="method-array-query"></a>
#### `Arr::query()` {.collection-method}

Метод `Arr::query` перетворює масив на рядок запиту:

```php
use Illuminate\Support\Arr;

$array = [
    'name' => 'Taylor',
    'order' => [
        'column' => 'created_at',
        'direction' => 'desc'
    ]
];

Arr::query($array);

// name=Taylor&order%5Bcolumn%5D=created_at&order%5Bdirection%5D=desc
```

<a name="method-array-random"></a>
#### `Arr::random()` {.collection-method}

Метод `Arr::random` повертає випадкове значення з масиву:

```php
use Illuminate\Support\Arr;

$array = [1, 2, 3, 4, 5];

$random = Arr::random($array);

// 4 - (retrieved randomly)
```

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

```php
use Illuminate\Support\Arr;

$items = Arr::random($array, 2);

// [2, 5] - (retrieved randomly)
```

<a name="method-array-reject"></a>
#### `Arr::reject()` {.collection-method}

Метод `Arr::reject` вилучає елементи з масиву за допомогою заданого замикання:

```php
use Illuminate\Support\Arr;

$array = [100, '200', 300, '400', 500];

$filtered = Arr::reject($array, function (string|int $value, int $key) {
    return is_string($value);
});

// [0 => 100, 2 => 300, 4 => 500]
```

<a name="method-array-select"></a>
#### `Arr::select()` {.collection-method}

Метод `Arr::select` вибирає з масиву масив значень:

```php
use Illuminate\Support\Arr;

$array = [
    ['id' => 1, 'name' => 'Desk', 'price' => 200],
    ['id' => 2, 'name' => 'Table', 'price' => 150],
    ['id' => 3, 'name' => 'Chair', 'price' => 300],
];

Arr::select($array, ['name', 'price']);

// [['name' => 'Desk', 'price' => 200], ['name' => 'Table', 'price' => 150], ['name' => 'Chair', 'price' => 300]]
```

<a name="method-array-set"></a>
#### `Arr::set()` {.collection-method}

Метод `Arr::set` встановлює значення в глибоко вкладеному масиві за «крапковою» нотацією:

```php
use Illuminate\Support\Arr;

$array = ['products' => ['desk' => ['price' => 100]]];

Arr::set($array, 'products.desk.price', 200);

// ['products' => ['desk' => ['price' => 200]]]
```

<a name="method-array-shuffle"></a>
#### `Arr::shuffle()` {.collection-method}

Метод `Arr::shuffle` випадково перемішує елементи масиву:

```php
use Illuminate\Support\Arr;

$array = Arr::shuffle([1, 2, 3, 4, 5]);

// [3, 2, 5, 1, 4] - (generated randomly)
```

<a name="method-array-sole"></a>
#### `Arr::sole()` {.collection-method}

Метод `Arr::sole` дістає з масиву єдине значення за допомогою заданого замикання. Якщо заданий тест проходить більше ніж одне значення масиву, буде викинуто виняток `Illuminate\Support\MultipleItemsFoundException`. Якщо тест не проходить жодне значення, буде викинуто виняток `Illuminate\Support\ItemNotFoundException`:

```php
use Illuminate\Support\Arr;

$array = ['Desk', 'Table', 'Chair'];

$value = Arr::sole($array, fn (string $value) => $value === 'Desk');

// 'Desk'
```

<a name="method-array-some"></a>
#### `Arr::some()` {.collection-method}

Метод `Arr::some` переконується, що заданий тест проходить щонайменше одне значення масиву:

```php
use Illuminate\Support\Arr;

$array = [1, 2, 3];

Arr::some($array, fn ($i) => $i > 2);

// true
```

<a name="method-array-sort"></a>
#### `Arr::sort()` {.collection-method}

Метод `Arr::sort` сортує масив за його значеннями:

```php
use Illuminate\Support\Arr;

$array = ['Desk', 'Table', 'Chair'];

$sorted = Arr::sort($array);

// ['Chair', 'Desk', 'Table']
```

Ви також можете відсортувати масив за результатами заданого замикання:

```php
use Illuminate\Support\Arr;

$array = [
    ['name' => 'Desk'],
    ['name' => 'Table'],
    ['name' => 'Chair'],
];

$sorted = array_values(Arr::sort($array, function (array $value) {
    return $value['name'];
}));

/*
    [
        ['name' => 'Chair'],
        ['name' => 'Desk'],
        ['name' => 'Table'],
    ]
*/
```

<a name="method-array-sort-desc"></a>
#### `Arr::sortDesc()` {.collection-method}

Метод `Arr::sortDesc` сортує масив за його значеннями у спадному порядку:

```php
use Illuminate\Support\Arr;

$array = ['Desk', 'Table', 'Chair'];

$sorted = Arr::sortDesc($array);

// ['Table', 'Desk', 'Chair']
```

Ви також можете відсортувати масив за результатами заданого замикання:

```php
use Illuminate\Support\Arr;

$array = [
    ['name' => 'Desk'],
    ['name' => 'Table'],
    ['name' => 'Chair'],
];

$sorted = array_values(Arr::sortDesc($array, function (array $value) {
    return $value['name'];
}));

/*
    [
        ['name' => 'Table'],
        ['name' => 'Desk'],
        ['name' => 'Chair'],
    ]
*/
```

<a name="method-array-sort-recursive"></a>
#### `Arr::sortRecursive()` {.collection-method}

Метод `Arr::sortRecursive` рекурсивно сортує масив, використовуючи функцію `sort` для підмасивів з числовими індексами та функцію `ksort` для асоціативних підмасивів:

```php
use Illuminate\Support\Arr;

$array = [
    ['Roman', 'Taylor', 'Li'],
    ['PHP', 'Ruby', 'JavaScript'],
    ['one' => 1, 'two' => 2, 'three' => 3],
];

$sorted = Arr::sortRecursive($array);

/*
    [
        ['JavaScript', 'PHP', 'Ruby'],
        ['one' => 1, 'three' => 3, 'two' => 2],
        ['Li', 'Roman', 'Taylor'],
    ]
*/
```

Якщо ви хочете отримати результати у спадному порядку, скористайтеся методом `Arr::sortRecursiveDesc`.

```php
$sorted = Arr::sortRecursiveDesc($array);
```

<a name="method-array-string"></a>
#### `Arr::string()` {.collection-method}

Метод `Arr::string` дістає значення з глибоко вкладеного масиву за «крапковою» нотацією (так само, як [Arr::get()](#method-array-get)), але викидає `InvalidArgumentException`, якщо запитане значення не є `string`:

```php
use Illuminate\Support\Arr;

$array = ['name' => 'Joe', 'languages' => ['PHP', 'Ruby']];

$value = Arr::string($array, 'name');

// Joe

$value = Arr::string($array, 'languages');

// throws InvalidArgumentException
```

<a name="method-array-take"></a>
#### `Arr::take()` {.collection-method}

Метод `Arr::take` повертає новий масив із вказаною кількістю елементів:

```php
use Illuminate\Support\Arr;

$array = [0, 1, 2, 3, 4, 5];

$chunk = Arr::take($array, 3);

// [0, 1, 2]
```

Ви також можете передати від'ємне ціле число, щоб узяти вказану кількість елементів з кінця масиву:

```php
$array = [0, 1, 2, 3, 4, 5];

$chunk = Arr::take($array, -2);

// [4, 5]
```

<a name="method-array-to-css-classes"></a>
#### `Arr::toCssClasses()` {.collection-method}

Метод `Arr::toCssClasses` умовно збирає рядок CSS-класів. Метод приймає масив класів, де ключ масиву містить клас або класи, які ви хочете додати, а значення є булевим виразом. Якщо елемент масиву має числовий ключ, він завжди потрапить до згенерованого списку класів:

```php
use Illuminate\Support\Arr;

$isActive = false;
$hasError = true;

$array = ['p-4', 'font-bold' => $isActive, 'bg-red' => $hasError];

$classes = Arr::toCssClasses($array);

/*
    'p-4 bg-red'
*/
```

<a name="method-array-to-css-styles"></a>
#### `Arr::toCssStyles()` {.collection-method}

Метод `Arr::toCssStyles` умовно збирає рядок CSS-стилів. Метод приймає масив CSS-оголошень, де ключ масиву містить CSS-оголошення, яке ви хочете додати, а значення є булевим виразом. Якщо елемент масиву має числовий ключ, він завжди потрапить до зібраного рядка CSS-стилів:

```php
use Illuminate\Support\Arr;

$hasColor = true;

$array = ['background-color: blue', 'color: blue' => $hasColor];

$classes = Arr::toCssStyles($array);

/*
    'background-color: blue; color: blue;'
*/
```

Цей метод лежить в основі можливості Laravel [зливати класи з мішком атрибутів Blade-компонента](/docs/{{version}}/blade#conditionally-merge-classes), а також [директиви Blade](/docs/{{version}}/blade#conditional-classes) `@class`.

<a name="method-array-undot"></a>
#### `Arr::undot()` {.collection-method}

Метод `Arr::undot` розгортає одновимірний масив з «крапковою» нотацією на багатовимірний:

```php
use Illuminate\Support\Arr;

$array = [
    'user.name' => 'Kevin Malone',
    'user.occupation' => 'Accountant',
];

$array = Arr::undot($array);

// ['user' => ['name' => 'Kevin Malone', 'occupation' => 'Accountant']]
```

<a name="method-array-where"></a>
#### `Arr::where()` {.collection-method}

Метод `Arr::where` фільтрує масив за допомогою заданого замикання:

```php
use Illuminate\Support\Arr;

$array = [100, '200', 300, '400', 500];

$filtered = Arr::where($array, function (string|int $value, int $key) {
    return is_string($value);
});

// [1 => '200', 3 => '400']
```

<a name="method-array-where-not-null"></a>
#### `Arr::whereNotNull()` {.collection-method}

Метод `Arr::whereNotNull` вилучає із заданого масиву всі значення `null`:

```php
use Illuminate\Support\Arr;

$array = [0, null];

$filtered = Arr::whereNotNull($array);

// [0 => 0]
```

<a name="method-array-wrap"></a>
#### `Arr::wrap()` {.collection-method}

Метод `Arr::wrap` загортає задане значення в масив. Якщо задане значення вже є масивом, його буде повернуто без змін:

```php
use Illuminate\Support\Arr;

$string = 'Laravel';

$array = Arr::wrap($string);

// ['Laravel']
```

Якщо задане значення - `null`, буде повернуто порожній масив:

```php
use Illuminate\Support\Arr;

$array = Arr::wrap(null);

// []
```

<a name="method-data-fill"></a>
#### `data_fill()` {.collection-method}

Функція `data_fill` встановлює відсутнє значення у вкладеному масиві чи об'єкті за «крапковою» нотацією:

```php
$data = ['products' => ['desk' => ['price' => 100]]];

data_fill($data, 'products.desk.price', 200);

// ['products' => ['desk' => ['price' => 100]]]

data_fill($data, 'products.desk.discount', 10);

// ['products' => ['desk' => ['price' => 100, 'discount' => 10]]]
```

Ця функція також приймає зірочки як підстановки й заповнить ціль відповідно:

```php
$data = [
    'products' => [
        ['name' => 'Desk 1', 'price' => 100],
        ['name' => 'Desk 2'],
    ],
];

data_fill($data, 'products.*.price', 200);

/*
    [
        'products' => [
            ['name' => 'Desk 1', 'price' => 100],
            ['name' => 'Desk 2', 'price' => 200],
        ],
    ]
*/
```

<a name="method-data-get"></a>
#### `data_get()` {.collection-method}

Функція `data_get` дістає значення з вкладеного масиву чи об'єкта за «крапковою» нотацією:

```php
$data = ['products' => ['desk' => ['price' => 100]]];

$price = data_get($data, 'products.desk.price');

// 100
```

Функція `data_get` також приймає значення за замовчуванням, яке буде повернуто, якщо вказаного ключа не знайдено:

```php
$discount = data_get($data, 'products.desk.discount', 0);

// 0
```

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

```php
$data = [
    'product-one' => ['name' => 'Desk 1', 'price' => 100],
    'product-two' => ['name' => 'Desk 2', 'price' => 150],
];

data_get($data, '*.name');

// ['Desk 1', 'Desk 2'];
```

Плейсхолдери `{first}` та `{last}` дозволяють дістати перший чи останній елементи масиву:

```php
$flight = [
    'segments' => [
        ['from' => 'LHR', 'departure' => '9:00', 'to' => 'IST', 'arrival' => '15:00'],
        ['from' => 'IST', 'departure' => '16:00', 'to' => 'PKX', 'arrival' => '20:00'],
    ],
];

data_get($flight, 'segments.{first}.arrival');

// 15:00
```

<a name="method-data-set"></a>
#### `data_set()` {.collection-method}

Функція `data_set` встановлює значення у вкладеному масиві чи об'єкті за «крапковою» нотацією:

```php
$data = ['products' => ['desk' => ['price' => 100]]];

data_set($data, 'products.desk.price', 200);

// ['products' => ['desk' => ['price' => 200]]]
```

Ця функція також приймає підстановки у вигляді зірочок і встановить значення в цілі відповідно:

```php
$data = [
    'products' => [
        ['name' => 'Desk 1', 'price' => 100],
        ['name' => 'Desk 2', 'price' => 150],
    ],
];

data_set($data, 'products.*.price', 200);

/*
    [
        'products' => [
            ['name' => 'Desk 1', 'price' => 200],
            ['name' => 'Desk 2', 'price' => 200],
        ],
    ]
*/
```

За замовчуванням будь-які наявні значення перезаписуються. Якщо ви хочете встановити значення, лише коли його ще немає, передайте четвертим аргументом функції `false`:

```php
$data = ['products' => ['desk' => ['price' => 100]]];

data_set($data, 'products.desk.price', 200, overwrite: false);

// ['products' => ['desk' => ['price' => 100]]]
```

<a name="method-data-forget"></a>
#### `data_forget()` {.collection-method}

Функція `data_forget` вилучає значення з вкладеного масиву чи об'єкта за «крапковою» нотацією:

```php
$data = ['products' => ['desk' => ['price' => 100]]];

data_forget($data, 'products.desk.price');

// ['products' => ['desk' => []]]
```

Ця функція також приймає підстановки у вигляді зірочок і вилучить значення в цілі відповідно:

```php
$data = [
    'products' => [
        ['name' => 'Desk 1', 'price' => 100],
        ['name' => 'Desk 2', 'price' => 150],
    ],
];

data_forget($data, 'products.*.price');

/*
    [
        'products' => [
            ['name' => 'Desk 1'],
            ['name' => 'Desk 2'],
        ],
    ]
*/
```

<a name="method-head"></a>
#### `head()` {.collection-method}

Функція `head` повертає перший елемент заданого масиву. Якщо масив порожній, буде повернуто `false`:

```php
$array = [100, 200, 300];

$first = head($array);

// 100
```

<a name="method-last"></a>
#### `last()` {.collection-method}

Функція `last` повертає останній елемент заданого масиву. Якщо масив порожній, буде повернуто `false`:

```php
$array = [100, 200, 300];

$last = last($array);

// 300
```

<a name="numbers"></a>
## Числа

<a name="method-number-abbreviate"></a>
#### `Number::abbreviate()` {.collection-method}

Метод `Number::abbreviate` повертає зручний для читання формат переданого числового значення зі скороченням одиниць:

```php
use Illuminate\Support\Number;

$number = Number::abbreviate(1000);

// 1K

$number = Number::abbreviate(489939);

// 490K

$number = Number::abbreviate(1230000, precision: 2);

// 1.23M
```

<a name="method-number-clamp"></a>
#### `Number::clamp()` {.collection-method}

Метод `Number::clamp` гарантує, що задане число лишається у вказаному діапазоні. Якщо число менше за мінімум, повертається мінімальне значення. Якщо число більше за максимум, повертається максимальне значення:

```php
use Illuminate\Support\Number;

$number = Number::clamp(105, min: 10, max: 100);

// 100

$number = Number::clamp(5, min: 10, max: 100);

// 10

$number = Number::clamp(10, min: 10, max: 100);

// 10

$number = Number::clamp(20, min: 10, max: 100);

// 20
```

<a name="method-number-currency"></a>
#### `Number::currency()` {.collection-method}

Метод `Number::currency` повертає рядкове представлення заданого значення у вигляді валюти:

```php
use Illuminate\Support\Number;

$currency = Number::currency(1000);

// $1,000.00

$currency = Number::currency(1000, in: 'EUR');

// €1,000.00

$currency = Number::currency(1000, in: 'EUR', locale: 'de');

// 1.000,00 €

$currency = Number::currency(1000, in: 'EUR', locale: 'de', precision: 0);

// 1.000 €
```

<a name="method-default-currency"></a>
#### `Number::defaultCurrency()` {.collection-method}

Метод `Number::defaultCurrency` повертає валюту за замовчуванням, яку використовує клас `Number`:

```php
use Illuminate\Support\Number;

$currency = Number::defaultCurrency();

// USD
```

<a name="method-default-locale"></a>
#### `Number::defaultLocale()` {.collection-method}

Метод `Number::defaultLocale` повертає локаль за замовчуванням, яку використовує клас `Number`:

```php
use Illuminate\Support\Number;

$locale = Number::defaultLocale();

// en
```

<a name="method-number-file-size"></a>
#### `Number::fileSize()` {.collection-method}

Метод `Number::fileSize` повертає рядкове представлення заданої кількості байтів у вигляді розміру файлу:

```php
use Illuminate\Support\Number;

$size = Number::fileSize(1024);

// 1 KB

$size = Number::fileSize(1024 * 1024);

// 1 MB

$size = Number::fileSize(1024, precision: 2);

// 1.00 KB
```

<a name="method-number-for-humans"></a>
#### `Number::forHumans()` {.collection-method}

Метод `Number::forHumans` повертає зручний для читання формат переданого числового значення:

```php
use Illuminate\Support\Number;

$number = Number::forHumans(1000);

// 1 thousand

$number = Number::forHumans(489939);

// 490 thousand

$number = Number::forHumans(1230000, precision: 2);

// 1.23 million
```

<a name="method-number-format"></a>
#### `Number::format()` {.collection-method}

Метод `Number::format` форматує задане число в рядок відповідно до локалі:

```php
use Illuminate\Support\Number;

$number = Number::format(100000);

// 100,000

$number = Number::format(100000, precision: 2);

// 100,000.00

$number = Number::format(100000.123, maxPrecision: 2);

// 100,000.12

$number = Number::format(100000, locale: 'de');

// 100.000
```

<a name="method-number-ordinal"></a>
#### `Number::ordinal()` {.collection-method}

Метод `Number::ordinal` повертає порядкове представлення числа:

```php
use Illuminate\Support\Number;

$number = Number::ordinal(1);

// 1st

$number = Number::ordinal(2);

// 2nd

$number = Number::ordinal(21);

// 21st
```

<a name="method-number-pairs"></a>
#### `Number::pairs()` {.collection-method}

Метод `Number::pairs` генерує масив пар чисел (піддіапазонів) на основі заданого діапазону й кроку. Цей метод стане в пригоді, коли треба розділити великий діапазон чисел на менші, зручні піддіапазони - наприклад, для пагінації чи пакетної обробки. Метод `pairs` повертає масив масивів, де кожен внутрішній масив представляє пару (піддіапазон) чисел:

```php
use Illuminate\Support\Number;

$result = Number::pairs(25, 10);

// [[0, 9], [10, 19], [20, 25]]

$result = Number::pairs(25, 10, offset: 0);

// [[0, 10], [10, 20], [20, 25]]
```

<a name="method-number-parse"></a>
#### `Number::parse()` {.collection-method}

Метод `Number::parse` розбирає локалізований числовий рядок за допомогою PHP-класу `NumberFormatter`:

```php
use Illuminate\Support\Number;

$result = Number::parse('10,123', locale: 'en');

// 10123.0

$result = Number::parse('10,123', locale: 'fr');

// 10.123
```

<a name="method-number-parse-int"></a>
#### `Number::parseInt()` {.collection-method}

Метод `Number::parseInt` розбирає рядок на ціле число відповідно до вказаної локалі:

```php
use Illuminate\Support\Number;

$result = Number::parseInt('10.123');

// (int) 10

$result = Number::parseInt('10,123', locale: 'fr');

// (int) 10
```

<a name="method-number-parse-float"></a>
#### `Number::parseFloat()` {.collection-method}

Метод `Number::parseFloat` розбирає рядок на число з рухомою комою відповідно до вказаної локалі:

```php
use Illuminate\Support\Number;

$result = Number::parseFloat('10');

// (float) 10.0

$result = Number::parseFloat('10', locale: 'fr');

// (float) 10.0
```

<a name="method-number-percentage"></a>
#### `Number::percentage()` {.collection-method}

Метод `Number::percentage` повертає рядкове представлення заданого значення у вигляді відсотка:

```php
use Illuminate\Support\Number;

$percentage = Number::percentage(10);

// 10%

$percentage = Number::percentage(10, precision: 2);

// 10.00%

$percentage = Number::percentage(10.123, maxPrecision: 2);

// 10.12%

$percentage = Number::percentage(10, precision: 2, locale: 'de');

// 10,00%
```

<a name="method-number-spell"></a>
#### `Number::spell()` {.collection-method}

Метод `Number::spell` перетворює задане число на рядок зі слів:

```php
use Illuminate\Support\Number;

$number = Number::spell(102);

// one hundred and two

$number = Number::spell(88, locale: 'fr');

// quatre-vingt-huit
```

Аргумент `after` дозволяє вказати значення, після якого всі числа слід писати словами:

```php
$number = Number::spell(10, after: 10);

// 10

$number = Number::spell(11, after: 10);

// eleven
```

Аргумент `until` дозволяє вказати значення, до якого всі числа слід писати словами:

```php
$number = Number::spell(5, until: 10);

// five

$number = Number::spell(10, until: 10);

// 10
```

<a name="method-number-spell-ordinal"></a>
#### `Number::spellOrdinal()` {.collection-method}

Метод `Number::spellOrdinal` повертає порядкове представлення числа у вигляді рядка зі слів:

```php
use Illuminate\Support\Number;

$number = Number::spellOrdinal(1);

// first

$number = Number::spellOrdinal(2);

// second

$number = Number::spellOrdinal(21);

// twenty-first
```

<a name="method-number-trim"></a>
#### `Number::trim()` {.collection-method}

Метод `Number::trim` вилучає всі кінцеві нулі після десяткової крапки заданого числа:

```php
use Illuminate\Support\Number;

$number = Number::trim(12.0);

// 12

$number = Number::trim(12.30);

// 12.3
```

<a name="method-number-use-locale"></a>
#### `Number::useLocale()` {.collection-method}

Метод `Number::useLocale` глобально задає локаль чисел за замовчуванням, що впливає на форматування чисел і валюти під час наступних викликів методів класу `Number`:

```php
use Illuminate\Support\Number;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Number::useLocale('de');
}
```

<a name="method-number-with-locale"></a>
#### `Number::withLocale()` {.collection-method}

Метод `Number::withLocale` виконує задане замикання із вказаною локаллю, а після виконання колбека відновлює оригінальну локаль:

```php
use Illuminate\Support\Number;

$number = Number::withLocale('de', function () {
    return Number::format(1500);
});
```

<a name="method-number-use-currency"></a>
#### `Number::useCurrency()` {.collection-method}

Метод `Number::useCurrency` глобально задає валюту чисел за замовчуванням, що впливає на форматування валюти під час наступних викликів методів класу `Number`:

```php
use Illuminate\Support\Number;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Number::useCurrency('GBP');
}
```

<a name="method-number-with-currency"></a>
#### `Number::withCurrency()` {.collection-method}

Метод `Number::withCurrency` виконує задане замикання із вказаною валютою, а після виконання колбека відновлює оригінальну валюту:

```php
use Illuminate\Support\Number;

$number = Number::withCurrency('GBP', function () {
    // ...
});
```

<a name="paths"></a>
## Шляхи

<a name="method-app-path"></a>
#### `app_path()` {.collection-method}

Функція `app_path` повертає повний шлях до каталогу `app` вашого застосунку. Ви також можете скористатися функцією `app_path`, щоб згенерувати повний шлях до файлу відносно каталогу застосунку:

```php
$path = app_path();

$path = app_path('Http/Controllers/Controller.php');
```

<a name="method-base-path"></a>
#### `base_path()` {.collection-method}

Функція `base_path` повертає повний шлях до кореневого каталогу вашого застосунку. Ви також можете скористатися функцією `base_path`, щоб згенерувати повний шлях до заданого файлу відносно кореня проєкту:

```php
$path = base_path();

$path = base_path('vendor/bin');
```

<a name="method-config-path"></a>
#### `config_path()` {.collection-method}

Функція `config_path` повертає повний шлях до каталогу `config` вашого застосунку. Ви також можете скористатися функцією `config_path`, щоб згенерувати повний шлях до заданого файлу в каталозі конфігурації застосунку:

```php
$path = config_path();

$path = config_path('app.php');
```

<a name="method-database-path"></a>
#### `database_path()` {.collection-method}

Функція `database_path` повертає повний шлях до каталогу `database` вашого застосунку. Ви також можете скористатися функцією `database_path`, щоб згенерувати повний шлях до заданого файлу в каталозі бази даних:

```php
$path = database_path();

$path = database_path('factories/UserFactory.php');
```

<a name="method-lang-path"></a>
#### `lang_path()` {.collection-method}

Функція `lang_path` повертає повний шлях до каталогу `lang` вашого застосунку. Ви також можете скористатися функцією `lang_path`, щоб згенерувати повний шлях до заданого файлу в цьому каталозі:

```php
$path = lang_path();

$path = lang_path('en/messages.php');
```

> [!NOTE]
> За замовчуванням каркас застосунку Laravel не містить каталогу `lang`. Якщо ви хочете налаштувати мовні файли Laravel, опублікуйте їх командою Artisan `lang:publish`.

<a name="method-public-path"></a>
#### `public_path()` {.collection-method}

Функція `public_path` повертає повний шлях до каталогу `public` вашого застосунку. Ви також можете скористатися функцією `public_path`, щоб згенерувати повний шлях до заданого файлу в каталозі public:

```php
$path = public_path();

$path = public_path('css/app.css');
```

<a name="method-resource-path"></a>
#### `resource_path()` {.collection-method}

Функція `resource_path` повертає повний шлях до каталогу `resources` вашого застосунку. Ви також можете скористатися функцією `resource_path`, щоб згенерувати повний шлях до заданого файлу в каталозі resources:

```php
$path = resource_path();

$path = resource_path('sass/app.scss');
```

<a name="method-storage-path"></a>
#### `storage_path()` {.collection-method}

Функція `storage_path` повертає повний шлях до каталогу `storage` вашого застосунку. Ви також можете скористатися функцією `storage_path`, щоб згенерувати повний шлях до заданого файлу в каталозі storage:

```php
$path = storage_path();

$path = storage_path('app/file.txt');
```

<a name="urls"></a>
## URL

<a name="method-action"></a>
#### `action()` {.collection-method}

Функція `action` генерує URL для заданої дії контролера:

```php
use App\Http\Controllers\HomeController;

$url = action([HomeController::class, 'index']);
```

Якщо метод приймає параметри маршруту, передайте їх другим аргументом:

```php
$url = action([UserController::class, 'profile'], ['id' => 1]);
```

<a name="method-asset"></a>
#### `asset()` {.collection-method}

Функція `asset` генерує URL для ресурсу, використовуючи поточну схему запиту (HTTP чи HTTPS):

```php
$url = asset('img/photo.jpg');
```

Ви можете налаштувати хост URL ресурсів, задавши змінну `ASSET_URL` у файлі `.env`. Це стане в пригоді, якщо ви розміщуєте ресурси на зовнішньому сервісі на кшталт Amazon S3 чи іншого CDN:

```php
// ASSET_URL=http://example.com/assets

$url = asset('img/photo.jpg'); // http://example.com/assets/img/photo.jpg
```

<a name="method-route"></a>
#### `route()` {.collection-method}

Функція `route` генерує URL для заданого [іменованого маршруту](/docs/{{version}}/routing#named-routes):

```php
$url = route('route.name');
```

Якщо маршрут приймає параметри, передайте їх другим аргументом функції:

```php
$url = route('route.name', ['id' => 1]);
```

За замовчуванням функція `route` генерує абсолютний URL. Якщо ви хочете згенерувати відносний URL, передайте третім аргументом функції `false`:

```php
$url = route('route.name', ['id' => 1], false);
```

<a name="method-secure-asset"></a>
#### `secure_asset()` {.collection-method}

Функція `secure_asset` генерує URL для ресурсу через HTTPS:

```php
$url = secure_asset('img/photo.jpg');
```

<a name="method-secure-url"></a>
#### `secure_url()` {.collection-method}

Функція `secure_url` генерує повний HTTPS-URL до заданого шляху. Додаткові сегменти URL можна передати другим аргументом функції:

```php
$url = secure_url('user/profile');

$url = secure_url('user/profile', [1]);
```

<a name="method-to-action"></a>
#### `to_action()` {.collection-method}

Функція `to_action` генерує [HTTP-відповідь із перенаправленням](/docs/{{version}}/responses#redirects) для заданої дії контролера:

```php
use App\Http\Controllers\UserController;

return to_action([UserController::class, 'show'], ['user' => 1]);
```

За потреби ви можете передати третім і четвертим аргументами методу `to_action` HTTP-статус, який слід призначити перенаправленню, і будь-які додаткові заголовки відповіді:

```php
return to_action(
    [UserController::class, 'show'],
    ['user' => 1],
    302,
    ['X-Framework' => 'Laravel']
);
```

<a name="method-to-route"></a>
#### `to_route()` {.collection-method}

Функція `to_route` генерує [HTTP-відповідь із перенаправленням](/docs/{{version}}/responses#redirects) для заданого [іменованого маршруту](/docs/{{version}}/routing#named-routes):

```php
return to_route('users.show', ['user' => 1]);
```

За потреби ви можете передати третім і четвертим аргументами методу `to_route` HTTP-статус, який слід призначити перенаправленню, і будь-які додаткові заголовки відповіді:

```php
return to_route('users.show', ['user' => 1], 302, ['X-Framework' => 'Laravel']);
```

<a name="method-uri"></a>
#### `uri()` {.collection-method}

Функція `uri` генерує [плавний екземпляр URI](#uri) для заданого URI:

```php
$uri = uri('https://example.com')
    ->withPath('/users')
    ->withQuery(['page' => 1]);
```

Якщо функції `uri` передано масив із парою «контролер - метод», яку можна викликати, функція створить екземпляр `Uri` для шляху маршруту цього методу контролера:

```php
use App\Http\Controllers\UserController;

$uri = uri([UserController::class, 'show'], ['user' => $user]);
```

Якщо контролер викликаємий, ви можете просто передати назву його класу:

```php
use App\Http\Controllers\UserIndexController;

$uri = uri(UserIndexController::class);
```

Якщо значення, передане функції `uri`, збігається з іменем [іменованого маршруту](/docs/{{version}}/routing#named-routes), буде згенеровано екземпляр `Uri` для шляху цього маршруту:

```php
$uri = uri('users.show', ['user' => $user]);
```

<a name="method-url"></a>
#### `url()` {.collection-method}

Функція `url` генерує повний URL до заданого шляху:

```php
$url = url('user/profile');

$url = url('user/profile', [1]);
```

Якщо шлях не передано, повертається екземпляр `Illuminate\Routing\UrlGenerator`:

```php
$current = url()->current();

$full = url()->full();

$previous = url()->previous();
```

Докладніше про роботу з функцією `url` читайте в [документації з генерації URL](/docs/{{version}}/urls#generating-urls).

<a name="miscellaneous"></a>
## Різне

<a name="method-abort"></a>
#### `abort()` {.collection-method}

Функція `abort` викидає [HTTP-виняток](/docs/{{version}}/errors#http-exceptions), який відрендерить [обробник винятків](/docs/{{version}}/errors#handling-exceptions):

```php
abort(403);
```

Ви також можете передати повідомлення винятку й власні заголовки HTTP-відповіді, які слід надіслати браузеру:

```php
abort(403, 'Unauthorized.', $headers);
```

<a name="method-abort-if"></a>
#### `abort_if()` {.collection-method}

Функція `abort_if` викидає HTTP-виняток, якщо заданий булевий вираз дає `true`:

```php
abort_if(! Auth::user()->isAdmin(), 403);
```

Як і методу `abort`, ви можете передати функції третім аргументом текст відповіді винятку, а четвертим - масив власних заголовків відповіді.

<a name="method-abort-unless"></a>
#### `abort_unless()` {.collection-method}

Функція `abort_unless` викидає HTTP-виняток, якщо заданий булевий вираз дає `false`:

```php
abort_unless(Auth::user()->isAdmin(), 403);
```

Як і методу `abort`, ви можете передати функції третім аргументом текст відповіді винятку, а четвертим - масив власних заголовків відповіді.

<a name="method-app"></a>
#### `app()` {.collection-method}

Функція `app` повертає екземпляр [сервіс-контейнера](/docs/{{version}}/container):

```php
$container = app();
```

Ви можете передати назву класу чи інтерфейсу, щоб розв'язати його з контейнера:

```php
$api = app('HelpSpot\API');
```

<a name="method-auth"></a>
#### `auth()` {.collection-method}

Функція `auth` повертає екземпляр [автентифікатора](/docs/{{version}}/authentication). Ви можете скористатися нею як альтернативою фасаду `Auth`:

```php
$user = auth()->user();
```

За потреби ви можете вказати, до якого гарда хочете звернутися:

```php
$user = auth('admin')->user();
```

<a name="method-back"></a>
#### `back()` {.collection-method}

Функція `back` генерує [HTTP-відповідь із перенаправленням](/docs/{{version}}/responses#redirects) на попереднє місце користувача:

```php
return back($status = 302, $headers = [], $fallback = '/');

return back();
```

<a name="method-bcrypt"></a>
#### `bcrypt()` {.collection-method}

Функція `bcrypt` [хешує](/docs/{{version}}/hashing) задане значення алгоритмом Bcrypt. Ви можете скористатися цією функцією як альтернативою фасаду `Hash`:

```php
$password = bcrypt('my-secret-password');
```

<a name="method-blank"></a>
#### `blank()` {.collection-method}

Функція `blank` визначає, чи є задане значення «порожнім»:

```php
blank('');
blank('   ');
blank(null);
blank(collect());

// true

blank(0);
blank(true);
blank(false);

// false
```

Протилежність `blank` - функція [filled](#method-filled).

<a name="method-broadcast"></a>
#### `broadcast()` {.collection-method}

Функція `broadcast` [надсилає](/docs/{{version}}/broadcasting) задану [подію](/docs/{{version}}/events) її слухачам:

```php
broadcast(new UserRegistered($user));

broadcast(new UserRegistered($user))->toOthers();
```

<a name="method-broadcast-if"></a>
#### `broadcast_if()` {.collection-method}

Функція `broadcast_if` [надсилає](/docs/{{version}}/broadcasting) задану [подію](/docs/{{version}}/events) її слухачам, якщо заданий булевий вираз дає `true`:

```php
broadcast_if($user->isActive(), new UserRegistered($user));

broadcast_if($user->isActive(), new UserRegistered($user))->toOthers();
```

<a name="method-broadcast-unless"></a>
#### `broadcast_unless()` {.collection-method}

Функція `broadcast_unless` [надсилає](/docs/{{version}}/broadcasting) задану [подію](/docs/{{version}}/events) її слухачам, якщо заданий булевий вираз дає `false`:

```php
broadcast_unless($user->isBanned(), new UserRegistered($user));

broadcast_unless($user->isBanned(), new UserRegistered($user))->toOthers();
```

<a name="method-cache"></a>
#### `cache()` {.collection-method}

Функція `cache` дозволяє отримувати значення з [кешу](/docs/{{version}}/cache). Якщо заданого ключа в кеші немає, буде повернуто необов'язкове значення за замовчуванням:

```php
$value = cache('key');

$value = cache('key', 'default');
```

Ви можете додавати елементи до кешу, передавши функції масив пар ключ / значення. Також слід передати кількість секунд або тривалість, протягом якої закешоване значення вважатиметься дійсним:

```php
cache(['key' => 'value'], 300);

cache(['key' => 'value'], now()->plus(seconds: 10));
```

<a name="method-class-uses-recursive"></a>
#### `class_uses_recursive()` {.collection-method}

Функція `class_uses_recursive` повертає всі трейти, які використовує клас, включно з трейтами всіх його батьківських класів:

```php
$traits = class_uses_recursive(App\Models\User::class);
```

<a name="method-collect"></a>
#### `collect()` {.collection-method}

Функція `collect` створює екземпляр [колекції](/docs/{{version}}/collections) із заданого значення:

```php
$collection = collect(['Taylor', 'Abigail']);
```

<a name="method-config"></a>
#### `config()` {.collection-method}

Функція `config` отримує значення змінної [конфігурації](/docs/{{version}}/configuration). До значень конфігурації звертаються за «крапковим» синтаксисом, який містить назву файлу та потрібну опцію. Ви також можете передати значення за замовчуванням, яке буде повернуто, якщо опції конфігурації не існує:

```php
$value = config('app.timezone');

$value = config('app.timezone', $default);
```

Ви можете задавати змінні конфігурації під час виконання, передавши масив пар ключ / значення. Проте зауважте: ця функція впливає лише на значення конфігурації для поточного запиту й не оновлює ваших справжніх значень конфігурації:

```php
config(['app.debug' => true]);
```

<a name="method-context"></a>
#### `context()` {.collection-method}

Функція `context` отримує значення з поточного [контексту](/docs/{{version}}/context). Ви також можете передати значення за замовчуванням, яке буде повернуто, якщо ключа контексту не існує:

```php
$value = context('trace_id');

$value = context('trace_id', $default);
```

Ви можете задавати значення контексту, передавши масив пар ключ / значення:

```php
use Illuminate\Support\Str;

context(['trace_id' => Str::uuid()->toString()]);
```

<a name="method-cookie"></a>
#### `cookie()` {.collection-method}

Функція `cookie` створює новий екземпляр [cookie](/docs/{{version}}/requests#cookies):

```php
$cookie = cookie('name', 'value', $minutes);
```

<a name="method-csrf-field"></a>
#### `csrf_field()` {.collection-method}

Функція `csrf_field` генерує HTML-поле `hidden` зі значенням CSRF-токена. Наприклад, із [синтаксисом Blade](/docs/{{version}}/blade):

```blade
{{ csrf_field() }}
```

<a name="method-csrf-token"></a>
#### `csrf_token()` {.collection-method}

Функція `csrf_token` дістає значення поточного CSRF-токена:

```php
$token = csrf_token();
```

<a name="method-decrypt"></a>
#### `decrypt()` {.collection-method}

Функція `decrypt` [розшифровує](/docs/{{version}}/encryption) задане значення. Ви можете скористатися цією функцією як альтернативою фасаду `Crypt`:

```php
$password = decrypt($value);
```

Протилежність `decrypt` - функція [encrypt](#method-encrypt).

<a name="method-dd"></a>
#### `dd()` {.collection-method}

Функція `dd` виводить задані змінні та припиняє виконання скрипта:

```php
dd($value);

dd($value1, $value2, $value3, ...);
```

Якщо ви не хочете зупиняти виконання скрипта, скористайтеся натомість функцією [dump](#method-dump).

<a name="method-dispatch"></a>
#### `dispatch()` {.collection-method}

Функція `dispatch` кладе задане [завдання](/docs/{{version}}/queues#creating-jobs) до [черги завдань](/docs/{{version}}/queues) Laravel:

```php
dispatch(new App\Jobs\SendEmails);
```

<a name="method-dispatch-sync"></a>
#### `dispatch_sync()` {.collection-method}

Функція `dispatch_sync` кладе задане завдання до черги [sync](/docs/{{version}}/queues#synchronous-dispatching), тож воно обробляється негайно:

```php
dispatch_sync(new App\Jobs\SendEmails);
```

<a name="method-dump"></a>
#### `dump()` {.collection-method}

Функція `dump` виводить задані змінні:

```php
dump($value);

dump($value1, $value2, $value3, ...);
```

Якщо ви хочете припинити виконання скрипта після виведення змінних, скористайтеся натомість функцією [dd](#method-dd).

<a name="method-encrypt"></a>
#### `encrypt()` {.collection-method}

Функція `encrypt` [шифрує](/docs/{{version}}/encryption) задане значення. Ви можете скористатися цією функцією як альтернативою фасаду `Crypt`:

```php
$secret = encrypt('my-secret-value');
```

Протилежність `encrypt` - функція [decrypt](#method-decrypt).

<a name="method-env"></a>
#### `env()` {.collection-method}

Функція `env` дістає значення [змінної середовища](/docs/{{version}}/configuration#environment-configuration) або повертає значення за замовчуванням:

```php
$env = env('APP_ENV');

$env = env('APP_ENV', 'production');
```

> [!WARNING]
> Якщо під час розгортання ви виконуєте команду `config:cache`, переконайтеся, що викликаєте функцію `env` лише у файлах конфігурації. Щойно конфігурацію закешовано, файл `.env` не завантажується, і всі виклики функції `env` повертатимуть зовнішні змінні середовища - на рівні сервера чи системи - або `null`.

<a name="method-event"></a>
#### `event()` {.collection-method}

Функція `event` диспетчеризує задану [подію](/docs/{{version}}/events) її слухачам:

```php
event(new UserRegistered($user));
```

<a name="method-fake"></a>
#### `fake()` {.collection-method}

Функція `fake` розв'язує з контейнера сінглтон [Faker](https://github.com/FakerPHP/Faker), що стане в пригоді для створення фейкових даних у фабриках моделей, наповненні бази, тестах і прототипуванні представлень:

```blade
@for ($i = 0; $i < 10; $i++)
    <dl>
        <dt>Name</dt>
        <dd>{{ fake()->name() }}</dd>

        <dt>Email</dt>
        <dd>{{ fake()->unique()->safeEmail() }}</dd>
    </dl>
@endfor
```

За замовчуванням функція `fake` використовує опцію конфігурації `app.faker_locale` у вашому `config/app.php`. Зазвичай цю опцію задають через змінну середовища `APP_FAKER_LOCALE`. Ви також можете вказати локаль, передавши її функції `fake`. Для кожної локалі буде розв'язано окремий сінглтон:

```php
fake('nl_NL')->name();
```

<a name="method-filled"></a>
#### `filled()` {.collection-method}

Функція `filled` визначає, чи не є задане значення «порожнім»:

```php
filled(0);
filled(true);
filled(false);

// true

filled('');
filled('   ');
filled(null);
filled(collect());

// false
```

Протилежність `filled` - функція [blank](#method-blank).

<a name="method-info"></a>
#### `info()` {.collection-method}

Функція `info` запише інформацію до [логу](/docs/{{version}}/logging) вашого застосунку:

```php
info('Some helpful information!');
```

Функції також можна передати масив контекстних даних:

```php
info('User login attempt failed.', ['id' => $user->id]);
```

<a name="method-literal"></a>
#### `literal()` {.collection-method}

Функція `literal` створює новий екземпляр [stdClass](https://www.php.net/manual/en/class.stdclass.php), де задані іменовані аргументи стають властивостями:

```php
$obj = literal(
    name: 'Joe',
    languages: ['PHP', 'Ruby'],
);

$obj->name; // 'Joe'
$obj->languages; // ['PHP', 'Ruby']
```

<a name="method-logger"></a>
#### `logger()` {.collection-method}

Функція `logger` дозволяє записати до [логу](/docs/{{version}}/logging) повідомлення рівня `debug`:

```php
logger('Debug message');
```

Функції також можна передати масив контекстних даних:

```php
logger('User has logged in.', ['id' => $user->id]);
```

Якщо функції не передано значення, буде повернуто екземпляр [логера](/docs/{{version}}/logging):

```php
logger()->error('You are not allowed here.');
```

<a name="method-method-field"></a>
#### `method_field()` {.collection-method}

Функція `method_field` генерує HTML-поле `hidden`, яке містить підмінене значення HTTP-дієслова форми. Наприклад, із [синтаксисом Blade](/docs/{{version}}/blade):

```blade
<form method="POST">
    {{ method_field('DELETE') }}
</form>
```

<a name="method-now"></a>
#### `now()` {.collection-method}

Функція `now` створює новий екземпляр `Illuminate\Support\Carbon` для поточного часу:

```php
$now = now();
```

<a name="method-old"></a>
#### `old()` {.collection-method}

Функція `old` [дістає](/docs/{{version}}/requests#retrieving-input) значення [старого вводу](/docs/{{version}}/requests#old-input), збереженого в сесії:

```php
$value = old('value');

$value = old('value', 'default');
```

Оскільки «значення за замовчуванням», яке передають другим аргументом функції `old`, часто є атрибутом моделі Eloquent, Laravel дозволяє просто передати другим аргументом усю модель Eloquent. У такому разі Laravel вважатиме, що перший аргумент функції `old` - це назва атрибута Eloquent, який слід узяти за «значення за замовчуванням»:

```blade
{{ old('name', $user->name) }}

// Is equivalent to...

{{ old('name', $user) }}
```

<a name="method-once"></a>
#### `once()` {.collection-method}

Функція `once` виконує заданий колбек і кешує результат у пам'яті на час запиту. Будь-які наступні виклики функції `once` з тим самим колбеком повертатимуть раніше закешований результат:

```php
function random(): int
{
    return once(function () {
        return random_int(1, 1000);
    });
}

random(); // 123
random(); // 123 (cached result)
random(); // 123 (cached result)
```

Коли функцію `once` виконано зсередини екземпляра об'єкта, закешований результат буде унікальним для цього екземпляра:

```php
<?php

class NumberService
{
    public function all(): array
    {
        return once(fn () => [1, 2, 3]);
    }
}

$service = new NumberService;

$service->all();
$service->all(); // (cached result)

$secondService = new NumberService;

$secondService->all();
$secondService->all(); // (cached result)
```
<a name="method-optional"></a>
#### `optional()` {.collection-method}

Функція `optional` приймає будь-який аргумент і дозволяє звертатися до властивостей або викликати методи цього об'єкта. Якщо заданий об'єкт - `null`, властивості й методи повернуть `null` замість того, щоб спричинити помилку:

```php
return optional($user->address)->street;

{!! old('name', optional($user)->name) !!}
```

Функція `optional` також приймає замикання другим аргументом. Замикання буде викликано, якщо значення, передане першим аргументом, не є null:

```php
return optional(User::find($id), function (User $user) {
    return $user->name;
});
```

<a name="method-policy"></a>
#### `policy()` {.collection-method}

Метод `policy` дістає екземпляр [політики](/docs/{{version}}/authorization#creating-policies) для заданого класу:

```php
$policy = policy(App\Models\User::class);
```

<a name="method-redirect"></a>
#### `redirect()` {.collection-method}

Функція `redirect` повертає [HTTP-відповідь із перенаправленням](/docs/{{version}}/responses#redirects) або, якщо викликана без аргументів, екземпляр редиректора:

```php
return redirect($to = null, $status = 302, $headers = [], $secure = null);

return redirect('/home');

return redirect()->route('route.name');
```

<a name="method-report"></a>
#### `report()` {.collection-method}

Функція `report` повідомить про виняток через ваш [обробник винятків](/docs/{{version}}/errors#handling-exceptions):

```php
report($e);
```

Функція `report` також приймає рядок як аргумент. Коли функції передано рядок, вона створить виняток із цим рядком як повідомленням:

```php
report('Something went wrong.');
```

<a name="method-report-if"></a>
#### `report_if()` {.collection-method}

Функція `report_if` повідомить про виняток через ваш [обробник винятків](/docs/{{version}}/errors#handling-exceptions), якщо заданий булевий вираз дає `true`:

```php
report_if($shouldReport, $e);

report_if($shouldReport, 'Something went wrong.');
```

<a name="method-report-unless"></a>
#### `report_unless()` {.collection-method}

Функція `report_unless` повідомить про виняток через ваш [обробник винятків](/docs/{{version}}/errors#handling-exceptions), якщо заданий булевий вираз дає `false`:

```php
report_unless($reportingDisabled, $e);

report_unless($reportingDisabled, 'Something went wrong.');
```

<a name="method-request"></a>
#### `request()` {.collection-method}

Функція `request` повертає поточний екземпляр [запиту](/docs/{{version}}/requests) або дістає значення поля вводу з поточного запиту:

```php
$request = request();

$value = request('key', $default);
```

<a name="method-rescue"></a>
#### `rescue()` {.collection-method}

Функція `rescue` виконує задане замикання й ловить будь-які винятки, що трапляються під час його виконання. Усі спіймані винятки буде надіслано вашому [обробнику винятків](/docs/{{version}}/errors#handling-exceptions); проте обробка запиту продовжиться:

```php
return rescue(function () {
    return $this->method();
});
```

Ви також можете передати функції `rescue` другий аргумент. Це буде значення «за замовчуванням», яке слід повернути, якщо під час виконання замикання станеться виняток:

```php
return rescue(function () {
    return $this->method();
}, false);

return rescue(function () {
    return $this->method();
}, function () {
    return $this->failure();
});
```

Функції `rescue` можна передати аргумент `report`, щоб визначити, чи слід повідомляти про виняток через функцію `report`:

```php
return rescue(function () {
    return $this->method();
}, report: function (Throwable $throwable) {
    return $throwable instanceof InvalidArgumentException;
});
```

<a name="method-resolve"></a>
#### `resolve()` {.collection-method}

Функція `resolve` розв'язує задану назву класу чи інтерфейсу в екземпляр через [сервіс-контейнер](/docs/{{version}}/container):

```php
$api = resolve('HelpSpot\API');
```

<a name="method-response"></a>
#### `response()` {.collection-method}

Функція `response` створює екземпляр [відповіді](/docs/{{version}}/responses) або отримує екземпляр фабрики відповідей:

```php
return response('Hello World', 200, $headers);

return response()->json(['foo' => 'bar'], 200, $headers);
```

<a name="method-retry"></a>
#### `retry()` {.collection-method}

Функція `retry` намагається виконати заданий колбек, доки не буде досягнуто заданої максимальної кількості спроб. Якщо колбек не викидає винятку, буде повернуто його значення. Якщо колбек викидає виняток, спробу буде автоматично повторено. Якщо максимальну кількість спроб перевищено, виняток буде викинуто:

```php
return retry(5, function () {
    // Attempt 5 times while resting 100ms between attempts...
}, 100);
```

Тривалість паузи також приймає екземпляр `CarbonInterval`:

```php
use function Illuminate\Support\seconds;

return retry(5, function () {
    // Attempt 5 times while resting 5 seconds between attempts...
}, seconds(5));
```

Якщо ви хочете вручну обчислювати кількість мілісекунд паузи між спробами, передайте третім аргументом функції `retry` замикання:

```php
use Exception;

return retry(5, function () {
    // ...
}, function (int $attempt, Exception $exception) {
    return $attempt * 100;
});
```

Для зручності ви можете передати першим аргументом функції `retry` масив. За цим масивом визначатиметься, скільки мілісекунд чекати між наступними спробами:

```php
return retry([100, 200], function () {
    // Sleep for 100ms on first retry, 200ms on second retry...
});
```

Щоб повторювати спроби лише за певних умов, передайте четвертим аргументом функції `retry` замикання:

```php
use App\Exceptions\TemporaryException;
use Exception;

return retry(5, function () {
    // ...
}, 100, function (Exception $exception) {
    return $exception instanceof TemporaryException;
});
```

<a name="method-session"></a>
#### `session()` {.collection-method}

Функція `session` дозволяє отримувати або задавати значення [сесії](/docs/{{version}}/session):

```php
$value = session('key');
```

Ви можете задавати значення, передавши функції масив пар ключ / значення:

```php
session(['chairs' => 7, 'instruments' => 3]);
```

Якщо функції не передано значення, буде повернуто сховище сесії:

```php
$value = session()->get('key');

session()->put('key', $value);
```

<a name="method-tap"></a>
#### `tap()` {.collection-method}

Функція `tap` приймає два аргументи: довільне `$value` і замикання. `$value` буде передано до замикання, а потім повернуто функцією `tap`. Значення, яке повертає замикання, не має значення:

```php
$user = tap(User::first(), function (User $user) {
    $user->name = 'Taylor';

    $user->save();
});
```

Якщо функції `tap` не передано замикання, ви можете викликати будь-який метод на заданому `$value`. Значенням, яке поверне викликаний метод, завжди буде `$value`, незалежно від того, що метод справді повертає у своєму описі. Наприклад, метод Eloquent `update` зазвичай повертає ціле число. Проте ми можемо змусити метод повернути саму модель, зробивши виклик `update` ланцюжком через функцію `tap`:

```php
$user = tap($user)->update([
    'name' => $name,
    'email' => $email,
]);
```

Щоб додати до класу метод `tap`, додайте до нього трейт `Illuminate\Support\Traits\Tappable`. Метод `tap` цього трейта приймає єдиним аргументом Closure. Сам екземпляр об'єкта буде передано до Closure, а потім повернуто методом `tap`:

```php
return $user->tap(function (User $user) {
    // ...
});
```

<a name="method-throw-if"></a>
#### `throw_if()` {.collection-method}

Функція `throw_if` викидає заданий виняток, якщо заданий булевий вираз дає `true`:

```php
throw_if(! Auth::user()->isAdmin(), AuthorizationException::class);

throw_if(
    ! Auth::user()->isAdmin(),
    AuthorizationException::class,
    'You are not allowed to access this page.'
);
```

<a name="method-throw-unless"></a>
#### `throw_unless()` {.collection-method}

Функція `throw_unless` викидає заданий виняток, якщо заданий булевий вираз дає `false`:

```php
throw_unless(Auth::user()->isAdmin(), AuthorizationException::class);

throw_unless(
    Auth::user()->isAdmin(),
    AuthorizationException::class,
    'You are not allowed to access this page.'
);
```

<a name="method-today"></a>
#### `today()` {.collection-method}

Функція `today` створює новий екземпляр `Illuminate\Support\Carbon` для поточної дати:

```php
$today = today();
```

<a name="method-trait-uses-recursive"></a>
#### `trait_uses_recursive()` {.collection-method}

Функція `trait_uses_recursive` повертає всі трейти, які використовує трейт:

```php
$traits = trait_uses_recursive(\Illuminate\Notifications\Notifiable::class);
```

<a name="method-transform"></a>
#### `transform()` {.collection-method}

Функція `transform` виконує замикання над заданим значенням, якщо воно не [порожнє](#method-blank), і повертає значення, яке повернуло замикання:

```php
$callback = function (int $value) {
    return $value * 2;
};

$result = transform(5, $callback);

// 10
```

Третім аргументом функції можна передати значення за замовчуванням або замикання. Його буде повернуто, якщо задане значення порожнє:

```php
$result = transform(null, $callback, 'The value is blank');

// The value is blank
```

<a name="method-validator"></a>
#### `validator()` {.collection-method}

Функція `validator` створює новий екземпляр [валідатора](/docs/{{version}}/validation) із заданими аргументами. Ви можете скористатися нею як альтернативою фасаду `Validator`:

```php
$validator = validator($data, $rules, $messages);
```

<a name="method-value"></a>
#### `value()` {.collection-method}

Функція `value` повертає передане їй значення. Проте, якщо ви передасте функції замикання, воно буде виконане, і буде повернуто його результат:

```php
$result = value(true);

// true

$result = value(function () {
    return false;
});

// false
```

Функції `value` можна передати додаткові аргументи. Якщо перший аргумент - замикання, додаткові параметри буде передано замиканню як аргументи, інакше їх буде проігноровано:

```php
$result = value(function (string $name) {
    return $name;
}, 'Taylor');

// 'Taylor'
```

<a name="method-view"></a>
#### `view()` {.collection-method}

Функція `view` дістає екземпляр [представлення](/docs/{{version}}/views):

```php
return view('auth.login');
```

<a name="method-with"></a>
#### `with()` {.collection-method}

Функція `with` повертає передане їй значення. Якщо другим аргументом функції передано замикання, воно буде виконане, і буде повернуто його результат:

```php
$callback = function (mixed $value) {
    return is_numeric($value) ? $value * 2 : 0;
};

$result = with(5, $callback);

// 10

$result = with(null, $callback);

// 0

$result = with(5, null);

// 5
```

<a name="method-when"></a>
#### `when()` {.collection-method}

Функція `when` повертає передане їй значення, якщо задана умова дає `true`. Інакше повертається `null`. Якщо другим аргументом функції передано замикання, воно буде виконане, і буде повернуто його результат:

```php
$value = when(true, 'Hello World');

$value = when(true, fn () => 'Hello World');
```

Функція `when` насамперед корисна для умовного рендерингу HTML-атрибутів:

```blade
<div {!! when($condition, 'wire:poll="calculate"') !!}>
    ...
</div>
```

<a name="other-utilities"></a>
## Інші утиліти

<a name="benchmarking"></a>
### Бенчмаркінг

Інколи вам може захотітися швидко перевірити швидкодію певних частин застосунку. У таких випадках скористайтеся допоміжним класом `Benchmark`, щоб виміряти, скільки мілісекунд потрібно заданим колбекам на виконання:

```php
<?php

use App\Models\User;
use Illuminate\Support\Benchmark;

Benchmark::dd(fn () => User::find(1)); // 0.1 ms

Benchmark::dd([
    'Scenario 1' => fn () => User::count(), // 0.5 ms
    'Scenario 2' => fn () => User::all()->count(), // 20.0 ms
]);
```

За замовчуванням задані колбеки виконуються один раз (одна ітерація), а їхня тривалість відображається в браузері / консолі.

Щоб викликати колбек більше одного разу, вкажіть другим аргументом методу кількість ітерацій. Коли колбек виконується кілька разів, клас `Benchmark` поверне середню кількість мілісекунд, витрачених на його виконання по всіх ітераціях:

```php
Benchmark::dd(fn () => User::count(), iterations: 10); // 0.5 ms
```

Інколи вам може захотітися виміряти виконання колбека й водночас отримати значення, яке він повертає. Метод `value` поверне кортеж зі значенням, яке повернув колбек, і кількістю мілісекунд, витрачених на його виконання:

```php
[$count, $duration] = Benchmark::value(fn () => User::count());
```

<a name="dates"></a>
### Дата й час

Laravel містить [Carbon](https://carbon.nesbot.com/guide/getting-started/introduction.html) - потужну бібліотеку для роботи з датою й часом. Щоб створити новий екземпляр `Carbon`, викличте функцію `now`. Ця функція глобально доступна у вашому застосунку Laravel:

```php
$now = now();
```

Або ж ви можете створити новий екземпляр `Carbon` через клас `Illuminate\Support\Carbon`:

```php
use Illuminate\Support\Carbon;

$now = Carbon::now();
```

Laravel також розширює екземпляри `Carbon` методами `plus` і `minus`, які дозволяють легко змінювати дату й час екземпляра:

```php
return now()->plus(minutes: 5);
return now()->plus(hours: 8);
return now()->plus(weeks: 4);

return now()->minus(minutes: 5);
return now()->minus(hours: 8);
return now()->minus(weeks: 4);
```

Докладний огляд Carbon і його можливостей ви знайдете в [офіційній документації Carbon](https://carbon.nesbot.com/guide/getting-started/introduction.html).

<a name="interval-functions"></a>
#### Функції інтервалів

Laravel також пропонує функції `milliseconds`, `seconds`, `minutes`, `hours`, `days`, `weeks`, `months` та `years`, які повертають екземпляри `CarbonInterval`, що розширюють PHP-клас [DateInterval](https://www.php.net/manual/en/class.dateinterval.php). Ці функції можна використовувати всюди, де Laravel приймає екземпляр `DateInterval`:

```php
use Illuminate\Support\Facades\Cache;

use function Illuminate\Support\{minutes};

Cache::put('metrics', $metrics, minutes(10));
```

<a name="deferred-functions"></a>
### Відкладені функції

Хоч [завдання в черзі](/docs/{{version}}/queues) Laravel і дозволяють ставити задачі в чергу на фонову обробку, інколи у вас є прості задачі, які хочеться відкласти, не налаштовуючи й не підтримуючи довгограючого воркера черги.

Відкладені функції дозволяють відкласти виконання замикання до моменту, коли HTTP-відповідь уже надіслано користувачеві, - і застосунок і далі здається швидким та чуйним. Щоб відкласти виконання замикання, просто передайте його функції `Illuminate\Support\defer`:

```php
use App\Services\Metrics;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use function Illuminate\Support\defer;

Route::post('/orders', function (Request $request) {
    // Create order...

    defer(fn () => Metrics::reportOrder($order));

    return $order;
});
```

За замовчуванням відкладені функції виконуються, лише якщо HTTP-відповідь, команда Artisan чи завдання в черзі, з яких викликано `Illuminate\Support\defer`, завершилися успішно. Це означає, що відкладені функції не виконаються, якщо запит дасть HTTP-відповідь `4xx` чи `5xx`. Якщо ви хочете, щоб відкладена функція виконувалася завжди, додайте до неї ланцюжком метод `always`:

```php
defer(fn () => Metrics::reportOrder($order))->always();
```

> [!WARNING]
> Якщо у вас встановлено [розширення PHP Swoole](https://www.php.net/manual/en/book.swoole.php), функція `defer` Laravel може конфліктувати з власною глобальною функцією `defer` від Swoole, що призведе до помилок вебсервера. Обов'язково викликайте хелпер `defer` Laravel, явно вказавши простір імен: `use function Illuminate\Support\defer;`

<a name="cancelling-deferred-functions"></a>
#### Скасування відкладених функцій

Якщо вам потрібно скасувати відкладену функцію до її виконання, скористайтеся методом `forget`, щоб скасувати функцію за її іменем. Щоб назвати відкладену функцію, передайте другий аргумент функції `Illuminate\Support\defer`:

```php
defer(fn () => Metrics::report(), 'reportMetrics');

defer()->forget('reportMetrics');
```

<a name="disabling-deferred-functions-in-tests"></a>
#### Вимкнення відкладених функцій у тестах

Пишучи тести, буває корисно вимкнути відкладені функції. Ви можете викликати у своєму тесті `withoutDefer`, щоб Laravel виконував усі відкладені функції негайно:

```php tab=Pest
test('without defer', function () {
    $this->withoutDefer();

    // ...
});
```

```php tab=PHPUnit
use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_without_defer(): void
    {
        $this->withoutDefer();

        // ...
    }
}
```

Якщо ви хочете вимкнути відкладені функції для всіх тестів у тест-кейсі, викличте метод `withoutDefer` у методі `setUp` вашого базового класу `TestCase`:

```php
<?php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    protected function setUp(): void// [tl! add:start]
    {
        parent::setUp();

        $this->withoutDefer();
    }// [tl! add:end]
}
```

<a name="lottery"></a>
### Лотерея

Клас лотереї Laravel дозволяє виконувати колбеки на основі заданих шансів. Це особливо корисно, коли ви хочете виконувати код лише для певного відсотка вхідних запитів:

```php
use Illuminate\Support\Lottery;

Lottery::odds(1, 20)
    ->winner(fn () => $user->won())
    ->loser(fn () => $user->lost())
    ->choose();
```

Ви можете поєднувати клас лотереї Laravel з іншими можливостями фреймворку. Наприклад, ви можете хотіти повідомляти обробнику винятків лише про невеликий відсоток повільних запитів. А оскільки клас лотереї є викликаємим, ми можемо передати його екземпляр до будь-якого методу, який приймає щось викликаєме:

```php
use Carbon\CarbonInterval;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Lottery;

DB::whenQueryingForLongerThan(
    CarbonInterval::seconds(2),
    Lottery::odds(1, 100)->winner(fn () => report('Querying > 2 seconds.')),
);
```

<a name="testing-lotteries"></a>
#### Тестування лотерей

Laravel надає кілька простих методів, які дозволяють легко тестувати виклики лотереї у вашому застосунку:

```php
// Lottery will always win...
Lottery::alwaysWin();

// Lottery will always lose...
Lottery::alwaysLose();

// Lottery will win then lose, and finally return to normal behavior...
Lottery::fix([true, false]);

// Lottery will return to normal behavior...
Lottery::determineResultsNormally();
```

<a name="pipeline"></a>
### Конвеєр

Фасад `Pipeline` Laravel надає зручний спосіб «пропустити» заданий ввід через низку викликаємих класів, замикань чи колбеків, даючи кожному класу можливість оглянути чи змінити ввід і викликати наступний елемент конвеєра:

```php
use Closure;
use App\Models\User;
use Illuminate\Support\Facades\Pipeline;

$user = Pipeline::send($user)
    ->through([
        function (User $user, Closure $next) {
            // ...

            return $next($user);
        },
        function (User $user, Closure $next) {
            // ...

            return $next($user);
        },
    ])
    ->then(fn (User $user) => $user);
```

Як бачите, кожен викликаємий клас чи замикання в конвеєрі отримує ввід і замикання `$next`. Виклик замикання `$next` викличе наступний елемент конвеєра. Як ви могли помітити, це дуже схоже на [middleware](/docs/{{version}}/middleware).

Коли останній елемент конвеєра викликає замикання `$next`, буде викликано колбек, переданий методу `then`. Зазвичай цей колбек просто повертає заданий ввід. Для зручності, якщо ви просто хочете повернути ввід після обробки, скористайтеся методом `thenReturn`.

Звісно, як уже зазначалося, ви не обмежені замиканнями в конвеєрі. Ви можете передавати й викликаємі класи. Якщо передано назву класу, його буде створено через [сервіс-контейнер](/docs/{{version}}/container) Laravel, що дозволяє впровадити залежності у викликаємий клас:

```php
$user = Pipeline::send($user)
    ->through([
        GenerateProfilePhoto::class,
        ActivateSubscription::class,
        SendWelcomeEmail::class,
    ])
    ->thenReturn();
```

Метод `withinTransaction` можна викликати на конвеєрі, щоб автоматично огорнути всі його кроки однією транзакцією бази даних:

```php
$user = Pipeline::send($user)
    ->withinTransaction()
    ->through([
        ProcessOrder::class,
        TransferFunds::class,
        UpdateInventory::class,
    ])
    ->thenReturn();
```

<a name="sleep"></a>
### Sleep

Клас `Sleep` Laravel - це легка обгортка над нативними функціями PHP `sleep` та `usleep`, яка забезпечує кращу тестованість і водночас надає зручний для розробника API для роботи з часом:

```php
use Illuminate\Support\Sleep;

$waiting = true;

while ($waiting) {
    Sleep::for(1)->second();

    $waiting = /* ... */;
}
```

Клас `Sleep` пропонує різноманітні методи для роботи з різними одиницями часу:

```php
// Return a value after sleeping...
$result = Sleep::for(1)->second()->then(fn () => 1 + 1);

// Sleep while a given value is true...
Sleep::for(1)->second()->while(fn () => shouldKeepSleeping());

// Pause execution for 90 seconds...
Sleep::for(1.5)->minutes();

// Pause execution for 2 seconds...
Sleep::for(2)->seconds();

// Pause execution for 500 milliseconds...
Sleep::for(500)->milliseconds();

// Pause execution for 5,000 microseconds...
Sleep::for(5000)->microseconds();

// Pause execution until a given time...
Sleep::until(now()->plus(minutes: 1));

// Alias of PHP's native "sleep" function...
Sleep::sleep(2);

// Alias of PHP's native "usleep" function...
Sleep::usleep(5000);
```

Щоб легко поєднувати одиниці часу, скористайтеся методом `and`:

```php
Sleep::for(1)->second()->and(10)->milliseconds();
```

<a name="testing-sleep"></a>
#### Тестування Sleep

Коли ви тестуєте код, який використовує клас `Sleep` чи нативні функції sleep у PHP, ваш тест зупинятиме виконання. Як ви й очікуєте, це суттєво сповільнює набір тестів. Наприклад, уявіть, що ви тестуєте такий код:

```php
$waiting = /* ... */;

$seconds = 1;

while ($waiting) {
    Sleep::for($seconds++)->seconds();

    $waiting = /* ... */;
}
```

Зазвичай тестування цього коду тривало б _щонайменше_ секунду. На щастя, клас `Sleep` дозволяє «підмінити» паузи, тож наш набір тестів лишається швидким:

```php tab=Pest
it('waits until ready', function () {
    Sleep::fake();

    // ...
});
```

```php tab=PHPUnit
public function test_it_waits_until_ready()
{
    Sleep::fake();

    // ...
}
```

Коли клас `Sleep` підмінено, справжню паузу у виконанні буде пропущено, і тест стане значно швидшим.

Щойно клас `Sleep` підмінено, можна робити перевірки очікуваних «пауз». Щоб проілюструвати це, уявімо, що ми тестуємо код, який зупиняє виконання тричі, і кожна пауза довша на секунду. Методом `assertSequence` ми можемо перевірити, що наш код «спав» належний час, і водночас лишити тест швидким:

```php tab=Pest
it('checks if ready three times', function () {
    Sleep::fake();

    // ...

    Sleep::assertSequence([
        Sleep::for(1)->second(),
        Sleep::for(2)->seconds(),
        Sleep::for(3)->seconds(),
    ]);
});
```

```php tab=PHPUnit
public function test_it_checks_if_ready_three_times()
{
    Sleep::fake();

    // ...

    Sleep::assertSequence([
        Sleep::for(1)->second(),
        Sleep::for(2)->seconds(),
        Sleep::for(3)->seconds(),
    ]);
}
```

Звісно, клас `Sleep` пропонує й низку інших перевірок, якими ви можете скористатися під час тестування:

```php
use Carbon\CarbonInterval as Duration;
use Illuminate\Support\Sleep;

// Assert that sleep was called 3 times...
Sleep::assertSleptTimes(3);

// Assert against the duration of sleep...
Sleep::assertSlept(function (Duration $duration): bool {
    return /* ... */;
}, times: 1);

// Assert that the Sleep class was never invoked...
Sleep::assertNeverSlept();

// Assert that, even if Sleep was called, no execution paused occurred...
Sleep::assertInsomniac();
```

Інколи буває корисно виконувати якусь дію щоразу, коли трапляється підмінена пауза. Для цього передайте колбек методу `whenFakingSleep`. У прикладі нижче ми користуємося [хелперами маніпуляції часом](/docs/{{version}}/mocking#interacting-with-time) Laravel, щоб миттєво просувати час на тривалість кожної паузи:

```php
use Carbon\CarbonInterval as Duration;

$this->freezeTime();

Sleep::fake();

Sleep::whenFakingSleep(function (Duration $duration) {
    // Progress time when faking sleep...
    $this->travel($duration->totalMilliseconds)->milliseconds();
});
```

Оскільки просування часу - поширена потреба, метод `fake` приймає аргумент `syncWithCarbon`, який тримає Carbon синхронізованим під час пауз у тесті:

```php
Sleep::fake(syncWithCarbon: true);

$start = now();

Sleep::for(1)->second();

$start->diffForHumans(); // 1 second ago
```

Laravel використовує клас `Sleep` усередині, коли зупиняє виконання. Наприклад, хелпер [retry](#method-retry) під час пауз користується класом `Sleep`, що покращує тестованість роботи з цим хелпером.

<a name="timebox"></a>
### Timebox

Клас `Timebox` Laravel гарантує, що заданий колбек завжди виконується фіксований час, навіть якщо насправді завершується раніше. Це особливо корисно для криптографічних операцій і перевірок автентифікації користувачів, де зловмисники могли б скористатися різницею в часі виконання, щоб вивідати чутливу інформацію.

Якщо виконання перевищує фіксовану тривалість, `Timebox` не має ефекту. Розробник сам має обрати достатньо довгу фіксовану тривалість, щоб урахувати найгірші сценарії.

Метод call приймає замикання й ліміт часу в мікросекундах, після чого виконує замикання й чекає, доки не буде досягнуто ліміту:

```php
use Illuminate\Support\Timebox;

(new Timebox)->call(function ($timebox) {
    // ...
}, microseconds: 10000);
```

Якщо всередині замикання викинуто виняток, цей клас дотримається заданої затримки й перевикине виняток після неї.

<a name="uri"></a>
### URI

Клас `Uri` Laravel надає зручний і плавний інтерфейс для створення URI та роботи з ними. Цей клас обгортає функціональність пакета League URI й безшовно інтегрується із системою маршрутизації Laravel.

Створити екземпляр `Uri` легко за допомогою статичних методів:

```php
use App\Http\Controllers\UserController;
use App\Http\Controllers\InvokableController;
use Illuminate\Support\Uri;

// Generate a URI instance from the given string...
$uri = Uri::of('https://example.com/path');

// Generate URI instances to paths, named routes, or controller actions...
$uri = Uri::to('/dashboard');
$uri = Uri::route('users.show', ['user' => 1]);
$uri = Uri::signedRoute('users.show', ['user' => 1]);
$uri = Uri::temporarySignedRoute('user.index', now()->plus(minutes: 5));
$uri = Uri::action([UserController::class, 'index']);
$uri = Uri::action(InvokableController::class);

// Generate a URI instance from the current request URL...
$uri = $request->uri();
```

Щойно ви маєте екземпляр URI, ви можете плавно його змінювати:

```php
$uri = Uri::of('https://example.com')
    ->withScheme('http')
    ->withHost('test.com')
    ->withPort(8000)
    ->withPath('/users')
    ->withQuery(['page' => 2])
    ->withFragment('section-1');
```

<a name="inspecting-uris"></a>
#### Огляд URI

Клас `Uri` також дозволяє легко оглядати різні складові URI:

```php
$scheme = $uri->scheme();
$authority = $uri->authority();
$host = $uri->host();
$port = $uri->port();
$path = $uri->path();
$segments = $uri->pathSegments();
$query = $uri->query();
$fragment = $uri->fragment();
```

<a name="manipulating-query-strings"></a>
#### Робота з рядками запиту

Клас `Uri` пропонує кілька методів для роботи з рядком запиту URI. Метод `withQuery` дозволяє злити додаткові параметри до наявного рядка запиту:

```php
$uri = $uri->withQuery(['sort' => 'name']);
```

Метод `withQueryIfMissing` дозволяє злити додаткові параметри до наявного рядка запиту, якщо заданих ключів у ньому ще немає:

```php
$uri = $uri->withQueryIfMissing(['page' => 1]);
```

Метод `replaceQuery` дозволяє повністю замінити наявний рядок запиту новим:

```php
$uri = $uri->replaceQuery(['page' => 1]);
```

Метод `pushOntoQuery` дозволяє додати додаткові параметри до параметра рядка запиту, який має значення-масив:

```php
$uri = $uri->pushOntoQuery('filter', ['active', 'pending']);
```

Метод `withoutQuery` дозволяє вилучити параметри з рядка запиту:

```php
$uri = $uri->withoutQuery(['page']);
```

<a name="generating-responses-from-uris"></a>
#### Генерація відповідей з URI

Метод `redirect` дозволяє згенерувати екземпляр `RedirectResponse` до заданого URI:

```php
$uri = Uri::of('https://example.com');

return $uri->redirect();
```

Або ж ви можете просто повернути екземпляр `Uri` з маршруту чи дії контролера - і буде автоматично згенеровано відповідь із перенаправленням на цей URI:

```php
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Uri;

Route::get('/redirect', function () {
    return Uri::to('/index')
        ->withQuery(['sort' => 'name']);
});
```

---

Джерело: https://laravelukraine.com/docs/13.x/http-client

# HTTP-клієнт

- [Вступ](#introduction)
- [Виконання запитів](#making-requests)
    - [Дані запиту](#request-data)
    - [Заголовки](#headers)
    - [Автентифікація](#authentication)
    - [Таймаут](#timeout)
    - [Повторні спроби](#retries)
    - [Обробка помилок](#error-handling)
    - [Middleware Guzzle](#guzzle-middleware)
    - [Опції Guzzle](#guzzle-options)
- [Паралельні запити](#concurrent-requests)
    - [Пул запитів](#request-pooling)
    - [Пакети запитів](#request-batching)
- [Макроси](#macros)
- [Тестування](#testing)
    - [Підміна відповідей](#faking-responses)
    - [Огляд запитів](#inspecting-requests)
    - [Запобігання «блукаючим» запитам](#preventing-stray-requests)
- [Події](#events)

<a name="introduction"></a>
## Вступ

Laravel надає виразний мінімалістичний API навколо [HTTP-клієнта Guzzle](http://docs.guzzlephp.org/en/stable/), який дозволяє швидко виконувати вихідні HTTP-запити для спілкування з іншими вебзастосунками. Обгортка Laravel навколо Guzzle зосереджена на найпоширеніших сценаріях і чудовому досвіді розробника.

<a name="making-requests"></a>
## Виконання запитів

Щоб виконувати запити, скористайтеся методами `head`, `get`, `post`, `put`, `patch` та `delete` фасаду `Http`. Спершу розгляньмо, як виконати простий `GET`-запит на іншу URL-адресу:

```php
use Illuminate\Support\Facades\Http;

$response = Http::get('http://example.com');
```

Метод `get` повертає екземпляр `Illuminate\Http\Client\Response`, який надає низку методів для огляду відповіді:

```php
$response->body() : string;
$response->json($key = null, $default = null, $flags = null) : mixed;
$response->object() : object;
$response->collect($key = null) : Illuminate\Support\Collection;
$response->resource() : resource;
$response->status() : int;
$response->successful() : bool;
$response->redirect(): bool;
$response->failed() : bool;
$response->clientError() : bool;
$response->header($header) : string;
$response->headers() : array;
```

Об'єкт `Illuminate\Http\Client\Response` також реалізує PHP-інтерфейс `ArrayAccess`, тож ви можете звертатися до даних JSON-відповіді безпосередньо на відповіді:

```php
return Http::get('http://example.com/users/1')['name'];
```

Окрім перелічених вище методів відповіді, наступні методи дозволяють визначити, чи має відповідь конкретний статус-код:

```php
$response->ok() : bool;                  // 200 OK
$response->created() : bool;             // 201 Created
$response->accepted() : bool;            // 202 Accepted
$response->noContent() : bool;           // 204 No Content
$response->movedPermanently() : bool;    // 301 Moved Permanently
$response->found() : bool;               // 302 Found
$response->badRequest() : bool;          // 400 Bad Request
$response->unauthorized() : bool;        // 401 Unauthorized
$response->paymentRequired() : bool;     // 402 Payment Required
$response->forbidden() : bool;           // 403 Forbidden
$response->notFound() : bool;            // 404 Not Found
$response->requestTimeout() : bool;      // 408 Request Timeout
$response->conflict() : bool;            // 409 Conflict
$response->unprocessableEntity() : bool; // 422 Unprocessable Entity
$response->tooManyRequests() : bool;     // 429 Too Many Requests
$response->serverError() : bool;         // >= 500 Server Error
```

<a name="uri-templates"></a>
#### Шаблони URI

HTTP-клієнт також дозволяє будувати URL запитів за [специфікацією шаблонів URI](https://www.rfc-editor.org/rfc/rfc6570). Щоб описати параметри URL, які може розгорнути ваш шаблон URI, скористайтеся методом `withUrlParameters`:

```php
Http::withUrlParameters([
    'endpoint' => 'https://laravel.com',
    'page' => 'docs',
    'version' => '13.x',
    'topic' => 'validation',
])->get('{+endpoint}/{page}/{version}/{topic}');
```

<a name="dumping-requests"></a>
#### Виведення запитів

Якщо ви хочете вивести екземпляр вихідного запиту перед надсиланням і припинити виконання скрипта, додайте метод `dd` на початок опису запиту:

```php
return Http::dd()->get('http://example.com');
```

<a name="request-data"></a>
### Дані запиту

Звісно, під час запитів `POST`, `PUT` і `PATCH` часто потрібно надсилати додаткові дані, тому ці методи приймають другим аргументом масив даних. За замовчуванням дані надсилаються з типом вмісту `application/json`:

```php
use Illuminate\Support\Facades\Http;

$response = Http::post('http://example.com/users', [
    'name' => 'Steve',
    'role' => 'Network Administrator',
]);
```

<a name="get-request-query-parameters"></a>
#### Параметри запиту в GET-запитах

Виконуючи `GET`-запити, ви можете або додати рядок запиту до URL напряму, або передати масив пар ключ / значення другим аргументом методу `get`:

```php
$response = Http::get('http://example.com/users', [
    'name' => 'Taylor',
    'page' => 1,
]);
```

Як варіант, можна скористатися методом `withQueryParameters`:

```php
Http::retry(3, 100)->withQueryParameters([
    'name' => 'Taylor',
    'page' => 1,
])->get('http://example.com/users');
```

<a name="sending-form-url-encoded-requests"></a>
#### Надсилання запитів у форматі form URL encoded

Якщо ви хочете надіслати дані з типом вмісту `application/x-www-form-urlencoded`, викличте перед запитом метод `asForm`:

```php
$response = Http::asForm()->post('http://example.com/users', [
    'name' => 'Sara',
    'role' => 'Privacy Consultant',
]);
```

<a name="sending-a-raw-request-body"></a>
#### Надсилання сирого тіла запиту

Якщо під час запиту ви хочете передати сире тіло, скористайтеся методом `withBody`. Тип вмісту можна передати другим аргументом методу:

```php
$response = Http::withBody(
    base64_encode($photo), 'image/jpeg'
)->post('http://example.com/photo');
```

<a name="multi-part-requests"></a>
#### Multi-part запити

Якщо ви хочете надсилати файли як multi-part запити, викличте перед запитом метод `attach`. Цей метод приймає ім'я файлу та його вміст. За потреби ви можете передати третій аргумент, який стане ім'ям файлу, а четвертим аргументом - заголовки, пов'язані з файлом:

```php
$response = Http::attach(
    'attachment', file_get_contents('photo.jpg'), 'photo.jpg', ['Content-Type' => 'image/jpeg']
)->post('http://example.com/attachments');
```

Замість сирого вмісту файлу ви можете передати потоковий ресурс:

```php
$photo = fopen('photo.jpg', 'r');

$response = Http::attach(
    'attachment', $photo, 'photo.jpg'
)->post('http://example.com/attachments');
```

<a name="headers"></a>
### Заголовки

Заголовки до запитів додаються методом `withHeaders`. Метод `withHeaders` приймає масив пар ключ / значення:

```php
$response = Http::withHeaders([
    'X-First' => 'foo',
    'X-Second' => 'bar'
])->post('http://example.com/users', [
    'name' => 'Taylor',
]);
```

Метод `accept` дозволяє вказати тип вмісту, який ваш застосунок очікує у відповідь на запит:

```php
$response = Http::accept('application/json')->get('http://example.com/users');
```

Для зручності методом `acceptJson` можна швидко вказати, що ваш застосунок очікує у відповідь тип вмісту `application/json`:

```php
$response = Http::acceptJson()->get('http://example.com/users');
```

Метод `withHeaders` зливає нові заголовки з наявними заголовками запиту. За потреби ви можете повністю замінити всі заголовки методом `replaceHeaders`:

```php
$response = Http::withHeaders([
    'X-Original' => 'foo',
])->replaceHeaders([
    'X-Replacement' => 'bar',
])->post('http://example.com/users', [
    'name' => 'Taylor',
]);
```

<a name="authentication"></a>
### Автентифікація

Облікові дані для basic- і digest-автентифікації можна вказати методами `withBasicAuth` та `withDigestAuth` відповідно:

```php
// Basic authentication...
$response = Http::withBasicAuth('taylor@laravel.com', 'secret')->post(/* ... */);

// Digest authentication...
$response = Http::withDigestAuth('taylor@laravel.com', 'secret')->post(/* ... */);
```

<a name="bearer-tokens"></a>
#### Bearer-токени

Якщо ви хочете швидко додати bearer-токен до заголовка `Authorization` запиту, скористайтеся методом `withToken`:

```php
$response = Http::withToken('token')->post(/* ... */);
```

<a name="timeout"></a>
### Таймаут

Метод `timeout` дозволяє вказати максимальну кількість секунд очікування відповіді. За замовчуванням HTTP-клієнт завершиться таймаутом за 30 секунд:

```php
$response = Http::timeout(3)->get(/* ... */);
```

Якщо заданий таймаут перевищено, буде викинуто екземпляр `Illuminate\Http\Client\ConnectionException`.

Максимальну кількість секунд очікування під час спроби підключитися до сервера можна вказати методом `connectTimeout`. За замовчуванням це 10 секунд:

```php
$response = Http::connectTimeout(3)->get(/* ... */);
```

<a name="retries"></a>
### Повторні спроби

Якщо ви хочете, щоб HTTP-клієнт автоматично повторював запит у разі клієнтської чи серверної помилки, скористайтеся методом `retry`. Метод `retry` приймає максимальну кількість спроб виконати запит і кількість мілісекунд, які Laravel має чекати між спробами:

```php
$response = Http::retry(3, 100)->post(/* ... */);
```

Якщо ви хочете вручну обчислювати кількість мілісекунд паузи між спробами, передайте другим аргументом методу `retry` замикання:

```php
use Exception;

$response = Http::retry(3, function (int $attempt, Exception $exception) {
    return $attempt * 100;
})->post(/* ... */);
```

Для зручності ви також можете передати першим аргументом методу `retry` масив. За цим масивом визначатиметься, скільки мілісекунд чекати між наступними спробами:

```php
$response = Http::retry([100, 200])->post(/* ... */);
```

За потреби ви можете передати методу `retry` третій аргумент. Ним має бути щось викликаєме, що визначає, чи справді слід робити повторні спроби. Наприклад, ви можете хотіти повторювати запит, лише якщо початковий запит натрапив на `ConnectionException`:

```php
use Illuminate\Http\Client\PendingRequest;
use Throwable;

$response = Http::retry(3, 100, function (Throwable $exception, PendingRequest $request) {
    return $exception instanceof ConnectionException;
})->post(/* ... */);
```

Якщо спроба запиту провалилася, ви можете захотіти щось змінити в запиті перед новою спробою. Це робиться зміною аргументу запиту, переданого до того викликаємого, яке ви передали методу `retry`. Наприклад, ви можете захотіти повторити запит з новим токеном авторизації, якщо перша спроба повернула помилку автентифікації:

```php
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\RequestException;
use Throwable;

$response = Http::withToken($this->getToken())->retry(2, 0, function (Throwable $exception, PendingRequest $request) {
    if (! $exception instanceof RequestException || $exception->response->status() !== 401) {
        return false;
    }

    $request->withToken($this->getNewToken());

    return true;
})->post(/* ... */);
```

Якщо всі запити провалилися, буде викинуто екземпляр `Illuminate\Http\Client\RequestException`. Якщо ви хочете вимкнути цю поведінку, передайте аргумент `throw` зі значенням `false`. Коли її вимкнено, після всіх спроб буде повернуто останню отриману клієнтом відповідь:

```php
$response = Http::retry(3, 100, throw: false)->post(/* ... */);
```

> [!WARNING]
> Якщо всі запити провалилися через проблему зі з'єднанням, `Illuminate\Http\Client\ConnectionException` буде викинуто навіть тоді, коли аргумент `throw` має значення `false`.

<a name="error-handling"></a>
### Обробка помилок

На відміну від типової поведінки Guzzle, обгортка HTTP-клієнта Laravel не викидає винятків на клієнтських чи серверних помилках (відповіді серверів рівня `400` та `500`). Визначити, чи повернуто одну з таких помилок, можна методами `successful`, `clientError` або `serverError`:

```php
// Determine if the status code is >= 200 and < 300...
$response->successful();

// Determine if the status code is >= 400...
$response->failed();

// Determine if the response has a 400 level status code...
$response->clientError();

// Determine if the response has a 500 level status code...
$response->serverError();

// Immediately execute the given callback if there was a client or server error...
$response->onError(callable $callback);
```

<a name="throwing-exceptions"></a>
#### Викидання винятків

Якщо ви маєте екземпляр відповіді й хочете викинути екземпляр `Illuminate\Http\Client\RequestException`, коли статус-код відповіді вказує на клієнтську чи серверну помилку, скористайтеся методами `throw` або `throwIf`:

```php
use Illuminate\Http\Client\Response;

$response = Http::post(/* ... */);

// Throw an exception if a client or server error occurred...
$response->throw();

// Throw an exception if an error occurred and the given condition is true...
$response->throwIf($condition);

// Throw an exception if an error occurred and the given closure resolves to true...
$response->throwIf(fn (Response $response) => true);

// Throw an exception if an error occurred and the given condition is false...
$response->throwUnless($condition);

// Throw an exception if an error occurred and the given closure resolves to false...
$response->throwUnless(fn (Response $response) => false);

// Throw an exception if the response has a specific status code...
$response->throwIfStatus(403);

// Throw an exception unless the response has a specific status code...
$response->throwUnlessStatus(200);

// Throw an exception if a server error occurred (status >= 500)...
$response->throwIfServerError();

// Throw an exception if a client error occurred (status >= 400 and < 500)...
$response->throwIfClientError();

return $response['user']['id'];
```

Екземпляр `Illuminate\Http\Client\RequestException` має публічну властивість `$response`, яка дозволяє оглянути повернену відповідь.

Метод `throw` повертає екземпляр відповіді, якщо помилки не сталося, тож ви можете додавати до нього ланцюжком інші операції:

```php
return Http::post(/* ... */)->throw()->json();
```

Якщо ви хочете виконати додаткову логіку перед викиданням винятку, передайте методу `throw` замикання. Виняток буде викинуто автоматично після виклику замикання, тож перевикидати його всередині замикання не потрібно:

```php
use Illuminate\Http\Client\Response;
use Illuminate\Http\Client\RequestException;

return Http::post(/* ... */)->throw(function (Response $response, RequestException $e) {
    // ...
})->json();
```

За замовчуванням повідомлення `RequestException` обрізаються до 120 символів під час логування чи повідомлення. Щоб змінити чи вимкнути цю поведінку, скористайтеся методами `truncateAt` і `dontTruncate`, налаштовуючи зареєстровану поведінку застосунку у файлі `bootstrap/app.php`:

```php
use Illuminate\Http\Client\RequestException;

->registered(function (): void {
    // Truncate request exception messages to 240 characters...
    RequestException::truncateAt(240);

    // Disable request exception message truncation...
    RequestException::dontTruncate();
})
```

Як варіант, ви можете налаштувати обрізання винятків для окремого запиту методом `truncateExceptionsAt`:

```php
return Http::truncateExceptionsAt(240)->post(/* ... */);
```

<a name="guzzle-middleware"></a>
### Middleware Guzzle

Оскільки HTTP-клієнт Laravel працює на Guzzle, ви можете скористатися [middleware Guzzle](https://docs.guzzlephp.org/en/stable/handlers-and-middleware.html), щоб змінювати вихідний запит або оглядати вхідну відповідь. Щоб змінювати вихідний запит, зареєструйте middleware Guzzle методом `withRequestMiddleware`:

```php
use Illuminate\Support\Facades\Http;
use Psr\Http\Message\RequestInterface;

$response = Http::withRequestMiddleware(
    function (RequestInterface $request) {
        return $request->withHeader('X-Example', 'Value');
    }
)->get('http://example.com');
```

Так само ви можете оглядати вхідну HTTP-відповідь, зареєструвавши middleware методом `withResponseMiddleware`:

```php
use Illuminate\Support\Facades\Http;
use Psr\Http\Message\ResponseInterface;

$response = Http::withResponseMiddleware(
    function (ResponseInterface $response) {
        $header = $response->getHeader('X-Example');

        // ...

        return $response;
    }
)->get('http://example.com');
```

<a name="global-middleware"></a>
#### Глобальні middleware

Інколи вам може захотітися зареєструвати middleware, який застосовується до кожного вихідного запиту й вхідної відповіді. Для цього скористайтеся методами `globalRequestMiddleware` та `globalResponseMiddleware`. Зазвичай ці методи викликають у методі `boot` вашого `AppServiceProvider`:

```php
use Illuminate\Support\Facades\Http;

Http::globalRequestMiddleware(fn ($request) => $request->withHeader(
    'User-Agent', 'Example Application/1.0'
));

Http::globalResponseMiddleware(fn ($response) => $response->withHeader(
    'X-Finished-At', now()->toDateTimeString()
));
```

<a name="guzzle-options"></a>
### Опції Guzzle

Ви можете вказати додаткові [опції запиту Guzzle](http://docs.guzzlephp.org/en/stable/request-options.html) для вихідного запиту методом `withOptions`. Метод `withOptions` приймає масив пар ключ / значення:

```php
$response = Http::withOptions([
    'debug' => true,
])->get('http://example.com/users');
```

<a name="global-options"></a>
#### Глобальні опції

Щоб налаштувати опції за замовчуванням для кожного вихідного запиту, скористайтеся методом `globalOptions`. Зазвичай цей метод викликають у методі `boot` вашого `AppServiceProvider`:

```php
use Illuminate\Support\Facades\Http;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Http::globalOptions([
        'allow_redirects' => false,
    ]);
}
```

<a name="concurrent-requests"></a>
## Паралельні запити

Інколи вам може захотітися виконати кілька HTTP-запитів паралельно. Іншими словами, ви хочете відправити кілька запитів одночасно, а не послідовно. Це може суттєво пришвидшити роботу з повільними HTTP-API.

<a name="request-pooling"></a>
### Пул запитів

На щастя, це можна зробити методом `pool`. Метод `pool` приймає замикання, яке отримує екземпляр `Illuminate\Http\Client\Pool`, - і ви легко додаєте запити до пулу на відправлення:

```php
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;

$responses = Http::pool(fn (Pool $pool) => [
    $pool->get('http://localhost/first'),
    $pool->get('http://localhost/second'),
    $pool->get('http://localhost/third'),
]);

return $responses[0]->ok() &&
       $responses[1]->ok() &&
       $responses[2]->ok();
```

Як бачите, до кожного екземпляра відповіді можна звернутися за порядком, у якому його додано до пулу. За бажанням ви можете назвати запити методом `as`, і тоді звертатися до відповідних відповідей за іменем:

```php
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;

$responses = Http::pool(fn (Pool $pool) => [
    $pool->as('first')->get('http://localhost/first'),
    $pool->as('second')->get('http://localhost/second'),
    $pool->as('third')->get('http://localhost/third'),
]);

return $responses['first']->ok();
```

Максимальною паралельністю пулу запитів можна керувати, передавши методу `pool` аргумент `concurrency`. Це значення визначає максимальну кількість HTTP-запитів, які можуть одночасно виконуватися під час обробки пулу:

```php
$responses = Http::pool(fn (Pool $pool) => [
    // ...
], concurrency: 5);
```

Якщо запит із пулу зазнає невдачі на рівні підключення (наприклад, таймаут або помилка DNS), відповідний елемент масиву `$responses` буде екземпляром `Illuminate\Http\Client\ConnectionException` замість екземпляра `Response`:

```php
foreach ($responses as $response) {
    if ($response instanceof Throwable) {
        // Запит не зміг підключитися...
    } elseif ($response->failed()) {
        // Запит підключився, але отримав відповідь з помилкою...
    }
}
```

<a name="customizing-concurrent-requests"></a>
#### Налаштування паралельних запитів

Метод `pool` не можна поєднувати ланцюжком з іншими методами HTTP-клієнта на кшталт `withHeaders` чи `middleware`. Якщо ви хочете застосувати власні заголовки чи middleware до запитів у пулі, налаштуйте ці опції на кожному запиті пулу:

```php
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;

$headers = [
    'X-Example' => 'example',
];

$responses = Http::pool(fn (Pool $pool) => [
    $pool->withHeaders($headers)->get('http://laravel.test/test'),
    $pool->withHeaders($headers)->get('http://laravel.test/test'),
    $pool->withHeaders($headers)->get('http://laravel.test/test'),
]);
```

<a name="request-batching"></a>
### Пакети запитів

Ще один спосіб працювати з паралельними запитами в Laravel - метод `batch`. Як і метод `pool`, він приймає замикання, яке отримує екземпляр `Illuminate\Http\Client\Batch`, - і ви легко додаєте запити до пулу на відправлення, але при цьому можете описати колбеки завершення:

```php
use Illuminate\Http\Client\Batch;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;

$responses = Http::batch(fn (Batch $batch) => [
    $batch->get('http://localhost/first'),
    $batch->get('http://localhost/second'),
    $batch->get('http://localhost/third'),
])->before(function (Batch $batch) {
    // The batch has been created but no requests have been initialized...
})->progress(function (Batch $batch, int|string $key, Response $response) {
    // An individual request has completed successfully...
})->then(function (Batch $batch, array $results) {
    // All requests completed successfully...
})->catch(function (Batch $batch, int|string $key, Response|RequestException|ConnectionException $response) {
    // Batch request failure detected...
})->finally(function (Batch $batch, array $results) {
    // The batch has finished executing...
})->send();
```

Як і з методом `pool`, ви можете скористатися методом `as`, щоб назвати свої запити:

```php
$responses = Http::batch(fn (Batch $batch) => [
    $batch->as('first')->get('http://localhost/first'),
    $batch->as('second')->get('http://localhost/second'),
    $batch->as('third')->get('http://localhost/third'),
])->send();
```

Щойно пакет запущено викликом методу `send`, додавати до нього нові запити не можна. Спроба зробити це призведе до викидання винятку `Illuminate\Http\Client\BatchInProgressException`.

Максимальною паралельністю пакета запитів можна керувати методом `concurrency`. Це значення визначає максимальну кількість HTTP-запитів, які можуть одночасно виконуватися під час обробки пакета:

```php
$responses = Http::batch(fn (Batch $batch) => [
    // ...
])->concurrency(5)->send();
```

<a name="inspecting-batches"></a>
#### Огляд пакетів

Екземпляр `Illuminate\Http\Client\Batch`, який передається до колбеків завершення пакета, має низку властивостей і методів, що допомагають працювати з пакетом запитів і оглядати його:

```php
// The number of requests assigned to the batch...
$batch->totalRequests;
 
// The number of requests that have not been processed yet...
$batch->pendingRequests;
 
// The number of requests that have failed...
$batch->failedRequests;

// The number of requests that have been processed thus far...
$batch->processedRequests();

// Indicates if the batch has finished executing...
$batch->finished();

// Indicates if the batch has request failures...
$batch->hasFailures();
```
<a name="deferring-batches"></a>
#### Відкладення пакетів

Коли викликано метод `defer`, пакет запитів не виконується негайно. Натомість Laravel виконає пакет після того, як HTTP-відповідь на поточний запит застосунку вже надіслано користувачеві, - і застосунок і далі здається швидким та чуйним:

```php
use Illuminate\Http\Client\Batch;
use Illuminate\Support\Facades\Http;

$responses = Http::batch(fn (Batch $batch) => [
    $batch->get('http://localhost/first'),
    $batch->get('http://localhost/second'),
    $batch->get('http://localhost/third'),
])->then(function (Batch $batch, array $results) {
    // All requests completed successfully...
})->defer();
```

<a name="macros"></a>
## Макроси

HTTP-клієнт Laravel дозволяє описувати «макроси», які можуть слугувати плавним і виразним механізмом налаштування типових шляхів і заголовків запитів під час роботи із сервісами у вашому застосунку. Для початку опишіть макрос у методі `boot` класу `App\Providers\AppServiceProvider` вашого застосунку:

```php
use Illuminate\Support\Facades\Http;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Http::macro('github', function () {
        return Http::withHeaders([
            'X-Example' => 'example',
        ])->baseUrl('https://github.com');
    });
}
```

Щойно ваш макрос налаштовано, ви можете викликати його звідусіль у застосунку, щоб створити відкладений запит із вказаною конфігурацією:

```php
$response = Http::github()->get('/');
```

<a name="testing"></a>
## Тестування

Багато сервісів Laravel надають можливості, які допомагають легко й виразно писати тести, і HTTP-клієнт Laravel не виняток. Метод `fake` фасаду `Http` дозволяє сказати HTTP-клієнту повертати підставні / фіктивні відповіді на запити.

<a name="faking-responses"></a>
### Підміна відповідей

Наприклад, щоб HTTP-клієнт повертав порожні відповіді зі статус-кодом `200` на кожен запит, викличте метод `fake` без аргументів:

```php
use Illuminate\Support\Facades\Http;

Http::fake();

$response = Http::post(/* ... */);
```

<a name="faking-specific-urls"></a>
#### Підміна конкретних URL

Як варіант, ви можете передати методу `fake` масив. Ключі масиву мають бути шаблонами URL, які ви хочете підмінити, а значення - відповідними відповідями. Символ `*` можна використовувати як підстановку. Щоб побудувати підставні / фейкові відповіді для цих точок, скористайтеся методом `response` фасаду `Http`:

```php
Http::fake([
    // Stub a JSON response for GitHub endpoints...
    'github.com/*' => Http::response(['foo' => 'bar'], 200, $headers),

    // Stub a string response for Google endpoints...
    'google.com/*' => Http::response('Hello World', 200, $headers),
]);
```

Будь-які запити на URL, які не підмінено, буде виконано насправді. Якщо ви хочете задати запасний шаблон URL, який підмінить усі неспівпалі URL, скористайтеся єдиним символом `*`:

```php
Http::fake([
    // Stub a JSON response for GitHub endpoints...
    'github.com/*' => Http::response(['foo' => 'bar'], 200, ['Headers']),

    // Stub a string response for all other endpoints...
    '*' => Http::response('Hello World', 200, ['Headers']),
]);
```

Для зручності прості рядкові, JSON- та порожні відповіді можна згенерувати, передавши як відповідь рядок, масив чи ціле число:

```php
Http::fake([
    'google.com/*' => 'Hello World',
    'github.com/*' => ['foo' => 'bar'],
    'chatgpt.com/*' => 200,
]);
```

<a name="faking-connection-exceptions"></a>
#### Підміна винятків

Інколи вам може знадобитися перевірити поведінку застосунку, коли HTTP-клієнт натрапляє на `Illuminate\Http\Client\ConnectionException` під час спроби виконати запит. Ви можете сказати HTTP-клієнту викинути виняток з'єднання методом `failedConnection`:

```php
Http::fake([
    'github.com/*' => Http::failedConnection(),
]);
```

Щоб перевірити поведінку застосунку, коли викинуто `Illuminate\Http\Client\RequestException`, скористайтеся методом `failedRequest`:

```php
$this->mock(GithubService::class)
    ->shouldReceive('getUser')
    ->andThrow(
        Http::failedRequest(['code' => 'not_found'], 404)
    );
```

<a name="faking-response-sequences"></a>
#### Підміна послідовностей відповідей

Інколи вам може знадобитися вказати, що один URL має повертати серію фейкових відповідей у певному порядку. Це робиться методом `Http::sequence`, яким будують відповіді:

```php
Http::fake([
    // Stub a series of responses for GitHub endpoints...
    'github.com/*' => Http::sequence()
        ->push('Hello World', 200)
        ->push(['foo' => 'bar'], 200)
        ->pushStatus(404),
]);
```

Коли всі відповіді в послідовності спожито, будь-які наступні запити змусять послідовність викинути виняток. Якщо ви хочете задати відповідь за замовчуванням, яку слід повертати, коли послідовність порожня, скористайтеся методом `whenEmpty`:

```php
Http::fake([
    // Stub a series of responses for GitHub endpoints...
    'github.com/*' => Http::sequence()
        ->push('Hello World', 200)
        ->push(['foo' => 'bar'], 200)
        ->whenEmpty(Http::response()),
]);
```

Якщо ви хочете підмінити послідовність відповідей, але вам не потрібно вказувати конкретний шаблон URL, скористайтеся методом `Http::fakeSequence`:

```php
Http::fakeSequence()
    ->push('Hello World', 200)
    ->whenEmpty(Http::response());
```

<a name="fake-callback"></a>
#### Колбек підміни

Якщо вам потрібна складніша логіка, щоб визначити, які відповіді повертати для певних точок, передайте методу `fake` замикання. Це замикання отримає екземпляр `Illuminate\Http\Client\Request` і має повернути екземпляр відповіді. Усередині замикання ви можете виконати будь-яку логіку, потрібну, щоб визначити, який тип відповіді повернути:

```php
use Illuminate\Http\Client\Request;

Http::fake(function (Request $request) {
    return Http::response('Hello World', 200);
});
```

<a name="inspecting-requests"></a>
### Огляд запитів

Підміняючи відповіді, ви інколи можете захотіти оглянути запити, які отримує клієнт, аби переконатися, що ваш застосунок надсилає правильні дані чи заголовки. Це робиться викликом методу `Http::assertSent` після виклику `Http::fake`.

Метод `assertSent` приймає замикання, яке отримає екземпляр `Illuminate\Http\Client\Request` і має повернути булеве значення, що вказує, чи відповідає запит вашим очікуванням. Щоб тест пройшов, має бути виконано щонайменше один запит, який відповідає заданим очікуванням:

```php
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;

Http::fake();

Http::withHeaders([
    'X-First' => 'foo',
])->post('http://example.com/users', [
    'name' => 'Taylor',
    'role' => 'Developer',
]);

Http::assertSent(function (Request $request) {
    return $request->hasHeader('X-First', 'foo') &&
           $request->url() == 'http://example.com/users' &&
           $request['name'] == 'Taylor' &&
           $request['role'] == 'Developer';
});
```

За потреби ви можете перевірити, що конкретного запиту не було надіслано, методом `assertNotSent`:

```php
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;

Http::fake();

Http::post('http://example.com/users', [
    'name' => 'Taylor',
    'role' => 'Developer',
]);

Http::assertNotSent(function (Request $request) {
    return $request->url() === 'http://example.com/posts';
});
```

Метод `assertSentCount` дозволяє перевірити, скільки запитів було «надіслано» під час тесту:

```php
Http::fake();

Http::assertSentCount(5);
```

Або ж метод `assertNothingSent` дозволяє перевірити, що під час тесту не було надіслано жодного запиту:

```php
Http::fake();

Http::assertNothingSent();
```

<a name="recording-requests-and-responses"></a>
#### Запис запитів / відповідей

Метод `recorded` дозволяє зібрати всі запити та відповідні їм відповіді. Метод `recorded` повертає колекцію масивів, що містять екземпляри `Illuminate\Http\Client\Request` та `Illuminate\Http\Client\Response`:

```php
Http::fake([
    'https://laravel.com' => Http::response(status: 500),
    'https://nova.laravel.com/' => Http::response(),
]);

Http::get('https://laravel.com');
Http::get('https://nova.laravel.com/');

$recorded = Http::recorded();

[$request, $response] = $recorded[0];
```

Крім того, метод `recorded` приймає замикання, яке отримає екземпляри `Illuminate\Http\Client\Request` та `Illuminate\Http\Client\Response` і дозволяє відфільтрувати пари запит / відповідь за вашими очікуваннями:

```php
use Illuminate\Http\Client\Request;
use Illuminate\Http\Client\Response;

Http::fake([
    'https://laravel.com' => Http::response(status: 500),
    'https://nova.laravel.com/' => Http::response(),
]);

Http::get('https://laravel.com');
Http::get('https://nova.laravel.com/');

$recorded = Http::recorded(function (Request $request, Response $response) {
    return $request->url() !== 'https://laravel.com' &&
           $response->successful();
});
```

<a name="preventing-stray-requests"></a>
### Запобігання «блукаючим» запитам

Якщо ви хочете переконатися, що всі запити, надіслані через HTTP-клієнт, підмінено в окремому тесті чи в усьому наборі тестів, викличте метод `preventStrayRequests`. Після виклику цього методу будь-які запити, для яких немає відповідної фейкової відповіді, викидатимуть виняток замість того, щоб виконувати справжній HTTP-запит:

```php
use Illuminate\Support\Facades\Http;

Http::preventStrayRequests();

Http::fake([
    'github.com/*' => Http::response('ok'),
]);

// An "ok" response is returned...
Http::get('https://github.com/laravel/framework');

// An exception is thrown...
Http::get('https://laravel.com');
```

Інколи вам може захотітися заблокувати більшість блукаючих запитів, але дозволити виконання певних. Для цього передайте масив шаблонів URL методу `allowStrayRequests`. Будь-який запит, що відповідає одному із заданих шаблонів, буде дозволено, а всі інші й далі викидатимуть виняток:

```php
use Illuminate\Support\Facades\Http;

Http::preventStrayRequests();

Http::allowStrayRequests([
    'http://127.0.0.1:5000/*',
]);

// This request is executed...
Http::get('http://127.0.0.1:5000/generate');

// An exception is thrown...
Http::get('https://laravel.com');
```

<a name="events"></a>
## Події

Під час надсилання HTTP-запитів Laravel запускає три події. Подія `RequestSending` запускається перед надсиланням запиту, а подія `ResponseReceived` - після отримання відповіді на запит. Подія `ConnectionFailed` запускається, якщо відповіді на запит не отримано.

Події `RequestSending` та `ConnectionFailed` містять публічну властивість `$request`, яка дозволяє оглянути екземпляр `Illuminate\Http\Client\Request`. Так само подія `ResponseReceived` містить властивість `$request`, а також властивість `$response`, яка дозволяє оглянути екземпляр `Illuminate\Http\Client\Response`. Ви можете створити [слухачів подій](/docs/{{version}}/events) для цих подій у своєму застосунку:

```php
use Illuminate\Http\Client\Events\RequestSending;

class LogRequest
{
    /**
     * Handle the event.
     */
    public function handle(RequestSending $event): void
    {
        // $event->request ...
    }
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/images

# Обробка зображень

- [Вступ](#introduction)
- [Встановлення](#installation)
    - [Конфігурація](#configuration)
- [Читання зображень](#reading-images)
    - [Завантажені файли](#uploaded-files)
    - [Файли у сховищі](#storage-files)
    - [Інші джерела](#other-sources)
- [Обробка зображень](#manipulating-images)
    - [Зміна розміру зображень](#resizing-images)
    - [Інші перетворення](#other-transformations)
- [Кодування зображень](#encoding-images)
- [Збереження зображень](#storing-images)
- [Огляд зображень](#inspecting-images)
- [Драйвери зображень](#image-drivers)
    - [Власні драйвери зображень](#custom-image-drivers)
    - [Власні перетворення](#custom-transformations)

<a name="introduction"></a>
## Вступ

Laravel надає плавний API для обробки зображень, який дозволяє змінювати розмір, обрізати, кодувати й зберігати зображення за тими самими виразними домовленостями, що діють в усьому фреймворку. Можливості роботи із зображеннями в Laravel побудовані на [Intervention Image](https://image.intervention.io/) і підтримують PHP-розширення GD та Imagick.

API зображень стане в пригоді під час роботи із завантаженими файлами, файлами на [дисках файлової системи](/docs/{{version}}/filesystem) Laravel, локальними файлами, віддаленими URL чи сирими байтами зображення:

```php
use Illuminate\Support\Facades\Image;

$path = Image::fromStorage('avatars/photo.jpg', 'public')
    ->cover(400, 400)
    ->toWebp()
    ->quality(80)
    ->storePublicly('avatars', 'public');
```

> [!WARNING]
> Обробка зображень може бути вимогливою до процесора й пам'яті. Розгляньте можливість виконувати великі обсяги обробки зображень у [завданні в черзі](/docs/{{version}}/queues), а не під час HTTP-запиту, який приймає завантаження.

<a name="installation"></a>
## Встановлення

Перш ніж користуватися можливостями обробки зображень у Laravel, встановіть пакет Intervention Image через Composer:

```shell
composer require intervention/image:^4.0
```

Також переконайтеся, що у вашій інсталяції PHP встановлено розширення GD або Imagick - залежно від того, який драйвер використовуватиме ваш застосунок.

<a name="configuration"></a>
### Конфігурація

Файл конфігурації зображень Laravel лежить у `config/images.php`. Якщо у вашому застосунку немає файлу конфігурації `images`, опублікуйте його командою Artisan `config:publish`:

```shell
php artisan config:publish images
```

Файл конфігурації зображень дозволяє вказати драйвер зображень за замовчуванням для вашого застосунку. Ви також можете вказати драйвер за замовчуванням через змінну середовища `IMAGE_DRIVER`. Підтримувані драйвери - `gd` та `imagick`:

```ini
IMAGE_DRIVER=imagick
```

<a name="reading-images"></a>
## Читання зображень

Фасад `Image` надає кілька методів для читання зображень із поширених джерел. Вміст зображень завантажується ліниво, тож джерело зазвичай не читається, доки зображення не оброблено або доки не запитано його байти.

<a name="uploaded-files"></a>
### Завантажені файли

Ви можете дістати завантажене зображення з вхідного запиту методом `image`. Цей метод повертає екземпляр `Illuminate\Image\Image` для завантаженого файлу або `null`, якщо файлу немає:

```php
use Illuminate\Http\Request;

Route::post('/avatar', function (Request $request) {
    $request->validate(['avatar' => ['required', 'image']]);

    $path = $request->image('avatar')
        ->cover(400, 400)
        ->toWebp()
        ->storePublicly('avatars', 'public');

    // ...
});
```

Як варіант, ви можете створити екземпляр зображення з екземпляра `Illuminate\Http\UploadedFile` методом `fromUpload`:

```php
use Illuminate\Support\Facades\Image;

$image = Image::fromUpload($request->file('avatar'));
```

Коли зображення створено із завантаженого файлу, ви можете дістати цей файл методом `file`:

```php
$file = $image->file();
```

<a name="storage-files"></a>
### Файли у сховищі

Ви можете створити екземпляр зображення з файлу, що зберігається на одному з [дисків файлової системи](/docs/{{version}}/filesystem) вашого застосунку, методом `fromStorage`. Перший аргумент - шлях до файлу, другий - назва диска:

```php
use Illuminate\Support\Facades\Image;

$image = Image::fromStorage('avatars/photo.jpg', disk: 'public');
```

Ви також можете створювати екземпляри зображень безпосередньо з екземпляра диска методом `image`:

```php
use Illuminate\Support\Facades\Storage;

$image = Storage::disk('public')->image('avatars/photo.jpg');
```

<a name="other-sources"></a>
### Інші джерела

Фасад `Image` також містить методи для створення екземплярів зображень із сирих байтів, локальних шляхів, віддалених URL і рядків у Base64:

```php
use Illuminate\Support\Facades\Image;

$image = Image::fromBytes($contents);
$image = Image::fromBase64($base64);
$image = Image::fromPath(storage_path('app/avatars/photo.jpg'));
$image = Image::fromUrl('https://example.com/photo.jpg');
```

<a name="manipulating-images"></a>
## Обробка зображень

Екземпляри зображень незмінні. Кожен метод обробки повертає новий екземпляр зображення з доданим до його конвеєра перетворенням, тож методи можна плавно поєднувати ланцюжком:

```php
$image = $request->image('avatar')
    ->orient()
    ->cover(400, 400)
    ->sharpen(10);
```

Перетворення обробляються в порядку, у якому їх додано до конвеєра зображення, а саме зображення кодується лише один раз, у кінці.

<a name="resizing-images"></a>
### Зміна розміру зображень

Метод `resize` змінює розмір зображення до заданих величин. Ви можете передати і ширину, і висоту або лише одну величину через іменовані аргументи:

```php
$image = $image->resize(800, 600);
$image = $image->resize(width: 800);
$image = $image->resize(height: 600);
```

Метод `scale` пропорційно зменшує зображення так, щоб воно вмістилося в задані величини. Цей метод ніколи не збільшує зображення:

```php
$image = $image->scale(800, 600);
$image = $image->scale(width: 800);
$image = $image->scale(height: 600);
```

Метод `cover` змінює розмір і обрізає зображення так, щоб воно повністю вкривало задані величини:

```php
$image = $image->cover(400, 400);
```

Метод `contain` змінює розмір зображення так, щоб воно вмістилося в задані величини, зберігши все зображення. За потреби порожній простір буде заповнено необов'язковим кольором тла:

```php
$image = $image->contain(400, 400);
$image = $image->contain(400, 400, '#ffffff');
$image = $image->contain(400, 400, 'dominant');
```

Ви можете вказати `dominant` як колір тла, щоб заповнити порожній простір домінантним кольором зображення.

Обрізати зображення можна методом `crop`. Перші два аргументи - потрібні ширина й висота, а необов'язкові третій і четвертий задають координати `x` та `y` обрізання:

```php
$image = $image->crop(300, 200);
$image = $image->crop(300, 200, x: 50, y: 25);
```

<a name="other-transformations"></a>
### Інші перетворення

Laravel також надає низку додаткових методів перетворення зображень:

```php
$image = $image->orient();
$image = $image->rotate(90);
$image = $image->rotate(90, '#ffffff');
$image = $image->rotate(90, 'dominant');
$image = $image->blur(5);
$image = $image->grayscale();
$image = $image->sharpen(10);
$image = $image->flipVertically();
$image = $image->flipHorizontally();
```

Метод `orient` повертає зображення відповідно до даних орієнтації EXIF. Метод `rotate` повертає зображення за годинниковою стрілкою на заданий кут і приймає необов'язковий колір тла. Методи `blur` і `sharpen` приймають значення від `0` до `100`.

<a name="conditional-transformations"></a>
#### Умовні перетворення

Екземпляри зображень підтримують трейт `Conditionable` Laravel, тож ви можете застосовувати перетворення умовно, методами `when` та `unless`:

```php
$image = $request->image('avatar')
    ->when($request->boolean('crop'), fn ($image) => $image->cover(400, 400))
    ->unless($request->boolean('preserve_format'), fn ($image) => $image->toWebp());
```

<a name="encoding-images"></a>
## Кодування зображень

За замовчуванням оброблені зображення кодуються у своєму оригінальному форматі. Проте ви можете конвертувати зображення в інший підтримуваний формат перед отриманням чи збереженням:

```php
$image = $image->toWebp();
$image = $image->toJpg();
$image = $image->toJpeg();
$image = $image->toPng();
$image = $image->toGif();
$image = $image->toAvif();
$image = $image->toBmp();
```

Метод `quality` дозволяє задати якість на виході. Якість буде обмежено діапазоном від `1` до `100`:

```php
$image = $image->toWebp()->quality(80);
```

Метод `optimize` - зручне скорочення для конвертації зображення в заданий формат із заданням якості. За замовчуванням зображення оптимізуються як WebP з якістю `70`:

```php
$image = $image->optimize();

$image = $image->optimize(format: 'jpg', quality: 85);
```

Ви можете отримати вміст обробленого зображення як рядок байтів, рядок у Base64 чи data URI:

```php
$bytes = $image->toBytes();
$base64 = $image->toBase64();
$dataUri = $image->toDataUri();
```

Екземпляр зображення можна також привести до рядка, щоб отримати data URI:

```php
$dataUri = (string) $image;
```

<a name="storing-images"></a>
## Збереження зображень

Метод `store` зберігає оброблене зображення на одному з дисків файлової системи вашого застосунку. Як і для завантажених файлів, Laravel згенерує унікальне ім'я файлу й поверне шлях збереження. Другим аргументом можна вказати диск:

```php
$path = $request->image('avatar')
    ->cover(400, 400)
    ->store(path: 'avatars');

$path = $request->image('avatar')
    ->cover(400, 400)
    ->store(path: 'avatars', disk: 's3');
```

Метод `storeAs` дозволяє вказати ім'я збереженого файлу:

```php
$path = $request->image('avatar')
    ->cover(400, 400)
    ->storeAs(path: 'avatars', name: 'avatar.jpg', disk: 'public');
```

Методи `storePublicly` та `storePubliclyAs` зберігають зображення з видимістю `public`:

```php
$path = $request->image('avatar')
    ->cover(400, 400)
    ->storePublicly(path: 'avatars', disk: 'public');

$path = $request->image('avatar')
    ->cover(400, 400)
    ->storePubliclyAs(path: 'avatars', name: 'avatar.webp', disk: 'public');
```

Якщо зображення не вдалося зберегти, методи збереження повертають `false`.

<a name="inspecting-images"></a>
## Огляд зображень

Отримати MIME-тип, розширення, розміри, ширину, висоту й домінантний колір зображення можна такими методами:

```php
$mimeType = $image->mimeType();
$extension = $image->extension();

[$width, $height] = $image->dimensions();
$width = $image->width();
$height = $image->height();

$dominantColor = $image->dominantColor();
```

Ці методи працюють з обробленим зображенням. Наприклад, виклик `width` після `cover(400, 400)` поверне `400`.

<a name="image-drivers"></a>
## Драйвери зображень

<a name="custom-image-drivers"></a>
### Власні драйвери зображень

Менеджер зображень Laravel розширює базовий клас `Illuminate\Support\Manager`. Це означає, що ви можете реєструвати власні драйвери зображень методом `extend`, доступним у менеджері зображень і фасаді `Image`.

Власні драйвери зображень мають реалізовувати інтерфейс `Illuminate\Contracts\Image\Driver`. Метод `process` отримує оригінальний вміст зображення та впорядкований `Illuminate\Image\ImagePipeline`, який слід застосувати до зображення, і має повернути оброблені байти. Метод `dimensions` має повернути ширину й висоту зображення, а метод `dominantColor` - середній колір зображення у вигляді hex-рядка:

```php
<?php

namespace App\Images;

use Illuminate\Contracts\Image\Driver;
use Illuminate\Image\ImagePipeline;

class VipsDriver implements Driver
{
    /**
     * Process the given image contents with the specified pipeline.
     */
    public function process(string $contents, ImagePipeline $pipeline): string
    {
        // Apply the pipeline's transformations and output options...

        return $contents;
    }

    /**
     * Get the dimensions of the given image contents.
     */
    public function dimensions(string $contents): array
    {
        // Read the image's width and height...

        return [0, 0];
    }

    /**
     * Get the dominant (average) color of the image as a hex string.
     */
    public function dominantColor(string $contents): string
    {
        // Calculate the image's average color...

        return '#000000';
    }

    /**
     * Register a transformation handler.
     */
    public function transformUsing(string $transformation, callable $callback): static
    {
        // Store the handler so it may be applied while processing the pipeline...

        return $this;
    }
}
```

> [!NOTE]
> Щоб краще зрозуміти, як реалізувати власний драйвер зображень, перегляньте вбудований клас фреймворку `Illuminate\Image\Drivers\InterventionDriver`.

Щойно ви реалізували власний драйвер, зареєструйте його методом `extend` фасаду `Image`. Зазвичай це роблять у методі `boot` сервіс-провайдера:

```php
use App\Images\VipsDriver;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Image;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Image::extend('vips', function (Application $app) {
        return new VipsDriver;
    });
}
```

Зареєструвавши драйвер, ви можете скористатися ним для конкретного зображення методом `using`:

```php
$image = $request->image('avatar')
    ->using('vips')
    ->cover(400, 400);
```

Ви також можете зробити власний драйвер драйвером зображень за замовчуванням для вашого застосунку через опцію `default` у файлі `config/images.php` або змінну середовища `IMAGE_DRIVER`:

```ini
IMAGE_DRIVER=vips
```

<a name="custom-transformations"></a>
### Власні перетворення

Застосунки й пакети можуть описувати власні перетворення, створивши клас, який реалізує контракт `Illuminate\Contracts\Image\Transformation`. Далі власні перетворення можна додавати до конвеєра зображення методом `transform`:

```php
<?php

namespace App\Images\Transformations;

use Illuminate\Contracts\Image\Transformation;

class Pixelate implements Transformation
{
    public function __construct(
        public readonly int $size,
    ) {
        //
    }
}
```

Далі зареєструйте обробник для перетворення й драйвера методом `transformUsing` фасаду `Image`. Зазвичай це роблять у методі `boot` сервіс-провайдера:

```php
use App\Images\Transformations\Pixelate;
use Illuminate\Support\Facades\Image;
use Intervention\Image\Interfaces\ImageInterface;

Image::transformUsing('gd', Pixelate::class, function (ImageInterface $image, Pixelate $transformation) {
    return $image->pixelate($transformation->size);
});
```

Щойно обробник перетворення зареєстровано, ви можете застосувати перетворення до зображення:

```php
use App\Images\Transformations\Pixelate;

$image = $request->image('avatar')
    ->transform(new Pixelate(12))
    ->store('avatars');
```

---

Джерело: https://laravelukraine.com/docs/13.x/localization

# Локалізація

- [Вступ](#introduction)
    - [Публікація мовних файлів](#publishing-the-language-files)
    - [Налаштування локалі](#configuring-the-locale)
    - [Мова множини](#pluralization-language)
- [Опис рядків перекладу](#defining-translation-strings)
    - [Короткі ключі](#using-short-keys)
    - [Рядки перекладу як ключі](#using-translation-strings-as-keys)
- [Отримання рядків перекладу](#retrieving-translation-strings)
    - [Заміна параметрів у рядках перекладу](#replacing-parameters-in-translation-strings)
    - [Множина](#pluralization)
- [Перевизначення мовних файлів пакетів](#overriding-package-language-files)

<a name="introduction"></a>
## Вступ

> [!NOTE]
> За замовчуванням каркас застосунку Laravel не містить каталогу `lang`. Якщо ви хочете налаштувати мовні файли Laravel, опублікуйте їх командою Artisan `lang:publish`.

Можливості локалізації Laravel надають зручний спосіб отримувати рядки різними мовами, дозволяючи легко підтримувати кілька мов у вашому застосунку.

Laravel пропонує два способи керувати рядками перекладу. Перший: мовні рядки можна зберігати у файлах у каталозі `lang` застосунку. У цьому каталозі можуть бути підкаталоги для кожної мови, яку підтримує застосунок. Саме цей підхід Laravel використовує для рядків перекладу вбудованих можливостей - наприклад, повідомлень про помилки валідації:

```text
/lang
    /en
        messages.php
    /es
        messages.php
```

Або ж рядки перекладу можна описати у JSON-файлах, розміщених у каталозі `lang`. За такого підходу кожна мова, яку підтримує ваш застосунок, матиме відповідний JSON-файл у цьому каталозі. Цей підхід рекомендують для застосунків з великою кількістю рядків для перекладу:

```text
/lang
    en.json
    es.json
```

Ми розглянемо кожен підхід до керування рядками перекладу в цій документації.

<a name="publishing-the-language-files"></a>
### Публікація мовних файлів

За замовчуванням каркас застосунку Laravel не містить каталогу `lang`. Якщо ви хочете налаштувати мовні файли Laravel або створити власні, згенеруйте каталог `lang` командою Artisan `lang:publish`. Команда `lang:publish` створить у вашому застосунку каталог `lang` і опублікує стандартний набір мовних файлів, які використовує Laravel:

```shell
php artisan lang:publish
```

<a name="configuring-the-locale"></a>
### Налаштування локалі

Мова за замовчуванням для вашого застосунку зберігається в опції конфігурації `locale` у файлі `config/app.php`, яку зазвичай задають змінною середовища `APP_LOCALE`. Ви вільні змінити це значення під потреби свого застосунку.

Ви також можете налаштувати «запасну мову», яка використовуватиметься, коли в мові за замовчуванням немає потрібного рядка перекладу. Як і мову за замовчуванням, запасну мову налаштовують у файлі `config/app.php`, а її значення зазвичай задають змінною середовища `APP_FALLBACK_LOCALE`.

Ви можете змінити мову за замовчуванням для окремого HTTP-запиту під час виконання методом `setLocale` фасаду `App`:

```php
use Illuminate\Support\Facades\App;

Route::get('/greeting/{locale}', function (string $locale) {
    if (! in_array($locale, ['en', 'es', 'fr'])) {
        abort(400);
    }

    App::setLocale($locale);

    // ...
});
```

<a name="determining-the-current-locale"></a>
#### Визначення поточної локалі

Методи `currentLocale` та `isLocale` фасаду `App` дозволяють визначити поточну локаль або перевірити, чи має вона задане значення:

```php
use Illuminate\Support\Facades\App;

$locale = App::currentLocale();

if (App::isLocale('en')) {
    // ...
}
```

<a name="pluralization-language"></a>
### Мова множини

<style>
.code-list-no-flex-break code {
    display: contents !important;
}
</style>

<div class="code-list-no-flex-break">

Ви можете вказати «плюралізатору» Laravel, який Eloquent та інші частини фреймворку використовують для перетворення рядків з однини на множину, працювати з мовою, відмінною від англійської. Це робиться викликом методу `useLanguage` у методі `boot` одного з сервіс-провайдерів вашого застосунку. Наразі плюралізатор підтримує такі мови: `french`, `norwegian-bokmal`, `portuguese`, `spanish` та `turkish`:

</div>

```php
use Illuminate\Support\Pluralizer;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Pluralizer::useLanguage('spanish');

    // ...
}
```

> [!WARNING]
> Якщо ви змінюєте мову плюралізатора, вам слід явно описати [назви таблиць](/docs/{{version}}/eloquent#table-names) ваших моделей Eloquent.

<a name="defining-translation-strings"></a>
## Опис рядків перекладу

<a name="using-short-keys"></a>
### Короткі ключі

Зазвичай рядки перекладу зберігаються у файлах у каталозі `lang`. У цьому каталозі має бути підкаталог для кожної мови, яку підтримує ваш застосунок. Саме цей підхід Laravel використовує для рядків перекладу вбудованих можливостей - наприклад, повідомлень про помилки валідації:

```text
/lang
    /en
        messages.php
    /es
        messages.php
```

Усі мовні файли повертають масив рядків із ключами. Наприклад:

```php
<?php

// lang/en/messages.php

return [
    'welcome' => 'Welcome to our application!',
];
```

> [!WARNING]
> Для мов, які різняться за територією, називайте мовні каталоги за стандартом ISO 15897. Наприклад, для британської англійської слід використовувати «en_GB», а не «en-gb».

<a name="using-translation-strings-as-keys"></a>
### Рядки перекладу як ключі

У застосунках з великою кількістю рядків для перекладу опис кожного рядка «коротким ключем» може заплутувати під час звертання до ключів у представленнях, та й вигадувати ключі для кожного рядка перекладу обтяжливо.

Тому Laravel також підтримує опис рядків перекладу, де ключем є «стандартний» переклад рядка. Мовні файли, у яких рядки перекладу є ключами, зберігаються як JSON-файли в каталозі `lang`. Наприклад, якщо ваш застосунок має іспанський переклад, створіть файл `lang/es.json`:

```json
{
    "I love programming.": "Me encanta programar."
}
```

#### Конфлікти ключів і файлів

Не описуйте ключі рядків перекладу, які конфліктують з іменами інших мовних файлів. Наприклад, переклад `__('Action')` для локалі «NL», коли файл `nl/action.php` існує, а файлу `nl.json` немає, призведе до того, що перекладач поверне весь вміст `nl/action.php`.

<a name="retrieving-translation-strings"></a>
## Отримання рядків перекладу

Отримати рядки перекладу з мовних файлів можна функцією-хелпером `__`. Якщо ви описуєте рядки перекладу «короткими ключами», передайте функції `__` файл, який містить ключ, і сам ключ через «крапковий» синтаксис. Наприклад, дістаньмо рядок перекладу `welcome` з мовного файлу `lang/en/messages.php`:

```php
echo __('messages.welcome');
```

Якщо вказаного рядка перекладу не існує, функція `__` поверне ключ рядка перекладу. Тож у прикладі вище функція `__` повернула б `messages.welcome`, якби рядка перекладу не існувало.

Якщо ви використовуєте [стандартні рядки перекладу як ключі](#using-translation-strings-as-keys), передайте функції `__` стандартний переклад свого рядка;

```php
echo __('I love programming.');
```

Знову ж таки, якщо рядка перекладу не існує, функція `__` поверне переданий їй ключ рядка перекладу.

Якщо ви користуєтеся [шаблонізатором Blade](/docs/{{version}}/blade), вивести рядок перекладу можна синтаксисом виведення `{{ }}`:

```blade
{{ __('messages.welcome') }}
```

<a name="replacing-parameters-in-translation-strings"></a>
### Заміна параметрів у рядках перекладу

За бажанням ви можете описувати в рядках перекладу плейсхолдери. Усі плейсхолдери мають префікс `:`. Наприклад, ви можете описати вітальне повідомлення з плейсхолдером імені:

```php
'welcome' => 'Welcome, :name',
```

Щоб замінити плейсхолдери під час отримання рядка перекладу, передайте масив замін другим аргументом функції `__`:

```php
echo __('messages.welcome', ['name' => 'dayle']);
```

Якщо ваш плейсхолдер написано великими літерами або з великої літери, перекладене значення буде відповідно капіталізовано:

```php
'welcome' => 'Welcome, :NAME', // Welcome, DAYLE
'goodbye' => 'Goodbye, :Name', // Goodbye, Dayle
```

<a name="object-replacement-formatting"></a>
#### Форматування підстановки об'єктів

Якщо ви спробуєте передати об'єкт як плейсхолдер перекладу, буде викликано його метод `__toString`. Метод [__toString](https://www.php.net/manual/en/language.oop5.magic.php#object.tostring) - один із вбудованих «магічних методів» PHP. Проте інколи ви можете не контролювати метод `__toString` певного класу - наприклад, коли клас, з яким ви працюєте, належить сторонній бібліотеці.

У таких випадках Laravel дозволяє зареєструвати власний обробник форматування для конкретного типу об'єктів. Для цього викличте метод `stringable` перекладача. Метод `stringable` приймає замикання, у якому слід вказати тип об'єкта, за форматування якого воно відповідає. Зазвичай метод `stringable` викликають у методі `boot` класу `AppServiceProvider` вашого застосунку:

```php
use Illuminate\Support\Facades\Lang;
use Money\Money;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Lang::stringable(function (Money $money) {
        return $money->formatTo('en_GB');
    });
}
```

<a name="pluralization"></a>
### Множина

Множина - складна задача, адже різні мови мають розмаїті складні правила її утворення; проте Laravel допоможе перекладати рядки по-різному за правилами множини, які ви опишете. Символом `|` ви можете розділити форми однини та множини рядка:

```php
'apples' => 'There is one apple|There are many apples',
```

Звісно, множина підтримується й тоді, коли ви використовуєте [рядки перекладу як ключі](#using-translation-strings-as-keys):

```json
{
    "There is one apple|There are many apples": "Hay una manzana|Hay muchas manzanas"
}
```

Ви можете створювати й складніші правила множини, які задають рядки перекладу для кількох діапазонів значень:

```php
'apples' => '{0} There are none|[1,19] There are some|[20,*] There are many',
```

Описавши рядок перекладу з варіантами множини, ви можете скористатися функцією `trans_choice`, щоб отримати рядок для заданої «кількості». У цьому прикладі, оскільки кількість більша за одиницю, буде повернуто форму множини:

```php
echo trans_choice('messages.apples', 10);
```

Ви також можете описувати в рядках множини плейсхолдери-атрибути. Замінити їх можна, передавши масив третім аргументом функції `trans_choice`:

```php
'minutes_ago' => '{1} :value minute ago|[2,*] :value minutes ago',

echo trans_choice('time.minutes_ago', 5, ['value' => 5]);
```

Якщо ви хочете вивести ціле число, передане функції `trans_choice`, скористайтеся вбудованим плейсхолдером `:count`:

```php
'apples' => '{0} There are none|{1} There is one|[2,*] There are :count',
```

<a name="overriding-package-language-files"></a>
## Перевизначення мовних файлів пакетів

Деякі пакети можуть постачатися з власними мовними файлами. Замість змінювати основні файли пакета заради правки цих рядків, ви можете перевизначити їх, розмістивши файли в каталозі `lang/vendor/{package}/{locale}`.

Наприклад, якщо вам потрібно перевизначити англійські рядки перекладу в `messages.php` для пакета `skyrim/hearthfire`, покладіть мовний файл за шляхом: `lang/vendor/hearthfire/en/messages.php`. У цьому файлі описуйте лише ті рядки перекладу, які хочете перевизначити. Усі рядки, які ви не перевизначили, і далі завантажуватимуться з оригінальних мовних файлів пакета.

---

Джерело: https://laravelukraine.com/docs/13.x/mail

# Пошта

- [Вступ](#introduction)
    - [Конфігурація](#configuration)
    - [Передумови драйверів](#driver-prerequisites)
    - [Конфігурація failover](#failover-configuration)
    - [Конфігурація round robin](#round-robin-configuration)
- [Генерація mailable-класів](#generating-mailables)
- [Написання mailable-класів](#writing-mailables)
    - [Налаштування відправника](#configuring-the-sender)
    - [Налаштування представлення](#configuring-the-view)
    - [Дані представлення](#view-data)
    - [Вкладення](#attachments)
    - [Вбудовані вкладення](#inline-attachments)
    - [Об'єкти, придатні для вкладення](#attachable-objects)
    - [Заголовки](#headers)
    - [Теги й метадані](#tags-and-metadata)
    - [Налаштування повідомлення Symfony](#customizing-the-symfony-message)
- [Markdown-листи](#markdown-mailables)
    - [Генерація Markdown-листів](#generating-markdown-mailables)
    - [Написання Markdown-повідомлень](#writing-markdown-messages)
    - [Налаштування компонентів](#customizing-the-components)
- [Надсилання пошти](#sending-mail)
    - [Пошта в черзі](#queueing-mail)
- [Рендеринг mailable-класів](#rendering-mailables)
    - [Попередній перегляд листів у браузері](#previewing-mailables-in-the-browser)
- [Локалізація листів](#localizing-mailables)
- [Тестування](#testing-mailables)
    - [Тестування вмісту листа](#testing-mailable-content)
    - [Тестування надсилання листа](#testing-mailable-sending)
- [Пошта й локальна розробка](#mail-and-local-development)
- [Події](#events)
- [Власні транспорти](#custom-transports)
    - [Додаткові транспорти Symfony](#additional-symfony-transports)

<a name="introduction"></a>
## Вступ

Надсилання пошти не мусить бути складним. Laravel надає чистий і простий API для електронної пошти, побудований на популярному компоненті [Symfony Mailer](https://symfony.com/doc/current/mailer.html). Laravel і Symfony Mailer надають драйвери для надсилання пошти через SMTP, Cloudflare, Mailgun, Postmark, Resend, Amazon SES та `sendmail`, тож ви можете швидко почати надсилати пошту через локальний чи хмарний сервіс на свій вибір.

<a name="configuration"></a>
### Конфігурація

Поштові сервіси Laravel налаштовуються у файлі `config/mail.php` вашого застосунку. Кожен налаштований у цьому файлі мейлер може мати власну унікальну конфігурацію й навіть власний унікальний «транспорт», що дозволяє застосунку надсилати різні повідомлення через різні поштові сервіси. Наприклад, ваш застосунок може надсилати транзакційні листи через Postmark, а масові розсилки - через Amazon SES.

У файлі конфігурації `mail` ви знайдете масив конфігурації `mailers`. Цей масив містить зразок конфігурації для кожного з основних поштових драйверів / транспортів, які підтримує Laravel, а значення конфігурації `default` визначає, який мейлер використовуватиметься за замовчуванням, коли вашому застосунку треба надіслати листа.

<a name="driver-prerequisites"></a>
### Передумови драйверів / транспортів

Драйвери на основі API - Mailgun, Postmark і Resend - часто простіші та швидші за надсилання пошти через SMTP-сервери. Коли це можливо, ми радимо користуватися одним із них.

<a name="cloudflare-driver"></a>
#### Драйвер Cloudflare

Щоб скористатися драйвером Cloudflare, встановіть HTTP-клієнт Symfony через Composer:

```shell
composer require symfony/http-client
```

Далі вам потрібно внести дві зміни у файл конфігурації `config/mail.php` вашого застосунку. Спершу задайте мейлером за замовчуванням `cloudflare`:

```php
'default' => env('MAIL_MAILER', 'cloudflare'),
```

По-друге, додайте такий масив конфігурації до вашого масиву `mailers`:

```php
'cloudflare' => [
    'transport' => 'cloudflare',
],
```

Налаштувавши мейлер за замовчуванням, додайте такі опції до файлу конфігурації `config/services.php`:

```php
'cloudflare' => [
    'account_id' => env('CLOUDFLARE_ACCOUNT_ID'),
    'key' => env('CLOUDFLARE_KEY'),
],
```

<a name="mailgun-driver"></a>
#### Драйвер Mailgun

Щоб скористатися драйвером Mailgun, встановіть транспорт Mailgun Mailer від Symfony через Composer:

```shell
composer require symfony/mailgun-mailer symfony/http-client
```

Далі вам потрібно внести дві зміни у файл конфігурації `config/mail.php` вашого застосунку. Спершу задайте мейлером за замовчуванням `mailgun`:

```php
'default' => env('MAIL_MAILER', 'mailgun'),
```

По-друге, додайте такий масив конфігурації до вашого масиву `mailers`:

```php
'mailgun' => [
    'transport' => 'mailgun',
    // 'client' => [
    //     'timeout' => 5,
    // ],
],
```

Налаштувавши мейлер за замовчуванням, додайте такі опції до файлу конфігурації `config/services.php`:

```php
'mailgun' => [
    'domain' => env('MAILGUN_DOMAIN'),
    'secret' => env('MAILGUN_SECRET'),
    'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
    'scheme' => 'https',
],
```

Якщо ви не користуєтеся [регіоном Mailgun](https://documentation.mailgun.com/docs/mailgun/api-reference/api-overview#mailgun-regions) для США, вкажіть точку свого регіону у файлі конфігурації `services`:

```php
'mailgun' => [
    'domain' => env('MAILGUN_DOMAIN'),
    'secret' => env('MAILGUN_SECRET'),
    'endpoint' => env('MAILGUN_ENDPOINT', 'api.eu.mailgun.net'),
    'scheme' => 'https',
],
```

<a name="postmark-driver"></a>
#### Драйвер Postmark

Щоб скористатися драйвером [Postmark](https://postmarkapp.com/), встановіть транспорт Postmark Mailer від Symfony через Composer:

```shell
composer require symfony/postmark-mailer symfony/http-client
```

Далі задайте опції `default` у файлі `config/mail.php` вашого застосунку значення `postmark`. Налаштувавши мейлер за замовчуванням, переконайтеся, що файл конфігурації `config/services.php` містить такі опції:

```php
'postmark' => [
    'key' => env('POSTMARK_API_KEY'),
],
```

Якщо ви хочете вказати потік повідомлень Postmark, який має використовувати конкретний мейлер, додайте до його масиву конфігурації опцію `message_stream_id`. Цей масив конфігурації лежить у файлі `config/mail.php` вашого застосунку:

```php
'postmark' => [
    'transport' => 'postmark',
    'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'),
    // 'client' => [
    //     'timeout' => 5,
    // ],
],
```

Так ви можете налаштувати й кілька мейлерів Postmark з різними потоками повідомлень.

<a name="resend-driver"></a>
#### Драйвер Resend

Щоб скористатися драйвером [Resend](https://resend.com/), встановіть PHP SDK від Resend через Composer:

```shell
composer require resend/resend-php
```

Далі задайте опції `default` у файлі `config/mail.php` вашого застосунку значення `resend`. Налаштувавши мейлер за замовчуванням, переконайтеся, що файл конфігурації `config/services.php` містить такі опції:

```php
'resend' => [
    'key' => env('RESEND_API_KEY'),
],
```

<a name="ses-driver"></a>
#### Драйвер SES

Щоб скористатися драйвером Amazon SES, спершу встановіть Amazon AWS SDK для PHP. Цю бібліотеку можна встановити через менеджер пакетів Composer:

```shell
composer require aws/aws-sdk-php
```

Далі задайте опції `default` у файлі `config/mail.php` значення `ses` і переконайтеся, що файл конфігурації `config/services.php` містить такі опції:

```php
'ses' => [
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
```

Щоб скористатися [тимчасовими обліковими даними](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html) AWS через токен сесії, додайте ключ `token` до конфігурації SES вашого застосунку:

```php
'ses' => [
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    'token' => env('AWS_SESSION_TOKEN'),
],
```

Щоб працювати з [можливостями керування підписками](https://docs.aws.amazon.com/ses/latest/dg/sending-email-subscription-management.html) SES, поверніть заголовок `X-Ses-List-Management-Options` у масиві, який повертає метод [headers](#headers) поштового повідомлення:

```php
/**
 * Get the message headers.
 */
public function headers(): Headers
{
    return new Headers(
        text: [
            'X-Ses-List-Management-Options' => 'contactListName=MyContactList;topicName=MyTopic',
        ],
    );
}
```

Щоб надіслати лист через SES [tenant](https://docs.aws.amazon.com/ses/latest/dg/tenants.html), поверніть заголовок `X-Ses-Tenant-Name` з методу `headers`. Laravel передасть значення заголовка як опцію `TenantName` до SES під час надсилання повідомлення:

```php
public function headers(): Headers
{
    return new Headers(
        text: [
            'X-Ses-Tenant-Name' => 'tenant-id',
        ],
    );
}
```

Якщо ви хочете описати [додаткові опції](https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-sesv2-2019-09-27.html#sendemail), які Laravel має передавати методу `SendEmail` з AWS SDK під час надсилання листа, опишіть масив `options` у своїй конфігурації `ses`:

```php
'ses' => [
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    'options' => [
        'ConfigurationSetName' => 'MyConfigurationSet',
        'EmailTags' => [
            ['Name' => 'foo', 'Value' => 'bar'],
        ],
    ],
],
```

<a name="failover-configuration"></a>
### Конфігурація failover

Інколи зовнішній сервіс, який ви налаштували для надсилання пошти, може бути недоступним. У таких випадках корисно описати одну чи кілька резервних конфігурацій доставки, які використовуватимуться, якщо основний драйвер лежить.

Для цього опишіть у файлі конфігурації `mail` мейлер, який використовує транспорт `failover`. Масив конфігурації мейлера `failover` має містити масив `mailers` із порядком, у якому слід обирати налаштовані мейлери для доставки:

```php
'mailers' => [
    'failover' => [
        'transport' => 'failover',
        'mailers' => [
            'postmark',
            'mailgun',
            'sendmail',
        ],
        'retry_after' => 60,
    ],

    // ...
],
```

Щойно ви налаштували мейлер із транспортом `failover`, вам потрібно зробити його мейлером за замовчуванням у файлі `.env` вашого застосунку, щоб скористатися цією можливістю:

```ini
MAIL_MAILER=failover
```

<a name="round-robin-configuration"></a>
### Конфігурація round robin

Транспорт `roundrobin` дозволяє розподілити навантаження з надсилання пошти між кількома мейлерами. Для початку опишіть у файлі конфігурації `mail` мейлер, який використовує транспорт `roundrobin`. Масив конфігурації мейлера `roundrobin` має містити масив `mailers` із переліком налаштованих мейлерів, які слід використовувати для доставки:

```php
'mailers' => [
    'roundrobin' => [
        'transport' => 'roundrobin',
        'mailers' => [
            'ses',
            'postmark',
        ],
        'retry_after' => 60,
    ],

    // ...
],
```

Щойно ваш мейлер round robin описано, зробіть його мейлером за замовчуванням, вказавши його ім'я значенням ключа конфігурації `default` у файлі `mail` вашого застосунку:

```php
'default' => env('MAIL_MAILER', 'roundrobin'),
```

Транспорт round robin обирає випадковий мейлер зі списку налаштованих, а для кожного наступного листа перемикається на наступний доступний. На відміну від транспорту `failover`, який допомагає досягти *[високої доступності](https://en.wikipedia.org/wiki/High_availability)*, транспорт `roundrobin` забезпечує *[балансування навантаження](https://en.wikipedia.org/wiki/Load_balancing_(computing))*.

<a name="generating-mailables"></a>
## Генерація mailable-класів

Створюючи застосунки Laravel, кожен тип листа, який надсилає ваш застосунок, представляють класом «mailable». Ці класи зберігаються в каталозі `app/Mail`. Не переймайтеся, якщо цього каталогу у вашому застосунку немає: його буде згенеровано, коли ви створите свій перший mailable-клас командою Artisan `make:mail`:

```shell
php artisan make:mail OrderShipped
```

<a name="writing-mailables"></a>
## Написання mailable-класів

Щойно ви згенерували mailable-клас, відкрийте його, щоб дослідити вміст. Конфігурація mailable-класу відбувається в кількох методах: `envelope`, `content` та `attachments`.

Метод `envelope` повертає об'єкт `Illuminate\Mail\Mailables\Envelope`, який описує тему і, часом, отримувачів повідомлення. Метод `content` повертає об'єкт `Illuminate\Mail\Mailables\Content`, який описує [шаблон Blade](/docs/{{version}}/blade), що згенерує вміст повідомлення.

<a name="configuring-the-sender"></a>
### Налаштування відправника

<a name="using-the-envelope"></a>
#### Через конверт

Спершу розгляньмо налаштування відправника листа. Іншими словами, від кого лист. Налаштувати відправника можна двома способами. По-перше, ви можете вказати адресу «from» у конверті повідомлення:

```php
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Envelope;

/**
 * Get the message envelope.
 */
public function envelope(): Envelope
{
    return new Envelope(
        from: new Address('jeffrey@example.com', 'Jeffrey Way'),
        subject: 'Order Shipped',
    );
}
```

За бажанням ви можете вказати й адресу `replyTo`:

```php
return new Envelope(
    from: new Address('jeffrey@example.com', 'Jeffrey Way'),
    replyTo: [
        new Address('taylor@example.com', 'Taylor Otwell'),
    ],
    subject: 'Order Shipped',
);
```

<a name="using-a-global-from-address"></a>
#### Глобальна адреса `from`

Проте, якщо ваш застосунок використовує ту саму адресу «from» для всіх листів, додавати її до кожного згенерованого mailable-класу стає обтяжливо. Натомість ви можете вказати глобальну адресу «from» у файлі конфігурації `config/mail.php`. Ця адреса використовуватиметься, якщо в mailable-класі не вказано іншої адреси «from»:

```php
'from' => [
    'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
    'name' => env('MAIL_FROM_NAME', 'Example'),
],
```

Крім того, ви можете описати глобальну адресу «reply_to» у файлі конфігурації `config/mail.php`:

```php
'reply_to' => [
    'address' => 'example@example.com',
    'name' => 'App Name',
],
```

<a name="configuring-the-view"></a>
### Налаштування представлення

У методі `content` mailable-класу ви можете описати `view`, тобто шаблон, який слід використати для рендерингу вмісту листа. Оскільки кожен лист зазвичай рендерить свій вміст [шаблоном Blade](/docs/{{version}}/blade), під час створення HTML вашого листа вам доступні вся потуга й зручність шаблонізатора Blade:

```php
/**
 * Get the message content definition.
 */
public function content(): Content
{
    return new Content(
        view: 'mail.orders.shipped',
    );
}
```

> [!NOTE]
> Вам може захотітися створити каталог `resources/views/mail`, щоб зібрати там усі шаблони листів; утім, ви вільні класти їх будь-де в каталозі `resources/views`.

<a name="plain-text-emails"></a>
#### Листи у простому тексті

Якщо ви хочете описати текстову версію свого листа, вкажіть текстовий шаблон під час створення опису `Content`. Як і параметр `view`, параметр `text` має бути назвою шаблону, який відрендерить вміст листа. Ви вільні описати і HTML-, і текстову версію свого повідомлення:

```php
/**
 * Get the message content definition.
 */
public function content(): Content
{
    return new Content(
        view: 'mail.orders.shipped',
        text: 'mail.orders.shipped-text'
    );
}
```

Задля ясності параметр `html` можна використовувати як аліас параметра `view`:

```php
return new Content(
    html: 'mail.orders.shipped',
    text: 'mail.orders.shipped-text'
);
```

<a name="view-data"></a>
### Дані представлення

<a name="via-public-properties"></a>
#### Через публічні властивості

Зазвичай вам захочеться передати до представлення дані, які можна використати під час рендерингу HTML листа. Зробити дані доступними для представлення можна двома способами. По-перше, будь-яка публічна властивість, описана у вашому mailable-класі, автоматично стане доступною представленню. Тож ви можете передати дані в конструктор mailable-класу й записати їх у публічні властивості класу:

```php
<?php

namespace App\Mail;

use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Queue\SerializesModels;

class OrderShipped extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     */
    public function __construct(
        public Order $order,
    ) {}

    /**
     * Get the message content definition.
     */
    public function content(): Content
    {
        return new Content(
            view: 'mail.orders.shipped',
        );
    }
}
```

Щойно дані записано в публічну властивість, вони автоматично стануть доступні у вашому представленні, тож ви можете звертатися до них як до будь-яких інших даних у шаблонах Blade:

```blade
<div>
    Price: {{ $order->price }}
</div>
```

<a name="via-the-with-parameter"></a>
#### Через параметр `with`:

Якщо ви хочете змінити формат даних листа перед тим, як вони потраплять до шаблону, ви можете передати дані до представлення вручну через параметр `with` опису `Content`. Зазвичай ви все одно передаєте дані в конструктор mailable-класу; проте записуйте їх у властивості `protected` чи `private`, щоб дані не потрапляли до шаблону автоматично:

```php
<?php

namespace App\Mail;

use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Queue\SerializesModels;

class OrderShipped extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     */
    public function __construct(
        protected Order $order,
    ) {}

    /**
     * Get the message content definition.
     */
    public function content(): Content
    {
        return new Content(
            view: 'mail.orders.shipped',
            with: [
                'orderName' => $this->order->name,
                'orderPrice' => $this->order->price,
            ],
        );
    }
}
```

Щойно дані передано через параметр `with`, вони автоматично стануть доступні у вашому представленні, тож ви можете звертатися до них як до будь-яких інших даних у шаблонах Blade:

```blade
<div>
    Price: {{ $orderPrice }}
</div>
```

<a name="attachments"></a>
### Вкладення

Щоб додати вкладення до листа, додайте їх до масиву, який повертає метод `attachments` повідомлення. По-перше, ви можете додати вкладення, передавши шлях до файлу методу `fromPath` класу `Attachment`:

```php
use Illuminate\Mail\Mailables\Attachment;

/**
 * Get the attachments for the message.
 *
 * @return array<int, \Illuminate\Mail\Mailables\Attachment>
 */
public function attachments(): array
{
    return [
        Attachment::fromPath('/path/to/file'),
    ];
}
```

Вкладаючи файли до повідомлення, ви можете вказати відображувану назву та / або MIME-тип вкладення методами `as` і `withMime`:

```php
/**
 * Get the attachments for the message.
 *
 * @return array<int, \Illuminate\Mail\Mailables\Attachment>
 */
public function attachments(): array
{
    return [
        Attachment::fromPath('/path/to/file')
            ->as('name.pdf')
            ->withMime('application/pdf'),
    ];
}
```

<a name="attaching-files-from-disk"></a>
#### Вкладення файлів з диска

Якщо ви зберегли файл на одному з [дисків файлової системи](/docs/{{version}}/filesystem), вкласти його до листа можна методом `fromStorage`:

```php
/**
 * Get the attachments for the message.
 *
 * @return array<int, \Illuminate\Mail\Mailables\Attachment>
 */
public function attachments(): array
{
    return [
        Attachment::fromStorage('/path/to/file'),
    ];
}
```

Звісно, ви також можете вказати назву та MIME-тип вкладення:

```php
/**
 * Get the attachments for the message.
 *
 * @return array<int, \Illuminate\Mail\Mailables\Attachment>
 */
public function attachments(): array
{
    return [
        Attachment::fromStorage('/path/to/file')
            ->as('name.pdf')
            ->withMime('application/pdf'),
    ];
}
```

Метод `fromStorageDisk` дозволяє вказати диск, відмінний від диска за замовчуванням:

```php
/**
 * Get the attachments for the message.
 *
 * @return array<int, \Illuminate\Mail\Mailables\Attachment>
 */
public function attachments(): array
{
    return [
        Attachment::fromStorageDisk('s3', '/path/to/file')
            ->as('name.pdf')
            ->withMime('application/pdf'),
    ];
}
```

<a name="raw-data-attachments"></a>
#### Вкладення сирих даних

Метод `fromData` дозволяє вкласти сирий рядок байтів як вкладення. Наприклад, ви можете скористатися цим методом, якщо згенерували PDF у пам'яті й хочете вкласти його до листа, не записуючи на диск. Метод `fromData` приймає замикання, яке повертає сирі байти даних, і назву, яку слід призначити вкладенню:

```php
/**
 * Get the attachments for the message.
 *
 * @return array<int, \Illuminate\Mail\Mailables\Attachment>
 */
public function attachments(): array
{
    return [
        Attachment::fromData(fn () => $this->pdf, 'Report.pdf')
            ->withMime('application/pdf'),
    ];
}
```

<a name="inline-attachments"></a>
### Вбудовані вкладення

Вбудовувати зображення в листи зазвичай марудно; проте Laravel надає зручний спосіб вкладати зображення до ваших листів. Щоб вбудувати зображення, скористайтеся методом `embed` змінної `$message` у шаблоні листа. Laravel автоматично робить змінну `$message` доступною в усіх ваших шаблонах листів, тож передавати її вручну не потрібно:

```blade
<body>
    Here is an image:

    <img src="{{ $message->embed($pathToImage) }}">
</body>
```

> [!WARNING]
> Змінна `$message` недоступна в шаблонах повідомлень у простому тексті, адже такі повідомлення не використовують вбудованих вкладень.

<a name="embedding-raw-data-attachments"></a>
#### Вбудовування вкладень із сирих даних

Якщо ви вже маєте рядок сирих даних зображення, який хочете вбудувати в шаблон листа, викличте метод `embedData` змінної `$message`. Викликаючи метод `embedData`, вам потрібно передати ім'я файлу, яке слід призначити вбудованому зображенню:

```blade
<body>
    Here is an image from raw data:

    <img src="{{ $message->embedData($data, 'example-image.jpg') }}">
</body>
```

<a name="attachable-objects"></a>
### Об'єкти, придатні для вкладення

Хоч вкладати файли до повідомлень простими рядковими шляхами часто й достатньо, у багатьох випадках сутності, які ви вкладаєте, представлені у вашому застосунку класами. Наприклад, якщо ваш застосунок вкладає до повідомлення фото, у ньому може бути й модель `Photo`, яка це фото представляє. Хіба не було б зручно просто передати модель `Photo` методу `attach`? Об'єкти, придатні для вкладення, дозволяють саме це.

Для початку реалізуйте інтерфейс `Illuminate\Contracts\Mail\Attachable` в об'єкті, який можна буде вкладати до повідомлень. Цей інтерфейс вимагає, щоб ваш клас описав метод `toMailAttachment`, який повертає екземпляр `Illuminate\Mail\Attachment`:

```php
<?php

namespace App\Models;

use Illuminate\Contracts\Mail\Attachable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Mail\Attachment;

class Photo extends Model implements Attachable
{
    /**
     * Get the attachable representation of the model.
     */
    public function toMailAttachment(): Attachment
    {
        return Attachment::fromPath('/path/to/file');
    }
}
```

Щойно ви описали свій об'єкт для вкладення, ви можете повертати його екземпляр із методу `attachments` під час побудови листа:

```php
/**
 * Get the attachments for the message.
 *
 * @return array<int, \Illuminate\Mail\Mailables\Attachment>
 */
public function attachments(): array
{
    return [$this->photo];
}
```

Звісно, дані вкладення можуть зберігатися у віддаленому файловому сховищі на кшталт Amazon S3. Тож Laravel також дозволяє генерувати екземпляри вкладень із даних, збережених на одному з [дисків файлової системи](/docs/{{version}}/filesystem) вашого застосунку:

```php
// Create an attachment from a file on your default disk...
return Attachment::fromStorage($this->path);

// Create an attachment from a file on a specific disk...
return Attachment::fromStorageDisk('backblaze', $this->path);
```

Крім того, ви можете створювати екземпляри вкладень із даних, які маєте в пам'яті. Для цього передайте замикання методу `fromData`. Замикання має повернути сирі дані, які представляють вкладення:

```php
return Attachment::fromData(fn () => $this->content, 'Photo Name');
```

Laravel також надає додаткові методи для налаштування ваших вкладень. Наприклад, методами `as` і `withMime` ви можете змінити ім'я файлу та MIME-тип:

```php
return Attachment::fromPath('/path/to/file')
    ->as('Photo Name')
    ->withMime('image/jpeg');
```

<a name="headers"></a>
### Заголовки

Інколи вам може знадобитися додати до вихідного повідомлення додаткові заголовки. Наприклад, вам може знадобитися задати власний `Message-Id` чи інші довільні текстові заголовки.

Для цього опишіть у своєму mailable-класі метод `headers`. Метод `headers` має повернути екземпляр `Illuminate\Mail\Mailables\Headers`. Цей клас приймає параметри `messageId`, `references` та `text`. Звісно, ви можете передати лише ті параметри, які потрібні для конкретного повідомлення:

```php
use Illuminate\Mail\Mailables\Headers;

/**
 * Get the message headers.
 */
public function headers(): Headers
{
    return new Headers(
        messageId: 'custom-message-id@example.com',
        references: ['previous-message@example.com'],
        text: [
            'X-Custom-Header' => 'Custom Value',
        ],
    );
}
```

<a name="tags-and-metadata"></a>
### Теги й метадані

Деякі сторонні поштові провайдери на кшталт Mailgun і Postmark підтримують «теги» й «метадані» повідомлень, які дозволяють групувати та відстежувати листи, надіслані вашим застосунком. Додати теги й метадані до листа можна через опис `Envelope`:

```php
use Illuminate\Mail\Mailables\Envelope;

/**
 * Get the message envelope.
 *
 * @return \Illuminate\Mail\Mailables\Envelope
 */
public function envelope(): Envelope
{
    return new Envelope(
        subject: 'Order Shipped',
        tags: ['shipment'],
        metadata: [
            'order_id' => $this->order->id,
        ],
    );
}
```

Якщо ваш застосунок використовує драйвер Mailgun, докладніше про [теги](https://documentation.mailgun.com/docs/mailgun/user-manual/tracking-messages/#tags) та [метадані](https://documentation.mailgun.com/docs/mailgun/user-manual/sending-messages/#attaching-metadata-to-messages) читайте в документації Mailgun. Так само в документації Postmark можна знайти більше про їхню підтримку [тегів](https://postmarkapp.com/blog/tags-support-for-smtp) і [метаданих](https://postmarkapp.com/support/article/1125-custom-metadata-faq).

Якщо ваш застосунок надсилає листи через Amazon SES, скористайтеся методом `metadata`, щоб додати до повідомлення [«теги» SES](https://docs.aws.amazon.com/ses/latest/APIReference/API_MessageTag.html).

<a name="customizing-the-symfony-message"></a>
### Налаштування повідомлення Symfony

Поштові можливості Laravel побудовані на Symfony Mailer. Laravel дозволяє реєструвати власні колбеки, які буде викликано з екземпляром Symfony Message перед надсиланням повідомлення. Це дає вам змогу глибоко налаштувати повідомлення перед відправкою. Для цього опишіть параметр `using` у своєму описі `Envelope`:

```php
use Illuminate\Mail\Mailables\Envelope;
use Symfony\Component\Mime\Email;

/**
 * Get the message envelope.
 */
public function envelope(): Envelope
{
    return new Envelope(
        subject: 'Order Shipped',
        using: [
            function (Email $message) {
                // ...
            },
        ]
    );
}
```

<a name="markdown-mailables"></a>
## Markdown-листи

Markdown-листи дозволяють скористатися готовими шаблонами й компонентами [поштових сповіщень](/docs/{{version}}/notifications#mail-notifications) у ваших mailable-класах. Оскільки повідомлення пишуться в Markdown, Laravel може відрендерити для них гарні адаптивні HTML-шаблони, а заразом автоматично згенерувати текстовий відповідник.

<a name="generating-markdown-mailables"></a>
### Генерація Markdown-листів

Щоб згенерувати mailable-клас із відповідним Markdown-шаблоном, скористайтеся опцією `--markdown` команди Artisan `make:mail`:

```shell
php artisan make:mail OrderShipped --markdown=mail.orders.shipped
```

Далі, налаштовуючи опис `Content` у методі `content`, використовуйте параметр `markdown` замість `view`:

```php
use Illuminate\Mail\Mailables\Content;

/**
 * Get the message content definition.
 */
public function content(): Content
{
    return new Content(
        markdown: 'mail.orders.shipped',
        with: [
            'url' => $this->orderUrl,
        ],
    );
}
```

<a name="writing-markdown-messages"></a>
### Написання Markdown-повідомлень

Markdown-листи поєднують компоненти Blade і синтаксис Markdown, що дозволяє легко будувати поштові повідомлення, користуючись готовими UI-компонентами Laravel для листів:

```blade
<x-mail::message>
# Order Shipped

Your order has been shipped!

<x-mail::button :url="$url">
View Order
</x-mail::button>

Thanks,<br>
{{ config('app.name') }}
</x-mail::message>
```

> [!NOTE]
> Не робіть зайвих відступів, коли пишете Markdown-листи. За стандартами Markdown парсери рендерять вміст із відступами як блоки коду.

<a name="button-component"></a>
#### Компонент button

Компонент button рендерить відцентроване посилання-кнопку. Компонент приймає два аргументи: `url` та необов'язковий `color`. Підтримувані кольори - `primary`, `success` та `error`. Ви можете додати до повідомлення скільки завгодно компонентів button:

```blade
<x-mail::button :url="$url" color="success">
View Order
</x-mail::button>
```

<a name="panel-component"></a>
#### Компонент panel

Компонент panel рендерить заданий блок тексту в панелі, колір тла якої трохи відрізняється від решти повідомлення. Це дозволяє привернути увагу до певного блоку тексту:

```blade
<x-mail::panel>
This is the panel content.
</x-mail::panel>
```

<a name="table-component"></a>
#### Компонент table

Компонент table дозволяє перетворити таблицю Markdown на HTML-таблицю. Компонент приймає таблицю Markdown як свій вміст. Вирівнювання стовпців підтримується стандартним синтаксисом вирівнювання таблиць Markdown:

```blade
<x-mail::table>
| Laravel       | Table         | Example       |
| ------------- | :-----------: | ------------: |
| Col 2 is      | Centered      | $10           |
| Col 3 is      | Right-Aligned | $20           |
</x-mail::table>
```

<a name="customizing-the-components"></a>
### Налаштування компонентів

Ви можете експортувати всі поштові компоненти Markdown до власного застосунку, щоб їх налаштувати. Щоб експортувати компоненти, опублікуйте тег ресурсів `laravel-mail` командою Artisan `vendor:publish`:

```shell
php artisan vendor:publish --tag=laravel-mail
```

Ця команда опублікує поштові компоненти Markdown до каталогу `resources/views/vendor/mail`. Каталог `mail` міститиме каталоги `html` і `text`, у кожному з яких лежатиме відповідне представлення кожного доступного компонента. Ви вільні налаштовувати ці компоненти як заманеться.

<a name="customizing-the-css"></a>
#### Налаштування CSS

Після експорту компонентів каталог `resources/views/vendor/mail/html/themes` міститиме файл `default.css`. Ви можете змінити CSS у цьому файлі, і ваші стилі автоматично буде перетворено на вбудований CSS у HTML-представленнях ваших Markdown-листів.

Якщо ви хочете створити цілком нову тему для компонентів Markdown у Laravel, покладіть CSS-файл у каталог `html/themes`. Назвавши й зберігши свій CSS-файл, оновіть опцію `theme` у файлі конфігурації `config/mail.php` вашого застосунку відповідно до назви нової теми.

Щоб змінити тему для окремого mailable-класу, задайте властивості `$theme` цього класу назву теми, яку слід використати під час надсилання.

<a name="sending-mail"></a>
## Надсилання пошти

Щоб надіслати повідомлення, скористайтеся методом `to` [фасаду](/docs/{{version}}/facades) `Mail`. Метод `to` приймає адресу електронної пошти, екземпляр користувача або колекцію користувачів. Якщо ви передаєте об'єкт чи колекцію об'єктів, мейлер автоматично використає їхні властивості `email` і `name` для визначення отримувачів листа, тож переконайтеся, що ці атрибути є у ваших об'єктах. Щойно ви вказали отримувачів, передайте екземпляр свого mailable-класу методу `send`:

```php
<?php

namespace App\Http\Controllers;

use App\Mail\OrderShipped;
use App\Models\Order;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;

class OrderShipmentController extends Controller
{
    /**
     * Ship the given order.
     */
    public function store(Request $request): RedirectResponse
    {
        $order = Order::findOrFail($request->order_id);

        // Ship the order...

        Mail::to($request->user())->send(new OrderShipped($order));

        return redirect('/orders');
    }
}
```

Надсилаючи повідомлення, ви не обмежені лише отримувачами «to». Ви вільні задати отримувачів «to», «cc» та «bcc», поєднавши відповідні методи ланцюжком:

```php
Mail::to($request->user())
    ->cc($moreUsers)
    ->bcc($evenMoreUsers)
    ->send(new OrderShipped($order));
```

<a name="looping-over-recipients"></a>
#### Цикл по отримувачах

Інколи вам може знадобитися надіслати лист списку отримувачів, проходячи масив отримувачів / адрес. Проте, оскільки метод `to` додає адреси до списку отримувачів листа, кожна ітерація циклу надсилатиме ще один лист усім попереднім отримувачам. Тому завжди створюйте екземпляр листа заново для кожного отримувача:

```php
foreach (['taylor@example.com', 'dries@example.com'] as $recipient) {
    Mail::to($recipient)->send(new OrderShipped($order));
}
```

<a name="sending-mail-via-a-specific-mailer"></a>
#### Надсилання через конкретний мейлер

За замовчуванням Laravel надсилає пошту через мейлер, налаштований як `default` у файлі конфігурації `mail` вашого застосунку. Проте ви можете скористатися методом `mailer`, щоб надіслати повідомлення через конкретну конфігурацію мейлера:

```php
Mail::mailer('postmark')
    ->to($request->user())
    ->send(new OrderShipped($order));
```

<a name="queueing-mail"></a>
### Пошта в черзі

<a name="queueing-a-mail-message"></a>
#### Постановка листа в чергу

Оскільки надсилання листів може негативно вплинути на час відповіді вашого застосунку, багато розробників ставлять листи в чергу на фонове надсилання. Laravel спрощує це завдяки вбудованому [єдиному API черг](/docs/{{version}}/queues). Щоб поставити лист у чергу, скористайтеся методом `queue` фасаду `Mail` після вказання отримувачів:

```php
Mail::to($request->user())
    ->cc($moreUsers)
    ->bcc($evenMoreUsers)
    ->queue(new OrderShipped($order));
```

Цей метод автоматично подбає про те, щоб покласти завдання в чергу й надіслати повідомлення у фоні. Перш ніж користуватися цією можливістю, вам потрібно [налаштувати черги](/docs/{{version}}/queues).

<a name="delayed-message-queueing"></a>
#### Відкладена постановка листа в чергу

Якщо ви хочете відкласти доставку листа з черги, скористайтеся методом `later`. Першим аргументом метод `later` приймає екземпляр `DateTime`, який вказує, коли слід надіслати повідомлення:

```php
Mail::to($request->user())
    ->cc($moreUsers)
    ->bcc($evenMoreUsers)
    ->later(now()->plus(minutes: 10), new OrderShipped($order));
```

<a name="pushing-to-specific-queues"></a>
#### Відправка в конкретні черги

Оскільки всі mailable-класи, згенеровані командою `make:mail`, використовують трейт `Illuminate\Bus\Queueable`, ви можете викликати методи `onQueue` та `onConnection` на будь-якому екземплярі mailable-класу, щоб вказати підключення та ім'я черги для повідомлення:

```php
$message = (new OrderShipped($order))
    ->onConnection('sqs')
    ->onQueue('emails');

Mail::to($request->user())
    ->cc($moreUsers)
    ->bcc($evenMoreUsers)
    ->queue($message);
```

Як варіант, ви можете вказати підключення й чергу атрибутами `Connection` та `Queue` у mailable-класі:

```php
use Illuminate\Queue\Attributes\Connection;
use Illuminate\Queue\Attributes\Queue;

#[Connection('sqs')]
#[Queue('emails')]
class OrderShipped extends Mailable
{
    // ...
}
```

<a name="queueing-by-default"></a>
#### Постановка в чергу за замовчуванням

Якщо у вас є mailable-класи, які ви хочете завжди ставити в чергу, реалізуйте в них контракт `ShouldQueue`. Тепер, навіть якщо ви викличете метод `send`, лист усе одно потрапить у чергу, адже клас реалізує цей контракт:

```php
use Illuminate\Contracts\Queue\ShouldQueue;

class OrderShipped extends Mailable implements ShouldQueue
{
    // ...
}
```

<a name="queued-mailables-and-database-transactions"></a>
#### Листи в черзі та транзакції бази даних

Коли листи в черзі диспетчеризуються всередині транзакцій бази даних, черга може обробити їх ще до того, як транзакцію буде зафіксовано. Коли таке трапляється, будь-які зміни, які ви внесли до моделей чи записів у базі під час транзакції, ще можуть не бути в базі. Ба більше, будь-які моделі чи записи, створені всередині транзакції, можуть у базі не існувати. Якщо ваш лист залежить від цих моделей, під час обробки завдання, яке його надсилає, можуть виникнути несподівані помилки.

Якщо опція конфігурації `after_commit` вашого підключення черги має значення `false`, ви все одно можете вказати, що конкретний лист у черзі слід диспетчеризувати після фіксації всіх відкритих транзакцій, - викличте метод `afterCommit` під час надсилання повідомлення:

```php
Mail::to($request->user())->send(
    (new OrderShipped($order))->afterCommit()
);
```

Як варіант, ви можете викликати метод `afterCommit` у конструкторі свого mailable-класу:

```php
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class OrderShipped extends Mailable implements ShouldQueue
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     */
    public function __construct()
    {
        $this->afterCommit();
    }
}
```

> [!NOTE]
> Щоб дізнатися більше про обхід цих проблем, перегляньте документацію про [завдання в черзі та транзакції бази даних](/docs/{{version}}/queues#jobs-and-database-transactions).

<a name="queued-email-failures"></a>
#### Невдачі листів у черзі

Коли лист у черзі зазнає невдачі, у mailable-класі буде викликано метод `failed`, якщо його описано. До методу `failed` буде передано екземпляр `Throwable`, який спричинив невдачу:

```php
<?php

namespace App\Mail;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Throwable;

class OrderDelayed extends Mailable implements ShouldQueue
{
    use SerializesModels;

    /**
     * Handle a queued email's failure.
     */
    public function failed(Throwable $exception): void
    {
        // ...
    }
}
```

<a name="rendering-mailables"></a>
## Рендеринг mailable-класів

Інколи вам може захотітися отримати HTML-вміст листа, не надсилаючи його. Для цього викличте метод `render` на mailable-класі. Цей метод поверне обчислений HTML-вміст листа рядком:

```php
use App\Mail\InvoicePaid;
use App\Models\Invoice;

$invoice = Invoice::find(1);

return (new InvoicePaid($invoice))->render();
```

<a name="previewing-mailables-in-the-browser"></a>
### Попередній перегляд листів у браузері

Розробляючи шаблон листа, зручно швидко переглядати відрендерений лист у браузері, як звичайний шаблон Blade. Тому Laravel дозволяє повертати будь-який mailable-клас напряму із замикання маршруту чи контролера. Коли повернуто mailable-клас, його буде відрендерено й показано в браузері, тож ви зможете швидко переглянути дизайн, не надсилаючи листа на справжню адресу:

```php
Route::get('/mailable', function () {
    $invoice = App\Models\Invoice::find(1);

    return new App\Mail\InvoicePaid($invoice);
});
```

<a name="localizing-mailables"></a>
## Локалізація листів

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

Для цього фасад `Mail` пропонує метод `locale`, щоб задати потрібну мову. Застосунок перейде в цю локаль під час обчислення шаблону листа, а після завершення повернеться до попередньої:

```php
Mail::to($request->user())->locale('es')->send(
    new OrderShipped($order)
);
```

<a name="user-preferred-locales"></a>
#### Бажані локалі користувачів

Інколи застосунки зберігають бажану локаль кожного користувача. Реалізувавши в одній чи кількох своїх моделях контракт `HasLocalePreference`, ви можете сказати Laravel використовувати цю збережену локаль під час надсилання пошти:

```php
use Illuminate\Contracts\Translation\HasLocalePreference;

class User extends Model implements HasLocalePreference
{
    /**
     * Get the user's preferred locale.
     */
    public function preferredLocale(): string
    {
        return $this->locale;
    }
}
```

Щойно ви реалізували цей інтерфейс, Laravel автоматично використовуватиме бажану локаль, надсилаючи моделі листи та сповіщення. Тому викликати метод `locale` при використанні цього інтерфейсу не потрібно:

```php
Mail::to($request->user())->send(new OrderShipped($order));
```

<a name="testing-mailables"></a>
## Тестування

<a name="testing-mailable-content"></a>
### Тестування вмісту листа

Laravel надає низку методів для огляду структури вашого листа. Крім того, Laravel надає кілька зручних методів, щоб перевірити, що ваш лист містить очікуваний вміст:

```php tab=Pest
use App\Mail\InvoicePaid;
use App\Models\User;

test('mailable content', function () {
    $user = User::factory()->create();

    $mailable = new InvoicePaid($user);

    $mailable->assertFrom('jeffrey@example.com');
    $mailable->assertTo('taylor@example.com');
    $mailable->assertHasCc('abigail@example.com');
    $mailable->assertHasBcc('victoria@example.com');
    $mailable->assertHasReplyTo('tyler@example.com');
    $mailable->assertHasSubject('Invoice Paid');
    $mailable->assertHasTag('example-tag');
    $mailable->assertHasMetadata('key', 'value');

    $mailable->assertSeeInHtml($user->email);
    $mailable->assertDontSeeInHtml('Invoice Not Paid');
    $mailable->assertSeeInOrderInHtml(['Invoice Paid', 'Thanks']);

    $mailable->assertSeeInText($user->email);
    $mailable->assertDontSeeInText('Invoice Not Paid');
    $mailable->assertSeeInOrderInText(['Invoice Paid', 'Thanks']);

    $mailable->assertHasAttachment('/path/to/file');
    $mailable->assertHasAttachment(Attachment::fromPath('/path/to/file'));
    $mailable->assertHasAttachedData($pdfData, 'name.pdf', ['mime' => 'application/pdf']);
    $mailable->assertHasAttachmentFromStorage('/path/to/file', 'name.pdf', ['mime' => 'application/pdf']);
    $mailable->assertHasAttachmentFromStorageDisk('s3', '/path/to/file', 'name.pdf', ['mime' => 'application/pdf']);
});
```

```php tab=PHPUnit
use App\Mail\InvoicePaid;
use App\Models\User;

public function test_mailable_content(): void
{
    $user = User::factory()->create();

    $mailable = new InvoicePaid($user);

    $mailable->assertFrom('jeffrey@example.com');
    $mailable->assertTo('taylor@example.com');
    $mailable->assertHasCc('abigail@example.com');
    $mailable->assertHasBcc('victoria@example.com');
    $mailable->assertHasReplyTo('tyler@example.com');
    $mailable->assertHasSubject('Invoice Paid');
    $mailable->assertHasTag('example-tag');
    $mailable->assertHasMetadata('key', 'value');

    $mailable->assertSeeInHtml($user->email);
    $mailable->assertDontSeeInHtml('Invoice Not Paid');
    $mailable->assertSeeInOrderInHtml(['Invoice Paid', 'Thanks']);

    $mailable->assertSeeInText($user->email);
    $mailable->assertDontSeeInText('Invoice Not Paid');
    $mailable->assertSeeInOrderInText(['Invoice Paid', 'Thanks']);

    $mailable->assertHasAttachment('/path/to/file');
    $mailable->assertHasAttachment(Attachment::fromPath('/path/to/file'));
    $mailable->assertHasAttachedData($pdfData, 'name.pdf', ['mime' => 'application/pdf']);
    $mailable->assertHasAttachmentFromStorage('/path/to/file', 'name.pdf', ['mime' => 'application/pdf']);
    $mailable->assertHasAttachmentFromStorageDisk('s3', '/path/to/file', 'name.pdf', ['mime' => 'application/pdf']);
}
```

Як ви й очікуєте, перевірки «HTML» стверджують, що HTML-версія вашого листа містить заданий рядок, а перевірки «text» - що заданий рядок містить текстова версія.

<a name="testing-mailable-sending"></a>
### Тестування надсилання листа

Радимо тестувати вміст ваших листів окремо від тестів, які перевіряють, що конкретний лист було «надіслано» конкретному користувачеві. Зазвичай вміст листів не стосується коду, який ви тестуєте, і достатньо просто перевірити, що Laravel отримав вказівку надіслати заданий лист.

Метод `fake` фасаду `Mail` дозволяє завадити надсиланню пошти. Після виклику методу `fake` фасаду `Mail` ви можете перевіряти, що листи мали бути надіслані користувачам, і навіть оглядати дані, які вони отримали:

```php tab=Pest
<?php

use App\Mail\OrderShipped;
use Illuminate\Support\Facades\Mail;

test('orders can be shipped', function () {
    Mail::fake();

    // Perform order shipping...

    // Assert that no mailables were sent...
    Mail::assertNothingSent();

    // Assert that a mailable was sent...
    Mail::assertSent(OrderShipped::class);

    // Assert a mailable was sent twice...
    Mail::assertSent(OrderShipped::class, 2);

    // Assert a mailable was sent to an email address...
    Mail::assertSent(OrderShipped::class, 'example@laravel.com');

    // Assert a mailable was sent to multiple email addresses...
    Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']);

    // Assert a mailable was not sent...
    Mail::assertNotSent(AnotherMailable::class);

    // Assert a mailable was sent twice...
    Mail::assertSentTimes(OrderShipped::class, 2);

    // Assert that a mailable was sent exactly once...
    Mail::assertSentOnce(OrderShipped::class);

    // Assert 3 total mailables were sent...
    Mail::assertSentCount(3);
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use App\Mail\OrderShipped;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_orders_can_be_shipped(): void
    {
        Mail::fake();

        // Perform order shipping...

        // Assert that no mailables were sent...
        Mail::assertNothingSent();

        // Assert that a mailable was sent...
        Mail::assertSent(OrderShipped::class);

        // Assert a mailable was sent twice...
        Mail::assertSent(OrderShipped::class, 2);

        // Assert a mailable was sent to an email address...
        Mail::assertSent(OrderShipped::class, 'example@laravel.com');

        // Assert a mailable was sent to multiple email addresses...
        Mail::assertSent(OrderShipped::class, ['example@laravel.com', '...']);

        // Assert a mailable was not sent...
        Mail::assertNotSent(AnotherMailable::class);

        // Assert a mailable was sent twice...
        Mail::assertSentTimes(OrderShipped::class, 2);

        // Assert that a mailable was sent exactly once...
        Mail::assertSentOnce(OrderShipped::class);

        // Assert 3 total mailables were sent...
        Mail::assertSentCount(3);
    }
}
```

Якщо ви ставите листи в чергу на фонову доставку, користуйтеся методом `assertQueued` замість `assertSent`:

```php
Mail::assertQueued(OrderShipped::class);
Mail::assertQueuedOnce(OrderShipped::class);
Mail::assertNotQueued(OrderShipped::class);
Mail::assertNothingQueued();
Mail::assertQueuedCount(3);
```

Ви також можете перевірити загальну кількість надісланих або поставлених у чергу листів методом `assertOutgoingCount`:

```php
Mail::assertOutgoingCount(3);
```

Ви можете передати замикання методам `assertSent`, `assertNotSent`, `assertQueued` чи `assertNotQueued`, щоб перевірити, що було надіслано лист, який проходить заданий тест. Якщо надіслано щонайменше один лист, який проходить цей тест, перевірка буде успішною:

```php
Mail::assertSent(function (OrderShipped $mail) use ($order) {
    return $mail->order->id === $order->id;
});
```

Коли ви викликаєте методи перевірок фасаду `Mail`, екземпляр листа, який приймає передане замикання, надає корисні методи для його огляду:

```php
Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) use ($user) {
    return $mail->hasTo($user->email) &&
           $mail->hasCc('...') &&
           $mail->hasBcc('...') &&
           $mail->hasReplyTo('...') &&
           $mail->hasFrom('...') &&
           $mail->hasSubject('...') &&
           $mail->hasMetadata('order_id', $mail->order->id) &&
           $mail->usesMailer('ses');
});
```

Екземпляр листа також містить кілька корисних методів для огляду вкладень:

```php
use Illuminate\Mail\Mailables\Attachment;

Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) {
    return $mail->hasAttachment(
        Attachment::fromPath('/path/to/file')
            ->as('name.pdf')
            ->withMime('application/pdf')
    );
});

Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) {
    return $mail->hasAttachment(
        Attachment::fromStorageDisk('s3', '/path/to/file')
    );
});

Mail::assertSent(OrderShipped::class, function (OrderShipped $mail) use ($pdfData) {
    return $mail->hasAttachment(
        Attachment::fromData(fn () => $pdfData, 'name.pdf')
    );
});
```

Ви могли помітити, що є два методи для перевірки, що пошту не надіслано: `assertNotSent` та `assertNotQueued`. Інколи вам може захотітися перевірити, що пошту не було ані надіслано, **ані** поставлено в чергу. Для цього скористайтеся методами `assertNothingOutgoing` та `assertNotOutgoing`:

```php
Mail::assertNothingOutgoing();

Mail::assertNotOutgoing(function (OrderShipped $mail) use ($order) {
    return $mail->order->id === $order->id;
});
```

<a name="mail-and-local-development"></a>
## Пошта й локальна розробка

Розробляючи застосунок, який надсилає пошту, ви, найімовірніше, не хочете справді надсилати листи на живі адреси. Laravel надає кілька способів «вимкнути» реальне надсилання листів під час локальної розробки.

<a name="log-driver"></a>
#### Драйвер log

Замість надсилати листи, поштовий драйвер `log` записуватиме всі повідомлення до ваших файлів логу для огляду. Зазвичай цей драйвер використовують лише під час локальної розробки. Докладніше про налаштування застосунку залежно від середовища читайте в [документації з конфігурації](/docs/{{version}}/configuration#environment-configuration).

<a name="mailtrap"></a>
#### HELO / Mailtrap / Mailpit

Як варіант, ви можете скористатися сервісом на кшталт [HELO](https://usehelo.com) чи [Mailtrap](https://mailtrap.io) і драйвером `smtp`, щоб надсилати листи до «фіктивної» поштової скриньки, де ви зможете переглянути їх у справжньому поштовому клієнті. Перевага цього підходу в тому, що ви можете справді оглянути фінальні листи в переглядачі повідомлень Mailtrap.

Якщо ви користуєтеся [Laravel Sail](/docs/{{version}}/sail), переглядати повідомлення можна через [Mailpit](https://github.com/axllent/mailpit). Коли Sail запущено, інтерфейс Mailpit доступний за адресою: `http://localhost:8025`.

<a name="using-a-global-to-address"></a>
#### Глобальна адреса `to`

Нарешті, ви можете вказати глобальну адресу «to», викликавши метод `alwaysTo` фасаду `Mail`. Зазвичай цей метод викликають у методі `boot` одного із сервіс-провайдерів вашого застосунку:

```php
use Illuminate\Support\Facades\Mail;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    if ($this->app->environment('local')) {
        Mail::alwaysTo('taylor@example.com');
    }
}
```

Коли ви користуєтеся методом `alwaysTo`, будь-які додаткові адреси «cc» чи «bcc» у листах буде вилучено.

<a name="events"></a>
## Події

Надсилаючи листи, Laravel диспетчеризує дві події. Подія `MessageSending` диспетчеризується перед надсиланням повідомлення, а подія `MessageSent` - після. Пам'ятайте: ці події диспетчеризуються, коли пошту *надсилають*, а не коли її ставлять у чергу. Ви можете створити [слухачів подій](/docs/{{version}}/events) для цих подій у своєму застосунку:

```php
use Illuminate\Mail\Events\MessageSending;
// use Illuminate\Mail\Events\MessageSent;

class LogMessage
{
    /**
     * Handle the event.
     */
    public function handle(MessageSending $event): void
    {
        // ...
    }
}
```

<a name="custom-transports"></a>
## Власні транспорти

Laravel містить різноманітні поштові транспорти; проте вам може захотітися написати власний, щоб доставляти пошту через сервіси, які Laravel не підтримує «з коробки». Для початку опишіть клас, який розширює `Symfony\Component\Mailer\Transport\AbstractTransport`. Далі реалізуйте у своєму транспорті методи `doSend` та `__toString`:

```php
<?php

namespace App\Mail;

use MailchimpTransactional\ApiClient;
use Symfony\Component\Mailer\SentMessage;
use Symfony\Component\Mailer\Transport\AbstractTransport;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\MessageConverter;

class MailchimpTransport extends AbstractTransport
{
    /**
     * Create a new Mailchimp transport instance.
     */
    public function __construct(
        protected ApiClient $client,
    ) {
        parent::__construct();
    }

    /**
     * {@inheritDoc}
     */
    protected function doSend(SentMessage $message): void
    {
        $email = MessageConverter::toEmail($message->getOriginalMessage());

        $this->client->messages->send(['message' => [
            'from_email' => $email->getFrom(),
            'to' => collect($email->getTo())->map(function (Address $email) {
                return ['email' => $email->getAddress(), 'type' => 'to'];
            })->all(),
            'subject' => $email->getSubject(),
            'text' => $email->getTextBody(),
        ]]);
    }

    /**
     * Get the string representation of the transport.
     */
    public function __toString(): string
    {
        return 'mailchimp';
    }
}
```

Щойно ви описали власний транспорт, зареєструйте його методом `extend` фасаду `Mail`. Зазвичай це роблять у методі `boot` вашого `AppServiceProvider`. До замикання, переданого методу `extend`, буде передано аргумент `$config`. Він міститиме масив конфігурації, описаний для мейлера у файлі `config/mail.php` вашого застосунку:

```php
use App\Mail\MailchimpTransport;
use Illuminate\Support\Facades\Mail;
use MailchimpTransactional\ApiClient;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Mail::extend('mailchimp', function (array $config = []) {
        $client = new ApiClient;

        $client->setApiKey($config['key']);

        return new MailchimpTransport($client);
    });
}
```

Щойно ваш власний транспорт описано й зареєстровано, ви можете створити у файлі `config/mail.php` опис мейлера, який використовує новий транспорт:

```php
'mailchimp' => [
    'transport' => 'mailchimp',
    'key' => env('MAILCHIMP_API_KEY'),
    // ...
],
```

<a name="additional-symfony-transports"></a>
### Додаткові транспорти Symfony

Laravel підтримує деякі наявні поштові транспорти, які підтримує Symfony, - як-от Mailgun і Postmark. Проте вам може захотітися розширити Laravel підтримкою інших транспортів від Symfony. Це робиться підключенням потрібного мейлера Symfony через Composer і реєстрацією транспорту в Laravel. Наприклад, ви можете встановити й зареєструвати мейлер Symfony «Brevo» (раніше «Sendinblue»):

```shell
composer require symfony/brevo-mailer symfony/http-client
```

Щойно пакет мейлера Brevo встановлено, додайте запис із обліковими даними API Brevo до файлу конфігурації `services` вашого застосунку:

```php
'brevo' => [
    'key' => env('BREVO_API_KEY'),
],
```

Далі скористайтеся методом `extend` фасаду `Mail`, щоб зареєструвати транспорт у Laravel. Зазвичай це роблять у методі `boot` сервіс-провайдера:

```php
use Illuminate\Support\Facades\Mail;
use Symfony\Component\Mailer\Bridge\Brevo\Transport\BrevoTransportFactory;
use Symfony\Component\Mailer\Transport\Dsn;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Mail::extend('brevo', function () {
        return (new BrevoTransportFactory)->create(
            new Dsn(
                'brevo+api',
                'default',
                config('services.brevo.key')
            )
        );
    });
}
```

Щойно ваш транспорт зареєстровано, ви можете створити у файлі `config/mail.php` опис мейлера, який використовує новий транспорт:

```php
'brevo' => [
    'transport' => 'brevo',
    // ...
],
```

---

Джерело: https://laravelukraine.com/docs/13.x/notifications

# Сповіщення

- [Вступ](#introduction)
- [Генерація сповіщень](#generating-notifications)
- [Надсилання сповіщень](#sending-notifications)
    - [Через трейт Notifiable](#using-the-notifiable-trait)
    - [Через фасад Notification](#using-the-notification-facade)
    - [Задання каналів доставки](#specifying-delivery-channels)
    - [Сповіщення в черзі](#queueing-notifications)
    - [Сповіщення на льоту](#on-demand-notifications)
- [Поштові сповіщення](#mail-notifications)
    - [Форматування поштових повідомлень](#formatting-mail-messages)
    - [Налаштування відправника](#customizing-the-sender)
    - [Налаштування отримувача](#customizing-the-recipient)
    - [Налаштування теми](#customizing-the-subject)
    - [Налаштування мейлера](#customizing-the-mailer)
    - [Налаштування шаблонів](#customizing-the-templates)
    - [Вкладення](#mail-attachments)
    - [Додавання тегів і метаданих](#adding-tags-metadata)
    - [Налаштування повідомлення Symfony](#customizing-the-symfony-message)
    - [Використання mailable-класів](#using-mailables)
    - [Попередній перегляд поштових сповіщень](#previewing-mail-notifications)
- [Поштові сповіщення в Markdown](#markdown-mail-notifications)
    - [Генерація повідомлення](#generating-the-message)
    - [Написання повідомлення](#writing-the-message)
    - [Налаштування компонентів](#customizing-the-components)
- [Сповіщення в базі даних](#database-notifications)
    - [Передумови](#database-prerequisites)
    - [Форматування сповіщень у базі даних](#formatting-database-notifications)
    - [Доступ до сповіщень](#accessing-the-notifications)
    - [Позначення сповіщень прочитаними](#marking-notifications-as-read)
- [Сповіщення через бродкастинг](#broadcast-notifications)
    - [Передумови](#broadcast-prerequisites)
    - [Форматування сповіщень бродкастингу](#formatting-broadcast-notifications)
    - [Прослуховування сповіщень](#listening-for-notifications)
- [SMS-сповіщення](#sms-notifications)
    - [Передумови](#sms-prerequisites)
    - [Форматування SMS-сповіщень](#formatting-sms-notifications)
    - [Налаштування номера «From»](#customizing-the-from-number)
    - [Додавання клієнтського посилання](#adding-a-client-reference)
    - [Маршрутизація SMS-сповіщень](#routing-sms-notifications)
- [Сповіщення в Slack](#slack-notifications)
    - [Передумови](#slack-prerequisites)
    - [Форматування сповіщень Slack](#formatting-slack-notifications)
    - [Інтерактивність Slack](#slack-interactivity)
    - [Маршрутизація сповіщень Slack](#routing-slack-notifications)
    - [Сповіщення зовнішніх робочих просторів Slack](#notifying-external-slack-workspaces)
- [Локалізація сповіщень](#localizing-notifications)
- [Тестування](#testing)
- [Події сповіщень](#notification-events)
- [Власні канали](#custom-channels)

<a name="introduction"></a>
## Вступ

Окрім підтримки [надсилання пошти](/docs/{{version}}/mail), Laravel підтримує надсилання сповіщень різними каналами доставки: електронною поштою, SMS (через [Vonage](https://www.vonage.com/communications-apis/), раніше відомий як Nexmo) та [Slack](https://slack.com). Крім того, спільнота створила чимало [каналів сповіщень](https://laravel-notification-channels.com/about/#suggesting-a-new-channel), які дозволяють надсилати сповіщення десятками різних каналів! Сповіщення можна також зберігати в базі даних, щоб показувати їх у вебінтерфейсі.

Зазвичай сповіщення мають бути короткими інформаційними повідомленнями, які розповідають користувачам про щось, що сталося у вашому застосунку. Наприклад, якщо ви пишете застосунок для виставлення рахунків, ви можете надсилати користувачам сповіщення «Рахунок оплачено» каналами пошти та SMS.

<a name="generating-notifications"></a>
## Генерація сповіщень

У Laravel кожне сповіщення представлено окремим класом, який зазвичай зберігається в каталозі `app/Notifications`. Не переймайтеся, якщо цього каталогу у вашому застосунку немає - його буде створено, коли ви виконаєте команду Artisan `make:notification`:

```shell
php artisan make:notification InvoicePaid
```

Ця команда покладе свіжий клас сповіщення до каталогу `app/Notifications`. Кожен клас сповіщення містить метод `via` і змінну кількість методів побудови повідомлення - на кшталт `toMail` чи `toDatabase`, - які перетворюють сповіщення на повідомлення, придатне для конкретного каналу.

<a name="sending-notifications"></a>
## Надсилання сповіщень

<a name="using-the-notifiable-trait"></a>
### Через трейт Notifiable

Сповіщення можна надсилати двома способами: методом `notify` трейта `Notifiable` або через [фасад](/docs/{{version}}/facades) `Notification`. Трейт `Notifiable` за замовчуванням підключено до моделі `App\Models\User` вашого застосунку:

```php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;
}
```

Метод `notify`, який надає цей трейт, очікує екземпляр сповіщення:

```php
use App\Notifications\InvoicePaid;

$user->notify(new InvoicePaid($invoice));
```

> [!NOTE]
> Пам'ятайте: ви можете використовувати трейт `Notifiable` у будь-якій зі своїх моделей. Ви не обмежені лише моделлю `User`.

<a name="using-the-notification-facade"></a>
### Через фасад Notification

Як варіант, ви можете надсилати сповіщення через [фасад](/docs/{{version}}/facades) `Notification`. Цей підхід корисний, коли треба надіслати сповіщення кільком сутностям - наприклад, колекції користувачів. Щоб надіслати сповіщення через фасад, передайте всі сутності та екземпляр сповіщення методу `send`:

```php
use Illuminate\Support\Facades\Notification;

Notification::send($users, new InvoicePaid($invoice));
```

Ви також можете надіслати сповіщення негайно методом `sendNow`. Цей метод надішле сповіщення одразу, навіть якщо воно реалізує інтерфейс `ShouldQueue`:

```php
Notification::sendNow($developers, new DeploymentCompleted($deployment));
```

<a name="specifying-delivery-channels"></a>
### Задання каналів доставки

Кожен клас сповіщення має метод `via`, який визначає, якими каналами буде доставлено сповіщення. Сповіщення можна надсилати каналами `mail`, `database`, `broadcast`, `vonage` та `slack`.

> [!NOTE]
> Якщо ви хочете скористатися іншими каналами доставки - наприклад, Telegram чи Pusher, - погляньте на [сайт Laravel Notification Channels](http://laravel-notification-channels.com), який веде спільнота.

Метод `via` отримує екземпляр `$notifiable` - екземпляр класу, якому надсилається сповіщення. Ви можете скористатися `$notifiable`, щоб визначити, якими каналами слід доставити сповіщення:

```php
/**
 * Get the notification's delivery channels.
 *
 * @return array<int, string>
 */
public function via(object $notifiable): array
{
    return $notifiable->prefers_sms ? ['vonage'] : ['mail', 'database'];
}
```

<a name="queueing-notifications"></a>
### Сповіщення в черзі

> [!WARNING]
> Перш ніж ставити сповіщення в чергу, налаштуйте чергу й [запустіть воркер](/docs/{{version}}/queues#running-the-queue-worker).

Надсилання сповіщень може забирати час, особливо якщо каналу треба зробити зовнішній виклик API. Щоб пришвидшити час відповіді вашого застосунку, дозвольте ставити сповіщення в чергу, додавши до класу інтерфейс `ShouldQueue` і трейт `Queueable`. Інтерфейс і трейт уже імпортовано в усі сповіщення, згенеровані командою `make:notification`, тож ви можете одразу додати їх до свого класу:

```php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

class InvoicePaid extends Notification implements ShouldQueue
{
    use Queueable;

    // ...
}
```

Щойно до сповіщення додано інтерфейс `ShouldQueue`, ви можете надсилати його як зазвичай. Laravel помітить інтерфейс `ShouldQueue` у класі й автоматично поставить доставку сповіщення в чергу:

```php
$user->notify(new InvoicePaid($invoice));
```

Коли сповіщення ставлять у чергу, для кожної комбінації отримувача й каналу створюється окреме завдання. Наприклад, якщо ваше сповіщення має трьох отримувачів і два канали, у чергу потрапить шість завдань.

<a name="delaying-notifications"></a>
#### Затримка сповіщень

Якщо ви хочете відкласти доставку сповіщення, додайте ланцюжком метод `delay` під час його створення:

```php
$delay = now()->plus(minutes: 10);

$user->notify((new InvoicePaid($invoice))->delay($delay));
```

Ви можете передати методу `delay` масив, щоб задати затримку для конкретних каналів:

```php
$user->notify((new InvoicePaid($invoice))->delay([
    'mail' => now()->plus(minutes: 5),
    'sms' => now()->plus(minutes: 10),
]));
```

Як варіант, ви можете описати метод `withDelay` у самому класі сповіщення. Метод `withDelay` має повернути масив назв каналів і значень затримки:

```php
/**
 * Determine the notification's delivery delay.
 *
 * @return array<string, \Illuminate\Support\Carbon>
 */
public function withDelay(object $notifiable): array
{
    return [
        'mail' => now()->plus(minutes: 5),
        'sms' => now()->plus(minutes: 10),
    ];
}
```

<a name="customizing-the-notification-queue-connection"></a>
#### Налаштування підключення черги сповіщень

За замовчуванням сповіщення потрапляють у чергу через підключення черги за замовчуванням вашого застосунку. Якщо ви хочете вказати інше підключення для конкретного сповіщення, викличте метод `onConnection` у його конструкторі:

```php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

class InvoicePaid extends Notification implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new notification instance.
     */
    public function __construct()
    {
        $this->onConnection('redis');
    }
}
```

Або ж, якщо ви хочете вказати конкретне підключення черги для кожного каналу, який підтримує сповіщення, опишіть у сповіщенні метод `viaConnections`. Цей метод має повернути масив пар «назва каналу - назва підключення черги»:

```php
/**
 * Determine which connections should be used for each notification channel.
 *
 * @return array<string, string>
 */
public function viaConnections(): array
{
    return [
        'mail' => 'redis',
        'database' => 'sync',
    ];
}
```

<a name="customizing-notification-channel-queues"></a>
#### Налаштування черг каналів сповіщень

Якщо ви хочете вказати конкретну чергу для кожного каналу, який підтримує сповіщення, опишіть у сповіщенні метод `viaQueues`. Цей метод має повернути масив пар «назва каналу - назва черги»:

```php
/**
 * Determine which queues should be used for each notification channel.
 *
 * @return array<string, string>
 */
public function viaQueues(): array
{
    return [
        'mail' => 'mail-queue',
        'slack' => 'slack-queue',
    ];
}
```

<a name="customizing-queued-notification-job-properties"></a>
#### Налаштування атрибутів завдання сповіщення в черзі

Ви можете налаштувати поведінку завдання в черзі, описавши атрибути черги у класі сповіщення. Ці атрибути успадкує завдання, яке надсилатиме сповіщення:

```php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
use Illuminate\Queue\Attributes\FailOnTimeout;
use Illuminate\Queue\Attributes\MaxExceptions;
use Illuminate\Queue\Attributes\Timeout;
use Illuminate\Queue\Attributes\Tries;

#[Tries(5)]
#[Timeout(120)]
#[MaxExceptions(3)]
#[FailOnTimeout]
class InvoicePaid extends Notification implements ShouldQueue
{
    use Queueable;

    // ...
}
```

Якщо ви хочете забезпечити приватність і цілісність даних сповіщення в черзі за допомогою [шифрування](/docs/{{version}}/encryption), додайте до класу сповіщення інтерфейс `ShouldBeEncrypted`:

```php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

class InvoicePaid extends Notification implements ShouldQueue, ShouldBeEncrypted
{
    use Queueable;

    // ...
}
```

Окрім опису цих атрибутів безпосередньо у класі сповіщення, ви можете описати методи `backoff` та `retryUntil`, щоб задати стратегію відступу й таймаут повторів для завдання сповіщення в черзі:

```php
use DateTime;

/**
 * Calculate the number of seconds to wait before retrying the notification.
 */
public function backoff(): int
{
    return 3;
}

/**
 * Determine the time at which the notification should timeout.
 */
public function retryUntil(): DateTime
{
    return now()->plus(minutes: 5);
}
```

> [!NOTE]
> Докладніше про ці атрибути й методи завдань читайте в документації про [завдання в черзі](/docs/{{version}}/queues#max-job-attempts-and-timeout).

<a name="queued-notification-middleware"></a>
#### Middleware сповіщень у черзі

Сповіщення в черзі можуть описувати middleware [так само, як завдання в черзі](/docs/{{version}}/queues#job-middleware). Для початку опишіть у класі сповіщення метод `middleware`. Метод `middleware` отримає змінні `$notifiable` та `$channel`, що дозволяє налаштувати повернений middleware залежно від призначення сповіщення:

```php
use Illuminate\Queue\Middleware\RateLimited;

/**
 * Get the middleware the notification job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(object $notifiable, string $channel)
{
    return match ($channel) {
        'mail' => [new RateLimited('postmark')],
        'slack' => [new RateLimited('slack')],
        default => [],
    };
}
```

<a name="queued-notifications-and-database-transactions"></a>
#### Сповіщення в черзі та транзакції бази даних

Коли сповіщення в черзі диспетчеризуються всередині транзакцій бази даних, черга може обробити їх ще до того, як транзакцію буде зафіксовано. Коли таке трапляється, будь-які зміни, які ви внесли до моделей чи записів у базі під час транзакції, ще можуть не бути в базі. Ба більше, будь-які моделі чи записи, створені всередині транзакції, можуть у базі не існувати. Якщо ваше сповіщення залежить від цих моделей, під час обробки завдання, яке його надсилає, можуть виникнути несподівані помилки.

Якщо опція конфігурації `after_commit` вашого підключення черги має значення `false`, ви все одно можете вказати, що конкретне сповіщення в черзі слід диспетчеризувати після фіксації всіх відкритих транзакцій, - викличте метод `afterCommit` під час надсилання сповіщення:

```php
use App\Notifications\InvoicePaid;

$user->notify((new InvoicePaid($invoice))->afterCommit());
```

Як варіант, ви можете викликати метод `afterCommit` у конструкторі свого сповіщення:

```php
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

class InvoicePaid extends Notification implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new notification instance.
     */
    public function __construct()
    {
        $this->afterCommit();
    }
}
```

> [!NOTE]
> Щоб дізнатися більше про обхід цих проблем, перегляньте документацію про [завдання в черзі та транзакції бази даних](/docs/{{version}}/queues#jobs-and-database-transactions).

<a name="determining-if-the-queued-notification-should-be-sent"></a>
#### Визначення, чи слід надсилати сповіщення з черги

Після того, як сповіщення в черзі диспетчеризовано на фонову обробку, його зазвичай підхопить воркер черги й надішле призначеному отримувачу.

Проте, якщо ви хочете ухвалити остаточне рішення про надсилання сповіщення вже під час обробки воркером, опишіть у класі сповіщення метод `shouldSend`. Якщо цей метод поверне `false`, сповіщення не буде надіслано:

```php
/**
 * Determine if the notification should be sent.
 */
public function shouldSend(object $notifiable, string $channel): bool
{
    return $this->invoice->isPaid();
}
```

<a name="after-sending-notifications"></a>
#### Після надсилання сповіщень

Якщо ви хочете виконати код після того, як сповіщення надіслано, опишіть у класі сповіщення метод `afterSending`. Цей метод отримає сутність-отримувача, назву каналу та відповідь від каналу:

```php
/**
 * Handle the notification after it has been sent.
 */
public function afterSending(object $notifiable, string $channel, mixed $response): void
{
    // ...
}
```

<a name="on-demand-notifications"></a>
### Сповіщення на льоту

Інколи вам може знадобитися надіслати сповіщення комусь, хто не зберігається як «користувач» вашого застосунку. Метод `route` фасаду `Notification` дозволяє вказати разову інформацію маршрутизації сповіщення перед надсиланням:

```php
use Illuminate\Broadcasting\Channel;
use Illuminate\Support\Facades\Notification;

Notification::route('mail', 'taylor@example.com')
    ->route('vonage', '5555555555')
    ->route('slack', '#slack-channel')
    ->route('broadcast', [new Channel('channel-name')])
    ->notify(new InvoicePaid($invoice));
```

Якщо, надсилаючи сповіщення на льоту маршрутом `mail`, ви хочете передати ім'я отримувача, передайте масив, у якому ключем першого елемента є адреса, а значенням - ім'я:

```php
Notification::route('mail', [
    'barrett@example.com' => 'Barrett Blair',
])->notify(new InvoicePaid($invoice));
```

Метод `routes` дозволяє передати разову інформацію маршрутизації одразу для кількох каналів сповіщень:

```php
Notification::routes([
    'mail' => ['barrett@example.com' => 'Barrett Blair'],
    'vonage' => '5555555555',
])->notify(new InvoicePaid($invoice));
```

<a name="mail-notifications"></a>
## Поштові сповіщення

<a name="formatting-mail-messages"></a>
### Форматування поштових повідомлень

Якщо сповіщення можна надсилати поштою, опишіть у класі сповіщення метод `toMail`. Цей метод отримає сутність `$notifiable` і має повернути екземпляр `Illuminate\Notifications\Messages\MailMessage`.

Клас `MailMessage` містить кілька простих методів, які допомагають будувати транзакційні листи. Поштові повідомлення можуть містити рядки тексту, а також «заклик до дії». Погляньмо на приклад методу `toMail`:

```php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    $url = url('/invoice/'.$this->invoice->id);

    return (new MailMessage)
        ->greeting('Hello!')
        ->line('One of your invoices has been paid!')
        ->lineIf($this->amount > 0, "Amount paid: {$this->amount}")
        ->action('View Invoice', $url)
        ->line('Thank you for using our application!');
}
```

> [!NOTE]
> Зверніть увагу: у методі `toMail` ми використовуємо `$this->invoice->id`. Ви можете передати в конструктор сповіщення будь-які дані, потрібні йому для формування повідомлення.

У цьому прикладі ми реєструємо привітання, рядок тексту, заклик до дії й ще один рядок тексту. Ці методи об'єкта `MailMessage` роблять форматування невеликих транзакційних листів простим і швидким. Далі поштовий канал перетворить складові повідомлення на гарний адаптивний HTML-шаблон із текстовим відповідником. Ось приклад листа, згенерованого каналом `mail`:

<img src="https://laravel.com/img/docs/notification-example-2.png">

> [!NOTE]
> Надсилаючи поштові сповіщення, обов'язково задайте опцію конфігурації `name` у файлі `config/app.php`. Це значення використовуватиметься в шапці та підвалі ваших поштових сповіщень.

<a name="error-messages"></a>
#### Повідомлення про помилки

Деякі сповіщення інформують користувачів про помилки - наприклад, про невдалу оплату рахунка. Ви можете вказати, що поштове повідомлення стосується помилки, викликавши метод `error` під час його побудови. Коли ви користуєтеся методом `error`, кнопка заклику до дії буде червоною, а не чорною:

```php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->error()
        ->subject('Invoice Payment Failed')
        ->line('...');
}
```

<a name="other-mail-notification-formatting-options"></a>
#### Інші варіанти форматування поштових сповіщень

Замість описувати «рядки» тексту у класі сповіщення, ви можете скористатися методом `view`, щоб указати власний шаблон для рендерингу листа сповіщення:

```php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)->view(
        'mail.invoice.paid', ['invoice' => $this->invoice]
    );
}
```

Текстове представлення поштового повідомлення можна вказати, передавши назву представлення другим елементом масиву, який передається методу `view`:

```php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)->view(
        ['mail.invoice.paid', 'mail.invoice.paid-text'],
        ['invoice' => $this->invoice]
    );
}
```

Або ж, якщо ваше повідомлення має лише текстове представлення, скористайтеся методом `text`:

```php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)->text(
        'mail.invoice.paid-text', ['invoice' => $this->invoice]
    );
}
```

<a name="customizing-the-sender"></a>
### Налаштування відправника

За замовчуванням відправника / адресу «from» листа задано у файлі конфігурації `config/mail.php`. Проте ви можете вказати адресу «from» для конкретного сповіщення методом `from`:

```php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->from('barrett@example.com', 'Barrett Blair')
        ->line('...');
}
```

<a name="customizing-the-recipient"></a>
### Налаштування отримувача

Надсилаючи сповіщення каналом `mail`, система сповіщень автоматично шукає властивість `email` у вашій сутності-отримувачі. Ви можете змінити адресу, на яку доставляється сповіщення, описавши в цій сутності метод `routeNotificationForMail`:

```php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * Route notifications for the mail channel.
     *
     * @return  array<string, string>|string
     */
    public function routeNotificationForMail(Notification $notification): array|string
    {
        // Return email address only...
        return $this->email_address;

        // Return email address and name...
        return [$this->email_address => $this->name];
    }
}
```

<a name="customizing-the-subject"></a>
### Налаштування теми

За замовчуванням темою листа є назва класу сповіщення, відформатована в «Title Case». Тож, якщо ваш клас сповіщення називається `InvoicePaid`, темою листа буде `Invoice Paid`. Якщо ви хочете вказати іншу тему, викличте метод `subject` під час побудови повідомлення:

```php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->subject('Notification Subject')
        ->line('...');
}
```

<a name="customizing-the-mailer"></a>
### Налаштування мейлера

За замовчуванням поштове сповіщення надсилається мейлером за замовчуванням, описаним у файлі `config/mail.php`. Проте ви можете вказати інший мейлер під час виконання, викликавши метод `mailer` під час побудови повідомлення:

```php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->mailer('postmark')
        ->line('...');
}
```

<a name="customizing-the-templates"></a>
### Налаштування шаблонів

Ви можете змінити HTML- і текстовий шаблони, які використовують поштові сповіщення, опублікувавши ресурси пакета сповіщень. Після виконання цієї команди шаблони поштових сповіщень опиняться в каталозі `resources/views/vendor/notifications`:

```shell
php artisan vendor:publish --tag=laravel-notifications
```

<a name="mail-attachments"></a>
### Вкладення

Щоб додати вкладення до поштового сповіщення, скористайтеся методом `attach` під час побудови повідомлення. Метод `attach` приймає першим аргументом абсолютний шлях до файлу:

```php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->greeting('Hello!')
        ->attach('/path/to/file');
}
```

> [!NOTE]
> Метод `attach` поштових повідомлень сповіщень також приймає [об'єкти, придатні для вкладення](/docs/{{version}}/mail#attachable-objects). Щоб дізнатися більше, перегляньте вичерпну [документацію про такі об'єкти](/docs/{{version}}/mail#attachable-objects).

Вкладаючи файли до повідомлення, ви можете вказати відображувану назву та / або MIME-тип, передавши `array` другим аргументом методу `attach`:

```php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->greeting('Hello!')
        ->attach('/path/to/file', [
            'as' => 'name.pdf',
            'mime' => 'application/pdf',
        ]);
}
```

За потреби до повідомлення можна вкласти кілька файлів методом `attachMany`:

```php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->greeting('Hello!')
        ->attachMany([
            '/path/to/forge.svg',
            '/path/to/vapor.svg' => [
                'as' => 'Logo.svg',
                'mime' => 'image/svg+xml',
            ],
        ]);
}
```

Метод `attachFromStorageDisk` дозволяє вкласти файл, що лежить на конкретному [диску файлової системи](/docs/{{version}}/filesystem). Цей метод приймає назву диска й шлях до файлу на ньому:

```php
use App\Mail\InvoicePaid as InvoicePaidMailable;

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): Mailable
{
    return (new InvoicePaidMailable($this->invoice))
        ->to($notifiable->email)
        ->attachFromStorageDisk('s3', '/path/to/file', 'invoice.pdf', [
            'mime' => 'application/pdf',
        ]);
}
```

<a name="raw-data-attachments"></a>
#### Вкладення сирих даних

Метод `attachData` дозволяє вкласти сирий рядок байтів як вкладення. Викликаючи метод `attachData`, передайте ім'я файлу, яке слід призначити вкладенню:

```php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->greeting('Hello!')
        ->attachData($this->pdf, 'name.pdf', [
            'mime' => 'application/pdf',
        ]);
}
```

<a name="adding-tags-metadata"></a>
### Додавання тегів і метаданих

Деякі сторонні поштові провайдери на кшталт Mailgun і Postmark підтримують «теги» й «метадані» повідомлень, які дозволяють групувати та відстежувати листи, надіслані вашим застосунком. Додати теги й метадані до листа можна методами `tag` та `metadata`:

```php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->greeting('Comment Upvoted!')
        ->tag('upvote')
        ->metadata('comment_id', $this->comment->id);
}
```

Якщо ваш застосунок використовує драйвер Mailgun, докладніше про [теги](https://documentation.mailgun.com/docs/mailgun/user-manual/tracking-messages/#tags) та [метадані](https://documentation.mailgun.com/docs/mailgun/user-manual/sending-messages/#attaching-metadata-to-messages) читайте в документації Mailgun. Так само в документації Postmark можна знайти більше про їхню підтримку [тегів](https://postmarkapp.com/blog/tags-support-for-smtp) і [метаданих](https://postmarkapp.com/support/article/1125-custom-metadata-faq).

Якщо ваш застосунок надсилає листи через Amazon SES, скористайтеся методом `metadata`, щоб додати до повідомлення [«теги» SES](https://docs.aws.amazon.com/ses/latest/APIReference/API_MessageTag.html).

<a name="customizing-the-symfony-message"></a>
### Налаштування повідомлення Symfony

Метод `withSymfonyMessage` класу `MailMessage` дозволяє зареєструвати замикання, яке буде викликано з екземпляром Symfony Message перед надсиланням повідомлення. Це дає вам змогу глибоко налаштувати повідомлення перед доставкою:

```php
use Symfony\Component\Mime\Email;

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->withSymfonyMessage(function (Email $message) {
            $message->getHeaders()->addTextHeader(
                'Custom-Header', 'Header Value'
            );
        });
}
```

<a name="using-mailables"></a>
### Використання mailable-класів

За потреби ви можете повернути з методу `toMail` свого сповіщення повноцінний [mailable-об'єкт](/docs/{{version}}/mail). Коли ви повертаєте `Mailable` замість `MailMessage`, вам потрібно вказати отримувача методом `to` mailable-об'єкта:

```php
use App\Mail\InvoicePaid as InvoicePaidMailable;
use Illuminate\Mail\Mailable;

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): Mailable
{
    return (new InvoicePaidMailable($this->invoice))
        ->to($notifiable->email);
}
```

<a name="mailables-and-on-demand-notifications"></a>
#### Mailable-класи та сповіщення на льоту

Якщо ви надсилаєте [сповіщення на льоту](#on-demand-notifications), екземпляр `$notifiable`, переданий методу `toMail`, буде екземпляром `Illuminate\Notifications\AnonymousNotifiable`, який пропонує метод `routeNotificationFor` для отримання адреси, на яку слід надіслати сповіщення:

```php
use App\Mail\InvoicePaid as InvoicePaidMailable;
use Illuminate\Notifications\AnonymousNotifiable;
use Illuminate\Mail\Mailable;

/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): Mailable
{
    $address = $notifiable instanceof AnonymousNotifiable
        ? $notifiable->routeNotificationFor('mail')
        : $notifiable->email;

    return (new InvoicePaidMailable($this->invoice))
        ->to($address);
}
```

<a name="previewing-mail-notifications"></a>
### Попередній перегляд поштових сповіщень

Розробляючи шаблон поштового сповіщення, зручно швидко переглядати відрендерене повідомлення в браузері, як звичайний шаблон Blade. Тому Laravel дозволяє повертати будь-яке поштове повідомлення, згенероване поштовим сповіщенням, напряму із замикання маршруту чи контролера. Коли повернуто `MailMessage`, його буде відрендерено й показано в браузері, тож ви зможете швидко переглянути дизайн, не надсилаючи листа на справжню адресу:

```php
use App\Models\Invoice;
use App\Notifications\InvoicePaid;

Route::get('/notification', function () {
    $invoice = Invoice::find(1);

    return (new InvoicePaid($invoice))
        ->toMail($invoice->user);
});
```

<a name="markdown-mail-notifications"></a>
## Поштові сповіщення в Markdown

Поштові сповіщення в Markdown дозволяють скористатися готовими шаблонами поштових сповіщень і водночас дають більше свободи писати довші, налаштовані повідомлення. Оскільки повідомлення пишуться в Markdown, Laravel може відрендерити для них гарні адаптивні HTML-шаблони, а заразом автоматично згенерувати текстовий відповідник.

<a name="generating-the-message"></a>
### Генерація повідомлення

Щоб згенерувати сповіщення з відповідним Markdown-шаблоном, скористайтеся опцією `--markdown` команди Artisan `make:notification`:

```shell
php artisan make:notification InvoicePaid --markdown=mail.invoice.paid
```

Як і всі інші поштові сповіщення, сповіщення з Markdown-шаблонами мають описувати у своєму класі метод `toMail`. Проте замість методів `line` та `action` для побудови сповіщення скористайтеся методом `markdown`, щоб указати назву Markdown-шаблону. Масив даних, які ви хочете зробити доступними шаблону, можна передати другим аргументом методу:

```php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    $url = url('/invoice/'.$this->invoice->id);

    return (new MailMessage)
        ->subject('Invoice Paid')
        ->markdown('mail.invoice.paid', ['url' => $url]);
}
```

<a name="writing-the-message"></a>
### Написання повідомлення

Поштові сповіщення в Markdown поєднують компоненти Blade і синтаксис Markdown, що дозволяє легко будувати сповіщення, користуючись готовими компонентами сповіщень Laravel:

```blade
<x-mail::message>
# Invoice Paid

Your invoice has been paid!

<x-mail::button :url="$url">
View Invoice
</x-mail::button>

Thanks,<br>
{{ config('app.name') }}
</x-mail::message>
```

> [!NOTE]
> Не робіть зайвих відступів, коли пишете Markdown-листи. За стандартами Markdown парсери рендерять вміст із відступами як блоки коду.

<a name="button-component"></a>
#### Компонент button

Компонент button рендерить відцентроване посилання-кнопку. Компонент приймає два аргументи: `url` та необов'язковий `color`. Підтримувані кольори - `primary`, `green` та `red`. Ви можете додати до сповіщення скільки завгодно компонентів button:

```blade
<x-mail::button :url="$url" color="green">
View Invoice
</x-mail::button>
```

<a name="panel-component"></a>
#### Компонент panel

Компонент panel рендерить заданий блок тексту в панелі, колір тла якої трохи відрізняється від решти сповіщення. Це дозволяє привернути увагу до певного блоку тексту:

```blade
<x-mail::panel>
This is the panel content.
</x-mail::panel>
```

<a name="table-component"></a>
#### Компонент table

Компонент table дозволяє перетворити таблицю Markdown на HTML-таблицю. Компонент приймає таблицю Markdown як свій вміст. Вирівнювання стовпців підтримується стандартним синтаксисом вирівнювання таблиць Markdown:

```blade
<x-mail::table>
| Laravel       | Table         | Example       |
| ------------- | :-----------: | ------------: |
| Col 2 is      | Centered      | $10           |
| Col 3 is      | Right-Aligned | $20           |
</x-mail::table>
```

<a name="customizing-the-components"></a>
### Налаштування компонентів

Ви можете експортувати всі компоненти сповіщень Markdown до власного застосунку, щоб їх налаштувати. Щоб експортувати компоненти, опублікуйте тег ресурсів `laravel-mail` командою Artisan `vendor:publish`:

```shell
php artisan vendor:publish --tag=laravel-mail
```

Ця команда опублікує поштові компоненти Markdown до каталогу `resources/views/vendor/mail`. Каталог `mail` міститиме каталоги `html` і `text`, у кожному з яких лежатиме відповідне представлення кожного доступного компонента. Ви вільні налаштовувати ці компоненти як заманеться.

<a name="customizing-the-css"></a>
#### Налаштування CSS

Після експорту компонентів каталог `resources/views/vendor/mail/html/themes` міститиме файл `default.css`. Ви можете змінити CSS у цьому файлі, і ваші стилі автоматично буде вбудовано в HTML-представлення ваших Markdown-сповіщень.

Якщо ви хочете створити цілком нову тему для компонентів Markdown у Laravel, покладіть CSS-файл у каталог `html/themes`. Назвавши й зберігши свій CSS-файл, оновіть опцію `theme` у файлі конфігурації `mail` відповідно до назви нової теми.

Щоб змінити тему для окремого сповіщення, викличте метод `theme` під час побудови його поштового повідомлення. Метод `theme` приймає назву теми, яку слід використати під час надсилання сповіщення:

```php
/**
 * Get the mail representation of the notification.
 */
public function toMail(object $notifiable): MailMessage
{
    return (new MailMessage)
        ->theme('invoice')
        ->subject('Invoice Paid')
        ->markdown('mail.invoice.paid', ['url' => $url]);
}
```

<a name="database-notifications"></a>
## Сповіщення в базі даних

<a name="database-prerequisites"></a>
### Передумови

Канал сповіщень `database` зберігає інформацію про сповіщення в таблиці бази даних. Ця таблиця міститиме тип сповіщення, а також JSON-структуру даних, яка його описує.

Ви можете робити запити до цієї таблиці, щоб показувати сповіщення в інтерфейсі вашого застосунку. Проте спершу вам потрібно створити таблицю, яка їх зберігатиме. Скористайтеся командою `make:notifications-table`, щоб згенерувати [міграцію](/docs/{{version}}/migrations) з належною схемою таблиці:

```shell
php artisan make:notifications-table

php artisan migrate
```

> [!NOTE]
> Якщо ваші моделі-отримувачі використовують [первинні ключі UUID чи ULID](/docs/{{version}}/eloquent#uuid-and-ulid-keys), замініть у міграції таблиці сповіщень метод `morphs` на [uuidMorphs](/docs/{{version}}/migrations#column-method-uuidMorphs) або [ulidMorphs](/docs/{{version}}/migrations#column-method-ulidMorphs).

<a name="formatting-database-notifications"></a>
### Форматування сповіщень у базі даних

Якщо сповіщення можна зберігати в таблиці бази даних, опишіть у класі сповіщення метод `toDatabase` або `toArray`. Цей метод отримає сутність `$notifiable` і має повернути звичайний PHP-масив. Повернений масив буде закодовано в JSON і збережено у стовпці `data` вашої таблиці `notifications`. Погляньмо на приклад методу `toArray`:

```php
/**
 * Get the array representation of the notification.
 *
 * @return array<string, mixed>
 */
public function toArray(object $notifiable): array
{
    return [
        'invoice_id' => $this->invoice->id,
        'amount' => $this->invoice->amount,
    ];
}
```

Коли сповіщення збережено в базі вашого застосунку, стовпцю `type` за замовчуванням присвоюється назва класу сповіщення, а стовпець `read_at` матиме значення `null`. Проте ви можете змінити цю поведінку, описавши у класі сповіщення методи `databaseType` та `initialDatabaseReadAtValue`:

```php
use Illuminate\Support\Carbon;

/**
 * Get the notification's database type.
 */
public function databaseType(object $notifiable): string
{
    return 'invoice-paid';
}

/**
 * Get the initial value for the "read_at" column.
 */
public function initialDatabaseReadAtValue(): ?Carbon
{
    return null;
}
```

<a name="todatabase-vs-toarray"></a>
#### `toDatabase` проти `toArray`

Метод `toArray` використовує й канал `broadcast`, щоб визначити, які дані надсилати вашому фронтенду на JavaScript. Якщо ви хочете мати два різні масиви для каналів `database` і `broadcast`, опишіть метод `toDatabase` замість `toArray`.

<a name="accessing-the-notifications"></a>
### Доступ до сповіщень

Щойно сповіщення зберігаються в базі, вам потрібен зручний спосіб звертатися до них із ваших сутностей-отримувачів. Трейт `Illuminate\Notifications\Notifiable`, який підключено до стандартної моделі `App\Models\User` у Laravel, містить [зв'язок Eloquent](/docs/{{version}}/eloquent-relationships) `notifications`, що повертає сповіщення сутності. Щоб дістати сповіщення, звертайтеся до цього методу як до будь-якого іншого зв'язку Eloquent. За замовчуванням сповіщення сортуються за міткою часу `created_at`, і найновіші стоять на початку колекції:

```php
$user = App\Models\User::find(1);

foreach ($user->notifications as $notification) {
    echo $notification->type;
}
```

Якщо ви хочете дістати лише «непрочитані» сповіщення, скористайтеся зв'язком `unreadNotifications`. Знову ж таки, ці сповіщення сортуються за міткою часу `created_at`, і найновіші стоять на початку колекції:

```php
$user = App\Models\User::find(1);

foreach ($user->unreadNotifications as $notification) {
    echo $notification->type;
}
```

Якщо ви хочете дістати лише «прочитані» сповіщення, скористайтеся зв'язком `readNotifications`:

```php
$user = App\Models\User::find(1);

foreach ($user->readNotifications as $notification) {
    echo $notification->type;
}
```

> [!NOTE]
> Щоб звертатися до сповіщень із клієнта на JavaScript, опишіть у застосунку контролер сповіщень, який повертає сповіщення сутності-отримувача - наприклад, поточного користувача. Далі ви можете робити HTTP-запит на URL цього контролера зі свого JavaScript-клієнта.

<a name="marking-notifications-as-read"></a>
### Позначення сповіщень прочитаними

Зазвичай вам захочеться позначати сповіщення «прочитаним», коли користувач його переглядає. Трейт `Illuminate\Notifications\Notifiable` надає метод `markAsRead`, який оновлює стовпець `read_at` у записі сповіщення:

```php
$user = App\Models\User::find(1);

foreach ($user->unreadNotifications as $notification) {
    $notification->markAsRead();
}
```

Проте, замість проходити кожне сповіщення в циклі, ви можете викликати метод `markAsRead` безпосередньо на колекції сповіщень:

```php
$user->unreadNotifications->markAsRead();
```

Ви також можете скористатися масовим оновленням, щоб позначити всі сповіщення прочитаними, не діставши їх із бази:

```php
$user = App\Models\User::find(1);

$user->unreadNotifications()->update(['read_at' => now()]);
```

Щоб цілком вилучити сповіщення з таблиці, ви можете їх видалити методом `delete`:

```php
$user->notifications()->delete();
```

<a name="broadcast-notifications"></a>
## Сповіщення через бродкастинг

<a name="broadcast-prerequisites"></a>
### Передумови

Перш ніж надсилати сповіщення бродкастингом, налаштуйте сервіси [бродкастингу подій](/docs/{{version}}/broadcasting) Laravel і ознайомтеся з ними. Бродкастинг подій дає спосіб реагувати на серверні події Laravel із вашого фронтенду на JavaScript.

<a name="formatting-broadcast-notifications"></a>
### Форматування сповіщень бродкастингу

Канал `broadcast` надсилає сповіщення через сервіси [бродкастингу подій](/docs/{{version}}/broadcasting) Laravel, дозволяючи вашому фронтенду на JavaScript ловити сповіщення в реальному часі. Якщо сповіщення підтримує бродкастинг, опишіть у його класі метод `toBroadcast`. Цей метод отримає сутність `$notifiable` і має повернути екземпляр `BroadcastMessage`. Якщо методу `toBroadcast` немає, дані для надсилання буде зібрано методом `toArray`. Повернені дані буде закодовано в JSON і надіслано вашому фронтенду. Погляньмо на приклад методу `toBroadcast`:

```php
use Illuminate\Notifications\Messages\BroadcastMessage;

/**
 * Get the broadcastable representation of the notification.
 */
public function toBroadcast(object $notifiable): BroadcastMessage
{
    return new BroadcastMessage([
        'invoice_id' => $this->invoice->id,
        'amount' => $this->invoice->amount,
    ]);
}
```

<a name="broadcast-queue-configuration"></a>
#### Конфігурація черги бродкастингу

Усі сповіщення бродкастингу потрапляють у чергу. Якщо ви хочете налаштувати підключення чи ім'я черги, яку використовує операція бродкастингу, скористайтеся методами `onConnection` та `onQueue` класу `BroadcastMessage`:

```php
return (new BroadcastMessage($data))
    ->onConnection('sqs')
    ->onQueue('broadcasts');
```

<a name="customizing-the-notification-type"></a>
#### Налаштування типу сповіщення

Окрім вказаних вами даних, усі сповіщення бродкастингу мають поле `type` із повною назвою класу сповіщення. Якщо ви хочете змінити `type` сповіщення, опишіть у його класі метод `broadcastType`:

```php
/**
 * Get the type of the notification being broadcast.
 */
public function broadcastType(): string
{
    return 'broadcast.message';
}
```

<a name="listening-for-notifications"></a>
### Прослуховування сповіщень

Сповіщення надсилаються в приватний канал, назва якого будується за домовленістю `{notifiable}.{id}`. Тож, якщо ви надсилаєте сповіщення екземпляру `App\Models\User` з ID `1`, сповіщення буде надіслано в приватний канал `App.Models.User.1`. Користуючись [Laravel Echo](/docs/{{version}}/broadcasting#client-side-installation), ви можете легко слухати сповіщення в каналі методом `notification`:

```js
Echo.private('App.Models.User.' + userId)
    .notification((notification) => {
        console.log(notification.type);
    });
```

<a name="using-react-or-vue"></a>
#### Використання React, Vue чи Svelte

Laravel Echo містить хуки для React, Vue та Svelte, які роблять прослуховування сповіщень безболісним. Для початку викличте хук `useEchoNotification`, який слухає сповіщення. Хук `useEchoNotification` автоматично виходить з каналів, коли компонент, який його використовує, демонтується:

```js tab=React
import { useEchoNotification } from "@laravel/echo-react";

useEchoNotification(
    `App.Models.User.${userId}`,
    (notification) => {
        console.log(notification.type);
    },
);
```

```vue tab=Vue
<script setup lang="ts">
import { useEchoNotification } from "@laravel/echo-vue";

useEchoNotification(
    `App.Models.User.${userId}`,
    (notification) => {
        console.log(notification.type);
    },
);
</script>
```

```svelte tab=Svelte
<script>
import { useEchoNotification } from "@laravel/echo-svelte";

useEchoNotification(
    `App.Models.User.${userId}`,
    (notification) => {
        console.log(notification.type);
    },
);
</script>
```

За замовчуванням хук слухає всі сповіщення. Щоб указати типи сповіщень, які ви хочете слухати, передайте до `useEchoNotification` рядок або масив типів:

```js tab=React
import { useEchoNotification } from "@laravel/echo-react";

useEchoNotification(
    `App.Models.User.${userId}`,
    (notification) => {
        console.log(notification.type);
    },
    'App.Notifications.InvoicePaid',
);
```

```vue tab=Vue
<script setup lang="ts">
import { useEchoNotification } from "@laravel/echo-vue";

useEchoNotification(
    `App.Models.User.${userId}`,
    (notification) => {
        console.log(notification.type);
    },
    'App.Notifications.InvoicePaid',
);
</script>
```

```svelte tab=Svelte
<script>
import { useEchoNotification } from "@laravel/echo-svelte";

useEchoNotification(
    `App.Models.User.${userId}`,
    (notification) => {
        console.log(notification.type);
    },
    'App.Notifications.InvoicePaid',
);
</script>
```

Ви також можете описати форму даних сповіщення, отримавши кращу типобезпеку й зручність редагування:

```ts
type InvoicePaidNotification = {
    invoice_id: number;
    created_at: string;
};

useEchoNotification<InvoicePaidNotification>(
    `App.Models.User.${userId}`,
    (notification) => {
        console.log(notification.invoice_id);
        console.log(notification.created_at);
        console.log(notification.type);
    },
    'App.Notifications.InvoicePaid',
);
```

<a name="customizing-the-notification-channel"></a>
#### Налаштування каналу сповіщень

Якщо ви хочете змінити канал, у який надсилаються сповіщення бродкастингу для сутності, опишіть у цій сутності метод `receivesBroadcastNotificationsOn`:

```php
<?php

namespace App\Models;

use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The channels the user receives notification broadcasts on.
     */
    public function receivesBroadcastNotificationsOn(): string
    {
        return 'users.'.$this->id;
    }
}
```

<a name="sms-notifications"></a>
## SMS-сповіщення

<a name="sms-prerequisites"></a>
### Передумови

Надсилання SMS-сповіщень у Laravel працює на [Vonage](https://www.vonage.com/) (раніше відомий як Nexmo). Перш ніж надсилати сповіщення через Vonage, вам потрібно встановити пакети `laravel/vonage-notification-channel` та `guzzlehttp/guzzle`:

```shell
composer require laravel/vonage-notification-channel guzzlehttp/guzzle
```

Пакет містить [файл конфігурації](https://github.com/laravel/vonage-notification-channel/blob/3.x/config/vonage.php). Проте експортувати цей файл до свого застосунку не обов'язково. Ви можете просто скористатися змінними середовища `VONAGE_KEY` та `VONAGE_SECRET`, щоб задати свої публічний і секретний ключі Vonage.

Задавши ключі, встановіть змінну середовища `VONAGE_SMS_FROM`, яка визначає номер телефону, з якого за замовчуванням надсилатимуться ваші SMS. Цей номер можна згенерувати в панелі керування Vonage:

```ini
VONAGE_SMS_FROM=15556666666
```

<a name="formatting-sms-notifications"></a>
### Форматування SMS-сповіщень

Якщо сповіщення можна надсилати як SMS, опишіть у класі сповіщення метод `toVonage`. Цей метод отримає сутність `$notifiable` і має повернути екземпляр `Illuminate\Notifications\Messages\VonageMessage`:

```php
use Illuminate\Notifications\Messages\VonageMessage;

/**
 * Get the Vonage / SMS representation of the notification.
 */
public function toVonage(object $notifiable): VonageMessage
{
    return (new VonageMessage)
        ->content('Your SMS message content');
}
```

<a name="unicode-content"></a>
#### Вміст у Unicode

Якщо ваше SMS міститиме символи Unicode, викличте метод `unicode` під час створення екземпляра `VonageMessage`:

```php
use Illuminate\Notifications\Messages\VonageMessage;

/**
 * Get the Vonage / SMS representation of the notification.
 */
public function toVonage(object $notifiable): VonageMessage
{
    return (new VonageMessage)
        ->content('Your unicode message')
        ->unicode();
}
```

<a name="customizing-the-from-number"></a>
### Налаштування номера «From»

Якщо ви хочете надсилати деякі сповіщення з номера, відмінного від указаного змінною середовища `VONAGE_SMS_FROM`, викличте метод `from` на екземплярі `VonageMessage`:

```php
use Illuminate\Notifications\Messages\VonageMessage;

/**
 * Get the Vonage / SMS representation of the notification.
 */
public function toVonage(object $notifiable): VonageMessage
{
    return (new VonageMessage)
        ->content('Your SMS message content')
        ->from('15554443333');
}
```

<a name="adding-a-client-reference"></a>
### Додавання клієнтського посилання

Якщо ви хочете відстежувати витрати за користувачем, командою чи клієнтом, додайте до сповіщення «клієнтське посилання». Vonage дозволить будувати звіти за цим посиланням, щоб ви краще розуміли, скільки SMS витрачає конкретний клієнт. Клієнтське посилання може бути будь-яким рядком до 40 символів:

```php
use Illuminate\Notifications\Messages\VonageMessage;

/**
 * Get the Vonage / SMS representation of the notification.
 */
public function toVonage(object $notifiable): VonageMessage
{
    return (new VonageMessage)
        ->clientReference((string) $notifiable->id)
        ->content('Your SMS message content');
}
```

<a name="routing-sms-notifications"></a>
### Маршрутизація SMS-сповіщень

Щоб спрямувати сповіщення Vonage на потрібний номер телефону, опишіть у своїй сутності-отримувачі метод `routeNotificationForVonage`:

```php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * Route notifications for the Vonage channel.
     */
    public function routeNotificationForVonage(Notification $notification): string
    {
        return $this->phone_number;
    }
}
```

<a name="slack-notifications"></a>
## Сповіщення в Slack

<a name="slack-prerequisites"></a>
### Передумови

Перш ніж надсилати сповіщення в Slack, встановіть канал сповіщень Slack через Composer:

```shell
composer require laravel/slack-notification-channel
```

Крім того, вам потрібно створити [застосунок Slack](https://api.slack.com/apps?new_app=1) для свого робочого простору Slack.

Якщо вам треба надсилати сповіщення лише до того робочого простору, у якому створено застосунок, переконайтеся, що ваш застосунок має скопи `chat:write`, `chat:write.public` та `chat:write.customize`. Ці скопи додаються на вкладці керування застосунком «OAuth & Permissions» у Slack.

Далі скопіюйте «Bot User OAuth Token» застосунку й покладіть його в масив конфігурації `slack` у файлі `services.php` вашого застосунку. Цей токен можна знайти на вкладці «OAuth & Permissions» у Slack:

```php
'slack' => [
    'notifications' => [
        'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'),
        'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'),
    ],
],
```

<a name="slack-app-distribution"></a>
#### Розповсюдження застосунку

Якщо ваш застосунок надсилатиме сповіщення до зовнішніх робочих просторів Slack, які належать вашим користувачам, вам потрібно «розповсюдити» свій застосунок через Slack. Розповсюдженням керують на вкладці «Manage Distribution» вашого застосунку в Slack. Щойно застосунок розповсюджено, ви можете скористатися [Socialite](/docs/{{version}}/socialite), щоб [отримувати бот-токени Slack](/docs/{{version}}/socialite#slack-bot-scopes) від імені користувачів вашого застосунку.

<a name="formatting-slack-notifications"></a>
### Форматування сповіщень Slack

Якщо сповіщення можна надсилати як повідомлення Slack, опишіть у класі сповіщення метод `toSlack`. Цей метод отримає сутність `$notifiable` і має повернути екземпляр `Illuminate\Notifications\Slack\SlackMessage`. Ви можете будувати насичені сповіщення за допомогою [Block Kit API від Slack](https://api.slack.com/block-kit). Приклад нижче можна переглянути в [конструкторі Block Kit від Slack](https://app.slack.com/block-kit-builder/T01KWS6K23Z#%7B%22blocks%22:%5B%7B%22type%22:%22header%22,%22text%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Invoice%20Paid%22%7D%7D,%7B%22type%22:%22context%22,%22elements%22:%5B%7B%22type%22:%22plain_text%22,%22text%22:%22Customer%20%231234%22%7D%5D%7D,%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22plain_text%22,%22text%22:%22An%20invoice%20has%20been%20paid.%22%7D,%22fields%22:%5B%7B%22type%22:%22mrkdwn%22,%22text%22:%22*Invoice%20No:*%5Cn1000%22%7D,%7B%22type%22:%22mrkdwn%22,%22text%22:%22*Invoice%20Recipient:*%5Cntaylor@laravel.com%22%7D%5D%7D,%7B%22type%22:%22divider%22%7D,%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Congratulations!%22%7D%7D%5D%7D):

```php
use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock;
use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock;
use Illuminate\Notifications\Slack\SlackMessage;

/**
 * Get the Slack representation of the notification.
 */
public function toSlack(object $notifiable): SlackMessage
{
    return (new SlackMessage)
        ->text('One of your invoices has been paid!')
        ->headerBlock('Invoice Paid')
        ->contextBlock(function (ContextBlock $block) {
            $block->text('Customer #1234');
        })
        ->sectionBlock(function (SectionBlock $block) {
            $block->text('An invoice has been paid.');
            $block->field("*Invoice No:*\n1000")->markdown();
            $block->field("*Invoice Recipient:*\ntaylor@laravel.com")->markdown();
        })
        ->dividerBlock()
        ->sectionBlock(function (SectionBlock $block) {
            $block->text('Congratulations!');
        });
}
```

<a name="using-slacks-block-kit-builder-template"></a>
#### Використання шаблону з конструктора Block Kit

Замість плавних методів побудови повідомлення ви можете передати сирі JSON-дані, згенеровані конструктором Block Kit від Slack, методу `usingBlockKitTemplate`:

```php
use Illuminate\Notifications\Slack\SlackMessage;
use Illuminate\Support\Str;

/**
 * Get the Slack representation of the notification.
 */
public function toSlack(object $notifiable): SlackMessage
{
    $template = <<<JSON
        {
          "blocks": [
            {
              "type": "header",
              "text": {
                "type": "plain_text",
                "text": "Team Announcement"
              }
            },
            {
              "type": "section",
              "text": {
                "type": "plain_text",
                "text": "We are hiring!"
              }
            }
          ]
        }
    JSON;

    return (new SlackMessage)
        ->usingBlockKitTemplate($template);
}
```

<a name="slack-interactivity"></a>
### Інтерактивність Slack

Система сповіщень Block Kit від Slack надає потужні можливості для [обробки взаємодії з користувачем](https://api.slack.com/interactivity/handling). Щоб ними скористатися, у вашому застосунку Slack має бути увімкнено «Interactivity» і налаштовано «Request URL», який вказує на URL вашого застосунку. Ці налаштування керуються на вкладці «Interactivity & Shortcuts» вашого застосунку в Slack.

У прикладі нижче, який використовує метод `actionsBlock`, Slack надішле `POST`-запит на ваш «Request URL» із даними про користувача Slack, який натиснув кнопку, ID натиснутої кнопки тощо. Далі ваш застосунок може визначити, яку дію виконати, на основі цих даних. Вам також слід [перевіряти, що запит](https://api.slack.com/authentication/verifying-requests-from-slack) справді надійшов від Slack:

```php
use Illuminate\Notifications\Slack\BlockKit\Blocks\ActionsBlock;
use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock;
use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock;
use Illuminate\Notifications\Slack\SlackMessage;

/**
 * Get the Slack representation of the notification.
 */
public function toSlack(object $notifiable): SlackMessage
{
    return (new SlackMessage)
        ->text('One of your invoices has been paid!')
        ->headerBlock('Invoice Paid')
        ->contextBlock(function (ContextBlock $block) {
            $block->text('Customer #1234');
        })
        ->sectionBlock(function (SectionBlock $block) {
            $block->text('An invoice has been paid.');
        })
        ->actionsBlock(function (ActionsBlock $block) {
             // ID defaults to "button_acknowledge_invoice"...
            $block->button('Acknowledge Invoice')->primary();

            // Manually configure the ID...
            $block->button('Deny')->danger()->id('deny_invoice');
        });
}
```

<a name="slack-confirmation-modals"></a>
#### Модальні вікна підтвердження

Якщо ви хочете, щоб користувачі підтверджували дію перед її виконанням, викличте метод `confirm` під час опису кнопки. Метод `confirm` приймає повідомлення й замикання, яке отримує екземпляр `ConfirmObject`:

```php
use Illuminate\Notifications\Slack\BlockKit\Blocks\ActionsBlock;
use Illuminate\Notifications\Slack\BlockKit\Blocks\ContextBlock;
use Illuminate\Notifications\Slack\BlockKit\Blocks\SectionBlock;
use Illuminate\Notifications\Slack\BlockKit\Composites\ConfirmObject;
use Illuminate\Notifications\Slack\SlackMessage;

/**
 * Get the Slack representation of the notification.
 */
public function toSlack(object $notifiable): SlackMessage
{
    return (new SlackMessage)
        ->text('One of your invoices has been paid!')
        ->headerBlock('Invoice Paid')
        ->contextBlock(function (ContextBlock $block) {
            $block->text('Customer #1234');
        })
        ->sectionBlock(function (SectionBlock $block) {
            $block->text('An invoice has been paid.');
        })
        ->actionsBlock(function (ActionsBlock $block) {
            $block->button('Acknowledge Invoice')
                ->primary()
                ->confirm(
                    'Acknowledge the payment and send a thank you email?',
                    function (ConfirmObject $dialog) {
                        $dialog->confirm('Yes');
                        $dialog->deny('No');
                    }
                );
        });
}
```

<a name="inspecting-slack-blocks"></a>
#### Огляд блоків Slack

Якщо ви хочете швидко оглянути блоки, які будуєте, викличте метод `dd` на екземплярі `SlackMessage`. Метод `dd` згенерує й виведе URL до [конструктора Block Kit](https://app.slack.com/block-kit-builder/) від Slack, який покаже попередній перегляд даних і сповіщення у вашому браузері. Ви можете передати методу `dd` значення `true`, щоб вивести сирі дані:

```php
return (new SlackMessage)
    ->text('One of your invoices has been paid!')
    ->headerBlock('Invoice Paid')
    ->dd();
```

<a name="routing-slack-notifications"></a>
### Маршрутизація сповіщень Slack

Щоб спрямувати сповіщення Slack до потрібної команди й каналу, опишіть у своїй моделі-отримувачі метод `routeNotificationForSlack`. Цей метод може повернути одне з трьох значень:

- `null` - тоді маршрутизацію віддано каналу, налаштованому в самому сповіщенні. Ви можете скористатися методом `to` під час побудови `SlackMessage`, щоб налаштувати канал у сповіщенні.
- Рядок із каналом Slack, до якого слід надіслати сповіщення, наприклад `#support-channel`.
- Екземпляр `SlackRoute`, який дозволяє вказати OAuth-токен і назву каналу, наприклад `SlackRoute::make($this->slack_channel, $this->slack_token)`. Цей спосіб слід використовувати для надсилання сповіщень до зовнішніх робочих просторів.

Наприклад, якщо метод `routeNotificationForSlack` поверне `#support-channel`, сповіщення буде надіслано в канал `#support-channel` того робочого простору, який пов'язано з токеном Bot User OAuth із файлу `services.php` вашого застосунку:

```php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * Route notifications for the Slack channel.
     */
    public function routeNotificationForSlack(Notification $notification): mixed
    {
        return '#support-channel';
    }
}
```

<a name="notifying-external-slack-workspaces"></a>
### Сповіщення зовнішніх робочих просторів Slack

> [!NOTE]
> Перш ніж надсилати сповіщення до зовнішніх робочих просторів Slack, ваш застосунок Slack має бути [розповсюджено](#slack-app-distribution).

Звісно, часто вам захочеться надсилати сповіщення до робочих просторів Slack, які належать користувачам вашого застосунку. Для цього спершу потрібно отримати OAuth-токен Slack для користувача. На щастя, [Laravel Socialite](/docs/{{version}}/socialite) містить драйвер Slack, який дозволяє легко автентифікувати користувачів вашого застосунку в Slack і [отримати бот-токен](/docs/{{version}}/socialite#slack-bot-scopes).

Щойно ви отримали бот-токен і зберегли його в базі даних свого застосунку, скористайтеся методом `SlackRoute::make`, щоб спрямувати сповіщення до робочого простору користувача. Крім того, вашому застосунку, найімовірніше, треба буде дати користувачеві змогу вказати, до якого каналу надсилати сповіщення:

```php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Slack\SlackRoute;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * Route notifications for the Slack channel.
     */
    public function routeNotificationForSlack(Notification $notification): mixed
    {
        return SlackRoute::make($this->slack_channel, $this->slack_token);
    }
}
```

<a name="localizing-notifications"></a>
## Локалізація сповіщень

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

Для цього клас `Illuminate\Notifications\Notification` пропонує метод `locale`, щоб задати потрібну мову. Застосунок перейде в цю локаль під час обчислення сповіщення, а після завершення повернеться до попередньої:

```php
$user->notify((new InvoicePaid($invoice))->locale('es'));
```

Локалізації для кількох отримувачів можна досягти й через фасад `Notification`:

```php
Notification::locale('es')->send(
    $users, new InvoicePaid($invoice)
);
```

<a name="user-preferred-locales"></a>
#### Бажані локалі користувачів

Інколи застосунки зберігають бажану локаль кожного користувача. Реалізувавши у своїй моделі-отримувачі контракт `HasLocalePreference`, ви можете сказати Laravel використовувати цю збережену локаль під час надсилання сповіщення:

```php
use Illuminate\Contracts\Translation\HasLocalePreference;

class User extends Model implements HasLocalePreference
{
    /**
     * Get the user's preferred locale.
     */
    public function preferredLocale(): string
    {
        return $this->locale;
    }
}
```

Щойно ви реалізували цей інтерфейс, Laravel автоматично використовуватиме бажану локаль, надсилаючи моделі сповіщення та листи. Тому викликати метод `locale` при використанні цього інтерфейсу не потрібно:

```php
$user->notify(new InvoicePaid($invoice));
```

<a name="testing"></a>
## Тестування

Метод `fake` фасаду `Notification` дозволяє завадити надсиланню сповіщень. Зазвичай надсилання сповіщень не стосується коду, який ви насправді тестуєте. Найімовірніше, достатньо просто перевірити, що Laravel отримав вказівку надіслати задане сповіщення.

Після виклику методу `fake` фасаду `Notification` ви можете перевіряти, що сповіщення мали бути надіслані користувачам, і навіть оглядати дані, які вони отримали:

```php tab=Pest
<?php

use App\Notifications\OrderShipped;
use Illuminate\Support\Facades\Notification;

test('orders can be shipped', function () {
    Notification::fake();

    // Perform order shipping...

    // Assert that no notifications were sent...
    Notification::assertNothingSent();

    // Assert a notification was sent to the given users...
    Notification::assertSentTo(
        [$user], OrderShipped::class
    );

    // Assert a notification was not sent...
    Notification::assertNotSentTo(
        [$user], AnotherNotification::class
    );

    // Assert a notification was sent twice...
    Notification::assertSentTimes(WeeklyReminder::class, 2);

    // Assert that a notification was sent to a user exactly once...
    Notification::assertSentToOnce($user, OrderShipped::class);

    // Assert that a given number of notifications were sent...
    Notification::assertCount(3);
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use App\Notifications\OrderShipped;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_orders_can_be_shipped(): void
    {
        Notification::fake();

        // Perform order shipping...

        // Assert that no notifications were sent...
        Notification::assertNothingSent();

        // Assert a notification was sent to the given users...
        Notification::assertSentTo(
            [$user], OrderShipped::class
        );

        // Assert a notification was not sent...
        Notification::assertNotSentTo(
            [$user], AnotherNotification::class
        );

        // Assert a notification was sent twice...
        Notification::assertSentTimes(WeeklyReminder::class, 2);

        // Assert that a notification was sent to a user exactly once...
        Notification::assertSentToOnce($user, OrderShipped::class);

        // Assert that a given number of notifications were sent...
        Notification::assertCount(3);
    }
}
```

Ви можете передати замикання методам `assertSentTo` чи `assertNotSentTo`, щоб перевірити, що було надіслано сповіщення, яке проходить заданий тест. Якщо надіслано щонайменше одне сповіщення, яке проходить цей тест, перевірка буде успішною:

```php
Notification::assertSentTo(
    $user,
    function (OrderShipped $notification, array $channels) use ($order) {
        return $notification->order->id === $order->id;
    }
);
```

Альтернативно, ви можете передати масив очікуваних значень властивостей третім аргументом до `assertSentTo` чи `assertNotSentTo`:

```php
Notification::assertSentTo($user, OrderShipped::class, ['order' => $order]);
```

Усі вказані властивості повинні збігатися. Значення порівнюються за строгою рівністю, а моделі Eloquent порівнюються за допомогою свого методу `is`. Ви також можете передати масив очікуваних значень властивостей другим аргументом до `assertSentOnDemand`.

<a name="testing-on-demand-notifications"></a>
#### Сповіщення на льоту

Якщо код, який ви тестуєте, надсилає [сповіщення на льоту](#on-demand-notifications), перевірити це можна методом `assertSentOnDemand`:

```php
Notification::assertSentOnDemand(OrderShipped::class);
Notification::assertSentOnDemandOnce(OrderShipped::class);
```

Передавши замикання другим аргументом методу `assertSentOnDemand`, ви можете визначити, чи надіслано сповіщення на льоту за правильною «маршрутною» адресою:

```php
Notification::assertSentOnDemand(
    OrderShipped::class,
    function (OrderShipped $notification, array $channels, object $notifiable) use ($user) {
        return $notifiable->routes['mail'] === $user->email;
    }
);
```

<a name="notification-events"></a>
## Події сповіщень

<a name="notification-sending-event"></a>
#### Подія надсилання сповіщення

Коли сповіщення надсилається, система сповіщень диспетчеризує подію `Illuminate\Notifications\Events\NotificationSending`. Вона містить сутність-отримувача та сам екземпляр сповіщення. Ви можете створити [слухачів подій](/docs/{{version}}/events) для цієї події у своєму застосунку:

```php
use Illuminate\Notifications\Events\NotificationSending;

class CheckNotificationStatus
{
    /**
     * Handle the event.
     */
    public function handle(NotificationSending $event): void
    {
        // ...
    }
}
```

Сповіщення не буде надіслано, якщо слухач події `NotificationSending` поверне зі свого методу `handle` значення `false`:

```php
/**
 * Handle the event.
 */
public function handle(NotificationSending $event): bool
{
    return false;
}
```

Усередині слухача ви можете звернутися до властивостей події `notifiable`, `notification` і `channel`, щоб дізнатися більше про отримувача сповіщення чи саме сповіщення:

```php
/**
 * Handle the event.
 */
public function handle(NotificationSending $event): void
{
    // $event->channel
    // $event->notifiable
    // $event->notification
}
```

<a name="notification-sent-event"></a>
#### Подія надісланого сповіщення

Коли сповіщення надіслано, система сповіщень диспетчеризує [подію](/docs/{{version}}/events) `Illuminate\Notifications\Events\NotificationSent`. Вона містить сутність-отримувача та сам екземпляр сповіщення. Ви можете створити [слухачів подій](/docs/{{version}}/events) для цієї події у своєму застосунку:

```php
use Illuminate\Notifications\Events\NotificationSent;

class LogNotification
{
    /**
     * Handle the event.
     */
    public function handle(NotificationSent $event): void
    {
        // ...
    }
}
```

Усередині слухача ви можете звернутися до властивостей події `notifiable`, `notification`, `channel` та `response`, щоб дізнатися більше про отримувача сповіщення чи саме сповіщення:

```php
/**
 * Handle the event.
 */
public function handle(NotificationSent $event): void
{
    // $event->channel
    // $event->notifiable
    // $event->notification
    // $event->response
}
```

<a name="custom-channels"></a>
## Власні канали

Laravel постачається з кількома каналами сповіщень, але ви можете захотіти написати власні драйвери для доставки сповіщень іншими каналами. Laravel це спрощує. Для початку опишіть клас, який містить метод `send`. Цей метод має приймати два аргументи: `$notifiable` та `$notification`.

У методі `send` ви можете викликати методи сповіщення, щоб отримати об'єкт повідомлення, зрозумілий вашому каналу, а потім надіслати сповіщення екземпляру `$notifiable` як заманеться:

```php
<?php

namespace App\Notifications;

use Illuminate\Notifications\Notification;

class VoiceChannel
{
    /**
     * Send the given notification.
     */
    public function send(object $notifiable, Notification $notification): void
    {
        $message = $notification->toVoice($notifiable);

        // Send notification to the $notifiable instance...
    }
}
```

Щойно клас вашого каналу сповіщень описано, ви можете повертати його назву з методу `via` будь-якого зі своїх сповіщень. У цьому прикладі метод `toVoice` вашого сповіщення може повертати будь-який об'єкт на ваш вибір, який представляє голосові повідомлення. Наприклад, ви можете описати власний клас `VoiceMessage`:

```php
<?php

namespace App\Notifications;

use App\Notifications\Messages\VoiceMessage;
use App\Notifications\VoiceChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;

class InvoicePaid extends Notification
{
    use Queueable;

    /**
     * Get the notification channels.
     */
    public function via(object $notifiable): string
    {
        return VoiceChannel::class;
    }

    /**
     * Get the voice representation of the notification.
     */
    public function toVoice(object $notifiable): VoiceMessage
    {
        // ...
    }
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/packages

# Розробка пакетів

- [Вступ](#introduction)
    - [Створення пакета](#creating-a-package)
    - [Кілька слів про фасади](#a-note-on-facades)
- [Виявлення пакетів](#package-discovery)
- [Сервіс-провайдери](#service-providers)
- [Ресурси](#resources)
    - [Конфігурація](#configuration)
    - [Маршрути](#routes)
    - [Міграції](#migrations)
    - [Мовні файли](#language-files)
    - [Представлення](#views)
    - [Компоненти представлень](#view-components)
    - [Команда Artisan «about»](#about-artisan-command)
- [Команди](#commands)
    - [Команди оптимізації](#optimize-commands)
    - [Команди перезавантаження](#reload-commands)
- [Публічні ресурси](#public-assets)
- [Публікація груп файлів](#publishing-file-groups)

<a name="introduction"></a>
## Вступ

Пакети - основний спосіб додавати функціональність до Laravel. Пакетом може бути будь-що: від чудового способу працювати з датами на кшталт [Carbon](https://github.com/briannesbitt/Carbon) до пакета, який дозволяє прив'язувати файли до моделей Eloquent, - як-от [Laravel Media Library](https://github.com/spatie/laravel-medialibrary) від Spatie.

Пакети бувають різних типів. Деякі з них самостійні, тобто працюють із будь-яким PHP-фреймворком. Carbon і Pest - приклади самостійних пакетів. Будь-який із них можна використовувати з Laravel, підключивши його у файлі `composer.json`.

Натомість інші пакети призначені саме для Laravel. Такі пакети можуть мати маршрути, контролери, представлення й конфігурацію, створені спеціально для розширення застосунку Laravel. Цей посібник насамперед описує розробку саме таких пакетів, специфічних для Laravel.

<a name="creating-a-package"></a>
### Створення пакета

Найпростіший спосіб почати створювати новий пакет для Laravel - офіційний [каркас пакета Laravel](https://github.com/laravel/package-skeleton). Каркас надає все потрібне для створення пакета Laravel: сервіс-провайдер, тестування через Pest, статичний аналіз через Larastan, форматування коду через Pint і застосунок-верстак для наскрізної розробки пакета. Створити новий пакет можна командою `package` з [CLI-інсталятора Laravel](/docs/{{version}}/installation#creating-a-laravel-project):

```shell
laravel package my-package
```

Інтерактивний скрипт конфігурації персоналізує каркас під ваш пакет, налаштувавши простір імен, сервіс-провайдер і лише ті можливості, які вам потрібні: файли конфігурації, маршрути, представлення, переклади, міграції, ресурси, команди та фасад.

<a name="a-note-on-facades"></a>
### Кілька слів про фасади

Пишучи застосунок Laravel, зазвичай не має значення, чи користуєтеся ви контрактами, чи фасадами, - обидва дають по суті однаковий рівень тестованості. Проте, коли ви пишете пакети, ваш пакет зазвичай не має доступу до всіх тестових хелперів Laravel. Якщо ви хочете писати тести пакета так, ніби пакет встановлено у звичайному застосунку Laravel, скористайтеся пакетом [Orchestral Testbench](https://github.com/orchestral/testbench).

<a name="package-discovery"></a>
## Виявлення пакетів

Файл `bootstrap/providers.php` застосунку Laravel містить список сервіс-провайдерів, які має завантажити Laravel. Проте, замість змушувати користувачів вручну додавати ваш сервіс-провайдер до списку, ви можете описати провайдер у секції `extra` файлу `composer.json` вашого пакета, і Laravel завантажить його автоматично. Окрім сервіс-провайдерів, ви можете перелічити й [фасади](/docs/{{version}}/facades), які хочете зареєструвати:

```json
"extra": {
    "laravel": {
        "providers": [
            "Barryvdh\\Debugbar\\ServiceProvider"
        ],
        "aliases": {
            "Debugbar": "Barryvdh\\Debugbar\\Facade"
        }
    }
},
```

Щойно ваш пакет налаштовано на виявлення, Laravel автоматично зареєструє його сервіс-провайдери та фасади під час встановлення, створюючи зручний досвід встановлення для користувачів вашого пакета.

<a name="opting-out-of-package-discovery"></a>
#### Відмова від виявлення пакетів

Якщо ви споживач пакета й хочете вимкнути виявлення для нього, перелічіть назву пакета в секції `extra` файлу `composer.json` вашого застосунку:

```json
"extra": {
    "laravel": {
        "dont-discover": [
            "barryvdh/laravel-debugbar"
        ]
    }
},
```

Ви можете вимкнути виявлення для всіх пакетів, скориставшись символом `*` у директиві `dont-discover` вашого застосунку:

```json
"extra": {
    "laravel": {
        "dont-discover": [
            "*"
        ]
    }
},
```

<a name="service-providers"></a>
## Сервіс-провайдери

[Сервіс-провайдери](/docs/{{version}}/providers) - це точка з'єднання між вашим пакетом і Laravel. Сервіс-провайдер відповідає за прив'язку речей до [сервіс-контейнера](/docs/{{version}}/container) Laravel і за те, щоб повідомити Laravel, звідки завантажувати ресурси пакета: представлення, конфігурацію та мовні файли.

Сервіс-провайдер розширює клас `Illuminate\Support\ServiceProvider` і містить два методи: `register` та `boot`. Базовий клас `ServiceProvider` лежить у Composer-пакеті `illuminate/support`, який слід додати до залежностей вашого пакета. Щоб дізнатися більше про структуру й призначення сервіс-провайдерів, перегляньте [їхню документацію](/docs/{{version}}/providers).

<a name="resources"></a>
## Ресурси

<a name="configuration"></a>
### Конфігурація

Зазвичай вам знадобиться публікувати файл конфігурації вашого пакета до каталогу `config` застосунку. Це дозволить користувачам вашого пакета легко перевизначати ваші опції за замовчуванням. Щоб дозволити публікацію файлів конфігурації, викличте метод `publishes` у методі `boot` вашого сервіс-провайдера:

```php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->publishes([
        __DIR__.'/../config/courier.php' => config_path('courier.php'),
    ]);
}
```

Тепер, коли користувачі вашого пакета виконають команду Laravel `vendor:publish`, ваш файл буде скопійовано до вказаного місця публікації. Щойно конфігурацію опубліковано, до її значень можна звертатися як до будь-якого іншого файлу конфігурації:

```php
$value = config('courier.option');
```

> [!WARNING]
> Не описуйте замикань у файлах конфігурації. Їх не вдасться коректно серіалізувати, коли користувачі виконають команду Artisan `config:cache`.

<a name="default-package-configuration"></a>
#### Конфігурація пакета за замовчуванням

Ви також можете злити власний файл конфігурації пакета з опублікованою копією застосунку. Це дозволить вашим користувачам описувати в опублікованій копії лише ті опції, які вони справді хочуть перевизначити. Щоб злити значення файлу конфігурації, скористайтеся методом `mergeConfigFrom` у методі `register` вашого сервіс-провайдера.

Метод `mergeConfigFrom` приймає першим аргументом шлях до файлу конфігурації вашого пакета, а другим - ім'я копії файлу конфігурації в застосунку:

```php
/**
 * Register any package services.
 */
public function register(): void
{
    $this->mergeConfigFrom(
        __DIR__.'/../config/courier.php', 'courier'
    );
}
```

> [!WARNING]
> Цей метод зливає лише перший рівень масиву конфігурації. Якщо ваші користувачі частково опишуть багатовимірний масив конфігурації, відсутні опції не буде злито.

<a name="routes"></a>
### Маршрути

Якщо ваш пакет містить маршрути, завантажити їх можна методом `loadRoutesFrom`. Цей метод автоматично визначить, чи закешовані маршрути застосунку, і не завантажуватиме ваш файл маршрутів, якщо їх уже закешовано:

```php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
}
```

<a name="migrations"></a>
### Міграції

Якщо ваш пакет містить [міграції бази даних](/docs/{{version}}/migrations), скористайтеся методом `publishesMigrations`, щоб повідомити Laravel, що заданий каталог чи файл містить міграції. Публікуючи міграції, Laravel автоматично оновить мітку часу в їхніх іменах відповідно до поточних дати й часу:

```php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->publishesMigrations([
        __DIR__.'/../database/migrations' => database_path('migrations'),
    ]);
}
```

<a name="language-files"></a>
### Мовні файли

Якщо ваш пакет містить [мовні файли](/docs/{{version}}/localization), скористайтеся методом `loadTranslationsFrom`, щоб повідомити Laravel, як їх завантажувати. Наприклад, якщо ваш пакет називається `courier`, додайте до методу `boot` вашого сервіс-провайдера таке:

```php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');
}
```

До рядків перекладу пакета звертаються за домовленістю `package::file.line`. Тож завантажити рядок `welcome` пакета `courier` із файлу `messages` можна так:

```php
echo trans('courier::messages.welcome');
```

Зареєструвати JSON-файли перекладів для вашого пакета можна методом `loadJsonTranslationsFrom`. Цей метод приймає шлях до каталогу, який містить JSON-файли перекладів вашого пакета:

```php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->loadJsonTranslationsFrom(__DIR__.'/../lang');
}
```

<a name="publishing-language-files"></a>
#### Публікація мовних файлів

Якщо ви хочете публікувати мовні файли вашого пакета до каталогу `lang/vendor` застосунку, скористайтеся методом `publishes` сервіс-провайдера. Метод `publishes` приймає масив шляхів пакета та бажаних місць публікації. Наприклад, щоб опублікувати мовні файли пакета `courier`, зробіть так:

```php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->loadTranslationsFrom(__DIR__.'/../lang', 'courier');

    $this->publishes([
        __DIR__.'/../lang' => $this->app->langPath('vendor/courier'),
    ]);
}
```

Тепер, коли користувачі вашого пакета виконають команду Artisan `vendor:publish`, мовні файли вашого пакета буде опубліковано до вказаного місця.

<a name="views"></a>
### Представлення

Щоб зареєструвати [представлення](/docs/{{version}}/views) вашого пакета в Laravel, вам потрібно вказати Laravel, де вони лежать. Це робиться методом `loadViewsFrom` сервіс-провайдера. Метод `loadViewsFrom` приймає два аргументи: шлях до ваших шаблонів представлень і назву вашого пакета. Наприклад, якщо ваш пакет називається `courier`, додайте до методу `boot` вашого сервіс-провайдера таке:

```php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');
}
```

До представлень пакета звертаються за домовленістю `package::view`. Тож, щойно шлях до представлень зареєстровано в сервіс-провайдері, ви можете завантажити представлення `dashboard` з пакета `courier` так:

```php
Route::get('/dashboard', function () {
    return view('courier::dashboard');
});
```

<a name="overriding-package-views"></a>
#### Перевизначення представлень пакета

Коли ви користуєтеся методом `loadViewsFrom`, Laravel насправді реєструє для ваших представлень два розташування: каталог `resources/views/vendor` застосунку й вказаний вами каталог. Тож, на прикладі пакета `courier`, Laravel спершу перевірить, чи розробник поклав власну версію представлення в каталог `resources/views/vendor/courier`. Далі, якщо представлення не змінювали, Laravel шукатиме в каталозі представлень пакета, який ви вказали у виклику `loadViewsFrom`. Це спрощує користувачам пакета налаштування / перевизначення ваших представлень.

<a name="publishing-views"></a>
#### Публікація представлень

Якщо ви хочете зробити свої представлення доступними для публікації до каталогу `resources/views/vendor` застосунку, скористайтеся методом `publishes` сервіс-провайдера. Метод `publishes` приймає масив шляхів до представлень пакета та бажаних місць публікації:

```php
/**
 * Bootstrap the package services.
 */
public function boot(): void
{
    $this->loadViewsFrom(__DIR__.'/../resources/views', 'courier');

    $this->publishes([
        __DIR__.'/../resources/views' => resource_path('views/vendor/courier'),
    ]);
}
```

Тепер, коли користувачі вашого пакета виконають команду Artisan `vendor:publish`, представлення вашого пакета буде скопійовано до вказаного місця публікації.

<a name="view-components"></a>
### Компоненти представлень

Якщо ви створюєте пакет, який використовує компоненти Blade, або кладете компоненти в нетипові каталоги, вам доведеться вручну зареєструвати клас компонента та його HTML-аліас, щоб Laravel знав, де його шукати. Зазвичай компоненти реєструють у методі `boot` сервіс-провайдера вашого пакета:

```php
use Illuminate\Support\Facades\Blade;
use VendorPackage\View\Components\AlertComponent;

/**
 * Bootstrap your package's services.
 */
public function boot(): void
{
    Blade::component('package-alert', AlertComponent::class);
}
```

Щойно ваш компонент зареєстровано, його можна відрендерити за його тегом-аліасом:

```blade
<x-package-alert/>
```

<a name="autoloading-package-components"></a>
#### Автозавантаження компонентів пакета

Як варіант, ви можете скористатися методом `componentNamespace`, щоб автозавантажувати класи компонентів за домовленістю. Наприклад, пакет `Nightshade` може мати компоненти `Calendar` і `ColorPicker`, які лежать у просторі імен `Nightshade\Views\Components`:

```php
use Illuminate\Support\Facades\Blade;

/**
 * Bootstrap your package's services.
 */
public function boot(): void
{
    Blade::componentNamespace('Nightshade\\Views\\Components', 'nightshade');
}
```

Це дозволить користуватися компонентами пакета за простором імен вендора через синтаксис `package-name::`:

```blade
<x-nightshade::calendar />
<x-nightshade::color-picker />
```

Blade автоматично визначить клас, пов'язаний із цим компонентом, перетворивши назву компонента в PascalCase. Підкаталоги також підтримуються через «крапкову» нотацію.

<a name="anonymous-components"></a>
#### Анонімні компоненти

Якщо ваш пакет містить анонімні компоненти, їх слід класти в каталог `components` каталогу представлень вашого пакета (вказаного в [методі loadViewsFrom](#views)). Далі ви можете рендерити їх, додавши до назви компонента префікс простору імен представлень пакета:

```blade
<x-courier::alert />
```

<a name="about-artisan-command"></a>
### Команда Artisan «about»

Вбудована команда Artisan `about` дає стислий огляд середовища й конфігурації застосунку. Пакети можуть додавати до виводу цієї команди власну інформацію через клас `AboutCommand`. Зазвичай цю інформацію додають у методі `boot` сервіс-провайдера вашого пакета:

```php
use Illuminate\Foundation\Console\AboutCommand;

/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    AboutCommand::add('My Package', fn () => ['Version' => '1.0.0']);
}
```

<a name="commands"></a>
## Команди

Щоб зареєструвати команди Artisan вашого пакета в Laravel, скористайтеся методом `commands`. Цей метод очікує масив назв класів команд. Щойно команди зареєстровано, ви можете виконувати їх через [CLI Artisan](/docs/{{version}}/artisan):

```php
use Courier\Console\Commands\InstallCommand;
use Courier\Console\Commands\NetworkCommand;

/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->commands([
            InstallCommand::class,
            NetworkCommand::class,
        ]);
    }
}
```

<a name="optimize-commands"></a>
### Команди оптимізації

[Команда optimize](/docs/{{version}}/deployment#optimization) Laravel кешує конфігурацію, події, маршрути та представлення застосунку. Методом `optimizes` ви можете зареєструвати власні команди Artisan вашого пакета, які слід викликати під час виконання команд `optimize` та `optimize:clear`:

```php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->optimizes(
            optimize: 'package:optimize',
            clear: 'package:clear-optimizations',
        );
    }
}
```

<a name="reload-commands"></a>
### Команди перезавантаження

[Команда reload](/docs/{{version}}/deployment#reloading-services) Laravel зупиняє всі запущені сервіси, щоб системний монітор процесів автоматично їх перезапустив. Методом `reloads` ви можете зареєструвати власні команди Artisan вашого пакета, які слід викликати під час виконання команди `reload`:

```php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->reloads('package:reload');
    }
}
```

<a name="public-assets"></a>
## Публічні ресурси

Ваш пакет може мати ресурси - JavaScript, CSS та зображення. Щоб опублікувати ці ресурси до каталогу `public` застосунку, скористайтеся методом `publishes` сервіс-провайдера. У цьому прикладі ми також додамо тег групи ресурсів `public`, який дозволяє легко публікувати групи пов'язаних ресурсів:

```php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->publishes([
        __DIR__.'/../public' => public_path('vendor/courier'),
    ], 'public');
}
```

Тепер, коли користувачі вашого пакета виконають команду `vendor:publish`, ваші ресурси буде скопійовано до вказаного місця публікації. Оскільки користувачам зазвичай доводиться перезаписувати ресурси щоразу під час оновлення пакета, вони можуть скористатися прапорцем `--force`:

```shell
php artisan vendor:publish --tag=public --force
```

<a name="publishing-file-groups"></a>
## Публікація груп файлів

Вам може захотітися публікувати групи ресурсів пакета окремо. Наприклад, ви можете хотіти дозволити користувачам публікувати файли конфігурації вашого пакета, не змушуючи їх публікувати ресурси. Це робиться «тегуванням» під час виклику методу `publishes` із сервіс-провайдера пакета. Наприклад, скористаймося тегами, щоб описати дві групи публікації для пакета `courier` (`courier-config` та `courier-migrations`) у методі `boot` сервіс-провайдера пакета:

```php
/**
 * Bootstrap any package services.
 */
public function boot(): void
{
    $this->publishes([
        __DIR__.'/../config/package.php' => config_path('package.php')
    ], 'courier-config');

    $this->publishesMigrations([
        __DIR__.'/../database/migrations/' => database_path('migrations')
    ], 'courier-migrations');
}
```

Тепер ваші користувачі можуть публікувати ці групи окремо, вказавши їхній тег під час виконання команди `vendor:publish`:

```shell
php artisan vendor:publish --tag=courier-config
```

Ваші користувачі також можуть опублікувати всі публіковані файли, описані сервіс-провайдером вашого пакета, за допомогою прапорця `--provider`:

```shell
php artisan vendor:publish --provider="Your\Package\ServiceProvider"
```

---

Джерело: https://laravelukraine.com/docs/13.x/processes

# Процеси

- [Вступ](#introduction)
- [Виклик процесів](#invoking-processes)
    - [Опції процесу](#process-options)
    - [Вивід процесу](#process-output)
    - [Конвеєри](#process-pipelines)
- [Асинхронні процеси](#asynchronous-processes)
    - [ID процесів і сигнали](#process-ids-and-signals)
    - [Вивід асинхронних процесів](#asynchronous-process-output)
    - [Таймаути асинхронних процесів](#asynchronous-process-timeouts)
- [Паралельні процеси](#concurrent-processes)
    - [Іменування процесів пулу](#naming-pool-processes)
    - [ID процесів пулу та сигнали](#pool-process-ids-and-signals)
- [Тестування](#testing)
    - [Підміна процесів](#faking-processes)
    - [Підміна конкретних процесів](#faking-specific-processes)
    - [Підміна послідовностей процесів](#faking-process-sequences)
    - [Підміна життєвого циклу асинхронних процесів](#faking-asynchronous-process-lifecycles)
    - [Доступні перевірки](#available-assertions)
    - [Запобігання «блукаючим» процесам](#preventing-stray-processes)

<a name="introduction"></a>
## Вступ

Laravel надає виразний мінімалістичний API навколо [компонента Symfony Process](https://symfony.com/doc/current/components/process.html), який дозволяє зручно викликати зовнішні процеси з вашого застосунку Laravel. Можливості роботи з процесами в Laravel зосереджені на найпоширеніших сценаріях і чудовому досвіді розробника.

<a name="invoking-processes"></a>
## Виклик процесів

Щоб викликати процес, скористайтеся методами `run` та `start` фасаду `Process`. Метод `run` викликає процес і чекає на завершення його виконання, а метод `start` призначений для асинхронного виконання. Ми розглянемо обидва підходи в цій документації. Спершу подивімося, як викликати простий синхронний процес і оглянути його результат:

```php
use Illuminate\Support\Facades\Process;

$result = Process::run('ls -la');

return $result->output();
```

Звісно, екземпляр `Illuminate\Contracts\Process\ProcessResult`, який повертає метод `run`, пропонує низку корисних методів для огляду результату процесу:

```php
$result = Process::run('ls -la');

$result->command();
$result->successful();
$result->failed();
$result->output();
$result->errorOutput();
$result->exitCode();
```

<a name="throwing-exceptions"></a>
#### Викидання винятків

Якщо ви маєте результат процесу й хочете викинути екземпляр `Illuminate\Process\Exceptions\ProcessFailedException`, коли код виходу більший за нуль (що означає невдачу), скористайтеся методами `throw` та `throwIf`. Якщо процес не провалився, буде повернуто екземпляр `ProcessResult`:

```php
$result = Process::run('ls -la')->throw();

$result = Process::run('ls -la')->throwIf($condition);
```

<a name="process-options"></a>
### Опції процесу

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

<a name="working-directory-path"></a>
#### Шлях робочого каталогу

Метод `path` дозволяє вказати робочий каталог процесу. Якщо цей метод не викликано, процес успадкує робочий каталог PHP-скрипта, що виконується:

```php
$result = Process::path(__DIR__)->run('ls -la');
```

<a name="input"></a>
#### Ввід

Ви можете передати ввід через «стандартний ввід» процесу методом `input`:

```php
$result = Process::input('Hello World')->run('cat');
```

<a name="timeouts"></a>
#### Таймаути

За замовчуванням процеси викидають екземпляр `Illuminate\Process\Exceptions\ProcessTimedOutException` після понад 60 секунд виконання. Проте ви можете змінити цю поведінку методом `timeout`:

```php
$result = Process::timeout(120)->run('bash import.sh');
```

Методи `timeout` та `idleTimeout` також приймають екземпляри `CarbonInterval`:

```php
use function Illuminate\Support\minutes;

$result = Process::timeout(minutes(2))->run('bash import.sh');
```

Або ж, якщо ви хочете вимкнути таймаут процесу взагалі, викличте метод `forever`:

```php
$result = Process::forever()->run('bash import.sh');
```

Метод `idleTimeout` дозволяє вказати максимальну кількість секунд, які процес може працювати, не повертаючи жодного виводу:

```php
$result = Process::timeout(60)->idleTimeout(30)->run('bash import.sh');
```

<a name="environment-variables"></a>
#### Змінні середовища

Змінні середовища можна передати процесу методом `env`. Викликаний процес також успадкує всі змінні середовища, задані вашою системою:

```php
$result = Process::forever()
    ->env(['IMPORT_PATH' => __DIR__])
    ->run('bash import.sh');
```

Якщо ви хочете прибрати успадковану змінну середовища з викликаного процесу, передайте цій змінній значення `false`:

```php
$result = Process::forever()
    ->env(['LOAD_PATH' => false])
    ->run('bash import.sh');
```

<a name="tty-mode"></a>
#### Режим TTY

Метод `tty` дозволяє увімкнути для вашого процесу режим TTY. Режим TTY під'єднує ввід і вивід процесу до вводу й виводу вашої програми, дозволяючи процесу відкрити редактор на кшталт Vim чи Nano:

```php
Process::forever()->tty()->run('vim');
```

> [!WARNING]
> Режим TTY не підтримується у Windows.

<a name="process-output"></a>
### Вивід процесу

Як уже згадувалося, до виводу процесу можна звернутися методами `output` (stdout) та `errorOutput` (stderr) на результаті процесу:

```php
use Illuminate\Support\Facades\Process;

$result = Process::run('ls -la');

echo $result->output();
echo $result->errorOutput();
```

Проте вивід можна збирати й у реальному часі, передавши замикання другим аргументом методу `run`. Замикання отримає два аргументи: «тип» виводу (`stdout` чи `stderr`) і сам рядок виводу:

```php
$result = Process::run('ls -la', function (string $type, string $output) {
    echo $output;
});
```

Laravel також пропонує методи `seeInOutput` та `seeInErrorOutput`, які дають зручний спосіб визначити, чи містив вивід процесу заданий рядок:

```php
if (Process::run('ls -la')->seeInOutput('laravel')) {
    // ...
}
```

<a name="disabling-process-output"></a>
#### Вимкнення виводу процесу

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

```php
use Illuminate\Support\Facades\Process;

$result = Process::quietly()->run('bash import.sh');
```

<a name="process-pipelines"></a>
### Конвеєри

Інколи вам може захотітися зробити вивід одного процесу вводом іншого. Це часто називають «пайпінгом» виводу одного процесу в інший. Метод `pipe` фасаду `Process` спрощує це завдання. Метод `pipe` виконає процеси конвеєра синхронно й поверне результат останнього процесу в конвеєрі:

```php
use Illuminate\Process\Pipe;
use Illuminate\Support\Facades\Process;

$result = Process::pipe(function (Pipe $pipe) {
    $pipe->command('cat example.txt');
    $pipe->command('grep -i "laravel"');
});

if ($result->successful()) {
    // ...
}
```

Якщо вам не потрібно налаштовувати окремі процеси конвеєра, ви можете просто передати методу `pipe` масив рядків-команд:

```php
$result = Process::pipe([
    'cat example.txt',
    'grep -i "laravel"',
]);
```

Вивід процесу можна збирати в реальному часі, передавши замикання другим аргументом методу `pipe`. Замикання отримає два аргументи: «тип» виводу (`stdout` чи `stderr`) і сам рядок виводу:

```php
$result = Process::pipe(function (Pipe $pipe) {
    $pipe->command('cat example.txt');
    $pipe->command('grep -i "laravel"');
}, function (string $type, string $output) {
    echo $output;
});
```

Laravel також дозволяє призначати рядкові ключі кожному процесу конвеєра методом `as`. Цей ключ також буде передано до замикання виводу, переданого методу `pipe`, тож ви зможете визначити, якому процесу належить вивід:

```php
$result = Process::pipe(function (Pipe $pipe) {
    $pipe->as('first')->command('cat example.txt');
    $pipe->as('second')->command('grep -i "laravel"');
}, function (string $type, string $output, string $key) {
    // ...
});
```

<a name="asynchronous-processes"></a>
## Асинхронні процеси

Якщо метод `run` викликає процеси синхронно, то метод `start` дозволяє викликати процес асинхронно. Це дає вашому застосунку змогу виконувати інші задачі, доки процес працює у фоні. Щойно процес викликано, ви можете скористатися методом `running`, щоб визначити, чи він ще працює:

```php
$process = Process::timeout(120)->start('bash import.sh');

while ($process->running()) {
    // ...
}

$result = $process->wait();
```

Як ви могли помітити, ви можете викликати метод `wait`, щоб дочекатися завершення процесу й отримати екземпляр `ProcessResult`:

```php
$process = Process::timeout(120)->start('bash import.sh');

// ...

$result = $process->wait();
```

<a name="process-ids-and-signals"></a>
### ID процесів і сигнали

Метод `id` дозволяє дізнатися ID запущеного процесу, призначений операційною системою:

```php
$process = Process::start('bash import.sh');

return $process->id();
```

Метод `signal` дозволяє надіслати «сигнал» запущеному процесу. Список наперед визначених констант сигналів можна знайти в [документації PHP](https://www.php.net/manual/en/pcntl.constants.php):

```php
$process->signal(SIGUSR2);
```

<a name="asynchronous-process-output"></a>
### Вивід асинхронних процесів

Доки асинхронний процес працює, ви можете звернутися до всього його поточного виводу методами `output` та `errorOutput`; проте методи `latestOutput` і `latestErrorOutput` дозволяють отримати вивід процесу, що з'явився з моменту останнього отримання:

```php
$process = Process::timeout(120)->start('bash import.sh');

while ($process->running()) {
    echo $process->latestOutput();
    echo $process->latestErrorOutput();

    sleep(1);
}
```

Як і з методом `run`, вивід асинхронних процесів можна збирати в реальному часі, передавши замикання другим аргументом методу `start`. Замикання отримає два аргументи: «тип» виводу (`stdout` чи `stderr`) і сам рядок виводу:

```php
$process = Process::start('bash import.sh', function (string $type, string $output) {
    echo $output;
});

$result = $process->wait();
```

Замість чекати на завершення процесу, ви можете скористатися методом `waitUntil`, щоб припинити очікування на основі виводу процесу. Laravel перестане чекати на завершення процесу, коли замикання, передане методу `waitUntil`, поверне `true`:

```php
$process = Process::start('bash import.sh');

$process->waitUntil(function (string $type, string $output) {
    return $output === 'Ready...';
});
```

<a name="asynchronous-process-timeouts"></a>
### Таймаути асинхронних процесів

Доки асинхронний процес працює, ви можете перевірити, що він не вичерпав таймаут, методом `ensureNotTimedOut`. Цей метод викине [виняток таймауту](#timeouts), якщо процес його вичерпав:

```php
$process = Process::timeout(120)->start('bash import.sh');

while ($process->running()) {
    $process->ensureNotTimedOut();

    // ...

    sleep(1);
}
```

<a name="concurrent-processes"></a>
## Паралельні процеси

Laravel також дуже спрощує керування пулом паралельних асинхронних процесів, дозволяючи легко виконувати багато задач одночасно. Для початку викличте метод `pool`, який приймає замикання, що отримує екземпляр `Illuminate\Process\Pool`.

У цьому замиканні ви можете описати процеси, які належать пулу. Щойно пул процесів запущено методом `start`, ви можете звернутися до [колекції](/docs/{{version}}/collections) запущених процесів методом `running`:

```php
use Illuminate\Process\Pool;
use Illuminate\Support\Facades\Process;

$pool = Process::pool(function (Pool $pool) {
    $pool->path(__DIR__)->command('bash import-1.sh');
    $pool->path(__DIR__)->command('bash import-2.sh');
    $pool->path(__DIR__)->command('bash import-3.sh');
})->start(function (string $type, string $output, int $key) {
    // ...
});

while ($pool->running()->isNotEmpty()) {
    // ...
}

$results = $pool->wait();
```

Як бачите, ви можете дочекатися завершення всіх процесів пулу й отримати їхні результати методом `wait`. Метод `wait` повертає об'єкт із доступом як до масиву, що дозволяє звертатися до екземпляра `ProcessResult` кожного процесу пулу за його ключем:

```php
$results = $pool->wait();

echo $results[0]->output();
```

Або ж, для зручності, метод `concurrently` дозволяє запустити асинхронний пул процесів і одразу дочекатися його результатів. Це дає особливо виразний синтаксис у поєднанні з деструктуризацією масивів PHP:

```php
[$first, $second, $third] = Process::concurrently(function (Pool $pool) {
    $pool->path(__DIR__)->command('ls -la');
    $pool->path(app_path())->command('ls -la');
    $pool->path(storage_path())->command('ls -la');
});

echo $first->output();
```

<a name="naming-pool-processes"></a>
### Іменування процесів пулу

Звертатися до результатів пулу за числовим ключем не надто виразно; тому Laravel дозволяє призначати рядкові ключі кожному процесу пулу методом `as`. Цей ключ також буде передано до замикання, переданого методу `start`, тож ви зможете визначити, якому процесу належить вивід:

```php
$pool = Process::pool(function (Pool $pool) {
    $pool->as('first')->command('bash import-1.sh');
    $pool->as('second')->command('bash import-2.sh');
    $pool->as('third')->command('bash import-3.sh');
})->start(function (string $type, string $output, string $key) {
    // ...
});

$results = $pool->wait();

return $results['first']->output();
```

<a name="pool-process-ids-and-signals"></a>
### ID процесів пулу та сигнали

Оскільки метод `running` пулу процесів надає колекцію всіх викликаних у пулі процесів, ви можете легко дістати ID цих процесів:

```php
$processIds = $pool->running()->map->id();
```

А для зручності ви можете викликати на пулі процесів метод `signal`, щоб надіслати сигнал кожному процесу пулу:

```php
$pool->signal(SIGUSR2);
```

<a name="testing"></a>
## Тестування

Багато сервісів Laravel надають можливості, які допомагають легко й виразно писати тести, і сервіс процесів Laravel не виняток. Метод `fake` фасаду `Process` дозволяє сказати Laravel повертати підставні / фіктивні результати під час виклику процесів.

<a name="faking-processes"></a>
### Підміна процесів

Щоб дослідити можливості Laravel з підміни процесів, уявімо маршрут, який викликає процес:

```php
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Route;

Route::get('/import', function () {
    Process::run('bash import.sh');

    return 'Import complete!';
});
```

Тестуючи цей маршрут, ми можемо сказати Laravel повертати фейковий успішний результат для кожного викликаного процесу, викликавши метод `fake` фасаду `Process` без аргументів. Ба більше, ми можемо навіть [перевірити](#available-assertions), що заданий процес було «запущено»:

```php tab=Pest
<?php

use Illuminate\Contracts\Process\ProcessResult;
use Illuminate\Process\PendingProcess;
use Illuminate\Support\Facades\Process;

test('process is invoked', function () {
    Process::fake();

    $response = $this->get('/import');

    // Simple process assertion...
    Process::assertRan('bash import.sh');

    // Or, inspecting the process configuration...
    Process::assertRan(function (PendingProcess $process, ProcessResult $result) {
        return $process->command === 'bash import.sh' &&
               $process->timeout === 60;
    });
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Illuminate\Contracts\Process\ProcessResult;
use Illuminate\Process\PendingProcess;
use Illuminate\Support\Facades\Process;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_process_is_invoked(): void
    {
        Process::fake();

        $response = $this->get('/import');

        // Simple process assertion...
        Process::assertRan('bash import.sh');

        // Or, inspecting the process configuration...
        Process::assertRan(function (PendingProcess $process, ProcessResult $result) {
            return $process->command === 'bash import.sh' &&
                   $process->timeout === 60;
        });
    }
}
```

Як уже зазначалося, виклик методу `fake` фасаду `Process` змусить Laravel завжди повертати успішний результат процесу без виводу. Проте ви легко можете задати вивід і код виходу підмінених процесів методом `result` фасаду `Process`:

```php
Process::fake([
    '*' => Process::result(
        output: 'Test output',
        errorOutput: 'Test error output',
        exitCode: 1,
    ),
]);
```

<a name="faking-specific-processes"></a>
### Підміна конкретних процесів

Як ви могли помітити в попередньому прикладі, фасад `Process` дозволяє задавати різні фейкові результати для різних процесів, передавши методу `fake` масив.

Ключі масиву мають бути шаблонами команд, які ви хочете підмінити, а значення - відповідними результатами. Символ `*` можна використовувати як підстановку. Будь-які команди процесів, які не підмінено, буде викликано насправді. Щоб побудувати підставні / фейкові результати для цих команд, скористайтеся методом `result` фасаду `Process`:

```php
Process::fake([
    'cat *' => Process::result(
        output: 'Test "cat" output',
    ),
    'ls *' => Process::result(
        output: 'Test "ls" output',
    ),
]);
```

Якщо вам не потрібно налаштовувати код виходу чи вивід помилок підміненого процесу, вам може бути зручніше задати фейкові результати простими рядками:

```php
Process::fake([
    'cat *' => 'Test "cat" output',
    'ls *' => 'Test "ls" output',
]);
```

<a name="faking-process-sequences"></a>
### Підміна послідовностей процесів

Якщо код, який ви тестуєте, викликає кілька процесів з однаковою командою, ви можете захотіти призначити кожному виклику інший фейковий результат. Це робиться методом `sequence` фасаду `Process`:

```php
Process::fake([
    'ls *' => Process::sequence()
        ->push(Process::result('First invocation'))
        ->push(Process::result('Second invocation')),
]);
```

<a name="faking-asynchronous-process-lifecycles"></a>
### Підміна життєвого циклу асинхронних процесів

Досі ми переважно обговорювали підміну процесів, які викликаються синхронно методом `run`. Проте, якщо ви намагаєтеся тестувати код, який працює з асинхронними процесами, запущеними через `start`, вам може знадобитися складніший підхід до опису фейкових процесів.

Наприклад, уявімо такий маршрут, який працює з асинхронним процесом:

```php
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;

Route::get('/import', function () {
    $process = Process::start('bash import.sh');

    while ($process->running()) {
        Log::info($process->latestOutput());
        Log::info($process->latestErrorOutput());
    }

    return 'Done';
});
```

Щоб коректно підмінити цей процес, нам потрібно описати, скільки разів метод `running` має повертати `true`. Крім того, ми можемо хотіти вказати кілька рядків виводу, які слід повертати послідовно. Для цього ми можемо скористатися методом `describe` фасаду `Process`:

```php
Process::fake([
    'bash import.sh' => Process::describe()
        ->output('First line of standard output')
        ->errorOutput('First line of error output')
        ->output('Second line of standard output')
        ->exitCode(0)
        ->iterations(3),
]);
```

Розберімо приклад вище. Методами `output` та `errorOutput` ми можемо задати кілька рядків виводу, які повертатимуться послідовно. Метод `exitCode` дозволяє задати фінальний код виходу фейкового процесу. Нарешті, метод `iterations` дозволяє вказати, скільки разів метод `running` має повертати `true`.

<a name="available-assertions"></a>
### Доступні перевірки

Як [зазначалося раніше](#faking-processes), Laravel надає кілька перевірок процесів для ваших функціональних тестів. Розгляньмо кожну з них нижче.

<a name="assert-process-ran"></a>
#### assertRan

Перевірити, що заданий процес було викликано:

```php
use Illuminate\Support\Facades\Process;

Process::assertRan('ls -la');
```

Коли процес було викликано з масивом аргументів, ви можете передати той самий масив до перевірки:

```php
Process::assertRan(['php', 'artisan', 'migrate']);
```

Методи `assertRanTimes` і `assertDidntRun` також приймають команди у вигляді масиву.

Метод `assertRan` також приймає замикання, яке отримає екземпляр процесу та результат процесу, дозволяючи оглянути налаштовані опції процесу. Якщо це замикання поверне `true`, перевірка «пройде»:

```php
Process::assertRan(fn ($process, $result) =>
    $process->command === 'ls -la' &&
    $process->path === __DIR__ &&
    $process->timeout === 60
);
```

`$process`, переданий до замикання `assertRan`, є екземпляром `Illuminate\Process\PendingProcess`, а `$result` - екземпляром `Illuminate\Contracts\Process\ProcessResult`.

<a name="assert-process-didnt-run"></a>
#### assertDidntRun

Перевірити, що заданий процес не було викликано:

```php
use Illuminate\Support\Facades\Process;

Process::assertDidntRun('ls -la');
```

Як і метод `assertRan`, метод `assertDidntRun` також приймає замикання, яке отримає екземпляр процесу та результат процесу, дозволяючи оглянути налаштовані опції процесу. Якщо це замикання поверне `true`, перевірка «провалиться»:

```php
Process::assertDidntRun(fn (PendingProcess $process, ProcessResult $result) =>
    $process->command === 'ls -la'
);
```

<a name="assert-process-ran-times"></a>
#### assertRanTimes

Перевірити, що заданий процес було викликано задану кількість разів:

```php
use Illuminate\Support\Facades\Process;

Process::assertRanTimes('ls -la', times: 3);
```

Метод `assertRanTimes` також приймає замикання, яке отримає екземпляри `PendingProcess` та `ProcessResult`, дозволяючи оглянути налаштовані опції процесу. Якщо це замикання поверне `true` і процес було викликано вказану кількість разів, перевірка «пройде»:

```php
Process::assertRanTimes(function (PendingProcess $process, ProcessResult $result) {
    return $process->command === 'ls -la';
}, times: 3);
```

<a name="assert-processes-ran-in-order"></a>
#### assertRanInOrder

Перевірте, що процеси були викликані в заданому порядку:

```php
Process::assertRanInOrder([
    'git fetch',
    'composer install',
]);
```

Метод `assertRanInOrder` приймає рядки команд, масиви аргументів команд або замикання, як і інші твердження процесів.

<a name="preventing-stray-processes"></a>
### Запобігання «блукаючим» процесам

Якщо ви хочете переконатися, що всі викликані процеси підмінено в окремому тесті чи в усьому наборі тестів, викличте метод `preventStrayProcesses`. Після виклику цього методу будь-які процеси, для яких немає відповідного фейкового результату, викидатимуть виняток замість того, щоб запускати справжній процес:

```php
use Illuminate\Support\Facades\Process;

Process::preventStrayProcesses();

Process::fake([
    'ls *' => 'Test output...',
]);

// Fake response is returned...
Process::run('ls -la');

// An exception is thrown...
Process::run('bash import.sh');
```

---

Джерело: https://laravelukraine.com/docs/13.x/queues

# Черги

- [Вступ](#introduction)
    - [Підключення проти черг](#connections-vs-queues)
    - [Нотатки та передумови драйверів](#driver-prerequisites)
- [Створення завдань](#creating-jobs)
    - [Генерація класів завдань](#generating-job-classes)
    - [Структура класу](#class-structure)
    - [Унікальні завдання](#unique-jobs)
    - [Завдання з дебаунсом](#debounced-jobs)
    - [Зашифровані завдання](#encrypted-jobs)
- [Middleware завдань](#job-middleware)
    - [Обмеження частоти](#rate-limiting)
    - [Запобігання накладанню завдань](#preventing-job-overlaps)
    - [Тротлінг винятків](#throttling-exceptions)
    - [Звільнення завдань](#releasing-jobs)
    - [Пропуск завдань](#skipping-jobs)
- [Диспетчеризація завдань](#dispatching-jobs)
    - [Відкладена диспетчеризація](#delayed-dispatching)
    - [Синхронна диспетчеризація](#synchronous-dispatching)
    - [Масова диспетчеризація](#bulk-dispatching)
    - [Підготовка завдань перед диспетчеризацією](#preparing-jobs-before-dispatch)
    - [Завдання й транзакції бази даних](#jobs-and-database-transactions)
    - [Ланцюжки завдань](#job-chaining)
    - [Налаштування черги та підключення](#customizing-the-queue-and-connection)
    - [Задання максимуму спроб / таймауту](#max-job-attempts-and-timeout)
    - [SQS FIFO та справедливі черги](#sqs-fifo-and-fair-queues)
    - [Failover черги](#queue-failover)
    - [Обробка помилок](#error-handling)
- [Пакети завдань](#job-batching)
    - [Опис пакетованих завдань](#defining-batchable-jobs)
    - [Диспетчеризація пакетів](#dispatching-batches)
    - [Ланцюжки й пакети](#chains-and-batches)
    - [Додавання завдань до пакетів](#adding-jobs-to-batches)
    - [Огляд пакетів](#inspecting-batches)
    - [Скасування пакетів](#cancelling-batches)
    - [Невдачі пакетів](#batch-failures)
    - [Очищення пакетів](#pruning-batches)
    - [Зберігання пакетів у DynamoDB](#storing-batches-in-dynamodb)
- [Замикання в черзі](#queueing-closures)
- [Запуск воркера черги](#running-the-queue-worker)
    - [Команда `queue:work`](#the-queue-work-command)
    - [Пріоритети черг](#queue-priorities)
    - [Воркери черги й розгортання](#queue-workers-and-deployment)
    - [Реакція на сигнали воркера](#reacting-to-worker-signals)
    - [Спливання й таймаути завдань](#job-expirations-and-timeouts)
    - [Призупинення й поновлення воркерів черги](#pausing-and-resuming-queue-workers)
- [Конфігурація Supervisor](#supervisor-configuration)
- [Робота з невдалими завданнями](#dealing-with-failed-jobs)
    - [Прибирання після невдалих завдань](#cleaning-up-after-failed-jobs)
    - [Повторний запуск невдалих завдань](#retrying-failed-jobs)
    - [Ігнорування відсутніх моделей](#ignoring-missing-models)
    - [Очищення невдалих завдань](#pruning-failed-jobs)
    - [Зберігання невдалих завдань у DynamoDB](#storing-failed-jobs-in-dynamodb)
    - [Вимкнення зберігання невдалих завдань](#disabling-failed-job-storage)
    - [Події невдалих завдань](#failed-job-events)
- [Очищення черг від завдань](#clearing-jobs-from-queues)
- [Моніторинг черг](#monitoring-your-queues)
- [Тестування](#testing)
    - [Підміна частини завдань](#faking-a-subset-of-jobs)
    - [Тестування ланцюжків завдань](#testing-job-chains)
    - [Тестування пакетів завдань](#testing-job-batches)
    - [Тестування взаємодії завдання з чергою](#testing-job-queue-interactions)
- [Події завдань](#job-events)

<a name="introduction"></a>
## Вступ

Створюючи вебзастосунок, ви можете мати задачі - як-от розбір і збереження завантаженого CSV-файлу, - які виконуються надто довго для звичайного вебзапиту. На щастя, Laravel дозволяє легко створювати завдання в черзі, які обробляються у фоні. Перенісши тривалі задачі до черги, ваш застосунок відповідатиме на вебзапити блискавично й дасть клієнтам кращий досвід.

Черги Laravel надають єдиний API для черг поверх різних бекендів - [Amazon SQS](https://aws.amazon.com/sqs/), [Redis](https://redis.io) чи навіть реляційної бази даних.

Опції конфігурації черг Laravel зберігаються у файлі `config/queue.php` вашого застосунку. У цьому файлі ви знайдете конфігурації підключень для кожного драйвера черги, що входить до фреймворку, - database, [Amazon SQS](https://aws.amazon.com/sqs/), [Redis](https://redis.io) та [Beanstalkd](https://beanstalkd.github.io/), - а також синхронний драйвер, який виконує завдання негайно (для розробки чи тестування). Є й драйвер `null`, який відкидає завдання з черги.

> [!NOTE]
> Laravel Horizon - гарна панель керування й система конфігурації для ваших черг на Redis. Докладніше читайте в повній [документації Horizon](/docs/{{version}}/horizon).

<a name="connections-vs-queues"></a>
### Підключення проти черг

Перш ніж братися до черг Laravel, важливо зрозуміти різницю між «підключеннями» та «чергами». У файлі конфігурації `config/queue.php` є масив `connections`. Ця опція описує підключення до бекендів черг - на кшталт Amazon SQS, Beanstalk чи Redis. Проте кожне підключення може мати кілька «черг», які можна уявляти як різні стоси чи купи завдань.

Зверніть увагу: кожен приклад конфігурації підключення у файлі `queue` містить атрибут `queue`. Це черга за замовчуванням, до якої потраплятимуть завдання, надіслані цьому підключенню. Іншими словами, якщо ви диспетчеризуєте завдання, явно не вказавши черги, воно потрапить до черги, заданої атрибутом `queue` конфігурації підключення:

```php
use App\Jobs\ProcessPodcast;

// This job is sent to the default connection's default queue...
ProcessPodcast::dispatch();

// This job is sent to the default connection's "emails" queue...
ProcessPodcast::dispatch()->onQueue('emails');
```

Деяким застосункам ніколи не знадобиться класти завдання в кілька черг - їм досить однієї простої черги. Проте кілька черг особливо корисні застосункам, які хочуть пріоритезувати чи сегментувати обробку завдань, адже воркер черги Laravel дозволяє вказати, які черги й у якому пріоритеті йому обробляти. Наприклад, якщо ви кладете завдання в чергу `high`, ви можете запустити воркер, який надасть їм вищий пріоритет обробки:

```shell
php artisan queue:work --queue=high,default
```

<a name="driver-prerequisites"></a>
### Нотатки та передумови драйверів

<a name="database"></a>
#### Database

Щоб скористатися драйвером черги `database`, вам потрібна таблиця для зберігання завдань. Зазвичай вона входить до стандартної [міграції](/docs/{{version}}/migrations) Laravel `0001_01_01_000002_create_jobs_table.php`; проте, якщо у вашому застосунку цієї міграції немає, створити її можна командою Artisan `make:queue-table`:

```shell
php artisan make:queue-table

php artisan migrate
```

<a name="redis"></a>
#### Redis

Щоб скористатися драйвером черги `redis`, налаштуйте підключення до бази Redis у файлі конфігурації `config/database.php`.

> [!WARNING]
> Опції Redis `serializer` та `compression` драйвер черги `redis` не підтримує.

<a name="redis-cluster"></a>
##### Redis Cluster

Якщо ваше підключення черги Redis використовує [Redis Cluster](https://redis.io/docs/latest/operate/rs/databases/durability-ha/clustering), назви ваших черг мають містити [хеш-тег ключа](https://redis.io/docs/latest/develop/using-commands/keyspace/#hashtags). Це потрібно, щоб усі ключі Redis для конкретної черги потрапили в один хеш-слот:

```php
'redis' => [
    'driver' => 'redis',
    'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
    'queue' => env('REDIS_QUEUE', '{default}'),
    'retry_after' => env('REDIS_QUEUE_RETRY_AFTER', 90),
    'block_for' => null,
    'after_commit' => false,
],
```

<a name="blocking"></a>
##### Блокування

Користуючись чергою Redis, ви можете скористатися опцією конфігурації `block_for`, щоб указати, як довго драйвер має чекати на появу завдання, перш ніж пройти цикл воркера й знову опитати базу Redis.

Підбір цього значення під ваше навантаження може бути ефективнішим, ніж постійне опитування Redis на предмет нових завдань. Наприклад, ви можете задати значення `5`, щоб драйвер блокувався на п'ять секунд, чекаючи на завдання:

```php
'redis' => [
    'driver' => 'redis',
    'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
    'queue' => env('REDIS_QUEUE', 'default'),
    'retry_after' => env('REDIS_QUEUE_RETRY_AFTER', 90),
    'block_for' => 5,
    'after_commit' => false,
],
```

> [!WARNING]
> Значення `block_for`, рівне `0`, змусить воркери черги блокуватися нескінченно, доки не з'явиться завдання. Це також завадить обробляти сигнали на кшталт `SIGTERM`, доки не буде оброблено наступне завдання.

<a name="sqs-overflow-storage"></a>
#### Сховище переповнення SQS

Amazon SQS обмежує максимальний розмір даних повідомлення в черзі. Якщо вам потрібно диспетчеризувати завдання з даними, які можуть перевищити цей ліміт, ви можете налаштувати Laravel зберігати завеликі дані SQS у сховищі кешу й надсилати через SQS лише вказівник. Щоб увімкнути цю можливість, додайте масив `overflow` до конфігурації підключення черги SQS:

```php
'sqs' => [
    'driver' => 'sqs',
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
    'queue' => env('SQS_QUEUE', 'default'),
    'suffix' => env('SQS_SUFFIX'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    'after_commit' => false,
    'overflow' => [
        'enabled' => env('SQS_OVERFLOW_ENABLED', false),
        'store' => env('SQS_OVERFLOW_STORE'),
        'always' => false,
        'delete_after_processing' => true,
        'flush_on_clear' => env('SQS_OVERFLOW_FLUSH_ON_CLEAR', false),
    ],
],
```

Коли сховище переповнення увімкнено, Laravel зберігатиме в налаштованому сховищі кешу дані розміром від 1 МБ. Якщо опція `always` має значення `true`, у сховищі кешу опинятимуться всі дані SQS, незалежно від розміру. Оскільки завданням у черзі доведеться діставати свої дані зі сховища кешу під час обробки, обирайте сховище, яке здатне зберігати ці дані, доки ваші воркери їх не обробили. За замовчуванням збережені дані видаляються після успішної обробки завдань і їх видалення з SQS.

Якщо опція `flush_on_clear` має значення `true`, налаштоване сховище кешу переповнення буде очищено, коли команда `queue:clear` очищає чергу SQS. Оскільки очищення сховища кешу може вилучити з нього всі елементи, вмикаючи цю опцію, налаштуйте для сховища переповнення SQS окреме сховище кешу.

<a name="other-driver-prerequisites"></a>
#### Передумови інших драйверів

Для перелічених драйверів черг потрібні такі залежності. Їх можна встановити через менеджер пакетів Composer:

<div class="content-list" markdown="1">

- Amazon SQS: `aws/aws-sdk-php ~3.0`
- Beanstalkd: `pda/pheanstalk ^7.0|^8.0`
- Redis: `predis/predis ~3.0` або PHP-розширення phpredis
- [MongoDB](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/queues/): `mongodb/laravel-mongodb`

</div>

<a name="creating-jobs"></a>
## Створення завдань

<a name="generating-job-classes"></a>
### Генерація класів завдань

За замовчуванням усі завдання для черги зберігаються в каталозі `app/Jobs`. Якщо каталогу `app/Jobs` не існує, його буде створено, коли ви виконаєте команду Artisan `make:job`:

```shell
php artisan make:job ProcessPodcast
```

Згенерований клас реалізує інтерфейс `Illuminate\Contracts\Queue\ShouldQueue`, повідомляючи Laravel, що завдання слід покласти в чергу для асинхронного виконання.

> [!NOTE]
> Стаби завдань можна налаштувати через [публікацію стабів](/docs/{{version}}/artisan#stub-customization).

<a name="class-structure"></a>
### Структура класу

Класи завдань дуже прості: зазвичай вони містять лише метод `handle`, який викликається під час обробки завдання чергою. Для початку погляньмо на приклад класу завдання. У цьому прикладі уявімо, що ми керуємо сервісом публікації подкастів і маємо обробляти завантажені файли подкастів перед публікацією:

```php
<?php

namespace App\Jobs;

use App\Models\Podcast;
use App\Services\AudioProcessor;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;

class ProcessPodcast implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new job instance.
     */
    public function __construct(
        public Podcast $podcast,
    ) {}

    /**
     * Execute the job.
     */
    public function handle(AudioProcessor $processor): void
    {
        // Process uploaded podcast...
    }
}
```

Зверніть увагу: у цьому прикладі ми змогли передати [модель Eloquent](/docs/{{version}}/eloquent) прямо в конструктор завдання. Завдяки трейту `Queueable`, який використовує завдання, моделі Eloquent та їхні завантажені зв'язки коректно серіалізуються й десеріалізуються під час обробки завдання.

Якщо ваше завдання в черзі приймає модель Eloquent у конструкторі, у чергу серіалізується лише її ідентифікатор. Коли завдання справді обробляється, система черг автоматично дістане з бази повний екземпляр моделі та її завантажені зв'язки. Такий підхід до серіалізації моделей дозволяє надсилати драйверу черги значно менші дані завдань.

<a name="handle-method-dependency-injection"></a>
#### Впровадження залежностей у метод `handle`

Метод `handle` викликається, коли завдання обробляє черга. Зверніть увагу: ми можемо вказати типи залежностей у методі `handle` завдання. [Сервіс-контейнер](/docs/{{version}}/container) Laravel автоматично їх впровадить.

Якщо ви хочете повністю контролювати, як контейнер впроваджує залежності в метод `handle`, скористайтеся методом контейнера `bindMethod`. Метод `bindMethod` приймає колбек, який отримує завдання й контейнер. У колбеку ви вільні викликати метод `handle` як заманеться. Зазвичай цей метод викликають у методі `boot` [сервіс-провайдера](/docs/{{version}}/providers) `App\Providers\AppServiceProvider`:

```php
use App\Jobs\ProcessPodcast;
use App\Services\AudioProcessor;
use Illuminate\Contracts\Foundation\Application;

$this->app->bindMethod([ProcessPodcast::class, 'handle'], function (ProcessPodcast $job, Application $app) {
    return $job->handle($app->make(AudioProcessor::class));
});
```

> [!WARNING]
> Бінарні дані - наприклад, сирий вміст зображень - слід пропускати через функцію `base64_encode`, перш ніж передавати завданню в черзі. Інакше завдання може некоректно серіалізуватися в JSON під час потрапляння в чергу.

<a name="handling-relationships"></a>
#### Зв'язки в черзі

Оскільки всі завантажені зв'язки моделей Eloquent теж серіалізуються, коли завдання потрапляє в чергу, серіалізований рядок завдання інколи стає доволі великим. Ба більше, коли завдання десеріалізується й зв'язки моделей дістаються з бази наново, вони дістаються повністю. Будь-які обмеження зв'язку, застосовані до серіалізації моделі під час постановки завдання в чергу, після десеріалізації вже не діятимуть. Тому, якщо ви хочете працювати з підмножиною зв'язку, обмежте його заново всередині свого завдання.

Або ж, щоб зв'язки не серіалізувалися, викличте на моделі метод `withoutRelations`, задаючи значення властивості. Цей метод поверне екземпляр моделі без завантажених зв'язків:

```php
/**
 * Create a new job instance.
 */
public function __construct(
    Podcast $podcast,
) {
    $this->podcast = $podcast->withoutRelations();
}
```

Якщо вам потрібно вилучити лише певні зв'язки, лишивши решту, скористайтеся методом `withoutRelation`:

```php
$this->podcast = $podcast->withoutRelation('comments');
```

Якщо ви користуєтеся [просуванням властивостей конструктора PHP](https://www.php.net/manual/en/language.oop5.decon.php#language.oop5.decon.constructor.promotion) і хочете вказати, що зв'язки моделі Eloquent не слід серіалізувати, скористайтеся атрибутом `WithoutRelations`:

```php
use Illuminate\Queue\Attributes\WithoutRelations;

/**
 * Create a new job instance.
 */
public function __construct(
    #[WithoutRelations]
    public Podcast $podcast,
) {}
```

Для зручності, якщо ви хочете серіалізувати всі моделі без зв'язків, застосуйте атрибут `WithoutRelations` до всього класу, а не до кожної моделі окремо:

```php
<?php

namespace App\Jobs;

use App\Models\DistributionPlatform;
use App\Models\Podcast;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\Attributes\WithoutRelations;

#[WithoutRelations]
class ProcessPodcast implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new job instance.
     */
    public function __construct(
        public Podcast $podcast,
        public DistributionPlatform $platform,
    ) {}
}
```

Якщо завдання отримує колекцію чи масив моделей Eloquent замість однієї моделі, зв'язки моделей у цій колекції не буде відновлено під час десеріалізації та виконання завдання. Це запобігає надмірному споживанню ресурсів у завданнях, які працюють з великою кількістю моделей.

<a name="unique-jobs"></a>
### Унікальні завдання

> [!WARNING]
> Унікальні завдання потребують драйвера кешу, який підтримує [блокування](/docs/{{version}}/cache#atomic-locks). Наразі атомарні блокування підтримують драйвери кешу `memcached`, `redis`, `dynamodb`, `database`, `file` та `array`.

> [!WARNING]
> Обмеження унікальності не діють для завдань усередині пакетів.

Інколи вам може знадобитися гарантувати, що в черзі одночасно перебуває лише один екземпляр конкретного завдання. Для цього реалізуйте у класі завдання інтерфейс `ShouldBeUnique`. Цей інтерфейс не вимагає описувати у класі жодних додаткових методів:

```php
<?php

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUnique;

class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
    // ...
}
```

У прикладі вище завдання `UpdateSearchIndex` є унікальним. Тож його не буде диспетчеризовано, якщо інший його екземпляр уже в черзі й не завершив обробку.

У певних випадках вам може знадобитися задати конкретний «ключ», який робить завдання унікальним, або вказати таймаут, після якого завдання перестає бути унікальним. Для цього скористайтеся атрибутом `UniqueFor` і опишіть у класі завдання метод `uniqueId`:

```php
<?php

namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Queue\Attributes\UniqueFor;

#[UniqueFor(3600)]
class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
    /**
     * The product instance.
     *
     * @var \App\Models\Product
     */
    public $product;

    /**
     * Get the unique ID for the job.
     */
    public function uniqueId(): string
    {
        return $this->product->id;
    }
}
```
У прикладі вище завдання `UpdateSearchIndex` унікальне за ID продукту. Тож будь-які нові диспетчеризації завдання з тим самим ID продукту ігноруватимуться, доки наявне завдання не завершить обробку. Крім того, якщо наявне завдання не оброблено протягом години, унікальне блокування буде звільнено, і в чергу можна буде диспетчеризувати інше завдання з тим самим унікальним ключем.

> [!WARNING]
> Якщо ваш застосунок диспетчеризує завдання з кількох вебсерверів чи контейнерів, подбайте, щоб усі сервери спілкувалися з одним центральним сервером кешу, - тоді Laravel зможе точно визначити, чи є завдання унікальним.

<a name="keeping-jobs-unique-until-processing-begins"></a>
#### Тримати завдання унікальними до початку обробки

За замовчуванням унікальні завдання «розблоковуються» після того, як завдання завершить обробку або вичерпає всі спроби. Проте бувають ситуації, коли ви хочете, щоб завдання розблоковувалося безпосередньо перед обробкою. Для цього ваше завдання має реалізувати контракт `ShouldBeUniqueUntilProcessing` замість `ShouldBeUnique`:

```php
<?php

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;

class UpdateSearchIndex implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
    // ...
}
```

<a name="unique-job-locks"></a>
#### Блокування унікальних завдань

Під капотом, коли диспетчеризується завдання `ShouldBeUnique`, Laravel намагається отримати [блокування](/docs/{{version}}/cache#atomic-locks) з ключем `uniqueId`. Якщо блокування вже утримується, завдання не диспетчеризують. Це блокування звільняється, коли завдання завершує обробку або вичерпує всі спроби. За замовчуванням Laravel використовує для цього блокування драйвер кешу за замовчуванням. Проте, якщо ви хочете отримувати блокування іншим драйвером, опишіть метод `uniqueVia`, який поверне потрібний драйвер кешу:

```php
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Facades\Cache;

class UpdateSearchIndex implements ShouldQueue, ShouldBeUnique
{
    // ...

    /**
     * Get the cache driver for the unique job lock.
     */
    public function uniqueVia(): Repository
    {
        return Cache::driver('redis');
    }
}
```

> [!NOTE]
> Якщо вам потрібно лише обмежити одночасну обробку завдання, скористайтеся натомість middleware завдання [WithoutOverlapping](/docs/{{version}}/queues#preventing-job-overlaps).

<a name="debounced-jobs"></a>
### Завдання з дебаунсом

Інколи вам може знадобитися гарантувати, що коли те саме завдання диспетчеризують багато разів за короткий проміжок, виконається лише остання диспетчеризація. Для цього додайте до свого завдання атрибут `DebounceFor`:

```php
<?php

namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\Attributes\DebounceFor;

#[DebounceFor(30)]
class UpdateSearchIndex implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new job instance.
     */
    public function __construct(public int $productId)
    {
    }

    /**
     * Get the debounce ID for the job.
     */
    public function debounceId(): string
    {
        return (string) $this->productId;
    }
}
```

У прикладі вище повторні диспетчеризації `UpdateSearchIndex` для того самого продукту протягом `30` секунд дебаунсуватимуть завдання так, що виконається лише остання.

Якщо ви хочете обмежити, як довго можна відкладати завдання, яке часто передиспетчеризують, передайте атрибуту `DebounceFor` аргумент `maxWait`:

```php
#[DebounceFor(30, maxWait: 120)]
class UpdateSearchIndex implements ShouldQueue
{
    use Queueable;

    // ...
}
```

Ви можете змінити сховище кешу, яке використовується для відстеження дебаунсу, описавши у своєму завданні метод `debounceVia`:

```php
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Facades\Cache;

public function debounceVia(): Repository
{
    return Cache::driver('redis');
}
```

Якщо завдання з дебаунсом витіснено новішою диспетчеризацією, Laravel диспетчеризує подію `Illuminate\Queue\Events\JobDebounced` і вилучить витіснене завдання з черги.

> [!WARNING]
> Завдання з дебаунсом і унікальні завдання взаємно виключні. Завдання з атрибутом `DebounceFor` не має реалізовувати `ShouldBeUnique`.

> [!WARNING]
> Якщо ваш застосунок диспетчеризує завдання з дебаунсом з кількох вебсерверів чи контейнерів, подбайте, щоб усі сервери спілкувалися з одним центральним сервером кешу.

<a name="encrypted-jobs"></a>
### Зашифровані завдання

Laravel дозволяє забезпечити приватність і цілісність даних завдання за допомогою [шифрування](/docs/{{version}}/encryption). Для початку просто додайте до класу завдання інтерфейс `ShouldBeEncrypted`. Щойно цей інтерфейс додано до класу, Laravel автоматично зашифрує ваше завдання перед тим, як покласти його в чергу:

```php
<?php

use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;

class UpdateSearchIndex implements ShouldQueue, ShouldBeEncrypted
{
    // ...
}
```

<a name="job-middleware"></a>
## Middleware завдань

Middleware завдань дозволяє огорнути виконання завдань у черзі власною логікою, зменшивши кількість шаблонного коду в самих завданнях. Наприклад, погляньте на метод `handle` нижче, який використовує можливості обмеження частоти через Redis, щоб дозволити обробляти лише одне завдання кожні п'ять секунд:

```php
use Illuminate\Support\Facades\Redis;

/**
 * Execute the job.
 */
public function handle(): void
{
    Redis::throttle('key')->block(0)->allow(1)->every(5)->then(function () {
        info('Lock obtained...');

        // Handle job...
    }, function () {
        // Could not obtain lock...

        return $this->release(5);
    });
}
```

Хоч цей код і робочий, реалізація методу `handle` стає галасливою, бо захаращена логікою обмеження частоти через Redis. Ба більше, цю логіку доведеться дублювати в кожному завданні, частоту якого ми хочемо обмежити. Замість обмежувати частоту в методі handle, ми могли б описати middleware завдання, який цим займеться:

```php
<?php

namespace App\Jobs\Middleware;

use Closure;
use Illuminate\Support\Facades\Redis;

class RateLimited
{
    /**
     * Process the queued job.
     *
     * @param  \Closure(object): void  $next
     */
    public function handle(object $job, Closure $next): void
    {
        Redis::throttle('key')
            ->block(0)->allow(1)->every(5)
            ->then(function () use ($job, $next) {
                // Lock obtained...

                $next($job);
            }, function () use ($job) {
                // Could not obtain lock...

                $job->release(5);
            });
    }
}
```

Як бачите, подібно до [middleware маршрутів](/docs/{{version}}/middleware), middleware завдань отримує завдання, яке обробляється, і колбек, який слід викликати, щоб продовжити обробку.

Згенерувати новий клас middleware завдання можна командою Artisan `make:job-middleware`. Створивши middleware завдання, ви можете причепити його до завдання, повернувши з методу `middleware` завдання. Цього методу немає в завданнях, згенерованих командою Artisan `make:job`, тож вам доведеться додати його до свого класу вручну:

```php
use App\Jobs\Middleware\RateLimited;

/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [new RateLimited];
}
```

> [!NOTE]
> Middleware завдань можна також призначати [слухачам подій у черзі](/docs/{{version}}/events#queued-event-listeners), [mailable-класам](/docs/{{version}}/mail#queueing-mail) та [сповіщенням](/docs/{{version}}/notifications#queueing-notifications).

<a name="rate-limiting"></a>
### Обмеження частоти

Хоч ми щойно показали, як написати власний middleware обмеження частоти, Laravel насправді містить готовий middleware, яким можна обмежувати частоту завдань. Як і [обмежувачі частоти маршрутів](/docs/{{version}}/routing#defining-rate-limiters), обмежувачі частоти завдань описують методом `for` фасаду `RateLimiter`.

Наприклад, ви можете хотіти дозволити користувачам робити резервну копію своїх даних раз на годину, не накладаючи такого обмеження на преміум-клієнтів. Для цього опишіть `RateLimiter` у методі `boot` вашого `AppServiceProvider`:

```php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    RateLimiter::for('backups', function (object $job) {
        return $job->user->vipCustomer()
            ? Limit::none()
            : Limit::perHour(1)->by($job->user->id);
    });
}
```

У прикладі вище ми описали погодинне обмеження; проте ви легко можете описати обмеження за хвилинами методом `perMinute`. Крім того, методу `by` обмеження можна передати будь-яке значення; проте найчастіше його використовують, щоб сегментувати обмеження за клієнтом:

```php
return Limit::perMinute(50)->by($job->user->id);
```

Описавши обмеження, причепіть обмежувач до свого завдання через middleware `Illuminate\Queue\Middleware\RateLimited`. Щоразу, коли завдання перевищить обмеження, цей middleware поверне його до черги з відповідною затримкою на основі тривалості обмеження:

```php
use Illuminate\Queue\Middleware\RateLimited;

/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [new RateLimited('backups')];
}
```

Повернення обмеженого за частотою завдання до черги все одно збільшує загальну кількість його спроб `attempts`. Тож вам, можливо, варто відповідно налаштувати атрибути `Tries` і `MaxExceptions` у класі завдання. Або ж скористатися [методом retryUntil](#time-based-attempts), щоб задати час, після якого спроби виконати завдання припиняються.

Методом `releaseAfter` ви можете вказати кількість секунд, які мають минути, перш ніж повернене завдання спробують виконати знову:

```php
/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new RateLimited('backups'))->releaseAfter(60)];
}
```

Якщо ви не хочете, щоб обмежене за частотою завдання повторювали, скористайтеся методом `dontRelease`:

```php
/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new RateLimited('backups'))->dontRelease()];
}
```

<a name="rate-limiting-with-redis"></a>
#### Обмеження частоти через Redis

Якщо ви користуєтеся Redis, скористайтеся middleware `Illuminate\Queue\Middleware\RateLimitedWithRedis`, який заточено під Redis і який ефективніший за базовий middleware обмеження частоти:

```php
use Illuminate\Queue\Middleware\RateLimitedWithRedis;

public function middleware(): array
{
    return [new RateLimitedWithRedis('backups')];
}
```

Метод `connection` дозволяє вказати, яке підключення Redis має використовувати middleware:

```php
return [(new RateLimitedWithRedis('backups'))->connection('limiter')];
```

<a name="preventing-job-overlaps"></a>
### Запобігання накладанню завдань

Laravel містить middleware `Illuminate\Queue\Middleware\WithoutOverlapping`, який дозволяє запобігти накладанню завдань за довільним ключем. Це може стати в пригоді, коли завдання в черзі змінює ресурс, який має змінювати лише одне завдання за раз.

Наприклад, уявімо, що у вас є завдання в черзі, яке оновлює кредитний рейтинг користувача, і ви хочете запобігти накладанню таких завдань для того самого ID користувача. Для цього поверніть middleware `WithoutOverlapping` з методу `middleware` вашого завдання:

```php
use Illuminate\Queue\Middleware\WithoutOverlapping;

/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [new WithoutOverlapping($this->user->id)];
}
```

Повернення завдання, що накладається, до черги все одно збільшує загальну кількість його спроб. Тож вам, можливо, варто відповідно налаштувати атрибути `Tries` і `MaxExceptions` у класі завдання. Наприклад, якщо лишити `Tries` рівним 1, як за замовчуванням, жодне завдання, що накладається, не буде повторено пізніше.

Будь-які завдання того самого типу, що накладаються, буде повернуто до черги. Ви також можете вказати кількість секунд, які мають минути, перш ніж повернене завдання спробують виконати знову:

```php
/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new WithoutOverlapping($this->order->id))->releaseAfter(60)];
}
```

Якщо ви хочете негайно видаляти завдання, що накладаються, аби їх не повторювали, скористайтеся методом `dontRelease`:

```php
/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new WithoutOverlapping($this->order->id))->dontRelease()];
}
```

Middleware `WithoutOverlapping` побудовано на можливості атомарних блокувань Laravel. Інколи ваше завдання може несподівано провалитися чи вичерпати таймаут так, що блокування не звільниться. Тому ви можете явно задати час спливання блокування методом `expireAfter`. Наприклад, код нижче скаже Laravel звільнити блокування `WithoutOverlapping` через три хвилини після початку обробки завдання:

```php
/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new WithoutOverlapping($this->order->id))->expireAfter(180)];
}
```

> [!WARNING]
> Middleware `WithoutOverlapping` потребує драйвера кешу, який підтримує [блокування](/docs/{{version}}/cache#atomic-locks). Наразі атомарні блокування підтримують драйвери кешу `memcached`, `redis`, `dynamodb`, `database`, `file` та `array`.

<a name="sharing-lock-keys"></a>
#### Спільні ключі блокувань між класами завдань

За замовчуванням middleware `WithoutOverlapping` запобігає накладанню лише завдань того самого класу. Тож, хоч два різні класи завдань і можуть використовувати той самий ключ блокування, їм ніщо не завадить накластися. Проте ви можете сказати Laravel застосовувати ключ між класами завдань методом `shared`:

```php
use Illuminate\Queue\Middleware\WithoutOverlapping;

class ProviderIsDown
{
    // ...

    public function middleware(): array
    {
        return [
            (new WithoutOverlapping("status:{$this->provider}"))->shared(),
        ];
    }
}

class ProviderIsUp
{
    // ...

    public function middleware(): array
    {
        return [
            (new WithoutOverlapping("status:{$this->provider}"))->shared(),
        ];
    }
}
```

<a name="throttling-exceptions"></a>
### Тротлінг винятків

Laravel містить middleware `Illuminate\Queue\Middleware\ThrottlesExceptions`, який дозволяє тротлити винятки. Щойно завдання викине задану кількість винятків, усі подальші спроби його виконати відкладаються, доки не мине вказаний проміжок часу. Цей middleware особливо корисний для завдань, які працюють із нестабільними сторонніми сервісами.

Наприклад, уявімо завдання в черзі, яке працює зі стороннім API, що почав викидати винятки. Щоб тротлити винятки, поверніть middleware `ThrottlesExceptions` із методу `middleware` вашого завдання. Зазвичай цей middleware поєднують із завданням, що реалізує [спроби на основі часу](#time-based-attempts):

```php
use DateTime;
use Illuminate\Queue\Middleware\ThrottlesExceptions;

/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [new ThrottlesExceptions(10, 5 * 60)];
}

/**
 * Determine the time at which the job should timeout.
 */
public function retryUntil(): DateTime
{
    return now()->plus(minutes: 30);
}
```

Перший аргумент конструктора middleware - кількість винятків, які завдання може викинути, перш ніж його затротлять, а другий - кількість секунд, які мають минути, перш ніж завдання спробують знову після тротлінгу. У прикладі коду вище, якщо завдання викине 10 винятків поспіль, ми чекатимемо 5 хвилин перед наступною спробою - у межах 30-хвилинного ліміту.

Коли завдання викидає виняток, але порога ще не досягнуто, завдання зазвичай повторюють негайно. Проте ви можете вказати кількість хвилин затримки для такого завдання, викликавши метод `backoff` під час чіпляння middleware до завдання:

```php
use Illuminate\Queue\Middleware\ThrottlesExceptions;

/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new ThrottlesExceptions(10, 5 * 60))->backoff(5)];
}
```

Метод `backoff` також приймає замикання, яке отримує викинутий виняток, - тож затримку можна визначати динамічно:

```php
use App\Exceptions\RateLimitedException;
use Illuminate\Queue\Middleware\ThrottlesExceptions;
use Throwable;

/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new ThrottlesExceptions(10, 5 * 60))->backoff(
        fn (Throwable $throwable) => $throwable instanceof RateLimitedException
            ? $throwable->retryAfterMinutes()
            : 5
    )];
}
```

Під капотом цей middleware реалізує обмеження частоти через систему кешу Laravel, а «ключем» кешу слугує назва класу завдання. Ви можете перевизначити цей ключ, викликавши метод `by` під час чіпляння middleware до завдання. Це стане в пригоді, якщо у вас кілька завдань працюють з тим самим стороннім сервісом і ви хочете, щоб вони мали спільний «кошик» тротлінгу й дотримувалися єдиного спільного ліміту:

```php
use Illuminate\Queue\Middleware\ThrottlesExceptions;

/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new ThrottlesExceptions(10, 10 * 60))->by('key')];
}
```

За замовчуванням цей middleware тротлить кожен виняток. Ви можете змінити цю поведінку, викликавши метод `when` під час чіпляння middleware до завдання. Тоді виняток тротлитимуть, лише якщо замикання, передане методу `when`, поверне `true`:

```php
use Illuminate\Http\Client\HttpClientException;
use Illuminate\Queue\Middleware\ThrottlesExceptions;

/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new ThrottlesExceptions(10, 10 * 60))->when(
        fn (Throwable $throwable) => $throwable instanceof HttpClientException
    )];
}
```

На відміну від методу `when`, який повертає завдання до черги або викидає виняток, метод `deleteWhen` дозволяє повністю видалити завдання, коли трапляється заданий виняток:

```php
use App\Exceptions\CustomerDeletedException;
use Illuminate\Queue\Middleware\ThrottlesExceptions;

/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new ThrottlesExceptions(2, 10 * 60))->deleteWhen(CustomerDeletedException::class)];
}
```

Якщо ви хочете, щоб про затротлені винятки повідомляли обробнику винятків вашого застосунку, викличте метод `report` під час чіпляння middleware до завдання. За бажанням ви можете передати методу `report` замикання, і тоді про виняток повідомлятимуть, лише якщо це замикання поверне `true`:

```php
use Illuminate\Http\Client\HttpClientException;
use Illuminate\Queue\Middleware\ThrottlesExceptions;

/**
 * Get the middleware the job should pass through.
 *
 * @return array<int, object>
 */
public function middleware(): array
{
    return [(new ThrottlesExceptions(10, 10 * 60))->report(
        fn (Throwable $throwable) => $throwable instanceof HttpClientException
    )];
}
```

<a name="throttling-exceptions-with-redis"></a>
#### Тротлінг винятків через Redis

Якщо ви користуєтеся Redis, скористайтеся middleware `Illuminate\Queue\Middleware\ThrottlesExceptionsWithRedis`, який заточено під Redis і який ефективніший за базовий middleware тротлінгу винятків:

```php
use Illuminate\Queue\Middleware\ThrottlesExceptionsWithRedis;

public function middleware(): array
{
    return [new ThrottlesExceptionsWithRedis(10, 10 * 60)];
}
```

Метод `connection` дозволяє вказати, яке підключення Redis має використовувати middleware:

```php
return [(new ThrottlesExceptionsWithRedis(10, 10 * 60))->connection('limiter')];
```

<a name="releasing-jobs"></a>
### Звільнення завдань

Middleware `Release` дозволяє повернути завдання до черги, не виконуючи його. Метод `Release::when` поверне завдання, якщо задана умова дає `true`, а метод `Release::unless` - якщо умова дає `false`:

```php
use Illuminate\Queue\Middleware\Release;

/**
 * Get the middleware the job should pass through.
 */
public function middleware(): array
{
    return [
        Release::when($condition, releaseAfter: 60),
    ];
}
```

Повернення завдання до черги все одно збільшує загальну кількість його спроб. Тож вам, можливо, варто відповідно налаштувати атрибути `Tries` і `MaxExceptions` у класі завдання.

Для складніших умов ви можете передати методам `when` та `unless` `Closure`:

```php
use Illuminate\Queue\Middleware\Release;

/**
 * Get the middleware the job should pass through.
 */
public function middleware(): array
{
    return [
        Release::when(function (): bool {
            return ! $this->order->isPaid();
        }, releaseAfter: 60),
    ];
}
```

<a name="skipping-jobs"></a>
### Пропуск завдань

Middleware `Skip` дозволяє вказати, що завдання слід пропустити / видалити, не змінюючи його логіки. Метод `Skip::when` видалить завдання, якщо задана умова дає `true`, а метод `Skip::unless` - якщо умова дає `false`:

```php
use Illuminate\Queue\Middleware\Skip;

/**
 * Get the middleware the job should pass through.
 */
public function middleware(): array
{
    return [
        Skip::when($condition),
    ];
}
```

Для складніших умов ви можете передати методам `when` та `unless` `Closure`:

```php
use Illuminate\Queue\Middleware\Skip;

/**
 * Get the middleware the job should pass through.
 */
public function middleware(): array
{
    return [
        Skip::when(function (): bool {
            return $this->shouldSkip();
        }),
    ];
}
```

<a name="dispatching-jobs"></a>
## Диспетчеризація завдань

Щойно ви написали клас завдання, ви можете диспетчеризувати його методом `dispatch` на самому завданні. Аргументи, передані методу `dispatch`, потраплять до конструктора завдання:

```php
<?php

namespace App\Http\Controllers;

use App\Jobs\ProcessPodcast;
use App\Models\Podcast;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class PodcastController extends Controller
{
    /**
     * Store a new podcast.
     */
    public function store(Request $request): RedirectResponse
    {
        $podcast = Podcast::create(/* ... */);

        // ...

        ProcessPodcast::dispatch($podcast);

        return redirect('/podcasts');
    }
}
```

Якщо ви хочете диспетчеризувати завдання умовно, скористайтеся методами `dispatchIf` та `dispatchUnless`:

```php
ProcessPodcast::dispatchIf($accountActive, $podcast);

ProcessPodcast::dispatchUnless($accountSuspended, $podcast);
```

У нових застосунках Laravel чергою за замовчуванням задано підключення `database`. Ви можете вказати інше підключення за замовчуванням, змінивши змінну середовища `QUEUE_CONNECTION` у файлі `.env` вашого застосунку.

<a name="delayed-dispatching"></a>
### Відкладена диспетчеризація

Якщо ви хочете вказати, що завдання не має одразу ставати доступним для обробки воркером, скористайтеся методом `delay` під час диспетчеризації. Наприклад, укажімо, що завдання не має бути доступним для обробки протягом 10 хвилин після диспетчеризації:

```php
<?php

namespace App\Http\Controllers;

use App\Jobs\ProcessPodcast;
use App\Models\Podcast;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class PodcastController extends Controller
{
    /**
     * Store a new podcast.
     */
    public function store(Request $request): RedirectResponse
    {
        $podcast = Podcast::create(/* ... */);

        // ...

        ProcessPodcast::dispatch($podcast)
            ->delay(now()->plus(minutes: 10));

        return redirect('/podcasts');
    }
}
```

Інколи завдання можуть мати налаштовану затримку за замовчуванням. Якщо вам потрібно обійти цю затримку й диспетчеризувати завдання на негайну обробку, скористайтеся методом `withoutDelay`:

```php
ProcessPodcast::dispatch($podcast)->withoutDelay();
```

> [!WARNING]
> Максимальний час затримки в сервісі черг Amazon SQS - 15 хвилин.

<a name="synchronous-dispatching"></a>
### Синхронна диспетчеризація

Якщо ви хочете диспетчеризувати завдання негайно (синхронно), скористайтеся методом `dispatchSync`. За такого підходу завдання не потрапить у чергу, а виконається одразу в поточному процесі:

```php
<?php

namespace App\Http\Controllers;

use App\Jobs\ProcessPodcast;
use App\Models\Podcast;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class PodcastController extends Controller
{
    /**
     * Store a new podcast.
     */
    public function store(Request $request): RedirectResponse
    {
        $podcast = Podcast::create(/* ... */);

        // Create podcast...

        ProcessPodcast::dispatchSync($podcast);

        return redirect('/podcasts');
    }
}
```

<a name="deferred-dispatching"></a>
#### Відкладена диспетчеризація

За допомогою відкладеної синхронної диспетчеризації ви можете диспетчеризувати завдання на обробку в поточному процесі, але вже після того, як HTTP-відповідь надіслано користувачеві. Це дозволяє обробляти завдання «з черги» синхронно, не сповільнюючи роботу застосунку для користувача. Щоб відкласти виконання синхронного завдання, диспетчеризуйте його до підключення `deferred`:

```php
RecordDelivery::dispatch($order)->onConnection('deferred');
```

Підключення `deferred` також слугує [резервною чергою](#queue-failover) за замовчуванням.

Так само підключення `background` обробляє завдання після надсилання HTTP-відповіді користувачеві; проте завдання обробляється в окремо породженому процесі PHP, тож PHP-FPM / воркер застосунку лишається вільним для обробки наступного вхідного HTTP-запиту:

```php
RecordDelivery::dispatch($order)->onConnection('background');
```

<a name="bulk-dispatching"></a>
### Масова диспетчеризація

Якщо вам треба диспетчеризувати багато незалежних завдань одразу й вам не потрібне відстеження [пакетів](#job-batching) чи колбеки, скористайтеся методом `bulk` фасаду `Bus`. Laravel згрупує завдання за налаштованим підключенням і назвою черги й покладе кожну групу до відповідної черги гуртом:

```php
use App\Jobs\ProcessUser;
use Illuminate\Support\Facades\Bus;

Bus::bulk(
    $users->map(fn ($user) => new ProcessUser($user))
);
```

<a name="preparing-jobs-before-dispatch"></a>
### Підготовка завдань перед диспетчеризацією

Якщо завданню потрібно підготуватися чи оглянути свій стан перед потраплянням у чергу, воно може реалізувати інтерфейс `Illuminate\Contracts\Queue\PreparesForDispatch`. Laravel викличе метод `prepareForDispatch` завдання перед його диспетчеризацією. Якщо цей метод поверне `false`, завдання не буде диспетчеризовано:

```php
<?php

namespace App\Jobs;

use Illuminate\Contracts\Queue\PreparesForDispatch;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Cache;

class SyncPodcasts implements PreparesForDispatch, ShouldQueue
{
    use Queueable;

    /**
     * Create a new job instance.
     */
    public function __construct(
        public array $podcastIds,
    ) {}

    /**
     * Prepare the job before dispatching.
     */
    public function prepareForDispatch(): bool
    {
        return collect($this->podcastIds)
            ->reject(fn (int $id) => Cache::has("podcast-syncing:{$id}"))
            ->isNotEmpty();
    }
}
```

<a name="jobs-and-database-transactions"></a>
### Завдання й транзакції бази даних

Хоч диспетчеризувати завдання всередині транзакцій бази даних цілком нормально, вам варто подбати, щоб ваше завдання справді змогло успішно виконатися. Коли ви диспетчеризуєте завдання всередині транзакції, можливо, що воркер обробить його ще до фіксації батьківської транзакції. Коли таке трапляється, будь-які зміни, які ви внесли до моделей чи записів у базі під час транзакції (транзакцій), ще можуть не бути в базі. Ба більше, будь-які моделі чи записи, створені всередині транзакції (транзакцій), можуть у базі не існувати.

На щастя, Laravel надає кілька способів обійти цю проблему. По-перше, ви можете задати опцію `after_commit` у масиві конфігурації свого підключення черги:

```php
'redis' => [
    'driver' => 'redis',
    // ...
    'after_commit' => true,
],
```

Коли опція `after_commit` має значення `true`, ви можете диспетчеризувати завдання всередині транзакцій; проте Laravel зачекає, доки відкриті батьківські транзакції не буде зафіксовано, і лише тоді справді диспетчеризує завдання. Звісно, якщо жодної транзакції наразі не відкрито, завдання буде диспетчеризовано негайно.

Якщо транзакцію відкочено через виняток, що стався під час неї, завдання, диспетчеризовані в межах цієї транзакції, буде відкинуто.

> [!NOTE]
> Значення `true` для опції конфігурації `after_commit` також змусить усі слухачі подій у черзі, mailable-класи, сповіщення та події бродкастингу диспетчеризуватися після фіксації всіх відкритих транзакцій бази даних.

<a name="specifying-commit-dispatch-behavior-inline"></a>
#### Задання поведінки фіксації на місці

Якщо ви не задаєте опції конфігурації `after_commit` значення `true`, ви все одно можете вказати, що конкретне завдання слід диспетчеризувати після фіксації всіх відкритих транзакцій. Для цього додайте ланцюжком метод `afterCommit` до операції диспетчеризації:

```php
use App\Jobs\ProcessPodcast;

ProcessPodcast::dispatch($podcast)->afterCommit();
```

Так само, якщо опція конфігурації `after_commit` має значення `true`, ви можете вказати, що конкретне завдання слід диспетчеризувати негайно, не чекаючи фіксації відкритих транзакцій:

```php
ProcessPodcast::dispatch($podcast)->beforeCommit();
```

<a name="job-chaining"></a>
### Ланцюжки завдань

Ланцюжки завдань дозволяють задати список завдань у черзі, які слід виконати послідовно після успішного виконання основного завдання. Якщо одне завдання в послідовності провалиться, решта не виконається. Щоб виконати ланцюжок завдань у черзі, скористайтеся методом `chain` фасаду `Bus`. Командна шина Laravel - це нижчорівневий компонент, поверх якого побудовано диспетчеризацію завдань у черзі:

```php
use App\Jobs\OptimizePodcast;
use App\Jobs\ProcessPodcast;
use App\Jobs\ReleasePodcast;
use Illuminate\Support\Facades\Bus;

Bus::chain([
    new ProcessPodcast,
    new OptimizePodcast,
    new ReleasePodcast,
])->dispatch();
```

Окрім екземплярів класів завдань, ви можете додавати в ланцюжок і замикання:

```php
Bus::chain([
    new ProcessPodcast,
    new OptimizePodcast,
    function () {
        Podcast::update(/* ... */);
    },
])->dispatch();
```

> [!WARNING]
> Видалення завдання методом `$this->delete()` усередині завдання не завадить обробці завдань з ланцюжка. Ланцюжок зупиниться, лише якщо завдання в ньому провалиться.

<a name="chain-connection-queue"></a>
#### Підключення та черга ланцюжка

Якщо ви хочете вказати підключення й чергу для завдань ланцюжка, скористайтеся методами `onConnection` та `onQueue`. Ці методи задають підключення й назву черги, які буде використано, якщо завданню явно не призначено інших:

```php
Bus::chain([
    new ProcessPodcast,
    new OptimizePodcast,
    new ReleasePodcast,
])->onConnection('redis')->onQueue('podcasts')->dispatch();
```

<a name="adding-jobs-to-the-chain"></a>
#### Додавання завдань до ланцюжка

Інколи вам може знадобитися додати завдання на початок чи в кінець наявного ланцюжка зсередини іншого завдання цього ланцюжка. Це робиться методами `prependToChain` та `appendToChain`:

```php
/**
 * Execute the job.
 */
public function handle(): void
{
    // ...

    // Prepend to the current chain, run job immediately after current job...
    $this->prependToChain(new TranscribePodcast);

    // Append to the current chain, run job at end of chain...
    $this->appendToChain(new TranscribePodcast);
}
```

<a name="chain-failures"></a>
#### Невдачі ланцюжка

Будуючи ланцюжок завдань, ви можете скористатися методом `catch`, щоб задати замикання, яке буде викликано, якщо завдання в ланцюжку провалиться. Цей колбек отримає екземпляр `Throwable`, що спричинив невдачу:

```php
use Illuminate\Support\Facades\Bus;
use Throwable;

Bus::chain([
    new ProcessPodcast,
    new OptimizePodcast,
    new ReleasePodcast,
])->catch(function (Throwable $e) {
    // A job within the chain has failed...
})->dispatch();
```

> [!WARNING]
> Оскільки колбеки ланцюжка серіалізуються й виконуються пізніше чергою Laravel, не використовуйте в них змінну `$this`.

<a name="customizing-the-queue-and-connection"></a>
### Налаштування черги та підключення

<a name="dispatching-to-a-particular-queue"></a>
#### Диспетчеризація до конкретної черги

Кладучи завдання в різні черги, ви можете «категоризувати» їх і навіть пріоритезувати, скільки воркерів призначити різним чергам. Пам'ятайте: це кладе завдання не в різні «підключення» черг, описані у вашому файлі конфігурації, а лише в конкретні черги в межах одного підключення. Щоб указати чергу, скористайтеся методом `onQueue` під час диспетчеризації завдання:

```php
<?php

namespace App\Http\Controllers;

use App\Jobs\ProcessPodcast;
use App\Models\Podcast;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class PodcastController extends Controller
{
    /**
     * Store a new podcast.
     */
    public function store(Request $request): RedirectResponse
    {
        $podcast = Podcast::create(/* ... */);

        // Create podcast...

        ProcessPodcast::dispatch($podcast)->onQueue('processing');

        return redirect('/podcasts');
    }
}
```

Як варіант, ви можете вказати чергу завдання, викликавши метод `onQueue` у його конструкторі:

```php
<?php

namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;

class ProcessPodcast implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new job instance.
     */
    public function __construct()
    {
        $this->onQueue('processing');
    }
}
```

<a name="dispatching-to-a-particular-connection"></a>
#### Диспетчеризація до конкретного підключення

Якщо ваш застосунок працює з кількома підключеннями черг, вказати, до якого з них покласти завдання, можна методом `onConnection`:

```php
<?php

namespace App\Http\Controllers;

use App\Jobs\ProcessPodcast;
use App\Models\Podcast;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class PodcastController extends Controller
{
    /**
     * Store a new podcast.
     */
    public function store(Request $request): RedirectResponse
    {
        $podcast = Podcast::create(/* ... */);

        // Create podcast...

        ProcessPodcast::dispatch($podcast)->onConnection('sqs');

        return redirect('/podcasts');
    }
}
```

Ви можете поєднати методи `onConnection` та `onQueue` ланцюжком, щоб задати завданню й підключення, і чергу:

```php
ProcessPodcast::dispatch($podcast)
    ->onConnection('sqs')
    ->onQueue('processing');
```

Як варіант, ви можете вказати підключення завдання, викликавши метод `onConnection` у його конструкторі:

```php
<?php

namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;

class ProcessPodcast implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new job instance.
     */
    public function __construct()
    {
        $this->onConnection('sqs');
    }
}
```

<a name="queue-routing"></a>
#### Маршрутизація черг

Метод `route` фасаду `Queue` дозволяє описати підключення й чергу за замовчуванням для конкретних класів завдань. Це корисно, коли ви хочете, щоб певні завдання завжди йшли в конкретні черги, не вказуючи підключення чи черги в самому завданні.

Окрім конкретних класів завдань, методу `route` можна передати інтерфейс, трейт чи батьківський клас. Тоді будь-яке завдання, яке реалізує цей інтерфейс, використовує трейт чи розширює батьківський клас, автоматично використовуватиме налаштовані підключення й чергу.

Зазвичай метод `route` викликають у методі `boot` сервіс-провайдера:

```php
use App\Concerns\RequiresVideo;
use App\Jobs\ProcessPodcast;
use App\Jobs\ProcessVideo;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Support\Facades\Queue;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Queue::route(ProcessPodcast::class, connection: 'redis', queue: 'podcasts');
    Queue::route(RequiresVideo::class, queue: 'video');
    Queue::route(ShouldBroadcast::class, queue: 'events');
}
```

Коли вказано підключення без черги, завдання потрапить до черги за замовчуванням:

```php
Queue::route(ProcessPodcast::class, connection: 'redis');
```

Ви також можете маршрутизувати кілька класів завдань одразу, передавши методу `route` масив:

```php
Queue::route([
    ProcessPodcast::class => ['redis', 'podcasts'], // Connection and queue
    ProcessVideo::class => 'videos', // Queue only (uses default connection)
]);
```

> [!NOTE]
> Маршрутизацію черг усе одно можна перевизначити на рівні окремого завдання.

Ви можете скористатися методом `forward`, щоб перенаправити завдання з однієї черги в іншу чергу та / або підключення. Це корисно, коли потрібно змінити інфраструктуру черг без зміни окремих завдань чи місць їх відправлення:

```php
Queue::forward('reports', 'reports.fifo', 'sqs');
Queue::forward('payments', connection: 'sqs');
Queue::forward('updates', 'notifications');
```

Ви також можете перенаправити кілька черг одразу, передавши масив:

```php
Queue::forward([
    'reports' => 'reports.fifo',
    'emails' => 'emails.fifo',
], connection: 'sqs');
```

Явне підключення, налаштоване на завданні, має перевагу над перенаправленим підключенням.

<a name="max-job-attempts-and-timeout"></a>
### Задання максимуму спроб / таймауту

<a name="max-attempts"></a>
#### Максимум спроб

Спроби завдань - ключове поняття системи черг Laravel, на якому побудовано багато просунутих можливостей. Хоч спершу вони й можуть здаватися заплутаними, важливо зрозуміти, як вони працюють, перш ніж змінювати конфігурацію за замовчуванням.

Коли завдання диспетчеризовано, воно потрапляє в чергу. Далі воркер бере його й намагається виконати. Це і є спроба завдання.

Проте спроба не обов'язково означає, що метод `handle` завдання виконався. Спроби можуть «витрачатися» й кількома іншими способами:

<div class="content-list" markdown="1">

- Завдання натрапило на необроблений виняток під час виконання.
- Завдання вручну повернуто до черги через `$this->release()`.
- Middleware на кшталт `WithoutOverlapping` чи `RateLimited` не зміг отримати блокування й повернув завдання.
- Завдання вичерпало таймаут.
- Метод `handle` завдання виконався й завершився без винятків.

</div>

Ви навряд чи хочете намагатися виконати завдання нескінченно. Тому Laravel надає різні способи вказати, скільки разів або як довго можна намагатися виконати завдання.

> [!NOTE]
> За замовчуванням Laravel намагається виконати завдання лише один раз. Якщо ваше завдання використовує middleware на кшталт `WithoutOverlapping` чи `RateLimited` або якщо ви вручну повертаєте завдання до черги, вам, найімовірніше, доведеться збільшити кількість дозволених спроб опцією `tries`.

Один зі способів задати максимальну кількість спроб - перемикач `--tries` у командному рядку Artisan. Він застосується до всіх завдань, які обробляє воркер, якщо саме завдання не вказує кількості спроб:

```shell
php artisan queue:work --tries=3
```

Якщо завдання перевищить максимальну кількість спроб, воно вважатиметься «невдалим». Докладніше про роботу з невдалими завданнями читайте в [документації про невдалі завдання](#dealing-with-failed-jobs). Якщо команді `queue:work` передано `--tries=0`, завдання повторюватимуть нескінченно.

Ви можете підійти тонше й задати максимальну кількість спроб у самому класі завдання атрибутом `Tries`. Якщо максимум спроб указано в завданні, він матиме перевагу над значенням `--tries` із командного рядка:

```php
<?php

namespace App\Jobs;

use Illuminate\Queue\Attributes\Tries;

#[Tries(5)]
class ProcessPodcast implements ShouldQueue
{
    // ...
}
```

Якщо вам потрібен динамічний контроль над максимумом спроб конкретного завдання, опишіть у ньому метод `tries`:

```php
/**
 * Determine number of times the job may be attempted.
 */
public function tries(): int
{
    return 5;
}
```

<a name="time-based-attempts"></a>
#### Спроби на основі часу

Як альтернативу заданню кількості спроб перед невдачею ви можете вказати момент, після якого спроби виконати завдання припиняються. Це дозволяє виконувати завдання будь-яку кількість разів у межах заданого проміжку часу. Щоб задати момент, після якого завдання більше не намагатимуться виконати, додайте до класу завдання метод `retryUntil`. Цей метод має повернути екземпляр `DateTime`:

```php
use DateTime;

/**
 * Determine the time at which the job should timeout.
 */
public function retryUntil(): DateTime
{
    return now()->plus(minutes: 10);
}
```

Якщо описано і `retryUntil`, і `tries`, Laravel віддає перевагу методу `retryUntil`.

> [!NOTE]
> Ви можете описати атрибут `Tries` чи метод `retryUntil` і у своїх [слухачах подій у черзі](/docs/{{version}}/events#queued-event-listeners) та [сповіщеннях у черзі](/docs/{{version}}/notifications#queueing-notifications).

<a name="max-exceptions"></a>
#### Максимум винятків

Інколи вам може знадобитися вказати, що завдання можна намагатися виконати багато разів, але воно має зазнати невдачі, якщо повтори спричинено заданою кількістю необроблених винятків (на відміну від звільнення методом `release` напряму). Для цього скористайтеся атрибутами `Tries` і `MaxExceptions` у класі завдання:

```php
<?php

namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\Attributes\MaxExceptions;
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Support\Facades\Redis;

#[Tries(25)]
#[MaxExceptions(3)]
class ProcessPodcast implements ShouldQueue
{
    use Queueable;

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        Redis::throttle('key')->allow(10)->every(60)->then(function () {
            // Lock obtained, process the podcast...
        }, function () {
            // Unable to obtain lock...
            return $this->release(10);
        });
    }
}
```

У цьому прикладі завдання повертається до черги на десять секунд, якщо застосунку не вдалося отримати блокування Redis, і повторюватиметься до 25 разів. Проте завдання зазнає невдачі, якщо викине три необроблені винятки.

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

```php
use Illuminate\Queue\Attributes\CountCrashesAsExceptions;
use Illuminate\Queue\Attributes\MaxExceptions;
use Illuminate\Queue\Attributes\Tries;

#[Tries(25)]
#[MaxExceptions(3)]
#[CountCrashesAsExceptions]
class ProcessPodcast implements ShouldQueue
{
    // ...
}
```

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

<a name="stopping-retries-by-exception"></a>
#### Припинення повторів через виняток

Інколи виняток означає, що завдання в черзі має негайно зазнати невдачі, а не повертатися на нову спробу. Ви можете налаштувати типи винятків, які припиняють повтори, методом винятків `dontRetry` у файлі `bootstrap/app.php` вашого застосунку:

```php
use App\Exceptions\InvalidPodcastSourceException;
use Illuminate\Foundation\Configuration\Exceptions;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontRetry([
        InvalidPodcastSourceException::class,
    ]);
})
```

Якщо вам потрібен тонший контроль над тим, коли припиняти повтори, передайте замикання методу `dontRetryWhen`. Коли замикання повертає `true`, завдання буде позначено невдалим і не повторюватиметься:

```php
use App\Exceptions\PodcastProcessingException;
use Illuminate\Foundation\Configuration\Exceptions;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontRetryWhen(function (PodcastProcessingException $e) {
        return $e->reason() === 'Subscription expired';
    });
})
```

<a name="timeout"></a>
#### Таймаут

Часто ви приблизно знаєте, скільки часу мають виконуватися ваші завдання в черзі. Тому Laravel дозволяє задати значення «таймауту». За замовчуванням таймаут становить 60 секунд. Якщо завдання обробляється довше за вказану кількість секунд, воркер, який його обробляє, завершиться з помилкою. Зазвичай воркер автоматично перезапустить [менеджер процесів, налаштований на вашому сервері](#supervisor-configuration).

Максимальну кількість секунд, які можуть виконуватися завдання, можна задати перемикачем `--timeout` у командному рядку Artisan:

```shell
php artisan queue:work --timeout=30
```

Якщо завдання перевищить максимум спроб через постійні таймаути, його буде позначено невдалим.

Ви також можете задати максимальну кількість секунд, які дозволено виконуватися завданню, атрибутом `Timeout` у класі завдання. Якщо таймаут указано в завданні, він матиме перевагу над таймаутом із командного рядка:

```php
<?php

namespace App\Jobs;

use Illuminate\Queue\Attributes\Timeout;

#[Timeout(120)]
class ProcessPodcast implements ShouldQueue
{
    // ...
}
```

Інколи процеси, що блокуються на введенні-виведенні - сокети чи вихідні HTTP-з'єднання, - можуть не зважати на вказаний вами таймаут. Тому, користуючись цими можливостями, завжди намагайтеся задати таймаут і через їхні API. Наприклад, працюючи з [Guzzle](https://docs.guzzlephp.org), завжди вказуйте таймаути з'єднання та запиту.

> [!WARNING]
> Щоб задавати таймаути завдань, має бути встановлено PHP-розширення [PCNTL](https://www.php.net/manual/en/book.pcntl.php). Крім того, «таймаут» завдання завжди має бути меншим за його значення [«retry after»](#job-expiration). Інакше завдання можуть спробувати виконати знову, перш ніж воно справді завершиться чи вичерпає таймаут. Опція `--timeout` не діє, коли команду `queue:work` викликано з опцією `--once`.

<a name="failing-on-timeout"></a>
#### Невдача за таймаутом

Якщо ви хочете, щоб завдання позначалося [невдалим](#dealing-with-failed-jobs) за таймаутом, скористайтеся атрибутом `FailOnTimeout` у класі завдання:

```php
<?php

namespace App\Jobs;

use Illuminate\Queue\Attributes\FailOnTimeout;

#[FailOnTimeout]
class ProcessPodcast implements ShouldQueue
{
    // ...
}
```

> [!NOTE]
> За замовчуванням, коли завдання вичерпує таймаут, воно витрачає одну спробу й повертається до черги (якщо повтори дозволено). Проте, якщо ви налаштували завдання зазнавати невдачі за таймаутом, його не повторюватимуть, незалежно від значення tries.

<a name="sqs-fifo-and-fair-queues"></a>
### SQS FIFO та справедливі черги

Laravel підтримує черги [Amazon SQS FIFO (First-In-First-Out)](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-fifo-queues.html) та [справедливі](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-fair-queues.html) черги. Черги FIFO дозволяють обробляти завдання точно в тому порядку, у якому їх надіслано, забезпечуючи обробку рівно один раз завдяки дедуплікації повідомлень.

Черги FIFO потребують ID групи повідомлень, щоб визначити, які завдання можна обробляти паралельно. Завдання з однаковим ID групи обробляються послідовно, а повідомлення з різними ID груп - паралельно.

Laravel надає плавний метод `onGroup`, щоб указати ID групи повідомлень під час диспетчеризації завдань:

```php
ProcessOrder::dispatch($order)
    ->onGroup("customer-{$order->customer_id}");
```

Якщо ви диспетчеризуєте завдання до черги SQS FIFO, не вказавши групу повідомлень, Laravel використає назву черги як ID групи повідомлень.

Черги SQS FIFO підтримують дедуплікацію повідомлень, щоб забезпечити обробку рівно один раз. Реалізуйте у класі завдання метод `deduplicationId`, щоб задати власний ID дедуплікації:

```php
<?php

namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;

class ProcessSubscriptionRenewal implements ShouldQueue
{
    use Queueable;

    // ...

    /**
     * Get the job's deduplication ID.
     */
    public function deduplicationId(): string
    {
        return "renewal-{$this->subscription->id}";
    }
}
```

<a name="fair-queues"></a>
#### Справедливі черги

Якщо ви користуєтеся стандартною чергою SQS, задання групи повідомлень вмикає справедливе розподілення. Іншими словами, щойно ви призначаєте групи, SQS використовує їх, щоб забезпечити справедливу доставку між орендарями / навантаженнями. Додаткова конфігурація Laravel не потрібна.

Замість викликати `onGroup` під час диспетчеризації, ви можете описати метод `messageGroup` безпосередньо в завданні:

```php
<?php

namespace App\Jobs;

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;

class ProcessOrder implements ShouldQueue
{
    use Queueable;

    // ...

    /**
     * Get the job's message group.
     */
    public function messageGroup(): string
    {
        return "customer-{$this->order->customer_id}";
    }
}
```

<a name="fifo-listeners-mail-and-notifications"></a>
#### FIFO для слухачів, пошти та сповіщень

Користуючись чергами FIFO, вам також доведеться описати групи повідомлень у слухачах, пошті та сповіщеннях. Як варіант, ви можете диспетчеризувати ці об'єкти в чергу, відмінну від FIFO.

Щоб задати групу повідомлень для [слухача подій у черзі](/docs/{{version}}/events#queued-event-listeners), опишіть у слухачі метод `messageGroup`. За бажанням ви можете описати й метод `deduplicator`, який приймає подію і повертає замикання, що генерує ідентифікатор дедуплікації:

```php
<?php

namespace App\Listeners;

use App\Events\OrderShipped;
use Closure;

class SendShipmentNotification
{
    // ...

    /**
     * Get the job's message group.
     */
    public function messageGroup(): string
    {
        return 'shipments';
    }

    /**
     * Get the job's deduplicator.
     */
    public function deduplicator(OrderShipped $event): Closure
    {
        return fn () => "shipment-notification-{$event->order->id}";
    }
}
```

Надсилаючи [поштове повідомлення](/docs/{{version}}/mail), яке потрапить у чергу FIFO, викличте метод `onGroup` і, за бажанням, метод `withDeduplicator`:

```php
use App\Mail\InvoicePaid;
use Illuminate\Support\Facades\Mail;

$invoicePaid = (new InvoicePaid($invoice))
    ->onGroup('invoices')
    ->withDeduplicator(fn () => 'invoices-'.$invoice->id);

Mail::to($request->user())->send($invoicePaid);
```

Надсилаючи [сповіщення](/docs/{{version}}/notifications), яке потрапить у чергу FIFO, викличте метод `onGroup` і, за бажанням, метод `withDeduplicator`:

```php
use App\Notifications\InvoicePaid;

$invoicePaid = (new InvoicePaid($invoice))
    ->onGroup('invoices')
    ->withDeduplicator(fn () => 'invoices-'.$invoice->id);

$user->notify($invoicePaid);
```

<a name="queue-failover"></a>
### Failover черги

Драйвер черги `failover` забезпечує автоматичне перемикання під час покладання завдань у чергу. Якщо основне підключення черги з конфігурації `failover` з якоїсь причини провалиться, Laravel автоматично спробує покласти завдання до наступного підключення зі списку. Це особливо корисно для забезпечення високої доступності в продакшн-середовищах, де надійність черг критична.

Щоб налаштувати підключення `failover`, укажіть драйвер `failover` і передайте масив назв підключень, які слід пробувати по черзі. За замовчуванням Laravel містить приклад конфігурації failover у файлі `config/queue.php` вашого застосунку:

```php
'failover' => [
    'driver' => 'failover',
    'connections' => [
        'redis',
        'database',
        'sync',
    ],
],
```

Щойно ви налаштували підключення з драйвером `failover`, вам потрібно зробити його підключенням черги за замовчуванням у файлі `.env` вашого застосунку, щоб скористатися цією можливістю:

```ini
QUEUE_CONNECTION=failover
```

Далі запустіть щонайменше один воркер для кожного підключення зі свого списку failover:

```shell
php artisan queue:work redis
php artisan queue:work database
```

> [!NOTE]
> Запускати воркер для підключень із драйверами черг `sync`, `background` чи `deferred` не потрібно, адже ці драйвери обробляють завдання в поточному процесі PHP.

Коли операція з підключенням черги провалюється й активується failover, Laravel диспетчеризує подію `Illuminate\Queue\Events\QueueFailedOver`, тож ви можете повідомити про це чи залогувати, що підключення черги провалилося.

> [!NOTE]
> Якщо ви користуєтеся Laravel Horizon, пам'ятайте: Horizon керує лише чергами Redis. Якщо ваш список failover містить `database`, вам слід запустити звичайний процес `php artisan queue:work database` поряд із Horizon.

<a name="error-handling"></a>
### Обробка помилок

Якщо під час обробки завдання викинуто виняток, завдання автоматично повернеться до черги, щоб його спробували виконати знову. Завдання повертатимуть, доки не буде вичерпано максимальну кількість спроб, дозволену вашим застосунком. Максимум спроб задають перемикачем `--tries` команди Artisan `queue:work`. Як варіант, максимум спроб можна задати в самому класі завдання. Докладніше про запуск воркера черги [читайте нижче](#running-the-queue-worker).

<a name="manually-releasing-a-job"></a>
#### Ручне звільнення завдання

Інколи вам може захотітися вручну повернути завдання до черги, щоб його спробували виконати пізніше. Це робиться викликом методу `release`:

```php
/**
 * Execute the job.
 */
public function handle(): void
{
    // ...

    $this->release();
}
```

За замовчуванням метод `release` поверне завдання до черги на негайну обробку. Проте ви можете сказати черзі не робити завдання доступним для обробки, доки не мине задана кількість секунд, - передайте методу `release` ціле число чи екземпляр дати:

```php
$this->release(10);

$this->release(now()->plus(seconds: 10));
```

<a name="manually-failing-a-job"></a>
#### Ручне позначення завдання невдалим

Інколи вам може знадобитися вручну позначити завдання «невдалим». Для цього викличте метод `fail`:

```php
/**
 * Execute the job.
 */
public function handle(): void
{
    // ...

    $this->fail();
}
```

Якщо ви хочете позначити завдання невдалим через спійманий виняток, передайте цей виняток методу `fail`. Або, для зручності, передайте рядок із повідомленням про помилку, який буде перетворено на виняток за вас:

```php
$this->fail($exception);

$this->fail('Something went wrong.');
```

> [!NOTE]
> Докладніше про невдалі завдання читайте в [документації про роботу з невдалими завданнями](#dealing-with-failed-jobs).

<a name="fail-jobs-on-exceptions"></a>
#### Невдача завдань на конкретних винятках

[Middleware завдання](#job-middleware) `FailOnException` дозволяє припинити повтори, коли викинуто конкретні винятки. Це дозволяє повторювати спроби на тимчасових винятках - наприклад, помилках зовнішнього API, - але остаточно провалювати завдання на стійких, як-от відкликанні прав користувача:

```php
<?php

namespace App\Jobs;

use App\Models\User;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\Middleware\FailOnException;
use Illuminate\Support\Facades\Http;

#[Tries(3)]
class SyncChatHistory implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new job instance.
     */
    public function __construct(
        public User $user,
    ) {}

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        $this->user->authorize('sync-chat-history');

        $response = Http::throw()->get(
            "https://chat.laravel.test/?user={$this->user->uuid}"
        );

        // ...
    }

    /**
     * Get the middleware the job should pass through.
     */
    public function middleware(): array
    {
        return [
            new FailOnException([AuthorizationException::class])
        ];
    }
}
```

<a name="job-batching"></a>
## Пакети завдань

Пакети завдань у Laravel дозволяють легко виконати групу завдань паралельно, а потім зробити щось, коли пакет завершив виконання.

Перш ніж почати, створіть міграцію бази даних, яка збудує таблицю з метаінформацією про ваші пакети завдань - наприклад, відсотком виконання. Цю міграцію можна згенерувати командою Artisan `make:queue-batches-table`:

```shell
php artisan make:queue-batches-table

php artisan migrate
```

<a name="defining-batchable-jobs"></a>
### Опис пакетованих завдань

Щоб описати пакетоване завдання, [створіть завдання для черги](#creating-jobs) як зазвичай; проте додайте до класу завдання трейт `Illuminate\Bus\Batchable`. Цей трейт надає доступ до методу `batch`, яким можна дістати поточний пакет, у межах якого виконується завдання:

```php
<?php

namespace App\Jobs;

use Illuminate\Bus\Batchable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;

class ImportCsv implements ShouldQueue
{
    use Batchable, Queueable;

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        if ($this->batch()->cancelled()) {
            // Determine if the batch has been cancelled...

            return;
        }

        // Import a portion of the CSV file...
    }
}
```

<a name="dispatching-batches"></a>
### Диспетчеризація пакетів

Щоб диспетчеризувати пакет завдань, скористайтеся методом `batch` фасаду `Bus`. Звісно, пакети насамперед корисні в поєднанні з колбеками завершення. Тож ви можете скористатися методами `then`, `catch` та `finally`, щоб описати колбеки завершення пакета. Кожен із них отримає екземпляр `Illuminate\Bus\Batch` під час виклику.

Коли працює кілька воркерів черги, завдання пакета обробляються паралельно. Тому порядок їх завершення може не збігатися з порядком, у якому їх додано до пакета. Про те, як виконати серію завдань послідовно, читайте в нашій документації про [ланцюжки й пакети](#chains-and-batches).

У цьому прикладі уявімо, що ми ставимо в чергу пакет завдань, кожне з яких обробляє задану кількість рядків CSV-файлу:

```php
use App\Jobs\ImportCsv;
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;
use Throwable;

$batch = Bus::batch([
    new ImportCsv(1, 100),
    new ImportCsv(101, 200),
    new ImportCsv(201, 300),
    new ImportCsv(301, 400),
    new ImportCsv(401, 500),
])->before(function (Batch $batch) {
    // The batch has been created but no jobs have been added...
})->progress(function (Batch $batch) {
    // A single job has completed successfully...
})->then(function (Batch $batch) {
    // All jobs completed successfully...
})->catch(function (Batch $batch, Throwable $e) {
    // Batch job failure detected...
})->finally(function (Batch $batch) {
    // The batch has finished executing...
})->dispatch();

return $batch->id;
```

ID пакета, доступний через властивість `$batch->id`, дозволяє [запитувати командну шину Laravel](#inspecting-batches) про інформацію щодо пакета після його диспетчеризації.

> [!WARNING]
> Оскільки колбеки пакета серіалізуються й виконуються пізніше чергою Laravel, не використовуйте в них змінну `$this`. Крім того, оскільки пакетовані завдання огорнуто транзакціями бази даних, усередині них не слід виконувати SQL-інструкцій, які спричиняють неявну фіксацію.

<a name="naming-batches"></a>
#### Іменування пакетів

Деякі інструменти - як-от [Laravel Horizon](/docs/{{version}}/horizon) та [Laravel Telescope](/docs/{{version}}/telescope) - можуть давати зручнішу налагоджувальну інформацію про пакети, якщо ті мають назви. Щоб призначити пакету довільну назву, викличте метод `name` під час його опису:

```php
$batch = Bus::batch([
    // ...
])->then(function (Batch $batch) {
    // All jobs completed successfully...
})->name('Import CSV')->dispatch();
```

<a name="batch-connection-queue"></a>
#### Підключення та черга пакета

Якщо ви хочете вказати підключення й чергу для пакетованих завдань, скористайтеся методами `onConnection` та `onQueue`. Усі пакетовані завдання мають виконуватися в межах одного підключення й однієї черги:

```php
$batch = Bus::batch([
    // ...
])->then(function (Batch $batch) {
    // All jobs completed successfully...
})->onConnection('redis')->onQueue('imports')->dispatch();
```

<a name="chains-and-batches"></a>
### Ланцюжки й пакети

Ви можете описати набір [завдань у ланцюжку](#job-chaining) усередині пакета, поклавши ці завдання в масив. Наприклад, ми можемо виконати два ланцюжки завдань паралельно й виконати колбек, коли обидва завершать обробку:

```php
use App\Jobs\ReleasePodcast;
use App\Jobs\SendPodcastReleaseNotification;
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;

Bus::batch([
    [
        new ReleasePodcast(1),
        new SendPodcastReleaseNotification(1),
    ],
    [
        new ReleasePodcast(2),
        new SendPodcastReleaseNotification(2),
    ],
])->then(function (Batch $batch) {
    // All jobs completed successfully...
})->dispatch();
```

І навпаки, ви можете виконувати пакети завдань усередині [ланцюжка](#job-chaining), описавши пакети в ньому. Наприклад, ви могли б спершу виконати пакет завдань, який публікує кілька подкастів, а потім пакет, який надсилає сповіщення про публікацію:

```php
use App\Jobs\FlushPodcastCache;
use App\Jobs\ReleasePodcast;
use App\Jobs\SendPodcastReleaseNotification;
use Illuminate\Support\Facades\Bus;

Bus::chain([
    new FlushPodcastCache,
    Bus::batch([
        new ReleasePodcast(1),
        new ReleasePodcast(2),
    ]),
    Bus::batch([
        new SendPodcastReleaseNotification(1),
        new SendPodcastReleaseNotification(2),
    ]),
])->dispatch();
```

<a name="adding-jobs-to-batches"></a>
### Додавання завдань до пакетів

Інколи буває корисно додати до пакета додаткові завдання зсередини пакетованого завдання. Цей патерн стане в пригоді, коли вам треба запакувати тисячі завдань, диспетчеризація яких зайняла б надто багато часу під час вебзапиту. Тож натомість ви можете диспетчеризувати початковий пакет «завантажувальних» завдань, які наповнять пакет іще більшою кількістю завдань:

```php
$batch = Bus::batch([
    new LoadImportBatch,
    new LoadImportBatch,
    new LoadImportBatch,
])->then(function (Batch $batch) {
    // All jobs completed successfully...
})->name('Import Contacts')->dispatch();
```

У цьому прикладі ми скористаємося завданням `LoadImportBatch`, щоб наповнити пакет додатковими завданнями. Для цього ми можемо скористатися методом `add` на екземплярі пакета, доступному через метод `batch` завдання:

```php
use App\Jobs\ImportContacts;
use Illuminate\Support\Collection;

/**
 * Execute the job.
 */
public function handle(): void
{
    if ($this->batch()->cancelled()) {
        return;
    }

    $this->batch()->add(Collection::times(1000, function () {
        return new ImportContacts;
    }));
}
```

> [!WARNING]
> Додавати завдання до пакета можна лише зсередини завдання, яке належить до цього самого пакета.

<a name="inspecting-batches"></a>
### Огляд пакетів

Екземпляр `Illuminate\Bus\Batch`, який передається до колбеків завершення пакета, має низку властивостей і методів, що допомагають працювати з пакетом завдань і оглядати його:

```php
// The UUID of the batch...
$batch->id;

// The name of the batch (if applicable)...
$batch->name;

// The number of jobs assigned to the batch...
$batch->totalJobs;

// The number of jobs that have not been processed by the queue...
$batch->pendingJobs;

// The number of jobs that have failed...
$batch->failedJobs;

// The number of jobs that have been processed thus far...
$batch->processedJobs();

// The completion percentage of the batch (0-100)...
$batch->progress();

// Indicates if the batch has finished executing...
$batch->finished();

// Cancel the execution of the batch...
$batch->cancel();

// Indicates if the batch has been cancelled...
$batch->cancelled();
```

<a name="returning-batches-from-routes"></a>
#### Повернення пакетів із маршрутів

Усі екземпляри `Illuminate\Bus\Batch` серіалізуються в JSON, тож ви можете повертати їх напряму з маршрутів свого застосунку, щоб отримати JSON-дані з інформацією про пакет, включно з прогресом виконання. Це зручно, щоб показувати прогрес пакета в інтерфейсі вашого застосунку.

Щоб дістати пакет за його ID, скористайтеся методом `findBatch` фасаду `Bus`:

```php
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Route;

Route::get('/batch/{batchId}', function (string $batchId) {
    return Bus::findBatch($batchId);
});
```

<a name="cancelling-batches"></a>
### Скасування пакетів

Інколи вам може знадобитися скасувати виконання пакета. Це робиться викликом методу `cancel` на екземплярі `Illuminate\Bus\Batch`:

```php
/**
 * Execute the job.
 */
public function handle(): void
{
    if ($this->user->exceedsImportLimit()) {
        $this->batch()->cancel();

        return;
    }

    if ($this->batch()->cancelled()) {
        return;
    }
}
```

Як ви могли помітити в попередніх прикладах, пакетовані завдання зазвичай мають перевіряти, чи не скасовано відповідний пакет, перш ніж продовжувати виконання. Проте для зручності ви можете натомість призначити завданню [middleware](#job-middleware) `SkipIfBatchCancelled`. Як випливає з назви, цей middleware скаже Laravel не обробляти завдання, якщо відповідний пакет скасовано:

```php
use Illuminate\Queue\Middleware\SkipIfBatchCancelled;

/**
 * Get the middleware the job should pass through.
 */
public function middleware(): array
{
    return [new SkipIfBatchCancelled];
}
```

<a name="batch-failures"></a>
### Невдачі пакетів

Коли пакетоване завдання зазнає невдачі, буде викликано колбек `catch` (якщо його призначено). Цей колбек викликається лише для першого завдання, що провалилося в пакеті.

<a name="allowing-failures"></a>
#### Дозвіл на невдачі

Коли завдання в пакеті провалюється, Laravel автоматично позначає пакет «скасованим». За бажанням ви можете вимкнути цю поведінку, щоб невдача завдання не скасовувала пакет автоматично. Це робиться викликом методу `allowFailures` під час диспетчеризації пакета:

```php
$batch = Bus::batch([
    // ...
])->then(function (Batch $batch) {
    // All jobs completed successfully...
})->allowFailures()->dispatch();
```

За бажанням ви можете передати методу `allowFailures` замикання, яке виконуватиметься на кожній невдачі завдання:

```php
$batch = Bus::batch([
    // ...
])->allowFailures(function (Batch $batch, $exception) {
    // Handle individual job failures...
})->dispatch();
```

<a name="retrying-failed-batch-jobs"></a>
#### Повторний запуск невдалих завдань пакета

Для зручності Laravel надає команду Artisan `queue:retry-batch`, яка дозволяє легко повторити всі невдалі завдання конкретного пакета. Ця команда приймає UUID пакета, невдалі завдання якого слід повторити:

```shell
php artisan queue:retry-batch 32dbc76c-4f82-4749-b610-a639fe0099b5
```

<a name="pruning-batches"></a>
### Очищення пакетів

Без очищення таблиця `job_batches` дуже швидко накопичує записи. Щоб цьому зарадити, [заплануйте](/docs/{{version}}/scheduling) щоденне виконання команди Artisan `queue:prune-batches`:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('queue:prune-batches')->daily();
```

За замовчуванням буде очищено всі завершені пакети, старші за 24 години. Ви можете скористатися опцією `hours` під час виклику команди, щоб визначити, як довго зберігати дані пакетів. Наприклад, команда нижче видалить усі пакети, які завершилися понад 48 годин тому:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('queue:prune-batches --hours=48')->daily();
```

Інколи ваша таблиця `job_batches` може накопичувати записи пакетів, які так і не завершилися успішно, - наприклад, пакетів, у яких завдання провалилося й так і не було успішно повторено. Ви можете сказати команді `queue:prune-batches` очищати такі незавершені записи опцією `unfinished`:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('queue:prune-batches --hours=48 --unfinished=72')->daily();
```

Так само ваша таблиця `job_batches` може накопичувати записи скасованих пакетів. Ви можете сказати команді `queue:prune-batches` очищати такі записи опцією `cancelled`:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('queue:prune-batches --hours=48 --cancelled=72')->daily();
```

<a name="storing-batches-in-dynamodb"></a>
### Зберігання пакетів у DynamoDB

Laravel також підтримує зберігання метаінформації пакетів у [DynamoDB](https://aws.amazon.com/dynamodb) замість реляційної бази даних. Проте вам доведеться вручну створити таблицю DynamoDB для зберігання всіх записів пакетів.

Зазвичай цю таблицю називають `job_batches`, але вам слід назвати її відповідно до значення конфігурації `queue.batching.table` у файлі конфігурації `queue` вашого застосунку.

<a name="dynamodb-batch-table-configuration"></a>
#### Конфігурація таблиці пакетів у DynamoDB

Таблиця `job_batches` має мати рядковий первинний ключ розділу `application` і рядковий первинний ключ сортування `id`. Частина ключа `application` міститиме назву вашого застосунку, задану значенням конфігурації `name` у файлі конфігурації `app`. Оскільки назва застосунку є частиною ключа таблиці DynamoDB, ви можете зберігати в одній таблиці пакети завдань кількох застосунків Laravel.

Крім того, ви можете описати для своєї таблиці атрибут `ttl`, якщо хочете скористатися [автоматичним очищенням пакетів](#pruning-batches-in-dynamodb).

<a name="dynamodb-configuration"></a>
#### Конфігурація DynamoDB

Далі встановіть AWS SDK, щоб ваш застосунок Laravel міг спілкуватися з Amazon DynamoDB:

```shell
composer require aws/aws-sdk-php
```

Потім задайте опції конфігурації `queue.batching.driver` значення `dynamodb`. Крім того, опишіть у масиві конфігурації `batching` опції `key`, `secret` та `region`. Вони використовуватимуться для автентифікації в AWS. Коли ви користуєтеся драйвером `dynamodb`, опція конфігурації `queue.batching.database` не потрібна:

```php
'batching' => [
    'driver' => env('QUEUE_BATCHING_DRIVER', 'dynamodb'),
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    'table' => 'job_batches',
],
```

<a name="pruning-batches-in-dynamodb"></a>
#### Очищення пакетів у DynamoDB

Коли ви зберігаєте інформацію про пакети завдань у [DynamoDB](https://aws.amazon.com/dynamodb), звичайні команди очищення, які працюють із реляційною базою, не діятимуть. Натомість ви можете скористатися [рідною функціональністю TTL у DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html), щоб автоматично вилучати записи старих пакетів.

Якщо ви описали свою таблицю DynamoDB з атрибутом `ttl`, ви можете задати параметри конфігурації, які скажуть Laravel, як очищати записи пакетів. Значення конфігурації `queue.batching.ttl_attribute` задає назву атрибута з TTL, а значення `queue.batching.ttl` - кількість секунд, після яких запис пакета можна вилучити з таблиці DynamoDB, відлічуючи від останнього оновлення запису:

```php
'batching' => [
    'driver' => env('QUEUE_BATCHING_DRIVER', 'dynamodb'),
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    'table' => 'job_batches',
    'ttl_attribute' => 'ttl',
    'ttl' => 60 * 60 * 24 * 7, // 7 days...
],
```

<a name="queueing-closures"></a>
## Замикання в черзі

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

```php
use App\Models\Podcast;

$podcast = Podcast::find(1);

dispatch(function () use ($podcast) {
    $podcast->publish();
});
```

Щоб призначити замиканню в черзі назву, яку зможуть використовувати панелі звітності черг і яку показуватиме команда `queue:work`, скористайтеся методом `name`:

```php
dispatch(function () {
    // ...
})->name('Publish Podcast');
```

Методом `catch` ви можете передати замикання, яке слід виконати, якщо замикання в черзі не завершиться успішно, вичерпавши всі [налаштовані спроби](#max-job-attempts-and-timeout) вашої черги:

```php
use Throwable;

dispatch(function () use ($podcast) {
    $podcast->publish();
})->catch(function (Throwable $e) {
    // This job has failed...
});
```

> [!WARNING]
> Оскільки колбеки `catch` серіалізуються й виконуються пізніше чергою Laravel, не використовуйте в них змінну `$this`.

<a name="running-the-queue-worker"></a>
## Запуск воркера черги

<a name="the-queue-work-command"></a>
### Команда `queue:work`

Laravel містить команду Artisan, яка запускає воркер черги й обробляє нові завдання, щойно вони потрапляють у чергу. Запустити воркер можна командою Artisan `queue:work`. Зверніть увагу: щойно команду `queue:work` запущено, вона працюватиме, доки ви не зупините її вручну чи не закриєте термінал:

```shell
php artisan queue:work
```

> [!NOTE]
> Щоб процес `queue:work` постійно працював у фоні, скористайтеся монітором процесів на кшталт [Supervisor](#supervisor-configuration), який подбає, щоб воркер черги не зупинявся.

Ви можете додати прапорець `-v` під час виклику команди `queue:work`, якщо хочете, щоб у виводі команди були ID оброблених завдань, назви підключень і черг:

```shell
php artisan queue:work -v
```

Пам'ятайте: воркери черги - це довгограючі процеси, які тримають завантажений стан застосунку в пам'яті. Тому вони не помітять змін у вашій кодовій базі після запуску. Отже, під час розгортання обов'язково [перезапускайте свої воркери черги](#queue-workers-and-deployment). Крім того, пам'ятайте, що будь-який статичний стан, створений чи змінений вашим застосунком, не скидатиметься автоматично між завданнями.

Як варіант, ви можете виконати команду `queue:listen`. Коли ви користуєтеся `queue:listen`, вам не доводиться вручну перезапускати воркер, щоб підхопити оновлений код чи скинути стан застосунку; проте ця команда значно менш ефективна за `queue:work`:

```shell
php artisan queue:listen
```

<a name="running-multiple-queue-workers"></a>
#### Запуск кількох воркерів черги

Щоб призначити черзі кілька воркерів і обробляти завдання паралельно, просто запустіть кілька процесів `queue:work`. Це можна зробити локально в кількох вкладках термінала або в продакшені через налаштування вашого менеджера процесів. [Користуючись Supervisor](#supervisor-configuration), скористайтеся значенням конфігурації `numprocs`.

<a name="specifying-the-connection-queue"></a>
#### Задання підключення та черги

Ви також можете вказати, яке підключення черги має використовувати воркер. Назва підключення, передана команді `work`, має відповідати одному з підключень, описаних у файлі `config/queue.php`:

```shell
php artisan queue:work redis
```

За замовчуванням команда `queue:work` обробляє завдання лише з черги за замовчуванням заданого підключення. Проте ви можете налаштувати воркер іще тонше, обробляючи лише певні черги конкретного підключення. Наприклад, якщо всі ваші листи обробляються в черзі `emails` на підключенні `redis`, ви можете виконати таку команду, щоб запустити воркер, який обробляє лише цю чергу:

```shell
php artisan queue:work redis --queue=emails
```

<a name="processing-a-specified-number-of-jobs"></a>
#### Обробка заданої кількості завдань

Опція `--once` дозволяє сказати воркеру обробити з черги лише одне завдання:

```shell
php artisan queue:work --once
```

Опція `--max-jobs` дозволяє сказати воркеру обробити задану кількість завдань і завершитися. Ця опція стане в пригоді в поєднанні із [Supervisor](#supervisor-configuration), щоб ваші воркери автоматично перезапускалися після обробки певної кількості завдань, звільняючи накопичену пам'ять:

```shell
php artisan queue:work --max-jobs=1000
```

<a name="processing-all-queued-jobs-then-exiting"></a>
#### Обробка всіх завдань у черзі й завершення

Опція `--stop-when-empty` дозволяє сказати воркеру обробити всі завдання й коректно завершитися. Ця опція стане в пригоді під час обробки черг Laravel у Docker-контейнері, якщо ви хочете зупинити контейнер, коли черга спорожніє:

```shell
php artisan queue:work --stop-when-empty
```

<a name="processing-jobs-for-a-given-number-of-seconds"></a>
#### Обробка завдань протягом заданої кількості секунд

Опція `--max-time` дозволяє сказати воркеру обробляти завдання протягом заданої кількості секунд і завершитися. Ця опція стане в пригоді в поєднанні із [Supervisor](#supervisor-configuration), щоб ваші воркери автоматично перезапускалися після певного часу роботи, звільняючи накопичену пам'ять:

```shell
# Process jobs for one hour and then exit...
php artisan queue:work --max-time=3600
```

<a name="worker-sleep-duration"></a>
#### Тривалість сну воркера

Коли в черзі є завдання, воркер оброблятиме їх без затримок між ними. Проте опція `sleep` визначає, скільки секунд воркер «спатиме», якщо завдань немає. Звісно, поки він спить, нові завдання не оброблятимуться:

```shell
php artisan queue:work --sleep=3
```

<a name="maintenance-mode-queues"></a>
#### Режим обслуговування й черги

Доки ваш застосунок у [режимі обслуговування](/docs/{{version}}/configuration#maintenance-mode), жодне завдання з черги не оброблятиметься. Завдання оброблятимуться як зазвичай, щойно застосунок вийде з режиму обслуговування.

Щоб змусити воркери обробляти завдання навіть у режимі обслуговування, скористайтеся опцією `--force`:

```shell
php artisan queue:work --force
```

<a name="resource-considerations"></a>
#### Міркування щодо ресурсів

Опція `--memory` вказує ліміт пам'яті в мегабайтах, при досягненні якого воркер завершить роботу. За замовчуванням цей ліміт становить 128 мегабайт. Ви також можете вказати відсоток від налаштованого в PHP `memory_limit`:

```shell
php artisan queue:work --memory=60%
```

Ліміти на основі відсотків вимагають скінченного PHP `memory_limit`. Якщо ліміт пам'яті PHP необмежений (`-1`), використовуйте ліміт у мегабайтах. Монітор процесів на кшталт [Supervisor](#supervisor-configuration) може автоматично перезапустити воркер після його завершення.

Демонізовані воркери черги не «перезавантажують» фреймворк перед обробкою кожного завдання. Тому вам слід звільняти важкі ресурси після завершення кожного завдання. Наприклад, якщо ви робите [обробку зображень](/docs/{{version}}/images) [бібліотекою GD](https://www.php.net/manual/en/book.image.php), звільняйте пам'ять через `imagedestroy`, коли завершили роботу із зображенням.

<a name="queue-priorities"></a>
### Пріоритети черг

Інколи вам може захотітися пріоритезувати обробку своїх черг. Наприклад, у файлі `config/queue.php` ви можете задати чергою `queue` за замовчуванням для підключення `redis` значення `low`. Проте інколи вам може захотітися покласти завдання в чергу високого пріоритету `high` ось так:

```php
dispatch((new Job)->onQueue('high'));
```

Щоб запустити воркер, який гарантує, що всі завдання черги `high` оброблено перед переходом до завдань черги `low`, передайте команді `work` список назв черг через кому:

```shell
php artisan queue:work --queue=high,low
```

<a name="queue-workers-and-deployment"></a>
### Воркери черги й розгортання

Оскільки воркери черги - довгограючі процеси, вони не помітять змін у вашому коді без перезапуску. Тож найпростіший спосіб розгортати застосунок із воркерами черги - перезапускати їх під час розгортання. Коректно перезапустити всі воркери можна командою `queue:restart`:

```shell
php artisan queue:restart
```

Ця команда скаже всім воркерам черги коректно завершитися після обробки поточного завдання, щоб жодне наявне завдання не було втрачено. Оскільки воркери завершаться під час виконання `queue:restart`, вам слід мати менеджер процесів на кшталт [Supervisor](#supervisor-configuration), який автоматично їх перезапустить.

> [!NOTE]
> Черга використовує [кеш](/docs/{{version}}/cache) для зберігання сигналів перезапуску, тож перед використанням цієї можливості переконайтеся, що драйвер кешу належно налаштовано у вашому застосунку.

<a name="reacting-to-worker-signals"></a>
### Реакція на сигнали воркера

Коли воркер черги отримує сигнал завершення - `SIGQUIT`, `SIGTERM` чи `SIGINT` - під час обробки завдання, він завершить поточне завдання й лише тоді вийде. Проте вашому завданню може знадобитися відреагувати на сигнал, перш ніж сервер чи оркестратор контейнерів зупинить процес. Наприклад, довготривале завдання імпорту може мати припинити діставати нові записи й зберегти поточний прогрес.

Щоб реагувати на сигнали воркера зсередини завдання, реалізуйте інтерфейс `Illuminate\Contracts\Queue\Interruptible` й опишіть у завданні метод `interrupted`. Номер сигналу, отриманого воркером, буде передано до методу `interrupted`:

```php
<?php

namespace App\Jobs;

use App\Models\Import;
use Illuminate\Contracts\Queue\Interruptible;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;

class ImportProducts implements ShouldQueue, Interruptible
{
    use Queueable;

    protected bool $shouldStop = false;

    /**
     * Create a new job instance.
     */
    public function __construct(
        public Import $import,
    ) {}

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        foreach ($this->import->pendingRows() as $row) {
            if ($this->shouldStop) {
                break;
            }

            // Import the product row...
        }

        $this->import->saveProgress();
    }

    /**
     * Handle a signal received by the queue worker.
     */
    public function interrupted(int $signal): void
    {
        $this->shouldStop = true;
    }
}
```

Метод `interrupted` викликається лише тоді, коли воркер отримує сигнал процесу під час виконання завдання. Він не замінює [таймаутів](#worker-timeouts) чи [методу `failed`](#cleaning-up-after-failed-jobs) завдання.

<a name="job-expirations-and-timeouts"></a>
### Спливання й таймаути завдань

<a name="job-expiration"></a>
#### Спливання завдання

У файлі конфігурації `config/queue.php` кожне підключення черги описує опцію `retry_after`. Ця опція задає, скільки секунд підключення має чекати, перш ніж повторити завдання, яке обробляється. Наприклад, якщо значення `retry_after` дорівнює `90`, завдання повернеться до черги, якщо воно оброблялося 90 секунд і не було звільнене чи видалене. Зазвичай значення `retry_after` варто задавати рівним максимальній кількості секунд, яку ваші завдання розумно можуть обробляти.

> [!WARNING]
> Єдине підключення черги, яке не має значення `retry_after`, - Amazon SQS. SQS повторить завдання на основі [Default Visibility Timeout](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/AboutVT.html), яким керують у консолі AWS.

<a name="worker-timeouts"></a>
#### Таймаути воркера

Команда Artisan `queue:work` має опцію `--timeout`. За замовчуванням значення `--timeout` становить 60 секунд. Якщо завдання обробляється довше за вказану кількість секунд, воркер, який його обробляє, завершиться з помилкою. Зазвичай воркер автоматично перезапустить [менеджер процесів, налаштований на вашому сервері](#supervisor-configuration):

```shell
php artisan queue:work --timeout=60
```

Опція конфігурації `retry_after` та CLI-опція `--timeout` різні, але працюють разом, щоб завдання не губилися й оброблялися успішно лише один раз.

> [!WARNING]
> Значення `--timeout` завжди має бути щонайменше на кілька секунд меншим за значення конфігурації `retry_after`. Це гарантує, що воркер, який обробляє зависле завдання, завжди завершиться до того, як завдання повторять. Якщо ваша опція `--timeout` довша за значення `retry_after`, ваші завдання можуть оброблятися двічі.

<a name="pausing-and-resuming-queue-workers"></a>
### Призупинення й поновлення воркерів черги

Інколи вам може знадобитися тимчасово завадити воркеру обробляти нові завдання, не зупиняючи його повністю. Наприклад, ви можете хотіти призупинити обробку завдань під час обслуговування системи. Laravel надає команди Artisan `queue:pause` та `queue:continue`, щоб призупиняти й поновлювати воркери черги.

Щоб призупинити конкретну чергу, передайте назву підключення й назву черги:

```shell
php artisan queue:pause database:default
```

У цьому прикладі `database` - назва підключення черги, а `default` - назва черги. Щойно чергу призупинено, воркери, які обробляють з неї завдання, завершать поточне завдання, але не візьмуть нових, доки чергу не поновлять.

Щоб призупинити обробку завдань для кожної черги на кожному підключенні, скористайтеся опцією `--all`:

```shell
php artisan queue:pause --all
```

Щоб поновити обробку завдань у призупиненій черзі, скористайтеся командою `queue:continue`:

```shell
php artisan queue:continue database:default
```

Щоб поновити обробку завдань для кожної черги на кожному підключенні, скористайтеся опцією `--all` з командою `queue:resume`:

```shell
php artisan queue:resume --all
```

Після поновлення черги воркери одразу почнуть обробляти з неї нові завдання. Поновлення всіх черг не поновлює черг, які були призупинені індивідуально. Зверніть увагу: призупинення черги не зупиняє самого процесу воркера - воно лише не дає воркеру брати нові завдання з указаної черги.

<a name="worker-restart-and-pause-signals"></a>
#### Сигнали перезапуску й призупинення воркера

За замовчуванням воркери черги опитують драйвер кешу на предмет сигналів перезапуску й призупинення на кожній ітерації завдання. Хоч це опитування й потрібне для реакції на команди `queue:restart` і `queue:pause`, воно додає невеликі накладні витрати.

Якщо вам треба оптимізувати швидкодію й ці можливості переривання не потрібні, ви можете вимкнути опитування глобально, викликавши метод `withoutInterruptionPolling` фасаду `Queue`. Зазвичай це роблять у методі `boot` вашого `AppServiceProvider`:

```php
use Illuminate\Support\Facades\Queue;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Queue::withoutInterruptionPolling();
}
```

Як варіант, ви можете вимкнути опитування перезапуску чи призупинення окремо, задавши статичні властивості `$restartable` або `$pausable` класу `Illuminate\Queue\Worker`:

```php
use Illuminate\Queue\Worker;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Worker::$restartable = false;
    Worker::$pausable = false;
}
```

> [!WARNING]
> Коли опитування переривань вимкнено, воркери не реагуватимуть на команди `queue:restart` чи `queue:pause` (залежно від того, які можливості вимкнено).

<a name="supervisor-configuration"></a>
## Конфігурація Supervisor

У продакшені вам потрібен спосіб тримати процеси `queue:work` запущеними. Процес `queue:work` може зупинитися з різних причин - через перевищений таймаут воркера чи виконання команди `queue:restart`.

Тому вам потрібно налаштувати монітор процесів, який виявляє, коли ваші процеси `queue:work` завершуються, і автоматично їх перезапускає. Крім того, монітори процесів дозволяють указати, скільки процесів `queue:work` ви хочете запускати паралельно. Supervisor - монітор процесів, який часто використовують у Linux-середовищах, і далі ми розглянемо, як його налаштувати.

<a name="installing-supervisor"></a>
#### Встановлення Supervisor

Supervisor - монітор процесів для операційної системи Linux, який автоматично перезапустить ваші процеси `queue:work`, якщо вони провалилися. Щоб встановити Supervisor в Ubuntu, скористайтеся такою командою:

```shell
sudo apt-get install supervisor
```

> [!NOTE]
> Якщо налаштовувати Supervisor і керувати ним самотужки звучить надто складно, розгляньте [Laravel Cloud](https://cloud.laravel.com) - повністю керовану платформу для запуску воркерів черг Laravel.

<a name="configuring-supervisor"></a>
#### Налаштування Supervisor

Файли конфігурації Supervisor зазвичай зберігаються в каталозі `/etc/supervisor/conf.d`. У цьому каталозі ви можете створити скільки завгодно файлів конфігурації, які кажуть supervisor, як стежити за вашими процесами. Наприклад, створімо файл `laravel-worker.conf`, який запускає процеси `queue:work` і стежить за ними:

```ini
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /home/forge/app.com/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=forge
numprocs=8
redirect_stderr=true
stdout_logfile=/home/forge/app.com/worker.log
stopwaitsecs=3600
```

У цьому прикладі директива `numprocs` скаже Supervisor запустити вісім процесів `queue:work` і стежити за всіма, автоматично перезапускаючи їх у разі падіння. Змініть директиву `command` конфігурації відповідно до потрібного вам підключення черги й опцій воркера.

> [!WARNING]
> Подбайте, щоб значення `stopwaitsecs` було більшим за кількість секунд, які триває ваше найдовше завдання. Інакше Supervisor може вбити завдання, перш ніж воно завершить обробку.

<a name="starting-supervisor"></a>
#### Запуск Supervisor

Щойно файл конфігурації створено, ви можете оновити конфігурацію Supervisor і запустити процеси такими командами:

```shell
sudo supervisorctl reread

sudo supervisorctl update

sudo supervisorctl start "laravel-worker:*"
```

Докладніше про Supervisor читайте в [документації Supervisor](http://supervisord.org/index.html).

<a name="dealing-with-failed-jobs"></a>
## Робота з невдалими завданнями

Інколи ваші завдання в черзі провалюються. Не переймайтеся, не все й не завжди йде за планом! Laravel містить зручний спосіб [задати максимальну кількість спроб виконати завдання](#max-job-attempts-and-timeout). Коли асинхронне завдання перевищить цю кількість спроб, його буде вставлено до таблиці `failed_jobs`. [Синхронно диспетчеризовані завдання](/docs/{{version}}/queues#synchronous-dispatching), які провалилися, у цій таблиці не зберігаються, а їхні винятки одразу обробляє застосунок.

Міграція для створення таблиці `failed_jobs` зазвичай уже є в нових застосунках Laravel. Проте, якщо у вашому застосунку її немає, створити міграцію можна командою `make:queue-failed-table`:

```shell
php artisan make:queue-failed-table

php artisan migrate
```

Запускаючи процес [воркера черги](#running-the-queue-worker), ви можете задати максимальну кількість спроб виконати завдання перемикачем `--tries` команди `queue:work`. Якщо ви не вкажете значення опції `--tries`, завдання виконуватимуться лише раз або стільки разів, скільки задано атрибутом `Tries` класу завдання:

```shell
php artisan queue:work redis --tries=3
```

Опцією `--backoff` ви можете вказати, скільки секунд Laravel має чекати перед повторною спробою виконати завдання, яке натрапило на виняток. За замовчуванням завдання одразу повертається до черги, щоб його спробували знову:

```shell
php artisan queue:work redis --tries=3 --backoff=3
```

Якщо ви хочете налаштувати, скільки секунд Laravel має чекати перед повтором завдання, що натрапило на виняток, для кожного завдання окремо, скористайтеся атрибутом `Backoff` у класі завдання:

```php
<?php

namespace App\Jobs;

use Illuminate\Queue\Attributes\Backoff;

#[Backoff(3)]
class ProcessPodcast implements ShouldQueue
{
    // ...
}
```

Якщо для визначення часу відступу завдання вам потрібна складніша логіка, опишіть у класі завдання метод `backoff`:

```php
/**
 * Calculate the number of seconds to wait before retrying the job.
 */
public function backoff(): int
{
    return 3;
}
```

Ви легко можете налаштувати «експоненційні» відступи, описавши масив значень. У цьому прикладі затримка перед повтором становитиме 1 секунду для першого повтору, 5 секунд для другого, 10 секунд для третього і 10 секунд для кожного наступного, якщо спроби ще лишилися:

```php
<?php

namespace App\Jobs;

use Illuminate\Queue\Attributes\Backoff;

#[Backoff([1, 5, 10])]
class ProcessPodcast implements ShouldQueue
{
    // ...
}
```

<a name="cleaning-up-after-failed-jobs"></a>
### Прибирання після невдалих завдань

Коли конкретне завдання провалюється, вам може захотітися надіслати сповіщення користувачам або відкотити дії, які завдання виконало частково. Для цього опишіть у класі завдання метод `failed`. До методу `failed` буде передано екземпляр `Throwable`, який спричинив невдачу:

```php
<?php

namespace App\Jobs;

use App\Models\Podcast;
use App\Services\AudioProcessor;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Throwable;

class ProcessPodcast implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new job instance.
     */
    public function __construct(
        public Podcast $podcast,
    ) {}

    /**
     * Execute the job.
     */
    public function handle(AudioProcessor $processor): void
    {
        // Process uploaded podcast...
    }

    /**
     * Handle a job failure.
     */
    public function failed(?Throwable $exception): void
    {
        // Send user notification of failure, etc...
    }
}
```

> [!WARNING]
> Перед викликом методу `failed` створюється новий екземпляр завдання; тому будь-які зміни властивостей класу, зроблені в методі `handle`, буде втрачено.

Невдале завдання - не обов'язково те, що натрапило на необроблений виняток. Завдання може вважатися невдалим і тоді, коли воно вичерпало всі дозволені спроби. Ці спроби можуть витрачатися кількома способами:

<div class="content-list" markdown="1">

- Завдання вичерпало таймаут.
- Завдання натрапило на необроблений виняток під час виконання.
- Завдання повернуто до черги вручну або middleware.

</div>

Якщо остання спроба провалилася через виняток, викинутий під час виконання завдання, цей виняток буде передано до методу `failed`. Проте, якщо завдання провалилося, бо досягло максимальної кількості дозволених спроб, `$exception` буде екземпляром `Illuminate\Queue\MaxAttemptsExceededException`. Так само, якщо завдання провалилося через перевищення налаштованого таймауту, `$exception` буде екземпляром `Illuminate\Queue\TimeoutExceededException`.

<a name="retrying-failed-jobs"></a>
### Повторний запуск невдалих завдань

Щоб переглянути всі невдалі завдання, які потрапили до таблиці `failed_jobs`, скористайтеся командою Artisan `queue:failed`:

```shell
php artisan queue:failed
```

Команда `queue:failed` виведе ID завдання, підключення, чергу, час невдачі та іншу інформацію. ID завдання дозволяє повторити невдале завдання. Наприклад, щоб повторити невдале завдання з ID `ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece`, виконайте таку команду:

```shell
php artisan queue:retry ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece
```

За потреби ви можете передати команді кілька ID:

```shell
php artisan queue:retry ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece 91401d2c-0784-4f43-824c-34f94a33c24d
```

Ви також можете повторити всі невдалі завдання конкретної черги:

```shell
php artisan queue:retry --queue=name
```

Щоб повторити всі свої невдалі завдання, виконайте команду `queue:retry` і передайте `all` як ID:

```shell
php artisan queue:retry all
```

Якщо ви хочете видалити невдале завдання, скористайтеся командою `queue:forget`:

```shell
php artisan queue:forget 91401d2c-0784-4f43-824c-34f94a33c24d
```

> [!NOTE]
> Користуючись [Horizon](/docs/{{version}}/horizon), видаляйте невдалі завдання командою `horizon:forget`, а не `queue:forget`.

Щоб видалити всі свої невдалі завдання з таблиці `failed_jobs`, скористайтеся командою `queue:flush`:

```shell
php artisan queue:flush
```

Команда `queue:flush` вилучає з вашої черги всі записи невдалих завдань, незалежно від їхнього віку. Ви можете скористатися опцією `--hours`, щоб видалити лише завдання, які провалилися певну кількість годин тому чи раніше:

```shell
php artisan queue:flush --hours=48
```

<a name="ignoring-missing-models"></a>
### Ігнорування відсутніх моделей

Коли ви впроваджуєте модель Eloquent у завдання, вона автоматично серіалізується перед потраплянням у чергу й дістається з бази наново під час обробки завдання. Проте, якщо модель було видалено, доки завдання чекало на обробку воркером, ваше завдання може провалитися з `ModelNotFoundException`.

Для зручності ви можете автоматично видаляти завдання з відсутніми моделями за допомогою атрибута `DeleteWhenMissingModels` у класі завдання. Коли цей атрибут присутній, Laravel тихо відкине завдання, не викидаючи винятку:

```php
<?php

namespace App\Jobs;

use Illuminate\Queue\Attributes\DeleteWhenMissingModels;

#[DeleteWhenMissingModels]
class ProcessPodcast implements ShouldQueue
{
    // ...
}
```

<a name="pruning-failed-jobs"></a>
### Очищення невдалих завдань

Ви можете очищати записи в таблиці `failed_jobs` вашого застосунку командою Artisan `queue:prune-failed`:

```shell
php artisan queue:prune-failed
```

За замовчуванням буде очищено всі записи невдалих завдань, старші за 24 години. Якщо ви передасте команді опцію `--hours`, буде збережено лише записи невдалих завдань, вставлені за останні N годин. Наприклад, команда нижче видалить усі записи невдалих завдань, вставлені понад 48 годин тому:

```shell
php artisan queue:prune-failed --hours=48
```

<a name="storing-failed-jobs-in-dynamodb"></a>
### Зберігання невдалих завдань у DynamoDB

Laravel також підтримує зберігання записів невдалих завдань у [DynamoDB](https://aws.amazon.com/dynamodb) замість таблиці реляційної бази даних. Проте вам доведеться вручну створити таблицю DynamoDB для зберігання всіх записів невдалих завдань. Зазвичай цю таблицю називають `failed_jobs`, але вам слід назвати її відповідно до значення конфігурації `queue.failed.table` у файлі конфігурації `queue` вашого застосунку.

Таблиця `failed_jobs` має мати рядковий первинний ключ розділу `application` і рядковий первинний ключ сортування `uuid`. Частина ключа `application` міститиме назву вашого застосунку, задану значенням конфігурації `name` у файлі конфігурації `app`. Оскільки назва застосунку є частиною ключа таблиці DynamoDB, ви можете зберігати в одній таблиці невдалі завдання кількох застосунків Laravel.

Крім того, встановіть AWS SDK, щоб ваш застосунок Laravel міг спілкуватися з Amazon DynamoDB:

```shell
composer require aws/aws-sdk-php
```

Далі задайте опції конфігурації `queue.failed.driver` значення `dynamodb`. Крім того, опишіть у масиві конфігурації невдалих завдань опції `key`, `secret` та `region`. Вони використовуватимуться для автентифікації в AWS. Коли ви користуєтеся драйвером `dynamodb`, опція конфігурації `queue.failed.database` не потрібна:

```php
'failed' => [
    'driver' => env('QUEUE_FAILED_DRIVER', 'dynamodb'),
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    'table' => 'failed_jobs',
],
```

<a name="disabling-failed-job-storage"></a>
### Вимкнення зберігання невдалих завдань

Ви можете сказати Laravel відкидати невдалі завдання, не зберігаючи їх, задавши опції конфігурації `queue.failed.driver` значення `null`. Зазвичай це роблять через змінну середовища `QUEUE_FAILED_DRIVER`:

```ini
QUEUE_FAILED_DRIVER=null
```

<a name="failed-job-events"></a>
### Події невдалих завдань

Якщо ви хочете зареєструвати слухача події, який буде викликано, коли завдання провалюється, скористайтеся методом `failing` фасаду `Queue`. Наприклад, ми можемо причепити до цієї події замикання в методі `boot` класу `AppServiceProvider`, який входить до Laravel:

```php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Queue;
use Illuminate\Support\ServiceProvider;
use Illuminate\Queue\Events\JobFailed;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Queue::failing(function (JobFailed $event) {
            // $event->connectionName
            // $event->job
            // $event->exception
        });
    }
}
```

<a name="clearing-jobs-from-queues"></a>
## Очищення черг від завдань

> [!NOTE]
> Користуючись [Horizon](/docs/{{version}}/horizon), очищайте чергу від завдань командою `horizon:clear`, а не `queue:clear`.

Якщо ви хочете видалити всі завдання з черги за замовчуванням підключення за замовчуванням, скористайтеся командою Artisan `queue:clear`:

```shell
php artisan queue:clear
```

Ви також можете передати аргумент `connection` та опцію `queue`, щоб видалити завдання з конкретного підключення й черги:

```shell
php artisan queue:clear redis --queue=emails
```

> [!WARNING]
> Очищення черг від завдань доступне лише для драйверів черг SQS, Redis і database. Крім того, процес видалення повідомлень у SQS триває до 60 секунд, тож завдання, надіслані до черги SQS протягом 60 секунд після очищення, теж можуть бути видалені.

<a name="monitoring-your-queues"></a>
## Моніторинг черг

Якщо ваша черга раптово отримує напливи завдань, вона може перевантажитися, і завдання довго чекатимуть на завершення. За бажанням Laravel може сповіщати вас, коли кількість завдань у черзі перевищує заданий поріг.

Для початку заплануйте [щохвилинне виконання](/docs/{{version}}/scheduling) команди `queue:monitor`. Команда приймає назви черг, за якими ви хочете стежити, а також бажаний поріг кількості завдань:

```shell
php artisan queue:monitor redis:default,redis:deployments --max=100
```

Самого лише планування цієї команди недостатньо, щоб отримати сповіщення про перевантаження черги. Коли команда натрапляє на чергу, кількість завдань у якій перевищує ваш поріг, буде диспетчеризовано подію `Illuminate\Queue\Events\QueueBusy`. Ви можете слухати цю подію у своєму `AppServiceProvider`, щоб надіслати сповіщення собі чи своїй команді розробників:

```php
use App\Notifications\QueueHasLongWaitTime;
use Illuminate\Queue\Events\QueueBusy;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Notification;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Event::listen(function (QueueBusy $event) {
        Notification::route('mail', 'dev@example.com')
            ->notify(new QueueHasLongWaitTime(
                $event->connectionName,
                $event->queue,
                $event->size
            ));
    });
}
```

<a name="testing"></a>
## Тестування

Тестуючи код, який диспетчеризує завдання, ви можете захотіти сказати Laravel не виконувати самого завдання, адже його код можна протестувати напряму й окремо від коду, який його диспетчеризує. Звісно, щоб протестувати саме завдання, ви можете створити його екземпляр і викликати метод `handle` напряму у своєму тесті.

Метод `fake` фасаду `Queue` дозволяє завадити реальному потраплянню завдань у чергу. Після виклику методу `fake` фасаду `Queue` ви можете перевіряти, що застосунок намагався покласти завдання в чергу:

```php tab=Pest
<?php

use App\Jobs\AnotherJob;
use App\Jobs\ShipOrder;
use Illuminate\Support\Facades\Queue;

test('orders can be shipped', function () {
    Queue::fake();

    // Perform order shipping...

    // Assert that no jobs were pushed...
    Queue::assertNothingPushed();

    // Assert a job was pushed to a given queue...
    Queue::assertPushedOn('queue-name', ShipOrder::class);

    // Assert a job was pushed
    Queue::assertPushed(ShipOrder::class);

    // Assert a job was pushed exactly once...
    Queue::assertPushedOnce(ShipOrder::class);

    // Assert a job was pushed twice...
    Queue::assertPushedTimes(ShipOrder::class, 2);

    // Assert a job was not pushed...
    Queue::assertNotPushed(AnotherJob::class);

    // Assert that a closure was pushed to the queue...
    Queue::assertClosurePushed();

    // Assert that a closure was not pushed...
    Queue::assertClosureNotPushed();

    // Assert the total number of jobs that were pushed...
    Queue::assertCount(3);
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use App\Jobs\AnotherJob;
use App\Jobs\ShipOrder;
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_orders_can_be_shipped(): void
    {
        Queue::fake();

        // Perform order shipping...

        // Assert that no jobs were pushed...
        Queue::assertNothingPushed();

        // Assert a job was pushed to a given queue...
        Queue::assertPushedOn('queue-name', ShipOrder::class);

        // Assert a job was pushed
        Queue::assertPushed(ShipOrder::class);

        // Assert a job was pushed exactly once...
        Queue::assertPushedOnce(ShipOrder::class);

        // Assert a job was pushed twice...
        Queue::assertPushedTimes(ShipOrder::class, 2);

        // Assert a job was not pushed...
        Queue::assertNotPushed(AnotherJob::class);

        // Assert that a closure was pushed to the queue...
        Queue::assertClosurePushed();

        // Assert that a closure was not pushed...
        Queue::assertClosureNotPushed();

        // Assert the total number of jobs that were pushed...
        Queue::assertCount(3);
    }
}
```

Ви можете передати замикання методам `assertPushed`, `assertNotPushed`, `assertClosurePushed` чи `assertClosureNotPushed`, щоб перевірити, що покладено завдання, яке проходить заданий тест. Якщо покладено щонайменше одне завдання, яке проходить цей тест, перевірка буде успішною:

```php
use Illuminate\Queue\CallQueuedClosure;

Queue::assertPushed(function (ShipOrder $job) use ($order) {
    return $job->order->id === $order->id;
});

Queue::assertClosurePushed(function (CallQueuedClosure $job) {
    return $job->name === 'validate-order';
});
```

Як варіант, ви можете передати масив очікуваних значень властивостей як другий аргумент до `assertPushed` або `assertNotPushed`, або як третій аргумент до `assertPushedOn`:

```php
Queue::assertPushed(ShipOrder::class, ['order' => $order]);

Queue::assertPushedOn('shipping', ShipOrder::class, ['order' => $order]);
```

Усі вказані властивості повинні збігатися. Значення порівнюються за строгою рівністю, а моделі Eloquent порівнюються за допомогою методу `is`. Методи `assertDispatched`, `assertNotDispatched`, `assertDispatchedSync` і `assertDispatchedAfterResponse` фасаду `Bus` також приймають масив очікуваних значень властивостей як другий аргумент.

<a name="faking-a-subset-of-jobs"></a>
### Підміна частини завдань

Якщо вам потрібно підмінити лише конкретні завдання, дозволивши іншим виконуватися як зазвичай, передайте методу `fake` назви класів завдань, які слід підмінити:

```php tab=Pest
test('orders can be shipped', function () {
    Queue::fake([
        ShipOrder::class,
    ]);

    // Perform order shipping...

    // Assert a job was pushed twice...
    Queue::assertPushedTimes(ShipOrder::class, 2);
});
```

```php tab=PHPUnit
public function test_orders_can_be_shipped(): void
{
    Queue::fake([
        ShipOrder::class,
    ]);

    // Perform order shipping...

    // Assert a job was pushed twice...
    Queue::assertPushedTimes(ShipOrder::class, 2);
}
```

Ви можете підмінити всі завдання, окрім заданого набору, методом `except`:

```php
Queue::fake()->except([
    ShipOrder::class,
]);
```

<a name="testing-job-chains"></a>
### Тестування ланцюжків завдань

Щоб тестувати ланцюжки завдань, вам знадобляться можливості підміни фасаду `Bus`. Метод `assertChained` фасаду `Bus` дозволяє перевірити, що [ланцюжок завдань](/docs/{{version}}/queues#job-chaining) було диспетчеризовано. Метод `assertChained` приймає першим аргументом масив завдань ланцюжка:

```php
use App\Jobs\RecordShipment;
use App\Jobs\ShipOrder;
use App\Jobs\UpdateInventory;
use Illuminate\Support\Facades\Bus;

Bus::fake();

// ...

Bus::assertChained([
    ShipOrder::class,
    RecordShipment::class,
    UpdateInventory::class
]);
```

Як бачите в прикладі вище, масив завдань ланцюжка може бути масивом назв класів. Проте ви можете передати й масив справжніх екземплярів завдань. У такому разі Laravel переконається, що екземпляри належать до тих самих класів і мають ті самі значення властивостей, що й завдання ланцюжка, диспетчеризовані вашим застосунком:

```php
Bus::assertChained([
    new ShipOrder,
    new RecordShipment,
    new UpdateInventory,
]);
```

Метод `assertDispatchedWithoutChain` дозволяє перевірити, що завдання покладено без ланцюжка:

```php
Bus::assertDispatchedWithoutChain(ShipOrder::class);
```

<a name="testing-chain-modifications"></a>
#### Тестування змін ланцюжка

Якщо завдання ланцюжка [додає завдання на початок чи в кінець наявного ланцюжка](#adding-jobs-to-the-chain), скористайтеся методом завдання `assertHasChain`, щоб перевірити, що воно має очікуваний ланцюжок решти завдань:

```php
$job = new ProcessPodcast;

$job->handle();

$job->assertHasChain([
    new TranscribePodcast,
    new OptimizePodcast,
    new ReleasePodcast,
]);
```

Метод `assertDoesntHaveChain` дозволяє перевірити, що решта ланцюжка завдання порожня:

```php
$job->assertDoesntHaveChain();
```

<a name="testing-chained-batches"></a>
#### Тестування пакетів у ланцюжку

Якщо ваш ланцюжок завдань [містить пакет завдань](#chains-and-batches), ви можете перевірити, що цей пакет відповідає вашим очікуванням, вставивши опис `Bus::chainedBatch` у свою перевірку ланцюжка:

```php
use App\Jobs\ShipOrder;
use App\Jobs\UpdateInventory;
use Illuminate\Bus\PendingBatch;
use Illuminate\Support\Facades\Bus;

Bus::assertChained([
    new ShipOrder,
    Bus::chainedBatch(function (PendingBatch $batch) {
        return $batch->jobs->count() === 3;
    }),
    new UpdateInventory,
]);
```

<a name="testing-job-batches"></a>
### Тестування пакетів завдань

Метод `assertBatched` фасаду `Bus` дозволяє перевірити, що [пакет завдань](/docs/{{version}}/queues#job-batching) було диспетчеризовано. Замикання, передане методу `assertBatched`, отримує екземпляр `Illuminate\Bus\PendingBatch`, який дозволяє оглянути завдання в пакеті:

```php
use Illuminate\Bus\PendingBatch;
use Illuminate\Support\Facades\Bus;

Bus::fake();

// ...

Bus::assertBatched(function (PendingBatch $batch) {
    return $batch->name == 'Import CSV' &&
           $batch->jobs->count() === 10;
});
```

Метод `hasJobs` на відкладеному пакеті дозволяє перевірити, що пакет містить очікувані завдання. Метод приймає масив екземплярів завдань, назв класів чи замикань:

```php
Bus::assertBatched(function (PendingBatch $batch) {
    return $batch->hasJobs([
        new ProcessCsvRow(row: 1),
        new ProcessCsvRow(row: 2),
        new ProcessCsvRow(row: 3),
    ]);
});
```

Коли ви користуєтеся замиканнями, замикання отримає екземпляр завдання. Очікуваний тип завдання буде виведено з типу в сигнатурі замикання:

```php
Bus::assertBatched(function (PendingBatch $batch) {
    return $batch->hasJobs([
        fn (ProcessCsvRow $job) => $job->row === 1,
        fn (ProcessCsvRow $job) => $job->row === 2,
        fn (ProcessCsvRow $job) => $job->row === 3,
    ]);
});
```

Метод `assertBatchCount` дозволяє перевірити, що диспетчеризовано задану кількість пакетів:

```php
Bus::assertBatchCount(3);
```

Метод `assertNothingBatched` дозволяє перевірити, що жодного пакета не диспетчеризовано:

```php
Bus::assertNothingBatched();
```

<a name="testing-job-batch-interaction"></a>
#### Тестування взаємодії завдання з пакетом

Крім того, вам інколи може знадобитися протестувати взаємодію окремого завдання з його пакетом. Наприклад, вам може знадобитися перевірити, чи скасувало завдання подальшу обробку свого пакета. Для цього призначте завданню фейковий пакет методом `withFakeBatch`. Метод `withFakeBatch` повертає кортеж з екземпляра завдання й фейкового пакета:

```php
[$job, $batch] = (new ShipOrder)->withFakeBatch();

$job->handle();

$this->assertTrue($batch->cancelled());
$this->assertEmpty($batch->added);
```

<a name="testing-job-queue-interactions"></a>
### Тестування взаємодії завдання з чергою

Інколи вам може знадобитися перевірити, що завдання в черзі [повертає себе до черги](#manually-releasing-a-job). Або що завдання видалило себе. Ви можете протестувати ці взаємодії, створивши екземпляр завдання й викликавши метод `withFakeQueueInteractions`.

Щойно взаємодії завдання з чергою підмінено, ви можете викликати на завданні метод `handle`. Після виклику завдання вам доступні різні методи перевірок, щоб пересвідчитися у взаємодіях завдання з чергою:

```php
use App\Exceptions\CorruptedAudioException;
use App\Jobs\ProcessPodcast;

$job = (new ProcessPodcast)->withFakeQueueInteractions();

$job->handle();

$job->assertReleased(delay: 30);
$job->assertDeleted();
$job->assertNotDeleted();
$job->assertFailed();
$job->assertFailedWith(CorruptedAudioException::class);
$job->assertNotFailed();
```

<a name="job-events"></a>
## Події завдань

Методами `before` та `after` [фасаду](/docs/{{version}}/facades) `Queue` ви можете задати колбеки, які виконуватимуться до чи після обробки завдання з черги. Ці колбеки - чудова нагода додатково логувати чи збільшувати лічильники для панелі. Зазвичай ці методи викликають у методі `boot` [сервіс-провайдера](/docs/{{version}}/providers). Наприклад, ми можемо скористатися `AppServiceProvider`, який входить до Laravel:

```php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Queue;
use Illuminate\Support\ServiceProvider;
use Illuminate\Queue\Events\JobProcessed;
use Illuminate\Queue\Events\JobProcessing;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Queue::before(function (JobProcessing $event) {
            // $event->connectionName
            // $event->job
            // $event->job->payload()
        });

        Queue::after(function (JobProcessed $event) {
            // $event->connectionName
            // $event->job
            // $event->job->payload()
        });
    }
}
```

Методом `looping` [фасаду](/docs/{{version}}/facades) `Queue` ви можете задати колбеки, які виконуються, перш ніж воркер спробує взяти завдання з черги. Наприклад, ви можете зареєструвати замикання, яке відкочує транзакції, залишені відкритими попереднім невдалим завданням:

```php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;

Queue::looping(function () {
    while (DB::transactionLevel() > 0) {
        DB::rollBack();
    }
});
```

Laravel також диспетчеризує подію `Illuminate\Queue\Events\WorkerIdle`, коли воркер черги не може дістати завдання з черги:

```php
use Illuminate\Queue\Events\WorkerIdle;
use Illuminate\Support\Facades\Event;

Event::listen(function (WorkerIdle $event) {
    // $event->connectionName
    // $event->queue
    // $event->workerOptions
});
```

---

Джерело: https://laravelukraine.com/docs/13.x/rate-limiting

# Обмеження частоти

- [Вступ](#introduction)
    - [Конфігурація кешу](#cache-configuration)
- [Базове використання](#basic-usage)
    - [Ручне збільшення лічильника спроб](#manually-incrementing-attempts)
    - [Скидання спроб](#clearing-attempts)

<a name="introduction"></a>
## Вступ

Laravel містить просту абстракцію обмеження частоти, яка разом із [кешем](cache) вашого застосунку дає легкий спосіб обмежити будь-яку дію в межах заданого проміжку часу.

> [!NOTE]
> Якщо вас цікавить обмеження частоти вхідних HTTP-запитів, зверніться до [документації middleware обмежувача частоти](/docs/{{version}}/routing#rate-limiting).

<a name="cache-configuration"></a>
### Конфігурація кешу

Зазвичай обмежувач частоти використовує кеш застосунку за замовчуванням, заданий ключем `default` у файлі конфігурації `cache`. Проте ви можете вказати, який драйвер кешу має використовувати обмежувач частоти, описавши ключ `limiter` у файлі конфігурації `cache` вашого застосунку:

```php
'default' => env('CACHE_STORE', 'database'),

'limiter' => 'redis', // [tl! add]
```

<a name="basic-usage"></a>
## Базове використання

Для роботи з обмежувачем частоти призначено фасад `Illuminate\Support\Facades\RateLimiter`. Найпростіший метод, який пропонує обмежувач, - `attempt`: він обмежує частоту виконання заданого колбека протягом заданої кількості секунд.

Метод `attempt` повертає `false`, коли в колбека не лишилося доступних спроб; інакше `attempt` поверне результат колбека або `true`. Перший аргумент, який приймає метод `attempt`, - «ключ» обмежувача частоти: це може бути будь-який рядок на ваш вибір, що представляє дію, частоту якої обмежують:

```php
use Illuminate\Support\Facades\RateLimiter;

$executed = RateLimiter::attempt(
    'send-message:'.$user->id,
    $perMinute = 5,
    function() {
        // Send message...
    }
);

if (! $executed) {
    return 'Too many messages sent!';
}
```

За потреби ви можете передати методу `attempt` четвертий аргумент - «швидкість згасання», тобто кількість секунд до скидання доступних спроб. Наприклад, ми можемо змінити приклад вище так, щоб дозволити п'ять спроб кожні дві хвилини:

```php
$executed = RateLimiter::attempt(
    'send-message:'.$user->id,
    $perTwoMinutes = 5,
    function() {
        // Send message...
    },
    $decayRate = 120,
);
```

<a name="manually-incrementing-attempts"></a>
### Ручне збільшення лічильника спроб

Якщо ви хочете працювати з обмежувачем частоти вручну, вам доступна низка інших методів. Наприклад, ви можете викликати метод `tooManyAttempts`, щоб визначити, чи перевищив заданий ключ обмежувача максимальну дозволену кількість спроб за хвилину:

```php
use Illuminate\Support\Facades\RateLimiter;

if (RateLimiter::tooManyAttempts('send-message:'.$user->id, $perMinute = 5)) {
    return 'Too many attempts!';
}

RateLimiter::increment('send-message:'.$user->id);

// Send message...
```

Обмежуючи частоту точки, яка може отримувати багато одночасних запитів, вам, можливо, варто перевіряти значення, яке повертає метод `increment`, замість використовувати `tooManyAttempts` та `increment` як окремі операції. Зі сховищами кешу `redis`, `memcached` чи `database` це значення збільшується атомарно, тож кожен паралельний запит отримує унікальний лічильник:

```php
use Illuminate\Support\Facades\RateLimiter;

$perMinute = 5;

if (RateLimiter::increment('send-message:'.$user->id) > $perMinute) {
    return 'Too many attempts!';
}

// Send message...
```

Як варіант, ви можете скористатися методом `remaining`, щоб дізнатися, скільки спроб лишилося для заданого ключа. Якщо для ключа є спроби, ви можете викликати метод `increment`, щоб збільшити загальну кількість спроб:

```php
use Illuminate\Support\Facades\RateLimiter;

if (RateLimiter::remaining('send-message:'.$user->id, $perMinute = 5)) {
    RateLimiter::increment('send-message:'.$user->id);

    // Send message...
}
```

Якщо ви хочете збільшити значення для заданого ключа обмежувача більше ніж на одиницю, передайте потрібну величину методу `increment`:

```php
RateLimiter::increment('send-message:'.$user->id, amount: 5);
```

<a name="determining-limiter-availability"></a>
#### Визначення доступності обмежувача

Коли в ключа не лишилося спроб, метод `availableIn` повертає кількість секунд до того, як спроби знову стануть доступними:

```php
use Illuminate\Support\Facades\RateLimiter;

if (RateLimiter::tooManyAttempts('send-message:'.$user->id, $perMinute = 5)) {
    $seconds = RateLimiter::availableIn('send-message:'.$user->id);

    return 'You may try again in '.$seconds.' seconds.';
}

RateLimiter::increment('send-message:'.$user->id);

// Send message...
```

<a name="clearing-attempts"></a>
### Скидання спроб

Ви можете скинути кількість спроб для заданого ключа обмежувача методом `clear`. Наприклад, ви можете скидати кількість спроб, коли отримувач прочитав повідомлення:

```php
use App\Models\Message;
use Illuminate\Support\Facades\RateLimiter;

/**
 * Mark the message as read.
 */
public function read(Message $message): Message
{
    $message->markAsRead();

    RateLimiter::clear('send-message:'.$message->user_id);

    return $message;
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/search

# Пошук

- [Вступ](#introduction)
    - [Повнотекстовий пошук](#introduction-full-text-search)
    - [Семантичний / векторний пошук](#introduction-semantic-vector-search)
    - [Переранжування](#introduction-reranking)
    - [Пошукові рушії Scout](#introduction-scout-search-engines)
- [Повнотекстовий пошук](#full-text-search)
    - [Додавання повнотекстових індексів](#adding-full-text-indexes)
    - [Виконання повнотекстових запитів](#running-full-text-queries)
- [Семантичний / векторний пошук](#semantic-vector-search)
    - [Генерація ембедингів](#generating-embeddings)
    - [Збереження та індексація векторів](#storing-and-indexing-vectors)
    - [Запити за схожістю](#querying-by-similarity)
- [Переранжування результатів](#reranking-results)
- [Laravel Scout](#laravel-scout)
    - [Рушій бази даних](#database-engine)
    - [Сторонні рушії](#third-party-engines)
- [Поєднання технік](#combining-techniques)

<a name="introduction"></a>
## Вступ

Майже кожному застосунку потрібен пошук. Незалежно від того, чи шукають ваші користувачі релевантні статті в базі знань, чи гортають каталог товарів, чи ставлять запитання природною мовою до корпусу документів, - Laravel надає вбудовані інструменти для кожного з цих сценаріїв, і зовнішні сервіси для цього часто взагалі не потрібні.

Більшості застосунків цілком вистачить вбудованих можливостей на основі бази даних, які надає Laravel: зовнішні пошукові сервіси потрібні лише тоді, коли вам потрібні толерантність до одруківок, фасетна фільтрація чи геопошук у величезних масштабах.

<a name="introduction-full-text-search"></a>
#### Повнотекстовий пошук

Коли вам потрібне ранжування за релевантністю ключових слів - тобто щоб база даних оцінювала й сортувала результати за тим, наскільки добре вони збігаються з пошуковими термінами, - метод конструктора запитів `whereFullText` використовує рідні повнотекстові індекси MariaDB, MySQL та PostgreSQL. Повнотекстовий пошук розуміє межі слів і словотвір, тож пошук за «running» може знайти записи зі словом «run». Зовнішній сервіс не потрібен.

<a name="introduction-semantic-vector-search"></a>
#### Семантичний / векторний пошук

Для семантичного пошуку на базі AI, який добирає результати за *змістом*, а не за точними ключовими словами, метод конструктора запитів `whereVectorSimilarTo` використовує векторні ембединги, збережені в PostgreSQL із розширенням `pgvector` або в MariaDB. Наприклад, пошук за «best wineries in Napa Valley» може підняти статтю «Top Vineyards to Visit», хоч слова й не перетинаються. Векторний пошук потребує PostgreSQL із розширенням `pgvector` або MariaDB 11.7 чи новішої версії, а також [Laravel AI SDK](/docs/{{version}}/ai-sdk).

<a name="introduction-reranking"></a>
#### Переранжування

[AI SDK](/docs/{{version}}/ai-sdk) Laravel надає можливості переранжування, які за допомогою AI-моделей перевпорядковують будь-який набір результатів за семантичною релевантністю до запиту. Переранжування особливо потужне як другий етап після швидкого початкового відбору - наприклад, повнотекстового пошуку, - даючи вам і швидкість, і семантичну точність.

<a name="introduction-scout-search-engines"></a>
#### Пошук через Laravel Scout

Для застосунків, яким потрібен трейт `Searchable`, що автоматично тримає пошукові індекси синхронізованими з моделями Eloquent, [Laravel Scout](/docs/{{version}}/scout) пропонує і вбудований рушій бази даних, і драйвери для сторонніх сервісів на кшталт Algolia, Meilisearch, Typesense та Turbopuffer.

<a name="full-text-search"></a>
## Повнотекстовий пошук

Хоч запити з `LIKE` і добре працюють для простого пошуку підрядка, вони не розуміють мови. Пошук `LIKE` за «running» не знайде запису зі словом «run», та й результати не ранжуються за релевантністю - вони просто повертаються в тому порядку, у якому їх знайшла база. Повнотекстовий пошук розв'язує обидві проблеми за допомогою спеціалізованих індексів, які розуміють межі слів, словотвір і оцінку релевантності, дозволяючи базі повертати найрелевантніші результати першими.

Швидкий повнотекстовий пошук вбудовано в MariaDB, MySQL та PostgreSQL - зовнішній пошуковий сервіс не потрібен. Вам треба лише додати повнотекстовий індекс до стовпців, за якими хочете шукати, а потім скористатися методом конструктора запитів `whereFullText`.

> [!WARNING]
> Наразі повнотекстовий пошук підтримують MariaDB, MySQL та PostgreSQL.

<a name="adding-full-text-indexes"></a>
### Додавання повнотекстових індексів

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

```php
Schema::create('articles', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('body');
    $table->timestamps();

    $table->fullText(['title', 'body']);
});
```

У PostgreSQL ви можете вказати мовну конфігурацію індексу, яка керує словотвором:

```php
$table->fullText('body')->language('english');
```

Докладніше про створення індексів читайте в [документації з міграцій](/docs/{{version}}/migrations#available-index-types).

<a name="running-full-text-queries"></a>
### Виконання повнотекстових запитів

Щойно індекс на місці, скористайтеся методом конструктора запитів `whereFullText`, щоб шукати за ним. Laravel згенерує відповідний SQL для вашого драйвера бази - наприклад, `MATCH(...) AGAINST(...)` для MariaDB та MySQL і `to_tsvector(...) @@ plainto_tsquery(...)` для PostgreSQL:

```php
$articles = Article::whereFullText('body', 'web developer')->get();
```

У MariaDB та MySQL результати автоматично впорядковуються за оцінкою релевантності. У PostgreSQL `whereFullText` фільтрує відповідні записи, але не впорядковує їх за релевантністю; якщо вам потрібне автоматичне впорядкування за релевантністю в PostgreSQL, розгляньте [рушій бази даних Scout](#database-engine), який робить це за вас.

Якщо ви створили складений повнотекстовий індекс по кількох стовпцях, шукати по всіх них можна, передавши той самий масив стовпців до `whereFullText`:

```php
$articles = Article::whereFullText(
    ['title', 'body'], 'web developer'
)->get();
```

Метод `orWhereFullText` дозволяє додати умову повнотекстового пошуку як умову «або». Усі подробиці шукайте в [документації конструктора запитів](/docs/{{version}}/queries#full-text-where-clauses).

<a name="semantic-vector-search"></a>
## Семантичний / векторний пошук

Повнотекстовий пошук покладається на збіг ключових слів: слова із запиту мають (у якійсь формі) траплятися в даних. Семантичний пошук діє принципово інакше: він використовує згенеровані AI векторні ембединги, щоб представити *зміст* тексту як масиви чисел, а потім знаходить результати, зміст яких найбільш схожий на запит. Наприклад, пошук за «best wineries in Napa Valley» може підняти статтю «Top Vineyards to Visit», хоч слова взагалі не перетинаються.

Базовий робочий процес векторного пошуку такий: згенерувати ембединг (числовий масив) для кожної одиниці контенту й зберегти його поруч із даними, а під час пошуку згенерувати ембединг запиту користувача й знайти збережені ембединги, найближчі до нього у векторному просторі.

> [!NOTE]
> Векторний пошук потребує [Laravel AI SDK](/docs/{{version}}/ai-sdk) і підтримується PostgreSQL (потрібне розширення `pgvector`), MariaDB 11.7 або новішою версією, та MongoDB (потрібен [пакет Laravel MongoDB](https://laravel.com/docs/13.x/mongodb)). Усі бази Postgres на [Laravel Cloud](https://laravel.com/cloud) уже мають встановлений `pgvector`.

<a name="generating-embeddings"></a>
### Генерація ембедингів

Ембединг - це багатовимірний числовий масив (зазвичай сотні чи тисячі чисел), який представляє семантичний зміст фрагмента тексту. Згенерувати ембединги для рядка можна методом `toEmbeddings`, доступним у класі `Stringable` Laravel:

```php
use Illuminate\Support\Str;

$embedding = Str::of('Napa Valley has great wine.')->toEmbeddings();
```

Щоб згенерувати ембединги для кількох вхідних значень одразу - що ефективніше, ніж по одному, адже потребує лише одного виклику API постачальника ембедингів, - скористайтеся класом `Embeddings`:

```php
use Laravel\Ai\Embeddings;

$response = Embeddings::for([
    'Napa Valley has great wine.',
    'Laravel is a PHP framework.',
])->generate();

$response->embeddings; // [[0.123, 0.456, ...], [0.789, 0.012, ...]]
```

Докладніше про налаштування постачальників ембедингів, зміну розмірності та кешування читайте в [документації AI SDK](/docs/{{version}}/ai-sdk#embeddings).

<a name="storing-and-indexing-vectors"></a>
### Збереження та індексація векторів

Щоб зберігати векторні ембединги, опишіть у міграції стовпець `vector`, вказавши кількість вимірів, що відповідає виходу вашого постачальника ембедингів (наприклад, 1536 для моделі OpenAI `text-embedding-3-small`). Також викличте на стовпці `index`, щоб створити індекс HNSW (Hierarchical Navigable Small World), який різко пришвидшує пошук за схожістю на великих наборах даних:

```php
Schema::ensureVectorExtensionExists();

Schema::create('documents', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('content');
    $table->vector('embedding', dimensions: 1536)->index();
    $table->timestamps();
});
```

Метод `Schema::ensureVectorExtensionExists` переконується, що розширення `pgvector` увімкнено у вашій базі PostgreSQL, перед створенням таблиці.

У моделі Eloquent використовуйте приведення `AsVector`, щоб Laravel автоматично конвертував між PHP-масивами й векторним форматом бази:

```php
use Illuminate\Database\Eloquent\Casts\AsVector;

protected function casts(): array
{
    return [
        'embedding' => AsVector::class,
    ];
}
```

Докладніше про векторні стовпці та індекси читайте в [документації з міграцій](/docs/{{version}}/migrations#available-column-types).

<a name="querying-by-similarity"></a>
### Запити за схожістю

Щойно ви зберегли ембединги свого контенту, ви можете шукати схожі записи методом `whereVectorSimilarTo`. Цей метод порівнює заданий ембединг зі збереженими векторами за косинусною схожістю, відсіює результати нижче порога `minSimilarity` й автоматично впорядковує результати за релевантністю - найсхожіші першими. Поріг має бути значенням від `0.0` до `1.0`, де `1.0` означає, що вектори ідентичні:

```php
$documents = Document::query()
    ->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4)
    ->limit(10)
    ->get();
```

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

```php
$documents = Document::query()
    ->whereVectorSimilarTo('embedding', 'best wineries in Napa Valley')
    ->limit(10)
    ->get();
```

Для нижчорівневого контролю над векторними запитами доступні також методи `whereVectorDistanceLessThan`, `selectVectorDistance` та `orderByVectorDistance`. Вони дозволяють працювати безпосередньо зі значеннями відстані, а не з оцінками схожості, вибирати обчислену відстань як стовпець результатів або вручну керувати впорядкуванням. Усі подробиці шукайте в [документації конструктора запитів](/docs/{{version}}/queries#vector-similarity-clauses) та [документації AI SDK](/docs/{{version}}/ai-sdk#querying-embeddings).

<a name="reranking-results"></a>
## Переранжування результатів

Переранжування - це техніка, коли AI-модель перевпорядковує набір результатів за тим, наскільки семантично релевантний кожен результат до заданого запиту. На відміну від векторного пошуку, який вимагає наперед обчислити й зберегти ембединги, переранжування працює з будь-якою добіркою тексту: воно приймає сирий контент і запит, а повертає елементи, відсортовані за релевантністю.

Переранжування особливо потужне як другий етап після швидкого початкового відбору. Наприклад, ви можете повнотекстовим пошуком швидко звузити тисячі записів до 50 кандидатів, а потім переранжуванням підняти найрелевантніші вгору. Цей патерн «відібрати, потім переранжувати» дає вам і швидкість, і семантичну точність.

Переранжувати масив рядків можна класом `Reranking`:

```php
use Laravel\Ai\Reranking;

$response = Reranking::of([
    'Django is a Python web framework.',
    'Laravel is a PHP web application framework.',
    'React is a JavaScript library for building user interfaces.',
])->rerank('PHP frameworks');

$response->first()->document; // "Laravel is a PHP web application framework."
```

Колекції Laravel також мають макрос `rerank`, який приймає назву поля (чи замикання) і запит, що спрощує переранжування результатів Eloquent:

```php
$articles = Article::all()
    ->rerank('body', 'Laravel tutorials');
```

Усі подробиці про налаштування постачальників переранжування та доступні опції шукайте в [документації AI SDK](/docs/{{version}}/ai-sdk#reranking).

<a name="laravel-scout"></a>
## Laravel Scout

Описані вище техніки пошуку - це методи конструктора запитів, які ви викликаєте безпосередньо у своєму коді. [Laravel Scout](/docs/{{version}}/scout) діє інакше: він надає трейт `Searchable`, який ви додаєте до моделей Eloquent, і Scout автоматично тримає пошукові індекси синхронізованими, коли записи створюються, оновлюються й видаляються. Це особливо зручно, коли ви хочете, щоб ваші моделі завжди були доступні для пошуку без ручного керування оновленням індексів.

<a name="database-engine"></a>
### Рушій бази даних

Вбудований рушій бази даних Scout виконує повнотекстовий пошук і пошук через `LIKE` у вашій наявній базі - зовнішній сервіс чи додаткова інфраструктура не потрібні. Просто додайте до моделі трейт `Searchable` й опишіть метод `toSearchableArray`, який поверне стовпці, за якими має відбуватися пошук.

Ви можете керувати стратегією пошуку для кожного стовпця за допомогою PHP-атрибутів. `SearchUsingFullText` використає повнотекстовий індекс вашої бази, `SearchUsingPrefix` шукатиме лише з початку рядка (`example%`), а всі стовпці без атрибута використовують стандартну стратегію `LIKE` з підстановками з обох боків (`%example%`):

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Attributes\SearchUsingFullText;
use Laravel\Scout\Attributes\SearchUsingPrefix;
use Laravel\Scout\Searchable;

class Article extends Model
{
    use Searchable;

    #[SearchUsingPrefix(['id'])]
    #[SearchUsingFullText(['title', 'body'])]
    public function toSearchableArray(): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'body' => $this->body,
        ];
    }
}
```

> [!WARNING]
> Перш ніж вказувати, що стовпець має використовувати повнотекстові умови запиту, переконайтеся, що цьому стовпцю призначено [повнотекстовий індекс](/docs/{{version}}/migrations#available-index-types).

Щойно трейт додано, ви можете шукати у своїй моделі методом `search` зі Scout. Рушій бази даних Scout автоматично впорядкує результати за релевантністю, навіть у PostgreSQL:

```php
$articles = Article::search('Laravel')->get();
```

Рушій бази даних - чудовий вибір, коли ваші потреби в пошуку помірні й вам потрібна зручність автоматичної синхронізації індексів у Scout без розгортання зовнішнього сервісу. Він добре покриває найпоширеніші сценарії пошуку, включно з фільтрацією, пагінацією та обробкою м'яко видалених записів. Усі подробиці шукайте в [документації Scout](/docs/{{version}}/scout#database-engine).

<a name="third-party-engines"></a>
### Сторонні рушії

Scout також підтримує сторонні пошукові рушії - [Algolia](https://www.algolia.com/), [Meilisearch](https://www.meilisearch.com) та [Typesense](https://typesense.org). Ці спеціалізовані пошукові сервіси пропонують просунуті можливості: толерантність до одруківок, фасетну фільтрацію, геопошук і власні правила ранжування - те, що стає важливим у дуже великих масштабах або коли вам потрібен добре відшліфований досвід пошуку під час набору.

Оскільки Scout надає єдиний API для всіх своїх драйверів, перехід з рушія бази даних на сторонній згодом потребує мінімальних змін у коді. Ви можете почати з рушія бази даних і мігрувати на сторонній сервіс, лише якщо потреби вашого застосунку переростуть можливості бази.

Усі подробиці про налаштування сторонніх рушіїв шукайте в [документації Scout](/docs/{{version}}/scout).

> [!NOTE]
> Багатьом застосункам зовнішній пошуковий рушій ніколи не знадобиться. Вбудовані техніки, описані на цій сторінці, покривають переважну більшість сценаріїв.

<a name="combining-techniques"></a>
## Поєднання технік

Описані на цій сторінці техніки пошуку не є взаємовиключними - їх поєднання часто дає найкращі результати. Ось два поширені патерни, які демонструють, як ці інструменти працюють разом.

**Повнотекстовий відбір + переранжування**

Скористайтеся повнотекстовим пошуком, щоб швидко звузити великий набір даних до набору кандидатів, а потім застосуйте переранжування, щоб відсортувати цих кандидатів за семантичною релевантністю. Це дає вам швидкість рідного для бази повнотекстового пошуку разом із точністю оцінки релевантності на базі AI:

```php
$articles = Article::query()
    ->whereFullText('body', $request->input('query'))
    ->limit(50)
    ->get()
    ->rerank('body', $request->input('query'), limit: 10);
```

**Векторний пошук + традиційні фільтри**

Поєднайте векторну схожість зі звичайними умовами `where`, щоб обмежити семантичний пошук підмножиною записів. Це корисно, коли вам потрібен пошук за змістом, але результати треба обмежити за власником, категорією чи будь-яким іншим атрибутом:

```php
$documents = Document::query()
    ->where('team_id', $user->team_id)
    ->whereVectorSimilarTo('embedding', $request->input('query'))
    ->limit(10)
    ->get();
```

---

Джерело: https://laravelukraine.com/docs/13.x/strings

# Рядки

- [Вступ](#introduction)
- [Доступні методи](#available-methods)

<a name="introduction"></a>
## Вступ

Laravel має цілу низку функцій для роботи з рядковими значеннями. Багато з них використовує сам фреймворк, але ви вільні застосовувати їх і у власних застосунках, якщо вважаєте зручними.

<a name="available-methods"></a>
## Доступні методи

<style>
    .collection-method-list > p {
        columns: 10.8em 3; -moz-columns: 10.8em 3; -webkit-columns: 10.8em 3;
    }

    .collection-method-list a {
        display: block;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
</style>

<a name="strings-method-list"></a>
### Рядки

<div class="collection-method-list" markdown="1">

[\__](#method-__)
[class_basename](#method-class-basename)
[e](#method-e)
[preg_replace_array](#method-preg-replace-array)
[Str::after](#method-str-after)
[Str::afterLast](#method-str-after-last)
[Str::apa](#method-str-apa)
[Str::ascii](#method-str-ascii)
[Str::before](#method-str-before)
[Str::beforeLast](#method-str-before-last)
[Str::between](#method-str-between)
[Str::betweenFirst](#method-str-between-first)
[Str::camel](#method-camel-case)
[Str::charAt](#method-char-at)
[Str::chopStart](#method-str-chop-start)
[Str::chopEnd](#method-str-chop-end)
[Str::contains](#method-str-contains)
[Str::containsAll](#method-str-contains-all)
[Str::counted](#method-str-counted)
[Str::doesntContain](#method-str-doesnt-contain)
[Str::doesntEndWith](#method-str-doesnt-end-with)
[Str::doesntStartWith](#method-str-doesnt-start-with)
[Str::deduplicate](#method-deduplicate)
[Str::endsWith](#method-ends-with)
[Str::excerpt](#method-excerpt)
[Str::finish](#method-str-finish)
[Str::fromBase64](#method-str-from-base64)
[Str::headline](#method-str-headline)
[Str::initials](#method-str-initials)
[Str::inlineMarkdown](#method-str-inline-markdown)
[Str::is](#method-str-is)
[Str::isAscii](#method-str-is-ascii)
[Str::isJson](#method-str-is-json)
[Str::isUlid](#method-str-is-ulid)
[Str::isUrl](#method-str-is-url)
[Str::isUuid](#method-str-is-uuid)
[Str::kebab](#method-kebab-case)
[Str::lcfirst](#method-str-lcfirst)
[Str::length](#method-str-length)
[Str::limit](#method-str-limit)
[Str::lower](#method-str-lower)
[Str::markdown](#method-str-markdown)
[Str::mask](#method-str-mask)
[Str::match](#method-str-match)
[Str::matchAll](#method-str-match-all)
[Str::isMatch](#method-str-is-match)
[Str::orderedUuid](#method-str-ordered-uuid)
[Str::padBoth](#method-str-padboth)
[Str::padLeft](#method-str-padleft)
[Str::padRight](#method-str-padright)
[Str::password](#method-str-password)
[Str::plural](#method-str-plural)
[Str::pluralStudly](#method-str-plural-studly)
[Str::position](#method-str-position)
[Str::random](#method-str-random)
[Str::remove](#method-str-remove)
[Str::repeat](#method-str-repeat)
[Str::replace](#method-str-replace)
[Str::replaceArray](#method-str-replace-array)
[Str::replaceFirst](#method-str-replace-first)
[Str::replaceLast](#method-str-replace-last)
[Str::replaceMatches](#method-str-replace-matches)
[Str::replaceStart](#method-str-replace-start)
[Str::replaceEnd](#method-str-replace-end)
[Str::reverse](#method-str-reverse)
[Str::singular](#method-str-singular)
[Str::slug](#method-str-slug)
[Str::snake](#method-snake-case)
[Str::squish](#method-str-squish)
[Str::start](#method-str-start)
[Str::startsWith](#method-starts-with)
[Str::studly](#method-studly-case)
[Str::substr](#method-str-substr)
[Str::substrCount](#method-str-substrcount)
[Str::substrReplace](#method-str-substrreplace)
[Str::swap](#method-str-swap)
[Str::take](#method-take)
[Str::title](#method-title-case)
[Str::toBase64](#method-str-to-base64)
[Str::transliterate](#method-str-transliterate)
[Str::trim](#method-str-trim)
[Str::ltrim](#method-str-ltrim)
[Str::rtrim](#method-str-rtrim)
[Str::ucfirst](#method-str-ucfirst)
[Str::ucsplit](#method-str-ucsplit)
[Str::ucwords](#method-str-ucwords)
[Str::upper](#method-str-upper)
[Str::ulid](#method-str-ulid)
[Str::unwrap](#method-str-unwrap)
[Str::uuid](#method-str-uuid)
[Str::uuid7](#method-str-uuid7)
[Str::wordCount](#method-str-word-count)
[Str::wordWrap](#method-str-word-wrap)
[Str::words](#method-str-words)
[Str::wrap](#method-str-wrap)
[str](#method-str)
[trans](#method-trans)
[trans_choice](#method-trans-choice)

</div>

<a name="fluent-strings-method-list"></a>
### Плавні рядки

<div class="collection-method-list" markdown="1">

[after](#method-fluent-str-after)
[afterLast](#method-fluent-str-after-last)
[apa](#method-fluent-str-apa)
[append](#method-fluent-str-append)
[ascii](#method-fluent-str-ascii)
[basename](#method-fluent-str-basename)
[before](#method-fluent-str-before)
[beforeLast](#method-fluent-str-before-last)
[between](#method-fluent-str-between)
[betweenFirst](#method-fluent-str-between-first)
[camel](#method-fluent-str-camel)
[charAt](#method-fluent-str-char-at)
[classBasename](#method-fluent-str-class-basename)
[chopStart](#method-fluent-str-chop-start)
[chopEnd](#method-fluent-str-chop-end)
[contains](#method-fluent-str-contains)
[containsAll](#method-fluent-str-contains-all)
[counted](#method-fluent-str-counted)
[decrypt](#method-fluent-str-decrypt)
[deduplicate](#method-fluent-str-deduplicate)
[dirname](#method-fluent-str-dirname)
[doesntContain](#method-fluent-str-doesnt-contain)
[doesntEndWith](#method-fluent-str-doesnt-end-with)
[doesntStartWith](#method-fluent-str-doesnt-start-with)
[encrypt](#method-fluent-str-encrypt)
[endsWith](#method-fluent-str-ends-with)
[exactly](#method-fluent-str-exactly)
[excerpt](#method-fluent-str-excerpt)
[explode](#method-fluent-str-explode)
[finish](#method-fluent-str-finish)
[fromBase64](#method-fluent-str-from-base64)
[hash](#method-fluent-str-hash)
[headline](#method-fluent-str-headline)
[initials](#method-fluent-str-initials)
[inlineMarkdown](#method-fluent-str-inline-markdown)
[is](#method-fluent-str-is)
[isAscii](#method-fluent-str-is-ascii)
[isEmpty](#method-fluent-str-is-empty)
[isNotEmpty](#method-fluent-str-is-not-empty)
[isJson](#method-fluent-str-is-json)
[isUlid](#method-fluent-str-is-ulid)
[isUrl](#method-fluent-str-is-url)
[isUuid](#method-fluent-str-is-uuid)
[kebab](#method-fluent-str-kebab)
[lcfirst](#method-fluent-str-lcfirst)
[length](#method-fluent-str-length)
[limit](#method-fluent-str-limit)
[lower](#method-fluent-str-lower)
[markdown](#method-fluent-str-markdown)
[mask](#method-fluent-str-mask)
[match](#method-fluent-str-match)
[matchAll](#method-fluent-str-match-all)
[isMatch](#method-fluent-str-is-match)
[newLine](#method-fluent-str-new-line)
[padBoth](#method-fluent-str-padboth)
[padLeft](#method-fluent-str-padleft)
[padRight](#method-fluent-str-padright)
[pipe](#method-fluent-str-pipe)
[plural](#method-fluent-str-plural)
[position](#method-fluent-str-position)
[prepend](#method-fluent-str-prepend)
[remove](#method-fluent-str-remove)
[repeat](#method-fluent-str-repeat)
[replace](#method-fluent-str-replace)
[replaceArray](#method-fluent-str-replace-array)
[replaceFirst](#method-fluent-str-replace-first)
[replaceLast](#method-fluent-str-replace-last)
[replaceMatches](#method-fluent-str-replace-matches)
[replaceStart](#method-fluent-str-replace-start)
[replaceEnd](#method-fluent-str-replace-end)
[scan](#method-fluent-str-scan)
[singular](#method-fluent-str-singular)
[slug](#method-fluent-str-slug)
[snake](#method-fluent-str-snake)
[split](#method-fluent-str-split)
[squish](#method-fluent-str-squish)
[start](#method-fluent-str-start)
[startsWith](#method-fluent-str-starts-with)
[stripTags](#method-fluent-str-strip-tags)
[studly](#method-fluent-str-studly)
[substr](#method-fluent-str-substr)
[substrReplace](#method-fluent-str-substrreplace)
[swap](#method-fluent-str-swap)
[take](#method-fluent-str-take)
[tap](#method-fluent-str-tap)
[test](#method-fluent-str-test)
[title](#method-fluent-str-title)
[toBase64](#method-fluent-str-to-base64)
[toHtmlString](#method-fluent-str-to-html-string)
[toUri](#method-fluent-str-to-uri)
[transliterate](#method-fluent-str-transliterate)
[trim](#method-fluent-str-trim)
[ltrim](#method-fluent-str-ltrim)
[rtrim](#method-fluent-str-rtrim)
[ucfirst](#method-fluent-str-ucfirst)
[ucsplit](#method-fluent-str-ucsplit)
[ucwords](#method-fluent-str-ucwords)
[unwrap](#method-fluent-str-unwrap)
[upper](#method-fluent-str-upper)
[when](#method-fluent-str-when)
[whenContains](#method-fluent-str-when-contains)
[whenContainsAll](#method-fluent-str-when-contains-all)
[whenDoesntEndWith](#method-fluent-str-when-doesnt-end-with)
[whenDoesntStartWith](#method-fluent-str-when-doesnt-start-with)
[whenEmpty](#method-fluent-str-when-empty)
[whenNotEmpty](#method-fluent-str-when-not-empty)
[whenStartsWith](#method-fluent-str-when-starts-with)
[whenEndsWith](#method-fluent-str-when-ends-with)
[whenExactly](#method-fluent-str-when-exactly)
[whenNotExactly](#method-fluent-str-when-not-exactly)
[whenIs](#method-fluent-str-when-is)
[whenIsAscii](#method-fluent-str-when-is-ascii)
[whenIsUlid](#method-fluent-str-when-is-ulid)
[whenIsUuid](#method-fluent-str-when-is-uuid)
[whenTest](#method-fluent-str-when-test)
[wordCount](#method-fluent-str-word-count)
[words](#method-fluent-str-words)
[wrap](#method-fluent-str-wrap)

</div>

<a name="strings"></a>
## Рядки

<a name="method-__"></a>
#### `__()` {.collection-method}

Функція `__` перекладає заданий рядок або ключ перекладу за допомогою ваших [мовних файлів](/docs/{{version}}/localization):

```php
echo __('Welcome to our application');

echo __('messages.welcome');
```

Якщо вказаного рядка чи ключа перекладу не існує, функція `__` повернає передане значення. Тож у прикладі вище функція `__` повернула б `messages.welcome`, якби такого ключа перекладу не було.

<a name="method-class-basename"></a>
#### `class_basename()` {.collection-method}

Функція `class_basename` повертає назву заданого класу без його простору імен:

```php
$class = class_basename('Foo\Bar\Baz');

// Baz
```

<a name="method-e"></a>
#### `e()` {.collection-method}

Функція `e` виконує PHP-функцію `htmlspecialchars`, у якій опція `double_encode` за замовчуванням дорівнює `true`:

```php
echo e('<html>foo</html>');

// &lt;html&gt;foo&lt;/html&gt;
```

<a name="method-preg-replace-array"></a>
#### `preg_replace_array()` {.collection-method}

Функція `preg_replace_array` послідовно замінює в рядку заданий шаблон значеннями з масиву:

```php
$string = 'The event will take place between :start and :end';

$replaced = preg_replace_array('/:[a-z_]+/', ['8:30', '9:00'], $string);

// The event will take place between 8:30 and 9:00
```

<a name="method-str-after"></a>
#### `Str::after()` {.collection-method}

Метод `Str::after` повертає все, що йде в рядку після заданого значення. Якщо значення в рядку немає, буде повернуто цілий рядок:

```php
use Illuminate\Support\Str;

$slice = Str::after('This is my name', 'This is');

// ' my name'
```

<a name="method-str-after-last"></a>
#### `Str::afterLast()` {.collection-method}

Метод `Str::afterLast` повертає все, що йде в рядку після останнього входження заданого значення. Якщо значення в рядку немає, буде повернуто цілий рядок:

```php
use Illuminate\Support\Str;

$slice = Str::afterLast('App\Http\Controllers\Controller', '\\');

// 'Controller'
```

<a name="method-str-apa"></a>
#### `Str::apa()` {.collection-method}

Метод `Str::apa` перетворює заданий рядок на заголовний регістр за [настановами APA](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case):

```php
use Illuminate\Support\Str;

$title = Str::apa('Creating A Project');

// 'Creating a Project'
```

<a name="method-str-ascii"></a>
#### `Str::ascii()` {.collection-method}

Метод `Str::ascii` спробує транслітерувати рядок у значення ASCII:

```php
use Illuminate\Support\Str;

$slice = Str::ascii('û');

// 'u'
```

<a name="method-str-before"></a>
#### `Str::before()` {.collection-method}

Метод `Str::before` повертає все, що йде в рядку до заданого значення:

```php
use Illuminate\Support\Str;

$slice = Str::before('This is my name', 'my name');

// 'This is '
```

<a name="method-str-before-last"></a>
#### `Str::beforeLast()` {.collection-method}

Метод `Str::beforeLast` повертає все, що йде в рядку до останнього входження заданого значення:

```php
use Illuminate\Support\Str;

$slice = Str::beforeLast('This is my name', 'is');

// 'This '
```

<a name="method-str-between"></a>
#### `Str::between()` {.collection-method}

Метод `Str::between` повертає частину рядка між двома значеннями:

```php
use Illuminate\Support\Str;

$slice = Str::between('This is my name', 'This', 'name');

// ' is my '
```

<a name="method-str-between-first"></a>
#### `Str::betweenFirst()` {.collection-method}

Метод `Str::betweenFirst` повертає найменшу можливу частину рядка між двома значеннями:

```php
use Illuminate\Support\Str;

$slice = Str::betweenFirst('[a] bc [d]', '[', ']');

// 'a'
```

<a name="method-camel-case"></a>
#### `Str::camel()` {.collection-method}

Метод `Str::camel` перетворює заданий рядок на `camelCase`:

```php
use Illuminate\Support\Str;

$converted = Str::camel('foo_bar');

// 'fooBar'
```

<a name="method-char-at"></a>
#### `Str::charAt()` {.collection-method}

Метод `Str::charAt` повертає символ за вказаним індексом. Якщо індекс поза межами рядка, повертається `false`:

```php
use Illuminate\Support\Str;

$character = Str::charAt('This is my name.', 6);

// 's'
```

<a name="method-str-chop-start"></a>
#### `Str::chopStart()` {.collection-method}

Метод `Str::chopStart` видаляє перше входження заданого значення лише тоді, коли воно стоїть на початку рядка:

```php
use Illuminate\Support\Str;

$url = Str::chopStart('https://laravel.com', 'https://');

// 'laravel.com'
```

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

```php
use Illuminate\Support\Str;

$url = Str::chopStart('http://laravel.com', ['https://', 'http://']);

// 'laravel.com'
```

<a name="method-str-chop-end"></a>
#### `Str::chopEnd()` {.collection-method}

Метод `Str::chopEnd` видаляє останнє входження заданого значення лише тоді, коли воно стоїть у кінці рядка:

```php
use Illuminate\Support\Str;

$url = Str::chopEnd('app/Models/Photograph.php', '.php');

// 'app/Models/Photograph'
```

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

```php
use Illuminate\Support\Str;

$url = Str::chopEnd('laravel.com/index.php', ['/index.html', '/index.php']);

// 'laravel.com'
```

<a name="method-str-contains"></a>
#### `Str::contains()` {.collection-method}

Метод `Str::contains` визначає, чи містить заданий рядок задане значення. За замовчуванням метод чутливий до регістру:

```php
use Illuminate\Support\Str;

$contains = Str::contains('This is my name', 'my');

// true
```

Ви можете також передати масив значень, щоб перевірити, чи містить рядок будь-яке зі значень масиву:

```php
use Illuminate\Support\Str;

$contains = Str::contains('This is my name', ['my', 'foo']);

// true
```

Вимкнути чутливість до регістру можна, задавши аргументу `ignoreCase` значення `true`:

```php
use Illuminate\Support\Str;

$contains = Str::contains('This is my name', 'MY', ignoreCase: true);

// true
```

<a name="method-str-contains-all"></a>
#### `Str::containsAll()` {.collection-method}

Метод `Str::containsAll` визначає, чи містить заданий рядок усі значення із заданого масиву:

```php
use Illuminate\Support\Str;

$containsAll = Str::containsAll('This is my name', ['my', 'name']);

// true
```

Вимкнути чутливість до регістру можна, задавши аргументу `ignoreCase` значення `true`:

```php
use Illuminate\Support\Str;

$containsAll = Str::containsAll('This is my name', ['MY', 'NAME'], ignoreCase: true);

// true
```

<a name="method-str-doesnt-contain"></a>
#### `Str::doesntContain()` {.collection-method}

Метод `Str::doesntContain` визначає, чи не містить заданий рядок задане значення. За замовчуванням метод чутливий до регістру:

```php
use Illuminate\Support\Str;

$doesntContain = Str::doesntContain('This is name', 'my');

// true
```

Ви можете також передати масив значень, щоб перевірити, чи не містить рядок жодного зі значень масиву:

```php
use Illuminate\Support\Str;

$doesntContain = Str::doesntContain('This is name', ['my', 'framework']);

// true
```

Вимкнути чутливість до регістру можна, задавши аргументу `ignoreCase` значення `true`:

```php
use Illuminate\Support\Str;

$doesntContain = Str::doesntContain('This is name', 'MY', ignoreCase: true);

// true
```

<a name="method-deduplicate"></a>
#### `Str::deduplicate()` {.collection-method}

Метод `Str::deduplicate` замінює послідовні входження символу в заданому рядку одним таким символом. За замовчуванням метод дедуплікує пробіли:

```php
use Illuminate\Support\Str;

$result = Str::deduplicate('The   Laravel   Framework');

// The Laravel Framework
```

Ви можете вказати інший символ для дедуплікації, передавши його другим аргументом методу:

```php
use Illuminate\Support\Str;

$result = Str::deduplicate('The---Laravel---Framework', '-');

// The-Laravel-Framework
```

<a name="method-str-doesnt-end-with"></a>
#### `Str::doesntEndWith()` {.collection-method}

Метод `Str::doesntEndWith` визначає, чи не закінчується заданий рядок заданим значенням:

```php
use Illuminate\Support\Str;

$result = Str::doesntEndWith('This is my name', 'dog');

// true
```

Ви можете також передати масив значень, щоб перевірити, чи не закінчується рядок жодним зі значень масиву:

```php
use Illuminate\Support\Str;

$result = Str::doesntEndWith('This is my name', ['this', 'foo']);

// true

$result = Str::doesntEndWith('This is my name', ['name', 'foo']);

// false
```

<a name="method-str-doesnt-start-with"></a>
#### `Str::doesntStartWith()` {.collection-method}

Метод `Str::doesntStartWith` визначає, чи не починається заданий рядок заданим значенням:

```php
use Illuminate\Support\Str;

$result = Str::doesntStartWith('This is my name', 'That');

// true
```

Якщо передати масив можливих значень, метод `doesntStartWith` повернає `true`, коли рядок не починається жодним із заданих значень:

```php
$result = Str::doesntStartWith('This is my name', ['What', 'That', 'There']);

// true
```

<a name="method-ends-with"></a>
#### `Str::endsWith()` {.collection-method}

Метод `Str::endsWith` визначає, чи закінчується заданий рядок заданим значенням:

```php
use Illuminate\Support\Str;

$result = Str::endsWith('This is my name', 'name');

// true
```

Ви можете також передати масив значень, щоб перевірити, чи закінчується рядок будь-яким зі значень масиву:

```php
use Illuminate\Support\Str;

$result = Str::endsWith('This is my name', ['name', 'foo']);

// true

$result = Str::endsWith('This is my name', ['this', 'foo']);

// false
```

<a name="method-excerpt"></a>
#### `Str::excerpt()` {.collection-method}

Метод `Str::excerpt` дістає із заданого рядка уривок навколо першого входження заданої фрази:

```php
use Illuminate\Support\Str;

$excerpt = Str::excerpt('This is my name', 'my', [
    'radius' => 3
]);

// '...is my na...'
```

Опція `radius`, що за замовчуванням дорівнює `100`, задає кількість символів, які мають з'явитися з кожного боку обрізаного рядка.

Крім того, опцією `omission` можна задати рядок, який буде додано на початку та в кінці обрізаного рядка:

```php
use Illuminate\Support\Str;

$excerpt = Str::excerpt('This is my name', 'name', [
    'radius' => 3,
    'omission' => '(...) '
]);

// '(...) my name'
```

<a name="method-str-finish"></a>
#### `Str::finish()` {.collection-method}

Метод `Str::finish` додає до рядка одне входження заданого значення, якщо рядок ще не закінчується цим значенням:

```php
use Illuminate\Support\Str;

$adjusted = Str::finish('this/string', '/');

// this/string/

$adjusted = Str::finish('this/string/', '/');

// this/string/
```

<a name="method-str-from-base64"></a>
#### `Str::fromBase64()` {.collection-method}

Метод `Str::fromBase64` декодує заданий рядок Base64:

```php
use Illuminate\Support\Str;

$decoded = Str::fromBase64('TGFyYXZlbA==');

// Laravel
```

<a name="method-str-headline"></a>
#### `Str::headline()` {.collection-method}

Метод `Str::headline` перетворює рядки, розділені регістром, дефісами чи підкресленнями, на рядок, розділений пробілами, де перша літера кожного слова велика:

```php
use Illuminate\Support\Str;

$headline = Str::headline('steve_jobs');

// Steve Jobs

$headline = Str::headline('EmailNotificationSent');

// Email Notification Sent
```

<a name="method-str-initials"></a>
#### `Str::initials()` {.collection-method}

Метод `Str::initials` повертає ініціали заданого рядка, за бажанням переводячи їх у верхній регістр:

```php
use Illuminate\Support\Str;

$initials = Str::initials('taylor otwell');

// to

$initials = Str::initials('taylor otwell', capitalize: true);

// TO
```

<a name="method-str-inline-markdown"></a>
#### `Str::inlineMarkdown()` {.collection-method}

Метод `Str::inlineMarkdown` перетворює Markdown у стилі GitHub на інлайновий HTML за допомогою [CommonMark](https://commonmark.thephpleague.com/). Проте, на відміну від методу `markdown`, він не обгортає весь згенерований HTML у блоковий елемент:

```php
use Illuminate\Support\Str;

$html = Str::inlineMarkdown('**Laravel**');

// <strong>Laravel</strong>
```

#### Безпека Markdown

За замовчуванням Markdown підтримує сирий HTML, а це відкриває вразливість до міжсайтового скриптингу (XSS), якщо подавати туди сирий користувацький ввід. Як радить [документація з безпеки CommonMark](https://commonmark.thephpleague.com/security/), ви можете скористатися опцією `html_input`, щоб екранувати чи вирізати сирий HTML, і опцією `allow_unsafe_links`, щоб указати, чи дозволяти небезпечні посилання. Якщо вам потрібно дозволити частину сирого HTML, проженіть скомпільований Markdown через HTML Purifier:

```php
use Illuminate\Support\Str;

Str::inlineMarkdown('Inject: <script>alert("Hello XSS!");</script>', [
    'html_input' => 'strip',
    'allow_unsafe_links' => false,
]);

// Inject: alert(&quot;Hello XSS!&quot;);
```

<a name="method-str-is"></a>
#### `Str::is()` {.collection-method}

Метод `Str::is` визначає, чи відповідає заданий рядок заданому шаблону. Зірочки можна використовувати як символи підстановки:

```php
use Illuminate\Support\Str;

$matches = Str::is('foo*', 'foobar');

// true

$matches = Str::is('baz*', 'foobar');

// false
```

Вимкнути чутливість до регістру можна, задавши аргументу `ignoreCase` значення `true`:

```php
use Illuminate\Support\Str;

$matches = Str::is('*.jpg', 'photo.JPG', ignoreCase: true);

// true
```

<a name="method-str-is-ascii"></a>
#### `Str::isAscii()` {.collection-method}

Метод `Str::isAscii` визначає, чи є заданий рядок 7-бітним ASCII:

```php
use Illuminate\Support\Str;

$isAscii = Str::isAscii('Taylor');

// true

$isAscii = Str::isAscii('ü');

// false
```

<a name="method-str-is-json"></a>
#### `Str::isJson()` {.collection-method}

Метод `Str::isJson` визначає, чи є заданий рядок коректним JSON:

```php
use Illuminate\Support\Str;

$result = Str::isJson('[1,2,3]');

// true

$result = Str::isJson('{"first": "John", "last": "Doe"}');

// true

$result = Str::isJson('{first: "John", last: "Doe"}');

// false
```

<a name="method-str-is-url"></a>
#### `Str::isUrl()` {.collection-method}

Метод `Str::isUrl` визначає, чи є заданий рядок коректним URL:

```php
use Illuminate\Support\Str;

$isUrl = Str::isUrl('http://example.com');

// true

$isUrl = Str::isUrl('laravel');

// false
```

Метод `isUrl` вважає коректними чимало протоколів. Втім, ви можете вказати, які протоколи вважати коректними, передавши їх методу `isUrl`:

```php
$isUrl = Str::isUrl('http://example.com', ['http', 'https']);
```

<a name="method-str-is-ulid"></a>
#### `Str::isUlid()` {.collection-method}

Метод `Str::isUlid` визначає, чи є заданий рядок коректним ULID:

```php
use Illuminate\Support\Str;

$isUlid = Str::isUlid('01gd6r360bp37zj17nxb55yv40');

// true

$isUlid = Str::isUlid('laravel');

// false
```

<a name="method-str-is-uuid"></a>
#### `Str::isUuid()` {.collection-method}

Метод `Str::isUuid` визначає, чи є заданий рядок коректним UUID:

```php
use Illuminate\Support\Str;

$isUuid = Str::isUuid('a0a2a2d2-0b87-4a18-83f2-2529882be2de');

// true

$isUuid = Str::isUuid('laravel');

// false
```

Ви можете також перевірити, чи відповідає заданий UUID специфікації певної версії (1, 3, 4, 5, 6, 7 або 8):

```php
use Illuminate\Support\Str;

$isUuid = Str::isUuid('a0a2a2d2-0b87-4a18-83f2-2529882be2de', version: 4);

// true

$isUuid = Str::isUuid('a0a2a2d2-0b87-4a18-83f2-2529882be2de', version: 1);

// false
```

<a name="method-kebab-case"></a>
#### `Str::kebab()` {.collection-method}

Метод `Str::kebab` перетворює заданий рядок на `kebab-case`:

```php
use Illuminate\Support\Str;

$converted = Str::kebab('fooBar');

// foo-bar
```

<a name="method-str-lcfirst"></a>
#### `Str::lcfirst()` {.collection-method}

Метод `Str::lcfirst` повертає заданий рядок, у якому перший символ переведено в нижній регістр:

```php
use Illuminate\Support\Str;

$string = Str::lcfirst('Foo Bar');

// foo Bar
```

<a name="method-str-length"></a>
#### `Str::length()` {.collection-method}

Метод `Str::length` повертає довжину заданого рядка:

```php
use Illuminate\Support\Str;

$length = Str::length('Laravel');

// 7
```

<a name="method-str-limit"></a>
#### `Str::limit()` {.collection-method}

Метод `Str::limit` обрізає заданий рядок до вказаної довжини:

```php
use Illuminate\Support\Str;

$truncated = Str::limit('The quick brown fox jumps over the lazy dog', 20);

// The quick brown fox...
```

Третім аргументом методу можна змінити рядок, який буде дописано в кінці обрізаного рядка:

```php
$truncated = Str::limit('The quick brown fox jumps over the lazy dog', 20, ' (...)');

// The quick brown fox (...)
```

Якщо ви хочете зберегти цілі слова під час обрізання, скористайтеся аргументом `preserveWords`. Коли він `true`, рядок буде обрізано до найближчої межі цілого слова:

```php
$truncated = Str::limit('The quick brown fox', 12, preserveWords: true);

// The quick...
```

<a name="method-str-lower"></a>
#### `Str::lower()` {.collection-method}

Метод `Str::lower` переводить заданий рядок у нижній регістр:

```php
use Illuminate\Support\Str;

$converted = Str::lower('LARAVEL');

// laravel
```

<a name="method-str-markdown"></a>
#### `Str::markdown()` {.collection-method}

Метод `Str::markdown` перетворює Markdown у стилі GitHub на HTML за допомогою [CommonMark](https://commonmark.thephpleague.com/):

```php
use Illuminate\Support\Str;

$html = Str::markdown('# Laravel');

// <h1>Laravel</h1>

$html = Str::markdown('# Taylor <b>Otwell</b>', [
    'html_input' => 'strip',
]);

// <h1>Taylor Otwell</h1>
```

#### Безпека Markdown

За замовчуванням Markdown підтримує сирий HTML, а це відкриває вразливість до міжсайтового скриптингу (XSS), якщо подавати туди сирий користувацький ввід. Як радить [документація з безпеки CommonMark](https://commonmark.thephpleague.com/security/), ви можете скористатися опцією `html_input`, щоб екранувати чи вирізати сирий HTML, і опцією `allow_unsafe_links`, щоб указати, чи дозволяти небезпечні посилання. Якщо вам потрібно дозволити частину сирого HTML, проженіть скомпільований Markdown через HTML Purifier:

```php
use Illuminate\Support\Str;

Str::markdown('Inject: <script>alert("Hello XSS!");</script>', [
    'html_input' => 'strip',
    'allow_unsafe_links' => false,
]);

// <p>Inject: alert(&quot;Hello XSS!&quot;);</p>
```

<a name="method-str-mask"></a>
#### `Str::mask()` {.collection-method}

Метод `Str::mask` маскує частину рядка повторюваним символом; ним можна приховувати фрагменти рядків - наприклад, адреси електронної пошти чи номери телефонів:

```php
use Illuminate\Support\Str;

$string = Str::mask('taylor@example.com', '*', 3);

// tay***************
```

За потреби третім аргументом методу `mask` можна передати від'ємне число - тоді маскування почнеться на заданій відстані від кінця рядка:

```php
$string = Str::mask('taylor@example.com', '*', -15, 3);

// tay***@example.com
```

<a name="method-str-match"></a>
#### `Str::match()` {.collection-method}

Метод `Str::match` повертає частину рядка, що відповідає заданому шаблону регулярного виразу:

```php
use Illuminate\Support\Str;

$result = Str::match('/bar/', 'foo bar');

// 'bar'

$result = Str::match('/foo (.*)/', 'foo bar');

// 'bar'
```

<a name="method-str-match-all"></a>
#### `Str::matchAll()` {.collection-method}

Метод `Str::matchAll` повертає колекцію з частинами рядка, що відповідають заданому шаблону регулярного виразу:

```php
use Illuminate\Support\Str;

$result = Str::matchAll('/bar/', 'bar foo bar');

// collect(['bar', 'bar'])
```

Якщо ви вкажете у виразі групу захоплення, Laravel повернає колекцію збігів першої такої групи:

```php
use Illuminate\Support\Str;

$result = Str::matchAll('/f(\w*)/', 'bar fun bar fly');

// collect(['un', 'ly']);
```

Якщо збігів не знайдено, буде повернуто порожню колекцію.

<a name="method-str-is-match"></a>
#### `Str::isMatch()` {.collection-method}

Метод `Str::isMatch` повертає `true`, якщо рядок відповідає заданому регулярному виразу:

```php
use Illuminate\Support\Str;

$result = Str::isMatch('/foo (.*)/', 'foo bar');

// true

$result = Str::isMatch('/foo (.*)/', 'laravel');

// false
```

<a name="method-str-ordered-uuid"></a>
#### `Str::orderedUuid()` {.collection-method}

Метод `Str::orderedUuid` генерує UUID, у якому першою йде часова позначка, - такий UUID ефективно зберігати в проіндексованому стовпці бази даних. Кожен згенерований цим методом UUID сортуватиметься після UUID, згенерованих ним раніше:

```php
use Illuminate\Support\Str;

return (string) Str::orderedUuid();
```

<a name="method-str-padboth"></a>
#### `Str::padBoth()` {.collection-method}

Метод `Str::padBoth` обгортає PHP-функцію `str_pad` і доповнює рядок з обох боків іншим рядком, доки той не досягне потрібної довжини:

```php
use Illuminate\Support\Str;

$padded = Str::padBoth('James', 10, '_');

// '__James___'

$padded = Str::padBoth('James', 10);

// '  James   '
```

<a name="method-str-padleft"></a>
#### `Str::padLeft()` {.collection-method}

Метод `Str::padLeft` обгортає PHP-функцію `str_pad` і доповнює рядок зліва іншим рядком, доки той не досягне потрібної довжини:

```php
use Illuminate\Support\Str;

$padded = Str::padLeft('James', 10, '-=');

// '-=-=-James'

$padded = Str::padLeft('James', 10);

// '     James'
```

<a name="method-str-padright"></a>
#### `Str::padRight()` {.collection-method}

Метод `Str::padRight` обгортає PHP-функцію `str_pad` і доповнює рядок справа іншим рядком, доки той не досягне потрібної довжини:

```php
use Illuminate\Support\Str;

$padded = Str::padRight('James', 10, '-');

// 'James-----'

$padded = Str::padRight('James', 10);

// 'James     '
```

<a name="method-str-password"></a>
#### `Str::password()` {.collection-method}

Методом `Str::password` можна згенерувати надійний випадковий пароль заданої довжини. Пароль складатиметься з літер, цифр і символів. За замовчуванням довжина пароля - 32 символи:

```php
use Illuminate\Support\Str;

$password = Str::password();

// 'EbJo2vE-AS:U,$%_gkrV4n,q~1xy/-_4'

$password = Str::password(12);

// 'qwuar>#V|i]N'
```

<a name="method-str-counted"></a>
#### `Str::counted()` {.collection-method}

Метод `Str::counted` перетворює слово в однині на однину чи множину залежно від заданої кількості й додає перед результатом відформатовану кількість:

```php
use Illuminate\Support\Str;

$label = Str::counted('order', 1);

// 1 order

$label = Str::counted('order', 1000);

// 1,000 orders
```

<a name="method-str-plural"></a>
#### `Str::plural()` {.collection-method}

Метод `Str::plural` перетворює слово в однині на форму множини. Ця функція підтримує [будь-яку з мов, які підтримує плюралізатор Laravel](/docs/{{version}}/localization#pluralization-language):

```php
use Illuminate\Support\Str;

$plural = Str::plural('car');

// cars

$plural = Str::plural('child');

// children
```

Другим аргументом функції можна передати ціле число, щоб отримати форму однини або множини:

```php
use Illuminate\Support\Str;

$plural = Str::plural('child', 2);

// children

$singular = Str::plural('child', 1);

// child
```

Аргументом `prependCount` можна додати перед формою множини відформатоване значення `$count`:

```php
use Illuminate\Support\Str;

$label = Str::plural('car', 1000, prependCount: true);

// 1,000 cars
```

<a name="method-str-plural-studly"></a>
#### `Str::pluralStudly()` {.collection-method}

Метод `Str::pluralStudly` перетворює слово в однині, записане в studly caps, на форму множини. Ця функція підтримує [будь-яку з мов, які підтримує плюралізатор Laravel](/docs/{{version}}/localization#pluralization-language):

```php
use Illuminate\Support\Str;

$plural = Str::pluralStudly('VerifiedHuman');

// VerifiedHumans

$plural = Str::pluralStudly('UserFeedback');

// UserFeedback
```

Другим аргументом функції можна передати ціле число, щоб отримати форму однини або множини:

```php
use Illuminate\Support\Str;

$plural = Str::pluralStudly('VerifiedHuman', 2);

// VerifiedHumans

$singular = Str::pluralStudly('VerifiedHuman', 1);

// VerifiedHuman
```

<a name="method-str-position"></a>
#### `Str::position()` {.collection-method}

Метод `Str::position` повертає позицію першого входження підрядка в рядок. Якщо підрядка в заданому рядку немає, повертається `false`:

```php
use Illuminate\Support\Str;

$position = Str::position('Hello, World!', 'Hello');

// 0

$position = Str::position('Hello, World!', 'W');

// 7
```

<a name="method-str-random"></a>
#### `Str::random()` {.collection-method}

Метод `Str::random` генерує випадковий рядок указаної довжини. Ця функція використовує PHP-функцію `random_bytes`:

```php
use Illuminate\Support\Str;

$random = Str::random(40);
```

Під час тестування буває корисно «підмінити» значення, яке повертає метод `Str::random`. Для цього скористайтеся методом `createRandomStringsUsing`:

```php
Str::createRandomStringsUsing(function () {
    return 'fake-random-string';
});
```

Щоб метод `random` знову генерував випадкові рядки як звичайно, викличте метод `createRandomStringsNormally`:

```php
Str::createRandomStringsNormally();
```

<a name="method-str-remove"></a>
#### `Str::remove()` {.collection-method}

Метод `Str::remove` видаляє з рядка задане значення або масив значень:

```php
use Illuminate\Support\Str;

$string = 'Peter Piper picked a peck of pickled peppers.';

$removed = Str::remove('e', $string);

// Ptr Pipr pickd a pck of pickld ppprs.
```

Ви можете також передати третім аргументом `false`, щоб метод `remove` не звертав уваги на регістр.

<a name="method-str-repeat"></a>
#### `Str::repeat()` {.collection-method}

Метод `Str::repeat` повторює заданий рядок:

```php
use Illuminate\Support\Str;

$string = 'a';

$repeat = Str::repeat($string, 5);

// aaaaa
```

<a name="method-str-replace"></a>
#### `Str::replace()` {.collection-method}

Метод `Str::replace` замінює в рядку заданий рядок:

```php
use Illuminate\Support\Str;

$string = 'Laravel 11.x';

$replaced = Str::replace('11.x', '12.x', $string);

// Laravel 12.x
```

Метод `replace` приймає також аргумент `caseSensitive`. За замовчуванням метод `replace` чутливий до регістру:

```php
$replaced = Str::replace(
    'php',
    'Laravel',
    'PHP Framework for Web Artisans',
    caseSensitive: false
);

// Laravel Framework for Web Artisans
```

<a name="method-str-replace-array"></a>
#### `Str::replaceArray()` {.collection-method}

Метод `Str::replaceArray` послідовно замінює задане значення в рядку значеннями з масиву:

```php
use Illuminate\Support\Str;

$string = 'The event will take place between ? and ?';

$replaced = Str::replaceArray('?', ['8:30', '9:00'], $string);

// The event will take place between 8:30 and 9:00
```

<a name="method-str-replace-first"></a>
#### `Str::replaceFirst()` {.collection-method}

Метод `Str::replaceFirst` замінює перше входження заданого значення в рядку:

```php
use Illuminate\Support\Str;

$replaced = Str::replaceFirst('the', 'a', 'the quick brown fox jumps over the lazy dog');

// a quick brown fox jumps over the lazy dog
```

<a name="method-str-replace-last"></a>
#### `Str::replaceLast()` {.collection-method}

Метод `Str::replaceLast` замінює останнє входження заданого значення в рядку:

```php
use Illuminate\Support\Str;

$replaced = Str::replaceLast('the', 'a', 'the quick brown fox jumps over the lazy dog');

// the quick brown fox jumps over a lazy dog
```

<a name="method-str-replace-matches"></a>
#### `Str::replaceMatches()` {.collection-method}

Метод `Str::replaceMatches` замінює всі частини рядка, що відповідають шаблону, заданим рядком заміни:

```php
use Illuminate\Support\Str;

$replaced = Str::replaceMatches(
    pattern: '/[^A-Za-z0-9]++/',
    replace: '',
    subject: '(+1) 501-555-1000'
)

// '15015551000'
```

Метод `replaceMatches` приймає також замикання, яке буде викликано для кожної частини рядка, що відповідає заданому шаблону: у ньому ви виконуєте логіку заміни й повертаєте замінене значення:

```php
use Illuminate\Support\Str;

$replaced = Str::replaceMatches('/\d/', function (array $matches) {
    return '['.$matches[0].']';
}, '123');

// '[1][2][3]'
```

<a name="method-str-replace-start"></a>
#### `Str::replaceStart()` {.collection-method}

Метод `Str::replaceStart` замінює перше входження заданого значення лише тоді, коли воно стоїть на початку рядка:

```php
use Illuminate\Support\Str;

$replaced = Str::replaceStart('Hello', 'Laravel', 'Hello World');

// Laravel World

$replaced = Str::replaceStart('World', 'Laravel', 'Hello World');

// Hello World
```

<a name="method-str-replace-end"></a>
#### `Str::replaceEnd()` {.collection-method}

Метод `Str::replaceEnd` замінює останнє входження заданого значення лише тоді, коли воно стоїть у кінці рядка:

```php
use Illuminate\Support\Str;

$replaced = Str::replaceEnd('World', 'Laravel', 'Hello World');

// Hello Laravel

$replaced = Str::replaceEnd('Hello', 'Laravel', 'Hello World');

// Hello World
```

<a name="method-str-reverse"></a>
#### `Str::reverse()` {.collection-method}

Метод `Str::reverse` перевертає заданий рядок:

```php
use Illuminate\Support\Str;

$reversed = Str::reverse('Hello World');

// dlroW olleH
```

<a name="method-str-singular"></a>
#### `Str::singular()` {.collection-method}

Метод `Str::singular` перетворює рядок на форму однини. Ця функція підтримує [будь-яку з мов, які підтримує плюралізатор Laravel](/docs/{{version}}/localization#pluralization-language):

```php
use Illuminate\Support\Str;

$singular = Str::singular('cars');

// car

$singular = Str::singular('children');

// child
```

<a name="method-str-slug"></a>
#### `Str::slug()` {.collection-method}

Метод `Str::slug` генерує із заданого рядка дружній до URL «slug»:

```php
use Illuminate\Support\Str;

$slug = Str::slug('Laravel 5 Framework', '-');

// laravel-5-framework
```

<a name="method-snake-case"></a>
#### `Str::snake()` {.collection-method}

Метод `Str::snake` перетворює заданий рядок на `snake_case`:

```php
use Illuminate\Support\Str;

$converted = Str::snake('fooBar');

// foo_bar

$converted = Str::snake('fooBar', '-');

// foo-bar
```

<a name="method-str-squish"></a>
#### `Str::squish()` {.collection-method}

Метод `Str::squish` прибирає з рядка всі надлишкові пробіли, зокрема й між словами:

```php
use Illuminate\Support\Str;

$string = Str::squish('    laravel    framework    ');

// laravel framework
```

<a name="method-str-start"></a>
#### `Str::start()` {.collection-method}

Метод `Str::start` додає до рядка одне входження заданого значення, якщо рядок ще не починається цим значенням:

```php
use Illuminate\Support\Str;

$adjusted = Str::start('this/string', '/');

// /this/string

$adjusted = Str::start('/this/string', '/');

// /this/string
```

<a name="method-starts-with"></a>
#### `Str::startsWith()` {.collection-method}

Метод `Str::startsWith` визначає, чи починається заданий рядок заданим значенням:

```php
use Illuminate\Support\Str;

$result = Str::startsWith('This is my name', 'This');

// true
```

Якщо передати масив можливих значень, метод `startsWith` повернає `true`, коли рядок починається будь-яким із заданих значень:

```php
$result = Str::startsWith('This is my name', ['This', 'That', 'There']);

// true
```

<a name="method-studly-case"></a>
#### `Str::studly()` {.collection-method}

Метод `Str::studly` перетворює заданий рядок на `StudlyCase`:

```php
use Illuminate\Support\Str;

$converted = Str::studly('foo_bar');

// FooBar
```

<a name="method-str-substr"></a>
#### `Str::substr()` {.collection-method}

Метод `Str::substr` повертає частину рядка, задану параметрами початку та довжини:

```php
use Illuminate\Support\Str;

$converted = Str::substr('The Laravel Framework', 4, 7);

// Laravel
```

<a name="method-str-substrcount"></a>
#### `Str::substrCount()` {.collection-method}

Метод `Str::substrCount` повертає кількість входжень заданого значення в заданому рядку:

```php
use Illuminate\Support\Str;

$count = Str::substrCount('If you like ice cream, you will like snow cones.', 'like');

// 2
```

<a name="method-str-substrreplace"></a>
#### `Str::substrReplace()` {.collection-method}

Метод `Str::substrReplace` замінює текст у частині рядка, починаючи з позиції, заданої третім аргументом, і замінюючи стільку символів, скільки задано четвертим аргументом. Якщо передати четвертим аргументом `0`, рядок буде вставлено у вказану позицію, не замінюючи жодного наявного символу:

```php
use Illuminate\Support\Str;

$result = Str::substrReplace('1300', ':', 2);
// 13:

$result = Str::substrReplace('1300', ':', 2, 0);
// 13:00
```

<a name="method-str-swap"></a>
#### `Str::swap()` {.collection-method}

Метод `Str::swap` замінює в заданому рядку кілька значень за допомогою PHP-функції `strtr`:

```php
use Illuminate\Support\Str;

$string = Str::swap([
    'Tacos' => 'Burritos',
    'great' => 'fantastic',
], 'Tacos are great!');

// Burritos are fantastic!
```

<a name="method-take"></a>
#### `Str::take()` {.collection-method}

Метод `Str::take` повертає вказану кількість символів із початку рядка:

```php
use Illuminate\Support\Str;

$taken = Str::take('Build something amazing!', 5);

// Build
```

<a name="method-title-case"></a>
#### `Str::title()` {.collection-method}

Метод `Str::title` перетворює заданий рядок на `Title Case`:

```php
use Illuminate\Support\Str;

$converted = Str::title('a nice title uses the correct case');

// A Nice Title Uses The Correct Case
```

<a name="method-str-to-base64"></a>
#### `Str::toBase64()` {.collection-method}

Метод `Str::toBase64` перетворює заданий рядок на Base64:

```php
use Illuminate\Support\Str;

$base64 = Str::toBase64('Laravel');

// TGFyYXZlbA==
```

<a name="method-str-transliterate"></a>
#### `Str::transliterate()` {.collection-method}

Метод `Str::transliterate` спробує перетворити заданий рядок на найближче представлення в ASCII:

```php
use Illuminate\Support\Str;

$email = Str::transliterate('ⓣⓔⓢⓣ@ⓛⓐⓡⓐⓥⓔⓛ.ⓒⓞⓜ');

// 'test@laravel.com'
```

<a name="method-str-trim"></a>
#### `Str::trim()` {.collection-method}

Метод `Str::trim` прибирає пробіли (чи інші символи) з початку та кінця заданого рядка. На відміну від нативної PHP-функції `trim`, метод `Str::trim` прибирає ще й пробільні символи Unicode:

```php
use Illuminate\Support\Str;

$string = Str::trim(' foo bar ');

// 'foo bar'
```

<a name="method-str-ltrim"></a>
#### `Str::ltrim()` {.collection-method}

Метод `Str::ltrim` прибирає пробіли (чи інші символи) з початку заданого рядка. На відміну від нативної PHP-функції `ltrim`, метод `Str::ltrim` прибирає ще й пробільні символи Unicode:

```php
use Illuminate\Support\Str;

$string = Str::ltrim('  foo bar  ');

// 'foo bar  '
```

<a name="method-str-rtrim"></a>
#### `Str::rtrim()` {.collection-method}

Метод `Str::rtrim` прибирає пробіли (чи інші символи) з кінця заданого рядка. На відміну від нативної PHP-функції `rtrim`, метод `Str::rtrim` прибирає ще й пробільні символи Unicode:

```php
use Illuminate\Support\Str;

$string = Str::rtrim('  foo bar  ');

// '  foo bar'
```

<a name="method-str-ucfirst"></a>
#### `Str::ucfirst()` {.collection-method}

Метод `Str::ucfirst` повертає заданий рядок, у якому перший символ переведено у верхній регістр:

```php
use Illuminate\Support\Str;

$string = Str::ucfirst('foo bar');

// Foo bar
```

<a name="method-str-ucsplit"></a>
#### `Str::ucsplit()` {.collection-method}

Метод `Str::ucsplit` розбиває заданий рядок на масив за великими літерами:

```php
use Illuminate\Support\Str;

$segments = Str::ucsplit('FooBar');

// [0 => 'Foo', 1 => 'Bar']
```

<a name="method-str-ucwords"></a>
#### `Str::ucwords()` {.collection-method}

Метод `Str::ucwords` переводить перший символ кожного слова в заданому рядку у верхній регістр:

```php
use Illuminate\Support\Str;

$string = Str::ucwords('laravel framework');

// Laravel Framework
```

<a name="method-str-upper"></a>
#### `Str::upper()` {.collection-method}

Метод `Str::upper` переводить заданий рядок у верхній регістр:

```php
use Illuminate\Support\Str;

$string = Str::upper('laravel');

// LARAVEL
```

<a name="method-str-ulid"></a>
#### `Str::ulid()` {.collection-method}

Метод `Str::ulid` генерує ULID - компактний унікальний ідентифікатор, упорядкований за часом:

```php
use Illuminate\Support\Str;

return (string) Str::ulid();

// 01gd6r360bp37zj17nxb55yv40
```

Якщо ви хочете отримати екземпляр дати `Illuminate\Support\Carbon`, що відповідає даті й часу створення заданого ULID, скористайтеся методом `createFromId` з інтеграції Carbon у Laravel:

```php
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;

$date = Carbon::createFromId((string) Str::ulid());
```

Під час тестування буває корисно «підмінити» значення, яке повертає метод `Str::ulid`. Для цього скористайтеся методом `createUlidsUsing`:

```php
use Symfony\Component\Uid\Ulid;

Str::createUlidsUsing(function () {
    return new Ulid('01HRDBNHHCKNW2AK4Z29SN82T9');
});
```

Щоб метод `ulid` знову генерував ULID як звичайно, викличте метод `createUlidsNormally`:

```php
Str::createUlidsNormally();
```

<a name="method-str-unwrap"></a>
#### `Str::unwrap()` {.collection-method}

Метод `Str::unwrap` прибирає вказані рядки з початку та кінця заданого рядка:

```php
use Illuminate\Support\Str;

Str::unwrap('-Laravel-', '-');

// Laravel

Str::unwrap('{framework: "Laravel"}', '{', '}');

// framework: "Laravel"
```

<a name="method-str-uuid"></a>
#### `Str::uuid()` {.collection-method}

Метод `Str::uuid` генерує UUID (версії 4):

```php
use Illuminate\Support\Str;

return (string) Str::uuid();
```

Під час тестування буває корисно «підмінити» значення, яке повертає метод `Str::uuid`. Для цього скористайтеся методом `createUuidsUsing`:

```php
use Ramsey\Uuid\Uuid;

Str::createUuidsUsing(function () {
    return Uuid::fromString('eadbfeac-5258-45c2-bab7-ccb9b5ef74f9');
});
```

Щоб метод `uuid` знову генерував UUID як звичайно, викличте метод `createUuidsNormally`:

```php
Str::createUuidsNormally();
```

<a name="method-str-uuid7"></a>
#### `Str::uuid7()` {.collection-method}

Метод `Str::uuid7` генерує UUID (версії 7):

```php
use Illuminate\Support\Str;

return (string) Str::uuid7();
```

Необов'язковим параметром можна передати `DateTimeInterface`, який буде використано для генерації впорядкованого UUID:

```php
return (string) Str::uuid7(time: now());
```

<a name="method-str-word-count"></a>
#### `Str::wordCount()` {.collection-method}

Метод `Str::wordCount` повертає кількість слів у рядку:

```php
use Illuminate\Support\Str;

Str::wordCount('Hello, world!'); // 2
```

<a name="method-str-word-wrap"></a>
#### `Str::wordWrap()` {.collection-method}

Метод `Str::wordWrap` переносить рядок по заданій кількості символів:

```php
use Illuminate\Support\Str;

$text = "The quick brown fox jumped over the lazy dog.";

Str::wordWrap($text, characters: 20, break: "<br />\n");

/*
The quick brown fox<br />
jumped over the lazy<br />
dog.
*/
```

<a name="method-str-words"></a>
#### `Str::words()` {.collection-method}

Метод `Str::words` обмежує кількість слів у рядку. Третім аргументом методу можна передати додатковий рядок, який буде дописано в кінці обрізаного рядка:

```php
use Illuminate\Support\Str;

return Str::words('Perfectly balanced, as all things should be.', 3, ' >>>');

// Perfectly balanced, as >>>
```

<a name="method-str-wrap"></a>
#### `Str::wrap()` {.collection-method}

Метод `Str::wrap` обгортає заданий рядок додатковим рядком або парою рядків:

```php
use Illuminate\Support\Str;

Str::wrap('Laravel', '"');

// "Laravel"

Str::wrap('is', before: 'This ', after: ' Laravel!');

// This is Laravel!
```

<a name="method-str"></a>
#### `str()` {.collection-method}

Функція `str` повертає новий екземпляр `Illuminate\Support\Stringable` для заданого рядка. Ця функція рівнозначна методу `Str::of`:

```php
$string = str('Taylor')->append(' Otwell');

// 'Taylor Otwell'
```

Якщо функції `str` не передати аргументу, вона повертає об'єкт, який проксує виклики методів до `Illuminate\Support\Str`:

```php
$snake = str()->snake('FooBar');

// 'foo_bar'
```

<a name="method-trans"></a>
#### `trans()` {.collection-method}

Функція `trans` перекладає заданий ключ перекладу за допомогою ваших [мовних файлів](/docs/{{version}}/localization):

```php
echo trans('messages.welcome');
```

Якщо вказаного ключа перекладу не існує, функція `trans` повернає переданий ключ. Тож у прикладі вище функція `trans` повернула б `messages.welcome`, якби такого ключа перекладу не було.

<a name="method-trans-choice"></a>
#### `trans_choice()` {.collection-method}

Функція `trans_choice` перекладає заданий ключ перекладу з урахуванням числової форми:

```php
echo trans_choice('messages.notifications', $unreadCount);
```

Якщо вказаного ключа перекладу не існує, функція `trans_choice` повернає переданий ключ. Тож у прикладі вище функція `trans_choice` повернула б `messages.notifications`, якби такого ключа перекладу не було.

<a name="fluent-strings"></a>
## Плавні рядки

Плавні рядки дають плавніший, об'єктно-орієнтований інтерфейс для роботи з рядковими значеннями: ви можете зчіплювати кілька операцій над рядком ланцюжком, і синтаксис читається краще, ніж у традиційних операціях із рядками.

<a name="method-fluent-str-after"></a>
#### `after` {.collection-method}

Метод `after` повертає все, що йде в рядку після заданого значення. Якщо значення в рядку немає, буде повернуто цілий рядок:

```php
use Illuminate\Support\Str;

$slice = Str::of('This is my name')->after('This is');

// ' my name'
```

<a name="method-fluent-str-after-last"></a>
#### `afterLast` {.collection-method}

Метод `afterLast` повертає все, що йде в рядку після останнього входження заданого значення. Якщо значення в рядку немає, буде повернуто цілий рядок:

```php
use Illuminate\Support\Str;

$slice = Str::of('App\Http\Controllers\Controller')->afterLast('\\');

// 'Controller'
```

<a name="method-fluent-str-apa"></a>
#### `apa` {.collection-method}

Метод `apa` перетворює заданий рядок на заголовний регістр за [настановами APA](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case):

```php
use Illuminate\Support\Str;

$converted = Str::of('a nice title uses the correct case')->apa();

// A Nice Title Uses the Correct Case
```

<a name="method-fluent-str-append"></a>
#### `append` {.collection-method}

Метод `append` дописує задані значення в кінець рядка:

```php
use Illuminate\Support\Str;

$string = Str::of('Taylor')->append(' Otwell');

// 'Taylor Otwell'
```

<a name="method-fluent-str-ascii"></a>
#### `ascii` {.collection-method}

Метод `ascii` спробує транслітерувати рядок у значення ASCII:

```php
use Illuminate\Support\Str;

$string = Str::of('ü')->ascii();

// 'u'
```

<a name="method-fluent-str-basename"></a>
#### `basename` {.collection-method}

Метод `basename` повертає останній компонент назви із заданого рядка:

```php
use Illuminate\Support\Str;

$string = Str::of('/foo/bar/baz')->basename();

// 'baz'
```

За потреби ви можете передати «розширення», яке буде прибрано з останнього компонента:

```php
use Illuminate\Support\Str;

$string = Str::of('/foo/bar/baz.jpg')->basename('.jpg');

// 'baz'
```

<a name="method-fluent-str-before"></a>
#### `before` {.collection-method}

Метод `before` повертає все, що йде в рядку до заданого значення:

```php
use Illuminate\Support\Str;

$slice = Str::of('This is my name')->before('my name');

// 'This is '
```

<a name="method-fluent-str-before-last"></a>
#### `beforeLast` {.collection-method}

Метод `beforeLast` повертає все, що йде в рядку до останнього входження заданого значення:

```php
use Illuminate\Support\Str;

$slice = Str::of('This is my name')->beforeLast('is');

// 'This '
```

<a name="method-fluent-str-between"></a>
#### `between` {.collection-method}

Метод `between` повертає частину рядка між двома значеннями:

```php
use Illuminate\Support\Str;

$converted = Str::of('This is my name')->between('This', 'name');

// ' is my '
```

<a name="method-fluent-str-between-first"></a>
#### `betweenFirst` {.collection-method}

Метод `betweenFirst` повертає найменшу можливу частину рядка між двома значеннями:

```php
use Illuminate\Support\Str;

$converted = Str::of('[a] bc [d]')->betweenFirst('[', ']');

// 'a'
```

<a name="method-fluent-str-camel"></a>
#### `camel` {.collection-method}

Метод `camel` перетворює заданий рядок на `camelCase`:

```php
use Illuminate\Support\Str;

$converted = Str::of('foo_bar')->camel();

// 'fooBar'
```

<a name="method-fluent-str-char-at"></a>
#### `charAt` {.collection-method}

Метод `charAt` повертає символ за вказаним індексом. Якщо індекс поза межами рядка, повертається `false`:

```php
use Illuminate\Support\Str;

$character = Str::of('This is my name.')->charAt(6);

// 's'
```

<a name="method-fluent-str-class-basename"></a>
#### `classBasename` {.collection-method}

Метод `classBasename` повертає назву заданого класу без його простору імен:

```php
use Illuminate\Support\Str;

$class = Str::of('Foo\Bar\Baz')->classBasename();

// 'Baz'
```

<a name="method-fluent-str-chop-start"></a>
#### `chopStart` {.collection-method}

Метод `chopStart` видаляє перше входження заданого значення лише тоді, коли воно стоїть на початку рядка:

```php
use Illuminate\Support\Str;

$url = Str::of('https://laravel.com')->chopStart('https://');

// 'laravel.com'
```

Ви можете також передати масив. Якщо рядок починається з будь-якого зі значень масиву, це значення буде з нього видалено:

```php
use Illuminate\Support\Str;

$url = Str::of('http://laravel.com')->chopStart(['https://', 'http://']);

// 'laravel.com'
```

<a name="method-fluent-str-chop-end"></a>
#### `chopEnd` {.collection-method}

Метод `chopEnd` видаляє останнє входження заданого значення лише тоді, коли воно стоїть у кінці рядка:

```php
use Illuminate\Support\Str;

$url = Str::of('https://laravel.com')->chopEnd('.com');

// 'https://laravel'
```

Ви можете також передати масив. Якщо рядок закінчується будь-яким зі значень масиву, це значення буде з нього видалено:

```php
use Illuminate\Support\Str;

$url = Str::of('http://laravel.com')->chopEnd(['.com', '.io']);

// 'http://laravel'
```

<a name="method-fluent-str-contains"></a>
#### `contains` {.collection-method}

Метод `contains` визначає, чи містить заданий рядок задане значення. За замовчуванням метод чутливий до регістру:

```php
use Illuminate\Support\Str;

$contains = Str::of('This is my name')->contains('my');

// true
```

Ви можете також передати масив значень, щоб перевірити, чи містить рядок будь-яке зі значень масиву:

```php
use Illuminate\Support\Str;

$contains = Str::of('This is my name')->contains(['my', 'foo']);

// true
```

Вимкнути чутливість до регістру можна, задавши аргументу `ignoreCase` значення `true`:

```php
use Illuminate\Support\Str;

$contains = Str::of('This is my name')->contains('MY', ignoreCase: true);

// true
```

<a name="method-fluent-str-contains-all"></a>
#### `containsAll` {.collection-method}

Метод `containsAll` визначає, чи містить заданий рядок усі значення із заданого масиву:

```php
use Illuminate\Support\Str;

$containsAll = Str::of('This is my name')->containsAll(['my', 'name']);

// true
```

Вимкнути чутливість до регістру можна, задавши аргументу `ignoreCase` значення `true`:

```php
use Illuminate\Support\Str;

$containsAll = Str::of('This is my name')->containsAll(['MY', 'NAME'], ignoreCase: true);

// true
```

<a name="method-fluent-str-decrypt"></a>
#### `decrypt` {.collection-method}

Метод `decrypt` [розшифровує](/docs/{{version}}/encryption) зашифрований рядок:

```php
use Illuminate\Support\Str;

$decrypted = $encrypted->decrypt();

// 'secret'
```

Обернений до `decrypt` - метод [encrypt](#method-fluent-str-encrypt).

<a name="method-fluent-str-deduplicate"></a>
#### `deduplicate` {.collection-method}

Метод `deduplicate` замінює послідовні входження символу в заданому рядку одним таким символом. За замовчуванням метод дедуплікує пробіли:

```php
use Illuminate\Support\Str;

$result = Str::of('The   Laravel   Framework')->deduplicate();

// The Laravel Framework
```

Ви можете вказати інший символ для дедуплікації, передавши його другим аргументом методу:

```php
use Illuminate\Support\Str;

$result = Str::of('The---Laravel---Framework')->deduplicate('-');

// The-Laravel-Framework
```

<a name="method-fluent-str-dirname"></a>
#### `dirname` {.collection-method}

Метод `dirname` повертає частину заданого рядка з батьківським каталогом:

```php
use Illuminate\Support\Str;

$string = Str::of('/foo/bar/baz')->dirname();

// '/foo/bar'
```

За потреби ви можете вказати, скільки рівнів каталогів обрізати з рядка:

```php
use Illuminate\Support\Str;

$string = Str::of('/foo/bar/baz')->dirname(2);

// '/foo'
```

<a name="method-fluent-str-doesnt-contain"></a>
#### `doesntContain()` {.collection-method}

Метод `doesntContain` визначає, чи не містить заданий рядок задане значення. Цей метод обернений до [contains](#method-fluent-str-contains). За замовчуванням він чутливий до регістру:

```php
use Illuminate\Support\Str;

$doesntContain = Str::of('This is name')->doesntContain('my');

// true
```

Ви можете також передати масив значень, щоб перевірити, чи не містить рядок жодного зі значень масиву:

```php
use Illuminate\Support\Str;

$doesntContain = Str::of('This is name')->doesntContain(['my', 'framework']);

// true
```

Вимкнути чутливість до регістру можна, задавши аргументу `ignoreCase` значення `true`:

```php
use Illuminate\Support\Str;

$doesntContain = Str::of('This is my name')->doesntContain('MY', ignoreCase: true);

// false
```

<a name="method-fluent-str-doesnt-end-with"></a>
#### `doesntEndWith` {.collection-method}

Метод `doesntEndWith` визначає, чи не закінчується заданий рядок заданим значенням:

```php
use Illuminate\Support\Str;

$result = Str::of('This is my name')->doesntEndWith('dog');

// true
```

Ви можете також передати масив значень, щоб перевірити, чи не закінчується рядок жодним зі значень масиву:

```php
use Illuminate\Support\Str;

$result = Str::of('This is my name')->doesntEndWith(['this', 'foo']);

// true

$result = Str::of('This is my name')->doesntEndWith(['name', 'foo']);

// false
```

<a name="method-fluent-str-doesnt-start-with"></a>
#### `doesntStartWith` {.collection-method}

Метод `doesntStartWith` визначає, чи не починається заданий рядок заданим значенням:

```php
use Illuminate\Support\Str;

$result = Str::of('This is my name')->doesntStartWith('That');

// true
```

Ви можете також передати масив значень, щоб перевірити, чи не починається рядок жодним зі значень масиву:

```php
use Illuminate\Support\Str;

$result = Str::of('This is my name')->doesntStartWith(['What', 'That', 'There']);

// true
```

<a name="method-fluent-str-encrypt"></a>
#### `encrypt` {.collection-method}

Метод `encrypt` [шифрує](/docs/{{version}}/encryption) рядок:

```php
use Illuminate\Support\Str;

$encrypted = Str::of('secret')->encrypt();
```

Обернений до `encrypt` - метод [decrypt](#method-fluent-str-decrypt).

<a name="method-fluent-str-ends-with"></a>
#### `endsWith` {.collection-method}

Метод `endsWith` визначає, чи закінчується заданий рядок заданим значенням:

```php
use Illuminate\Support\Str;

$result = Str::of('This is my name')->endsWith('name');

// true
```

Ви можете також передати масив значень, щоб перевірити, чи закінчується рядок будь-яким зі значень масиву:

```php
use Illuminate\Support\Str;

$result = Str::of('This is my name')->endsWith(['name', 'foo']);

// true

$result = Str::of('This is my name')->endsWith(['this', 'foo']);

// false
```

<a name="method-fluent-str-exactly"></a>
#### `exactly` {.collection-method}

Метод `exactly` визначає, чи точно збігається заданий рядок з іншим рядком:

```php
use Illuminate\Support\Str;

$result = Str::of('Laravel')->exactly('Laravel');

// true
```

<a name="method-fluent-str-excerpt"></a>
#### `excerpt` {.collection-method}

Метод `excerpt` дістає з рядка уривок навколо першого входження заданої фрази:

```php
use Illuminate\Support\Str;

$excerpt = Str::of('This is my name')->excerpt('my', [
    'radius' => 3
]);

// '...is my na...'
```

Опція `radius`, що за замовчуванням дорівнює `100`, задає кількість символів, які мають з'явитися з кожного боку обрізаного рядка.

Крім того, опцією `omission` можна змінити рядок, який буде додано на початку та в кінці обрізаного рядка:

```php
use Illuminate\Support\Str;

$excerpt = Str::of('This is my name')->excerpt('name', [
    'radius' => 3,
    'omission' => '(...) '
]);

// '(...) my name'
```

<a name="method-fluent-str-explode"></a>
#### `explode` {.collection-method}

Метод `explode` розбиває рядок за заданим розділювачем і повертає колекцію з кожною частиною розбитого рядка:

```php
use Illuminate\Support\Str;

$collection = Str::of('foo bar baz')->explode(' ');

// collect(['foo', 'bar', 'baz'])
```

<a name="method-fluent-str-finish"></a>
#### `finish` {.collection-method}

Метод `finish` додає до рядка одне входження заданого значення, якщо рядок ще не закінчується цим значенням:

```php
use Illuminate\Support\Str;

$adjusted = Str::of('this/string')->finish('/');

// this/string/

$adjusted = Str::of('this/string/')->finish('/');

// this/string/
```

<a name="method-fluent-str-from-base64"></a>
#### `fromBase64` {.collection-method}

Метод `fromBase64` декодує заданий рядок Base64:

```php
use Illuminate\Support\Str;

$decoded = Str::of('TGFyYXZlbA==')->fromBase64();

// Laravel
```

<a name="method-fluent-str-hash"></a>
#### `hash` {.collection-method}

Метод `hash` хешує рядок заданим [алгоритмом](https://www.php.net/manual/en/function.hash-algos.php):

```php
use Illuminate\Support\Str;

$hashed = Str::of('secret')->hash(algorithm: 'sha256');

// '2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b'
```

<a name="method-fluent-str-headline"></a>
#### `headline` {.collection-method}

Метод `headline` перетворює рядки, розділені регістром, дефісами чи підкресленнями, на рядок, розділений пробілами, де перша літера кожного слова велика:

```php
use Illuminate\Support\Str;

$headline = Str::of('taylor_otwell')->headline();

// Taylor Otwell

$headline = Str::of('EmailNotificationSent')->headline();

// Email Notification Sent
```

<a name="method-fluent-str-initials"></a>
#### `initials` {.collection-method}

Метод `initials` перетворює рядок на його ініціали:

```php
use Illuminate\Support\Str;

$initials = Str::of('Taylor Otwell')->initials()->upper();

// TO
```

<a name="method-fluent-str-inline-markdown"></a>
#### `inlineMarkdown` {.collection-method}

Метод `inlineMarkdown` перетворює Markdown у стилі GitHub на інлайновий HTML за допомогою [CommonMark](https://commonmark.thephpleague.com/). Проте, на відміну від методу `markdown`, він не обгортає весь згенерований HTML у блоковий елемент:

```php
use Illuminate\Support\Str;

$html = Str::of('**Laravel**')->inlineMarkdown();

// <strong>Laravel</strong>
```

#### Безпека Markdown

За замовчуванням Markdown підтримує сирий HTML, а це відкриває вразливість до міжсайтового скриптингу (XSS), якщо подавати туди сирий користувацький ввід. Як радить [документація з безпеки CommonMark](https://commonmark.thephpleague.com/security/), ви можете скористатися опцією `html_input`, щоб екранувати чи вирізати сирий HTML, і опцією `allow_unsafe_links`, щоб указати, чи дозволяти небезпечні посилання. Якщо вам потрібно дозволити частину сирого HTML, проженіть скомпільований Markdown через HTML Purifier:

```php
use Illuminate\Support\Str;

Str::of('Inject: <script>alert("Hello XSS!");</script>')->inlineMarkdown([
    'html_input' => 'strip',
    'allow_unsafe_links' => false,
]);

// Inject: alert(&quot;Hello XSS!&quot;);
```

<a name="method-fluent-str-is"></a>
#### `is` {.collection-method}

Метод `is` визначає, чи відповідає заданий рядок заданому шаблону. Зірочки можна використовувати як символи підстановки

```php
use Illuminate\Support\Str;

$matches = Str::of('foobar')->is('foo*');

// true

$matches = Str::of('foobar')->is('baz*');

// false
```

<a name="method-fluent-str-is-ascii"></a>
#### `isAscii` {.collection-method}

Метод `isAscii` визначає, чи є заданий рядок рядком ASCII:

```php
use Illuminate\Support\Str;

$result = Str::of('Taylor')->isAscii();

// true

$result = Str::of('ü')->isAscii();

// false
```

<a name="method-fluent-str-is-empty"></a>
#### `isEmpty` {.collection-method}

Метод `isEmpty` визначає, чи є заданий рядок порожнім:

```php
use Illuminate\Support\Str;

$result = Str::of('  ')->trim()->isEmpty();

// true

$result = Str::of('Laravel')->trim()->isEmpty();

// false
```

<a name="method-fluent-str-is-not-empty"></a>
#### `isNotEmpty` {.collection-method}

Метод `isNotEmpty` визначає, чи не є заданий рядок порожнім:

```php
use Illuminate\Support\Str;

$result = Str::of('  ')->trim()->isNotEmpty();

// false

$result = Str::of('Laravel')->trim()->isNotEmpty();

// true
```

<a name="method-fluent-str-is-json"></a>
#### `isJson` {.collection-method}

Метод `isJson` визначає, чи є заданий рядок коректним JSON:

```php
use Illuminate\Support\Str;

$result = Str::of('[1,2,3]')->isJson();

// true

$result = Str::of('{"first": "John", "last": "Doe"}')->isJson();

// true

$result = Str::of('{first: "John", last: "Doe"}')->isJson();

// false
```

<a name="method-fluent-str-is-ulid"></a>
#### `isUlid` {.collection-method}

Метод `isUlid` визначає, чи є заданий рядок ULID:

```php
use Illuminate\Support\Str;

$result = Str::of('01gd6r360bp37zj17nxb55yv40')->isUlid();

// true

$result = Str::of('Taylor')->isUlid();

// false
```

<a name="method-fluent-str-is-url"></a>
#### `isUrl` {.collection-method}

Метод `isUrl` визначає, чи є заданий рядок URL:

```php
use Illuminate\Support\Str;

$result = Str::of('http://example.com')->isUrl();

// true

$result = Str::of('Taylor')->isUrl();

// false
```

Метод `isUrl` вважає коректними чимало протоколів. Втім, ви можете вказати, які протоколи вважати коректними, передавши їх методу `isUrl`:

```php
$result = Str::of('http://example.com')->isUrl(['http', 'https']);
```

<a name="method-fluent-str-is-uuid"></a>
#### `isUuid` {.collection-method}

Метод `isUuid` визначає, чи є заданий рядок UUID:

```php
use Illuminate\Support\Str;

$result = Str::of('5ace9ab9-e9cf-4ec6-a19d-5881212a452c')->isUuid();

// true

$result = Str::of('Taylor')->isUuid();

// false
```

Ви можете також перевірити, чи відповідає заданий UUID специфікації певної версії (1, 3, 4, 5, 6, 7 або 8):

```php
use Illuminate\Support\Str;

$isUuid = Str::of('a0a2a2d2-0b87-4a18-83f2-2529882be2de')->isUuid(version: 4);

// true

$isUuid = Str::of('a0a2a2d2-0b87-4a18-83f2-2529882be2de')->isUuid(version: 1);

// false
```

<a name="method-fluent-str-kebab"></a>
#### `kebab` {.collection-method}

Метод `kebab` перетворює заданий рядок на `kebab-case`:

```php
use Illuminate\Support\Str;

$converted = Str::of('fooBar')->kebab();

// foo-bar
```

<a name="method-fluent-str-lcfirst"></a>
#### `lcfirst` {.collection-method}

Метод `lcfirst` повертає заданий рядок, у якому перший символ переведено в нижній регістр:

```php
use Illuminate\Support\Str;

$string = Str::of('Foo Bar')->lcfirst();

// foo Bar
```

<a name="method-fluent-str-length"></a>
#### `length` {.collection-method}

Метод `length` повертає довжину заданого рядка:

```php
use Illuminate\Support\Str;

$length = Str::of('Laravel')->length();

// 7
```

<a name="method-fluent-str-limit"></a>
#### `limit` {.collection-method}

Метод `limit` обрізає заданий рядок до вказаної довжини:

```php
use Illuminate\Support\Str;

$truncated = Str::of('The quick brown fox jumps over the lazy dog')->limit(20);

// The quick brown fox...
```

Другим аргументом можна змінити рядок, який буде дописано в кінці обрізаного рядка:

```php
$truncated = Str::of('The quick brown fox jumps over the lazy dog')->limit(20, ' (...)');

// The quick brown fox (...)
```

Якщо ви хочете зберегти цілі слова під час обрізання, скористайтеся аргументом `preserveWords`. Коли він `true`, рядок буде обрізано до найближчої межі цілого слова:

```php
$truncated = Str::of('The quick brown fox')->limit(12, preserveWords: true);

// The quick...
```

<a name="method-fluent-str-lower"></a>
#### `lower` {.collection-method}

Метод `lower` переводить заданий рядок у нижній регістр:

```php
use Illuminate\Support\Str;

$result = Str::of('LARAVEL')->lower();

// 'laravel'
```

<a name="method-fluent-str-markdown"></a>
#### `markdown` {.collection-method}

Метод `markdown` перетворює Markdown у стилі GitHub на HTML:

```php
use Illuminate\Support\Str;

$html = Str::of('# Laravel')->markdown();

// <h1>Laravel</h1>

$html = Str::of('# Taylor <b>Otwell</b>')->markdown([
    'html_input' => 'strip',
]);

// <h1>Taylor Otwell</h1>
```

#### Безпека Markdown

За замовчуванням Markdown підтримує сирий HTML, а це відкриває вразливість до міжсайтового скриптингу (XSS), якщо подавати туди сирий користувацький ввід. Як радить [документація з безпеки CommonMark](https://commonmark.thephpleague.com/security/), ви можете скористатися опцією `html_input`, щоб екранувати чи вирізати сирий HTML, і опцією `allow_unsafe_links`, щоб указати, чи дозволяти небезпечні посилання. Якщо вам потрібно дозволити частину сирого HTML, проженіть скомпільований Markdown через HTML Purifier:

```php
use Illuminate\Support\Str;

Str::of('Inject: <script>alert("Hello XSS!");</script>')->markdown([
    'html_input' => 'strip',
    'allow_unsafe_links' => false,
]);

// <p>Inject: alert(&quot;Hello XSS!&quot;);</p>
```

<a name="method-fluent-str-mask"></a>
#### `mask` {.collection-method}

Метод `mask` маскує частину рядка повторюваним символом; ним можна приховувати фрагменти рядків - наприклад, адреси електронної пошти чи номери телефонів:

```php
use Illuminate\Support\Str;

$string = Str::of('taylor@example.com')->mask('*', 3);

// tay***************
```

За потреби другим або третім аргументом методу `mask` можна передати від'ємні числа - тоді маскування почнеться на заданій відстані від кінця рядка:

```php
$string = Str::of('taylor@example.com')->mask('*', -15, 3);

// tay***@example.com

$string = Str::of('taylor@example.com')->mask('*', 4, -4);

// tayl**********.com
```

<a name="method-fluent-str-match"></a>
#### `match` {.collection-method}

Метод `match` повертає частину рядка, що відповідає заданому шаблону регулярного виразу:

```php
use Illuminate\Support\Str;

$result = Str::of('foo bar')->match('/bar/');

// 'bar'

$result = Str::of('foo bar')->match('/foo (.*)/');

// 'bar'
```

<a name="method-fluent-str-match-all"></a>
#### `matchAll` {.collection-method}

Метод `matchAll` повертає колекцію з частинами рядка, що відповідають заданому шаблону регулярного виразу:

```php
use Illuminate\Support\Str;

$result = Str::of('bar foo bar')->matchAll('/bar/');

// collect(['bar', 'bar'])
```

Якщо ви вкажете у виразі групу захоплення, Laravel повернає колекцію збігів першої такої групи:

```php
use Illuminate\Support\Str;

$result = Str::of('bar fun bar fly')->matchAll('/f(\w*)/');

// collect(['un', 'ly']);
```

Якщо збігів не знайдено, буде повернуто порожню колекцію.

<a name="method-fluent-str-is-match"></a>
#### `isMatch` {.collection-method}

Метод `isMatch` повертає `true`, якщо рядок відповідає заданому регулярному виразу:

```php
use Illuminate\Support\Str;

$result = Str::of('foo bar')->isMatch('/foo (.*)/');

// true

$result = Str::of('laravel')->isMatch('/foo (.*)/');

// false
```

<a name="method-fluent-str-new-line"></a>
#### `newLine` {.collection-method}

Метод `newLine` дописує до рядка символ «кінець рядка»:

```php
use Illuminate\Support\Str;

$padded = Str::of('Laravel')->newLine()->append('Framework');

// 'Laravel
//  Framework'
```

<a name="method-fluent-str-padboth"></a>
#### `padBoth` {.collection-method}

Метод `padBoth` обгортає PHP-функцію `str_pad` і доповнює рядок з обох боків іншим рядком, доки той не досягне потрібної довжини:

```php
use Illuminate\Support\Str;

$padded = Str::of('James')->padBoth(10, '_');

// '__James___'

$padded = Str::of('James')->padBoth(10);

// '  James   '
```

<a name="method-fluent-str-padleft"></a>
#### `padLeft` {.collection-method}

Метод `padLeft` обгортає PHP-функцію `str_pad` і доповнює рядок зліва іншим рядком, доки той не досягне потрібної довжини:

```php
use Illuminate\Support\Str;

$padded = Str::of('James')->padLeft(10, '-=');

// '-=-=-James'

$padded = Str::of('James')->padLeft(10);

// '     James'
```

<a name="method-fluent-str-padright"></a>
#### `padRight` {.collection-method}

Метод `padRight` обгортає PHP-функцію `str_pad` і доповнює рядок справа іншим рядком, доки той не досягне потрібної довжини:

```php
use Illuminate\Support\Str;

$padded = Str::of('James')->padRight(10, '-');

// 'James-----'

$padded = Str::of('James')->padRight(10);

// 'James     '
```

<a name="method-fluent-str-pipe"></a>
#### `pipe` {.collection-method}

Метод `pipe` дозволяє перетворити рядок, передавши його поточне значення заданому виклику:

```php
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

$hash = Str::of('Laravel')->pipe('md5')->prepend('Checksum: ');

// 'Checksum: a5c95b86291ea299fcbe64458ed12702'

$closure = Str::of('foo')->pipe(function (Stringable $str) {
    return 'bar';
});

// 'bar'
```

<a name="method-fluent-str-counted"></a>
#### `counted` {.collection-method}

Метод `counted` перетворює слово в однині на однину чи множину залежно від заданої кількості й додає перед результатом відформатовану кількість:

```php
use Illuminate\Support\Str;

$label = Str::of('order')->counted(1);

// 1 order

$label = Str::of('order')->counted(1000);

// 1,000 orders
```

<a name="method-fluent-str-plural"></a>
#### `plural` {.collection-method}

Метод `plural` перетворює слово в однині на форму множини. Ця функція підтримує [будь-яку з мов, які підтримує плюралізатор Laravel](/docs/{{version}}/localization#pluralization-language):

```php
use Illuminate\Support\Str;

$plural = Str::of('car')->plural();

// cars

$plural = Str::of('child')->plural();

// children
```

Функції можна передати аргумент - ціле число, щоб отримати форму однини або множини:

```php
use Illuminate\Support\Str;

$plural = Str::of('child')->plural(2);

// children

$plural = Str::of('child')->plural(1);

// child
```

Аргументом `prependCount` можна додати перед формою множини відформатоване значення `$count`:

```php
use Illuminate\Support\Str;

$label = Str::of('car')->plural(1000, prependCount: true);

// 1,000 cars
```

<a name="method-fluent-str-position"></a>
#### `position` {.collection-method}

Метод `position` повертає позицію першого входження підрядка в рядок. Якщо підрядка в рядку немає, повертається `false`:

```php
use Illuminate\Support\Str;

$position = Str::of('Hello, World!')->position('Hello');

// 0

$position = Str::of('Hello, World!')->position('W');

// 7
```

<a name="method-fluent-str-prepend"></a>
#### `prepend` {.collection-method}

Метод `prepend` додає задані значення на початок рядка:

```php
use Illuminate\Support\Str;

$string = Str::of('Framework')->prepend('Laravel ');

// Laravel Framework
```

<a name="method-fluent-str-remove"></a>
#### `remove` {.collection-method}

Метод `remove` видаляє з рядка задане значення або масив значень:

```php
use Illuminate\Support\Str;

$string = Str::of('Arkansas is quite beautiful!')->remove('quite ');

// Arkansas is beautiful!
```

Ви можете також передати другим параметром `false`, щоб метод не звертав уваги на регістр.

<a name="method-fluent-str-repeat"></a>
#### `repeat` {.collection-method}

Метод `repeat` повторює заданий рядок:

```php
use Illuminate\Support\Str;

$repeated = Str::of('a')->repeat(5);

// aaaaa
```

<a name="method-fluent-str-replace"></a>
#### `replace` {.collection-method}

Метод `replace` замінює в рядку заданий рядок:

```php
use Illuminate\Support\Str;

$replaced = Str::of('Laravel 6.x')->replace('6.x', '7.x');

// Laravel 7.x
```

Метод `replace` приймає також аргумент `caseSensitive`. За замовчуванням метод `replace` чутливий до регістру:

```php
$replaced = Str::of('macOS 13.x')->replace(
    'macOS', 'iOS', caseSensitive: false
);
```

<a name="method-fluent-str-replace-array"></a>
#### `replaceArray` {.collection-method}

Метод `replaceArray` послідовно замінює задане значення в рядку значеннями з масиву:

```php
use Illuminate\Support\Str;

$string = 'The event will take place between ? and ?';

$replaced = Str::of($string)->replaceArray('?', ['8:30', '9:00']);

// The event will take place between 8:30 and 9:00
```

<a name="method-fluent-str-replace-first"></a>
#### `replaceFirst` {.collection-method}

Метод `replaceFirst` замінює перше входження заданого значення в рядку:

```php
use Illuminate\Support\Str;

$replaced = Str::of('the quick brown fox jumps over the lazy dog')->replaceFirst('the', 'a');

// a quick brown fox jumps over the lazy dog
```

<a name="method-fluent-str-replace-last"></a>
#### `replaceLast` {.collection-method}

Метод `replaceLast` замінює останнє входження заданого значення в рядку:

```php
use Illuminate\Support\Str;

$replaced = Str::of('the quick brown fox jumps over the lazy dog')->replaceLast('the', 'a');

// the quick brown fox jumps over a lazy dog
```

<a name="method-fluent-str-replace-matches"></a>
#### `replaceMatches` {.collection-method}

Метод `replaceMatches` замінює всі частини рядка, що відповідають шаблону, заданим рядком заміни:

```php
use Illuminate\Support\Str;

$replaced = Str::of('(+1) 501-555-1000')->replaceMatches('/[^A-Za-z0-9]++/', '');

// '15015551000'
```

Метод `replaceMatches` приймає також замикання, яке буде викликано для кожної частини рядка, що відповідає заданому шаблону: у ньому ви виконуєте логіку заміни й повертаєте замінене значення:

```php
use Illuminate\Support\Str;

$replaced = Str::of('123')->replaceMatches('/\d/', function (array $matches) {
    return '['.$matches[0].']';
});

// '[1][2][3]'
```

<a name="method-fluent-str-replace-start"></a>
#### `replaceStart` {.collection-method}

Метод `replaceStart` замінює перше входження заданого значення лише тоді, коли воно стоїть на початку рядка:

```php
use Illuminate\Support\Str;

$replaced = Str::of('Hello World')->replaceStart('Hello', 'Laravel');

// Laravel World

$replaced = Str::of('Hello World')->replaceStart('World', 'Laravel');

// Hello World
```

<a name="method-fluent-str-replace-end"></a>
#### `replaceEnd` {.collection-method}

Метод `replaceEnd` замінює останнє входження заданого значення лише тоді, коли воно стоїть у кінці рядка:

```php
use Illuminate\Support\Str;

$replaced = Str::of('Hello World')->replaceEnd('World', 'Laravel');

// Hello Laravel

$replaced = Str::of('Hello World')->replaceEnd('Hello', 'Laravel');

// Hello World
```

<a name="method-fluent-str-scan"></a>
#### `scan` {.collection-method}

Метод `scan` розбирає вхідний рядок у колекцію за форматом, що його підтримує [PHP-функція `sscanf`](https://www.php.net/manual/en/function.sscanf.php):

```php
use Illuminate\Support\Str;

$collection = Str::of('filename.jpg')->scan('%[^.].%s');

// collect(['filename', 'jpg'])
```

<a name="method-fluent-str-singular"></a>
#### `singular` {.collection-method}

Метод `singular` перетворює рядок на форму однини. Ця функція підтримує [будь-яку з мов, які підтримує плюралізатор Laravel](/docs/{{version}}/localization#pluralization-language):

```php
use Illuminate\Support\Str;

$singular = Str::of('cars')->singular();

// car

$singular = Str::of('children')->singular();

// child
```

<a name="method-fluent-str-slug"></a>
#### `slug` {.collection-method}

Метод `slug` генерує із заданого рядка дружній до URL «slug»:

```php
use Illuminate\Support\Str;

$slug = Str::of('Laravel Framework')->slug('-');

// laravel-framework
```

<a name="method-fluent-str-snake"></a>
#### `snake` {.collection-method}

Метод `snake` перетворює заданий рядок на `snake_case`:

```php
use Illuminate\Support\Str;

$converted = Str::of('fooBar')->snake();

// foo_bar
```

<a name="method-fluent-str-split"></a>
#### `split` {.collection-method}

Метод `split` розбиває рядок у колекцію за регулярним виразом:

```php
use Illuminate\Support\Str;

$segments = Str::of('one, two, three')->split('/[\s,]+/');

// collect(["one", "two", "three"])
```

<a name="method-fluent-str-squish"></a>
#### `squish` {.collection-method}

Метод `squish` прибирає з рядка всі надлишкові пробіли, зокрема й між словами:

```php
use Illuminate\Support\Str;

$string = Str::of('    laravel    framework    ')->squish();

// laravel framework
```

<a name="method-fluent-str-start"></a>
#### `start` {.collection-method}

Метод `start` додає до рядка одне входження заданого значення, якщо рядок ще не починається цим значенням:

```php
use Illuminate\Support\Str;

$adjusted = Str::of('this/string')->start('/');

// /this/string

$adjusted = Str::of('/this/string')->start('/');

// /this/string
```

<a name="method-fluent-str-starts-with"></a>
#### `startsWith` {.collection-method}

Метод `startsWith` визначає, чи починається заданий рядок заданим значенням:

```php
use Illuminate\Support\Str;

$result = Str::of('This is my name')->startsWith('This');

// true
```

Ви можете також передати масив значень, щоб перевірити, чи починається рядок будь-яким зі значень масиву:

```php
use Illuminate\Support\Str;

$result = Str::of('This is my name')->startsWith(['This', 'That']);

// true
```

<a name="method-fluent-str-strip-tags"></a>
#### `stripTags` {.collection-method}

Метод `stripTags` прибирає з рядка всі теги HTML і PHP:

```php
use Illuminate\Support\Str;

$result = Str::of('<a href="https://laravel.com">Taylor <b>Otwell</b></a>')->stripTags();

// Taylor Otwell

$result = Str::of('<a href="https://laravel.com">Taylor <b>Otwell</b></a>')->stripTags('<b>');

// Taylor <b>Otwell</b>
```

<a name="method-fluent-str-studly"></a>
#### `studly` {.collection-method}

Метод `studly` перетворює заданий рядок на `StudlyCase`:

```php
use Illuminate\Support\Str;

$converted = Str::of('foo_bar')->studly();

// FooBar
```

<a name="method-fluent-str-substr"></a>
#### `substr` {.collection-method}

Метод `substr` повертає частину рядка, задану параметрами початку та довжини:

```php
use Illuminate\Support\Str;

$string = Str::of('Laravel Framework')->substr(8);

// Framework

$string = Str::of('Laravel Framework')->substr(8, 5);

// Frame
```

<a name="method-fluent-str-substrreplace"></a>
#### `substrReplace` {.collection-method}

Метод `substrReplace` замінює текст у частині рядка, починаючи з позиції, заданої другим аргументом, і замінюючи стільку символів, скільки задано третім аргументом. Якщо передати третім аргументом `0`, рядок буде вставлено у вказану позицію, не замінюючи жодного наявного символу:

```php
use Illuminate\Support\Str;

$string = Str::of('1300')->substrReplace(':', 2);

// 13:

$string = Str::of('The Framework')->substrReplace(' Laravel', 3, 0);

// The Laravel Framework
```

<a name="method-fluent-str-swap"></a>
#### `swap` {.collection-method}

Метод `swap` замінює в рядку кілька значень за допомогою PHP-функції `strtr`:

```php
use Illuminate\Support\Str;

$string = Str::of('Tacos are great!')
    ->swap([
        'Tacos' => 'Burritos',
        'great' => 'fantastic',
    ]);

// Burritos are fantastic!
```

<a name="method-fluent-str-take"></a>
#### `take` {.collection-method}

Метод `take` повертає вказану кількість символів із початку рядка:

```php
use Illuminate\Support\Str;

$taken = Str::of('Build something amazing!')->take(5);

// Build
```

<a name="method-fluent-str-tap"></a>
#### `tap` {.collection-method}

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

```php
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

$string = Str::of('Laravel')
    ->append(' Framework')
    ->tap(function (Stringable $string) {
        dump('String after append: '.$string);
    })
    ->upper();

// LARAVEL FRAMEWORK
```

<a name="method-fluent-str-test"></a>
#### `test` {.collection-method}

Метод `test` визначає, чи відповідає рядок заданому шаблону регулярного виразу:

```php
use Illuminate\Support\Str;

$result = Str::of('Laravel Framework')->test('/Laravel/');

// true
```

<a name="method-fluent-str-title"></a>
#### `title` {.collection-method}

Метод `title` перетворює заданий рядок на `Title Case`:

```php
use Illuminate\Support\Str;

$converted = Str::of('a nice title uses the correct case')->title();

// A Nice Title Uses The Correct Case
```

<a name="method-fluent-str-to-base64"></a>
#### `toBase64` {.collection-method}

Метод `toBase64` перетворює заданий рядок на Base64:

```php
use Illuminate\Support\Str;

$base64 = Str::of('Laravel')->toBase64();

// TGFyYXZlbA==
```

<a name="method-fluent-str-to-html-string"></a>
#### `toHtmlString` {.collection-method}

Метод `toHtmlString` перетворює заданий рядок на екземпляр `Illuminate\Support\HtmlString`, який не буде екрановано під час рендеру в Blade-шаблонах:

```php
use Illuminate\Support\Str;

$htmlString = Str::of('Nuno Maduro')->toHtmlString();
```

<a name="method-fluent-str-to-uri"></a>
#### `toUri` {.collection-method}

Метод `toUri` перетворює заданий рядок на екземпляр [Illuminate\Support\Uri](/docs/{{version}}/helpers#uri):

```php
use Illuminate\Support\Str;

$uri = Str::of('https://example.com')->toUri();
```

<a name="method-fluent-str-transliterate"></a>
#### `transliterate` {.collection-method}

Метод `transliterate` спробує перетворити заданий рядок на найближче представлення в ASCII:

```php
use Illuminate\Support\Str;

$email = Str::of('ⓣⓔⓢⓣ@ⓛⓐⓡⓐⓥⓔⓛ.ⓒⓞⓜ')->transliterate();

// 'test@laravel.com'
```

<a name="method-fluent-str-trim"></a>
#### `trim` {.collection-method}

Метод `trim` обрізає заданий рядок. На відміну від нативної PHP-функції `trim`, метод `trim` у Laravel прибирає ще й пробільні символи Unicode:

```php
use Illuminate\Support\Str;

$string = Str::of('  Laravel  ')->trim();

// 'Laravel'

$string = Str::of('/Laravel/')->trim('/');

// 'Laravel'
```

<a name="method-fluent-str-ltrim"></a>
#### `ltrim` {.collection-method}

Метод `ltrim` обрізає рядок зліва. На відміну від нативної PHP-функції `ltrim`, метод `ltrim` у Laravel прибирає ще й пробільні символи Unicode:

```php
use Illuminate\Support\Str;

$string = Str::of('  Laravel  ')->ltrim();

// 'Laravel  '

$string = Str::of('/Laravel/')->ltrim('/');

// 'Laravel/'
```

<a name="method-fluent-str-rtrim"></a>
#### `rtrim` {.collection-method}

Метод `rtrim` обрізає заданий рядок справа. На відміну від нативної PHP-функції `rtrim`, метод `rtrim` у Laravel прибирає ще й пробільні символи Unicode:

```php
use Illuminate\Support\Str;

$string = Str::of('  Laravel  ')->rtrim();

// '  Laravel'

$string = Str::of('/Laravel/')->rtrim('/');

// '/Laravel'
```

<a name="method-fluent-str-ucfirst"></a>
#### `ucfirst` {.collection-method}

Метод `ucfirst` повертає заданий рядок, у якому перший символ переведено у верхній регістр:

```php
use Illuminate\Support\Str;

$string = Str::of('foo bar')->ucfirst();

// Foo bar
```

<a name="method-fluent-str-ucsplit"></a>
#### `ucsplit` {.collection-method}

Метод `ucsplit` розбиває заданий рядок у колекцію за великими літерами:

```php
use Illuminate\Support\Str;

$string = Str::of('Foo Bar')->ucsplit();

// collect(['Foo ', 'Bar'])
```

<a name="method-fluent-str-ucwords"></a>
#### `ucwords` {.collection-method}

Метод `ucwords` переводить перший символ кожного слова в заданому рядку у верхній регістр:

```php
use Illuminate\Support\Str;

$string = Str::of('laravel framework')->ucwords();

// Laravel Framework
```

<a name="method-fluent-str-unwrap"></a>
#### `unwrap` {.collection-method}

Метод `unwrap` прибирає вказані рядки з початку та кінця заданого рядка:

```php
use Illuminate\Support\Str;

Str::of('-Laravel-')->unwrap('-');

// Laravel

Str::of('{framework: "Laravel"}')->unwrap('{', '}');

// framework: "Laravel"
```

<a name="method-fluent-str-upper"></a>
#### `upper` {.collection-method}

Метод `upper` переводить заданий рядок у верхній регістр:

```php
use Illuminate\Support\Str;

$adjusted = Str::of('laravel')->upper();

// LARAVEL
```

<a name="method-fluent-str-when"></a>
#### `when` {.collection-method}

Метод `when` викликає задане замикання, якщо задана умова - `true`. Замикання отримає екземпляр плавного рядка:

```php
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

$string = Str::of('Taylor')
    ->when(true, function (Stringable $string) {
        return $string->append(' Otwell');
    });

// 'Taylor Otwell'
```

За потреби третім параметром методу `when` можна передати ще одне замикання. Воно виконається, якщо параметр умови дасть `false`.

<a name="method-fluent-str-when-contains"></a>
#### `whenContains` {.collection-method}

Метод `whenContains` викликає задане замикання, якщо рядок містить задане значення. Замикання отримає екземпляр плавного рядка:

```php
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

$string = Str::of('tony stark')
    ->whenContains('tony', function (Stringable $string) {
        return $string->title();
    });

// 'Tony Stark'
```

За потреби третім параметром можна передати ще одне замикання. Воно буде викликане, якщо рядок не містить заданого значення.

Ви можете також передати масив значень, щоб перевірити, чи містить рядок будь-яке зі значень масиву:

```php
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

$string = Str::of('tony stark')
    ->whenContains(['tony', 'hulk'], function (Stringable $string) {
        return $string->title();
    });

// Tony Stark
```

<a name="method-fluent-str-when-contains-all"></a>
#### `whenContainsAll` {.collection-method}

Метод `whenContainsAll` викликає задане замикання, якщо рядок містить усі задані підрядки. Замикання отримає екземпляр плавного рядка:

```php
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

$string = Str::of('tony stark')
    ->whenContainsAll(['tony', 'stark'], function (Stringable $string) {
        return $string->title();
    });

// 'Tony Stark'
```

За потреби третім параметром можна передати ще одне замикання. Воно буде викликане, якщо параметр умови дасть `false`.

<a name="method-fluent-str-when-doesnt-end-with"></a>
#### `whenDoesntEndWith` {.collection-method}

Метод `whenDoesntEndWith` викликає задане замикання, якщо рядок не закінчується заданим підрядком. Замикання отримає екземпляр плавного рядка:

```php
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

$string = Str::of('disney world')->whenDoesntEndWith('land', function (Stringable $string) {
    return $string->title();
});

// 'Disney World'
```

<a name="method-fluent-str-when-doesnt-start-with"></a>
#### `whenDoesntStartWith` {.collection-method}

Метод `whenDoesntStartWith` викликає задане замикання, якщо рядок не починається заданим підрядком. Замикання отримає екземпляр плавного рядка:

```php
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

$string = Str::of('disney world')->whenDoesntStartWith('sea', function (Stringable $string) {
    return $string->title();
});

// 'Disney World'
```

<a name="method-fluent-str-when-empty"></a>
#### `whenEmpty` {.collection-method}

Метод `whenEmpty` викликає задане замикання, якщо рядок порожній. Якщо замикання повертає значення, метод `whenEmpty` теж повернає це значення. Якщо замикання не повертає значення, буде повернуто екземпляр плавного рядка:

```php
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

$string = Str::of('  ')->trim()->whenEmpty(function (Stringable $string) {
    return $string->prepend('Laravel');
});

// 'Laravel'
```

<a name="method-fluent-str-when-not-empty"></a>
#### `whenNotEmpty` {.collection-method}

Метод `whenNotEmpty` викликає задане замикання, якщо рядок не порожній. Якщо замикання повертає значення, метод `whenNotEmpty` теж повернає це значення. Якщо замикання не повертає значення, буде повернуто екземпляр плавного рядка:

```php
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

$string = Str::of('Framework')->whenNotEmpty(function (Stringable $string) {
    return $string->prepend('Laravel ');
});

// 'Laravel Framework'
```

<a name="method-fluent-str-when-starts-with"></a>
#### `whenStartsWith` {.collection-method}

Метод `whenStartsWith` викликає задане замикання, якщо рядок починається заданим підрядком. Замикання отримає екземпляр плавного рядка:

```php
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

$string = Str::of('disney world')->whenStartsWith('disney', function (Stringable $string) {
    return $string->title();
});

// 'Disney World'
```

<a name="method-fluent-str-when-ends-with"></a>
#### `whenEndsWith` {.collection-method}

Метод `whenEndsWith` викликає задане замикання, якщо рядок закінчується заданим підрядком. Замикання отримає екземпляр плавного рядка:

```php
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

$string = Str::of('disney world')->whenEndsWith('world', function (Stringable $string) {
    return $string->title();
});

// 'Disney World'
```

<a name="method-fluent-str-when-exactly"></a>
#### `whenExactly` {.collection-method}

Метод `whenExactly` викликає задане замикання, якщо рядок точно збігається із заданим рядком. Замикання отримає екземпляр плавного рядка:

```php
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

$string = Str::of('laravel')->whenExactly('laravel', function (Stringable $string) {
    return $string->title();
});

// 'Laravel'
```

<a name="method-fluent-str-when-not-exactly"></a>
#### `whenNotExactly` {.collection-method}

Метод `whenNotExactly` викликає задане замикання, якщо рядок не збігається точно із заданим рядком. Замикання отримає екземпляр плавного рядка:

```php
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

$string = Str::of('framework')->whenNotExactly('laravel', function (Stringable $string) {
    return $string->title();
});

// 'Framework'
```

<a name="method-fluent-str-when-is"></a>
#### `whenIs` {.collection-method}

Метод `whenIs` викликає задане замикання, якщо рядок відповідає заданому шаблону. Зірочки можна використовувати як символи підстановки. Замикання отримає екземпляр плавного рядка:

```php
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

$string = Str::of('foo/bar')->whenIs('foo/*', function (Stringable $string) {
    return $string->append('/baz');
});

// 'foo/bar/baz'
```

<a name="method-fluent-str-when-is-ascii"></a>
#### `whenIsAscii` {.collection-method}

Метод `whenIsAscii` викликає задане замикання, якщо рядок є 7-бітним ASCII. Замикання отримає екземпляр плавного рядка:

```php
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

$string = Str::of('laravel')->whenIsAscii(function (Stringable $string) {
    return $string->title();
});

// 'Laravel'
```

<a name="method-fluent-str-when-is-ulid"></a>
#### `whenIsUlid` {.collection-method}

Метод `whenIsUlid` викликає задане замикання, якщо рядок є коректним ULID. Замикання отримає екземпляр плавного рядка:

```php
use Illuminate\Support\Str;

$string = Str::of('01gd6r360bp37zj17nxb55yv40')->whenIsUlid(function (Stringable $string) {
    return $string->substr(0, 8);
});

// '01gd6r36'
```

<a name="method-fluent-str-when-is-uuid"></a>
#### `whenIsUuid` {.collection-method}

Метод `whenIsUuid` викликає задане замикання, якщо рядок є коректним UUID. Замикання отримає екземпляр плавного рядка:

```php
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

$string = Str::of('a0a2a2d2-0b87-4a18-83f2-2529882be2de')->whenIsUuid(function (Stringable $string) {
    return $string->substr(0, 8);
});

// 'a0a2a2d2'
```

<a name="method-fluent-str-when-test"></a>
#### `whenTest` {.collection-method}

Метод `whenTest` викликає задане замикання, якщо рядок відповідає заданому регулярному виразу. Замикання отримає екземпляр плавного рядка:

```php
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;

$string = Str::of('laravel framework')->whenTest('/laravel/', function (Stringable $string) {
    return $string->title();
});

// 'Laravel Framework'
```

<a name="method-fluent-str-word-count"></a>
#### `wordCount` {.collection-method}

Метод `wordCount` повертає кількість слів у рядку:

```php
use Illuminate\Support\Str;

Str::of('Hello, world!')->wordCount(); // 2
```

<a name="method-fluent-str-words"></a>
#### `words` {.collection-method}

Метод `words` обмежує кількість слів у рядку. За потреби ви можете вказати додатковий рядок, який буде дописано до обрізаного рядка:

```php
use Illuminate\Support\Str;

$string = Str::of('Perfectly balanced, as all things should be.')->words(3, ' >>>');

// Perfectly balanced, as >>>
```

<a name="method-fluent-str-wrap"></a>
#### `wrap` {.collection-method}

Метод `wrap` обгортає заданий рядок додатковим рядком або парою рядків:

```php
use Illuminate\Support\Str;

Str::of('Laravel')->wrap('"');

// "Laravel"

Str::of('is')->wrap(before: 'This ', after: ' Laravel!');

// This is Laravel!
```

---

Джерело: https://laravelukraine.com/docs/13.x/scheduling

# Планування завдань

- [Вступ](#introduction)
- [Опис розкладу](#defining-schedules)
    - [Планування команд Artisan](#scheduling-artisan-commands)
    - [Планування завдань у черзі](#scheduling-queued-jobs)
    - [Планування команд оболонки](#scheduling-shell-commands)
    - [Варіанти частоти розкладу](#schedule-frequency-options)
    - [Часові пояси](#timezones)
    - [Запобігання накладанню завдань](#preventing-task-overlaps)
    - [Виконання завдань на одному сервері](#running-tasks-on-one-server)
    - [Фонові завдання](#background-tasks)
    - [Режим обслуговування](#maintenance-mode)
    - [Призупинення запланованих завдань](#pausing-scheduled-tasks)
    - [Групи розкладу](#schedule-groups)
- [Запуск планувальника](#running-the-scheduler)
    - [Завдання частіше ніж раз на хвилину](#sub-minute-scheduled-tasks)
    - [Запуск планувальника локально](#running-the-scheduler-locally)
- [Вивід завдань](#task-output)
- [Хуки завдань](#task-hooks)
- [Події](#events)

<a name="introduction"></a>
## Вступ

Раніше ви, можливо, писали запис у конфігурації cron для кожного завдання, яке треба було запланувати на сервері. Проте це швидко стає болючим, адже ваш розклад завдань більше не під контролем версій, і вам доводиться заходити на сервер через SSH, щоб переглянути наявні записи cron чи додати нові.

Планувальник команд Laravel пропонує свіжий підхід до керування запланованими завданнями на вашому сервері. Планувальник дозволяє плавно й виразно описати розклад команд усередині самого застосунку Laravel. Коли ви користуєтеся планувальником, на сервері потрібен лише один запис cron. Розклад завдань зазвичай описують у файлі `routes/console.php` вашого застосунку.

<a name="defining-schedules"></a>
## Опис розкладу

Ви можете описати всі свої заплановані завдання у файлі `routes/console.php`. Для початку погляньмо на приклад. У ньому ми заплануємо виклик замикання щодня опівночі. Усередині замикання ми виконаємо запит до бази, щоб очистити таблицю:

```php
<?php

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->daily();
```

Окрім планування замикань, ви можете планувати й [викликаємі об'єкти](https://secure.php.net/manual/en/language.oop5.magic.php#object.invoke). Викликаємі об'єкти - це прості PHP-класи, які містять метод `__invoke`:

```php
Schedule::call(new DeleteRecentUsers)->daily();
```

Якщо ви волієте лишити файл `routes/console.php` лише для описів команд, скористайтеся методом `withSchedule` у файлі `bootstrap/app.php`, щоб описати заплановані завдання. Цей метод приймає замикання, яке отримує екземпляр планувальника:

```php
use Illuminate\Console\Scheduling\Schedule;

->withSchedule(function (Schedule $schedule) {
    $schedule->call(new DeleteRecentUsers)->daily();
})
```

Якщо ви хочете переглянути огляд своїх запланованих завдань і час їх наступного запуску, скористайтеся командою Artisan `schedule:list`:

```shell
php artisan schedule:list
```

<a name="scheduling-artisan-commands"></a>
### Планування команд Artisan

Окрім замикань, ви можете планувати [команди Artisan](/docs/{{version}}/artisan) і системні команди. Наприклад, метод `command` дозволяє запланувати команду Artisan за її назвою або класом.

Плануючи команди Artisan за назвою класу, ви можете передати масив додаткових аргументів командного рядка, які слід передати команді під час виклику:

```php
use App\Console\Commands\SendEmailsCommand;
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send Taylor --force')->daily();

Schedule::command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();
```

<a name="scheduling-artisan-closure-commands"></a>
#### Планування команд Artisan на замиканнях

Якщо ви хочете запланувати команду Artisan, описану замиканням, додайте методи планування ланцюжком після опису команди:

```php
Artisan::command('delete:recent-users', function () {
    DB::table('recent_users')->delete();
})->purpose('Delete recent users')->daily();
```

Якщо вам потрібно передати аргументи команді на замиканні, передайте їх методу `schedule`:

```php
Artisan::command('emails:send {user} {--force}', function ($user) {
    // ...
})->purpose('Send emails to the specified user')->schedule(['Taylor', '--force'])->daily();
```

<a name="scheduling-queued-jobs"></a>
### Планування завдань у черзі

Метод `job` дозволяє запланувати [завдання в черзі](/docs/{{version}}/queues). Цей метод дає зручний спосіб планувати завдання в черзі, не описуючи замикань через метод `call`:

```php
use App\Jobs\Heartbeat;
use Illuminate\Support\Facades\Schedule;

Schedule::job(new Heartbeat)->everyFiveMinutes();
```

Методу `job` можна передати необов'язкові другий і третій аргументи, які вказують ім'я черги та підключення черги, куди слід покласти завдання:

```php
use App\Jobs\Heartbeat;
use Illuminate\Support\Facades\Schedule;

// Dispatch the job to the "heartbeats" queue on the "sqs" connection...
Schedule::job(new Heartbeat, 'heartbeats', 'sqs')->everyFiveMinutes();
```

<a name="scheduling-shell-commands"></a>
### Планування команд оболонки

Метод `exec` дозволяє віддати команду операційній системі:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::exec('node /home/forge/script.js')->daily();
```

<a name="schedule-frequency-options"></a>
### Варіанти частоти розкладу

Ми вже бачили кілька прикладів того, як налаштувати виконання завдання із заданим інтервалом. Проте частот розкладу, які можна призначити завданню, значно більше:

<div class="overflow-auto">

| Метод                              | Опис                                                     |
| ---------------------------------- | -------------------------------------------------------- |
| `->cron('* * * * *');`             | Виконувати завдання за власним розкладом cron.           |
| `->everySecond();`                 | Виконувати завдання щосекунди.                           |
| `->everyTwoSeconds();`             | Виконувати завдання кожні дві секунди.                   |
| `->everyFiveSeconds();`            | Виконувати завдання кожні п'ять секунд.                  |
| `->everyTenSeconds();`             | Виконувати завдання кожні десять секунд.                 |
| `->everyFifteenSeconds();`         | Виконувати завдання кожні п'ятнадцять секунд.            |
| `->everyTwentySeconds();`          | Виконувати завдання кожні двадцять секунд.               |
| `->everyThirtySeconds();`          | Виконувати завдання кожні тридцять секунд.               |
| `->everyMinute();`                 | Виконувати завдання щохвилини.                           |
| `->everyTwoMinutes();`             | Виконувати завдання кожні дві хвилини.                   |
| `->everyThreeMinutes();`           | Виконувати завдання кожні три хвилини.                   |
| `->everyFourMinutes();`            | Виконувати завдання кожні чотири хвилини.                |
| `->everyFiveMinutes();`            | Виконувати завдання кожні п'ять хвилин.                  |
| `->everyTenMinutes();`             | Виконувати завдання кожні десять хвилин.                 |
| `->everyFifteenMinutes();`         | Виконувати завдання кожні п'ятнадцять хвилин.            |
| `->everyThirtyMinutes();`          | Виконувати завдання кожні тридцять хвилин.               |
| `->hourly();`                      | Виконувати завдання щогодини.                            |
| `->hourlyAt(17);`                  | Виконувати завдання щогодини на 17-й хвилині.            |
| `->everyOddHour($minutes = 0);`    | Виконувати завдання щонепарної години.                   |
| `->everyTwoHours($minutes = 0);`   | Виконувати завдання кожні дві години.                    |
| `->everyThreeHours($minutes = 0);` | Виконувати завдання кожні три години.                    |
| `->everyFourHours($minutes = 0);`  | Виконувати завдання кожні чотири години.                 |
| `->everySixHours($minutes = 0);`   | Виконувати завдання кожні шість годин.                   |
| `->daily();`                       | Виконувати завдання щодня опівночі.                      |
| `->dailyAt('13:00');`              | Виконувати завдання щодня о 13:00.                       |
| `->twiceDaily(1, 13);`             | Виконувати завдання щодня о 1:00 та 13:00.               |
| `->twiceDailyAt(1, 13, 15);`       | Виконувати завдання щодня о 1:15 та 13:15.               |
| `->daysOfMonth([1, 10, 20]);`      | Виконувати завдання в певні дні місяця.                  |
| `->weekly();`                      | Виконувати завдання щонеділі о 00:00.                    |
| `->weeklyOn(1, '8:00');`           | Виконувати завдання щотижня в понеділок о 8:00.          |
| `->monthly();`                     | Виконувати завдання першого числа щомісяця о 00:00.      |
| `->monthlyOn(4, '15:00');`         | Виконувати завдання щомісяця 4-го числа о 15:00.         |
| `->twiceMonthly(1, 16, '13:00');`  | Виконувати завдання щомісяця 1-го та 16-го о 13:00.      |
| `->lastDayOfMonth('15:00');`       | Виконувати завдання останнього дня місяця о 15:00.       |
| `->quarterly();`                   | Виконувати завдання першого дня кожного кварталу о 00:00. |
| `->quarterlyOn(4, '14:00');`       | Виконувати завдання щокварталу 4-го числа о 14:00.       |
| `->yearly();`                      | Виконувати завдання першого дня кожного року о 00:00.    |
| `->yearlyOn(6, 1, '17:00');`       | Виконувати завдання щороку 1 червня о 17:00.             |
| `->timezone('America/New_York');`  | Задати часовий пояс для завдання.                        |

</div>

Ці методи можна поєднувати з додатковими обмеженнями, щоб створювати ще тонше налаштовані розклади, які виконуються лише в певні дні тижня. Наприклад, ви можете запланувати команду на щотижневе виконання в понеділок:

```php
use Illuminate\Support\Facades\Schedule;

// Run once per week on Monday at 1 PM...
Schedule::call(function () {
    // ...
})->weekly()->mondays()->at('13:00');

// Run hourly from 8 AM to 5 PM on weekdays...
Schedule::command('foo')
    ->weekdays()
    ->hourly()
    ->timezone('America/Chicago')
    ->between('8:00', '17:00');
```

Список додаткових обмежень розкладу наведено нижче:

<div class="overflow-auto">

| Метод                                    | Опис                                                   |
| ---------------------------------------- | ------------------------------------------------------ |
| `->weekdays();`                          | Обмежити завдання буднями.                             |
| `->weekends();`                          | Обмежити завдання вихідними.                           |
| `->sundays();`                           | Обмежити завдання неділею.                             |
| `->mondays();`                           | Обмежити завдання понеділком.                          |
| `->tuesdays();`                          | Обмежити завдання вівторком.                           |
| `->wednesdays();`                        | Обмежити завдання середою.                             |
| `->thursdays();`                         | Обмежити завдання четвергом.                           |
| `->fridays();`                           | Обмежити завдання п'ятницею.                           |
| `->saturdays();`                         | Обмежити завдання суботою.                             |
| `->days(array\|mixed);`                  | Обмежити завдання певними днями.                       |
| `->between($startTime, $endTime);`       | Виконувати завдання лише між заданими часами.          |
| `->unlessBetween($startTime, $endTime);` | Не виконувати завдання між заданими часами.            |
| `->when(Closure);`                       | Обмежити завдання за результатом перевірки.            |
| `->environments($env);`                  | Обмежити завдання певними середовищами.                |

</div>

<a name="day-constraints"></a>
#### Обмеження за днями

Метод `days` дозволяє обмежити виконання завдання певними днями тижня. Наприклад, ви можете запланувати щогодинне виконання команди в неділю та середу:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')
    ->hourly()
    ->days([0, 3]);
```

Як варіант, описуючи дні виконання завдання, ви можете скористатися константами класу `Illuminate\Console\Scheduling\Schedule`:

```php
use Illuminate\Support\Facades;
use Illuminate\Console\Scheduling\Schedule;

Facades\Schedule::command('emails:send')
    ->hourly()
    ->days([Schedule::SUNDAY, Schedule::WEDNESDAY]);
```

<a name="between-time-constraints"></a>
#### Обмеження за часом

Метод `between` дозволяє обмежити виконання завдання за часом доби:

```php
Schedule::command('emails:send')
    ->hourly()
    ->between('7:00', '22:00');
```

Так само метод `unlessBetween` дозволяє виключити виконання завдання на певний проміжок часу:

```php
Schedule::command('emails:send')
    ->hourly()
    ->unlessBetween('23:00', '4:00');
```

<a name="truth-test-constraints"></a>
#### Обмеження за перевіркою

Метод `when` дозволяє обмежити виконання завдання результатом заданої перевірки. Іншими словами, якщо задане замикання повертає `true`, завдання виконається - за умови, що жодна інша обмежувальна умова цьому не завадить:

```php
Schedule::command('emails:send')->daily()->when(function () {
    return true;
});
```

Метод `skip` можна вважати протилежністю `when`. Якщо метод `skip` поверне `true`, заплановане завдання не виконається:

```php
Schedule::command('emails:send')->daily()->skip(function () {
    return true;
});
```

Коли методи `when` йдуть ланцюжком, запланована команда виконається, лише якщо всі умови `when` повернуть `true`.

<a name="environment-constraints"></a>
#### Обмеження за середовищем

Метод `environments` дозволяє виконувати завдання лише в заданих середовищах (визначених [змінною середовища](/docs/{{version}}/configuration#environment-configuration) `APP_ENV`):

```php
Schedule::command('emails:send')
    ->daily()
    ->environments(['staging', 'production']);
```

<a name="timezones"></a>
### Часові пояси

Метод `timezone` дозволяє вказати, що час запланованого завдання слід тлумачити в заданому часовому поясі:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('report:generate')
    ->timezone('America/New_York')
    ->at('2:00')
```

Якщо ви раз у раз призначаєте той самий часовий пояс усім своїм запланованим завданням, ви можете вказати пояс для всіх розкладів, описавши опцію `schedule_timezone` у файлі конфігурації `app` вашого застосунку:

```php
'timezone' => 'UTC',

'schedule_timezone' => 'America/Chicago',
```

> [!WARNING]
> Пам'ятайте, що деякі часові пояси використовують перехід на літній час. Коли такий перехід стається, ваше заплановане завдання може виконатися двічі або взагалі не виконатися. Тому ми радимо уникати планування з часовими поясами, коли це можливо.

<a name="preventing-task-overlaps"></a>
### Запобігання накладанню завдань

За замовчуванням заплановані завдання виконуються, навіть якщо попередній екземпляр завдання ще працює. Щоб цьому запобігти, скористайтеся методом `withoutOverlapping`:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')->withoutOverlapping();
```

У цьому прикладі [команда Artisan](/docs/{{version}}/artisan) `emails:send` виконуватиметься щохвилини, якщо вона ще не працює. Метод `withoutOverlapping` особливо корисний, коли ваші завдання суттєво різняться за часом виконання й ви не можете точно передбачити, скільки триватиме конкретне завдання.

За потреби ви можете вказати, скільки хвилин має минути, перш ніж спливе блокування «без накладання». За замовчуванням блокування спливає за 24 години:

```php
Schedule::command('emails:send')->withoutOverlapping(10);
```

Під капотом метод `withoutOverlapping` використовує [кеш](/docs/{{version}}/cache) вашого застосунку, щоб отримати блокування. За потреби ви можете очистити ці блокування командою Artisan `schedule:clear-cache`. Зазвичай це потрібно лише тоді, коли завдання зависло через несподівану проблему на сервері.

<a name="running-tasks-on-one-server"></a>
### Виконання завдань на одному сервері

> [!WARNING]
> Щоб скористатися цією можливістю, ваш застосунок має використовувати драйвер кешу `database`, `memcached`, `dynamodb` або `redis` як драйвер за замовчуванням. Крім того, усі сервери мають спілкуватися з одним центральним сервером кешу.

Якщо планувальник вашого застосунку працює на кількох серверах, ви можете обмежити заплановане завдання виконанням лише на одному сервері. Наприклад, припустімо, у вас є заплановане завдання, яке щоп'ятниці ввечері генерує новий звіт. Якщо планувальник працює на трьох робочих серверах, заплановане завдання виконається на всіх трьох і згенерує звіт тричі. Недобре!

Щоб вказати, що завдання має виконуватися лише на одному сервері, скористайтеся методом `onOneServer` під час опису запланованого завдання. Перший сервер, який отримає завдання, встановить атомарне блокування, щоб інші сервери не виконували те саме завдання одночасно:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('report:generate')
    ->fridays()
    ->at('17:00')
    ->onOneServer();
```

Щоб застосувати `onOneServer` до всіх запланованих завдань, ви можете викликати метод `alwaysOnOneServer` у методі `boot` вашого `AppServiceProvider`:

```php
use Illuminate\Support\Facades\Schedule;

public function boot(): void
{
    Schedule::alwaysOnOneServer();
}
```

Заплановані замикання без [назви](#naming-unique-jobs) виключаються і продовжуватимуть виконуватися на кожному сервері.

Метод `useCache` дозволяє змінити сховище кешу, яке планувальник використовує для отримання атомарних блокувань, потрібних для завдань на одному сервері:

```php
Schedule::useCache('database');
```

<a name="naming-unique-jobs"></a>
#### Іменування завдань для одного сервера

Інколи вам може знадобитися планувати те саме завдання з різними параметрами, водночас вказуючи Laravel виконувати кожну його варіацію на одному сервері. Для цього призначте кожному опису розкладу унікальне ім'я методом `name`:

```php
Schedule::job(new CheckUptime('https://laravel.com'))
    ->name('check_uptime:laravel.com')
    ->everyFiveMinutes()
    ->onOneServer();

Schedule::job(new CheckUptime('https://vapor.laravel.com'))
    ->name('check_uptime:vapor.laravel.com')
    ->everyFiveMinutes()
    ->onOneServer();
```

Так само запланованим замиканням треба призначити ім'я, якщо їх має виконувати один сервер:

```php
Schedule::call(fn () => User::resetApiRequestCount())
    ->name('reset-api-request-count')
    ->daily()
    ->onOneServer();
```

<a name="background-tasks"></a>
### Фонові завдання

За замовчуванням кілька завдань, запланованих на той самий час, виконуються послідовно в порядку їх опису в методі `schedule`. Якщо у вас є довгограючі завдання, наступні завдання можуть стартувати значно пізніше, ніж очікувалося. Якщо ви хочете виконувати завдання у фоні, щоб вони працювали одночасно, скористайтеся методом `runInBackground`:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('analytics:report')
    ->daily()
    ->runInBackground();
```

> [!WARNING]
> Метод `runInBackground` можна використовувати лише під час планування завдань методами `command` та `exec`.

<a name="maintenance-mode"></a>
### Режим обслуговування

Заплановані завдання вашого застосунку не виконуватимуться, коли застосунок у [режимі обслуговування](/docs/{{version}}/configuration#maintenance-mode), адже ми не хочемо, щоб ваші завдання заважали незавершеному обслуговуванню сервера. Проте, якщо ви хочете примусово виконувати завдання навіть у режимі обслуговування, викличте метод `evenInMaintenanceMode` під час опису завдання:

```php
Schedule::command('emails:send')->evenInMaintenanceMode();
```

<a name="pausing-scheduled-tasks"></a>
### Призупинення запланованих завдань

Ви можете тимчасово призупинити обробку запланованих завдань, не змінюючи розгорнутий код, командою Artisan `schedule:pause`:

```shell
php artisan schedule:pause
```

Доки планувальник призупинено, жодне заплановане завдання не виконуватиметься. Поновити обробку запланованих завдань можна командою `schedule:continue`:

```shell
php artisan schedule:continue
```

Якщо завдання має виконуватися навіть тоді, коли планувальник призупинено, позначте його методом `evenWhenPaused`:

```php
Schedule::command('emails:send')->evenWhenPaused();
```

<a name="schedule-groups"></a>
### Групи розкладу

Описуючи кілька запланованих завдань зі схожими налаштуваннями, ви можете скористатися групуванням завдань у Laravel, щоб не повторювати ті самі налаштування для кожного завдання. Групування спрощує ваш код і забезпечує узгодженість між пов'язаними завданнями.

Щоб створити групу запланованих завдань, викличте потрібні методи налаштування, а за ними - метод `group`. Метод `group` приймає замикання, яке відповідає за опис завдань зі спільною конфігурацією:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::daily()
    ->onOneServer()
    ->timezone('America/New_York')
    ->group(function () {
        Schedule::command('emails:send --force');
        Schedule::command('emails:prune');
    });
```

<a name="running-the-scheduler"></a>
## Запуск планувальника

Тепер, коли ми навчилися описувати заплановані завдання, поговорімо про те, як власне запускати їх на сервері. Команда Artisan `schedule:run` перевірить усі ваші заплановані завдання й визначить, чи потрібно їх виконати, спираючись на поточний час сервера.

Тож, користуючись планувальником Laravel, нам треба додати на сервер лише один запис конфігурації cron, який щохвилини виконує команду `schedule:run`. Якщо ви не знаєте, як додавати записи cron на сервер, розгляньте керовану платформу на кшталт [Laravel Cloud](https://cloud.laravel.com), яка може керувати виконанням запланованих завдань за вас:

```shell
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
```

<a name="sub-minute-scheduled-tasks"></a>
### Завдання частіше ніж раз на хвилину

У більшості операційних систем завдання cron можуть виконуватися щонайбільше раз на хвилину. Проте планувальник Laravel дозволяє планувати завдання з частішими інтервалами - навіть щосекунди:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::call(function () {
    DB::table('recent_users')->delete();
})->everySecond();
```

Коли у вашому застосунку описано завдання частіше ніж раз на хвилину, команда `schedule:run` працюватиме до кінця поточної хвилини, а не завершиться одразу. Це дозволяє команді викликати всі потрібні завдання протягом хвилини.

Оскільки такі завдання, якщо вони виконуються довше за очікуване, можуть затримати виконання наступних, радимо, щоб усі вони диспетчеризували завдання в черзі чи фонові команди, які й робитимуть справжню роботу:

```php
use App\Jobs\DeleteRecentUsers;

Schedule::job(new DeleteRecentUsers)->everyTenSeconds();

Schedule::command('users:delete')->everyTenSeconds()->runInBackground();
```

<a name="interrupting-sub-minute-tasks"></a>
#### Переривання завдань частіше ніж раз на хвилину

Оскільки за наявності таких завдань команда `schedule:run` працює всю хвилину від виклику, вам інколи може знадобитися перервати її під час розгортання застосунку. Інакше вже запущений екземпляр `schedule:run` до кінця поточної хвилини й далі використовуватиме раніше розгорнутий код.

Щоб перервати виконання `schedule:run`, додайте команду `schedule:interrupt` до скрипта розгортання вашого застосунку. Цю команду слід викликати після завершення розгортання:

```shell
php artisan schedule:interrupt
```

<a name="running-the-scheduler-locally"></a>
### Запуск планувальника локально

Зазвичай ви не додаватимете запис cron планувальника на локальну машину розробки. Натомість скористайтеся командою Artisan `schedule:work`. Ця команда працюватиме на передньому плані й викликатиме планувальник щохвилини, доки ви її не завершите. Коли описано завдання частіше ніж раз на хвилину, планувальник і далі працюватиме в межах кожної хвилини, щоб їх обробити:

```shell
php artisan schedule:work
```

<a name="task-output"></a>
## Вивід завдань

Планувальник Laravel надає кілька зручних методів для роботи з виводом, який генерують заплановані завдання. По-перше, методом `sendOutputTo` ви можете надіслати вивід до файлу для подальшого огляду:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')
    ->daily()
    ->sendOutputTo($filePath);
```

Якщо ви хочете дописувати вивід до заданого файлу, скористайтеся методом `appendOutputTo`:

```php
Schedule::command('emails:send')
    ->daily()
    ->appendOutputTo($filePath);
```

Методом `emailOutputTo` ви можете надіслати вивід на обрану вами електронну адресу. Перш ніж надсилати вивід завдання поштою, налаштуйте [поштові сервіси](/docs/{{version}}/mail) Laravel:

```php
Schedule::command('report:generate')
    ->daily()
    ->sendOutputTo($filePath)
    ->emailOutputTo('taylor@example.com');
```

Якщо ви хочете надсилати вивід поштою, лише коли запланована команда Artisan чи системна команда завершується з ненульовим кодом виходу, скористайтеся методом `emailOutputOnFailure`:

```php
Schedule::command('report:generate')
    ->daily()
    ->emailOutputOnFailure('taylor@example.com');
```

> [!WARNING]
> Методи `emailOutputTo`, `emailOutputOnFailure`, `sendOutputTo` та `appendOutputTo` доступні лише для методів `command` та `exec`.

<a name="task-hooks"></a>
## Хуки завдань

Методами `before` та `after` ви можете вказати код, який слід виконати до й після виконання запланованого завдання:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('emails:send')
    ->daily()
    ->before(function () {
        // The task is about to execute...
    })
    ->after(function () {
        // The task has executed...
    });
```

Методи `onSuccess` та `onFailure` дозволяють вказати код, який слід виконати, якщо заплановане завдання завершилося успішно чи невдало. Невдача означає, що запланована команда Artisan чи системна команда завершилася з ненульовим кодом виходу:

```php
Schedule::command('emails:send')
    ->daily()
    ->onSuccess(function () {
        // The task succeeded...
    })
    ->onFailure(function () {
        // The task failed...
    });
```

Якщо ваша команда дає вивід, ви можете звернутися до нього в хуках `after`, `onSuccess` чи `onFailure`, вказавши тип `Illuminate\Support\Stringable` для аргументу `$output` в описі замикання хука:

```php
use Illuminate\Support\Stringable;

Schedule::command('emails:send')
    ->daily()
    ->onSuccess(function (Stringable $output) {
        // The task succeeded...
    })
    ->onFailure(function (Stringable $output) {
        // The task failed...
    });
```

<a name="pinging-urls"></a>
#### Пінгування URL

Методами `pingBefore` та `thenPing` планувальник може автоматично пінгувати заданий URL до чи після виконання завдання. Це стане в пригоді, щоб повідомити зовнішній сервіс - наприклад, [Envoyer](https://envoyer.io) - що ваше заплановане завдання починається чи завершилося:

```php
Schedule::command('emails:send')
    ->daily()
    ->pingBefore($url)
    ->thenPing($url);
```

Методи `pingOnSuccess` та `pingOnFailure` дозволяють пінгувати заданий URL, лише якщо завдання завершилося успішно чи невдало. Невдача означає, що запланована команда Artisan чи системна команда завершилася з ненульовим кодом виходу:

```php
Schedule::command('emails:send')
    ->daily()
    ->pingOnSuccess($successUrl)
    ->pingOnFailure($failureUrl);
```

Методи `pingBeforeIf`,`thenPingIf`,`pingOnSuccessIf` та `pingOnFailureIf` дозволяють пінгувати заданий URL, лише якщо задана умова є `true`:

```php
Schedule::command('emails:send')
    ->daily()
    ->pingBeforeIf($condition, $url)
    ->thenPingIf($condition, $url);

Schedule::command('emails:send')
    ->daily()
    ->pingOnSuccessIf($condition, $successUrl)
    ->pingOnFailureIf($condition, $failureUrl);
```

<a name="events"></a>
## Події

Під час планування Laravel диспетчеризує різні [події](/docs/{{version}}/events). Ви можете [описати слухачів](/docs/{{version}}/events) для будь-якої з них:

<div class="overflow-auto">

| Ім'я події                                                  |
| ----------------------------------------------------------- |
| `Illuminate\Console\Events\ScheduledTaskStarting`           |
| `Illuminate\Console\Events\ScheduledTaskFinished`           |
| `Illuminate\Console\Events\ScheduledBackgroundTaskFinished` |
| `Illuminate\Console\Events\ScheduledTaskSkipped`            |
| `Illuminate\Console\Events\ScheduledTaskFailed`             |

</div>

---

Джерело: https://laravelukraine.com/docs/13.x/authentication

# Автентифікація

- [Вступ](#introduction)
    - [Стартові набори](#starter-kits)
    - [Що врахувати щодо бази даних](#introduction-database-considerations)
    - [Огляд екосистеми](#ecosystem-overview)
- [Швидкий старт автентифікації](#authentication-quickstart)
    - [Встановлення стартового набору](#install-a-starter-kit)
    - [Отримання автентифікованого користувача](#retrieving-the-authenticated-user)
    - [Захист маршрутів](#protecting-routes)
    - [Обмеження спроб входу](#login-throttling)
- [Ручна автентифікація користувачів](#authenticating-users)
    - [Запам'ятовування користувачів](#remembering-users)
    - [Інші методи автентифікації](#other-authentication-methods)
- [HTTP Basic Authentication](#http-basic-authentication)
    - [HTTP Basic Authentication без збереження стану](#stateless-http-basic-authentication)
- [Вихід із системи](#logging-out)
    - [Скасування сесій на інших пристроях](#invalidating-sessions-on-other-devices)
- [Підтвердження пароля](#password-confirmation)
    - [Конфігурація](#password-confirmation-configuration)
    - [Маршрутизація](#password-confirmation-routing)
    - [Захист маршрутів](#password-confirmation-protecting-routes)
- [Додавання власних гардів](#adding-custom-guards)
    - [Гарди на замиканнях запиту](#closure-request-guards)
- [Додавання власних провайдерів користувачів](#adding-custom-user-providers)
    - [Контракт User Provider](#the-user-provider-contract)
    - [Контракт Authenticatable](#the-authenticatable-contract)
- [Автоматичне перехешування паролів](#automatic-password-rehashing)
- [Соціальна автентифікація](/docs/{{version}}/socialite)
- [Події](#events)

<a name="introduction"></a>
## Вступ

Багато вебзастосунків дають користувачам змогу автентифікуватися й «увійти». Реалізація цієї можливості у вебзастосунках може бути складною й потенційно ризикованою справою. Тому Laravel прагне дати вам інструменти, які дозволять реалізувати автентифікацію швидко, безпечно й легко.

За своєю суттю засоби автентифікації Laravel складаються з «гардів» (guards) і «провайдерів». Гарди визначають, як користувачі автентифікуються на кожному запиті. Наприклад, Laravel постачається з гардом `session`, який зберігає стан у сховищі сесій і в cookie.

Провайдери визначають, як користувачі дістаються з вашого постійного сховища. Laravel підтримує отримання користувачів через [Eloquent](/docs/{{version}}/eloquent) і конструктор запитів до бази даних. Проте ви вільні визначати додаткові провайдери відповідно до потреб вашого застосунку.

Конфігураційний файл автентифікації вашого застосунку розташований у `config/auth.php`. Цей файл містить кілька добре задокументованих опцій для налаштування поведінки сервісів автентифікації Laravel.

> [!NOTE]
> Не плутайте гарди й провайдери з «ролями» та «дозволами». Щоб дізнатися більше про авторизацію дій користувача через дозволи, зверніться до документації з [авторизації](/docs/{{version}}/authorization).

<a name="starter-kits"></a>
### Стартові набори

Хочете швидко почати? Встановіть [стартовий набір застосунку Laravel](/docs/{{version}}/starter-kits) у свіжий застосунок Laravel. Після міграції бази даних відкрийте у браузері `/register` чи будь-який інший URL вашого застосунку. Стартові набори створять усю вашу систему автентифікації!

**Навіть якщо ви вирішите не використовувати стартовий набір у своєму фінальному застосунку Laravel, встановлення [стартового набору](/docs/{{version}}/starter-kits) може стати чудовою нагодою навчитися реалізовувати всю функціональність автентифікації Laravel у справжньому проєкті.** Оскільки стартові набори Laravel уже містять контролери, маршрути та представлення автентифікації, ви можете вивчити код цих файлів і зрозуміти, як реалізуються можливості автентифікації в Laravel.

<a name="introduction-database-considerations"></a>
### Що врахувати щодо бази даних

За замовчуванням Laravel містить [модель Eloquent](/docs/{{version}}/eloquent) `App\Models\User` у вашому каталозі `app/Models`. Цю модель можна використовувати зі стандартним драйвером автентифікації Eloquent.

Якщо ваш застосунок не використовує Eloquent, ви можете скористатися провайдером автентифікації `database`, який працює через конструктор запитів Laravel. Якщо ваш застосунок використовує MongoDB, погляньте на офіційну [документацію MongoDB щодо автентифікації користувачів у Laravel](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/user-authentication/).

Будуючи схему бази даних для моделі `App\Models\User`, переконайтеся, що довжина стовпця пароля - щонайменше 60 символів. Звісно, міграція таблиці `users`, що входить до нових застосунків Laravel, уже створює стовпець із більшою довжиною.

Крім того, переконайтеся, що ваша таблиця `users` (або її аналог) містить nullable-стовпець `remember_token` типу string на 100 символів. У цьому стовпці зберігатиметься токен для користувачів, які обрали опцію «запам'ятати мене» під час входу у ваш застосунок. І знову ж таки: стандартна міграція таблиці `users` у нових застосунках Laravel уже містить цей стовпець.

<a name="ecosystem-overview"></a>
### Огляд екосистеми

Laravel пропонує кілька пакетів, пов'язаних з автентифікацією. Перш ніж рухатися далі, розгляньмо загальну екосистему автентифікації в Laravel і обговорімо призначення кожного пакета.

Спершу подумаймо, як працює автентифікація. Користуючись веббраузером, користувач вводить своє ім'я та пароль у формі входу. Якщо ці облікові дані правильні, застосунок збереже інформацію про автентифікованого користувача в [сесії](/docs/{{version}}/session) користувача. Виданий браузеру cookie містить ID сесії, тож наступні запити до застосунку можуть пов'язати користувача з правильною сесією. Отримавши сесійний cookie, застосунок дістане дані сесії за її ID, побачить, що інформацію про автентифікацію збережено в сесії, і вважатиме користувача «автентифікованим».

Коли віддаленому сервісу потрібно автентифікуватися для доступу до API, cookie зазвичай не використовуються, бо немає веббраузера. Натомість віддалений сервіс надсилає до API токен на кожному запиті. Застосунок може перевірити вхідний токен за таблицею дійсних API-токенів і «автентифікувати» запит як виконаний користувачем, пов'язаним із цим токеном.

<a name="laravels-built-in-browser-authentication-services"></a>
#### Вбудовані сервіси браузерної автентифікації Laravel

Laravel містить вбудовані сервіси автентифікації та сесій, до яких зазвичай звертаються через фасади `Auth` і `Session`. Ці можливості забезпечують автентифікацію на основі cookie для запитів, ініційованих із веббраузерів. Вони надають методи, які дозволяють перевірити облікові дані користувача й автентифікувати його. Крім того, ці сервіси автоматично збережуть потрібні дані автентифікації в сесії користувача й видадуть йому сесійний cookie. Про використання цих сервісів ідеться в цій документації.

**Стартові набори застосунку**

Як розповідається в цій документації, ви можете взаємодіяти з цими сервісами автентифікації вручну, щоб побудувати власний шар автентифікації. Проте, щоб допомогти вам почати швидше, ми випустили [безкоштовні стартові набори](/docs/{{version}}/starter-kits), які дають надійний сучасний каркас усього шару автентифікації.

<a name="laravels-api-authentication-services"></a>
#### Сервіси API-автентифікації Laravel

Laravel пропонує два необов'язкові пакети, які допоможуть вам керувати API-токенами й автентифікувати запити з ними: [Passport](/docs/{{version}}/passport) та [Sanctum](/docs/{{version}}/sanctum). Зауважте, що ці бібліотеки й вбудовані бібліотеки автентифікації Laravel на основі cookie не є взаємовиключними. Ці бібліотеки зосереджені передусім на автентифікації через API-токени, тоді як вбудовані сервіси - на браузерній автентифікації через cookie. Багато застосунків використовуватимуть і вбудовані сервіси автентифікації Laravel на основі cookie, і один із пакетів API-автентифікації.

**Passport**

Passport - це провайдер автентифікації OAuth2, який пропонує різні «типи надання» (grant types) OAuth2 і дозволяє видавати різні типи токенів. Загалом це надійний і складний пакет для API-автентифікації. Проте більшості застосунків не потрібні складні можливості специфікації OAuth2, які можуть заплутати і користувачів, і розробників. До того ж розробники історично плуталися в тому, як автентифікувати SPA-застосунки чи мобільні застосунки через провайдери автентифікації OAuth2 на кшталт Passport.

**Sanctum**

У відповідь на складність OAuth2 і плутанину серед розробників ми взялися створити простіший, зручніший пакет автентифікації, який упорався б і з власними вебзапитами з браузера, і з API-запитами через токени. Цю мету втілив випуск [Laravel Sanctum](/docs/{{version}}/sanctum) - його варто вважати кращим і рекомендованим пакетом автентифікації для застосунків, що пропонують власний вебінтерфейс на додачу до API, або працюють на односторінковому застосунку (SPA), який існує окремо від бекенду Laravel, або мають мобільний клієнт.

Laravel Sanctum - це гібридний пакет вебавтентифікації / API-автентифікації, який може керувати всім процесом автентифікації вашого застосунку. Це можливо тому, що коли застосунок на Sanctum отримує запит, Sanctum спершу визначає, чи містить запит сесійний cookie, що вказує на автентифіковану сесію. Для цього Sanctum викликає вбудовані сервіси автентифікації Laravel, які ми обговорювали раніше. Якщо запит автентифікується не через сесійний cookie, Sanctum перевірить його на наявність API-токена. Якщо токен присутній, Sanctum автентифікує запит за ним. Щоб дізнатися більше про цей процес, зверніться до документації Sanctum [«як це працює»](/docs/{{version}}/sanctum#how-it-works).

<a name="summary-choosing-your-stack"></a>
#### Підсумок і вибір свого стека

Підсумовуючи: якщо доступ до вашого застосунку відбувається через браузер і ви будуєте монолітний застосунок Laravel, ваш застосунок використовуватиме вбудовані сервіси автентифікації Laravel.

Далі, якщо ваш застосунок пропонує API, яким користуватимуться треті сторони, ви обиратимете між [Passport](/docs/{{version}}/passport) і [Sanctum](/docs/{{version}}/sanctum) для автентифікації через API-токени. Загалом варто віддавати перевагу Sanctum там, де це можливо, адже це просте й повноцінне рішення для API-автентифікації, автентифікації SPA та мобільної автентифікації, з підтримкою «скопів» (scopes) чи «можливостей» (abilities).

Якщо ви будуєте односторінковий застосунок (SPA) на бекенді Laravel, вам варто скористатися [Laravel Sanctum](/docs/{{version}}/sanctum). Із Sanctum вам доведеться або [реалізувати власні маршрути автентифікації на бекенді вручну](#authenticating-users), або скористатися [Laravel Fortify](/docs/{{version}}/fortify) як headless-бекендом автентифікації, що надає маршрути й контролери для реєстрації, скидання пароля, підтвердження пошти тощо.

Passport варто обрати, коли вашому застосунку конче потрібні всі можливості специфікації OAuth2. Крім того, якщо ви будуєте [MCP-сервер](/docs/{{version}}/mcp), до якого звертатимуться AI-клієнти, вам варто скористатися Passport, адже MCP-клієнти зазвичай очікують [автентифікації через OAuth](/docs/{{version}}/mcp#oauth).

А якщо ви хочете швидко почати, ми радо рекомендуємо [наші стартові набори застосунку](/docs/{{version}}/starter-kits) як швидкий спосіб розпочати новий застосунок Laravel, що вже використовує наш улюблений стек автентифікації на вбудованих сервісах Laravel.

<a name="authentication-quickstart"></a>
## Швидкий старт автентифікації

> [!WARNING]
> У цій частині документації йдеться про автентифікацію користувачів через [стартові набори застосунку Laravel](/docs/{{version}}/starter-kits), які містять каркас UI, щоб допомогти вам швидко почати. Якщо ви хочете інтегруватися із системами автентифікації Laravel напряму, погляньте на документацію про [ручну автентифікацію користувачів](#authenticating-users).

<a name="install-a-starter-kit"></a>
### Встановлення стартового набору

Спершу вам слід [встановити стартовий набір застосунку Laravel](/docs/{{version}}/starter-kits). Наші стартові набори пропонують гарно оформлені відправні точки для впровадження автентифікації у ваш свіжий застосунок Laravel.

<a name="retrieving-the-authenticated-user"></a>
### Отримання автентифікованого користувача

Створивши застосунок зі стартового набору й дозволивши користувачам реєструватися та автентифікуватися, ви часто матимете справу з поточним автентифікованим користувачем. Під час обробки вхідного запиту ви можете дістатися автентифікованого користувача методом `user` фасада `Auth`:

```php
use Illuminate\Support\Facades\Auth;

// Retrieve the currently authenticated user...
$user = Auth::user();

// Retrieve the currently authenticated user's ID...
$id = Auth::id();
```

Або ж, коли користувач автентифікований, ви можете дістатися його через екземпляр `Illuminate\Http\Request`. Пам'ятайте: класи з підказками типів автоматично впроваджуються в методи ваших контролерів. Вказавши тип об'єкта `Illuminate\Http\Request`, ви отримаєте зручний доступ до автентифікованого користувача з будь-якого методу контролера через метод запиту `user`:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class FlightController extends Controller
{
    /**
     * Update the flight information for an existing flight.
     */
    public function update(Request $request): RedirectResponse
    {
        $user = $request->user();

        // ...

        return redirect('/flights');
    }
}
```

<a name="determining-if-the-current-user-is-authenticated"></a>
#### Визначення, чи автентифікований поточний користувач

Щоб визначити, чи автентифікований користувач, який робить вхідний HTTP-запит, скористайтеся методом `check` на фасаді `Auth`. Цей метод поверне `true`, якщо користувач автентифікований:

```php
use Illuminate\Support\Facades\Auth;

if (Auth::check()) {
    // The user is logged in...
}
```

> [!NOTE]
> Хоча визначити автентифікованість користувача можна методом `check`, зазвичай ви користуватиметеся `middleware`, щоб перевірити автентифікацію, перш ніж пускати користувача до певних маршрутів чи контролерів. Щоб дізнатися більше, погляньте на документацію про [захист маршрутів](/docs/{{version}}/authentication#protecting-routes).

<a name="protecting-routes"></a>
### Захист маршрутів

[Маршрутне `middleware`](/docs/{{version}}/middleware) дозволяє пускати на певний маршрут лише автентифікованих користувачів. Laravel постачається з `middleware` `auth` - це [аліас `middleware`](/docs/{{version}}/middleware#middleware-aliases) для класу `Illuminate\Auth\Middleware\Authenticate`. Оскільки Laravel уже реєструє цей аліас усередині, вам залишається лише додати `middleware` до визначення маршруту:

```php
Route::get('/flights', function () {
    // Only authenticated users may access this route...
})->middleware('auth');
```

<a name="redirecting-unauthenticated-users"></a>
#### Перенаправлення неавтентифікованих користувачів

Коли `middleware` `auth` виявляє неавтентифікованого користувача, воно перенаправляє його на [іменований маршрут](/docs/{{version}}/routing#named-routes) `login`. Ви можете змінити цю поведінку методом `redirectGuestsTo` у файлі `bootstrap/app.php` вашого застосунку:

```php
use Illuminate\Http\Request;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->redirectGuestsTo('/login');

    // Using a closure...
    $middleware->redirectGuestsTo(fn (Request $request) => route('login'));
})
```

<a name="redirecting-authenticated-users"></a>
#### Перенаправлення автентифікованих користувачів

Коли `middleware` `guest` виявляє автентифікованого користувача, воно перенаправляє його на іменований маршрут `dashboard` або `home`. Ви можете змінити цю поведінку методом `redirectUsersTo` у файлі `bootstrap/app.php` вашого застосунку:

```php
use Illuminate\Http\Request;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->redirectUsersTo('/panel');

    // Using a closure...
    $middleware->redirectUsersTo(fn (Request $request) => route('panel'));
})
```

<a name="specifying-a-guard"></a>
#### Вказання гарда

Додаючи `middleware` `auth` до маршруту, ви також можете вказати, який «гард» слід використати для автентифікації користувача. Указаний гард має відповідати одному з ключів масиву `guards` у вашому конфігураційному файлі `auth.php`:

```php
Route::get('/flights', function () {
    // Only authenticated users may access this route...
})->middleware('auth:admin');
```

<a name="login-throttling"></a>
### Обмеження спроб входу

Якщо ви користуєтеся одним із наших [стартових наборів застосунку](/docs/{{version}}/starter-kits), обмеження частоти застосовуватиметься до спроб входу автоматично. За замовчуванням користувач не зможе увійти протягом хвилини, якщо після кількох спроб він так і не ввів правильні облікові дані. Обмеження унікальне для комбінації імені користувача / адреси пошти та його IP-адреси.

> [!NOTE]
> Якщо ви хочете обмежити частоту запитів до інших маршрутів вашого застосунку, погляньте на [документацію про обмеження частоти](/docs/{{version}}/routing#rate-limiting).

<a name="authenticating-users"></a>
## Ручна автентифікація користувачів

Вам не обов'язково користуватися каркасом автентифікації зі [стартових наборів застосунку](/docs/{{version}}/starter-kits) Laravel. Якщо ви вирішите не використовувати цей каркас, вам доведеться керувати автентифікацією користувачів безпосередньо через класи автентифікації Laravel. Не хвилюйтеся, це дуже просто!

Ми звертатимемося до сервісів автентифікації Laravel через [фасад](/docs/{{version}}/facades) `Auth`, тож не забудьте імпортувати фасад `Auth` на початку класу. Далі погляньмо на метод `attempt`. Метод `attempt` зазвичай обробляє спроби автентифікації з форми «входу» вашого застосунку. Якщо автентифікація успішна, вам слід перегенерувати [сесію](/docs/{{version}}/session) користувача, щоб запобігти [фіксації сесії](https://en.wikipedia.org/wiki/Session_fixation):

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    /**
     * Handle an authentication attempt.
     */
    public function authenticate(Request $request): RedirectResponse
    {
        $credentials = $request->validate([
            'email' => ['required', 'email'],
            'password' => ['required'],
        ]);

        if (Auth::attempt($credentials)) {
            $request->session()->regenerate();

            return redirect()->intended('dashboard');
        }

        return back()->withErrors([
            'email' => 'The provided credentials do not match our records.',
        ])->onlyInput('email');
    }
}
```

Метод `attempt` приймає першим аргументом масив пар ключ / значення. Значення з масиву використовуються для пошуку користувача у вашій таблиці бази даних. Тож у прикладі вище користувача буде знайдено за значенням стовпця `email`. Якщо користувача знайдено, збережений у базі захешований пароль буде порівняно зі значенням `password`, переданим методу в масиві. Вам не слід хешувати значення `password` із вхідного запиту, адже фреймворк автоматично захешує його перед порівнянням із хешем у базі. Якщо два хеші паролів збігаються, для користувача буде розпочато автентифіковану сесію.

Пам'ятайте: сервіси автентифікації Laravel діставатимуть користувачів із вашої бази даних відповідно до конфігурації «провайдера» вашого гарда автентифікації. У стандартному конфігураційному файлі `config/auth.php` вказано провайдер користувачів Eloquent і задано використовувати модель `App\Models\User`. Ви можете змінити ці значення у своєму конфігураційному файлі відповідно до потреб застосунку.

Метод `attempt` поверне `true`, якщо автентифікація успішна. Інакше буде повернено `false`.

Метод `intended` редиректора Laravel перенаправить користувача на URL, до якого він намагався дістатися, перш ніж його перехопило `middleware` автентифікації. Цьому методу можна передати запасний URI на випадок, якщо потрібне місце призначення недоступне.

<a name="specifying-additional-conditions"></a>
#### Вказання додаткових умов

За бажання ви можете додати до запиту автентифікації додаткові умови, окрім пошти й пароля користувача. Для цього просто додайте умови запиту до масиву, переданого методу `attempt`. Наприклад, ми можемо перевірити, що користувач позначений як «активний»:

```php
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
    // Authentication was successful...
}
```

Для складних умов запиту ви можете передати в масиві облікових даних замикання. Це замикання буде викликано з екземпляром запиту, тож ви зможете налаштувати запит відповідно до потреб вашого застосунку:

```php
use Illuminate\Database\Eloquent\Builder;

if (Auth::attempt([
    'email' => $email,
    'password' => $password,
    fn (Builder $query) => $query->has('activeSubscription'),
])) {
    // Authentication was successful...
}
```

> [!WARNING]
> У цих прикладах `email` не є обов'язковою опцією - він наведений лише як приклад. Вам слід використовувати те ім'я стовпця, яке відповідає «імені користувача» у вашій таблиці бази даних.

Метод `attemptWhen`, який приймає замикання другим аргументом, дозволяє ретельніше перевірити потенційного користувача, перш ніж власне його автентифікувати. Замикання отримує потенційного користувача й має повернути `true` або `false`, вказуючи, чи можна його автентифікувати:

```php
if (Auth::attemptWhen([
    'email' => $email,
    'password' => $password,
], function (User $user) {
    return $user->isNotBanned();
})) {
    // Authentication was successful...
}
```

<a name="accessing-specific-guard-instances"></a>
#### Доступ до конкретних екземплярів гардів

Методом `guard` фасада `Auth` ви можете вказати, який екземпляр гарда використати для автентифікації користувача. Це дозволяє керувати автентифікацією окремих частин застосунку через цілком окремі моделі, придатні до автентифікації, чи таблиці користувачів.

Ім'я гарда, передане методу `guard`, має відповідати одному з гардів, налаштованих у вашому конфігураційному файлі `auth.php`:

```php
if (Auth::guard('admin')->attempt($credentials)) {
    // ...
}
```

<a name="remembering-users"></a>
### Запам'ятовування користувачів

Багато вебзастосунків мають на формі входу чекбокс «запам'ятати мене». Якщо ви хочете реалізувати цю функціональність у своєму застосунку, передайте булеве значення другим аргументом до методу `attempt`.

Коли це значення дорівнює `true`, Laravel триматиме користувача автентифікованим необмежено довго - або доки він не вийде вручну. Ваша таблиця `users` має містити стовпець `remember_token` типу string, у якому зберігатиметься токен «запам'ятати мене». Міграція таблиці `users`, що входить до нових застосунків Laravel, уже містить цей стовпець:

```php
use Illuminate\Support\Facades\Auth;

if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
    // The user is being remembered...
}
```

Якщо ваш застосунок пропонує функціональність «запам'ятати мене», ви можете скористатися методом `viaRemember`, щоб визначити, чи був поточний автентифікований користувач автентифікований саме через cookie «запам'ятати мене»:

```php
use Illuminate\Support\Facades\Auth;

if (Auth::viaRemember()) {
    // ...
}
```

<a name="other-authentication-methods"></a>
### Інші методи автентифікації

<a name="authenticate-a-user-instance"></a>
#### Автентифікація екземпляра користувача

Якщо вам потрібно зробити наявний екземпляр користувача поточним автентифікованим користувачем, передайте цей екземпляр методу `login` фасада `Auth`. Переданий екземпляр має реалізовувати [контракт](/docs/{{version}}/contracts) `Illuminate\Contracts\Auth\Authenticatable`. Модель `App\Models\User`, що входить до Laravel, уже реалізує цей інтерфейс. Такий спосіб автентифікації стає в пригоді, коли ви вже маєте дійсний екземпляр користувача - наприклад, одразу після його реєстрації у вашому застосунку:

```php
use Illuminate\Support\Facades\Auth;

Auth::login($user);
```

Ви можете передати методу `login` булеве значення другим аргументом. Воно вказує, чи потрібна для автентифікованої сесії функціональність «запам'ятати мене». Пам'ятайте: це означає, що сесія лишатиметься автентифікованою необмежено довго - або доки користувач не вийде із застосунку вручну:

```php
Auth::login($user, $remember = true);
```

За потреби ви можете вказати гард автентифікації перед викликом методу `login`:

```php
Auth::guard('admin')->login($user);
```

<a name="authenticate-a-user-by-id"></a>
#### Автентифікація користувача за ID

Щоб автентифікувати користувача за первинним ключем його запису в базі даних, скористайтеся методом `loginUsingId`. Цей метод приймає первинний ключ користувача, якого ви хочете автентифікувати:

```php
Auth::loginUsingId(1);
```

Ви можете передати булеве значення в аргумент `remember` методу `loginUsingId`. Воно вказує, чи потрібна для автентифікованої сесії функціональність «запам'ятати мене». Пам'ятайте: це означає, що сесія лишатиметься автентифікованою необмежено довго - або доки користувач не вийде із застосунку вручну:

```php
Auth::loginUsingId(1, remember: true);
```

<a name="authenticate-a-user-once"></a>
#### Одноразова автентифікація користувача

Метод `once` дозволяє автентифікувати користувача в застосунку на один-єдиний запит. При виклику цього методу не використовуються ні сесії, ні cookie, а подія `Login` не відправляється:

```php
if (Auth::once($credentials)) {
    // ...
}
```

<a name="http-basic-authentication"></a>
## HTTP Basic Authentication

[HTTP Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) дає швидкий спосіб автентифікувати користувачів вашого застосунку без окремої сторінки «входу». Для початку додайте до маршруту [`middleware`](/docs/{{version}}/middleware) `auth.basic`. `middleware` `auth.basic` входить до фреймворку Laravel, тож визначати його не потрібно:

```php
Route::get('/profile', function () {
    // Only authenticated users may access this route...
})->middleware('auth.basic');
```

Щойно `middleware` додано до маршруту, при зверненні до нього у браузері у вас автоматично запитають облікові дані. За замовчуванням `middleware` `auth.basic` вважатиме «іменем користувача» стовпець `email` у вашій таблиці `users`.

<a name="a-note-on-fastcgi"></a>
#### Зауваження щодо FastCGI

Якщо ви віддаєте свій застосунок Laravel через [PHP FastCGI](https://www.php.net/manual/en/install.fpm.php) та Apache, HTTP Basic Authentication може працювати некоректно. Щоб виправити ці проблеми, додайте до файлу `.htaccess` вашого застосунку такі рядки:

```apache
RewriteCond %{HTTP:Authorization} ^(.+)$
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
```

<a name="stateless-http-basic-authentication"></a>
### HTTP Basic Authentication без збереження стану

Ви також можете користуватися HTTP Basic Authentication, не встановлюючи cookie з ідентифікатором користувача в сесії. Це передусім корисно, якщо ви обрали HTTP-автентифікацію для запитів до API вашого застосунку. Щоб цього досягти, [визначте `middleware`](/docs/{{version}}/middleware), яке викликає метод `onceBasic`. Якщо метод `onceBasic` не повертає відповіді, запит можна пропустити далі в застосунок:

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;

class AuthenticateOnceWithBasicAuth
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        return Auth::onceBasic() ?: $next($request);
    }

}
```

Далі додайте `middleware` до маршруту:

```php
Route::get('/api/user', function () {
    // Only authenticated users may access this route...
})->middleware(AuthenticateOnceWithBasicAuth::class);
```

<a name="logging-out"></a>
## Вихід із системи

Щоб вручну вивести користувачів із вашого застосунку, скористайтеся методом `logout`, який надає фасад `Auth`. Він прибере інформацію про автентифікацію із сесії користувача, тож наступні запити не будуть автентифікованими.

Окрім виклику методу `logout`, рекомендується скасувати сесію користувача й перегенерувати його [CSRF-токен](/docs/{{version}}/csrf). Після виходу користувача зазвичай перенаправляють у корінь застосунку:

```php
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;

/**
 * Log the user out of the application.
 */
public function logout(Request $request): RedirectResponse
{
    Auth::logout();

    $request->session()->invalidate();

    $request->session()->regenerateToken();

    return redirect('/');
}
```

<a name="invalidating-sessions-on-other-devices"></a>
### Скасування сесій на інших пристроях

Laravel також надає механізм скасування й «виходу» із сесій користувача, активних на інших пристроях, не скасовуючи сесію на його поточному пристрої. Цю можливість зазвичай застосовують, коли користувач змінює чи оновлює свій пароль, і ви хочете скасувати сесії на інших пристроях, залишивши поточний автентифікованим.

Перш ніж почати, переконайтеся, що `middleware` `Illuminate\Session\Middleware\AuthenticateSession` додане до маршрутів, які мають отримувати автентифікацію сесії. Зазвичай це `middleware` розміщують у визначенні групи маршрутів, щоб застосувати його до більшості маршрутів вашого застосунку. За замовчуванням `middleware` `AuthenticateSession` можна додати до маршруту через [аліас `middleware`](/docs/{{version}}/middleware#middleware-aliases) `auth.session`:

```php
Route::middleware(['auth', 'auth.session'])->group(function () {
    Route::get('/', function () {
        // ...
    });
});
```

Далі ви можете скористатися методом `logoutOtherDevices`, який надає фасад `Auth`. Цей метод вимагає, щоб користувач підтвердив свій поточний пароль, - ваш застосунок має прийняти його через поле форми:

```php
use Illuminate\Support\Facades\Auth;

Auth::logoutOtherDevices($currentPassword);
```

Коли викликано метод `logoutOtherDevices`, інші сесії користувача буде повністю скасовано, тобто його «виведе» з усіх гардів, у яких він раніше був автентифікований.

<a name="password-confirmation"></a>
## Підтвердження пароля

Будуючи застосунок, ви час від часу матимете дії, які вимагають від користувача підтвердити свій пароль, перш ніж дію буде виконано або перш ніж користувача буде перенаправлено до чутливої частини застосунку. Laravel містить вбудоване `middleware`, яке робить цей процес легким. Реалізація цієї можливості вимагатиме визначити два маршрути: один - щоб показати представлення з проханням підтвердити пароль, і другий - щоб перевірити пароль і перенаправити користувача до місця призначення.

> [!NOTE]
> Далі йдеться про пряму інтеграцію з можливостями підтвердження пароля в Laravel; проте якщо ви хочете почати швидше, [стартові набори застосунку Laravel](/docs/{{version}}/starter-kits) уже містять підтримку цієї можливості!

<a name="password-confirmation-configuration"></a>
### Конфігурація

Підтвердивши свій пароль, користувач не отримуватиме повторного запиту протягом трьох годин. Проте ви можете налаштувати час до наступного запиту пароля, змінивши значення конфігурації `password_timeout` у файлі `config/auth.php` вашого застосунку.

<a name="password-confirmation-routing"></a>
### Маршрутизація

<a name="the-password-confirmation-form"></a>
#### Форма підтвердження пароля

Спершу визначимо маршрут, який показуватиме представлення з проханням підтвердити пароль:

```php
Route::get('/confirm-password', function () {
    return view('auth.confirm-password');
})->middleware('auth')->name('password.confirm');
```

Як і слід очікувати, представлення, яке повертає цей маршрут, має містити форму з полем `password`. Крім того, сміливо додайте до представлення текст, який пояснює, що користувач заходить у захищену частину застосунку й має підтвердити свій пароль.

<a name="confirming-the-password"></a>
#### Підтвердження пароля

Далі визначимо маршрут, який оброблятиме запит форми з представлення «підтвердити пароль». Цей маршрут відповідатиме за валідацію пароля й перенаправлення користувача до місця призначення:

```php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;

Route::post('/confirm-password', function (Request $request) {
    if (! Hash::check($request->password, $request->user()->password)) {
        return back()->withErrors([
            'password' => ['The provided password does not match our records.']
        ]);
    }

    $request->session()->passwordConfirmed();

    return redirect()->intended();
})->middleware(['auth', 'throttle:6,1']);
```

Перш ніж рухатися далі, розгляньмо цей маршрут докладніше. Спершу перевіряється, чи поле `password` запиту справді збігається з паролем автентифікованого користувача. Якщо пароль дійсний, нам треба повідомити сесію Laravel, що користувач підтвердив свій пароль. Метод `passwordConfirmed` запише в сесію користувача часову мітку, за якою Laravel зможе визначити, коли той востаннє підтверджував пароль. Нарешті, ми можемо перенаправити користувача до місця призначення.

<a name="password-confirmation-protecting-routes"></a>
### Захист маршрутів

Переконайтеся, що будь-якому маршруту, який виконує дію, що вимагає нещодавнього підтвердження пароля, призначено `middleware` `password.confirm`. Це `middleware` входить до стандартної установки Laravel і автоматично збереже в сесії місце призначення користувача, щоб перенаправити його туди після підтвердження пароля. Зберігши місце призначення в сесії, `middleware` перенаправить користувача на [іменований маршрут](/docs/{{version}}/routing#named-routes) `password.confirm`:

```php
Route::get('/settings', function () {
    // ...
})->middleware(['password.confirm']);

Route::post('/settings', function () {
    // ...
})->middleware(['password.confirm']);
```

<a name="adding-custom-guards"></a>
## Додавання власних гардів

Ви можете визначати власні гарди автентифікації методом `extend` на фасаді `Auth`. Виклик методу `extend` слід розміщувати в [сервіс-провайдері](/docs/{{version}}/providers). Оскільки Laravel уже постачається з `AppServiceProvider`, ми можемо розмістити код саме там:

```php
<?php

namespace App\Providers;

use App\Services\Auth\JwtGuard;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    // ...

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Auth::extend('jwt', function (Application $app, string $name, array $config) {
            // Return an instance of Illuminate\Contracts\Auth\Guard...

            return new JwtGuard(Auth::createUserProvider($config['provider']));
        });
    }
}
```

Як видно з прикладу вище, колбек, переданий методу `extend`, має повертати реалізацію `Illuminate\Contracts\Auth\Guard`. Цей інтерфейс містить кілька методів, які вам треба реалізувати, щоб визначити власний гард. Коли ваш гард визначено, ви можете послатися на нього в конфігурації `guards` вашого файлу `auth.php`:

```php
'guards' => [
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],
```

<a name="closure-request-guards"></a>
### Гарди на замиканнях запиту

Найпростіший спосіб реалізувати власну систему автентифікації на основі HTTP-запиту - метод `Auth::viaRequest`. Він дозволяє швидко описати процес автентифікації одним замиканням.

Для початку викличте метод `Auth::viaRequest` у методі `boot` вашого `AppServiceProvider`. Метод `viaRequest` приймає першим аргументом ім'я драйвера автентифікації. Це може бути будь-який рядок, що описує ваш гард. Другим аргументом має бути замикання, яке приймає вхідний HTTP-запит і повертає екземпляр користувача або `null`, якщо автентифікація не вдалася:

```php
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Auth::viaRequest('custom-token', function (Request $request) {
        return User::where('token', (string) $request->token)->first();
    });
}
```

Коли ваш драйвер автентифікації визначено, ви можете вказати його як драйвер у конфігурації `guards` вашого файлу `auth.php`:

```php
'guards' => [
    'api' => [
        'driver' => 'custom-token',
    ],
],
```

Нарешті, ви можете послатися на цей гард, призначаючи маршруту `middleware` автентифікації:

```php
Route::middleware('auth:api')->group(function () {
    // ...
});
```

<a name="adding-custom-user-providers"></a>
## Додавання власних провайдерів користувачів

Якщо ви зберігаєте користувачів не в традиційній реляційній базі даних, вам знадобиться розширити Laravel власним провайдером користувачів для автентифікації. Ми скористаємося методом `provider` на фасаді `Auth`, щоб визначити власний провайдер. Резолвер провайдера має повертати реалізацію `Illuminate\Contracts\Auth\UserProvider`:

```php
<?php

namespace App\Providers;

use App\Extensions\MongoUserProvider;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    // ...

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Auth::provider('mongo', function (Application $app, array $config) {
            // Return an instance of Illuminate\Contracts\Auth\UserProvider...

            return new MongoUserProvider($app->make('mongo.connection'));
        });
    }
}
```

Зареєструвавши провайдер методом `provider`, ви можете перемкнутися на новий провайдер користувачів у своєму конфігураційному файлі `auth.php`. Спершу визначте `provider`, що використовує ваш новий драйвер:

```php
'providers' => [
    'users' => [
        'driver' => 'mongo',
    ],
],
```

Нарешті, ви можете послатися на цей провайдер у своїй конфігурації `guards`:

```php
'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
],
```

<a name="the-user-provider-contract"></a>
### Контракт User Provider

Реалізації `Illuminate\Contracts\Auth\UserProvider` відповідають за отримання реалізації `Illuminate\Contracts\Auth\Authenticatable` із системи постійного зберігання - MySQL, MongoDB тощо. Ці два інтерфейси дозволяють механізмам автентифікації Laravel працювати незалежно від того, як зберігаються дані користувачів і який клас представляє автентифікованого користувача:

Погляньмо на контракт `Illuminate\Contracts\Auth\UserProvider`:

```php
<?php

namespace Illuminate\Contracts\Auth;

interface UserProvider
{
    public function retrieveById($identifier);
    public function retrieveByToken($identifier, $token);
    public function updateRememberToken(Authenticatable $user, $token);
    public function retrieveByCredentials(array $credentials);
    public function validateCredentials(Authenticatable $user, array $credentials);
    public function rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false);
}
```

Функція `retrieveById` зазвичай отримує ключ, що представляє користувача, - наприклад, автоінкрементний ID із бази MySQL. Метод має знайти й повернути реалізацію `Authenticatable`, що відповідає цьому ID.

Функція `retrieveByToken` знаходить користувача за унікальним `$identifier` і токеном «запам'ятати мене» `$token`, який зазвичай зберігається у стовпці на кшталт `remember_token`. Як і в попередньому методі, він має повернути реалізацію `Authenticatable` із відповідним значенням токена.

Метод `updateRememberToken` оновлює `remember_token` екземпляра `$user` новим значенням `$token`. Новий токен призначається користувачам при успішній спробі автентифікації «запам'ятати мене» або коли користувач виходить із системи.

Метод `retrieveByCredentials` отримує масив облікових даних, переданий методу `Auth::attempt` під час спроби автентифікації в застосунку. Далі метод має «запитати» постійне сховище про користувача з такими обліковими даними. Зазвичай цей метод виконує запит з умовою «where», що шукає запис користувача з «іменем користувача», яке дорівнює значенню `$credentials['username']`. Метод має повернути реалізацію `Authenticatable`. **Цей метод не повинен намагатися перевіряти пароль чи виконувати автентифікацію.**

Метод `validateCredentials` має порівняти заданий `$user` із `$credentials`, щоб автентифікувати користувача. Наприклад, зазвичай цей метод використовує метод `Hash::check`, щоб порівняти значення `$user->getAuthPassword()` зі значенням `$credentials['password']`. Метод має повернути `true` або `false`, вказуючи, чи дійсний пароль.

Метод `rehashPasswordIfRequired` має перехешувати пароль заданого `$user`, якщо це потрібно й підтримується. Наприклад, зазвичай цей метод використовує метод `Hash::needsRehash`, щоб визначити, чи потребує значення `$credentials['password']` перехешування. Якщо пароль потрібно перехешувати, метод має скористатися методом `Hash::make`, щоб перехешувати пароль і оновити запис користувача в постійному сховищі.

<a name="the-authenticatable-contract"></a>
### Контракт Authenticatable

Тепер, коли ми розглянули кожен метод `UserProvider`, погляньмо на контракт `Authenticatable`. Пам'ятайте: провайдери користувачів мають повертати реалізації цього інтерфейсу з методів `retrieveById`, `retrieveByToken` і `retrieveByCredentials`:

```php
<?php

namespace Illuminate\Contracts\Auth;

interface Authenticatable
{
    public function getAuthIdentifierName();
    public function getAuthIdentifier();
    public function getAuthPasswordName();
    public function getAuthPassword();
    public function getRememberToken();
    public function setRememberToken($value);
    public function getRememberTokenName();
}
```

Цей інтерфейс простий. Метод `getAuthIdentifierName` має повертати ім'я стовпця «первинного ключа» користувача, а метод `getAuthIdentifier` - сам «первинний ключ». У випадку бекенду MySQL це, найімовірніше, автоінкрементний первинний ключ, призначений запису користувача. Метод `getAuthPasswordName` має повертати ім'я стовпця з паролем користувача. Метод `getAuthPassword` має повертати захешований пароль користувача.

Цей інтерфейс дозволяє системі автентифікації працювати з будь-яким класом «користувача» - незалежно від того, який ORM чи шар абстракції сховища ви використовуєте. За замовчуванням Laravel містить клас `App\Models\User` у каталозі `app/Models`, який реалізує цей інтерфейс.

<a name="automatic-password-rehashing"></a>
## Автоматичне перехешування паролів

Стандартний алгоритм хешування паролів у Laravel - bcrypt. «Фактор складності» (work factor) для хешів bcrypt можна змінити у файлі `config/hashing.php` вашого застосунку або через змінну оточення `BCRYPT_ROUNDS`.

Зазвичай фактор складності bcrypt варто з часом збільшувати в міру зростання обчислювальної потужності CPU / GPU. Якщо ви збільшите фактор складності bcrypt у своєму застосунку, Laravel плавно й автоматично перехешує паролі користувачів у міру того, як вони автентифікуються через стартові набори Laravel або коли ви [автентифікуєте користувачів вручну](#authenticating-users) методом `attempt`.

Зазвичай автоматичне перехешування паролів не має заважати вашому застосунку; проте ви можете вимкнути цю поведінку, опублікувавши конфігураційний файл `hashing`:

```shell
php artisan config:publish hashing
```

Коли конфігураційний файл опубліковано, ви можете встановити значення конфігурації `rehash_on_login` у `false`:

```php
'rehash_on_login' => false,
```

<a name="events"></a>
## Події

Під час процесу автентифікації Laravel відправляє різні [події](/docs/{{version}}/events). Ви можете [визначити слухачів](/docs/{{version}}/events) для будь-якої з наведених нижче подій:

<div class="overflow-auto">

| Ім'я події                                     |
| ---------------------------------------------- |
| `Illuminate\Auth\Events\Registered`            |
| `Illuminate\Auth\Events\Attempting`            |
| `Illuminate\Auth\Events\Authenticated`         |
| `Illuminate\Auth\Events\Login`                 |
| `Illuminate\Auth\Events\Failed`                |
| `Illuminate\Auth\Events\Validated`             |
| `Illuminate\Auth\Events\Verified`              |
| `Illuminate\Auth\Events\Logout`                |
| `Illuminate\Auth\Events\CurrentDeviceLogout`   |
| `Illuminate\Auth\Events\OtherDeviceLogout`     |
| `Illuminate\Auth\Events\Lockout`               |
| `Illuminate\Auth\Events\PasswordReset`         |
| `Illuminate\Auth\Events\PasswordResetLinkSent` |

</div>

---

Джерело: https://laravelukraine.com/docs/13.x/authorization

# Авторизація

- [Вступ](#introduction)
- [Гейти](#gates)
    - [Написання гейтів](#writing-gates)
    - [Авторизація дій](#authorizing-actions-via-gates)
    - [Відповіді гейтів](#gate-responses)
    - [Перехоплення перевірок гейтів](#intercepting-gate-checks)
    - [Вбудована авторизація](#inline-authorization)
- [Створення політик](#creating-policies)
    - [Генерування політик](#generating-policies)
    - [Реєстрація політик](#registering-policies)
- [Написання політик](#writing-policies)
    - [Методи політик](#policy-methods)
    - [Відповіді політик](#policy-responses)
    - [Методи без моделей](#methods-without-models)
    - [Гості](#guest-users)
    - [Фільтри політик](#policy-filters)
- [Авторизація дій через політики](#authorizing-actions-using-policies)
    - [Через модель User](#via-the-user-model)
    - [Через фасад Gate](#via-the-gate-facade)
    - [Через middleware](#via-middleware)
    - [Через Blade-шаблони](#via-blade-templates)
    - [Передавання додаткового контексту](#supplying-additional-context)
- [Авторизація та Inertia](#authorization-and-inertia)

<a name="introduction"></a>
## Вступ

Окрім вбудованих сервісів [автентифікації](/docs/{{version}}/authentication), Laravel надає простий спосіб авторизувати дії користувача щодо заданого ресурсу. Наприклад, навіть якщо користувач автентифікований, він може не мати права оновлювати чи видаляти певні моделі Eloquent або записи в базі даних, якими керує ваш застосунок. Можливості авторизації Laravel дають простий і впорядкований спосіб керувати такими перевірками.

Laravel пропонує два основні способи авторизації дій: [гейти](#gates) та [політики](#creating-policies). Уявіть гейти й політики як маршрути та контролери. Гейти дають простий підхід до авторизації на замиканнях, тоді як політики, подібно до контролерів, групують логіку навколо певної моделі чи ресурсу. У цій документації ми спершу розглянемо гейти, а потім - політики.

Будуючи застосунок, вам не обов'язково обирати щось одне - лише гейти чи лише політики. Більшість застосунків, найімовірніше, поєднуватимуть і те, і те, і це цілком нормально! Гейти найкраще пасують до дій, не пов'язаних із жодною моделлю чи ресурсом, - наприклад, перегляд адміністративної панелі. Натомість політики варто застосовувати, коли ви хочете авторизувати дію щодо конкретної моделі чи ресурсу.

<a name="gates"></a>
## Гейти

<a name="writing-gates"></a>
### Написання гейтів

> [!WARNING]
> Гейти - чудовий спосіб опанувати основи авторизації в Laravel; проте, будуючи серйозні застосунки Laravel, вам варто розглянути [політики](#creating-policies) для впорядкування правил авторизації.

Гейти - це просто замикання, які визначають, чи має користувач право виконати задану дію. Зазвичай гейти визначають у методі `boot` класу `App\Providers\AppServiceProvider` через фасад `Gate`. Гейти завжди отримують першим аргументом екземпляр користувача й можуть за бажанням приймати додаткові аргументи - наприклад, відповідну модель Eloquent.

У цьому прикладі ми визначимо гейт, який вирішує, чи може користувач оновити задану модель `App\Models\Post`. Гейт зробить це, порівнявши `id` користувача з `user_id` того, хто створив допис:

```php
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Gate;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Gate::define('update-post', function (User $user, Post $post) {
        return $user->id === $post->user_id;
    });
}
```

Як і контролери, гейти можна визначати масивом-колбеком класу:

```php
use App\Policies\PostPolicy;
use Illuminate\Support\Facades\Gate;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Gate::define('update-post', [PostPolicy::class, 'update']);
}
```

<a name="authorizing-actions-via-gates"></a>
### Авторизація дій

Щоб авторизувати дію через гейти, скористайтеся методами `allows` чи `denies`, які надає фасад `Gate`. Зверніть увагу: передавати цим методам поточного автентифікованого користувача не потрібно - Laravel сам подбає про передавання користувача в замикання гейта. Зазвичай методи авторизації гейта викликають у контролерах вашого застосунку перед виконанням дії, що вимагає авторизації:

```php
<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;

class PostController extends Controller
{
    /**
     * Update the given post.
     */
    public function update(Request $request, Post $post): RedirectResponse
    {
        if (! Gate::allows('update-post', $post)) {
            abort(403);
        }

        // Update the post...

        return redirect('/posts');
    }
}
```

Якщо ви хочете визначити, чи має право виконати дію користувач, відмінний від поточного автентифікованого, скористайтеся методом `forUser` на фасаді `Gate`:

```php
if (Gate::forUser($user)->allows('update-post', $post)) {
    // The user can update the post...
}

if (Gate::forUser($user)->denies('update-post', $post)) {
    // The user can't update the post...
}
```

Ви можете авторизувати кілька дій одночасно методами `any` чи `none`:

```php
if (Gate::any(['update-post', 'delete-post'], $post)) {
    // The user can update or delete the post...
}

if (Gate::none(['update-post', 'delete-post'], $post)) {
    // The user can't update or delete the post...
}
```

<a name="authorizing-or-throwing-exceptions"></a>
#### Авторизація з киданням винятків

Якщо ви хочете спробувати авторизувати дію й автоматично кинути виняток `Illuminate\Auth\Access\AuthorizationException`, коли користувачеві не дозволено її виконати, скористайтеся методом `authorize` фасада `Gate`. Laravel автоматично перетворює екземпляри `AuthorizationException` на HTTP-відповідь 403:

```php
Gate::authorize('update-post', $post);

// The action is authorized...
```

<a name="gates-supplying-additional-context"></a>
#### Передавання додаткового контексту

Методи гейтів для авторизації можливостей (`allows`, `denies`, `check`, `any`, `none`, `authorize`, `can`, `cannot`) та [директиви Blade](#via-blade-templates) для авторизації (`@can`, `@cannot`, `@canany`) можуть приймати масив другим аргументом. Елементи цього масиву передаються параметрами до замикання гейта й можуть слугувати додатковим контекстом при ухваленні рішень щодо авторизації:

```php
use App\Models\Category;
use App\Models\User;
use Illuminate\Support\Facades\Gate;

Gate::define('create-post', function (User $user, Category $category, bool $pinned) {
    if (! $user->canPublishToGroup($category->group)) {
        return false;
    } elseif ($pinned && ! $user->canPinPosts()) {
        return false;
    }

    return true;
});

if (Gate::check('create-post', [$category, $pinned])) {
    // The user can create the post...
}
```

<a name="gate-responses"></a>
### Відповіді гейтів

Досі ми розглядали лише гейти, що повертають прості булеві значення. Проте іноді вам може знадобитися повернути докладнішу відповідь із повідомленням про помилку. Для цього поверніть із гейта `Illuminate\Auth\Access\Response`:

```php
use App\Models\User;
use Illuminate\Auth\Access\Response;
use Illuminate\Support\Facades\Gate;

Gate::define('edit-settings', function (User $user) {
    return $user->isAdmin
        ? Response::allow()
        : Response::deny('You must be an administrator.');
});
```

Навіть коли ви повертаєте з гейта відповідь авторизації, метод `Gate::allows` усе одно поверне просте булеве значення; проте ви можете скористатися методом `Gate::inspect`, щоб отримати повну відповідь авторизації, яку повернув гейт:

```php
$response = Gate::inspect('edit-settings');

if ($response->allowed()) {
    // The action is authorized...
} else {
    echo $response->message();
}
```

Коли ви користуєтеся методом `Gate::authorize`, який кидає `AuthorizationException`, якщо дію не авторизовано, повідомлення про помилку з відповіді авторизації буде передано до HTTP-відповіді:

```php
Gate::authorize('edit-settings');

// The action is authorized...
```

<a name="customizing-gate-response-status"></a>
#### Налаштування статусу HTTP-відповіді

Коли гейт відхиляє дію, повертається HTTP-відповідь `403`; проте іноді буває корисно повернути інший код статусу HTTP. Ви можете налаштувати код статусу HTTP для невдалої перевірки авторизації через статичний конструктор `denyWithStatus` класу `Illuminate\Auth\Access\Response`:

```php
use App\Models\User;
use Illuminate\Auth\Access\Response;
use Illuminate\Support\Facades\Gate;

Gate::define('edit-settings', function (User $user) {
    return $user->isAdmin
        ? Response::allow()
        : Response::denyWithStatus(404);
});
```

Оскільки приховування ресурсів відповіддю `404` - дуже поширений патерн у вебзастосунках, для зручності пропонується метод `denyAsNotFound`:

```php
use App\Models\User;
use Illuminate\Auth\Access\Response;
use Illuminate\Support\Facades\Gate;

Gate::define('edit-settings', function (User $user) {
    return $user->isAdmin
        ? Response::allow()
        : Response::denyAsNotFound();
});
```

<a name="intercepting-gate-checks"></a>
### Перехоплення перевірок гейтів

Іноді вам може знадобитися надати конкретному користувачеві всі можливості. Скористайтеся методом `before`, щоб визначити замикання, яке виконується перед усіма іншими перевірками авторизації:

```php
use App\Models\User;
use Illuminate\Support\Facades\Gate;

Gate::before(function (User $user, string $ability) {
    if ($user->isAdministrator()) {
        return true;
    }
});
```

Якщо замикання `before` поверне результат, відмінний від null, саме він вважатиметься результатом перевірки авторизації.

Метод `after` дозволяє визначити замикання, яке виконується після всіх інших перевірок авторизації:

```php
use App\Models\User;

Gate::after(function (User $user, string $ability, bool|null $result, mixed $arguments) {
    if ($user->isAdministrator()) {
        return true;
    }
});
```

Значення, повернені замиканнями `after`, не перевизначать результат перевірки авторизації, якщо тільки гейт чи політика не повернули `null`.

<a name="inline-authorization"></a>
### Вбудована авторизація

Іноді вам може знадобитися визначити, чи має поточний автентифікований користувач право виконати задану дію, не пишучи для неї окремого гейта. Laravel дозволяє робити такі «вбудовані» перевірки авторизації методами `Gate::allowIf` та `Gate::denyIf`. Вбудована авторизація не виконує жодних визначених [хуків авторизації «before» чи «after»](#intercepting-gate-checks):

```php
use App\Models\User;
use Illuminate\Support\Facades\Gate;

Gate::allowIf(fn (User $user) => $user->isAdministrator());

Gate::denyIf(fn (User $user) => $user->banned());
```

Якщо дію не авторизовано або якщо наразі жоден користувач не автентифікований, Laravel автоматично кине виняток `Illuminate\Auth\Access\AuthorizationException`. Обробник винятків Laravel автоматично перетворює екземпляри `AuthorizationException` на HTTP-відповідь 403.

<a name="creating-policies"></a>
## Створення політик

<a name="generating-policies"></a>
### Генерування політик

Політики - це класи, що впорядковують логіку авторизації навколо певної моделі чи ресурсу. Наприклад, якщо ваш застосунок - це блог, у вас може бути модель `App\Models\Post` і відповідна `App\Policies\PostPolicy` для авторизації дій користувача на кшталт створення чи оновлення дописів.

Згенерувати політику можна артизан-командою `make:policy`. Згенерована політика потрапить до каталогу `app/Policies`. Якщо цього каталогу у вашому застосунку немає, Laravel створить його за вас:

```shell
php artisan make:policy PostPolicy
```

Команда `make:policy` згенерує порожній клас політики. Якщо ви хочете згенерувати клас із прикладами методів політики для перегляду, створення, оновлення та видалення ресурсу, додайте до команди опцію `--model`:

```shell
php artisan make:policy PostPolicy --model=Post
```

<a name="registering-policies"></a>
### Реєстрація політик

<a name="policy-discovery"></a>
#### Автоматичний пошук політик

За замовчуванням Laravel автоматично знаходить політики, якщо модель і політика дотримуються стандартних угод іменування Laravel. Зокрема, політики мають бути в каталозі `Policies` на рівні каталогу з вашими моделями або вище. Тож, наприклад, моделі можуть лежати в каталозі `app/Models`, а політики - у `app/Policies`. У цьому випадку Laravel шукатиме політики спершу в `app/Models/Policies`, а потім у `app/Policies`. Крім того, ім'я політики має збігатися з іменем моделі та мати суфікс `Policy`. Тож моделі `User` відповідатиме клас політики `UserPolicy`.

Якщо ви хочете описати власну логіку пошуку політик, зареєструйте власний колбек методом `Gate::guessPolicyNamesUsing`. Зазвичай цей метод викликають у методі `boot` вашого `AppServiceProvider`:

```php
use Illuminate\Support\Facades\Gate;

Gate::guessPolicyNamesUsing(function (string $modelClass) {
    // Return the name of the policy class for the given model...
});
```

<a name="manually-registering-policies"></a>
#### Ручна реєстрація політик

Через фасад `Gate` ви можете вручну зареєструвати політики та відповідні їм моделі в методі `boot` вашого `AppServiceProvider`:

```php
use App\Models\Order;
use App\Policies\OrderPolicy;
use Illuminate\Support\Facades\Gate;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Gate::policy(Order::class, OrderPolicy::class);
}
```

Або ж ви можете застосувати до класу моделі атрибут `UsePolicy`, щоб повідомити Laravel про відповідну політику моделі:

```php
<?php

namespace App\Models;

use App\Policies\OrderPolicy;
use Illuminate\Database\Eloquent\Attributes\UsePolicy;
use Illuminate\Database\Eloquent\Model;

#[UsePolicy(OrderPolicy::class)]
class Order extends Model
{
    //
}
```

<a name="writing-policies"></a>
## Написання політик

<a name="policy-methods"></a>
### Методи політик

Коли клас політики зареєстровано, ви можете додати методи для кожної дії, яку він авторизує. Наприклад, визначмо метод `update` у нашій `PostPolicy`, який вирішує, чи може заданий `App\Models\User` оновити заданий екземпляр `App\Models\Post`.

Метод `update` отримає аргументами екземпляри `User` та `Post` і має повернути `true` чи `false`, вказуючи, чи має користувач право оновити заданий `Post`. Тож у цьому прикладі ми перевіримо, що `id` користувача збігається з `user_id` допису:

```php
<?php

namespace App\Policies;

use App\Models\Post;
use App\Models\User;

class PostPolicy
{
    /**
     * Determine if the given post can be updated by the user.
     */
    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }
}
```

Ви можете й далі визначати в політиці додаткові методи для різних дій, які вона авторизує. Наприклад, ви можете визначити методи `view` чи `delete`, щоб авторизувати різні дії з `Post`, - але пам'ятайте, що ви вільні давати методам політики будь-які імена.

Якщо, генеруючи політику через артизан-консоль, ви скористалися опцією `--model`, вона вже міститиме методи для дій `viewAny`, `view`, `create`, `update`, `delete`, `restore` та `forceDelete`.

> [!NOTE]
> Усі політики розв'язуються через [сервіс-контейнер](/docs/{{version}}/container) Laravel, тож ви можете вказати типи потрібних залежностей у конструкторі політики, і їх буде впроваджено автоматично.

<a name="policy-responses"></a>
### Відповіді політик

Досі ми розглядали лише методи політик, що повертають прості булеві значення. Проте іноді вам може знадобитися повернути докладнішу відповідь із повідомленням про помилку. Для цього поверніть із методу політики екземпляр `Illuminate\Auth\Access\Response`:

```php
use App\Models\Post;
use App\Models\User;
use Illuminate\Auth\Access\Response;

/**
 * Determine if the given post can be updated by the user.
 */
public function update(User $user, Post $post): Response
{
    return $user->id === $post->user_id
        ? Response::allow()
        : Response::deny('You do not own this post.');
}
```

Коли ви повертаєте з політики відповідь авторизації, метод `Gate::allows` усе одно поверне просте булеве значення; проте ви можете скористатися методом `Gate::inspect`, щоб отримати повну відповідь авторизації, яку повернув гейт:

```php
use Illuminate\Support\Facades\Gate;

$response = Gate::inspect('update', $post);

if ($response->allowed()) {
    // The action is authorized...
} else {
    echo $response->message();
}
```

Коли ви користуєтеся методом `Gate::authorize`, який кидає `AuthorizationException`, якщо дію не авторизовано, повідомлення про помилку з відповіді авторизації буде передано до HTTP-відповіді:

```php
Gate::authorize('update', $post);

// The action is authorized...
```

<a name="customizing-policy-response-status"></a>
#### Налаштування статусу HTTP-відповіді

Коли метод політики відхиляє дію, повертається HTTP-відповідь `403`; проте іноді буває корисно повернути інший код статусу HTTP. Ви можете налаштувати код статусу HTTP для невдалої перевірки авторизації через статичний конструктор `denyWithStatus` класу `Illuminate\Auth\Access\Response`:

```php
use App\Models\Post;
use App\Models\User;
use Illuminate\Auth\Access\Response;

/**
 * Determine if the given post can be updated by the user.
 */
public function update(User $user, Post $post): Response
{
    return $user->id === $post->user_id
        ? Response::allow()
        : Response::denyWithStatus(404);
}
```

Оскільки приховування ресурсів відповіддю `404` - дуже поширений патерн у вебзастосунках, для зручності пропонується метод `denyAsNotFound`:

```php
use App\Models\Post;
use App\Models\User;
use Illuminate\Auth\Access\Response;

/**
 * Determine if the given post can be updated by the user.
 */
public function update(User $user, Post $post): Response
{
    return $user->id === $post->user_id
        ? Response::allow()
        : Response::denyAsNotFound();
}
```

<a name="methods-without-models"></a>
### Методи без моделей

Деякі методи політик отримують лише екземпляр поточного автентифікованого користувача. Найчастіше так буває при авторизації дій `create`. Наприклад, якщо ви створюєте блог, вам може знадобитися визначити, чи має користувач право створювати дописи взагалі. У таких випадках ваш метод політики має очікувати лише екземпляр користувача:

```php
/**
 * Determine if the given user can create posts.
 */
public function create(User $user): bool
{
    return $user->role == 'writer';
}
```

<a name="guest-users"></a>
### Гості

За замовчуванням усі гейти й політики автоматично повертають `false`, якщо вхідний HTTP-запит ініціював не автентифікований користувач. Проте ви можете пропускати такі перевірки далі - до ваших гейтів і політик, - оголосивши «необов'язкову» підказку типу або задавши значення `null` за замовчуванням для аргументу користувача:

```php
<?php

namespace App\Policies;

use App\Models\Post;
use App\Models\User;

class PostPolicy
{
    /**
     * Determine if the given post can be updated by the user.
     */
    public function update(?User $user, Post $post): bool
    {
        return $user?->id === $post->user_id;
    }
}
```

<a name="policy-filters"></a>
### Фільтри політик

Для певних користувачів ви можете захотіти авторизувати всі дії в межах заданої політики. Для цього визначте в політиці метод `before`. Метод `before` виконуватиметься перед усіма іншими методами політики, даючи вам змогу авторизувати дію ще до того, як буде викликано потрібний метод. Найчастіше цю можливість використовують, щоб дозволити адміністраторам застосунку виконувати будь-які дії:

```php
use App\Models\User;

/**
 * Perform pre-authorization checks.
 */
public function before(User $user, string $ability): bool|null
{
    if ($user->isAdministrator()) {
        return true;
    }

    return null;
}
```

Якщо ви хочете відхилити всі перевірки авторизації для певного типу користувачів, поверніть із методу `before` значення `false`. Якщо повернути `null`, перевірка авторизації перейде до методу політики.

> [!WARNING]
> Метод `before` класу політики не буде викликано, якщо клас не містить методу, ім'я якого збігається з іменем можливості, що перевіряється.

<a name="authorizing-actions-using-policies"></a>
## Авторизація дій через політики

<a name="via-the-user-model"></a>
### Через модель User

Модель `App\Models\User`, що входить до вашого застосунку Laravel, містить два зручні методи для авторизації дій: `can` та `cannot`. Методи `can` і `cannot` приймають ім'я дії, яку ви хочете авторизувати, і відповідну модель. Наприклад, визначмо, чи має користувач право оновити задану модель `App\Models\Post`. Зазвичай це роблять у методі контролера:

```php
<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class PostController extends Controller
{
    /**
     * Update the given post.
     */
    public function update(Request $request, Post $post): RedirectResponse
    {
        if ($request->user()->cannot('update', $post)) {
            abort(403);
        }

        // Update the post...

        return redirect('/posts');
    }
}
```

Якщо для заданої моделі [зареєстровано політику](#registering-policies), метод `can` автоматично викличе потрібну політику й поверне булевий результат. Якщо для моделі політики не зареєстровано, метод `can` спробує викликати гейт на замиканні, що відповідає імені заданої дії.

<a name="user-model-actions-that-dont-require-models"></a>
#### Дії, що не потребують моделей

Пам'ятайте: деякі дії відповідають методам політики на кшталт `create`, яким не потрібен екземпляр моделі. У таких випадках ви можете передати методу `can` ім'я класу. За ним буде визначено, яку політику використати для авторизації дії:

```php
<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class PostController extends Controller
{
    /**
     * Create a post.
     */
    public function store(Request $request): RedirectResponse
    {
        if ($request->user()->cannot('create', Post::class)) {
            abort(403);
        }

        // Create the post...

        return redirect('/posts');
    }
}
```

<a name="via-the-gate-facade"></a>
### Через фасад `Gate`

Окрім зручних методів моделі `App\Models\User`, ви завжди можете авторизувати дії методом `authorize` фасада `Gate`.

Як і метод `can`, цей метод приймає ім'я дії, яку ви хочете авторизувати, і відповідну модель. Якщо дію не авторизовано, метод `authorize` кине виняток `Illuminate\Auth\Access\AuthorizationException`, який обробник винятків Laravel автоматично перетворить на HTTP-відповідь зі статусом 403:

```php
<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;

class PostController extends Controller
{
    /**
     * Update the given blog post.
     *
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function update(Request $request, Post $post): RedirectResponse
    {
        Gate::authorize('update', $post);

        // The current user can update the blog post...

        return redirect('/posts');
    }
}
```

<a name="controller-actions-that-dont-require-models"></a>
#### Дії, що не потребують моделей

Як уже обговорювалося, деяким методам політики на кшталт `create` не потрібен екземпляр моделі. У таких випадках передайте методу `authorize` ім'я класу. За ним буде визначено, яку політику використати для авторизації дії:

```php
use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;

/**
 * Create a new blog post.
 *
 * @throws \Illuminate\Auth\Access\AuthorizationException
 */
public function create(Request $request): RedirectResponse
{
    Gate::authorize('create', Post::class);

    // The current user can create blog posts...

    return redirect('/posts');
}
```

<a name="via-middleware"></a>
### Через middleware

Laravel містить `middleware`, яке може авторизувати дії ще до того, як вхідний запит дійде до ваших маршрутів чи контролерів. За замовчуванням `middleware` `Illuminate\Auth\Middleware\Authorize` можна додати до маршруту через [аліас `middleware`](/docs/{{version}}/middleware#middleware-aliases) `can`, який Laravel реєструє автоматично. Розгляньмо приклад використання `middleware` `can`, щоб авторизувати оновлення допису користувачем:

```php
use App\Models\Post;

Route::put('/post/{post}', function (Post $post) {
    // The current user may update the post...
})->middleware('can:update,post');
```

У цьому прикладі ми передаємо `middleware` `can` два аргументи. Перший - ім'я дії, яку ми хочемо авторизувати, другий - параметр маршруту, який ми хочемо передати методу політики. У цьому випадку, оскільки ми користуємося [неявним прив'язуванням моделей](/docs/{{version}}/routing#implicit-binding), методу політики буде передано модель `App\Models\Post`. Якщо користувач не має права виконати задану дію, `middleware` поверне HTTP-відповідь зі статусом 403.

Для зручності ви також можете додати `middleware` `can` до маршруту методом `can`:

```php
use App\Models\Post;

Route::put('/post/{post}', function (Post $post) {
    // The current user may update the post...
})->can('update', 'post');
```

Якщо ви користуєтеся [атрибутами `middleware` контролерів](/docs/{{version}}/controllers#middleware-attributes), ви можете застосувати `middleware` `can` через атрибут `Authorize`:

```php
use Illuminate\Routing\Attributes\Controllers\Authorize;

#[Authorize('update', 'post')]
public function update(Post $post)
{
    // The current user may update the post...
}
```

<a name="middleware-actions-that-dont-require-models"></a>
#### Дії, що не потребують моделей

Знову ж таки: деяким методам політики на кшталт `create` не потрібен екземпляр моделі. У таких випадках ви можете передати `middleware` ім'я класу. За ним буде визначено, яку політику використати для авторизації дії:

```php
Route::post('/post', function () {
    // The current user may create posts...
})->middleware('can:create,App\Models\Post');
```

Указувати повне ім'я класу в рядковому визначенні `middleware` буває незручно. Тому ви можете додати `middleware` `can` до маршруту методом `can`:

```php
use App\Models\Post;

Route::post('/post', function () {
    // The current user may create posts...
})->can('create', Post::class);
```

<a name="via-blade-templates"></a>
### Через Blade-шаблони

Пишучи Blade-шаблони, ви можете захотіти показувати частину сторінки лише тоді, коли користувач має право виконати задану дію. Наприклад, ви можете показувати форму оновлення допису лише тим, хто справді може його оновити. У такому разі скористайтеся директивами `@can` та `@cannot`:

```blade
@can('update', $post)
    <!-- The current user can update the post... -->
@elsecan('create', App\Models\Post::class)
    <!-- The current user can create new posts... -->
@else
    <!-- ... -->
@endcan

@cannot('update', $post)
    <!-- The current user cannot update the post... -->
@elsecannot('create', App\Models\Post::class)
    <!-- The current user cannot create new posts... -->
@endcannot
```

Ці директиви - зручні скорочення для конструкцій `@if` та `@unless`. Наведені вище конструкції `@can` і `@cannot` рівносильні таким:

```blade
@if (Auth::user()->can('update', $post))
    <!-- The current user can update the post... -->
@endif

@unless (Auth::user()->can('update', $post))
    <!-- The current user cannot update the post... -->
@endunless
```

Ви також можете визначити, чи має користувач право виконати будь-яку дію із заданого масиву дій. Для цього скористайтеся директивою `@canany`:

```blade
@canany(['update', 'view', 'delete'], $post)
    <!-- The current user can update, view, or delete the post... -->
@elsecanany(['create'], \App\Models\Post::class)
    <!-- The current user can create a post... -->
@endcanany
```

<a name="blade-actions-that-dont-require-models"></a>
#### Дії, що не потребують моделей

Як і в більшості інших методів авторизації, ви можете передати директивам `@can` і `@cannot` ім'я класу, якщо дії не потрібен екземпляр моделі:

```blade
@can('create', App\Models\Post::class)
    <!-- The current user can create posts... -->
@endcan

@cannot('create', App\Models\Post::class)
    <!-- The current user can't create posts... -->
@endcannot
```

<a name="supplying-additional-context"></a>
### Передавання додаткового контексту

Авторизуючи дії через політики, ви можете передати масив другим аргументом до різних функцій і хелперів авторизації. Перший елемент масиву визначатиме, яку політику викликати, а решта елементів передаються параметрами до методу політики й можуть слугувати додатковим контекстом при ухваленні рішень щодо авторизації. Розгляньмо, наприклад, таке визначення методу `PostPolicy` із додатковим параметром `$category`:

```php
/**
 * Determine if the given post can be updated by the user.
 */
public function update(User $user, Post $post, int $category): bool
{
    return $user->id === $post->user_id &&
           $user->canUpdateCategory($category);
}
```

Намагаючись визначити, чи може автентифікований користувач оновити заданий допис, ми можемо викликати цей метод політики так:

```php
/**
 * Update the given blog post.
 *
 * @throws \Illuminate\Auth\Access\AuthorizationException
 */
public function update(Request $request, Post $post): RedirectResponse
{
    Gate::authorize('update', [$post, $request->category]);

    // The current user can update the blog post...

    return redirect('/posts');
}
```

<a name="authorization-and-inertia"></a>
## Авторизація та Inertia

Хоча авторизацію завжди слід виконувати на сервері, часто буває зручно передати вашому фронтенду дані авторизації, щоб належно відрендерити UI застосунку. Laravel не встановлює обов'язкової угоди щодо того, як надавати інформацію про авторизацію фронтенду на Inertia.

Проте якщо ви користуєтеся одним зі [стартових наборів](/docs/{{version}}/starter-kits) Laravel на основі Inertia, ваш застосунок уже містить `middleware` `HandleInertiaRequests`. У методі `share` цього `middleware` ви можете повернути спільні дані, які надаватимуться всім сторінкам Inertia у вашому застосунку. Ці спільні дані - зручне місце, щоб описати інформацію про авторизацію користувача:

```php
<?php

namespace App\Http\Middleware;

use App\Models\Post;
use Illuminate\Http\Request;
use Inertia\Middleware;

class HandleInertiaRequests extends Middleware
{
    // ...

    /**
     * Define the props that are shared by default.
     *
     * @return array<string, mixed>
     */
    public function share(Request $request)
    {
        return [
            ...parent::share($request),
            'auth' => [
                'user' => $request->user(),
                'permissions' => [
                    'post' => [
                        'create' => $request->user()->can('create', Post::class),
                    ],
                ],
            ],
        ];
    }
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/verification

# Підтвердження електронної пошти

- [Вступ](#introduction)
    - [Підготовка моделі](#model-preparation)
    - [Підготовка бази даних](#database-preparation)
- [Маршрутизація](#verification-routing)
    - [Повідомлення про підтвердження пошти](#the-email-verification-notice)
    - [Обробник підтвердження пошти](#the-email-verification-handler)
    - [Повторне надсилання листа з підтвердженням](#resending-the-verification-email)
    - [Захист маршрутів](#protecting-routes)
- [Налаштування](#customization)
- [Події](#events)

<a name="introduction"></a>
## Вступ

Багато вебзастосунків вимагають, щоб користувачі підтвердили свої адреси електронної пошти, перш ніж почати користуватися застосунком. Замість того щоб змушувати вас реалізовувати цю можливість вручну в кожному новому застосунку, Laravel надає зручні вбудовані сервіси для надсилання та перевірки запитів на підтвердження пошти.

> [!NOTE]
> Хочете швидко почати? Встановіть один зі [стартових наборів застосунку Laravel](/docs/{{version}}/starter-kits) у свіжий застосунок Laravel. Стартові набори створять усю вашу систему автентифікації, зокрема й підтримку підтвердження електронної пошти.

<a name="model-preparation"></a>
### Підготовка моделі

Перш ніж почати, переконайтеся, що ваша модель `App\Models\User` реалізує контракт `Illuminate\Contracts\Auth\MustVerifyEmail`:

```php
<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable implements MustVerifyEmail
{
    use Notifiable;

    // ...
}
```

Щойно цей інтерфейс додано до вашої моделі, новозареєстрованим користувачам автоматично надсилатиметься лист із посиланням для підтвердження пошти. Це відбувається непомітно, бо Laravel автоматично реєструє [слухач](/docs/{{version}}/events) `Illuminate\Auth\Listeners\SendEmailVerificationNotification` для події `Illuminate\Auth\Events\Registered`.

Якщо ви реалізуєте реєстрацію у своєму застосунку вручну, а не через [стартовий набір](/docs/{{version}}/starter-kits), переконайтеся, що після успішної реєстрації користувача ви відправляєте подію `Illuminate\Auth\Events\Registered`:

```php
use Illuminate\Auth\Events\Registered;

event(new Registered($user));
```

<a name="database-preparation"></a>
### Підготовка бази даних

Далі ваша таблиця `users` має містити стовпець `email_verified_at`, щоб зберігати дату й час підтвердження адреси електронної пошти користувача. Зазвичай він уже є в стандартній міграції Laravel `0001_01_01_000000_create_users_table.php`.

<a name="verification-routing"></a>
## Маршрутизація

Щоб належно реалізувати підтвердження електронної пошти, знадобиться визначити три маршрути. Перший маршрут показуватиме користувачеві повідомлення про те, що йому слід натиснути посилання для підтвердження в листі, який Laravel надіслав після реєстрації.

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

Третій маршрут повторно надсилатиме посилання для підтвердження, якщо користувач випадково втратив перше.

<a name="the-email-verification-notice"></a>
### Повідомлення про підтвердження пошти

Як згадувалося раніше, слід визначити маршрут, який повертатиме представлення з проханням натиснути посилання для підтвердження, надіслане користувачеві після реєстрації. Це представлення показуватиметься користувачам, коли вони спробують дістатися інших частин застосунку, не підтвердивши спершу свою адресу електронної пошти. Пам'ятайте: посилання надсилається користувачеві автоматично, якщо ваша модель `App\Models\User` реалізує інтерфейс `MustVerifyEmail`:

```php
Route::get('/email/verify', function () {
    return view('auth.verify-email');
})->middleware('auth')->name('verification.notice');
```

Маршрут, що повертає повідомлення про підтвердження пошти, має називатися `verification.notice`. Важливо дати маршруту саме це ім'я, бо `middleware` `verified` [зі складу Laravel](#protecting-routes) автоматично перенаправлятиме на маршрут із цим іменем, якщо користувач не підтвердив свою адресу електронної пошти.

> [!NOTE]
> Реалізуючи підтвердження пошти вручну, ви маєте самі визначити вміст представлення з повідомленням. Якщо вам потрібен готовий каркас з усіма потрібними представленнями автентифікації та підтвердження, погляньте на [стартові набори застосунку Laravel](/docs/{{version}}/starter-kits).

<a name="the-email-verification-handler"></a>
### Обробник підтвердження пошти

Далі нам треба визначити маршрут, який оброблятиме запити, що виникають, коли користувач натискає надіслане йому посилання для підтвердження. Цей маршрут має називатися `verification.verify` і мати `middleware` `auth` та `signed`:

```php
use Illuminate\Foundation\Auth\EmailVerificationRequest;

Route::get('/email/verify/{id}/{hash}', function (EmailVerificationRequest $request) {
    $request->fulfill();

    return redirect('/home');
})->middleware(['auth', 'signed'])->name('verification.verify');
```

Перш ніж рухатися далі, розгляньмо цей маршрут ближче. По-перше, ви помітите, що ми використовуємо тип запиту `EmailVerificationRequest` замість звичного екземпляра `Illuminate\Http\Request`. `EmailVerificationRequest` - це [запит форми](/docs/{{version}}/validation#form-request-validation) зі складу Laravel. Він автоматично подбає про валідацію параметрів запиту `id` та `hash`.

Далі ми можемо одразу викликати на запиті метод `fulfill`. Цей метод викличе метод `markEmailAsVerified` на автентифікованому користувачі й відправить подію `Illuminate\Auth\Events\Verified`. Метод `markEmailAsVerified` доступний стандартній моделі `App\Models\User` через базовий клас `Illuminate\Foundation\Auth\User`. Щойно адресу електронної пошти користувача підтверджено, ви можете перенаправити його куди завгодно.

<a name="resending-the-verification-email"></a>
### Повторне надсилання листа з підтвердженням

Іноді користувач може загубити чи випадково видалити лист із підтвердженням адреси. Про такий випадок варто подбати, визначивши маршрут, який дозволить користувачеві попросити надіслати лист повторно. Далі ви можете звертатися до цього маршруту, розмістивши просту кнопку надсилання форми у вашому [представленні з повідомленням про підтвердження](#the-email-verification-notice):

```php
use Illuminate\Http\Request;

Route::post('/email/verification-notification', function (Request $request) {
    $request->user()->sendEmailVerificationNotification();

    return back()->with('message', 'Verification link sent!');
})->middleware(['auth', 'throttle:6,1'])->name('verification.send');
```

<a name="protecting-routes"></a>
### Захист маршрутів

[Маршрутне `middleware`](/docs/{{version}}/middleware) дозволяє пускати на певний маршрут лише користувачів із підтвердженою поштою. Laravel містить [аліас `middleware`](/docs/{{version}}/middleware#middleware-aliases) `verified` - це аліас для класу `middleware` `Illuminate\Auth\Middleware\EnsureEmailIsVerified`. Оскільки Laravel уже реєструє цей аліас автоматично, вам залишається тільки додати `middleware` `verified` до визначення маршруту. Зазвичай його поєднують із `middleware` `auth`:

```php
Route::get('/profile', function () {
    // Only verified users may access this route...
})->middleware(['auth', 'verified']);
```

Якщо користувач із непідтвердженою поштою спробує дістатися маршруту з цим `middleware`, його автоматично перенаправить на [іменований маршрут](/docs/{{version}}/routing#named-routes) `verification.notice`.

<a name="customization"></a>
## Налаштування

<a name="verification-email-customization"></a>
#### Налаштування листа з підтвердженням

Хоча стандартне сповіщення про підтвердження пошти задовольняє вимоги більшості застосунків, Laravel дозволяє налаштувати те, як формується поштове повідомлення з підтвердженням.

Для початку передайте замикання методу `toMailUsing`, який надає сповіщення `Illuminate\Auth\Notifications\VerifyEmail`. Замикання отримає екземпляр моделі-отримувача сповіщення, а також підписаний URL підтвердження, за яким користувач має перейти, щоб підтвердити свою адресу. Замикання має повернути екземпляр `Illuminate\Notifications\Messages\MailMessage`. Зазвичай метод `toMailUsing` викликають у методі `boot` класу `AppServiceProvider` вашого застосунку:

```php
use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Notifications\Messages\MailMessage;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    // ...

    VerifyEmail::toMailUsing(function (object $notifiable, string $url) {
        return (new MailMessage)
            ->subject('Verify Email Address')
            ->line('Click the button below to verify your email address.')
            ->action('Verify Email Address', $url);
    });
}
```

> [!NOTE]
> Щоб дізнатися більше про поштові сповіщення, зверніться до [документації про поштові сповіщення](/docs/{{version}}/notifications#mail-notifications).

<a name="events"></a>
## Події

Коли ви користуєтеся [стартовими наборами застосунку Laravel](/docs/{{version}}/starter-kits), Laravel відправляє [подію](/docs/{{version}}/events) `Illuminate\Auth\Events\Verified` під час процесу підтвердження пошти. Якщо ви обробляєте підтвердження пошти у своєму застосунку вручну, вам може знадобитися відправляти ці події самостійно після завершення підтвердження.

---

Джерело: https://laravelukraine.com/docs/13.x/encryption

# Шифрування

- [Вступ](#introduction)
- [Конфігурація](#configuration)
    - [Плавна ротація ключів шифрування](#gracefully-rotating-encryption-keys)
- [Використання шифрувальника](#using-the-encrypter)

<a name="introduction"></a>
## Вступ

Сервіси шифрування Laravel надають простий і зручний інтерфейс для шифрування та розшифрування тексту через OpenSSL із використанням AES-256 та AES-128. Усі зашифровані Laravel значення підписуються кодом автентифікації повідомлення (MAC), тож після шифрування їхнє початкове значення не можна змінити чи підробити.

<a name="configuration"></a>
## Конфігурація

Перш ніж користуватися шифрувальником Laravel, ви маєте задати опцію конфігурації `key` у файлі `config/app.php`. Це значення береться зі змінної оточення `APP_KEY`. Згенерувати значення цієї змінної варто командою `php artisan key:generate`, адже команда `key:generate` використовує безпечний генератор випадкових байтів PHP і створює криптографічно стійкий ключ для вашого застосунку. Зазвичай значення змінної оточення `APP_KEY` генерується автоматично під час [встановлення Laravel](/docs/{{version}}/installation).

<a name="gracefully-rotating-encryption-keys"></a>
### Плавна ротація ключів шифрування

Якщо ви зміните ключ шифрування застосунку, усі автентифіковані сесії користувачів завершаться. Так стається тому, що Laravel шифрує кожен cookie, зокрема й сесійні. Крім того, стане неможливо розшифрувати будь-які дані, зашифровані попереднім ключем.

Щоб пом'якшити цю проблему, Laravel дозволяє перелічити ваші попередні ключі шифрування у змінній оточення `APP_PREVIOUS_KEYS`. Ця змінна може містити список усіх ваших попередніх ключів шифрування через кому:

```ini
APP_KEY="base64:J63qRTDLub5NuZvP+kb8YIorGS6qFYHKVo6u7179stY="
APP_PREVIOUS_KEYS="base64:2nLsGFGzyoae2ax3EF2Lyq/hH6QghBGLIq5uL+Gp8/w="
```

Коли ви задаєте цю змінну оточення, Laravel завжди шифруватиме значення «поточним» ключем. Проте під час розшифрування Laravel спершу спробує поточний ключ, а якщо з ним розшифрувати не вдасться - перебере всі попередні ключі, доки один із них не розшифрує значення.

Такий підхід до плавного розшифрування дозволяє користувачам працювати з вашим застосунком без перебоїв, навіть якщо ключ шифрування було змінено.

<a name="using-the-encrypter"></a>
## Використання шифрувальника

<a name="encrypting-a-value"></a>
#### Шифрування значення

Зашифрувати значення можна методом `encryptString`, який надає фасад `Crypt`. Усі значення шифруються через OpenSSL шифром AES-256-CBC. Крім того, всі зашифровані значення підписуються кодом автентифікації повідомлення (MAC). Вбудований код автентифікації повідомлення не дасть розшифрувати значення, які зловмисники намагалися підробити:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;

class DigitalOceanTokenController extends Controller
{
    /**
     * Store a DigitalOcean API token for the user.
     */
    public function store(Request $request): RedirectResponse
    {
        $request->user()->fill([
            'token' => Crypt::encryptString($request->token),
        ])->save();

        return redirect('/secrets');
    }
}
```

<a name="decrypting-a-value"></a>
#### Розшифрування значення

Розшифрувати значення можна методом `decryptString`, який надає фасад `Crypt`. Якщо значення не вдається коректно розшифрувати - наприклад, коли код автентифікації повідомлення недійсний, - буде кинуто виняток `Illuminate\Contracts\Encryption\DecryptException`:

```php
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Support\Facades\Crypt;

try {
    $decrypted = Crypt::decryptString($encryptedValue);
} catch (DecryptException $e) {
    // ...
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/hashing

# Хешування

- [Вступ](#introduction)
- [Конфігурація](#configuration)
- [Базове використання](#basic-usage)
    - [Хешування паролів](#hashing-passwords)
    - [Перевірка відповідності пароля хешу](#verifying-that-a-password-matches-a-hash)
    - [Визначення потреби в перехешуванні пароля](#determining-if-a-password-needs-to-be-rehashed)
- [Перевірка алгоритму хешування](#hash-algorithm-verification)

<a name="introduction"></a>
## Вступ

[Фасад](/docs/{{version}}/facades) `Hash` у Laravel надає безпечне хешування Bcrypt та Argon2 для зберігання паролів користувачів. Якщо ви користуєтеся одним зі [стартових наборів застосунку Laravel](/docs/{{version}}/starter-kits), для реєстрації та автентифікації за замовчуванням використовуватиметься Bcrypt.

Bcrypt - чудовий вибір для хешування паролів, бо його «фактор складності» (work factor) можна регулювати: час генерування хешу можна збільшувати в міру зростання потужності обладнання. Коли йдеться про хешування паролів, повільно - це добре. Що довше алгоритм хешує пароль, то більше часу потрібно зловмисникам, щоб згенерувати «райдужні таблиці» з усіма можливими хешами рядків, які використовують в атаках перебором на застосунки.

<a name="configuration"></a>
## Конфігурація

За замовчуванням Laravel хешує дані драйвером `bcrypt`. Проте підтримуються й інші драйвери хешування, зокрема [argon](https://en.wikipedia.org/wiki/Argon2) та [argon2id](https://en.wikipedia.org/wiki/Argon2).

Драйвер хешування вашого застосунку можна вказати через змінну оточення `HASH_DRIVER`. Але якщо ви хочете налаштувати всі опції драйверів хешування Laravel, опублікуйте повний конфігураційний файл `hashing` артизан-командою `config:publish`:

```shell
php artisan config:publish hashing
```

<a name="basic-usage"></a>
## Базове використання

<a name="hashing-passwords"></a>
### Хешування паролів

Захешувати пароль можна викликом методу `make` на фасаді `Hash`:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;

class PasswordController extends Controller
{
    /**
     * Update the password for the user.
     */
    public function update(Request $request): RedirectResponse
    {
        // Validate the new password length...

        $request->user()->fill([
            'password' => Hash::make($request->newPassword)
        ])->save();

        return redirect('/profile');
    }
}
```

<a name="adjusting-the-bcrypt-work-factor"></a>
#### Налаштування фактора складності Bcrypt

Якщо ви користуєтеся алгоритмом Bcrypt, метод `make` дозволяє керувати фактором складності алгоритму через опцію `rounds`; проте значення за замовчуванням, яке підтримує Laravel, прийнятне для більшості застосунків:

```php
$hashed = Hash::make('password', [
    'rounds' => 12,
]);
```

<a name="adjusting-the-argon2-work-factor"></a>
#### Налаштування фактора складності Argon2

Якщо ви користуєтеся алгоритмом Argon2, метод `make` дозволяє керувати фактором складності алгоритму через опції `memory`, `time` та `threads`; проте значення за замовчуванням, які підтримує Laravel, прийнятні для більшості застосунків:

```php
$hashed = Hash::make('password', [
    'memory' => 1024,
    'time' => 2,
    'threads' => 2,
]);
```

> [!NOTE]
> Докладніше про ці опції читайте в [офіційній документації PHP щодо хешування Argon](https://secure.php.net/manual/en/function.password-hash.php).

<a name="verifying-that-a-password-matches-a-hash"></a>
### Перевірка відповідності пароля хешу

Метод `check`, який надає фасад `Hash`, дозволяє перевірити, чи відповідає заданий рядок відкритим текстом заданому хешу:

```php
if (Hash::check('plain-text', $hashedPassword)) {
    // The passwords match...
}
```

<a name="determining-if-a-password-needs-to-be-rehashed"></a>
### Визначення потреби в перехешуванні пароля

Метод `needsRehash`, який надає фасад `Hash`, дозволяє визначити, чи змінився фактор складності хешувальника відтоді, як пароль було захешовано. Деякі застосунки виконують цю перевірку під час автентифікації:

```php
if (Hash::needsRehash($hashed)) {
    $hashed = Hash::make('plain-text');
}
```

<a name="hash-algorithm-verification"></a>
## Перевірка алгоритму хешування

Щоб запобігти маніпуляціям з алгоритмом хешування, метод `Hash::check` у Laravel спершу перевіряє, чи було заданий хеш згенеровано обраним у застосунку алгоритмом хешування. Якщо алгоритми різні, буде кинуто виняток `RuntimeException`.

Саме такої поведінки очікують у більшості застосунків, де алгоритм хешування не має змінюватися, а інший алгоритм може свідчити про атаку зловмисника. Проте якщо вашому застосунку потрібно підтримувати кілька алгоритмів хешування - наприклад, під час переходу з одного алгоритму на інший, - ви можете вимкнути перевірку алгоритму, встановивши змінну оточення `HASH_VERIFY` у значення `false`:

```ini
HASH_VERIFY=false
```

---

Джерело: https://laravelukraine.com/docs/13.x/passwords

# Скидання паролів

- [Вступ](#introduction)
    - [Конфігурація](#configuration)
    - [Передумови для драйверів](#driver-prerequisites)
    - [Підготовка моделі](#model-preparation)
    - [Налаштування довірених хостів](#configuring-trusted-hosts)
- [Маршрутизація](#routing)
    - [Запит посилання для скидання пароля](#requesting-the-password-reset-link)
    - [Скидання пароля](#resetting-the-password)
- [Видалення прострочених токенів](#deleting-expired-tokens)
- [Налаштування](#password-customization)

<a name="introduction"></a>
## Вступ

Більшість вебзастосунків дають користувачам змогу скинути забутий пароль. Замість того щоб змушувати вас реалізовувати це вручну в кожному новому застосунку, Laravel надає зручні сервіси для надсилання посилань на скидання пароля та безпечного скидання паролів.

> [!NOTE]
> Хочете швидко почати? Встановіть [стартовий набір застосунку](/docs/{{version}}/starter-kits) Laravel у свіжий застосунок Laravel. Стартові набори Laravel створять усю вашу систему автентифікації, зокрема й скидання забутих паролів.

<a name="configuration"></a>
### Конфігурація

Конфігураційний файл скидання паролів вашого застосунку розташований у `config/auth.php`. Обов'язково перегляньте доступні в ньому опції. За замовчуванням Laravel налаштований на драйвер скидання паролів `database`.

Опція конфігурації `driver` для скидання паролів визначає, де зберігатимуться дані скидання. Laravel містить два драйвери:

<div class="content-list" markdown="1">

- `database` - дані скидання пароля зберігаються в реляційній базі даних.
- `cache` - дані скидання пароля зберігаються в одному з ваших сховищ кешу.

</div>

<a name="driver-prerequisites"></a>
### Передумови для драйверів

<a name="database"></a>
#### База даних

Коли ви користуєтеся драйвером `database` за замовчуванням, потрібно створити таблицю для зберігання токенів скидання паролів вашого застосунку. Зазвичай вона вже є в стандартній міграції Laravel `0001_01_01_000000_create_users_table.php`.

<a name="cache"></a>
#### Кеш

Для обробки скидання паролів доступний також драйвер кешу, який не потребує окремої таблиці в базі даних. Ключем записів є SHA-256-хеш адреси електронної пошти користувача:

```php
'passwords' => [
    'users' => [
        'driver' => 'cache',
        'provider' => 'users',
        'store' => 'passwords', // Optional...
        'expire' => 60,
        'throttle' => 60,
    ],
],
```

Щоб виклик `artisan cache:clear` не змив ваші дані скидання паролів, ви можете за бажанням вказати окреме сховище кешу ключем конфігурації `store`. Значення має відповідати сховищу, налаштованому у вашому конфігураційному файлі `config/cache.php`.

<a name="model-preparation"></a>
### Підготовка моделі

Перш ніж користуватися можливостями скидання паролів у Laravel, модель `App\Models\User` вашого застосунку має використовувати трейт `Illuminate\Notifications\Notifiable`. Зазвичай цей трейт уже підключено до стандартної моделі `App\Models\User`, яка створюється в нових застосунках Laravel.

Далі переконайтеся, що ваша модель `App\Models\User` реалізує контракт `Illuminate\Contracts\Auth\CanResetPassword`. Модель `App\Models\User`, що входить до фреймворку, вже реалізує цей інтерфейс і використовує трейт `Illuminate\Auth\Passwords\CanResetPassword`, який містить потрібні для цього методи.

<a name="configuring-trusted-hosts"></a>
### Налаштування довірених хостів

За замовчуванням Laravel відповідає на всі отримані запити незалежно від вмісту заголовка `Host` HTTP-запиту. До того ж значення заголовка `Host` використовується для генерування абсолютних URL до вашого застосунку під час вебзапиту.

Зазвичай ваш вебсервер (наприклад, Nginx чи Apache) варто налаштувати так, щоб він надсилав до застосунку лише запити, які відповідають заданому імені хоста. Проте якщо ви не можете налаштувати вебсервер напряму й вам потрібно вказати Laravel відповідати лише певним іменам хостів, скористайтеся методом `middleware` `trustHosts` у файлі `bootstrap/app.php` вашого застосунку. Це особливо важливо, коли ваш застосунок пропонує скидання паролів.

Щоб дізнатися більше про цей метод, зверніться до [документації про `middleware` TrustHosts](/docs/{{version}}/requests#configuring-trusted-hosts).

<a name="routing"></a>
## Маршрутизація

Щоб належно реалізувати підтримку скидання паролів користувачами, нам знадобиться визначити кілька маршрутів. Спершу потрібна пара маршрутів, які дозволять користувачеві запросити посилання на скидання пароля за адресою електронної пошти. Далі потрібна пара маршрутів, які власне скидатимуть пароль, коли користувач перейде за надісланим йому посиланням і заповнить форму скидання.

<a name="requesting-the-password-reset-link"></a>
### Запит посилання для скидання пароля

<a name="the-password-reset-link-request-form"></a>
#### Форма запиту посилання на скидання пароля

Спершу визначимо маршрути, потрібні для запиту посилань на скидання пароля. Для початку визначимо маршрут, який повертає представлення з формою запиту посилання:

```php
Route::get('/forgot-password', function () {
    return view('auth.forgot-password');
})->middleware('guest')->name('password.request');
```

Представлення, яке повертає цей маршрут, має містити форму з полем `email` - воно дозволить користувачеві запросити посилання на скидання пароля для заданої адреси електронної пошти.

<a name="password-reset-link-handling-the-form-submission"></a>
#### Обробка надсилання форми

Далі визначимо маршрут, який оброблятиме надсилання форми з представлення «забув пароль». Цей маршрут відповідатиме за валідацію адреси електронної пошти та надсилання запиту на скидання пароля відповідному користувачеві:

```php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;

Route::post('/forgot-password', function (Request $request) {
    $request->validate(['email' => 'required|email']);

    $status = Password::sendResetLink(
        $request->only('email')
    );

    return $status === Password::ResetLinkSent
        ? back()->with(['status' => __($status)])
        : back()->withErrors(['email' => __($status)]);
})->middleware('guest')->name('password.email');
```

Перш ніж рухатися далі, розгляньмо цей маршрут докладніше. Спершу валідується атрибут запиту `email`. Далі ми скористаємося вбудованим у Laravel «брокером паролів» (через фасад `Password`), щоб надіслати користувачеві посилання на скидання пароля. Брокер паролів подбає про пошук користувача за заданим полем (у цьому випадку - за адресою електронної пошти) і надішле йому посилання через вбудовану [систему сповіщень](/docs/{{version}}/notifications) Laravel.

Метод `sendResetLink` повертає слаг «статусу». Цей статус можна перекласти за допомогою хелперів [локалізації](/docs/{{version}}/localization) Laravel, щоб показати користувачеві зрозуміле повідомлення про стан його запиту. Переклад статусу скидання пароля визначається мовним файлом `lang/{lang}/passwords.php` вашого застосунку. Запис для кожного можливого значення слага статусу міститься у мовному файлі `passwords`.

> [!NOTE]
> За замовчуванням каркас застосунку Laravel не містить каталогу `lang`. Якщо ви хочете налаштувати мовні файли Laravel, опублікуйте їх артизан-командою `lang:publish`.

Ви можете замислитися, звідки Laravel знає, як дістати запис користувача з бази даних вашого застосунку при виклику методу `sendResetLink` фасада `Password`. Брокер паролів Laravel використовує «провайдери користувачів» вашої системи автентифікації, щоб отримувати записи з бази даних. Провайдер користувачів, який використовує брокер паролів, налаштовується в масиві конфігурації `passwords` вашого файлу `config/auth.php`. Щоб дізнатися більше про написання власних провайдерів користувачів, зверніться до [документації з автентифікації](/docs/{{version}}/authentication#adding-custom-user-providers).

> [!NOTE]
> Реалізуючи скидання паролів вручну, ви маєте самі визначити вміст представлень і маршрутів. Якщо вам потрібен готовий каркас з усією логікою автентифікації та підтвердження, погляньте на [стартові набори застосунку Laravel](/docs/{{version}}/starter-kits).

<a name="resetting-the-password"></a>
### Скидання пароля

<a name="the-password-reset-form"></a>
#### Форма скидання пароля

Далі визначимо маршрути, потрібні для власне скидання пароля після того, як користувач перейде за надісланим йому посиланням і введе новий пароль. Спершу визначмо маршрут, який показуватиме форму скидання пароля при переході за посиланням. Цей маршрут отримає параметр `token`, який ми пізніше використаємо для перевірки запиту на скидання:

```php
Route::get('/reset-password/{token}', function (string $token) {
    return view('auth.reset-password', ['token' => $token]);
})->middleware('guest')->name('password.reset');
```

Представлення, яке повертає цей маршрут, має показувати форму з полями `email`, `password`, `password_confirmation` і прихованим полем `token`, що містить значення секретного `$token`, отриманого нашим маршрутом.

<a name="password-reset-handling-the-form-submission"></a>
#### Обробка надсилання форми

Звісно, нам треба визначити маршрут, який власне оброблятиме надсилання форми скидання пароля. Цей маршрут відповідатиме за валідацію вхідного запиту та оновлення пароля користувача в базі даних:

```php
use App\Models\User;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;

Route::post('/reset-password', function (Request $request) {
    $request->validate([
        'token' => 'required',
        'email' => 'required|email',
        'password' => 'required|min:8|confirmed',
    ]);

    $status = Password::reset(
        $request->only('email', 'password', 'password_confirmation', 'token'),
        function (User $user, string $password) {
            $user->forceFill([
                'password' => Hash::make($password)
            ])->setRememberToken(Str::random(60));

            $user->save();

            event(new PasswordReset($user));
        }
    );

    return $status === Password::PasswordReset
        ? redirect()->route('login')->with('status', __($status))
        : back()->withErrors(['email' => [__($status)]]);
})->middleware('guest')->name('password.update');
```

Перш ніж рухатися далі, розгляньмо цей маршрут докладніше. Спершу валідуються атрибути запиту `token`, `email` і `password`. Далі ми скористаємося вбудованим у Laravel «брокером паролів» (через фасад `Password`), щоб перевірити облікові дані запиту на скидання пароля.

Якщо переданий брокеру паролів токен, адреса електронної пошти та пароль дійсні, буде викликано замикання, передане методу `reset`. Усередині цього замикання, яке отримує екземпляр користувача та введений у форму пароль відкритим текстом, ми можемо оновити пароль користувача в базі даних.

Метод `reset` повертає слаг «статусу». Цей статус можна перекласти за допомогою хелперів [локалізації](/docs/{{version}}/localization) Laravel, щоб показати користувачеві зрозуміле повідомлення про стан його запиту. Переклад статусу скидання пароля визначається мовним файлом `lang/{lang}/passwords.php` вашого застосунку. Запис для кожного можливого значення слага статусу міститься у мовному файлі `passwords`. Якщо у вашому застосунку немає каталогу `lang`, створіть його артизан-командою `lang:publish`.

Перш ніж рухатися далі, ви можете замислитися, звідки Laravel знає, як дістати запис користувача з бази даних вашого застосунку при виклику методу `reset` фасада `Password`. Брокер паролів Laravel використовує «провайдери користувачів» вашої системи автентифікації, щоб отримувати записи з бази даних. Провайдер користувачів, який використовує брокер паролів, налаштовується в масиві конфігурації `passwords` вашого файлу `config/auth.php`. Щоб дізнатися більше про написання власних провайдерів користувачів, зверніться до [документації з автентифікації](/docs/{{version}}/authentication#adding-custom-user-providers).

<a name="deleting-expired-tokens"></a>
## Видалення прострочених токенів

Якщо ви користуєтеся драйвером `database`, прострочені токени скидання паролів усе одно залишатимуться у вашій базі даних. Проте ви можете легко видалити ці записи артизан-командою `auth:clear-resets`:

```shell
php artisan auth:clear-resets
```

Якщо ви хочете автоматизувати цей процес, подумайте про додавання команди до [планувальника](/docs/{{version}}/scheduling) вашого застосунку:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('auth:clear-resets')->everyFifteenMinutes();
```

<a name="password-customization"></a>
## Налаштування

<a name="reset-link-customization"></a>
#### Налаштування посилання на скидання

Ви можете налаштувати URL посилання на скидання пароля методом `createUrlUsing`, який надає клас сповіщення `ResetPassword`. Цей метод приймає замикання, що отримує екземпляр користувача - отримувача сповіщення - і токен посилання на скидання пароля. Зазвичай цей метод викликають у методі `boot` вашого `AppServiceProvider`:

```php
use App\Models\User;
use Illuminate\Auth\Notifications\ResetPassword;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    ResetPassword::createUrlUsing(function (User $user, string $token) {
        return 'https://example.com/reset-password?token='.$token;
    });
}
```

<a name="reset-email-customization"></a>
#### Налаштування листа про скидання

Ви можете легко змінити клас сповіщення, яким користувачеві надсилається посилання на скидання пароля. Для початку перевизначте метод `sendPasswordResetNotification` на вашій моделі `App\Models\User`. Усередині цього методу ви можете надіслати сповіщення будь-яким [класом сповіщення](/docs/{{version}}/notifications) власного авторства. Токен скидання пароля `$token` - перший аргумент, який отримує метод. Ви можете скористатися цим `$token`, щоб побудувати потрібний вам URL скидання пароля й надіслати користувачеві сповіщення:

```php
use App\Notifications\ResetPasswordNotification;

/**
 * Send a password reset notification to the user.
 *
 * @param  string  $token
 */
public function sendPasswordResetNotification($token): void
{
    $url = 'https://example.com/reset-password?token='.$token;

    $this->notify(new ResetPasswordNotification($url));
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/database

# База даних: початок роботи

- [Вступ](#introduction)
    - [Конфігурація](#configuration)
    - [Підключення для читання та запису](#read-and-write-connections)
    - [Пулінг підключень PostgreSQL](#pooled-postgresql-connections)
- [Виконання SQL-запитів](#running-queries)
    - [Кілька підключень до бази даних](#using-multiple-database-connections)
    - [Прослуховування подій запитів](#listening-for-query-events)
    - [Моніторинг сумарного часу запитів](#monitoring-cumulative-query-time)
- [Транзакції](#database-transactions)
- [Підключення до CLI бази даних](#connecting-to-the-database-cli)
- [Огляд баз даних](#inspecting-your-databases)
- [Моніторинг баз даних](#monitoring-your-databases)

<a name="introduction"></a>
## Вступ

Майже кожен сучасний вебзастосунок працює з базою даних. Laravel робить цю роботу дуже простою для цілої низки підтримуваних баз: через чистий SQL, [конструктор запитів](/docs/{{version}}/queries) та [Eloquent ORM](/docs/{{version}}/eloquent). Наразі Laravel має власну підтримку п'яти баз даних:

<div class="content-list" markdown="1">

- MariaDB 10.3+ ([Політика версій](https://mariadb.org/about/#maintenance-policy))
- MySQL 5.7+ ([Політика версій](https://en.wikipedia.org/wiki/MySQL#Release_history))
- PostgreSQL 10.0+ ([Політика версій](https://www.postgresql.org/support/versioning/))
- SQLite 3.26.0+
- SQL Server 2017+ ([Політика версій](https://docs.microsoft.com/en-us/lifecycle/products/?products=sql-server))

</div>

Крім того, MongoDB підтримується через пакет `mongodb/laravel-mongodb`, який офіційно супроводжує сама MongoDB. Детальніше читайте в документації [Laravel MongoDB](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/).

<a name="configuration"></a>
### Конфігурація

Налаштування сервісів бази даних лежать у файлі `config/database.php` вашого застосунку. У цьому файлі ви описуєте всі підключення до баз даних і вказуєте, яке з них використовується за замовчуванням. Більшість опцій у цьому файлі спираються на значення змінних оточення застосунку. Тут-таки наведено приклади для більшості підтримуваних Laravel баз даних.

За замовчуванням зразкова [конфігурація середовища](/docs/{{version}}/configuration#environment-configuration) готова до роботи з [Laravel Sail](/docs/{{version}}/sail) - це конфігурація Docker для локальної розробки Laravel-застосунків. Втім, ви можете вільно змінити налаштування бази даних під свою локальну базу.

<a name="sqlite-configuration"></a>
#### Налаштування SQLite

База даних SQLite міститься в одному файлі на вашій файловій системі. Створити нову базу SQLite можна командою `touch` у терміналі: `touch database/database.sqlite`. Після цього достатньо вказати абсолютний шлях до бази у змінній оточення `DB_DATABASE`:

```ini
DB_CONNECTION=sqlite
DB_DATABASE=/absolute/path/to/database.sqlite
```

За замовчуванням для підключень SQLite увімкнено обмеження зовнішніх ключів. Щоб їх вимкнути, встановіть змінній оточення `DB_FOREIGN_KEYS` значення `false`:

```ini
DB_FOREIGN_KEYS=false
```

> [!NOTE]
> Якщо ви створюєте застосунок через [інсталятор Laravel](/docs/{{version}}/installation#creating-a-laravel-project) і обираєте SQLite, Laravel сам створить файл `database/database.sqlite` і виконає стандартні [міграції бази даних](/docs/{{version}}/migrations).

<a name="mssql-configuration"></a>
#### Налаштування Microsoft SQL Server

Щоб працювати з базою даних Microsoft SQL Server, переконайтеся, що у вас встановлено PHP-розширення `sqlsrv` і `pdo_sqlsrv`, а також усі потрібні їм залежності - зокрема драйвер Microsoft SQL ODBC.

<a name="configuration-using-urls"></a>
#### Налаштування через URL

Зазвичай підключення до бази даних описують кількома окремими значеннями: `host`, `database`, `username`, `password` тощо. Кожне з них має власну змінну оточення. Це означає, що на продакшн-сервері вам доводиться керувати одразу кількома змінними.

Деякі керовані провайдери баз даних, як-от AWS і Heroku, натомість дають єдиний «URL» бази, у якому одним рядком зібрано всю інформацію про підключення. Виглядає він приблизно так:

```html
mysql://root:password@127.0.0.1/forge?charset=UTF-8
```

Такі URL зазвичай побудовані за стандартною схемою:

```html
driver://username:password@host:port/database?options
```

Для зручності Laravel підтримує ці URL як альтернативу налаштуванню через кілька окремих опцій. Якщо задано опцію `url` (або відповідну змінну оточення `DB_URL`), Laravel візьме з неї дані підключення та облікові дані.

<a name="read-and-write-connections"></a>
### Підключення для читання та запису

Іноді вам потрібне одне підключення для запитів SELECT і зовсім інше - для INSERT, UPDATE та DELETE. Laravel робить це елементарно: потрібне підключення завжди буде обрано правильно, хоч ви пишете чистий SQL, хоч користуєтеся конструктором запитів чи Eloquent ORM.

Погляньмо на приклад, щоб зрозуміти, як налаштовуються підключення для читання та запису:

```php
'mysql' => [
    'driver' => 'mysql',
    
    'read' => [
        'host' => [
            '192.168.1.1',
            '196.168.1.2',
        ],
    ],
    'write' => [
        'host' => [
            '192.168.1.3',
        ],
    ],
    'sticky' => true,
    
    'port' => env('DB_PORT', '3306'),
    'database' => env('DB_DATABASE', 'laravel'),
    'username' => env('DB_USERNAME', 'root'),
    'password' => env('DB_PASSWORD', ''),
    'unix_socket' => env('DB_SOCKET', ''),
    'charset' => env('DB_CHARSET', 'utf8mb4'),
    'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
    'prefix' => '',
    'prefix_indexes' => true,
    'strict' => true,
    'engine' => null,
    'options' => extension_loaded('pdo_mysql') ? array_filter([
        (PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
    ]) : [],
],
```

Зверніть увагу: до масиву конфігурації додалося три ключі - `read`, `write` і `sticky`. Ключі `read` і `write` містять масиви з єдиним ключем `host`. Решту опцій для підключень `read` і `write` буде взято з основного масиву `mysql`.

Складати щось у масиви `read` і `write` потрібно лише тоді, коли ви хочете перевизначити значення з основного масиву `mysql`. Тож у цьому прикладі `192.168.1.1` буде хостом для підключення «read», а `192.168.1.3` - для «write». Облікові дані, префікс, кодування та всі інші опції з основного масиву `mysql` спільні для обох підключень. Якщо в масиві `host` кілька значень, для кожного запиту хост обирається випадково.

<a name="the-sticky-option"></a>
#### Опція `sticky`

`sticky` - це *необов'язкова* опція, яка дозволяє одразу читати записи, щойно записані в базу в межах поточного циклу запиту. Якщо `sticky` увімкнено і протягом поточного циклу запиту вже була операція «write», усі подальші операції «read» підуть через підключення «write». Так дані, записані під час запиту, можна одразу ж прочитати назад у межах цього самого запиту. Чи потрібна така поведінка вашому застосунку - вирішувати вам.

<a name="pooled-postgresql-connections"></a>
### Пулінг підключень PostgreSQL

Багато керованих провайдерів PostgreSQL пропонують пулінг підключень у режимі транзакцій - через PgBouncer або проксіювання підключень. Такі пулери чудово підходять для звичайних запитів застосунку, але частина операцій зі схемою, міграції та команди обслуговування потребують прямого підключення до бази.

Щоб використовувати транзакційний пулер із PostgreSQL, налаштуйте підключення через пул як завжди, а дані прямого підключення передайте в опції `direct`:

```php
'pgsql' => [
    'driver' => 'pgsql',
    // ...
    'pooled' => env('DB_POOLED', false),
    'direct' => array_filter([
        'host' => env('DB_DIRECT_HOST'),
        'port' => env('DB_DIRECT_PORT'),
        'username' => env('DB_DIRECT_USERNAME'),
        'password' => env('DB_DIRECT_PASSWORD'),
        'sslmode' => env('DB_DIRECT_SSLMODE'),
    ]),
],
```

Коли підключення PostgreSQL налаштоване як пульоване, Laravel автоматично вмикає для нього емуляцію підготовлених запитів. Пряме підключення успадковує всі опції, які явно не задані в конфігурації `direct`, і за замовчуванням користується нативними підготовленими запитами.

Laravel автоматично бере пряме підключення для міграцій, дампів і відновлення схеми, а також для `db:wipe`, `db:show` і `db:table`. Команда `db` теж за замовчуванням іде через пряме підключення, якщо увімкнено режим пулінгу й налаштоване пряме підключення; щоб підключитися через пул, передайте опцію `--pooled`:

```shell
php artisan db --pooled
```

Якщо вам потрібно явно скористатися прямим підключенням у застосунку, додайте до назви підключення суфікс `::direct`:

```php
DB::connection('pgsql::direct')->statement('create extension if not exists "uuid-ossp"');
```

<a name="running-queries"></a>
## Виконання SQL-запитів

Коли підключення налаштоване, ви можете виконувати запити до бази через фасад `DB`. Для кожного типу запиту він має свій метод: `select`, `update`, `insert`, `delete` і `statement`.

<a name="running-a-select-query"></a>
#### Виконання запиту SELECT

Щоб виконати базовий запит SELECT, скористайтеся методом `select` фасаду `DB`:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\DB;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * Show a list of all of the application's users.
     */
    public function index(): View
    {
        $users = DB::select('select * from users where active = ?', [1]);

        return view('user.index', ['users' => $users]);
    }
}
```

Перший аргумент методу `select` - це SQL-запит, а другий - прив'язки параметрів, які потрібно підставити в запит. Зазвичай це значення обмежень у `where`. Прив'язка параметрів захищає від SQL-ін'єкцій.

Метод `select` завжди повертає `array` результатів. Кожен результат у масиві - це PHP-об'єкт `stdClass`, який представляє запис із бази даних:

```php
use Illuminate\Support\Facades\DB;

$users = DB::select('select * from users');

foreach ($users as $user) {
    echo $user->name;
}
```

<a name="selecting-scalar-values"></a>
#### Отримання скалярних значень

Іноді запит до бази повертає єдине скалярне значення. Замість того щоб діставати його з об'єкта запису, ви можете отримати значення напряму методом `scalar`:

```php
$burgers = DB::scalar(
    "select count(case when food = 'burger' then 1 end) as burgers from menu"
);
```

<a name="selecting-multiple-result-sets"></a>
#### Отримання кількох наборів результатів

Якщо ваш застосунок викликає збережені процедури, які повертають кілька наборів результатів, скористайтеся методом `selectResultSets` - він поверне всі набори, які віддала процедура:

```php
[$options, $notifications] = DB::selectResultSets(
    "CALL get_user_options_and_notifications(?)", [$request->user()->id]
);
```

<a name="using-named-bindings"></a>
#### Іменовані прив'язки

Замість `?` для прив'язок параметрів ви можете виконувати запит з іменованими прив'язками:

```php
$results = DB::select('select * from users where id = :id', ['id' => 1]);
```

<a name="running-an-insert-statement"></a>
#### Виконання запиту INSERT

Щоб виконати запит `insert`, скористайтеся методом `insert` фасаду `DB`. Як і `select`, цей метод приймає SQL-запит першим аргументом, а прив'язки - другим:

```php
use Illuminate\Support\Facades\DB;

DB::insert('insert into users (id, name) values (?, ?)', [1, 'Marc']);
```

<a name="running-an-update-statement"></a>
#### Виконання запиту UPDATE

Метод `update` оновлює наявні записи в базі даних. Він повертає кількість рядків, які зачепив запит:

```php
use Illuminate\Support\Facades\DB;

$affected = DB::update(
    'update users set votes = 100 where name = ?',
    ['Anita']
);
```

<a name="running-a-delete-statement"></a>
#### Виконання запиту DELETE

Метод `delete` видаляє записи з бази даних. Як і `update`, він повертає кількість зачеплених рядків:

```php
use Illuminate\Support\Facades\DB;

$deleted = DB::delete('delete from users');
```

<a name="running-a-general-statement"></a>
#### Виконання довільного запиту

Деякі запити до бази не повертають жодного значення. Для таких операцій скористайтеся методом `statement` фасаду `DB`:

```php
DB::statement('drop table users');
```

<a name="running-an-unprepared-statement"></a>
#### Виконання непідготовленого запиту

Іноді потрібно виконати SQL-запит без прив'язки значень. Для цього у фасаду `DB` є метод `unprepared`:

```php
DB::unprepared('update users set votes = 100 where name = "Dries"');
```

> [!WARNING]
> Оскільки непідготовлені запити не прив'язують параметри, вони вразливі до SQL-ін'єкцій. Ніколи не допускайте в такий запит значення, які контролює користувач.

<a name="implicit-commits-in-transactions"></a>
#### Неявні коміти

Використовуючи методи `statement` і `unprepared` фасаду `DB` всередині транзакцій, стежте за тим, щоб не виконати запит, який спричиняє [неявний коміт](https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html). Такі запити змушують рушій бази даних непрямо закомітити всю транзакцію, і Laravel втрачає уявлення про поточний рівень вкладеності транзакцій. Приклад такого запиту - створення таблиці:

```php
DB::unprepared('create table a (col varchar(1) null)');
```

[Повний перелік запитів](https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html), які спричиняють неявні коміти, шукайте в посібнику MySQL.

<a name="using-multiple-database-connections"></a>
### Кілька підключень до бази даних

Якщо у вашому файлі `config/database.php` описано кілька підключень, звертатися до кожного з них можна методом `connection` фасаду `DB`. Назва підключення, яку ви передаєте в `connection`, має відповідати одному з підключень, перелічених у `config/database.php` або налаштованих під час виконання через хелпер `config`:

```php
use Illuminate\Support\Facades\DB;

$users = DB::connection('sqlite')->select(/* ... */);
```

Отримати «сирий» PDO-екземпляр, що стоїть за підключенням, можна методом `getPdo`:

```php
$pdo = DB::connection()->getPdo();
```

<a name="listening-for-query-events"></a>
### Прослуховування подій запитів

Якщо ви хочете задати замикання, яке викликатиметься для кожного SQL-запиту вашого застосунку, скористайтеся методом `listen` фасаду `DB`. Це зручно для логування запитів чи налагодження. Зареєструвати замикання-слухач можна в методі `boot` [сервіс-провайдера](/docs/{{version}}/providers):

```php
<?php

namespace App\Providers;

use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        DB::listen(function (QueryExecuted $query) {
            // $query->sql;
            // $query->bindings;
            // $query->time;
            // $query->toRawSql();
        });
    }
}
```

<a name="monitoring-cumulative-query-time"></a>
### Моніторинг сумарного часу запитів

Типове вузьке місце сучасних вебзастосунків - час, який вони витрачають на запити до бази даних. На щастя, Laravel може викликати ваше замикання чи колбек, коли застосунок забагато часу проводить у запитах до бази в межах одного HTTP-запиту. Для цього передайте методу `whenQueryingForLongerThan` поріг часу (у мілісекундах) і замикання. Викликати цей метод можна в методі `boot` [сервіс-провайдера](/docs/{{version}}/providers):

```php
<?php

namespace App\Providers;

use Illuminate\Database\Connection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Events\QueryExecuted;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        DB::whenQueryingForLongerThan(500, function (Connection $connection, QueryExecuted $event) {
            // Notify development team...
        });
    }
}
```

<a name="database-transactions"></a>
## Транзакції

Щоб виконати набір операцій у межах транзакції, скористайтеся методом `transaction` фасаду `DB`. Якщо всередині замикання транзакції буде викинуто виняток, транзакція автоматично відкотиться, а виняток буде викинуто далі. Якщо замикання відпрацює успішно, транзакцію буде автоматично закомічено. З методом `transaction` вам не доведеться вручну дбати про відкат чи коміт:

```php
use Illuminate\Support\Facades\DB;

DB::transaction(function () {
    DB::update('update users set votes = 1');

    DB::delete('delete from posts');
});
```

<a name="handling-deadlocks"></a>
#### Обробка взаємних блокувань

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

```php
use Illuminate\Support\Facades\DB;

DB::transaction(function () {
    DB::update('update users set votes = 1');

    DB::delete('delete from posts');
}, attempts: 5);
```

<a name="manually-using-transactions"></a>
#### Ручне керування транзакціями

Якщо ви хочете розпочати транзакцію вручну й повністю контролювати відкати та коміти, скористайтеся методом `beginTransaction` фасаду `DB`:

```php
use Illuminate\Support\Facades\DB;

DB::beginTransaction();
```

Відкотити транзакцію можна методом `rollBack`:

```php
DB::rollBack();
```

Нарешті, закомітити транзакцію можна методом `commit`:

```php
DB::commit();
```

> [!NOTE]
> Методи транзакцій фасаду `DB` керують транзакціями і для [конструктора запитів](/docs/{{version}}/queries), і для [Eloquent ORM](/docs/{{version}}/eloquent).

<a name="connecting-to-the-database-cli"></a>
## Підключення до CLI бази даних

Щоб підключитися до CLI вашої бази даних, скористайтеся artisan-командою `db`:

```shell
php artisan db
```

За потреби ви можете вказати назву підключення, щоб підключитися не до того, яке використовується за замовчуванням:

```shell
php artisan db mysql
```

<a name="inspecting-your-databases"></a>
## Огляд баз даних

Artisan-команди `db:show` і `db:table` дають цінну інформацію про вашу базу даних та її таблиці. Щоб побачити загальний огляд бази - її розмір, тип, кількість відкритих підключень і зведення по таблицях - скористайтеся командою `db:show`:

```shell
php artisan db:show
```

Вказати, яке саме підключення оглядати, можна опцією `--database` з назвою підключення:

```shell
php artisan db:show --database=pgsql
```

Щоб додати до виводу кількість рядків у таблицях і подробиці про представлення бази даних, передайте опції `--counts` і `--views` відповідно. На великих базах збір кількості рядків і даних про представлення може бути повільним:

```shell
php artisan db:show --counts --views
```

Крім того, для огляду бази даних вам стануть у пригоді такі методи `Schema`:

```php
use Illuminate\Support\Facades\Schema;

$tables = Schema::getTables();
$views = Schema::getViews();
$columns = Schema::getColumns('users');
$indexes = Schema::getIndexes('users');
$foreignKeys = Schema::getForeignKeys('users');
```

Якщо ви хочете оглянути підключення, яке не є підключенням за замовчуванням, скористайтеся методом `connection`:

```php
$columns = Schema::connection('sqlite')->getColumns('users');
```

<a name="table-overview"></a>
#### Огляд таблиці

Щоб отримати огляд окремої таблиці бази даних, виконайте artisan-команду `db:table`. Вона показує загальну інформацію про таблицю: її стовпці, типи, атрибути, ключі та індекси:

```shell
php artisan db:table users
```

<a name="monitoring-your-databases"></a>
## Моніторинг баз даних

За допомогою artisan-команди `db:monitor` ви можете наказати Laravel надсилати подію `Illuminate\Database\Events\DatabaseBusy`, якщо ваша база даних обслуговує більше за вказану кількість відкритих підключень.

Для початку заплануйте команду `db:monitor` на [щохвилинне виконання](/docs/{{version}}/scheduling). Команда приймає назви конфігурацій підключень, які ви хочете моніторити, і максимальну кількість відкритих підключень, яку варто терпіти, перш ніж надсилати подію:

```shell
php artisan db:monitor --databases=mysql,pgsql --max=100
```

Самого лише планування цієї команди замало, щоб отримати сповіщення про кількість відкритих підключень. Коли команда натрапляє на базу даних, у якої кількість відкритих підключень перевищує ваш поріг, надсилається подія `DatabaseBusy`. Щоб надіслати сповіщення собі чи команді розробників, прослухайте цю подію в `AppServiceProvider` вашого застосунку:

```php
use App\Notifications\DatabaseApproachingMaxConnections;
use Illuminate\Database\Events\DatabaseBusy;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Notification;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Event::listen(function (DatabaseBusy $event) {
        Notification::route('mail', 'dev@example.com')
            ->notify(new DatabaseApproachingMaxConnections(
                $event->connectionName,
                $event->connections
            ));
    });
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/queries

# База даних: конструктор запитів

- [Вступ](#introduction)
- [Виконання запитів до бази даних](#running-database-queries)
    - [Обробка результатів частинами](#chunking-results)
    - [Ліниве потокове читання результатів](#streaming-results-lazily)
    - [Агрегати](#aggregates)
- [Запити SELECT](#select-statements)
- [Сирі вирази](#raw-expressions)
- [Джойни](#joins)
- [Об'єднання запитів](#unions)
- [Базові умови WHERE](#basic-where-clauses)
    - [Умови WHERE](#where-clauses)
    - [Умови OR WHERE](#or-where-clauses)
    - [Умови WHERE NOT](#where-not-clauses)
    - [Умови Any / All / None](#where-any-all-none-clauses)
    - [Умови WHERE для JSON](#json-where-clauses)
    - [Додаткові умови WHERE](#additional-where-clauses)
    - [Логічне групування](#logical-grouping)
- [Складніші умови WHERE](#advanced-where-clauses)
    - [Умови WHERE EXISTS](#where-exists-clauses)
    - [Умови WHERE з підзапитом](#subquery-where-clauses)
    - [Умови повнотекстового пошуку](#full-text-where-clauses)
    - [Умови векторної схожості](#vector-similarity-clauses)
- [Сортування, групування, limit та offset](#ordering-grouping-limit-and-offset)
    - [Сортування](#ordering)
    - [Групування](#grouping)
    - [Limit та offset](#limit-and-offset)
- [Умовні вирази](#conditional-clauses)
- [Запити INSERT](#insert-statements)
    - [Upsert](#upserts)
- [Запити UPDATE](#update-statements)
    - [Оновлення стовпців JSON](#updating-json-columns)
    - [Збільшення та зменшення](#increment-and-decrement)
- [Запити DELETE](#delete-statements)
- [Песимістичне блокування](#pessimistic-locking)
- [Повторно використовувані компоненти запитів](#reusable-query-components)
- [Налагодження](#debugging)

<a name="introduction"></a>
## Вступ

Конструктор запитів Laravel дає зручний, плавний інтерфейс для створення й виконання запитів до бази даних. Ним можна виконати більшість операцій з базою у вашому застосунку, і він бездоганно працює з усіма підтримуваними Laravel системами баз даних.

Конструктор запитів Laravel використовує прив'язку параметрів PDO, щоб захистити ваш застосунок від атак SQL-ін'єкцією. Немає потреби чистити чи екранувати рядки, які ви передаєте конструктору як прив'язки.

> [!WARNING]
> PDO не підтримує прив'язку назв стовпців. Тому ніколи не дозволяйте користувацькому вводу визначати назви стовпців у ваших запитах, зокрема стовпці для «order by».

<a name="running-database-queries"></a>
## Виконання запитів до бази даних

<a name="retrieving-all-rows-from-a-table"></a>
#### Отримання всіх рядків таблиці

Щоб почати запит, скористайтеся методом `table` фасаду `DB`. Метод `table` повертає плавний екземпляр конструктора запитів для вказаної таблиці, тож ви можете додавати до запиту обмеження ланцюжком, а вкінці отримати результати методом `get`:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\DB;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * Show a list of all of the application's users.
     */
    public function index(): View
    {
        $users = DB::table('users')->get();

        return view('user.index', ['users' => $users]);
    }
}
```

Метод `get` повертає екземпляр `Illuminate\Support\Collection` з результатами запиту, де кожен результат - це екземпляр PHP-об'єкта `stdClass`. Значення кожного стовпця доступне як властивість об'єкта:

```php
use Illuminate\Support\Facades\DB;

$users = DB::table('users')->get();

foreach ($users as $user) {
    echo $user->name;
}
```

> [!NOTE]
> Колекції Laravel мають цілу низку надзвичайно потужних методів для перетворення та згортання даних. Детальніше про них читайте в [документації з колекцій](/docs/{{version}}/collections).

<a name="retrieving-a-single-row-column-from-a-table"></a>
#### Отримання одного рядка чи стовпця з таблиці

Якщо вам потрібен лише один рядок із таблиці, скористайтеся методом `first` фасаду `DB`. Він повертає один об'єкт `stdClass`:

```php
$user = DB::table('users')->where('name', 'John')->first();

return $user->email;
```

Якщо ви хочете отримати один рядок, але викинути `Illuminate\Database\RecordNotFoundException`, коли відповідного рядка немає, скористайтеся методом `firstOrFail`. Якщо `RecordNotFoundException` не перехоплено, клієнту автоматично надсилається HTTP-відповідь 404:

```php
$user = DB::table('users')->where('name', 'John')->firstOrFail();
```

Якщо вам не потрібен цілий рядок, ви можете дістати одне значення із запису методом `value`. Він повертає значення стовпця напряму:

```php
$email = DB::table('users')->where('name', 'John')->value('email');
```

Щоб отримати один рядок за значенням стовпця `id`, скористайтеся методом `find`:

```php
$user = DB::table('users')->find(3);
```

<a name="retrieving-a-list-of-column-values"></a>
#### Отримання списку значень стовпця

Якщо ви хочете отримати екземпляр `Illuminate\Support\Collection` зі значеннями одного стовпця, скористайтеся методом `pluck`. У цьому прикладі ми отримаємо колекцію посад користувачів:

```php
use Illuminate\Support\Facades\DB;

$titles = DB::table('users')->pluck('title');

foreach ($titles as $title) {
    echo $title;
}
```

Другим аргументом методу `pluck` можна вказати стовпець, значення якого стануть ключами колекції:

```php
$titles = DB::table('users')->pluck('title', 'name');

foreach ($titles as $name => $title) {
    echo $title;
}
```

<a name="chunking-results"></a>
### Обробка результатів частинами

Якщо вам потрібно опрацювати тисячі записів, розгляньте метод `chunk` фасаду `DB`. Він дістає з бази невелику частину результатів за раз і передає кожну частину в замикання для обробки. Наприклад, пройдімо всю таблицю `users` частинами по 100 записів:

```php
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

DB::table('users')->orderBy('id')->chunk(100, function (Collection $users) {
    foreach ($users as $user) {
        // ...
    }
});
```

Щоб зупинити обробку наступних частин, поверніть із замикання `false`:

```php
DB::table('users')->orderBy('id')->chunk(100, function (Collection $users) {
    // Process the records...

    return false;
});
```

Якщо під час обробки частинами ви оновлюєте записи, результати можуть змінитися неочікуваним чином. Коли ви плануєте оновлювати отримані записи, завжди краще скористатися методом `chunkById`. Він автоматично розбиває результати на сторінки за первинним ключем запису:

```php
DB::table('users')->where('active', false)
    ->chunkById(100, function (Collection $users) {
        foreach ($users as $user) {
            DB::table('users')
                ->where('id', $user->id)
                ->update(['active' => true]);
        }
    });
```

Оскільки методи `chunkById` і `lazyById` додають до запиту власні умови «where», свої умови варто [логічно згрупувати](#logical-grouping) в замиканні:

```php
DB::table('users')->where(function ($query) {
    $query->where('credits', 1)->orWhere('credits', 2);
})->chunkById(100, function (Collection $users) {
    foreach ($users as $user) {
        DB::table('users')
            ->where('id', $user->id)
            ->update(['credits' => 3]);
    }
});
```

> [!WARNING]
> Коли ви оновлюєте або видаляєте записи всередині колбека, будь-які зміни первинного чи зовнішніх ключів можуть вплинути на запит, що дістає частини. Через це частина записів може не потрапити до результатів.

<a name="streaming-results-lazily"></a>
### Ліниве потокове читання результатів

Метод `lazy` працює схоже на [метод chunk](#chunking-results) - він теж виконує запит частинами. Але замість того щоб передавати кожну частину в колбек, метод `lazy()` повертає [LazyCollection](/docs/{{version}}/collections#lazy-collections), і ви працюєте з результатами як з єдиним потоком:

```php
use Illuminate\Support\Facades\DB;

DB::table('users')->orderBy('id')->lazy()->each(function (object $user) {
    // ...
});
```

І знову ж таки: якщо ви плануєте оновлювати отримані записи під час обходу, краще скористатися методами `lazyById` або `lazyByIdDesc`. Вони автоматично розбивають результати на сторінки за первинним ключем запису:

```php
DB::table('users')->where('active', false)
    ->lazyById()->each(function (object $user) {
        DB::table('users')
            ->where('id', $user->id)
            ->update(['active' => true]);
    });
```

> [!WARNING]
> Коли ви оновлюєте або видаляєте записи під час обходу, будь-які зміни первинного чи зовнішніх ключів можуть вплинути на запит, що дістає частини. Через це частина записів може не потрапити до результатів.

<a name="aggregates"></a>
### Агрегати

Конструктор запитів має також низку методів для отримання агрегатних значень: `count`, `max`, `min`, `avg` і `sum`. Викликати будь-який із них можна після того, як ви побудували запит:

```php
use Illuminate\Support\Facades\DB;

$users = DB::table('users')->count();

$price = DB::table('orders')->max('price');
```

Звісно, ці методи можна поєднувати з іншими умовами, щоб точніше визначити, як обчислюється агрегат:

```php
$price = DB::table('orders')
    ->where('finalized', 1)
    ->avg('price');
```

<a name="determining-if-records-exist"></a>
#### Перевірка існування записів

Замість методу `count` для перевірки, чи є записи, що відповідають обмеженням запиту, скористайтеся методами `exists` і `doesntExist`:

```php
if (DB::table('orders')->where('finalized', 1)->exists()) {
    // ...
}

if (DB::table('orders')->where('finalized', 1)->doesntExist()) {
    // ...
}
```

<a name="select-statements"></a>
## Запити SELECT

<a name="specifying-a-select-clause"></a>
#### Задання виразу SELECT

Не завжди потрібно вибирати з таблиці всі стовпці. Методом `select` ви можете задати для запиту власний вираз «select»:

```php
use Illuminate\Support\Facades\DB;

$users = DB::table('users')
    ->select('name', 'email as user_email')
    ->get();
```

Метод `distinct` змушує запит повертати лише унікальні результати:

```php
$users = DB::table('users')->distinct()->get();
```

Якщо у вас уже є екземпляр конструктора запитів і ви хочете додати стовпець до наявного виразу select, скористайтеся методом `addSelect`:

```php
$query = DB::table('users')->select('name');

$users = $query->addSelect('age')->get();
```

<a name="raw-expressions"></a>
## Сирі вирази

Іноді вам потрібно вставити в запит довільний рядок. Щоб створити сирий рядковий вираз, скористайтеся методом `raw` фасаду `DB`:

```php
$users = DB::table('users')
    ->select(DB::raw('count(*) as user_count, status'))
    ->where('status', '<>', 1)
    ->groupBy('status')
    ->get();
```

> [!WARNING]
> Сирі вирази підставляються в запит як рядки, тому будьте надзвичайно уважні, щоб не створити вразливість до SQL-ін'єкції.

<a name="raw-methods"></a>
### Сирі методи

Замість методу `DB::raw` ви можете скористатися наведеними нижче методами, щоб вставити сирий вираз у різні частини запиту. **Пам'ятайте: Laravel не може гарантувати, що запит із сирими виразами захищений від SQL-ін'єкцій.**

<a name="selectraw"></a>
#### `selectRaw`

Метод `selectRaw` можна використати замість `addSelect(DB::raw(/* ... */))`. Другим аргументом він приймає необов'язковий масив прив'язок:

```php
$orders = DB::table('orders')
    ->selectRaw('price * ? as price_with_tax', [1.0825])
    ->get();
```

<a name="whereraw-orwhereraw"></a>
#### `whereRaw / orWhereRaw`

Методи `whereRaw` і `orWhereRaw` вставляють у ваш запит сирий вираз «where». Другим аргументом вони приймають необов'язковий масив прив'язок:

```php
$orders = DB::table('orders')
    ->whereRaw('price > IF(state = "TX", ?, 100)', [200])
    ->get();
```

<a name="havingraw-orhavingraw"></a>
#### `havingRaw / orHavingRaw`

Методи `havingRaw` і `orHavingRaw` задають сирий рядок як значення виразу «having». Другим аргументом вони приймають необов'язковий масив прив'язок:

```php
$orders = DB::table('orders')
    ->select('department', DB::raw('SUM(price) as total_sales'))
    ->groupBy('department')
    ->havingRaw('SUM(price) > ?', [2500])
    ->get();
```

<a name="orderbyraw"></a>
#### `orderByRaw`

Метод `orderByRaw` задає сирий рядок як значення виразу «order by»:

```php
$orders = DB::table('orders')
    ->orderByRaw('updated_at - created_at DESC')
    ->get();
```

<a name="groupbyraw"></a>
#### `groupByRaw`

Метод `groupByRaw` задає сирий рядок як значення виразу `group by`:

```php
$orders = DB::table('orders')
    ->select('city', 'state')
    ->groupByRaw('city, state')
    ->get();
```

<a name="joins"></a>
## Джойни

<a name="inner-join-clause"></a>
#### Вираз INNER JOIN

Конструктором запитів можна додавати до запитів і джойни. Щоб виконати базовий «inner join», скористайтеся методом `join` на екземплярі конструктора. Перший аргумент методу `join` - назва таблиці, яку потрібно приєднати, а решта аргументів задають умови для стовпців. Ви можете приєднати навіть кілька таблиць в одному запиті:

```php
use Illuminate\Support\Facades\DB;

$users = DB::table('users')
    ->join('contacts', 'users.id', '=', 'contacts.user_id')
    ->join('orders', 'users.id', '=', 'orders.user_id')
    ->select('users.*', 'contacts.phone', 'orders.price')
    ->get();
```

<a name="left-join-right-join-clause"></a>
#### Вирази LEFT JOIN / RIGHT JOIN

Якщо замість «inner join» вам потрібен «left join» чи «right join», скористайтеся методами `leftJoin` або `rightJoin`. Вони мають ту саму сигнатуру, що й метод `join`:

```php
$users = DB::table('users')
    ->leftJoin('posts', 'users.id', '=', 'posts.user_id')
    ->get();

$users = DB::table('users')
    ->rightJoin('posts', 'users.id', '=', 'posts.user_id')
    ->get();
```

<a name="cross-join-clause"></a>
#### Вираз CROSS JOIN

Щоб виконати «cross join», скористайтеся методом `crossJoin`. Cross join утворює декартовий добуток першої таблиці та приєднаної:

```php
$sizes = DB::table('sizes')
    ->crossJoin('colors')
    ->get();
```

<a name="advanced-join-clauses"></a>
#### Складніші вирази джойнів

Ви можете задавати й складніші умови джойну. Для цього передайте замикання другим аргументом методу `join`. Замикання отримає екземпляр `Illuminate\Database\Query\JoinClause`, у якому ви задаєте обмеження виразу «join»:

```php
DB::table('users')
    ->join('contacts', function (JoinClause $join) {
        $join->on('users.id', '=', 'contacts.user_id')->orOn(/* ... */);
    })
    ->get();
```

Якщо в джойні вам потрібен вираз «where», скористайтеся методами `where` і `orWhere` екземпляра `JoinClause`. Замість порівняння двох стовпців вони порівнюють стовпець зі значенням:

```php
DB::table('users')
    ->join('contacts', function (JoinClause $join) {
        $join->on('users.id', '=', 'contacts.user_id')
            ->where('contacts.user_id', '>', 5);
    })
    ->get();
```

<a name="subquery-joins"></a>
#### Джойни з підзапитом

Методами `joinSub`, `leftJoinSub` і `rightJoinSub` ви можете приєднати до запиту підзапит. Кожен із них приймає три аргументи: підзапит, його псевдонім таблиці та замикання, що визначає пов'язані стовпці. У цьому прикладі ми отримаємо колекцію користувачів, де кожен запис також містить час `created_at` останнього опублікованого поста цього користувача:

```php
$latestPosts = DB::table('posts')
    ->select('user_id', DB::raw('MAX(created_at) as last_post_created_at'))
    ->where('is_published', true)
    ->groupBy('user_id');

$users = DB::table('users')
    ->joinSub($latestPosts, 'latest_posts', function (JoinClause $join) {
        $join->on('users.id', '=', 'latest_posts.user_id');
    })->get();
```

<a name="lateral-joins"></a>
#### Латеральні джойни

> [!WARNING]
> Латеральні джойни наразі підтримують PostgreSQL, MySQL >= 8.0.14 і SQL Server.

Методами `joinLateral` і `leftJoinLateral` можна виконати «lateral join» із підзапитом. Кожен із них приймає два аргументи: підзапит і його псевдонім таблиці. Умови джойну слід задати у виразі `where` самого підзапиту. Латеральні джойни обчислюються для кожного рядка й можуть посилатися на стовпці за межами підзапиту.

У цьому прикладі ми отримаємо колекцію користувачів разом із трьома їхніми останніми постами. Кожен користувач може дати до трьох рядків у результаті - по одному на кожен останній пост. Умову джойну задано виразом `whereColumn` усередині підзапиту, який посилається на поточний рядок користувача:

```php
$latestPosts = DB::table('posts')
    ->select('id as post_id', 'title as post_title', 'created_at as post_created_at')
    ->whereColumn('user_id', 'users.id')
    ->orderBy('created_at', 'desc')
    ->limit(3);

$users = DB::table('users')
    ->joinLateral($latestPosts, 'latest_posts')
    ->get();
```

<a name="unions"></a>
## Об'єднання запитів

Конструктор запитів має також зручний метод для об'єднання («union») двох чи більше запитів. Наприклад, ви можете створити початковий запит і методом `union` об'єднати його з іншими:

```php
use Illuminate\Support\Facades\DB;

$usersWithoutFirstName = DB::table('users')
    ->whereNull('first_name');

$users = DB::table('users')
    ->whereNull('last_name')
    ->union($usersWithoutFirstName)
    ->get();
```

Крім методу `union`, конструктор запитів має метод `unionAll`. Запити, об'єднані через `unionAll`, не втрачають дублікати результатів. Сигнатура `unionAll` така сама, як у `union`.

<a name="basic-where-clauses"></a>
## Базові умови WHERE

<a name="where-clauses"></a>
### Умови WHERE

Щоб додати до запиту умови «where», скористайтеся методом `where` конструктора запитів. Найпростіший виклик `where` вимагає трьох аргументів. Перший - назва стовпця. Другий - оператор, будь-який із підтримуваних вашою базою даних. Третій - значення, з яким порівнюється значення стовпця.

Наприклад, цей запит дістає користувачів, у яких значення стовпця `votes` дорівнює `100`, а значення стовпця `age` більше за `35`:

```php
$users = DB::table('users')
    ->where('votes', '=', 100)
    ->where('age', '>', 35)
    ->get();
```

Для зручності, якщо ви перевіряєте стовпець на рівність `=` заданому значенню, передайте це значення другим аргументом методу `where`. Laravel вважатиме, що ви хочете оператор `=`:

```php
$users = DB::table('users')->where('votes', 100)->get();
```

Ви також можете передати методу `where` асоціативний масив, щоб швидко зробити запит за кількома стовпцями:

```php
$users = DB::table('users')->where([
    'first_name' => 'Jane',
    'last_name' => 'Doe',
])->get();
```

Як уже згадувалося, ви можете використовувати будь-який оператор, що його підтримує ваша система баз даних:

```php
$users = DB::table('users')
    ->where('votes', '>=', 100)
    ->get();

$users = DB::table('users')
    ->where('votes', '<>', 100)
    ->get();

$users = DB::table('users')
    ->where('name', 'like', 'T%')
    ->get();
```

Функції `where` можна також передати масив умов. Кожен елемент масиву має бути масивом із трьох аргументів, які зазвичай передають методу `where`:

```php
$users = DB::table('users')->where([
    ['status', '=', '1'],
    ['subscribed', '<>', '1'],
])->get();
```

> [!WARNING]
> PDO не підтримує прив'язку назв стовпців. Тому ніколи не дозволяйте користувацькому вводу визначати назви стовпців у ваших запитах, зокрема стовпці для «order by».

> [!WARNING]
> MySQL і MariaDB автоматично приводять рядки до цілих чисел, коли порівнюють рядок із числом. При цьому нечислові рядки перетворюються на `0`, що може дати неочікуваний результат. Наприклад, якщо у вашій таблиці є стовпець `secret` зі значенням `aaa` і ви виконаєте `User::where('secret', 0)`, цей рядок буде повернуто. Щоб такого не сталося, приводьте всі значення до належних типів, перш ніж використовувати їх у запитах.

<a name="or-where-clauses"></a>
### Умови OR WHERE

Коли ви ланцюжком викликаєте метод `where`, умови «where» з'єднуються оператором `and`. Втім, методом `orWhere` ви можете приєднати умову оператором `or`. Метод `orWhere` приймає ті самі аргументи, що й `where`:

```php
$users = DB::table('users')
    ->where('votes', '>', 100)
    ->orWhere('name', 'John')
    ->get();
```

Якщо вам потрібно згрупувати умову «or» у дужках, передайте замикання першим аргументом методу `orWhere`:

```php
use Illuminate\Database\Query\Builder;

$users = DB::table('users')
    ->where('votes', '>', 100)
    ->orWhere(function (Builder $query) {
        $query->where('name', 'Abigail')
            ->where('votes', '>', 50);
        })
    ->get();
```

Наведений вище приклад дасть такий SQL:

```sql
select * from users where votes > 100 or (name = 'Abigail' and votes > 50)
```

> [!WARNING]
> Завжди групуйте виклики `orWhere`, щоб уникнути неочікуваної поведінки, коли застосовуються глобальні скопи.

<a name="where-not-clauses"></a>
### Умови WHERE NOT

Методами `whereNot` і `orWhereNot` можна заперечити задану групу обмежень запиту. Наприклад, цей запит виключає товари, які продаються за акцією або дешевші за десять:

```php
$products = DB::table('products')
    ->whereNot(function (Builder $query) {
        $query->where('clearance', true)
            ->orWhere('price', '<', 10);
        })
    ->get();
```

<a name="where-any-all-none-clauses"></a>
### Умови Any / All / None

Іноді вам потрібно застосувати ті самі обмеження до кількох стовпців. Наприклад, ви хочете дістати всі записи, у яких будь-який зі стовпців зі списку `LIKE` задане значення. Це робить метод `whereAny`:

```php
$users = DB::table('users')
    ->where('active', true)
    ->whereAny([
        'name',
        'email',
        'phone',
    ], 'like', 'Example%')
    ->get();
```

Наведений вище запит дасть такий SQL:

```sql
SELECT *
FROM users
WHERE active = true AND (
    name LIKE 'Example%' OR
    email LIKE 'Example%' OR
    phone LIKE 'Example%'
)
```

Аналогічно, метод `whereAll` дістає записи, у яких усі задані стовпці відповідають обмеженню:

```php
$posts = DB::table('posts')
    ->where('published', true)
    ->whereAll([
        'title',
        'content',
    ], 'like', '%Laravel%')
    ->get();
```

Наведений вище запит дасть такий SQL:

```sql
SELECT *
FROM posts
WHERE published = true AND (
    title LIKE '%Laravel%' AND
    content LIKE '%Laravel%'
)
```

Метод `whereNone` дістає записи, у яких жоден із заданих стовпців не відповідає обмеженню:

```php
$albums = DB::table('albums')
    ->where('published', true)
    ->whereNone([
        'title',
        'lyrics',
        'tags',
    ], 'like', '%explicit%')
    ->get();
```

Наведений вище запит дасть такий SQL:

```sql
SELECT *
FROM albums
WHERE published = true AND NOT (
    title LIKE '%explicit%' OR
    lyrics LIKE '%explicit%' OR
    tags LIKE '%explicit%'
)
```

<a name="json-where-clauses"></a>
### Умови WHERE для JSON

Laravel підтримує запити до стовпців типу JSON у базах даних, які мають підтримку таких стовпців. Наразі це MariaDB 10.3+, MySQL 8.0+, PostgreSQL 12.0+, SQL Server 2017+ і SQLite 3.39.0+. Щоб звернутися до стовпця JSON, скористайтеся оператором `->`:

```php
$users = DB::table('users')
    ->where('preferences->dining->meal', 'salad')
    ->get();

$users = DB::table('users')
    ->whereIn('preferences->dining->meal', ['pasta', 'salad', 'sandwiches'])
    ->get();
```

Щоб робити запити до масивів JSON, скористайтеся методами `whereJsonContains` і `whereJsonDoesntContain`:

```php
$users = DB::table('users')
    ->whereJsonContains('options->languages', 'en')
    ->get();

$users = DB::table('users')
    ->whereJsonDoesntContain('options->languages', 'en')
    ->get();
```

Якщо ваш застосунок працює з MariaDB, MySQL або PostgreSQL, методам `whereJsonContains` і `whereJsonDoesntContain` можна передати масив значень:

```php
$users = DB::table('users')
    ->whereJsonContains('options->languages', ['en', 'de'])
    ->get();

$users = DB::table('users')
    ->whereJsonDoesntContain('options->languages', ['en', 'de'])
    ->get();
```

Крім того, методами `whereJsonContainsKey` і `whereJsonDoesntContainKey` ви можете дістати результати, які містять або не містять певний ключ JSON:

```php
$users = DB::table('users')
    ->whereJsonContainsKey('preferences->dietary_requirements')
    ->get();

$users = DB::table('users')
    ->whereJsonDoesntContainKey('preferences->dietary_requirements')
    ->get();
```

Нарешті, методом `whereJsonLength` можна робити запити до масивів JSON за їхньою довжиною:

```php
$users = DB::table('users')
    ->whereJsonLength('options->languages', 0)
    ->get();

$users = DB::table('users')
    ->whereJsonLength('options->languages', '>', 1)
    ->get();
```

<a name="additional-where-clauses"></a>
### Додаткові умови WHERE

**whereLike / orWhereLike / whereNotLike / orWhereNotLike**

Метод `whereLike` додає до запиту умови «LIKE» для пошуку за шаблоном. Ці методи дають незалежний від бази даних спосіб порівнювати рядки з можливістю керувати чутливістю до регістру. За замовчуванням порівняння рядків не чутливе до регістру:

```php
$users = DB::table('users')
    ->whereLike('name', '%John%')
    ->get();
```

Увімкнути чутливий до регістру пошук можна аргументом `caseSensitive`:

```php
$users = DB::table('users')
    ->whereLike('name', '%John%', caseSensitive: true)
    ->get();
```

Метод `orWhereLike` додає умову «or» з умовою LIKE:

```php
$users = DB::table('users')
    ->where('votes', '>', 100)
    ->orWhereLike('name', '%John%')
    ->get();
```

Метод `whereNotLike` додає до запиту умови «NOT LIKE»:

```php
$users = DB::table('users')
    ->whereNotLike('name', '%John%')
    ->get();
```

Аналогічно, `orWhereNotLike` додає умову «or» з умовою NOT LIKE:

```php
$users = DB::table('users')
    ->where('votes', '>', 100)
    ->orWhereNotLike('name', '%John%')
    ->get();
```

> [!WARNING]
> Опція чутливого до регістру пошуку в `whereLike` наразі не підтримується на SQL Server.

**whereIn / whereNotIn / orWhereIn / orWhereNotIn**

Метод `whereIn` перевіряє, що значення заданого стовпця міститься в заданому масиві:

```php
$users = DB::table('users')
    ->whereIn('id', [1, 2, 3])
    ->get();
```

Метод `whereNotIn` перевіряє, що значення заданого стовпця не міститься в заданому масиві:

```php
$users = DB::table('users')
    ->whereNotIn('id', [1, 2, 3])
    ->get();
```

Другим аргументом методу `whereIn` можна також передати об'єкт запиту:

```php
$activeUsers = DB::table('users')->select('id')->where('is_active', 1);

$comments = DB::table('comments')
    ->whereIn('user_id', $activeUsers)
    ->get();
```

Наведений вище приклад дасть такий SQL:

```sql
select * from comments where user_id in (
    select id
    from users
    where is_active = 1
)
```

> [!WARNING]
> Якщо ви додаєте до запиту великий масив цілочисельних прив'язок, методи `whereIntegerInRaw` або `whereIntegerNotInRaw` допоможуть значно зменшити споживання пам'яті.

**whereBetween / orWhereBetween**

Метод `whereBetween` перевіряє, що значення стовпця лежить між двома значеннями:

```php
$users = DB::table('users')
    ->whereBetween('votes', [1, 100])
    ->get();
```

**whereNotBetween / orWhereNotBetween**

Метод `whereNotBetween` перевіряє, що значення стовпця лежить за межами двох значень:

```php
$users = DB::table('users')
    ->whereNotBetween('votes', [1, 100])
    ->get();
```

**whereBetweenColumns / whereNotBetweenColumns / orWhereBetweenColumns / orWhereNotBetweenColumns**

Метод `whereBetweenColumns` перевіряє, що значення стовпця лежить між значеннями двох стовпців того самого рядка таблиці:

```php
$patients = DB::table('patients')
    ->whereBetweenColumns('weight', ['minimum_allowed_weight', 'maximum_allowed_weight'])
    ->get();
```

Метод `whereNotBetweenColumns` перевіряє, що значення стовпця лежить за межами значень двох стовпців того самого рядка таблиці:

```php
$patients = DB::table('patients')
    ->whereNotBetweenColumns('weight', ['minimum_allowed_weight', 'maximum_allowed_weight'])
    ->get();
```

**whereValueBetween / whereValueNotBetween / orWhereValueBetween / orWhereValueNotBetween**

Метод `whereValueBetween` перевіряє, що задане значення лежить між значеннями двох стовпців того самого типу в тому самому рядку таблиці:

```php
$products = DB::table('products')
    ->whereValueBetween(100, ['min_price', 'max_price'])
    ->get();
```

Метод `whereValueNotBetween` перевіряє, що значення лежить за межами значень двох стовпців того самого рядка таблиці:

```php
$products = DB::table('products')
    ->whereValueNotBetween(100, ['min_price', 'max_price'])
    ->get();
```

**whereNull / whereNotNull / orWhereNull / orWhereNotNull**

Метод `whereNull` перевіряє, що значення заданого стовпця дорівнює `NULL`:

```php
$users = DB::table('users')
    ->whereNull('updated_at')
    ->get();
```

Метод `whereNotNull` перевіряє, що значення стовпця не дорівнює `NULL`:

```php
$users = DB::table('users')
    ->whereNotNull('updated_at')
    ->get();
```

**whereNullSafeEquals / orWhereNullSafeEquals**

Методами `whereNullSafeEquals` і `orWhereNullSafeEquals` можна порівняти значення стовпця із заданим значенням, вважаючи два значення `NULL` рівними:

```php
$lastLoginIp = $request->input('last_login_ip');

$users = DB::table('users')
    ->whereNullSafeEquals('last_login_ip', $lastLoginIp)
    ->get();
```

**whereDate / whereMonth / whereDay / whereYear / whereTime**

Метод `whereDate` порівнює значення стовпця з датою:

```php
$users = DB::table('users')
    ->whereDate('created_at', '2016-12-31')
    ->get();
```

Метод `whereMonth` порівнює значення стовпця з конкретним місяцем:

```php
$users = DB::table('users')
    ->whereMonth('created_at', '12')
    ->get();
```

Метод `whereDay` порівнює значення стовпця з конкретним днем місяця:

```php
$users = DB::table('users')
    ->whereDay('created_at', '31')
    ->get();
```

Метод `whereYear` порівнює значення стовпця з конкретним роком:

```php
$users = DB::table('users')
    ->whereYear('created_at', '2016')
    ->get();
```

Метод `whereTime` порівнює значення стовпця з конкретним часом:

```php
$users = DB::table('users')
    ->whereTime('created_at', '=', '11:20:45')
    ->get();
```

**wherePast / whereFuture / whereToday / whereBeforeToday / whereAfterToday**

Методами `wherePast` і `whereFuture` можна визначити, чи значення стовпця в минулому або майбутньому:

```php
$invoices = DB::table('invoices')
    ->wherePast('due_at')
    ->get();

$invoices = DB::table('invoices')
    ->whereFuture('due_at')
    ->get();
```

Методами `whereNowOrPast` і `whereNowOrFuture` можна визначити, чи значення стовпця в минулому або майбутньому, включно з поточними датою та часом:

```php
$invoices = DB::table('invoices')
    ->whereNowOrPast('due_at')
    ->get();

$invoices = DB::table('invoices')
    ->whereNowOrFuture('due_at')
    ->get();
```

Методами `whereToday`, `whereBeforeToday` і `whereAfterToday` можна визначити, чи значення стовпця припадає на сьогодні, до сьогодні або після сьогодні відповідно:

```php
$invoices = DB::table('invoices')
    ->whereToday('due_at')
    ->get();

$invoices = DB::table('invoices')
    ->whereBeforeToday('due_at')
    ->get();

$invoices = DB::table('invoices')
    ->whereAfterToday('due_at')
    ->get();
```

Аналогічно, методами `whereTodayOrBefore` і `whereTodayOrAfter` можна визначити, чи значення стовпця припадає на час до сьогодні або після сьогодні, включно з сьогоднішньою датою:

```php
$invoices = DB::table('invoices')
    ->whereTodayOrBefore('due_at')
    ->get();

$invoices = DB::table('invoices')
    ->whereTodayOrAfter('due_at')
    ->get();
```

**whereColumn / orWhereColumn**

Методом `whereColumn` можна перевірити, що два стовпці рівні:

```php
$users = DB::table('users')
    ->whereColumn('first_name', 'last_name')
    ->get();
```

Методу `whereColumn` можна також передати оператор порівняння:

```php
$users = DB::table('users')
    ->whereColumn('updated_at', '>', 'created_at')
    ->get();
```

Методу `whereColumn` можна передати й масив порівнянь стовпців. Ці умови з'єднаються оператором `and`:

```php
$users = DB::table('users')
    ->whereColumn([
        ['first_name', '=', 'last_name'],
        ['updated_at', '>', 'created_at'],
    ])->get();
```

<a name="logical-grouping"></a>
### Логічне групування

Іноді вам потрібно згрупувати кілька умов «where» у дужках, щоб досягти потрібної логіки запиту. Власне, виклики методу `orWhere` варто завжди брати в дужки, щоб запит не поводився неочікувано. Для цього передайте методу `where` замикання:

```php
$users = DB::table('users')
    ->where('name', '=', 'John')
    ->where(function (Builder $query) {
        $query->where('votes', '>', 100)
            ->orWhere('title', '=', 'Admin');
    })
    ->get();
```

Як бачите, передане в метод `where` замикання наказує конструктору запитів почати групу обмежень. Замикання отримає екземпляр конструктора, у якому ви задаєте обмеження, що мають опинитися в дужках. Наведений вище приклад дасть такий SQL:

```sql
select * from users where name = 'John' and (votes > 100 or title = 'Admin')
```

> [!WARNING]
> Завжди групуйте виклики `orWhere`, щоб уникнути неочікуваної поведінки, коли застосовуються глобальні скопи.

<a name="advanced-where-clauses"></a>
## Складніші умови WHERE

<a name="where-exists-clauses"></a>
### Умови WHERE EXISTS

Метод `whereExists` дозволяє писати SQL-вирази «where exists». Він приймає замикання, яке отримає екземпляр конструктора запитів, - у ньому ви описуєте запит, що має опинитися всередині виразу «exists»:

```php
$users = DB::table('users')
    ->whereExists(function (Builder $query) {
        $query->select(DB::raw(1))
            ->from('orders')
            ->whereColumn('orders.user_id', 'users.id');
    })
    ->get();
```

Замість замикання методу `whereExists` можна передати об'єкт запиту:

```php
$orders = DB::table('orders')
    ->select(DB::raw(1))
    ->whereColumn('orders.user_id', 'users.id');

$users = DB::table('users')
    ->whereExists($orders)
    ->get();
```

Обидва наведені вище приклади дадуть такий SQL:

```sql
select * from users
where exists (
    select 1
    from orders
    where orders.user_id = users.id
)
```

<a name="subquery-where-clauses"></a>
### Умови WHERE з підзапитом

Іноді вам потрібно побудувати умову «where», яка порівнює результат підзапиту із заданим значенням. Для цього передайте методу `where` замикання і значення. Наприклад, цей запит дістане всіх користувачів, які мають нещодавнє «membership» заданого типу;

```php
use App\Models\User;
use Illuminate\Database\Query\Builder;

$users = User::where(function (Builder $query) {
    $query->select('type')
        ->from('membership')
        ->whereColumn('membership.user_id', 'users.id')
        ->orderByDesc('membership.start_date')
        ->limit(1);
}, 'Pro')->get();
```

Або вам може знадобитися умова «where», яка порівнює стовпець із результатом підзапиту. Для цього передайте методу `where` стовпець, оператор і замикання. Наприклад, цей запит дістане всі записи про дохід, де сума менша за середню;

```php
use App\Models\Income;
use Illuminate\Database\Query\Builder;

$incomes = Income::where('amount', '<', function (Builder $query) {
    $query->selectRaw('avg(i.amount)')->from('incomes as i');
})->get();
```

<a name="full-text-where-clauses"></a>
### Умови повнотекстового пошуку

> [!WARNING]
> Умови повнотекстового пошуку наразі підтримують MariaDB, MySQL і PostgreSQL.

Методами `whereFullText` і `orWhereFullText` можна додати до запиту повнотекстові умови «where» для стовпців, що мають [повнотекстові індекси](/docs/{{version}}/migrations#available-index-types). Laravel перетворить їх на відповідний SQL для вашої системи баз даних. Наприклад, для застосунків на MariaDB чи MySQL буде згенеровано вираз `MATCH AGAINST`:

```php
$users = DB::table('users')
    ->whereFullText('bio', 'web developer')
    ->get();
```

<a name="vector-similarity-clauses"></a>
### Умови векторної схожості

> [!NOTE]
> Умови векторної схожості наразі підтримуються на підключеннях PostgreSQL із розширенням `pgvector` та MariaDB 11.7 або новішої версії. Про те, як описувати векторні стовпці та індекси, читайте в [документації з міграцій](/docs/{{version}}/migrations#available-column-types).

Метод `whereVectorSimilarTo` фільтрує результати за косинусною схожістю до заданого вектора й сортує їх за релевантністю. Поріг `minSimilarity` має бути значенням між `0.0` і `1.0`, де `1.0` - повна ідентичність:

```php
$documents = DB::table('documents')
    ->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4)
    ->limit(10)
    ->get();
```

Якщо як вектор передано звичайний рядок, Laravel автоматично згенерує для нього ембединги за допомогою [Laravel AI SDK](/docs/{{version}}/ai-sdk#embeddings):

```php
$documents = DB::table('documents')
    ->whereVectorSimilarTo('embedding', 'Best wineries in Napa Valley')
    ->limit(10)
    ->get();
```

За замовчуванням `whereVectorSimilarTo` також сортує результати за відстанню (найсхожіші спершу). Вимкнути це сортування можна, передавши `false` в аргументі `order`:

```php
$documents = DB::table('documents')
    ->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4, order: false)
    ->orderBy('created_at', 'desc')
    ->limit(10)
    ->get();
```

Якщо вам потрібно більше контролю, скористайтеся методами `selectVectorDistance`, `whereVectorDistanceLessThan` і `orderByVectorDistance` окремо:

```php
$documents = DB::table('documents')
    ->select('*')
    ->selectVectorDistance('embedding', $queryEmbedding, as: 'distance')
    ->whereVectorDistanceLessThan('embedding', $queryEmbedding, maxDistance: 0.3)
    ->orderByVectorDistance('embedding', $queryEmbedding)
    ->limit(10)
    ->get();
```

На PostgreSQL розширення `pgvector` має бути завантажене, перш ніж можна створювати стовпці `vector`:

```php
Schema::ensureVectorExtensionExists();
```

<a name="ordering-grouping-limit-and-offset"></a>
## Сортування, групування, limit та offset

<a name="ordering"></a>
### Сортування

<a name="orderby"></a>
#### Метод `orderBy`

Метод `orderBy` сортує результати запиту за заданим стовпцем. Перший аргумент - стовпець, за яким сортувати, а другий визначає напрямок сортування: `asc` або `desc`:

```php
$users = DB::table('users')
    ->orderBy('name', 'desc')
    ->get();
```

Щоб сортувати за кількома стовпцями, просто викличте `orderBy` стільки разів, скільки потрібно:

```php
$users = DB::table('users')
    ->orderBy('name', 'desc')
    ->orderBy('email', 'asc')
    ->get();
```

Напрямок сортування необов'язковий, за замовчуванням - за зростанням. Щоб сортувати за спаданням, задайте другий параметр методу `orderBy` або просто скористайтеся `orderByDesc`:

```php
$users = DB::table('users')
    ->orderByDesc('verified_at')
    ->get();
```

Нарешті, за допомогою оператора `->` результати можна сортувати за значенням усередині стовпця JSON:

```php
$corporations = DB::table('corporations')
    ->where('country', 'US')
    ->orderBy('location->state')
    ->get();
```

<a name="latest-oldest"></a>
#### Методи `latest` і `oldest`

Методи `latest` і `oldest` дозволяють легко сортувати результати за датою. За замовчуванням результати сортуються за стовпцем `created_at` таблиці. Або ви можете передати назву стовпця, за яким хочете сортувати:

```php
$user = DB::table('users')
    ->latest()
    ->first();
```

<a name="random-ordering"></a>
#### Випадкове сортування

Метод `inRandomOrder` сортує результати запиту випадковим чином. Наприклад, так можна дістати випадкового користувача:

```php
$randomUser = DB::table('users')
    ->inRandomOrder()
    ->first();
```

<a name="removing-existing-orderings"></a>
#### Скидання наявного сортування

Метод `reorder` прибирає всі вирази «order by», які раніше застосували до запиту:

```php
$query = DB::table('users')->orderBy('name');

$unorderedUsers = $query->reorder()->get();
```

Викликаючи `reorder`, ви можете передати стовпець і напрямок, щоб прибрати всі наявні вирази «order by» й задати запиту цілком нове сортування:

```php
$query = DB::table('users')->orderBy('name');

$usersOrderedByEmail = $query->reorder('email', 'desc')->get();
```

Для зручності є метод `reorderDesc`, який перевпорядковує результати за спаданням:

```php
$query = DB::table('users')->orderBy('name');

$usersOrderedByEmail = $query->reorderDesc('email')->get();
```

<a name="grouping"></a>
### Групування

<a name="groupby-having"></a>
#### Методи `groupBy` і `having`

Як і слід очікувати, методи `groupBy` і `having` групують результати запиту. Сигнатура методу `having` схожа на сигнатуру `where`:

```php
$users = DB::table('users')
    ->groupBy('account_id')
    ->having('account_id', '>', 100)
    ->get();
```

Методом `havingBetween` можна відфільтрувати результати в заданому діапазоні:

```php
$report = DB::table('orders')
    ->selectRaw('count(id) as number_of_orders, customer_id')
    ->groupBy('customer_id')
    ->havingBetween('number_of_orders', [5, 15])
    ->get();
```

Щоб групувати за кількома стовпцями, передайте методу `groupBy` кілька аргументів:

```php
$users = DB::table('users')
    ->groupBy('first_name', 'status')
    ->having('account_id', '>', 100)
    ->get();
```

Щоб будувати складніші вирази `having`, дивіться метод [havingRaw](#raw-methods).

<a name="limit-and-offset"></a>
### Limit та offset

Методами `limit` і `offset` можна обмежити кількість результатів запиту або пропустити задану кількість результатів:

```php
$users = DB::table('users')
    ->offset(10)
    ->limit(5)
    ->get();
```

<a name="conditional-clauses"></a>
## Умовні вирази

Іноді вам потрібно, щоб певні умови застосовувалися до запиту залежно від іншої умови. Наприклад, ви хочете додати вираз `where`, лише якщо у вхідному HTTP-запиті є певне значення. Це робить метод `when`:

```php
$role = $request->input('role');

$users = DB::table('users')
    ->when($role, function (Builder $query, string $role) {
        $query->where('role_id', $role);
    })
    ->get();
```

Метод `when` виконує задане замикання лише тоді, коли перший аргумент - `true`. Якщо перший аргумент `false`, замикання не виконається. Тож у прикладі вище замикання буде викликано лише за умови, що поле `role` є у вхідному запиті й дає `true`.

Третім аргументом методу `when` можна передати ще одне замикання. Воно виконається лише тоді, коли перший аргумент дає `false`. Щоб показати, як це працює, налаштуємо сортування запиту за замовчуванням:

```php
$sortByVotes = $request->boolean('sort_by_votes');

$users = DB::table('users')
    ->when($sortByVotes, function (Builder $query, bool $sortByVotes) {
        $query->orderBy('votes');
    }, function (Builder $query) {
        $query->orderBy('name');
    })
    ->get();
```

<a name="insert-statements"></a>
## Запити INSERT

Конструктор запитів має також метод `insert` для вставки записів у таблицю. Метод `insert` приймає масив назв стовпців і значень:

```php
DB::table('users')->insert([
    'email' => 'kayla@example.com',
    'votes' => 0
]);
```

Ви можете вставити кілька записів одразу, передавши масив масивів. Кожен масив - це запис, який слід вставити в таблицю:

```php
DB::table('users')->insert([
    ['email' => 'picard@example.com', 'votes' => 0],
    ['email' => 'janeway@example.com', 'votes' => 0],
]);
```

Метод `insertOrIgnore` ігнорує помилки під час вставки записів. Використовуючи його, зважте: ігноруються не лише помилки дублювання записів - залежно від рушія бази даних можуть ігноруватися й інші типи помилок. Наприклад, `insertOrIgnore` [обходить strict mode у MySQL](https://dev.mysql.com/doc/refman/en/sql-mode.html#ignore-effect-on-execution):

```php
DB::table('users')->insertOrIgnore([
    ['id' => 1, 'email' => 'sisko@example.com'],
    ['id' => 2, 'email' => 'archer@example.com'],
]);
```

Метод `insertUsing` вставляє в таблицю нові записи, визначаючи дані для вставки підзапитом:

```php
DB::table('pruned_users')->insertUsing([
    'id', 'name', 'email', 'email_verified_at'
], DB::table('users')->select(
    'id', 'name', 'email', 'email_verified_at'
)->where('updated_at', '<=', now()->minus(months: 1)));
```

<a name="auto-incrementing-ids"></a>
#### Автоінкрементні ID

Якщо таблиця має автоінкрементний id, скористайтеся методом `insertGetId`, щоб вставити запис і одразу отримати його ID:

```php
$id = DB::table('users')->insertGetId(
    ['email' => 'john@example.com', 'votes' => 0]
);
```

> [!WARNING]
> На PostgreSQL метод `insertGetId` очікує, що автоінкрементний стовпець називається `id`. Якщо ви хочете отримати ID з іншої «послідовності», передайте назву стовпця другим параметром методу `insertGetId`.

<a name="upserts"></a>
### Upsert

Метод `upsert` вставляє записи, яких немає, і оновлює наявні заданими вами новими значеннями. Перший аргумент методу - значення для вставки чи оновлення, другий - стовпці, які унікально ідентифікують записи у відповідній таблиці. Третій і останній аргумент - масив стовпців, які слід оновити, якщо відповідний запис у базі вже є:

```php
DB::table('flights')->upsert(
    [
        ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
        ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
    ],
    ['departure', 'destination'],
    ['price']
);
```

У прикладі вище Laravel спробує вставити два записи. Якщо запис із такими самими значеннями стовпців `departure` і `destination` уже існує, Laravel оновить його стовпець `price`.

> [!WARNING]
> Усі бази даних, окрім SQL Server, вимагають, щоб стовпці з другого аргументу методу `upsert` мали індекс «primary» або «unique». Крім того, драйвери MariaDB і MySQL ігнорують другий аргумент `upsert` і завжди використовують індекси «primary» та «unique» таблиці, щоб виявити наявні записи.

<a name="update-statements"></a>
## Запити UPDATE

Крім вставки записів, конструктор запитів може оновлювати наявні - методом `update`. Як і `insert`, метод `update` приймає масив пар «стовпець - значення», які вказують, що саме оновити. Метод `update` повертає кількість зачеплених рядків. Обмежити запит `update` можна виразами `where`:

```php
$affected = DB::table('users')
    ->where('id', 1)
    ->update(['votes' => 1]);
```

<a name="update-or-insert"></a>
#### Оновити або вставити

Іноді вам потрібно оновити наявний запис у базі даних або створити його, якщо відповідного немає. У такому разі скористайтеся методом `updateOrInsert`. Він приймає два аргументи: масив умов, за якими шукати запис, і масив пар «стовпець - значення», які вказують, що оновити.

Метод `updateOrInsert` спробує знайти відповідний запис за парами «стовпець - значення» з першого аргументу. Якщо запис існує, його буде оновлено значеннями з другого аргументу. Якщо запису немає, буде вставлено новий зі об'єднаними атрибутами обох аргументів:

```php
DB::table('users')
    ->updateOrInsert(
        ['email' => 'john@example.com', 'name' => 'John'],
        ['votes' => '2']
    );
```

Методу `updateOrInsert` можна передати замикання, щоб задати атрибути для оновлення чи вставки залежно від того, чи знайдено відповідний запис:

```php
DB::table('users')->updateOrInsert(
    ['user_id' => $user_id],
    fn ($exists) => $exists ? [
        'name' => $data['name'],
        'email' => $data['email'],
    ] : [
        'name' => $data['name'],
        'email' => $data['email'],
        'marketable' => true,
    ],
);
```

<a name="updating-json-columns"></a>
### Оновлення стовпців JSON

Оновлюючи стовпець JSON, використовуйте синтаксис `->`, щоб оновити потрібний ключ в об'єкті JSON. Ця операція підтримується на MariaDB 10.3+, MySQL 5.7+ і PostgreSQL 9.5+:

```php
$affected = DB::table('users')
    ->where('id', 1)
    ->update(['options->enabled' => true]);
```

<a name="increment-and-decrement"></a>
### Збільшення та зменшення

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

```php
DB::table('users')->increment('votes');

DB::table('users')->increment('votes', 5);

DB::table('users')->decrement('votes');

DB::table('users')->decrement('votes', 5);
```

За потреби ви можете вказати й додаткові стовпці, які слід оновити під час збільшення чи зменшення:

```php
DB::table('users')->increment('votes', 1, ['name' => 'John']);
```

Крім того, методами `incrementEach` і `decrementEach` можна збільшити або зменшити кілька стовпців одразу:

```php
DB::table('users')->incrementEach([
    'votes' => 5,
    'balance' => 100,
]);
```

<a name="delete-statements"></a>
## Запити DELETE

Методом `delete` конструктора запитів можна видаляти записи з таблиці. Метод `delete` повертає кількість зачеплених рядків. Обмежити запит `delete` можна, додавши умови «where» перед викликом `delete`:

```php
$deleted = DB::table('users')->delete();

$deleted = DB::table('users')->where('votes', '>', 100)->delete();
```

<a name="pessimistic-locking"></a>
## Песимістичне блокування

Конструктор запитів має також кілька функцій, які допомагають реалізувати «песимістичне блокування» під час виконання запитів `select`. Щоб виконати запит зі «спільним блокуванням» (shared lock), викличте метод `sharedLock`. Спільне блокування не дає змінювати вибрані рядки, поки ваша транзакція не завершиться комітом:

```php
DB::table('users')
    ->where('votes', '>', 100)
    ->sharedLock()
    ->get();
```

Або ви можете скористатися методом `lockForUpdate`. Блокування «for update» не дає змінювати вибрані записи й вибирати їх з іншим спільним блокуванням:

```php
DB::table('users')
    ->where('votes', '>', 100)
    ->lockForUpdate()
    ->get();
```

Це не обов'язково, але песимістичні блокування радимо обгортати в [транзакцію](/docs/{{version}}/database#database-transactions). Так отримані дані лишаються незмінними в базі, поки вся операція не завершиться. У разі збою транзакція відкотить усі зміни й автоматично звільнить блокування:

```php
DB::transaction(function () {
    $sender = DB::table('users')
        ->lockForUpdate()
        ->find(1);

    $receiver = DB::table('users')
        ->lockForUpdate()
        ->find(2);

    if ($sender->balance < 100) {
        throw new RuntimeException('Balance too low.');
    }

    DB::table('users')
        ->where('id', $sender->id)
        ->update([
            'balance' => $sender->balance - 100
        ]);

    DB::table('users')
        ->where('id', $receiver->id)
        ->update([
            'balance' => $receiver->balance + 100
        ]);
});
```

<a name="reusable-query-components"></a>
## Повторно використовувані компоненти запитів

Якщо у вашому застосунку повторюється однакова логіка запитів, ви можете винести її в повторно використовувані об'єкти за допомогою методів `tap` і `pipe` конструктора запитів. Уявіть, що у вас є два такі різні запити:

```php
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;

$destination = $request->query('destination');

DB::table('flights')
    ->when($destination, function (Builder $query, string $destination) {
        $query->where('destination', $destination);
    })
    ->orderByDesc('price')
    ->get();

// ...

$destination = $request->query('destination');

DB::table('flights')
    ->when($destination, function (Builder $query, string $destination) {
        $query->where('destination', $destination);
    })
    ->where('user', $request->user()->id)
    ->orderBy('destination')
    ->get();
```

Спільну для обох запитів фільтрацію за призначенням варто винести в повторно використовуваний об'єкт:

```php
<?php

namespace App\Scopes;

use Illuminate\Database\Query\Builder;

class DestinationFilter
{
    public function __construct(
        private ?string $destination,
    ) {
        //
    }

    public function __invoke(Builder $query): void
    {
        $query->when($this->destination, function (Builder $query) {
            $query->where('destination', $this->destination);
        });
    }
}
```

Далі ви можете застосувати логіку об'єкта до запиту методом `tap` конструктора запитів:

```php
use App\Scopes\DestinationFilter;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;

DB::table('flights')
    ->when($destination, function (Builder $query, string $destination) { // [tl! remove]
        $query->where('destination', $destination); // [tl! remove]
    }) // [tl! remove]
    ->tap(new DestinationFilter($destination)) // [tl! add]
    ->orderByDesc('price')
    ->get();

// ...

DB::table('flights')
    ->when($destination, function (Builder $query, string $destination) { // [tl! remove]
        $query->where('destination', $destination); // [tl! remove]
    }) // [tl! remove]
    ->tap(new DestinationFilter($destination)) // [tl! add]
    ->where('user', $request->user()->id)
    ->orderBy('destination')
    ->get();
```

<a name="query-pipes"></a>
#### Пайпи запитів

Метод `tap` завжди повертає конструктор запитів. Якщо ви хочете винести об'єкт, який виконує запит і повертає інше значення, скористайтеся методом `pipe`.

Розгляньмо об'єкт запиту зі спільною логікою [пагінації](/docs/{{version}}/pagination), яка використовується в усьому застосунку. На відміну від `DestinationFilter`, що додає до запиту умови, об'єкт `Paginate` виконує запит і повертає екземпляр пагінатора:

```php
<?php

namespace App\Scopes;

use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Query\Builder;

class Paginate
{
    public function __construct(
        private string $sortBy = 'timestamp',
        private string $sortDirection = 'desc',
        private int $perPage = 25,
    ) {
        //
    }

    public function __invoke(Builder $query): LengthAwarePaginator
    {
        return $query->orderBy($this->sortBy, $this->sortDirection)
            ->paginate($this->perPage, pageName: 'p');
    }
}
```

За допомогою методу `pipe` конструктора запитів ми можемо застосувати цей об'єкт зі спільною логікою пагінації:

```php
$flights = DB::table('flights')
    ->tap(new DestinationFilter($destination))
    ->pipe(new Paginate);
```

<a name="debugging"></a>
## Налагодження

Під час побудови запиту ви можете скористатися методами `dd` і `dump`, щоб вивести поточні прив'язки та SQL. Метод `dd` покаже інформацію для налагодження й зупинить виконання запиту. Метод `dump` покаже цю інформацію, але дозволить запиту виконуватися далі:

```php
DB::table('users')->where('votes', '>', 100)->dd();

DB::table('users')->where('votes', '>', 100)->dump();
```

Методи `dumpRawSql` і `ddRawSql` виводять SQL запиту з усіма належно підставленими прив'язками параметрів:

```php
DB::table('users')->where('votes', '>', 100)->dumpRawSql();

DB::table('users')->where('votes', '>', 100)->ddRawSql();
```

---

Джерело: https://laravelukraine.com/docs/13.x/pagination

# База даних: пагінація

- [Вступ](#introduction)
- [Базове використання](#basic-usage)
    - [Пагінація результатів конструктора запитів](#paginating-query-builder-results)
    - [Пагінація результатів Eloquent](#paginating-eloquent-results)
    - [Курсорна пагінація](#cursor-pagination)
    - [Створення пагінатора вручну](#manually-creating-a-paginator)
    - [Налаштування URL пагінації](#customizing-pagination-urls)
- [Відображення результатів пагінації](#displaying-pagination-results)
    - [Налаштування вікна посилань пагінації](#adjusting-the-pagination-link-window)
    - [Перетворення результатів на JSON](#converting-results-to-json)
- [Налаштування представлення пагінації](#customizing-the-pagination-view)
    - [Використання Bootstrap](#using-bootstrap)
- [Методи екземплярів Paginator і LengthAwarePaginator](#paginator-instance-methods)
- [Методи екземпляра курсорного пагінатора](#cursor-paginator-instance-methods)

<a name="introduction"></a>
## Вступ

В інших фреймворках пагінація буває справжньою мукою. Сподіваємося, підхід Laravel стане для вас ковтком свіжого повітря. Пагінатор Laravel інтегрований із [конструктором запитів](/docs/{{version}}/queries) та [Eloquent ORM](/docs/{{version}}/eloquent) і дає зручну, просту пагінацію записів бази даних без жодних налаштувань.

За замовчуванням HTML, який генерує пагінатор, сумісний із [фреймворком Tailwind CSS](https://tailwindcss.com/); втім, доступна й підтримка пагінації Bootstrap.

<a name="tailwind"></a>
#### Tailwind

Якщо ви користуєтеся стандартними представленнями пагінації Laravel для Tailwind разом із Tailwind 4.x, файл `resources/css/app.css` вашого застосунку вже буде правильно налаштований на `@source` для представлень пагінації Laravel:

```css
@import 'tailwindcss';

@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
```

<a name="basic-usage"></a>
## Базове використання

<a name="paginating-query-builder-results"></a>
### Пагінація результатів конструктора запитів

Розбити елементи на сторінки можна кількома способами. Найпростіший - метод `paginate` [конструктора запитів](/docs/{{version}}/queries) або [запиту Eloquent](/docs/{{version}}/eloquent). Метод `paginate` сам подбає про «limit» і «offset» запиту відповідно до сторінки, яку зараз переглядає користувач. За замовчуванням поточна сторінка визначається за значенням параметра `page` у рядку запиту HTTP-запиту. Laravel визначає це значення автоматично й так само автоматично підставляє його в посилання, які генерує пагінатор.

У цьому прикладі методу `paginate` передається єдиний аргумент - кількість елементів, які ви хочете показувати «на сторінці». Задаймо `15` елементів на сторінку:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\DB;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * Show all application users.
     */
    public function index(): View
    {
        return view('user.index', [
            'users' => DB::table('users')->paginate(15)
        ]);
    }
}
```

<a name="simple-pagination"></a>
#### Проста пагінація

Перш ніж дістати записи з бази даних, метод `paginate` рахує загальну кількість записів, що відповідають запиту. Це потрібно, щоб пагінатор знав, скільки всього сторінок. Але якщо ви не збираєтеся показувати загальну кількість сторінок в інтерфейсі застосунку, цей підрахунок зайвий.

Тож коли вам потрібні лише прості посилання «Далі» й «Назад», скористайтеся методом `simplePaginate` - він виконає один ефективний запит:

```php
$users = DB::table('users')->simplePaginate(15);
```

<a name="paginating-eloquent-results"></a>
### Пагінація результатів Eloquent

Ви також можете розбивати на сторінки запити [Eloquent](/docs/{{version}}/eloquent). У цьому прикладі ми розіб'ємо на сторінки модель `App\Models\User` по 15 записів на сторінку. Як бачите, синтаксис майже не відрізняється від пагінації результатів конструктора запитів:

```php
use App\Models\User;

$users = User::paginate(15);
```

Звісно, метод `paginate` можна викликати після того, як ви задали інші обмеження запиту - наприклад, умови `where`:

```php
$users = User::where('votes', '>', 100)->paginate(15);
```

Для моделей Eloquent так само доступний метод `simplePaginate`:

```php
$users = User::where('votes', '>', 100)->simplePaginate(15);
```

Аналогічно, для курсорної пагінації моделей Eloquent є метод `cursorPaginate`:

```php
$users = User::where('votes', '>', 100)->cursorPaginate(15);
```

<a name="multiple-paginator-instances-per-page"></a>
#### Кілька екземплярів пагінатора на сторінці

Іноді вам потрібно вивести два окремі пагінатори на одному екрані. Але якщо обидва екземпляри зберігають поточну сторінку в параметрі `page` рядка запиту, вони конфліктуватимуть. Щоб вирішити цей конфлікт, передайте назву параметра рядка запиту, у якому зберігатиметься поточна сторінка пагінатора, третім аргументом методів `paginate`, `simplePaginate` і `cursorPaginate`:

```php
use App\Models\User;

$users = User::where('votes', '>', 100)->paginate(
    $perPage = 15, $columns = ['*'], $pageName = 'users'
);
```

<a name="cursor-pagination"></a>
### Курсорна пагінація

Якщо `paginate` і `simplePaginate` будують запити з SQL-виразом «offset», то курсорна пагінація створює умови «where», які порівнюють значення стовпців сортування із запиту. Це дає найкращу продуктивність бази даних з-поміж усіх методів пагінації Laravel. Такий спосіб особливо доречний для великих наборів даних і «нескінченного» прокручування в інтерфейсі.

На відміну від пагінації за зміщенням, яка додає номер сторінки до рядка запиту згенерованих URL, курсорна пагінація кладе в рядок запиту рядок-«курсор». Курсор - це закодований рядок, у якому міститься позиція, з якої наступний запит має продовжити пагінацію, і напрямок руху:

```text
http://localhost/users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0
```

Створити екземпляр курсорного пагінатора можна методом `cursorPaginate` конструктора запитів. Він повертає екземпляр `Illuminate\Pagination\CursorPaginator`:

```php
$users = DB::table('users')->orderBy('id')->cursorPaginate(15);
```

Отримавши екземпляр курсорного пагінатора, ви можете [відобразити результати пагінації](#displaying-pagination-results) так само, як робите це з `paginate` і `simplePaginate`. Докладніше про методи екземпляра курсорного пагінатора читайте в [документації з методів курсорного пагінатора](#cursor-paginator-instance-methods).

> [!WARNING]
> Щоб скористатися курсорною пагінацією, ваш запит обов'язково має містити вираз «order by». Крім того, стовпці, за якими сортується запит, мають належати таблиці, яку ви розбиваєте на сторінки.

<a name="cursor-vs-offset-pagination"></a>
#### Курсорна пагінація проти пагінації за зміщенням

Щоб проілюструвати різницю між пагінацією за зміщенням і курсорною, погляньмо на приклади SQL-запитів. Обидва наведені нижче запити покажуть «другу сторінку» результатів таблиці `users`, відсортованої за `id`:

```sql
# Offset Pagination...
select * from users order by id asc limit 15 offset 15;

# Cursor Pagination...
select * from users where id > 15 order by id asc limit 15;
```

Запит із курсорною пагінацією має такі переваги перед пагінацією за зміщенням:

- На великих наборах даних курсорна пагінація працює швидше, якщо стовпці з «order by» проіндексовані. Причина в тому, що вираз «offset» сканує всі раніше відібрані дані.
- На даних із частими записами пагінація за зміщенням може пропускати записи чи показувати дублікати, якщо на сторінку, яку зараз переглядає користувач, нещодавно щось додали або з неї щось видалили.

Втім, курсорна пагінація має такі обмеження:

- Як і `simplePaginate`, курсорна пагінація придатна лише для посилань «Далі» й «Назад» і не вміє генерувати посилання з номерами сторінок.
- Вона вимагає, щоб сортування спиралося щонайменше на один унікальний стовпець або на комбінацію унікальних стовпців. Стовпці зі значеннями `null` не підтримуються.
- Вирази запитів у «order by» підтримуються лише тоді, коли вони мають псевдонім і додані також до виразу «select».
- Вирази запитів із параметрами не підтримуються.

<a name="manually-creating-a-paginator"></a>
### Створення пагінатора вручну

Іноді вам може знадобитися створити пагінатор вручну, передавши йому масив елементів, які вже є в пам'яті. Залежно від потреби, створіть екземпляр `Illuminate\Pagination\Paginator`, `Illuminate\Pagination\LengthAwarePaginator` або `Illuminate\Pagination\CursorPaginator`.

Класам `Paginator` і `CursorPaginator` не потрібно знати загальну кількість елементів у наборі результатів; саме тому вони й не мають методів для отримання індексу останньої сторінки. `LengthAwarePaginator` приймає майже ті самі аргументи, що й `Paginator`, але додатково вимагає загальну кількість елементів у наборі результатів.

Іншими словами, `Paginator` відповідає методу `simplePaginate` конструктора запитів, `CursorPaginator` - методу `cursorPaginate`, а `LengthAwarePaginator` - методу `paginate`.

> [!WARNING]
> Створюючи пагінатор вручну, ви маєте самі «нарізати» масив результатів, який передаєте пагінатору. Якщо не певні, як це зробити, погляньте на PHP-функцію [array_slice](https://secure.php.net/manual/en/function.array-slice.php).

<a name="customizing-pagination-urls"></a>
### Налаштування URL пагінації

За замовчуванням посилання, які генерує пагінатор, збігаються з URI поточного запиту. Втім, метод `withPath` дозволяє задати власний URI для генерації посилань. Наприклад, якщо ви хочете, щоб пагінатор генерував посилання на кшталт `http://example.com/admin/users?page=N`, передайте `/admin/users` до методу `withPath`:

```php
use App\Models\User;

Route::get('/users', function () {
    $users = User::paginate(15);

    $users->withPath('/admin/users');

    // ...
});
```

<a name="appending-query-string-values"></a>
#### Додавання значень до рядка запиту

Ви можете додавати значення до рядка запиту посилань пагінації методом `appends`. Наприклад, щоб додати `sort=votes` до кожного посилання, викличте `appends` так:

```php
use App\Models\User;

Route::get('/users', function () {
    $users = User::paginate(15);

    $users->appends(['sort' => 'votes']);

    // ...
});
```

Якщо ви хочете додати до посилань пагінації всі значення рядка запиту з поточного HTTP-запиту, скористайтеся методом `withQueryString`:

```php
$users = User::paginate(15)->withQueryString();
```

<a name="appending-hash-fragments"></a>
#### Додавання хеш-фрагментів

Якщо до згенерованих пагінатором URL потрібно додати «хеш-фрагмент», скористайтеся методом `fragment`. Наприклад, щоб додати `#users` у кінець кожного посилання пагінації, викличте метод `fragment` так:

```php
$users = User::paginate(15)->fragment('users');
```

<a name="displaying-pagination-results"></a>
## Відображення результатів пагінації

Метод `paginate` повертає екземпляр `Illuminate\Pagination\LengthAwarePaginator`, метод `simplePaginate` - екземпляр `Illuminate\Pagination\Paginator`, а метод `cursorPaginate` - екземпляр `Illuminate\Pagination\CursorPaginator`.

Ці об'єкти мають кілька методів, які описують набір результатів. Крім цих допоміжних методів, екземпляри пагінатора є ітераторами, тож їх можна обходити в циклі як масив. Отже, отримавши результати, ви можете вивести їх і відрендерити посилання на сторінки за допомогою [Blade](/docs/{{version}}/blade):

```blade
<div class="container">
    @foreach ($users as $user)
        {{ $user->name }}
    @endforeach
</div>

{{ $users->links() }}
```

Метод `links` рендерить посилання на решту сторінок набору результатів. Кожне з цих посилань уже містить потрібну змінну `page` у рядку запиту. Пам'ятайте: HTML, який генерує метод `links`, сумісний із [фреймворком Tailwind CSS](https://tailwindcss.com).

<a name="adjusting-the-pagination-link-window"></a>
### Налаштування вікна посилань пагінації

Коли пагінатор виводить посилання, він показує номер поточної сторінки, а також посилання на три сторінки до та після неї. Методом `onEachSide` ви можете керувати тим, скільки додаткових посилань показувати з кожного боку від поточної сторінки в середньому рухомому вікні посилань:

```blade
{{ $users->onEachSide(5)->links() }}
```

<a name="converting-results-to-json"></a>
### Перетворення результатів на JSON

Класи пагінаторів Laravel реалізують контракт `Illuminate\Contracts\Support\Jsonable` і мають метод `toJson`, тож перетворити результати пагінації на JSON дуже просто. Ви також можете отримати JSON, просто повернувши екземпляр пагінатора з маршруту чи дії контролера:

```php
use App\Models\User;

Route::get('/users', function () {
    return User::paginate();
});
```

JSON від пагінатора міститиме метаінформацію: `total`, `current_page`, `last_page` та інше. Самі записи результатів доступні за ключем `data`. Ось приклад JSON, який утворюється, коли з маршруту повертають екземпляр пагінатора:

```json
{
   "total": 50,
   "per_page": 15,
   "current_page": 1,
   "last_page": 4,
   "first_page_url": "http://laravel.app?page=1",
   "last_page_url": "http://laravel.app?page=4",
   "next_page_url": "http://laravel.app?page=2",
   "prev_page_url": null,
   "path": "http://laravel.app",
   "from": 1,
   "to": 15,
   "data":[
        {
            // Record...
        },
        {
            // Record...
        }
   ]
}
```

<a name="customizing-the-pagination-view"></a>
## Налаштування представлення пагінації

За замовчуванням представлення, які рендерять посилання пагінації, сумісні з фреймворком [Tailwind CSS](https://tailwindcss.com). Втім, якщо ви не користуєтеся Tailwind, ви вільні описати власні представлення для цих посилань. Викликаючи метод `links` на екземплярі пагінатора, передайте назву представлення першим аргументом:

```blade
{{ $paginator->links('view.name') }}

<!-- Passing additional data to the view... -->
{{ $paginator->links('view.name', ['foo' => 'bar']) }}
```

Проте найпростіший спосіб налаштувати представлення пагінації - експортувати їх до каталогу `resources/views/vendor` командою `vendor:publish`:

```shell
php artisan vendor:publish --tag=laravel-pagination
```

Ця команда покладе представлення до каталогу `resources/views/vendor/pagination` вашого застосунку. Файл `tailwind.blade.php` у цьому каталозі відповідає представленню пагінації за замовчуванням. Відредагуйте цей файл, щоб змінити HTML пагінації.

Якщо ви хочете призначити представленням пагінації за замовчуванням інший файл, викличте методи `defaultView` і `defaultSimpleView` пагінатора в методі `boot` класу `App\Providers\AppServiceProvider`:

```php
<?php

namespace App\Providers;

use Illuminate\Pagination\Paginator;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Paginator::defaultView('view-name');

        Paginator::defaultSimpleView('view-name');
    }
}
```

<a name="using-bootstrap"></a>
### Використання Bootstrap

Laravel містить представлення пагінації, побудовані на [Bootstrap CSS](https://getbootstrap.com/). Щоб використати їх замість стандартних представлень Tailwind, викличте методи пагінатора `useBootstrapFour` або `useBootstrapFive` у методі `boot` класу `App\Providers\AppServiceProvider`:

```php
use Illuminate\Pagination\Paginator;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Paginator::useBootstrapFive();
    Paginator::useBootstrapFour();
}
```

<a name="paginator-instance-methods"></a>
## Методи екземплярів Paginator / LengthAwarePaginator

Кожен екземпляр пагінатора надає додаткову інформацію про пагінацію через такі методи:

<div class="overflow-auto">

| Метод                                   | Опис                                                                                                           |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `$paginator->count()`                   | Отримати кількість елементів на поточній сторінці.                                                            |
| `$paginator->currentPage()`             | Отримати номер поточної сторінки.                                                                            |
| `$paginator->firstItem()`               | Отримати порядковий номер першого елемента в результатах.                                                    |
| `$paginator->getOptions()`              | Отримати опції пагінатора.                                                                                   |
| `$paginator->getUrlRange($start, $end)` | Створити діапазон URL пагінації.                                                                             |
| `$paginator->hasPages()`                | Визначити, чи достатньо елементів, щоб розбити їх на кілька сторінок.                                         |
| `$paginator->hasMorePages()`            | Визначити, чи є ще елементи у сховищі даних.                                                                 |
| `$paginator->items()`                   | Отримати елементи поточної сторінки.                                                                         |
| `$paginator->lastItem()`                | Отримати порядковий номер останнього елемента в результатах.                                                 |
| `$paginator->lastPage()`                | Отримати номер останньої доступної сторінки. (Недоступний із `simplePaginate`).                              |
| `$paginator->nextPageUrl()`             | Отримати URL наступної сторінки.                                                                             |
| `$paginator->onFirstPage()`             | Визначити, чи пагінатор перебуває на першій сторінці.                                                        |
| `$paginator->onLastPage()`              | Визначити, чи пагінатор перебуває на останній сторінці.                                                      |
| `$paginator->perPage()`                 | Кількість елементів, які показуються на сторінці.                                                            |
| `$paginator->previousPageUrl()`         | Отримати URL попередньої сторінки.                                                                           |
| `$paginator->total()`                   | Визначити загальну кількість відповідних елементів у сховищі даних. (Недоступний із `simplePaginate`).        |
| `$paginator->url($page)`                | Отримати URL для заданого номера сторінки.                                                                   |
| `$paginator->getPageName()`             | Отримати змінну рядка запиту, у якій зберігається сторінка.                                                  |
| `$paginator->setPageName($name)`        | Задати змінну рядка запиту, у якій зберігається сторінка.                                                    |
| `$paginator->through($callback)`        | Перетворити кожен елемент за допомогою колбека.                                                              |

</div>

<a name="cursor-paginator-instance-methods"></a>
## Методи екземпляра курсорного пагінатора

Кожен екземпляр курсорного пагінатора надає додаткову інформацію про пагінацію через такі методи:

<div class="overflow-auto">

| Метод                           | Опис                                                                 |
| ------------------------------- | ----------------------------------------------------------------- |
| `$paginator->count()`           | Отримати кількість елементів на поточній сторінці.                  |
| `$paginator->cursor()`          | Отримати поточний екземпляр курсора.                                |
| `$paginator->getOptions()`      | Отримати опції пагінатора.                                          |
| `$paginator->hasPages()`        | Визначити, чи достатньо елементів, щоб розбити їх на кілька сторінок. |
| `$paginator->hasMorePages()`    | Визначити, чи є ще елементи у сховищі даних.                        |
| `$paginator->getCursorName()`   | Отримати змінну рядка запиту, у якій зберігається курсор.           |
| `$paginator->items()`           | Отримати елементи поточної сторінки.                                |
| `$paginator->nextCursor()`      | Отримати екземпляр курсора для наступного набору елементів.         |
| `$paginator->nextPageUrl()`     | Отримати URL наступної сторінки.                                    |
| `$paginator->onFirstPage()`     | Визначити, чи пагінатор перебуває на першій сторінці.               |
| `$paginator->onLastPage()`      | Визначити, чи пагінатор перебуває на останній сторінці.             |
| `$paginator->perPage()`         | Кількість елементів, які показуються на сторінці.                   |
| `$paginator->previousCursor()`  | Отримати екземпляр курсора для попереднього набору елементів.       |
| `$paginator->previousPageUrl()` | Отримати URL попередньої сторінки.                                  |
| `$paginator->setCursorName()`   | Задати змінну рядка запиту, у якій зберігається курсор.             |
| `$paginator->url($cursor)`      | Отримати URL для заданого екземпляра курсора.                       |

</div>

---

Джерело: https://laravelukraine.com/docs/13.x/migrations

# База даних: міграції

- [Вступ](#introduction)
- [Створення міграцій](#generating-migrations)
    - [Стискання міграцій](#squashing-migrations)
- [Структура міграції](#migration-structure)
- [Запуск міграцій](#running-migrations)
    - [Відкат міграцій](#rolling-back-migrations)
- [Таблиці](#tables)
    - [Створення таблиць](#creating-tables)
    - [Оновлення таблиць](#updating-tables)
    - [Перейменування та видалення таблиць](#renaming-and-dropping-tables)
- [Стовпці](#columns)
    - [Створення стовпців](#creating-columns)
    - [Доступні типи стовпців](#available-column-types)
    - [Модифікатори стовпців](#column-modifiers)
    - [Зміна стовпців](#modifying-columns)
    - [Перейменування стовпців](#renaming-columns)
    - [Видалення стовпців](#dropping-columns)
- [Індекси](#indexes)
    - [Створення індексів](#creating-indexes)
    - [Перейменування індексів](#renaming-indexes)
    - [Видалення індексів](#dropping-indexes)
    - [Обмеження зовнішніх ключів](#foreign-key-constraints)
- [Події](#events)

<a name="introduction"></a>
## Вступ

Міграції - це наче контроль версій для вашої бази даних: вони дозволяють команді описувати та спільно використовувати схему бази даних застосунку. Якщо вам колись доводилося просити колегу вручну додати стовпець до своєї локальної схеми після того, як він підтягнув ваші зміни з репозиторію, - ви стикалися саме з тією проблемою, яку розв'язують міграції.

[Фасад](/docs/{{version}}/facades) `Schema` в Laravel дає незалежну від бази даних підтримку створення таблиць і роботи з ними в усіх підтримуваних Laravel системах баз даних. Зазвичай міграції користуються цим фасадом, щоб створювати й змінювати таблиці та стовпці.

<a name="generating-migrations"></a>
## Створення міграцій

Щоб згенерувати міграцію, скористайтеся [artisan-командою](/docs/{{version}}/artisan) `make:migration`. Нова міграція потрапить до каталогу `database/migrations`. Назва кожного файлу міграції містить часову позначку, за якою Laravel визначає порядок міграцій:

```shell
php artisan make:migration create_flights_table
```

Laravel спробує вгадати за назвою міграції, як називається таблиця і чи створює міграція нову таблицю. Якщо Laravel зможе визначити назву таблиці з назви міграції, він заздалегідь підставить цю таблицю у згенерований файл. Інакше ви просто вкажете таблицю у файлі міграції вручну.

Якщо ви хочете задати власний шлях для згенерованої міграції, скористайтеся опцією `--path` під час виконання команди `make:migration`. Вказаний шлях має бути відносним до базового шляху вашого застосунку.

> [!NOTE]
> Заготовки міграцій можна налаштувати через [публікацію заготовок](/docs/{{version}}/artisan#stub-customization).

<a name="squashing-migrations"></a>
### Стискання міграцій

Розробляючи застосунок, ви з часом накопичуєте все більше міграцій. Через це каталог `database/migrations` може розростися до сотень файлів. За бажанням ви можете «стиснути» міграції в один SQL-файл. Для початку виконайте команду `schema:dump`:

```shell
php artisan schema:dump

# Dump the current database schema and prune all existing migrations...
php artisan schema:dump --prune
```

Коли ви виконуєте цю команду, Laravel запише файл «схеми» до каталогу `database/schema` вашого застосунку. Назва файлу схеми відповідатиме підключенню до бази даних. Тепер, коли ви спробуєте виконати міграції й жодної іншої міграції ще не виконано, Laravel спершу виконає SQL-запити з файлу схеми того підключення, яке ви використовуєте. А потім виконає всі решту міграцій, які не входили до дампу схеми.

Якщо тести вашого застосунку працюють з іншим підключенням до бази даних, ніж те, яким ви зазвичай користуєтеся локально, переконайтеся, що ви зробили дамп схеми і для цього підключення, - інакше тести не зможуть побудувати вашу базу. Це варто зробити після дампу того підключення, яким ви зазвичай користуєтеся під час локальної розробки:

```shell
php artisan schema:dump
php artisan schema:dump --database=testing --prune
```

Файл схеми бази даних варто закомітити в репозиторій, щоб нові розробники у вашій команді могли швидко створити початкову структуру бази даних застосунку.

> [!WARNING]
> Стискання міграцій доступне лише для баз даних MariaDB, MySQL, PostgreSQL і SQLite і використовує консольний клієнт бази даних.

<a name="migration-structure"></a>
## Структура міграції

Клас міграції містить два методи: `up` і `down`. Метод `up` додає до бази даних нові таблиці, стовпці чи індекси, а метод `down` має скасовувати те, що зробив метод `up`.

В обох методах ви можете користуватися конструктором схеми Laravel, щоб виразно створювати та змінювати таблиці. Про всі методи конструктора `Schema` читайте в [його документації](#creating-tables). Наприклад, ця міграція створює таблицю `flights`:

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('flights', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('airline');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::drop('flights');
    }
};
```

<a name="setting-the-migration-connection"></a>
#### Задання підключення для міграції

Якщо ваша міграція працюватиме не з підключенням до бази даних за замовчуванням, задайте властивість `$connection` вашої міграції:

```php
/**
 * The database connection that should be used by the migration.
 *
 * @var string
 */
protected $connection = 'pgsql';

/**
 * Run the migrations.
 */
public function up(): void
{
    // ...
}
```

<a name="skipping-migrations"></a>
#### Пропуск міграцій

Іноді міграція призначена для можливості, яка ще не активна, і ви поки не хочете її виконувати. У такому разі опишіть у міграції метод `shouldRun`. Якщо `shouldRun` повертає `false`, міграцію буде пропущено:

```php
use App\Models\Flight;
use Laravel\Pennant\Feature;

/**
 * Determine if this migration should run.
 */
public function shouldRun(): bool
{
    return Feature::active(Flight::class);
}
```

<a name="running-migrations"></a>
## Запуск міграцій

Щоб виконати всі міграції, які ще не виконано, запустіть artisan-команду `migrate`:

```shell
php artisan migrate
```

Якщо ви хочете побачити, які міграції вже виконано, а які ще очікують, скористайтеся artisan-командою `migrate:status`:

```shell
php artisan migrate:status
```

Якщо передати команді `migrate` опцію `--step`, кожна міграція виконається як окремий пакет, і згодом ви зможете відкотити окремі міграції командою `migrate:rollback`:

```shell
php artisan migrate --step
```

Якщо ви хочете побачити SQL-запити, які виконають міграції, не запускаючи їх насправді, передайте команді `migrate` прапорець `--pretend`:

```shell
php artisan migrate --pretend
```

<a name="isolating-migration-execution"></a>
#### Ізоляція виконання міграцій

Якщо ви розгортаєте застосунок на кількох серверах і виконуєте міграції в межах процесу розгортання, вам навряд чи потрібно, щоб два сервери одночасно намагалися міграцію виконати. Щоб цього не сталося, скористайтеся опцією `isolated` під час виклику команди `migrate`.

Коли передано опцію `isolated`, Laravel перед спробою виконати міграції отримає атомарне блокування через драйвер кешу вашого застосунку. Усі інші спроби запустити команду `migrate`, поки блокування тримається, не виконаються; проте команда все одно завершиться з кодом успішного виходу:

```shell
php artisan migrate --isolated
```

> [!WARNING]
> Щоб скористатися цією можливістю, ваш застосунок має використовувати драйвер кешу `memcached`, `redis`, `dynamodb`, `database`, `file` або `array` як драйвер за замовчуванням. Крім того, усі сервери повинні спілкуватися з одним центральним сервером кешу.

<a name="forcing-migrations-to-run-in-production"></a>
#### Примусовий запуск міграцій на продакшні

Деякі операції міграцій руйнівні, тобто через них ви можете втратити дані. Щоб убезпечити вас від запуску таких команд на продакшн-базі, Laravel запитає підтвердження, перш ніж їх виконати. Щоб виконати команди без запитання, скористайтеся прапорцем `--force`:

```shell
php artisan migrate --force
```

<a name="rolling-back-migrations"></a>
### Відкат міграцій

Щоб відкотити останню операцію міграції, скористайтеся artisan-командою `rollback`. Вона відкочує останній «пакет» міграцій, який може містити кілька файлів:

```shell
php artisan migrate:rollback
```

Ви можете відкотити обмежену кількість міграцій, передавши команді `rollback` опцію `step`. Наприклад, ця команда відкотить останні п'ять міграцій:

```shell
php artisan migrate:rollback --step=5
```

Ви можете відкотити конкретний «пакет» міграцій, передавши команді `rollback` опцію `batch`, значення якої відповідає значенню batch у таблиці `migrations` вашої бази даних. Наприклад, ця команда відкотить усі міграції з пакета номер три:

```shell
php artisan migrate:rollback --batch=3
```

Якщо ви хочете побачити SQL-запити, які виконають міграції, не запускаючи їх насправді, передайте команді `migrate:rollback` прапорець `--pretend`:

```shell
php artisan migrate:rollback --pretend
```

Команда `migrate:reset` відкотить усі міграції вашого застосунку:

```shell
php artisan migrate:reset
```

<a name="roll-back-migrate-using-a-single-command"></a>
#### Відкат і міграція однією командою

Команда `migrate:refresh` відкотить усі ваші міграції, а потім виконає команду `migrate`. Фактично вона перестворює всю вашу базу даних:

```shell
php artisan migrate:refresh

# Refresh the database and run all database seeds...
php artisan migrate:refresh --seed
```

Ви можете відкотити й повторно виконати обмежену кількість міграцій, передавши команді `refresh` опцію `step`. Наприклад, ця команда відкотить і виконає заново останні п'ять міграцій:

```shell
php artisan migrate:refresh --step=5
```

<a name="drop-all-tables-migrate"></a>
#### Видалення всіх таблиць і міграція

Команда `migrate:fresh` видалить із бази даних усі таблиці, а потім виконає команду `migrate`:

```shell
php artisan migrate:fresh

php artisan migrate:fresh --seed
```

За замовчуванням команда `migrate:fresh` видаляє таблиці лише з підключення за замовчуванням. Втім, опцією `--database` ви можете вказати, яке підключення слід міграціювати. Назва підключення має відповідати підключенню, описаному у [файлі конфігурації](/docs/{{version}}/configuration) `database` вашого застосунку:

```shell
php artisan migrate:fresh --database=admin
```

> [!WARNING]
> Команда `migrate:fresh` видалить усі таблиці бази даних незалежно від їхнього префікса. Користуйтеся нею обережно, якщо розробляєте на базі даних, спільній з іншими застосунками.

<a name="tables"></a>
## Таблиці

<a name="creating-tables"></a>
### Створення таблиць

Щоб створити нову таблицю, скористайтеся методом `create` фасаду `Schema`. Метод `create` приймає два аргументи: перший - назва таблиці, другий - замикання, яке отримує об'єкт `Blueprint` для опису нової таблиці:

```php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('email');
    $table->timestamps();
});
```

Створюючи таблицю, ви можете описувати її стовпці будь-якими [методами стовпців](#creating-columns) конструктора схеми.

<a name="determining-table-column-existence"></a>
#### Перевірка існування таблиці чи стовпця

Перевірити, чи існує таблиця, стовпець або індекс, можна методами `hasTable`, `hasColumn` і `hasIndex`:

```php
if (Schema::hasTable('users')) {
    // The "users" table exists...
}

if (Schema::hasColumn('users', 'email')) {
    // The "users" table exists and has an "email" column...
}

if (Schema::hasIndex('users', ['email'], 'unique')) {
    // The "users" table exists and has a unique index on the "email" column...
}
```

<a name="database-connection-table-options"></a>
#### Підключення до бази даних та опції таблиці

Якщо ви хочете виконати операцію зі схемою не на підключенні за замовчуванням, скористайтеся методом `connection`:

```php
Schema::connection('sqlite')->create('users', function (Blueprint $table) {
    $table->id();
});
```

Крім того, кілька інших властивостей і методів дозволяють задати інші аспекти створення таблиці. Властивістю `engine` можна вказати рушій сховища таблиці на MariaDB чи MySQL:

```php
Schema::create('users', function (Blueprint $table) {
    $table->engine('InnoDB');

    // ...
});
```

Властивостями `charset` і `collation` можна задати кодування та порядок сортування для створюваної таблиці на MariaDB чи MySQL:

```php
Schema::create('users', function (Blueprint $table) {
    $table->charset('utf8mb4');
    $table->collation('utf8mb4_unicode_ci');

    // ...
});
```

Методом `temporary` можна вказати, що таблиця має бути «тимчасовою». Тимчасові таблиці видимі лише в межах сесії поточного підключення й автоматично видаляються, коли підключення закривається:

```php
Schema::create('calculations', function (Blueprint $table) {
    $table->temporary();

    // ...
});
```

Якщо ви хочете додати до таблиці «коментар», викличте метод `comment` на екземплярі таблиці. Коментарі до таблиць наразі підтримують лише MariaDB, MySQL і PostgreSQL:

```php
Schema::create('calculations', function (Blueprint $table) {
    $table->comment('Business calculations');

    // ...
});
```

<a name="updating-tables"></a>
### Оновлення таблиць

Методом `table` фасаду `Schema` можна оновлювати наявні таблиці. Як і `create`, метод `table` приймає два аргументи: назву таблиці та замикання, яке отримує екземпляр `Blueprint` для додавання стовпців чи індексів:

```php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::table('users', function (Blueprint $table) {
    $table->integer('votes');
});
```

<a name="renaming-and-dropping-tables"></a>
### Перейменування та видалення таблиць

Щоб перейменувати наявну таблицю, скористайтеся методом `rename`:

```php
use Illuminate\Support\Facades\Schema;

Schema::rename($from, $to);
```

Щоб видалити наявну таблицю, скористайтеся методами `drop` або `dropIfExists`:

```php
Schema::drop('users');

Schema::dropIfExists('users');
```

<a name="renaming-tables-with-foreign-keys"></a>
#### Перейменування таблиць із зовнішніми ключами

Перш ніж перейменовувати таблицю, переконайтеся, що всі обмеження зовнішніх ключів на ній мають у ваших файлах міграцій явну назву, а не ту, яку Laravel призначає за конвенцією. Інакше назва обмеження зовнішнього ключа посилатиметься на стару назву таблиці.

<a name="columns"></a>
## Стовпці

<a name="creating-columns"></a>
### Створення стовпців

Методом `table` фасаду `Schema` можна оновлювати наявні таблиці. Як і `create`, метод `table` приймає два аргументи: назву таблиці та замикання, яке отримує екземпляр `Illuminate\Database\Schema\Blueprint` для додавання стовпців:

```php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::table('users', function (Blueprint $table) {
    $table->integer('votes');
});
```

<a name="available-column-types"></a>
### Доступні типи стовпців

Blueprint конструктора схеми має цілу низку методів, що відповідають різним типам стовпців, які ви можете додати до таблиць. Усі доступні методи перелічено в таблиці нижче:

<style>
    .collection-method-list > p {
        columns: 10.8em 3; -moz-columns: 10.8em 3; -webkit-columns: 10.8em 3;
    }

    .collection-method-list a {
        display: block;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .collection-method code {
        font-size: 14px;
    }

    .collection-method:not(.first-collection-method) {
        margin-top: 50px;
    }
</style>

<a name="booleans-method-list"></a>
#### Логічні типи

<div class="collection-method-list" markdown="1">

[boolean](#column-method-boolean)

</div>

<a name="strings-and-texts-method-list"></a>
#### Рядкові та текстові типи

<div class="collection-method-list" markdown="1">

[char](#column-method-char)
[longText](#column-method-longText)
[mediumText](#column-method-mediumText)
[string](#column-method-string)
[text](#column-method-text)
[tinyText](#column-method-tinyText)

</div>

<a name="numbers--method-list"></a>
#### Числові типи

<div class="collection-method-list" markdown="1">

[bigIncrements](#column-method-bigIncrements)
[bigInteger](#column-method-bigInteger)
[decimal](#column-method-decimal)
[double](#column-method-double)
[float](#column-method-float)
[id](#column-method-id)
[increments](#column-method-increments)
[integer](#column-method-integer)
[mediumIncrements](#column-method-mediumIncrements)
[mediumInteger](#column-method-mediumInteger)
[smallIncrements](#column-method-smallIncrements)
[smallInteger](#column-method-smallInteger)
[tinyIncrements](#column-method-tinyIncrements)
[tinyInteger](#column-method-tinyInteger)
[unsignedBigInteger](#column-method-unsignedBigInteger)
[unsignedInteger](#column-method-unsignedInteger)
[unsignedMediumInteger](#column-method-unsignedMediumInteger)
[unsignedSmallInteger](#column-method-unsignedSmallInteger)
[unsignedTinyInteger](#column-method-unsignedTinyInteger)

</div>

<a name="dates-and-times-method-list"></a>
#### Типи дати й часу

<div class="collection-method-list" markdown="1">

[dateTime](#column-method-dateTime)
[dateTimeTz](#column-method-dateTimeTz)
[date](#column-method-date)
[time](#column-method-time)
[timeTz](#column-method-timeTz)
[timestamp](#column-method-timestamp)
[timestamps](#column-method-timestamps)
[timestampsTz](#column-method-timestampsTz)
[softDeletes](#column-method-softDeletes)
[softDeletesTz](#column-method-softDeletesTz)
[year](#column-method-year)

</div>

<a name="binaries-method-list"></a>
#### Бінарні типи

<div class="collection-method-list" markdown="1">

[binary](#column-method-binary)

</div>

<a name="object-and-jsons-method-list"></a>
#### Об'єктні типи та JSON

<div class="collection-method-list" markdown="1">

[json](#column-method-json)
[jsonb](#column-method-jsonb)

</div>

<a name="uuids-and-ulids-method-list"></a>
#### Типи UUID та ULID

<div class="collection-method-list" markdown="1">

[ulid](#column-method-ulid)
[ulidMorphs](#column-method-ulidMorphs)
[uuid](#column-method-uuid)
[uuidMorphs](#column-method-uuidMorphs)
[nullableUlidMorphs](#column-method-nullableUlidMorphs)
[nullableUuidMorphs](#column-method-nullableUuidMorphs)

</div>

<a name="spatials-method-list"></a>
#### Просторові типи

<div class="collection-method-list" markdown="1">

[geography](#column-method-geography)
[geometry](#column-method-geometry)

</div>

<a name="relationship-method-list"></a>
#### Типи для зв'язків

<div class="collection-method-list" markdown="1">

[foreignId](#column-method-foreignId)
[foreignIdFor](#column-method-foreignIdFor)
[foreignUlid](#column-method-foreignUlid)
[foreignUuid](#column-method-foreignUuid)
[foreignUuidFor](#column-method-foreignUuidFor)
[morphs](#column-method-morphs)
[nullableMorphs](#column-method-nullableMorphs)

</div>

<a name="specifics-method-list"></a>
#### Спеціальні типи

<div class="collection-method-list" markdown="1">

[enum](#column-method-enum)
[set](#column-method-set)
[macAddress](#column-method-macAddress)
[ipAddress](#column-method-ipAddress)
[rememberToken](#column-method-rememberToken)
[vector](#column-method-vector)

</div>

<a name="column-method-bigIncrements"></a>
#### `bigIncrements()` {.collection-method .first-collection-method}

Метод `bigIncrements` створює автоінкрементний стовпець, еквівалентний `UNSIGNED BIGINT` (первинний ключ):

```php
$table->bigIncrements('id');
```

<a name="column-method-bigInteger"></a>
#### `bigInteger()` {.collection-method}

Метод `bigInteger` створює стовпець, еквівалентний `BIGINT`:

```php
$table->bigInteger('votes');
```

<a name="column-method-binary"></a>
#### `binary()` {.collection-method}

Метод `binary` створює стовпець, еквівалентний `BLOB`:

```php
$table->binary('photo');
```

На MySQL, MariaDB чи SQL Server ви можете передати аргументи `length` і `fixed`, щоб створити стовпець, еквівалентний `VARBINARY` або `BINARY`:

```php
$table->binary('data', length: 16); // VARBINARY(16)

$table->binary('data', length: 16, fixed: true); // BINARY(16)
```

<a name="column-method-boolean"></a>
#### `boolean()` {.collection-method}

Метод `boolean` створює стовпець, еквівалентний `BOOLEAN`:

```php
$table->boolean('confirmed');
```

<a name="column-method-char"></a>
#### `char()` {.collection-method}

Метод `char` створює стовпець, еквівалентний `CHAR`, заданої довжини:

```php
$table->char('name', length: 100);
```

<a name="column-method-dateTimeTz"></a>
#### `dateTimeTz()` {.collection-method}

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

```php
$table->dateTimeTz('created_at', precision: 0);
```

<a name="column-method-dateTime"></a>
#### `dateTime()` {.collection-method}

Метод `dateTime` створює стовпець, еквівалентний `DATETIME`, з необов'язковою точністю дробових секунд:

```php
$table->dateTime('created_at', precision: 0);
```

<a name="column-method-date"></a>
#### `date()` {.collection-method}

Метод `date` створює стовпець, еквівалентний `DATE`:

```php
$table->date('created_at');
```

<a name="column-method-decimal"></a>
#### `decimal()` {.collection-method}

Метод `decimal` створює стовпець, еквівалентний `DECIMAL`, із заданою точністю (загальна кількість цифр) і масштабом (кількість цифр після коми):

```php
$table->decimal('amount', total: 8, places: 2);
```

<a name="column-method-double"></a>
#### `double()` {.collection-method}

Метод `double` створює стовпець, еквівалентний `DOUBLE`:

```php
$table->double('amount');
```

<a name="column-method-enum"></a>
#### `enum()` {.collection-method}

Метод `enum` створює стовпець, еквівалентний `ENUM`, із заданими допустимими значеннями:

```php
$table->enum('difficulty', ['easy', 'hard']);
```

Звісно, замість того щоб описувати масив допустимих значень вручну, ви можете скористатися методом `Enum::cases()`:

```php
use App\Enums\Difficulty;

$table->enum('difficulty', Difficulty::cases());
```

<a name="column-method-float"></a>
#### `float()` {.collection-method}

Метод `float` створює стовпець, еквівалентний `FLOAT`, із заданою точністю:

```php
$table->float('amount', precision: 53);
```

<a name="column-method-foreignId"></a>
#### `foreignId()` {.collection-method}

Метод `foreignId` створює стовпець, еквівалентний `UNSIGNED BIGINT`:

```php
$table->foreignId('user_id');
```

<a name="column-method-foreignIdFor"></a>
#### `foreignIdFor()` {.collection-method}

Метод `foreignIdFor` додає для заданого класу моделі стовпець, еквівалентний `{column}_id`. Тип стовпця буде `UNSIGNED BIGINT`, `CHAR(36)` або `CHAR(26)` - залежно від типу ключа моделі:

```php
$table->foreignIdFor(User::class);
```

<a name="column-method-foreignUlid"></a>
#### `foreignUlid()` {.collection-method}

Метод `foreignUlid` створює стовпець, еквівалентний `ULID`:

```php
$table->foreignUlid('user_id');
```

<a name="column-method-foreignUuid"></a>
#### `foreignUuid()` {.collection-method}

Метод `foreignUuid` створює стовпець, еквівалентний `UUID`:

```php
$table->foreignUuid('user_id');
```

<a name="column-method-foreignUuidFor"></a>
#### `foreignUuidFor()` {.collection-method}

Метод `foreignUuidFor` додає для заданого класу моделі стовпець `{column}_id`, еквівалентний UUID:

```php
$table->foreignUuidFor(User::class);
```

<a name="column-method-geography"></a>
#### `geography()` {.collection-method}

Метод `geography` створює стовпець, еквівалентний `GEOGRAPHY`, із заданим просторовим типом і SRID (Spatial Reference System Identifier):

```php
$table->geography('coordinates', subtype: 'point', srid: 4326);
```

> [!NOTE]
> Підтримка просторових типів залежить від драйвера вашої бази даних. Звіряйтеся з документацією своєї бази. Якщо ваш застосунок працює з PostgreSQL, перед використанням методу `geography` потрібно встановити розширення [PostGIS](https://postgis.net).

<a name="column-method-geometry"></a>
#### `geometry()` {.collection-method}

Метод `geometry` створює стовпець, еквівалентний `GEOMETRY`, із заданим просторовим типом і SRID (Spatial Reference System Identifier):

```php
$table->geometry('positions', subtype: 'point', srid: 0);
```

> [!NOTE]
> Підтримка просторових типів залежить від драйвера вашої бази даних. Звіряйтеся з документацією своєї бази. Якщо ваш застосунок працює з PostgreSQL, перед використанням методу `geometry` потрібно встановити розширення [PostGIS](https://postgis.net).

<a name="column-method-id"></a>
#### `id()` {.collection-method}

Метод `id` - псевдонім методу `bigIncrements`. За замовчуванням він створює стовпець `id`; втім, ви можете передати назву стовпця, якщо хочете назвати його інакше:

```php
$table->id();
```

<a name="column-method-increments"></a>
#### `increments()` {.collection-method}

Метод `increments` створює автоінкрементний стовпець, еквівалентний `UNSIGNED INTEGER`, як первинний ключ:

```php
$table->increments('id');
```

<a name="column-method-integer"></a>
#### `integer()` {.collection-method}

Метод `integer` створює стовпець, еквівалентний `INTEGER`:

```php
$table->integer('votes');
```

<a name="column-method-ipAddress"></a>
#### `ipAddress()` {.collection-method}

Метод `ipAddress` створює стовпець, еквівалентний `VARCHAR`:

```php
$table->ipAddress('visitor');
```

На PostgreSQL буде створено стовпець `INET`.

<a name="column-method-json"></a>
#### `json()` {.collection-method}

Метод `json` створює стовпець, еквівалентний `JSON`:

```php
$table->json('options');
```

На SQLite буде створено стовпець `TEXT`.

<a name="column-method-jsonb"></a>
#### `jsonb()` {.collection-method}

Метод `jsonb` створює стовпець, еквівалентний `JSONB`:

```php
$table->jsonb('options');
```

На SQLite буде створено стовпець `TEXT`.

<a name="column-method-longText"></a>
#### `longText()` {.collection-method}

Метод `longText` створює стовпець, еквівалентний `LONGTEXT`:

```php
$table->longText('description');
```

На MySQL чи MariaDB ви можете застосувати до стовпця кодування `binary`, щоб створити стовпець, еквівалентний `LONGBLOB`:

```php
$table->longText('data')->charset('binary'); // LONGBLOB
```

<a name="column-method-macAddress"></a>
#### `macAddress()` {.collection-method}

Метод `macAddress` створює стовпець, призначений для зберігання MAC-адреси. Деякі системи баз даних, як-от PostgreSQL, мають для таких даних окремий тип стовпця. Інші використають стовпець, еквівалентний рядковому:

```php
$table->macAddress('device');
```

<a name="column-method-mediumIncrements"></a>
#### `mediumIncrements()` {.collection-method}

Метод `mediumIncrements` створює автоінкрементний стовпець, еквівалентний `UNSIGNED MEDIUMINT`, як первинний ключ:

```php
$table->mediumIncrements('id');
```

<a name="column-method-mediumInteger"></a>
#### `mediumInteger()` {.collection-method}

Метод `mediumInteger` створює стовпець, еквівалентний `MEDIUMINT`:

```php
$table->mediumInteger('votes');
```

<a name="column-method-mediumText"></a>
#### `mediumText()` {.collection-method}

Метод `mediumText` створює стовпець, еквівалентний `MEDIUMTEXT`:

```php
$table->mediumText('description');
```

На MySQL чи MariaDB ви можете застосувати до стовпця кодування `binary`, щоб створити стовпець, еквівалентний `MEDIUMBLOB`:

```php
$table->mediumText('data')->charset('binary'); // MEDIUMBLOB
```

<a name="column-method-morphs"></a>
#### `morphs()` {.collection-method}

Метод `morphs` - це зручний метод, який додає стовпець `{column}_type`, еквівалентний `VARCHAR`, і стовпець `{column}_id`. Тип стовпця `{column}_id` буде `UNSIGNED BIGINT`, `CHAR(36)` або `CHAR(26)` - залежно від типу ключа моделі.

Цей метод призначений для опису стовпців, потрібних для поліморфного [зв'язку Eloquent](/docs/{{version}}/eloquent-relationships). У прикладі нижче буде створено стовпці `taggable_type` і `taggable_id`:

```php
$table->morphs('taggable');
```

<a name="column-method-nullableMorphs"></a>
#### `nullableMorphs()` {.collection-method}

Метод схожий на [morphs](#column-method-morphs), але створені стовпці будуть «nullable»:

```php
$table->nullableMorphs('taggable');
```

<a name="column-method-nullableUlidMorphs"></a>
#### `nullableUlidMorphs()` {.collection-method}

Метод схожий на [ulidMorphs](#column-method-ulidMorphs), але створені стовпці будуть «nullable»:

```php
$table->nullableUlidMorphs('taggable');
```

<a name="column-method-nullableUuidMorphs"></a>
#### `nullableUuidMorphs()` {.collection-method}

Метод схожий на [uuidMorphs](#column-method-uuidMorphs), але створені стовпці будуть «nullable»:

```php
$table->nullableUuidMorphs('taggable');
```

<a name="column-method-rememberToken"></a>
#### `rememberToken()` {.collection-method}

Метод `rememberToken` створює nullable-стовпець, еквівалентний `VARCHAR(100)`, призначений для зберігання поточного [токена автентифікації](/docs/{{version}}/authentication#remembering-users) «запам'ятати мене»:

```php
$table->rememberToken();
```

<a name="column-method-set"></a>
#### `set()` {.collection-method}

Метод `set` створює стовпець, еквівалентний `SET`, із заданим списком допустимих значень:

```php
$table->set('flavors', ['strawberry', 'vanilla']);
```

<a name="column-method-smallIncrements"></a>
#### `smallIncrements()` {.collection-method}

Метод `smallIncrements` створює автоінкрементний стовпець, еквівалентний `UNSIGNED SMALLINT`, як первинний ключ:

```php
$table->smallIncrements('id');
```

<a name="column-method-smallInteger"></a>
#### `smallInteger()` {.collection-method}

Метод `smallInteger` створює стовпець, еквівалентний `SMALLINT`:

```php
$table->smallInteger('votes');
```

<a name="column-method-softDeletesTz"></a>
#### `softDeletesTz()` {.collection-method}

Метод `softDeletesTz` додає nullable-стовпець `deleted_at`, еквівалентний `TIMESTAMP` (з часовою зоною), з необов'язковою точністю дробових секунд. Цей стовпець призначений для зберігання часової позначки `deleted_at`, потрібної для м'якого видалення (soft delete) в Eloquent:

```php
$table->softDeletesTz('deleted_at', precision: 0);
```

<a name="column-method-softDeletes"></a>
#### `softDeletes()` {.collection-method}

Метод `softDeletes` додає nullable-стовпець `deleted_at`, еквівалентний `TIMESTAMP`, з необов'язковою точністю дробових секунд. Цей стовпець призначений для зберігання часової позначки `deleted_at`, потрібної для м'якого видалення в Eloquent:

```php
$table->softDeletes('deleted_at', precision: 0);
```

<a name="column-method-string"></a>
#### `string()` {.collection-method}

Метод `string` створює стовпець, еквівалентний `VARCHAR`, заданої довжини:

```php
$table->string('name', length: 100);
```

<a name="column-method-text"></a>
#### `text()` {.collection-method}

Метод `text` створює стовпець, еквівалентний `TEXT`:

```php
$table->text('description');
```

На MySQL чи MariaDB ви можете застосувати до стовпця кодування `binary`, щоб створити стовпець, еквівалентний `BLOB`:

```php
$table->text('data')->charset('binary'); // BLOB
```

<a name="column-method-timeTz"></a>
#### `timeTz()` {.collection-method}

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

```php
$table->timeTz('sunrise', precision: 0);
```

<a name="column-method-time"></a>
#### `time()` {.collection-method}

Метод `time` створює стовпець, еквівалентний `TIME`, з необов'язковою точністю дробових секунд:

```php
$table->time('sunrise', precision: 0);
```

<a name="column-method-timestampTz"></a>
#### `timestampTz()` {.collection-method}

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

```php
$table->timestampTz('added_at', precision: 0);
```

<a name="column-method-timestamp"></a>
#### `timestamp()` {.collection-method}

Метод `timestamp` створює стовпець, еквівалентний `TIMESTAMP`, з необов'язковою точністю дробових секунд:

```php
$table->timestamp('added_at', precision: 0);
```

<a name="column-method-timestampsTz"></a>
#### `timestampsTz()` {.collection-method}

Метод `timestampsTz` створює стовпці `created_at` і `updated_at`, еквівалентні `TIMESTAMP` (з часовою зоною), з необов'язковою точністю дробових секунд:

```php
$table->timestampsTz(precision: 0);
```

<a name="column-method-timestamps"></a>
#### `timestamps()` {.collection-method}

Метод `timestamps` створює стовпці `created_at` і `updated_at`, еквівалентні `TIMESTAMP`, з необов'язковою точністю дробових секунд:

```php
$table->timestamps(precision: 0);
```

<a name="column-method-tinyIncrements"></a>
#### `tinyIncrements()` {.collection-method}

Метод `tinyIncrements` створює автоінкрементний стовпець, еквівалентний `UNSIGNED TINYINT`, як первинний ключ:

```php
$table->tinyIncrements('id');
```

<a name="column-method-tinyInteger"></a>
#### `tinyInteger()` {.collection-method}

Метод `tinyInteger` створює стовпець, еквівалентний `TINYINT`:

```php
$table->tinyInteger('votes');
```

<a name="column-method-tinyText"></a>
#### `tinyText()` {.collection-method}

Метод `tinyText` створює стовпець, еквівалентний `TINYTEXT`:

```php
$table->tinyText('notes');
```

На MySQL чи MariaDB ви можете застосувати до стовпця кодування `binary`, щоб створити стовпець, еквівалентний `TINYBLOB`:

```php
$table->tinyText('data')->charset('binary'); // TINYBLOB
```

<a name="column-method-unsignedBigInteger"></a>
#### `unsignedBigInteger()` {.collection-method}

Метод `unsignedBigInteger` створює стовпець, еквівалентний `UNSIGNED BIGINT`:

```php
$table->unsignedBigInteger('votes');
```

<a name="column-method-unsignedInteger"></a>
#### `unsignedInteger()` {.collection-method}

Метод `unsignedInteger` створює стовпець, еквівалентний `UNSIGNED INTEGER`:

```php
$table->unsignedInteger('votes');
```

<a name="column-method-unsignedMediumInteger"></a>
#### `unsignedMediumInteger()` {.collection-method}

Метод `unsignedMediumInteger` створює стовпець, еквівалентний `UNSIGNED MEDIUMINT`:

```php
$table->unsignedMediumInteger('votes');
```

<a name="column-method-unsignedSmallInteger"></a>
#### `unsignedSmallInteger()` {.collection-method}

Метод `unsignedSmallInteger` створює стовпець, еквівалентний `UNSIGNED SMALLINT`:

```php
$table->unsignedSmallInteger('votes');
```

<a name="column-method-unsignedTinyInteger"></a>
#### `unsignedTinyInteger()` {.collection-method}

Метод `unsignedTinyInteger` створює стовпець, еквівалентний `UNSIGNED TINYINT`:

```php
$table->unsignedTinyInteger('votes');
```

<a name="column-method-ulidMorphs"></a>
#### `ulidMorphs()` {.collection-method}

Метод `ulidMorphs` - це зручний метод, який додає стовпець `{column}_type`, еквівалентний `VARCHAR`, і стовпець `{column}_id`, еквівалентний `CHAR(26)`.

Цей метод призначений для опису стовпців, потрібних для поліморфного [зв'язку Eloquent](/docs/{{version}}/eloquent-relationships), що використовує ідентифікатори ULID. У прикладі нижче буде створено стовпці `taggable_type` і `taggable_id`:

```php
$table->ulidMorphs('taggable');
```

<a name="column-method-uuidMorphs"></a>
#### `uuidMorphs()` {.collection-method}

Метод `uuidMorphs` - це зручний метод, який додає стовпець `{column}_type`, еквівалентний `VARCHAR`, і стовпець `{column}_id`, еквівалентний `CHAR(36)`.

Цей метод призначений для опису стовпців, потрібних для [поліморфного зв'язку Eloquent](/docs/{{version}}/eloquent-relationships#polymorphic-relationships), що використовує ідентифікатори UUID. У прикладі нижче буде створено стовпці `taggable_type` і `taggable_id`:

```php
$table->uuidMorphs('taggable');
```

<a name="column-method-ulid"></a>
#### `ulid()` {.collection-method}

Метод `ulid` створює стовпець, еквівалентний `ULID`:

```php
$table->ulid('id');
```

<a name="column-method-uuid"></a>
#### `uuid()` {.collection-method}

Метод `uuid` створює стовпець, еквівалентний `UUID`:

```php
$table->uuid('id');
```

<a name="column-method-vector"></a>
#### `vector()` {.collection-method}

Метод `vector` створює стовпець типу `vector`:

```php
$table->vector('embedding', dimensions: 1536);
```

Векторні стовпці підтримуються на PostgreSQL-підключеннях, що використовують розширення `pgvector`, та MariaDB 11.7 чи новіше. При використанні PostgreSQL `pgvector` має бути завантажено перед створенням векторних стовпців:

```php
Schema::ensureVectorExtensionExists();
```

Щоб пришвидшити [запити на схожість векторів](/docs/{{version}}/queries#vector-similarity-clauses), ви можете додати векторний індекс до стовпця. Виклик методу `index` на стовпці `vector` створює векторний індекс, використовуючи косинусну відстань:

```php
$table->vector('embedding', dimensions: 1536)->index();
```

<a name="column-method-year"></a>
#### `year()` {.collection-method}

Метод `year` створює стовпець, еквівалентний `YEAR`:

```php
$table->year('birth_year');
```

<a name="column-modifiers"></a>
### Модифікатори стовпців

Крім перелічених вище типів, є кілька «модифікаторів» стовпців, якими ви можете скористатися, додаючи стовпець до таблиці. Наприклад, щоб зробити стовпець «nullable», викличте метод `nullable`:

```php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::table('users', function (Blueprint $table) {
    $table->string('email')->nullable();
});
```

У таблиці нижче наведено всі доступні модифікатори стовпців. Цей список не містить [модифікаторів індексів](#creating-indexes):

<div class="overflow-auto">

| Модифікатор                         | Опис                                                                                           |
| ----------------------------------- | ---------------------------------------------------------------------------------------------- |
| `->after('column')`                 | Розмістити стовпець «після» іншого стовпця (MariaDB / MySQL).                                   |
| `->autoIncrement()`                 | Зробити стовпці `INTEGER` автоінкрементними (первинний ключ).                                   |
| `->charset('utf8mb4')`              | Задати кодування для стовпця (MariaDB / MySQL).                                                 |
| `->collation('utf8mb4_unicode_ci')` | Задати порядок сортування для стовпця.                                                          |
| `->comment('my comment')`           | Додати коментар до стовпця (MariaDB / MySQL / PostgreSQL).                                      |
| `->default($value)`                 | Задати значення стовпця за замовчуванням.                                                       |
| `->first()`                         | Розмістити стовпець «першим» у таблиці (MariaDB / MySQL).                                       |
| `->from($integer)`                  | Задати початкове значення автоінкрементного поля (MariaDB / MySQL / PostgreSQL).                |
| `->instant()`                       | Додати або змінити стовпець миттєвою операцією (MySQL).                                         |
| `->invisible()`                     | Зробити стовпець «невидимим» для запитів `SELECT *` (MariaDB / MySQL).                          |
| `->lock($mode)`                     | Задати режим блокування для операції зі стовпцем (MySQL).                                       |
| `->nullable($value = true)`         | Дозволити вставляти в стовпець значення `NULL`.                                                 |
| `->storedAs($expression)`           | Створити збережений генерований стовпець (MariaDB / MySQL / PostgreSQL / SQLite).               |
| `->unsigned()`                      | Зробити стовпці `INTEGER` типу `UNSIGNED` (MariaDB / MySQL).                                    |
| `->using($expression)`              | Задати вираз для приведення типу при зміні типу стовпця (PostgreSQL).                           |
| `->useCurrent()`                    | Задати стовпцям `TIMESTAMP` значення за замовчуванням `CURRENT_TIMESTAMP`.                      |
| `->useCurrentOnUpdate()`            | Задати стовпцям `TIMESTAMP` значення `CURRENT_TIMESTAMP` при оновленні запису (MariaDB / MySQL). |
| `->virtualAs($expression)`          | Створити віртуальний генерований стовпець (MariaDB / MySQL / SQLite).                           |
| `->generatedAs($expression)`        | Створити identity-стовпець із заданими опціями послідовності (PostgreSQL).                      |
| `->always()`                        | Задати перевагу значень послідовності над вхідними для identity-стовпця (PostgreSQL).            |

</div>

<a name="default-expressions"></a>
#### Вирази за замовчуванням

Модифікатор `default` приймає значення або екземпляр `Illuminate\Database\Query\Expression`. Використання `Expression` не дасть Laravel узяти значення в лапки й дозволить скористатися специфічними для бази функціями. Особливо це стає в пригоді, коли потрібно задати значення за замовчуванням для стовпців JSON:

```php
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Migrations\Migration;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('flights', function (Blueprint $table) {
            $table->id();
            $table->json('movies')->default(new Expression('(JSON_ARRAY())'));
            $table->timestamps();
        });
    }
};
```

> [!WARNING]
> Підтримка виразів за замовчуванням залежить від драйвера вашої бази даних, її версії та типу поля. Звіряйтеся з документацією своєї бази.

<a name="column-order"></a>
#### Порядок стовпців

На MariaDB чи MySQL методом `after` можна додавати стовпці після наявного стовпця в схемі:

```php
$table->after('password', function (Blueprint $table) {
    $table->string('address_line1');
    $table->string('address_line2');
    $table->string('city');
});
```

<a name="instant-column-operations"></a>
#### Миттєві операції зі стовпцями

На MySQL ви можете дописати до опису стовпця модифікатор `instant`, щоб вказати: стовпець слід додати чи змінити «миттєвим» алгоритмом MySQL. Цей алгоритм дозволяє виконати певні зміни схеми без повного перебудування таблиці, тож вони майже миттєві незалежно від її розміру:

```php
$table->string('name')->nullable()->instant();
```

Миттєве додавання стовпців може лише дописувати стовпці в кінець таблиці, тому модифікатор `instant` не можна поєднувати з `after` чи `first`. Крім того, алгоритм підтримує не всі типи стовпців і не всі операції. Якщо запитана операція несумісна, MySQL видасть помилку.

Щоб дізнатися, які операції сумісні з миттєвою зміною стовпців, звіряйтеся з [документацією MySQL](https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html).

<a name="ddl-locking"></a>
#### Блокування DDL

На MySQL ви можете дописати модифікатор `lock` до опису стовпця, індексу чи зовнішнього ключа, щоб керувати блокуванням таблиці під час операцій зі схемою. MySQL підтримує кілька режимів блокування: `none` дозволяє одночасні читання й записи, `shared` дозволяє одночасні читання, але блокує записи, `exclusive` блокує будь-який одночасний доступ, а `default` дає MySQL самому обрати найдоречніший режим:

```php
$table->string('name')->lock('none');

$table->index('email')->lock('shared');
```

Якщо запитаний режим блокування несумісний з операцією, MySQL видасть помилку. Модифікатор `lock` можна поєднувати з модифікатором `instant`, щоб додатково оптимізувати зміни схеми:

```php
$table->string('name')->instant()->lock('none');
```

<a name="modifying-columns"></a>
### Зміна стовпців

Метод `change` дозволяє змінити тип і атрибути наявних стовпців. Наприклад, ви можете захотіти збільшити розмір стовпця `string`. Щоб побачити `change` у дії, збільшимо розмір стовпця `name` з 25 до 50. Для цього ми просто описуємо новий стан стовпця й викликаємо метод `change`:

```php
Schema::table('users', function (Blueprint $table) {
    $table->string('name', 50)->change();
});
```

Змінюючи стовпець, ви маєте явно вказати всі модифікатори, які хочете на ньому зберегти, - будь-який пропущений атрибут буде скинуто. Наприклад, щоб зберегти атрибути `unsigned`, `default` і `comment`, кожен модифікатор потрібно викликати явно:

```php
Schema::table('users', function (Blueprint $table) {
    $table->integer('votes')->unsigned()->default(1)->comment('my comment')->change();
});
```

Метод `change` не змінює індексів стовпця. Тому, змінюючи стовпець, ви можете скористатися модифікаторами індексів, щоб явно додати чи видалити індекс:

```php
// Add an index...
$table->bigIncrements('id')->primary()->change();

// Drop an index...
$table->char('postal_code', 10)->unique(false)->change();
```

<a name="postgresql-column-modifications"></a>
#### Зміна стовпців PostgreSQL

Під час зміни типу стовпця в PostgreSQL ви можете використати модифікатор `using`, щоб вказати вираз для перетворення наявних значень:

```php
Schema::table('users', function (Blueprint $table) {
    $table->date('birthday')->using('birthday::date')->change();
});
```

<a name="renaming-columns"></a>
### Перейменування стовпців

Щоб перейменувати стовпець, скористайтеся методом `renameColumn` конструктора схеми:

```php
Schema::table('users', function (Blueprint $table) {
    $table->renameColumn('from', 'to');
});
```

<a name="dropping-columns"></a>
### Видалення стовпців

Щоб видалити стовпець, скористайтеся методом `dropColumn` конструктора схеми:

```php
Schema::table('users', function (Blueprint $table) {
    $table->dropColumn('votes');
});
```

Ви можете видалити з таблиці кілька стовпців, передавши методу `dropColumn` масив їхніх назв:

```php
Schema::table('users', function (Blueprint $table) {
    $table->dropColumn(['votes', 'avatar', 'location']);
});
```

<a name="available-command-aliases"></a>
#### Доступні псевдоніми команд

Laravel має кілька зручних методів для видалення поширених типів стовпців. Кожен із них описано в таблиці нижче:

<div class="overflow-auto">

| Команда                             | Опис                                                  |
| ----------------------------------- | ----------------------------------------------------- |
| `$table->dropMorphs('morphable');`  | Видалити стовпці `morphable_type` і `morphable_id`.   |
| `$table->dropRememberToken();`      | Видалити стовпець `remember_token`.                   |
| `$table->dropSoftDeletes();`        | Видалити стовпець `deleted_at`.                       |
| `$table->dropSoftDeletesTz();`      | Псевдонім методу `dropSoftDeletes()`.                 |
| `$table->dropTimestamps();`         | Видалити стовпці `created_at` і `updated_at`.         |
| `$table->dropTimestampsTz();`       | Псевдонім методу `dropTimestamps()`.                  |

</div>

<a name="indexes"></a>
## Індекси

<a name="creating-indexes"></a>
### Створення індексів

Конструктор схеми Laravel підтримує кілька типів індексів. У прикладі нижче створюється новий стовпець `email` і вказується, що його значення мають бути унікальними. Щоб створити індекс, ми дописуємо метод `unique` до опису стовпця:

```php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::table('users', function (Blueprint $table) {
    $table->string('email')->unique();
});
```

Або ж ви можете створити індекс після опису стовпця. Для цього викличте метод `unique` на blueprint конструктора схеми. Цей метод приймає назву стовпця, який має отримати унікальний індекс:

```php
$table->unique('email');
```

Ви можете навіть передати методу індексу масив стовпців, щоб створити складений (композитний) індекс:

```php
$table->index(['account_id', 'created_at']);
```

Створюючи індекс, Laravel автоматично згенерує його назву на основі таблиці, назв стовпців і типу індексу, але ви можете передати другий аргумент, щоб задати назву самостійно:

```php
$table->unique('email', 'unique_email');
```

<a name="available-index-types"></a>
#### Доступні типи індексів

Клас blueprint конструктора схеми Laravel має методи для створення кожного типу індексу, що його підтримує Laravel. Кожен метод індексу приймає необов'язковий другий аргумент - назву індексу. Якщо його не передати, назву буде утворено з назв таблиці й стовпців, використаних для індексу, а також типу індексу. Усі доступні методи індексів описано в таблиці нижче:

<div class="overflow-auto">

| Команда                                          | Опис                                                            |
| ------------------------------------------------ | -------------------------------------------------------------- |
| `$table->primary('id');`                         | Додає первинний ключ.                                          |
| `$table->primary(['id', 'parent_id']);`          | Додає композитні ключі.                                        |
| `$table->unique('email');`                       | Додає унікальний індекс.                                       |
| `$table->index('state');`                        | Додає індекс.                                                  |
| `$table->fullText('body');`                      | Додає повнотекстовий індекс (MariaDB / MySQL / PostgreSQL).     |
| `$table->fullText('body')->language('english');` | Додає повнотекстовий індекс заданої мови (PostgreSQL).          |
| `$table->spatialIndex('location');`              | Додає просторовий індекс (окрім SQLite).                        |
| `$table->vectorIndex('embedding');`              | Додає векторний індекс (MariaDB / PostgreSQL).                  |

</div>

<a name="online-index-creation"></a>
#### Створення індексу без блокування

За замовчуванням створення індексу на великій таблиці може заблокувати її й перекрити читання чи записи, поки індекс будується. На PostgreSQL або SQL Server ви можете дописати до опису індексу метод `online`, щоб створити індекс без блокування таблиці, - і ваш застосунок зможе далі читати й писати дані під час створення індексу:

```php
$table->unique('email')->online();
```

На PostgreSQL це додає до запиту створення індексу опцію `CONCURRENTLY`. На SQL Server - опцію `WITH (online = on)`.

На MySQL ви можете дописати до опису індексу чи зовнішнього ключа модифікатор `inplace`, щоб операція виконувалась за алгоритмом `INPLACE`:

```php
$table->index('email')->inplace();

$table->foreign('user_id')->references('id')->on('users')->inplace();
```

Модифікатор `inplace` можна поєднувати з модифікатором `lock`, щоб контролювати блокування таблиці під час операції:

```php
$table->index('email')->inplace()->lock('none');
```

При використанні модифікатора `inplace` для операції із зовнішнім ключем перевірки зовнішніх ключів мають бути вимкнені.

Дивіться [документацію MySQL](https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html), щоб дізнатися, які операції підтримують алгоритм `INPLACE` і режими блокування.

<a name="renaming-indexes"></a>
### Перейменування індексів

Щоб перейменувати індекс, скористайтеся методом `renameIndex` blueprint конструктора схеми. Він приймає поточну назву індексу першим аргументом і бажану назву - другим:

```php
$table->renameIndex('from', 'to');
```

<a name="dropping-indexes"></a>
### Видалення індексів

Щоб видалити індекс, ви маєте вказати його назву. За замовчуванням Laravel автоматично призначає назву індексу на основі назви таблиці, назви проіндексованого стовпця й типу індексу. Ось кілька прикладів:

<div class="overflow-auto">

| Команда                                                       | Опис                                                        |
| ------------------------------------------------------------- | ----------------------------------------------------------- |
| `$table->dropPrimary('users_id_primary');`                    | Видалити первинний ключ із таблиці «users».                  |
| `$table->dropUnique('users_email_unique');`                   | Видалити унікальний індекс із таблиці «users».               |
| `$table->dropIndex('geo_state_index');`                       | Видалити базовий індекс із таблиці «geo».                    |
| `$table->dropFullText('posts_body_fulltext');`                | Видалити повнотекстовий індекс із таблиці «posts».           |
| `$table->dropSpatialIndex('geo_location_spatialindex');`      | Видалити просторовий індекс із таблиці «geo» (окрім SQLite). |
| `$table->dropVectorIndex('documents_embedding_vectorindex');` | Видалити векторний індекс із таблиці «documents».            |

</div>

Якщо ви передасте методу видалення індексу масив стовпців, назву індексу буде згенеровано за конвенцією на основі назви таблиці, стовпців і типу індексу:

```php
Schema::table('geo', function (Blueprint $table) {
    $table->dropIndex(['state']); // Drops index 'geo_state_index'
});
```

<a name="foreign-key-constraints"></a>
### Обмеження зовнішніх ключів

Laravel також підтримує створення обмежень зовнішніх ключів, які забезпечують цілісність посилань на рівні бази даних. Наприклад, опишімо в таблиці `posts` стовпець `user_id`, що посилається на стовпець `id` таблиці `users`:

```php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::table('posts', function (Blueprint $table) {
    $table->unsignedBigInteger('user_id');

    $table->foreign('user_id')->references('id')->on('users');
});
```

Оскільки такий синтаксис досить громіздкий, Laravel має додаткові, лаконічніші методи, які спираються на конвенції й роблять роботу приємнішою. Якщо створювати стовпець методом `foreignId`, приклад вище можна переписати так:

```php
Schema::table('posts', function (Blueprint $table) {
    $table->foreignId('user_id')->constrained();
});
```

Метод `foreignId` створює стовпець, еквівалентний `UNSIGNED BIGINT`, а метод `constrained` за конвенціями визначає таблицю й стовпець, на які йде посилання. Якщо назва вашої таблиці не відповідає конвенціям Laravel, ви можете передати її методу `constrained` вручну. Крім того, можна задати й назву згенерованого індексу:

```php
Schema::table('posts', function (Blueprint $table) {
    $table->foreignId('user_id')->constrained(
        table: 'users', indexName: 'posts_user_id'
    );
});
```

Ви також можете задати бажану дію для властивостей обмеження «on delete» і «on update»:

```php
$table->foreignId('user_id')
    ->constrained()
    ->onUpdate('cascade')
    ->onDelete('cascade');
```

Для цих дій є й альтернативний, виразніший синтаксис:

<div class="overflow-auto">

| Метод                         | Опис                                              |
| ----------------------------- | ------------------------------------------------- |
| `$table->cascadeOnUpdate();`  | Оновлення мають каскадуватися.                     |
| `$table->restrictOnUpdate();` | Оновлення мають бути обмежені.                     |
| `$table->nullOnUpdate();`     | Оновлення мають задавати зовнішньому ключу null.   |
| `$table->noActionOnUpdate();` | Жодних дій при оновленні.                          |
| `$table->cascadeOnDelete();`  | Видалення мають каскадуватися.                     |
| `$table->restrictOnDelete();` | Видалення мають бути обмежені.                     |
| `$table->nullOnDelete();`     | Видалення мають задавати зовнішньому ключу null.   |
| `$table->noActionOnDelete();` | Забороняє видалення, якщо є дочірні записи.        |

</div>

Будь-які додаткові [модифікатори стовпців](#column-modifiers) слід викликати перед методом `constrained`:

```php
$table->foreignId('user_id')
    ->nullable()
    ->constrained();
```

<a name="dropping-foreign-keys"></a>
#### Видалення зовнішніх ключів

Щоб видалити зовнішній ключ, скористайтеся методом `dropForeign`, передавши аргументом назву обмеження, яке слід видалити. Обмеження зовнішніх ключів мають ту саму конвенцію назв, що й індекси. Іншими словами, назва обмеження зовнішнього ключа складається з назви таблиці та стовпців в обмеженні, після яких додається суфікс «\_foreign»:

```php
$table->dropForeign('posts_user_id_foreign');
```

Або ж ви можете передати методу `dropForeign` масив із назвою стовпця, що містить зовнішній ключ. Масив буде перетворено на назву обмеження за конвенціями Laravel:

```php
$table->dropForeign(['user_id']);
```

<a name="toggling-foreign-key-constraints"></a>
#### Увімкнення та вимкнення обмежень зовнішніх ключів

Увімкнути або вимкнути обмеження зовнішніх ключів у ваших міграціях можна такими методами:

```php
Schema::enableForeignKeyConstraints();

Schema::disableForeignKeyConstraints();

Schema::withoutForeignKeyConstraints(function () {
    // Constraints disabled within this closure...
});
```

> [!WARNING]
> SQLite за замовчуванням вимикає обмеження зовнішніх ключів. Працюючи з SQLite, обов'язково [увімкніть підтримку зовнішніх ключів](/docs/{{version}}/database#configuration) у конфігурації бази даних, перш ніж намагатися створювати їх у міграціях.

<a name="events"></a>
## Події

Для зручності кожна операція міграції надсилає [подію](/docs/{{version}}/events). За винятком `SchemaDumped` і `SchemaLoaded`, усі наведені нижче події реалізують інтерфейс `Illuminate\Contracts\Database\Events\MigrationEvent`:

<div class="overflow-auto">

| Клас                                             | Опис                                             |
| ------------------------------------------------ | ------------------------------------------------ |
| `Illuminate\Database\Events\DatabaseRefreshed`   | Команда `migrate:fresh` або `migrate:refresh` завершилася. |
| `Illuminate\Database\Events\MigrationsStarted`   | Пакет міграцій ось-ось буде виконано.            |
| `Illuminate\Database\Events\MigrationsEnded`     | Пакет міграцій завершився.                       |
| `Illuminate\Database\Events\MigrationStarted`    | Окрему міграцію ось-ось буде виконано.           |
| `Illuminate\Database\Events\MigrationEnded`      | Окрема міграція завершилася.                     |
| `Illuminate\Database\Events\NoPendingMigrations` | Команда міграції не знайшла міграцій в очікуванні. |
| `Illuminate\Database\Events\SchemaDumped`        | Дамп схеми бази даних завершився.                |
| `Illuminate\Database\Events\SchemaLoaded`        | Наявний дамп схеми бази даних завантажено.       |

</div>

---

Джерело: https://laravelukraine.com/docs/13.x/seeding

# База даних: наповнення

- [Вступ](#introduction)
- [Написання сідерів](#writing-seeders)
    - [Використання фабрик моделей](#using-model-factories)
    - [Виклик додаткових сідерів](#calling-additional-seeders)
    - [Вимкнення подій моделей](#muting-model-events)
- [Запуск сідерів](#running-seeders)

<a name="introduction"></a>
## Вступ

Laravel уміє наповнювати вашу базу даних тестовими даними за допомогою класів-сідерів (seeder). Усі класи сідерів зберігаються в каталозі `database/seeders`. За замовчуванням для вас уже визначено клас `DatabaseSeeder`. З нього ви можете методом `call` запускати інші класи сідерів і таким чином керувати порядком наповнення.

> [!NOTE]
> Під час наповнення бази даних [захист від масового призначення](/docs/{{version}}/eloquent#mass-assignment) автоматично вимикається.

<a name="writing-seeders"></a>
## Написання сідерів

Щоб згенерувати сідер, виконайте [artisan-команду](/docs/{{version}}/artisan) `make:seeder`. Усі згенеровані фреймворком сідери потраплять до каталогу `database/seeders`:

```shell
php artisan make:seeder UserSeeder
```

За замовчуванням клас сідера містить лише один метод - `run`. Він викликається при виконанні [artisan-команди](/docs/{{version}}/artisan) `db:seed`. Усередині методу `run` ви можете наповнювати базу даних як завгодно: вручну вставляти дані через [конструктор запитів](/docs/{{version}}/queries) або скористатися [фабриками моделей Eloquent](/docs/{{version}}/eloquent-factories).

Наприклад, змінімо стандартний клас `DatabaseSeeder` і додаймо до методу `run` вставку даних:

```php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

class DatabaseSeeder extends Seeder
{
    /**
     * Run the database seeders.
     */
    public function run(): void
    {
        DB::table('users')->insert([
            'name' => Str::random(10),
            'email' => Str::random(10).'@example.com',
            'password' => Hash::make('password'),
        ]);
    }
}
```

> [!NOTE]
> У сигнатурі методу `run` ви можете вказати типи будь-яких потрібних вам залежностей. Laravel автоматично отримає їх із [сервіс-контейнера](/docs/{{version}}/container).

<a name="using-model-factories"></a>
### Використання фабрик моделей

Звісно, вручну задавати атрибути для кожної моделі - марудна робота. Замість цього ви можете скористатися [фабриками моделей](/docs/{{version}}/eloquent-factories), щоб зручно згенерувати велику кількість записів. Спершу перегляньте [документацію з фабрик моделей](/docs/{{version}}/eloquent-factories), щоб дізнатися, як їх описувати.

Наприклад, створімо 50 користувачів, кожен із яких має один пов'язаний пост:

```php
use App\Models\User;

/**
 * Run the database seeders.
 */
public function run(): void
{
    User::factory()
        ->count(50)
        ->hasPosts(1)
        ->create();
}
```

<a name="calling-additional-seeders"></a>
### Виклик додаткових сідерів

Усередині класу `DatabaseSeeder` ви можете методом `call` запускати додаткові класи сідерів. Метод `call` дозволяє розбити наповнення бази на кілька файлів, щоб жоден клас сідера не розростався надміру. Він приймає масив класів сідерів, які потрібно виконати:

```php
/**
 * Run the database seeders.
 */
public function run(): void
{
    $this->call([
        UserSeeder::class,
        PostSeeder::class,
        CommentSeeder::class,
    ]);
}
```

<a name="muting-model-events"></a>
### Вимкнення подій моделей

Під час наповнення бази ви можете захотіти, щоб моделі не надсилали події. Цього можна досягти за допомогою трейта `WithoutModelEvents`. Трейт `WithoutModelEvents` гарантує, що жодної події моделі не буде надіслано, навіть якщо через метод `call` виконуються додаткові класи сідерів:

```php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;

class DatabaseSeeder extends Seeder
{
    use WithoutModelEvents;

    /**
     * Run the database seeders.
     */
    public function run(): void
    {
        $this->call([
            UserSeeder::class,
        ]);
    }
}
```

<a name="running-seeders"></a>
## Запуск сідерів

Щоб наповнити базу даних, виконайте artisan-команду `db:seed`. За замовчуванням команда `db:seed` запускає клас `Database\Seeders\DatabaseSeeder`, який, своєю чергою, може викликати інші класи сідерів. Втім, опцією `--class` ви можете вказати конкретний клас сідера, який слід запустити окремо:

```shell
php artisan db:seed

php artisan db:seed --class=UserSeeder
```

Ви також можете наповнити базу даних командою `migrate:fresh` з опцією `--seed`: вона видалить усі таблиці та повторно виконає всі ваші міграції. Ця команда стане в пригоді, коли треба повністю перебудувати базу даних. Опцією `--seeder` можна вказати конкретний сідер, який слід запустити:

```shell
php artisan migrate:fresh --seed

php artisan migrate:fresh --seed --seeder=UserSeeder
```

<a name="forcing-seeding-production"></a>
#### Примусовий запуск сідерів на продакшні

Деякі операції наповнення можуть змінити або знищити дані. Щоб убезпечити вас від запуску таких команд на продакшн-базі, у середовищі `production` Laravel запитає підтвердження, перш ніж виконати сідери. Щоб запустити сідери без запитання, скористайтеся прапорцем `--force`:

```shell
php artisan db:seed --force
```

---

Джерело: https://laravelukraine.com/docs/13.x/redis

# Redis

- [Вступ](#introduction)
- [Конфігурація](#configuration)
    - [Кластери](#clusters)
    - [Predis](#predis)
    - [PhpRedis](#phpredis)
- [Робота з Redis](#interacting-with-redis)
    - [Транзакції](#transactions)
    - [Конвеєр команд](#pipelining-commands)
- [Pub / Sub](#pubsub)

<a name="introduction"></a>
## Вступ

[Redis](https://redis.io) - це відкрите сховище пар «ключ - значення» з широкими можливостями. Його часто називають сервером структур даних, бо ключі можуть містити [рядки](https://redis.io/docs/latest/develop/data-types/strings/), [хеші](https://redis.io/docs/latest/develop/data-types/hashes/), [списки](https://redis.io/docs/latest/develop/data-types/lists/), [множини](https://redis.io/docs/latest/develop/data-types/sets/) та [відсортовані множини](https://redis.io/docs/latest/develop/data-types/sorted-sets/).

Перед тим як використовувати Redis із Laravel, радимо встановити PHP-розширення [PhpRedis](https://github.com/phpredis/phpredis) через PECL. Встановити його складніше, ніж PHP-пакети «з користувацького простору», але для застосунків, які активно працюють із Redis, він може дати кращу продуктивність. Якщо ви користуєтеся [Laravel Sail](/docs/{{version}}/sail), це розширення вже встановлене в Docker-контейнері вашого застосунку.

Якщо встановити розширення PhpRedis не вдається, ви можете встановити через Composer пакет `predis/predis`. Predis - це клієнт Redis, повністю написаний на PHP, і він не потребує додаткових розширень:

```shell
composer require predis/predis
```

<a name="configuration"></a>
## Конфігурація

Налаштування Redis для вашого застосунку лежать у файлі `config/database.php`. У ньому ви побачите масив `redis` зі серверами Redis, які використовує ваш застосунок:

```php
'redis' => [

    'client' => env('REDIS_CLIENT', 'phpredis'),

    'options' => [
        'cluster' => env('REDIS_CLUSTER', 'redis'),
        'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'),
    ],

    'default' => [
        'url' => env('REDIS_URL'),
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'username' => env('REDIS_USERNAME'),
        'password' => env('REDIS_PASSWORD'),
        'port' => env('REDIS_PORT', '6379'),
        'database' => env('REDIS_DB', '0'),
    ],

    'cache' => [
        'url' => env('REDIS_URL'),
        'host' => env('REDIS_HOST', '127.0.0.1'),
        'username' => env('REDIS_USERNAME'),
        'password' => env('REDIS_PASSWORD'),
        'port' => env('REDIS_PORT', '6379'),
        'database' => env('REDIS_CACHE_DB', '1'),
    ],

],
```

Кожен сервер Redis, описаний у вашому файлі конфігурації, має мати назву, хост і порт - хіба що ви задаєте єдиний URL, який представляє підключення до Redis:

```php
'redis' => [

    'client' => env('REDIS_CLIENT', 'phpredis'),

    'options' => [
        'cluster' => env('REDIS_CLUSTER', 'redis'),
        'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'),
    ],

    'default' => [
        'url' => 'tcp://127.0.0.1:6379?database=0',
    ],

    'cache' => [
        'url' => 'tls://user:password@127.0.0.1:6380?database=1',
    ],

],
```

<a name="configuring-the-connection-scheme"></a>
#### Налаштування схеми підключення

За замовчуванням клієнти Redis підключаються до ваших серверів за схемою `tcp`; втім, ви можете скористатися шифруванням TLS / SSL, задавши опцію `scheme` у масиві конфігурації сервера Redis:

```php
'default' => [
    'scheme' => 'tls',
    'url' => env('REDIS_URL'),
    'host' => env('REDIS_HOST', '127.0.0.1'),
    'username' => env('REDIS_USERNAME'),
    'password' => env('REDIS_PASSWORD'),
    'port' => env('REDIS_PORT', '6379'),
    'database' => env('REDIS_DB', '0'),
],
```

<a name="clusters"></a>
### Кластери

Якщо ваш застосунок працює з кластером серверів Redis, опишіть ці кластери під ключем `clusters` конфігурації Redis. За замовчуванням цього ключа немає, тож вам доведеться створити його у файлі `config/database.php` вашого застосунку:

```php
'redis' => [

    'client' => env('REDIS_CLIENT', 'phpredis'),

    'options' => [
        'cluster' => env('REDIS_CLUSTER', 'redis'),
        'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'),
    ],

    'clusters' => [
        'default' => [
            [
                'url' => env('REDIS_URL'),
                'host' => env('REDIS_HOST', '127.0.0.1'),
                'username' => env('REDIS_USERNAME'),
                'password' => env('REDIS_PASSWORD'),
                'port' => env('REDIS_PORT', '6379'),
                'database' => env('REDIS_DB', '0'),
            ],
        ],
    ],

    // ...
],
```

За замовчуванням Laravel використовує нативну кластеризацію Redis, бо значення конфігурації `options.cluster` дорівнює `redis`. Кластеризація Redis - чудовий вибір за замовчуванням, бо вона коректно обробляє перемикання при відмові.

Laravel також підтримує шардинг на боці клієнта під час роботи з Predis. Проте шардинг на боці клієнта не обробляє відмов, тому підходить насамперед для тимчасових кешованих даних, які можна взяти з іншого основного сховища.

Якщо ви хочете використовувати шардинг на боці клієнта замість нативної кластеризації Redis, приберіть значення конфігурації `options.cluster` з файлу `config/database.php` вашого застосунку:

```php
'redis' => [

    'client' => env('REDIS_CLIENT', 'phpredis'),

    'clusters' => [
        // ...
    ],

    // ...
],
```

<a name="predis"></a>
### Predis

Якщо ви хочете, щоб ваш застосунок працював із Redis через пакет Predis, переконайтеся, що змінна оточення `REDIS_CLIENT` має значення `predis`:

```php
'redis' => [

    'client' => env('REDIS_CLIENT', 'predis'),

    // ...
],
```

Крім стандартних опцій конфігурації, Predis підтримує додаткові [параметри підключення](https://github.com/nrk/predis/wiki/Connection-Parameters), які можна задати для кожного з ваших серверів Redis. Щоб скористатися ними, додайте їх до конфігурації сервера Redis у файлі `config/database.php` вашого застосунку:

```php
'default' => [
    'url' => env('REDIS_URL'),
    'host' => env('REDIS_HOST', '127.0.0.1'),
    'username' => env('REDIS_USERNAME'),
    'password' => env('REDIS_PASSWORD'),
    'port' => env('REDIS_PORT', '6379'),
    'database' => env('REDIS_DB', '0'),
    'read_write_timeout' => 60,
],
```

<a name="phpredis"></a>
### PhpRedis

За замовчуванням Laravel спілкується з Redis через розширення PhpRedis. Який саме клієнт використовувати, визначає значення опції конфігурації `redis.client`, що зазвичай відображає значення змінної оточення `REDIS_CLIENT`:

```php
'redis' => [

    'client' => env('REDIS_CLIENT', 'phpredis'),

    // ...
],
```

Крім стандартних опцій конфігурації, PhpRedis підтримує такі додаткові параметри підключення: `name`, `persistent`, `persistent_id`, `prefix`, `read_timeout`, `retry_interval`, `max_retries`, `backoff_algorithm`, `backoff_base`, `backoff_cap`, `timeout` і `context`. Будь-яку з цих опцій можна додати до конфігурації сервера Redis у файлі `config/database.php`:

```php
'default' => [
    'url' => env('REDIS_URL'),
    'host' => env('REDIS_HOST', '127.0.0.1'),
    'username' => env('REDIS_USERNAME'),
    'password' => env('REDIS_PASSWORD'),
    'port' => env('REDIS_PORT', '6379'),
    'database' => env('REDIS_DB', '0'),
    'read_timeout' => 60,
    'context' => [
        // 'auth' => ['username', 'secret'],
        // 'stream' => ['verify_peer' => false],
    ],
],
```

<a name="retry-and-backoff-configuration"></a>
#### Налаштування повторних спроб і затримок

Опціями `retry_interval`, `max_retries`, `backoff_algorithm`, `backoff_base` і `backoff_cap` можна налаштувати, як клієнт PhpRedis має перепідключатися до сервера Redis. Підтримуються такі алгоритми затримки: `default`, `decorrelated_jitter`, `equal_jitter`, `exponential`, `uniform` і `constant`:

```php
'default' => [
    'url' => env('REDIS_URL'),
    'host' => env('REDIS_HOST', '127.0.0.1'),
    'username' => env('REDIS_USERNAME'),
    'password' => env('REDIS_PASSWORD'),
    'port' => env('REDIS_PORT', '6379'),
    'database' => env('REDIS_DB', '0'),
    'max_retries' => env('REDIS_MAX_RETRIES', 3),
    'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'),
    'backoff_base' => env('REDIS_BACKOFF_BASE', 100),
    'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000),
],
```

Laravel автоматично повторює безпечні команди читання один раз після тимчасового збою підключення. Опцію `command_retries` можна використати для налаштування кількості повторних спроб для всіх команд Redis:

```php
'default' => [
    // ...
    'command_retries' => env('REDIS_COMMAND_RETRIES', 0),
],
```

Predis 3.4.0 і новіші підтримують вбудоване налаштування повторних спроб і затримок через клас `Retry`. Кількість спроб задається опцією `max_retries`, а стратегія затримки - опцією `retry`. Опція `retry` має бути масивом, ключами якого є один із таких класів стратегій: `NoBackoff`, `EqualBackoff` або `ExponentialBackoff`:

```php
use Predis\Retry\Strategy\ExponentialBackoff;

'default' => [
    'url' => env('REDIS_URL'),
    // ...
    'retry' => [
        ExponentialBackoff::class => [
            env('REDIS_BACKOFF_BASE', 100),
            env('REDIS_BACKOFF_CAP', 1000),
            true, // Enable jitter...
        ],
    ],
    'max_retries' => env('REDIS_MAX_RETRIES', 3),
],
```

Коли ви використовуєте Predis із кластером Redis, налаштування повторних спроб можна задати в опції `parameters` конфігурації кластера:

```php
use Predis\Retry\Strategy\NoBackoff;

'clusters' => [
    'default' => [
        // ...
    ],
],

'options' => [
    'cluster' => env('REDIS_CLUSTER', 'redis'),
    'parameters' => [
        'retry' => [
            NoBackoff::class => [],
        ],
        'max_retries' => env('REDIS_MAX_RETRIES', 3),
    ],
],
```

<a name="unix-socket-connections"></a>
#### Підключення через Unix-сокет

Підключення до Redis можна налаштувати й на Unix-сокети замість TCP. Це може підвищити продуктивність, бо прибирає накладні витрати TCP для підключень до екземплярів Redis на тому самому сервері, де й ваш застосунок. Щоб налаштувати Redis на Unix-сокет, задайте змінній оточення `REDIS_HOST` шлях до сокета Redis, а змінній `REDIS_PORT` - значення `0`:

```env
REDIS_HOST=/run/redis/redis.sock
REDIS_PORT=0
```

<a name="phpredis-serialization"></a>
#### Серіалізація та стиснення в PhpRedis

Розширення PhpRedis можна також налаштувати на різні серіалізатори та алгоритми стиснення. Ці алгоритми задаються в масиві `options` вашої конфігурації Redis:

```php
'redis' => [

    'client' => env('REDIS_CLIENT', 'phpredis'),

    'options' => [
        'cluster' => env('REDIS_CLUSTER', 'redis'),
        'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'),
        'serializer' => Redis::SERIALIZER_MSGPACK,
        'compression' => Redis::COMPRESSION_LZ4,
    ],

    // ...
],
```

Наразі підтримуються такі серіалізатори: `Redis::SERIALIZER_NONE` (за замовчуванням), `Redis::SERIALIZER_PHP`, `Redis::SERIALIZER_JSON`, `Redis::SERIALIZER_IGBINARY` і `Redis::SERIALIZER_MSGPACK`.

Підтримувані алгоритми стиснення: `Redis::COMPRESSION_NONE` (за замовчуванням), `Redis::COMPRESSION_LZF`, `Redis::COMPRESSION_ZSTD` і `Redis::COMPRESSION_LZ4`.

<a name="interacting-with-redis"></a>
## Робота з Redis

Працювати з Redis можна, викликаючи різні методи [фасаду](/docs/{{version}}/facades) `Redis`. Фасад `Redis` підтримує динамічні методи: ви можете викликати на ньому будь-яку [команду Redis](https://redis.io/commands), і її буде передано безпосередньо до Redis. У цьому прикладі ми викличемо команду Redis `GET`, звернувшись до методу `get` фасаду `Redis`:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Redis;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * Show the profile for the given user.
     */
    public function show(string $id): View
    {
        return view('user.profile', [
            'user' => Redis::get('user:profile:'.$id)
        ]);
    }
}
```

Як згадано вище, на фасаді `Redis` можна викликати будь-яку команду Redis. Laravel використовує магічні методи, щоб передати команди на сервер Redis. Якщо команда Redis очікує аргументи, передайте їх до відповідного методу фасаду:

```php
use Illuminate\Support\Facades\Redis;

Redis::set('name', 'Taylor');

$values = Redis::lrange('names', 5, 10);
```

Або ж ви можете передавати команди на сервер методом `command` фасаду `Redis`: першим аргументом він приймає назву команди, а другим - масив значень:

```php
$values = Redis::command('lrange', ['name', 5, 10]);
```

<a name="using-multiple-redis-connections"></a>
#### Кілька підключень до Redis

Файл `config/database.php` вашого застосунку дозволяє описати кілька підключень чи серверів Redis. Отримати конкретне підключення можна методом `connection` фасаду `Redis`:

```php
$redis = Redis::connection('connection-name');
```

Щоб отримати екземпляр підключення до Redis за замовчуванням, викличте метод `connection` без додаткових аргументів:

```php
$redis = Redis::connection();
```

<a name="transactions"></a>
### Транзакції

Метод `transaction` фасаду `Redis` дає зручну обгортку над нативними командами Redis `MULTI` та `EXEC`. Метод `transaction` приймає єдиний аргумент - замикання. Воно отримає екземпляр підключення до Redis і може надсилати цьому екземпляру будь-які команди. Усі команди Redis, надіслані в межах замикання, буде виконано як одну атомарну транзакцію:

```php
use Redis;
use Illuminate\Support\Facades;

Facades\Redis::transaction(function (Redis $redis) {
    $redis->incr('user_visits', 1);
    $redis->incr('total_visits', 1);
});
```

> [!WARNING]
> Описуючи транзакцію Redis, ви не можете отримувати жодних значень із підключення до Redis. Пам'ятайте: ваша транзакція виконується як одна атомарна операція, а сама операція не виконується, поки все замикання не завершить надсилати команди.

#### Скрипти Lua

Метод `eval` - ще один спосіб виконати кілька команд Redis як одну атомарну операцію. Але `eval` має перевагу: під час цієї операції він може працювати зі значеннями ключів Redis і перевіряти їх. Скрипти Redis пишуть [мовою програмування Lua](https://www.lua.org).

Спершу метод `eval` може здатися дещо страшним, але розберімо простий приклад, щоб зламати кригу. Метод `eval` очікує кілька аргументів. Першим передайте скрипт Lua (рядком). Другим - кількість ключів (цілим числом), з якими працює скрипт. Третім - назви цих ключів. І нарешті, ви можете передати будь-які інші додаткові аргументи, потрібні вам у скрипті.

У цьому прикладі ми збільшимо один лічильник, перевіримо його нове значення й збільшимо другий лічильник, якщо значення першого більше за п'ять. Вкінці повернемо значення першого лічильника:

```php
$value = Redis::eval(<<<'LUA'
    local counter = redis.call("incr", KEYS[1])

    if counter > 5 then
        redis.call("incr", KEYS[2])
    end

    return counter
LUA, 2, 'first-counter', 'second-counter');
```

> [!WARNING]
> Детальніше про скриптинг у Redis читайте в [документації Redis](https://redis.io/commands/eval).

<a name="pipelining-commands"></a>
### Конвеєр команд

Іноді вам потрібно виконати десятки команд Redis. Замість того щоб для кожної команди йти по мережі до сервера Redis, скористайтеся методом `pipeline`. Він приймає один аргумент - замикання, яке отримає екземпляр Redis. Ви надсилаєте цьому екземпляру всі свої команди, і їх буде відправлено на сервер Redis одночасно, щоб зменшити кількість звернень по мережі. Команди все одно виконаються в тому порядку, у якому ви їх надіслали:

```php
use Redis;
use Illuminate\Support\Facades;

Facades\Redis::pipeline(function (Redis $pipe) {
    for ($i = 0; $i < 1000; $i++) {
        $pipe->set("key:$i", $i);
    }
});
```

<a name="pubsub"></a>
## Pub / Sub

Laravel дає зручний інтерфейс до команд Redis `publish` і `subscribe`. Ці команди дозволяють слухати повідомлення в заданому «каналі». Ви можете публікувати повідомлення в канал з іншого застосунку або навіть іншою мовою програмування, що дозволяє легко налагодити зв'язок між застосунками й процесами.

Спершу налаштуємо слухача каналу методом `subscribe`. Розмістимо цей виклик в [artisan-команді](/docs/{{version}}/artisan), бо метод `subscribe` запускає довготривалий процес:

```php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Redis;

class RedisSubscribe extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'redis:subscribe';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Subscribe to a Redis channel';

    /**
     * Execute the console command.
     */
    public function handle(): void
    {
        Redis::subscribe(['test-channel'], function (string $message) {
            echo $message;
        });
    }
}
```

Тепер ми можемо публікувати повідомлення в канал методом `publish`:

```php
use Illuminate\Support\Facades\Redis;

Route::get('/publish', function () {
    // ...

    Redis::publish('test-channel', json_encode([
        'name' => 'Adam Wathan'
    ]));
});
```

<a name="wildcard-subscriptions"></a>
#### Підписки за шаблоном

Методом `psubscribe` ви можете підписатися на канал за шаблоном - це буває корисно, щоб ловити всі повідомлення в усіх каналах. Назву каналу буде передано другим аргументом до вашого замикання:

```php
Redis::psubscribe(['*'], function (string $message, string $channel) {
    echo $message;
});

Redis::psubscribe(['users.*'], function (string $message, string $channel) {
    echo $message;
});
```

---

Джерело: https://laravelukraine.com/docs/13.x/mongodb

# MongoDB

- [Вступ](#introduction)
- [Встановлення](#installation)
    - [Драйвер MongoDB](#mongodb-driver)
    - [Запуск сервера MongoDB](#starting-a-mongodb-server)
    - [Встановлення пакета Laravel MongoDB](#install-the-laravel-mongodb-package)
- [Конфігурація](#configuration)
- [Можливості](#features)

<a name="introduction"></a>
## Вступ

[MongoDB](https://www.mongodb.com/resources/products/fundamentals/why-use-mongodb) - одна з найпопулярніших документоорієнтованих NoSQL-баз даних, яку обирають за високе навантаження на запис (корисно для аналітики чи IoT) і високу доступність (легко налаштувати набори реплік з автоматичним перемиканням при збої). Вона також легко шардить базу для горизонтального масштабування й має потужну мову запитів для агрегації, текстового пошуку чи геопросторових запитів.

Замість того щоб зберігати дані в таблицях із рядків і стовпців, як SQL-бази, кожен запис у базі MongoDB - це документ, описаний у BSON, двійковому представленні даних. Далі застосунки можуть отримувати цю інформацію у форматі JSON. Підтримується широкий набір типів даних, зокрема документи, масиви, вкладені документи та двійкові дані.

Перш ніж користуватися MongoDB разом із Laravel, ми рекомендуємо встановити пакет `mongodb/laravel-mongodb` через Composer. Пакет `laravel-mongodb` офіційно підтримує MongoDB, і хоча PHP підтримує MongoDB нативно через драйвер MongoDB, пакет [Laravel MongoDB](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/) дає багатшу інтеграцію з Eloquent та іншими можливостями Laravel:

```shell
composer require mongodb/laravel-mongodb
```

<a name="installation"></a>
## Встановлення

<a name="mongodb-driver"></a>
### Драйвер MongoDB

Щоб підключитися до бази даних MongoDB, потрібне розширення PHP `mongodb`. Якщо ви розробляєте локально через [Laravel Herd](https://herd.laravel.com) або встановили PHP через `php.new`, це розширення вже є у вашій системі. Проте якщо вам треба встановити його вручну, зробіть це через PECL:

```shell
pecl install mongodb
```

Як альтернативу, ви можете встановити розширення через [PIE](https://github.com/php/pie), офіційний встановлювач розширень PHP:

```shell
pie install mongodb/mongodb-extension
```

Докладніше про встановлення розширення MongoDB для PHP читайте в [інструкції зі встановлення розширення MongoDB PHP](https://www.php.net/manual/en/mongodb.installation.php).

<a name="starting-a-mongodb-server"></a>
### Запуск сервера MongoDB

MongoDB Community Server дозволяє запустити MongoDB локально й доступний для встановлення на Windows, macOS, Linux або як контейнер Docker. Щоб дізнатися, як встановити MongoDB, зверніться до [офіційного посібника зі встановлення MongoDB Community](https://docs.mongodb.com/manual/administration/install-community/).

Рядок підключення до сервера MongoDB можна задати у вашому файлі `.env`:

```ini
MONGODB_URI="mongodb://localhost:27017"
MONGODB_DATABASE="laravel_app"
```

Щоб розмістити MongoDB у хмарі, розгляньте [MongoDB Atlas](https://www.mongodb.com/cloud/atlas).
Щоб звертатися до кластера MongoDB Atlas локально зі свого застосунку, вам треба [додати власну IP-адресу в мережевих налаштуваннях кластера](https://www.mongodb.com/docs/atlas/security/add-ip-address-to-list/) до списку дозволених IP проєкту.

Рядок підключення до MongoDB Atlas також можна задати у вашому файлі `.env`:

```ini
MONGODB_URI="mongodb+srv://<username>:<password>@<cluster>.mongodb.net/<dbname>?retryWrites=true&w=majority"
MONGODB_DATABASE="laravel_app"
```

<a name="install-the-laravel-mongodb-package"></a>
### Встановлення пакета Laravel MongoDB

Нарешті, встановіть пакет Laravel MongoDB через Composer:

```shell
composer require mongodb/laravel-mongodb
```

> [!NOTE]
> Встановлення цього пакета зазнає невдачі, якщо розширення PHP `mongodb` не встановлено. Конфігурація PHP може відрізнятися для CLI та вебсервера, тож переконайтеся, що розширення увімкнено в обох.

<a name="configuration"></a>
## Конфігурація

Налаштувати підключення до MongoDB можна через конфігураційний файл `config/database.php` вашого застосунку. Додайте до нього підключення `mongodb`, що використовує драйвер `mongodb`:

```php
'connections' => [
    'mongodb' => [
        'driver' => 'mongodb',
        'dsn' => env('MONGODB_URI', 'mongodb://localhost:27017'),
        'database' => env('MONGODB_DATABASE', 'laravel_app'),
    ],
],
```

<a name="features"></a>
## Можливості

Коли конфігурацію завершено, ви можете користуватися пакетом `mongodb` і цим підключенням до бази у своєму застосунку, щоб скористатися низкою потужних можливостей:

- [Через Eloquent](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/eloquent-models/) моделі можуть зберігатися в колекціях MongoDB. Окрім стандартних можливостей Eloquent, пакет Laravel MongoDB надає додаткові - як-от вкладені зв'язки. Пакет також дає прямий доступ до драйвера MongoDB, яким можна виконувати сирі запити та конвеєри агрегації.
- [Пишіть складні запити](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/query-builder/) через конструктор запитів.
- [Пошук за схожістю / векторний пошук](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/fundamentals/vector-search/) через векторні ембединги та метод Eloquent `vectorSearch`.
- [Драйвер кешу](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/cache/) `mongodb` оптимізовано під можливості MongoDB - як-от TTL-індекси, що автоматично прибирають прострочені записи кешу.
- [Відправляйте й обробляйте завдання в черзі](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/queues/) через драйвер черги `mongodb`.
- [Зберігання файлів у GridFS](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/filesystems/) через [адаптер GridFS для Flysystem](https://flysystem.thephpleague.com/docs/adapter/gridfs/).
- [Повнотекстовий пошук](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/scout/) через рушій Scout `mongodb`.
- Більшість сторонніх пакетів, що працюють із підключенням до бази чи Eloquent, можна використовувати з MongoDB.

Щоб дізнатися більше про використання MongoDB разом із Laravel, зверніться до [посібника Quick Start](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/quick-start/) від MongoDB.

---

Джерело: https://laravelukraine.com/docs/13.x/eloquent

# Eloquent: початок роботи

- [Вступ](#introduction)
- [Створення класів моделей](#generating-model-classes)
- [Конвенції моделей Eloquent](#eloquent-model-conventions)
    - [Назви таблиць](#table-names)
    - [Первинні ключі](#primary-keys)
    - [Ключі UUID та ULID](#uuid-and-ulid-keys)
    - [Часові позначки](#timestamps)
    - [Підключення до бази даних](#database-connections)
    - [Значення атрибутів за замовчуванням](#default-attribute-values)
    - [Оновлення атрибутів після запису](#refreshing-attributes-after-writes)
    - [Налаштування строгості Eloquent](#configuring-eloquent-strictness)
- [Отримання моделей](#retrieving-models)
    - [Колекції](#collections)
    - [Обробка результатів частинами](#chunking-results)
    - [Обробка частинами через ліниві колекції](#chunking-using-lazy-collections)
    - [Курсори](#cursors)
    - [Складніші підзапити](#advanced-subqueries)
- [Отримання окремих моделей та агрегатів](#retrieving-single-models)
    - [Отримання або створення моделей](#retrieving-or-creating-models)
    - [Отримання агрегатів](#retrieving-aggregates)
- [Вставка та оновлення моделей](#inserting-and-updating-models)
    - [Вставка](#inserts)
    - [Оновлення](#updates)
    - [Масове призначення](#mass-assignment)
    - [Upsert](#upserts)
- [Видалення моделей](#deleting-models)
    - [М'яке видалення](#soft-deleting)
    - [Запити до м'яко видалених моделей](#querying-soft-deleted-models)
- [Очищення моделей](#pruning-models)
- [Копіювання моделей](#replicating-models)
- [Скопи запитів](#query-scopes)
    - [Глобальні скопи](#global-scopes)
    - [Локальні скопи](#local-scopes)
    - [Відкладені атрибути](#pending-attributes)
- [Порівняння моделей](#comparing-models)
- [Події](#events)
    - [Використання замикань](#events-using-closures)
    - [Спостерігачі](#observers)
    - [Вимкнення подій](#muting-events)

<a name="introduction"></a>
## Вступ

Laravel містить Eloquent - об'єктно-реляційний мапер (ORM), який робить роботу з базою даних приємною. Коли ви користуєтеся Eloquent, кожній таблиці бази даних відповідає своя «модель», через яку ви з цією таблицею працюєте. Крім читання записів, моделі Eloquent дозволяють вставляти, оновлювати та видаляти записи в таблиці.

> [!NOTE]
> Перш ніж почати, обов'язково налаштуйте підключення до бази даних у файлі `config/database.php` вашого застосунку. Детальніше про налаштування бази читайте в [документації з конфігурації бази даних](/docs/{{version}}/database#configuration).

<a name="generating-model-classes"></a>
## Створення класів моделей

Для початку створімо модель Eloquent. Моделі зазвичай лежать у каталозі `app\Models` і розширюють клас `Illuminate\Database\Eloquent\Model`. Щоб згенерувати нову модель, скористайтеся [artisan-командою](/docs/{{version}}/artisan) `make:model`:

```shell
php artisan make:model Flight
```

Якщо ви хочете разом із моделлю згенерувати [міграцію бази даних](/docs/{{version}}/migrations), скористайтеся опцією `--migration` або `-m`:

```shell
php artisan make:model Flight --migration
```

Разом із моделлю можна згенерувати й інші класи: фабрики, сідери, політики, контролери та form request. Ці опції можна поєднувати, щоб створити кілька класів одразу:

```shell
# Generate a model and a FlightFactory class...
php artisan make:model Flight --factory
php artisan make:model Flight -f

# Generate a model and a FlightSeeder class...
php artisan make:model Flight --seed
php artisan make:model Flight -s

# Generate a model and a FlightController class...
php artisan make:model Flight --controller
php artisan make:model Flight -c

# Generate a model, FlightController resource class, and form request classes...
php artisan make:model Flight --controller --resource --requests
php artisan make:model Flight -crR

# Generate a model and a FlightPolicy class...
php artisan make:model Flight --policy

# Generate a model and a migration, factory, seeder, and controller...
php artisan make:model Flight -mfsc

# Shortcut to generate a model, migration, factory, seeder, policy, controller, and form requests...
php artisan make:model Flight --all
php artisan make:model Flight -a

# Generate a pivot model...
php artisan make:model Member --pivot
php artisan make:model Member -p
```

<a name="inspecting-models"></a>
#### Огляд моделей

Іноді буває непросто зрозуміти всі доступні атрибути та зв'язки моделі, лише проглянувши її код. Натомість спробуйте artisan-команду `model:show` - вона дає зручний огляд усіх атрибутів і зв'язків моделі:

```shell
php artisan model:show Flight
```

<a name="eloquent-model-conventions"></a>
## Конвенції моделей Eloquent

Моделі, згенеровані командою `make:model`, потрапляють до каталогу `app/Models`. Розгляньмо простий клас моделі й обговорімо кілька ключових конвенцій Eloquent:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    // ...
}
```

<a name="table-names"></a>
### Назви таблиць

Погляньте на приклад вище: ми ніде не сказали Eloquent, яка таблиця відповідає нашій моделі `Flight`. За конвенцією назвою таблиці стає назва класу в «snake case» і в множині - хіба що ви явно вказали іншу. Тож у цьому випадку Eloquent вважатиме, що модель `Flight` зберігає записи в таблиці `flights`, а модель `AirTrafficController` - у таблиці `air_traffic_controllers`.

Якщо відповідна таблиця вашої моделі не відповідає цій конвенції, ви можете вказати назву таблиці вручну атрибутом `Table`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Model;

#[Table('my_flights')]
class Flight extends Model
{
    // ...
}
```


<a name="primary-keys"></a>
### Первинні ключі

Eloquent також вважає, що відповідна таблиця кожної моделі має стовпець первинного ключа з назвою `id`. За потреби ви можете вказати інший стовпець як первинний ключ моделі через аргумент `key` атрибута `Table`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Model;

#[Table(key: 'flight_id')]
class Flight extends Model
{
    // ...
}
```

Крім того, Eloquent вважає, що первинний ключ - це автоінкрементне ціле число, тож він автоматично приводить його до цілого. Якщо ви хочете скористатися неавтоінкрементним або нечисловим первинним ключем, задайте аргументи `keyType` та `incrementing` атрибута `Table`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Model;

#[Table(key: 'uuid', keyType: 'string', incrementing: false)]
class Flight extends Model
{
    // ...
}
```

Якщо вам потрібно лише вимкнути автоінкрементні ID, скористайтеся атрибутом `WithoutIncrementing`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\WithoutIncrementing;
use Illuminate\Database\Eloquent\Model;

#[WithoutIncrementing]
class Flight extends Model
{
    // ...
}
```

<a name="composite-primary-keys"></a>
#### «Складені» первинні ключі

Eloquent вимагає, щоб кожна модель мала щонайменше один унікальний «ID», який може служити первинним ключем. «Складені» первинні ключі моделі Eloquent не підтримують. Втім, ви вільні додавати до таблиць додаткові унікальні індекси з кількох стовпців - окрім самого первинного ключа, що унікально ідентифікує рядок.

<a name="uuid-and-ulid-keys"></a>
### Ключі UUID та ULID

Замість автоінкрементних цілих чисел як первинних ключів моделі Eloquent ви можете обрати UUID. UUID - це універсально унікальні буквенно-цифрові ідентифікатори довжиною 36 символів.

Якщо ви хочете, щоб модель використовувала ключ UUID замість автоінкрементного цілого, застосуйте до неї трейт `Illuminate\Database\Eloquent\Concerns\HasUuids`. Звісно, переконайтеся, що модель має [стовпець первинного ключа, еквівалентний UUID](/docs/{{version}}/migrations#column-method-uuid):

```php
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    use HasUuids;

    // ...
}

$article = Article::create(['title' => 'Traveling to Europe']);

$article->id; // "018f2b5c-6a7f-7b12-9d6f-2f8a4e0c9c11"
```

За замовчуванням трейт `HasUuids` генерує для ваших моделей ідентифікатори [UUIDv7](/docs/{{version}}/strings#method-str-uuid7). Такі UUID ефективніші для зберігання в проіндексованій базі, бо їх можна сортувати лексикографічно.

Ви можете перевизначити процес генерації UUID для конкретної моделі, описавши в ній метод `newUniqueId`. Крім того, методом `uniqueIds` можна вказати, які стовпці мають отримувати UUID:

```php
use Ramsey\Uuid\Uuid;

/**
 * Generate a new UUID for the model.
 */
public function newUniqueId(): string
{
    return (string) Uuid::uuid4();
}

/**
 * Get the columns that should receive a unique identifier.
 *
 * @return array<int, string>
 */
public function uniqueIds(): array
{
    return ['id', 'discount_code'];
}
```

За бажанням ви можете скористатися «ULID» замість UUID. ULID схожі на UUID, але мають довжину лише 26 символів. Як і впорядковані UUID, ULID сортуються лексикографічно, що дає ефективне індексування в базі. Щоб скористатися ULID, застосуйте до моделі трейт `Illuminate\Database\Eloquent\Concerns\HasUlids`. Також переконайтеся, що модель має [стовпець первинного ключа, еквівалентний ULID](/docs/{{version}}/migrations#column-method-ulid):

```php
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    use HasUlids;

    // ...
}

$article = Article::create(['title' => 'Traveling to Asia']);

$article->id; // "01gd4d3tgrrfqeda94gdbtdk5c"
```

<a name="timestamps"></a>
### Часові позначки

За замовчуванням Eloquent очікує, що у відповідній таблиці моделі є стовпці `created_at` та `updated_at`. Eloquent автоматично задає їхні значення, коли модель створюється чи оновлюється. Якщо ви не хочете, щоб Eloquent керував цими стовпцями автоматично, задайте `timestamps` значення `false` в атрибуті `Table` вашої моделі:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Model;

#[Table(timestamps: false)]
class Flight extends Model
{
    // ...
}
```

Якщо вам потрібно лише вимкнути часові позначки, скористайтеся атрибутом `WithoutTimestamps`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\WithoutTimestamps;
use Illuminate\Database\Eloquent\Model;

#[WithoutTimestamps]
class Flight extends Model
{
    // ...
}
```

Якщо вам потрібно змінити формат часових позначок моделі, скористайтеся аргументом `dateFormat` атрибута `Table`. Він визначає, як атрибути дати зберігаються в базі даних:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Model;

#[Table(dateFormat: 'U')]
class Flight extends Model
{
    // ...
}
```

Якщо вам потрібно задати лише формат дати, скористайтеся атрибутом `DateFormat`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\DateFormat;
use Illuminate\Database\Eloquent\Model;

#[DateFormat('U')]
class Flight extends Model
{
    // ...
}
```

Якщо вам потрібно змінити назви стовпців для зберігання часових позначок, опишіть у моделі константи `CREATED_AT` та `UPDATED_AT`:

```php
<?php

class Flight extends Model
{
    /**
     * The name of the "created at" column.
     *
     * @var string|null
     */
    public const CREATED_AT = 'creation_date';

    /**
     * The name of the "updated at" column.
     *
     * @var string|null
     */
    public const UPDATED_AT = 'updated_date';
}
```

Якщо ви хочете виконати операції з моделлю, не змінюючи її часову позначку `updated_at`, працюйте з моделлю в замиканні, переданому методу `withoutTimestamps`:

```php
Model::withoutTimestamps(fn () => $post->increment('reads'));
```

<a name="database-connections"></a>
### Підключення до бази даних

За замовчуванням усі моделі Eloquent використовують підключення до бази даних, налаштоване для вашого застосунку за замовчуванням. Якщо ви хочете вказати інше підключення для роботи з конкретною моделлю, скористайтеся атрибутом `Connection`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Connection;
use Illuminate\Database\Eloquent\Model;

#[Connection('mysql')]
class Flight extends Model
{
    // ...
}
```

<a name="default-attribute-values"></a>
### Значення атрибутів за замовчуванням

За замовчуванням новостворений екземпляр моделі не містить жодних значень атрибутів. Якщо ви хочете задати значення за замовчуванням для деяких атрибутів моделі, опишіть у ній властивість `$attributes`. Значення в масиві `$attributes` мають бути в «сирому» форматі для зберігання - так, ніби їх щойно прочитали з бази даних:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * The model's default values for attributes.
     *
     * @var array<string, mixed>
     */
    protected $attributes = [
        'options' => '[]',
        'delayed' => false,
    ];
}
```

<a name="refreshing-attributes-after-writes"></a>
### Оновлення атрибутів після запису

Якщо ваша база даних містить згенеровані стовпці, ви можете налаштувати Eloquent так, щоб після вставки чи оновлення моделі він заново зчитував певні атрибути з бази даних. Для цього визначте атрибут `Refreshes` на вашій моделі:

```php
use Illuminate\Database\Eloquent\Attributes\Refreshes;

#[Refreshes('name')]
class User extends Model
{
    // ...
}
```

Ви можете вказати кілька атрибутів як масив:

```php
#[Refreshes(['name', 'slug'])]
```

Після запису моделі налаштовані атрибути будуть оновлені з бази даних.

<a name="configuring-eloquent-strictness"></a>
### Налаштування строгості Eloquent

Laravel має кілька методів, якими можна налаштувати поведінку та «строгість» Eloquent у різних ситуаціях.

По-перше, метод `preventLazyLoading` приймає необов'язковий булевий аргумент, що вказує, чи слід забороняти ліниве завантаження (lazy loading). Наприклад, ви можете вимкнути ліниве завантаження лише в непродакшн-середовищах, щоб продакшн і далі працював нормально, навіть якщо в код випадково потрапило ліниве завантаження зв'язку. Зазвичай цей метод викликають у методі `boot` класу `AppServiceProvider` вашого застосунку:

```php
use Illuminate\Database\Eloquent\Model;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Model::preventLazyLoading(! $this->app->isProduction());
}
```

Також ви можете наказати Laravel викидати виняток при спробі заповнити атрибут, недоступний для заповнення, - для цього викличте метод `preventSilentlyDiscardingAttributes`. Це допомагає уникнути неочікуваних помилок під час локальної розробки, коли ви намагаєтеся задати атрибут, якого немає в масиві `fillable` моделі:

```php
Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());
```

<a name="retrieving-models"></a>
## Отримання моделей

Коли ви створили модель і [відповідну таблицю бази даних](/docs/{{version}}/migrations#generating-migrations), можна починати діставати дані з бази. Уявляйте кожну модель Eloquent як потужний [конструктор запитів](/docs/{{version}}/queries), що дозволяє плавно робити запити до пов'язаної з моделлю таблиці. Метод `all` моделі дістає всі записи з відповідної таблиці:

```php
use App\Models\Flight;

foreach (Flight::all() as $flight) {
    echo $flight->name;
}
```

<a name="building-queries"></a>
#### Побудова запитів

Метод `all` в Eloquent повертає всі результати з таблиці моделі. Але оскільки кожна модель Eloquent є [конструктором запитів](/docs/{{version}}/queries), ви можете додати до запиту обмеження, а потім викликати метод `get`, щоб отримати результати:

```php
$flights = Flight::where('active', 1)
    ->orderBy('name')
    ->limit(10)
    ->get();
```

> [!NOTE]
> Оскільки моделі Eloquent є конструкторами запитів, варто переглянути всі методи [конструктора запитів](/docs/{{version}}/queries) Laravel. Будь-який із них можна використовувати у своїх запитах Eloquent.

<a name="refreshing-models"></a>
#### Оновлення моделей

Якщо у вас уже є екземпляр моделі Eloquent, дістаний із бази даних, ви можете «оновити» його методами `fresh` і `refresh`. Метод `fresh` заново дістає модель із бази. Наявний екземпляр моделі при цьому не змінюється:

```php
$flight = Flight::where('number', 'FR 900')->first();

$freshFlight = $flight->fresh();
```

Метод `refresh` наповнює наявний екземпляр свіжими даними з бази. Крім того, оновлюються й усі завантажені зв'язки:

```php
$flight = Flight::where('number', 'FR 900')->first();

$flight->number = 'FR 456';

$flight->refresh();

$flight->number; // "FR 900"
```

Якщо потрібно оновити модель і встановити песимістичне блокування всередині транзакції, використовуйте метод `refreshForUpdate`. Цей метод перезавантажує модель із блокуванням `FOR UPDATE`:

```php
DB::transaction(function () use ($flight) {
    $flight->refreshForUpdate();

    // Оновіть заблоковану модель...
});
```

<a name="collections"></a>
### Колекції

Як ми бачили, методи Eloquent на кшталт `all` і `get` дістають із бази кілька записів. Проте вони повертають не звичайний PHP-масив, а екземпляр `Illuminate\Database\Eloquent\Collection`.

Клас `Collection` в Eloquent розширює базовий клас Laravel `Illuminate\Support\Collection`, який має [цілу низку корисних методів](/docs/{{version}}/collections#available-methods) для роботи з наборами даних. Наприклад, методом `reject` можна прибрати з колекції моделі за результатом виклику замикання:

```php
$flights = Flight::where('destination', 'Paris')->get();

$flights = $flights->reject(function (Flight $flight) {
    return $flight->cancelled;
});
```

Крім методів базового класу колекцій Laravel, клас колекції Eloquent має [кілька додаткових методів](/docs/{{version}}/eloquent-collections#available-methods), призначених саме для роботи з колекціями моделей Eloquent.

Оскільки всі колекції Laravel реалізують інтерфейси ітерування PHP, ви можете обходити колекції в циклі, наче масиви:

```php
foreach ($flights as $flight) {
    echo $flight->name;
}
```

<a name="chunking-results"></a>
### Обробка результатів частинами

Якщо ви спробуєте завантажити десятки тисяч записів Eloquent методами `all` чи `get`, вашому застосунку може забракнути пам'яті. Замість них скористайтеся методом `chunk` - він опрацьовує велику кількість моделей ефективніше.

Метод `chunk` дістає підмножину моделей Eloquent і передає їх у замикання для обробки. Оскільки за раз дістається лише поточна частина моделей, метод `chunk` значно зменшує споживання пам'яті при роботі з великою кількістю моделей:

```php
use App\Models\Flight;
use Illuminate\Database\Eloquent\Collection;

Flight::chunk(200, function (Collection $flights) {
    foreach ($flights as $flight) {
        // ...
    }
});
```

Перший аргумент методу `chunk` - кількість записів, які ви хочете отримувати в кожній «частині». Замикання, передане другим аргументом, буде викликано для кожної частини, дістаної з бази. Щоб отримати кожну частину записів для замикання, виконується окремий запит до бази.

Якщо ви фільтруєте результати методу `chunk` за стовпцем, який заодно оновлюєте під час обходу, скористайтеся методом `chunkById`. Метод `chunk` у таких випадках може дати неочікувані й неузгоджені результати. Усередині метод `chunkById` завжди дістає моделі зі значенням стовпця `id`, більшим за останню модель попередньої частини:

```php
Flight::where('departed', true)
    ->chunkById(200, function (Collection $flights) {
        $flights->each->update(['departed' => false]);
    }, column: 'id');
```

Оскільки методи `chunkById` і `lazyById` додають до запиту власні умови «where», свої умови варто [логічно згрупувати](/docs/{{version}}/queries#logical-grouping) в замиканні:

```php
Flight::where(function ($query) {
    $query->where('delayed', true)->orWhere('cancelled', true);
})->chunkById(200, function (Collection $flights) {
    $flights->each->update([
        'departed' => false,
        'cancelled' => true
    ]);
}, column: 'id');
```

<a name="chunking-using-lazy-collections"></a>
### Обробка частинами через ліниві колекції

Метод `lazy` працює схоже на [метод `chunk`](#chunking-results) у тому, що всередині виконує запит частинами. Але замість того щоб передавати кожну частину прямо в колбек, метод `lazy` повертає «розплющену» [LazyCollection](/docs/{{version}}/collections#lazy-collections) моделей Eloquent, і ви працюєте з результатами як з єдиним потоком:

```php
use App\Models\Flight;

foreach (Flight::lazy() as $flight) {
    // ...
}
```

Якщо ви фільтруєте результати методу `lazy` за стовпцем, який заодно оновлюєте під час обходу, скористайтеся методом `lazyById`. Усередині метод `lazyById` завжди дістає моделі зі значенням стовпця `id`, більшим за останню модель попередньої частини:

```php
Flight::where('departed', true)
    ->lazyById(200, column: 'id')
    ->each->update(['departed' => false]);
```

Ви можете фільтрувати результати за спаданням `id` методом `lazyByIdDesc`.

<a name="cursors"></a>
### Курсори

Як і метод `lazy`, метод `cursor` значно зменшує споживання пам'яті вашим застосунком, коли ви обходите десятки тисяч записів моделей Eloquent.

Метод `cursor` виконує лише один запит до бази даних; проте окремі моделі Eloquent не наповнюються даними, аж поки ви до них не дійдете в обході. Тому в будь-який момент обходу курсора в пам'яті тримається лише одна модель Eloquent.

> [!WARNING]
> Оскільки метод `cursor` тримає в пам'яті лише одну модель Eloquent за раз, він не може жадібно завантажувати зв'язки. Якщо вам потрібне жадібне завантаження, розгляньте [метод `lazy`](#chunking-using-lazy-collections).

Усередині метод `cursor` реалізує це через [генератори](https://www.php.net/manual/en/language.generators.overview.php) PHP:

```php
use App\Models\Flight;

foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) {
    // ...
}
```

`cursor` повертає екземпляр `Illuminate\Support\LazyCollection`. [Ліниві колекції](/docs/{{version}}/collections#lazy-collections) дозволяють користуватися багатьма методами звичайних колекцій Laravel, тримаючи в пам'яті лише одну модель за раз:

```php
use App\Models\User;

$users = User::cursor()->filter(function (User $user) {
    return $user->id > 500;
});

foreach ($users as $user) {
    echo $user->id;
}
```

Хоч метод `cursor` споживає значно менше пам'яті, ніж звичайний запит (бо тримає в пам'яті лише одну модель Eloquent за раз), пам'ять усе одно колись закінчиться. Причина в тому, що [драйвер PDO у PHP усередині кешує всі сирі результати запиту у своєму буфері](https://www.php.net/manual/en/mysqlinfo.concepts.buffering.php). Якщо ви маєте справу з дуже великою кількістю записів Eloquent, розгляньте [метод `lazy`](#chunking-using-lazy-collections).

<a name="advanced-subqueries"></a>
### Складніші підзапити

<a name="subquery-selects"></a>
#### Підзапити у SELECT

Eloquent також має розширену підтримку підзапитів, яка дозволяє дістати інформацію з пов'язаних таблиць одним запитом. Наприклад, уявімо, що ми маємо таблицю напрямків рейсів `destinations` і таблицю рейсів `flights` до цих напрямків. Таблиця `flights` містить стовпець `arrived_at`, що вказує, коли рейс прибув у пункт призначення.

За допомогою підзапитів у методах `select` і `addSelect` конструктора запитів ми можемо одним запитом вибрати всі `destinations` і назву рейсу, який прибув у цей пункт останнім:

```php
use App\Models\Destination;
use App\Models\Flight;

return Destination::addSelect(['last_flight' => Flight::select('name')
    ->whereColumn('destination_id', 'destinations.id')
    ->orderByDesc('arrived_at')
    ->limit(1)
])->get();
```

<a name="subquery-ordering"></a>
#### Сортування за підзапитом

Крім того, функція `orderBy` конструктора запитів підтримує підзапити. Продовжуючи приклад із рейсами, ми можемо відсортувати всі напрямки за часом прибуття останнього рейсу до кожного з них. І знову ж таки, це робиться одним запитом до бази:

```php
return Destination::orderByDesc(
    Flight::select('arrived_at')
        ->whereColumn('destination_id', 'destinations.id')
        ->orderByDesc('arrived_at')
        ->limit(1)
)->get();
```

<a name="retrieving-single-models"></a>
## Отримання окремих моделей та агрегатів

Крім отримання всіх записів, що відповідають запиту, ви можете дістати окремі записи методами `find`, `first` або `firstWhere`. Замість колекції моделей вони повертають один екземпляр моделі:

```php
use App\Models\Flight;

// Retrieve a model by its primary key...
$flight = Flight::find(1);

// Retrieve the first model matching the query constraints...
$flight = Flight::where('active', 1)->first();

// Alternative to retrieving the first model matching the query constraints...
$flight = Flight::firstWhere('active', 1);
```

Іноді ви хочете виконати якусь іншу дію, якщо результатів не знайдено. Методи `findOr` і `firstOr` повертають один екземпляр моделі або, якщо результатів немає, виконують задане замикання. Значення, яке повернуло замикання, стане результатом методу:

```php
$flight = Flight::findOr(1, function () {
    // ...
});

$flight = Flight::where('legs', '>', 3)->firstOr(function () {
    // ...
});
```

<a name="not-found-exceptions"></a>
#### Винятки «не знайдено»

Іноді ви хочете викинути виняток, якщо модель не знайдено. Це особливо зручно в маршрутах і контролерах. Методи `findOrFail` і `firstOrFail` дістають перший результат запиту; але якщо результату немає, буде викинуто `Illuminate\Database\Eloquent\ModelNotFoundException`:

```php
$flight = Flight::findOrFail(1);

$flight = Flight::where('legs', '>', 3)->firstOrFail();
```

Якщо `ModelNotFoundException` не перехоплено, клієнту автоматично надсилається HTTP-відповідь 404:

```php
use App\Models\Flight;

Route::get('/api/flights/{id}', function (string $id) {
    return Flight::findOrFail($id);
});
```

<a name="retrieving-or-creating-models"></a>
### Отримання або створення моделей

Метод `firstOrCreate` спробує знайти запис у базі за заданими парами «стовпець - значення». Якщо моделі в базі немає, буде вставлено запис з атрибутами, які утворюються об'єднанням першого масиву-аргументу з необов'язковим другим.

Метод `firstOrNew`, як і `firstOrCreate`, спробує знайти в базі запис, що відповідає заданим атрибутам. Але якщо моделі не знайдено, буде повернуто новий екземпляр моделі. Зверніть увагу: модель, яку повернув `firstOrNew`, ще не збережена в базі. Щоб зберегти її, потрібно вручну викликати метод `save`:

```php
use App\Models\Flight;

// Retrieve flight by name or create it if it doesn't exist...
$flight = Flight::firstOrCreate([
    'name' => 'London to Paris'
]);

// Retrieve flight by name or create it with the name, delayed, and arrival_time attributes...
$flight = Flight::firstOrCreate(
    ['name' => 'London to Paris'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);

// Retrieve flight by name or instantiate a new Flight instance...
$flight = Flight::firstOrNew([
    'name' => 'London to Paris'
]);

// Retrieve flight by name or instantiate with the name, delayed, and arrival_time attributes...
$flight = Flight::firstOrNew(
    ['name' => 'Tokyo to Sydney'],
    ['delayed' => 1, 'arrival_time' => '11:30']
);
```

<a name="retrieving-aggregates"></a>
### Отримання агрегатів

Працюючи з моделями Eloquent, ви можете також користуватися методами `count`, `sum`, `max` та іншими [агрегатними методами](/docs/{{version}}/queries#aggregates) [конструктора запитів](/docs/{{version}}/queries) Laravel. Як і слід очікувати, вони повертають скалярне значення, а не екземпляр моделі Eloquent:

```php
$count = Flight::where('active', 1)->count();

$max = Flight::where('active', 1)->max('price');
```

<a name="inserting-and-updating-models"></a>
## Вставка та оновлення моделей

<a name="inserts"></a>
### Вставка

Звісно, працюючи з Eloquent, ми не лише дістаємо моделі з бази - нам потрібно вставляти й нові записи. На щастя, Eloquent робить це просто. Щоб вставити новий запис, створіть екземпляр моделі й задайте йому атрибути. Далі викличте на екземплярі метод `save`:

```php
<?php

namespace App\Http\Controllers;

use App\Models\Flight;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class FlightController extends Controller
{
    /**
     * Store a new flight in the database.
     */
    public function store(Request $request): RedirectResponse
    {
        // Validate the request...

        $flight = new Flight;

        $flight->name = $request->name;

        $flight->save();

        return redirect('/flights');
    }
}
```

У цьому прикладі ми присвоюємо поле `name` із вхідного HTTP-запиту атрибуту `name` екземпляра моделі `App\Models\Flight`. Коли ми викликаємо метод `save`, у базу даних вставляється запис. Часові позначки `created_at` та `updated_at` буде задано автоматично при виклику `save`, тож задавати їх вручну не потрібно.

Якщо ви хочете зберегти модель у межах транзакції, скористайтеся методом `saveOrFail`. Якщо під час збереження буде викинуто виняток, транзакція автоматично відкотиться:

```php
$flight->saveOrFail();
```

Або ж ви можете скористатися методом `create`, щоб «зберегти» нову модель однією PHP-інструкцією. Метод `create` поверне вам вставлений екземпляр моделі:

```php
use App\Models\Flight;

$flight = Flight::create([
    'name' => 'London to Paris',
]);
```

Проте, перш ніж користуватися методом `create`, вам потрібно задати в класі моделі атрибут `Fillable` або `Guarded`. Ці атрибути обов'язкові, бо всі моделі Eloquent за замовчуванням захищені від вразливостей масового призначення. Детальніше про це читайте в [документації з масового призначення](#mass-assignment).

<a name="updates"></a>
### Оновлення

Методом `save` можна оновлювати й моделі, які вже є в базі даних. Щоб оновити модель, дістаньте її та задайте атрибути, які хочете змінити. Далі викличте метод `save`. І знову ж таки, часову позначку `updated_at` буде оновлено автоматично, тож задавати її вручну не потрібно:

```php
use App\Models\Flight;

$flight = Flight::find(1);

$flight->name = 'Paris to London';

$flight->save();
```

Якщо ви хочете оновити модель у межах транзакції, скористайтеся методом `updateOrFail`. Якщо під час оновлення буде викинуто виняток, транзакція автоматично відкотиться:

```php
$flight->updateOrFail(['name' => 'Paris to London']);
```

Іноді вам потрібно оновити наявну модель або створити нову, якщо відповідної немає. Як і `firstOrCreate`, метод `updateOrCreate` зберігає модель, тож викликати `save` вручну не потрібно.

У прикладі нижче, якщо існує рейс із пунктом відправлення `Oakland` і пунктом призначення `San Diego`, буде оновлено його стовпці `price` та `discounted`. Якщо такого рейсу немає, буде створено новий з атрибутами, які утворюються об'єднанням першого масиву-аргументу з другим:

```php
$flight = Flight::updateOrCreate(
    ['departure' => 'Oakland', 'destination' => 'San Diego'],
    ['price' => 99, 'discounted' => 1]
);
```

Користуючись методами на кшталт `firstOrCreate` чи `updateOrCreate`, ви можете не знати, чи створено нову модель, чи оновлено наявну. Властивість `wasRecentlyCreated` вказує, чи була модель створена протягом її поточного життєвого циклу:

```php
$flight = Flight::updateOrCreate(
    // ...
);

if ($flight->wasRecentlyCreated) {
    // New flight record was inserted...
}
```

<a name="mass-updates"></a>
#### Масові оновлення

Оновлення можна виконувати й над моделями, що відповідають заданому запиту. У цьому прикладі всі рейси, що є `active` і мають `destination` зі значенням `San Diego`, буде позначено як затримані:

```php
Flight::where('active', 1)
    ->where('destination', 'San Diego')
    ->update(['delayed' => 1]);
```

Метод `update` очікує масив пар «стовпець - значення», які вказують, що саме оновити. Метод `update` повертає кількість зачеплених рядків.

> [!WARNING]
> Коли ви робите масове оновлення через Eloquent, події моделі `saving`, `saved`, `updating` та `updated` для оновлених моделей не спрацюють. Причина в тому, що при масовому оновленні моделі насправді ніколи не дістаються з бази.

<a name="examining-attribute-changes"></a>
#### Перевірка змін атрибутів

Eloquent має методи `isDirty`, `isClean` і `wasChanged`, щоб перевірити внутрішній стан моделі й визначити, як змінилися її атрибути з моменту, коли модель дістали з бази.

Метод `isDirty` визначає, чи змінився хоч якийсь атрибут моделі відтоді, як її дістали. Ви можете передати `isDirty` назву конкретного атрибута або масив атрибутів, щоб перевірити, чи є серед них «брудні». Метод `isClean` визначає, чи лишився атрибут незмінним відтоді, як модель дістали. Він теж приймає необов'язковий аргумент-атрибут:

```php
use App\Models\User;

$user = User::create([
    'first_name' => 'Taylor',
    'last_name' => 'Otwell',
    'title' => 'Developer',
]);

$user->title = 'Painter';

$user->isDirty(); // true
$user->isDirty('title'); // true
$user->isDirty('first_name'); // false
$user->isDirty(['first_name', 'title']); // true

$user->isClean(); // false
$user->isClean('title'); // false
$user->isClean('first_name'); // true
$user->isClean(['first_name', 'title']); // false

$user->save();

$user->isDirty(); // false
$user->isClean(); // true
```

Метод `wasChanged` визначає, чи змінювалися атрибути при останньому збереженні моделі в межах поточного циклу запиту. За потреби ви можете передати назву атрибута, щоб перевірити, чи змінився саме він:

```php
$user = User::create([
    'first_name' => 'Taylor',
    'last_name' => 'Otwell',
    'title' => 'Developer',
]);

$user->title = 'Painter';

$user->save();

$user->wasChanged(); // true
$user->wasChanged('title'); // true
$user->wasChanged(['title', 'slug']); // true
$user->wasChanged('first_name'); // false
$user->wasChanged(['first_name', 'title']); // true
```

Метод `getOriginal` повертає масив із початковими атрибутами моделі незалежно від того, як вона змінилася відтоді, як її дістали. За потреби ви можете передати назву конкретного атрибута, щоб отримати його початкове значення:

```php
$user = User::find(1);

$user->name; // John
$user->email; // john@example.com

$user->name = 'Jack';
$user->name; // Jack

$user->getOriginal('name'); // John
$user->getOriginal(); // Array of original attributes...
```

Метод `getChanges` повертає масив атрибутів, які змінилися при останньому збереженні моделі, а метод `getPrevious` - масив значень атрибутів, які були до останнього збереження:

```php
$user = User::find(1);

$user->name; // John
$user->email; // john@example.com

$user->update([
    'name' => 'Jack',
    'email' => 'jack@example.com',
]);

$user->getChanges();

/*
    [
        'name' => 'Jack',
        'email' => 'jack@example.com',
    ]
*/

$user->getPrevious();

/*
    [
        'name' => 'John',
        'email' => 'john@example.com',
    ]
*/
```

<a name="mass-assignment"></a>
### Масове призначення

Ви можете скористатися методом `create`, щоб «зберегти» нову модель однією PHP-інструкцією. Метод поверне вам вставлений екземпляр моделі:

```php
use App\Models\Flight;

$flight = Flight::create([
    'name' => 'London to Paris',
]);
```

Проте, перш ніж користуватися методом `create`, вам потрібно задати в класі моделі атрибут `Fillable` або `Guarded`. Ці атрибути обов'язкові, бо всі моделі Eloquent за замовчуванням захищені від вразливостей масового призначення.

Вразливість масового призначення виникає, коли користувач передає неочікуване поле HTTP-запиту, і це поле змінює стовпець вашої бази даних, якого ви не очікували. Наприклад, зловмисник може надіслати в HTTP-запиті параметр `is_admin`, який потім потрапляє в метод `create` вашої моделі й дозволяє йому підвищити собі права до адміністратора.

Отже, для початку вам слід описати, які атрибути моделі мають бути доступні для масового призначення. Це робиться атрибутом `Fillable` на моделі. Наприклад, зробімо атрибут `name` нашої моделі `Flight` доступним для масового призначення:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;

#[Fillable(['name'])]
class Flight extends Model
{
    // ...
}
```

Коли ви вказали, які атрибути доступні для масового призначення, можна вставляти новий запис у базу методом `create`. Метод `create` повертає щойно створений екземпляр моделі:

```php
$flight = Flight::create(['name' => 'London to Paris']);
```

Якщо у вас уже є екземпляр моделі, ви можете наповнити його масивом атрибутів методом `fill`:

```php
$flight->fill(['name' => 'Amsterdam to Frankfurt']);
```

<a name="mass-assignment-json-columns"></a>
#### Масове призначення та стовпці JSON

Присвоюючи стовпці JSON, кожен ключ, доступний для масового призначення, потрібно вказати в атрибуті `Fillable` вашої моделі. З міркувань безпеки Laravel не підтримує оновлення вкладених атрибутів JSON при використанні атрибута `Guarded`:

```php
use Illuminate\Database\Eloquent\Attributes\Fillable;

#[Fillable(['options->enabled'])]
class Flight extends Model
{
    // ...
}
```

<a name="allowing-mass-assignment"></a>
#### Дозвіл масового призначення

Якщо ви хочете зробити доступними для масового призначення всі атрибути, скористайтеся на моделі атрибутом `Unguarded`. Якщо ви знімаєте захист із моделі, будьте особливо уважні: масиви, які передаються методам `fill`, `create` та `update` Eloquent, завжди складайте вручну:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Unguarded;
use Illuminate\Database\Eloquent\Model;

#[Unguarded]
class Flight extends Model
{
    // ...
}
```

<a name="mass-assignment-exceptions"></a>
#### Винятки при масовому призначенні

За замовчуванням атрибути, яких немає в атрибуті `Fillable`, під час масового призначення мовчки відкидаються. На продакшні це очікувана поведінка, але під час локальної розробки вона може спантеличити: незрозуміло, чому зміни моделі не застосовуються.

За бажанням ви можете наказати Laravel викидати виняток при спробі заповнити атрибут, недоступний для заповнення, - для цього викличте метод `preventSilentlyDiscardingAttributes`. Зазвичай цей метод викликають у методі `boot` класу `AppServiceProvider` вашого застосунку:

```php
use Illuminate\Database\Eloquent\Model;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Model::preventSilentlyDiscardingAttributes($this->app->isLocal());
}
```

<a name="upserts"></a>
### Upsert

Метод `upsert` в Eloquent оновлює або створює записи однією атомарною операцією. Перший аргумент методу - значення для вставки чи оновлення, другий - стовпці, які унікально ідентифікують записи у відповідній таблиці. Третій і останній аргумент - масив стовпців, які слід оновити, якщо відповідний запис у базі вже є. Метод `upsert` автоматично задасть часові позначки `created_at` і `updated_at`, якщо вони увімкнені на моделі:

```php
Flight::upsert([
    ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
    ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
], uniqueBy: ['departure', 'destination'], update: ['price']);
```

> [!WARNING]
> Усі бази даних, окрім SQL Server, вимагають, щоб стовпці з другого аргументу методу `upsert` мали індекс «primary» або «unique». Крім того, драйвери MariaDB і MySQL ігнорують другий аргумент `upsert` і завжди використовують індекси «primary» та «unique» таблиці, щоб виявити наявні записи.

<a name="deleting-models"></a>
## Видалення моделей

Щоб видалити модель, викличте на її екземплярі метод `delete`:

```php
use App\Models\Flight;

$flight = Flight::find(1);

$flight->delete();
```

Якщо ви хочете видалити модель у межах транзакції, скористайтеся методом `deleteOrFail`. Якщо під час видалення буде викинуто виняток, транзакція автоматично відкотиться:

```php
$flight->deleteOrFail();
```

<a name="deleting-an-existing-model-by-its-primary-key"></a>
#### Видалення наявної моделі за первинним ключем

У прикладі вище ми дістаємо модель із бази, перш ніж викликати метод `delete`. Але якщо ви знаєте первинний ключ моделі, її можна видалити, не дістаючи явно, - методом `destroy`. Крім одного первинного ключа, метод `destroy` приймає кілька первинних ключів, масив первинних ключів або [колекцію](/docs/{{version}}/collections) первинних ключів:

```php
Flight::destroy(1);

Flight::destroy(1, 2, 3);

Flight::destroy([1, 2, 3]);

Flight::destroy(collect([1, 2, 3]));
```

Якщо ви користуєтеся [м'яким видаленням моделей](#soft-deleting), ви можете видалити моделі назавжди методом `forceDestroy`:

```php
Flight::forceDestroy(1);
```

> [!WARNING]
> Метод `destroy` завантажує кожну модель окремо й викликає метод `delete`, щоб події `deleting` та `deleted` коректно надсилалися для кожної моделі.

<a name="deleting-models-using-queries"></a>
#### Видалення моделей запитами

Звісно, ви можете побудувати запит Eloquent, щоб видалити всі моделі, що відповідають його умовам. У цьому прикладі ми видалимо всі рейси, позначені як неактивні. Як і масові оновлення, масові видалення не надсилають подій моделі для видалених моделей:

```php
$deleted = Flight::where('active', 0)->delete();
```

Щоб видалити всі моделі в таблиці, виконайте запит без жодних умов:

```php
$deleted = Flight::query()->delete();
```

> [!WARNING]
> Коли ви виконуєте масове видалення через Eloquent, події моделі `deleting` та `deleted` для видалених моделей не надсилаються. Причина в тому, що при виконанні запиту на видалення моделі насправді ніколи не дістаються з бази.

<a name="soft-deleting"></a>
### М'яке видалення

Крім справжнього видалення записів із бази даних, Eloquent уміє «м'яко видаляти» моделі (soft delete). Коли модель видалено м'яко, вона насправді лишається в базі. Натомість їй задається атрибут `deleted_at` з датою й часом «видалення». Щоб увімкнути м'яке видалення для моделі, додайте до неї трейт `Illuminate\Database\Eloquent\SoftDeletes`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Flight extends Model
{
    use SoftDeletes;
}
```

> [!NOTE]
> Трейт `SoftDeletes` автоматично приведе атрибут `deleted_at` до екземпляра `DateTime` / `Carbon`.

Вам також слід додати до таблиці стовпець `deleted_at`. [Конструктор схеми](/docs/{{version}}/migrations) Laravel має для цього допоміжний метод:

```php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::table('flights', function (Blueprint $table) {
    $table->softDeletes();
});

Schema::table('flights', function (Blueprint $table) {
    $table->dropSoftDeletes();
});
```

Тепер, коли ви викликаєте на моделі метод `delete`, стовпцю `deleted_at` буде задано поточну дату й час. Проте запис моделі лишиться в таблиці. Коли ви робите запит до моделі з м'яким видаленням, м'яко видалені моделі автоматично виключаються з усіх результатів.

Щоб визначити, чи був конкретний екземпляр моделі м'яко видалений, скористайтеся методом `trashed`:

```php
if ($flight->trashed()) {
    // ...
}
```

<a name="restoring-soft-deleted-models"></a>
#### Відновлення м'яко видалених моделей

Іноді вам потрібно «скасувати видалення» м'яко видаленої моделі. Щоб відновити її, викличте на екземплярі моделі метод `restore`. Метод `restore` задасть стовпцю `deleted_at` значення `null`:

```php
$flight->restore();
```

Метод `restore` можна використати й у запиті, щоб відновити кілька моделей. І знову ж таки, як і інші «масові» операції, це не надішле жодних подій моделі для відновлених моделей:

```php
Flight::withTrashed()
    ->where('airline_id', 1)
    ->restore();
```

Метод `restore` можна також використовувати при побудові запитів до [зв'язків](/docs/{{version}}/eloquent-relationships):

```php
$flight->history()->restore();
```

<a name="permanently-deleting-models"></a>
#### Остаточне видалення моделей

Іноді вам потрібно справді прибрати модель із бази даних. Щоб назавжди видалити м'яко видалену модель із таблиці, скористайтеся методом `forceDelete`:

```php
$flight->forceDelete();
```

Метод `forceDelete` можна також використовувати при побудові запитів до зв'язків Eloquent:

```php
$flight->history()->forceDelete();
```

<a name="querying-soft-deleted-models"></a>
### Запити до м'яко видалених моделей

<a name="including-soft-deleted-models"></a>
#### Включення м'яко видалених моделей

Як зазначено вище, м'яко видалені моделі автоматично виключаються з результатів запитів. Втім, ви можете примусово включити їх до результатів, викликавши на запиті метод `withTrashed`:

```php
use App\Models\Flight;

$flights = Flight::withTrashed()
    ->where('account_id', 1)
    ->get();
```

Метод `withTrashed` можна викликати й при побудові запиту до [зв'язку](/docs/{{version}}/eloquent-relationships):

```php
$flight->history()->withTrashed()->get();
```

<a name="retrieving-only-soft-deleted-models"></a>
#### Отримання лише м'яко видалених моделей

Метод `onlyTrashed` дістане **лише** м'яко видалені моделі:

```php
$flights = Flight::onlyTrashed()
    ->where('airline_id', 1)
    ->get();
```

<a name="pruning-models"></a>
## Очищення моделей

Іноді вам потрібно періодично видаляти моделі, які більше не потрібні. Для цього додайте до таких моделей трейт `Illuminate\Database\Eloquent\Prunable` або `Illuminate\Database\Eloquent\MassPrunable`. Після цього реалізуйте метод `prunable`, що повертає конструктор запитів Eloquent, який відбирає непотрібні моделі:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Prunable;

class Flight extends Model
{
    use Prunable;

    /**
     * Get the prunable model query.
     */
    public function prunable(): Builder
    {
        return static::where('created_at', '<=', now()->minus(months: 1));
    }
}
```

Позначаючи моделі як `Prunable`, ви можете також описати в моделі метод `pruning`. Його буде викликано перед видаленням моделі. Це стане в пригоді, щоб видалити пов'язані з моделлю додаткові ресурси - наприклад, збережені файли, - перш ніж модель назавжди зникне з бази:

```php
/**
 * Prepare the model for pruning.
 */
protected function pruning(): void
{
    // ...
}
```

Налаштувавши модель для очищення, заплануйте artisan-команду `model:prune` у файлі `routes/console.php` вашого застосунку. Ви вільні обрати доречний інтервал запуску цієї команди:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('model:prune')->daily();
```

Усередині команда `model:prune` автоматично знаходить моделі «Prunable» у каталозі `app/Models` вашого застосунку. Якщо ваші моделі лежать деінде, вкажіть назви класів опцією `--model`:

```php
Schedule::command('model:prune', [
    '--model' => [Address::class, Flight::class],
])->daily();
```

Якщо ви хочете виключити певні моделі з очищення, а решту знайдених очищати, скористайтеся опцією `--except`:

```php
Schedule::command('model:prune', [
    '--except' => [Address::class, Flight::class],
])->daily();
```

Перевірити свій запит `prunable` можна, виконавши команду `model:prune` з опцією `--pretend`. У цьому режимі команда `model:prune` просто повідомить, скільки записів було б видалено, якби вона справді відпрацювала:

```shell
php artisan model:prune --pretend
```

> [!WARNING]
> Моделі з м'яким видаленням буде видалено назавжди (`forceDelete`), якщо вони відповідають запиту очищення.

<a name="mass-pruning"></a>
#### Масове очищення

Коли моделі позначені трейтом `Illuminate\Database\Eloquent\MassPrunable`, вони видаляються з бази запитами масового видалення. Тому метод `pruning` не буде викликано, а події моделі `deleting` та `deleted` не надішлються. Причина в тому, що моделі насправді ніколи не дістаються перед видаленням, і саме тому очищення відбувається значно ефективніше:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\MassPrunable;

class Flight extends Model
{
    use MassPrunable;

    /**
     * Get the prunable model query.
     */
    public function prunable(): Builder
    {
        return static::where('created_at', '<=', now()->minus(months: 1));
    }
}
```

<a name="replicating-models"></a>
## Копіювання моделей

Ви можете створити незбережену копію наявного екземпляра моделі методом `replicate`. Це особливо зручно, коли у вас є моделі, що мають багато однакових атрибутів:

```php
use App\Models\Address;

$shipping = Address::create([
    'type' => 'shipping',
    'line_1' => '123 Example Street',
    'city' => 'Victorville',
    'state' => 'CA',
    'postcode' => '90001',
]);

$billing = $shipping->replicate()->fill([
    'type' => 'billing'
]);

$billing->save();
```

Щоб виключити один чи кілька атрибутів із копіювання в нову модель, передайте методу `replicate` масив:

```php
$flight = Flight::create([
    'destination' => 'LAX',
    'origin' => 'LHR',
    'last_flown' => '2020-03-04 11:00:00',
    'last_pilot_id' => 747,
]);

$flight = $flight->replicate([
    'last_flown',
    'last_pilot_id'
]);
```

<a name="query-scopes"></a>
## Скопи запитів

<a name="global-scopes"></a>
### Глобальні скопи

Глобальні скопи (global scope) дозволяють додати обмеження до всіх запитів певної моделі. Власна функція [м'якого видалення](#soft-deleting) в Laravel користується глобальними скопами, щоб діставати з бази лише «невидалені» моделі. Власні глобальні скопи дають зручний і простий спосіб гарантувати, що кожен запит до певної моделі отримає потрібні обмеження.

<a name="generating-scopes"></a>
#### Створення скопів

Щоб згенерувати новий глобальний скоп, викличте artisan-команду `make:scope` - вона покладе згенерований скоп у каталог `app/Models/Scopes` вашого застосунку:

```shell
php artisan make:scope AncientScope
```

<a name="writing-global-scopes"></a>
#### Написання глобальних скопів

Написати глобальний скоп просто. Спершу командою `make:scope` згенеруйте клас, що реалізує інтерфейс `Illuminate\Database\Eloquent\Scope`. Інтерфейс `Scope` вимагає реалізувати один метод - `apply`. Метод `apply` може додавати до запиту обмеження `where` чи інші потрібні вирази:

```php
<?php

namespace App\Models\Scopes;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;

class AncientScope implements Scope
{
    /**
     * Apply the scope to a given Eloquent query builder.
     */
    public function apply(Builder $builder, Model $model): void
    {
        $builder->where('created_at', '<', now()->minus(years: 2000));
    }
}
```

> [!NOTE]
> Якщо ваш глобальний скоп додає стовпці до виразу select запиту, користуйтеся методом `addSelect`, а не `select`. Так ви не заміните ненавмисно наявний вираз select запиту.

<a name="applying-global-scopes"></a>
#### Застосування глобальних скопів

Щоб призначити моделі глобальний скоп, просто поставте на ній атрибут `ScopedBy`:

```php
<?php

namespace App\Models;

use App\Models\Scopes\AncientScope;
use Illuminate\Database\Eloquent\Attributes\ScopedBy;

#[ScopedBy([AncientScope::class])]
class User extends Model
{
    //
}
```

Або ж ви можете зареєструвати глобальний скоп вручну, перевизначивши метод `booted` моделі й викликавши метод `addGlobalScope`. Метод `addGlobalScope` приймає єдиний аргумент - екземпляр вашого скопа:

```php
<?php

namespace App\Models;

use App\Models\Scopes\AncientScope;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * The "booted" method of the model.
     */
    protected static function booted(): void
    {
        static::addGlobalScope(new AncientScope);
    }
}
```

Після додавання скопа з прикладу вище до моделі `App\Models\User` виклик методу `User::all()` виконає такий SQL-запит:

```sql
select * from `users` where `created_at` < 0021-02-18 00:00:00
```

<a name="anonymous-global-scopes"></a>
#### Анонімні глобальні скопи

Eloquent дозволяє також описувати глобальні скопи замиканнями - це особливо зручно для простих скопів, яким не потрібен окремий клас. Описуючи глобальний скоп замиканням, передайте першим аргументом методу `addGlobalScope` назву скопа на власний розсуд:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * The "booted" method of the model.
     */
    protected static function booted(): void
    {
        static::addGlobalScope('ancient', function (Builder $builder) {
            $builder->where('created_at', '<', now()->minus(years: 2000));
        });
    }
}
```

<a name="removing-global-scopes"></a>
#### Прибирання глобальних скопів

Якщо ви хочете прибрати глобальний скоп для конкретного запиту, скористайтеся методом `withoutGlobalScope`. Він приймає єдиний аргумент - назву класу глобального скопа:

```php
User::withoutGlobalScope(AncientScope::class)->get();
```

Або, якщо ви описали глобальний скоп замиканням, передайте рядкову назву, яку йому призначили:

```php
User::withoutGlobalScope('ancient')->get();
```

Якщо ви хочете прибрати кілька або навіть усі глобальні скопи запиту, скористайтеся методами `withoutGlobalScopes` та `withoutGlobalScopesExcept`:

```php
// Remove all of the global scopes...
User::withoutGlobalScopes()->get();

// Remove some of the global scopes...
User::withoutGlobalScopes([
    FirstScope::class, SecondScope::class
])->get();

// Remove all global scopes except the given ones...
User::withoutGlobalScopesExcept([
    SecondScope::class,
])->get();
```

<a name="local-scopes"></a>
### Локальні скопи

Локальні скопи дозволяють описати типові набори обмежень запиту, які легко перевикористовувати в усьому застосунку. Наприклад, вам може часто знадобитися діставати всіх користувачів, що вважаються «популярними». Щоб описати скоп, додайте до методу Eloquent атрибут `Scope`.

Скопи завжди мають повертати той самий екземпляр конструктора запитів або `void`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Scope a query to only include popular users.
     */
    #[Scope]
    protected function popular(Builder $query): void
    {
        $query->where('votes', '>', 100);
    }

    /**
     * Scope a query to only include active users.
     */
    #[Scope]
    protected function active(Builder $query): void
    {
        $query->where('active', 1);
    }
}
```

<a name="utilizing-a-local-scope"></a>
#### Використання локального скопа

Коли скоп описано, ви можете викликати його методи, роблячи запити до моделі. Виклики різних скопів можна навіть зчіплювати ланцюжком:

```php
use App\Models\User;

$users = User::popular()->active()->orderBy('created_at')->get();
```

Поєднання кількох скопів моделі Eloquent через оператор `or` може потребувати замикань, щоб досягти правильного [логічного групування](/docs/{{version}}/queries#logical-grouping):

```php
$users = User::popular()->orWhere(function (Builder $query) {
    $query->active();
})->get();
```

Але оскільки це громіздко, Laravel має метод `orWhere` «вищого порядку», який дозволяє плавно зчіплювати скопи без замикань:

```php
$users = User::popular()->orWhere->active()->get();
```

<a name="dynamic-scopes"></a>
#### Динамічні скопи

Іноді вам потрібен скоп, що приймає параметри. Для цього просто додайте свої додаткові параметри до сигнатури методу скопа. Параметри скопа описуються після параметра `$query`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Scope a query to only include users of a given type.
     */
    #[Scope]
    protected function ofType(Builder $query, string $type): void
    {
        $query->where('type', $type);
    }
}
```

Коли ви додали очікувані аргументи до сигнатури методу скопа, їх можна передавати при виклику скопа:

```php
$users = User::ofType('admin')->get();
```

Методи скопів з атрибутом мають бути `protected`. Викликаючи такий скоп зсередини класу моделі, робіть це через екземпляр конструктора запитів - наприклад, `static::query()->ofType('admin')`, - щоб виклик пройшов через механізм обробки скопів Eloquent.

<a name="pending-attributes"></a>
### Відкладені атрибути

Якщо ви хочете створювати скопами моделі з тими самими атрибутами, які цей скоп використовує як обмеження, скористайтеся методом `withAttributes` при побудові запиту скопа:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    /**
     * Scope the query to only include drafts.
     */
    #[Scope]
    protected function draft(Builder $query): void
    {
        $query->withAttributes([
            'hidden' => true,
        ]);
    }
}
```

Метод `withAttributes` додасть до запиту умови `where` із заданими атрибутами, а також задасть ці атрибути будь-яким моделям, створеним через цей скоп:

```php
$draft = Post::draft()->create(['title' => 'In Progress']);

$draft->hidden; // true
```

Щоб метод `withAttributes` не додавав до запиту умов `where`, задайте аргументу `asConditions` значення `false`:

```php
$query->withAttributes([
    'hidden' => true,
], asConditions: false);
```

<a name="comparing-models"></a>
## Порівняння моделей

Іноді вам потрібно визначити, чи є дві моделі «однаковими». Методи `is` та `isNot` швидко перевіряють, чи мають дві моделі однакові первинний ключ, таблицю й підключення до бази даних:

```php
if ($post->is($anotherPost)) {
    // ...
}

if ($post->isNot($anotherPost)) {
    // ...
}
```

Методи `is` та `isNot` доступні також при роботі зі [зв'язками](/docs/{{version}}/eloquent-relationships) `belongsTo`, `hasOne`, `morphTo` і `morphOne`. Це особливо зручно, коли ви хочете порівняти пов'язану модель, не виконуючи запиту для її отримання:

```php
if ($post->author()->is($user)) {
    // ...
}
```

<a name="events"></a>
## Події

> [!NOTE]
> Хочете транслювати події Eloquent прямо до клієнтської частини застосунку? Погляньте на [бродкастинг подій моделей](/docs/{{version}}/broadcasting#model-broadcasting) у Laravel.

Моделі Eloquent надсилають кілька подій, що дозволяють вклинитися в такі моменти життєвого циклу моделі: `retrieved`, `creating`, `created`, `updating`, `updated`, `saving`, `saved`, `deleting`, `deleted`, `trashed`, `forceDeleting`, `forceDeleted`, `restoring`, `restored` і `replicating`.

Подія `retrieved` надсилається, коли наявну модель дістають із бази даних. Коли нову модель зберігають уперше, надсилаються події `creating` та `created`. Події `updating` / `updated` надсилаються, коли наявну модель змінюють і викликають метод `save`. Події `saving` / `saved` надсилаються, коли модель створюють або оновлюють - навіть якщо атрибути моделі не змінилися. Назви подій, що закінчуються на `-ing`, надсилаються до того, як зміни моделі збережено, а ті, що закінчуються на `-ed`, - після збереження змін.

Щоб почати слухати події моделі, опишіть у моделі Eloquent властивість `$dispatchesEvents`. Вона зіставляє різні моменти життєвого циклу моделі з вашими власними [класами подій](/docs/{{version}}/events). Кожен клас події моделі має очікувати екземпляр відповідної моделі у своєму конструкторі:

```php
<?php

namespace App\Models;

use App\Events\UserDeleted;
use App\Events\UserSaved;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The event map for the model.
     *
     * @var array<string, string>
     */
    protected $dispatchesEvents = [
        'saved' => UserSaved::class,
        'deleted' => UserDeleted::class,
    ];
}
```

Описавши та зіставивши події Eloquent, ви можете обробляти їх [слухачами подій](/docs/{{version}}/events#defining-listeners).

> [!WARNING]
> Коли ви виконуєте масове оновлення чи видалення через Eloquent, події моделі `saved`, `updated`, `deleting` та `deleted` для зачеплених моделей не надсилаються. Причина в тому, що при масових оновленнях чи видаленнях моделі насправді ніколи не дістаються з бази.

<a name="events-using-closures"></a>
### Використання замикань

Замість власних класів подій ви можете зареєструвати замикання, які виконуються при надсиланні різних подій моделі. Зазвичай ці замикання реєструють у методі `booted` вашої моделі:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * The "booted" method of the model.
     */
    protected static function booted(): void
    {
        static::created(function (User $user) {
            // ...
        });
    }
}
```

За потреби, реєструючи події моделі, ви можете скористатися [анонімними слухачами подій, які можна ставити в чергу](/docs/{{version}}/events#queueable-anonymous-event-listeners). Так ви накажете Laravel виконувати слухача події моделі у фоні через [чергу](/docs/{{version}}/queues) вашого застосунку:

```php
use function Illuminate\Events\queueable;

static::created(queueable(function (User $user) {
    // ...
}));
```

<a name="observers"></a>
### Спостерігачі

<a name="defining-observers"></a>
#### Опис спостерігачів

Якщо ви слухаєте багато подій певної моделі, ви можете згрупувати всіх слухачів в одному класі-спостерігачі. Назви методів класу спостерігача відповідають подіям Eloquent, які ви хочете слухати. Кожен такий метод отримує єдиний аргумент - відповідну модель. Найпростіше створити новий клас спостерігача artisan-командою `make:observer`:

```shell
php artisan make:observer UserObserver --model=User
```

Ця команда покладе нового спостерігача в каталог `app/Observers`. Якщо каталогу немає, Artisan створить його за вас. Ваш новий спостерігач виглядатиме так:

```php
<?php

namespace App\Observers;

use App\Models\User;

class UserObserver
{
    /**
     * Handle the User "created" event.
     */
    public function created(User $user): void
    {
        // ...
    }

    /**
     * Handle the User "updated" event.
     */
    public function updated(User $user): void
    {
        // ...
    }

    /**
     * Handle the User "deleted" event.
     */
    public function deleted(User $user): void
    {
        // ...
    }

    /**
     * Handle the User "restored" event.
     */
    public function restored(User $user): void
    {
        // ...
    }

    /**
     * Handle the User "forceDeleted" event.
     */
    public function forceDeleted(User $user): void
    {
        // ...
    }
}
```

Щоб зареєструвати спостерігача, поставте на відповідній моделі атрибут `ObservedBy`:

```php
use App\Observers\UserObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;

#[ObservedBy([UserObserver::class])]
class User extends Authenticatable
{
    //
}
```

Або ж ви можете зареєструвати спостерігача вручну, викликавши метод `observe` на моделі, за якою хочете спостерігати. Реєструвати спостерігачів можна в методі `boot` класу `AppServiceProvider` вашого застосунку:

```php
use App\Models\User;
use App\Observers\UserObserver;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    User::observe(UserObserver::class);
}
```

> [!NOTE]
> Спостерігач може слухати й додаткові події - наприклад, `saving` і `retrieved`. Ці події описано в документації з [подій](#events).

<a name="observers-and-database-transactions"></a>
#### Спостерігачі та транзакції

Коли моделі створюються в межах транзакції, ви можете захотіти, щоб спостерігач виконував свої обробники подій лише після коміту транзакції. Для цього реалізуйте у спостерігачі інтерфейс `ShouldHandleEventsAfterCommit`. Якщо транзакції немає, обробники подій виконаються негайно:

```php
<?php

namespace App\Observers;

use App\Models\User;
use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;

class UserObserver implements ShouldHandleEventsAfterCommit
{
    /**
     * Handle the User "created" event.
     */
    public function created(User $user): void
    {
        // ...
    }
}
```

<a name="muting-events"></a>
### Вимкнення подій

Іноді вам потрібно тимчасово «вимкнути» всі події, які надсилає модель. Це робиться методом `withoutEvents`. Метод `withoutEvents` приймає єдиний аргумент - замикання. Будь-який код, виконаний у цьому замиканні, не надсилатиме подій моделі, а значення, яке поверне замикання, поверне й метод `withoutEvents`:

```php
use App\Models\User;

$user = User::withoutEvents(function () {
    User::findOrFail(1)->delete();

    return User::find(2);
});
```

<a name="saving-a-single-model-without-events"></a>
#### Збереження однієї моделі без подій

Іноді ви хочете «зберегти» певну модель, не надсилаючи жодних подій. Це робиться методом `saveQuietly`:

```php
$user = User::findOrFail(1);

$user->name = 'Victoria Faith';

$user->saveQuietly();
```

Ви можете також «оновити», «видалити», «м'яко видалити», «відновити» та «скопіювати» модель без надсилання подій:

```php
$user->deleteQuietly();
$user->forceDeleteQuietly();
$user->restoreQuietly();
```

---

Джерело: https://laravelukraine.com/docs/13.x/eloquent-relationships

# Eloquent: зв'язки

- [Вступ](#introduction)
- [Визначення зв'язків](#defining-relationships)
    - [Один до одного / Has One](#one-to-one)
    - [Один до багатьох / Has Many](#one-to-many)
    - [Один до багатьох (зворотний) / Belongs To](#one-to-many-inverse)
    - [Has One of Many](#has-one-of-many)
    - [Has One Through](#has-one-through)
    - [Has Many Through](#has-many-through)
- [Зв'язки зі скопами](#scoped-relationships)
- [Зв'язки «багато до багатьох»](#many-to-many)
    - [Отримання стовпців проміжної таблиці](#retrieving-intermediate-table-columns)
    - [Фільтрація запитів за стовпцями проміжної таблиці](#filtering-queries-via-intermediate-table-columns)
    - [Сортування запитів за стовпцями проміжної таблиці](#ordering-queries-via-intermediate-table-columns)
    - [Визначення власних моделей проміжної таблиці](#defining-custom-intermediate-table-models)
        - [Автоматична гідратація зв'язків pivot-моделі](#automatically-hydrating-pivot-relationships)
- [Поліморфні зв'язки](#polymorphic-relationships)
    - [Один до одного](#one-to-one-polymorphic-relations)
    - [Один до багатьох](#one-to-many-polymorphic-relations)
    - [Один з багатьох](#one-of-many-polymorphic-relations)
    - [Багато до багатьох](#many-to-many-polymorphic-relations)
    - [Власні поліморфні типи](#custom-polymorphic-types)
- [Динамічні зв'язки](#dynamic-relationships)
- [Запити до зв'язків](#querying-relations)
    - [Методи зв'язків проти динамічних властивостей](#relationship-methods-vs-dynamic-properties)
    - [Запити за наявністю зв'язку](#querying-relationship-existence)
    - [Запити за відсутністю зв'язку](#querying-relationship-absence)
    - [Запити до зв'язків Morph To](#querying-morph-to-relationships)
- [Агрегування пов'язаних моделей](#aggregating-related-models)
    - [Підрахунок пов'язаних моделей](#counting-related-models)
    - [Інші агрегатні функції](#other-aggregate-functions)
    - [Підрахунок пов'язаних моделей у зв'язках Morph To](#counting-related-models-on-morph-to-relationships)
- [Жадібне завантаження](#eager-loading)
    - [Обмеження жадібного завантаження](#constraining-eager-loads)
    - [Відкладене жадібне завантаження](#lazy-eager-loading)
    - [Автоматичне жадібне завантаження](#automatic-eager-loading)
    - [Заборона лінивого завантаження](#preventing-lazy-loading)
- [Вставлення й оновлення пов'язаних моделей](#inserting-and-updating-related-models)
    - [Метод `save`](#the-save-method)
    - [Метод `create`](#the-create-method)
    - [Зв'язки Belongs To](#updating-belongs-to-relationships)
    - [Зв'язки «багато до багатьох»](#updating-many-to-many-relationships)
- [Оновлення часових міток батьківської моделі](#touching-parent-timestamps)

<a name="introduction"></a>
## Вступ

Таблиці бази даних часто пов'язані одна з одною. Наприклад, допис у блозі може мати багато коментарів, а замовлення може бути пов'язане з користувачем, який його зробив. Eloquent робить керування такими зв'язками простим і підтримує різні поширені типи зв'язків:

<div class="content-list" markdown="1">

- [Один до одного](#one-to-one)
- [Один до багатьох](#one-to-many)
- [Багато до багатьох](#many-to-many)
- [Has One Through](#has-one-through)
- [Has Many Through](#has-many-through)
- [Один до одного (поліморфний)](#one-to-one-polymorphic-relations)
- [Один до багатьох (поліморфний)](#one-to-many-polymorphic-relations)
- [Багато до багатьох (поліморфний)](#many-to-many-polymorphic-relations)

</div>

<a name="defining-relationships"></a>
## Визначення зв'язків

Зв'язки Eloquent визначаються як методи в класах ваших моделей Eloquent. Оскільки зв'язки водночас є потужними [конструкторами запитів](/docs/{{version}}/queries), визначення їх як методів дає широкі можливості для ланцюжків методів і запитів. Наприклад, ми можемо додати додаткові обмеження до цього зв'язку `posts`:

```php
$user->posts()->where('active', 1)->get();
```

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

<a name="one-to-one"></a>
### Один до одного / Has One

Зв'язок «один до одного» - це дуже базовий тип зв'язку в базі даних. Наприклад, модель `User` може бути пов'язана з однією моделлю `Phone`. Щоб визначити цей зв'язок, ми додамо до моделі `User` метод `phone`. Метод `phone` має викликати метод `hasOne` і повернути його результат. Метод `hasOne` доступний вашій моделі через базовий клас моделі `Illuminate\Database\Eloquent\Model`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;

class User extends Model
{
    /**
     * Get the phone associated with the user.
     */
    public function phone(): HasOne
    {
        return $this->hasOne(Phone::class);
    }
}
```

Перший аргумент, який передається методу `hasOne`, - ім'я класу пов'язаної моделі. Коли зв'язок визначено, ми можемо отримати пов'язаний запис через динамічні властивості Eloquent. Динамічні властивості дозволяють звертатися до методів зв'язків так, ніби це властивості, визначені на моделі:

```php
$phone = User::find(1)->phone;
```

Eloquent визначає зовнішній ключ зв'язку на основі імені батьківської моделі. У цьому випадку автоматично припускається, що модель `Phone` має зовнішній ключ `user_id`. Якщо ви хочете перевизначити цю угоду, передайте методу `hasOne` другий аргумент:

```php
return $this->hasOne(Phone::class, 'foreign_key');
```

Крім того, Eloquent припускає, що значення зовнішнього ключа має збігатися зі значенням стовпця первинного ключа батьківської моделі. Інакше кажучи, Eloquent шукатиме значення стовпця `id` користувача у стовпці `user_id` запису `Phone`. Якщо ви хочете, щоб зв'язок використовував значення первинного ключа, відмінне від `id` або від первинного ключа вашої моделі, передайте методу `hasOne` третій аргумент:

```php
return $this->hasOne(Phone::class, 'foreign_key', 'local_key');
```

<a name="one-to-one-defining-the-inverse-of-the-relationship"></a>
#### Визначення зворотного зв'язку

Отже, ми можемо звернутися до моделі `Phone` з нашої моделі `User`. Тепер визначмо на моделі `Phone` зв'язок, який дозволить нам отримати користувача - власника телефону. Зворотний до `hasOne` зв'язок визначається методом `belongsTo`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Phone extends Model
{
    /**
     * Get the user that owns the phone.
     */
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}
```

Під час виклику методу `user` Eloquent спробує знайти модель `User`, чий `id` збігається зі стовпцем `user_id` моделі `Phone`.

Eloquent визначає ім'я зовнішнього ключа, беручи ім'я методу зв'язку та додаючи до нього суфікс `_id`. Тож у цьому випадку Eloquent припускає, що модель `Phone` має стовпець `user_id`. Проте якщо зовнішній ключ на моделі `Phone` називається не `user_id`, ви можете передати власне ім'я ключа другим аргументом до методу `belongsTo`:

```php
/**
 * Get the user that owns the phone.
 */
public function user(): BelongsTo
{
    return $this->belongsTo(User::class, 'foreign_key');
}
```

Якщо батьківська модель не використовує `id` як первинний ключ або ви хочете знаходити пов'язану модель за іншим стовпцем, передайте методу `belongsTo` третій аргумент із власним ключем батьківської таблиці:

```php
/**
 * Get the user that owns the phone.
 */
public function user(): BelongsTo
{
    return $this->belongsTo(User::class, 'foreign_key', 'owner_key');
}
```

<a name="one-to-many"></a>
### Один до багатьох / Has Many

Зв'язок «один до багатьох» використовується, коли одна модель є батьківською для однієї чи кількох дочірніх моделей. Наприклад, допис у блозі може мати нескінченну кількість коментарів. Як і всі інші зв'язки Eloquent, зв'язки «один до багатьох» визначаються методом на вашій моделі Eloquent:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Post extends Model
{
    /**
     * Get the comments for the blog post.
     */
    public function comments(): HasMany
    {
        return $this->hasMany(Comment::class);
    }
}
```

Пам'ятайте: Eloquent автоматично визначить потрібний стовпець зовнішнього ключа для моделі `Comment`. За угодою Eloquent візьме ім'я батьківської моделі у «snake case» і додасть суфікс `_id`. Тож у цьому прикладі Eloquent припустить, що стовпець зовнішнього ключа на моделі `Comment` - це `post_id`.

Коли метод зв'язку визначено, ми можемо звернутися до [колекції](/docs/{{version}}/eloquent-collections) пов'язаних коментарів через властивість `comments`. Пам'ятайте: оскільки Eloquent надає «динамічні властивості зв'язків», ми можемо звертатися до методів зв'язків так, ніби це властивості моделі:

```php
use App\Models\Post;

$comments = Post::find(1)->comments;

foreach ($comments as $comment) {
    // ...
}
```

Оскільки всі зв'язки водночас є конструкторами запитів, ви можете додати до запиту зв'язку додаткові обмеження, викликавши метод `comments` і продовживши ланцюжок умов:

```php
$comment = Post::find(1)->comments()
    ->where('title', 'foo')
    ->first();
```

Як і в методі `hasOne`, ви можете перевизначити зовнішній і локальний ключі, передавши додаткові аргументи методу `hasMany`:

```php
return $this->hasMany(Comment::class, 'foreign_key');

return $this->hasMany(Comment::class, 'foreign_key', 'local_key');
```

<a name="automatically-hydrating-parent-models-on-children"></a>
#### Автоматичне заповнення батьківських моделей у дочірніх

Навіть із жадібним завантаженням Eloquent можуть виникати проблеми запитів «N + 1», якщо ви звертаєтеся до батьківської моделі з дочірньої, перебираючи дочірні моделі:

```php
$posts = Post::with('comments')->get();

foreach ($posts as $post) {
    foreach ($post->comments as $comment) {
        echo $comment->post->title;
    }
}
```

У прикладі вище виникла проблема запитів «N + 1», бо, хоч коментарі й були жадібно завантажені для кожної моделі `Post`, Eloquent не заповнює автоматично батьківський `Post` у кожній дочірній моделі `Comment`.

Якщо ви хочете, щоб Eloquent автоматично заповнював батьківські моделі в дочірніх, викличте метод `chaperone` під час визначення зв'язку `hasMany`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Post extends Model
{
    /**
     * Get the comments for the blog post.
     */
    public function comments(): HasMany
    {
        return $this->hasMany(Comment::class)->chaperone();
    }
}
```

Або ж, якщо ви хочете вмикати автоматичне заповнення батьківських моделей під час виконання, викличте метод `chaperone` під час жадібного завантаження зв'язку:

```php
use App\Models\Post;

$posts = Post::with([
    'comments' => fn ($comments) => $comments->chaperone(),
])->get();
```

<a name="one-to-many-inverse"></a>
### Один до багатьох (зворотний) / Belongs To

Тепер, коли ми маємо доступ до всіх коментарів допису, визначмо зв'язок, який дозволить коментарю звернутися до свого батьківського допису. Щоб визначити зворотний до `hasMany` зв'язок, оголосіть на дочірній моделі метод зв'язку, який викликає метод `belongsTo`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Comment extends Model
{
    /**
     * Get the post that owns the comment.
     */
    public function post(): BelongsTo
    {
        return $this->belongsTo(Post::class);
    }
}
```

Коли зв'язок визначено, ми можемо отримати батьківський допис коментаря через «динамічну властивість зв'язку» `post`:

```php
use App\Models\Comment;

$comment = Comment::find(1);

return $comment->post->title;
```

У прикладі вище Eloquent спробує знайти модель `Post`, чий `id` збігається зі стовпцем `post_id` моделі `Comment`.

Eloquent визначає ім'я зовнішнього ключа за замовчуванням, беручи ім'я методу зв'язку й додаючи до нього `_`, а далі ім'я стовпця первинного ключа батьківської моделі. Тож у цьому прикладі Eloquent припустить, що зовнішній ключ моделі `Post` у таблиці `comments` - це `post_id`.

Проте якщо зовнішній ключ вашого зв'язку не дотримується цих угод, ви можете передати власне ім'я зовнішнього ключа другим аргументом до методу `belongsTo`:

```php
/**
 * Get the post that owns the comment.
 */
public function post(): BelongsTo
{
    return $this->belongsTo(Post::class, 'foreign_key');
}
```

Якщо ваша батьківська модель не використовує `id` як первинний ключ або ви хочете знаходити пов'язану модель за іншим стовпцем, передайте методу `belongsTo` третій аргумент із власним ключем батьківської таблиці:

```php
/**
 * Get the post that owns the comment.
 */
public function post(): BelongsTo
{
    return $this->belongsTo(Post::class, 'foreign_key', 'owner_key');
}
```

<a name="default-models"></a>
#### Моделі за замовчуванням

Зв'язки `belongsTo`, `hasOne`, `hasOneThrough` і `morphOne` дозволяють визначити модель за замовчуванням, яку буде повернено, якщо зв'язок дорівнює `null`. Цей патерн часто називають [патерном Null Object](https://en.wikipedia.org/wiki/Null_Object_pattern), і він допомагає позбутися умовних перевірок у коді. У наступному прикладі зв'язок `user` поверне порожню модель `App\Models\User`, якщо до моделі `Post` не прикріплено жодного користувача:

```php
/**
 * Get the author of the post.
 */
public function user(): BelongsTo
{
    return $this->belongsTo(User::class)->withDefault();
}
```

Щоб заповнити модель за замовчуванням атрибутами, передайте методу `withDefault` масив або замикання:

```php
/**
 * Get the author of the post.
 */
public function user(): BelongsTo
{
    return $this->belongsTo(User::class)->withDefault([
        'name' => 'Guest Author',
    ]);
}

/**
 * Get the author of the post.
 */
public function user(): BelongsTo
{
    return $this->belongsTo(User::class)->withDefault(function (User $user, Post $post) {
        $user->name = 'Guest Author';
    });
}
```

<a name="querying-belongs-to-relationships"></a>
#### Запити до зв'язків Belongs To

Роблячи запит до дочірніх записів зв'язку «belongs to», ви можете вручну побудувати умову `where`, щоб отримати відповідні моделі Eloquent:

```php
use App\Models\Post;

$posts = Post::where('user_id', $user->id)->get();
```

Проте зручнішим може виявитися метод `whereBelongsTo`, який автоматично визначить потрібний зв'язок і зовнішній ключ для заданої моделі:

```php
$posts = Post::whereBelongsTo($user)->get();
```

Ви також можете передати методу `whereBelongsTo` екземпляр [колекції](/docs/{{version}}/eloquent-collections). У такому разі Laravel поверне моделі, що належать будь-якій із батьківських моделей у колекції:

```php
$users = User::where('vip', true)->get();

$posts = Post::whereBelongsTo($users)->get();
```

За замовчуванням Laravel визначає зв'язок, пов'язаний із заданою моделлю, за іменем класу моделі; проте ви можете вказати ім'я зв'язку вручну, передавши його другим аргументом до методу `whereBelongsTo`:

```php
$posts = Post::whereBelongsTo($user, 'author')->get();
```

<a name="has-one-of-many"></a>
### Has One of Many

Іноді модель має багато пов'язаних моделей, але вам потрібно легко отримати «найновішу» чи «найстарішу» з них. Наприклад, модель `User` може бути пов'язана з багатьма моделями `Order`, але ви хочете мати зручний спосіб працювати з найновішим замовленням користувача. Цього можна досягти зв'язком типу `hasOne` у поєднанні з методами `ofMany`:

```php
/**
 * Get the user's most recent order.
 */
public function latestOrder(): HasOne
{
    return $this->hasOne(Order::class)->latestOfMany();
}
```

Так само ви можете визначити метод для отримання «найстарішої», тобто першої, пов'язаної моделі:

```php
/**
 * Get the user's oldest order.
 */
public function oldestOrder(): HasOne
{
    return $this->hasOne(Order::class)->oldestOfMany();
}
```

За замовчуванням методи `latestOfMany` та `oldestOfMany` отримують найновішу чи найстарішу пов'язану модель за первинним ключем моделі, який має бути придатним для сортування. Проте іноді потрібно отримати одну модель із більшого зв'язку за іншим критерієм сортування.

Наприклад, за допомогою методу `ofMany` можна отримати найдорожче замовлення користувача. Метод `ofMany` приймає першим аргументом стовпець для сортування, а другим - агрегатну функцію (`min` чи `max`), яку слід застосувати під час запиту пов'язаної моделі:

```php
/**
 * Get the user's largest order.
 */
public function largestOrder(): HasOne
{
    return $this->hasOne(Order::class)->ofMany('price', 'max');
}
```

> [!WARNING]
> Оскільки PostgreSQL не підтримує виконання функції `MAX` над стовпцями UUID, наразі неможливо використовувати зв'язки one-of-many разом зі стовпцями UUID у PostgreSQL.

<a name="converting-many-relationships-to-has-one-relationships"></a>
#### Перетворення зв'язків «Many» на зв'язки Has One

Часто, отримуючи одну модель за допомогою методів `latestOfMany`, `oldestOfMany` чи `ofMany`, ви вже маєте визначений зв'язок «has many» для тієї самої моделі. Для зручності Laravel дозволяє легко перетворити цей зв'язок на «has one», викликавши на ньому метод `one`:

```php
/**
 * Get the user's orders.
 */
public function orders(): HasMany
{
    return $this->hasMany(Order::class);
}

/**
 * Get the user's largest order.
 */
public function largestOrder(): HasOne
{
    return $this->orders()->one()->ofMany('price', 'max');
}
```

Метод `one` можна також використати, щоб перетворити зв'язки `HasManyThrough` на `HasOneThrough`:

```php
public function latestDeployment(): HasOneThrough
{
    return $this->deployments()->one()->latestOfMany();
}
```

<a name="advanced-has-one-of-many-relationships"></a>
#### Складніші зв'язки Has One of Many

Можна будувати й складніші зв'язки «has one of many». Наприклад, модель `Product` може мати багато пов'язаних моделей `Price`, які зберігаються в системі навіть після публікації нових цін. До того ж нові дані про ціни на товар можуть публікуватися заздалегідь і набирати чинності в майбутньому - через стовпець `published_at`.

Тож, підсумовуючи, нам потрібно отримати найновішу опубліковану ціну, дата публікації якої не в майбутньому. Крім того, якщо дві ціни мають однакову дату публікації, ми віддамо перевагу ціні з більшим ID. Щоб цього досягти, ми маємо передати методу `ofMany` масив зі стовпцями для сортування, які визначають найновішу ціну. Також другим аргументом методу `ofMany` буде замикання. Воно відповідатиме за додавання до запиту зв'язку додаткових обмежень за датою публікації:

```php
/**
 * Get the current pricing for the product.
 */
public function currentPricing(): HasOne
{
    return $this->hasOne(Price::class)->ofMany([
        'published_at' => 'max',
        'id' => 'max',
    ], function (Builder $query) {
        $query->where('published_at', '<', now());
    });
}
```

<a name="has-one-through"></a>
### Has One Through

Зв'язок «has-one-through» визначає зв'язок «один до одного» з іншою моделлю. Проте цей зв'язок вказує, що модель, яка його оголошує, може бути зіставлена з одним екземпляром іншої моделі _через_ третю модель.

Наприклад, у застосунку автомайстерні кожна модель `Mechanic` може бути пов'язана з однією моделлю `Car`, а кожна модель `Car` - з однією моделлю `Owner`. Хоча між механіком і власником немає прямого зв'язку в базі даних, механік може дістатися власника _через_ модель `Car`. Погляньмо на таблиці, потрібні для визначення цього зв'язку:

```text
mechanics
    id - integer
    name - string

cars
    id - integer
    model - string
    mechanic_id - integer

owners
    id - integer
    name - string
    car_id - integer
```

Тепер, коли ми розглянули структуру таблиць для цього зв'язку, визначмо зв'язок на моделі `Mechanic`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOneThrough;

class Mechanic extends Model
{
    /**
     * Get the car's owner.
     */
    public function carOwner(): HasOneThrough
    {
        return $this->hasOneThrough(Owner::class, Car::class);
    }
}
```

Перший аргумент, переданий методу `hasOneThrough`, - ім'я кінцевої моделі, до якої ми хочемо дістатися, а другий - ім'я проміжної моделі.

Або ж, якщо відповідні зв'язки вже визначені на всіх залучених моделях, ви можете плавно оголосити зв'язок «has-one-through», викликавши метод `through` і передавши імена цих зв'язків. Наприклад, якщо модель `Mechanic` має зв'язок `cars`, а модель `Car` - зв'язок `owner`, ви можете визначити зв'язок «has-one-through» між механіком і власником так:

```php
// String based syntax...
return $this->through('cars')->has('owner');

// Dynamic syntax...
return $this->throughCars()->hasOwner();
```

<a name="has-one-through-key-conventions"></a>
#### Угоди щодо ключів

Під час виконання запитів зв'язку використовуються типові угоди Eloquent щодо зовнішніх ключів. Якщо ви хочете налаштувати ключі зв'язку, передайте їх третім і четвертим аргументами до методу `hasOneThrough`. Третій аргумент - ім'я зовнішнього ключа на проміжній моделі. Четвертий - ім'я зовнішнього ключа на кінцевій моделі. П'ятий аргумент - локальний ключ, а шостий - локальний ключ проміжної моделі:

```php
class Mechanic extends Model
{
    /**
     * Get the car's owner.
     */
    public function carOwner(): HasOneThrough
    {
        return $this->hasOneThrough(
            Owner::class,
            Car::class,
            'mechanic_id', // Foreign key on the cars table...
            'car_id', // Foreign key on the owners table...
            'id', // Local key on the mechanics table...
            'id' // Local key on the cars table...
        );
    }
}
```

Або ж, як згадувалося раніше, якщо відповідні зв'язки вже визначені на всіх залучених моделях, ви можете плавно оголосити зв'язок «has-one-through», викликавши метод `through` і передавши імена цих зв'язків. Цей підхід має перевагу: він повторно використовує угоди щодо ключів, уже визначені в наявних зв'язках:

```php
// String based syntax...
return $this->through('cars')->has('owner');

// Dynamic syntax...
return $this->throughCars()->hasOwner();
```

<a name="has-many-through"></a>
### Has Many Through

Зв'язок «has-many-through» дає зручний спосіб дістатися віддалених зв'язків через проміжний зв'язок. Наприклад, уявімо, що ми будуємо платформу для розгортання на кшталт [Laravel Cloud](https://cloud.laravel.com). Модель `Application` може звертатися до багатьох моделей `Deployment` через проміжну модель `Environment`. Із цим прикладом ви легко зберете всі розгортання для заданого застосунку. Погляньмо на таблиці, потрібні для визначення цього зв'язку:

```text
applications
    id - integer
    name - string

environments
    id - integer
    application_id - integer
    name - string

deployments
    id - integer
    environment_id - integer
    commit_hash - string
```

Тепер, коли ми розглянули структуру таблиць для цього зв'язку, визначмо зв'язок на моделі `Application`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;

class Application extends Model
{
    /**
     * Get all of the deployments for the application.
     */
    public function deployments(): HasManyThrough
    {
        return $this->hasManyThrough(Deployment::class, Environment::class);
    }
}
```

Перший аргумент, переданий методу `hasManyThrough`, - ім'я кінцевої моделі, до якої ми хочемо дістатися, а другий - ім'я проміжної моделі.

Або ж, якщо відповідні зв'язки вже визначені на всіх залучених моделях, ви можете плавно оголосити зв'язок «has-many-through», викликавши метод `through` і передавши імена цих зв'язків. Наприклад, якщо модель `Application` має зв'язок `environments`, а модель `Environment` - зв'язок `deployments`, ви можете визначити зв'язок «has-many-through» між застосунком і розгортаннями так:

```php
// String based syntax...
return $this->through('environments')->has('deployments');

// Dynamic syntax...
return $this->throughEnvironments()->hasDeployments();
```

Хоча таблиця моделі `Deployment` не містить стовпця `application_id`, зв'язок `hasManyThrough` дає доступ до розгортань застосунку через `$application->deployments`. Щоб отримати ці моделі, Eloquent перевіряє стовпець `application_id` у таблиці проміжної моделі `Environment`. Знайшовши потрібні ID середовищ, він використовує їх для запиту до таблиці моделі `Deployment`.

<a name="has-many-through-key-conventions"></a>
#### Угоди щодо ключів

Під час виконання запитів зв'язку використовуються типові угоди Eloquent щодо зовнішніх ключів. Якщо ви хочете налаштувати ключі зв'язку, передайте їх третім і четвертим аргументами до методу `hasManyThrough`. Третій аргумент - ім'я зовнішнього ключа на проміжній моделі. Четвертий - ім'я зовнішнього ключа на кінцевій моделі. П'ятий аргумент - локальний ключ, а шостий - локальний ключ проміжної моделі:

```php
class Application extends Model
{
    public function deployments(): HasManyThrough
    {
        return $this->hasManyThrough(
            Deployment::class,
            Environment::class,
            'application_id', // Foreign key on the environments table...
            'environment_id', // Foreign key on the deployments table...
            'id', // Local key on the applications table...
            'id' // Local key on the environments table...
        );
    }
}
```

Або ж, як згадувалося раніше, якщо відповідні зв'язки вже визначені на всіх залучених моделях, ви можете плавно оголосити зв'язок «has-many-through», викликавши метод `through` і передавши імена цих зв'язків. Цей підхід має перевагу: він повторно використовує угоди щодо ключів, уже визначені в наявних зв'язках:

```php
// String based syntax...
return $this->through('environments')->has('deployments');

// Dynamic syntax...
return $this->throughEnvironments()->hasDeployments();
```

<a name="scoped-relationships"></a>
### Зв'язки зі скопами

Часто до моделей додають додаткові методи, які обмежують зв'язки. Наприклад, ви можете додати до моделі `User` метод `featuredPosts`, який обмежує ширший зв'язок `posts` додатковою умовою `where`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class User extends Model
{
    /**
     * Get the user's posts.
     */
    public function posts(): HasMany
    {
        return $this->hasMany(Post::class)->latest();
    }

    /**
     * Get the user's featured posts.
     */
    public function featuredPosts(): HasMany
    {
        return $this->posts()->where('featured', true);
    }
}
```

Проте якщо ви спробуєте створити модель через метод `featuredPosts`, її атрибут `featured` не набуде значення `true`. Якщо ви хочете створювати моделі через методи зв'язків і водночас задавати атрибути, які слід додавати до всіх створених через цей зв'язок моделей, скористайтеся методом `withAttributes` під час побудови запиту зв'язку:

```php
/**
 * Get the user's featured posts.
 */
public function featuredPosts(): HasMany
{
    return $this->posts()->withAttributes(['featured' => true]);
}
```

Метод `withAttributes` додасть до запиту умови `where` із заданими атрибутами, а також додасть ці атрибути до всіх моделей, створених через метод зв'язку:

```php
$post = $user->featuredPosts()->create(['title' => 'Featured Post']);

$post->featured; // true
```

Щоб метод `withAttributes` не додавав умов `where` до запиту, встановіть аргумент `asConditions` у значення `false`:

```php
return $this->posts()->withAttributes(['featured' => true], asConditions: false);
```

<a name="many-to-many"></a>
## Зв'язки «багато до багатьох»

Зв'язки «багато до багатьох» дещо складніші за зв'язки `hasOne` та `hasMany`. Приклад такого зв'язку - користувач, який має багато ролей, причому ці самі ролі мають й інші користувачі застосунку. Наприклад, користувачеві можна призначити ролі «Author» та «Editor»; проте ці ролі можуть бути призначені й іншим користувачам. Отже, користувач має багато ролей, а роль - багато користувачів.

<a name="many-to-many-table-structure"></a>
#### Структура таблиць

Щоб визначити цей зв'язок, потрібні три таблиці бази даних: `users`, `roles` і `role_user`. Ім'я таблиці `role_user` походить від імен пов'язаних моделей в алфавітному порядку, і вона містить стовпці `user_id` та `role_id`. Ця таблиця є проміжною й пов'язує користувачів із ролями.

Пам'ятайте: оскільки роль може належати багатьом користувачам, ми не можемо просто додати стовпець `user_id` до таблиці `roles`. Це означало б, що роль може належати лише одному користувачеві. Щоб ролі можна було призначати кільком користувачам, і потрібна таблиця `role_user`. Структуру таблиць цього зв'язку можна підсумувати так:

```text
users
    id - integer
    name - string

roles
    id - integer
    name - string

role_user
    user_id - integer
    role_id - integer
```

<a name="many-to-many-model-structure"></a>
#### Структура моделей

Зв'язки «багато до багатьох» визначаються методом, який повертає результат методу `belongsToMany`. Метод `belongsToMany` надає базовий клас `Illuminate\Database\Eloquent\Model`, який використовують усі моделі Eloquent вашого застосунку. Наприклад, визначмо метод `roles` на нашій моделі `User`. Перший аргумент, який передається цьому методу, - ім'я класу пов'язаної моделі:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class User extends Model
{
    /**
     * The roles that belong to the user.
     */
    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class);
    }
}
```

Коли зв'язок визначено, ви можете звертатися до ролей користувача через динамічну властивість зв'язку `roles`:

```php
use App\Models\User;

$user = User::find(1);

foreach ($user->roles as $role) {
    // ...
}
```

Оскільки всі зв'язки водночас є конструкторами запитів, ви можете додати до запиту зв'язку додаткові обмеження, викликавши метод `roles` і продовживши ланцюжок умов:

```php
$roles = User::find(1)->roles()->orderBy('name')->get();
```

Щоб визначити ім'я проміжної таблиці зв'язку, Eloquent з'єднає імена двох пов'язаних моделей в алфавітному порядку. Проте ви можете перевизначити цю угоду, передавши другий аргумент до методу `belongsToMany`:

```php
return $this->belongsToMany(Role::class, 'role_user');
```

Окрім імені проміжної таблиці, ви можете налаштувати й імена стовпців-ключів у ній, передавши додаткові аргументи до методу `belongsToMany`. Третій аргумент - ім'я зовнішнього ключа моделі, на якій ви визначаєте зв'язок, а четвертий - ім'я зовнішнього ключа моделі, до якої ви приєднуєтеся:

```php
return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');
```

<a name="many-to-many-defining-the-inverse-of-the-relationship"></a>
#### Визначення зворотного зв'язку

Щоб визначити «зворотний» бік зв'язку «багато до багатьох», оголосіть на пов'язаній моделі метод, який також повертає результат методу `belongsToMany`. Щоб завершити наш приклад із користувачами й ролями, визначмо метод `users` на моделі `Role`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Role extends Model
{
    /**
     * The users that belong to the role.
     */
    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class);
    }
}
```

Як бачите, зв'язок визначено точно так само, як і його відповідник на моделі `User`, за винятком посилання на модель `App\Models\User`. Оскільки ми повторно використовуємо метод `belongsToMany`, під час визначення «зворотного» боку зв'язків «багато до багатьох» доступні всі звичні опції налаштування таблиці та ключів.

<a name="retrieving-intermediate-table-columns"></a>
### Отримання стовпців проміжної таблиці

Як ви вже дізналися, робота зі зв'язками «багато до багатьох» вимагає наявності проміжної таблиці. Eloquent надає кілька дуже зручних способів взаємодії з нею. Наприклад, припустімо, що наша модель `User` має багато пов'язаних моделей `Role`. Звернувшись до цього зв'язку, ми можемо дістатися проміжної таблиці через атрибут `pivot` на моделях:

```php
use App\Models\User;

$user = User::find(1);

foreach ($user->roles as $role) {
    echo $role->pivot->created_at;
}
```

Зверніть увагу: кожній отриманій моделі `Role` автоматично призначається атрибут `pivot`. Цей атрибут містить модель, що представляє проміжну таблицю.

За замовчуванням на моделі `pivot` присутні лише ключі моделей. Якщо ваша проміжна таблиця містить додаткові атрибути, їх треба вказати під час визначення зв'язку:

```php
return $this->belongsToMany(Role::class)->withPivot('active', 'created_by');
```

Якщо ви хочете, щоб ваша проміжна таблиця мала часові мітки `created_at` та `updated_at`, які Eloquent підтримує автоматично, викличте метод `withTimestamps` під час визначення зв'язку:

```php
return $this->belongsToMany(Role::class)->withTimestamps();
```

> [!WARNING]
> Проміжні таблиці, що використовують автоматично підтримувані Eloquent часові мітки, мусять мати обидва стовпці - `created_at` та `updated_at`.

<a name="customizing-the-pivot-attribute-name"></a>
#### Налаштування імені атрибута `pivot`

Як зазначалося раніше, до атрибутів проміжної таблиці можна звертатися на моделях через атрибут `pivot`. Проте ви вільні змінити ім'я цього атрибута, щоб воно краще відображало його призначення у вашому застосунку.

Наприклад, якщо у вашому застосунку користувачі можуть підписуватися на подкасти, то між користувачами й подкастами, найімовірніше, є зв'язок «багато до багатьох». У такому разі ви можете захотіти перейменувати атрибут проміжної таблиці з `pivot` на `subscription`. Це робиться методом `as` під час визначення зв'язку:

```php
return $this->belongsToMany(Podcast::class)
    ->as('subscription')
    ->withTimestamps();
```

Коли власний атрибут проміжної таблиці задано, ви можете звертатися до її даних за новим іменем:

```php
$users = User::with('podcasts')->get();

foreach ($users->flatMap->podcasts as $podcast) {
    echo $podcast->subscription->created_at;
}
```

<a name="filtering-queries-via-intermediate-table-columns"></a>
### Фільтрація запитів за стовпцями проміжної таблиці

Ви також можете фільтрувати результати запитів зв'язку `belongsToMany` методами `wherePivot`, `wherePivotIn`, `wherePivotNotIn`, `wherePivotBetween`, `wherePivotNotBetween`, `wherePivotNull` та `wherePivotNotNull` під час визначення зв'язку:

```php
return $this->belongsToMany(Role::class)
    ->wherePivot('approved', 1);

return $this->belongsToMany(Role::class)
    ->wherePivotIn('priority', [1, 2]);

return $this->belongsToMany(Role::class)
    ->wherePivotNotIn('priority', [1, 2]);

return $this->belongsToMany(Podcast::class)
    ->as('subscriptions')
    ->wherePivotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']);

return $this->belongsToMany(Podcast::class)
    ->as('subscriptions')
    ->wherePivotNotBetween('created_at', ['2020-01-01 00:00:00', '2020-12-31 00:00:00']);

return $this->belongsToMany(Podcast::class)
    ->as('subscriptions')
    ->wherePivotNull('expired_at');

return $this->belongsToMany(Podcast::class)
    ->as('subscriptions')
    ->wherePivotNotNull('expired_at');
```

Метод `wherePivot` додає до запиту обмеження where, але не додає вказаного значення під час створення нових моделей через визначений зв'язок. Якщо вам потрібно і робити запити, і створювати зв'язки з певним значенням pivot, скористайтеся методом `withPivotValue`:

```php
return $this->belongsToMany(Role::class)
    ->withPivotValue('approved', 1);
```

<a name="ordering-queries-via-intermediate-table-columns"></a>
### Сортування запитів за стовпцями проміжної таблиці

Ви можете сортувати результати запитів зв'язку `belongsToMany` методами `orderByPivot` та `orderByPivotDesc`. У наступному прикладі ми отримаємо всі найновіші бейджі користувача:

```php
return $this->belongsToMany(Badge::class)
    ->where('rank', 'gold')
    ->orderByPivotDesc('created_at');
```

<a name="defining-custom-intermediate-table-models"></a>
### Визначення власних моделей проміжної таблиці

Якщо ви хочете визначити власну модель, що представлятиме проміжну таблицю вашого зв'язку «багато до багатьох», викличте метод `using` під час визначення зв'язку. Власні pivot-моделі дають змогу описати додаткову поведінку на pivot-моделі - наприклад, методи та приведення типів.

Власні pivot-моделі для зв'язків «багато до багатьох» мають успадковувати клас `Illuminate\Database\Eloquent\Relations\Pivot`, а власні pivot-моделі для поліморфних зв'язків «багато до багатьох» - клас `Illuminate\Database\Eloquent\Relations\MorphPivot`. Наприклад, ми можемо визначити модель `Role`, яка використовує власну pivot-модель `RoleUser`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Role extends Model
{
    /**
     * The users that belong to the role.
     */
    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class)->using(RoleUser::class);
    }
}
```

Визначаючи модель `RoleUser`, ви маєте успадкувати клас `Illuminate\Database\Eloquent\Relations\Pivot`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Relations\Pivot;

class RoleUser extends Pivot
{
    // ...
}
```

> [!WARNING]
> Pivot-моделі не можуть використовувати трейт `SoftDeletes`. Якщо вам потрібне м'яке видалення (soft delete) pivot-записів, подумайте про перетворення pivot-моделі на повноцінну модель Eloquent.

<a name="custom-pivot-models-and-incrementing-ids"></a>
#### Власні pivot-моделі та інкрементні ID

Якщо ви визначили зв'язок «багато до багатьох», що використовує власну pivot-модель, і ця pivot-модель має автоінкрементний первинний ключ, переконайтеся, що ваш клас pivot-моделі використовує атрибут `Table` зі значенням `incrementing`, встановленим у `true`:

```php
use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Relations\Pivot;

#[Table(incrementing: true)]
class RoleUser extends Pivot
{
    // ...
}
```

<a name="automatically-hydrating-pivot-relationships"></a>
#### Автоматична гідратація зв'язків pivot-моделі

Коли власна pivot-модель визначає зв'язки `belongsTo` для моделі, що оголошує зв'язок, і пов'язаної моделі, ви можете викликати `chaperone`, щоб автоматично гідратувати ці зв'язки на кожній pivot-моделі. Це уникає додаткових запитів при доступі до моделей через pivot-модель:

```php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\Pivot;

class RoleUser extends Pivot
{
    public function role(): BelongsTo
    {
        return $this->belongsTo(Role::class);
    }

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}

class Role extends Model
{
    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class)
            ->using(RoleUser::class)
            ->chaperone();
    }
}
```

Eloquent спробує визначити назви зв'язків pivot-моделі. Якщо ваша pivot-модель використовує нестандартні назви, передайте до `chaperone` назви зв'язку, що оголошує, і пов'язаного зв'язку:

```php
return $this->belongsToMany(User::class)
    ->using(RoleUser::class)
    ->chaperone(declaring: 'role', related: 'user');
```

<a name="polymorphic-relationships"></a>
## Поліморфні зв'язки

Поліморфний зв'язок дозволяє дочірній моделі належати більш ніж одному типу моделей через одну асоціацію. Наприклад, уявіть, що ви будуєте застосунок, у якому користувачі можуть ділитися дописами блогу та відео. У такому застосунку модель `Comment` могла б належати і моделі `Post`, і моделі `Video`.

<a name="one-to-one-polymorphic-relations"></a>
### Один до одного (поліморфний)

<a name="one-to-one-polymorphic-table-structure"></a>
#### Структура таблиць

Поліморфний зв'язок «один до одного» схожий на звичайний зв'язок «один до одного»; проте дочірня модель може належати більш ніж одному типу моделей через одну асоціацію. Наприклад, `Post` блогу та `User` можуть мати спільний поліморфний зв'язок із моделлю `Image`. Поліморфний зв'язок «один до одного» дозволяє мати одну таблицю унікальних зображень, які можуть бути пов'язані з дописами й користувачами. Спершу розгляньмо структуру таблиць:

```text
posts
    id - integer
    name - string

users
    id - integer
    name - string

images
    id - integer
    url - string
    imageable_type - string
    imageable_id - integer
```

Зверніть увагу на стовпці `imageable_id` та `imageable_type` у таблиці `images`. Стовпець `imageable_id` міститиме значення ID допису чи користувача, а стовпець `imageable_type` - ім'я класу батьківської моделі. Стовпець `imageable_type` Eloquent використовує, щоб визначити, який «тип» батьківської моделі повернути при зверненні до зв'язку `imageable`. У цьому випадку стовпець міститиме або `App\Models\Post`, або `App\Models\User`.

<a name="one-to-one-polymorphic-model-structure"></a>
#### Структура моделей

Далі розгляньмо визначення моделей, потрібні для побудови цього зв'язку:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;

class Image extends Model
{
    /**
     * Get the parent imageable model (user or post).
     */
    public function imageable(): MorphTo
    {
        return $this->morphTo();
    }
}

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;

class Post extends Model
{
    /**
     * Get the post's image.
     */
    public function image(): MorphOne
    {
        return $this->morphOne(Image::class, 'imageable');
    }
}

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;

class User extends Model
{
    /**
     * Get the user's image.
     */
    public function image(): MorphOne
    {
        return $this->morphOne(Image::class, 'imageable');
    }
}
```

<a name="one-to-one-polymorphic-retrieving-the-relationship"></a>
#### Отримання зв'язку

Коли таблицю бази даних і моделі визначено, ви можете звертатися до зв'язків через свої моделі. Наприклад, щоб отримати зображення допису, звернімося до динамічної властивості зв'язку `image`:

```php
use App\Models\Post;

$post = Post::find(1);

$image = $post->image;
```

Батьківську модель поліморфної моделі можна отримати, звернувшись до імені методу, який викликає `morphTo`. У цьому випадку це метод `imageable` на моделі `Image`. Тож ми звернемося до нього як до динамічної властивості зв'язку:

```php
use App\Models\Image;

$image = Image::find(1);

$imageable = $image->imageable;
```

Зв'язок `imageable` на моделі `Image` поверне екземпляр або `Post`, або `User` - залежно від того, який тип моделі володіє зображенням.

<a name="morph-one-to-one-key-conventions"></a>
#### Угоди щодо ключів

За потреби ви можете вказати імена стовпців «id» та «type», які використовує ваша поліморфна дочірня модель. Якщо ви це робите, завжди передавайте ім'я зв'язку першим аргументом до методу `morphTo`. Зазвичай це значення має збігатися з іменем методу, тож можна скористатися константою PHP `__FUNCTION__`:

```php
/**
 * Get the model that the image belongs to.
 */
public function imageable(): MorphTo
{
    return $this->morphTo(__FUNCTION__, 'imageable_type', 'imageable_id');
}
```

<a name="one-to-many-polymorphic-relations"></a>
### Один до багатьох (поліморфний)

<a name="one-to-many-polymorphic-table-structure"></a>
#### Структура таблиць

Поліморфний зв'язок «один до багатьох» схожий на звичайний зв'язок «один до багатьох»; проте дочірня модель може належати більш ніж одному типу моделей через одну асоціацію. Наприклад, уявіть, що користувачі вашого застосунку можуть «коментувати» дописи та відео. За допомогою поліморфних зв'язків ви можете тримати коментарі до дописів і до відео в одній таблиці `comments`. Спершу розгляньмо структуру таблиць, потрібну для побудови цього зв'язку:

```text
posts
    id - integer
    title - string
    body - text

videos
    id - integer
    title - string
    url - string

comments
    id - integer
    body - text
    commentable_type - string
    commentable_id - integer
```

<a name="one-to-many-polymorphic-model-structure"></a>
#### Структура моделей

Далі розгляньмо визначення моделей, потрібні для побудови цього зв'язку:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;

class Comment extends Model
{
    /**
     * Get the parent commentable model (post or video).
     */
    public function commentable(): MorphTo
    {
        return $this->morphTo();
    }
}

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;

class Post extends Model
{
    /**
     * Get all of the post's comments.
     */
    public function comments(): MorphMany
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;

class Video extends Model
{
    /**
     * Get all of the video's comments.
     */
    public function comments(): MorphMany
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}
```

<a name="one-to-many-polymorphic-retrieving-the-relationship"></a>
#### Отримання зв'язку

Коли таблицю бази даних і моделі визначено, ви можете звертатися до зв'язків через динамічні властивості зв'язків ваших моделей. Наприклад, щоб дістатися всіх коментарів допису, скористаймося динамічною властивістю `comments`:

```php
use App\Models\Post;

$post = Post::find(1);

foreach ($post->comments as $comment) {
    // ...
}
```

Ви також можете отримати батьківську модель поліморфної дочірньої моделі, звернувшись до імені методу, який викликає `morphTo`. У цьому випадку це метод `commentable` на моделі `Comment`. Тож ми звернемося до нього як до динамічної властивості зв'язку, щоб дістатися батьківської моделі коментаря:

```php
use App\Models\Comment;

$comment = Comment::find(1);

$commentable = $comment->commentable;
```

Зв'язок `commentable` на моделі `Comment` поверне екземпляр або `Post`, або `Video` - залежно від того, який тип моделі є батьківським для коментаря.

<a name="polymorphic-automatically-hydrating-parent-models-on-children"></a>
#### Автоматичне заповнення батьківських моделей у дочірніх

Навіть із жадібним завантаженням Eloquent можуть виникати проблеми запитів «N + 1», якщо ви звертаєтеся до батьківської моделі з дочірньої, перебираючи дочірні моделі:

```php
$posts = Post::with('comments')->get();

foreach ($posts as $post) {
    foreach ($post->comments as $comment) {
        echo $comment->commentable->title;
    }
}
```

У прикладі вище виникла проблема запитів «N + 1», бо, хоч коментарі й були жадібно завантажені для кожної моделі `Post`, Eloquent не заповнює автоматично батьківський `Post` у кожній дочірній моделі `Comment`.

Якщо ви хочете, щоб Eloquent автоматично заповнював батьківські моделі в дочірніх, викличте метод `chaperone` під час визначення зв'язку `morphMany`:

```php
class Post extends Model
{
    /**
     * Get all of the post's comments.
     */
    public function comments(): MorphMany
    {
        return $this->morphMany(Comment::class, 'commentable')->chaperone();
    }
}
```

Або ж, якщо ви хочете вмикати автоматичне заповнення батьківських моделей під час виконання, викличте метод `chaperone` під час жадібного завантаження зв'язку:

```php
use App\Models\Post;

$posts = Post::with([
    'comments' => fn ($comments) => $comments->chaperone(),
])->get();
```

<a name="one-of-many-polymorphic-relations"></a>
### Один з багатьох (поліморфний)

Іноді модель має багато пов'язаних моделей, але вам потрібно легко отримати «найновішу» чи «найстарішу» з них. Наприклад, модель `User` може бути пов'язана з багатьма моделями `Image`, але ви хочете мати зручний спосіб працювати з найновішим зображенням, яке завантажив користувач. Цього можна досягти зв'язком типу `morphOne` у поєднанні з методами `ofMany`:

```php
/**
 * Get the user's most recent image.
 */
public function latestImage(): MorphOne
{
    return $this->morphOne(Image::class, 'imageable')->latestOfMany();
}
```

Так само ви можете визначити метод для отримання «найстарішого», тобто першого, пов'язаного запису:

```php
/**
 * Get the user's oldest image.
 */
public function oldestImage(): MorphOne
{
    return $this->morphOne(Image::class, 'imageable')->oldestOfMany();
}
```

За замовчуванням методи `latestOfMany` та `oldestOfMany` отримують найновішу чи найстарішу пов'язану модель за первинним ключем моделі, який має бути придатним для сортування. Проте іноді потрібно отримати одну модель із більшого зв'язку за іншим критерієм сортування.

Наприклад, за допомогою методу `ofMany` можна отримати зображення користувача з найбільшою кількістю «вподобань». Метод `ofMany` приймає першим аргументом стовпець для сортування, а другим - агрегатну функцію (`min` чи `max`), яку слід застосувати під час запиту пов'язаної моделі:

```php
/**
 * Get the user's most popular image.
 */
public function bestImage(): MorphOne
{
    return $this->morphOne(Image::class, 'imageable')->ofMany('likes', 'max');
}
```

> [!NOTE]
> Можна будувати й складніші зв'язки «один з багатьох». Докладніше читайте в [документації про has one of many](#advanced-has-one-of-many-relationships).

<a name="many-to-many-polymorphic-relations"></a>
### Багато до багатьох (поліморфний)

<a name="many-to-many-polymorphic-table-structure"></a>
#### Структура таблиць

Поліморфні зв'язки «багато до багатьох» дещо складніші за зв'язки «morph one» та «morph many». Наприклад, моделі `Post` та `Video` можуть мати спільний поліморфний зв'язок із моделлю `Tag`. Поліморфний зв'язок «багато до багатьох» у цій ситуації дозволив би вашому застосунку мати одну таблицю унікальних тегів, які можуть бути пов'язані з дописами чи відео. Спершу розгляньмо структуру таблиць, потрібну для побудови цього зв'язку:

```text
posts
    id - integer
    name - string

videos
    id - integer
    name - string

tags
    id - integer
    name - string

taggables
    tag_id - integer
    taggable_type - string
    taggable_id - integer
```

> [!NOTE]
> Перш ніж заглиблюватися в поліморфні зв'язки «багато до багатьох», вам може бути корисно прочитати документацію про звичайні [зв'язки «багато до багатьох»](#many-to-many).

<a name="many-to-many-polymorphic-model-structure"></a>
#### Структура моделей

Далі ми готові визначити зв'язки на моделях. Моделі `Post` і `Video` матимуть метод `tags`, який викликає метод `morphToMany`, наданий базовим класом моделі Eloquent.

Метод `morphToMany` приймає ім'я пов'язаної моделі, а також «ім'я зв'язку». Зважаючи на ім'я, яке ми дали проміжній таблиці, та ключі, які вона містить, ми називатимемо цей зв'язок «taggable»:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;

class Post extends Model
{
    /**
     * Get all of the tags for the post.
     */
    public function tags(): MorphToMany
    {
        return $this->morphToMany(Tag::class, 'taggable');
    }
}
```

<a name="many-to-many-polymorphic-defining-the-inverse-of-the-relationship"></a>
#### Визначення зворотного зв'язку

Далі на моделі `Tag` слід визначити метод для кожної з її можливих батьківських моделей. Тож у цьому прикладі ми визначимо методи `posts` і `videos`. Обидва мають повертати результат методу `morphedByMany`.

Метод `morphedByMany` приймає ім'я пов'язаної моделі, а також «ім'я зв'язку». Зважаючи на ім'я, яке ми дали проміжній таблиці, та ключі, які вона містить, ми називатимемо цей зв'язок «taggable»:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;

class Tag extends Model
{
    /**
     * Get all of the posts that are assigned this tag.
     */
    public function posts(): MorphToMany
    {
        return $this->morphedByMany(Post::class, 'taggable');
    }

    /**
     * Get all of the videos that are assigned this tag.
     */
    public function videos(): MorphToMany
    {
        return $this->morphedByMany(Video::class, 'taggable');
    }
}
```

<a name="many-to-many-polymorphic-retrieving-the-relationship"></a>
#### Отримання зв'язку

Коли таблицю бази даних і моделі визначено, ви можете звертатися до зв'язків через свої моделі. Наприклад, щоб дістатися всіх тегів допису, скористайтеся динамічною властивістю зв'язку `tags`:

```php
use App\Models\Post;

$post = Post::find(1);

foreach ($post->tags as $tag) {
    // ...
}
```

Батьківську модель поліморфного зв'язку можна отримати з поліморфної дочірньої моделі, звернувшись до імені методу, який викликає `morphedByMany`. У цьому випадку це методи `posts` або `videos` на моделі `Tag`:

```php
use App\Models\Tag;

$tag = Tag::find(1);

foreach ($tag->posts as $post) {
    // ...
}

foreach ($tag->videos as $video) {
    // ...
}
```

<a name="custom-polymorphic-types"></a>
### Власні поліморфні типи

За замовчуванням Laravel зберігає «тип» пов'язаної моделі як повне ім'я класу. Наприклад, у наведеному вище прикладі зв'язку «один до багатьох», де модель `Comment` може належати моделі `Post` чи `Video`, значенням `commentable_type` за замовчуванням буде відповідно `App\Models\Post` або `App\Models\Video`. Проте ви можете захотіти відв'язати ці значення від внутрішньої структури вашого застосунку.

Наприклад, замість імен моделей ми можемо використовувати як «тип» прості рядки на кшталт `post` і `video`. Завдяки цьому значення поліморфного стовпця «type» у базі даних залишатимуться коректними, навіть якщо моделі перейменують:

```php
use Illuminate\Database\Eloquent\Relations\Relation;

Relation::enforceMorphMap([
    'post' => 'App\Models\Post',
    'video' => 'App\Models\Video',
]);
```

Викликати метод `enforceMorphMap` можна в методі `boot` вашого класу `App\Providers\AppServiceProvider` або, за бажанням, створити окремий сервіс-провайдер.

Морф-аліас заданої моделі можна визначити під час виконання методом моделі `getMorphClass`. І навпаки: повне ім'я класу, пов'язане з морф-аліасом, можна визначити методом `Relation::getMorphedModel`:

```php
use Illuminate\Database\Eloquent\Relations\Relation;

$alias = $post->getMorphClass();

$class = Relation::getMorphedModel($alias);
```

> [!WARNING]
> Коли ви додаєте «морф-мапу» до наявного застосунку, кожне значення морфованого стовпця `*_type` у базі даних, яке ще містить повне ім'я класу, доведеться перетворити на його ім'я з «мапи».

<a name="dynamic-relationships"></a>
### Динамічні зв'язки

Метод `resolveRelationUsing` дозволяє визначати зв'язки між моделями Eloquent під час виконання. Хоча для звичайної розробки застосунків це зазвичай не рекомендується, іноді це буває корисно при створенні пакетів для Laravel.

Метод `resolveRelationUsing` приймає бажане ім'я зв'язку першим аргументом. Другим аргументом має бути замикання, що приймає екземпляр моделі й повертає коректне визначення зв'язку Eloquent. Зазвичай динамічні зв'язки налаштовують у методі boot [сервіс-провайдера](/docs/{{version}}/providers):

```php
use App\Models\Order;
use App\Models\Customer;

Order::resolveRelationUsing('customer', function (Order $orderModel) {
    return $orderModel->belongsTo(Customer::class, 'customer_id');
});
```

> [!WARNING]
> Визначаючи динамічні зв'язки, завжди передавайте методам зв'язків Eloquent явні аргументи з іменами ключів.

<a name="querying-relations"></a>
## Запити до зв'язків

Оскільки всі зв'язки Eloquent визначені через методи, ви можете викликати ці методи, щоб отримати екземпляр зв'язку, не виконуючи запиту для завантаження пов'язаних моделей. До того ж усі типи зв'язків Eloquent водночас є [конструкторами запитів](/docs/{{version}}/queries), тож ви можете й далі додавати обмеження до запиту зв'язку, перш ніж остаточно виконати SQL-запит до вашої бази даних.

Наприклад, уявіть застосунок блогу, у якому модель `User` має багато пов'язаних моделей `Post`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class User extends Model
{
    /**
     * Get all of the posts for the user.
     */
    public function posts(): HasMany
    {
        return $this->hasMany(Post::class);
    }
}
```

Ви можете зробити запит до зв'язку `posts` і додати до нього додаткові обмеження ось так:

```php
use App\Models\User;

$user = User::find(1);

$user->posts()->where('active', 1)->get();
```

На зв'язку можна використовувати будь-які методи [конструктора запитів](/docs/{{version}}/queries) Laravel, тож обов'язково перегляньте його документацію, щоб дізнатися про всі доступні вам методи.

<a name="chaining-orwhere-clauses-after-relationships"></a>
#### Ланцюжки умов `orWhere` після зв'язків

Як показано в прикладі вище, ви вільні додавати до зв'язків додаткові обмеження під час запиту. Проте будьте обережні, додаючи до зв'язку умови `orWhere` у ланцюжок: логічно вони згрупуються на тому самому рівні, що й обмеження зв'язку:

```php
$user->posts()
    ->where('active', 1)
    ->orWhere('votes', '>=', 100)
    ->get();
```

Приклад вище згенерує такий SQL. Як бачите, умова `or` вказує запиту повертати _будь-який_ допис із понад 100 голосами. Запит більше не обмежений конкретним користувачем:

```sql
select *
from posts
where user_id = ? and active = 1 or votes >= 100
```

У більшості випадків варто скористатися [логічними групами](/docs/{{version}}/queries#logical-grouping), щоб згрупувати умовні перевірки в дужках:

```php
use Illuminate\Database\Eloquent\Builder;

$user->posts()
    ->where(function (Builder $query) {
        return $query->where('active', 1)
            ->orWhere('votes', '>=', 100);
    })
    ->get();
```

Приклад вище дасть такий SQL. Зверніть увагу, що логічне групування правильно згрупувало обмеження, і запит лишається обмеженим конкретним користувачем:

```sql
select *
from posts
where user_id = ? and (active = 1 or votes >= 100)
```

<a name="relationship-methods-vs-dynamic-properties"></a>
### Методи зв'язків проти динамічних властивостей

Якщо вам не потрібно додавати до запиту зв'язку Eloquent додаткові обмеження, ви можете звертатися до зв'язку так, ніби це властивість. Наприклад, продовжуючи роботу з моделями `User` і `Post`, ми можемо отримати всі дописи користувача ось так:

```php
use App\Models\User;

$user = User::find(1);

foreach ($user->posts as $post) {
    // ...
}
```

Динамічні властивості зв'язків виконують «ліниве завантаження» (lazy loading): вони завантажують дані зв'язку лише тоді, коли ви до них справді звертаєтеся. Через це розробники часто застосовують [жадібне завантаження](#eager-loading) (eager loading), щоб наперед завантажити зв'язки, до яких вони точно звертатимуться після завантаження моделі. Жадібне завантаження суттєво зменшує кількість SQL-запитів, потрібних для завантаження зв'язків моделі.

<a name="querying-relationship-existence"></a>
### Запити за наявністю зв'язку

Отримуючи записи моделей, ви можете захотіти обмежити результати наявністю зв'язку. Наприклад, уявіть, що вам потрібні всі дописи блогу, які мають щонайменше один коментар. Для цього передайте ім'я зв'язку методам `has` та `orHas`:

```php
use App\Models\Post;

// Retrieve all posts that have at least one comment...
$posts = Post::has('comments')->get();
```

Ви також можете вказати оператор і значення кількості, щоб додатково налаштувати запит:

```php
// Retrieve all posts that have three or more comments...
$posts = Post::has('comments', '>=', 3)->get();
```

Вкладені конструкції `has` будуються через «крапкову» нотацію. Наприклад, ви можете отримати всі дописи, які мають щонайменше один коментар, що має щонайменше одне зображення:

```php
// Retrieve posts that have at least one comment with images...
$posts = Post::has('comments.images')->get();
```

Якщо вам потрібно ще більше можливостей, скористайтеся методами `whereHas` та `orWhereHas`, щоб додати до ваших запитів `has` додаткові обмеження - наприклад, перевірити вміст коментаря:

```php
use Illuminate\Database\Eloquent\Builder;

// Retrieve posts with at least one comment containing words like code%...
$posts = Post::whereHas('comments', function (Builder $query) {
    $query->where('content', 'like', 'code%');
})->get();

// Retrieve posts with at least ten comments containing words like code%...
$posts = Post::whereHas('comments', function (Builder $query) {
    $query->where('content', 'like', 'code%');
}, '>=', 10)->get();
```

> [!WARNING]
> Наразі Eloquent не підтримує запити за наявністю зв'язку між різними базами даних. Зв'язки мають існувати в межах однієї бази даних.

<a name="many-to-many-relationship-existence-queries"></a>
#### Запити за наявністю зв'язку «багато до багатьох»

Метод `whereAttachedTo` дозволяє шукати моделі, приєднані зв'язком «багато до багатьох» до заданої моделі чи колекції моделей:

```php
$users = User::whereAttachedTo($role)->get();
```

Ви також можете передати методу `whereAttachedTo` екземпляр [колекції](/docs/{{version}}/eloquent-collections). У такому разі Laravel поверне моделі, приєднані до будь-якої з моделей у колекції:

```php
$tags = Tag::whereLike('name', '%laravel%')->get();

$posts = Post::whereAttachedTo($tags)->get();
```

<a name="inline-relationship-existence-queries"></a>
#### Вбудовані запити за наявністю зв'язку

Якщо ви хочете зробити запит за наявністю зв'язку з однією простою умовою where, приєднаною до запиту зв'язку, зручнішими можуть бути методи `whereRelation`, `orWhereRelation`, `whereMorphRelation` та `orWhereMorphRelation`. Наприклад, ми можемо знайти всі дописи, що мають незатверджені коментарі:

```php
use App\Models\Post;

$posts = Post::whereRelation('comments', 'is_approved', false)->get();
```

Звісно, як і у викликах методу `where` конструктора запитів, ви можете вказати оператор:

```php
$posts = Post::whereRelation(
    'comments', 'created_at', '>=', now()->minus(hours: 1)
)->get();
```

<a name="querying-relationship-absence"></a>
### Запити за відсутністю зв'язку

Отримуючи записи моделей, ви можете захотіти обмежити результати відсутністю зв'язку. Наприклад, уявіть, що вам потрібні всі дописи блогу, які **не** мають жодного коментаря. Для цього передайте ім'я зв'язку методам `doesntHave` та `orDoesntHave`:

```php
use App\Models\Post;

$posts = Post::doesntHave('comments')->get();
```

Якщо вам потрібно ще більше можливостей, скористайтеся методами `whereDoesntHave` та `orWhereDoesntHave`, щоб додати до ваших запитів `doesntHave` додаткові обмеження - наприклад, перевірити вміст коментаря:

```php
use Illuminate\Database\Eloquent\Builder;

$posts = Post::whereDoesntHave('comments', function (Builder $query) {
    $query->where('content', 'like', 'code%');
})->get();
```

Ви можете скористатися «крапковою» нотацією, щоб виконати запит до вкладеного зв'язку. Наприклад, наступний запит поверне всі дописи без коментарів, а також дописи з коментарями, жоден із яких не належить заблокованим користувачам:

```php
use Illuminate\Database\Eloquent\Builder;

$posts = Post::whereDoesntHave('comments.author', function (Builder $query) {
    $query->where('banned', 1);
})->get();
```

<a name="querying-morph-to-relationships"></a>
### Запити до зв'язків Morph To

Щоб зробити запит за наявністю зв'язків «morph to», скористайтеся методами `whereHasMorph` та `whereDoesntHaveMorph`. Ці методи приймають ім'я зв'язку першим аргументом. Далі вони приймають імена пов'язаних моделей, які ви хочете включити до запиту. Нарешті, ви можете передати замикання, що налаштує запит зв'язку:

```php
use App\Models\Comment;
use App\Models\Post;
use App\Models\Video;
use Illuminate\Database\Eloquent\Builder;

// Retrieve comments associated to posts or videos with a title like code%...
$comments = Comment::whereHasMorph(
    'commentable',
    [Post::class, Video::class],
    function (Builder $query) {
        $query->where('title', 'like', 'code%');
    }
)->get();

// Retrieve comments associated to posts with a title not like code%...
$comments = Comment::whereDoesntHaveMorph(
    'commentable',
    Post::class,
    function (Builder $query) {
        $query->where('title', 'like', 'code%');
    }
)->get();
```

Іноді вам може знадобитися додати обмеження запиту залежно від «типу» пов'язаної поліморфної моделі. Замикання, передане методу `whereHasMorph`, може приймати другим аргументом значення `$type`. Цей аргумент дозволяє перевірити «тип» запиту, який будується:

```php
use Illuminate\Database\Eloquent\Builder;

$comments = Comment::whereHasMorph(
    'commentable',
    [Post::class, Video::class],
    function (Builder $query, string $type) {
        $column = $type === Post::class ? 'content' : 'title';

        $query->where($column, 'like', 'code%');
    }
)->get();
```

Іноді потрібно зробити запит до дочірніх записів батьківської моделі зв'язку «morph to». Цього можна досягти методами `whereMorphedTo` та `whereNotMorphedTo`, які автоматично визначать правильне зіставлення морф-типу для заданої моделі. Ці методи приймають ім'я зв'язку `morphTo` першим аргументом і пов'язану батьківську модель другим:

```php
$comments = Comment::whereMorphedTo('commentable', $post)
    ->orWhereMorphedTo('commentable', $video)
    ->get();
```

<a name="querying-all-morph-to-related-models"></a>
#### Запит до всіх пов'язаних моделей

Замість масиву можливих поліморфних моделей ви можете передати `*` як символ підстановки. Це вкаже Laravel отримати з бази даних усі можливі поліморфні типи. Для цієї операції Laravel виконає додатковий запит:

```php
use Illuminate\Database\Eloquent\Builder;

$comments = Comment::whereHasMorph('commentable', '*', function (Builder $query) {
    $query->where('title', 'like', 'foo%');
})->get();
```

<a name="aggregating-related-models"></a>
## Агрегування пов'язаних моделей

<a name="counting-related-models"></a>
### Підрахунок пов'язаних моделей

Іноді потрібно порахувати кількість пов'язаних моделей для заданого зв'язку, не завантажуючи самі моделі. Для цього скористайтеся методом `withCount`. Метод `withCount` додасть до отриманих моделей атрибут `{relation}_count`:

```php
use App\Models\Post;

$posts = Post::withCount('comments')->get();

foreach ($posts as $post) {
    echo $post->comments_count;
}
```

Передавши методу `withCount` масив, ви можете додати «кількості» для кількох зв'язків, а також додаткові обмеження до запитів:

```php
use Illuminate\Database\Eloquent\Builder;

$posts = Post::withCount(['votes', 'comments' => function (Builder $query) {
    $query->where('content', 'like', 'code%');
}])->get();

echo $posts[0]->votes_count;
echo $posts[0]->comments_count;
```

Ви також можете дати результату підрахунку зв'язку аліас, що дозволяє мати кілька підрахунків для того самого зв'язку:

```php
use Illuminate\Database\Eloquent\Builder;

$posts = Post::withCount([
    'comments',
    'comments as pending_comments_count' => function (Builder $query) {
        $query->where('approved', false);
    },
])->get();

echo $posts[0]->comments_count;
echo $posts[0]->pending_comments_count;
```

<a name="deferred-count-loading"></a>
#### Відкладене завантаження кількості

За допомогою методу `loadCount` ви можете завантажити кількість для зв'язку вже після того, як батьківську модель отримано:

```php
$book = Book::first();

$book->loadCount('genres');
```

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

```php
$book->loadCount(['reviews' => function (Builder $query) {
    $query->where('rating', 5);
}])
```

<a name="relationship-counting-and-custom-select-statements"></a>
#### Підрахунок зв'язків і власні вирази select

Якщо ви поєднуєте `withCount` із виразом `select`, обов'язково викликайте `withCount` після методу `select`:

```php
$posts = Post::select(['title', 'body'])
    ->withCount('comments')
    ->get();
```

<a name="other-aggregate-functions"></a>
### Інші агрегатні функції

Окрім методу `withCount`, Eloquent надає методи `withMin`, `withMax`, `withAvg`, `withSum` та `withExists`. Вони додадуть до отриманих моделей атрибут `{relation}_{function}_{column}`:

```php
use App\Models\Post;

$posts = Post::withSum('comments', 'votes')->get();

foreach ($posts as $post) {
    echo $post->comments_sum_votes;
}
```

Якщо ви хочете звертатися до результату агрегатної функції за іншим іменем, вкажіть власний аліас:

```php
$posts = Post::withSum('comments as total_comments', 'votes')->get();

foreach ($posts as $post) {
    echo $post->total_comments;
}
```

Як і у випадку з методом `loadCount`, для цих методів також доступні відкладені версії. Ці додаткові агрегатні операції можна виконувати на вже отриманих моделях Eloquent:

```php
$post = Post::first();

$post->loadSum('comments', 'votes');
```

Якщо ви поєднуєте ці агрегатні методи з виразом `select`, обов'язково викликайте їх після методу `select`:

```php
$posts = Post::select(['title', 'body'])
    ->withExists('comments')
    ->get();
```

<a name="counting-related-models-on-morph-to-relationships"></a>
### Підрахунок пов'язаних моделей у зв'язках Morph To

Якщо ви хочете жадібно завантажити зв'язок «morph to», а також кількості пов'язаних моделей для різних сутностей, які цей зв'язок може повернути, скористайтеся методом `with` у поєднанні з методом `morphWithCount` зв'язку `morphTo`.

У цьому прикладі припустімо, що моделі `Photo` та `Post` можуть створювати моделі `ActivityFeed`. Припустімо також, що модель `ActivityFeed` визначає зв'язок «morph to» на ім'я `parentable`, який дозволяє отримати батьківську модель `Photo` чи `Post` для заданого екземпляра `ActivityFeed`. Крім того, припустімо, що моделі `Photo` «мають багато» моделей `Tag`, а моделі `Post` - багато моделей `Comment`.

Тепер уявімо, що ми хочемо отримати екземпляри `ActivityFeed` і жадібно завантажити батьківські моделі `parentable` для кожного з них. Крім того, ми хочемо отримати кількість тегів, пов'язаних із кожним батьківським фото, і кількість коментарів, пов'язаних із кожним батьківським дописом:

```php
use Illuminate\Database\Eloquent\Relations\MorphTo;

$activities = ActivityFeed::with([
    'parentable' => function (MorphTo $morphTo) {
        $morphTo->morphWithCount([
            Photo::class => ['tags'],
            Post::class => ['comments'],
        ]);
    }])->get();
```

<a name="morph-to-deferred-count-loading"></a>
#### Відкладене завантаження кількості

Припустімо, ми вже отримали набір моделей `ActivityFeed` і тепер хочемо завантажити вкладені кількості зв'язків для різних моделей `parentable`, пов'язаних зі стрічкою активності. Зробити це можна методом `loadMorphCount`:

```php
$activities = ActivityFeed::with('parentable')->get();

$activities->loadMorphCount('parentable', [
    Photo::class => ['tags'],
    Post::class => ['comments'],
]);
```

<a name="eager-loading"></a>
## Жадібне завантаження

Коли ви звертаєтеся до зв'язків Eloquent як до властивостей, пов'язані моделі завантажуються «ліниво» (lazy loading). Це означає, що дані зв'язку не завантажуються, доки ви вперше не звернетеся до властивості. Проте Eloquent уміє «жадібно завантажувати» (eager loading) зв'язки вже під час запиту до батьківської моделі. Жадібне завантаження знімає проблему запитів «N + 1». Щоб проілюструвати цю проблему, розгляньмо модель `Book`, яка «належить» моделі `Author`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Book extends Model
{
    /**
     * Get the author that wrote the book.
     */
    public function author(): BelongsTo
    {
        return $this->belongsTo(Author::class);
    }
}
```

А тепер отримаймо всі книжки та їхніх авторів:

```php
use App\Models\Book;

$books = Book::all();

foreach ($books as $book) {
    echo $book->author->name;
}
```

Цей цикл виконає один запит, щоб отримати всі книжки з таблиці бази даних, а потім ще по одному запиту на кожну книжку, щоб отримати її автора. Тож якщо в нас 25 книжок, наведений код виконає 26 запитів: один по самі книжки і ще 25, щоб отримати автора кожної книжки.

На щастя, ми можемо скористатися жадібним завантаженням і звести цю операцію лише до двох запитів. Будуючи запит, ви можете вказати, які зв'язки слід завантажити жадібно, за допомогою методу `with`:

```php
$books = Book::with('author')->get();

foreach ($books as $book) {
    echo $book->author->name;
}
```

Для цієї операції буде виконано лише два запити - один, щоб отримати всі книжки, і один, щоб отримати всіх авторів усіх книжок:

```sql
select * from books

select * from authors where id in (1, 2, 3, 4, 5, ...)
```

<a name="eager-loading-multiple-relationships"></a>
#### Жадібне завантаження кількох зв'язків

Іноді потрібно жадібно завантажити кілька різних зв'язків. Для цього просто передайте методу `with` масив зв'язків:

```php
$books = Book::with(['author', 'publisher'])->get();
```

<a name="nested-eager-loading"></a>
#### Вкладене жадібне завантаження

Щоб жадібно завантажити зв'язки зв'язку, скористайтеся «крапковим» синтаксисом. Наприклад, завантажмо жадібно всіх авторів книжки та всі особисті контакти автора:

```php
$books = Book::with('author.contacts')->get();
```

Або ж ви можете задати вкладені жадібно завантажувані зв'язки, передавши методу `with` вкладений масив, - це буває зручно при жадібному завантаженні кількох вкладених зв'язків:

```php
$books = Book::with([
    'author' => [
        'contacts',
        'publisher',
    ],
])->get();
```

<a name="nested-eager-loading-morphto-relationships"></a>
#### Вкладене жадібне завантаження зв'язків `morphTo`

Якщо ви хочете жадібно завантажити зв'язок `morphTo`, а також вкладені зв'язки різних сутностей, які цей зв'язок може повернути, скористайтеся методом `with` у поєднанні з методом `morphWith` зв'язку `morphTo`. Щоб проілюструвати цей метод, розгляньмо таку модель:

```php
<?php

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;

class ActivityFeed extends Model
{
    /**
     * Get the parent of the activity feed record.
     */
    public function parentable(): MorphTo
    {
        return $this->morphTo();
    }
}
```

У цьому прикладі припустімо, що моделі `Event`, `Photo` та `Post` можуть створювати моделі `ActivityFeed`. Крім того, припустімо, що моделі `Event` належать моделі `Calendar`, моделі `Photo` пов'язані з моделями `Tag`, а моделі `Post` належать моделі `Author`.

З такими визначеннями моделей і зв'язків ми можемо отримати екземпляри моделі `ActivityFeed` і жадібно завантажити всі моделі `parentable` та їхні вкладені зв'язки:

```php
use Illuminate\Database\Eloquent\Relations\MorphTo;

$activities = ActivityFeed::query()
    ->with(['parentable' => function (MorphTo $morphTo) {
        $morphTo->morphWith([
            Event::class => ['calendar'],
            Photo::class => ['tags'],
            Post::class => ['author'],
        ]);
    }])->get();
```

<a name="eager-loading-specific-columns"></a>
#### Жадібне завантаження певних стовпців

Не завжди вам потрібні всі стовпці зв'язків, які ви отримуєте. Тому Eloquent дозволяє вказати, які саме стовпці зв'язку ви хочете отримати:

```php
$books = Book::with('author:id,name,book_id')->get();
```

> [!WARNING]
> Користуючись цією можливістю, завжди включайте до списку потрібних стовпців стовпець `id` та всі відповідні стовпці зовнішніх ключів.

<a name="eager-loading-by-default"></a>
#### Жадібне завантаження за замовчуванням

Іноді потрібно завжди завантажувати певні зв'язки під час отримання моделі. Щоб цього досягти, визначте на моделі властивість `$with`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Book extends Model
{
    /**
     * The relationships that should always be loaded.
     *
     * @var array
     */
    protected $with = ['author'];

    /**
     * Get the author that wrote the book.
     */
    public function author(): BelongsTo
    {
        return $this->belongsTo(Author::class);
    }

    /**
     * Get the genre of the book.
     */
    public function genre(): BelongsTo
    {
        return $this->belongsTo(Genre::class);
    }
}
```

Якщо ви хочете прибрати елемент із властивості `$with` для одного запиту, скористайтеся методом `without`:

```php
$books = Book::without('author')->get();
```

Якщо ви хочете перевизначити всі елементи властивості `$with` для одного запиту, скористайтеся методом `withOnly`:

```php
$books = Book::withOnly('genre')->get();
```

<a name="constraining-eager-loads"></a>
### Обмеження жадібного завантаження

Іноді потрібно жадібно завантажити зв'язок, але водночас задати додаткові умови для запиту жадібного завантаження. Цього можна досягти, передавши методу `with` масив зв'язків, де ключ масиву - ім'я зв'язку, а значення - замикання, що додає додаткові обмеження до запиту жадібного завантаження:

```php
use App\Models\User;

$users = User::with(['posts' => function ($query) {
    $query->where('title', 'like', '%code%');
}])->get();
```

У цьому прикладі Eloquent жадібно завантажить лише ті дописи, чий стовпець `title` містить слово `code`. Ви можете викликати й інші методи [конструктора запитів](/docs/{{version}}/queries), щоб додатково налаштувати операцію жадібного завантаження:

```php
$users = User::with(['posts' => function ($query) {
    $query->orderBy('created_at', 'desc');
}])->get();
```

<a name="constraining-eager-loading-of-morph-to-relationships"></a>
#### Обмеження жадібного завантаження зв'язків `morphTo`

Якщо ви жадібно завантажуєте зв'язок `morphTo`, Eloquent виконає кілька запитів, щоб отримати кожен тип пов'язаної моделі. Ви можете додати до кожного з цих запитів додаткові обмеження методом `constrain` зв'язку `MorphTo`:

```php
use Illuminate\Database\Eloquent\Relations\MorphTo;

$comments = Comment::with(['commentable' => function (MorphTo $morphTo) {
    $morphTo->constrain([
        Post::class => function ($query) {
            $query->whereNull('hidden_at');
        },
        Video::class => function ($query) {
            $query->where('type', 'educational');
        },
    ]);
}])->get();
```

У цьому прикладі Eloquent жадібно завантажить лише неприховані дописи та відео зі значенням `type`, що дорівнює «educational».

<a name="constraining-eager-loads-with-relationship-existence"></a>
#### Обмеження жадібного завантаження наявністю зв'язку

Іноді вам може знадобитися перевірити наявність зв'язку й водночас завантажити цей зв'язок за тими самими умовами. Наприклад, ви можете захотіти отримати лише ті моделі `User`, що мають дочірні моделі `Post`, які відповідають заданій умові запиту, і водночас жадібно завантажити ці дописи. Цього можна досягти методом `withWhereHas`:

```php
use App\Models\User;

$users = User::withWhereHas('posts', function ($query) {
    $query->where('featured', true);
})->get();
```

<a name="lazy-eager-loading"></a>
### Відкладене жадібне завантаження

Іноді потрібно жадібно завантажити зв'язок уже після того, як батьківську модель отримано. Наприклад, це може бути корисно, якщо вам треба динамічно вирішувати, чи завантажувати пов'язані моделі:

```php
use App\Models\Book;

$books = Book::all();

if ($condition) {
    $books->load('author', 'publisher');
}
```

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

```php
$author->load(['books' => function ($query) {
    $query->orderBy('published_date', 'asc');
}]);
```

Щоб завантажити зв'язок лише тоді, коли його ще не завантажено, скористайтеся методом `loadMissing`:

```php
$book->loadMissing('author');
```

<a name="nested-lazy-eager-loading-morphto"></a>
#### Вкладене відкладене жадібне завантаження та `morphTo`

Якщо ви хочете жадібно завантажити зв'язок `morphTo`, а також вкладені зв'язки різних сутностей, які цей зв'язок може повернути, скористайтеся методом `loadMorph`.

Цей метод приймає ім'я зв'язку `morphTo` першим аргументом і масив пар модель / зв'язок другим. Щоб проілюструвати цей метод, розгляньмо таку модель:

```php
<?php

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;

class ActivityFeed extends Model
{
    /**
     * Get the parent of the activity feed record.
     */
    public function parentable(): MorphTo
    {
        return $this->morphTo();
    }
}
```

У цьому прикладі припустімо, що моделі `Event`, `Photo` та `Post` можуть створювати моделі `ActivityFeed`. Крім того, припустімо, що моделі `Event` належать моделі `Calendar`, моделі `Photo` пов'язані з моделями `Tag`, а моделі `Post` належать моделі `Author`.

З такими визначеннями моделей і зв'язків ми можемо отримати екземпляри моделі `ActivityFeed` і жадібно завантажити всі моделі `parentable` та їхні вкладені зв'язки:

```php
$activities = ActivityFeed::with('parentable')
    ->get()
    ->loadMorph('parentable', [
        Event::class => ['calendar'],
        Photo::class => ['tags'],
        Post::class => ['author'],
    ]);
```

<a name="automatic-eager-loading"></a>
### Автоматичне жадібне завантаження

У багатьох випадках Laravel може автоматично жадібно завантажувати зв'язки, до яких ви звертаєтеся. Щоб увімкнути автоматичне жадібне завантаження, викличте метод `Model::automaticallyEagerLoadRelationships` у методі `boot` вашого `AppServiceProvider`:

```php
use Illuminate\Database\Eloquent\Model;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Model::automaticallyEagerLoadRelationships();
}
```

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

```php
use App\Models\User;

$users = User::all();

foreach ($users as $user) {
    foreach ($user->posts as $post) {
        foreach ($post->comments as $comment) {
            echo $comment->content;
        }
    }
}
```

Зазвичай наведений код виконав би запит для кожного користувача, щоб отримати його дописи, а також запит для кожного допису, щоб отримати його коментарі. Проте коли можливість `automaticallyEagerLoadRelationships` увімкнено, Laravel автоматично [відкладено жадібно завантажить](#lazy-eager-loading) дописи всіх користувачів колекції, щойно ви спробуєте звернутися до дописів будь-кого з отриманих користувачів. Так само, коли ви спробуєте звернутися до коментарів будь-якого отриманого допису, коментарі буде відкладено жадібно завантажено для всіх спочатку отриманих дописів.

Якщо ви не хочете вмикати автоматичне жадібне завантаження глобально, ви все одно можете ввімкнути цю можливість для окремого екземпляра колекції Eloquent, викликавши на колекції метод `withRelationshipAutoloading`:

```php
$users = User::where('vip', true)->get();

return $users->withRelationshipAutoloading();
```

<a name="preventing-lazy-loading"></a>
### Заборона лінивого завантаження

Як уже обговорювалося, жадібне завантаження зв'язків часто дає суттєвий виграш у продуктивності вашого застосунку. Тож, за бажання, ви можете вказати Laravel завжди забороняти ліниве завантаження зв'язків. Для цього викличте метод `preventLazyLoading`, який надає базовий клас моделі Eloquent. Зазвичай цей метод викликають у методі `boot` класу `AppServiceProvider` вашого застосунку.

Метод `preventLazyLoading` приймає необов'язковий булевий аргумент, що вказує, чи слід забороняти ліниве завантаження. Наприклад, ви можете захотіти вимкнути ліниве завантаження лише в непродакшн-середовищах, щоб ваше продакшн-середовище й далі працювало нормально, навіть якщо в продакшн-коді випадково опиниться ліниво завантажений зв'язок:

```php
use Illuminate\Database\Eloquent\Model;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Model::preventLazyLoading(! $this->app->isProduction());
}
```

Після заборони лінивого завантаження Eloquent кидатиме виняток `Illuminate\Database\LazyLoadingViolationException`, коли ваш застосунок спробує ліниво завантажити будь-який зв'язок Eloquent.

Ви можете налаштувати поведінку при порушеннях лінивого завантаження методом `handleLazyLoadingViolationUsing`. Наприклад, за допомогою цього методу можна вказати, щоб порушення лише логувалися, а не переривали виконання застосунку винятками:

```php
Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) {
    $class = $model::class;

    info("Attempted to lazy load [{$relation}] on model [{$class}].");
});
```

<a name="inserting-and-updating-related-models"></a>
## Вставлення й оновлення пов'язаних моделей

<a name="the-save-method"></a>
### Метод `save`

Eloquent надає зручні методи для додавання нових моделей до зв'язків. Наприклад, вам може знадобитися додати новий коментар до допису. Замість того щоб вручну встановлювати атрибут `post_id` на моделі `Comment`, ви можете вставити коментар методом `save` зв'язку:

```php
use App\Models\Comment;
use App\Models\Post;

$comment = new Comment(['message' => 'A new comment.']);

$post = Post::find(1);

$post->comments()->save($comment);
```

Зверніть увагу: ми не зверталися до зв'язку `comments` як до динамічної властивості. Натомість ми викликали метод `comments`, щоб отримати екземпляр зв'язку. Метод `save` автоматично додасть новій моделі `Comment` відповідне значення `post_id`.

Якщо вам потрібно зберегти кілька пов'язаних моделей, скористайтеся методом `saveMany`:

```php
$post = Post::find(1);

$post->comments()->saveMany([
    new Comment(['message' => 'A new comment.']),
    new Comment(['message' => 'Another new comment.']),
]);
```

Методи `save` та `saveMany` збережуть передані екземпляри моделей, але не додадуть щойно збережені моделі до вже завантажених у пам'ять зв'язків батьківської моделі. Якщо ви плануєте звертатися до зв'язку після використання `save` чи `saveMany`, вам може знадобитися метод `refresh`, щоб перезавантажити модель та її зв'язки:

```php
$post->comments()->save($comment);

$post->refresh();

// All comments, including the newly saved comment...
$post->comments;
```

<a name="the-push-method"></a>
#### Рекурсивне збереження моделей і зв'язків

Якщо ви хочете зберегти (`save`) свою модель разом з усіма пов'язаними з нею зв'язками, скористайтеся методом `push`. У цьому прикладі буде збережено модель `Post`, а також її коментарі й авторів коментарів:

```php
$post = Post::find(1);

$post->comments[0]->message = 'Message';
$post->comments[0]->author->name = 'Author Name';

$post->push();
```

Метод `pushQuietly` дозволяє зберегти модель та пов'язані з нею зв'язки, не викликаючи жодних подій:

```php
$post->pushQuietly();
```

<a name="the-create-method"></a>
### Метод `create`

Окрім методів `save` та `saveMany`, ви можете скористатися методом `create`, який приймає масив атрибутів, створює модель і вставляє її в базу даних. Різниця між `save` та `create` полягає в тому, що `save` приймає повний екземпляр моделі Eloquent, а `create` - звичайний PHP-`array`. Щойно створену модель буде повернено методом `create`:

```php
use App\Models\Post;

$post = Post::find(1);

$comment = $post->comments()->create([
    'message' => 'A new comment.',
]);
```

Щоб створити кілька пов'язаних моделей, скористайтеся методом `createMany`:

```php
$post = Post::find(1);

$post->comments()->createMany([
    ['message' => 'A new comment.'],
    ['message' => 'Another new comment.'],
]);
```

Методи `createQuietly` та `createManyQuietly` дозволяють створити модель (моделі), не відправляючи жодних подій:

```php
$user = User::find(1);

$user->posts()->createQuietly([
    'title' => 'Post title.',
]);

$user->posts()->createManyQuietly([
    ['title' => 'First post.'],
    ['title' => 'Second post.'],
]);
```

Ви також можете скористатися методами `findOrNew`, `firstOrNew`, `firstOrCreate` та `updateOrCreate`, щоб [створювати й оновлювати моделі у зв'язках](/docs/{{version}}/eloquent#upserts).

> [!NOTE]
> Перш ніж використовувати метод `create`, обов'язково перегляньте документацію про [масове призначення](/docs/{{version}}/eloquent#mass-assignment).

<a name="updating-belongs-to-relationships"></a>
### Зв'язки Belongs To

Якщо ви хочете призначити дочірню модель новій батьківській, скористайтеся методом `associate`. У цьому прикладі модель `User` визначає зв'язок `belongsTo` із моделлю `Account`. Метод `associate` встановить зовнішній ключ на дочірній моделі:

```php
use App\Models\Account;

$account = Account::find(10);

$user->account()->associate($account);

$user->save();
```

Щоб прибрати батьківську модель із дочірньої, скористайтеся методом `dissociate`. Цей метод встановить зовнішній ключ зв'язку в `null`:

```php
$user->account()->dissociate();

$user->save();
```

<a name="updating-many-to-many-relationships"></a>
### Зв'язки «багато до багатьох»

<a name="attaching-detaching"></a>
#### Приєднання / від'єднання

Eloquent також надає методи, які роблять роботу зі зв'язками «багато до багатьох» зручнішою. Наприклад, уявімо, що користувач може мати багато ролей, а роль - багато користувачів. Метод `attach` дозволяє приєднати роль до користувача, вставивши запис у проміжну таблицю зв'язку:

```php
use App\Models\User;

$user = User::find(1);

$user->roles()->attach($roleId);
```

Приєднуючи зв'язок до моделі, ви також можете передати масив додаткових даних, які слід вставити до проміжної таблиці:

```php
$user->roles()->attach($roleId, ['expires' => $expires]);
```

Іноді буває потрібно забрати роль у користувача. Щоб видалити запис зв'язку «багато до багатьох», скористайтеся методом `detach`. Метод `detach` видалить відповідний запис із проміжної таблиці; проте обидві моделі залишаться в базі даних:

```php
// Detach a single role from the user...
$user->roles()->detach($roleId);

// Detach all roles from the user...
$user->roles()->detach();
```

Для зручності `attach` і `detach` також приймають на вхід масиви ID:

```php
$user = User::find(1);

$user->roles()->detach([1, 2, 3]);

$user->roles()->attach([
    1 => ['expires' => $expires],
    2 => ['expires' => $expires],
]);
```

<a name="syncing-associations"></a>
#### Синхронізація асоціацій

Ви також можете скористатися методом `sync`, щоб побудувати асоціації «багато до багатьох». Метод `sync` приймає масив ID, які слід розмістити в проміжній таблиці. Будь-які ID, яких немає в переданому масиві, буде видалено з проміжної таблиці. Тож після завершення цієї операції в проміжній таблиці залишаться лише ID із переданого масиву:

```php
$user->roles()->sync([1, 2, 3]);
```

Разом з ID ви можете передати й додаткові значення для проміжної таблиці:

```php
$user->roles()->sync([1 => ['expires' => true], 2, 3]);
```

Якщо ви хочете вставити однакові значення проміжної таблиці для кожного із синхронізованих ID моделей, скористайтеся методом `syncWithPivotValues`:

```php
$user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]);
```

Якщо ви не хочете від'єднувати наявні ID, яких немає в переданому масиві, скористайтеся методом `syncWithoutDetaching`:

```php
$user->roles()->syncWithoutDetaching([1, 2, 3]);
```

<a name="toggling-associations"></a>
#### Перемикання асоціацій

Зв'язок «багато до багатьох» також надає метод `toggle`, який «перемикає» статус приєднання для заданих ID пов'язаних моделей. Якщо заданий ID наразі приєднано, його буде від'єднано. І навпаки: якщо його наразі від'єднано, його буде приєднано:

```php
$user->roles()->toggle([1, 2, 3]);
```

Разом з ID ви можете передати й додаткові значення для проміжної таблиці:

```php
$user->roles()->toggle([
    1 => ['expires' => true],
    2 => ['expires' => true],
]);
```

<a name="transactional-pivot-operations"></a>
#### Транзакційні pivot-операції

Кожна з розглянутих вище pivot-операцій має також варіант `OrFail` (`attachOrFail`, `detachOrFail`, `syncOrFail`, `syncWithoutDetachingOrFail` та `toggleOrFail`), який загортає операцію в транзакцію бази даних, тож усі зміни автоматично відкочуються, якщо буде кинуто виняток:

```php
$user->roles()->attachOrFail([1, 2, 3]);

$user->roles()->syncOrFail([1, 2, 3]);
```

<a name="updating-a-record-on-the-intermediate-table"></a>
#### Оновлення запису в проміжній таблиці

Якщо вам потрібно оновити наявний рядок у проміжній таблиці вашого зв'язку, скористайтеся методом `updateExistingPivot`. Цей метод приймає зовнішній ключ проміжного запису та масив атрибутів для оновлення:

```php
$user = User::find(1);

$user->roles()->updateExistingPivot($roleId, [
    'active' => false,
]);
```

<a name="touching-parent-timestamps"></a>
## Оновлення часових міток батьківської моделі

Коли модель визначає зв'язок `belongsTo` чи `belongsToMany` з іншою моделлю - наприклад, `Comment`, що належить `Post`, - іноді буває корисно оновлювати часову мітку батьківської моделі при оновленні дочірньої.

Наприклад, коли оновлюється модель `Comment`, ви можете захотіти автоматично «торкнутися» часової мітки `updated_at` допису-власника `Post`, щоб їй було встановлено поточні дату й час. Щоб цього досягти, застосуйте до дочірньої моделі атрибут `Touches` з іменами зв'язків, чиї часові мітки `updated_at` слід оновлювати при оновленні дочірньої моделі:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Touches;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

#[Touches(['post'])]
class Comment extends Model
{
    /**
     * Get the post that the comment belongs to.
     */
    public function post(): BelongsTo
    {
        return $this->belongsTo(Post::class);
    }
}
```

> [!WARNING]
> Часові мітки батьківської моделі оновлюватимуться лише тоді, коли дочірню модель оновлено методом `save` Eloquent.

---

Джерело: https://laravelukraine.com/docs/13.x/eloquent-collections

# Eloquent: колекції

- [Вступ](#introduction)
- [Доступні методи](#available-methods)
- [Власні колекції](#custom-collections)

<a name="introduction"></a>
## Вступ

Усі методи Eloquent, що повертають більш ніж одну модель, повертають екземпляри класу `Illuminate\Database\Eloquent\Collection` - зокрема й результати методу `get` та отримані через зв'язок. Об'єкт колекції Eloquent розширює [базову колекцію](/docs/{{version}}/collections) Laravel, тож він природно успадковує десятки методів для плавної роботи з масивом моделей Eloquent, що лежить у його основі. Обов'язково перегляньте документацію з колекцій Laravel, щоб дізнатися про всі ці корисні методи!

Усі колекції є також ітераторами, тож ви можете обходити їх у циклі, наче звичайні PHP-масиви:

```php
use App\Models\User;

$users = User::where('active', 1)->get();

foreach ($users as $user) {
    echo $user->name;
}
```

Проте, як уже згадувалося, колекції значно потужніші за масиви й дають цілу низку операцій map / reduce, які можна зчіплювати ланцюжком через інтуїтивний інтерфейс. Наприклад, ми можемо прибрати всі неактивні моделі, а потім зібрати імена решти користувачів:

```php
$names = User::all()->reject(function (User $user) {
    return $user->active === false;
})->map(function (User $user) {
    return $user->name;
});
```

<a name="eloquent-collection-conversion"></a>
#### Перетворення колекцій Eloquent

Хоча більшість методів колекції Eloquent повертають новий екземпляр колекції Eloquent, методи `collapse`, `flatten`, `flip`, `keys`, `pluck` і `zip` повертають екземпляр [базової колекції](/docs/{{version}}/collections). Так само, якщо операція `map` повертає колекцію, у якій немає жодної моделі Eloquent, її буде перетворено на екземпляр базової колекції.

<a name="available-methods"></a>
## Доступні методи

Усі колекції Eloquent розширюють об'єкт базової [колекції Laravel](/docs/{{version}}/collections#available-methods), тож успадковують усі потужні методи базового класу колекцій.

Крім того, клас `Illuminate\Database\Eloquent\Collection` має надмножину методів для роботи з колекціями ваших моделей. Більшість методів повертають екземпляри `Illuminate\Database\Eloquent\Collection`; проте деякі - як-от `pluck` - повертають екземпляр `Illuminate\Support\Collection`.

<style>
    .collection-method-list > p {
        columns: 14.4em 1; -moz-columns: 14.4em 1; -webkit-columns: 14.4em 1;
    }

    .collection-method-list a {
        display: block;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .collection-method code {
        font-size: 14px;
    }

    .collection-method:not(.first-collection-method) {
        margin-top: 50px;
    }
</style>

<div class="collection-method-list" markdown="1">

[append](#method-append)
[contains](#method-contains)
[diff](#method-diff)
[except](#method-except)
[find](#method-find)
[findOrFail](#method-find-or-fail)
[fresh](#method-fresh)
[intersect](#method-intersect)
[load](#method-load)
[loadMissing](#method-loadMissing)
[modelKeys](#method-modelKeys)
[makeVisible](#method-makeVisible)
[makeHidden](#method-makeHidden)
[mergeVisible](#method-mergeVisible)
[mergeHidden](#method-mergeHidden)
[only](#method-only)
[partition](#method-partition)
[setAppends](#method-setAppends)
[setVisible](#method-setVisible)
[setHidden](#method-setHidden)
[toQuery](#method-toquery)
[unique](#method-unique)
[withoutAppends](#method-withoutAppends)

</div>

<a name="method-append"></a>
#### `append($attributes)` {.collection-method .first-collection-method}

Методом `append` можна вказати, що атрибут слід [додавати](/docs/{{version}}/eloquent-serialization#appending-values-to-json) для кожної моделі в колекції. Метод приймає масив атрибутів або один атрибут:

```php
$users->append('team');

$users->append(['team', 'is_admin']);
```

<a name="method-contains"></a>
#### `contains($key, $operator = null, $value = null)` {.collection-method}

Методом `contains` можна визначити, чи міститься в колекції заданий екземпляр моделі. Метод приймає первинний ключ або екземпляр моделі:

```php
$users->contains(1);

$users->contains(User::find(1));
```

<a name="method-diff"></a>
#### `diff($items)` {.collection-method}

Метод `diff` повертає всі моделі, яких немає в заданій колекції:

```php
use App\Models\User;

$users = $users->diff(User::whereIn('id', [1, 2, 3])->get());
```

<a name="method-except"></a>
#### `except($keys)` {.collection-method}

Метод `except` повертає всі моделі, які не мають заданих первинних ключів:

```php
$users = $users->except([1, 2, 3]);
```

<a name="method-find"></a>
#### `find($key)` {.collection-method}

Метод `find` повертає модель, первинний ключ якої збігається із заданим. Якщо `$key` - екземпляр моделі, `find` спробує повернути модель із таким самим первинним ключем. Якщо `$key` - масив ключів, `find` поверне всі моделі, чиї первинні ключі є в цьому масиві:

```php
$users = User::all();

$user = $users->find(1);
```

<a name="method-find-or-fail"></a>
#### `findOrFail($key)` {.collection-method}

Метод `findOrFail` повертає модель, первинний ключ якої збігається із заданим, або викидає виняток `Illuminate\Database\Eloquent\ModelNotFoundException`, якщо відповідної моделі в колекції немає:

```php
$users = User::all();

$user = $users->findOrFail(1);
```

<a name="method-fresh"></a>
#### `fresh($with = [])` {.collection-method}

Метод `fresh` дістає з бази даних свіжий екземпляр кожної моделі в колекції. Крім того, будь-які вказані зв'язки буде жадібно завантажено:

```php
$users = $users->fresh();

$users = $users->fresh('comments');
```

<a name="method-intersect"></a>
#### `intersect($items)` {.collection-method}

Метод `intersect` повертає всі моделі, які є також у заданій колекції:

```php
use App\Models\User;

$users = $users->intersect(User::whereIn('id', [1, 2, 3])->get());
```

<a name="method-load"></a>
#### `load($relations)` {.collection-method}

Метод `load` жадібно завантажує задані зв'язки для всіх моделей у колекції:

```php
$users->load(['comments', 'posts']);

$users->load('comments.author');

$users->load(['comments', 'posts' => fn ($query) => $query->where('active', 1)]);
```

<a name="method-loadMissing"></a>
#### `loadMissing($relations)` {.collection-method}

Метод `loadMissing` жадібно завантажує задані зв'язки для всіх моделей у колекції, якщо ці зв'язки ще не завантажено:

```php
$users->loadMissing(['comments', 'posts']);

$users->loadMissing('comments.author');

$users->loadMissing(['comments', 'posts' => fn ($query) => $query->where('active', 1)]);
```

<a name="method-modelKeys"></a>
#### `modelKeys()` {.collection-method}

Метод `modelKeys` повертає первинні ключі всіх моделей у колекції:

```php
$users->modelKeys();

// [1, 2, 3, 4, 5]
```

<a name="method-makeVisible"></a>
#### `makeVisible($attributes)` {.collection-method}

Метод `makeVisible` [робить видимими атрибути](/docs/{{version}}/eloquent-serialization#hiding-attributes-from-json), які зазвичай «приховані» в кожній моделі колекції:

```php
$users = $users->makeVisible(['address', 'phone_number']);
```

<a name="method-makeHidden"></a>
#### `makeHidden($attributes)` {.collection-method}

Метод `makeHidden` [приховує атрибути](/docs/{{version}}/eloquent-serialization#hiding-attributes-from-json), які зазвичай «видимі» в кожній моделі колекції:

```php
$users = $users->makeHidden(['address', 'phone_number']);
```

<a name="method-mergeVisible"></a>
#### `mergeVisible($attributes)` {.collection-method}

Метод `mergeVisible` [робить видимими додаткові атрибути](/docs/{{version}}/eloquent-serialization#hiding-attributes-from-json), зберігаючи наявні видимі атрибути:

```php
$users = $users->mergeVisible(['middle_name']);
```

<a name="method-mergeHidden"></a>
#### `mergeHidden($attributes)` {.collection-method}

Метод `mergeHidden` [приховує додаткові атрибути](/docs/{{version}}/eloquent-serialization#hiding-attributes-from-json), зберігаючи наявні приховані атрибути:

```php
$users = $users->mergeHidden(['last_login_at']);
```

<a name="method-only"></a>
#### `only($keys)` {.collection-method}

Метод `only` повертає всі моделі, які мають задані первинні ключі:

```php
$users = $users->only([1, 2, 3]);
```

<a name="method-partition"></a>
#### `partition` {.collection-method}

Метод `partition` повертає екземпляр `Illuminate\Support\Collection`, що містить екземпляри колекцій `Illuminate\Database\Eloquent\Collection`:

```php
$partition = $users->partition(fn ($user) => $user->age > 18);

dump($partition::class);    // Illuminate\Support\Collection
dump($partition[0]::class); // Illuminate\Database\Eloquent\Collection
dump($partition[1]::class); // Illuminate\Database\Eloquent\Collection
```

<a name="method-setAppends"></a>
#### `setAppends($attributes)` {.collection-method}

Метод `setAppends` тимчасово перевизначає всі [додані атрибути](/docs/{{version}}/eloquent-serialization#appending-values-to-json) кожної моделі в колекції:

```php
$users = $users->setAppends(['is_admin']);
```

<a name="method-setVisible"></a>
#### `setVisible($attributes)` {.collection-method}

Метод `setVisible` [тимчасово перевизначає](/docs/{{version}}/eloquent-serialization#temporarily-modifying-attribute-visibility) всі видимі атрибути кожної моделі в колекції:

```php
$users = $users->setVisible(['id', 'name']);
```

<a name="method-setHidden"></a>
#### `setHidden($attributes)` {.collection-method}

Метод `setHidden` [тимчасово перевизначає](/docs/{{version}}/eloquent-serialization#temporarily-modifying-attribute-visibility) всі приховані атрибути кожної моделі в колекції:

```php
$users = $users->setHidden(['email', 'password', 'remember_token']);
```

<a name="method-toquery"></a>
#### `toQuery()` {.collection-method}

Метод `toQuery` повертає екземпляр конструктора запитів Eloquent з обмеженням `whereIn` за первинними ключами моделей колекції:

```php
use App\Models\User;

$users = User::where('status', 'VIP')->get();

$users->toQuery()->update([
    'status' => 'Administrator',
]);
```

<a name="method-unique"></a>
#### `unique($key = null, $strict = false)` {.collection-method}

Метод `unique` повертає всі унікальні моделі колекції. Моделі з таким самим первинним ключем, як в іншої моделі колекції, буде прибрано:

```php
$users = $users->unique();
```

<a name="method-withoutAppends"></a>
#### `withoutAppends()` {.collection-method}

Метод `withoutAppends` тимчасово прибирає всі [додані атрибути](/docs/{{version}}/eloquent-serialization#appending-values-to-json) кожної моделі в колекції:

```php
$users = $users->withoutAppends();
```

<a name="custom-collections"></a>
## Власні колекції

Якщо ви хочете використовувати власний об'єкт `Collection` під час роботи з певною моделлю, додайте до неї атрибут `CollectedBy`:

```php
<?php

namespace App\Models;

use App\Support\UserCollection;
use Illuminate\Database\Eloquent\Attributes\CollectedBy;
use Illuminate\Database\Eloquent\Model;

#[CollectedBy(UserCollection::class)]
class User extends Model
{
    // ...
}
```

Або ж ви можете описати в моделі метод `newCollection`:

```php
<?php

namespace App\Models;

use App\Support\UserCollection;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Create a new Eloquent Collection instance.
     *
     * @param  array<int, \Illuminate\Database\Eloquent\Model>  $models
     * @return \Illuminate\Database\Eloquent\Collection<int, \Illuminate\Database\Eloquent\Model>
     */
    public function newCollection(array $models = []): Collection
    {
        $collection = new UserCollection($models);

        if (Model::isAutomaticallyEagerLoadingRelationships()) {
            $collection->withRelationshipAutoloading();
        }

        return $collection;
    }
}
```

Коли ви описали метод `newCollection` або додали до моделі атрибут `CollectedBy`, ви отримуватимете екземпляр власної колекції щоразу, коли Eloquent зазвичай повернув би екземпляр `Illuminate\Database\Eloquent\Collection`.

Якщо ви хочете використовувати власну колекцію для кожної моделі у вашому застосунку, опишіть метод `newCollection` у базовому класі моделі, який розширюють усі моделі вашого застосунку.

---

Джерело: https://laravelukraine.com/docs/13.x/eloquent-mutators

# Eloquent: мутатори та приведення типів

- [Вступ](#introduction)
- [Аксесори та мутатори](#accessors-and-mutators)
    - [Опис аксесора](#defining-an-accessor)
    - [Опис мутатора](#defining-a-mutator)
- [Приведення типів атрибутів](#attribute-casting)
    - [Приведення до масиву та JSON](#array-and-json-casting)
    - [Приведення до вектора](#vector-casting)
    - [Бінарне приведення](#binary-casting)
    - [Приведення дат](#date-casting)
    - [Приведення до enum](#enum-casting)
    - [Шифроване приведення](#encrypted-casting)
    - [Приведення під час запиту](#query-time-casting)
- [Власні приведення](#custom-casts)
    - [Приведення до об'єктів-значень](#value-object-casting)
    - [Серіалізація в масив / JSON](#array-json-serialization)
    - [Вхідне приведення](#inbound-casting)
    - [Параметри приведення](#cast-parameters)
    - [Порівняння приведених значень](#comparing-cast-values)
    - [Castable-об'єкти](#castables)

<a name="introduction"></a>
## Вступ

Аксесори, мутатори та приведення типів атрибутів дозволяють перетворювати значення атрибутів Eloquent, коли ви їх читаєте чи задаєте на екземплярах моделей. Наприклад, ви можете скористатися [шифрувальником Laravel](/docs/{{version}}/encryption), щоб зашифрувати значення для зберігання в базі даних, а потім автоматично розшифровувати цей атрибут при зверненні до нього на моделі Eloquent. Або ж ви можете перетворювати рядок JSON, що зберігається в базі, на масив при зверненні через модель Eloquent.

<a name="accessors-and-mutators"></a>
## Аксесори та мутатори

<a name="defining-an-accessor"></a>
### Опис аксесора

Аксесор перетворює значення атрибута Eloquent при зверненні до нього. Щоб описати аксесор, створіть у моделі метод `protected`, який представлятиме доступний атрибут. Назва методу має відповідати запису справжнього атрибута моделі чи стовпця бази даних у «camel case» - там, де це доречно.

У цьому прикладі ми опишемо аксесор для атрибута `first_name`. Eloquent автоматично викличе його при спробі отримати значення атрибута `first_name`. Усі методи аксесорів і мутаторів атрибутів мають оголошувати тип, що повертається, - `Illuminate\Database\Eloquent\Casts\Attribute`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get the user's first name.
     */
    protected function firstName(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => ucfirst($value),
        );
    }
}
```

Усі методи аксесорів повертають екземпляр `Attribute`, який описує, як до атрибута звертатимуться і, за бажанням, як його змінюватимуть. У цьому прикладі ми описуємо лише читання атрибута - для цього передаємо аргумент `get` конструктору класу `Attribute`.

Як бачите, початкове значення стовпця передається в аксесор, тож ви можете його змінити й повернути. Щоб отримати значення аксесора, просто зверніться до атрибута `first_name` на екземплярі моделі:

```php
use App\Models\User;

$user = User::find(1);

$firstName = $user->first_name;
```

> [!NOTE]
> Якщо ви хочете, щоб ці обчислені значення потрапляли до масиву чи JSON-представлення моделі, [їх потрібно додати](/docs/{{version}}/eloquent-serialization#appending-values-to-json).

<a name="building-value-objects-from-multiple-attributes"></a>
#### Побудова об'єктів-значень із кількох атрибутів

Іноді вашому аксесору потрібно перетворити кілька атрибутів моделі на один «об'єкт-значення». Для цього ваше замикання `get` може приймати другий аргумент - `$attributes`, який буде передано автоматично й який міститиме масив усіх поточних атрибутів моделі:

```php
use App\Support\Address;
use Illuminate\Database\Eloquent\Casts\Attribute;

/**
 * Interact with the user's address.
 */
protected function address(): Attribute
{
    return Attribute::make(
        get: fn (mixed $value, array $attributes) => new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        ),
    );
}
```

<a name="accessor-caching"></a>
#### Кешування аксесорів

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

```php
use App\Models\User;

$user = User::find(1);

$user->address->lineOne = 'Updated Address Line 1 Value';
$user->address->lineTwo = 'Updated Address Line 2 Value';

$user->save();
```

Втім, іноді вам може знадобитися кешування й для простих значень - рядків чи булевих, - особливо якщо їх дорого обчислювати. Для цього викличте метод `shouldCache` при описі аксесора:

```php
protected function hash(): Attribute
{
    return Attribute::make(
        get: fn (string $value) => bcrypt(gzuncompress($value)),
    )->shouldCache();
}
```

Якщо ви хочете вимкнути кешування об'єктів для атрибута, викличте при його описі метод `withoutObjectCaching`:

```php
/**
 * Interact with the user's address.
 */
protected function address(): Attribute
{
    return Attribute::make(
        get: fn (mixed $value, array $attributes) => new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        ),
    )->withoutObjectCaching();
}
```

<a name="defining-a-mutator"></a>
### Опис мутатора

Мутатор перетворює значення атрибута Eloquent при його задаванні. Щоб описати мутатор, передайте при описі атрибута аргумент `set`. Опишімо мутатор для атрибута `first_name`. Його буде автоматично викликано, коли ми спробуємо задати значення атрибута `first_name` на моделі:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Interact with the user's first name.
     */
    protected function firstName(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => ucfirst($value),
            set: fn (string $value) => strtolower($value),
        );
    }
}
```

Замикання мутатора отримає значення, яке задають атрибуту, тож ви можете змінити його й повернути змінене. Щоб скористатися нашим мутатором, достатньо задати атрибут `first_name` на моделі Eloquent:

```php
use App\Models\User;

$user = User::find(1);

$user->first_name = 'Sally';
```

У цьому прикладі колбек `set` буде викликано зі значенням `Sally`. Далі мутатор застосує до імені функцію `strtolower` і покладе результат у внутрішній масив `$attributes` моделі.

<a name="mutating-multiple-attributes"></a>
#### Зміна кількох атрибутів

Іноді вашому мутатору потрібно задати кілька атрибутів моделі. Для цього поверніть із замикання `set` масив. Кожен ключ масиву має відповідати атрибуту моделі чи стовпцю бази даних:

```php
use App\Support\Address;
use Illuminate\Database\Eloquent\Casts\Attribute;

/**
 * Interact with the user's address.
 */
protected function address(): Attribute
{
    return Attribute::make(
        get: fn (mixed $value, array $attributes) => new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        ),
        set: fn (Address $value) => [
            'address_line_one' => $value->lineOne,
            'address_line_two' => $value->lineTwo,
        ],
    );
}
```

<a name="attribute-casting"></a>
## Приведення типів атрибутів

Приведення типів атрибутів дає функціональність, схожу на аксесори та мутатори, але не вимагає описувати в моделі додаткові методи. Натомість метод `casts` вашої моделі дає зручний спосіб перетворювати атрибути на поширені типи даних.

Метод `casts` має повертати масив, де ключ - назва атрибута, який приводимо, а значення - тип, до якого ви хочете привести стовпець. Підтримувані типи приведення:

<div class="content-list" markdown="1">

- `array`
- `AsFluent::class`
- `AsStringable::class`
- `AsUri::class`
- `AsVector::class`
- `boolean`
- `collection`
- `date`
- `datetime`
- `immutable_date`
- `immutable_datetime`
- <code>decimal:&lt;precision&gt;</code>
- `double`
- `encrypted`
- `encrypted:array`
- `encrypted:collection`
- `encrypted:object`
- `float`
- `hashed`
- `integer`
- `object`
- `real`
- `string`
- `timestamp`

</div>

Щоб показати приведення типів у дії, приведімо атрибут `is_admin`, який зберігається в базі як ціле число (`0` або `1`), до булевого значення:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'is_admin' => 'boolean',
        ];
    }
}
```

Після опису приведення атрибут `is_admin` завжди буде приведено до булевого при зверненні до нього - навіть якщо в базі значення зберігається як ціле число:

```php
$user = App\Models\User::find(1);

if ($user->is_admin) {
    // ...
}
```

Якщо вам потрібно додати нове тимчасове приведення під час виконання, скористайтеся методом `mergeCasts`. Ці описи буде додано до тих, які вже описані в моделі:

```php
$user->mergeCasts([
    'is_admin' => 'integer',
    'options' => 'object',
]);
```

> [!WARNING]
> Атрибути зі значенням `null` не приводяться. Крім того, ніколи не описуйте приведення (чи атрибут) з тією самою назвою, що й зв'язок, і не призначайте приведення первинному ключу моделі.

<a name="stringable-casting"></a>
#### Приведення до Stringable

Класом приведення `Illuminate\Database\Eloquent\Casts\AsStringable` можна привести атрибут моделі до [плавного об'єкта Illuminate\Support\Stringable](/docs/{{version}}/strings#fluent-strings-method-list):

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Casts\AsStringable;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'directory' => AsStringable::class,
        ];
    }
}
```

<a name="array-and-json-casting"></a>
### Приведення до масиву та JSON

Приведення `array` особливо корисне при роботі зі стовпцями, у яких зберігається серіалізований JSON. Наприклад, якщо ваша база має поле типу `JSON` чи `TEXT` із серіалізованим JSON, приведення `array` для цього атрибута автоматично десеріалізує його в PHP-масив при зверненні через модель Eloquent:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'options' => 'array',
        ];
    }
}
```

Коли приведення описано, ви можете звернутися до атрибута `options`, і його буде автоматично десеріалізовано з JSON у PHP-масив. Коли ви задаєте значення атрибута `options`, переданий масив автоматично серіалізується назад у JSON для зберігання:

```php
use App\Models\User;

$user = User::find(1);

$options = $user->options;

$options['key'] = 'value';

$user->options = $options;

$user->save();
```

Щоб оновити одне поле атрибута JSON лаконічнішим синтаксисом, ви можете [зробити атрибут доступним для масового призначення](/docs/{{version}}/eloquent#mass-assignment-json-columns) і скористатися оператором `->` при виклику методу `update`:

```php
$user = User::find(1);

$user->update(['options->key' => 'value']);
```

<a name="json-and-unicode"></a>
#### JSON та Unicode

Якщо ви хочете зберігати атрибут-масив як JSON з неекранованими символами Unicode, скористайтеся приведенням `json:unicode`:

```php
/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => 'json:unicode',
    ];
}
```

<a name="array-object-and-collection-casting"></a>
#### Приведення до ArrayObject та колекції

Хоча стандартного приведення `array` вистачає для багатьох застосунків, воно має свої недоліки. Оскільки приведення `array` повертає примітивний тип, змінити елемент масиву напряму неможливо. Наприклад, такий код спричинить помилку PHP:

```php
$user = User::find(1);

$user->options['key'] = $value;
```

Щоб це розв'язати, Laravel має приведення `AsArrayObject`, яке приводить ваш атрибут JSON до класу [ArrayObject](https://www.php.net/manual/en/class.arrayobject.php). Цю можливість реалізовано через [власні приведення](#custom-casts) Laravel, що дозволяє йому розумно кешувати й перетворювати змінений об'єкт, тож окремі елементи можна змінювати без помилки PHP. Щоб скористатися приведенням `AsArrayObject`, просто призначте його атрибуту:

```php
use Illuminate\Database\Eloquent\Casts\AsArrayObject;

/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => AsArrayObject::class,
    ];
}
```

Так само Laravel має приведення `AsCollection`, яке приводить ваш атрибут JSON до екземпляра [колекції](/docs/{{version}}/collections) Laravel:

```php
use Illuminate\Database\Eloquent\Casts\AsCollection;

/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => AsCollection::class,
    ];
}
```

За замовчуванням призначення `null` атрибуту, приведеному через `AsArrayObject` або `AsCollection`, збереже JSON-значення `null` у базі даних. Якщо ви хочете, щоб значення `null` зберігалися як нативні SQL-значення `NULL`, викличте метод `nullable` під час визначення приведення:

```php
use Illuminate\Database\Eloquent\Casts\AsCollection;

/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => AsCollection::nullable(),
    ];
}
```

Метод `nullable` також можна поєднувати з власним класом колекції:

```php
'options' => AsCollection::nullable(OptionCollection::class),
```

Приведення `AsArrayObject` теж надає метод `nullable` з тією самою поведінкою.

Якщо ви хочете, щоб приведення `AsCollection` створювало екземпляр власного класу колекції замість базового класу Laravel, передайте назву класу колекції як аргумент приведення:

```php
use App\Collections\OptionCollection;
use Illuminate\Database\Eloquent\Casts\AsCollection;

/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => AsCollection::using(OptionCollection::class),
    ];
}
```

Методом `of` можна вказати, що елементи колекції слід перетворити на заданий клас через [метод mapInto](/docs/{{version}}/collections#method-mapinto) колекції:

```php
use App\ValueObjects\Option;
use Illuminate\Database\Eloquent\Casts\AsCollection;

/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'options' => AsCollection::of(Option::class)
    ];
}
```

Перетворюючи колекції на об'єкти, ці об'єкти мають реалізувати інтерфейси `Illuminate\Contracts\Support\Arrayable` та `JsonSerializable`, щоб описати, як їхні екземпляри серіалізуються в базу даних як JSON:

```php
<?php

namespace App\ValueObjects;

use Illuminate\Contracts\Support\Arrayable;
use JsonSerializable;

class Option implements Arrayable, JsonSerializable
{
    public string $name;
    public mixed $value;
    public bool $isLocked;

    /**
     * Create a new Option instance.
     */
    public function __construct(array $data)
    {
        $this->name = $data['name'];
        $this->value = $data['value'];
        $this->isLocked = $data['is_locked'];
    }

    /**
     * Get the instance as an array.
     *
     * @return array{name: string, data: string, is_locked: bool}
     */
    public function toArray(): array
    {
        return [
            'name' => $this->name,
            'value' => $this->value,
            'is_locked' => $this->isLocked,
        ];
    }

    /**
     * Specify the data which should be serialized to JSON.
     *
     * @return array{name: string, data: string, is_locked: bool}
     */
    public function jsonSerialize(): array
    {
        return $this->toArray();
    }
}
```

<a name="vector-casting"></a>
### Приведення до вектора

Ви можете використовувати клас `Illuminate\Database\Eloquent\Casts\AsVector` для приведення векторного стовпця бази даних до та з PHP-масиву:

```php
use Illuminate\Database\Eloquent\Casts\AsVector;

/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'embedding' => AsVector::class,
    ];
}
```

При встановленні атрибута приведення приймає PHP-масив або екземпляр `Arrayable`, наприклад колекцію Laravel. При отриманні атрибута приведення повертає масив дробових чисел.

<a name="binary-casting"></a>
### Бінарне приведення

Якщо ваша модель Eloquent має стовпець `uuid` чи `ulid` [бінарного типу](/docs/{{version}}/migrations#column-method-binary) на додачу до автоінкрементного стовпця ID, скористайтеся приведенням `AsBinary`, щоб автоматично перетворювати значення в бінарне представлення й назад:

```php
use Illuminate\Database\Eloquent\Casts\AsBinary;

/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'uuid' => AsBinary::uuid(),
        'ulid' => AsBinary::ulid(),
    ];
}
```

Коли приведення описано в моделі, ви можете задати значення атрибута UUID / ULID як екземпляр об'єкта або рядок. Eloquent автоматично приведе значення до бінарного представлення. Читаючи значення атрибута, ви завжди отримаєте звичайний текстовий рядок:

```php
use Illuminate\Support\Str;

$user->uuid = Str::uuid();

return $user->uuid;

// "6e8cdeed-2f32-40bd-b109-1e4405be2140"
```

<a name="date-casting"></a>
### Приведення дат

За замовчуванням Eloquent приводить стовпці `created_at` та `updated_at` до екземплярів [Carbon](https://github.com/briannesbitt/Carbon), що розширює PHP-клас `DateTime` і має цілу низку зручних методів. Ви можете привести й інші атрибути дат, описавши додаткові приведення в методі `casts` вашої моделі. Зазвичай дати приводять типами `datetime` або `immutable_datetime`.

Описуючи приведення `date` чи `datetime`, ви можете також задати формат дати. Його буде використано, коли [модель серіалізується в масив чи JSON](/docs/{{version}}/eloquent-serialization):

```php
/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'created_at' => 'datetime:Y-m-d',
    ];
}
```

Коли стовпець приведено як дату, ви можете задати відповідному атрибуту моделі значення у вигляді UNIX-часу, рядка дати (`Y-m-d`), рядка дати з часом або екземпляра `DateTime` / `Carbon`. Значення дати буде правильно перетворено й збережено у вашій базі даних.

Ви можете змінити формат серіалізації за замовчуванням для всіх дат моделі, описавши в ній метод `serializeDate`. Цей метод не впливає на те, як ваші дати форматуються для зберігання в базі даних:

```php
/**
 * Prepare a date for array / JSON serialization.
 */
protected function serializeDate(DateTimeInterface $date): string
{
    return $date->format('Y-m-d');
}
```

Щоб задати формат, у якому дати моделі справді зберігаються в базі даних, скористайтеся аргументом `dateFormat` атрибута `Table` вашої моделі:

```php
use Illuminate\Database\Eloquent\Attributes\Table;

#[Table(dateFormat: 'U')]
class Flight extends Model
{
    // ...
}
```

<a name="date-casting-and-timezones"></a>
#### Приведення дат, серіалізація та часові зони

За замовчуванням приведення `date` і `datetime` серіалізують дати в рядок UTC ISO-8601 (`YYYY-MM-DDTHH:MM:SS.uuuuuuZ`) незалежно від часової зони, заданої в опції конфігурації `timezone` вашого застосунку. Наполегливо радимо завжди користуватися саме цим форматом серіалізації, а також зберігати дати застосунку в часовій зоні UTC, не змінюючи опцію `timezone` з її значення за замовчуванням `UTC`. Послідовне використання UTC в усьому застосунку дасть максимальну сумісність з іншими бібліотеками роботи з датами на PHP та JavaScript.

Якщо до приведення `date` чи `datetime` застосовано власний формат - наприклад, `datetime:Y-m-d H:i:s`, - при серіалізації дати буде використано внутрішню часову зону екземпляра Carbon. Зазвичай це часова зона, задана в опції конфігурації `timezone` вашого застосунку. Проте важливо зауважити: стовпці `timestamp` на кшталт `created_at` та `updated_at` є винятком із цієї поведінки й завжди форматуються в UTC, незалежно від налаштування часової зони застосунку.

<a name="enum-casting"></a>
### Приведення до enum

Eloquent дозволяє також приводити значення атрибутів до PHP-[enum](https://www.php.net/manual/en/language.enumerations.backed.php). Для цього вкажіть атрибут і enum, до якого хочете привести, у методі `casts` вашої моделі:

```php
use App\Enums\ServerStatus;

/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'status' => ServerStatus::class,
    ];
}
```

Коли ви описали приведення в моделі, указаний атрибут автоматично приводитиметься до enum і назад при роботі з ним:

```php
if ($server->status == ServerStatus::Provisioned) {
    $server->status = ServerStatus::Ready;

    $server->save();
}
```

<a name="casting-arrays-of-enums"></a>
#### Приведення масивів enum

Іноді вашій моделі потрібно зберігати масив значень enum в одному стовпці. Для цього скористайтеся приведеннями `AsEnumArrayObject` чи `AsEnumCollection`, які дає Laravel:

```php
use App\Enums\ServerStatus;
use Illuminate\Database\Eloquent\Casts\AsEnumCollection;

/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'statuses' => AsEnumCollection::of(ServerStatus::class),
    ];
}
```

<a name="encrypted-casting"></a>
### Шифроване приведення

Приведення `encrypted` шифрує значення атрибута моделі вбудованими засобами [шифрування](/docs/{{version}}/encryption) Laravel. Крім того, приведення `encrypted:array`, `encrypted:collection`, `encrypted:object`, `AsEncryptedArrayObject` та `AsEncryptedCollection` працюють як їхні нешифровані відповідники; проте, як і слід очікувати, значення шифрується при зберіганні в базі даних.

Оскільки кінцева довжина зашифрованого тексту непередбачувана й більша за відкритий текст, переконайтеся, що відповідний стовпець бази даних має тип `TEXT` або більший. Крім того, оскільки значення в базі зашифровані, ви не зможете робити запити чи шукати за зашифрованими атрибутами.

<a name="key-rotation"></a>
#### Ротація ключа

Як ви, можливо, знаєте, Laravel шифрує рядки за допомогою значення конфігурації `key`, заданого у файлі конфігурації `app` вашого застосунку. Зазвичай це значення відповідає змінній оточення `APP_KEY`. Якщо вам потрібно змінити ключ шифрування застосунку, ви можете [зробити це коректно](/docs/{{version}}/encryption#gracefully-rotating-encryption-keys).

<a name="query-time-casting"></a>
### Приведення під час запиту

Іноді вам потрібно застосувати приведення під час виконання запиту - наприклад, коли ви вибираєте сире значення з таблиці. Розгляньмо такий запит:

```php
use App\Models\Post;
use App\Models\User;

$users = User::select([
    'users.*',
    'last_posted_at' => Post::selectRaw('MAX(created_at)')
        ->whereColumn('user_id', 'users.id')
])->get();
```

Атрибут `last_posted_at` у результатах цього запиту буде звичайним рядком. Було б чудово застосувати до нього приведення `datetime` прямо під час виконання запиту. На щастя, це можна зробити методом `withCasts`:

```php
$users = User::select([
    'users.*',
    'last_posted_at' => Post::selectRaw('MAX(created_at)')
        ->whereColumn('user_id', 'users.id')
])->withCasts([
    'last_posted_at' => 'datetime'
])->get();
```

<a name="custom-casts"></a>
## Власні приведення

Laravel має цілу низку вбудованих корисних типів приведення; проте іноді вам потрібно описати власні. Щоб створити приведення, виконайте artisan-команду `make:cast`. Новий клас приведення потрапить до каталогу `app/Casts`:

```shell
php artisan make:cast AsJson
```

Усі класи власних приведень реалізують інтерфейс `CastsAttributes`. Класи, що його реалізують, мають описати методи `get` і `set`. Метод `get` відповідає за перетворення сирого значення з бази даних на приведене значення, а метод `set` має перетворити приведене значення на сире, яке можна зберегти в базі. Для прикладу ми заново реалізуємо вбудований тип приведення `json` як власний:

```php
<?php

namespace App\Casts;

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;

class AsJson implements CastsAttributes
{
    /**
     * Cast the given value.
     *
     * @param  array<string, mixed>  $attributes
     * @return array<string, mixed>
     */
    public function get(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): array {
        return json_decode($value, true);
    }

    /**
     * Prepare the given value for storage.
     *
     * @param  array<string, mixed>  $attributes
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): string {
        return json_encode($value);
    }
}
```

Коли ви описали власний тип приведення, його можна прикріпити до атрибута моделі за назвою класу:

```php
<?php

namespace App\Models;

use App\Casts\AsJson;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'options' => AsJson::class,
        ];
    }
}
```

<a name="value-object-casting"></a>
### Приведення до об'єктів-значень

Ви не обмежені приведенням до примітивних типів - значення можна приводити й до об'єктів. Опис власних приведень до об'єктів дуже схожий на приведення до примітивів; проте, якщо ваш об'єкт-значення охоплює більш ніж один стовпець бази даних, метод `set` має повернути масив пар «ключ - значення», якими буде задано сирі значення для зберігання в моделі. Якщо ваш об'єкт-значення стосується лише одного стовпця, просто поверніть значення для зберігання.

Для прикладу опишімо клас власного приведення, що приводить кілька значень моделі до одного об'єкта-значення `Address`. Припустімо, що об'єкт-значення `Address` має дві публічні властивості: `lineOne` і `lineTwo`:

```php
<?php

namespace App\Casts;

use App\ValueObjects\Address;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
use InvalidArgumentException;

class AsAddress implements CastsAttributes
{
    /**
     * Cast the given value.
     *
     * @param  array<string, mixed>  $attributes
     */
    public function get(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): Address {
        return new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two']
        );
    }

    /**
     * Prepare the given value for storage.
     *
     * @param  array<string, mixed>  $attributes
     * @return array<string, string>
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): array {
        if (! $value instanceof Address) {
            throw new InvalidArgumentException('The given value is not an Address instance.');
        }

        return [
            'address_line_one' => $value->lineOne,
            'address_line_two' => $value->lineTwo,
        ];
    }
}
```

При приведенні до об'єктів-значень будь-які зміни такого об'єкта автоматично синхронізуються назад у модель перед її збереженням:

```php
use App\Models\User;

$user = User::find(1);

$user->address->lineOne = 'Updated Address Value';

$user->save();
```

> [!NOTE]
> Якщо ви плануєте серіалізувати в JSON чи масиви моделі Eloquent, що містять об'єкти-значення, реалізуйте на об'єкті-значенні інтерфейси `Illuminate\Contracts\Support\Arrayable` та `JsonSerializable`.

<a name="value-object-caching"></a>
#### Кешування об'єктів-значень

Коли атрибути, приведені до об'єктів-значень, обчислюються, Eloquent їх кешує. Тому при повторному зверненні до атрибута буде повернуто той самий екземпляр об'єкта.

Якщо ви хочете вимкнути кешування об'єктів у класах власних приведень, оголосіть у своєму класі приведення публічну властивість `withoutObjectCaching`:

```php
class AsAddress implements CastsAttributes
{
    public bool $withoutObjectCaching = true;

    // ...
}
```

<a name="array-json-serialization"></a>
### Серіалізація в масив / JSON

Коли модель Eloquent перетворюється на масив чи JSON методами `toArray` і `toJson`, ваші об'єкти-значення з власних приведень зазвичай теж серіалізуються - якщо вони реалізують інтерфейси `Illuminate\Contracts\Support\Arrayable` та `JsonSerializable`. Проте, коли ви користуєтеся об'єктами-значеннями зі сторонніх бібліотек, у вас може не бути змоги додати до них ці інтерфейси.

Тому ви можете вказати, що за серіалізацію об'єкта-значення відповідатиме ваш клас приведення. Для цього він має реалізувати інтерфейс `Illuminate\Contracts\Database\Eloquent\SerializesCastableAttributes`. Цей інтерфейс вимагає, щоб ваш клас містив метод `serialize`, який повертає серіалізовану форму вашого об'єкта-значення:

```php
/**
 * Get the serialized representation of the value.
 *
 * @param  array<string, mixed>  $attributes
 */
public function serialize(
    Model $model,
    string $key,
    mixed $value,
    array $attributes,
): string {
    return (string) $value;
}
```

<a name="inbound-casting"></a>
### Вхідне приведення

Іноді вам потрібен клас власного приведення, що перетворює лише значення, які задають моделі, і нічого не робить, коли атрибути з моделі читають.

Приведення лише для вхідних значень мають реалізувати інтерфейс `CastsInboundAttributes`, який вимагає описати тільки метод `set`. Artisan-команду `make:cast` можна викликати з опцією `--inbound`, щоб згенерувати такий клас:

```shell
php artisan make:cast AsHash --inbound
```

Класичний приклад приведення лише для вхідних значень - приведення з хешуванням. Наприклад, ми можемо описати приведення, що хешує вхідні значення заданим алгоритмом:

```php
<?php

namespace App\Casts;

use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
use Illuminate\Database\Eloquent\Model;

class AsHash implements CastsInboundAttributes
{
    /**
     * Create a new cast class instance.
     */
    public function __construct(
        protected string|null $algorithm = null,
    ) {}

    /**
     * Prepare the given value for storage.
     *
     * @param  array<string, mixed>  $attributes
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): string {
        return is_null($this->algorithm)
            ? bcrypt($value)
            : hash($this->algorithm, $value);
    }
}
```

<a name="cast-parameters"></a>
### Параметри приведення

Прикріплюючи власне приведення до моделі, ви можете задати параметри приведення, відділивши їх від назви класу символом `:`, а кілька параметрів - комами. Параметри буде передано в конструктор класу приведення:

```php
/**
 * Get the attributes that should be cast.
 *
 * @return array<string, string>
 */
protected function casts(): array
{
    return [
        'secret' => AsHash::class.':sha256',
    ];
}
```

<a name="comparing-cast-values"></a>
### Порівняння приведених значень

Якщо ви хочете описати, як порівнювати два приведені значення, щоб визначити, чи вони змінилися, ваш клас власного приведення може реалізувати інтерфейс `Illuminate\Contracts\Database\Eloquent\ComparesCastableAttributes`. Це дає тонкий контроль над тим, які значення Eloquent вважає зміненими й, відповідно, зберігає в базу при оновленні моделі.

Цей інтерфейс вимагає, щоб ваш клас містив метод `compare`, який повертає `true`, якщо задані значення вважаються рівними:

```php
/**
 * Determine if the given values are equal.
 *
 * @param  \Illuminate\Database\Eloquent\Model  $model
 * @param  string  $key
 * @param  mixed  $firstValue
 * @param  mixed  $secondValue
 * @return bool
 */
public function compare(
    Model $model,
    string $key,
    mixed $firstValue,
    mixed $secondValue
): bool {
    return $firstValue === $secondValue;
}
```

<a name="castables"></a>
### Castable-об'єкти

Ви можете захотіти, щоб об'єкти-значення вашого застосунку самі описували свої класи приведення. Замість того щоб прикріплювати клас приведення до моделі, ви можете прикріпити клас об'єкта-значення, що реалізує інтерфейс `Illuminate\Contracts\Database\Eloquent\Castable`:

```php
use App\ValueObjects\Address;

protected function casts(): array
{
    return [
        'address' => Address::class,
    ];
}
```

Об'єкти, що реалізують інтерфейс `Castable`, мають описати метод `castUsing`, який повертає назву класу приведення, відповідального за перетворення до класу `Castable` і назад:

```php
<?php

namespace App\ValueObjects;

use Illuminate\Contracts\Database\Eloquent\Castable;
use App\Casts\AsAddress;

class Address implements Castable
{
    /**
     * Get the name of the caster class to use when casting from / to this cast target.
     *
     * @param  array<string, mixed>  $arguments
     */
    public static function castUsing(array $arguments): string
    {
        return AsAddress::class;
    }
}
```

Користуючись класами `Castable`, ви все одно можете передавати аргументи в описі методу `casts`. Їх буде передано в метод `castUsing`:

```php
use App\ValueObjects\Address;

protected function casts(): array
{
    return [
        'address' => Address::class.':argument',
    ];
}
```

<a name="anonymous-cast-classes"></a>
#### Castable-об'єкти та анонімні класи приведення

Поєднавши «castable»-об'єкти з [анонімними класами](https://www.php.net/manual/en/language.oop5.anonymous.php) PHP, ви можете описати об'єкт-значення та логіку його приведення як один castable-об'єкт. Для цього поверніть із методу `castUsing` вашого об'єкта-значення анонімний клас. Він має реалізувати інтерфейс `CastsAttributes`:

```php
<?php

namespace App\ValueObjects;

use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;

class Address implements Castable
{
    // ...

    /**
     * Get the caster class to use when casting from / to this cast target.
     *
     * @param  array<string, mixed>  $arguments
     */
    public static function castUsing(array $arguments): CastsAttributes
    {
        return new class implements CastsAttributes
        {
            public function get(
                Model $model,
                string $key,
                mixed $value,
                array $attributes,
            ): Address {
                return new Address(
                    $attributes['address_line_one'],
                    $attributes['address_line_two']
                );
            }

            public function set(
                Model $model,
                string $key,
                mixed $value,
                array $attributes,
            ): array {
                return [
                    'address_line_one' => $value->lineOne,
                    'address_line_two' => $value->lineTwo,
                ];
            }
        };
    }
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/eloquent-resources

# Eloquent: API-ресурси

- [Вступ](#introduction)
- [Генерування ресурсів](#generating-resources)
- [Огляд концепції](#concept-overview)
    - [Колекції ресурсів](#resource-collections)
- [Написання ресурсів](#writing-resources)
    - [Обгортання даних](#data-wrapping)
    - [Пагінація](#pagination)
    - [Умовні атрибути](#conditional-attributes)
    - [Умовні зв'язки](#conditional-relationships)
    - [Додавання метаданих](#adding-meta-data)
- [Ресурси JSON:API](#jsonapi-resources)
    - [Генерування ресурсів JSON:API](#generating-jsonapi-resources)
    - [Визначення атрибутів](#defining-jsonapi-attributes)
    - [Визначення зв'язків](#defining-jsonapi-relationships)
    - [Тип та ID ресурсу](#jsonapi-resource-type-and-id)
    - [Розріджені набори полів і includes](#jsonapi-sparse-fieldsets-and-includes)
    - [Посилання та метадані](#jsonapi-links-and-meta)
- [Відповіді ресурсів](#resource-responses)

<a name="introduction"></a>
## Вступ

Коли ви будуєте API, вам може знадобитися шар перетворення, який стоїть між вашими моделями Eloquent і JSON-відповідями, що насправді повертаються користувачам застосунку. Наприклад, ви можете захотіти показувати певні атрибути лише частині користувачів, або завжди включати певні зв'язки до JSON-представлення ваших моделей. Класи ресурсів Eloquent дозволяють виразно й легко перетворювати моделі та колекції моделей на JSON.

Звісно, ви завжди можете перетворити моделі чи колекції Eloquent на JSON за допомогою методу `toJson`; проте ресурси Eloquent дають більш гнучкий і надійний контроль над JSON-серіалізацією ваших моделей та їхніх зв'язків.

<a name="generating-resources"></a>
## Генерування ресурсів

Щоб згенерувати клас ресурсу, скористайтеся артизан-командою `make:resource`. За замовчуванням ресурси потрапляють до каталогу `app/Http/Resources` вашого застосунку. Ресурси успадковують клас `Illuminate\Http\Resources\Json\JsonResource`:

```shell
php artisan make:resource UserResource
```

<a name="generating-resource-collections"></a>
#### Колекції ресурсів

Окрім ресурсів, які перетворюють окремі моделі, ви можете генерувати ресурси, відповідальні за перетворення колекцій моделей. Це дозволяє вашим JSON-відповідям містити посилання та іншу метаінформацію, що стосується всієї колекції певного ресурсу.

Щоб створити колекцію ресурсів, скористайтеся прапорцем `--collection` під час створення ресурсу. Або ж слово `Collection` в імені ресурсу підкаже Laravel, що потрібно створити саме колекцію. Колекції ресурсів успадковують клас `Illuminate\Http\Resources\Json\ResourceCollection`:

```shell
php artisan make:resource User --collection

php artisan make:resource UserCollection
```

<a name="concept-overview"></a>
## Огляд концепції

> [!NOTE]
> Це високорівневий огляд ресурсів і колекцій ресурсів. Наполегливо радимо прочитати інші розділи цієї документації, щоб глибше зрозуміти можливості налаштування та всю силу, яку дають ресурси.

Перш ніж заглиблюватися в усі доступні опції написання ресурсів, погляньмо згори на те, як ресурси використовуються в Laravel. Клас ресурсу представляє одну модель, яку потрібно перетворити на JSON-структуру. Ось, наприклад, простий клас ресурсу `UserResource`:

```php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ];
    }
}
```

Кожен клас ресурсу визначає метод `toArray`, який повертає масив атрибутів, що мають бути перетворені на JSON, коли ресурс повертається як відповідь із маршруту чи методу контролера.

Зверніть увагу: ми звертаємося до властивостей моделі напряму через змінну `$this`. Так відбувається тому, що клас ресурсу автоматично проксіює звернення до властивостей і методів до моделі, яка лежить в основі, - для зручності. Коли ресурс визначено, його можна повернути з маршруту чи контролера. Ресурс приймає екземпляр моделі через свій конструктор:

```php
use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/user/{id}', function (string $id) {
    return new UserResource(User::findOrFail($id));
});
```

Для зручності можна скористатися методом моделі `toResource`, який за угодами фреймворку автоматично знайде відповідний ресурс моделі:

```php
return User::findOrFail($id)->toResource();
```

Під час виклику методу `toResource` Laravel спробує знайти ресурс, ім'я якого збігається з іменем моделі та, можливо, має суфікс `Resource`, у просторі імен `Http\Resources`, найближчому до простору імен моделі.

Якщо ваш клас ресурсу не дотримується цієї угоди про іменування або розташований в іншому просторі імен, ви можете вказати ресурс за замовчуванням для моделі за допомогою атрибута `UseResource`:

```php
<?php

namespace App\Models;

use App\Http\Resources\CustomUserResource;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Attributes\UseResource;

#[UseResource(CustomUserResource::class)]
class User extends Model
{
    // ...
}
```

Або ж ви можете вказати клас ресурсу, передавши його до методу `toResource`:

```php
return User::findOrFail($id)->toResource(CustomUserResource::class);
```

<a name="resource-collections"></a>
### Колекції ресурсів

Якщо ви повертаєте колекцію ресурсів або відповідь із пагінацією, під час створення екземпляра ресурсу в маршруті чи контролері скористайтеся методом `collection`, який надає ваш клас ресурсу:

```php
use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/users', function () {
    return UserResource::collection(User::all());
});
```

Або, для зручності, скористайтеся методом колекції Eloquent `toResourceCollection`, який за угодами фреймворку автоматично знайде відповідну колекцію ресурсів моделі:

```php
return User::all()->toResourceCollection();
```

Під час виклику методу `toResourceCollection` Laravel спробує знайти колекцію ресурсів, ім'я якої збігається з іменем моделі та має суфікс `Collection`, у просторі імен `Http\Resources`, найближчому до простору імен моделі.

Якщо ваш клас колекції ресурсів не дотримується цієї угоди про іменування або розташований в іншому просторі імен, ви можете вказати колекцію ресурсів за замовчуванням для моделі за допомогою атрибута `UseResourceCollection`:

```php
<?php

namespace App\Models;

use App\Http\Resources\CustomUserCollection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Attributes\UseResourceCollection;

#[UseResourceCollection(CustomUserCollection::class)]
class User extends Model
{
    // ...
}
```

Або ж ви можете вказати клас колекції ресурсів, передавши його до методу `toResourceCollection`:

```php
return User::all()->toResourceCollection(CustomUserCollection::class);
```

<a name="custom-resource-collections"></a>
#### Власні колекції ресурсів

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

```shell
php artisan make:resource UserCollection
```

Коли клас колекції ресурсів згенеровано, ви можете легко визначити будь-які метадані, що мають потрапити до відповіді:

```php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
    /**
     * Transform the resource collection into an array.
     *
     * @return array<int|string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'data' => $this->collection,
            'links' => [
                'self' => 'link-value',
            ],
        ];
    }
}
```

Після визначення колекції ресурсів її можна повернути з маршруту чи контролера:

```php
use App\Http\Resources\UserCollection;
use App\Models\User;

Route::get('/users', function () {
    return new UserCollection(User::all());
});
```

Або, для зручності, скористайтеся методом колекції Eloquent `toResourceCollection`, який за угодами фреймворку автоматично знайде відповідну колекцію ресурсів моделі:

```php
return User::all()->toResourceCollection();
```

Під час виклику методу `toResourceCollection` Laravel спробує знайти колекцію ресурсів, ім'я якої збігається з іменем моделі та має суфікс `Collection`, у просторі імен `Http\Resources`, найближчому до простору імен моделі.

<a name="preserving-collection-keys"></a>
#### Збереження ключів колекції

Коли ви повертаєте колекцію ресурсів із маршруту, Laravel скидає ключі колекції так, щоб вони йшли в числовому порядку. Проте ви можете застосувати до класу ресурсу атрибут `PreserveKeys`, що вказує, чи слід зберігати початкові ключі колекції:

```php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Attributes\PreserveKeys;
use Illuminate\Http\Resources\Json\JsonResource;

#[PreserveKeys]
class UserResource extends JsonResource
{
    // ...
}
```

Коли властивість `preserveKeys` має значення `true`, ключі колекції зберігатимуться при поверненні колекції з маршруту чи контролера:

```php
use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/users', function () {
    return UserResource::collection(User::all()->keyBy->id);
});
```

<a name="customizing-the-underlying-resource-class"></a>
#### Налаштування базового класу ресурсу

Зазвичай властивість `$this->collection` колекції ресурсів автоматично заповнюється результатом перетворення кожного елемента колекції на його одиничний клас ресурсу. Вважається, що одиничний клас ресурсу - це ім'я класу колекції без кінцевої частини `Collection`. До того ж, залежно від ваших уподобань, одиничний клас ресурсу може мати або не мати суфікс `Resource`.

Наприклад, `UserCollection` спробує перетворити передані екземпляри користувачів на ресурс `UserResource`. Щоб змінити цю поведінку, застосуйте до колекції ресурсів атрибут `Collects`:

```php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Attributes\Collects;
use Illuminate\Http\Resources\Json\ResourceCollection;

#[Collects(Member::class)]
class UserCollection extends ResourceCollection
{
    // ...
}
```

<a name="writing-resources"></a>
## Написання ресурсів

> [!NOTE]
> Якщо ви ще не прочитали [огляд концепції](#concept-overview), наполегливо радимо зробити це, перш ніж рухатися далі.

Ресурсам потрібно лише перетворити задану модель на масив. Тож кожен ресурс містить метод `toArray`, який перекладає атрибути вашої моделі на зручний для API масив, що може бути повернений із маршрутів чи контролерів вашого застосунку:

```php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ];
    }
}
```

Коли ресурс визначено, його можна повернути прямо з маршруту чи контролера:

```php
use App\Models\User;

Route::get('/user/{id}', function (string $id) {
    return User::findOrFail($id)->toResource();
});
```

<a name="relationships"></a>
#### Зв'язки

Якщо ви хочете включити до відповіді пов'язані ресурси, додайте їх до масиву, який повертає метод `toArray` вашого ресурсу. У цьому прикладі ми скористаємося методом `collection` ресурсу `PostResource`, щоб додати до відповіді ресурсу дописи користувача:

```php
use App\Http\Resources\PostResource;
use Illuminate\Http\Request;

/**
 * Transform the resource into an array.
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'posts' => PostResource::collection($this->posts),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}
```

> [!NOTE]
> Якщо ви хочете включати зв'язки лише тоді, коли їх уже завантажено, перегляньте документацію про [умовні зв'язки](#conditional-relationships).

<a name="writing-resource-collections"></a>
#### Колекції ресурсів

Якщо ресурси перетворюють одну модель на масив, то колекції ресурсів перетворюють на масив колекцію моделей. Проте визначати клас колекції ресурсів для кожної моделі зовсім не обов'язково, адже всі колекції моделей Eloquent мають метод `toResourceCollection`, який на льоту створює «ad-hoc» колекцію ресурсів:

```php
use App\Models\User;

Route::get('/users', function () {
    return User::all()->toResourceCollection();
});
```

Однак якщо вам потрібно налаштувати метадані, що повертаються разом із колекцією, доведеться визначити власну колекцію ресурсів:

```php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
    /**
     * Transform the resource collection into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'data' => $this->collection,
            'links' => [
                'self' => 'link-value',
            ],
        ];
    }
}
```

Як і одиничні ресурси, колекції ресурсів можна повертати прямо з маршрутів чи контролерів:

```php
use App\Http\Resources\UserCollection;
use App\Models\User;

Route::get('/users', function () {
    return new UserCollection(User::all());
});
```

Або, для зручності, скористайтеся методом колекції Eloquent `toResourceCollection`, який за угодами фреймворку автоматично знайде відповідну колекцію ресурсів моделі:

```php
return User::all()->toResourceCollection();
```

Під час виклику методу `toResourceCollection` Laravel спробує знайти колекцію ресурсів, ім'я якої збігається з іменем моделі та має суфікс `Collection`, у просторі імен `Http\Resources`, найближчому до простору імен моделі.

<a name="data-wrapping"></a>
### Обгортання даних

За замовчуванням найзовнішніший ресурс обгортається в ключ `data`, коли відповідь ресурсу перетворюється на JSON. Тож типова відповідь колекції ресурсів має такий вигляд:

```json
{
    "data": [
        {
            "id": 1,
            "name": "Eladio Schroeder Sr.",
            "email": "therese28@example.com"
        },
        {
            "id": 2,
            "name": "Liliana Mayert",
            "email": "evandervort@example.com"
        }
    ]
}
```

Якщо ви хочете вимкнути обгортання найзовнішнішого ресурсу, викличте метод `withoutWrapping` на базовому класі `Illuminate\Http\Resources\Json\JsonResource`. Зазвичай цей метод викликають із `AppServiceProvider` або іншого [сервіс-провайдера](/docs/{{version}}/providers), який завантажується на кожному запиті до застосунку:

```php
<?php

namespace App\Providers;

use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        JsonResource::withoutWrapping();
    }
}
```

> [!WARNING]
> Метод `withoutWrapping` впливає лише на найзовнішнішу відповідь і не прибирає ключі `data`, які ви вручну додали до власних колекцій ресурсів.

<a name="wrapping-nested-resources"></a>
#### Обгортання вкладених ресурсів

Ви маєте цілковиту свободу визначати, як обгортаються зв'язки вашого ресурсу. Якщо ви хочете, щоб усі колекції ресурсів обгорталися в ключ `data` незалежно від рівня вкладеності, визначте клас колекції ресурсів для кожного ресурсу й повертайте колекцію всередині ключа `data`.

Ви можете замислитися, чи не призведе це до подвійного обгортання найзовнішнішого ресурсу в два ключі `data`. Не хвилюйтеся: Laravel ніколи не дозволить випадково обгорнути ваші ресурси двічі, тож про рівень вкладеності колекції ресурсів, яку ви перетворюєте, можна не турбуватися:

```php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class CommentsCollection extends ResourceCollection
{
    /**
     * Transform the resource collection into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return ['data' => $this->collection];
    }
}
```

<a name="data-wrapping-and-pagination"></a>
#### Обгортання даних і пагінація

Коли ви повертаєте колекції з пагінацією через відповідь ресурсу, Laravel обгортає дані ресурсу в ключ `data`, навіть якщо було викликано метод `withoutWrapping`. Так відбувається тому, що відповіді з пагінацією завжди містять ключі `meta` та `links` з інформацією про стан пагінатора:

```json
{
    "data": [
        {
            "id": 1,
            "name": "Eladio Schroeder Sr.",
            "email": "therese28@example.com"
        },
        {
            "id": 2,
            "name": "Liliana Mayert",
            "email": "evandervort@example.com"
        }
    ],
    "links":{
        "first": "http://example.com/users?page=1",
        "last": "http://example.com/users?page=1",
        "prev": null,
        "next": null
    },
    "meta":{
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "path": "http://example.com/users",
        "per_page": 15,
        "to": 10,
        "total": 10
    }
}
```

<a name="pagination"></a>
### Пагінація

Ви можете передати екземпляр пагінатора Laravel до методу `collection` ресурсу або до власної колекції ресурсів:

```php
use App\Http\Resources\UserCollection;
use App\Models\User;

Route::get('/users', function () {
    return new UserCollection(User::paginate());
});
```

Або, для зручності, скористайтеся методом пагінатора `toResourceCollection`, який за угодами фреймворку автоматично знайде колекцію ресурсів для моделі з пагінацією:

```php
return User::paginate()->toResourceCollection();
```

Відповіді з пагінацією завжди містять ключі `meta` та `links` з інформацією про стан пагінатора:

```json
{
    "data": [
        {
            "id": 1,
            "name": "Eladio Schroeder Sr.",
            "email": "therese28@example.com"
        },
        {
            "id": 2,
            "name": "Liliana Mayert",
            "email": "evandervort@example.com"
        }
    ],
    "links":{
        "first": "http://example.com/users?page=1",
        "last": "http://example.com/users?page=1",
        "prev": null,
        "next": null
    },
    "meta":{
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "path": "http://example.com/users",
        "per_page": 15,
        "to": 10,
        "total": 10
    }
}
```

<a name="customizing-the-pagination-information"></a>
#### Налаштування інформації про пагінацію

Якщо ви хочете налаштувати інформацію, що потрапляє до ключів `links` чи `meta` відповіді з пагінацією, визначте на ресурсі метод `paginationInformation`. Цей метод отримає дані `$paginated` і масив стандартної інформації `$default`, який містить ключі `links` та `meta`:

```php
/**
 * Customize the pagination information for the resource.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  array  $paginated
 * @param  array  $default
 * @return array
 */
public function paginationInformation($request, $paginated, $default)
{
    $default['links']['custom'] = 'https://example.com';

    return $default;
}
```

<a name="conditional-attributes"></a>
### Умовні атрибути

Іноді потрібно включати атрибут до відповіді ресурсу лише за певної умови. Наприклад, ви можете захотіти показувати значення лише тоді, коли поточний користувач - «адміністратор». Laravel надає для таких випадків набір допоміжних методів. Метод `when` дозволяє додавати атрибут до відповіді ресурсу за умовою:

```php
/**
 * Transform the resource into an array.
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'secret' => $this->when($request->user()->isAdmin(), 'secret-value'),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}
```

У цьому прикладі ключ `secret` потрапить до підсумкової відповіді ресурсу лише тоді, коли метод `isAdmin` автентифікованого користувача поверне `true`. Якщо метод поверне `false`, ключ `secret` буде видалено з відповіді ресурсу ще до того, як її буде надіслано клієнту. Метод `when` дозволяє виразно описувати ресурси, не вдаючись до умовних конструкцій під час побудови масиву.

Метод `when` також приймає замикання другим аргументом, тож ви можете обчислювати підсумкове значення лише тоді, коли задана умова істинна (`true`):

```php
'secret' => $this->when($request->user()->isAdmin(), function () {
    return 'secret-value';
}),
```

Метод `whenHas` дозволяє включити атрибут, якщо він справді присутній на моделі:

```php
'name' => $this->whenHas('name'),
```

Крім того, метод `whenNotNull` дозволяє включити атрибут до відповіді ресурсу, якщо цей атрибут не є null:

```php
'name' => $this->whenNotNull($this->name),
```

<a name="merging-conditional-attributes"></a>
#### Об'єднання умовних атрибутів

Іноді у вас є кілька атрибутів, які мають потрапляти до відповіді ресурсу за однією й тією ж умовою. У такому разі скористайтеся методом `mergeWhen`, щоб включити атрибути до відповіді лише тоді, коли задана умова істинна (`true`):

```php
/**
 * Transform the resource into an array.
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        $this->mergeWhen($request->user()->isAdmin(), [
            'first-secret' => 'value',
            'second-secret' => 'value',
        ]),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}
```

І знову: якщо задана умова хибна (`false`), ці атрибути буде видалено з відповіді ресурсу ще до надсилання клієнту.

> [!WARNING]
> Метод `mergeWhen` не слід використовувати всередині масивів, де змішані рядкові та числові ключі. Також його не варто застосовувати в масивах із числовими ключами, що йдуть не послідовно.

<a name="conditional-relationships"></a>
### Умовні зв'язки

Окрім умовного завантаження атрибутів, ви можете умовно включати до відповідей ресурсу зв'язки - залежно від того, чи вже завантажено зв'язок на моделі. Це дозволяє контролеру вирішувати, які зв'язки слід завантажити на моделі, а ресурс легко включить їх лише тоді, коли їх справді завантажено. Зрештою, так простіше уникати проблем із запитами «N+1» усередині ресурсів.

Метод `whenLoaded` дозволяє завантажувати зв'язок за умовою. Щоб не завантажувати зв'язки без потреби, цей метод приймає ім'я зв'язку, а не сам зв'язок:

```php
use App\Http\Resources\PostResource;

/**
 * Transform the resource into an array.
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'posts' => PostResource::collection($this->whenLoaded('posts')),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}
```

У цьому прикладі, якщо зв'язок не було завантажено, ключ `posts` буде видалено з відповіді ресурсу ще до надсилання клієнту.

<a name="conditional-relationship-counts"></a>
#### Умовна кількість пов'язаних записів

Окрім умовного включення зв'язків, ви можете умовно включати до відповідей ресурсу «кількість» пов'язаних записів - залежно від того, чи завантажено цю кількість на моделі:

```php
new UserResource($user->loadCount('posts'));
```

Метод `whenCounted` дозволяє умовно включити кількість пов'язаних записів до відповіді ресурсу. Цей метод уникає зайвого додавання атрибута, якщо кількості для зв'язку немає:

```php
/**
 * Transform the resource into an array.
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'posts_count' => $this->whenCounted('posts'),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}
```

У цьому прикладі, якщо кількість для зв'язку `posts` не було завантажено, ключ `posts_count` буде видалено з відповіді ресурсу ще до надсилання клієнту.

Інші види агрегатів, як-от `avg`, `sum`, `min` і `max`, також можна завантажувати за умовою за допомогою методу `whenAggregated`:

```php
'words_avg' => $this->whenAggregated('posts', 'words', 'avg'),
'words_sum' => $this->whenAggregated('posts', 'words', 'sum'),
'words_min' => $this->whenAggregated('posts', 'words', 'min'),
'words_max' => $this->whenAggregated('posts', 'words', 'max'),
```

<a name="conditional-pivot-information"></a>
#### Умовна інформація з проміжної таблиці

Окрім умовного включення інформації про зв'язки до відповідей ресурсу, ви можете умовно включати дані з проміжних таблиць зв'язків «багато до багатьох» за допомогою методу `whenPivotLoaded`. Метод `whenPivotLoaded` приймає першим аргументом ім'я проміжної таблиці. Другим аргументом має бути замикання, що повертає значення, яке слід повернути, якщо інформація з проміжної таблиці доступна на моделі:

```php
/**
 * Transform the resource into an array.
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'expires_at' => $this->whenPivotLoaded('role_user', function () {
            return $this->pivot->expires_at;
        }),
    ];
}
```

Якщо ваш зв'язок використовує [власну модель проміжної таблиці](/docs/{{version}}/eloquent-relationships#defining-custom-intermediate-table-models), ви можете передати екземпляр цієї моделі першим аргументом до методу `whenPivotLoaded`:

```php
'expires_at' => $this->whenPivotLoaded(new Membership, function () {
    return $this->pivot->expires_at;
}),
```

Якщо ваша проміжна таблиця використовує аксесор, відмінний від `pivot`, скористайтеся методом `whenPivotLoadedAs`:

```php
/**
 * Transform the resource into an array.
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'expires_at' => $this->whenPivotLoadedAs('subscription', 'role_user', function () {
            return $this->subscription->expires_at;
        }),
    ];
}
```

<a name="adding-meta-data"></a>
### Додавання метаданих

Деякі стандарти JSON API вимагають додавати метадані до відповідей ресурсів і колекцій ресурсів. Найчастіше це `links` на сам ресурс чи пов'язані ресурси або метадані про сам ресурс. Якщо вам потрібно повернути додаткові метадані про ресурс, включіть їх до методу `toArray`. Наприклад, ви можете додати інформацію `links` під час перетворення колекції ресурсів:

```php
/**
 * Transform the resource into an array.
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'data' => $this->collection,
        'links' => [
            'self' => 'link-value',
        ],
    ];
}
```

Повертаючи додаткові метадані з ресурсів, ви можете не хвилюватися про випадкове перезаписування ключів `links` чи `meta`, які Laravel автоматично додає до відповідей із пагінацією. Будь-які визначені вами додаткові `links` буде об'єднано з посиланнями, що їх надає пагінатор.

<a name="top-level-meta-data"></a>
#### Метадані верхнього рівня

Іноді потрібно включати певні метадані до відповіді ресурсу лише тоді, коли цей ресурс є найзовнішнішим із тих, що повертаються. Зазвичай це метаінформація про відповідь загалом. Щоб визначити такі метадані, додайте до класу ресурсу метод `with`. Цей метод має повертати масив метаданих, які потрапляють до відповіді ресурсу лише тоді, коли ресурс є найзовнішнішим із тих, що перетворюються:

```php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

class UserCollection extends ResourceCollection
{
    /**
     * Transform the resource collection into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return parent::toArray($request);
    }

    /**
     * Get additional data that should be returned with the resource array.
     *
     * @return array<string, mixed>
     */
    public function with(Request $request): array
    {
        return [
            'meta' => [
                'key' => 'value',
            ],
        ];
    }
}
```

<a name="adding-meta-data-when-constructing-resources"></a>
#### Додавання метаданих під час створення ресурсів

Ви також можете додавати дані верхнього рівня під час створення екземплярів ресурсів у маршруті чи контролері. Метод `additional`, доступний на всіх ресурсах, приймає масив даних, які слід додати до відповіді ресурсу:

```php
return User::all()
    ->load('roles')
    ->toResourceCollection()
    ->additional(['meta' => [
        'key' => 'value',
    ]]);
```

<a name="jsonapi-resources"></a>
## Ресурси JSON:API

Laravel постачається з `JsonApiResource` - класом ресурсу, який формує відповіді, сумісні зі [специфікацією JSON:API](https://jsonapi.org/). Він успадковує стандартний клас `JsonResource` й автоматично дбає про структуру об'єкта ресурсу, зв'язки, розріджені набори полів, includes, ліниве обчислення атрибутів, а також встановлює заголовок `Content-Type` у значення `application/vnd.api+json`.

> [!NOTE]
> Ресурси JSON:API в Laravel відповідають за серіалізацію ваших відповідей. Якщо вам також потрібно розбирати вхідні параметри запиту JSON:API, як-от фільтри та сортування, чудовим доповненням стане [Laravel Query Builder від Spatie](https://spatie.be/docs/laravel-query-builder).

<a name="generating-jsonapi-resources"></a>
### Генерування ресурсів JSON:API

Щоб згенерувати ресурс JSON:API, скористайтеся артизан-командою `make:resource` із прапорцем `--json-api`:

```shell
php artisan make:resource PostResource --json-api
```

Згенерований клас успадкує `Illuminate\Http\Resources\JsonApi\JsonApiResource` і міститиме властивості `$attributes` та `$relationships`, які вам потрібно заповнити:

```php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\JsonApi\JsonApiResource;

class PostResource extends JsonApiResource
{
    /**
     * The resource's attributes.
     */
    public $attributes = [
        // ...
    ];

    /**
     * The resource's relationships.
     */
    public $relationships = [
        // ...
    ];
}
```

Ресурси JSON:API можна повертати з маршрутів і контролерів так само, як і стандартні ресурси:

```php
use App\Http\Resources\PostResource;
use App\Models\Post;

Route::get('/api/posts/{post}', function (Post $post) {
    return new PostResource($post);
});
```

Або, для зручності, скористайтеся методом моделі `toResource`:

```php
Route::get('/api/posts/{post}', function (Post $post) {
    return $post->toResource();
});
```

Це дасть відповідь, сумісну з JSON:API:

```json
{
    "data": {
        "id": "1",
        "type": "posts",
        "attributes": {
            "title": "Hello World",
            "body": "This is my first post."
        }
    }
}
```

Щоб повернути колекцію ресурсів JSON:API, скористайтеся методом `collection` або зручним методом `toResourceCollection`:

```php
return PostResource::collection(Post::all());

return Post::all()->toResourceCollection();
```

<a name="defining-jsonapi-attributes"></a>
### Визначення атрибутів

Є два способи визначити, які атрибути потраплять до вашого ресурсу JSON:API.

Найпростіший підхід - визначити на ресурсі властивість `$attributes`. Ви можете перелічити імена атрибутів як значення, і їх буде прочитано напряму з моделі:

```php
public $attributes = [
    'title',
    'body',
    'created_at',
];
```

Якщо обчислення атрибута дороге, поверніть його з `toAttributes` як замикання - тоді його буде обчислено лише тоді, коли атрибут справді знадобиться у відповіді.

Або ж, для повного контролю над атрибутами ресурсу, перевизначте на ресурсі метод `toAttributes`:

```php
/**
 * Get the resource's attributes.
 *
 * @return array<string, mixed>
 */
public function toAttributes(Request $request): array
{
    return [
        'title' => $this->title,
        'body' => $this->body,
        'is_published' => fn () => $this->published_at !== null,
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}
```

<a name="defining-jsonapi-relationships"></a>
### Визначення зв'язків

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

#### Властивість `$relationships`

Ви можете визначити зв'язки ресурсу, доступні для включення, через властивість `$relationships`:

```php
public $relationships = [
    'author',
    'comments',
];
```

Коли ім'я зв'язку вказано як значення, Laravel визначить відповідний зв'язок Eloquent і автоматично знайде потрібний клас ресурсу. Якщо вам потрібно вказати клас ресурсу явно, визначте зв'язок як пару ключ / клас:

```php
use App\Http\Resources\UserResource;

public $relationships = [
    'author' => UserResource::class,
    'comments',
];
```

Або ж перевизначте на ресурсі метод `toRelationships`:

```php
/**
 * Get the resource's relationships.
 */
public function toRelationships(Request $request): array
{
    return [
        'author' => UserResource::class,
        'comments' => fn () => CommentResource::collection(
            $request->user()->is($this->resource)
                ? $this->comments
                : $this->comments->where('is_public', true),
        ),
    ];
}
```

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

#### Включення зв'язків

Клієнти можуть запитувати пов'язані ресурси через параметр запиту `include`:

```
GET /api/posts/1?include=author,comments
```

Це дасть відповідь з ідентифікаторами ресурсів у ключі `relationships` і повними об'єктами ресурсів у масиві `included` верхнього рівня:

```json
{
    "data": {
        "id": "1",
        "type": "posts",
        "attributes": {
            "title": "Hello World"
        },
        "relationships": {
            "author": {
                "data": {
                    "id": "1",
                    "type": "users"
                }
            },
            "comments": {
                "data": [
                    {
                        "id": "1",
                        "type": "comments"
                    }
                ]
            }
        }
    },
    "included": [
        {
            "id": "1",
            "type": "users",
            "attributes": {
                "name": "Taylor Otwell"
            }
        },
        {
            "id": "1",
            "type": "comments",
            "attributes": {
                "body": "Great post!"
            }
        }
    ]
}
```

Вкладені зв'язки можна включати через крапкову нотацію:

```
GET /api/posts/1?include=comments.author
```

<a name="jsonapi-relationship-depth"></a>
#### Глибина зв'язків

За замовчуванням включення вкладених зв'язків обмежене максимальною глибиною. Ви можете змінити це обмеження методом `maxRelationshipDepth`, зазвичай в одному із сервіс-провайдерів застосунку:

```php
use Illuminate\Http\Resources\JsonApi\JsonApiResource;

JsonApiResource::maxRelationshipDepth(3);
```

<a name="jsonapi-resource-type-and-id"></a>
### Тип та ID ресурсу

За замовчуванням `type` ресурсу виводиться з імені класу ресурсу. Наприклад, `PostResource` дає тип `posts`, а `BlogPostResource` - `blog_posts`. `id` ресурсу визначається з первинного ключа моделі.

Якщо вам потрібно змінити ці значення, перевизначте на ресурсі методи `toType` і `toId`:

```php
/**
 * Get the resource's type.
 */
public function toType(Request $request): string
{
    return 'articles';
}

/**
 * Get the resource's ID.
 */
public function toId(Request $request): string
{
    return (string) $this->uuid;
}
```

Це особливо корисно, коли тип ресурсу має відрізнятися від імені класу - наприклад, коли `AuthorResource` обгортає модель `User` і має віддавати тип `authors`.

<a name="jsonapi-sparse-fieldsets-and-includes"></a>
### Розріджені набори полів і includes

Ресурси JSON:API підтримують [розріджені набори полів](https://jsonapi.org/format/#fetching-sparse-fieldsets), що дозволяє клієнтам запитувати лише певні атрибути для кожного типу ресурсу через параметр запиту `fields`:

```
GET /api/posts?fields[posts]=title,created_at&fields[users]=name
```

Це включить лише атрибути `title` та `created_at` для ресурсів `posts` і атрибут `name` для ресурсів `users`.

<a name="jsonapi-ignoring-query-string"></a>
#### Ігнорування рядка запиту

Якщо ви хочете вимкнути фільтрацію за розрідженими наборами полів для певної відповіді ресурсу, викличте метод `ignoreFieldsAndIncludesInQueryString`:

```php
return $post->toResource()
    ->ignoreFieldsAndIncludesInQueryString();
```

<a name="jsonapi-including-previously-loaded-relationships"></a>
#### Включення раніше завантажених зв'язків

За замовчуванням зв'язки потрапляють до відповіді лише тоді, коли їх запитано через параметр запиту `include`. Якщо ви хочете включити всі раніше жадібно завантажені зв'язки незалежно від рядка запиту, викличте метод `includePreviouslyLoadedRelationships`:

```php
return $post->load('author', 'comments')
    ->toResource()
    ->includePreviouslyLoadedRelationships();
```

<a name="jsonapi-links-and-meta"></a>
### Посилання та метадані

Ви можете додати посилання й метаінформацію до об'єктів ресурсу JSON:API, перевизначивши на ресурсі методи `toLinks` і `toMeta`:

```php
/**
 * Get the resource's links.
 */
public function toLinks(Request $request): array
{
    return [
        'self' => route('api.posts.show', $this->resource),
    ];
}

/**
 * Get the resource's meta information.
 */
public function toMeta(Request $request): array
{
    return [
        'readable_created_at' => $this->created_at->diffForHumans(),
    ];
}
```

Це додасть до об'єкта ресурсу у відповіді ключі `links` та `meta`:

```json
{
    "data": {
        "id": "1",
        "type": "posts",
        "attributes": {
            "title": "Hello World"
        },
        "links": {
            "self": "https://example.com/api/posts/1"
        },
        "meta": {
            "readable_created_at": "2 hours ago"
        }
    }
}
```

<a name="resource-responses"></a>
## Відповіді ресурсів

Як ви вже прочитали, ресурси можна повертати прямо з маршрутів і контролерів:

```php
use App\Models\User;

Route::get('/user/{id}', function (string $id) {
    return User::findOrFail($id)->toResource();
});
```

Проте іноді потрібно налаштувати вихідну HTTP-відповідь, перш ніж її буде надіслано клієнту. Зробити це можна двома способами. По-перше, ви можете додати до ресурсу ланцюжком метод `response`. Цей метод поверне екземпляр `Illuminate\Http\JsonResponse`, даючи вам повний контроль над заголовками відповіді:

```php
use App\Http\Resources\UserResource;
use App\Models\User;

Route::get('/user', function () {
    return User::find(1)
        ->toResource()
        ->response()
        ->header('X-Value', 'True');
});
```

Або ж ви можете визначити метод `withResponse` усередині самого ресурсу. Цей метод буде викликано, коли ресурс повертається як найзовнішніший ресурс у відповіді:

```php
<?php

namespace App\Http\Resources;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
        ];
    }

    /**
     * Customize the outgoing response for the resource.
     */
    public function withResponse(Request $request, JsonResponse $response): void
    {
        $response->header('X-Value', 'True');
    }
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/eloquent-serialization

# Eloquent: серіалізація

- [Вступ](#introduction)
- [Серіалізація моделей і колекцій](#serializing-models-and-collections)
    - [Серіалізація в масиви](#serializing-to-arrays)
    - [Серіалізація в JSON](#serializing-to-json)
- [Приховування атрибутів у JSON](#hiding-attributes-from-json)
- [Додавання значень до JSON](#appending-values-to-json)
- [Серіалізація дат](#date-serialization)

<a name="introduction"></a>
## Вступ

Створюючи API на Laravel, ви часто перетворюватимете свої моделі та зв'язки на масиви чи JSON. Eloquent має для цього зручні методи, а також дозволяє керувати тим, які атрибути потрапляють до серіалізованого представлення ваших моделей.

> [!NOTE]
> Про ще потужніший спосіб серіалізувати моделі та колекції Eloquent у JSON читайте в документації з [ресурсів API Eloquent](/docs/{{version}}/eloquent-resources).

<a name="serializing-models-and-collections"></a>
## Серіалізація моделей і колекцій

<a name="serializing-to-arrays"></a>
### Серіалізація в масиви

Щоб перетворити модель і завантажені [зв'язки](/docs/{{version}}/eloquent-relationships) на масив, скористайтеся методом `toArray`. Метод рекурсивний, тож усі атрибути й усі зв'язки (включно зі зв'язками зв'язків) буде перетворено на масиви:

```php
use App\Models\User;

$user = User::with('roles')->first();

return $user->toArray();
```

Метод `attributesToArray` перетворює на масив атрибути моделі, але не її зв'язки:

```php
$user = User::first();

return $user->attributesToArray();
```

Ви можете також перетворити на масиви цілі [колекції](/docs/{{version}}/eloquent-collections) моделей, викликавши метод `toArray` на екземплярі колекції:

```php
$users = User::all();

return $users->toArray();
```

<a name="serializing-to-json"></a>
### Серіалізація в JSON

Щоб перетворити модель на JSON, скористайтеся методом `toJson`. Як і `toArray`, метод `toJson` рекурсивний, тож усі атрибути й зв'язки буде перетворено на JSON. Ви можете також задати будь-які опції кодування JSON, [що їх підтримує PHP](https://secure.php.net/manual/en/function.json-encode.php):

```php
use App\Models\User;

$user = User::find(1);

return $user->toJson();

return $user->toJson(JSON_PRETTY_PRINT);
```

Або ж ви можете привести модель чи колекцію до рядка - тоді метод `toJson` буде викликано автоматично:

```php
return (string) User::find(1);
```

Оскільки при приведенні до рядка моделі й колекції перетворюються на JSON, ви можете повертати об'єкти Eloquent прямо з маршрутів чи контролерів вашого застосунку. Laravel автоматично серіалізує ваші моделі та колекції Eloquent у JSON, коли їх повертають із маршруту чи контролера:

```php
Route::get('/users', function () {
    return User::all();
});
```

<a name="relationships"></a>
#### Зв'язки

Коли модель Eloquent перетворюється на JSON, її завантажені зв'язки автоматично потрапляють до JSON-об'єкта як атрибути. Крім того, хоча методи зв'язків в Eloquent описують у «camel case», атрибут зв'язку в JSON буде в «snake case».

<a name="hiding-attributes-from-json"></a>
## Приховування атрибутів у JSON

Іноді вам потрібно обмежити атрибути - наприклад, паролі, - які потрапляють до масиву чи JSON-представлення моделі. Для цього скористайтеся на моделі атрибутом `Hidden`. Атрибути, перелічені в `Hidden`, не потраплять до серіалізованого представлення вашої моделі:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Model;

#[Hidden(['password'])]
class User extends Model
{
    // ...
}
```


> [!NOTE]
> Щоб приховати зв'язки, додайте назву методу зв'язку до атрибута `Hidden` вашої моделі Eloquent.

Або ж ви можете скористатися атрибутом `Visible`, щоб описати «білий список» атрибутів, які мають потрапляти до масиву та JSON-представлення моделі. Усі атрибути, яких немає в `Visible`, буде приховано при перетворенні моделі на масив чи JSON:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Visible;
use Illuminate\Database\Eloquent\Model;

#[Visible(['first_name', 'last_name'])]
class User extends Model
{
    // ...
}
```

<a name="temporarily-modifying-attribute-visibility"></a>
#### Тимчасова зміна видимості атрибутів

Якщо ви хочете зробити видимими на конкретному екземплярі моделі кілька зазвичай прихованих атрибутів, скористайтеся методами `makeVisible` чи `mergeVisible`. Метод `makeVisible` повертає екземпляр моделі:

```php
return $user->makeVisible('attribute')->toArray();

return $user->mergeVisible(['name', 'email'])->toArray();
```

Так само, якщо ви хочете приховати кілька зазвичай видимих атрибутів, скористайтеся методами `makeHidden` чи `mergeHidden`:

```php
return $user->makeHidden('attribute')->toArray();

return $user->mergeHidden(['name', 'email'])->toArray();
```

Якщо ви хочете тимчасово перевизначити всі видимі чи приховані атрибути, скористайтеся методами `setVisible` та `setHidden` відповідно:

```php
return $user->setVisible(['id', 'name'])->toArray();

return $user->setHidden(['email', 'password', 'remember_token'])->toArray();
```

<a name="appending-values-to-json"></a>
## Додавання значень до JSON

Іноді, перетворюючи моделі на масиви чи JSON, ви хочете додати атрибути, яким не відповідає жоден стовпець у вашій базі даних. Для цього спершу опишіть для значення [аксесор](/docs/{{version}}/eloquent-mutators):

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Determine if the user is an administrator.
     */
    protected function isAdmin(): Attribute
    {
        return new Attribute(
            get: fn () => 'yes',
        );
    }
}
```

Якщо ви хочете, щоб аксесор завжди додавався до масиву та JSON-представлення моделі, скористайтеся на моделі атрибутом `Appends`. Зверніть увагу: на назви атрибутів зазвичай посилаються в їхньому серіалізованому вигляді «snake case», хоча PHP-метод аксесора описано в «camel case»:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Appends;
use Illuminate\Database\Eloquent\Model;

#[Appends(['is_admin'])]
class User extends Model
{
    // ...
}
```

Коли атрибут додано до списку `appends`, він потраплятиме і до масиву, і до JSON-представлення моделі. Атрибути з масиву `appends` також враховують налаштування `visible` та `hidden` вашої моделі.

<a name="appending-at-run-time"></a>
#### Додавання під час виконання

Під час виконання ви можете наказати екземпляру моделі додати додаткові атрибути методами `append` чи `mergeAppends`. Або ж методом `setAppends` можна перевизначити цілий масив доданих властивостей для конкретного екземпляра моделі:

```php
return $user->append('is_admin')->toArray();

return $user->mergeAppends(['is_admin', 'status'])->toArray();

return $user->setAppends(['is_admin'])->toArray();
```

Так само, якщо ви хочете прибрати з моделі всі додані властивості, скористайтеся методом `withoutAppends`:

```php
return $user->withoutAppends()->toArray();
```

<a name="date-serialization"></a>
## Серіалізація дат

<a name="customizing-the-default-date-format"></a>
#### Зміна формату дати за замовчуванням

Ви можете змінити формат серіалізації за замовчуванням, перевизначивши метод `serializeDate`. Цей метод не впливає на те, як ваші дати форматуються для зберігання в базі даних:

```php
/**
 * Prepare a date for array / JSON serialization.
 */
protected function serializeDate(DateTimeInterface $date): string
{
    return $date->format('Y-m-d');
}
```

<a name="customizing-the-date-format-per-attribute"></a>
#### Зміна формату дати для окремого атрибута

Ви можете змінити формат серіалізації окремих атрибутів дати в Eloquent, задавши формат дати в [оголошеннях приведення типів](/docs/{{version}}/eloquent-mutators#attribute-casting) моделі:

```php
protected function casts(): array
{
    return [
        'birthday' => 'date:Y-m-d',
        'joined_at' => 'datetime:Y-m-d H:00',
    ];
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/eloquent-factories

# Eloquent: фабрики

- [Вступ](#introduction)
- [Опис фабрик моделей](#defining-model-factories)
    - [Створення фабрик](#generating-factories)
    - [Стани фабрик](#factory-states)
    - [Колбеки фабрик](#factory-callbacks)
- [Створення моделей через фабрики](#creating-models-using-factories)
    - [Створення екземплярів моделей](#instantiating-models)
    - [Збереження моделей](#persisting-models)
    - [Послідовності](#sequences)
- [Зв'язки у фабриках](#factory-relationships)
    - [Зв'язки «має багато»](#has-many-relationships)
    - [Зв'язки «належить до»](#belongs-to-relationships)
    - [Зв'язки «багато до багатьох»](#many-to-many-relationships)
    - [Поліморфні зв'язки](#polymorphic-relationships)
    - [Опис зв'язків усередині фабрик](#defining-relationships-within-factories)
    - [Повторне використання наявної моделі у зв'язках](#recycling-an-existing-model-for-relationships)

<a name="introduction"></a>
## Вступ

Тестуючи застосунок чи наповнюючи базу даних, вам може знадобитися вставити в неї кілька записів. Замість того щоб вручну задавати значення кожного стовпця, Laravel дозволяє описати набір атрибутів за замовчуванням для кожної з ваших [моделей Eloquent](/docs/{{version}}/eloquent) - за допомогою фабрик моделей.

Щоб побачити приклад фабрики, погляньте на файл `database/factories/UserFactory.php` у вашому застосунку. Ця фабрика є в усіх нових застосунках Laravel і містить такий опис:

```php
namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
 */
class UserFactory extends Factory
{
    /**
     * The current password being used by the factory.
     */
    protected static ?string $password;

    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'name' => fake()->name(),
            'email' => fake()->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => static::$password ??= Hash::make('password'),
            'remember_token' => Str::random(10),
        ];
    }

    /**
     * Indicate that the model's email address should be unverified.
     */
    public function unverified(): static
    {
        return $this->state(fn (array $attributes) => [
            'email_verified_at' => null,
        ]);
    }
}
```

Як бачите, у найпростішому вигляді фабрики - це класи, що розширюють базовий клас фабрики Laravel і описують метод `definition`. Метод `definition` повертає набір значень атрибутів за замовчуванням, які слід застосувати при створенні моделі через фабрику.

Через хелпер `fake` фабрики мають доступ до PHP-бібліотеки [Faker](https://github.com/FakerPHP/Faker), яка дозволяє зручно генерувати різноманітні випадкові дані для тестування та наповнення бази.

> [!NOTE]
> Ви можете змінити локаль Faker для свого застосунку, оновивши опцію `faker_locale` у файлі конфігурації `config/app.php`.

<a name="defining-model-factories"></a>
## Опис фабрик моделей

<a name="generating-factories"></a>
### Створення фабрик

Щоб створити фабрику, виконайте [artisan-команду](/docs/{{version}}/artisan) `make:factory`:

```shell
php artisan make:factory PostFactory
```

Новий клас фабрики потрапить до каталогу `database/factories`.

<a name="factory-and-model-discovery-conventions"></a>
#### Конвенції пошуку моделей і фабрик

Коли ви описали фабрики, ви можете скористатися статичним методом `factory`, який моделі отримують від трейта `Illuminate\Database\Eloquent\Factories\HasFactory`, щоб створити екземпляр фабрики для цієї моделі.

Метод `factory` трейта `HasFactory` визначає потрібну фабрику за конвенціями. А саме: метод шукає в просторі імен `Database\Factories` фабрику, назва класу якої збігається з назвою моделі та має суфікс `Factory`. Якщо ці конвенції не підходять вашому застосунку чи фабриці, додайте до моделі атрибут `UseFactory`, щоб вказати фабрику вручну:

```php
use Illuminate\Database\Eloquent\Attributes\UseFactory;
use Database\Factories\Administration\FlightFactory;

#[UseFactory(FlightFactory::class)]
class Flight extends Model
{
    // ...
}
```

Або ж ви можете перевизначити в моделі метод `newFactory`, щоб він одразу повертав екземпляр відповідної фабрики:

```php
use Database\Factories\Administration\FlightFactory;

/**
 * Create a new factory instance for the model.
 */
protected static function newFactory()
{
    return FlightFactory::new();
}
```

Далі скористайтеся на відповідній фабриці атрибутом `UseModel`, щоб указати модель:

```php
use App\Administration\Flight;
use Illuminate\Database\Eloquent\Factories\Attributes\UseModel;
use Illuminate\Database\Eloquent\Factories\Factory;

#[UseModel(Flight::class)]
class FlightFactory extends Factory
{
    // ...
}
```

<a name="factory-states"></a>
### Стани фабрик

Методи маніпуляції станом дозволяють описати окремі зміни, які можна застосовувати до фабрик моделей у будь-яких поєднаннях. Наприклад, ваша фабрика `Database\Factories\UserFactory` може мати метод стану `suspended`, що змінює одне зі значень атрибутів за замовчуванням.

Методи перетворення стану зазвичай викликають метод `state` базового класу фабрики Laravel. Метод `state` приймає замикання, яке отримає масив сирих атрибутів, описаних для фабрики, і має повернути масив атрибутів для зміни:

```php
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * Indicate that the user is suspended.
 */
public function suspended(): Factory
{
    return $this->state(function (array $attributes) {
        return [
            'account_status' => 'suspended',
        ];
    });
}
```

<a name="trashed-state"></a>
#### Стан «у кошику»

Якщо вашу модель Eloquent можна [м'яко видаляти](/docs/{{version}}/eloquent#soft-deleting), ви можете викликати вбудований метод стану `trashed`, щоб указати: створена модель має бути вже «м'яко видаленою». Описувати стан `trashed` вручну не потрібно - він автоматично доступний усім фабрикам:

```php
use App\Models\User;

$user = User::factory()->trashed()->create();
```

<a name="factory-callbacks"></a>
### Колбеки фабрик

Колбеки фабрик реєструються методами `afterMaking` та `afterCreating` і дозволяють виконати додаткові завдання після створення моделі. Реєструйте ці колбеки в методі `configure` вашого класу фабрики. Laravel викличе цей метод автоматично при створенні екземпляра фабрики:

```php
namespace Database\Factories;

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;

class UserFactory extends Factory
{
    /**
     * Configure the model factory.
     */
    public function configure(): static
    {
        return $this->afterMaking(function (User $user) {
            // ...
        })->afterCreating(function (User $user) {
            // ...
        });
    }

    // ...
}
```

Ви можете також реєструвати колбеки фабрик у методах станів, щоб виконувати додаткові завдання, специфічні для конкретного стану:

```php
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * Indicate that the user is suspended.
 */
public function suspended(): Factory
{
    return $this->state(function (array $attributes) {
        return [
            'account_status' => 'suspended',
        ];
    })->afterMaking(function (User $user) {
        // ...
    })->afterCreating(function (User $user) {
        // ...
    });
}
```

<a name="creating-models-using-factories"></a>
## Створення моделей через фабрики

<a name="instantiating-models"></a>
### Створення екземплярів моделей

Коли ви описали фабрики, ви можете скористатися статичним методом `factory`, який моделі отримують від трейта `Illuminate\Database\Eloquent\Factories\HasFactory`, щоб створити екземпляр фабрики для цієї моделі. Погляньмо на кілька прикладів створення моделей. Спершу скористаємося методом `make`, щоб створити моделі, не зберігаючи їх у базі даних:

```php
use App\Models\User;

$user = User::factory()->make();
```

Створити колекцію з багатьох моделей можна методом `count`:

```php
$users = User::factory()->count(3)->make();
```

<a name="applying-states"></a>
#### Застосування станів

Ви можете також застосувати до моделей будь-які зі своїх [станів](#factory-states). Якщо ви хочете застосувати кілька перетворень стану, просто викличте методи перетворення один за одним:

```php
$users = User::factory()->count(5)->suspended()->make();
```

<a name="overriding-attributes"></a>
#### Перевизначення атрибутів

Якщо ви хочете перевизначити деякі значення моделей за замовчуванням, передайте методу `make` масив значень. Буде замінено лише вказані атрибути, а решта лишиться зі значеннями за замовчуванням, які задає фабрика:

```php
$user = User::factory()->make([
    'name' => 'Abigail Otwell',
]);
```

Або ж метод `state` можна викликати прямо на екземплярі фабрики, щоб виконати інлайнове перетворення стану:

```php
$user = User::factory()->state([
    'name' => 'Abigail Otwell',
])->make();
```

> [!NOTE]
> [Захист від масового призначення](/docs/{{version}}/eloquent#mass-assignment) автоматично вимикається, коли моделі створюються через фабрики.

<a name="persisting-models"></a>
### Збереження моделей

Метод `create` створює екземпляри моделей і зберігає їх у базі даних методом `save` Eloquent:

```php
use App\Models\User;

// Create a single App\Models\User instance...
$user = User::factory()->create();

// Create three App\Models\User instances...
$users = User::factory()->count(3)->create();
```

Ви можете перевизначити атрибути моделі за замовчуванням, передавши методу `create` масив атрибутів:

```php
$user = User::factory()->create([
    'name' => 'Abigail',
]);
```

<a name="sequences"></a>
### Послідовності

Іноді вам потрібно чергувати значення певного атрибута для кожної створеної моделі. Це робиться описом перетворення стану як послідовності. Наприклад, ви можете чергувати значення стовпця `admin` між `Y` і `N` для кожного створеного користувача:

```php
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Sequence;

$users = User::factory()
    ->count(10)
    ->state(new Sequence(
        ['admin' => 'Y'],
        ['admin' => 'N'],
    ))
    ->create();
```

У цьому прикладі буде створено п'ятьох користувачів зі значенням `admin`, що дорівнює `Y`, і п'ятьох зі значенням `N`.

За потреби ви можете передати як значення послідовності замикання. Його буде викликано щоразу, коли послідовності потрібне нове значення:

```php
use Illuminate\Database\Eloquent\Factories\Sequence;

$users = User::factory()
    ->count(10)
    ->state(new Sequence(
        fn (Sequence $sequence) => ['role' => UserRoles::all()->random()],
    ))
    ->create();
```

Усередині замикання послідовності ви маєте доступ до властивості `$index` екземпляра послідовності, який передається в замикання. Властивість `$index` містить кількість ітерацій послідовності, що вже відбулися:

```php
$users = User::factory()
    ->count(10)
    ->state(new Sequence(
        fn (Sequence $sequence) => ['name' => 'Name '.$sequence->index],
    ))
    ->create();
```

Для зручності послідовності можна застосовувати й методом `sequence`, який усередині просто викликає метод `state`. Метод `sequence` приймає замикання або масиви послідовних атрибутів:

```php
$users = User::factory()
    ->count(2)
    ->sequence(
        ['name' => 'First User'],
        ['name' => 'Second User'],
    )
    ->create();
```

<a name="factory-relationships"></a>
## Зв'язки у фабриках

<a name="has-many-relationships"></a>
### Зв'язки «має багато»

Далі розгляньмо побудову зв'язків моделей Eloquent за допомогою плавних методів фабрик Laravel. Спершу припустімо, що наш застосунок має моделі `App\Models\User` та `App\Models\Post`. Припустімо також, що модель `User` описує зв'язок `hasMany` з `Post`. Ми можемо створити користувача з трьома постами методом `has`, який дають фабрики Laravel. Метод `has` приймає екземпляр фабрики:

```php
use App\Models\Post;
use App\Models\User;

$user = User::factory()
    ->has(Post::factory()->count(3))
    ->create();
```

За конвенцією, коли ви передаєте методу `has` модель `Post`, Laravel вважатиме, що модель `User` має метод `posts`, який описує цей зв'язок. За потреби ви можете явно вказати назву зв'язку, з яким хочете працювати:

```php
$user = User::factory()
    ->has(Post::factory()->count(3), 'posts')
    ->create();
```

Звісно, ви можете виконувати маніпуляції станом і над пов'язаними моделями. Крім того, ви можете передати перетворення стану у вигляді замикання, якщо зміна стану потребує доступу до батьківської моделі:

```php
$user = User::factory()
    ->has(
        Post::factory()
            ->count(3)
            ->state(function (array $attributes, User $user) {
                return ['user_type' => $user->type];
            })
    )
    ->create();
```

<a name="has-many-relationships-using-magic-methods"></a>
#### Використання магічних методів

Для зручності ви можете будувати зв'язки магічними методами фабрик Laravel. Наприклад, у наведеному нижче прикладі за конвенцією буде визначено, що пов'язані моделі слід створити через метод зв'язку `posts` моделі `User`:

```php
$user = User::factory()
    ->hasPosts(3)
    ->create();
```

Створюючи зв'язки фабрик магічними методами, ви можете передати масив атрибутів, які слід перевизначити в пов'язаних моделях:

```php
$user = User::factory()
    ->hasPosts(3, [
        'published' => false,
    ])
    ->create();
```

Ви можете також передати кілька масивів атрибутів, щоб створити пов'язані моделі з різним станом для кожної. Laravel застосує кожен масив по черзі:

```php
$user = User::factory()
    ->hasPosts(
        ['title' => 'First Post'],
        ['title' => 'Second Post'],
        ['title' => 'Third Post'],
    )
    ->create();
```

Ви можете передати перетворення стану у вигляді замикання, якщо зміна стану потребує доступу до батьківської моделі:

```php
$user = User::factory()
    ->hasPosts(3, function (array $attributes, User $user) {
        return ['user_type' => $user->type];
    })
    ->create();
```

<a name="belongs-to-relationships"></a>
### Зв'язки «належить до»

Тепер, коли ми розібралися, як будувати зв'язки «має багато» через фабрики, розгляньмо обернений зв'язок. Методом `for` можна вказати батьківську модель, якій належать створені фабрикою моделі. Наприклад, ми можемо створити три екземпляри моделі `App\Models\Post`, що належать одному користувачеві:

```php
use App\Models\Post;
use App\Models\User;

$posts = Post::factory()
    ->count(3)
    ->for(User::factory()->state([
        'name' => 'Jessica Archer',
    ]))
    ->create();
```

Якщо у вас уже є екземпляр батьківської моделі, з яким слід пов'язати створювані моделі, передайте його методу `for`:

```php
$user = User::factory()->create();

$posts = Post::factory()
    ->count(3)
    ->for($user)
    ->create();
```

<a name="belongs-to-relationships-using-magic-methods"></a>
#### Використання магічних методів

Для зручності ви можете описувати зв'язки «належить до» магічними методами фабрик Laravel. Наприклад, у наведеному нижче прикладі за конвенцією буде визначено, що три пости мають належати зв'язку `user` моделі `Post`:

```php
$posts = Post::factory()
    ->count(3)
    ->forUser([
        'name' => 'Jessica Archer',
    ])
    ->create();
```

<a name="many-to-many-relationships"></a>
### Зв'язки «багато до багатьох»

Як і [зв'язки «має багато»](#has-many-relationships), зв'язки «багато до багатьох» можна створювати методом `has`:

```php
use App\Models\Role;
use App\Models\User;

$user = User::factory()
    ->has(Role::factory()->count(3))
    ->create();
```

<a name="pivot-table-attributes"></a>
#### Атрибути проміжної таблиці

Якщо вам потрібно задати атрибути проміжної (pivot) таблиці, що зв'язує моделі, скористайтеся методом `hasAttached`. Другим аргументом він приймає масив назв і значень атрибутів проміжної таблиці:

```php
use App\Models\Role;
use App\Models\User;

$user = User::factory()
    ->hasAttached(
        Role::factory()->count(3),
        ['active' => true]
    )
    ->create();
```

Ви можете передати перетворення стану у вигляді замикання, якщо зміна стану потребує доступу до пов'язаної моделі:

```php
$user = User::factory()
    ->hasAttached(
        Role::factory()
            ->count(3)
            ->state(function (array $attributes, User $user) {
                return ['name' => $user->name.' Role'];
            }),
        ['active' => true]
    )
    ->create();
```

Ви можете також передати масив масивів проміжних даних, щоб задати унікальні дані для кожної пов'язаної моделі:

```php
$user = User::factory()
    ->hasAttached(
        Role::factory(),
        [
            ['active' => true],
            ['active' => false],
        ]
    )
    ->create();
```

Якщо у вас уже є екземпляри моделей, які ви хочете приєднати до створюваних, передайте їх методу `hasAttached`. У цьому прикладі ті самі три ролі буде приєднано до всіх трьох користувачів:

```php
$roles = Role::factory()->count(3)->create();

$users = User::factory()
    ->count(3)
    ->hasAttached($roles, ['active' => true])
    ->create();
```

<a name="many-to-many-relationships-using-magic-methods"></a>
#### Використання магічних методів

Для зручності ви можете описувати зв'язки «багато до багатьох» магічними методами фабрик Laravel. Наприклад, у наведеному нижче прикладі за конвенцією буде визначено, що пов'язані моделі слід створити через метод зв'язку `roles` моделі `User`:

```php
$user = User::factory()
    ->hasRoles(1, [
        'name' => 'Editor'
    ])
    ->create();
```

<a name="polymorphic-relationships"></a>
### Поліморфні зв'язки

[Поліморфні зв'язки](/docs/{{version}}/eloquent-relationships#polymorphic-relationships) теж можна створювати через фабрики. Поліморфні зв'язки «morph many» створюються так само, як звичайні зв'язки «має багато». Наприклад, якщо модель `App\Models\Post` має зв'язок `morphMany` з моделлю `App\Models\Comment`:

```php
use App\Models\Post;

$post = Post::factory()->hasComments(3)->create();
```

<a name="morph-to-relationships"></a>
#### Зв'язки Morph To

Магічними методами не можна створювати зв'язки `morphTo`. Натомість слід напряму скористатися методом `for` і явно вказати назву зв'язку. Наприклад, уявіть, що модель `Comment` має метод `commentable`, який описує зв'язок `morphTo`. У такому разі ми можемо створити три коментарі, що належать одному посту, скориставшись методом `for` напряму:

```php
$comments = Comment::factory()->count(3)->for(
    Post::factory(), 'commentable'
)->create();
```

<a name="polymorphic-many-to-many-relationships"></a>
#### Поліморфні зв'язки «багато до багатьох»

Поліморфні зв'язки «багато до багатьох» (`morphToMany` / `morphedByMany`) створюються так само, як неполіморфні зв'язки «багато до багатьох»:

```php
use App\Models\Tag;
use App\Models\Video;

$video = Video::factory()
    ->hasAttached(
        Tag::factory()->count(3),
        ['public' => true]
    )
    ->create();
```

Звісно, магічний метод `has` теж можна використовувати для створення поліморфних зв'язків «багато до багатьох»:

```php
$video = Video::factory()
    ->hasTags(3, ['public' => true])
    ->create();
```

<a name="defining-relationships-within-factories"></a>
### Опис зв'язків усередині фабрик

Щоб описати зв'язок усередині фабрики моделі, ви зазвичай присвоюєте новий екземпляр фабрики зовнішньому ключу цього зв'язку. Так роблять для «обернених» зв'язків - як-от `belongsTo` та `morphTo`. Наприклад, якщо ви хочете створювати нового користувача разом зі створенням поста, зробіть так:

```php
use App\Models\User;

/**
 * Define the model's default state.
 *
 * @return array<string, mixed>
 */
public function definition(): array
{
    return [
        'user_id' => User::factory(),
        'title' => fake()->title(),
        'content' => fake()->paragraph(),
    ];
}
```

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

```php
/**
 * Define the model's default state.
 *
 * @return array<string, mixed>
 */
public function definition(): array
{
    return [
        'user_id' => User::factory(),
        'user_type' => function (array $attributes) {
            return User::find($attributes['user_id'])->type;
        },
        'title' => fake()->title(),
        'content' => fake()->paragraph(),
    ];
}
```

<a name="recycling-an-existing-model-for-relationships"></a>
### Повторне використання наявної моделі у зв'язках

Якщо у вас є моделі, що мають спільний зв'язок з іншою моделлю, скористайтеся методом `recycle`, щоб для всіх зв'язків, створених фабрикою, використовувався один екземпляр пов'язаної моделі.

Наприклад, уявіть, що ви маєте моделі `Airline`, `Flight` і `Ticket`, де квиток належить авіакомпанії та рейсу, а рейс теж належить авіакомпанії. Створюючи квитки, ви, найімовірніше, захочете, щоб і квиток, і рейс мали ту саму авіакомпанію, - тож передайте екземпляр авіакомпанії методу `recycle`:

```php
Ticket::factory()
    ->recycle(Airline::factory()->create())
    ->create();
```

Метод `recycle` стане особливо в пригоді, якщо ваші моделі належать спільному користувачеві чи команді.

Метод `recycle` приймає також колекцію наявних моделей. Коли ви передаєте методу `recycle` колекцію, фабрика обиратиме з неї випадкову модель щоразу, коли їй знадобиться модель цього типу:

```php
Ticket::factory()
    ->recycle($airlines)
    ->create();
```

---

Джерело: https://laravelukraine.com/docs/13.x/ai-sdk

# Laravel AI SDK

- [Вступ](#introduction)
- [Встановлення](#installation)
    - [Конфігурація](#configuration)
    - [Власні базові URL](#custom-base-urls)
    - [Провайдери, сумісні з OpenAI](#openai-compatible-providers)
    - [Провайдери на вимогу](#on-demand-providers)
    - [Підтримка провайдерів](#provider-support)
- [Агенти](#agents)
    - [Промптинг](#prompting)
    - [Контекст розмови](#conversation-context)
    - [Структурований вивід](#structured-output)
    - [Вкладення](#attachments)
    - [Стримінг](#streaming)
    - [Бродкастинг](#broadcasting)
    - [Черги](#queueing)
    - [Інструменти](#tools)
    - [Відкладене завантаження інструментів](#deferred-tool-loading)
    - [Інструменти файлового сховища](#file-storage-tools)
    - [MCP-інструменти](#mcp-tools)
    - [Навички](#skills)
    - [Інструменти провайдера](#provider-tools)
    - [Субагенти](#sub-agents)
    - [Middleware](#middleware)
    - [Анонімні агенти](#anonymous-agents)
    - [Конфігурація агента](#agent-configuration)
    - [Опції провайдера](#provider-options)
    - [Кешування промптів](#prompt-caching)
- [Схвалення інструментів людиною](#human-tool-approval)
    - [Повний потік схвалення](#complete-approval-flow)
- [Зображення](#images)
- [Аудіо (TTS)](#audio)
- [Транскрибування (STT)](#transcription)
- [Стислий переказ тексту](#text-summarization)
- [Ембединги](#embeddings)
    - [Мультимодальні ембединги](#multimodal-embeddings)
    - [Запити до ембедингів](#querying-embeddings)
    - [Кешування ембедингів](#caching-embeddings)
- [Переранжування](#reranking)
- [Класифікація](#classification)
    - [Рішення «так» чи «ні»](#yes-or-no-decisions)
    - [Вибір із колекцій](#choosing-from-collections)
- [Файли](#files)
- [Векторні сховища](#vector-stores)
    - [Додавання файлів до сховищ](#adding-files-to-stores)
- [Використання токенів](#usage)
- [Резервні провайдери](#failover)
- [Тестування](#testing)
    - [Агенти](#testing-agents)
    - [Зображення](#testing-images)
    - [Аудіо](#testing-audio)
    - [Транскрибування](#testing-transcriptions)
    - [Ембединги](#testing-embeddings)
    - [Переранжування](#testing-reranking)
    - [Класифікація](#testing-classification)
    - [Файли](#testing-files)
    - [Векторні сховища](#testing-vector-stores)
- [Події](#events)

<a name="introduction"></a>
## Вступ

[Laravel AI SDK](https://github.com/laravel/ai) дає уніфікований, виразний API для взаємодії з AI-провайдерами, як-от OpenAI, Anthropic, Gemini тощо. За допомогою AI SDK ви можете створювати розумних агентів з інструментами та структурованим виводом, генерувати зображення, синтезувати й транскрибувати аудіо, створювати векторні ембединги та багато іншого - і все це через узгоджений, дружній до Laravel інтерфейс.

<a name="installation"></a>
## Встановлення

Встановити Laravel AI SDK можна через Composer:

```shell
composer require laravel/ai
```

Далі вам слід опублікувати конфігураційний файл і файли міграцій AI SDK артизан-командою `vendor:publish`:

```shell
php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
```

Насамкінець виконайте міграції бази даних вашого застосунку. Це створить таблиці `agent_conversations` і `agent_conversation_messages`, які AI SDK використовує для зберігання розмов:

```shell
php artisan migrate
```

<a name="configuration"></a>
### Конфігурація

Ви можете визначити облікові дані своїх AI-провайдерів у конфігураційному файлі `config/ai.php` вашого застосунку або як змінні оточення у файлі `.env`:

```ini
ANTHROPIC_API_KEY=
AZURE_OPENAI_API_KEY=
COHERE_API_KEY=
DEEPSEEK_API_KEY=
ELEVENLABS_API_KEY=
GEMINI_API_KEY=
GROQ_API_KEY=
MISTRAL_API_KEY=
OLLAMA_API_KEY=
OPENAI_API_KEY=
OPENAI_COMPATIBLE_API_KEY=
OPENAI_COMPATIBLE_URL=
OPENROUTER_API_KEY=
JINA_API_KEY=
TYPESAFE_API_KEY=
VOYAGEAI_API_KEY=
XAI_API_KEY=
```

Моделі за замовчуванням для тексту, зображень, аудіо, транскрибування та ембедингів також можна налаштувати в конфігураційному файлі `config/ai.php` вашого застосунку.

<a name="custom-base-urls"></a>
### Власні базові URL

За замовчуванням Laravel AI SDK підключається безпосередньо до публічного API-ендпоїнта кожного провайдера. Однак вам може знадобитися спрямувати запити через інший ендпоїнт - наприклад, коли ви користуєтеся проксі-сервісом для централізованого керування API-ключами, реалізуєте обмеження частоти чи спрямовуєте трафік через корпоративний шлюз.

Ви можете налаштувати власні базові URL, додавши параметр `url` до конфігурації свого провайдера:

```php
'providers' => [
    'openai' => [
        'driver' => 'openai',
        'key' => env('OPENAI_API_KEY'),
        'url' => env('OPENAI_URL'),
    ],

    'anthropic' => [
        'driver' => 'anthropic',
        'key' => env('ANTHROPIC_API_KEY'),
        'url' => env('ANTHROPIC_BASE_URL'),
    ],
],
```

Це корисно, коли ви спрямовуєте запити через проксі-сервіс (як-от LiteLLM чи Azure OpenAI Gateway) або використовуєте альтернативні ендпоїнти.

Власні базові URL підтримуються для таких провайдерів: OpenAI, Anthropic, Gemini, Groq, Cohere, DeepSeek, xAI та OpenRouter.

<a name="openai-compatible-providers"></a>
### Провайдери, сумісні з OpenAI

Якщо ви користуєтеся API, сумісним з OpenAI, як-от LM Studio, vLLM, Together, Fireworks чи локальним шлюзом, ви можете налаштувати провайдер `openai-compatible`. Опція `url` обов'язкова, а опція `key` необов'язкова і, якщо присутня, надсилатиметься як bearer-токен:

```php
'providers' => [
    'local' => [
        'driver' => 'openai-compatible',
        'url' => env('LOCAL_AI_URL'),
        'key' => env('LOCAL_AI_API_KEY'),
    ],
],
```

Щойно провайдер налаштовано, ви можете використовувати його за іменем, як і будь-який інший:

```php
agent()->prompt('What is Laravel?', provider: 'local', model: 'local-model');
```

Ви також можете налаштувати для провайдера текстову модель за замовчуванням, щоб не передавати модель явно:

```php
'local' => [
    'driver' => 'openai-compatible',
    'url' => env('LOCAL_AI_URL'),
    'key' => env('LOCAL_AI_API_KEY'),
    'models' => [
        'text' => [
            'default' => env('LOCAL_AI_MODEL'),
        ],
    ],
],
```

Ви можете додати власні HTTP-заголовки до кожного вихідного запиту провайдера, визначивши масив `headers` у його конфігурації. Це корисно, коли ендпоїнт потребує додаткового ідентифікаційного або автентифікаційного заголовка на додачу до bearer-токена:

```php
'local' => [
    'driver' => 'openai-compatible',
    'url' => env('LOCAL_AI_URL'),
    'key' => env('LOCAL_AI_API_KEY'),
    'headers' => [
        'X-Tenant-Id' => env('LOCAL_AI_TENANT_ID'),
    ],
],
```

Провайдери, сумісні з OpenAI, підтримують генерацію тексту, стримінг, інструменти, структурований вивід, вкладення зображень, ембединги та транскрипцію. Якщо ваш ендпоїнт потребує додаткових полів у тілі запиту, передайте їх через [опції провайдера](#provider-options).

<a name="openai-compatible-embeddings"></a>
#### Ембединги для провайдерів, сумісних з OpenAI

Оскільки довільні ендпоїнти не мають відомих моделей, для використання `embeddings()` із провайдером, сумісним з OpenAI, ви повинні налаштувати модель ембедингів за замовчуванням. Ви також можете задати фіксоване значення `dimensions`; якщо його не вказано, запит надсилається без параметра `dimensions` і використовується власна розмірність моделі.

```php
'local' => [
    'driver' => 'openai-compatible',
    'url' => env('LOCAL_AI_URL'),
    'key' => env('LOCAL_AI_API_KEY'),
    'models' => [
        'embeddings' => [
            'default' => 'text-embedding-qwen3-embedding-0.6b',
            'dimensions' => 1024, // необовʼязково
        ],
    ],
],
```

<a name="openai-compatible-transcriptions"></a>
#### Транскрипції, сумісні з OpenAI

Так само ви маєте налаштувати модель транскрипції за замовчуванням, щоб використовувати `Transcription` з OpenAI-сумісним провайдером. Аудіо буде завантажено на маршрут `/audio/transcriptions` ендпоінту як стандартний multipart-запит:

```php
'local' => [
    'driver' => 'openai-compatible',
    'url' => env('LOCAL_AI_URL'),
    'key' => env('LOCAL_AI_API_KEY'),
    'models' => [
        'transcription' => [
            'default' => 'whisper-1',
        ],
    ],
],
```

> [!NOTE]
> OpenAI-сумісні провайдери та Groq не підтримують діаризацію. Виклик методу `diarize` при використанні цих провайдерів призведе до виключення.

<a name="on-demand-providers"></a>
### Провайдери на вимогу

Іноді потрібно використати облікові дані провайдера, яких немає в конфігураційному файлі застосунку, - наприклад, API-ключі, що зберігаються в базі даних окремо для кожного орендаря в застосунку з кількома орендарями (multi-tenant). Метод `Ai::build` створює провайдер із масиву конфігурації. Масив має ту саму структуру, що й запис провайдера в конфігураційному файлі `config/ai.php` вашого застосунку:

```php
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Ai;

$response = (new SalesCoach)->prompt('Analyze this sales transcript...', provider: [
    Ai::build([
        'driver' => 'anthropic',
        'key' => $user->anthropic_key,
    ]),
]);
```

Провайдери на вимогу можна використовувати всюди, де приймається провайдер, зокрема в списках [резервних провайдерів](#failover) і під час генерації зображень, аудіо, транскрипцій та ембедингів. Щоб задати модель для провайдера на вимогу, додайте до його конфігурації масив `models`:

```php
$response = (new SalesCoach)->prompt('Analyze this sales transcript...', provider: [
    Ai::build(['driver' => 'anthropic', 'key' => $user->anthropic_key]),
    Ai::build([
        'driver' => 'openai',
        'key' => $user->openai_key,
        'models' => ['text' => ['default' => 'gpt-6']],
    ]),
]);
```

Якщо агент завжди має використовувати провайдер на вимогу, поверніть його з методу `provider` агента. Провайдер створюється заново щоразу, коли агента використовують, тому такий підхід добре працює й для агентів [у черзі](#queueing):

```php
use Laravel\Ai\Ai;
use Laravel\Ai\Providers\Provider;

public function provider(): Provider
{
    return Ai::build($this->user->aiConfiguration());
}
```

> [!NOTE]
> Провайдери на вимогу слід передавати в масиві, як у прикладах вище. Якщо в масиві конфігурації ви задаєте провайдеру `name`, це ім'я не може збігатися з вбудованим провайдером чи провайдером, визначеним у конфігураційному файлі `config/ai.php`.

<a name="provider-support"></a>
### Підтримка провайдерів

AI SDK підтримує різні провайдери для своїх можливостей. Наведена нижче таблиця підсумовує, які провайдери доступні для кожної можливості:

<div class="overflow-auto">

| Feature | Providers |
|---|---|
| Text | OpenAI, OpenAI Compatible, Anthropic, Gemini, Azure, Bedrock, Groq, xAI, DeepSeek, Mistral, Ollama, OpenRouter |
| Images | OpenAI, Gemini, xAI, Azure, Bedrock, OpenRouter |
| TTS | OpenAI, ElevenLabs, Gemini, Mistral, OpenRouter |
| STT | OpenAI, OpenAI Compatible, ElevenLabs, Groq, Mistral, Gemini, OpenRouter |
| Embeddings | OpenAI, OpenAI Compatible, Gemini, Azure, Bedrock, Cohere, Mistral, Jina, VoyageAI, Ollama, OpenRouter |
| Reranking | Cohere, Jina, VoyageAI, Bedrock, OpenRouter |
| Classification | TypeSafe, OpenRouter |
| Files | OpenAI, Anthropic, Gemini, Azure, OpenRouter |

</div>

Enum `Laravel\Ai\Enums\Lab` можна використовувати для посилання на провайдерів у вашому коді замість звичайних рядків:

```php
use Laravel\Ai\Enums\Lab;

Lab::Anthropic;
Lab::OpenAI;
Lab::OpenAiCompatible;
Lab::Gemini;
// ...
```

<a name="agents"></a>
## Агенти

Агенти - це фундаментальний будівельний блок для взаємодії з AI-провайдерами в Laravel AI SDK. Кожен агент - це окремий PHP-клас, що інкапсулює інструкції, контекст розмови, інструменти й схему виводу, потрібні для взаємодії з великою мовною моделлю. Уявіть агента як спеціалізованого асистента - тренера з продажів, аналізатора документів, бота підтримки, - якого ви налаштовуєте один раз і промптите за потреби у своєму застосунку.

Створити агента можна артизан-командою `make:agent`:

```shell
php artisan make:agent SalesCoach

php artisan make:agent SalesCoach --structured
```

У згенерованому класі агента ви можете визначити системний промпт / інструкції, контекст повідомлень, доступні інструменти й схему виводу (якщо потрібно):

```php
<?php

namespace App\Ai\Agents;

use App\Ai\Tools\RetrievePreviousTranscripts;
use App\Models\History;
use App\Models\User;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Messages\Message;
use Laravel\Ai\Promptable;
use Stringable;

class SalesCoach implements Agent, Conversational, HasTools, HasStructuredOutput
{
    use Promptable;

    public function __construct(public User $user) {}

    /**
     * Get the instructions that the agent should follow.
     */
    public function instructions(): Stringable|string
    {
        return 'You are a sales coach, analyzing transcripts and providing feedback and an overall sales strength score.';
    }

    /**
     * Get the list of messages comprising the conversation so far.
     */
    public function messages(): iterable
    {
        return History::where('user_id', $this->user->id)
            ->latest()
            ->limit(50)
            ->get()
            ->reverse()
            ->map(function ($message) {
                return new Message($message->role, $message->content);
            })->all();
    }

    /**
     * Get the tools available to the agent.
     *
     * @return Tool[]
     */
    public function tools(): iterable
    {
        return [
            new RetrievePreviousTranscripts,
        ];
    }

    /**
     * Get the agent's structured output schema definition.
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'feedback' => $schema->string()->required(),
            'score' => $schema->integer()->min(1)->max(10)->required(),
        ];
    }
}
```

<a name="prompting"></a>
### Промптинг

Щоб надіслати промпт агенту, спершу створіть екземпляр методом `make` чи звичайним інстанціюванням, а потім викличте `prompt`:

```php
$response = (new SalesCoach)
    ->prompt('Analyze this sales transcript...');

return (string) $response;
```

Метод `make` створює вашого агента через контейнер, що дозволяє автоматичне впровадження залежностей. Ви також можете передати аргументи до конструктора агента:

```php
$agent = SalesCoach::make(user: $user);
```

Передавши до методу `prompt` додаткові аргументи, ви можете перевизначити провайдера, модель чи HTTP-тайм-аут за замовчуванням під час промптингу:

```php
$response = (new SalesCoach)->prompt(
    'Analyze this sales transcript...',
    provider: Lab::Anthropic,
    model: 'claude-sonnet-5',
    timeout: 120,
);
```

<a name="raw-http-responses"></a>
#### Сирі HTTP-відповіді

Кожна відповідь, яку повертає агент генерації тексту, надає через властивість `raw` сиру HTTP-відповідь від виклику API провайдера. Це дає доступ до специфічної для провайдера інформації, якої немає в узагальненій відповіді AI SDK - заголовків ліміту запитів, ідентифікаторів запитів чи інших точних полів payload:

```php
$response = (new SalesCoach)->prompt('Analyze this sales transcript...');

$response->raw; // Illuminate\Http\Client\Response|null

$response->raw->header('X-RateLimit-Remaining-Requests');
$response->raw->json('id');
```

У циклі виклику інструментів кожен крок зберігає сиру відповідь власного запиту:

```php
foreach ($response->steps as $step) {
    $step->raw?->header('X-RateLimit-Remaining-Requests');
}
```

> **Примітка:** Властивість `raw` має значення `null` під час стримінгу відповіді, при використанні провайдера Bedrock (який виконує виклики API через AWS SDK замість HTTP-клієнта), а також для підроблених відповідей, якщо її не надано явно через `withRawResponse`.

<a name="conversation-context"></a>
### Контекст розмови

Якщо ваш агент реалізує інтерфейс `Conversational`, ви можете скористатися методом `messages`, щоб повернути попередній контекст розмови, якщо він є:

```php
/**
 * Get the list of messages comprising the conversation so far.
 */
public function messages(): iterable
{
    return $this->user->history()
        ->latest()
        ->limit(50)
        ->get()
        ->reverse()
        ->map(fn ($message) => new Message(
            $message->role, $message->content,
        ))->all();
}
```

Якщо ваш агент не реалізує інтерфейс `Conversational`, ви можете передати історію розмови для одного запуску методом `withMessages` - наприклад, історію, яку надіслав фронтенд вашого застосунку:

```php
use Laravel\Ai\Messages\Message;

$response = (new SalesCoach)
    ->withMessages([
        new Message('user', 'Analyze this sales transcript...'),
        new Message('assistant', 'The rep never asked for the close.'),
    ])
    ->prompt('What should they say next time?');
```

Агенти, що реалізують інтерфейс `Conversational`, самі завантажують свою історію, тож поєднання цих двох підходів викидає `LogicException`.

<a name="remembering-conversations"></a>
#### Запам'ятовування розмов

> **Warning:** Перш ніж користуватися трейтом `RemembersConversations`, вам слід опублікувати й виконати міграції AI SDK артизан-командою `vendor:publish`. Ці міграції створять потрібні таблиці бази даних для зберігання розмов.

Якщо ви хочете, щоб Laravel автоматично зберігав і завантажував історію розмов вашого агента, скористайтеся трейтом `RemembersConversations`. Цей трейт дає простий спосіб зберігати повідомлення розмови в базі даних без ручної реалізації інтерфейсу `Conversational`:

```php
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Concerns\RemembersConversations;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Promptable;

class SalesCoach implements Agent, Conversational
{
    use Promptable, RemembersConversations;

    /**
     * Get the instructions that the agent should follow.
     */
    public function instructions(): string
    {
        return 'You are a sales coach...';
    }
}
```

Використовуючи трейт `RemembersConversations`, не визначайте метод `messages` у класі агента вручну. Якщо метод `messages` присутній, він матиме перевагу над реалізацією трейта, і історія розмови не завантажуватиметься з бази даних.

Щоб розпочати нову розмову для користувача, викличте метод `forUser` перед промптингом:

```php
$response = (new SalesCoach)->forUser($user)->prompt('Hello!');

$conversationId = $response->conversationId;
```

ID розмови повертається у відповіді, і його можна зберегти для подальшого використання. Якщо ви хочете отримувати всі розмови користувача через Eloquent, додайте трейт `HasConversations` до своєї моделі користувача:

```php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Ai\Concerns\HasConversations;

class User extends Authenticatable
{
    use HasConversations;
}
```

Щойно трейт додано до вашої моделі, ви можете отримувати розмови користувача й робити до них запити через зв'язок `conversations`:

```php
$conversations = $user->conversations()
    ->latest('updated_at')
    ->paginate(20);
```

Щоб продовжити наявну розмову, скористайтеся методом `continue`:

```php
$response = (new SalesCoach)
    ->continue($conversationId, as: $user)
    ->prompt('Tell me more about that.');
```

Метод `continueOrStart` продовжує вказану розмову або починає нову, якщо переданий ID дорівнює `null`:

```php
$response = (new SalesCoach)
    ->continueOrStart($conversationId, as: $user)
    ->prompt('Hello!');
```

Використовуючи трейт `RemembersConversations`, попередні повідомлення автоматично завантажуються й додаються до контексту розмови під час промптингу. Нові повідомлення (як користувача, так і асистента) автоматично зберігаються після кожної взаємодії. Кожна відповідь також містить ID розмови та збережених повідомлень:

```php
$response->conversationId;
$response->userMessageId;
$response->assistantMessageId;
```

<a name="conversation-participants"></a>
#### Учасники розмови

Хоча користувачі - найпоширеніші учасники розмов, розмови можуть належати будь-якій Eloquent-моделі. Скористайтеся методом `forParticipant`, щоб розпочати розмову для іншого типу моделі:

```php
$response = (new SalesCoach)
    ->forParticipant($team)
    ->prompt('Review our latest sales results.');
```

Morph-клас і первинний ключ учасника зберігаються разом з розмовою. Тому моделі різних типів з однаковим первинним ключем, як-от `User` з ID `1` і `Team` з ID `1`, мають окремі історії розмов. Метод `forUser` є аліасом до `forParticipant`.

Ви можете продовжити найновішу розмову учасника з агентом методом `continueLastConversation`. Розмови прив'язані до агента, тож продовжено буде лише ту розмову, у якій цей агент брав участь:

```php
$response = (new SalesCoach)
    ->continueLastConversation($team)
    ->prompt('Tell me more about that.');
```

Продовжуючи конкретну розмову, передайте учасника до методу `continue`:

```php
$response = (new SalesCoach)
    ->continue($conversationId, as: $team)
    ->prompt('Tell me more about that.');
```

Трейт `HasConversations` можна додати до будь-якої Eloquent-моделі, що бере участь у розмовах. Отриманий зв'язок `conversations` є поліморфним зв'язком, обмеженим типом і первинним ключем цієї моделі. Ви також можете звернутися до учасника, якому належить розмова, через зворотний зв'язок:

```php
$conversations = $team->conversations;

$participant = $conversation->participant;
```

Якщо ваш застосунок використовує кілька типів моделей-учасників, вам варто визначити [morph-мапу Eloquent](/docs/{{version}}/eloquent-relationships#custom-polymorphic-types), щоб збережені типи учасників не були прив'язані до імен ваших класів моделей.

> [!WARNING]
> Методи `continue` і `continueOrStart` не перевіряють, чи заданий учасник володіє розмовою. Ваш застосунок має авторизувати доступ до розмови, перш ніж продовжувати її.

<a name="inspecting-stored-conversations"></a>
#### Перегляд збережених розмов

Щоб показати розмову користувачам, зазвичай потрібні подробиці на кшталт ID повідомлень, часових позначок і викликів інструментів. Ви можете отримати сховище розмов із сервіс-контейнера й читати збережені повідомлення, не звертаючись напряму до таблиць AI SDK:

```php
use Laravel\Ai\Contracts\ConversationStore;

$store = app(ConversationStore::class);
```

Повідомлення розбиваються на сторінки від найновіших за допомогою курсора й повертаються як екземпляри `StoredMessage`, що містять ID кожного повідомлення, часові позначки, використання токенів, метадані й вкладення:

```php
$messages = $store->paginateConversationMessages($conversationId, perPage: 25);

foreach ($messages as $message) {
    $message->id;
    $message->role;
    $message->content;
    $message->createdAt;
    $message->usage;
    $message->status;
}
```

Кожен хід - промпт користувача разом із відповіддю асистента - зберігається як список кроків. Крок - це один запит до провайдера, тож хід, у якому модель викликає інструменти, міститиме кілька кроків. Кожен результат інструмента записується на виклику інструмента, що його отримав. Методи `toolCalls`, `providerToolCalls` і `toolResults` послідовно розгортають ці кроки в плоский список, тож обходити їх самостійно не доведеться:

```php
$message->steps;

$message->toolCalls();
$message->providerToolCalls();
$message->toolResults();
```

Виклик інструмента містить `result`, щойно його виконано. Виклики інструментів, що мають `approval_reason`, але не мають `result`, досі чекають на [схвалення інструмента](#human-tool-approval).

Властивість `status` містить екземпляр `Laravel\Ai\Enums\MessageStatus`. Хід, що впав на півдорозі, зберігається зі статусом `Failed` разом із кроками, які вже встигли завершитися, тож виклики інструментів, виконані до збою, лишаються в історії. Коли розмова продовжується, кожен виклик інструмента без записаного результату надсилається моделі з позначкою «перервано», адже Laravel не може визначити, чи він виконався.

Перш ніж продовжувати розмову за ID, який надав фронтенд вашого застосунку, перевірте, що розмову збережено саме для цього учасника:

```php
abort_unless($store->conversationBelongsTo(
    $conversationId, $user->getMorphClass(), $user->getKey()
), 403);
```

Якщо останній хід призупинено в очікуванні [схвалення інструмента](#human-tool-approval), ви можете після перезавантаження сторінки відрендерити його очікувані виклики інструментів, не відновлюючи запуск:

```php
foreach ($store->pendingApprovalsFor($conversationId) as $approval) {
    // $approval->id, $approval->tool, $approval->arguments, $approval->reason...
}
```

Ці методи визначено контрактами `PaginatesConversations`, `VerifiesConversationOwnership` і `ResolvesPendingApprovals`. Вбудоване сховище в базі даних реалізує всі три, а власне сховище може реалізувати лише ті контракти, які йому потрібні.

<a name="structured-output"></a>
### Структурований вивід

Якщо ви хочете, щоб ваш агент повертав структурований вивід, реалізуйте інтерфейс `HasStructuredOutput`, який вимагає, щоб агент визначив метод `schema`:

```php
<?php

namespace App\Ai\Agents;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;

class SalesCoach implements Agent, HasStructuredOutput
{
    use Promptable;

    // ...

    /**
     * Get the agent's structured output schema definition.
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'score' => $schema->integer()->required(),
        ];
    }
}
```

Промптуючи агента, який повертає структурований вивід, ви можете звертатися до отриманого `StructuredAgentResponse` як до масиву:

```php
$response = (new SalesCoach)->prompt('Analyze this sales transcript...');

return $response['score'];
```

<a name="structured-output-nested-objects"></a>
#### Вкладені об'єкти

Щоб визначити вкладений структурований вивід, скористайтеся методом `object` із замиканням:

```php
<?php

namespace App\Ai\Agents;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;

class SalesCoach implements Agent, HasStructuredOutput
{
    use Promptable;

    // ...

    /**
     * Get the agent's structured output schema definition.
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'score' => $schema->integer()->required(),
            'metadata' => $schema->object(fn ($schema) => [
                'confidence' => $schema->string()->enum(['low', 'medium', 'high'])->required(),
                'language' => $schema->string()->required(),
            ])->required(),
        ];
    }
}
```

<a name="structured-output-arrays-of-objects"></a>
#### Масиви об'єктів

Якщо ваш агент має повертати список структурованих елементів, поєднайте методи `array` та `object`:

```php
public function schema(JsonSchema $schema): array
{
    return [
        'feedback' => $schema->array()
            ->items(
                $schema->object(fn ($schema) => [
                    'comment' => $schema->string()->required(),
                    'score' => $schema->integer()->required(),
                ])
            )
            ->required(),
    ];
}
```

Якщо значення може відповідати одній з кількох схем, скористайтеся методом `anyOf`:

```php
public function schema(JsonSchema $schema): array
{
    return [
        'content' => $schema->anyOf([
            $schema->object(fn ($schema) => [
                'type' => $schema->string()->enum(['article'])->required(),
                'title' => $schema->string()->required(),
            ]),
            $schema->object(fn ($schema) => [
                'type' => $schema->string()->enum(['image'])->required(),
                'url' => $schema->string()->required(),
            ]),
        ])->required(),
    ];
}
```

<a name="attachments"></a>
### Вкладення

Промптуючи, ви також можете передати разом з промптом вкладення, щоб модель могла оглянути зображення й документи:

```php
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Files;

$response = (new SalesCoach)->prompt(
    'Analyze the attached sales transcript...',
    attachments: [
        Files\Document::fromStorage('transcript.pdf'), // Attach a document from a filesystem disk...
        Files\Document::fromPath('/home/laravel/transcript.md'), // Attach a document from a local path...
        $request->file('transcript'), // Attach an uploaded file...
    ]
);
```

Так само клас `Laravel\Ai\Files\Image` можна використати, щоб прикріпити зображення до промпта:

```php
use App\Ai\Agents\ImageAnalyzer;
use Laravel\Ai\Files;

$response = (new ImageAnalyzer)->prompt(
    'What is in this image?',
    attachments: [
        Files\Image::fromStorage('photo.jpg'), // Attach an image from a filesystem disk...
        Files\Image::fromPath('/home/laravel/photo.jpg'), // Attach an image from a local path...
        $request->file('photo'), // Attach an uploaded file...
    ]
);
```

<a name="streaming"></a>
### Стримінг

Ви можете стримити відповідь агента, викликавши метод `stream`. Повернутий `StreamableAgentResponse` можна повернути з маршруту, щоб автоматично надіслати клієнту потокову відповідь (SSE):

```php
use App\Ai\Agents\SalesCoach;

Route::get('/coach', function () {
    return (new SalesCoach)->stream('Analyze this sales transcript...');
});
```

Метод `then` можна використати, щоб передати замикання, яке буде викликано, коли всю відповідь буде передано клієнту:

```php
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Responses\StreamedAgentResponse;

Route::get('/coach', function () {
    return (new SalesCoach)
        ->stream('Analyze this sales transcript...')
        ->then(function (StreamedAgentResponse $response) {
            // $response->text, $response->events, $response->usage...
        });
});
```

Як альтернативу ви можете вручну проходити по потокових подіях:

```php
$stream = (new SalesCoach)->stream('Analyze this sales transcript...');

foreach ($stream as $event) {
    // ...
}
```

Відповідь також містить міркування моделі та джерела, на які вона послалася. Якщо використовується трейт `RemembersConversations`, і те, і інше зберігається разом із повідомленням асистента:

```php
use Laravel\Ai\Responses\StreamedAgentResponse;

(new SalesCoach)
    ->stream('Analyze this sales transcript...')
    ->then(function (StreamedAgentResponse $response) {
        $response->reasoning; // '' unless the model returned reasoning text...
        $response->meta->citations;
    });
```

Міркування доступні й у відповідях, які повертає метод `prompt`.

<a name="streaming-using-the-vercel-ai-sdk-protocol"></a>
<a name="stream-protocols"></a>
#### Протоколи стримінгу

За замовчуванням потокові відповіді використовують власний формат подій AI SDK. Втім, замість нього ви можете використати фронтенд-протокол стримінгу - тоді агента можна поєднати з готовим чат-інтерфейсом, а не будувати власний.

Ви можете стримити події за [протоколом стримінгу Vercel AI SDK](https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol), викликавши метод `usingVercelDataProtocol` на потоковій відповіді:

```php
use App\Ai\Agents\SalesCoach;

Route::get('/coach', function () {
    return (new SalesCoach)
        ->stream('Analyze this sales transcript...')
        ->usingVercelDataProtocol();
});
```

Ви можете передати ID повідомлення, якщо фронтенд вашого застосунку призначає власний:

```php
->usingVercelDataProtocol($request->string('messageId'));
```

Альтернативно, метод `usingAgentUserInteractionProtocol` дозволяє стримити за [протоколом Agent User Interaction (AG-UI)](https://docs.ag-ui.com):

```php
Route::post('/coach', function (Request $request) {
    return (new SalesCoach)
        ->forUser($request->user())
        ->stream($request->string('prompt'))
        ->usingAgentUserInteractionProtocol();
});
```

Аргументи `threadId` і `runId` необов'язкові; за замовчуванням вони дорівнюють ID розмови та ID виклику:

```php
->usingAgentUserInteractionProtocol(
    threadId: $request->input('threadId'),
    runId: $request->input('runId'),
);
```

Щоб використати протокол, якого AI SDK не реалізує, передайте власну реалізацію `Laravel\Ai\Streaming\Protocols\StreamProtocol` у метод `usingProtocol`:

```php
use App\Ai\Protocols\CustomProtocol;

return (new SalesCoach)
    ->stream('Analyze this sales transcript...')
    ->usingProtocol(new CustomProtocol);
```

<a name="chat-requests"></a>
<a name="frontend-integration"></a>
#### Інтеграція з фронтендом

Чат-інтерфейси, побудовані на бібліотеках на кшталт `useChat` від Vercel чи CopilotKit, уже вміють рендерити повідомлення, виклики інструментів і запити на схвалення, тож вашому застосунку лишається лише обробляти запити, які вони надсилають. Кожен такий запит містить історію розмови, найновіше повідомлення користувача та відповіді на запити схвалення інструментів.

Методи `Vercel::chat` і `AgentUserInteraction::chat` перетворюють такий запит на об'єкт, який можна передати безпосередньо в метод агента `stream`:

```php
use Laravel\Ai\Vercel\Vercel;

Route::post('/chat', function (Request $request) {
    $chat = Vercel::chat($request);

    return (new SupportAgent)
        ->withMessages($chat->history())
        ->stream($chat)
        ->usingProtocol($chat->protocol());
});
```

Якщо запит містить [рішення щодо схвалення](#human-tool-approval), агент відновить роботу з цими рішеннями. Інакше агенту передається промптом найновіше повідомлення користувача з запиту та його вкладення. Метод `protocol` повертає протокол, який використовує клієнт.

> [!NOTE]
> Агенти, що реалізують інтерфейс `Conversational`, самі завантажують свою історію, тож метод `withMessages` можна пропустити.

Метод `AgentUserInteraction::chat` надає той самий API для клієнтів AG-UI, а також ID потоку й запуску з запиту:

```php
use Laravel\Ai\AgentUserInteraction\AgentUserInteraction;

$chat = AgentUserInteraction::chat($request);

$chat->threadId();
$chat->runId();
```

Ви також можете перетворити збережені повідомлення назад у формат, якого очікує клієнт, - тоді клієнт зможе показати попередню розмову, наприклад після перезавантаження сторінки:

```php
$messages = $conversation->messages()->oldest()->get();

return ['messages' => Vercel::toUiMessages($messages)];
```

Метод `AgentUserInteraction::toClientState` робить таке саме перетворення для клієнтів AG-UI, а крім того повертає переривання для очікуваних схвалень:

```php
return AgentUserInteraction::toClientState($messages);
```

<a name="broadcasting"></a>
### Бродкастинг

Ви можете бродкастити потокові події кількома способами. По-перше, ви можете просто викликати метод `broadcast` чи `broadcastNow` на потоковій події:

```php
use App\Ai\Agents\SalesCoach;
use Illuminate\Broadcasting\Channel;

$stream = (new SalesCoach)->stream('Analyze this sales transcript...');

foreach ($stream as $event) {
    $event->broadcast(new Channel('channel-name'));
}
```

Або ж ви можете викликати метод `broadcastOnQueue` агента, щоб поставити операцію агента в чергу й бродкастити потокові події, щойно вони стають доступними:

```php
(new SalesCoach)->broadcastOnQueue(
    'Analyze this sales transcript...',
    new Channel('channel-name'),
);
```

<a name="skipping-oversized-events"></a>
#### Пропуск завеликих подій

Деякі платформи бродкастингу обмежують WebSocket-повідомлення приблизно 10 КБ. Насичені даними потокові події, як-от великі результати інструментів, можуть перевищити це обмеження і зламати бродкастинг. Ви можете виключити конкретні типи подій з бродкастингу атрибутом `WithoutBroadcasting`:

```php
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Attributes\WithoutBroadcasting;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;
use Laravel\Ai\Streaming\Events\ToolCall;
use Laravel\Ai\Streaming\Events\ToolResult;

#[WithoutBroadcasting(ToolCall::class, ToolResult::class)]
class SearchAgent implements Agent, HasTools
{
    use Promptable;

    // ...
}
```

Виключені події ніколи не бродкастяться, але їх усе одно зберігають у таблиці `agent_conversation_messages`, тож ваш фронтенд може завантажити повні дані інструментів після завершення потоку. Це працює як для бродкастингу через чергу (`broadcastOnQueue`), так і для синхронного (`broadcast` / `broadcastNow`).

<a name="queueing"></a>
### Черги

За допомогою методу `queue` агента ви можете надіслати йому промпт, але дозволити обробити відповідь у фоні, завдяки чому ваш застосунок залишатиметься швидким і чуйним. Методи `then` і `catch` можна використати, щоб зареєструвати замикання, які буде викликано, коли відповідь стане доступною або якщо станеться виняток:

```php
use Illuminate\Http\Request;
use Laravel\Ai\Responses\AgentResponse;
use Throwable;

Route::post('/coach', function (Request $request) {
    (new SalesCoach)
        ->queue($request->input('transcript'))
        ->then(function (AgentResponse $response) {
            // ...
        })
        ->catch(function (Throwable $e) {
            // ...
        });

    return back();
});
```

<a name="tools"></a>
### Інструменти

Інструменти можна використати, щоб дати агентам додаткову функціональність, якою вони можуть скористатися, відповідаючи на промпти. Створити інструменти можна артизан-командою `make:tool`:

```shell
php artisan make:tool RandomNumberGenerator
```

Згенерований інструмент буде розміщено в каталозі `app/Ai/Tools` вашого застосунку. Кожен інструмент містить метод `handle`, який агент викличе, коли йому знадобиться скористатися інструментом:

```php
<?php

namespace App\Ai\Tools;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;

class RandomNumberGenerator implements Tool
{
    /**
     * Get the description of the tool's purpose.
     */
    public function description(): Stringable|string
    {
        return 'This tool may be used to generate cryptographically secure random numbers.';
    }

    /**
     * Execute the tool.
     */
    public function handle(Request $request): Stringable|string
    {
        return (string) random_int($request['min'], $request['max']);
    }

    /**
     * Get the tool's schema definition.
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'min' => $schema->integer()->min(0)->required(),
            'max' => $schema->integer()->required(),
        ];
    }
}
```

Щойно ви визначили свій інструмент, ви можете повернути його з методу `tools` будь-якого зі своїх агентів:

```php
use App\Ai\Tools\RandomNumberGenerator;

/**
 * Get the tools available to the agent.
 *
 * @return Tool[]
 */
public function tools(): iterable
{
    return [
        new RandomNumberGenerator,
    ];
}
```

<a name="runtime-tool-overrides"></a>
#### Перевизначення інструментів під час виконання

Метод `withTools` дозволяє замінити інструменти, оголошені екземпляром агента. Це зручно, коли набір інструментів залежить від клієнта (tenant) чи від фіче-флагу:

```php
$response = (new SupportAgent)
    ->withTools([new LookupOrder])
    ->prompt('Where is order 12345?');
```

Ви також можете передати замикання: воно отримує оголошені інструменти агента, тож їх можна доповнити чи відфільтрувати:

```php
$response = (new SupportAgent)
    ->withTools(fn (array $tools) => [...$tools, new LookupOrder])
    ->prompt('Where is order 12345?');
```

<a name="validating-tool-arguments"></a>
#### Валідація аргументів інструменту

Хоча схема вашого інструменту обмежує аргументи, які модель може надати, ви можете валідувати вхідні аргументи за допомогою методу `validate` запиту:

```php
public function handle(Request $request): Stringable|string
{
    $validated = $request->validate([
        'city' => 'required|string',
        'days' => 'required|integer|max:7',
    ]);

    return $this->forecast($validated['city'], $validated['days']);
}
```

Коли валідація не проходить, повідомлення про помилки повертаються моделі як результат інструменту, що дозволяє їй виправити аргументи й викликати інструмент знову.

<a name="repairing-tool-calls"></a>
#### Виправлення викликів інструментів

Використовуйте атрибут `RepairToolCalls`, щоб дозволити агенту відновитися, коли модель викликає невідомий локальний інструмент. Laravel повертає невдалий виклик моделі з іменами доступних локальних інструментів, дозволяючи їй виправити виклик:

```php
use Laravel\Ai\Attributes\RepairToolCalls;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;

#[RepairToolCalls]
class SupportAgent implements Agent, HasTools
{
    use Promptable;

    // ...
}
```

Коли Laravel визначає максимальну кількість кроків автоматично, цей атрибут додає один крок для виправленого виклику. Явні обмеження `MaxSteps` залишаються незмінними.

<a name="similarity-search"></a>
#### Пошук за схожістю

Інструмент `SimilaritySearch` дозволяє агентам шукати документи, схожі на заданий запит, використовуючи векторні ембединги, збережені у вашій базі даних. Це корисно для retrieval-augmented generation (RAG), коли ви хочете дати агентам доступ до пошуку в даних вашого застосунку.

Найпростіший спосіб створити інструмент пошуку за схожістю - метод `usingModel` з Eloquent-моделлю, яка має векторні ембединги:

```php
use App\Models\Document;
use Laravel\Ai\Tools\SimilaritySearch;

public function tools(): iterable
{
    return [
        SimilaritySearch::usingModel(Document::class, 'embedding'),
    ];
}
```

Перший аргумент - клас Eloquent-моделі, а другий - колонка, що містить векторні ембединги.

Ви також можете передати мінімальний поріг схожості між `0.0` і `1.0` та замикання для налаштування запиту:

```php
SimilaritySearch::usingModel(
    model: Document::class,
    column: 'embedding',
    minSimilarity: 0.7,
    limit: 10,
    query: fn ($query) => $query->where('published', true),
),
```

Для більшого контролю ви можете створити інструмент пошуку за схожістю з власним замиканням, яке повертає результати пошуку:

```php
use App\Models\Document;
use Laravel\Ai\Tools\SimilaritySearch;

public function tools(): iterable
{
    return [
        new SimilaritySearch(using: function (string $query) {
            return Document::query()
                ->where('user_id', $this->user->id)
                ->whereVectorSimilarTo('embedding', $query)
                ->limit(10)
                ->get();
        }),
    ];
}
```

Ви можете змінити опис інструмента методом `withDescription`:

```php
SimilaritySearch::usingModel(Document::class, 'embedding')
    ->withDescription('Search the knowledge base for relevant articles.'),
```

<a name="deferred-tool-loading"></a>
### Відкладене завантаження інструментів

За замовчуванням усі інструменти, які надає агент, відправляються провайдеру з кожним запитом. Коли агент надає велику кількість інструментів, це споживає токени й може знизити точність вибору інструментів моделлю. Використовуючи інструмент провайдера `ToolSearch` з OpenAI або Anthropic, ви можете відкласти визначення інструментів, щоб провайдер завантажував їх лише тоді, коли вони потрібні:

```php
use App\Ai\Tools\RefundOrder;
use App\Ai\Tools\SearchInvoices;
use App\Ai\Tools\Weather;
use Laravel\Ai\Providers\Tools\ToolSearch;

public function tools(): iterable
{
    return [
        new Weather,
        new ToolSearch(tools: [
            new SearchInvoices,
            new RefundOrder,
        ]),
    ];
}
```

Обгорнуті інструменти не потребують жодних модифікацій. Провайдер шукатиме й завантажуватиме їх, коли вони стануть релевантними для промпту, після чого агент зможе викликати їх як будь-який інший інструмент.

При використанні Anthropic аргумент `strategy` може використовуватися для визначення того, як провайдер має шукати відкладені інструменти. Підтримувані стратегії - `regex` (за замовчуванням) і `bm25`:

```php
new ToolSearch(tools: [new SearchInvoices], strategy: 'bm25'),
```

При використанні Anthropic додаткові опції, специфічні для провайдера, можуть передаватися інструменту пошуку через метод `withProviderOptions`:

```php
(new ToolSearch(tools: [new SearchInvoices]))
    ->withProviderOptions(['cache_control' => ['type' => 'ephemeral']]),
```

> [!WARNING]
> Провайдери, які не підтримують пошук інструментів, викинуть виняток замість того, щоб мовчки відкинути відкладені інструменти. Крім того, Anthropic вимагає, щоб принаймні один інструмент був наданий поза обгорткою `ToolSearch`.

<a name="file-storage-tools"></a>
### Інструменти файлового сховища

Фабрика інструментів `FileStorage` дозволяє дати агентам доступ до [диска файлової системи](/docs/{{version}}/filesystem) Laravel. Метод `all` повертає інструменти, які дозволяють агенту перелічувати, читати, оглядати, генерувати URL, записувати, видаляти й копіювати файли на заданому диску:

```php
use Laravel\Ai\Tools\FileStorage;

public function tools(): iterable
{
    return FileStorage::all('local');
}
```

Якщо ваш агент має мати змогу лише оглядати файли, скористайтеся методом `readOnly`:

```php
return FileStorage::readOnly('local');
```

Ці методи повертають `Illuminate\Support\Collection`, що дозволяє додатково відфільтрувати інструменти, які надаються агенту:

```php
use Laravel\Ai\Tools\Filesystem\DeleteFile;

return FileStorage::all('s3')
    ->reject(fn ($tool) => $tool instanceof DeleteFile);
```

<a name="mcp-tools"></a>
### MCP-інструменти

Якщо ваш застосунок використовує [Laravel MCP](/docs/{{version}}/mcp), ви можете дати своїм агентам інструменти, які надають сервери [Model Context Protocol](https://modelcontextprotocol.io). За допомогою [клієнта Laravel MCP](/docs/{{version}}/mcp#client) ви можете підключитися до віддаленого чи локального MCP-сервера й передати його інструменти безпосередньо своєму агенту.

> [!NOTE]
> MCP-інструменти потребують, щоб у вашому застосунку було встановлено пакет [Laravel MCP](/docs/{{version}}/mcp).

Оскільки метод `tools` MCP-клієнта повертає колекцію, розгорніть її в масив `tools` вашого агента оператором `...`:

```php
use App\Ai\Tools\RandomNumberGenerator;
use Laravel\Mcp\Client;

/**
 * Get the tools available to the agent.
 *
 * @return Tool[]
 */
public function tools(): iterable
{
    return [
        ...Client::web('https://mcp.example.com')
            ->withToken($token)
            ->tools(),

        new RandomNumberGenerator,
    ];
}
```

AI SDK автоматично загортає кожен MCP-інструмент, щоб агент міг викликати його як будь-який інший. Ви також можете скористатися [іменованим MCP-клієнтом](/docs/{{version}}/mcp#named-clients):

```php
use Laravel\Mcp\Facades\Mcp;

public function tools(): iterable
{
    return [
        ...Mcp::client('github')->tools(),
    ];
}
```

Або підключитися до [локального MCP-сервера](/docs/{{version}}/mcp#client-connecting):

```php
use Laravel\Mcp\Client;

public function tools(): iterable
{
    return [
        ...Client::local('php', ['artisan', 'mcp:start'])->tools(),
    ];
}
```

Докладніше про створення й автентифікацію MCP-клієнтів, включно з bearer-токенами й OAuth, дивіться в [документації MCP-клієнта](/docs/{{version}}/mcp#client).

<a name="skills"></a>
### Навички

[Навички агентів](https://agentskills.io) (Agent Skills) - це каталоги з інструкціями та допоміжними файлами, які вчать агента виконувати певне завдання. Навички відповідають відкритому стандарту, тож ту саму навичку можуть використовувати і агенти вашого застосунку, і AI-агенти для написання коду, з якими ви цей застосунок розробляєте.

Кожна навичка - це окремий каталог у `resources/skills` вашого застосунку з файлом `SKILL.md`. Решта файлів у цьому каталозі, як-от довідкові документи, постачаються разом із навичкою:

```text
resources/skills/
└── refund-policy/
    ├── SKILL.md
    └── references/EDGE-CASES.md
```

Файл `SKILL.md` починається з YAML frontmatter, що містить `name` навички та `description` - опис того, коли її слід використовувати. Далі йдуть інструкції навички:

```markdown
---
name: refund-policy
description: Use when a customer asks for a refund or disputes a charge.
---

# Refund Policy

Customers may request a full refund within 30 days of purchase...
```

Якщо `name` не вказано, використовується назва каталогу навички. Навички без `description` ігноруються.

Щоб надати агенту доступ до навичок, реалізуйте інтерфейс `HasSkills` і поверніть джерела навичок із методу `skills`:

```php
use Laravel\Ai\Contracts\HasSkills;
use Laravel\Ai\Skills\Skill;

class SupportAgent implements Agent, HasSkills
{
    use Promptable;

    public function skills(): iterable
    {
        return [
            resource_path('skills'),
            base_path('.agents/skills'),
            new Skill('house-style', 'Use when you write copy for a customer.', view('skills.house-style')),
            fn () => $this->user->team->skills->map(
                fn ($skill) => new Skill($skill->name, $skill->description, $skill->instructions)
            ),
        ];
    }
}
```

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

Агент отримує інструмент `LoadSkill`, який перелічує ім'я та опис кожної навички, а її повні інструкції й текстові файли завантажує лише тоді, коли цього потребує промпт. Бінарні файли та файли, більші за 256 КБ, не повертаються.

<a name="provider-tools"></a>
### Інструменти провайдера

Інструменти провайдера - це особливі інструменти, реалізовані нативно самими AI-провайдерами, які пропонують можливості на кшталт вебпошуку, завантаження URL і пошуку файлів. На відміну від звичайних інструментів, інструменти провайдера виконує сам провайдер, а не ваш застосунок.

Інструменти провайдера можна повертати з методу `tools` вашого агента.

<a name="web-search"></a>
#### Вебпошук

Інструмент провайдера `WebSearch` дозволяє агентам шукати в мережі інформацію в реальному часі. Це корисно для відповідей на питання про поточні події, свіжі дані чи теми, які могли змінитися після дати відсічення тренувальних даних моделі.

**Підтримувані провайдери:** Anthropic, OpenAI, Azure, Gemini, xAI, OpenRouter

```php
use Laravel\Ai\Providers\Tools\WebSearch;

public function tools(): iterable
{
    return [
        new WebSearch,
    ];
}
```

Ви можете налаштувати інструмент вебпошуку, щоб обмежити кількість пошуків чи звузити результати до конкретних доменів:

```php
(new WebSearch)->max(5)->allow(['laravel.com', 'php.net']),
```

Щоб уточнити результати пошуку за місцем розташування користувача, скористайтеся методом `location`:

```php
(new WebSearch)->location(
    city: 'New York',
    region: 'NY',
    country: 'US'
);
```

<a name="web-fetch"></a>
#### Завантаження вебсторінок

Інструмент провайдера `WebFetch` дозволяє агентам завантажувати й читати вміст вебсторінок. Це корисно, коли вам потрібно, щоб агент проаналізував конкретні URL чи отримав докладну інформацію з відомих вебсторінок.

**Підтримувані провайдери:** Anthropic, Gemini, OpenRouter

```php
use Laravel\Ai\Providers\Tools\WebFetch;

public function tools(): iterable
{
    return [
        new WebFetch,
    ];
}
```

Ви можете налаштувати інструмент завантаження вебсторінок, щоб обмежити кількість завантажень чи звузити його до конкретних доменів:

```php
(new WebFetch)->max(3)->allow(['docs.laravel.com']),
```

<a name="file-search"></a>
#### Пошук файлів

Інструмент провайдера `FileSearch` дозволяє агентам шукати серед [файлів](#files), збережених у [векторних сховищах](#vector-stores). Це уможливлює retrieval-augmented generation (RAG), дозволяючи агенту шукати релевантну інформацію у ваших завантажених документах.

**Підтримувані провайдери:** OpenAI, Gemini, xAI

```php
use Laravel\Ai\Providers\Tools\FileSearch;

public function tools(): iterable
{
    return [
        new FileSearch(stores: ['store_id']),
    ];
}
```

Ви можете передати кілька ID векторних сховищ, щоб шукати в кількох сховищах:

```php
new FileSearch(stores: ['store_1', 'store_2']);
```

Якщо ваші файли мають [метадані](#adding-files-to-stores), ви можете відфільтрувати результати пошуку, передавши аргумент `where`. Для простих фільтрів на рівність передайте масив:

```php
new FileSearch(stores: ['store_id'], where: [
    'author' => 'Taylor Otwell',
    'year' => 2026,
]);
```

Для складніших фільтрів ви можете передати замикання, яке отримує екземпляр `FileSearchQuery`:

```php
use Laravel\Ai\Providers\Tools\FileSearchQuery;

new FileSearch(stores: ['store_id'], where: fn (FileSearchQuery $query) =>
    $query->where('author', 'Taylor Otwell')
        ->whereNot('status', 'draft')
        ->whereIn('category', ['news', 'updates'])
);
```

<a name="code-execution"></a>
#### Виконання коду

Інструмент провайдера `CodeExecution` дозволяє агентам запускати код у пісочниці, яку розміщує AI-провайдер. Це зручно для обчислень і аналізу даних.

**Підтримувані провайдери:** Anthropic, OpenAI, Azure, Gemini, xAI

```php
use Laravel\Ai\Providers\Tools\CodeExecution;

public function tools(): iterable
{
    return [new CodeExecution];
}
```

Із OpenAI чи Azure ви можете зробити [збережені файли](#files) доступними в пісочниці через опції провайдера:

```php
(new CodeExecution)->withProviderOptions([
    'container' => ['type' => 'auto', 'file_ids' => ['file_123']],
]);
```

<a name="sub-agents"></a>
### Субагенти

Агентів також можна повертати з методу `tools` іншого агента. Коли агента повернуто як інструмент, батьківський агент може делегувати субагенту конкретне завдання й використати його відповідь, відповідаючи на початковий промпт. Це корисно, коли агенту загального призначення потрібен доступ до спеціалізованих агентів з власними інструкціями, інструментами, конфігурацією моделі чи вподобаннями щодо провайдера.

Наприклад, агент підтримки клієнтів міг би делегувати питання про право на повернення коштів окремому агенту з повернень:

```php
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasTools;
use Laravel\Ai\Promptable;

class CustomerSupportAgent implements Agent, HasTools
{
    use Promptable;

    /**
     * Get the instructions that the agent should follow.
     */
    public function instructions(): string
    {
        return 'You help customers with account, order, and billing questions. Delegate refund policy questions to the refunds specialist.';
    }

    /**
     * Get the tools available to the agent.
     *
     * @return Tool[]
     */
    public function tools(): iterable
    {
        return [
            new RefundsAgent,
        ];
    }
}
```

Щоб налаштувати, як субагент подається батьківському агенту, реалізуйте на субагенті інтерфейс `CanActAsTool` і визначте ім'я та опис для інструмента:

```php
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Contracts\CanActAsTool;
use Laravel\Ai\Enums\Lab;

#[Provider(Lab::Anthropic)]
class RefundsAgent implements Agent, CanActAsTool, HasTools
{
    /**
     * Get the agent's tool name.
     */
    public function name(): string
    {
        return 'refunds_specialist';
    }

    /**
     * Get the agent's tool description.
     */
    public function description(): string
    {
        return 'Determine whether an order is eligible for a refund and explain the next step.';
    }

    // ...
}
```

Якщо субагент не реалізує `CanActAsTool`, Laravel використає базове ім'я класу агента як ім'я інструмента й загальний опис, який просить батьківського агента передати чіткий, самодостатній опис завдання. Кожен виклик субагента виконується ізольовано й не отримує історії розмови батьківського агента.

Коли батьківський агент [стримить](#streaming), його субагенти теж стримлять. Батьківський агент надсилає події `ToolResult` з текстом, який субагент уже встиг згенерувати. Ці події позначені як попередні, і за ними йде остаточний результат виклику інструмента, тож, перебираючи події вручну, їх можна пропускати:

```php
use Laravel\Ai\Streaming\Events\ToolResult;

foreach ($stream as $event) {
    if ($event instanceof ToolResult && $event->preliminary) {
        continue;
    }

    // ...
}
```

Значення відповіді на кшталт `text`, `usage` і `toolResults` попередні події ігнорують. [Протокол Vercel](#stream-protocols) рендерить їх як нативний потоковий вивід інструмента, тож `useChat` показує прогрес без жодного власного коду, а протокол AG-UI передає їх як знімки активності. Текст, міркування, джерела й використання токенів завершеної відповіді враховують і внесок субагента.

<a name="middleware"></a>
### Middleware

Агенти підтримують `middleware`, що дозволяє перехоплювати й змінювати кожен крок генерації, перш ніж його буде надіслано провайдеру. `Middleware` викликається один раз на крок, тож запуск із трьох кроків викличе його тричі. Створити `middleware` можна артизан-командою `make:agent-middleware`:

```shell
php artisan make:agent-middleware LogPrompts
```

Згенерований `middleware` буде розміщено в каталозі `app/Ai/Middleware` вашого застосунку. Щоб додати `middleware` до агента, реалізуйте інтерфейс `HasMiddleware` і визначте метод `middleware`, який повертає масив класів `middleware`:

```php
<?php

namespace App\Ai\Agents;

use App\Ai\Middleware\LogPrompts;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasMiddleware;
use Laravel\Ai\Promptable;

class SalesCoach implements Agent, HasMiddleware
{
    use Promptable;

    // ...

    /**
     * Get the agent's middleware.
     */
    public function middleware(): array
    {
        return [
            new LogPrompts,
        ];
    }
}
```

Кожен клас `middleware` має визначати метод `handle`, який отримує `PendingStep` і `Closure`, що передає крок наступному `middleware`:

```php
<?php

namespace App\Ai\Middleware;

use Closure;
use Illuminate\Support\Facades\Log;
use Laravel\Ai\PendingStep;

class LogPrompts
{
    /**
     * Handle the pending generation step.
     */
    public function handle(PendingStep $step, Closure $next)
    {
        Log::info('Prompting agent', ['model' => $step->model]);

        return $next($step);
    }
}
```

Окрім `provider`, `model`, `instructions`, `messages` і `tools`, які от-от буде надіслано, крок надає вже завершені кроки, їхнє сумарне використання токенів і прогрес запуску:

```php
$step->steps;
$step->usage;

$step->number;
$step->isFirstStep();
$step->isFinalStep;
```

Методи `withModel`, `withInstructions`, `withMessages`, `withTools`, `onlyTools`, `withoutTools`, `withToolChoice`, `withMaxTokens` і `withProviderOptions` повертають копію кроку. Наприклад, ви можете прибрати дорогий інструмент, щойно агент ним скористався:

```php
public function handle(PendingStep $step, Closure $next)
{
    if (! $step->isFirstStep()) {
        $step = $step->withoutTools('SearchDocumentation');
    }

    return $next($step);
}
```

Або можна втримати довгий цикл викликів інструментів у межах контекстного вікна, стиснувши середину розмови в короткий переказ:

```php
use App\Ai\Agents\Summarizer;
use Laravel\Ai\Messages\UserMessage;

public function handle(PendingStep $step, Closure $next)
{
    if (count($step->messages) > 40) {
        $summary = (new Summarizer)->prompt(
            collect(array_slice($step->messages, 1, -10))->map->content->implode("\n"),
        )->text;

        $step = $step->withMessages([
            $step->messages[0],
            new UserMessage("Summary of the conversation so far: {$summary}"),
            ...array_slice($step->messages, -10),
        ]);
    }

    return $next($step);
}
```

Повідомлення, передані в метод `withMessages`, змінюють лише те, що надсилається на поточному кроці. Наступні кроки й збережена розмова й далі використовують повну історію, без переказу.

Ви можете скористатися методом `then`, щоб виконати код, щойно модель відповіла на крок, але до виконання її викликів інструментів. Це працює як для синхронних, так і для потокових відповідей:

```php
use Laravel\Ai\Gateway\StepResponse;

public function handle(PendingStep $step, Closure $next)
{
    return $next($step)->then(function (StepResponse $response) {
        Log::info('Agent responded', ['text' => $response->text]);
    });
}
```

`Middleware` має повертати результат `$next` або власний `StepResponse`, щоб відповісти на крок без виклику моделі - наприклад, віддаючи закешовану відповідь. Повернення будь-якого іншого значення викидає `LogicException`.

<a name="anonymous-agents"></a>
### Анонімні агенти

Іноді вам може знадобитися швидко звернутися до моделі, не створюючи окремого класу агента. Ви можете створити спонтанного, анонімного агента функцією `agent`:

```php
use function Laravel\Ai\{agent};

$response = agent(
    instructions: 'You are an expert at software development.',
    messages: [],
    tools: [],
)->prompt('Tell me about Laravel');
```

Анонімні агенти також можуть давати структурований вивід:

```php
use Illuminate\Contracts\JsonSchema\JsonSchema;

use function Laravel\Ai\{agent};

$response = agent(
    schema: fn (JsonSchema $schema) => [
        'number' => $schema->integer()->required(),
    ],
)->prompt('Generate a random number less than 100');
```

<a name="agent-configuration"></a>
### Конфігурація агента

Ви можете налаштувати опції генерації тексту для агента за допомогою PHP-атрибутів. Доступні такі атрибути:

- `MaxSteps`: максимальна кількість кроків, які агент може зробити, користуючись інструментами.
- `MaxTokens`: максимальна кількість токенів, які модель може згенерувати.
- `Model`: модель, яку має використовувати агент.
- `Provider`: AI-провайдер (чи провайдери для резервування), який слід використовувати для агента.
- `Temperature`: температура семплювання для генерації (від 0.0 до 1.0).
- `Timeout`: HTTP-тайм-аут у секундах для запитів агента (за замовчуванням: 60).
- `TopP`: імовірність nucleus-семплювання для генерації (від 0.0 до 1.0).
- `UseCheapestModel`: використовувати найдешевшу текстову модель провайдера для оптимізації витрат.
- `UseSmartestModel`: використовувати найпотужнішу текстову модель провайдера для складних завдань.

```php
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Attributes\MaxSteps;
use Laravel\Ai\Attributes\MaxTokens;
use Laravel\Ai\Attributes\Model;
use Laravel\Ai\Attributes\Provider;
use Laravel\Ai\Attributes\Temperature;
use Laravel\Ai\Attributes\Timeout;
use Laravel\Ai\Attributes\TopP;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;

#[Provider(Lab::Anthropic)]
#[Model('claude-sonnet-5')]
#[MaxSteps(10)]
#[MaxTokens(4096)]
#[Temperature(0.7)]
#[Timeout(120)]
#[TopP(0.9)]
class SalesCoach implements Agent
{
    use Promptable;

    // ...
}
```

Атрибути `UseCheapestModel` і `UseSmartestModel` дозволяють автоматично обрати найвигіднішу за ціною чи найпотужнішу модель для заданого провайдера, не вказуючи імені моделі. Це корисно, коли ви хочете оптимізувати витрати чи можливості для різних провайдерів:

```php
use Laravel\Ai\Attributes\UseCheapestModel;
use Laravel\Ai\Attributes\UseSmartestModel;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Promptable;

#[UseCheapestModel]
class SimpleSummarizer implements Agent
{
    use Promptable;

    // Will use the cheapest model (e.g., Haiku)...
}

#[UseSmartestModel]
class ComplexReasoner implements Agent
{
    use Promptable;

    // Will use the most capable model (e.g., Opus)...
}
```

> [!NOTE]
> Модель, яку обирають `UseCheapestModel` і `UseSmartestModel`, може змінюватися між випусками Laravel AI SDK, оскільки провайдери випускають нові моделі. Зміна моделі може призвести до змін у поведінці, застарілих параметрів і суттєвої різниці у вартості. Якщо вам потрібна стабільна, передбачувана модель і ціна, вкажіть модель явно атрибутом `Model`.

<a name="provider-options"></a>
### Опції провайдера

Якщо вашому агенту потрібно передати специфічні для провайдера опції (як-от reasoning effort в OpenAI чи налаштування штрафів), реалізуйте контракт `HasProviderOptions` і визначте метод `providerOptions`:

```php
<?php

namespace App\Ai\Agents;

use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasProviderOptions;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;

class SalesCoach implements Agent, HasProviderOptions
{
    use Promptable;

    // ...

    /**
     * Get provider-specific generation options.
     */
    public function providerOptions(Lab|string $provider): array
    {
        return match ($provider) {
            Lab::OpenAI => [
                'reasoning' => ['effort' => 'low'],
                'frequency_penalty' => 0.5,
                'presence_penalty' => 0.3,
            ],
            Lab::Anthropic => [
                'thinking' => ['budget_tokens' => 1024],
                'cache_control' => ['type' => 'ephemeral'],
            ],
            default => [],
        };
    }
}
```

Метод `providerOptions` отримує провайдера, який використовується зараз (enum `Lab` чи рядок), що дозволяє повертати різні опції для різних провайдерів. Це особливо корисно під час використання [резервування](#failover), оскільки кожен резервний провайдер може отримати власну конфігурацію.

Наведений вище приклад з Anthropic також вмикає [кешування промптів](#prompt-caching) через `cache_control`.

Опції провайдера приймають і виклики для [зображень](#images), [аудіо](#audio), [транскрибування](#transcription), [ембедингів](#embeddings) та [переранжування](#reranking):

```php
use Laravel\Ai\Audio;

$audio = Audio::of('I love coding with Laravel.')
    ->withProviderOptions(['speed' => 1.25])
    ->generate();
```

Замість масиву можна передати замикання - воно отримає провайдер, який зараз використовується.

<a name="custom-http-headers"></a>
#### Власні HTTP-заголовки

Заголовки, налаштовані для провайдера в конфігураційному файлі `config/ai.php` вашого застосунку, надсилаються з кожним запитом цього провайдера. Щоб надсилати заголовки для окремого запиту - наприклад, метадані для AI-шлюзу, - скористайтеся методом `withHeaders`. Він доступний для зображень, аудіо, транскрибування, ембедингів і переранжування, а також для [завантаження файлів](#files):

```php
use Laravel\Ai\Embeddings;

$embeddings = Embeddings::for($chunks)
    ->withHeaders(['cf-aig-metadata' => json_encode(['team' => $team->id])])
    ->withProviderOptions(['dimensions' => 1024])
    ->generate();
```

Заголовки також можна задати замиканням, яке отримує провайдер, що зараз використовується. Заголовки не потрапляють у тіло запиту й не впливають на [ключі кешу ембедингів](#caching-embeddings).

<a name="prompt-caching"></a>
### Кешування промптів

Більшість провайдерів автоматично кешують повторювані префікси промптів і виставляють рахунок за кешовану частину зі знижкою. OpenAI, Gemini, Groq, DeepSeek та xAI не потребують налаштування, і ви можете перевірити економію через використання у відповіді:

```php
$response->usage->cacheReadInputTokens;
$response->usage->cacheWriteInputTokens;
```

Обидва ці лічильники - частина загальної кількості вхідних токенів; докладніше про це в [розділі про використання токенів](#usage).

Провайдери `anthropic` та `bedrock` кешують лише за запитом. Атрибути `CacheInstructions` та `CacheToolDefinitions` розміщують точку розриву кешу в кінці інструкцій вашого агента та визначень інструментів, тож кожна розмова зчитує цей префікс з кешу замість того, щоб писати його знову:

```php
use Laravel\Ai\Attributes\CacheInstructions;
use Laravel\Ai\Attributes\CacheToolDefinitions;

#[CacheInstructions]
#[CacheToolDefinitions]
class SalesCoach implements Agent
{
    use Promptable;

    // ...
}
```

Якщо ваші інструкції змінюються з кожним запитом, наприклад, коли вони містять поточну дату, використовуйте лише `CacheToolDefinitions`. Кешування префікса, який змінюється з кожним запитом, створює новий запис кешу щоразу, тож ви платите за його запис до кешу, але ніколи не використовуєте повторно.

Провайдери, які не підтримують ці атрибути, ігнорують їх, тож агент може безпечно оголошувати їх під час використання [резервування](#failover).

Кешовані префікси зберігаються п'ять хвилин за замовчуванням. Anthropic може зберігати їх годину, якщо ви передасте TTL до атрибута:

```php
#[CacheInstructions('1h')]
#[CacheToolDefinitions('1h')]
```

Альтернативно, автоматичне кешування Anthropic може бути ввімкнено через [опцію провайдера](#provider-options) `cache_control` верхнього рівня. Це розміщує одну точку розриву після останнього блоку запиту, тож точка розриву просувається в міру зростання розмови, і кожен хід зчитує попередні ходи з кешу. Обидва механізми можуть поєднуватися.

> [!WARNING]
> Оскільки провайдери будують промпти в порядку інструменти, інструкції та повідомлення, кешування інструкцій протягом години також вимагає кешування визначень інструментів протягом години. Змішування цих двох викидає `InvalidArgumentException`.

<a name="human-tool-approval"></a>
## Схвалення інструментів людиною

> [!WARNING]
> Щоб схвалення інструментів працювало, історія призупиненого ходу має бути доступна, коли запуск відновлюється. Використайте агента `Conversational` (наприклад, із трейтом `RemembersConversations`) або передайте історію з фронтенду вашого застосунку [методом `withMessages`](#conversation-context). Агенти, що не роблять ні того, ні іншого, викидають `ApprovalNotResumableException`, коли інструмент призупиняє роботу.

Інструменти, що виконують чутливі чи незворотні дії, можуть потребувати схвалення людиною перед виконанням. Щоб зробити інструмент таким, що потребує схвалення, реалізуйте контракт `Approvable` і використайте трейт `InteractsWithApprovals`. Такі інструменти за замовчуванням потребують схвалення:

```php
<?php

namespace App\Ai\Tools;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Facades\Storage;
use Laravel\Ai\Concerns\InteractsWithApprovals;
use Laravel\Ai\Contracts\Approvable;
use Laravel\Ai\Contracts\Tool;
use Laravel\Ai\Tools\Request;
use Stringable;

class DeleteFile implements Approvable, Tool
{
    use InteractsWithApprovals;

    /**
     * Get the description of the tool's purpose.
     */
    public function description(): Stringable|string
    {
        return 'Delete a file from storage.';
    }

    /**
     * Execute the tool.
     */
    public function handle(Request $request): Stringable|string
    {
        Storage::delete($request['path']);

        return "Deleted [{$request['path']}].";
    }

    /**
     * Get the tool's schema definition.
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'path' => $schema->string()->required(),
        ];
    }
}
```

Щоб визначити, чи потрібне схвалення, на основі аргументів виклику інструмента, визначте на інструменті метод `needsApproval`. Цей метод може повернути булеве значення чи екземпляр `Approval`, який містить причину запиту на схвалення:

```php
use Laravel\Ai\Approvals\Approval;

/**
 * Determine whether the tool needs approval for the given request.
 */
protected function needsApproval(Request $request): Approval|bool
{
    return str_starts_with($request['path'], 'temporary/')
        ? false
        : Approval::required('This will permanently delete a file.');
}
```

Ви можете перевизначити вимогу схвалення для інструмента, повертаючи його з методу `tools` агента:

```php
public function tools(): iterable
{
    return [
        (new SendNotification)->withoutApproval(),
        (new DeleteFile)->requireApproval('Deletion review required.'),
    ];
}
```

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

```php
$response = (new FileAssistant)
    ->forUser($user)
    ->prompt('Delete the old invoice.');

if ($response->hasPendingApprovals()) {
    foreach ($response->pendingApprovals as $approval) {
        // $approval->id
        // $approval->tool
        // $approval->arguments
        // $approval->reason
    }
}
```

Щоб відновити роботу агента, продовжте розмову й передайте екземпляр `Decisions`, що містить рішення для кожного очікуваного виклику інструмента. Рішення можуть схвалити виклик, відхилити його чи змінити його аргументи перед виконанням:

```php
use Laravel\Ai\Approvals\Decision;
use Laravel\Ai\Approvals\Decisions;

$response = (new FileAssistant)
    ->continue($conversationId, as: $user)
    ->prompt(Decisions::from([
        'call_abc' => Decision::approve(),
        'call_ghi' => Decision::reject('The invoice must be retained.'),
    ]));
```

> [!IMPORTANT]
> Призупинені ходи зіставляються за розмовою та очікуваними викликами інструментів, а не за учасником, який їх призупинив. Тому ваш застосунок має авторизувати доступ до розмови, перш ніж відновлювати її, як показано в [повному потоці схвалення](#complete-approval-flow), або перевірити доступ методом сховища розмов `conversationBelongsTo`.

Булеві значення `true` і `false` можна використовувати як скорочення для схвалення й відхилення. Кожен очікуваний виклик інструмента має отримати рішення. Невідомі, відсутні чи вже розв'язані ID викликів інструментів спричинять виняток `ApprovalMismatchException`. Ви можете задати значення за замовчуванням для викликів без явного рішення методами `approveRemaining` чи `rejectRemaining`:

```php
$decisions = Decisions::from([
    'call_abc' => true,
])->rejectRemaining('Not approved.');

$response = (new FileAssistant)
    ->continue($conversationId, as: $user)
    ->prompt($decisions);
```

Відхилення з результатом, як-от `Decision::reject('Not approved.')`, повертається моделі, щоб вона могла продовжити відповідь. Відхилення без результату зупиняє цикл генерації після запису відхилення.

Схвалення інструментів підтримують методи `prompt`, `stream`, `queue`, `broadcast`, `broadcastNow` і `broadcastOnQueue`.

Під час стримінгу й бродкастингу призупинення представлене подією `tool_approval_request`. Використовуючи [протокол стримінгу Vercel AI SDK](#stream-protocols), запити на схвалення й результати випромінюються через нативні частини схвалення інструментів цього протоколу, а протокол Agent User Interaction передає їх як переривання.

Клієнти обох протоколів надсилають свої рішення разом з рештою розмови, тож [запит із чату](#frontend-integration) можна передати агенту безпосередньо:

```php
$chat = Vercel::chat($request);

return (new FileAssistant)
    ->continue($conversationId, as: $request->user())
    ->stream($chat)
    ->usingProtocol($chat->protocol());
```

Коли призупинений хід відновлюється, відновлені кроки об'єднуються з цим ходом, тож кожен хід зберігається як одне повідомлення асистента. `assistantMessageId` відповіді містить ID призупиненого повідомлення, а використання токенів цього повідомлення охоплює і призупинення, і відновлення.

Для агентів у черзі отримана відповідь передається до колбека `then`, а Laravel також диспетчеризує подію `ToolApprovalRequested`.

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

<a name="complete-approval-flow"></a>
### Повний потік схвалення

Наведений нижче маршрут демонструє повний потік схвалення: він приймає або новий текстовий промпт, або рішення про схвалення з екрана чату. Цей приклад припускає, що модель `User` застосунку використовує трейт `HasConversations`:

```php
use App\Ai\Agents\FileAssistant;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Route;
use Illuminate\Validation\Rule;
use Laravel\Ai\Approvals\Decision;
use Laravel\Ai\Approvals\Decisions;
use Laravel\Ai\Models\Conversation;

Route::post('/chat/{conversation}', function (Request $request, Conversation $conversation) {
    Gate::authorize('view', $conversation);

    $validated = $request->validate([
        'message' => ['nullable', 'string', 'required_without:decisions', 'prohibits:decisions'],
        'decisions' => ['nullable', 'array', 'required_without:message', 'prohibits:message'],
        'decisions.*.action' => ['required_with:decisions', Rule::in(['approve', 'reject'])],
        'decisions.*.result' => ['nullable', 'string'],
    ]);

    $prompt = isset($validated['decisions'])
        ? Decisions::from(collect($validated['decisions'])->map(
            fn (array $decision) => match ($decision['action']) {
                'approve' => Decision::approve(),
                'reject' => Decision::reject($decision['result'] ?? null),
            }
        )->all())
        : $validated['message'];

    $response = (new FileAssistant)
        ->continue($conversation->id, as: $request->user())
        ->prompt($prompt);

    return [
        'conversation_id' => $response->conversationId,
        'status' => $response->hasPendingApprovals() ? 'awaiting_approval' : 'complete',
        'message' => $response->text,
        'approvals' => $response->pendingApprovals,
    ];
})->middleware('auth');
```

Коли статус відповіді - `awaiting_approval`, екран чату має відрендерити очікувані схвалення й надіслати вибір користувача на той самий ендпоїнт, використовуючи ID виклику інструмента як ключ кожного рішення. В іншому разі екран може надіслати просте значення `message`:

```json
{
    "decisions": {
        "call_abc": {
            "action": "approve"
        },
        "call_def": {
            "action": "reject",
            "result": "The invoice must be retained."
        }
    }
}
```

<a name="images"></a>
## Зображення

Клас `Laravel\Ai\Image` можна використати для генерації зображень через провайдерів `openai`, `gemini` чи `xai`:

```php
use Laravel\Ai\Image;

$image = Image::of('A donut sitting on the kitchen counter')->generate();

$rawContent = (string) $image;
```

Методи `square`, `portrait` і `landscape` можна використати, щоб керувати співвідношенням сторін зображення, а метод `quality` - щоб підказати моделі бажану якість готового зображення (`high`, `medium`, `low`). Метод `timeout` можна використати, щоб задати HTTP-тайм-аут у секундах:

```php
use Laravel\Ai\Image;

$image = Image::of('A donut sitting on the kitchen counter')
    ->quality('high')
    ->landscape()
    ->timeout(120)
    ->generate();
```

Ви можете прикріпити референсні зображення методом `attachments`:

```php
use Laravel\Ai\Files;
use Laravel\Ai\Image;

$image = Image::of('Update this photo of me to be in the style of an impressionist painting.')
    ->attachments([
        Files\Image::fromStorage('photo.jpg'),
        // Files\Image::fromPath('/home/laravel/photo.jpg'),
        // Files\Image::fromUrl('https://example.com/photo.jpg'),
        // $request->file('photo'),
    ])
    ->landscape()
    ->generate();
```

Деякі провайдери можуть згенерувати кілька зображень за один запит. OpenAI, Azure і xAI приймають [опцію провайдера](#provider-options) `n`, і відповідь міститиме всі повернуті зображення:

```php
$response = Image::of('A donut sitting on the kitchen counter')
    ->withProviderOptions(['n' => 4])
    ->generate();

count($response);           // 4
$response->images;          // A collection of generated images...
$response->firstImage();    // The first generated image...
```

Згенеровані зображення легко зберегти на диску за замовчуванням, налаштованому в конфігураційному файлі `config/filesystems.php` вашого застосунку:

```php
$image = Image::of('A donut sitting on the kitchen counter');

$path = $image->store();
$path = $image->storeAs('image.jpg');
$path = $image->storePublicly();
$path = $image->storePubliclyAs('image.jpg');
```

Генерацію зображень також можна поставити в чергу:

```php
use Laravel\Ai\Image;
use Laravel\Ai\Responses\ImageResponse;

Image::of('A donut sitting on the kitchen counter')
    ->portrait()
    ->queue()
    ->then(function (ImageResponse $image) {
        $path = $image->store();

        // ...
    });
```

<a name="audio"></a>
## Аудіо

Клас `Laravel\Ai\Audio` можна використати для генерації аудіо із заданого тексту:

```php
use Laravel\Ai\Audio;

$audio = Audio::of('I love coding with Laravel.')->generate();

$rawContent = (string) $audio;
```

Ви також можете згенерувати аудіо з рядка методом `toAudio`, доступним через клас `Stringable` у Laravel:

```php
use Illuminate\Support\Str;

$audio = Str::of('I love coding with Laravel.')->toAudio();
```

Методи `male`, `female` і `voice` можна використати, щоб визначити голос згенерованого аудіо:

```php
$audio = Audio::of('I love coding with Laravel.')
    ->female()
    ->generate();

$audio = Audio::of('I love coding with Laravel.')
    ->voice('voice-id-or-name')
    ->generate();
```

Так само метод `instructions` можна використати, щоб динамічно підказати моделі, як має звучати згенероване аудіо:

```php
$audio = Audio::of('I love coding with Laravel.')
    ->female()
    ->instructions('Said like a pirate')
    ->generate();
```

Згенероване аудіо легко зберегти на диску за замовчуванням, налаштованому в конфігураційному файлі `config/filesystems.php` вашого застосунку:

```php
$audio = Audio::of('I love coding with Laravel.')->generate();

$path = $audio->store();
$path = $audio->storeAs('audio.mp3');
$path = $audio->storePublicly();
$path = $audio->storePubliclyAs('audio.mp3');
```

Генерацію аудіо також можна поставити в чергу:

```php
use Laravel\Ai\Audio;
use Laravel\Ai\Responses\AudioResponse;

Audio::of('I love coding with Laravel.')
    ->queue()
    ->then(function (AudioResponse $audio) {
        $path = $audio->store();

        // ...
    });
```

<a name="transcription"></a>
## Транскрибування

Клас `Laravel\Ai\Transcription` можна використати, щоб згенерувати транскрипт заданого аудіо:

```php
use Laravel\Ai\Transcription;

$transcript = Transcription::fromPath('/home/laravel/audio.mp3')->generate();
$transcript = Transcription::fromStorage('audio.mp3')->generate();
$transcript = Transcription::fromUpload($request->file('audio'))->generate();

return (string) $transcript;
```

Метод `diarize` можна використати, щоб указати, що ви хочете отримати у відповіді діаризований транскрипт на додачу до сирого текстового, - це дасть доступ до транскрипту, сегментованого за мовцями:

```php
$transcript = Transcription::fromStorage('audio.mp3')
    ->diarize()
    ->generate();
```

Генерацію транскриптів також можна поставити в чергу:

```php
use Laravel\Ai\Transcription;
use Laravel\Ai\Responses\TranscriptionResponse;

Transcription::fromStorage('audio.mp3')
    ->queue()
    ->then(function (TranscriptionResponse $transcript) {
        // ...
    });
```

<a name="text-summarization"></a>
## Стислий переказ тексту

Ви можете стисло переказати текст методом `summarize`, доступним через клас `Stringable` у Laravel. За замовчуванням переказ міститиме не більше трьох речень і буде згенерований найдешевшою текстовою моделлю налаштованого провайдера:

```php
use Illuminate\Support\Str;

$summary = Str::of($article)->summarize();
```

Ви можете вказати максимальну кількість речень, провайдера, модель і тайм-аут, які використовуються для генерації переказу. Клас `Str` також пропонує статичну версію методу:

```php
use Laravel\Ai\Enums\Lab;

$summary = Str::of($article)->summarize(
    sentences: 4,
    provider: Lab::Anthropic,
    model: 'claude-sonnet-5',
    timeout: 30,
);

$summary = Str::summarize($article, sentences: 4);
```

<a name="embeddings"></a>
## Ембединги

Ви можете легко згенерувати векторні ембединги для будь-якого рядка новим методом `toEmbeddings`, доступним через клас `Stringable` у Laravel:

```php
use Illuminate\Support\Str;

$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings();
```

Як альтернативу ви можете скористатися класом `Embeddings`, щоб згенерувати ембединги для кількох вхідних значень одразу:

```php
use Laravel\Ai\Embeddings;

$response = Embeddings::for([
    'Napa Valley has great wine.',
    'Laravel is a PHP framework.',
])->generate();

$response->embeddings; // [[0.123, 0.456, ...], [0.789, 0.012, ...]]
```

Ви можете вказати розмірність і провайдера для ембедингів:

```php
$response = Embeddings::for(['Napa Valley has great wine.'])
    ->dimensions(1536)
    ->generate(Lab::OpenAI, 'text-embedding-3-small');
```

<a name="multimodal-embeddings"></a>
### Мультимодальні ембединги

Окрім рядків, метод `Embeddings::for` приймає зображення, аудіо, документи й відео, що дозволяє генерувати ембединги для нетекстового вмісту. Gemini підтримує ембединги зображень, аудіо, документів і відео, а VoyageAI - ембединги зображень і відео:

```php
use Laravel\Ai\Embeddings;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Files\Image;
use Laravel\Ai\Files\Video;

$response = Embeddings::for([
    'A vineyard at sunset.',
    Image::fromStorage('vineyard.jpg'),
    Video::fromPath('/home/laravel/tour.mp4'),
])->generate(Lab::Gemini);
```

Мультимодальні вхідні дані використовують ті самі [класи файлів, що й вкладення](#attachments). Ці файли можна створити з локального шляху, диска файлової системи, віддаленого URL чи вмісту в кодуванні Base64. Зображення, документи й відео також можна створити із завантажених файлів, а документи - із сирого рядкового вмісту:

```php
use Laravel\Ai\Files\Audio;
use Laravel\Ai\Files\Document;
use Laravel\Ai\Files\Image;
use Laravel\Ai\Files\Video;

Image::fromPath('/home/laravel/photo.jpg');
Image::fromStorage('photo.jpg');
Image::fromUpload($request->file('photo'));

Audio::fromPath('/home/laravel/clip.mp3');
Audio::fromStorage('clip.mp3');
Audio::fromUpload($request->file('clip.mp3'));

Video::fromPath('/home/laravel/video.mp4');
Video::fromStorage('video.mp4');
Video::fromUpload($request->file('video'));

Document::fromUrl('https://example.com/report.pdf');
Document::fromString('Laravel is a PHP framework.', 'text/plain');
Document::fromUpload($request->file('report'));
```

> [!NOTE]
> VoyageAI не дозволяє змішувати медіа за віддаленим URL і медіа в кодуванні Base64 в одному запиті. Локальні, збережені й завантажені файли надсилаються як вміст у кодуванні Base64, а текстові вхідні дані можна поєднувати з будь-яким із цих джерел медіа. Зверніться до документації свого провайдера, щоб дізнатися, які мультимодальні моделі й вхідні дані доступні.

<a name="querying-embeddings"></a>
### Запити до ембедингів

Щойно ви згенерували ембединги, ви зазвичай зберігатимете їх у колонці `vector` своєї бази даних для подальших запитів. Laravel нативно підтримує векторні колонки в PostgreSQL через розширення `pgvector` і MariaDB. Для початку визначте колонку `vector` у своїй міграції, указавши кількість вимірів:

```php
Schema::ensureVectorExtensionExists();

Schema::create('documents', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('content');
    $table->vector('embedding', dimensions: 1536);
    $table->timestamps();
});
```

Ви також можете додати векторний індекс, щоб пришвидшити пошук за схожістю. Викликаючи `index` на векторній колонці, Laravel автоматично створить HNSW-індекс з косинусною відстанню:

```php
$table->vector('embedding', dimensions: 1536)->index();
```

У своїй Eloquent-моделі вам слід привести векторну колонку за допомогою приведення `AsVector`:

```php
use Illuminate\Database\Eloquent\Casts\AsVector;

protected function casts(): array
{
    return [
        'embedding' => AsVector::class,
    ];
}
```

Щоб шукати схожі записи, скористайтеся методом `whereVectorSimilarTo`. Цей метод фільтрує результати за мінімальною косинусною схожістю (між `0.0` і `1.0`, де `1.0` - ідентичність) і впорядковує результати за схожістю:

```php
use App\Models\Document;

$documents = Document::query()
    ->whereVectorSimilarTo('embedding', $queryEmbedding, minSimilarity: 0.4)
    ->limit(10)
    ->get();
```

`$queryEmbedding` може бути масивом чисел з рухомою комою або звичайним рядком. Коли передано рядок, Laravel автоматично згенерує для нього ембединги:

```php
$documents = Document::query()
    ->whereVectorSimilarTo('embedding', 'best wineries in Napa Valley')
    ->limit(10)
    ->get();
```

Якщо вам потрібно більше контролю, ви можете окремо скористатися нижчорівневими методами `whereVectorDistanceLessThan`, `selectVectorDistance` та `orderByVectorDistance`:

```php
$documents = Document::query()
    ->select('*')
    ->selectVectorDistance('embedding', $queryEmbedding, as: 'distance')
    ->whereVectorDistanceLessThan('embedding', $queryEmbedding, maxDistance: 0.3)
    ->orderByVectorDistance('embedding', $queryEmbedding)
    ->limit(10)
    ->get();
```

Якщо ви хочете дати агенту можливість виконувати пошук за схожістю як інструмент, погляньте на документацію інструмента [Пошук за схожістю](#similarity-search).

> [!NOTE]
> Векторні запити наразі підтримуються на підключеннях PostgreSQL з розширенням `pgvector` або на MariaDB 11.7 чи новішій версії з її вбудованою підтримкою векторів.

<a name="caching-embeddings"></a>
### Кешування ембедингів

Генерацію ембедингів можна кешувати, щоб уникнути зайвих API-викликів для однакових вхідних даних. Щоб увімкнути кешування, встановіть параметр конфігурації `ai.caching.embeddings.cache` у `true`:

```php
'caching' => [
    'embeddings' => [
        'cache' => true,
        'store' => env('CACHE_STORE', 'database'),
        'individually' => true,
        // ...
    ],
],
```

Коли кешування увімкнено, ембединги кешуються на 30 днів. Ключ кешу базується на провайдері, моделі, розмірності та вхідному вмісті, що гарантує повернення закешованих результатів для однакових запитів і генерацію свіжих ембедингів для інших конфігурацій.

За замовчуванням ембединг кожного вхідного елемента кешується під власним ключем, тому наступний запит може потрапити в кеш для вхідних даних, які він бачив раніше, навіть якщо набір вхідних даних або їхній порядок змінився. Щоб натомість кешувати весь набір вхідних даних під одним ключем, встановіть параметр конфігурації `ai.caching.embeddings.individually` у `false`.

Ви також можете увімкнути кешування для конкретного запиту методом `cache`, навіть коли глобальне кешування вимкнено:

```php
$response = Embeddings::for(['Napa Valley has great wine.'])
    ->cache()
    ->generate();
```

Ви можете вказати власну тривалість кешування в секундах:

```php
$response = Embeddings::for(['Napa Valley has great wine.'])
    ->cache(seconds: 3600) // Cache for 1 hour
    ->generate();
```

Метод `toEmbeddings` у Stringable також приймає аргумент `cache`:

```php
// Cache with default duration...
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: true);

// Cache for a specific duration...
$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: 3600);
```

<a name="reranking"></a>
## Переранжування

Переранжування дозволяє переупорядкувати список документів за їхньою релевантністю до заданого запиту. Це корисно для покращення результатів пошуку завдяки семантичному розумінню:

Клас `Laravel\Ai\Reranking` можна використати для переранжування документів:

```php
use Laravel\Ai\Reranking;

$response = Reranking::of([
    'Django is a Python web framework.',
    'Laravel is a PHP web application framework.',
    'React is a JavaScript library for building user interfaces.',
])->rerank('PHP frameworks');

// Access the top result...
$response->first()->document; // "Laravel is a PHP web application framework."
$response->first()->score;    // 0.95
$response->first()->index;    // 1 (original position)
```

Метод `limit` можна використати, щоб обмежити кількість повернутих результатів, а метод `timeout` - щоб задати HTTP-тайм-аут у секундах (за замовчуванням 30):

```php
$response = Reranking::of($documents)
    ->limit(5)
    ->timeout(60)
    ->rerank('search query');
```

<a name="reranking-collections"></a>
### Переранжування колекцій

Для зручності колекції Laravel можна переранжувати макросом `rerank`. Перший аргумент указує, яке поле (чи поля) використовувати для переранжування, а другий - запит:

```php
// Rerank by a single field...
$posts = Post::all()
    ->rerank('body', 'Laravel tutorials');

// Rerank by multiple fields (sent as JSON)...
$reranked = $posts->rerank(['title', 'body'], 'Laravel tutorials');

// Rerank using a closure to build the document...
$reranked = $posts->rerank(
    fn ($post) => $post->title.': '.$post->body,
    'Laravel tutorials'
);
```

Ви також можете обмежити кількість результатів і вказати провайдера:

```php
$reranked = $posts->rerank(
    by: 'content',
    query: 'Laravel tutorials',
    limit: 10,
    provider: Lab::Cohere,
    timeout: 60,
);
```

<a name="classification"></a>
## Класифікація

> [!WARNING]
> Класифікація поки що експериментальна, і її API може змінитися в майбутніх мінорних випусках AI SDK.

Класифікація дозволяє поставити фіксований набір запитань про заданий рядок чи масив даних і отримати на кожне типізовану відповідь із ймовірністю, а не довільний текст. Це корисно для маршрутизації, модерації й оцінювання - там, де відповідь треба порівняти з порогом або перевірити в тестах.

Класифікувати вміст можна класом `Laravel\Ai\Classification`. Кожне запитання має ключ, і за цим ключем відповідну відповідь можна дістати з результату:

```php
use Laravel\Ai\Classification;
use Laravel\Ai\Classification\Boolean;
use Laravel\Ai\Classification\Choice;
use Laravel\Ai\Classification\Score;

$result = Classification::of($supportRequest)
    ->questions([
        'urgent' => new Boolean('Does this request need an immediate response?', [
            'true' => 'Explicitly time-sensitive',
            'false' => 'No urgency expressed',
        ]),
        'department' => new Choice('Which team should handle this request?', [
            'billing' => 'Payments, invoices, and refunds',
            'technical' => 'Bugs, outages, and integrations',
            'sales' => 'Pricing, plans, and upgrades',
        ]),
        'frustration' => new Score('How frustrated is the customer?', [
            'Calm',
            'Frustrated',
            'Very angry',
        ]),
    ])
    ->classify();
```

Запитання `Boolean` повертають ймовірність того, що відповідь - «так». Метод `isTrue` визначає, чи досягає ця ймовірність заданого порогу, який за замовчуванням дорівнює `0.5`:

```php
$result['urgent']->probability;             // 0.94
$result['urgent']->isTrue(threshold: 0.8);  // true
```

Запитання `Choice` повертають один із запропонованих варіантів разом з ймовірністю кожного варіанта. Властивість `confidence` показує, наскільки провайдер упевнений з огляду на весь набір ймовірностей, і дорівнює `null`, якщо провайдер не може її виміряти:

```php
$result['department']->choice;                      // 'technical'
$result['department']->probabilityOf('technical');  // 0.87
$result['department']->probabilities;               // ['billing' => 0.08, 'technical' => 0.87, 'sales' => 0.05]
$result['department']->confidence;                  // 0.82
```

Запитання `Score` повертають позицію на впорядкованій шкалі переданих рівнів. Властивість `score` зважена за ймовірностями й може опинитися між двома рівнями, а методи `level` і `label` описують найімовірніший рівень:

```php
$result['frustration']->score;          // 1.24, the probability-weighted level
$result['frustration']->level();        // 1, the most probable level
$result['frustration']->label();        // 'Frustrated'
$result['frustration']->normalized();   // 0.62, the score as a fraction of the highest level
$result['frustration']->probabilities;  // [0.12, 0.52, 0.36]
```

Критерії для запитання `Boolean`, описи варіантів для запитання `Choice` і рівні для запитання `Score` можна передати масивом, якщо одного речення замало. Запитання `Choice` потребують щонайменше двох варіантів, а `Score` - щонайменше двох рівнів.

Результат можна перебирати, рахувати й читати як масив. Крім того, метод `answer` повертає одну відповідь, а метод `collect` - усі відповіді як [колекцію](/docs/{{version}}/collections):

```php
$result->answer('urgent');
$result->collect();

$result->usage;
$result->meta->provider;
```

<a name="yes-or-no-decisions"></a>
### Рішення «так» чи «ні»

Для одного рішення «так чи ні» можна скористатися методом `decide`, доступним через клас Laravel `Stringable`: він повертає булеве значення замість повного результату. Ви можете описати, що означають «так» і «ні», і задати ймовірність, якої має досягти відповідь (за замовчуванням `0.5`):

```php
use Illuminate\Support\Str;

if (Str::of($message)->decide('Is this spam?')) {
    // ...
}

$spam = Str::of($message)->decide('Is this spam?', criteria: [
    'true' => 'Unsolicited bulk mail.',
    'false' => 'A genuine message from a customer.',
], threshold: 0.9);
```

<a name="choosing-from-collections"></a>
### Вибір із колекцій

Щоб швидко вибрати один елемент зі списку варіантів, скористайтеся методом `decide`, доступним у класі Laravel `Collection`. Метод приймає запитання й текст, який треба класифікувати, і повертає вибраний елемент колекції.

Колекції рядків і enum можна використовувати напряму, а для інших елементів треба вказати назву через аргумент `by`. Через аргумент `describe` можна також передати поле, масив полів або замикання, щоб дати моделі більше відомостей про кожен варіант:

```php
$department = collect(['billing', 'technical', 'sales'])
    ->decide('Which team should handle this request?', $ticket->body);

$priority = collect(Priority::cases())
    ->decide('How urgent is this request?', $ticket->body);

$department = Department::all()->decide(
    'Which department should handle this request?',
    $ticket->body,
    by: 'name',
    describe: 'description',
);
```

Опис, заданий через аргумент `describe`, слугує критерієм, за яким модель визначає, чи відповідає текст цьому варіанту. Це корисно, коли сама назва варіанта неоднозначна. Якщо в аргумент `describe` передано замикання, воно отримує кожен елемент і має повернути рядок чи масив, що його описує:

```php
$priority = collect(Priority::cases())->decide(
    'How urgent is this request?',
    $ticket->body,
    describe: fn (Priority $priority) => $priority->description(),
);
```

Якщо колекція рядків має рядкові ключі, варіантами слугують ключі, їхніми описами - значення, а повертається вибраний ключ. Якщо задано `threshold` і ймовірність вибраного варіанта нижча за нього, повертається `null`:

```php
$department = collect([
    'billing' => 'Payments, invoices, and refunds',
    'technical' => 'Bugs, outages, and integrations',
])->decide('Which team should handle this request?', $ticket->body, threshold: 0.6) ?? 'triage';
```

<a name="files"></a>
## Файли

Клас `Laravel\Ai\Files` чи окремі класи файлів можна використати, щоб зберігати файли у вашого AI-провайдера для подальшого використання в розмовах. Це корисно для великих документів чи файлів, на які ви хочете посилатися багато разів, не завантажуючи їх щоразу заново:

```php
use Laravel\Ai\Files\Document;
use Laravel\Ai\Files\Image;

// Store a file from a local path...
$response = Document::fromPath('/home/laravel/document.pdf')->put();
$response = Image::fromPath('/home/laravel/photo.jpg')->put();

// Store a file that is stored on a filesystem disk...
$response = Document::fromStorage('document.pdf', disk: 'local')->put();
$response = Image::fromStorage('photo.jpg', disk: 'local')->put();

// Store a file that is stored on a remote URL...
$response = Document::fromUrl('https://example.com/document.pdf')->put();
$response = Image::fromUrl('https://example.com/photo.jpg')->put();

return $response->id;
```

Ви також можете зберігати сирий вміст чи завантажені файли:

```php
use Laravel\Ai\Files;
use Laravel\Ai\Files\Document;

// Store raw content...
$stored = Document::fromString('Hello, World!', 'text/plain')->put();

// Store an uploaded file...
$stored = Document::fromUpload($request->file('document'))->put();
```

Щойно файл збережено, ви можете посилатися на нього під час генерації тексту через агентів, замість завантажувати його заново:

```php
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Files;

$response = (new SalesCoach)->prompt(
    'Analyze the attached sales transcript...',
    attachments: [
        Files\Document::fromId('file-id') // Attach a stored document...
    ]
);
```

Щоб отримати раніше збережений файл, скористайтеся методом `get` на екземплярі файлу:

```php
use Laravel\Ai\Files\Document;

$file = Document::fromId('file-id')->get();

$file->id;
$file->mimeType();
```

Щоб видалити файл у провайдера, скористайтеся методом `delete`:

```php
Document::fromId('file-id')->delete();
```

За замовчуванням клас `Files` використовує AI-провайдера за замовчуванням, налаштованого в конфігураційному файлі `config/ai.php` вашого застосунку. Для більшості операцій ви можете вказати іншого провайдера аргументом `provider`:

```php
$response = Document::fromPath(
    '/home/laravel/document.pdf'
)->put(provider: Lab::Anthropic);
```

Ви можете передати специфічні для провайдера опції завантаження методом `withProviderOptions`. Наприклад, ви можете задати `purpose` файлу в OpenAI:

```php
use Laravel\Ai\Files\Document;

$response = Document::fromPath('/home/laravel/knowledge.txt')
    ->withProviderOptions(['purpose' => 'assistants'])
    ->put();
```

Щоб обмежити опції окремим провайдером, передайте замикання, яке отримує поточного провайдера:

```php
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Files\Document;

$response = Document::fromPath('/home/laravel/training.jsonl')
    ->withProviderOptions(fn (Lab|string $provider) => match ($provider) {
        Lab::OpenAI => ['purpose' => 'fine-tune'],
        default => [],
    })
    ->put();
```

<a name="using-stored-files-in-conversations"></a>
### Використання збережених файлів у розмовах

Щойно файл збережено у провайдера, ви можете посилатися на нього в розмовах агента методом `fromId` на класах `Document` чи `Image`:

```php
use App\Ai\Agents\DocumentAnalyzer;
use Laravel\Ai\Files;
use Laravel\Ai\Files\Document;

$stored = Document::fromPath('/path/to/report.pdf')->put();

$response = (new DocumentAnalyzer)->prompt(
    'Summarize this document.',
    attachments: [
        Document::fromId($stored->id),
    ],
);
```

Так само на збережені зображення можна посилатися через клас `Image`:

```php
use Laravel\Ai\Files;
use Laravel\Ai\Files\Image;

$stored = Image::fromPath('/path/to/photo.jpg')->put();

$response = (new ImageAnalyzer)->prompt(
    'What is in this image?',
    attachments: [
        Image::fromId($stored->id),
    ],
);
```

<a name="vector-stores"></a>
## Векторні сховища

Векторні сховища дозволяють створювати придатні для пошуку колекції файлів, які можна використовувати для retrieval-augmented generation (RAG). Клас `Laravel\Ai\Stores` надає методи для створення, отримання й видалення векторних сховищ:

```php
use Laravel\Ai\Stores;

// Create a new vector store...
$store = Stores::create('Knowledge Base');

// Create a store with additional options...
$store = Stores::create(
    name: 'Knowledge Base',
    description: 'Documentation and reference materials.',
    expiresWhenIdleFor: days(30),
);

return $store->id;
```

Щоб отримати наявне векторне сховище за його ID, скористайтеся методом `get`:

```php
use Laravel\Ai\Stores;

$store = Stores::get('store_id');

$store->id;
$store->name;
$store->fileCounts;
$store->ready;
```

Щоб видалити векторне сховище, скористайтеся методом `delete` на класі `Stores` чи на екземплярі сховища:

```php
use Laravel\Ai\Stores;

// Delete by ID...
Stores::delete('store_id');

// Or delete via a store instance...
$store = Stores::get('store_id');

$store->delete();
```

<a name="adding-files-to-stores"></a>
### Додавання файлів до сховищ

Щойно у вас є векторне сховище, ви можете додавати до нього [файли](#files) методом `add`. Файли, додані до сховища, автоматично індексуються для семантичного пошуку через [інструмент провайдера для пошуку файлів](#file-search):

```php
use Laravel\Ai\Files\Document;
use Laravel\Ai\Stores;

$store = Stores::get('store_id');

// Add a file that has already been stored with the provider...
$document = $store->add('file_id');
$document = $store->add(Document::fromId('file_id'));

// Or, store and add a file in one step...
$document = $store->add(Document::fromPath('/path/to/document.pdf'));
$document = $store->add(Document::fromStorage('manual.pdf'));
$document = $store->add($request->file('document'));

$document->id;
$document->fileId;
```

> **Note:** Зазвичай, коли ви додаєте до векторних сховищ раніше збережені файли, повернутий ID документа збігатиметься з раніше призначеним ID файлу; однак деякі провайдери векторних сховищ можуть повернути новий, інший «ID документа». Тому рекомендується завжди зберігати обидва ID у своїй базі даних для подальшого використання.

Додаючи файл до сховища Gemini, Laravel чекає, доки імпорт завершиться, щоб документ був доступний для пошуку, щойно виклик поверне результат. Якщо імпорт не вдасться або триватиме довше п'яти хвилин, буде викинуто `Laravel\Ai\Exceptions\AiException`, тож файли Gemini, можливо, краще додавати з [завдання в черзі](/docs/{{version}}/queues).

Ви можете прикріпити до файлів метадані, додаючи їх до сховища. Ці метадані згодом можна використати для фільтрації результатів пошуку через [інструмент провайдера для пошуку файлів](#file-search):

```php
$store->add(Document::fromPath('/path/to/document.pdf'), metadata: [
    'author' => 'Taylor Otwell',
    'department' => 'Engineering',
    'year' => 2026,
]);
```

Щоб прибрати файл зі сховища, скористайтеся методом `remove`:

```php
$store->remove('file_id');
```

Прибирання файлу з векторного сховища не видаляє його з [файлового сховища](#files) провайдера. Щоб прибрати файл з векторного сховища й остаточно видалити його з файлового сховища, скористайтеся аргументом `deleteFile`:

```php
$store->remove('file_abc123', deleteFile: true);
```

<a name="usage"></a>
## Використання токенів

Кожна відповідь містить властивість `usage` з кількістю токенів, яку повідомив провайдер. Кількості вхідних і вихідних токенів - загальні, тож токени, враховані як кешовані чи як токени міркувань, теж входять до загальної кількості, до якої вони належать:

```php
$response = (new SalesCoach)->prompt('Analyze this sales transcript...');

$response->usage->inputTokens;
$response->usage->outputTokens;
$response->usage->totalTokens();
```

Генерація тексту повертає екземпляр `Laravel\Ai\Responses\Data\TextUsage`, який розбиває ці підсумки докладніше. Кожне з цих значень дорівнює `null`, а не `0`, якщо провайдер його не повідомляє:

```php
$response->usage->cacheReadInputTokens; // Subset of the input tokens read from a prompt cache...
$response->usage->cacheWriteInputTokens; // Subset of the input tokens written to a prompt cache...
$response->usage->reasoningTokens; // Subset of the output tokens spent on reasoning...

$response->usage->uncachedInputTokens(); // Input tokens that were neither read from nor written to the cache...
```

Читання з кешу, запис у кеш і некешований вхід тарифікуються за різними ставками, тож рахуйте вартість цих трьох величин окремо, а не лише за загальною кількістю вхідних токенів.

Решта можливостей повертають об'єкт використання з власними лічильниками:

<div class="overflow-auto">

| Можливість | Об'єкт використання | Додає |
|---|---|---|
| Текст, класифікація | `TextUsage` | Токени читання з кешу, запису в кеш і міркувань |
| Зображення | `ImageUsage` | `imageInputTokens` і `imageOutputTokens` |
| Транскрибування | `TranscriptionUsage` | `audioSeconds` - тривалість транскрибованого аудіо |
| Переранжування | `RerankingUsage` | `searchUnits`, які деякі провайдери тарифікують замість токенів |
| Аудіо, ембединги | `Usage` | |

</div>

Не кожен провайдер повідомляє кожен лічильник, і лічильники, яких провайдер не повідомляє, дорівнюватимуть `null`:

```php
use Laravel\Ai\Image;
use Laravel\Ai\Transcription;

Image::of('A donut sitting on the kitchen counter')->generate()->usage->imageOutputTokens;

Transcription::fromPath('/home/laravel/meeting.mp3')->generate()->usage->audioSeconds;
```

<a name="failover"></a>
## Резервні провайдери

Промптуючи чи генеруючи інші медіа, ви можете передати масив провайдерів / моделей, щоб автоматично перемкнутися на резервного провайдера / модель, якщо на основному станеться збій сервісу чи спрацює обмеження частоти:

```php
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Image;

$response = (new SalesCoach)->prompt(
    'Analyze this sales transcript...',
    provider: [Lab::OpenAI, Lab::Anthropic],
);

$image = Image::of('A donut sitting on the kitchen counter')
    ->generate(provider: [Lab::Gemini, Lab::xAI]);
```

Перемикання на резерв відбувається лише тоді, коли видано `FailoverableException` - як-от обмеження частоти (`RateLimitedException`), перевантажений чи недоступний провайдер (`ProviderOverloadedException`) або нестача кредитів (`InsufficientCreditsException`). Звичайні помилки, як-от помилка валідації чи хибний запит, перемикання на резерв не спричинять.

Коли ви передаєте простий список провайдерів, як-от `[Lab::OpenAI, Lab::Anthropic]`, кожен провайдер використовує свою модель за замовчуванням. Щоб указати конкретну модель для кожного провайдера в ланцюжку резервування, передайте асоціативний масив з ключами за провайдерами, використовуючи `value` з enum `Lab` як ключ (випадки enum не можна використовувати напряму як ключі масивів PHP):

```php
use Laravel\Ai\Enums\Lab;

$response = (new SalesCoach)->prompt(
    'Analyze this sales transcript...',
    provider: [
        Lab::Gemini->value => 'gemini-3-flash-preview',
        Lab::DeepSeek->value => 'deepseek-v4-pro',
    ],
);
```

<a name="testing"></a>
## Тестування

Коли ви фейкаєте генерацію зображень, аудіо, транскрипцій або векторів у черзі, будь-який колбек `then`, зареєстрований на генерації в черзі, буде викликано з фейковою відповіддю, що дозволяє вам тестувати логіку всередині колбека. Якщо ви хочете, щоб ці колбеки не викликалися, ви можете також зафейкати чергу за допомогою `Queue::fake()`.

<a name="testing-agents"></a>
### Агенти

Щоб підробити відповіді агента під час тестів, викличте метод `fake` на класі агента. За бажанням ви можете передати масив відповідей чи замикання:

```php
use App\Ai\Agents\SalesCoach;
use Laravel\Ai\Prompts\AgentPrompt;

// Automatically generate a fixed response for every prompt...
SalesCoach::fake();

// Provide a list of prompt responses...
SalesCoach::fake([
    'First response',
    'Second response',
]);

// Dynamically handle prompt responses based on the incoming prompt...
SalesCoach::fake(function (AgentPrompt $prompt) {
    return 'Response for: '.$prompt->prompt;
});
```

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

```php
SalesCoach::fake([
    ['score' => 87],
]);
```

Ви також можете підробити відповідь, яка очікує на схвалення інструмента:

```php
use Laravel\Ai\Approvals\PendingApproval;
use Laravel\Ai\Responses\AgentResponse;

FileAssistant::fake([
    AgentResponse::fakeWithPendingApprovals([
        new PendingApproval(
            id: 'call_abc',
            tool: 'DeleteFile',
            arguments: ['path' => 'invoice.pdf'],
            reason: 'This will permanently delete a file.',
        ),
    ]),
]);

$response = (new FileAssistant)->prompt('Delete the invoice.');

$response->hasPendingApprovals(); // true
```

Крім того, можна підробити відповідь із міркуваннями. Така підробка випромінює події міркувань, тож міркування потрапляють і в потокові запуски:

```php
use Laravel\Ai\Responses\AgentResponse;

SalesCoach::fake([
    AgentResponse::fakeWithReasoning('They asked about pricing.', 'Plans start at $10.'),
]);

$response = (new SalesCoach)->stream('What does it cost?');

foreach ($response as $event) {
    // ...
}

$response->reasoning; // 'They asked about pricing.'
```

> **Note:** Коли `Agent::fake()` викликано на агенті, який повертає структурований вивід, а підроблений вивід не було задано явно, Laravel автоматично згенерує підроблені дані, що відповідають визначеній схемі виводу вашого агента.

Після промптингу агента ви можете робити твердження щодо отриманих промптів:

```php
use Laravel\Ai\Prompts\AgentPrompt;

SalesCoach::assertPrompted('Analyze this...');

SalesCoach::assertPrompted(function (AgentPrompt $prompt) {
    return $prompt->contains('Analyze');
});

SalesCoach::assertPromptedTimes(3);

SalesCoach::assertNotPrompted('Missing prompt');

SalesCoach::assertNeverPrompted();
```

Стверджуючи щодо продовження після схвалення, ви можете оглянути рішення про схвалення в промпті:

```php
use Laravel\Ai\Approvals\Decisions;
use Laravel\Ai\Prompts\AgentPrompt;

FileAssistant::fake();

(new FileAssistant)->prompt(Decisions::from([
    'call_abc' => true,
]));

FileAssistant::assertPrompted(function (AgentPrompt $prompt) {
    return $prompt->hasApprovalDecisions()
        && $prompt->approvalDecisions->get('call_abc')->isApproved();
});
```

Для викликів агентів через чергу скористайтеся методами тверджень для черги:

```php
use Laravel\Ai\QueuedAgentPrompt;

SalesCoach::assertQueued('Analyze this...');

SalesCoach::assertQueued(function (QueuedAgentPrompt $prompt) {
    return $prompt->contains('Analyze');
});

SalesCoach::assertNotQueued('Missing prompt');

SalesCoach::assertNeverQueued();
```

Щоб пересвідчитися, що всі виклики агента мають відповідну підроблену відповідь, скористайтеся `preventStrayPrompts`. Якщо агента буде викликано без визначеної підробленої відповіді, буде видано виняток:

```php
SalesCoach::fake()->preventStrayPrompts();
```

<a name="testing-images"></a>
### Зображення

Генерацію зображень можна підробити, викликавши метод `fake` на класі `Image`. Щойно зображення підроблено, можна виконувати різні твердження щодо записаних промптів генерації зображень:

```php
use Laravel\Ai\Image;
use Laravel\Ai\Prompts\ImagePrompt;
use Laravel\Ai\Prompts\QueuedImagePrompt;

// Automatically generate a fixed response for every prompt...
Image::fake();

// Provide a list of prompt responses...
Image::fake([
    base64_encode($firstImage),
    base64_encode($secondImage),
]);

// Dynamically handle prompt responses based on the incoming prompt...
Image::fake(function (ImagePrompt $prompt) {
    return base64_encode('...');
});
```

Після генерації зображень ви можете робити твердження щодо отриманих промптів:

```php
Image::assertGenerated(function (ImagePrompt $prompt) {
    return $prompt->contains('sunset') && $prompt->isLandscape();
});

Image::assertNotGenerated('Missing prompt');

Image::assertNothingGenerated();
```

Для генерації зображень через чергу скористайтеся методами тверджень для черги:

```php
Image::assertQueued(
    fn (QueuedImagePrompt $prompt) => $prompt->contains('sunset')
);

Image::assertNotQueued('Missing prompt');

Image::assertNothingQueued();
```

Щоб пересвідчитися, що всі генерації зображень мають відповідну підроблену відповідь, скористайтеся `preventStrayImages`. Якщо зображення буде згенеровано без визначеної підробленої відповіді, буде видано виняток:

```php
Image::fake()->preventStrayImages();
```

<a name="testing-audio"></a>
### Аудіо

Генерацію аудіо можна підробити, викликавши метод `fake` на класі `Audio`. Щойно аудіо підроблено, можна виконувати різні твердження щодо записаних промптів генерації аудіо:

```php
use Laravel\Ai\Audio;
use Laravel\Ai\Prompts\AudioPrompt;
use Laravel\Ai\Prompts\QueuedAudioPrompt;

// Automatically generate a fixed response for every prompt...
Audio::fake();

// Provide a list of prompt responses...
Audio::fake([
    base64_encode($firstAudio),
    base64_encode($secondAudio),
]);

// Dynamically handle prompt responses based on the incoming prompt...
Audio::fake(function (AudioPrompt $prompt) {
    return base64_encode('...');
});
```

Після генерації аудіо ви можете робити твердження щодо отриманих промптів:

```php
Audio::assertGenerated(function (AudioPrompt $prompt) {
    return $prompt->contains('Hello') && $prompt->isFemale();
});

Audio::assertNotGenerated('Missing prompt');

Audio::assertNothingGenerated();
```

Для генерації аудіо через чергу скористайтеся методами тверджень для черги:

```php
Audio::assertQueued(
    fn (QueuedAudioPrompt $prompt) => $prompt->contains('Hello')
);

Audio::assertNotQueued('Missing prompt');

Audio::assertNothingQueued();
```

Щоб пересвідчитися, що всі генерації аудіо мають відповідну підроблену відповідь, скористайтеся `preventStrayAudio`. Якщо аудіо буде згенеровано без визначеної підробленої відповіді, буде видано виняток:

```php
Audio::fake()->preventStrayAudio();
```

<a name="testing-transcriptions"></a>
### Транскрибування

Генерацію транскриптів можна підробити, викликавши метод `fake` на класі `Transcription`. Щойно транскрибування підроблено, можна виконувати різні твердження щодо записаних промптів генерації транскриптів:

```php
use Laravel\Ai\Transcription;
use Laravel\Ai\Prompts\TranscriptionPrompt;
use Laravel\Ai\Prompts\QueuedTranscriptionPrompt;

// Automatically generate a fixed response for every prompt...
Transcription::fake();

// Provide a list of prompt responses...
Transcription::fake([
    'First transcription text.',
    'Second transcription text.',
]);

// Dynamically handle prompt responses based on the incoming prompt...
Transcription::fake(function (TranscriptionPrompt $prompt) {
    return 'Transcribed text...';
});
```

Після генерації транскриптів ви можете робити твердження щодо отриманих промптів:

```php
Transcription::assertGenerated(function (TranscriptionPrompt $prompt) {
    return $prompt->language === 'en' && $prompt->isDiarized();
});

Transcription::assertNotGenerated(
    fn (TranscriptionPrompt $prompt) => $prompt->language === 'fr'
);

Transcription::assertNothingGenerated();
```

Для генерації транскриптів через чергу скористайтеся методами тверджень для черги:

```php
Transcription::assertQueued(
    fn (QueuedTranscriptionPrompt $prompt) => $prompt->isDiarized()
);

Transcription::assertNotQueued(
    fn (QueuedTranscriptionPrompt $prompt) => $prompt->language === 'fr'
);

Transcription::assertNothingQueued();
```

Щоб пересвідчитися, що всі генерації транскриптів мають відповідну підроблену відповідь, скористайтеся `preventStrayTranscriptions`. Якщо транскрипт буде згенеровано без визначеної підробленої відповіді, буде видано виняток:

```php
Transcription::fake()->preventStrayTranscriptions();
```

<a name="testing-embeddings"></a>
### Ембединги

Генерацію ембедингів можна підробити, викликавши метод `fake` на класі `Embeddings`. Щойно ембединги підроблено, можна виконувати різні твердження щодо записаних промптів генерації ембедингів:

```php
use Laravel\Ai\Embeddings;
use Laravel\Ai\Prompts\EmbeddingsPrompt;
use Laravel\Ai\Prompts\QueuedEmbeddingsPrompt;

// Automatically generate fake embeddings of the proper dimensions for every prompt...
Embeddings::fake();

// Provide a list of prompt responses...
Embeddings::fake([
    [$firstEmbeddingVector],
    [$secondEmbeddingVector],
]);

// Dynamically handle prompt responses based on the incoming prompt...
Embeddings::fake(function (EmbeddingsPrompt $prompt) {
    return array_map(
        fn () => Embeddings::fakeEmbedding($prompt->dimensions),
        $prompt->inputs
    );
});
```

Після генерації ембедингів ви можете робити твердження щодо отриманих промптів:

```php
Embeddings::assertGenerated(function (EmbeddingsPrompt $prompt) {
    return $prompt->contains('Laravel') && $prompt->dimensions === 1536;
});

Embeddings::assertNotGenerated(
    fn (EmbeddingsPrompt $prompt) => $prompt->contains('Other')
);

Embeddings::assertNothingGenerated();
```

Для генерації ембедингів через чергу скористайтеся методами тверджень для черги:

```php
Embeddings::assertQueued(
    fn (QueuedEmbeddingsPrompt $prompt) => $prompt->contains('Laravel')
);

Embeddings::assertNotQueued(
    fn (QueuedEmbeddingsPrompt $prompt) => $prompt->contains('Other')
);

Embeddings::assertNothingQueued();
```

Щоб пересвідчитися, що всі генерації ембедингів мають відповідну підроблену відповідь, скористайтеся `preventStrayEmbeddings`. Якщо ембединги буде згенеровано без визначеної підробленої відповіді, буде видано виняток:

```php
Embeddings::fake()->preventStrayEmbeddings();
```

<a name="testing-reranking"></a>
### Переранжування

Операції переранжування можна підробити, викликавши метод `fake` на класі `Reranking`:

```php
use Laravel\Ai\Reranking;
use Laravel\Ai\Prompts\RerankingPrompt;
use Laravel\Ai\Responses\Data\RankedDocument;

// Automatically generate a fake reranked responses...
Reranking::fake();

// Provide custom responses...
Reranking::fake([
    [
        new RankedDocument(index: 0, document: 'First', score: 0.95),
        new RankedDocument(index: 1, document: 'Second', score: 0.80),
    ],
]);
```

Після переранжування ви можете робити твердження щодо виконаних операцій:

```php
Reranking::assertReranked(function (RerankingPrompt $prompt) {
    return $prompt->contains('Laravel') && $prompt->limit === 5;
});

Reranking::assertNotReranked(
    fn (RerankingPrompt $prompt) => $prompt->contains('Django')
);

Reranking::assertNothingReranked();
```

<a name="testing-classification"></a>
### Класифікація

Класифікацію можна підробити, викликавши метод `fake` на класі `Classification`. Якщо власних відповідей не передано, Laravel автоматично згенерує відповіді відповідної форми для кожного запитання:

```php
use Laravel\Ai\Classification;
use Laravel\Ai\Prompts\ClassificationPrompt;
use Laravel\Ai\Responses\Data\BooleanAnswer;
use Laravel\Ai\Responses\Data\ChoiceAnswer;

// Automatically generate fake answers...
Classification::fake();

// Provide answers for specific questions...
Classification::fake([
    [
        'urgent' => new BooleanAnswer(0.94),
        'department' => new ChoiceAnswer('technical', [
            'billing' => 0.08,
            'technical' => 0.87,
            'sales' => 0.05,
        ], confidence: 0.82),
    ],
]);

// Build answers from the prompt...
Classification::fake(fn (ClassificationPrompt $prompt) => [
    'urgent' => new BooleanAnswer($prompt->contains('ASAP') ? 1.0 : 0.0),
]);
```

Запитання, пропущені в підробленій відповіді, однаково отримають згенеровану відповідь, тож вашим тестам достатньо передати лише ті відповіді, щодо яких вони роблять перевірки.

Після класифікації ви можете перевірити, які операції було виконано:

```php
Classification::assertClassified(function (ClassificationPrompt $prompt) {
    return $prompt->contains('refund') && $prompt->asks('department');
});

Classification::assertNotClassified(
    fn (ClassificationPrompt $prompt) => $prompt->asks('sentiment')
);

Classification::assertNothingClassified();
```

<a name="testing-files"></a>
### Файли

Файлові операції можна підробити, викликавши метод `fake` на класі `Files`:

```php
use Laravel\Ai\Files;

Files::fake();
```

Щойно файлові операції підроблено, ви можете робити твердження щодо завантажень і видалень, які сталися:

```php
use Laravel\Ai\Contracts\Files\StorableFile;
use Laravel\Ai\Files\Document;

// Store files...
Document::fromString('Hello, Laravel!', mimeType: 'text/plain')
    ->as('hello.txt')
    ->put();

// Make assertions...
Files::assertStored(fn (StorableFile $file) =>
    (string) $file === 'Hello, Laravel!' &&
        $file->mimeType() === 'text/plain'
);

Files::assertNotStored(fn (StorableFile $file) =>
    (string) $file === 'Hello, World!'
);

Files::assertNothingStored();
```

Щоб стверджувати щодо видалення файлів, ви можете передати ID файлу:

```php
Files::assertDeleted('file-id');
Files::assertNotDeleted('file-id');
Files::assertNothingDeleted();
```

<a name="testing-vector-stores"></a>
### Векторні сховища

Операції з векторними сховищами можна підробити, викликавши метод `fake` на класі `Stores`. Підробка сховищ також автоматично підробить [файлові операції](#files):

```php
use Laravel\Ai\Stores;

Stores::fake();
```

Щойно операції зі сховищами підроблено, ви можете робити твердження щодо створених чи видалених сховищ:

```php
use Laravel\Ai\Stores;

// Create store...
$store = Stores::create('Knowledge Base');

// Make assertions...
Stores::assertCreated('Knowledge Base');

Stores::assertCreated(fn (string $name, ?string $description) =>
    $name === 'Knowledge Base'
);

Stores::assertNotCreated('Other Store');

Stores::assertNothingCreated();
```

Щоб стверджувати щодо видалення сховищ, ви можете передати ID сховища:

```php
Stores::assertDeleted('store_id');
Stores::assertNotDeleted('other_store_id');
Stores::assertNothingDeleted();
```

Щоб ствердити, що файли було додано до сховища чи прибрано з нього, скористайтеся методами тверджень на відповідному екземплярі `Store`:

```php
Stores::fake();

$store = Stores::get('store_id');

// Add / remove files...
$store->add('added_id');
$store->remove('removed_id');

// Make assertions...
$store->assertAdded('added_id');
$store->assertRemoved('removed_id');

$store->assertNotAdded('other_file_id');
$store->assertNotRemoved('other_file_id');
```

Якщо файл зберігається у [файловому сховищі](#files) провайдера й додається до векторного сховища в межах одного запиту, ви можете не знати ID файлу у провайдера. У цьому разі ви можете передати до методу `assertAdded` замикання, щоб стверджувати щодо вмісту доданого файлу:

```php
use Laravel\Ai\Contracts\Files\StorableFile;
use Laravel\Ai\Files\Document;

$store->add(Document::fromString('Hello, World!', 'text/plain')->as('hello.txt'));

$store->assertAdded(fn (StorableFile $file) => $file->name() === 'hello.txt');
$store->assertAdded(fn (StorableFile $file) => $file->content() === 'Hello, World!');
```

<a name="events"></a>
## Події

Laravel AI SDK диспетчеризує різноманітні [події](/docs/{{version}}/events), зокрема:

- `AddingFileToStore`
- `AgentFailed`
- `AgentFailedOver`
- `AgentPrompted`
- `AgentStreamed`
- `AudioGenerated`
- `Classified`
- `Classifying`
- `CreatingStore`
- `EmbeddingsGenerated`
- `FileAddedToStore`
- `FileDeleted`
- `FileRemovedFromStore`
- `FileStored`
- `GeneratingAudio`
- `GeneratingEmbeddings`
- `GeneratingImage`
- `GeneratingTranscription`
- `ImageGenerated`
- `InvokingTool`
- `PromptingAgent`
- `ProviderFailedOver`
- `RemovingFileFromStore`
- `Reranked`
- `Reranking`
- `StartingStep`
- `StepCompleted`
- `StepFailed`
- `StoreCreated`
- `StoreDeleted`
- `StoringFile`
- `StreamingAgent`
- `ToolApprovalRequested`
- `ToolApprovalResolved`
- `ToolFailed`
- `ToolInvoked`
- `TranscriptionGenerated`

Ви можете слухати будь-яку з цих подій, щоб логувати чи зберігати інформацію про використання AI SDK.

---

Джерело: https://laravelukraine.com/docs/13.x/mcp

# Laravel MCP

- [Вступ](#introduction)
- [Встановлення](#installation)
    - [Публікація маршрутів](#publishing-routes)
- [Створення серверів](#creating-servers)
    - [Реєстрація сервера](#server-registration)
    - [Вебсервери](#web-servers)
    - [Локальні сервери](#local-servers)
    - [Підказки кешування](#cache-hints)
- [Інструменти](#tools)
    - [Створення інструментів](#creating-tools)
    - [Каталоги інструментів із пошуком](#searchable-tool-catalogs)
    - [Вхідні схеми інструментів](#tool-input-schemas)
    - [Вихідні схеми інструментів](#tool-output-schemas)
    - [Валідація аргументів інструмента](#validating-tool-arguments)
    - [Впровадження залежностей в інструменти](#tool-dependency-injection)
    - [Анотації інструментів](#tool-annotations)
    - [Умовна реєстрація інструментів](#conditional-tool-registration)
    - [Відповіді інструментів](#tool-responses)
- [Промпти](#prompts)
    - [Створення промптів](#creating-prompts)
    - [Аргументи промпта](#prompt-arguments)
    - [Валідація аргументів промпта](#validating-prompt-arguments)
    - [Впровадження залежностей у промпти](#prompt-dependency-injection)
    - [Умовна реєстрація промптів](#conditional-prompt-registration)
    - [Відповіді промптів](#prompt-responses)
- [Ресурси](#resources)
    - [Створення ресурсів](#creating-resources)
    - [Шаблони ресурсів](#resource-templates)
    - [URI та MIME-тип ресурсу](#resource-uri-and-mime-type)
    - [Запит ресурсу](#resource-request)
    - [Впровадження залежностей у ресурси](#resource-dependency-injection)
    - [Анотації ресурсів](#resource-annotations)
    - [Умовна реєстрація ресурсів](#conditional-resource-registration)
    - [Відповіді ресурсів](#resource-responses)
- [Застосунки](#apps)
    - [Створення ресурсів застосунку](#creating-app-resources)
    - [Рендеринг застосунків з інструментів](#rendering-apps-from-tools)
    - [Видимість інструментів застосунку](#app-tool-visibility)
    - [Конфігурація застосунку](#app-configuration)
    - [Створення застосунків за допомогою Boost](#building-apps-with-boost)
- [Метадані](#metadata)
- [Іконки](#icons)
- [Автентифікація](#authentication)
    - [OAuth 2.1](#oauth)
    - [Sanctum](#sanctum)
- [Авторизація](#authorization)
- [MCP-клієнт](#client)
    - [Підключення до серверів](#client-connecting)
    - [Іменовані клієнти](#named-clients)
    - [Автентифікація клієнта](#client-authentication)
    - [Інструменти](#client-tools)
    - [Промпти](#client-prompts)
    - [Ресурси](#client-resources)
- [Тестування серверів](#testing-servers)
    - [MCP Inspector](#mcp-inspector)
    - [Юніт-тести](#unit-tests)

<a name="introduction"></a>
## Вступ

[Laravel MCP](https://github.com/laravel/mcp) дає простий та елегантний спосіб для AI-клієнтів взаємодіяти з вашим Laravel-застосунком через [Model Context Protocol](https://modelcontextprotocol.io/docs/getting-started/intro). Він пропонує виразний, плавний інтерфейс для визначення серверів, інструментів, ресурсів і промптів, які уможливлюють взаємодію з вашим застосунком за допомогою AI.

<a name="installation"></a>
## Встановлення

Для початку встановіть Laravel MCP у свій проєкт за допомогою менеджера пакетів Composer:

```shell
composer require laravel/mcp
```

<a name="publishing-routes"></a>
### Публікація маршрутів

Після встановлення Laravel MCP виконайте артизан-команду `vendor:publish`, щоб опублікувати файл `routes/ai.php`, у якому ви визначатимете свої MCP-сервери:

```shell
php artisan vendor:publish --tag=ai-routes
```

Ця команда створює файл `routes/ai.php` у каталозі `routes` вашого застосунку, який ви використовуватимете для реєстрації своїх MCP-серверів.

<a name="creating-servers"></a>
## Створення серверів

Створити MCP-сервер можна артизан-командою `make:mcp-server`. Сервери є центральною точкою комунікації, яка надає AI-клієнтам можливості MCP: інструменти, ресурси й промпти:

```shell
php artisan make:mcp-server WeatherServer
```

Ця команда створить новий клас сервера в каталозі `app/Mcp/Servers`. Згенерований клас сервера розширює базовий клас `Laravel\Mcp\Server` з Laravel MCP і надає атрибути та властивості для налаштування сервера й реєстрації інструментів, ресурсів і промптів:

```php
<?php

namespace App\Mcp\Servers;

use Laravel\Mcp\Server\Attributes\Instructions;
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Version;
use Laravel\Mcp\Server;

#[Name('Weather Server')]
#[Version('1.0.0')]
#[Instructions('This server provides weather information and forecasts.')]
class WeatherServer extends Server
{
    /**
     * The tools registered with this MCP server.
     *
     * @var array<int, class-string<\Laravel\Mcp\Server\Tool>>
     */
    protected array $tools = [
        // GetCurrentWeatherTool::class,
    ];

    /**
     * The resources registered with this MCP server.
     *
     * @var array<int, class-string<\Laravel\Mcp\Server\Resource>>
     */
    protected array $resources = [
        // WeatherGuidelinesResource::class,
    ];

    /**
     * The prompts registered with this MCP server.
     *
     * @var array<int, class-string<\Laravel\Mcp\Server\Prompt>>
     */
    protected array $prompts = [
        // DescribeWeatherPrompt::class,
    ];
}
```

<a name="server-registration"></a>
### Реєстрація сервера

Щойно ви створили сервер, вам потрібно зареєструвати його у файлі `routes/ai.php`, щоб зробити його доступним. Laravel MCP пропонує два методи реєстрації серверів: `web` для серверів, доступних через HTTP, і `local` для серверів командного рядка.

<a name="web-servers"></a>
### Вебсервери

Вебсервери - найпоширеніший тип серверів; вони доступні через HTTP POST-запити, що робить їх ідеальними для віддалених AI-клієнтів чи вебінтеграцій. Зареєструйте вебсервер методом `web`:

```php
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::web('/mcp/weather', WeatherServer::class);
```

Так само як і зі звичайними маршрутами, ви можете застосувати `middleware`, щоб захистити свої вебсервери:

```php
Mcp::web('/mcp/weather', WeatherServer::class)
    ->middleware(['throttle:mcp']);
```

<a name="local-servers"></a>
### Локальні сервери

Локальні сервери працюють як артизан-команди - ідеально для створення локальних інтеграцій з AI-асистентами, як-от [Laravel Boost](/docs/{{version}}/installation#installing-laravel-boost). Зареєструйте локальний сервер методом `local`:

```php
use App\Mcp\Servers\WeatherServer;
use Laravel\Mcp\Facades\Mcp;

Mcp::local('weather', WeatherServer::class);
```

Щойно сервер зареєстровано, вам зазвичай не потрібно вручну запускати артизан-команду `mcp:start`. Натомість налаштуйте свій MCP-клієнт (AI-агента) запускати сервер або скористайтеся [MCP Inspector](#mcp-inspector).

<a name="cache-hints"></a>
### Підказки кешування

Laravel MCP включає підказки кешування з відповідями, які можуть бути кешовані, такими як виявлення сервера, список примітивів і читання ресурсів. За замовчуванням ці відповіді позначаються як приватні з часом життя нуль мілісекунд.

Ви можете налаштувати підказку кешування за замовчуванням для сервера, використовуючи атрибут `Cacheable`:

```php
use Laravel\Mcp\Enums\CacheScope;
use Laravel\Mcp\Server\Attributes\Cacheable;

#[Cacheable(ttlMs: 60_000, scope: CacheScope::Public)]
class WeatherServer extends Server
{
    /**
     * Get the cache hints for individual MCP methods.
     *
     * @return array<string, \Laravel\Mcp\Server\Attributes\Cacheable>
     */
    protected function cacheHints(): array
    {
        return [
            'tools/list' => new Cacheable(ttlMs: 30_000, scope: CacheScope::Public),
        ];
    }
}
```

Область `CacheScope::Private` обмежує кешовані відповіді тим самим контекстом авторизації, тоді як `CacheScope::Public` дозволяє спільно використовувати відповіді між користувачами. Підказки кешування є рекомендаційними; MCP-клієнт або хост визначає, чи фактично кешується відповідь. Підказки для конкретних методів, що повертаються `cacheHints`, мають пріоритет над атрибутом `Cacheable` сервера.

Ви можете перевизначити підказку кешування сервера для окремого ресурсу, застосувавши атрибут `Cacheable` до класу ресурсу:

```php
#[Cacheable(ttlMs: 300_000, scope: CacheScope::Public)]
class WeatherGuidelinesResource extends Resource
{
    // ...
}
```

Атрибут `Cacheable` ресурсу має пріоритет як над підказкою для конкретного методу, так і над підказкою сервера за замовчуванням.

<a name="tools"></a>
## Інструменти

Інструменти дозволяють вашому серверу надавати функціональність, яку можуть викликати AI-клієнти. Вони дають мовним моделям змогу виконувати дії, запускати код чи взаємодіяти із зовнішніми системами:

```php
<?php

namespace App\Mcp\Tools;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Tool;

#[Description('Fetches the current weather forecast for a specified location.')]
class CurrentWeatherTool extends Tool
{
    /**
     * Handle the tool request.
     */
    public function handle(Request $request): Response
    {
        $location = $request->get('location');

        // Get weather...

        return Response::text('The weather is...');
    }

    /**
     * Get the tool's input schema.
     *
     * @return array<string, \Illuminate\JsonSchema\Types\Type>
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'location' => $schema->string()
                ->description('The location to get the weather for.')
                ->required(),
        ];
    }
}
```

<a name="creating-tools"></a>
### Створення інструментів

Щоб створити інструмент, виконайте артизан-команду `make:mcp-tool`:

```shell
php artisan make:mcp-tool CurrentWeatherTool
```

Після створення інструмента зареєструйте його у властивості `$tools` свого сервера:

```php
<?php

namespace App\Mcp\Servers;

use App\Mcp\Tools\CurrentWeatherTool;
use Laravel\Mcp\Server;

class WeatherServer extends Server
{
    /**
     * The tools registered with this MCP server.
     *
     * @var array<int, class-string<\Laravel\Mcp\Server\Tool>>
     */
    protected array $tools = [
        CurrentWeatherTool::class,
    ];
}
```

<a name="searchable-tool-catalogs"></a>
### Каталоги інструментів із пошуком

Сервери з великою кількістю інструментів можуть розмістити частину з них у каталозі з пошуком, замість оголошувати кожен AI-клієнту. Каталог із пошуком надає два інструменти: `search_tools`, який шукає в каталозі за назвою, описом і схемою вхідних даних; і `execute_tools`, який викликає один або кілька знайдених.

Щоб створити каталог із пошуком, використовуйте клас `ToolSearch` як ключ масиву у властивості `$tools` вашого сервера:

```php
<?php

namespace App\Mcp\Servers;

use App\Mcp\Tools\CurrentWeatherTool;
use App\Mcp\Tools\HistoricalWeatherTool;
use App\Mcp\Tools\WeatherAlertsTool;
use Laravel\Mcp\Server;
use Laravel\Mcp\Server\Tools\ToolSearch;

class WeatherServer extends Server
{
    /**
     * The tools registered with this MCP server.
     *
     * @var array<int|string, \Laravel\Mcp\Server\Tool|class-string<\Laravel\Mcp\Server\Tool>|array<int, \Laravel\Mcp\Server\Tool|class-string<\Laravel\Mcp\Server\Tool>>>
     */
    protected array $tools = [
        CurrentWeatherTool::class,

        ToolSearch::class => [
            HistoricalWeatherTool::class,
            WeatherAlertsTool::class,
        ],
    ];
}
```

У цьому прикладі `CurrentWeatherTool` оголошується безпосередньо, тоді як інструменти історичної погоди та погодних сповіщень доступні через каталог із пошуком. Умовна реєстрація інструментів все ще враховується, коли інструменти з каталогу шукаються або виконуються.

Максимальна кількість інструментів, які можуть бути виконані в одному виклику `execute_tools`, та максимальний розмір відповіді контролюються значеннями конфігурації `mcp.tool_search.max_tool_calls` і `mcp.tool_search.max_output_bytes`.

<a name="tool-name-title-description"></a>
#### Ім'я, заголовок і опис інструмента

За замовчуванням ім'я та заголовок інструмента виводяться з імені класу. Наприклад, `CurrentWeatherTool` матиме ім'я `current-weather` і заголовок `Current Weather Tool`. Ви можете змінити ці значення атрибутами `Name` і `Title`:

```php
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;

#[Name('get-optimistic-weather')]
#[Title('Get Optimistic Weather Forecast')]
class CurrentWeatherTool extends Tool
{
    // ...
}
```

Описи інструментів не генеруються автоматично. Вам слід завжди надавати змістовний опис через атрибут `Description`:

```php
use Laravel\Mcp\Server\Attributes\Description;

#[Description('Fetches the current weather forecast for a specified location.')]
class CurrentWeatherTool extends Tool
{
    //
}
```

> [!NOTE]
> Опис - критично важлива частина метаданих інструмента, адже він допомагає AI-моделям зрозуміти, коли і як ефективно використовувати інструмент.

<a name="tool-input-schemas"></a>
### Вхідні схеми інструментів

Інструменти можуть визначати вхідні схеми, щоб указати, які аргументи вони приймають від AI-клієнтів. Скористайтеся білдером `Illuminate\Contracts\JsonSchema\JsonSchema` з Laravel, щоб визначити вимоги до вхідних даних вашого інструмента:

```php
<?php

namespace App\Mcp\Tools;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Server\Tool;

class CurrentWeatherTool extends Tool
{
    /**
     * Get the tool's input schema.
     *
     * @return array<string, \Illuminate\JsonSchema\Types\Type>
     */
    public function schema(JsonSchema $schema): array
    {
        return [
            'location' => $schema->string()
                ->description('The location to get the weather for.')
                ->required(),

            'units' => $schema->string()
                ->enum(['celsius', 'fahrenheit'])
                ->description('The temperature units to use.')
                ->default('celsius'),
        ];
    }
}
```

<a name="tool-output-schemas"></a>
### Вихідні схеми інструментів

Інструменти можуть визначати [вихідні схеми](https://modelcontextprotocol.io/specification/2026-07-28/server/tools#output-schema), щоб указати структуру своїх відповідей. Це дає кращу інтеграцію з AI-клієнтами, яким потрібні придатні до розбору результати інструментів. Скористайтеся методом `outputSchema`, щоб визначити структуру виводу вашого інструмента:

```php
<?php

namespace App\Mcp\Tools;

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Mcp\Server\Tool;

class CurrentWeatherTool extends Tool
{
    /**
     * Get the tool's output schema.
     *
     * @return array<string, \Illuminate\JsonSchema\Types\Type>
     */
    public function outputSchema(JsonSchema $schema): array
    {
        return [
            'temperature' => $schema->number()
                ->description('Temperature in Celsius')
                ->required(),

            'conditions' => $schema->string()
                ->description('Weather conditions')
                ->required(),

            'humidity' => $schema->integer()
                ->description('Humidity percentage')
                ->required(),
        ];
    }
}
```

<a name="validating-tool-arguments"></a>
### Валідація аргументів інструмента

Визначення JSON Schema дають базову структуру для аргументів інструмента, але ви також можете захотіти застосувати складніші правила валідації.

Laravel MCP безшовно інтегрується з [можливостями валідації](/docs/{{version}}/validation) Laravel. Ви можете валідувати вхідні аргументи інструмента в його методі `handle`:

```php
<?php

namespace App\Mcp\Tools;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Tool;

class CurrentWeatherTool extends Tool
{
    /**
     * Handle the tool request.
     */
    public function handle(Request $request): Response
    {
        $validated = $request->validate([
            'location' => 'required|string|max:100',
            'units' => 'in:celsius,fahrenheit',
        ]);

        // Fetch weather data using the validated arguments...
    }
}
```

У разі невдалої валідації AI-клієнти діятимуть на основі наданих вами повідомлень про помилки. Тому критично важливо надавати зрозумілі повідомлення, з яких випливає, що робити:

```php
$validated = $request->validate([
    'location' => ['required','string','max:100'],
    'units' => 'in:celsius,fahrenheit',
],[
    'location.required' => 'You must specify a location to get the weather for. For example, "New York City" or "Tokyo".',
    'units.in' => 'You must specify either "celsius" or "fahrenheit" for the units.',
]);
```

<a name="tool-dependency-injection"></a>
#### Впровадження залежностей в інструменти

[Сервіс-контейнер](/docs/{{version}}/container) Laravel використовується для створення всіх інструментів. Тому ви можете вказати типи будь-яких залежностей, потрібних вашому інструменту, у його конструкторі. Оголошені залежності будуть автоматично отримані й впроваджені в екземпляр інструмента:

```php
<?php

namespace App\Mcp\Tools;

use App\Repositories\WeatherRepository;
use Laravel\Mcp\Server\Tool;

class CurrentWeatherTool extends Tool
{
    /**
     * Create a new tool instance.
     */
    public function __construct(
        protected WeatherRepository $weather,
    ) {}

    // ...
}
```

Окрім впровадження через конструктор, ви також можете вказати типи залежностей у методі `handle()` свого інструмента. Сервіс-контейнер автоматично отримає й впровадить залежності під час виклику методу:

```php
<?php

namespace App\Mcp\Tools;

use App\Repositories\WeatherRepository;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Tool;

class CurrentWeatherTool extends Tool
{
    /**
     * Handle the tool request.
     */
    public function handle(Request $request, WeatherRepository $weather): Response
    {
        $location = $request->get('location');

        $forecast = $weather->getForecastFor($location);

        // ...
    }
}
```

<a name="tool-annotations"></a>
### Анотації інструментів

Ви можете збагатити свої інструменти [анотаціями](https://modelcontextprotocol.io/specification/2026-07-28/schema#toolannotations), щоб надати AI-клієнтам додаткові метадані. Ці анотації допомагають AI-моделям зрозуміти поведінку й можливості інструмента. Анотації додаються до інструментів через атрибути:

```php
<?php

namespace App\Mcp\Tools;

use Laravel\Mcp\Server\Tools\Annotations\IsIdempotent;
use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
use Laravel\Mcp\Server\Tool;

#[IsIdempotent]
#[IsReadOnly]
class CurrentWeatherTool extends Tool
{
    //
}
```

Доступні анотації:

<div class="overflow-auto">

| Annotation         | Type    | Description                                                                                  |
| ------------------ | ------- | -------------------------------------------------------------------------------------------- |
| `#[IsReadOnly]`    | boolean | Вказує, що інструмент не змінює своє середовище.                                          |
| `#[IsDestructive]` | boolean | Вказує, що інструмент може виконувати руйнівні оновлення (має сенс лише коли не read-only).     |
| `#[IsIdempotent]`  | boolean | Вказує, що повторні виклики з тими самими аргументами не мають додаткового ефекту (коли не read-only). |
| `#[IsOpenWorld]`   | boolean | Вказує, що інструмент може взаємодіяти із зовнішніми сутностями.                                      |

</div>

Значення анотацій можна задати явно булевими аргументами:

```php
use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;
use Laravel\Mcp\Server\Tools\Annotations\IsDestructive;
use Laravel\Mcp\Server\Tools\Annotations\IsOpenWorld;
use Laravel\Mcp\Server\Tools\Annotations\IsIdempotent;
use Laravel\Mcp\Server\Tool;

#[IsReadOnly(true)]
#[IsDestructive(false)]
#[IsOpenWorld(false)]
#[IsIdempotent(true)]
class CurrentWeatherTool extends Tool
{
    //
}
```

<a name="conditional-tool-registration"></a>
### Умовна реєстрація інструментів

Ви можете умовно реєструвати інструменти під час виконання, реалізувавши метод `shouldRegister` у класі інструмента. Цей метод дозволяє визначити, чи має інструмент бути доступним, залежно від стану застосунку, конфігурації чи параметрів запиту:

```php
<?php

namespace App\Mcp\Tools;

use Laravel\Mcp\Request;
use Laravel\Mcp\Server\Tool;

class CurrentWeatherTool extends Tool
{
    /**
     * Determine if the tool should be registered.
     */
    public function shouldRegister(Request $request): bool
    {
        return $request?->user()?->subscribed() ?? false;
    }
}
```

Коли метод `shouldRegister` інструмента повертає `false`, той не з'явиться у списку доступних інструментів і не може бути викликаний AI-клієнтами.

<a name="tool-responses"></a>
### Відповіді інструментів

Інструменти мають повертати екземпляр `Laravel\Mcp\Response`. Клас Response надає кілька зручних методів для створення різних типів відповідей:

Для простих текстових відповідей скористайтеся методом `text`:

```php
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;

/**
 * Handle the tool request.
 */
public function handle(Request $request): Response
{
    // ...

    return Response::text('Weather Summary: Sunny, 72°F');
}
```

Щоб позначити, що під час виконання інструмента сталася помилка, скористайтеся методом `error`:

```php
return Response::error('Unable to fetch weather data. Please try again.');
```

Щоб повернути зображення чи аудіо, скористайтеся методами `image` та `audio`:

```php
return Response::image(file_get_contents(storage_path('weather/radar.png')), 'image/png');

return Response::audio(file_get_contents(storage_path('weather/alert.mp3')), 'audio/mp3');
```

Ви також можете завантажити зображення й аудіо безпосередньо з диска файлової системи Laravel методом `fromStorage`. MIME-тип буде визначено з файлу автоматично:

```php
return Response::fromStorage('weather/radar.png');
```

За потреби ви можете вказати конкретний диск або перевизначити MIME-тип:

```php
return Response::fromStorage('weather/radar.png', disk: 's3');

return Response::fromStorage('weather/radar.png', mimeType: 'image/webp');
```

<a name="multiple-content-responses"></a>
#### Відповіді з кількома частинами вмісту

Інструменти можуть повертати кілька частин вмісту, повертаючи масив екземплярів `Response`:

```php
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;

/**
 * Handle the tool request.
 *
 * @return array<int, \Laravel\Mcp\Response>
 */
public function handle(Request $request): array
{
    // ...

    return [
        Response::text('Weather Summary: Sunny, 72°F'),
        Response::text("**Detailed Forecast**\n- Morning: 65°F\n- Afternoon: 78°F\n- Evening: 70°F")
    ];
}
```

<a name="structured-responses"></a>
#### Структуровані відповіді

Інструменти можуть повертати [структурований вміст](https://modelcontextprotocol.io/specification/2026-07-28/server/tools#structured-content) методом `structured`. Це дає AI-клієнтам придатні до розбору дані, водночас зберігаючи зворотну сумісність із текстовим представленням у форматі JSON:

```php
return Response::structured([
    'temperature' => 22.5,
    'conditions' => 'Partly cloudy',
    'humidity' => 65,
]);
```

Якщо вам потрібно надати власний текст поряд зі структурованим вмістом, скористайтеся методом `withStructuredContent` на фабриці відповідей:

```php
return Response::make(
    Response::text('Weather is 22.5°C and sunny')
)->withStructuredContent([
    'temperature' => 22.5,
    'conditions' => 'Sunny',
]);
```

<a name="streaming-responses"></a>
#### Потокові відповіді

Для довготривалих операцій чи потокової передачі даних у реальному часі інструменти можуть повертати [генератор](https://www.php.net/manual/en/language.generators.overview.php) зі свого методу `handle`. Це дозволяє надсилати клієнту проміжні оновлення перед фінальною відповіддю:

```php
<?php

namespace App\Mcp\Tools;

use Generator;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Tool;

class CurrentWeatherTool extends Tool
{
    /**
     * Handle the tool request.
     *
     * @return \Generator<int, \Laravel\Mcp\Response>
     */
    public function handle(Request $request): Generator
    {
        $locations = $request->array('locations');

        foreach ($locations as $index => $location) {
            yield Response::notification('processing/progress', [
                'current' => $index + 1,
                'total' => count($locations),
                'location' => $location,
            ]);

            yield Response::text($this->forecastFor($location));
        }
    }
}
```

Використовуючи вебсервери, потокові відповіді автоматично відкривають SSE-потік (Server-Sent Events), надсилаючи кожне видане повідомлення клієнту як подію.

<a name="prompts"></a>
## Промпти

[Промпти](https://modelcontextprotocol.io/specification/2026-07-28/server/prompts) дозволяють вашому серверу ділитися багаторазовими шаблонами промптів, які AI-клієнти можуть використовувати для взаємодії з мовними моделями. Вони дають стандартизований спосіб структурувати типові запити й взаємодії.

<a name="creating-prompts"></a>
### Створення промптів

Щоб створити промпт, виконайте артизан-команду `make:mcp-prompt`:

```shell
php artisan make:mcp-prompt DescribeWeatherPrompt
```

Після створення промпта зареєструйте його у властивості `$prompts` свого сервера:

```php
<?php

namespace App\Mcp\Servers;

use App\Mcp\Prompts\DescribeWeatherPrompt;
use Laravel\Mcp\Server;

class WeatherServer extends Server
{
    /**
     * The prompts registered with this MCP server.
     *
     * @var array<int, class-string<\Laravel\Mcp\Server\Prompt>>
     */
    protected array $prompts = [
        DescribeWeatherPrompt::class,
    ];
}
```

<a name="prompt-name-title-and-description"></a>
#### Ім'я, заголовок і опис промпта

За замовчуванням ім'я та заголовок промпта виводяться з імені класу. Наприклад, `DescribeWeatherPrompt` матиме ім'я `describe-weather` і заголовок `Describe Weather Prompt`. Ви можете змінити ці значення атрибутами `Name` і `Title`:

```php
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;

#[Name('weather-assistant')]
#[Title('Weather Assistant Prompt')]
class DescribeWeatherPrompt extends Prompt
{
    // ...
}
```

Описи промптів не генеруються автоматично. Вам слід завжди надавати змістовний опис через атрибут `Description`:

```php
use Laravel\Mcp\Server\Attributes\Description;

#[Description('Generates a natural-language explanation of the weather for a given location.')]
class DescribeWeatherPrompt extends Prompt
{
    //
}
```

> [!NOTE]
> Опис - критично важлива частина метаданих промпта, адже він допомагає AI-моделям зрозуміти, коли і як найкраще скористатися промптом.

<a name="prompt-arguments"></a>
### Аргументи промпта

Промпти можуть визначати аргументи, які дозволяють AI-клієнтам налаштовувати шаблон промпта конкретними значеннями. Скористайтеся методом `arguments`, щоб визначити, які аргументи приймає ваш промпт:

```php
<?php

namespace App\Mcp\Prompts;

use Laravel\Mcp\Server\Prompt;
use Laravel\Mcp\Server\Prompts\Argument;

class DescribeWeatherPrompt extends Prompt
{
    /**
     * Get the prompt's arguments.
     *
     * @return array<int, \Laravel\Mcp\Server\Prompts\Argument>
     */
    public function arguments(): array
    {
        return [
            new Argument(
                name: 'tone',
                description: 'The tone to use in the weather description (e.g., formal, casual, humorous).',
                required: true,
            ),
        ];
    }
}
```

<a name="validating-prompt-arguments"></a>
### Валідація аргументів промпта

Аргументи промпта валідуються автоматично на основі їхнього визначення, але ви також можете захотіти застосувати складніші правила валідації.

Laravel MCP безшовно інтегрується з [можливостями валідації](/docs/{{version}}/validation) Laravel. Ви можете валідувати вхідні аргументи промпта в його методі `handle`:

```php
<?php

namespace App\Mcp\Prompts;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Prompt;

class DescribeWeatherPrompt extends Prompt
{
    /**
     * Handle the prompt request.
     */
    public function handle(Request $request): Response
    {
        $validated = $request->validate([
            'tone' => 'required|string|max:50',
        ]);

        $tone = $validated['tone'];

        // Generate the prompt response using the given tone...
    }
}
```

У разі невдалої валідації AI-клієнти діятимуть на основі наданих вами повідомлень про помилки. Тому критично важливо надавати зрозумілі повідомлення, з яких випливає, що робити:

```php
$validated = $request->validate([
    'tone' => ['required','string','max:50'],
],[
    'tone.*' => 'You must specify a tone for the weather description. Examples include "formal", "casual", or "humorous".',
]);
```

<a name="prompt-dependency-injection"></a>
### Впровадження залежностей у промпти

[Сервіс-контейнер](/docs/{{version}}/container) Laravel використовується для створення всіх промптів. Тому ви можете вказати типи будь-яких залежностей, потрібних вашому промпту, у його конструкторі. Оголошені залежності будуть автоматично отримані й впроваджені в екземпляр промпта:

```php
<?php

namespace App\Mcp\Prompts;

use App\Repositories\WeatherRepository;
use Laravel\Mcp\Server\Prompt;

class DescribeWeatherPrompt extends Prompt
{
    /**
     * Create a new prompt instance.
     */
    public function __construct(
        protected WeatherRepository $weather,
    ) {}

    //
}
```

Окрім впровадження через конструктор, ви також можете вказати типи залежностей у методі `handle` свого промпта. Сервіс-контейнер автоматично отримає й впровадить залежності під час виклику методу:

```php
<?php

namespace App\Mcp\Prompts;

use App\Repositories\WeatherRepository;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Prompt;

class DescribeWeatherPrompt extends Prompt
{
    /**
     * Handle the prompt request.
     */
    public function handle(Request $request, WeatherRepository $weather): Response
    {
        $isAvailable = $weather->isServiceAvailable();

        // ...
    }
}
```

<a name="conditional-prompt-registration"></a>
### Умовна реєстрація промптів

Ви можете умовно реєструвати промпти під час виконання, реалізувавши метод `shouldRegister` у класі промпта. Цей метод дозволяє визначити, чи має промпт бути доступним, залежно від стану застосунку, конфігурації чи параметрів запиту:

```php
<?php

namespace App\Mcp\Prompts;

use Laravel\Mcp\Request;
use Laravel\Mcp\Server\Prompt;

class CurrentWeatherPrompt extends Prompt
{
    /**
     * Determine if the prompt should be registered.
     */
    public function shouldRegister(Request $request): bool
    {
        return $request?->user()?->subscribed() ?? false;
    }
}
```

Коли метод `shouldRegister` промпта повертає `false`, той не з'явиться у списку доступних промптів і не може бути викликаний AI-клієнтами.

<a name="prompt-responses"></a>
### Відповіді промптів

Промпти можуть повертати один `Laravel\Mcp\Response` або ітерований набір екземплярів `Laravel\Mcp\Response`. Ці відповіді інкапсулюють вміст, який буде надіслано AI-клієнту:

```php
<?php

namespace App\Mcp\Prompts;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Prompt;

class DescribeWeatherPrompt extends Prompt
{
    /**
     * Handle the prompt request.
     *
     * @return array<int, \Laravel\Mcp\Response>
     */
    public function handle(Request $request): array
    {
        $tone = $request->string('tone');

        $systemMessage = "You are a helpful weather assistant. Please provide a weather description in a {$tone} tone.";

        $userMessage = "What is the current weather like in New York City?";

        return [
            Response::text($systemMessage)->asAssistant(),
            Response::text($userMessage),
        ];
    }
}
```

Ви можете скористатися методом `asAssistant()`, щоб позначити, що повідомлення-відповідь слід вважати таким, що надійшло від AI-асистента, тоді як звичайні повідомлення вважаються введенням користувача.

<a name="resources"></a>
## Ресурси

[Ресурси](https://modelcontextprotocol.io/specification/2026-07-28/server/resources) дозволяють вашому серверу надавати дані й вміст, які AI-клієнти можуть читати й використовувати як контекст під час взаємодії з мовними моделями. Вони дають спосіб ділитися статичною чи динамічною інформацією: документацією, конфігурацією чи будь-якими даними, що допомагають формувати відповіді AI.

<a name="creating-resources"></a>
## Створення ресурсів

Щоб створити ресурс, виконайте артизан-команду `make:mcp-resource`:

```shell
php artisan make:mcp-resource WeatherGuidelinesResource
```

Після створення ресурсу зареєструйте його у властивості `$resources` свого сервера:

```php
<?php

namespace App\Mcp\Servers;

use App\Mcp\Resources\WeatherGuidelinesResource;
use Laravel\Mcp\Server;

class WeatherServer extends Server
{
    /**
     * The resources registered with this MCP server.
     *
     * @var array<int, class-string<\Laravel\Mcp\Server\Resource>>
     */
    protected array $resources = [
        WeatherGuidelinesResource::class,
    ];
}
```

<a name="resource-name-title-and-description"></a>
#### Ім'я, заголовок і опис ресурсу

За замовчуванням ім'я та заголовок ресурсу виводяться з імені класу. Наприклад, `WeatherGuidelinesResource` матиме ім'я `weather-guidelines` і заголовок `Weather Guidelines Resource`. Ви можете змінити ці значення атрибутами `Name` і `Title`:

```php
use Laravel\Mcp\Server\Attributes\Name;
use Laravel\Mcp\Server\Attributes\Title;

#[Name('weather-api-docs')]
#[Title('Weather API Documentation')]
class WeatherGuidelinesResource extends Resource
{
    // ...
}
```

Описи ресурсів не генеруються автоматично. Вам слід завжди надавати змістовний опис через атрибут `Description`:

```php
use Laravel\Mcp\Server\Attributes\Description;

#[Description('Comprehensive guidelines for using the Weather API.')]
class WeatherGuidelinesResource extends Resource
{
    //
}
```

> [!NOTE]
> Опис - критично важлива частина метаданих ресурсу, адже він допомагає AI-моделям зрозуміти, коли і як ефективно використовувати ресурс.

<a name="resource-templates"></a>
### Шаблони ресурсів

[Шаблони ресурсів](https://modelcontextprotocol.io/specification/2026-07-28/server/resources#resource-templates) дозволяють вашому серверу надавати динамічні ресурси, що відповідають URI-шаблонам зі змінними. Замість визначати статичний URI для кожного ресурсу, ви можете створити один ресурс, який обробляє кілька URI за шаблоном.

<a name="creating-resource-templates"></a>
#### Створення шаблонів ресурсів

Щоб створити шаблон ресурсу, реалізуйте інтерфейс `HasUriTemplate` у класі свого ресурсу й визначте метод `uriTemplate`, що повертає екземпляр `UriTemplate`:

```php
<?php

namespace App\Mcp\Resources;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Attributes\MimeType;
use Laravel\Mcp\Server\Contracts\HasUriTemplate;
use Laravel\Mcp\Server\Resource;
use Laravel\Mcp\Support\UriTemplate;

#[Description('Access user files by ID')]
#[MimeType('text/plain')]
class UserFileResource extends Resource implements HasUriTemplate
{
    /**
     * Get the URI template for this resource.
     */
    public function uriTemplate(): UriTemplate
    {
        return new UriTemplate('file://users/{userId}/files/{fileId}');
    }

    /**
     * Handle the resource request.
     */
    public function handle(Request $request): Response
    {
        $userId = $request->get('userId');
        $fileId = $request->get('fileId');

        // Fetch and return the file content...

        return Response::text($content);
    }
}
```

Коли ресурс реалізує інтерфейс `HasUriTemplate`, його буде зареєстровано як шаблон ресурсу, а не як статичний ресурс. AI-клієнти зможуть запитувати ресурси за URI, що відповідають шаблону, а змінні з URI будуть автоматично витягнуті й доступні в методі `handle` вашого ресурсу.

<a name="uri-template-syntax"></a>
#### Синтаксис URI-шаблонів

URI-шаблони використовують плейсхолдери у фігурних дужках, щоб визначити змінні сегменти URI:

```php
new UriTemplate('file://users/{userId}');
new UriTemplate('file://users/{userId}/files/{fileId}');
new UriTemplate('https://api.example.com/{version}/{resource}/{id}');
```

<a name="accessing-template-variables"></a>
#### Доступ до змінних шаблону

Коли URI відповідає вашому шаблону ресурсу, витягнуті змінні автоматично додаються до запиту, і до них можна звернутися методом `get`:

```php
<?php

namespace App\Mcp\Resources;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Contracts\HasUriTemplate;
use Laravel\Mcp\Server\Resource;
use Laravel\Mcp\Support\UriTemplate;

class UserProfileResource extends Resource implements HasUriTemplate
{
    public function uriTemplate(): UriTemplate
    {
        return new UriTemplate('file://users/{userId}/profile');
    }

    public function handle(Request $request): Response
    {
        // Access the extracted variable
        $userId = $request->get('userId');

        // Access the full URI if needed
        $uri = $request->uri();

        // Fetch user profile...

        return Response::text("Profile for user {$userId}");
    }
}
```

Об'єкт `Request` надає і витягнуті змінні, і початковий URI, який було запитано, даючи вам повний контекст для обробки запиту ресурсу.

<a name="resource-uri-and-mime-type"></a>
### URI та MIME-тип ресурсу

Кожен ресурс ідентифікується унікальним URI і має пов'язаний MIME-тип, який допомагає AI-клієнтам зрозуміти формат ресурсу.

За замовчуванням URI ресурсу генерується на основі його імені, тож `WeatherGuidelinesResource` матиме URI `weather://resources/weather-guidelines`. MIME-тип за замовчуванням - `text/plain`.

Ви можете змінити ці значення атрибутами `Uri` і `MimeType`:

```php
<?php

namespace App\Mcp\Resources;

use Laravel\Mcp\Server\Attributes\MimeType;
use Laravel\Mcp\Server\Attributes\Uri;
use Laravel\Mcp\Server\Resource;

#[Uri('weather://resources/guidelines')]
#[MimeType('application/pdf')]
class WeatherGuidelinesResource extends Resource
{
}
```

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

<a name="resource-request"></a>
### Запит ресурсу

На відміну від інструментів і промптів, ресурси не можуть визначати вхідні схеми чи аргументи. Однак ви все одно можете працювати з об'єктом запиту в методі `handle` свого ресурсу:

```php
<?php

namespace App\Mcp\Resources;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Resource;

class WeatherGuidelinesResource extends Resource
{
    /**
     * Handle the resource request.
     */
    public function handle(Request $request): Response
    {
        // ...
    }
}
```

<a name="resource-dependency-injection"></a>
### Впровадження залежностей у ресурси

[Сервіс-контейнер](/docs/{{version}}/container) Laravel використовується для створення всіх ресурсів. Тому ви можете вказати типи будь-яких залежностей, потрібних вашому ресурсу, у його конструкторі. Оголошені залежності будуть автоматично отримані й впроваджені в екземпляр ресурсу:

```php
<?php

namespace App\Mcp\Resources;

use App\Repositories\WeatherRepository;
use Laravel\Mcp\Server\Resource;

class WeatherGuidelinesResource extends Resource
{
    /**
     * Create a new resource instance.
     */
    public function __construct(
        protected WeatherRepository $weather,
    ) {}

    // ...
}
```

Окрім впровадження через конструктор, ви також можете вказати типи залежностей у методі `handle` свого ресурсу. Сервіс-контейнер автоматично отримає й впровадить залежності під час виклику методу:

```php
<?php

namespace App\Mcp\Resources;

use App\Repositories\WeatherRepository;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Resource;

class WeatherGuidelinesResource extends Resource
{
    /**
     * Handle the resource request.
     */
    public function handle(WeatherRepository $weather): Response
    {
        $guidelines = $weather->guidelines();

        return Response::text($guidelines);
    }
}
```

<a name="resource-annotations"></a>
### Анотації ресурсів

Ви можете збагатити свої ресурси [анотаціями](https://modelcontextprotocol.io/specification/2026-07-28/schema#annotations), щоб надати AI-клієнтам додаткові метадані. Анотації додаються до ресурсів через атрибути:

```php
<?php

namespace App\Mcp\Resources;

use Laravel\Mcp\Enums\Role;
use Laravel\Mcp\Server\Annotations\Audience;
use Laravel\Mcp\Server\Annotations\LastModified;
use Laravel\Mcp\Server\Annotations\Priority;
use Laravel\Mcp\Server\Resource;

#[Audience(Role::User)]
#[LastModified('2025-01-12T15:00:58Z')]
#[Priority(0.9)]
class UserDashboardResource extends Resource
{
    //
}
```

Доступні анотації:

<div class="overflow-auto">

| Annotation        | Type          | Description                                                                 |
| ----------------- | ------------- | --------------------------------------------------------------------------- |
| `#[Audience]`     | Role or array | Вказує цільову аудиторію (`Role::User`, `Role::Assistant` або обидві). |
| `#[Priority]`     | float         | Числова оцінка від 0.0 до 1.0, що вказує на важливість ресурсу.       |
| `#[LastModified]` | string        | Позначка часу ISO 8601, що показує, коли ресурс востаннє оновлювався.           |

</div>

<a name="conditional-resource-registration"></a>
### Умовна реєстрація ресурсів

Ви можете умовно реєструвати ресурси під час виконання, реалізувавши метод `shouldRegister` у класі ресурсу. Цей метод дозволяє визначити, чи має ресурс бути доступним, залежно від стану застосунку, конфігурації чи параметрів запиту:

```php
<?php

namespace App\Mcp\Resources;

use Laravel\Mcp\Request;
use Laravel\Mcp\Server\Resource;

class WeatherGuidelinesResource extends Resource
{
    /**
     * Determine if the resource should be registered.
     */
    public function shouldRegister(Request $request): bool
    {
        return $request?->user()?->subscribed() ?? false;
    }
}
```

Коли метод `shouldRegister` ресурсу повертає `false`, той не з'явиться у списку доступних ресурсів і буде недоступним для AI-клієнтів.

<a name="resource-responses"></a>
### Відповіді ресурсів

Ресурси мають повертати екземпляр `Laravel\Mcp\Response`. Клас Response надає кілька зручних методів для створення різних типів відповідей:

Для простого текстового вмісту скористайтеся методом `text`:

```php
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;

/**
 * Handle the resource request.
 */
public function handle(Request $request): Response
{
    // ...

    return Response::text($weatherData);
}
```

<a name="resource-link-responses"></a>
#### Відповіді з посиланням на ресурс

Щоб повернути посилання на ресурс, скористайтеся методом `resourceLink`, указавши URI та ім'я. На відміну від вбудованого ресурсу, посилання на ресурс повертає вказівник URI, який AI-клієнт завантажує самостійно:

```php
return Response::resourceLink(
    uri: 'file:///data/report.json',
    name: 'monthly-report',
    mimeType: 'application/json',
);
```

Ви також можете передати зареєстрований клас чи екземпляр ресурсу, який автоматично успадкує URI, ім'я, заголовок, опис і MIME-тип ресурсу:

```php
return Response::resourceLink(new WeatherForecastResource);
```

<a name="resource-blob-responses"></a>
#### Blob-відповіді

Щоб повернути blob-вміст, скористайтеся методом `blob`, передавши цей вміст:

```php
return Response::blob(file_get_contents(storage_path('weather/radar.png')));
```

Повертаючи blob-вміст, MIME-тип буде визначено налаштованим MIME-типом вашого ресурсу:

```php
<?php

namespace App\Mcp\Resources;

use Laravel\Mcp\Server\Attributes\MimeType;
use Laravel\Mcp\Server\Resource;

#[MimeType('image/png')]
class WeatherGuidelinesResource extends Resource
{
    //
}
```

<a name="resource-error-responses"></a>
#### Відповіді з помилкою

Щоб позначити, що під час отримання ресурсу сталася помилка, скористайтеся методом `error()`:

```php
return Response::error('Unable to fetch weather data for the specified location.');
```

<a name="apps"></a>
## Застосунки

Laravel MCP підтримує [MCP Apps](https://modelcontextprotocol.io/extensions/apps/overview) - розширення Model Context Protocol, яке дозволяє інструментам рендерити інтерактивні HTML-застосунки всередині ізольованих iframe у підтримуваних хостах. Це дає змогу створювати панелі, форми, візуалізації та інші багаті інтерфейси, що виходять за межі простих текстових відповідей.

MCP-застосунок складається з двох частин, які працюють разом:

- **Ресурс застосунку**, який повертає самодостатній HTML вашого застосунку.
- **Інструмент**, пов'язаний з ресурсом застосунку через атрибут `#[RendersApp]`. Коли інструмент викликається, хост завантажує й рендерить пов'язаний ресурс.

<a name="creating-app-resources"></a>
### Створення ресурсів застосунку

Створити ресурс застосунку можна артизан-командою `make:mcp-app-resource`:

```shell
php artisan make:mcp-app-resource WeatherDashboardApp
```

Ця команда створює два файли: PHP-клас у `app/Mcp/Resources` і Blade-представлення в `resources/views/mcp`. Ім'я представлення виводиться з імені класу автоматично. Наприклад, `WeatherDashboardApp` відповідає `mcp.weather-dashboard-app`:

```php
<?php

namespace App\Mcp\Resources;

use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\AppMeta;
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\AppResource;

#[Description('An interactive weather dashboard.')]
#[AppMeta]
class WeatherDashboardApp extends AppResource
{
    /**
     * Handle the app resource request.
     */
    public function handle(Request $request): Response
    {
        return Response::view('mcp.weather-dashboard-app', [
            'title' => $this->title(),
        ]);
    }
}
```

`AppResource` розширює базовий клас `Resource` і автоматично налаштовує схему URI `ui://` та MIME-тип `text/html;profile=mcp-app`, які вимагає специфікація MCP Apps. Як і будь-який інший ресурс, ви маєте зареєструвати його в масиві `$resources` свого сервера.

Згенероване Blade-представлення використовує компонент `<x-mcp::app>`, який рендерить повний HTML-документ із вбудованим клієнтським MCP SDK, готовим до використання:

```blade
<x-mcp::app :title="$title">
    <x-slot:head>
        <script type="module">
        createMcpApp(async (app) => {
            document.getElementById('run-btn').addEventListener('click', async () => {
                const result = await app.callServerTool('get-weather-data', {});
                document.getElementById('output').textContent = result.content[0]?.text ?? '';
            });
        });
        </script>
    </x-slot:head>

    <div id="app">
        <button id="run-btn">Refresh</button>
        <p id="output"></p>
    </div>
</x-mcp::app>
```

Глобальна функція `createMcpApp` надається вбудованим SDK і бере на себе підключення iframe до сервера, застосування теми хоста та надання хелперів на кшталт `callServerTool`, `sendMessage`, `openLink` і колбеків подій. Повний клієнтський API дивіться в [специфікації MCP Apps](https://modelcontextprotocol.io/extensions/apps/overview).

<a name="rendering-apps-from-tools"></a>
### Рендеринг застосунків з інструментів

Щоб показати ресурс застосунку, пов'яжіть з ним інструмент через атрибут `#[RendersApp]`. Коли інструмент викликається, Laravel MCP додає URI ресурсу до метаданих інструмента, щоб хост міг відрендерити застосунок в ізольованому iframe:

```php
<?php

namespace App\Mcp\Tools;

use App\Mcp\Resources\WeatherDashboardApp;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Attributes\RendersApp;
use Laravel\Mcp\Server\Tool;

#[RendersApp(resource: WeatherDashboardApp::class)]
class ShowWeatherDashboard extends Tool
{
    /**
     * Handle the tool request.
     */
    public function handle(Request $request): Response
    {
        return Response::text('Weather dashboard loaded.');
    }
}
```

Laravel MCP автоматично оголошує розширення `io.modelcontextprotocol/ui` у можливості `extensions` сервера, щойно зареєстровано будь-який `AppResource`, тож додаткова конфігурація сервера не потрібна.

<a name="app-tool-visibility"></a>
### Видимість інструментів застосунку

Кожен інструмент з `#[RendersApp]` може обмежити, хто може його викликати, через аргумент `visibility`. Це корисно, щоб надавати приватні інструменти лише для застосунку, які UI викликає для завантаження чи оновлення даних, не роблячи їх видимими для моделі:

```php
use Laravel\Mcp\Server\Attributes\RendersApp;
use Laravel\Mcp\Server\Ui\Enums\Visibility;

#[RendersApp(resource: WeatherDashboardApp::class, visibility: [Visibility::App])]
class GetWeatherData extends Tool
{
    // ...
}
```

Enum `Visibility` має два випадки, `Model` і `App`, і за замовчуванням охоплює обидва. Використовуйте `[Visibility::App]` для бекенд-дій, які UI викликає безпосередньо, або `[Visibility::Model]`, щоб зробити інструмент недоступним для UI.

<a name="app-configuration"></a>
### Конфігурація застосунку

Атрибут `#[AppMeta]` на ресурсі вашого застосунку налаштовує Content Security Policy для iframe, дозволи браузера та будь-які бібліотечні скрипти, які слід додати до `<head>` представлення:

```php
use Laravel\Mcp\Server\Attributes\AppMeta;
use Laravel\Mcp\Server\Ui\Enums\Library;
use Laravel\Mcp\Server\Ui\Enums\Permission;

#[AppMeta(
    connectDomains: ['https://api.weather.com'],
    permissions: [Permission::Geolocation],
    libraries: [Library::Tailwind, Library::Alpine],
)]
class WeatherDashboardApp extends AppResource
{
    // ...
}
```

Enum `Library` містить заздалегідь налаштовані CDN-скрипти для поширених фронтенд-бібліотек, як-от `Library::Tailwind` і `Library::Alpine`, і їхні CDN-джерела автоматично додаються до CSP. Enum `Permission` охоплює дозволи браузера, як-от `Camera`, `Microphone`, `Geolocation` та `ClipboardWrite`.

Для обчислюваної чи динамічної конфігурації перевизначте метод `appMeta` на своєму ресурсі, скориставшись плавними білдерами `AppMeta`, `Csp` і `Permissions` з простору імен `Laravel\Mcp\Server\Ui`.

<a name="building-apps-with-boost"></a>
### Створення застосунків за допомогою Boost

Laravel MCP містить окремий довідник скіла [Boost](/docs/{{version}}/boost) для створення MCP Apps. Якщо у вас встановлено [Laravel Boost](/docs/{{version}}/boost), ваш AI-агент для програмування може викликати скіл `mcp-development` і попросити його згенерувати ресурс застосунку, Blade-представлення й пов'язаний інструмент.

Повний довідник протоколу, включно з повним клієнтським API та деталями схеми, дивіться в офіційній [документації MCP Apps](https://modelcontextprotocol.io/extensions/apps/overview).

<a name="metadata"></a>
## Метадані

Laravel MCP також підтримує поле `_meta`, визначене в [специфікації MCP](https://modelcontextprotocol.io/specification/2026-07-28/basic#_meta), яке потрібне деяким MCP-клієнтам чи інтеграціям. Метадані можна застосувати до всіх примітивів MCP, включно з інструментами, ресурсами й промптами, а також до їхніх відповідей.

Ви можете прикріпити метадані до окремого вмісту відповіді методом `withMeta`:

```php
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;

/**
 * Handle the tool request.
 */
public function handle(Request $request): Response
{
    return Response::text('The weather is sunny.')
        ->withMeta(['source' => 'weather-api', 'cached' => true]);
}
```

Для метаданих рівня результату, що стосуються всієї оболонки відповіді, загорніть свої відповіді в `Response::make` і викличте `withMeta` на отриманому екземплярі фабрики відповідей:

```php
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;

/**
 * Handle the tool request.
 */
public function handle(Request $request): ResponseFactory
{
    return Response::make(
        Response::text('The weather is sunny.')
    )->withMeta(['request_id' => '12345']);
}
```

Щоб прикріпити метадані до самого інструмента, ресурсу чи промпта, визначте на класі властивість `$meta`:

```php
use Laravel\Mcp\Server\Attributes\Description;
use Laravel\Mcp\Server\Tool;

#[Description('Fetches the current weather forecast.')]
class CurrentWeatherTool extends Tool
{
    protected ?array $meta = [
        'version' => '2.0',
        'author' => 'Weather Team',
    ];

    // ...
}
```

<a name="icons"></a>
## Іконки

MCP-клієнти можуть показувати іконки для вашого сервера та його примітивів. Ви можете оголосити іконки на сервері, інструменті, ресурсі чи промпті атрибутом `Icon`:

```php
use Laravel\Mcp\Enums\IconTheme;
use Laravel\Mcp\Server\Attributes\Icon;

#[Icon('mcp/server.png', mimeType: 'image/png', sizes: ['48x48'])]
#[Icon('mcp/server-dark.svg', theme: IconTheme::Dark)]
class WeatherServer extends Server
{
    // ...
}
```

Атрибут `Icon` можна повторювати, тож ви можете оголосити кілька іконок, щоб надати різні розміри чи варіанти для світлої й темної теми.

Як альтернативу ви можете визначити іконки програмно, перевизначивши метод `icons`, що корисно, коли іконка залежить від умов виконання:

```php
use Laravel\Mcp\Schema\Icon;

class CurrentWeatherTool extends Tool
{
    /**
     * Get the tool's icons.
     *
     * @return array<int, Icon>
     */
    public function icons(): array
    {
        return [
            Icon::from('mcp/tool.png', mimeType: 'image/png'),
        ];
    }
}
```

Іконки, визначені через атрибут і через метод `icons`, об'єднуються автоматично. Шляхи до іконок резолвляться так:

<div class="content-list" markdown="1">

- Шляхи зі схемою URI, як-от `https:` чи `data:`, використовуються як є.
- Відносні шляхи перетворюються на URL за допомогою хелпера `asset` з Laravel.

</div>

<a name="authentication"></a>
## Автентифікація

Так само як і маршрути, ви можете автентифікувати вебсервери MCP за допомогою `middleware`. Додавання автентифікації до вашого MCP-сервера вимагатиме від користувача автентифікуватися, перш ніж користуватися будь-якою можливістю сервера.

Є два способи автентифікувати доступ до вашого MCP-сервера: проста автентифікація на основі токенів через [Laravel Sanctum](/docs/{{version}}/sanctum) чи будь-який токен, що передається в HTTP-заголовку `Authorization`. Або ж ви можете автентифікувати через OAuth за допомогою [Laravel Passport](/docs/{{version}}/passport).

<a name="oauth"></a>
### OAuth 2.1

Найнадійніший спосіб захистити ваші вебсервери MCP - OAuth за допомогою [Laravel Passport](/docs/{{version}}/passport).

Автентифікуючи свій MCP-сервер через OAuth, викличте метод `Mcp::oauthRoutes` у файлі `routes/ai.php`, щоб зареєструвати потрібні маршрути виявлення OAuth2 і реєстрації клієнтів. Далі застосуйте `middleware` `auth:api` з Passport до свого маршруту `Mcp::web` у файлі `routes/ai.php`:

```php
use App\Mcp\Servers\WeatherExample;
use Laravel\Mcp\Facades\Mcp;

Mcp::oauthRoutes();

Mcp::web('/mcp/weather', WeatherExample::class)
    ->middleware('auth:api');
```

#### Нове встановлення Passport

Якщо ваш застосунок ще не використовує Laravel Passport, дотримуйтеся [посібника зі встановлення й розгортання](/docs/{{version}}/passport#installation) Passport, щоб додати його до свого застосунку. Перш ніж рухатися далі, у вас має бути модель `OAuthenticatable`, новий гард автентифікації й ключі passport.

Далі вам слід опублікувати надане Laravel MCP представлення авторизації Passport:

```shell
php artisan vendor:publish --tag=mcp-views
```

Потім вкажіть Passport використовувати це представлення методом `Passport::authorizationView`. Зазвичай цей метод слід викликати в методі `boot` `AppServiceProvider` вашого застосунку:

```php
use Laravel\Passport\Passport;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Passport::authorizationView(function ($parameters) {
        return view('mcp.authorize', $parameters);
    });
}
```

Це представлення буде показано кінцевому користувачеві під час автентифікації, щоб відхилити чи схвалити спробу автентифікації AI-агента.

![Приклад екрана авторизації](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABOAAAAROCAMAAABKc73cAAAA81BMVEX////7+/v4+PgXFxfl5eUKCgr9/f1zc3P29vby8vLs7Ozj4+Pp6env7++RkZF5eXlRUVF9fX2Li4uEhISOjo4bGxt0dHS0tLTd3d12dnbLy8vW1tapqanFxcVMTEygoKDDw8PIyMgODg7BwcGwsLASEhKbm5uBgYFGRkbh4eHf398gICCXl5fS0tLR0dG6urolJSXPz8+Tk5MVFRVbW1va2tq4uLijo6NnZ2eZmZnNzc02NjZWVlaIiIhAQEClpaUuLi6enp6Hh4e2trZsbGzY2NjU1NStra28vLwyMjJjY2MpKSmVlZVfX187Ozu+vr5OTk7PbglOAABlU0lEQVR42uzBgQAAAACAoP2pF6kCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGB27Ci3QRiIoqiNkGWQvf/tFlNKE5VG+R1yjncwUq5eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwilAKIn325Y8zwv1VO7NuplvEFEqSeNeOeKWgYDKJkncy7zlwwSEkQ8S958zb3Xprc1AIK31peaNb3FXyjCG27LOQEjrMknclZKOvLX9SjW7DwRSct23SdsTp3DPjvlW2zhQTkBAeQyUVhXucr9NfeQtAWGNxPVJ4f7ut2ndLuMmEFrp87wq3IOSfvpmvkF4i8I9OfdbTUB49btwArcrafSt6xvcxFa4rnCP+23x/xRuY/yeFe53wNU29wTcRJ9bFbhzwG3n+PhLwH18sW8HNw4CURQEsYXQgMb5p7uGFQ6iqQrBh9b7jLxNR+pvwL3HdKBCyb7O8Ra4a8CNzzoXIGSuH0eqAQdNJtzpfkL1/1NIef0/pD47cPeFeixAyuFGvRbcexwuVKjZ1+PxN+p2Bm6f/sQANWOdu8C9XsMnOOg5P8KNh3+EuwP35N8AkjaB2+7ALUDMN3APf2XYFoGDKIG73hgEDoq+gXv4M+q2CBxECZwFB1kCZ8FBlsBZcJAlcBYcZAmcBQdZAmfBQZbAWXCQJXAWHGQJnAUHWQJnwUGWwFlwkCVwFhxkCZwFB1kCZ8FBlsBZcJAlcBYcZAmcBQdZAmfBQZbAWXCQJXAWHGQJnAUHWQJnwUGWwFlwkCVwFhxkCZwFB1kCZ8FBlsBZcJAlcBYcZAmcBQdZAmfBQZbAWXCQJXAWHGQJnAUHWQJnwUGWwFlwkCVwFhz8sXcHOWoDURRFLXkDX39e+99mQMhtKRBIRUSCV+eMuxlevbILEUvgLDiIJXAWHMQSOAtuNWP0xiIEzoJby6j9QuIWIXAW3FLGpW4Ktw6Bs+BW0pe2SdxCBM6CW8f1eKpwSxE4C24Z1+Opwq1F4Cy4VVyPpxK3GIGz4NZwPZ4q3HIEzoJbwS1vErccgbPg8t3yJnELEjgLLt1d3uoucRqXSuAsuGgPxltt437F9dgIJHAWXKoxqv50Iu39XolcHoGz4LKMm67aH6lx3Bl5qLp7XGldBoGz4GKM2l/p36/FefuQTeAsuBSvi1Vj7u/3jS8ncBZcitd5m05ibXw3gbPgQvRM3g5twmUTOAsuRM/k7dRP/8+7hi8ncBZciJqs26kFLpbAWXAh5ut2Gl0ewkUSOAsuw3hQp6mru90lcHEEzoLL0GfW6nZd958y2RdV5S1DCIGz4DL0W6/nlodwGQTOgstwJunzPo0JAmfB8SRJ2zsMX9fKIHAWXIZd4BA4Cy7VUaQSOATOgksjcAicBRfrzYFzES6DwFlwGX6K9JEfx98SuM2C48mZUuAQOAsuzLDgEDgLLpaXDAicBRdL4BA4Cy6Wi74InAUXq44kfeBX95khcBYc/ztwvmyfQeAsuAz1kySBQ+AsuDAtcAicBZfqTNLnHXiZIHAWHM9u+n7eO1kmCNxmwfEgcAcvURE4Cy7N+ZZB4BA4Cy7MOwPnh59TCJwFF+J8COcRHAJnwYUZ+8EJ9Rf7dpCbQAwEUXTRF7B63/e/ZqREgEiIgBlW5feWHOCrbDMInAWX5nZGFTgEzoILcw3ccgWHwFlwYeZTXWpXcDEEzoJLMfWhCVdOqDEEzoKLsT6zvFrgcgicBRdj6mIMOATOggtze2Yw4BA4Cy7M1MV4YkDgLLgwtwlnwCFwFlyYOV+nMuCSCJwFl6SuxoBD4Cy4LH3yv3BtwGUROAsuyjq3wMqAyyJwFlyUqas5NwBJIHAWXJYzjerymX0YgbPgwqzDhWsH1DgCZ8GFmTpYuCkH1DgCZ8GlOTjEphxQ8wicBRdnHSpcOaAGEjgLLs4cadVyQE0kcBZcnn67cLMcUCMJnAUX6N3CTelbJoGz4BL1W8VqfUslcBZcpK7XR9wqDwypBM6Cy/RytUbfggmcBRfqrlur/596+hZM4Cy4VOs+XfP4dUHfogmcBRern9Vrlr6FEzgLLlfXvf6bN++n2QTOggvW9Uv3tW4/efP9QjaBs+CSzaoHjZvvnx1PNyBwFly2rkf0bRMCZ8GF63puuX4LJXAWXLyWt20JnAW3gfvEeTzdh8BZcFtol29bEjgLbg/d8rYhgbPgdjHt7m07AmfBbWRa3fYicBbcXqa/6yZvexA4Cw5iCZwFB7EEzoKDWAJnwUEsgbPgIJbAWXAQS+AsOIglcBYcxBI4C44vduuABAAAAEDQ/9f9CB0RsSVwDg62BM7BwZbAOTjYEjgHB1sC5+BgS+AcHGwJnIODLYFzcLAlcA4OtgTOwcGWwDk42BI4BwdbAufgYEvgHBxsCZyDgy2Bc3CwJXAODrYEzsHBlsA5ONgSOAcHWwLn4GBL4BwcbAmcg4MtgXNwsCVwsVu3LQlDYRzGb8t7PaeVUVmtmkIPRlpKBor1IiSh7/95ysUEdVOnFoe76/dWlJ3juPiz4ACzCBwLDjCLwLHgALMIHAsOMIvAseAAswgcCw4wi8Cx4ACzCBwLDjCLwLHgALMIHAsOMIvAseAAswgcCw4wi8Cx4ACzCBwLDjCLwLHgALMIHAsOMIvAseAAswgcCw4wi8Cx4ACzCJyrC67/VOv9/2YJvxSSZcnlQ2VZypN9HzITQxyS/gK9zEDaT29LZ0/Xu2elYy/pWxFPZuGdVpuF68/yVXbSsR7zoYZYQ+BcXXD7+sOXRRU09CBL0tHQrizM10Qb4o689q3Od7CsjLnRgWMZcrXX00jtpDjlsiot/+TckwlWjt5rGmnt3yW+F1UN1cUaAufqgove9CBL4FJzKHD3MmozSAjcZUeHvZWnX1Yll3hVXrOmw266BO6/cXTBFTVSkDlsfRA4NwLny6imxgbutq3jGtvTL6tWlViXPR0THKz8ReAyz9viBgLn6IJb00hL0lp/9bVB4NwIXLAjI9qxgStWNM5haYbLepIYF4HGaWV/PXDdXEW74gYC5+aCyxzqwKOkUnqpqxK4L/bOtC11HAzDL8tbKYK4VRBxoYCDLHPAgiIoouKCiMf//2tmSE3atClQcK7p0dwfZjxasAnN7dNsDYrg8BJ41JJIcM8lFGNk51cWpsGJkkIPRvH/VHDx83dElIILDMFMcGm02PcX3xDxDxPcfs1NkIZRVxPcNfAUUSC4Aotbg5vnbuLp+WaTZbhH7j13apT7175OLXgGDu6RUn5LPyWyt1s9/KSv/peC20KUggsSwUxwLZwyIv+thoIluPtXwsWXCS4DwWZZwZlKKcWAY2Jqhyv6epXKJ81a4mEfTcYxz8qql9EkBTwX+Mmk6R5yaLmvi6dXwlAK7tsRyARnDrSVnpDwECzBUaTg5hTsw1RUEeyEIuRPV8de9BB12WsILJQ3NNmdUVkTJGi8Rc80JOhX3KUxQZO853UhBfftCGSCu/q8uSnjlIkUnG8CIbhd01rAYDeDf3GCO0aTHeDZR0Ik6V1ZyYaoF+4VCSVHyg73kVCWgvs5BDLBmRdi7lN0JVUKzi+BEFzbfGEIbAxxStMuuNAYCZvgIHSEhNSMytoSDKS2dY9u0mgVCRdScD+GICY4s2EYMUjoOOXu6wQXbv86zTVjoopIdIu1y5dHb5uGD4q1NPhDfbq4O72oJ/8rwSXr6atcPqp4n0C9WHvg1j0lm89XFxXF6w2bxdpzNi4QnKC2frdjswQHI5zyYn8dUcwYmOCsAGesg5MKEsYzKusWCfuCAPce8pqD97qQ4GLtwm2tmI2CJ4ls7uplXZGCCzBBTHB/4ZQJu6rLNslohDWw2NcICWhO/1dCE81E4S7k9rCEhMEt8DxuVdmw27EKjKZGKADEbkjvEcCbRngCgBdNxC8ml9N39qa3Mb+CyzQ0wiUwbjRCOQyEZiuCJqVeTgETdoLTg+oT3fz5JEFvEY+QYEyi7jIqxT6a9J9DMwRX2Wmgid56UbwFZ7b2IVjkccoeJ7h372HpHhLOvCsrj65uDFVHQtercxfHLsGZdT0BRqi4qaPJYJdWHjvwfnrEbQ8JRsusysn0J8hdf9uwDFJw3zvBKQ3WrXKMhIolOCR0XDc9GIUsurELLtxBi6MMWByU0Y6RCjtWVPxiE1G5hvEwa1ZWeK+ENiKnfhPcHRIiZ+xkdPP0HmHKyQfa6V04ImsMYhPnr2/37N9RHGWsD9Bi3PUSXHQT7YwPPQXXxSlVBRgpnFKwCy6OJglws4OEnHdl5ZCw556IMgIBZSREnYIrOaa03FXRhj4MA4EeuAVQaCAjcqXQeuL5C5ZACu6bJ7gCTiEJJGk26fuvEFySt1g1DpS7Ejp4bzsEl6miT8FleuhgovoSHLvVKitAUAdIyLGWzbPhEFyyj3YuAfIa2qnxZcxqvOYfxIL7pSGPvuElODAlkAcGKYIWYoKzClIGAXWNkPKurL/c8W9IhywEXGmE9GzBqRPXBZFxCC6no517KbiAEsAE16EOY+t6Bl8guLxTOK+8R3iM31zjTw/Qp+BODHTxHvYlOGa0c66gazDlQkcnpQNOcMkRckSieYfIjXV7Ge8Mp7geRILbQDdrXoJbc8xSq9NPjwmOfVlbakQmPHKPoo4X6wbzFlxigC60DCe4nLPuH6XggknwElzMbGi/7Tklv7rg3Jffb3qhMyKWNbQze+OfoA/B/QKARJVaYjSZjHQqAmGbvc07oW3zqcRaD0DRbsmDEpVma3g9ol+H7AXaRwfsOMa1vYwjdNJQ3YL7zZp0v7M5xk+KQsHRMD5w3HKmOcGVkXC4lOC2kBBRgRFCgq4sKzilzCrgutM3kNW7dWBHQwdlRQoukAQvweXs6xdUA6cMFxGc+vAvz0goPZhwAjM6hWgoU+zRCMevErouViCWrbFJ9WHW+BlGw+AaRuLBBm0WmwpAqIwE/TxGSjGk4W6xtah9tmLcMldCM8VbsfVlYStjxrkGEp4dxta2mqr6d0pHhrGWj4cPdg0k6CFHGV9zj7G/L1lLHToFxyZaVH+Rl1bKtJ9QLLiQxoepnjk8zgluQL/0Lzh1S3evZEggoQHLCu4vNOnESQO5Muy/o4SU3tVjTM2yyq1A9uFfRkjYfiC0wT9ScN88wR1xYadFO+TmC857mohJpAuE0PWnNsg/Gkgw7mj7+ECTXb7xV8+7cQBuVJbjRUfCOAkAp86O+jQSej4FB6+0i0f54JZ1jM0GqXDTZ3GHL3Cj4lzZqzWB0NSR8MiVMUKDWI0243Wn4CZIOEo4FrXviQUHHa4PNUM/G7vgNCSoiwkufUA5Od5q0DRZAYs6Et6XFdwjEgw20P7Z/6onOMG9KkDI60h4kNNEAkngElyUv2MpIqG4uuAMdp+7XjK/odjuMgtAUTaRMFbsjT+lzp44XNGQYNRtMx8OXWuKmj4Fp5qJQO++IWELCPUIIe/od2pxBW5k2C/iFW8JpmgXnM5aJauVc05wbJqFZtkoPKZ/gdyCY27tcf13OU5woQXXuR+hNzlgsJvozWUFt4+EG2CcImHDLrhJyLGz164UXCAJXILbZXYhxCL00ltVcCeuDUHWraGviWAi/Iut8R/NWRkRe0eTWxKQ3G2sotNg6ktwcGCgjXIIxJj5bsQVuO0casQX5xSUDbvghu7pFGWH4Dbcm5DmqCoFgmMdqhXbu+oqJzhFp11mvgUn7uk6QcLRkoILa2Zgt/fqDaimmeCOQgB8eu5IwQWSwCW4kRmYHDdpenxVwRnu+RdtFklK62AjxaTHGn9ljuBe0X6rtoeErrtcA5+C4zfArUZnb7+i20+w4X4PYLy4b8MN+5t30STJC65nnkYMLOLUjkLBQcsSKSSoebhb1CoS4ssKzjgFjkckjJYUXNGmfkdIi1qCK7iWW0yk4AJJ0BJc03krd4GE2mqC41+yzwR3SI0iyAA9m+BgtuBqXMKijXE9auPdqVl22EnUSZx3J0UvgIDEyXHqGk3sJ7jHpyzeriduwZWBYWmnbhMcS1ujqB2aVsWCu6XvzWLjqUNwIyQcLCm4zTOgcMatLim4GyQUozZSSMhaglOAoUrBBZmgJbg9vimygbj3VQV3KhRcjr6foBtQsxp/b7bgDnUkNBJ0OqsnSd9rUWMj/OQGHCSLe/0IMnjB5YTT/b0F1xFt6/nCCS6KnvQ8BJfUrWUKH/SDEk0TKSwhOK18lQAeZmEMLSe4IXpSZAdWQQruDyFgCS5URULEAk0eVxRcViA49sNt4DCQEGONf3+W4NjcCT3PXu/Jk2/BwQGbf8KLb7eHFJHgmi7B3c4U3LZoo7VbTnBZ9EQTCo59fcfWiPbBIbg9JOwsJrhcltKMeiisR425lOA20ZM3duCHFNyfQsASXBq92FpRcH8LBZcSPDiAJbAz1qTPZwku3EeTK3bJe9P1L7gmqwEbSq6BboQnyASXmym4WxD0Q75xgntATwwvwV2ZcmZnsesU3AWNgAIyDULH19YrbOaKgFyDcOwtuHf0ZMM6UAruTyFgCa6FXjQUD8F1VhHchnAnCw0J4cUEN0R2UvMTXMa34JJjZAGRoVwjQx9db32B4HZFCS63aIJreAkuyvb0a9FK5wWn6t7VsI2EDV+C+80uGDcfSGguleCOpeD+PIKV4JIl9KTgIbj3VQT3LNrTP0y7qRcS3DGavMecCTAlIOxLcLzKGknnNhvY33rIhACgtbrghqI+uAInuAR6FuxNIDh+99KYQQc3ecHBkeAz4D/bui/BhQ0kXHhul6Qpc/vgOik3j1Jwfx7BSnCn6E3HQ3C4iuDyomnvBfrNRQTXLdHFq84GbnzJjr7baHGt8LM8RnkgfIngjkSjqI+iUdTNBXf0tddX6/P+9i+34NJI0OseO59iVfElOEjRp9V47UDVmj+K2gUBUnB/HsFKcH1hQkCCEaOC4y6wx5UElxTdHw1ZpJkvuHgDCfqL6P1XF1xBF+2I1HE6dXN1wUXiQGHf01XRPLhGyIfg6P7MYfOcm27Bwbt4B17WubkH/gSXKHlkwrMqEoqegmO6PZWC+x4EKsG1ac82z5bVPkOuWZwdXnBKiYaMhQQHLXckXDeQkF9AcKEyCsYg/0bCZFXBsQHaQbzHdcONnBbH1QWHe+5T+wBecOdIuPMhOHqyaY2kKhAILs0/VIvRoeb1KTjYR6Gl1B6d9egpODaOPw4vJ7htOmYfDKTgApXg/qKrmnnqttZWNQ9h2eUGqeD4FQOLCu43mvxyrUXtwQKCS6FJy6PviKG2Nqdk/QiOPVBPz8JTyVrKDzHnuq/CVwhObzuXH+GleC1qIwaMLinXtSoQHPepDqhEOcHx3YxHKlgoN2z6n1/BsXGZLcVuIvprDj0Fx48fM/ZJGY8XEdwzHZIOBlJwQUpwythjFaHZOPQoUwfrjbpCp+CukfCyoOCUARKMNJjEWmhytYDgcmgyUoV9PT2mDOXVbOUhf4Lbpx5iv/ba1qbeFTB51L5CcKg9gMlxCQl60mM3kZbq2A1gOCPBddGiIBIcJAdoMrhjwemlh0ht6ldwrGMUy+wPV+i0iiathXYT0S6AH0bSK4sIromEckCMIgUXpAR34nUDtIOEDVtk6h+qoJ5MkBecdUD1VAVQunMFx7a70SdNBUC9G9A0ocwX3JOBJsd5Gxlrz0TjWCGNq3tEN2wTCG7t3I3p52d2KtZbbtiHac3d5nYiuJrgGJ3ndugg1+JWsXOCS1S5BzGoFwO6ltdbcNBASjUkFBzUS+yIrdvD9Xpu54hZ9wD8Cw7ukNLbfu5G88epEX7yrnoLjhuh3jPN2t5HlpfnC05Fk+snBSD+vz8RUgouSAmuQ2ODk0frpvEAGYZ5+BovuF9I0Ro6KnMFBxtIKQ2qSGkkYL7gjlBExyYCNPqdvX6JWlnhPTBnyLhtmOUwi1b5LG/WClJYvV6b9A1kqCsIboxORuEZO/pq5b1ODz9JwSzB7XHFEgkODjUUY+SXe8ZiTUcxg/jCO/rqo0nqQ2M7vi8kOOgjfXUjInf0/f8JUIKLRTwnIfSsi3GCPKk7XnBwjRaLCA5e0U3kEBYQXN9bTgXRMxni4Edw6ggJDyyVsG64glMDu0iorCC4LadjSlnRMxl2RWerzBScdbZpL8FBZoAiIi/LPkT2whD77WzJZzJ0YTHBdeWW5YEiQAmONUM3u1ZQUMe83xSn4Coln4KDWknwEKUVBQftHjroJ8GX4CbOBzlsWt1w92hHfy4i4XAFwZ3n+Wqo5sVP1XrQ0MFQgZmCC2nUwzFPwUFyqKOLVnT5p2QflNGF/lcY5gsO1JbLb3VYUHAwlIILEgFKcB9mI1DBTcXWhbP+jozSOYBTcPB74FNw0ORlVNoPw8qCgxjfYo19FXwJ7goJvTBQEhrrhlOGaBEpwBM9zxUEBw8RtOhlQCw4OOPN0agpMFtw0KG+AoHgGAebyDMu+hiREfAwQp5yfd5ie0pN40XbhoUFp67pUnDBITgJLqrTRiCgb1t9E94dIyGylQCX4MgBEWqVOYJjZIcRlt5qSYBVBUeIb7AWpt0nAXwJLquzZwEyntHqhnuiOmjcJwDC5tGvKwkOkmy44uNCmfFk+8dUlfXTXYYA5gkujSY5seAYT9t9y5v7WYDVBAehwv7ANtpQAVhUcBDLMY2XOu2F5sEx6uylu7AEUnDfNMEtjpLPbe8WmzHvA6KHl/e7d4UwLEz48eX4fuM5/7UVcXaY293O5aMKfD3xbO7mvNgNwUrwEo/V0xs3t4eJuepoFy5vNorNJHwxicNibef8+TDzVTWWKeQ2dq7S2aj/+u0W3+6PC5UlKjh2kN7YecudwXJIwX3HBCf5n2CCk3w7pOD+yAQnkYKTSMHJBCeRgvvRSMHJBPfjkYL7vkjByQT345GC+75IwckE9+ORgvu+SMHJBPfjkYL7vkjByQT345GC+75IwckE9+ORgvu+SMHJBPfjeeoRgrIJrUQKTiY4iUQiBScTnETyg5GCkwlOIvm2SMHJBCeRfFuk4GSC+4e9821NHIvC+EkJeUxqDEkkMVbpSEVRasWCioKIFmtfFPr9v82ae73XZE2z3e0fdpzze2Mnc3NyTpn8eK4ahmEuFhYcJziGuVhYcJzgGOZiYcFxgmOYi4UFxwmOYS4WFhwnOIa5WFhwnOAY5mJhwXGCY5iLhQXHCY5hLhYWHCc4hrlYWHCc4BjmYmHBcYJjmIuFBccJjmEuFhYcJziGuVhYcJzgGOZiYcFxgiunYv6dCtGVaRpn/5Kq9LU4pitexLX+FbZo8tPoqcS8qiWBOviJ0fJYacuMgAXHCe7n6ODvLIluAJPyJNjS11LDLR1YAC79O16AJn0ePZWYV7UkUAc/MVoeA/hFjIAFxwmujD9XcO5FC84lhgXHCe6LsQxJDa+GxCq8tzeLFtF3CO5psbDpIwzvE5JMFospfRI91f9EcM1gQQwLjhPcN1HD/Sfv7c9boJwxEvoG/ieCuwcLjgXHCS4LC44F96fDguMEx4JjwV0sLDhOcP9VcE438Pzai0GCbu+FBNPNNvFr4yvKYse1frJdTEhw3etZ1Jgf1j02DRI0e10ylvf9KNxM8xZo9uYkqbzUfX/xZNIRc133veD2WrxT1+sB6B14E/UdEljPaTf1J5dUl02yJotDL7eijD48O67f9I7HR721nKpccEM9hcR46IWHC77YlMP49Rom/ccX4zTa1V3NT1atal5wxRU2erobOnDVfuxH/cf2FTEsOE5w3yO4TgLBys6FkhYkvkkn1Fr0rOPphlpXl6fHqNl1SNZGtmIMnwRVH4JkSSmWqhCNxWetinkmWxnqcHKjpojtGgTRHWme4FuUsgNiEmywlj2UCi7OT0FOAEnfoQzXfUhC/cuqhhBEv7Tg3q+QQNEmIjfML6mF4ZAYFhwnuC8UXBd+q2k+b4BNLm8B+xu78eSjXzktjxDEO7dxC7SPp7cQ3k0H7UdgaxwFF+C1M5i+hcC6SHBuH/3uztn1gDt5HPdvA2dUR2QS7TqdEOgcaJx8ZG2A1sgZtkN4jWPN7hav48bwzkc0IMUAGCg9b0mc6mFSJrjiKRwfUevZOVRXKtOt7yfOIE4wP7ax6KO1NJ9jD+gowb1fodnpQE5XJbK38GY7ZzfzsBVL+sCUGBYcJ7gvFBxCGR/mgJO59zfHW3gCNEnxippx/CFQp/eE/6wYiI+6wpMlNqL3wM254OwVfJfkAc8gMhJ0Saz3sc+9B6d9ZM3hjeSaHryBrAk8UYoZYU0Kq4+xePVxVFkDyVWJ4IqnsGrwbqTSVggMUszgO8cfYMs6qjMzhGdrwRVXyL8HVwmwrcpIu0VQYcGx4DjBfYfgHBIMgVHm3g/QIkG87tARI1iNSNBGYsnTvQoJrDo8QwrukSSuh8W54J6QNEhghak7B6uVS4I57osFNwVmRMqCj7ImuiRZICBNS/71FKtbKcAZFlQmuIIpxJExSZ6BESnWq5gEVaAh6gjNCkbAixZccYW84GZKZ3rA6c0NP+bFguME95WC03awgV+Ze38Or1r6aaQrX2Z0usGfpeB0CukiquQFJ6z2qKvMJpQhxrZYcGv4OgO9IHKkWVR7e/jZxsQl95hNZLUa2qWCK5xinrngFms6wwCaoo5QoyRAXQmuuIIWnNJ7jxQLhMSw4DjBfbngdAK5ygtuECFZPxcFCsNsxh7gyNN3dMSVCSZGpO/sJjDMC054tEvnVIadLtAvFtwq03IDmEjBkUDW1RgensUOtWp46bmVCG6p4AqnWME3FUrIGqs6Gt8CHVmnlgmPiRJcSQUtOFtlPL3lZVhwnOC+XHDDYsHR0gMQvTavKMswrkcQCMGJF4mVCHPFwlLZXW9ecIPzx0uth1YIwTuC87AnhQ3ciZqLQsHRJl3bQEA0T909Qp1KBVc4hYcsWzrhvNwnSFGCm5NiDFSU4EoqKMENpKklS+CaGBYcJ7hv+B5cseDInW2FdBqksTcAvNp6vNeCs0mgNKT2meoefsgLTkhvQjmGAQD/tXVXf09wCVqkcIE3WbNYcM30+vs0HD1gJfaq5YIrnCIBwhOvpLC6CZAEt09NJbhMZ2+ijBBccYW84IbAMtM1BsSw4DjB/ZTgBE5zkyCp0hGjBm9mWkQ01YJT+lNbrhiJRUcmBVtUF4gpi+MjbLvi44f3BLdFnRQjYFkmODuCY/XT7sQeNcR1ueAKp9iiR0V0Ea0bBhFZWnD3pNiLyYXgiivkBecC3VNh3qKy4DjB/bDgBFX/9OeRNsFOC65DAuG8idANtBBniOyzDxl8LLRNTIeoi75NVCq4DRKDSC2CWSY4ekS7IYU4x8xBSOWCK5xig9Cicyr6bTNDC66vF75iRUpwokK54Cwfr5mm/T/3zmXBcYL7ccE1g5qlwkWohaV/7GrBrSyS9JBUpOBaqqKP2vnXRPbwXJIs8ET0iDVJTlvUyDq1l/8u3tUKAZUK7g2bLt6kj1dt7D8iuPwUuQsak4lsV+VWwbUWnF44AGItuOIKgnv1+9+fPqc2E+yJYcFxgvspwZnARLksOLnDs1SUUYLDWKe7tdQNounxDSugcy44M0HdkDEwitxUKT0SmDgKrgnscu0ZdSRDWXOD6KFccFV4flpX7FG32H1EcPkpxAX7rrzgHH7l7DkJmp8EF8qFV49IqlpwxRUELaHQA1X9jIMdwnfSI6bJT6Wy4DjB/cQW9RFe0yKqLD20Ml/RaJkWWTf1BKjK00PMTcOqxhF8W+gm9cvYsYzrHvBqnQuOxsDi2SCr7WEt89qdS2S0/ei4wJQPFli6PfH0wy+HjJu5aLJUcLQCaiRYA57xIcEt/jaFvYU/uSJr0NLbV2nMWsMgMm8jqK/XLeAvbarsVsCMtOCKKwjugJklpht6qB/OtZd1eAN+VIsFxwnuBwVnh4BfDyLI5yS1MeCvPCRLoCFPn66AxAPQHyjdDD3ASwDUbCoQnBUDSIIEaBnpdQMAYRghvFNb01sg2W7XmfbcMF2UQFqkXHCxkiA9A3P6kODc/BTiqVNEq/TIHZ1oAvACH5gFGMs68R0APwLQspTgiipojBDwVv3Uj7sEiLYRkOyIBceC4wT3g4KjyszHAa9rk8Z4S49F9wPLQ+d4ut310mV7V+tGJhz0Xww6E5zg5jWtUm+TwG6lq5O1PQAceeluAmCebc/dezhQW9I/Cq6ByNaJq/kxwRnZKQROS1ywN6QsDyscCJrUw/woOHqu40BddKYFV1xB4GyAYwAcbiIA0WZILDgWHCe4H8ZyGw3HoByGMx3aOT+mx9QyrZsrs+Fa9D72YKjOEKt3uTefRMlB/ggZ1enApu9DTJG/oOiq4HdyPk2us/IKYt6paZGgYu74vxlkwXGC+3+SCYBZwTEMEQuOiBPc7w0LjnkXFhwnuN8dFhzzLiw4TnC/Oyw45l1YcJzgfndYcMy7sOA4wf3u2KNRhfKYoxtiGCIWHCc4hmFYcJzgGObPhgXHCY5hLhYWHCc4hrlYWHB/sXfHrYkjYRzHn0iYX0yNISqjsUorikHRioIVBREt1v5R6Pt/N3edccakpluv3YM79/n+sXdkNU5m8cMTt9vyBMdxVxsDxxMcx11tDBxPcBx3tTFwPMFx3NXGwPEEx3FXGwPHExzHXW0MHE9wHHe1MXA8wXHc1cbA8QTHcVcbA8cTHMddbQwcT3A5Cdct0ql/dXcc1w3o73z1n2z5xwuu6/x68a5PmYruhwsat7uroSCT5x7zBZ3n7dtvw99zoT/4A/H0ZrgOcQwcT3A/ygEmdKqHmP61hkDL/Ei+s3KP3wIufV4RQOxRui2ARzI1yhLvJXXzqBVMYfkwpmydCMDmN13o97I/VXAJBMQxcDzB/dnAoZ05EqeAc+oSkEklBpDMDXDp6oJSzQG5fGrStwv+FeBEkTgGjie4/zxwpeWy+7uBS1CmVPdYSwNcUAbKU4dI+D2JuGGAGzjvee60DCwEnVoiDn7i9yhOXehPgXtaLjVsveiFOAaOJ7j/PHCq3wxcHSjRqRpeLHDL1M2qmyAqKuDSZ2wBYzqVYEs/aPLjvbPApZNg4Bg4nuD+UOCGEepkcyEDA9wUeCPbXOJwBlwhwl2WEgbufxsDxxPcFQI36CERqeWP6AhcIUFZ0KkWYs8AZ3tFhYG7khg4nuAuB050XhNZHa0EHRtu1mFYObiUSTxv12FUPhTVc/sHMt33N0qgejmJd8spqZx+/zkDmXP/Wo2T2YtzAq7QLUfxulX6CJzz0K/GUe2l+BG4G2Bvl5OgaYDrAo0PVj6fAbdAlXSlfr8P4O9fB0SP/brdkL7ipdnvEQ0XuzgZPZDJe6lF0fJJne/JPL3/pi/UbtAujmpPgT1dk8R0mcS7O5coZxcscM3+Ir2qflDYqqXoWv034hg4nuC+B1yhBl3N0/dyC+jkM6XyytCFe0WKtcOJ0COiTgxdX5j3bhq4mwS6atEAV6pCJe+zwPkV6BL/A3BUweKEWFgwwG30ZGdztv3mGXAVzAzgMN0SLTH6MFXWUXZa0N2Jo4kRVPGKiJYwLVJTmGMOx7fmdPViGSrZpZxdsM+tI0qvCiVaIPRId/OuNcfA8QT3PeA2CF+GxfFBoq8eVAZGq5LbrCFukE3MEC/aJbcdoeoQeSF6pBtD+kS3EpX6PmjcAe0c4IIEyWHqD+rxkagylglaK/e5HgKdNHB+BNl69ofdSDGQAa6L2LMDWYsMcDVs6LwscANpV1zsdDrA7u9fg0+A22Dde75ZVYAumeX39v6+rw7sO50q0Pm7xulCxRZojf1hu4qwcTxdb4fXSeP9SuTgbBeywKVX1fFocPpQ8YDqn/s+ZuB4gvshcCLSItEB8NWbDRM9g90hLJGpYeaIOTAlohYih1R9LNUnXGXHfNSVA9wjIp/0/6CojyMcqyNuFWHxBJwoI7zVqqxRcbLAFSXapPJiNAxwIsTTV8C5a8jS+add+cABI0ddRBVV9dprRAHp3wud9Gdw9kLFwlyP10c4OF7icV2uxOZ8F7LAZVdFM8NaIeTP5Rg4nuC+DZwHNPUbc7MZKjhGhhQ9penu16+kS9RJBsCDfpR8l8+prMekaiMW58Bt1nVSlYCGPm5RGgMvJ+BuT4t8BsZZ4GiLsnmZqjDA+UD7E+Dqzfc6T1up2LwQOIM1vQAeET3ZYVZU0cwFbg48ks6LMNOns/u3ROVsF34N3IO59g5i/uJfBo4nuO8CRxFqHtk6yhFdCwmdVzb/RKGvDVDSmBRQwRlwmTU09fHQvmoFtRNwCzUa6nbYfADuASgdV/FEBrgA6OYDlyq5ocuB65yYcZVqM3t5j9Nc4DapZb9A+hq4kr3NjHJ2IR84K+nouKgFcQwcT3DfBe4NiHoNx6IG19SCdChTsO+2oNVpQgYKodQ5HbdZDwH/E+BEaTy5Azr6eDn9VR0n4NaIXJOFxQLnRKgfRyDfAkch6p8Bt3uv8rpoenQ5cBomu6Yi0KNTucCtMUrfzk81cKQygmV34ZfA2b/GcYEhcQwcT3BfJwwEmQFN9CSAcHEr9CdqmQKyee27CCoFnBMp2Rr2DmpYr0mocoHzX0YxVJ2Pg8kE8CxwIdLtPgBHByRC3TTOyABnT5b/GZztcuBikQFuADS/Ai7EIb3Urjrd8gw4swtfA1fQZ+yhQhwDxxPcJVWMNqoRXkk1OCi5yoF6v6Oaqpj57huy2u+1j8BRDztB1DraUtwCCMubySEXONGLgbhy99S0wLXI9AYULXBxZgGvWeAUNnt1A9dJAddC6FAqZ1ed/AS4iDLADYHpV8DFqesJgDdzOgtcdhe+AE4fDD1yIrSJY+B4grukLWp0qoqW3S63OwN2jrJCUE5jYDZWt3mzI3C+xJwKIRrHLy4JH11BRPNc4HqQG3UbLCxwIzIdEAsL3A59Os8Cp78Ubo7YSwHXBMaUag88/HPgBD4BLgDqXwG3Qy29V6sz4LK7cAFwgUSXmggLxDFwPMFdUh2xTyZX4u0DYfdEXcDPH/6WgsgCp2nYUAcV8+SGwSUHOA+YGA8McImgY69YkwVui6r4FXBvCD3aYEEp4ERN4WxrIfQuB85MiTefASciLO0yXD8XuC1iu4InwD0DLrsLFwBHC+zEDAfiGDie4C6qJLFJjXNhQES9ysEatiEqytNct5/e0DFHomPmHHOOZ4Te7HgH9Ygq6Xp5wM0tmzcWODRJNwDqJ+Cmp99wptPgDLiiRKcQ4jYNHA0lWiJNdY8uBW5jVz75DDg66K3SHj7px0pxAi677MIaFcoDLrsL+cBNyDYA3oAScQwcT3CXdYC8J5V4g8bh3n5jtBoO+iFzUrWBod3P0Ix7HQucqKJn7qDezJ2thzzgBsDAjCUWuGqgPZghLp0wcWpIAn3+BSLvDDjqo9xEIjLAUQu4c0j3HCIpXgxcB1I/aCo/Bc6NUXO0+VIGx5vi/Qk4vexY75bYQj7kAJfdhXzgdijTqRlwHC9Lrst3qgwcT3Bf5UVA/9l3Sg+vQNVRcERYN4goeARWx/fnpEhU6kosyDZCNPaIinV5OjwBzB1UA2i5gsRtLQZKZ8A5IcoNh8i9k0BbH18iWhXJ26+VtBYTKu4QTQskBi2lwBlwUyBBj7LAFVpA9HjjOaXxCAhduhg4H6gMSNxM4qifD5y+0uWzQ6IdHnV3gb5HJPSFmn/tcO+Tc7tQB/KAy+5CHnBb5aYQdhLFlN5LgDlxDBxPcF8UvAKABIBt8WhTDCSzKoCtUCPJCECSQM1ENjcEZCWRGB0wMubEJ0E2AKJ1iHgFNM6AoyaAsBIBjxVM9PF6F0AkAbREGhMKEkCuQwBdygHOiQC4KeB008heWc2ly4GjtgTCCIiGm0+BE3UAcSUGWg6p7oB4t9ucLpSCKoBqDOV1LnDZXcgDzpVAVIlKpBIAHAaOgeMJ7vJEtxYCCMtNMvkbxULSdUhVmCQAUFkJSnWzBIDo0XlDQscWmNEx5y0CIEcDEaJzDhw9rNU5m9TH4ggcPdcUR6sUJiq/FQJAf0h5wNEBqNEZcBQcEkXcrCPoQuB0q4oE4lZAnwCnun0FIGttOwv34tN3EzELUMsurygPuLNdyAOO5hXAfu5WeLeSgWPgeIL7Rwl/7gtK5/jzYSAyjxgW6WPeYF9y6NMc86T8RNBo+PSh4mBQzD1Xae8WvnFhw73r0T/Pm1/wasXB0Mleb2NQ+LjsubqeS3ch/2XsA+4h+YdtMXA8wXFXmdhhSxwDxxMcd43d8n0pA8cTHHetLbH+c9/ADBxPcNxVV5LoEsfA8QTHXWMHhB5xDBxPcNw1th83iGPgeILjuD8sBo4nOI672hg4nuA47mpj4HiC47irjYHjCY7jrjYGjic4jrvaGDie4DjuamPgeILjuKuNgeMJjuOutr/YrQMSAAAAAEH/X/cjdEQkcA4OtgTOwcGWwDk42BI4BwdbAufgYEvgHBxsCZyDgy2Bc3CwJXAODrYEzsHBlsA5ONgSOAcHWwLn4GBL4BwcbAmcg4MtgXNwsCVwDg62BM7BwZbAOTjYEjgHB1sC5+BgS+AcHGwJnIODLYFzcLAlcA4OtgTOwcGWwDk42BI4BwdbAufgYEvgHBxsCZyDgy2Bc3CwJXAODrYEzsHBlsA5ONgSOAcHWwLn4GBL4BwcbAmcg4MtgXNwsCVwDg62BM7BwZbAOTjYEjgHB1sC5+BgS+AcHGwJnIODLYFzcLAlcA4OYtcMUhyIYSCIRDNIRv7/d5d1Qg65jo2GUNVPMBQF8s+C4LYW3BU5SgBwixoZF4J7WMHFFABsYgaCe1DBRUmqmeEGADfwyFmSKhDcQwrOhlTTbeGMsRv7x2dJwxDcEwouJOVbbhQcwB0+lktJgeD6Cy6l4etdKDjG7u6jOR9SIrjugpuvfKPgAPYV3CKlieB6Cy6l/GQbBcfYnoJbSykRXGfBxfIbBQewv+BWwwWC6ys4e/UbBcfYrn2fGgzBtRXc0DCj4AB28W25oYHgugouJH+/CFdUxvYXnLkUCK6p4EppFBzAuYKzVCG4noILlZlTcIydKzgrBYJrKbipaRQcwMmCs6mJ4DoK7pKcgmPsbMG5dCG4hoILlVFwAGcLzkqB4BoKLjXXG/APjrGDBTeVCK6h4IaSggM4XXCpgeAaCq4UFBxjhwvOQoXgGgpOcgoO4GzBubmE4BoKTjJzrqiMnS04Q3BNBWcUHMDZgjNHcBQcY78yCo6C+2O3jk0QCoIoirImi/J/ESIIEwn2X5zBilawwTzOfQVMNhwpNYIjOLPcERzBSakRHMGZ5Y7gCE5KjeAIzix3BEdwUmoER3BmuSM4gpNSIziCM8sdwRGclBrBEZxZ7giO4KTUCI7gzHJHcAQnpUZwBGeWO4IjOCk1giM4s9wRHMFJqREcwZnljuAITkqN4AjOLHcE10twl6pz7O6oY/w6q65DahnBNRPcbc7HbtiN93z+b7zmvKOk9RzBNRPcenB7Ww/u23pw0oe9s+tNFIjCcA4heUHRCWD4Kk3XaCSaukaTLdGEbLCx9qJJ//+/WeEgWNfutjdVm/OcC5EZhrl6+9Bx9CoRgxODe8fg9m+i5fJGDE7qOksMTgzupMHJCq7wHRCDE4P7t8Fpspgrdb0lBicGJwYnfFvE4K7f4LSfIze0e36r7m++dhPlxBZtljnxSNawZ9vdB5MGy8f6lGOHbv5UXbXvMzPfGBxNlkuLdgyXfdJW90m4zsdvZ2AMXtwwWcyMamDT79m7Xr9IBFBKuwSFE4O7YoMz7sGEU2ICGyXeKoKikh8hSmw9xohKZh6YITG3VZ9E54CrmAJj2tGF3+lWA7/SARMbjNuigrECE9OOres+kyB8FDG4S+U8BqflwO+5HgxcqA0HTggvjsZzPwyHVcANAPU4nUQpkrQKry3g/gj0eQ7MON8ANVwFP3IkTm1wbwJuu8biYRO82vCCZg6WDTuOrMAPcVdO2UU2aFv9JVAk2x3gi8NJicFdPWcxOC2FmlOBuYQKijMO1BOrlOtxwFkKGQ+/Cj2MqtjKTSp4BvrFwArrNiedh9MGB/hU0PaQUs0jbKs6QKe4K6ATz8PdB5wgfBgxuAvlLAZ3AzwS07Gx4EB6qLOJA24I/CJmWIVXF7ZJTA8OEfnADTExThsctsQs4TRzSNdDKtH5LnOgRQVBmnaIJtOpLgYnJQZ39ZzF4FLYBlXM4FlEv6HqMz0OOBf3VGGGGHEYPVNFVCrXGov6Ht47BqcTs4VNf2OwCraBWJZghctADO6qDY4owwv327EBVkWodeszcRlwJuDzmfKCEYfaYFzRB57IAIb1Zet3DI5qDbTp6E+kPn/IgVvaMQKyWVu+DEXqAkoM7soNTiE+bHslSpDur6dnKNa1W9ozwoiKhjf0Sa+Cs+T+tMEteVAOuAP0h5cQBXybzj12JNsxCcJnEYO7WM5icCFi7sYB90xkI611bQbFD4239aklRmUDVNLws+jTrw1udNrg8pMGp21DIMxyv7/PUW2el4EXG+JwUmJw34WzGJyLHu2ZAxGRgy4xR4+oDD9+9oGADunw0gTjnDa4/KTBbeH9/mUUTQeiaGz8NZCSIHwWMbgL5SwGN0JoUIVfRtEdlNkklaIdCe7rIOMFhAmHUYOmmjgy1EcNbodZJ6PxdkxtC7RF4aQuQOHE4K7W4CKgT4yZwSGiVfXBET5ULHLY1L7FdpZhbRAziaZElCJsEfOKTxjcDWDxEYem1s1u91kqXyQnfBoxuEvlCw2uwegh/MVdR/BWfEZNqWCcVAGn28h0zsOwCq+ojsGJwgMH1R3f07LxCYML6ofdlA0uh2Nw4HpYEbXa7Y54nJQY3NXzVQaXBjVjok4Ge6CT8XQHDLiLCy9etX/6oUqhqGATFlu1xlEKp4vRXtPuLKJWlFQulwLdwKBOX3nd9w2OtCODMxS6G4NonHs8gScPoyKsxzm8luxkED6JGNyl8kUGd4hT5FkCwA3RPJm2FUpU4ENRSeRVF3TqjfQ+ALUGkLVYuO4BhAng9eP6Nxn+t4rK6xVQjgIeHZ7BDPDWCxvAq+xFlRKD+y58kcEdBxxZW4Udvajp5DvK7j12DlRr/DtTyaJvUK/Oqb6DHfbMJEZ7dbHjZUPxv/4Hp3HANawy7Mj6tETKvrhAQW9FshdV+CxicJfKlxgcncLQb4JO070hx4KOsA92NbSCjaXxdfxi8Thvbs1o9cvf7Vprs7HosKPZfppUE5IdDVIkn4P7Dnz5L9sfJxofmAcjZYjLINNbVNEBItLeDM5HR+NodOIdX3fcftTxrwnJlwMLH0YM7nI5xy/bMxoXHzTLmrQCItrx3OzIjxFaTaZxNQZXHxy1UnMDOtFetx6PzC9icFJicNfPhRgc9eBOqGBqY8napvDCT4zPHmb0b4PTxOCEb4MY3LczuD/s1jFKQ0EYhVFmmiEwxEJIDL5CCWhnEZD0Bnzuf0XywiOkyArunO82w7+A4ZT+3Np2ukzb1j57ufZ7aIePeX/+ae1UC8HZKCO4PMGVft61pd28KWvfp3bt6VgLwWmYCC5OcMuxv1+mv69+/3G9vO3n4+tmuRCcjTKCCxRcLY9wtkZwGiiCixRcvW093N4EZyON4MYRXCU4jRbBERzBWewIjuAITrERHMERnMWO4AiO4BQbwREcwVnsCI7gCE6xERzBEZzFjuAIjuAUG8ERHMFZ7AiO4AhOsREcwRGcxY7gCI7gFBvBERzBWewIjuAITrERHMERnMWO4AiO4BQbwREcwVnsCI7gCE6xERzBEZz9s3c2XGkjURg+NzCHhA/RbhEKaiVABSmK4idQoBY/AEH//6/ZubmTmTTNsXp2z9aw952zdZhMZpLUPPuEBLq2hQ2ODY4NjrO2YYNjg2OD47K2hQ2ODY4NjrO2YYNjg2OD47K2hQ2ODY4NjrO2YYNjg2OD47K2hQ2ODY4NjrO2YYNjg2OD47K2hQ2ODY4NjrO2YYNjg/uXDA7Y4Li8u8IGxwbHBsdZ27DBscG9aHDwSoMz+XMGB6yKXEKFDY4N7kWDux+Pn35vcK3Lv1Jny08f686fM7jKeDxOAofDBueHDe63BlcQYg9eNrjswUz46S1rf8rgNuT0++xwXNjg/LDB/d7gEHAvGpyz1SO0LbquV5mm4Y8YHAGOw2GD88MG948N7nwghJgc1BxsaX15lq8WGX8hGxyXf6Owwb2L/C8MLrS8Kpc3bmzQqU2EWOXY4DjvI2xwbHD/wOBKbSGat9hiO0DJrYSYAr8Hx+V9FDa4tTY4eLPBUaINLmx49kCIK/y58ZwQ3QMHLorFUqYtGsnXqBsbHOfXsMG92/x3BgfZ/kU5iYsjsKNfOZXjahbCV4v27XFH2RbWT5zQ9SmkyxcdR7cFDS4wL/13LUQ3B5BsCi/PLfnHD7gR4kAP6dwd77aMyoWhrDb7sFY7tCC8561aNQdesL6bDq6qxi7LxiiDA7tUezp3og4gLrmzg0sAkuWLkzSENwCy1acM1ajOfhi/wgYXO4NLnw4EZvDowanU7vXah+DFWcl6xWPXmDol8nTiQqfX6xUhuVWQje4ZnreH2wmsD0vg5avskIN7D1bu/CRgcFHzYpy2EBcAyQXOM5I9z+SaOci6/irW/Z7AtLdagMnLKfJAuZT1KWD2N13s1BjeAqWLvezrCbauyjjRxsKbuAYYtSvlpbdasxhhcLvzHi7rXZUhlNKnAi4pTM3hzvzVVfvl43TZ640AfniHojvGlp2Rtx/f+UMZMQsbXNwMDqoL4Wdw7p18eJpT95SsfsdKcqY7FfqAOZHVeqcrKIk7eCqoeq+qAZGeChV3HAJceF6cmC5Qr2TvDRtsb90RAkpMAJNsmo248IiIhHwAzLmE6wyBYi2FnvO7nnM7rde9hMOBXz81u/LZFSopKwQ4e9sM+fHnYzhuCJVFXx3gekLoNsXDoQSrlRcqU3D0Np6xxMWrsMHFzeAeXORFc7qHfGp7ZoRk+YiVoqzM1Tv9MrOrsy7iyPapcF3A1q4HopqL9YXXz/EBMcWxBwSB+wDgouddCnEE0JevHz2u4GAHstIUDc+McKLG6mroTeIR7lbCpJvGviO5qKM2XrYt5wNEbcmfMzVC3kxcZLFH5e4M642KvytTfDUgRqd+BpzlaeNMDunSBlGoD2bSbOPadRJXQptswrYHDbgtcyiKm7gZgx7hlhOnsMHFzOCSbTyjkUgOnuN7vhg1TgBwGcHqVC76lMRFc1n77FOhISb1LPju1h4nlZfdm5MfL1jt2gQXp4OAC89Ly7oAIGdY2OCz6gkARmLhc6aZAZnrhhwu6dvmEgAO/M06wk6eED4SnWnOhuhdlwBKK+VhFYAWjpeiXcHMdi2A802huGYA9xcCy7PSGqKxZo7iLhLv5lw2jOUcbRu9FJvmWXTKpsCtVIBzXZHvW5CbCy9XVRucTzgrK1y8ChtcvAwOIfIBKFd0+gJUetLTHNiUDtQBjGTBCrykG0EqLFrgs0S0qWsdLUcD7hOANsBvGnDR8+YkDgFgoNfakmBCBLbFpmLZptqDSwSL3v4x7Ls+zG4kS7LgZSR3ggBnXKrj8W0HMK2GZKHelVGODpLCrQFcyyU4Y25dXKgzMEaHAovD4o5uqOONMJsS4PzdB6drHNGS67s2cGIUNrh4GZyTkJRygFJqqBMSxsimU3MFVXt4qABlRqc4UeEOgLSJXAuTJaciQLT9oWsCEakBFzlvxfvTli++UPsZ9c0h8TzIuh2gWHIrJuDbZqKz8FUTjh4eqobdDcsH3FfAUP3A8Kmtd6UMlLTs4aYDgPuG1+JgxhQt/zCW8U6vBZQh0tJrWvlNWTlUwlGAOzP98EjotzgrrHBxKmxw8TK4ohEp0o+ZPpExV7+slnOpD1EBDNSEDZSGOpk30FpofdIpkdWAi5y3KkQe4FZj1VrQAF88N0ojTcAPXt0lQdkmhlTTAhNnSFMS1M4Nn4VPwGcNuOCzK3m6xibA0cb17ODx2qEq9bzWv+ZHRx2zMg5FF851BbXvAahtAeWDrHeAE6OwwcXL4NDSEnM/+AK0GJEV6Vj9r6m9rtdsqGAAN6HRfwZc3V8XcOwjDbjIeQ899ctpAFyqK+aVmNnKEoNriD5o2yQmaryVLvPDiSsCgHNpKwhwdgTgPuFiPe1pAHCF4Lx0BaoO55BgaempqelWv9zB3qq1EgDcjyDg2ODiVNjg4mVweRGOYloZ63fm77J/VhAqBnB/BQCXggiD6+hpb+SrYw24yHkt13tLrK3e8rprk3htECd/iFA0JpBkA9C/cJnUQqgYwDV96My8vmCFAfeoj1ANCWsA54hw8oAhK3UdAI040j3XVkPRDeFtApxLbQS4NBtcXMMGFy+DC4HG6M2ZIPug2FcC486GW0HA3QQAt02DBwFntMXybkU+aMBFzzsRvRwRYDKufSsgUWvONWEvDDg1HBBFCIkWPV8sMIvn7TYBTs/pA24FEQZ3rVYnwB28BDj6WIUGnMEbNYmcfvmEaqgBZwwuxwYX18IGFy+De8TrscNAWtR+SkCrBkRvcN2x6RSOBlyUwRX1tHMlKwibyHlplq/01jymsS8J5SaE2Mz59z+Hh8EQitPka8PAm2Ttm10Uq60Q4CwNuAiDy+sj9JluSQQvUQuHweQAzP8EqtrgdNOTHuqjf4kaMrgcG1xcwwYXL4NDL/oAJtTHe+wCH+pdpAFTwjujlnKUtxjcRz3kCs9rDZvIeaHkions058ImV4d6h5kU7a+u7EZ8euGz7JM6RoTI3E3SVN16y0GZ4bOE5g14EZ0kyE8r+p57ePNSeIR36KdtgwBi2xw61TY4OJlcE6BHqulpLNZB2SSXbzB8KQfbrgP3HoUbzG4rgPmYq0ZgE30vGf0pFtuJz89RTSVP6TqaVCRJHPvQCWXzRIlvuENBqspha9MW2KoOnqDwSFoqDmdkGM5GnD087s+XtksDmnep9yzgDL3Bj4S8ocDlBbKn8MGt05hg4uXwcF24K22r+qNLRt50aFnMb76rCrrG4NvMDhxEHjQ99HAJnpeyE6kKWajf6k8nxta1FLtqaEf6Anfw4Lw5A9ODOBuxVsMTuwpLE0J6wZwSZe+4wSTnXmczlzKOD896HvSkKBVVF2qncbFKWCDW6fCBhcvg4NsF9+AsnHxGE9z/9rui/+RrT5iBM9aB9H3pfEWg8PVkqg6M1mbOBo2UfMSlOTiNqJDJXmdBwgonHhOenO3pRql0ZHaCmxjxRXHlQ0ZkDleCJnkaw1ONqNLZYaC4G4ABx9x48kPR/TkypMaetdFnrYAoNhWTzX3G0g13LbSMypsFtjg1ilscPEyOAv28Yxsb25vJvB8rABAXV+aHjWIHznvw+TN1LAr3D3Z69UGt4ljjgpCxq1BEHAR82LuRjjTcGP8VK2f5psSIHf6lyqLyOo1U8suDlf3P2LfB8xSQRlnFIPpmQQZAub8dQZHnQujrsB8CH3YfojzrebTGT0kYgCH7MPpNhe41TtaR0VvNFy52FYDNri1KmxwMTM4gP5C+FkgD84TdHPBl5clXQlS3Gv8gKj9WoOrzYVK4hg04KLnVQTe6Ilg2vWAbe7p5kbRh8UpeEl36QsCMm2hMi/iBkQbnAVWCHA33wWG+Bb+uqR88BmRIODgsiFUulWgBL8uqQ/ABrdWYYOLm8FZkN6Y0On46NB1qXk8xGqqd9jLxJbmMSAI7l5rcPv2Z7olmipBGHCheTXinPulj4j2/MI2Gy0rO02iZT7jvx84BJUHQV8I15p6MJ5cwzm+hfhag7uBJ29sd7gf8YWX+8uGt3DZh58BB5WpB+TePKN/5Q+3CLKzjTQAG9x6FTa42BkcprV/v58JdQ51z1bvay3Qie6m6wYQVqt2XHH0/GYtPW/Ev/OQPDq+3y/ZekiTbLn+ULJf3IbcSf24ohtCc4brBnA49o+TXHixqjqdYtF88zpFL6mXvSW6zT5/ut9tRc9q6vxtvjEMG1z8DM6caMF+5oduDLeYRrOyqSvAhVcMt5jhzPqhTi9uqhXehuCq0XOG1yDAhfuY0cODRm9t8F8Ze83M9JP9LV6FDS6OBvfr355uCJPR0MgU00vXtcGFV6RXoXpgVFMN88K0hTuYbYgaP7LNrGEMzrQHF1NVl+Ci8JIwkbH2Yp01LnZhg2OD+8cGR1U2OC7vr7DBscH9SwZnscFx3l3Y4Njg2ODY4Na2sMGxwbHBscGtbdjg2OBUPbO7u5uLhcE5cktbbHBc2OBeDhucqavEwuC8sMFx2OB+Eza4X60kBganChscFza4l8IGxwb3N7t2tNo4DIRhVDZDkIz8/q+70a0ppstayDucbyBQWkIo5OcQQnC5IziCIziCS3sER3AER3BpIziCIziCS3sER3AER3BpIziCIziCS3sER3AER3BpIziCIziCS3sER3AER3BpIziCIziCS3sER3AER3BpIziCIziCS3sER3AER3BpIziCIziCS3sER3AER3BpIziCIziCS3sER3AER3BpIziCIziCS3sER3AER3BpIziCIziCS3sER3AER3BpI7j3CG6/6IDgCM49LrjdwC0RXI9KcASnbaLgxkONbuAWCO6IRnAE52YLrsVh4BYIrsVJcASnbbLgzmgGboHganSCIzg3W3A9qoFbILhPxE5wBKeJghuPER8Dt0Bw5Yxz8z04gnNTBXfGWQzcAsGVGp3gCE5zBdejGrglgis92uYzOIJzEwXXohcDt0RwpUbsBEdwmie4PaIauEWCK0ccm8/gCM5NE9wRRzFwiwRXtohGcASnWYJrEZuBWya4UsfCERzBuSmCaxG1GLhFghu1iEZwBKcJghv71oqBWya40TkWjuAIzj0uuBZxFgO3QHAXwx07wRGcnhXcfgy/GbilghvVGIjzPTiCcw8KrkVELQZuueBK2Y6Ifu4ER3B6BnD72SOOrRi4FwjuW+0xNm78P28Bd/nxVnCXLn91ee4bwd2b6V5wox9e728Ed/dKf3iOX/3q7wW3Edz/ddt4Q451i/7lm4F7heBG9QxJD3XWUgzcawQ3+tR29JD0T/Wj1e8mGbhXCU7SuzJwn2LgpKQZOIKT0mbgCE5Km4EjOCltBo7gpLQZOIKT0mbgCO4Pu3VAAgAAACDo/+t+hI6IYEvgHBxsCZyDgy2Bc3CwJXAODrYEzsHBlsA5ONgSOAcHWwLn4GBL4BwcbAmcg4MtgXNwsCVwDg62BM7BwZbAOTjYEjgHB1sC5+BgS+AcHGwJnIODLYFzcLAlcA4OtgTOwcGWwDk42BI4BwdbAufgIHbrgAQAAABA0P/X/QgdEW0JnIODLYFzcLAlcA4OtgTOwcGWwDk42BI4BwdbAufgYEvgHBxsCZyDgy2Bc3CwJXAODrYEzsHBlsDFfh31pgnFUQA/KCcToxVUqtHWZSqTlnV1wy0uNlSndVGs9ft/ml2UVB5NxMTU/+/J6JF7n04OsuCE+LCk4I5dcPfzWFPXcKzOZHJzYEoIIQV38gW34V5wpeEor2T/wJQQQgru5Atuw6SNIQUnxNmQgkthwQWVSG85IjnSpeCEOBdScCksuBJiXYcc4gj1RuP7gSkhhBTcyRdcsuBQJZ0chBDnQQouzQWHOskmhBDnQQou1QWXIdmBUrgKvLYXfCtiyzLNBjotz3kBTNPMYb4ct8OnBgBjFdj+2jIQuTHNCiKZr25f/b+Xx86jOXayo+FLMqVo/6LvF7MGEucUy2v1xOsidvKWukroTjIQ4tJIwaW64JokfwF49rkT6oi45LRK5QEgadS4s8I8TAYrpAlFD7njT6FoJmO9ZArGmrGB8X7Orc0t7wcib+9XuQdQcxynCiEuhBRcqguuRGYzQJlkf2ANPHJciIunxH3BffHpux5J5zFLhm6bpJmsroAcz/7MxqT/AMAi7ddVaU3ycyJlBFS5QcsmuTDic8oOudj0SY4yAHSbDKzV9ioq0qVKQIgLIQWX4oLTfpK8Bm7b5DAHoPhEjqIPLpXB252hASRtv5aBNnWotOpAYUZS31fXnYoUo7u55CcAYfzi2yMXcSruN+83AO1vm1wC8TlWAdBUhtFvz6QLJW+TXSk4cWGk4P6zb7+9SUNRAMYPyCNUBw63CSoif5yCiJK1yCYMa4Q5QZjf/9OY21tajDVG0nc9v1fLcrKONHly4NIUNjiKxuJ4BPhlkSb0JOAMoGHDE3UFoCXGAuiZ/knpHG7jdH2EphhLz3skUgLXjq08z4mmKtC5kUAFGIfXeWNflg+ndqe8FuOV501E7nzffy1KZYQGLt0nGbZ1kYdAW6wT8MLw5MQC/DBL8aAPn+PALYFJTiIDKJbFiqd8KIqV86EaXie8lZtgmZTXMGqJUtmkgUvxWVT3Z1ClGlALbWBqwzOXELZFNj7kJTBlP3D358Doaa0sVhVwV5P+b4FzgK8SWkDTXmcb/8YE7ugM2J509QxVZZEGLoUNzntr9B2xrmDfwIanKiHgRRS4rSQFTurfMTrHNTHyMwLbH/k4cPbENnQBvr3Oye+Bkw9nGO6lPvygskcDl+YpqnWRGLiKhIBPUeDmyYGT0uPvBC5zYjR6BOaFaGq4H7hKFLjKfuCMwpMBRuedKJUxGri0TlFj34BC7OiAwBnLL6vB3tnEUaPoA81oqgw0JORBLyFwVq59OjsHXohS2aKBS3+DWwJ9iR0SOCt3Cvck1j2H99HUCJ5KaA3FhMDFHA9molS2aODS3+BkCwuxahcXN/8fuO3aL9vCDaAgxfX6NlrUGtFUFc7GEvgCPEsK3Hi9Dov5HHxRKls0cOlvcNKATmP3k/vg/wO3gaoYpXt0SiZfvbwYTehGU30XfPPX5a4DPUnc4EZg49gORrrT6fSlKJURGrh0NzhrBXgvW69XHagc8Ba11YHjynB4NQ+q9GAUfNOj3tiA60RT0gDc2dXJHDh7mBy4x+CuJvXW4xG80ScZVMZo4NLd4Kz8gp3rgz6Dm7AzeCsiw3vs1KIpM3ZOyB9KcuDkkp1eXgOnMkYDl/YGZ31tYhy3DjxkGG46AG6xL8bRI5u4WVuiKWO5cAHW1478LXD3a3OM0akjGjiVMRq4eINLV3l4e+PI4XL1buttLv43C+27cUn+kB/ftR/+4+45z2+fFbJ7h3+xd8c0AAAACMP8u8bHaEUsfHBM4DzbQ5bAebaHLIGz4CBL4Cw4yBI4Cw6yBM6CgyyBs+AgS+AsOMgSOAsOsgTOgoMsgbPgIEvgLDjGbh2cMAwEQRAUwhidUP7xmgM/HMCBRauKjWAfQ5Nl4BQcZBk4BQdZBk7BQZaBU3CQZeAUHGQZOAUHWQZOwUGWgVNwkGXgFBxkGTgFB1kGTsFBloFTcJBl4BQcZBk4BQdZBk7BQZaBU3CQZeAUHGQZOAUHWQZOwUGWgVNwkGXgFBxkGbhVBbeP85rOsW/ALRi4JQX3Htev8dx3wp0YuBUFd1zT63vTsQF/92HvjlvaBsI4jj+px/2SmDY0LVdTM2ppMSi6sqEWBSl1qx0o9P2/mzXLXS7G6iyZMMjzYQztRP8YfPkluW4cuH+w4Dydt4z50CPG2P44cP/bgmvruon8l/6wTbtJITyy9vvBgRBCUpkjhPDJaq3Gm+F1q/TjtMAhxpqGA2cXXJ39Vl1wb284B5iRNUVMH3cP4JjKJgCebWtHMTLhPKCcB0NFi5Pm/jWzZuLA1V1wbhBUF5z+xP2cwM2prF8O3F0IoBuFAOKJDZx1y094WaNw4GouOGnzVhD6E/kJgUsRu2QJqHsTOGcB9CbZn3onXWBjAjdxMq1gNVKI+N4gaxIOXM0F59mL0uTq6eDpKrHXqd4nBG6g8IWsKW4vTOB+AadmobnPUMc6cCdkDIEpMdYcHLiaC85ut58HuZ92z31C4DZLdKggU9yZwLVDLKUNb4pI2sBpt+gSY83Bgau34FpBxvTNFE4EudYnBO4SsOF8ROiYwC3R9ci6A45fBe4G4BN6rEE4cHbB1ToClxxYibkP5308cO5Npxvfj87JOF/ch2G0FtXAOV0MyBghIR24Q+CGStwY01eB+wL4xFhjcODqLTjfXI1eHVhXgeZ/OHCii1yil+EcOfVYCRxNkUobsSMTuDEQUNn0dPAqcFMoPg7HGoQDV2/BFQ8Ung6sp+Iu3EcDJx8QfRHt4al++Ol0gIu7QAz7iI8qgTsEVsUg60kTuCl6VFUNnNtDnxhrDg5cvQVXPGI4KMtf3SNwQs8vGeWZGgAzSVvOCGHwMnAUYU65Dm7IBO4W3/8WOG8JjImx5uDA1V9w4nXgMmKPwF0CbX3nbeESuTEuTJRCTCuBm5ijcAGUXwQuxfyNwHWGf/xKQmBGjDUIB67mgjOql6j7LbgASCQVxsAZaQnSSuA8cxRugO9UBO4ByzcCZ6khMdYkHLh6C87XW636kEFkL+/xkOEUiDbCRg3CSKCcl4EjfRROphjrwJkXdwfuIXPfWW74CSprGA5cvQXn5S2rHBN5799MkiEGpNmB5t1iK10L0rkra1cC9zW/Y7dC6NrADZDKt+7BMdZQHDhJ9Q/6ispBX6NFO0QYkWbzRPJyGWMrcYjoFuiVeJXA6aNwC8xL3+EOOKeyee+UA8cajgNXb8FRkKu8VUvk0aNdluiT1UNCmnP04wFYEFGCUFKVDRytkUpqhViVAucrTKmkFWPNgWMNx4GzC67ONaoov9ne8GiXAWKfDKGwIUtOAUE0Afz3AncGrGiMVJY34BqxIGsIHHPgWMNx4GouOKkvUS074CTtEigsSnMubBPJTjTWvVQYZr/bXbf6dlgNXH4U7hkDKgfOTdF3yfC76PA9ONZ0HLiaC45c2zTzkebSbmso3Ry50W8gHaHvUMZV+JZ/yXXx7tHz14HbIDxUEDZwerNFPuVEhPiIA8eajgNXZ8HZi1RD2Np5byaxC5w++k7w9RnoObS1Ulj6RFIsodpEJJdQM48omCjM6XXgPIUUfSoCl5vEiJMjT7ZXawUM+SkqazwOXI0Fp7WLponSgmvTm9rP2FLYWuoMbgD18L0LYEIZ5wJAmgIYOdXAmYMkJzZwmoiKb9y95GMijHHg6iw4u+EMYffbO+SkHwIIO0Myrp+R6XylXGuWYiu6k7QrcN+A2LOBM5xZhMz9zONzcIxx4GosOMs1WSu49BfSv/YllbXE6tB7+RXnHu3NOzs+4/944Td7d4wCIBAEQVAMhBP//14xOoxNjrbqCRs0ky0I3NcFNx3j/fX5v+eElQjcXHDf7OO8Hufwmg8WIXBzwQExAndsAgdRAmfBQZbAWXCQJXAWHGQJnAUHWQJnwUGWwFlwkCVwFhxkCZwFB1kCZ8FBlsBZcJAlcBYcZAmcBQdZAmfBQZbAWXCQJXAWHGQJnAUHWQJnwUGWwFlwkCVwFhxkCZwFBzd7dpCaMBBAYXiSDMGERKTQfcGlGzeu2h7A+1+o1AuoUMj0+X1HmJA/j0wsgbPgIJbAWXAQS+AsOIglcBYcxBI4Cw5iCZwFB7EEzoKDWAJnwUEsgbPgIJbAWXAQS+AsOIglcBYcxBI4Cw5iCZwFB7EEzoKDWAJnwUEsgbPgIJbAWXAQS+AsOIglcBYcxBI4Cw5iCZwFB7EEzoKDWAJnwUEsgbPgIJbAWXAQS+AsOIglcBYcxBI4Cw5iCZwFB7EEzoKDWAJnwUEsgbPgIJbAWXAQS+AsOIglcBYcxBI4Cw5iCZwFB7EEzoKDWAJnwUEsgfu7BddP81JpwzJPfXnax/fX52mgBafPr+8PgWtlwY2TuLVmmcbyhPntOtCW69sscC0suOn2Pu261z3Htozd7vbFmcrD9qZbi057gdt8wXVzrXNfaEv/+1i68pDLYaBNh4vAbbvg+lqXXaE9u6XWvjzgeB5o1fkocFsuuL7W9XXPr23j+lDhju8D7Xo/Ctx2C66rdS20aq21K3dc7Le2nS8Ct9mCm/WtaWudyx3+v7XuIHBbLbipLq97eP/BuNy7S93/sHcHKw0DURiFZ7w3aRJikaxddOFCDIq4KrSN4qKgSPH9n0bpAwxtKMzc2/M9QkNPf5JpKyjdksDlWXC1Ks8Xytao1iGh43xI+caOwGVZcK12AWXr0hNuEJRvIHBZFlyvnH8r3Y32IYHvL1jwReAyLLjjewelS34KrQQWrAhchgXXahtQuuRV2gks2BG4DAuu4xGDAU3qRulWYMGWwGVYcL3GgNLF1I2EjcCCDYHLsOBUr/eVs6NOXV0OidgwErgMC44fAjYhdZkENhC4mQuOwLlH4BwgcCw4EDi3CBwLDgTOLQLHggOBc4vAseBA4NwicCw4EDi3CBwLDgTOLQLHggOBc4vAseBA4NwicCw4EDi3CBwLDgTOLQLHggOBc4vAseBA4NwicCy4mZ7v334eXsJlPe33xfwfj8XATevvg5xonKaDnGO9WPyKMQSOBTdHHF6ro4/PJlzQY1UtQyEMBm6K/06t1l2Mt3IOjfFdjCFwf+zdb1MSURTH8fvjcGNREApWSAETJDfI2hQRgVYcQQQEff+vpsPd5Z8hkvRgs/uZqbuul21snO+cdWPSE5ywEmJRISFWO3fkVOtQB84vTsF2aDWrQUwHTgfuP5ngElImnp6wxCoXZxw252cmc2zxwdmeDpw/mEGwbZNW2L1FhZgOnA7cfzLBFeRi4RLq4xVO8lK2vKrdtaQ0IzpwvlABrrNAhVZI49WBcyzrn/u/KHTg9AS3WDT1kbXyi3ekHG0JT9qUMqQD5wtHQC4EHNEKTwL31unA6QnOK9x6fWPXUhbPxVRXynxEB84HBgbSZBswbHqODpwO3P83wXmFW6tvrCxlTMwESMquYDjItYrmqF0SSiYePxC4ssr51rAjPMaFZRdH7QsIT2THKefLzteoDtzGYsAHohTwkVyJarVNrstqNU5UrVbBeDmdBC5+lDZKdxWaaCTrEaNU2LVJcarVr2Qn98NXRDvV6pCIMtWZBo3FM/vh8P5VjvxHB05PcNPCrde3CG/5Jubs9npJXr7Z0lW8nFwyFhxJJX8glK2edFkRoVydSdcgqAO3qTowIhoCdXKlgCS5fgA1Ikxl3cCZV3BdkKsWgCviNo831QdpqPveAvCeiLYxYxHR4AGeBPmODpye4GaFe7lv7EFKW/zuS5k75sTaA172vSuGWrw3Po7aWVqwaIMPyxWryKfPBfsg+ajyvsKvbUV04DbjAHu8mFuAQ2xJ4DqdDhgvXTdwGf51d2gA6BNTfYtmuycGEGgTuYErYCFw2Y4LgNHgvpV4b/byMgsgRn6jA6cnOMVt24t9Y6dSOuJ3MU5VmNcAR63vXpA9jjv2Pe/e1IYdzludv2u2R+6eLH+iZozL15SyYejAbeTeC8wBcE9sSeBo8WdwLJ0ziRp88pBY3wAyNh+MvgHh0WRTcNexy17gptoAjnn9DgTjxIZRoEY+owOnJ7hZ4V7uG2tL2RS/i7daN5PrjLxV5ozJS+K8JKUs7omxOh9tCZGb/nnhspQZHbhNmEEYA2KPQNAktkbgSj0aq7nPG8wI0CWlvA1kvE3b6rJPAueEgRtecwAeSTkG9slndOD0BDdhSSZeVFO1et6elDQJ3K1QPkvZ4KUn5a5w7QyHBbHNOw5nea3owG2iAnwnpQQMia0RuBgpIwA2UR8I98jV5mPb3dQmZSFwdgdI26Tmxe/kKocBh/xFB05PcAsTXEK8JKYmtOWi2euPtpTFyfW875CMClyYT1yJOSk+kfIMeYsO3CZSbsJYEkgRWyNwDiktN3D3wB55BgBy7qYGKQuB6wKGRawOZCseAEPyFx04PcHN+matUzgexwgLfxlMsL1QTyrTwFliPnD7aqSbcykXlHXgNjAAUNpXAHWzukbgDJPYNHBdIEMTW0DfDZy7aSFwMUz+MUoQC3bJX3Tg9AQ36xv//nLh0rznu5hTkbIthDGULN9qNmeBiy8E7ht/Pi3mnOrA/T0xLOASLQTuZGngoqS4gVP7uzQRBWrzm+YDZxnTnVs6cG/Qm5vg3L6JtQrXkLIiZgJn6vlAX0ozdAge1J4L3BZf+0jM6fKJ4ExEB24D3wCkPQBOiOYDZ2KdwF0C5+QZAXh8JnB2GujYpNwCO62ZMvmLDpye4GZ9W69wn3hLdeGdW2db4pxPZsXYj+cCJ8pSHgvXSbfbER1+zRf9Vq2/wlGPTj2tAGCRCtwOKbm1AhcCArwobcAYPBO4GyDskKsLXJB/6cDpCW7at3ULV5Oy/EN46kXOlmpYTyjvnw1cTEo7Isai3Dq+wkjd3Cqp09NbHbjX+wrc09Qd8JmXa+CIlKtp4E6AELFlgbPD7uuYfQtUaXngjgG0yVMBjB4pg1gsRD6jA6cnuEnf1i7clslD26UhGE75uBVWT0TzahpLy2cDV+KsWfASabnn85nJjrMtHbhXMyOARVNtIGIS1QBjROwe08ClgAdiywLHmfQaZt4BgTgtDdyjMT+0mbdAYUCsvAckyWd04PQEN+nb+oWrt9RbrmLvm/b4oDT+mvIcsZ93mVBxfOrdksCxH8S7axc7DoetKlif91YOPn3u5zls+hb19YbAPs2Uw0Cfu2UA0fuPB2kEP00CdwwELtq19tLAlU8A3J62M0EAp0TLAmdvA7i+dz0SjcLAdqLSTJ4AJZt8RgdOT3BcImvJiVWMkJzKfRFjP6WreFOUMrIscOzwTLrMBzEWaMuJn/pncBtIAQmak3FvMPsGlKCVmgSunMZYdmngaFCAJ5A0iZYFznn6sDa3Dc95j/xGB05PcIWE9TR5VkG84K6pWpVvHgnXu2tbMutQtKQsLA0c+zHM8yZ7WBKem5wci3/SDxk2MDAQWIhLBTBavA7rBhA+6JEKnNLrrggcmaE6mJGKE1srcDRIBsGCCd/NbzpweoJ7NaNTKJwbYgalwuHLX1n49mFxU/Tk7jYsfOgfCtwKdtwp06Ky08ytaNHAavJL/ojZe3xskR/pwOkJTnvLgfvP6cDpCU7TgXuzdOD0BKf9Yu+OcRSGoSiKvsRfUWw5CCFNj0RJQ0MFLID9b2hE2hkIVP753LOERFyeiFEIXFgEjgUHAhcWgWPBgcCFReBYcCBwYRE4FhwIXFgEjgUHAhcWgWPBgcCFReBYcCBwYRE4FhwIXFgEjgUHAhcWgWPBgcCFReBYcCBwYRE4FhwIXFgEjgUHAhcWgWuy4L73yq3H8Oruenv9J/53JHANFly1TvCus6qnLglrcCFwDRZcsVHwbrSip64Ja3AlcA0WXDaX7yDA+3fplrAGNwLXYMH1VgXvqvV6ap+wBnsC12DBzZ8d+LbwLeTvDaD46y4C12DBKVsRfCuW9cIuwb8dgWuy4AbjMYNz48JZnsJBEf+OhcA1WXDKVr/34q3BUJceBG0SvNuIwDVZcFKxSfBrsqIF2wTftiJwjRacOqNwjk22fBb7fErw7HQmcM0WnHqz6Xuvn2/DZNZr0eEnwa+fgwhcswU3F67ypMGjsc59W3Zgw/l1OojAtVpws66YFc7DedM/bkunt5z5Hc6r7VkEruGCm2Uzq3nsvvc6+jJ0Y65mlvW2DadFPDpuJALXdMHNhlwNvtQ86ANlx38avLnvigicgwX30OdC5LyoJff62P52vTDkfDherre9JALnYsEB8IfADSJwQFAEjgUHhEXgWHBAWASOBQeEReBYcEBYBI4FB4RF4FhwQFgEjgUHhEXgWHBAWASOBQeEReBYcEBYBI4FB4RF4FhwQFgEjgUHhEXgWHBAWASOBQeEReBYcEBYBI4F98u+veU4DkJRFDURQsHA/KfbIXai6keq+pfrtZiBP7aOQwxhCZwFB2EJnAUHYQmcBQdhCZwFB2EJnAUHYQmcBQdhCZwFB2EJnAUHYQmcBQdhCZwFB2EJnAUHYQmcBQdhCZwFB2EJnAUHYQmcBQdhCdx7wV33EUBQReDeCy5tQChJ4OaCSzNw9w0I5T4Dl64duO0ZuJ7rBoRSc7964LZjwe1534BQ9rzPwF25b2fghh/hIJiU8xC4eY1aR/aOCrHUGbhr3zG8r1F77hsQSM/96peor2tUEw6CmQPu8ncMx49wtzp67pd+DBBL6bmPerv4T3Dvbxn2nMcGBDFy3r2hvv8JN7qXVAij5ty9of424XzOAEHcswH3dcLd6lA4COLZt1FvBtyXi9SevaVCADV7Qf1zwp2FG54HLK2Ms28G3KGUL4XrRhwsrHZ9+1y4NhPnu1RYUpp5a/r2qXB7b+24f0keDSykpOO/EK31/eybwJ3Kq3B177m1DCyptdz3qm+fNtwccbNxKgdraQ+5933o24cNdybu2biHBiwiP8y6HXnTt7+8Cnevz8YBi5l1q3d9+6fyStxs3DQcx1nmTLNu8vZJmdLROGA5z7qlom/fjLiSTjdgGelB3n5O3JSABR1107dvEwesa+MHReZgRcbb/yqO4yx1AAAAAH6xBwcCAAAAAED+r42gqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqirtwSEBAAAAgKD/r81+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYALG6vVOk3uGfAAAAABJRU5ErkJggg==)

> [!NOTE]
> У цьому сценарії ми просто використовуємо OAuth як шар перетворення до моделі автентифікації, що лежить в основі. Ми ігноруємо багато аспектів OAuth, як-от скопи.

#### Використання наявного встановлення Passport

Якщо ваш застосунок уже використовує Laravel Passport, Laravel MCP має безшовно працювати у вашому наявному встановленні Passport, але власні скопи наразі не підтримуються, оскільки OAuth використовується переважно як шар перетворення до моделі автентифікації, що лежить в основі.

Laravel MCP через метод `Mcp::oauthRoutes`, згаданий вище, додає, оголошує й використовує єдиний скоп `mcp:use`.

#### Passport чи Sanctum?

OAuth2.1 - це задокументований механізм автентифікації у специфікації Model Context Protocol, і він найширше підтримується серед MCP-клієнтів. З цієї причини ми рекомендуємо використовувати Passport, коли це можливо.

Якщо ваш застосунок уже використовує [Sanctum](/docs/{{version}}/sanctum), додавання Passport може виявитися обтяжливим. У такому разі ми рекомендуємо використовувати Sanctum без Passport, доки у вас не з'явиться чітка, необхідна вимога працювати з MCP-клієнтом, який підтримує лише OAuth.

<a name="sanctum"></a>
### Sanctum

Якщо ви хочете захистити свій MCP-сервер за допомогою [Sanctum](/docs/{{version}}/sanctum), просто додайте `middleware` автентифікації Sanctum до свого сервера у файлі `routes/ai.php`. Далі переконайтеся, що ваші MCP-клієнти надсилають заголовок `Authorization: Bearer <token>`, щоб автентифікація була успішною:

```php
use App\Mcp\Servers\WeatherExample;
use Laravel\Mcp\Facades\Mcp;

Mcp::web('/mcp/demo', WeatherExample::class)
    ->middleware('auth:sanctum');
```

<a name="custom-mcp-authentication"></a>
#### Власна автентифікація MCP

Якщо ваш застосунок видає власні API-токени, ви можете автентифікувати свій MCP-сервер, призначивши маршрутам `Mcp::web` будь-який `middleware` на свій розсуд. Ваш власний `middleware` може вручну перевіряти заголовок `Authorization`, щоб автентифікувати вхідний MCP-запит.

<a name="authorization"></a>
## Авторизація

Ви можете звернутися до поточного автентифікованого користувача методом `$request->user()`, що дозволяє виконувати [перевірки авторизації](/docs/{{version}}/authorization) у ваших MCP-інструментах і ресурсах:

```php
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;

/**
 * Handle the tool request.
 */
public function handle(Request $request): Response
{
    if (! $request->user()->can('read-weather')) {
        return Response::error('Permission denied.');
    }

    // ...
}
```

<a name="client"></a>
## MCP-клієнт

Окрім створення серверів, Laravel MCP містить клієнт для підключення до інших MCP-серверів - як власних, так і сторонніх. Клієнт дозволяє вашому застосунку виявляти й викликати інструменти, які надає MCP-сервер, що особливо корисно, щоб дати вашим [AI-агентам](/docs/{{version}}/ai-sdk#mcp-tools) доступ до можливостей зовнішніх MCP-серверів.

<a name="client-connecting"></a>
### Підключення до серверів

Підключитися до MCP-сервера, доступного через HTTP, можна методом `Client::web`, передавши URL сервера:

```php
use Laravel\Mcp\Client;

$client = Client::web('https://api.githubcopilot.com/mcp/');
```

Щоб підключитися до локального MCP-сервера, який працює як команда, скористайтеся методом `Client::local`, указавши команду й будь-які аргументи, потрібні для запуску сервера:

```php
use Laravel\Mcp\Client;

$client = Client::local('php', ['artisan', 'mcp:start']);
```

Клієнт підключається ліниво, автоматично встановлюючи з'єднання під час першого переліку чи виклику інструментів. Якщо вам потрібно керувати з'єднанням вручну, скористайтеся методами `connect`, `connected` і `disconnect`:

```php
$client->connect();

if ($client->connected()) {
    $capabilities = $client->capabilities();
    $server = $client->serverInfo();
}

$client->disconnect();
```

Ви можете змінити тайм-аут запиту методом `withTimeout`:

```php
$client = Client::web('https://api.githubcopilot.com/mcp/')->withTimeout(30);
```

<a name="named-clients"></a>
### Іменовані клієнти

Замість створювати клієнта щоразу, коли він вам потрібен, ви можете зареєструвати багаторазові іменовані клієнти. Зазвичай це роблять у методі `boot` сервіс-провайдера за допомогою фасада `Mcp`:

```php
use Laravel\Mcp\Client;
use Laravel\Mcp\Facades\Mcp;

Mcp::registerClient('github', fn () => Client::web('https://api.githubcopilot.com/mcp/'));
```

Щойно клієнта зареєстровано, ви можете отримати його будь-де у своєму застосунку за іменем:

```php
use Laravel\Mcp\Facades\Mcp;

$client = Mcp::client('github');
```

Іменовані клієнти створюються один раз на запит і автоматично від'єднуються наприкінці життєвого циклу запиту.

<a name="client-authentication"></a>
### Автентифікація клієнта

Щоб підключитися до вебсервера MCP, захищеного bearer-токеном, скористайтеся методом `withToken`. Ви можете передати рядок токена або замикання, яке ліниво його поверне:

```php
use Illuminate\Support\Facades\Auth;
use Laravel\Mcp\Client;

$client = Client::web('https://api.githubcopilot.com/mcp/')->withToken(
    config('services.github_mcp.token'),
);

$client = Client::web('https://api.githubcopilot.com/mcp/')->withToken(
    fn () => Auth::user()->github_mcp_token,
);
```

Для серверів, захищених [OAuth 2.1](#oauth), налаштуйте клієнта методом `withOAuth`. Це клієнтський відповідник захисту ваших власних серверів через OAuth:

```php
use Laravel\Mcp\Client;
use Laravel\Mcp\Facades\Mcp;

Mcp::registerClient('github', fn () => Client::web('https://api.githubcopilot.com/mcp/')->withOAuth(
    clientId: config('services.github_mcp.client_id'),
    clientSecret: config('services.github_mcp.client_secret'),
));
```

> [!NOTE]
> Аргументи `clientId` і `clientSecret` можна опустити. Laravel використає [Client ID Metadata Document](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/client-registration#client-id-metadata-documents), коли сервер авторизації їх підтримує, і повертається до [динамічної реєстрації клієнтів](https://datatracker.ietf.org/doc/html/rfc7591) для застарілих серверів.

Сервер авторизації має оголосити підтримку методу виклику коду PKCE `S256` у своїх метаданих. Laravel відхилить спробу авторизації, якщо підтримка PKCE не оголошена.

Далі зареєструйте OAuth-маршрути для іменованого клієнта у файлі `routes/ai.php` методом `oAuthRoutesFor`. Замикання, яке ви надаєте, отримує ім'я клієнта й отриманий `TokenSet` після обміну коду авторизації на токен доступу:

```php
use Illuminate\Support\Facades\Auth;
use Laravel\Mcp\Client\OAuth\TokenSet;
use Laravel\Mcp\Facades\Mcp;

Mcp::oAuthRoutesFor('github', function (string $client, TokenSet $token) {
    Auth::user()->update([
        'github_mcp_token' => $token->accessToken,
    ]);

    return redirect('/dashboard');
});
```

Це реєструє три іменовані маршрути: маршрут підключення (`mcp.oauth.{client}.connect`), який перенаправляє користувача на сервер авторизації, маршрут зворотного виклику (`mcp.oauth.{client}.callback`), який обмінює код авторизації й викликає ваш обробник, і публічний маршрут Client ID Metadata Document (`mcp.oauth.{client}.client-metadata`). Маршрути підключення й зворотного виклику за замовчуванням використовують групу `middleware` `web`, яку ви можете перевизначити аргументом `middleware`. Маршрут метаданих цей `middleware` не використовує, бо сервер авторизації має мати змогу його отримати.

Документ метаданих описує ваш застосунок як публічного OAuth-клієнта й використовує `APP_URL` вашого застосунку для генерації ідентифікатора клієнта та URL зворотного виклику. Тому переконайтеся, що змінна оточення `APP_URL` правильно встановлена у продакшені. Ви можете налаштувати маршрут метаданих і надати додаткові метадані за допомогою аргументів `clientMetadataUri` і `clientMetadata`:

```php
use Laravel\Mcp\Client\OAuth\TokenSet;
use Laravel\Mcp\Facades\Mcp;

Mcp::oAuthRoutesFor(
    'github',
    function (string $client, TokenSet $token) {
        // Store the token...

        return redirect('/dashboard');
    },
    clientMetadataUri: 'oauth/github/client.json',
    clientMetadata: [
        'client_name' => 'Acme Dashboard',
        'logo_uri' => 'https://acme.com/logo.png',
    ],
);
```

Щоб розпочати потік авторизації, перенаправте користувача на маршрут підключення:

```php
return redirect()->route('mcp.oauth.github.connect');
```

<a name="client-tools"></a>
### Інструменти

Отримати інструменти, які надає MCP-сервер, можна методом `tools`, що повертає колекцію інструментів з ключами за іменами:

```php
use Laravel\Mcp\Facades\Mcp;

$tools = Mcp::client('github')->tools();

foreach ($tools as $tool) {
    $tool->name;
    $tool->title;
    $tool->description;
    $tool->inputSchema;
}
```

Клієнт автоматично проходить сторінками всіх доступних інструментів. Ви можете обмежити кількість повернутих інструментів аргументом `limit`:

```php
$tools = Mcp::client('github')->tools(limit: 10);
```

Щоб викликати інструмент, скористайтеся методом `callTool`, передавши ім'я інструмента й масив аргументів. Повернутий екземпляр `ToolResult` надає відповідь інструмента:

```php
use Laravel\Mcp\Facades\Mcp;

$result = Mcp::client('github')->callTool('list_issues', [
    'owner' => 'laravel',
    'repo' => 'framework',
]);

$result->text(); // The text content of the response...
(string) $result; // Equivalent to calling text()...
$result->isError; // Whether the tool reported an error...
$result->structuredContent;  // Structured content, if any...
```

Як альтернативу ви можете викликати інструмент безпосередньо з екземпляра переліченого інструмента:

```php
$tools = Mcp::client('github')->tools();

$result = $tools['list_issues']->call([
    'owner' => 'laravel',
    'repo' => 'framework',
]);
```

Якщо ви створюєте агентів за допомогою [Laravel AI SDK](/docs/{{version}}/ai-sdk), ви також можете передати інструменти з MCP-клієнта безпосередньо агенту, дозволивши моделі викликати їх під час відповіді на промпт. Докладніше дивіться в розділі [MCP Tools](/docs/{{version}}/ai-sdk#mcp-tools) документації AI SDK.

<a name="client-prompts"></a>
### Промпти

Отримати промпти, які надає MCP-сервер, можна методом `prompts`, що повертає колекцію промптів з ключами за іменами:

```php
use Laravel\Mcp\Facades\Mcp;

$prompts = Mcp::client('github')->prompts();

foreach ($prompts as $prompt) {
    $prompt->name;
    $prompt->title;
    $prompt->description;
    $prompt->arguments;
}
```

Клієнт автоматично проходить сторінками всіх доступних промптів. Ви можете обмежити кількість повернутих промптів аргументом `limit`:

```php
$prompts = Mcp::client('github')->prompts(limit: 10);
```

Щоб отримати промпт, скористайтеся методом `getPrompt`, передавши ім'я промпта й масив аргументів. Повернутий екземпляр `PromptResult` надає згенеровані повідомлення:

```php
use Laravel\Mcp\Facades\Mcp;

$result = Mcp::client('github')->getPrompt('issue_to_fix_workflow', [
    'owner' => 'laravel',
    'repo' => 'framework',
    'title' => 'Fix typo in README',
    'description' => 'The installation section has a typo.',
]);

$result->text(); // The text content of the messages...
(string) $result; // Equivalent to calling text()...
$result->messages; // The raw messages returned by the prompt...
$result->description; // The prompt description, if any...
```

<a name="client-resources"></a>
### Ресурси

Отримати ресурси, які надає MCP-сервер, можна методом `resources`, що повертає колекцію ресурсів з ключами за URI:

```php
use Laravel\Mcp\Facades\Mcp;

$resources = Mcp::client('github')->resources();

foreach ($resources as $resource) {
    $resource->uri;
    $resource->name;
    $resource->title;
    $resource->description;
    $resource->mimeType;
    $resource->size;
}
```

Клієнт автоматично проходить сторінками всіх доступних ресурсів. Ви можете обмежити кількість повернутих ресурсів аргументом `limit`:

```php
$resources = Mcp::client('github')->resources(limit: 10);
```

Щоб прочитати ресурс, скористайтеся методом `readResource`, передавши URI ресурсу. Повернутий екземпляр `ResourceReadResult` надає вміст ресурсу:

```php
use Laravel\Mcp\Facades\Mcp;

$result = Mcp::client('github')->readResource('repo://laravel/framework/contents/README.md');

$result->content(); // The content of the resource, decoding base64 blobs as needed...
(string) $result; // Equivalent to calling content()...
$result->mimeType(); // The MIME type of the resource, if any...
$result->contents; // The raw contents returned by the resource...
```

<a name="testing-servers"></a>
## Тестування серверів

Ви можете тестувати свої MCP-сервери за допомогою вбудованого MCP Inspector або пишучи юніт-тести.

<a name="mcp-inspector"></a>
### MCP Inspector

[MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) - це інтерактивний інструмент для тестування й налагодження ваших MCP-серверів. Використовуйте його, щоб підключитися до сервера, перевірити автентифікацію й випробувати інструменти, ресурси та промпти.

Ви можете запустити інспектор для будь-якого зареєстрованого сервера:

```shell
# Web server...
php artisan mcp:inspector mcp/weather

# Local server named "weather"...
php artisan mcp:inspector weather
```

Ця команда запускає MCP Inspector і надає налаштування клієнта, які ви можете скопіювати до свого MCP-клієнта, щоб переконатися, що все налаштовано правильно. Якщо ваш вебсервер захищено `middleware` автентифікації, обов'язково додайте потрібні заголовки, як-от bearer-токен `Authorization`, під час підключення.

<a name="unit-tests"></a>
### Юніт-тести

Ви можете писати юніт-тести для своїх MCP-серверів, інструментів, ресурсів і промптів.

Для початку створіть новий тест-кейс і викличте потрібний примітив на сервері, який його реєструє. Наприклад, щоб протестувати інструмент на `WeatherServer`:

```php tab=Pest
test('tool', function () {
    $response = WeatherServer::tool(CurrentWeatherTool::class, [
        'location' => 'New York City',
        'units' => 'fahrenheit',
    ]);

    $response
        ->assertOk()
        ->assertSee('The current weather in New York City is 72°F and sunny.');
});
```

```php tab=PHPUnit
/**
 * Test a tool.
 */
public function test_tool(): void
{
    $response = WeatherServer::tool(CurrentWeatherTool::class, [
        'location' => 'New York City',
        'units' => 'fahrenheit',
    ]);

    $response
        ->assertOk()
        ->assertSee('The current weather in New York City is 72°F and sunny.');
}
```

Так само ви можете тестувати промпти й ресурси:

```php
$response = WeatherServer::prompt(...);
$response = WeatherServer::resource(...);
```

Ви також можете діяти від імені автентифікованого користувача, додавши метод `actingAs` ланцюжком перед викликом примітива:

```php
$response = WeatherServer::actingAs($user)->tool(...);
```

Отримавши відповідь, ви можете скористатися різними методами тверджень, щоб перевірити її вміст і статус.

Ви можете стверджувати, що відповідь успішна, методом `assertOk`. Він перевіряє, що відповідь не містить помилок:

```php
$response->assertOk();
```

Ви можете стверджувати, що відповідь містить певний текст, методом `assertSee`:

```php
$response->assertSee('The current weather in New York City is 72°F and sunny.');
```

Ви можете стверджувати, що відповідь містить помилку, методом `assertHasErrors`:

```php
$response->assertHasErrors();

$response->assertHasErrors([
    'Something went wrong.',
]);
```

Ви можете стверджувати, що відповідь не містить помилки, методом `assertHasNoErrors`:

```php
$response->assertHasNoErrors();
```

Ви можете стверджувати, що відповідь містить певні метадані, методами `assertName()`, `assertTitle()` та `assertDescription()`:

```php
$response->assertName('current-weather');
$response->assertTitle('Current Weather Tool');
$response->assertDescription('Fetches the current weather forecast for a specified location.');
```

Ви можете стверджувати, що сповіщення були надіслані, методами `assertSentNotification` і `assertNotificationCount`:

```php
$response->assertSentNotification('processing/progress', [
    'step' => 1,
    'total' => 5,
]);

$response->assertSentNotification('processing/progress', [
    'step' => 2,
    'total' => 5,
]);

$response->assertNotificationCount(5);
```

Насамкінець, якщо ви хочете оглянути сирий вміст відповіді, скористайтеся методами `dd` чи `dump`, щоб вивести відповідь для налагодження:

```php
$response->dd();
$response->dump();
```

---

Джерело: https://laravelukraine.com/docs/13.x/boost

# Laravel Boost

- [Вступ](#introduction)
- [Встановлення](#installation)
    - [Налаштування агентів](#set-up-your-agents)
    - [Оновлення ресурсів Boost](#keeping-boost-resources-updated)
- [MCP-сервер](#mcp-server)
    - [Доступні інструменти MCP](#available-mcp-tools)
    - [Ручна реєстрація MCP-сервера](#manually-registering-the-mcp-server)
- [Настанови для AI](#ai-guidelines)
    - [Доступні настанови для AI](#available-ai-guidelines)
    - [Додавання власних настанов для AI](#adding-custom-ai-guidelines)
    - [Перевизначення настанов Boost](#overriding-boost-ai-guidelines)
    - [Настанови для AI у сторонніх пакетах](#third-party-package-ai-guidelines)
- [Навички агентів](#agent-skills)
    - [Доступні навички](#available-skills)
    - [Власні навички](#custom-skills)
    - [Перевизначення навичок](#overriding-skills)
    - [Навички у сторонніх пакетах](#third-party-package-skills)
- [Настанови проти навичок](#guidelines-vs-skills)
- [Правила проєкту](#project-rules)
    - [Запис правил](#recording-rules)
    - [Виведення конвенцій вашого застосунку](#inferring-your-applications-conventions)
    - [Вимкнення правил проєкту](#disabling-project-rules)
- [API документації](#documentation-api)
- [Розширення Boost](#extending-boost)
    - [Додавання підтримки інших IDE та AI-агентів](#adding-support-for-other-ides-ai-agents)

<a name="introduction"></a>
## Вступ

Laravel Boost пришвидшує розробку з допомогою AI, надаючи ключові настанови й навички агентів, які допомагають AI-агентам писати якісні застосунки Laravel відповідно до найкращих практик фреймворку.

Boost також надає потужний API документації екосистеми Laravel, що поєднує вбудований інструмент MCP із великою базою знань, яка містить понад 17 000 фрагментів інформації про Laravel, - усе це підсилено семантичним пошуком на ембедингах для точних результатів з урахуванням контексту. Boost вказує AI-агентам на кшталт Claude Code і Cursor користуватися цим API, щоб дізнаватися про найновіші можливості й найкращі практики Laravel.

<a name="installation"></a>
## Встановлення

Laravel Boost можна встановити через Composer:

```shell
composer require laravel/boost --dev
```

Далі встановіть MCP-сервер і настанови щодо написання коду:

```shell
php artisan boost:install
```

Команда `boost:install` згенерує відповідні файли настанов і навичок для тих агентів, яких ви обрали під час встановлення.

Коли Laravel Boost встановлено, ви готові писати код у Cursor, Claude Code чи будь-якому іншому AI-агенті на ваш вибір.

> [!NOTE]
> Сміливо додавайте згенерований конфігураційний файл MCP (`.mcp.json`), файли настанов (`CLAUDE.md`, `AGENTS.md`, `junie/` тощо) та конфігураційний файл `boost.json` до `.gitignore` вашого застосунку - ці файли автоматично перегенеровуються під час виконання `boost:install` і `boost:update`.

<a name="set-up-your-agents"></a>
### Налаштування агентів

```text tab=Cursor
1. Open the command palette (`Cmd+Shift+P` or `Ctrl+Shift+P`)
2. Press `enter` on "/open MCP Settings"
3. Turn the toggle on for `laravel-boost`
```

```text tab=Claude Code
Claude Code support is typically enabled automatically. If you find it isn't, open a shell in the project's directory and run the following command:

claude mcp add -s local -t stdio laravel-boost php artisan boost:mcp
```

```text tab=Codex
Codex support is typically enabled automatically. If you find it isn't, open a shell in the project's directory and run the following command:

codex mcp add laravel-boost -- php "artisan" "boost:mcp"
```

```text tab=Gemini CLI
Gemini CLI support is typically enabled automatically. If you find it isn't, open a shell in the project's directory and run the following command:

gemini mcp add -s project -t stdio laravel-boost php artisan boost:mcp
```

```text tab=GitHub Copilot (VS Code)
1. Open the command palette (`Cmd+Shift+P` or `Ctrl+Shift+P`)
2. Press `enter` on "MCP: List Servers"
3. Arrow to `laravel-boost` and press `enter`
4. Choose "Start server"
```

```text tab=Junie
1. Press `shift` twice to open the command palette
2. Search "MCP Settings" and press `enter`
3. Check the box next to `laravel-boost`
4. Click "Apply" at the bottom right
```

<a name="keeping-boost-resources-updated"></a>
### Оновлення ресурсів Boost

Вам, можливо, варто час від часу оновлювати локальні ресурси Boost (настанови для AI та навички), щоб вони відповідали найновішим версіям встановлених у вас пакетів екосистеми Laravel. Для цього скористайтеся артизан-командою `boost:update`.

```shell
php artisan boost:update
```

Ви також можете автоматизувати цей процес, додавши команду до скриптів Composer «post-update-cmd»:

```json
{
  "scripts": {
    "post-update-cmd": [
      "@php artisan boost:update --ansi"
    ]
  }
}
```

За замовчуванням команда `boost:update` оновлюватиме лише ті ресурси Boost, які вже опубліковано у вашому застосунку. Якщо ви хочете, щоб Boost просканував ваш застосунок на нововстановлені пакети й запропонував опублікувати відповідні настанови й навички, скористайтеся опцією `--discover`:

```shell
php artisan boost:update --discover
```

<a name="mcp-server"></a>
## MCP-сервер

Laravel Boost надає MCP-сервер (Model Context Protocol), який відкриває AI-агентам інструменти для взаємодії з вашим застосунком Laravel. Ці інструменти дають агентам змогу оглядати структуру застосунку, робити запити до бази даних, виконувати код тощо.

<a name="available-mcp-tools"></a>
### Доступні інструменти MCP

<div class="overflow-auto">

| Ім'я                 | Примітки                                                                                                    |
| -------------------- | ----------------------------------------------------------------------------------------------------------- |
| Application Info     | Читає версії PHP і Laravel, рушій бази даних, список пакетів екосистеми з версіями та моделі Eloquent       |
| Browser Logs         | Читає логи та помилки з браузера                                                                            |
| Database Connections | Оглядає доступні підключення до бази даних, зокрема стандартне                                              |
| Database Query       | Виконує запит до бази даних                                                                                 |
| Database Schema      | Читає схему бази даних                                                                                      |
| Get Absolute URL     | Перетворює відносні URI на абсолютні, щоб агенти генерували коректні URL                                    |
| Last Error           | Читає останню помилку з файлів логу застосунку                                                              |
| Read Log Entries     | Читає останні N записів логу                                                                                |
| Record Rule          | Записує довготривале [правило проєкту](#project-rules) у `.ai/rules`, щоб майбутні агенти його успадкували |
| Search Docs          | Робить запит до хостованого сервісу API документації Laravel, щоб отримати документацію за встановленими пакетами |

</div>

<a name="manually-registering-the-mcp-server"></a>
### Ручна реєстрація MCP-сервера

Іноді вам може знадобитися зареєструвати MCP-сервер Laravel Boost у вашому редакторі вручну. Реєструйте MCP-сервер із такими даними:

<table>
<tr><td><strong>Command</strong></td><td><code>php</code></td></tr>
<tr><td><strong>Args</strong></td><td><code>artisan boost:mcp</code></td></tr>
</table>

Приклад JSON:

```json
{
    "mcpServers": {
        "laravel-boost": {
            "command": "php",
            "args": ["artisan", "boost:mcp"]
        }
    }
}
```

<a name="ai-guidelines"></a>
## Настанови для AI

Настанови для AI - це складані файли інструкцій, які завантажуються наперед, щоб дати AI-агентам ключовий контекст про пакети екосистеми Laravel. Ці настанови містять основні угоди, найкращі практики та специфічні для фреймворку патерни, які допомагають агентам генерувати послідовний і якісний код.

<a name="available-ai-guidelines"></a>
### Доступні настанови для AI

Laravel Boost містить настанови для AI для таких пакетів і фреймворків. Настанови `core` дають загальні поради AI щодо відповідного пакета, застосовні до всіх версій.

<div class="overflow-auto">

| Пакет             | Підтримувані версії    |
| ----------------- | ---------------------- |
| Core & Boost      | core                   |
| Laravel Framework | core, 10.x, 11.x, 12.x, 13.x |
| Livewire          | core, 2.x, 3.x, 4.x    |
| Flux UI           | core, free, pro        |
| Folio             | core                   |
| Herd              | core                   |
| Inertia Laravel   | core, 1.x, 2.x, 3.x    |
| Inertia React     | core, 1.x, 2.x, 3.x    |
| Inertia Vue       | core, 1.x, 2.x, 3.x    |
| Inertia Svelte    | core, 1.x, 2.x, 3.x    |
| MCP               | core                   |
| Pennant           | core                   |
| Pest              | core, 3.x, 4.x         |
| PHPUnit           | core                   |
| Pint              | core                   |
| Sail              | core                   |
| Tailwind CSS      | core, 3.x, 4.x         |
| Livewire Volt     | core                   |
| Wayfinder         | core                   |
| Enforce Tests     | conditional            |

</div>

> **Примітка:** щоб тримати настанови для AI актуальними, дивіться розділ [Оновлення ресурсів Boost](#keeping-boost-resources-updated).

<a name="adding-custom-ai-guidelines"></a>
### Додавання власних настанов для AI

Щоб доповнити Laravel Boost власними настановами для AI, додайте файли `.blade.php` чи `.md` до каталогу `.ai/guidelines/*` вашого застосунку. Ці файли автоматично потраплять до настанов Laravel Boost, коли ви виконаєте `boost:install`.

<a name="overriding-boost-ai-guidelines"></a>
### Перевизначення настанов Boost

Ви можете перевизначити вбудовані настанови Boost для AI, створивши власні настанови з такими самими шляхами до файлів. Коли ви створюєте власну настанову, шлях якої збігається зі шляхом наявної настанови Boost, Boost використає вашу версію замість вбудованої.

Наприклад, щоб перевизначити настанови Boost «Inertia React v2 Form Guidance», створіть файл `.ai/guidelines/inertia-react/2/forms.blade.php`. Коли ви виконаєте `boost:install`, Boost включить вашу настанову замість стандартної.

<a name="third-party-package-ai-guidelines"></a>
### Настанови для AI у сторонніх пакетах

Якщо ви підтримуєте сторонній пакет і хочете, щоб Boost включав настанови для AI щодо нього, додайте до пакета файл `resources/boost/guidelines/core.blade.php`. Коли користувачі вашого пакета виконають `php artisan boost:install`, Boost автоматично завантажить ваші настанови.

Настанови для AI мають коротко описувати, що робить ваш пакет, окреслювати потрібну структуру файлів чи угоди й пояснювати, як створювати чи використовувати його основні можливості (з прикладами команд або фрагментами коду). Тримайте їх стислими, дієвими й зосередженими на найкращих практиках, щоб AI міг генерувати правильний код для ваших користувачів. Ось приклад:

```markdown
## Package Name

This package provides [brief description of functionality].

### Features

- Feature 1: [clear & short description].
- Feature 2: [clear & short description]. Example usage:

@verbatim
<code-snippet name="How to use Feature 2" lang="php">
$result = PackageName::featureTwo($param1, $param2);
</code-snippet>
@endverbatim
```

<a name="agent-skills"></a>
## Навички агентів

[Навички агентів](https://agentskills.io/home) - це легкі цільові модулі знань, які агенти можуть активувати на вимогу, працюючи в конкретних доменах. На відміну від настанов, які завантажуються наперед, навички дозволяють підтягувати детальні патерни й найкращі практики лише тоді, коли вони доречні, - це зменшує роздування контексту й підвищує релевантність згенерованого AI коду.

Коли ви виконуєте `boost:install` і обираєте навички як можливість, вони встановлюються автоматично - за пакетами, виявленими у вашому `composer.json`. Наприклад, якщо ваш проєкт містить `livewire/livewire`, навичку `livewire-development` буде встановлено автоматично. Навички, що йдуть у комплекті з Boost, як-от `infer-conventions`, встановлюються незалежно від того, які пакети у вас є.

<a name="available-skills"></a>
### Доступні навички

<div class="overflow-auto">

| Навичка                    | Пакет          |
| -------------------------- | -------------- |
| fluxui-development         | Flux UI        |
| folio-routing              | Folio          |
| infer-conventions          | Boost          |
| inertia-react-development  | Inertia React  |
| inertia-svelte-development | Inertia Svelte |
| inertia-vue-development    | Inertia Vue    |
| livewire-development       | Livewire       |
| mcp-development            | MCP            |
| pennant-development        | Pennant        |
| pest-testing               | Pest           |
| tailwindcss-development    | Tailwind CSS   |
| volt-development           | Volt           |
| wayfinder-development      | Wayfinder      |

</div>

> **Примітка:** щоб тримати навички актуальними, дивіться розділ [Оновлення ресурсів Boost](#keeping-boost-resources-updated).

<a name="custom-skills"></a>
### Власні навички

Щоб створити власні навички, додайте файл `SKILL.md` до каталогу `.ai/skills/{skill-name}/` вашого застосунку. Коли ви виконаєте `boost:update`, ваші навички буде встановлено разом із вбудованими навичками Boost.

Наприклад, щоб створити власну навичку для доменної логіки вашого застосунку:

```
.ai/skills/creating-invoices/SKILL.md
```

<a name="overriding-skills"></a>
### Перевизначення навичок

Ви можете перевизначити вбудовані навички Boost, створивши власні навички з такими самими іменами. Коли ви створюєте власну навичку, ім'я якої збігається з іменем наявної навички Boost, Boost використає вашу версію замість вбудованої.

Наприклад, щоб перевизначити навичку Boost `livewire-development`, створіть файл `.ai/skills/livewire-development/SKILL.md`. Коли ви виконаєте `boost:update`, Boost включить вашу навичку замість стандартної.

<a name="third-party-package-skills"></a>
### Навички у сторонніх пакетах

Якщо ви підтримуєте сторонній пакет і хочете, щоб Boost включав навички щодо нього, додайте до пакета файл `resources/boost/skills/{skill-name}/SKILL.md`. Коли користувачі вашого пакета виконають `php artisan boost:install`, Boost автоматично встановить ваші навички відповідно до їхніх уподобань.

Навички Boost підтримують [формат Agent Skills](https://agentskills.io/what-are-skills) і мають бути оформлені як каталог із файлом `SKILL.md`, що містить YAML-хедер та інструкції в Markdown. Файл `SKILL.md` має містити обов'язковий хедер (`name` та `description`), а також може за бажання містити скрипти, шаблони й довідкові матеріали.

Навички мають окреслювати потрібну структуру файлів чи угоди й пояснювати, як створювати чи використовувати основні можливості (з прикладами команд або фрагментами коду). Тримайте їх стислими, дієвими й зосередженими на найкращих практиках, щоб AI міг генерувати правильний код для ваших користувачів:

```markdown
---
name: package-name-development
description: Build and work with PackageName features, including components and workflows.
---

# Package Name Development

## When to use this skill
Use this skill when working with PackageName features...

## Features

- Feature 1: [clear & short description].
- Feature 2: [clear & short description]. Example usage:

$result = PackageName::featureTwo($param1, $param2);
```

<a name="guidelines-vs-skills"></a>
## Настанови проти навичок

Laravel Boost надає два різні способи дати AI-агентам контекст про ваш застосунок: **настанови** та **навички**.

**Настанови** завантажуються наперед при старті AI-агента й дають ключовий контекст про угоди та найкращі практики Laravel, які широко застосовні у всій вашій кодовій базі.

**Навички** активуються на вимогу під час роботи над конкретними завданнями й містять детальні патерни для окремих доменів (як-от компоненти Livewire чи тести Pest). Завантаження навичок лише за потреби зменшує роздування контексту й підвищує якість коду.

<div class="overflow-auto">

| Аспект          | Настанови                          | Навички                          |
| --------------- | ---------------------------------- | -------------------------------- |
| **Завантаження**| Наперед, присутні завжди           | На вимогу, коли доречні          |
| **Обсяг**       | Широкий, фундаментальний           | Вузький, під конкретне завдання  |
| **Призначення** | Основні угоди й найкращі практики  | Детальні патерни реалізації      |

</div>

І настанови, і навички описують екосистему Laravel. Щоб зафіксувати угоди вашого власного застосунку, варто використовувати [правила проєкту](#project-rules).

<a name="project-rules"></a>
## Правила проєкту

Хоча рекомендації та навички навчають агентів писати Laravel, правила проєкту навчають їх писати ваш застосунок. Правило - це все, що вам інакше довелося б пояснювати знову в кожній новій сесії:

<div class="content-list" markdown="1">

- Рішення, прийняті по ходу вами, вашими агентами або вашими колегами.
- Стильові рекомендації та вподобання, яким складно навчити агента слідувати.
- Пастки та обмеження, які не можна вивести з навколишнього коду.

</div>

Правила зберігаються як Markdown-файли в каталозі `.ai/rules` вашого застосунку і мають комітитися до системи контролю версій. На відміну від власної пам'яті агента, яка є персональною та обмеженою сесією, ваші правила спільні з вашою командою і з кожним агентом, що працює над вашим застосунком.

Кожен файл правил оголошує маски файлів, до яких він застосовується, у своєму frontmatter:

```markdown
---
paths:
  - app/Http/Controllers/**
---

# Http Controllers

## Extend BaseController for tenant scoping

All controllers must extend `App\Http\Controllers\BaseController`, which applies the
current tenant's query scope. Extending Laravel's base controller directly will leak
data across tenants.
```

Крім того, Boost підтримує файл `.ai/rules/index.md`, який зіставляє маски з їхніми файлами правил. Агентам наказано консультуватися з цим індексом перед плануванням або редагуванням будь-якого файлу, тому правило завантажується лише тоді, коли воно є релевантним:

```markdown
# Project Rules Index

Before planning or editing, find the row whose globs match the file's path and read that rule file.

| Applies to | Rule file |
| --- | --- |
| app/Http/Controllers/** | .ai/rules/controllers.md |
| app/Models/** | .ai/rules/models.md |
```

> [!NOTE]
> На відміну від `.mcp.json` та згенерованих файлів рекомендацій, каталог `.ai/rules` має комітитися до системи контролю версій, щоб ваші правила були спільними з вашою командою.

<a name="recording-rules"></a>
### Запис правил

Щоб записати правило, ви можете просто попросити свого агента запам'ятати його:

```text
Remember that all money values are stored as integer cents, never as floats.
```

Агент викличе MCP-інструмент `record-rule` Boost з параметрами `glob`, коротким `title` і `note`. Boost потім збереже правило під відповідною областю, створивши файл правила за потреби, і оновить індекс.

Ви завжди повинні записувати правила за допомогою інструменту `record-rule`, а не створювати файли правил вручну. Boost регенерує `.ai/rules/index.md` як частину процесу запису правила, і агенти покладаються на цей індекс для виявлення, які правила застосовуються до файлу, над яким вони працюють. Файл правила, доданий вручну, не буде виявлено, доки індекс не буде регенеровано наступного разу.

<a name="inferring-your-applications-conventions"></a>
### Виведення конвенцій вашого застосунку

Запис правил по одному добре працює в майбутньому; проте, існуючий застосунок уже містить роки конвенцій. Навичка `infer-conventions` підготує ваші правила з коду, який ви вже написали. Для початку попросіть свого агента використати навичку:

```text
Use the infer-conventions skill
```

Навичка проаналізує ваш застосунок за контрольним списком аспектів конвенцій Laravel, включаючи валідацію, контролери, авторизацію, моделі, архітектуру, тестування, фронтенд, базу даних та консоль, а потім виконає відкритий прохід для шаблонів на кшталт базових класів, спільних трейтів та структури модулів.

Навичка документує те, що ваш код насправді робить, а не те, що він повинен робити. Вона записує лише добре підтримані, не-дефолтні конвенції, пропускає налаштування фреймворку за замовчуванням та все, що вже забезпечують Pint або Rector, і повідомляє про справді змішані шаблони замість їх запису. Перед записом будь-яких правил навичка представить кожну виявлену конвенцію разом із підтверджуючими доказами для вашого схвалення. Якщо ви хочете, щоб навичка записала всі виявлені конвенції без підтвердження, ви можете сказати їй "yolo".

<a name="disabling-project-rules"></a>
### Вимкнення правил проєкту

Правила проєкту увімкнено за замовчуванням. Щоб вимкнути їх повністю, визначте наступну змінну оточення. Це видалить інструмент MCP `record-rule` і зупинить керування Boost каталогом `.ai/rules`:

```ini
BOOST_RULES_ENABLED=false
```

<a name="documentation-api"></a>
## API документації

Laravel Boost містить API документації, який дає AI-агентам доступ до великої бази знань із понад 17 000 фрагментів інформації про Laravel. Цей API використовує семантичний пошук на ембедингах, щоб видавати точні результати з урахуванням контексту.

Інструмент MCP `Search Docs` дозволяє агентам робити запити до хостованого сервісу API документації Laravel і отримувати документацію за встановленими у вас пакетами. Настанови й навички Boost автоматично вкажуть вашому агенту користуватися цим API.

<div class="overflow-auto">

| Пакет             | Підтримувані версії |
| ----------------- | ------------------ |
| Laravel Framework | 10.x, 11.x, 12.x, 13.x |
| Filament          | 2.x, 3.x, 4.x, 5.x |
| Flux UI           | 2.x Free, 2.x Pro  |
| Inertia           | 1.x, 2.x           |
| Livewire          | 1.x, 2.x, 3.x, 4.x |
| Nova              | 4.x, 5.x           |
| Pest              | 3.x, 4.x           |
| Tailwind CSS      | 3.x, 4.x           |

</div>

<a name="extending-boost"></a>
## Розширення Boost

Boost працює з багатьма популярними IDE та AI-агентами одразу з коробки. Якщо ваш інструмент ще не підтримується, ви можете створити власного агента й інтегрувати його з Boost.

<a name="adding-support-for-other-ides-ai-agents"></a>
### Додавання підтримки інших IDE та AI-агентів

Щоб додати підтримку нової IDE чи AI-агента, створіть клас, який успадковує `Laravel\Boost\Install\Agents\Agent`, і реалізуйте один чи кілька таких контрактів - залежно від ваших потреб:

- `Laravel\Boost\Contracts\SupportsGuidelines` - додає підтримку настанов для AI.
- `Laravel\Boost\Contracts\SupportsMcp` - додає підтримку MCP.
- `Laravel\Boost\Contracts\SupportsSkills` - додає підтримку навичок агентів.

<a name="writing-the-agent"></a>
#### Написання агента

```php
<?php

declare(strict_types=1);

namespace App;

use Laravel\Boost\Contracts\SupportsGuidelines;
use Laravel\Boost\Contracts\SupportsMcp;
use Laravel\Boost\Contracts\SupportsSkills;
use Laravel\Boost\Install\Agents\Agent;

class CustomAgent extends Agent implements SupportsGuidelines, SupportsMcp, SupportsSkills
{
    // Your implementation...
}
```

Приклад реалізації дивіться в [ClaudeCode.php](https://github.com/laravel/boost/blob/main/src/Install/Agents/ClaudeCode.php).

<a name="registering-the-agent"></a>
#### Реєстрація агента

Зареєструйте свого агента в методі `boot` класу `App\Providers\AppServiceProvider` вашого застосунку:

```php
use Laravel\Boost\Boost;

public function boot(): void
{
    Boost::registerAgent('customagent', CustomAgent::class);
}
```

Коли агента зареєстровано, він буде доступний для вибору під час виконання `php artisan boost:install`.

---

Джерело: https://laravelukraine.com/docs/13.x/testing

# Тестування: перші кроки

- [Вступ](#introduction)
- [Середовище](#environment)
- [Створення тестів](#creating-tests)
- [Запуск тестів](#running-tests)
    - [Паралельний запуск тестів](#running-tests-in-parallel)
    - [Звіт про покриття тестами](#reporting-test-coverage)
    - [Профілювання тестів](#profiling-tests)
- [Кешування конфігурації](#configuration-caching)

<a name="introduction"></a>
## Вступ

Laravel створювався з думкою про тестування. Підтримка тестування через [Pest](https://pestphp.com) та [PHPUnit](https://phpunit.de) доступна одразу з коробки, а файл `phpunit.xml` уже налаштований для вашого застосунку. Фреймворк також містить зручні допоміжні методи, які дозволяють виразно тестувати ваші застосунки.

За замовчуванням каталог `tests` вашого застосунку містить два підкаталоги: `Feature` та `Unit`. Юніт-тести зосереджені на дуже маленькій, ізольованій частині коду. Власне, більшість юніт-тестів, найімовірніше, перевіряють один метод. Тести з каталогу «Unit» не запускають ваш застосунок Laravel, а тому не мають доступу ні до бази даних, ні до інших сервісів фреймворку.

Функціональні тести можуть перевіряти більшу частину вашого коду - зокрема те, як кілька об'єктів взаємодіють між собою, чи навіть повний HTTP-запит до JSON-ендпоїнта. **Загалом більшість ваших тестів мають бути функціональними. Саме такі тести дають найбільшу впевненість, що ваша система працює як задумано.**

Файл `ExampleTest.php` є в обох каталогах - `Feature` та `Unit`. Після встановлення нового застосунку Laravel виконайте команду `vendor/bin/pest`, `vendor/bin/phpunit` чи `php artisan test`, щоб запустити тести.

<a name="environment"></a>
## Середовище

Під час запуску тестів Laravel автоматично встановлює [середовище конфігурації](/docs/{{version}}/configuration#environment-configuration) у `testing` - завдяки змінним оточення, визначеним у файлі `phpunit.xml`. Laravel також автоматично налаштовує сесію та кеш на драйвер `array`, тож під час тестування дані сесії й кешу ніде не зберігатимуться.

Ви вільні задавати за потреби й інші значення конфігурації тестового середовища. Змінні оточення `testing` можна налаштувати у файлі `phpunit.xml` вашого застосунку, але не забудьте скинути кеш конфігурації артизан-командою `config:clear`, перш ніж запускати тести!

<a name="the-env-testing-environment-file"></a>
#### Файл оточення `.env.testing`

Крім того, ви можете створити файл `.env.testing` у корені вашого проєкту. Він використовуватиметься замість файлу `.env` під час запуску тестів Pest і PHPUnit або артизан-команд з опцією `--env=testing`.

<a name="creating-tests"></a>
## Створення тестів

Щоб створити новий тест-кейс, скористайтеся артизан-командою `make:test`. За замовчуванням тести потрапляють до каталогу `tests/Feature`:

```shell
php artisan make:test UserTest
```

Якщо ви хочете створити тест у каталозі `tests/Unit`, додайте до команди `make:test` опцію `--unit`:

```shell
php artisan make:test UserTest --unit
```

Якщо у вас є тестовий клас, який здебільшого спирається на можливості тестування Laravel, але окремому тестовому методу не потрібен запущений фреймворк, застосуйте до цього методу атрибут `#[UnitTest]`, щоб пропустити запуск застосунку саме для нього.

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\Attributes\UnitTest;
use Tests\TestCase;

class LocationServiceTest extends TestCase
{
    public function test_get_coordinates_resolves_address(): void
    {
        // This test uses Laravel's testing features...
    }

    #[UnitTest]
    public function test_get_state_returns_state_from_abbreviation(): void
    {
        // This test runs without booting the application...
    }
}
```

> [!NOTE]
> Заготовки тестів можна налаштувати через [публікацію заготовок](/docs/{{version}}/artisan#stub-customization).

Коли тест згенеровано, ви можете описати його як зазвичай - через Pest чи PHPUnit. Щоб запустити тести, виконайте в терміналі команду `vendor/bin/pest`, `vendor/bin/phpunit` чи `php artisan test`:

```php tab=Pest
<?php

test('basic', function () {
    expect(true)->toBeTrue();
});
```

```php tab=PHPUnit
<?php

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     */
    public function test_basic_test(): void
    {
        $this->assertTrue(true);
    }
}
```

> [!WARNING]
> Якщо ви визначаєте у тестовому класі власні методи `setUp` / `tearDown`, обов'язково викликайте відповідні методи батьківського класу `parent::setUp()` / `parent::tearDown()`. Зазвичай `parent::setUp()` викликають на початку власного методу `setUp`, а `parent::tearDown()` - наприкінці методу `tearDown`.

<a name="running-tests"></a>
## Запуск тестів

Як згадувалося раніше, коли ви написали тести, запустити їх можна через `pest` чи `phpunit`:

```shell tab=Pest
./vendor/bin/pest
```

```shell tab=PHPUnit
./vendor/bin/phpunit
```

Окрім команд `pest` чи `phpunit`, ви можете запускати тести артизан-командою `test`. Артизан-раннер тестів дає докладні звіти, що полегшує розробку й налагодження:

```shell
php artisan test
```

Будь-які аргументи, які можна передати командам `pest` чи `phpunit`, можна передати й артизан-команді `test`:

```shell
php artisan test --testsuite=Feature --stop-on-failure
```

<a name="running-tests-in-parallel"></a>
### Паралельний запуск тестів

За замовчуванням Laravel і Pest / PHPUnit виконують ваші тести послідовно в одному процесі. Проте ви можете суттєво скоротити час прогону, запускаючи тести одночасно в кількох процесах. Для початку встановіть Composer-пакет `brianium/paratest` як «dev»-залежність. Далі додайте опцію `--parallel` до артизан-команди `test`:

```shell
composer require brianium/paratest --dev

php artisan test --parallel
```

За замовчуванням Laravel створить стільки процесів, скільки ядер CPU доступно на вашій машині. Проте ви можете змінити їхню кількість опцією `--processes`:

```shell
php artisan test --parallel --processes=4
```

> [!WARNING]
> Під час паралельного запуску тестів деякі опції Pest / PHPUnit (наприклад, `--do-not-cache-result`) можуть бути недоступні.

<a name="parallel-testing-and-databases"></a>
#### Паралельне тестування й бази даних

Якщо ви налаштували основне підключення до бази даних, Laravel автоматично подбає про створення та міграцію тестової бази для кожного паралельного процесу, що виконує ваші тести. До імен тестових баз додаватиметься токен процесу, унікальний для кожного з них. Наприклад, якщо у вас два паралельні тестові процеси, Laravel створить і використовуватиме тестові бази `your_db_test_1` та `your_db_test_2`.

За замовчуванням тестові бази зберігаються між викликами артизан-команди `test`, щоб їх можна було використати повторно. Проте ви можете створити їх наново опцією `--recreate-databases`:

```shell
php artisan test --parallel --recreate-databases
```

<a name="parallel-testing-hooks"></a>
#### Хуки паралельного тестування

Іноді вам може знадобитися підготувати певні ресурси, які використовують тести вашого застосунку, щоб кілька тестових процесів могли безпечно з ними працювати.

Через фасад `ParallelTesting` ви можете вказати код, який виконуватиметься на `setUp` і `tearDown` процесу чи тест-кейса. Передані замикання отримують змінні `$token` і `$testCase`, що містять токен процесу та поточний тест-кейс відповідно:

```php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\ParallelTesting;
use Illuminate\Support\ServiceProvider;
use PHPUnit\Framework\TestCase;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        ParallelTesting::setUpProcess(function (int $token) {
            // ...
        });

        ParallelTesting::setUpTestCase(function (int $token, TestCase $testCase) {
            // ...
        });

        // Executed when a test database is created...
        ParallelTesting::setUpTestDatabase(function (string $database, int $token) {
            Artisan::call('db:seed');
        });

        ParallelTesting::tearDownTestCase(function (int $token, TestCase $testCase) {
            // ...
        });

        ParallelTesting::tearDownProcess(function (int $token) {
            // ...
        });
    }
}
```

<a name="accessing-the-parallel-testing-token"></a>
#### Доступ до токена паралельного тестування

Якщо ви хочете дістатися «токена» поточного паралельного процесу з будь-якого іншого місця тестового коду вашого застосунку, скористайтеся методом `token`. Цей токен - унікальний рядковий ідентифікатор окремого тестового процесу, який можна використати, щоб розділити ресурси між паралельними процесами. Наприклад, Laravel автоматично додає цей токен у кінець імен тестових баз, створених кожним паралельним процесом:

    $token = ParallelTesting::token();

<a name="reporting-test-coverage"></a>
### Звіт про покриття тестами

> [!WARNING]
> Ця можливість потребує [Xdebug](https://xdebug.org) або [PCOV](https://pecl.php.net/package/pcov).

Запускаючи тести застосунку, ви можете захотіти дізнатися, чи справді ваші тест-кейси покривають код застосунку і яка його частина задіяна під час прогону. Для цього додайте опцію `--coverage` до команди `test`:

```shell
php artisan test --coverage
```

<a name="enforcing-a-minimum-coverage-threshold"></a>
#### Установлення мінімального порога покриття

Опція `--min` дозволяє задати мінімальний поріг покриття тестами для вашого застосунку. Якщо цього порога не досягнуто, набір тестів завершиться з помилкою:

```shell
php artisan test --coverage --min=80.3
```

<a name="profiling-tests"></a>
### Профілювання тестів

Артизан-раннер тестів має також зручний механізм для показу найповільніших тестів вашого застосунку. Викличте команду `test` з опцією `--profile`, щоб побачити десятку найповільніших тестів і легко з'ясувати, що саме можна поліпшити, аби пришвидшити набір тестів:

```shell
php artisan test --profile
```

<a name="configuration-caching"></a>
## Кешування конфігурації

Під час запуску тестів Laravel завантажує застосунок для кожного окремого тестового методу. Без кешованого файлу конфігурації кожен конфігураційний файл вашого застосунку доводиться завантажувати на початку кожного тесту. Щоб зібрати конфігурацію один раз і перевикористати її для всіх тестів у межах одного прогону, скористайтеся трейтом `Illuminate\Foundation\Testing\WithCachedConfig`:

```php tab=Pest
<?php

use Illuminate\Foundation\Testing\WithCachedConfig;

pest()->use(WithCachedConfig::class);

// ...
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\WithCachedConfig;
use Tests\TestCase;

class ConfigTest extends TestCase
{
    use WithCachedConfig;

    // ...
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/http-tests

# Тестування HTTP

- [Вступ](#introduction)
- [Виконання запитів](#making-requests)
    - [Налаштування заголовків запиту](#customizing-request-headers)
    - [Cookie](#cookies)
    - [Сесія / автентифікація](#session-and-authentication)
    - [Налагодження відповідей](#debugging-responses)
    - [Обробка винятків](#exception-handling)
- [Тестування JSON API](#testing-json-apis)
    - [Плавне тестування JSON](#fluent-json-testing)
- [Тестування завантаження файлів](#testing-file-uploads)
- [Тестування представлень](#testing-views)
    - [Рендеринг Blade і компонентів](#rendering-blade-and-components)
- [Кешування маршрутів](#caching-routes)
- [Доступні твердження](#available-assertions)
    - [Твердження щодо відповіді](#response-assertions)
    - [Твердження щодо автентифікації](#authentication-assertions)
    - [Твердження щодо валідації](#validation-assertions)

<a name="introduction"></a>
## Вступ

Laravel надає дуже плавний API для виконання HTTP-запитів до вашого застосунку та перевірки відповідей. Погляньте, наприклад, на такий функціональний тест:

```php tab=Pest
<?php

test('the application returns a successful response', function () {
    $response = $this->get('/');

    $response->assertStatus(200);
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     */
    public function test_the_application_returns_a_successful_response(): void
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }
}
```

Метод `get` виконує до застосунку запит `GET`, а метод `assertStatus` перевіряє, що повернена відповідь має заданий код статусу HTTP. Окрім цього простого твердження, Laravel містить різні твердження для перевірки заголовків відповіді, її вмісту, структури JSON тощо.

<a name="making-requests"></a>
## Виконання запитів

Щоб виконати запит до вашого застосунку, викличте у своєму тесті методи `get`, `post`, `put`, `patch` чи `delete`. Ці методи не роблять «справжнього» HTTP-запиту до застосунку. Натомість увесь мережевий запит симулюється всередині.

Замість екземпляра `Illuminate\Http\Response` тестові методи запитів повертають екземпляр `Illuminate\Testing\TestResponse`, який надає [різні корисні твердження](#available-assertions) для перевірки відповідей вашого застосунку:

```php tab=Pest
<?php

test('basic request', function () {
    $response = $this->get('/');

    $response->assertStatus(200);
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     */
    public function test_a_basic_request(): void
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }
}
```

Загалом кожен ваш тест має робити лише один запит до застосунку. Якщо в межах одного тестового методу виконується кілька запитів, поведінка може бути несподіваною.

> [!NOTE]
> Для зручності під час прогону тестів `middleware` CSRF автоматично вимикається.

<a name="customizing-request-headers"></a>
### Налаштування заголовків запиту

Метод `withHeaders` дозволяє налаштувати заголовки запиту, перш ніж його буде надіслано до застосунку. Цей метод дозволяє додати до запиту будь-які власні заголовки:

```php tab=Pest
<?php

test('interacting with headers', function () {
    $response = $this->withHeaders([
        'X-Header' => 'Value',
    ])->post('/user', ['name' => 'Sally']);

    $response->assertStatus(201);
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic functional test example.
     */
    public function test_interacting_with_headers(): void
    {
        $response = $this->withHeaders([
            'X-Header' => 'Value',
        ])->post('/user', ['name' => 'Sally']);

        $response->assertStatus(201);
    }
}
```

<a name="cookies"></a>
### Cookie

Методи `withCookie` чи `withCookies` дозволяють задати значення cookie перед виконанням запиту. Метод `withCookie` приймає двома аргументами ім'я та значення cookie, а метод `withCookies` - масив пар ім'я / значення:

```php tab=Pest
<?php

test('interacting with cookies', function () {
    $response = $this->withCookie('color', 'blue')->get('/');

    $response = $this->withCookies([
        'color' => 'blue',
        'name' => 'Taylor',
    ])->get('/');

    //
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_interacting_with_cookies(): void
    {
        $response = $this->withCookie('color', 'blue')->get('/');

        $response = $this->withCookies([
            'color' => 'blue',
            'name' => 'Taylor',
        ])->get('/');

        //
    }
}
```

<a name="session-and-authentication"></a>
### Сесія / автентифікація

Laravel надає кілька хелперів для роботи із сесією під час тестування HTTP. Спершу ви можете задати дані сесії заданим масивом через метод `withSession`. Це стає в пригоді, коли треба наповнити сесію даними перед запитом до вашого застосунку:

```php tab=Pest
<?php

test('interacting with the session', function () {
    $response = $this->withSession(['banned' => false])->get('/');

    //
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_interacting_with_the_session(): void
    {
        $response = $this->withSession(['banned' => false])->get('/');

        //
    }
}
```

Сесію в Laravel зазвичай використовують, щоб зберігати стан поточного автентифікованого користувача. Тому допоміжний метод `actingAs` дає простий спосіб автентифікувати заданого користувача як поточного. Наприклад, ми можемо скористатися [фабрикою моделі](/docs/{{version}}/eloquent-factories), щоб згенерувати й автентифікувати користувача:

```php tab=Pest
<?php

use App\Models\User;

test('an action that requires authentication', function () {
    $user = User::factory()->create();

    $response = $this->actingAs($user)
        ->withSession(['banned' => false])
        ->get('/');

    //
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use App\Models\User;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_an_action_that_requires_authentication(): void
    {
        $user = User::factory()->create();

        $response = $this->actingAs($user)
            ->withSession(['banned' => false])
            ->get('/');

        //
    }
}
```

Ви також можете вказати, який гард слід використати для автентифікації заданого користувача, передавши ім'я гарда другим аргументом до методу `actingAs`. Переданий методу `actingAs` гард стане також гардом за замовчуванням на час тесту:

```php
$this->actingAs($user, 'web');
```

Якщо ви хочете переконатися, що запит неавтентифікований, скористайтеся методом `actingAsGuest`:

```php
$this->actingAsGuest();
```

<a name="debugging-responses"></a>
### Налагодження відповідей

Після виконання тестового запиту до вашого застосунку методи `dump`, `dumpHeaders` та `dumpSession` дозволяють оглянути й налагодити вміст відповіді:

```php tab=Pest
<?php

test('basic test', function () {
    $response = $this->get('/');

    $response->dump();
    $response->dumpHeaders();
    $response->dumpSession();
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     */
    public function test_basic_test(): void
    {
        $response = $this->get('/');

        $response->dump();
        $response->dumpHeaders();
        $response->dumpSession();
    }
}
```

Або ж ви можете скористатися методами `dd`, `ddHeaders`, `ddBody`, `ddJson` та `ddSession`, щоб вивести інформацію про відповідь і зупинити виконання:

```php tab=Pest
<?php

test('basic test', function () {
    $response = $this->get('/');

    $response->dd();
    $response->ddHeaders();
    $response->ddBody();
    $response->ddJson();
    $response->ddSession();
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     */
    public function test_basic_test(): void
    {
        $response = $this->get('/');

        $response->dd();
        $response->ddHeaders();
        $response->ddBody();
        $response->ddJson();
        $response->ddSession();
    }
}
```

<a name="exception-handling"></a>
### Обробка винятків

Іноді вам може знадобитися перевірити, що ваш застосунок кидає певний виняток. Для цього ви можете «підробити» обробник винятків через фасад `Exceptions`. Коли обробник підроблено, ви можете скористатися методами `assertReported` та `assertNotReported`, щоб робити твердження щодо винятків, кинутих під час запиту:

```php tab=Pest
<?php

use App\Exceptions\InvalidOrderException;
use Illuminate\Support\Facades\Exceptions;

test('exception is thrown', function () {
    Exceptions::fake();

    $response = $this->get('/order/1');

    // Assert an exception was thrown...
    Exceptions::assertReported(InvalidOrderException::class);

    // Assert against the exception...
    Exceptions::assertReported(function (InvalidOrderException $e) {
        return $e->getMessage() === 'The order was invalid.';
    });
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use App\Exceptions\InvalidOrderException;
use Illuminate\Support\Facades\Exceptions;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     */
    public function test_exception_is_thrown(): void
    {
        Exceptions::fake();

        $response = $this->get('/');

        // Assert an exception was thrown...
        Exceptions::assertReported(InvalidOrderException::class);

        // Assert against the exception...
        Exceptions::assertReported(function (InvalidOrderException $e) {
            return $e->getMessage() === 'The order was invalid.';
        });
    }
}
```

Методи `assertNotReported` та `assertNothingReported` дозволяють перевірити, що заданий виняток не було кинуто під час запиту або що винятків не було взагалі:

```php
Exceptions::assertNotReported(InvalidOrderException::class);

Exceptions::assertNothingReported();
```

Ви можете повністю вимкнути обробку винятків для конкретного запиту, викликавши метод `withoutExceptionHandling` перед його виконанням:

```php
$response = $this->withoutExceptionHandling()->get('/');
```

Крім того, якщо ви хочете переконатися, що ваш застосунок не використовує можливостей, які оголошено застарілими в мові PHP чи у ваших бібліотеках, викличте перед запитом метод `withoutDeprecationHandling`. Коли обробку застарілих можливостей вимкнено, попередження про них перетворюються на винятки, і тест провалюється:

```php
$response = $this->withoutDeprecationHandling()->get('/');
```

Метод `assertThrows` дозволяє перевірити, що код усередині заданого замикання кидає виняток указаного типу:

```php
$this->assertThrows(
    fn () => (new ProcessOrder)->execute(),
    OrderInvalid::class
);
```

Якщо ви хочете оглянути кинутий виняток і зробити щодо нього твердження, передайте замикання другим аргументом до методу `assertThrows`:

```php
$this->assertThrows(
    fn () => (new ProcessOrder)->execute(),
    fn (OrderInvalid $e) => $e->orderId() === 123
);
```

Метод `assertDoesntThrow` дозволяє перевірити, що код усередині заданого замикання не кидає жодних винятків:

```php
$this->assertDoesntThrow(fn () => (new ProcessOrder)->execute());
```

<a name="testing-json-apis"></a>
## Тестування JSON API

Laravel також надає кілька хелперів для тестування JSON API та їхніх відповідей. Наприклад, методи `json`, `getJson`, `postJson`, `putJson`, `patchJson`, `deleteJson` та `optionsJson` дозволяють виконувати JSON-запити з різними HTTP-дієсловами. Ви також можете легко передавати цим методам дані та заголовки. Для початку напишімо тест, який робить запит `POST` до `/api/user` і перевіряє, що повернулися очікувані JSON-дані:

```php tab=Pest
<?php

test('making an api request', function () {
    $response = $this->postJson('/api/user', ['name' => 'Sally']);

    $response
        ->assertStatus(201)
        ->assertJson([
            'created' => true,
        ]);
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic functional test example.
     */
    public function test_making_an_api_request(): void
    {
        $response = $this->postJson('/api/user', ['name' => 'Sally']);

        $response
            ->assertStatus(201)
            ->assertJson([
                'created' => true,
            ]);
    }
}
```

Крім того, до даних JSON-відповіді можна звертатися як до змінних масиву на самій відповіді - так зручно перевіряти окремі значення, повернені в JSON:

```php tab=Pest
expect($response['created'])->toBeTrue();
```

```php tab=PHPUnit
$this->assertTrue($response['created']);
```

> [!NOTE]
> Метод `assertJson` перетворює відповідь на масив, щоб перевірити, що заданий масив присутній у JSON-відповіді застосунку. Тож якщо в JSON-відповіді є й інші властивості, цей тест усе одно пройде, доки заданий фрагмент є у відповіді.

<a name="verifying-exact-match"></a>
#### Перевірка точного збігу JSON

Як згадувалося раніше, метод `assertJson` дозволяє перевірити, що фрагмент JSON присутній у JSON-відповіді. Якщо ви хочете переконатися, що заданий масив **точно збігається** з JSON, поверненим вашим застосунком, скористайтеся методом `assertExactJson`:

```php tab=Pest
<?php

test('asserting an exact json match', function () {
    $response = $this->postJson('/user', ['name' => 'Sally']);

    $response
        ->assertStatus(201)
        ->assertExactJson([
            'created' => true,
        ]);
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic functional test example.
     */
    public function test_asserting_an_exact_json_match(): void
    {
        $response = $this->postJson('/user', ['name' => 'Sally']);

        $response
            ->assertStatus(201)
            ->assertExactJson([
                'created' => true,
            ]);
    }
}
```

<a name="verifying-json-paths"></a>
#### Твердження щодо шляхів у JSON

Якщо ви хочете переконатися, що JSON-відповідь містить задані дані за вказаним шляхом, скористайтеся методом `assertJsonPath`:

```php tab=Pest
<?php

test('asserting a json path value', function () {
    $response = $this->postJson('/user', ['name' => 'Sally']);

    $response
        ->assertStatus(201)
        ->assertJsonPath('team.owner.name', 'Darian');
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic functional test example.
     */
    public function test_asserting_a_json_paths_value(): void
    {
        $response = $this->postJson('/user', ['name' => 'Sally']);

        $response
            ->assertStatus(201)
            ->assertJsonPath('team.owner.name', 'Darian');
    }
}
```

Метод `assertJsonPath` також приймає замикання, яке дозволяє динамічно вирішити, чи має твердження пройти:

```php
$response->assertJsonPath('team.owner.name', fn (string $name) => strlen($name) >= 3);
```

Якщо вам потрібно перевірити кілька шляхів у JSON одночасно, скористайтеся методом `assertJsonPaths`. Очікуване значення для кожного шляху також може бути замиканням:

```php
$response->assertJsonPaths([
    'team.owner.name' => 'Darian',
    'team.owner.email' => fn (string $email) => str($email)->is('*@laravel.com'),
    'team.members.0.name' => 'Sally',
]);
```

Метод `assertJsonMissingPaths` дозволяє перевірити, що кількох шляхів у JSON у відповіді немає:

```php
$response->assertJsonMissingPaths([
    'team.owner.password',
    'team.members.0.api_token',
]);
```

<a name="fluent-json-testing"></a>
### Плавне тестування JSON

Laravel також пропонує гарний спосіб плавно тестувати JSON-відповіді вашого застосунку. Для початку передайте замикання методу `assertJson`. Це замикання буде викликано з екземпляром `Illuminate\Testing\Fluent\AssertableJson`, через який можна робити твердження щодо JSON, поверненого вашим застосунком. Метод `where` дозволяє робити твердження щодо конкретного атрибута JSON, а метод `missing` - перевіряти, що певного атрибута в JSON немає:

```php tab=Pest
use Illuminate\Testing\Fluent\AssertableJson;

test('fluent json', function () {
    $response = $this->getJson('/users/1');

    $response
        ->assertJson(fn (AssertableJson $json) =>
            $json->where('id', 1)
                ->where('name', 'Victoria Faith')
                ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com'))
                ->whereNot('status', 'pending')
                ->missing('password')
                ->etc()
        );
});
```

```php tab=PHPUnit
use Illuminate\Testing\Fluent\AssertableJson;

/**
 * A basic functional test example.
 */
public function test_fluent_json(): void
{
    $response = $this->getJson('/users/1');

    $response
        ->assertJson(fn (AssertableJson $json) =>
            $json->where('id', 1)
                ->where('name', 'Victoria Faith')
                ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com'))
                ->whereNot('status', 'pending')
                ->missing('password')
                ->etc()
        );
}
```

#### Як працює метод `etc`

У прикладі вище ви могли помітити, що наприкінці ланцюжка тверджень ми викликали метод `etc`. Цей метод повідомляє Laravel, що в об'єкті JSON можуть бути й інші атрибути. Якщо метод `etc` не використати, тест провалиться, коли в об'єкті JSON виявляться інші атрибути, щодо яких ви не робили тверджень.

Задум такої поведінки - захистити вас від ненавмисного розкриття чутливої інформації у ваших JSON-відповідях, змушуючи або явно зробити твердження щодо атрибута, або явно дозволити додаткові атрибути методом `etc`.

Проте майте на увазі: відсутність методу `etc` у вашому ланцюжку тверджень не гарантує, що додаткові атрибути не додаються до масивів, вкладених у ваш об'єкт JSON. Метод `etc` гарантує лише те, що додаткових атрибутів немає на тому рівні вкладеності, на якому його викликано.

<a name="asserting-json-attribute-presence-and-absence"></a>
#### Перевірка наявності / відсутності атрибутів

Щоб перевірити, що атрибут присутній чи відсутній, скористайтеся методами `has` та `missing`:

```php
$response->assertJson(fn (AssertableJson $json) =>
    $json->has('data')
        ->missing('message')
);
```

Крім того, методи `hasAll` та `missingAll` дозволяють перевірити наявність чи відсутність кількох атрибутів одночасно:

```php
$response->assertJson(fn (AssertableJson $json) =>
    $json->hasAll(['status', 'data'])
        ->missingAll(['message', 'code'])
);
```

Метод `hasAny` дозволяє визначити, чи присутній хоча б один атрибут із заданого списку:

```php
$response->assertJson(fn (AssertableJson $json) =>
    $json->has('status')
        ->hasAny('data', 'message', 'code')
);
```

<a name="asserting-against-json-collections"></a>
#### Твердження щодо колекцій JSON

Часто ваш маршрут повертатиме JSON-відповідь із кількома елементами - наприклад, кількома користувачами:

```php
Route::get('/users', function () {
    return User::all();
});
```

У таких випадках ми можемо скористатися методом `has` плавного об'єкта JSON, щоб робити твердження щодо користувачів у відповіді. Наприклад, перевірмо, що JSON-відповідь містить трьох користувачів. Далі зробімо кілька тверджень щодо першого користувача в колекції методом `first`. Метод `first` приймає замикання, яке отримує інший придатний до тверджень JSON-рядок, - з ним ми можемо робити твердження щодо першого об'єкта колекції:

```php
$response
    ->assertJson(fn (AssertableJson $json) =>
        $json->has(3)
            ->first(fn (AssertableJson $json) =>
                $json->where('id', 1)
                    ->where('name', 'Victoria Faith')
                    ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com'))
                    ->missing('password')
                    ->etc()
            )
    );
```

Якщо ви хочете зробити ті самі твердження щодо кожного елемента колекції JSON, скористайтеся методом `each`:

```php
$response
  ->assertJson(fn (AssertableJson $json) =>
      $json->has(3)
          ->each(fn (AssertableJson $json) =>
              $json->whereType('id', 'integer')
                  ->whereType('name', 'string')
                  ->whereType('email', 'string')
                  ->missing('password')
                  ->etc()
          )
  );
```

<a name="scoping-json-collection-assertions"></a>
#### Обмеження тверджень щодо колекцій JSON

Іноді маршрути вашого застосунку повертатимуть колекції JSON з іменованими ключами:

```php
Route::get('/users', function () {
    return [
        'meta' => [...],
        'users' => User::all(),
    ];
});
```

Тестуючи такі маршрути, ви можете скористатися методом `has`, щоб перевірити кількість елементів у колекції. Крім того, метод `has` дозволяє обмежити ланцюжок тверджень:

```php
$response
    ->assertJson(fn (AssertableJson $json) =>
        $json->has('meta')
            ->has('users', 3)
            ->has('users.0', fn (AssertableJson $json) =>
                $json->where('id', 1)
                    ->where('name', 'Victoria Faith')
                    ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com'))
                    ->missing('password')
                    ->etc()
            )
    );
```

Проте замість двох окремих викликів методу `has` для перевірки колекції `users` ви можете зробити один виклик, передавши замикання третім параметром. У такому разі замикання буде викликано автоматично й обмежено першим елементом колекції:

```php
$response
    ->assertJson(fn (AssertableJson $json) =>
        $json->has('meta')
            ->has('users', 3, fn (AssertableJson $json) =>
                $json->where('id', 1)
                    ->where('name', 'Victoria Faith')
                    ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com'))
                    ->missing('password')
                    ->etc()
            )
    );
```

<a name="asserting-json-types"></a>
#### Перевірка типів у JSON

Ви можете захотіти лише перевірити, що властивості JSON-відповіді мають певний тип. Клас `Illuminate\Testing\Fluent\AssertableJson` надає для цього методи `whereType` та `whereAllType`:

```php
$response->assertJson(fn (AssertableJson $json) =>
    $json->whereType('id', 'integer')
        ->whereAllType([
            'users.0.name' => 'string',
            'meta' => 'array'
        ])
);
```

Ви можете вказати кілька типів через символ `|` або передати масив типів другим параметром до методу `whereType`. Твердження буде успішним, якщо значення у відповіді має будь-який із перелічених типів:

```php
$response->assertJson(fn (AssertableJson $json) =>
    $json->whereType('name', 'string|null')
        ->whereType('id', ['string', 'integer'])
);
```

Методи `whereType` та `whereAllType` розпізнають такі типи: `string`, `integer`, `double`, `boolean`, `array` та `null`.

<a name="testing-file-uploads"></a>
## Тестування завантаження файлів

Клас `Illuminate\Http\UploadedFile` має метод `fake`, який дозволяє генерувати фіктивні файли чи зображення для тестування. У поєднанні з методом `fake` фасада `Storage` це суттєво спрощує тестування завантаження файлів. Наприклад, ви можете поєднати ці дві можливості, щоб легко протестувати форму завантаження аватара:

```php tab=Pest
<?php

use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;

test('avatars can be uploaded', function () {
    Storage::fake('avatars');

    $file = UploadedFile::fake()->image('avatar.jpg');

    $response = $this->post('/avatar', [
        'avatar' => $file,
    ]);

    Storage::disk('avatars')->assertExists($file->hashName());
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_avatars_can_be_uploaded(): void
    {
        Storage::fake('avatars');

        $file = UploadedFile::fake()->image('avatar.jpg');

        $response = $this->post('/avatar', [
            'avatar' => $file,
        ]);

        Storage::disk('avatars')->assertExists($file->hashName());
    }
}
```

Якщо ви хочете перевірити, що заданого файлу не існує, скористайтеся методом `assertMissing`, який надає фасад `Storage`:

```php
Storage::fake('avatars');

// ...

Storage::disk('avatars')->assertMissing('missing.jpg');
```

<a name="fake-file-customization"></a>
#### Налаштування фіктивних файлів

Створюючи файли методом `fake` класу `UploadedFile`, ви можете вказати ширину, висоту й розмір зображення (у кілобайтах), щоб краще перевірити правила валідації вашого застосунку:

```php
UploadedFile::fake()->image('avatar.jpg', $width, $height)->size(100);
```

Окрім зображень, ви можете створювати файли будь-якого іншого типу методом `create`:

```php
UploadedFile::fake()->create('document.pdf', $sizeInKilobytes);
```

За потреби ви можете передати методу аргумент `$mimeType`, щоб явно вказати MIME-тип, який має повертати файл:

```php
UploadedFile::fake()->create(
    'document.pdf', $sizeInKilobytes, 'application/pdf'
);
```

<a name="testing-views"></a>
## Тестування представлень

Laravel також дозволяє відрендерити представлення, не виконуючи симульованого HTTP-запиту до застосунку. Для цього викличте у своєму тесті метод `view`. Метод `view` приймає ім'я представлення та необов'язковий масив даних. Він повертає екземпляр `Illuminate\Testing\TestView`, який пропонує кілька методів для зручних тверджень щодо вмісту представлення:

```php tab=Pest
<?php

test('a welcome view can be rendered', function () {
    $view = $this->view('welcome', ['name' => 'Taylor']);

    $view->assertSee('Taylor');
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    public function test_a_welcome_view_can_be_rendered(): void
    {
        $view = $this->view('welcome', ['name' => 'Taylor']);

        $view->assertSee('Taylor');
    }
}
```

Клас `TestView` надає такі методи тверджень: `assertSee`, `assertSeeInOrder`, `assertSeeText`, `assertSeeTextInOrder`, `assertDontSee` та `assertDontSeeText`.

За потреби ви можете отримати сирий відрендерений вміст представлення, привівши екземпляр `TestView` до рядка:

```php
$contents = (string) $this->view('welcome');
```

<a name="sharing-errors"></a>
#### Передавання помилок

Деякі представлення можуть залежати від помилок, переданих у [глобальному контейнері помилок Laravel](/docs/{{version}}/validation#quick-displaying-the-validation-errors). Щоб наповнити контейнер помилок повідомленнями, скористайтеся методом `withViewErrors`:

```php
$view = $this->withViewErrors([
    'name' => ['Please provide a valid name.']
])->view('form');

$view->assertSee('Please provide a valid name.');
```

<a name="rendering-blade-and-components"></a>
### Рендеринг Blade і компонентів

За потреби ви можете скористатися методом `blade`, щоб обчислити й відрендерити сирий рядок [Blade](/docs/{{version}}/blade). Як і метод `view`, метод `blade` повертає екземпляр `Illuminate\Testing\TestView`:

```php
$view = $this->blade(
    '<x-component :name="$name" />',
    ['name' => 'Taylor']
);

$view->assertSee('Taylor');
```

Метод `component` дозволяє обчислити й відрендерити [компонент Blade](/docs/{{version}}/blade#components). Метод `component` повертає екземпляр `Illuminate\Testing\TestComponent`:

```php
$view = $this->component(Profile::class, ['name' => 'Taylor']);

$view->assertSee('Taylor');
```

<a name="caching-routes"></a>
## Кешування маршрутів

Перед прогоном тесту Laravel завантажує свіжий екземпляр застосунку, зокрема збирає всі визначені маршрути. Якщо у вашому застосунку багато файлів маршрутів, вам може знадобитися додати до тест-кейсів трейт `Illuminate\Foundation\Testing\WithCachedRoutes`. У тестах, що використовують цей трейт, маршрути будуються один раз і зберігаються в пам'яті - тобто процес збирання маршрутів виконується лише раз для всього вашого набору тестів:

```php tab=Pest
<?php

use App\Http\Controllers\UserController;
use Illuminate\Foundation\Testing\WithCachedRoutes;

pest()->use(WithCachedRoutes::class);

test('basic example', function () {
    $this->get(action([UserController::class, 'index']));

    // ...
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use App\Http\Controllers\UserController;
use Illuminate\Foundation\Testing\WithCachedRoutes;
use Tests\TestCase;

class BasicTest extends TestCase
{
    use WithCachedRoutes;

    /**
     * A basic functional test example.
     */
    public function test_basic_example(): void
    {
        $response = $this->get(action([UserController::class, 'index']));

        // ...
    }
}
```

<a name="available-assertions"></a>
## Доступні твердження

<a name="response-assertions"></a>
### Твердження щодо відповіді

Клас `Illuminate\Testing\TestResponse` у Laravel надає різні власні методи тверджень, якими ви можете скористатися під час тестування свого застосунку. Ці твердження доступні на відповіді, яку повертають тестові методи `json`, `get`, `post`, `put` та `delete`:

<style>
    .collection-method-list > p {
        columns: 14.4em 2; -moz-columns: 14.4em 2; -webkit-columns: 14.4em 2;
    }

    .collection-method-list a {
        display: block;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
</style>

<div class="collection-method-list" markdown="1">

[assertAccepted](#assert-accepted)
[assertBadRequest](#assert-bad-request)
[assertClientError](#assert-client-error)
[assertConflict](#assert-conflict)
[assertCookie](#assert-cookie)
[assertCookieExpired](#assert-cookie-expired)
[assertCookieNotExpired](#assert-cookie-not-expired)
[assertCookieMissing](#assert-cookie-missing)
[assertCreated](#assert-created)
[assertDontSee](#assert-dont-see)
[assertDontSeeText](#assert-dont-see-text)
[assertDownload](#assert-download)
[assertExactJson](#assert-exact-json)
[assertExactJsonStructure](#assert-exact-json-structure)
[assertFailedDependency](#assert-failed-dependency)
[assertForbidden](#assert-forbidden)
[assertFound](#assert-found)
[assertGone](#assert-gone)
[assertHeader](#assert-header)
[assertHeaderContains](#assert-header-contains)
[assertHeaderMissing](#assert-header-missing)
[assertInternalServerError](#assert-internal-server-error)
[assertJson](#assert-json)
[assertJsonCount](#assert-json-count)
[assertJsonFragment](#assert-json-fragment)
[assertJsonIsArray](#assert-json-is-array)
[assertJsonIsObject](#assert-json-is-object)
[assertJsonMissing](#assert-json-missing)
[assertJsonMissingExact](#assert-json-missing-exact)
[assertJsonMissingValidationErrors](#assert-json-missing-validation-errors)
[assertJsonPath](#assert-json-path)
[assertJsonPaths](#assert-json-paths)
[assertJsonMissingPath](#assert-json-missing-path)
[assertJsonMissingPaths](#assert-json-missing-paths)
[assertJsonStructure](#assert-json-structure)
[assertJsonValidationErrors](#assert-json-validation-errors)
[assertJsonValidationErrorFor](#assert-json-validation-error-for)
[assertLocation](#assert-location)
[assertMethodNotAllowed](#assert-method-not-allowed)
[assertMovedPermanently](#assert-moved-permanently)
[assertContent](#assert-content)
[assertNoContent](#assert-no-content)
[assertStreamed](#assert-streamed)
[assertStreamedContent](#assert-streamed-content)
[assertNotFound](#assert-not-found)
[assertOk](#assert-ok)
[assertPaymentRequired](#assert-payment-required)
[assertPlainCookie](#assert-plain-cookie)
[assertRedirect](#assert-redirect)
[assertRedirectBack](#assert-redirect-back)
[assertRedirectBackWithErrors](#assert-redirect-back-with-errors)
[assertRedirectBackWithoutErrors](#assert-redirect-back-without-errors)
[assertRedirectContains](#assert-redirect-contains)
[assertRedirectToRoute](#assert-redirect-to-route)
[assertRedirectToSignedRoute](#assert-redirect-to-signed-route)
[assertRequestTimeout](#assert-request-timeout)
[assertSee](#assert-see)
[assertSeeInOrder](#assert-see-in-order)
[assertSeeText](#assert-see-text)
[assertSeeTextInOrder](#assert-see-text-in-order)
[assertServerError](#assert-server-error)
[assertServiceUnavailable](#assert-service-unavailable)
[assertSessionHas](#assert-session-has)
[assertSessionHasInput](#assert-session-has-input)
[assertSessionHasAll](#assert-session-has-all)
[assertSessionHasErrors](#assert-session-has-errors)
[assertSessionHasErrorsIn](#assert-session-has-errors-in)
[assertSessionHasNoErrors](#assert-session-has-no-errors)
[assertSessionDoesntHaveErrors](#assert-session-doesnt-have-errors)
[assertSessionMissing](#assert-session-missing)
[assertSessionMissingInput](#assert-session-missing-input)
[assertStatus](#assert-status)
[assertSuccessful](#assert-successful)
[assertTooManyRequests](#assert-too-many-requests)
[assertUnauthorized](#assert-unauthorized)
[assertUnprocessable](#assert-unprocessable)
[assertUnsupportedMediaType](#assert-unsupported-media-type)
[assertValid](#assert-valid)
[assertInvalid](#assert-invalid)
[assertViewHas](#assert-view-has)
[assertViewHasAll](#assert-view-has-all)
[assertViewIs](#assert-view-is)
[assertViewMissing](#assert-view-missing)

</div>

<a name="assert-accepted"></a>
#### assertAccepted

Перевіряє, що відповідь має код статусу HTTP «accepted» (202):

```php
$response->assertAccepted();
```

<a name="assert-bad-request"></a>
#### assertBadRequest

Перевіряє, що відповідь має код статусу HTTP «bad request» (400):

```php
$response->assertBadRequest();
```

<a name="assert-client-error"></a>
#### assertClientError

Перевіряє, що відповідь має код статусу HTTP клієнтської помилки (>= 400, < 500):

```php
$response->assertClientError();
```

<a name="assert-conflict"></a>
#### assertConflict

Перевіряє, що відповідь має код статусу HTTP «conflict» (409):

```php
$response->assertConflict();
```

<a name="assert-cookie"></a>
#### assertCookie

Перевіряє, що відповідь містить заданий cookie:

```php
$response->assertCookie($cookieName, $value = null);
```

<a name="assert-cookie-expired"></a>
#### assertCookieExpired

Перевіряє, що відповідь містить заданий cookie і що його термін дії минув:

```php
$response->assertCookieExpired($cookieName);
```

<a name="assert-cookie-not-expired"></a>
#### assertCookieNotExpired

Перевіряє, що відповідь містить заданий cookie і що його термін дії не минув:

```php
$response->assertCookieNotExpired($cookieName);
```

<a name="assert-cookie-missing"></a>
#### assertCookieMissing

Перевіряє, що відповідь не містить заданого cookie:

```php
$response->assertCookieMissing($cookieName);
```

<a name="assert-created"></a>
#### assertCreated

Перевіряє, що відповідь має код статусу HTTP 201:

```php
$response->assertCreated();
```

<a name="assert-dont-see"></a>
#### assertDontSee

Перевіряє, що заданого рядка немає у відповіді, поверненій застосунком. Це твердження автоматично екранує заданий рядок, якщо ви не передасте другим аргументом `false`:

```php
$response->assertDontSee($value, $escape = true);
```

<a name="assert-dont-see-text"></a>
#### assertDontSeeText

Перевіряє, що заданого рядка немає в тексті відповіді. Це твердження автоматично екранує заданий рядок, якщо ви не передасте другим аргументом `false`. Перед перевіркою метод пропускає вміст відповіді через PHP-функцію `strip_tags`:

```php
$response->assertDontSeeText($value, $escape = true);
```

<a name="assert-download"></a>
#### assertDownload

Перевіряє, що відповідь є «завантаженням». Зазвичай це означає, що викликаний маршрут повернув відповідь `Response::download`, `BinaryFileResponse` чи `Storage::download`:

```php
$response->assertDownload();
```

За бажання ви можете перевірити, що файлу для завантаження було призначено задане ім'я:

```php
$response->assertDownload('image.jpg');
```

<a name="assert-exact-json"></a>
#### assertExactJson

Перевіряє, що відповідь точно збігається із заданими даними JSON:

```php
$response->assertExactJson(array $data);
```

<a name="assert-exact-json-structure"></a>
#### assertExactJsonStructure

Перевіряє, що відповідь точно збігається із заданою структурою JSON:

```php
$response->assertExactJsonStructure(array $data);
```

Цей метод - суворіший варіант [assertJsonStructure](#assert-json-structure). На відміну від `assertJsonStructure`, він провалиться, якщо відповідь містить будь-які ключі, не включені явно до очікуваної структури JSON.

<a name="assert-failed-dependency"></a>
#### assertFailedDependency

Перевіряє, що відповідь має код статусу HTTP «failed dependency» (424):

```php
$response->assertFailedDependency();
```

<a name="assert-forbidden"></a>
#### assertForbidden

Перевіряє, що відповідь має код статусу HTTP «forbidden» (403):

```php
$response->assertForbidden();
```

<a name="assert-found"></a>
#### assertFound

Перевіряє, що відповідь має код статусу HTTP «found» (302):

```php
$response->assertFound();
```

<a name="assert-gone"></a>
#### assertGone

Перевіряє, що відповідь має код статусу HTTP «gone» (410):

```php
$response->assertGone();
```

<a name="assert-header"></a>
#### assertHeader

Перевіряє, що у відповіді присутній заданий заголовок із заданим значенням:

```php
$response->assertHeader($headerName, $value = null);
```

<a name="assert-header-contains"></a>
#### assertHeaderContains

Перевіряє, що заданий заголовок містить задане значення-підрядок:

```php
$response->assertHeaderContains($headerName, $value);
```

<a name="assert-header-missing"></a>
#### assertHeaderMissing

Перевіряє, що заданого заголовка у відповіді немає:

```php
$response->assertHeaderMissing($headerName);
```

<a name="assert-internal-server-error"></a>
#### assertInternalServerError

Перевіряє, що відповідь має код статусу HTTP «Internal Server Error» (500):

```php
$response->assertInternalServerError();
```

<a name="assert-json"></a>
#### assertJson

Перевіряє, що відповідь містить задані дані JSON:

```php
$response->assertJson(array $data, $strict = false);
```

Метод `assertJson` перетворює відповідь на масив, щоб перевірити, що заданий масив присутній у JSON-відповіді застосунку. Тож якщо в JSON-відповіді є й інші властивості, цей тест усе одно пройде, доки заданий фрагмент є у відповіді.

<a name="assert-json-count"></a>
#### assertJsonCount

Перевіряє, що JSON відповіді містить масив з очікуваною кількістю елементів за заданим ключем:

```php
$response->assertJsonCount($count, $key = null);
```

<a name="assert-json-fragment"></a>
#### assertJsonFragment

Перевіряє, що відповідь містить задані дані JSON будь-де у відповіді:

```php
Route::get('/users', function () {
    return [
        'users' => [
            [
                'name' => 'Taylor Otwell',
            ],
        ],
    ];
});

$response->assertJsonFragment(['name' => 'Taylor Otwell']);
```

<a name="assert-json-is-array"></a>
#### assertJsonIsArray

Перевіряє, що JSON відповіді є масивом:

```php
$response->assertJsonIsArray();
```

<a name="assert-json-is-object"></a>
#### assertJsonIsObject

Перевіряє, що JSON відповіді є об'єктом:

```php
$response->assertJsonIsObject();
```

<a name="assert-json-missing"></a>
#### assertJsonMissing

Перевіряє, що відповідь не містить заданих даних JSON:

```php
$response->assertJsonMissing(array $data);
```

<a name="assert-json-missing-exact"></a>
#### assertJsonMissingExact

Перевіряє, що відповідь не містить точно заданих даних JSON:

```php
$response->assertJsonMissingExact(array $data);
```

<a name="assert-json-missing-validation-errors"></a>
#### assertJsonMissingValidationErrors

Перевіряє, що у відповіді немає помилок валідації JSON для заданих ключів:

```php
$response->assertJsonMissingValidationErrors($keys);
```

> [!NOTE]
> Загальніший метод [assertValid](#assert-valid) дозволяє перевірити, що у відповіді немає помилок валідації, повернених як JSON, **і** що жодних помилок не було передано до сховища сесії.

<a name="assert-json-path"></a>
#### assertJsonPath

Перевіряє, що відповідь містить задані дані за вказаним шляхом:

```php
$response->assertJsonPath($path, $expectedValue);
```

Наприклад, якщо ваш застосунок повертає таку JSON-відповідь:

```json
{
    "user": {
        "name": "Steve Schoger"
    }
}
```

Ви можете перевірити, що властивість `name` об'єкта `user` дорівнює заданому значенню, ось так:

```php
$response->assertJsonPath('user.name', 'Steve Schoger');
```

<a name="assert-json-paths"></a>
#### assertJsonPaths

Перевіряє, що відповідь містить задані дані за вказаними шляхами:

```php
$response->assertJsonPaths(array $paths);
```

Наприклад, ви можете перевірити кілька значень у відповіді одночасно:

```php
$response->assertJsonPaths([
    'user.name' => 'Steve Schoger',
    'user.email' => fn (string $email) => str($email)->endsWith('@laravel.com'),
]);
```

<a name="assert-json-missing-path"></a>
#### assertJsonMissingPath

Перевіряє, що відповідь не містить заданого шляху:

```php
$response->assertJsonMissingPath($path);
```

Наприклад, якщо ваш застосунок повертає таку JSON-відповідь:

```json
{
    "user": {
        "name": "Steve Schoger"
    }
}
```

Ви можете перевірити, що вона не містить властивості `email` об'єкта `user`:

```php
$response->assertJsonMissingPath('user.email');
```

<a name="assert-json-missing-paths"></a>
#### assertJsonMissingPaths

Перевіряє, що відповідь не містить заданих шляхів:

```php
$response->assertJsonMissingPaths($paths);
```

Наприклад, ви можете перевірити, що у відповіді немає кількох шляхів:

```php
$response->assertJsonMissingPaths([
    'user.email',
    'user.password',
]);
```

<a name="assert-json-structure"></a>
#### assertJsonStructure

Перевіряє, що відповідь має задану структуру JSON:

```php
$response->assertJsonStructure(array $structure);
```

Наприклад, якщо JSON-відповідь вашого застосунку містить такі дані:

```json
{
    "user": {
        "name": "Steve Schoger"
    }
}
```

Ви можете перевірити, що структура JSON відповідає вашим очікуванням, ось так:

```php
$response->assertJsonStructure([
    'user' => [
        'name',
    ]
]);
```

Іноді JSON-відповіді вашого застосунку можуть містити масиви об'єктів:

```json
{
    "user": [
        {
            "name": "Steve Schoger",
            "age": 55,
            "location": "Earth"
        },
        {
            "name": "Mary Schoger",
            "age": 60,
            "location": "Earth"
        }
    ]
}
```

У цій ситуації ви можете скористатися символом `*`, щоб перевірити структуру всіх об'єктів масиву:

```php
$response->assertJsonStructure([
    'user' => [
        '*' => [
             'name',
             'age',
             'location'
        ]
    ]
]);
```

<a name="assert-json-validation-errors"></a>
#### assertJsonValidationErrors

Перевіряє, що відповідь має задані помилки валідації JSON для заданих ключів. Цей метод варто використовувати, коли ви перевіряєте відповіді, у яких помилки валідації повертаються структурою JSON, а не передаються до сесії:

```php
$response->assertJsonValidationErrors(array $data, $responseKey = 'errors');
```

> [!NOTE]
> Загальніший метод [assertInvalid](#assert-invalid) дозволяє перевірити, що у відповіді є помилки валідації, повернені як JSON, **або** що помилки було передано до сховища сесії.

<a name="assert-json-validation-error-for"></a>
#### assertJsonValidationErrorFor

Перевіряє, що відповідь має будь-які помилки валідації JSON для заданого ключа:

```php
$response->assertJsonValidationErrorFor(string $key, $responseKey = 'errors');
```

<a name="assert-method-not-allowed"></a>
#### assertMethodNotAllowed

Перевіряє, що відповідь має код статусу HTTP «method not allowed» (405):

```php
$response->assertMethodNotAllowed();
```

<a name="assert-moved-permanently"></a>
#### assertMovedPermanently

Перевіряє, що відповідь має код статусу HTTP «moved permanently» (301):

```php
$response->assertMovedPermanently();
```

<a name="assert-location"></a>
#### assertLocation

Перевіряє, що відповідь має заданий URI у заголовку `Location`:

```php
$response->assertLocation($uri);
```

<a name="assert-content"></a>
#### assertContent

Перевіряє, що заданий рядок збігається з вмістом відповіді:

```php
$response->assertContent($value);
```

<a name="assert-no-content"></a>
#### assertNoContent

Перевіряє, що відповідь має заданий код статусу HTTP і не має вмісту:

```php
$response->assertNoContent($status = 204);
```

<a name="assert-streamed"></a>
#### assertStreamed

Перевіряє, що відповідь була потоковою:

    $response->assertStreamed();

<a name="assert-streamed-content"></a>
#### assertStreamedContent

Перевіряє, що заданий рядок збігається з вмістом потокової відповіді:

```php
$response->assertStreamedContent($value);
```

<a name="assert-not-found"></a>
#### assertNotFound

Перевіряє, що відповідь має код статусу HTTP «not found» (404):

```php
$response->assertNotFound();
```

<a name="assert-ok"></a>
#### assertOk

Перевіряє, що відповідь має код статусу HTTP 200:

```php
$response->assertOk();
```

<a name="assert-payment-required"></a>
#### assertPaymentRequired

Перевіряє, що відповідь має код статусу HTTP «payment required» (402):

```php
$response->assertPaymentRequired();
```

<a name="assert-plain-cookie"></a>
#### assertPlainCookie

Перевіряє, що відповідь містить заданий незашифрований cookie:

```php
$response->assertPlainCookie($cookieName, $value = null);
```

<a name="assert-redirect"></a>
#### assertRedirect

Перевіряє, що відповідь є перенаправленням на заданий URI:

```php
$response->assertRedirect($uri = null);
```

<a name="assert-redirect-back"></a>
#### assertRedirectBack

Перевіряє, чи є відповідь перенаправленням назад на попередню сторінку:

```php
$response->assertRedirectBack();
```

<a name="assert-redirect-back-with-errors"></a>
#### assertRedirectBackWithErrors

Перевіряє, чи є відповідь перенаправленням назад на попередню сторінку і чи [сесія містить задані помилки](#assert-session-has-errors):

```php
$response->assertRedirectBackWithErrors(
    array $keys = [], $format = null, $errorBag = 'default'
);
```

<a name="assert-redirect-back-without-errors"></a>
#### assertRedirectBackWithoutErrors

Перевіряє, чи є відповідь перенаправленням назад на попередню сторінку і чи сесія не містить жодних повідомлень про помилки:

```php
$response->assertRedirectBackWithoutErrors();
```

<a name="assert-redirect-contains"></a>
#### assertRedirectContains

Перевіряє, чи є відповідь перенаправленням на URI, що містить заданий рядок:

```php
$response->assertRedirectContains($string);
```

<a name="assert-redirect-to-route"></a>
#### assertRedirectToRoute

Перевіряє, що відповідь є перенаправленням на заданий [іменований маршрут](/docs/{{version}}/routing#named-routes):

```php
$response->assertRedirectToRoute($name, $parameters = []);
```

<a name="assert-redirect-to-signed-route"></a>
#### assertRedirectToSignedRoute

Перевіряє, що відповідь є перенаправленням на заданий [підписаний маршрут](/docs/{{version}}/urls#signed-urls):

```php
$response->assertRedirectToSignedRoute($name = null, $parameters = []);
```

<a name="assert-request-timeout"></a>
#### assertRequestTimeout

Перевіряє, що відповідь має код статусу HTTP «request timeout» (408):

```php
$response->assertRequestTimeout();
```

<a name="assert-see"></a>
#### assertSee

Перевіряє, що заданий рядок є у відповіді. Це твердження автоматично екранує заданий рядок, якщо ви не передасте другим аргументом `false`:

```php
$response->assertSee($value, $escape = true);
```

<a name="assert-see-in-order"></a>
#### assertSeeInOrder

Перевіряє, що задані рядки є у відповіді саме в такому порядку. Це твердження автоматично екранує задані рядки, якщо ви не передасте другим аргументом `false`:

```php
$response->assertSeeInOrder(array $values, $escape = true);
```

<a name="assert-see-text"></a>
#### assertSeeText

Перевіряє, що заданий рядок є в тексті відповіді. Це твердження автоматично екранує заданий рядок, якщо ви не передасте другим аргументом `false`. Перед перевіркою вміст відповіді буде пропущено через PHP-функцію `strip_tags`:

```php
$response->assertSeeText($value, $escape = true);
```

<a name="assert-see-text-in-order"></a>
#### assertSeeTextInOrder

Перевіряє, що задані рядки є в тексті відповіді саме в такому порядку. Це твердження автоматично екранує задані рядки, якщо ви не передасте другим аргументом `false`. Перед перевіркою вміст відповіді буде пропущено через PHP-функцію `strip_tags`:

```php
$response->assertSeeTextInOrder(array $values, $escape = true);
```

<a name="assert-server-error"></a>
#### assertServerError

Перевіряє, що відповідь має код статусу HTTP серверної помилки (>= 500 , < 600):

```php
$response->assertServerError();
```

<a name="assert-service-unavailable"></a>
#### assertServiceUnavailable

Перевіряє, що відповідь має код статусу HTTP «Service Unavailable» (503):

```php
$response->assertServiceUnavailable();
```

<a name="assert-session-has"></a>
#### assertSessionHas

Перевіряє, що сесія містить заданий фрагмент даних:

```php
$response->assertSessionHas($key, $value = null);
```

За потреби другим аргументом до методу `assertSessionHas` можна передати замикання. Твердження пройде, якщо замикання поверне `true`:

```php
$response->assertSessionHas($key, function (User $value) {
    return $value->name === 'Taylor Otwell';
});
```

<a name="assert-session-has-input"></a>
#### assertSessionHasInput

Перевіряє, що сесія має задане значення в [масиві переданих даних форми](/docs/{{version}}/responses#redirecting-with-flashed-session-data):

```php
$response->assertSessionHasInput($key, $value = null);
```

За потреби другим аргументом до методу `assertSessionHasInput` можна передати замикання. Твердження пройде, якщо замикання поверне `true`:

```php
use Illuminate\Support\Facades\Crypt;

$response->assertSessionHasInput($key, function (string $value) {
    return Crypt::decryptString($value) === 'secret';
});
```

<a name="assert-session-has-all"></a>
#### assertSessionHasAll

Перевіряє, що сесія містить заданий масив пар ключ / значення:

```php
$response->assertSessionHasAll(array $data);
```

Наприклад, якщо сесія вашого застосунку містить ключі `name` та `status`, ви можете перевірити, що обидва існують і мають указані значення, ось так:

```php
$response->assertSessionHasAll([
    'name' => 'Taylor Otwell',
    'status' => 'active',
]);
```

<a name="assert-session-has-errors"></a>
#### assertSessionHasErrors

Перевіряє, що сесія містить помилку для заданих `$keys`. Якщо `$keys` - асоціативний масив, перевіряє, що сесія містить конкретне повідомлення про помилку (значення) для кожного поля (ключа). Цей метод варто використовувати, тестуючи маршрути, які передають помилки валідації до сесії, а не повертають їх структурою JSON:

```php
$response->assertSessionHasErrors(
    array $keys = [], $format = null, $errorBag = 'default'
);
```

Наприклад, щоб перевірити, що поля `name` та `email` мають передані до сесії повідомлення про помилки валідації, викличте метод `assertSessionHasErrors` ось так:

```php
$response->assertSessionHasErrors(['name', 'email']);
```

Або ж ви можете перевірити, що задане поле має конкретне повідомлення про помилку валідації:

```php
$response->assertSessionHasErrors([
    'name' => 'The given name was invalid.'
]);
```

> [!NOTE]
> Загальніший метод [assertInvalid](#assert-invalid) дозволяє перевірити, що у відповіді є помилки валідації, повернені як JSON, **або** що помилки було передано до сховища сесії.

<a name="assert-session-has-errors-in"></a>
#### assertSessionHasErrorsIn

Перевіряє, що сесія містить помилку для заданих `$keys` у конкретному [контейнері помилок](/docs/{{version}}/validation#named-error-bags). Якщо `$keys` - асоціативний масив, перевіряє, що сесія містить конкретне повідомлення про помилку (значення) для кожного поля (ключа) в межах цього контейнера:

```php
$response->assertSessionHasErrorsIn($errorBag, $keys = [], $format = null);
```

<a name="assert-session-has-no-errors"></a>
#### assertSessionHasNoErrors

Перевіряє, що сесія не має помилок валідації:

```php
$response->assertSessionHasNoErrors();
```

<a name="assert-session-doesnt-have-errors"></a>
#### assertSessionDoesntHaveErrors

Перевіряє, що сесія не має помилок валідації для заданих ключів:

```php
$response->assertSessionDoesntHaveErrors($keys = [], $format = null, $errorBag = 'default');
```

> [!NOTE]
> Загальніший метод [assertValid](#assert-valid) дозволяє перевірити, що у відповіді немає помилок валідації, повернених як JSON, **і** що жодних помилок не було передано до сховища сесії.

<a name="assert-session-missing"></a>
#### assertSessionMissing

Перевіряє, що сесія не містить заданого ключа:

```php
$response->assertSessionMissing($key);
```

<a name="assert-session-missing-input"></a>
#### assertSessionMissingInput

Перевіряє, що в сесії немає заданого ключа в масиві переданих даних форми:

```php
$response->assertSessionMissingInput($key);
```

<a name="assert-status"></a>
#### assertStatus

Перевіряє, що відповідь має заданий код статусу HTTP:

```php
$response->assertStatus($code);
```

<a name="assert-successful"></a>
#### assertSuccessful

Перевіряє, що відповідь має успішний код статусу HTTP (>= 200 і < 300):

```php
$response->assertSuccessful();
```

<a name="assert-too-many-requests"></a>
#### assertTooManyRequests

Перевіряє, що відповідь має код статусу HTTP «too many requests» (429):

```php
$response->assertTooManyRequests();
```

<a name="assert-unauthorized"></a>
#### assertUnauthorized

Перевіряє, що відповідь має код статусу HTTP «unauthorized» (401):

```php
$response->assertUnauthorized();
```

<a name="assert-unprocessable"></a>
#### assertUnprocessable

Перевіряє, що відповідь має код статусу HTTP «unprocessable entity» (422):

```php
$response->assertUnprocessable();
```

<a name="assert-unsupported-media-type"></a>
#### assertUnsupportedMediaType

Перевіряє, що відповідь має код статусу HTTP «unsupported media type» (415):

```php
$response->assertUnsupportedMediaType();
```

<a name="assert-valid"></a>
#### assertValid

Перевіряє, що у відповіді немає помилок валідації для заданих ключів. Цей метод можна використовувати для перевірки відповідей, у яких помилки валідації повертаються структурою JSON або передаються до сесії:

```php
// Assert that no validation errors are present...
$response->assertValid();

// Assert that the given keys do not have validation errors...
$response->assertValid(['name', 'email']);
```

<a name="assert-invalid"></a>
#### assertInvalid

Перевіряє, що у відповіді є помилки валідації для заданих ключів. Цей метод можна використовувати для перевірки відповідей, у яких помилки валідації повертаються структурою JSON або передаються до сесії:

```php
$response->assertInvalid(['name', 'email']);
```

Ви також можете перевірити, що заданий ключ має конкретне повідомлення про помилку валідації. При цьому ви можете передати повне повідомлення або лише невелику його частину:

```php
$response->assertInvalid([
    'name' => 'The name field is required.',
    'email' => 'valid email address',
]);
```

Якщо ви хочете перевірити, що задані поля - єдині з помилками валідації, скористайтеся методом `assertOnlyInvalid`:

```php
$response->assertOnlyInvalid(['name', 'email']);
```

<a name="assert-view-has"></a>
#### assertViewHas

Перевіряє, що представлення відповіді містить заданий фрагмент даних:

```php
$response->assertViewHas($key, $value = null);
```

Передавши замикання другим аргументом до методу `assertViewHas`, ви зможете оглянути конкретний фрагмент даних представлення й зробити щодо нього твердження:

```php
$response->assertViewHas('user', function (User $user) {
    return $user->name === 'Taylor';
});
```

Крім того, до даних представлення можна звертатися як до змінних масиву на відповіді - так їх зручно оглядати:

```php tab=Pest
expect($response['name'])->toBe('Taylor');
```

```php tab=PHPUnit
$this->assertEquals('Taylor', $response['name']);
```

<a name="assert-view-has-all"></a>
#### assertViewHasAll

Перевіряє, що представлення відповіді має заданий перелік даних:

```php
$response->assertViewHasAll(array $data);
```

Цей метод дозволяє перевірити, що представлення просто містить дані за заданими ключами:

```php
$response->assertViewHasAll([
    'name',
    'email',
]);
```

Або ж ви можете перевірити, що дані представлення присутні й мають конкретні значення:

```php
$response->assertViewHasAll([
    'name' => 'Taylor Otwell',
    'email' => 'taylor@example.com,',
]);
```

<a name="assert-view-is"></a>
#### assertViewIs

Перевіряє, що маршрут повернув задане представлення:

```php
$response->assertViewIs($value);
```

<a name="assert-view-missing"></a>
#### assertViewMissing

Перевіряє, що заданий ключ даних не було передано до представлення, поверненого у відповіді застосунку:

```php
$response->assertViewMissing($key);
```

<a name="authentication-assertions"></a>
### Твердження щодо автентифікації

Laravel також надає різні твердження, пов'язані з автентифікацією, якими ви можете скористатися у функціональних тестах свого застосунку. Зверніть увагу: ці методи викликаються на самому тестовому класі, а не на екземплярі `Illuminate\Testing\TestResponse`, який повертають методи на кшталт `get` і `post`.

<a name="assert-authenticated"></a>
#### assertAuthenticated

Перевіряє, що користувач автентифікований:

```php
$this->assertAuthenticated($guard = null);
```

<a name="assert-guest"></a>
#### assertGuest

Перевіряє, що користувач не автентифікований:

```php
$this->assertGuest($guard = null);
```

<a name="assert-authenticated-as"></a>
#### assertAuthenticatedAs

Перевіряє, що автентифікований конкретний користувач:

```php
$this->assertAuthenticatedAs($user, $guard = null);
```

<a name="validation-assertions"></a>
## Твердження щодо валідації

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

<a name="validation-assert-valid"></a>
#### assertValid

Перевіряє, що у відповіді немає помилок валідації для заданих ключів. Цей метод можна використовувати для перевірки відповідей, у яких помилки валідації повертаються структурою JSON або передаються до сесії:

```php
// Assert that no validation errors are present...
$response->assertValid();

// Assert that the given keys do not have validation errors...
$response->assertValid(['name', 'email']);
```

<a name="validation-assert-invalid"></a>
#### assertInvalid

Перевіряє, що у відповіді є помилки валідації для заданих ключів. Цей метод можна використовувати для перевірки відповідей, у яких помилки валідації повертаються структурою JSON або передаються до сесії:

```php
$response->assertInvalid(['name', 'email']);
```

Ви також можете перевірити, що заданий ключ має конкретне повідомлення про помилку валідації. При цьому ви можете передати повне повідомлення або лише невелику його частину:

```php
$response->assertInvalid([
    'name' => 'The name field is required.',
    'email' => 'valid email address',
]);
```

---

Джерело: https://laravelukraine.com/docs/13.x/console-tests

# Тестування консольних команд

- [Вступ](#introduction)
- [Очікування успіху / невдачі](#success-failure-expectations)
- [Очікування щодо вводу / виводу](#input-output-expectations)
- [Консольні події](#console-events)

<a name="introduction"></a>
## Вступ

Окрім спрощення тестування HTTP, Laravel надає простий API для тестування [власних консольних команд](/docs/{{version}}/artisan) вашого застосунку.

<a name="success-failure-expectations"></a>
## Очікування успіху / невдачі

Для початку розгляньмо, як робити твердження щодо коду виходу артизан-команди. Для цього ми скористаємося методом `artisan`, щоб викликати артизан-команду з нашого тесту. Далі методом `assertExitCode` перевіримо, що команда завершилася із заданим кодом виходу:

```php tab=Pest
test('console command', function () {
    $this->artisan('inspire')->assertExitCode(0);
});
```

```php tab=PHPUnit
/**
 * Test a console command.
 */
public function test_console_command(): void
{
    $this->artisan('inspire')->assertExitCode(0);
}
```

Метод `assertNotExitCode` дозволяє перевірити, що команда завершилася не із заданим кодом виходу:

```php
$this->artisan('inspire')->assertNotExitCode(1);
```

Звісно, всі термінальні команди зазвичай завершуються з кодом статусу `0`, коли вони успішні, і з ненульовим кодом, коли ні. Тому для зручності ви можете скористатися твердженнями `assertSuccessful` та `assertFailed`, щоб перевірити, чи завершилася команда з успішним кодом виходу:

```php
$this->artisan('inspire')->assertSuccessful();

$this->artisan('inspire')->assertFailed();
```

<a name="input-output-expectations"></a>
## Очікування щодо вводу / виводу

Laravel дозволяє легко «підробити» ввід користувача для ваших консольних команд методом `expectsQuestion`. Крім того, ви можете вказати код виходу й текст, який очікуєте побачити у виводі команди, методами `assertExitCode` та `expectsOutput`. Розгляньмо, наприклад, таку консольну команду:

```php
Artisan::command('question', function () {
    $name = $this->ask('What is your name?');

    $language = $this->choice('Which language do you prefer?', [
        'PHP',
        'Ruby',
        'Python',
    ]);

    $this->line('Your name is '.$name.' and you prefer '.$language.'.');
});
```

Протестувати цю команду можна таким тестом:

```php tab=Pest
test('console command', function () {
    $this->artisan('question')
        ->expectsQuestion('What is your name?', 'Taylor Otwell')
        ->expectsQuestion('Which language do you prefer?', 'PHP')
        ->expectsOutput('Your name is Taylor Otwell and you prefer PHP.')
        ->doesntExpectOutput('Your name is Taylor Otwell and you prefer Ruby.')
        ->assertExitCode(0);
});
```

```php tab=PHPUnit
/**
 * Test a console command.
 */
public function test_console_command(): void
{
    $this->artisan('question')
        ->expectsQuestion('What is your name?', 'Taylor Otwell')
        ->expectsQuestion('Which language do you prefer?', 'PHP')
        ->expectsOutput('Your name is Taylor Otwell and you prefer PHP.')
        ->doesntExpectOutput('Your name is Taylor Otwell and you prefer Ruby.')
        ->assertExitCode(0);
}
```

Якщо ви користуєтеся функціями `search` чи `multisearch` із [Laravel Prompts](/docs/{{version}}/prompts), скористайтеся твердженням `expectsSearch`, щоб підробити ввід користувача, результати пошуку та вибір:

```php tab=Pest
test('console command', function () {
    $this->artisan('example')
        ->expectsSearch('What is your name?', search: 'Tay', answers: [
            'Taylor Otwell',
            'Taylor Swift',
            'Darian Taylor'
        ], answer: 'Taylor Otwell')
        ->assertExitCode(0);
});
```

```php tab=PHPUnit
/**
 * Test a console command.
 */
public function test_console_command(): void
{
    $this->artisan('example')
        ->expectsSearch('What is your name?', search: 'Tay', answers: [
            'Taylor Otwell',
            'Taylor Swift',
            'Darian Taylor'
        ], answer: 'Taylor Otwell')
        ->assertExitCode(0);
}
```

Ви також можете перевірити, що консольна команда не дає жодного виводу, методом `doesntExpectOutput`:

```php tab=Pest
test('console command', function () {
    $this->artisan('example')
        ->doesntExpectOutput()
        ->assertExitCode(0);
});
```

```php tab=PHPUnit
/**
 * Test a console command.
 */
public function test_console_command(): void
{
    $this->artisan('example')
        ->doesntExpectOutput()
        ->assertExitCode(0);
}
```

Методи `expectsOutputToContain` та `doesntExpectOutputToContain` дозволяють робити твердження щодо частини виводу:

```php tab=Pest
test('console command', function () {
    $this->artisan('example')
        ->expectsOutputToContain('Taylor')
        ->assertExitCode(0);
});
```

```php tab=PHPUnit
/**
 * Test a console command.
 */
public function test_console_command(): void
{
    $this->artisan('example')
        ->expectsOutputToContain('Taylor')
        ->assertExitCode(0);
}
```

<a name="confirmation-expectations"></a>
#### Очікування підтвердження

Пишучи команду, яка очікує підтвердження у вигляді відповіді «так» чи «ні», скористайтеся методом `expectsConfirmation`:

```php
$this->artisan('module:import')
    ->expectsConfirmation('Do you really wish to run this command?', 'no')
    ->assertExitCode(1);
```

<a name="table-expectations"></a>
#### Очікування щодо таблиць

Якщо ваша команда показує таблицю з інформацією через артизан-метод `table`, писати очікування щодо виводу всієї таблиці буває незручно. Натомість скористайтеся методом `expectsTable`. Він приймає заголовки таблиці першим аргументом і дані таблиці другим:

```php
$this->artisan('users:all')
    ->expectsTable([
        'ID',
        'Email',
    ], [
        [1, 'taylor@example.com'],
        [2, 'abigail@example.com'],
    ]);
```

<a name="console-events"></a>
## Консольні події

За замовчуванням події `Illuminate\Console\Events\CommandStarting` та `Illuminate\Console\Events\CommandFinished` не відправляються під час прогону тестів вашого застосунку. Проте ви можете ввімкнути ці події для певного тестового класу, додавши до нього трейт `Illuminate\Foundation\Testing\WithConsoleEvents`:

```php tab=Pest
<?php

use Illuminate\Foundation\Testing\WithConsoleEvents;

pest()->use(WithConsoleEvents::class);

// ...
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\WithConsoleEvents;
use Tests\TestCase;

class ConsoleEventTest extends TestCase
{
    use WithConsoleEvents;

    // ...
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/dusk

# Laravel Dusk

- [Вступ](#introduction)
- [Встановлення](#installation)
    - [Керування встановленням ChromeDriver](#managing-chromedriver-installations)
    - [Використання інших браузерів](#using-other-browsers)
- [Перші кроки](#getting-started)
    - [Генерування тестів](#generating-tests)
    - [Скидання бази даних після кожного тесту](#resetting-the-database-after-each-test)
    - [Запуск тестів](#running-tests)
    - [Робота із середовищем](#environment-handling)
- [Основи роботи з браузером](#browser-basics)
    - [Створення браузерів](#creating-browsers)
    - [Навігація](#navigation)
    - [Зміна розміру вікна браузера](#resizing-browser-windows)
    - [Макроси браузера](#browser-macros)
    - [Автентифікація](#authentication)
    - [Cookie](#cookies)
    - [Виконання JavaScript](#executing-javascript)
    - [Знімок екрана](#taking-a-screenshot)
    - [Збереження виводу консолі на диск](#storing-console-output-to-disk)
    - [Збереження коду сторінки на диск](#storing-page-source-to-disk)
- [Взаємодія з елементами](#interacting-with-elements)
    - [Селектори Dusk](#dusk-selectors)
    - [Текст, значення та атрибути](#text-values-and-attributes)
    - [Робота з формами](#interacting-with-forms)
    - [Прикріплення файлів](#attaching-files)
    - [Натискання кнопок](#pressing-buttons)
    - [Клацання по посиланнях](#clicking-links)
    - [Використання клавіатури](#using-the-keyboard)
    - [Використання миші](#using-the-mouse)
    - [Діалоги JavaScript](#javascript-dialogs)
    - [Робота з вбудованими фреймами](#interacting-with-iframes)
    - [Обмеження селекторів](#scoping-selectors)
    - [Очікування елементів](#waiting-for-elements)
    - [Прокручування до елемента](#scrolling-an-element-into-view)
- [Доступні твердження](#available-assertions)
- [Сторінки](#pages)
    - [Генерування сторінок](#generating-pages)
    - [Налаштування сторінок](#configuring-pages)
    - [Перехід до сторінок](#navigating-to-pages)
    - [Скорочені селектори](#shorthand-selectors)
    - [Методи сторінок](#page-methods)
- [Компоненти](#components)
    - [Генерування компонентів](#generating-components)
    - [Використання компонентів](#using-components)
- [Неперервна інтеграція](#continuous-integration)
    - [Heroku CI](#running-tests-on-heroku-ci)
    - [Travis CI](#running-tests-on-travis-ci)
    - [GitHub Actions](#running-tests-on-github-actions)
    - [Chipper CI](#running-tests-on-chipper-ci)

<a name="introduction"></a>
## Вступ

> [!WARNING]
> [Pest 4](https://pestphp.com/) тепер містить автоматизоване тестування у браузері, яке суттєво виграє в продуктивності та зручності порівняно з Laravel Dusk. Для нових проєктів ми рекомендуємо тестувати у браузері саме через Pest.

[Laravel Dusk](https://github.com/laravel/dusk) надає виразний і простий у користуванні API для автоматизації браузера й тестування. За замовчуванням Dusk не вимагає встановлювати JDK чи Selenium на ваш комп'ютер. Натомість Dusk використовує окремо встановлений [ChromeDriver](https://sites.google.com/chromium.org/driver). Проте ви вільні скористатися будь-яким іншим сумісним із Selenium драйвером.

<a name="installation"></a>
## Встановлення

Для початку встановіть [Google Chrome](https://www.google.com/chrome) і додайте до свого проєкту Composer-залежність `laravel/dusk`:

```shell
composer require laravel/dusk --dev
```

> [!WARNING]
> Якщо ви реєструєте сервіс-провайдер Dusk вручну, **ніколи** не реєструйте його у продакшн-середовищі: це може призвести до того, що будь-хто зможе автентифікуватися у вашому застосунку.

Після встановлення пакета Dusk виконайте артизан-команду `dusk:install`. Команда `dusk:install` створить каталог `tests/Browser`, приклад тесту Dusk і встановить бінарник Chrome Driver для вашої операційної системи:

```shell
php artisan dusk:install
```

Далі задайте змінну оточення `APP_URL` у файлі `.env` вашого застосунку. Це значення має збігатися з URL, за яким ви звертаєтеся до застосунку у браузері.

> [!NOTE]
> Якщо ви користуєтеся [Laravel Sail](/docs/{{version}}/sail) для керування локальним середовищем розробки, зверніться також до документації Sail щодо [налаштування й запуску тестів Dusk](/docs/{{version}}/sail#laravel-dusk).

<a name="managing-chromedriver-installations"></a>
### Керування встановленням ChromeDriver

Якщо ви хочете встановити версію ChromeDriver, відмінну від тієї, яку встановлює Laravel Dusk командою `dusk:install`, скористайтеся командою `dusk:chrome-driver`:

```shell
# Install the latest version of ChromeDriver for your OS...
php artisan dusk:chrome-driver

# Install a given version of ChromeDriver for your OS...
php artisan dusk:chrome-driver 86

# Install a given version of ChromeDriver for all supported OSs...
php artisan dusk:chrome-driver --all

# Install the version of ChromeDriver that matches the detected version of Chrome / Chromium for your OS...
php artisan dusk:chrome-driver --detect
```

> [!WARNING]
> Dusk вимагає, щоб бінарники `chromedriver` були виконуваними. Якщо у вас виникають проблеми із запуском Dusk, переконайтеся, що бінарники виконувані, такою командою: `chmod -R 0755 vendor/laravel/dusk/bin/`.

<a name="using-other-browsers"></a>
### Використання інших браузерів

За замовчуванням Dusk запускає ваші браузерні тести в Google Chrome через окремо встановлений [ChromeDriver](https://sites.google.com/chromium.org/driver). Проте ви можете запустити власний сервер Selenium і виконувати тести в будь-якому браузері.

Для початку відкрийте файл `tests/DuskTestCase.php` - це базовий тест-кейс Dusk для вашого застосунку. У ньому ви можете прибрати виклик методу `startChromeDriver`. Це зупинить автоматичний запуск ChromeDriver у Dusk:

```php
/**
 * Prepare for Dusk test execution.
 *
 * @beforeClass
 */
public static function prepare(): void
{
    // static::startChromeDriver();
}
```

Далі ви можете змінити метод `driver`, щоб підключитися до потрібних вам URL і порту. Крім того, ви можете змінити «бажані можливості» (desired capabilities), які передаються до WebDriver:

```php
use Facebook\WebDriver\Remote\RemoteWebDriver;

/**
 * Create the RemoteWebDriver instance.
 */
protected function driver(): RemoteWebDriver
{
    return RemoteWebDriver::create(
        'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs()
    );
}
```

<a name="getting-started"></a>
## Перші кроки

<a name="generating-tests"></a>
### Генерування тестів

Щоб згенерувати тест Dusk, скористайтеся артизан-командою `dusk:make`. Згенерований тест потрапить до каталогу `tests/Browser`:

```shell
php artisan dusk:make LoginTest
```

<a name="resetting-the-database-after-each-test"></a>
### Скидання бази даних після кожного тесту

Більшість ваших тестів працюватимуть зі сторінками, які отримують дані з бази даних застосунку; проте ваші тести Dusk ніколи не повинні використовувати трейт `RefreshDatabase`. Трейт `RefreshDatabase` спирається на транзакції бази даних, які не застосовні й недоступні між HTTP-запитами. Натомість у вас є два варіанти: трейт `DatabaseMigrations` і трейт `DatabaseTruncation`.

<a name="reset-migrations"></a>
#### Використання міграцій бази даних

Трейт `DatabaseMigrations` запускатиме ваші міграції перед кожним тестом. Проте видалення й повторне створення таблиць для кожного тесту зазвичай повільніше за їх очищення:

```php tab=Pest
<?php

use Illuminate\Foundation\Testing\DatabaseMigrations;

pest()->use(DatabaseMigrations::class);

//
```

```php tab=PHPUnit
<?php

namespace Tests\Browser;

use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;

class ExampleTest extends DuskTestCase
{
    use DatabaseMigrations;

    //
}
```

> [!WARNING]
> Бази даних SQLite в пам'яті не можна використовувати під час прогону тестів Dusk. Оскільки браузер виконується у власному процесі, він не матиме доступу до баз даних у пам'яті інших процесів.

<a name="reset-truncation"></a>
#### Використання очищення бази даних

Трейт `DatabaseTruncation` мігрує вашу базу даних на першому тесті, щоб переконатися, що таблиці створено належним чином. Проте в наступних тестах таблиці бази просто очищатимуться - це швидше, ніж щоразу проганяти всі міграції:

```php tab=Pest
<?php

use Illuminate\Foundation\Testing\DatabaseTruncation;

pest()->use(DatabaseTruncation::class);

//
```

```php tab=PHPUnit
<?php

namespace Tests\Browser;

use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTruncation;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;

class ExampleTest extends DuskTestCase
{
    use DatabaseTruncation;

    //
}
```

За замовчуванням цей трейт очищує всі таблиці, окрім `migrations`. Якщо ви хочете вказати, які саме таблиці слід очищати, визначте у своєму тестовому класі властивість `$tablesToTruncate`:

> [!NOTE]
> Якщо ви користуєтеся Pest, визначайте властивості чи методи в базовому класі `DuskTestCase` або в будь-якому класі, який успадковує ваш тестовий файл.

```php
/**
 * Indicates which tables should be truncated.
 *
 * @var array
 */
protected $tablesToTruncate = ['users'];
```

Або ж ви можете визначити у своєму тестовому класі властивість `$exceptTables`, щоб указати, які таблиці слід виключити з очищення:

```php
/**
 * Indicates which tables should be excluded from truncation.
 *
 * @var array
 */
protected $exceptTables = ['users'];
```

Щоб указати підключення до баз даних, чиї таблиці слід очищати, визначте у своєму тестовому класі властивість `$connectionsToTruncate`:

```php
/**
 * Indicates which connections should have their tables truncated.
 *
 * @var array
 */
protected $connectionsToTruncate = ['mysql'];
```

Якщо ви хочете виконати код до або після очищення бази даних, визначте у своєму тестовому класі методи `beforeTruncatingDatabase` чи `afterTruncatingDatabase`:

```php
/**
 * Perform any work that should take place before the database has started truncating.
 */
protected function beforeTruncatingDatabase(): void
{
    //
}

/**
 * Perform any work that should take place after the database has finished truncating.
 */
protected function afterTruncatingDatabase(): void
{
    //
}
```

<a name="running-tests"></a>
### Запуск тестів

Щоб запустити ваші браузерні тести, виконайте артизан-команду `dusk`:

```shell
php artisan dusk
```

Якщо під час останнього запуску команди `dusk` у вас були провалені тести, ви можете заощадити час, спершу перезапустивши саме їх командою `dusk:fails`:

```shell
php artisan dusk:fails
```

Команда `dusk` приймає будь-який аргумент, який зазвичай приймає раннер тестів Pest / PHPUnit, - наприклад, щоб запустити лише тести певної [групи](https://docs.phpunit.de/en/10.5/annotations.html#group):

```shell
php artisan dusk --group=foo
```

> [!NOTE]
> Якщо ви користуєтеся [Laravel Sail](/docs/{{version}}/sail) для керування локальним середовищем розробки, зверніться до документації Sail щодо [налаштування й запуску тестів Dusk](/docs/{{version}}/sail#laravel-dusk).

<a name="manually-starting-chromedriver"></a>
#### Ручний запуск ChromeDriver

За замовчуванням Dusk автоматично намагається запустити ChromeDriver. Якщо у вашій системі це не працює, ви можете запустити ChromeDriver вручну перед виконанням команди `dusk`. Якщо ви вирішите запускати ChromeDriver вручну, закоментуйте такий рядок у файлі `tests/DuskTestCase.php`:

```php
/**
 * Prepare for Dusk test execution.
 *
 * @beforeClass
 */
public static function prepare(): void
{
    // static::startChromeDriver();
}
```

Крім того, якщо ви запускаєте ChromeDriver на порту, відмінному від 9515, змініть метод `driver` того самого класу, щоб указати правильний порт:

```php
use Facebook\WebDriver\Remote\RemoteWebDriver;

/**
 * Create the RemoteWebDriver instance.
 */
protected function driver(): RemoteWebDriver
{
    return RemoteWebDriver::create(
        'http://localhost:9515', DesiredCapabilities::chrome()
    );
}
```

<a name="environment-handling"></a>
### Робота із середовищем

Щоб змусити Dusk використовувати власний файл оточення під час прогону тестів, створіть у корені проєкту файл `.env.dusk.{environment}`. Наприклад, якщо ви запускатимете команду `dusk` із середовища `local`, створіть файл `.env.dusk.local`.

Під час прогону тестів Dusk створить резервну копію вашого файлу `.env` і перейменує ваше оточення Dusk на `.env`. Коли тести завершаться, ваш файл `.env` буде відновлено.

<a name="browser-basics"></a>
## Основи роботи з браузером

<a name="creating-browsers"></a>
### Створення браузерів

Для початку напишімо тест, який перевіряє, що ми можемо увійти до нашого застосунку. Згенерувавши тест, ми можемо змінити його так, щоб перейти на сторінку входу, ввести облікові дані й натиснути кнопку «Login». Щоб створити екземпляр браузера, викличте метод `browse` усередині вашого тесту Dusk:

```php tab=Pest
<?php

use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;

pest()->use(DatabaseMigrations::class);

test('basic example', function () {
    $user = User::factory()->create([
        'email' => 'taylor@laravel.com',
    ]);

    $this->browse(function (Browser $browser) use ($user) {
        $browser->visit('/login')
            ->type('email', $user->email)
            ->type('password', 'password')
            ->press('Login')
            ->assertPathIs('/home');
    });
});
```

```php tab=PHPUnit
<?php

namespace Tests\Browser;

use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;

class ExampleTest extends DuskTestCase
{
    use DatabaseMigrations;

    /**
     * A basic browser test example.
     */
    public function test_basic_example(): void
    {
        $user = User::factory()->create([
            'email' => 'taylor@laravel.com',
        ]);

        $this->browse(function (Browser $browser) use ($user) {
            $browser->visit('/login')
                ->type('email', $user->email)
                ->type('password', 'password')
                ->press('Login')
                ->assertPathIs('/home');
        });
    }
}
```

Як видно з прикладу вище, метод `browse` приймає замикання. Dusk автоматично передасть до нього екземпляр браузера - це головний об'єкт для взаємодії з вашим застосунком і тверджень щодо нього.

<a name="creating-multiple-browsers"></a>
#### Створення кількох браузерів

Іноді для належного виконання тесту вам може знадобитися кілька браузерів. Наприклад, вони можуть знадобитися, щоб протестувати екран чату, який працює через вебсокети. Щоб створити кілька браузерів, просто додайте більше аргументів-браузерів до сигнатури замикання, переданого методу `browse`:

```php
$this->browse(function (Browser $first, Browser $second) {
    $first->loginAs(User::find(1))
        ->visit('/home')
        ->waitForText('Message');

    $second->loginAs(User::find(2))
        ->visit('/home')
        ->waitForText('Message')
        ->type('message', 'Hey Taylor')
        ->press('Send');

    $first->waitForText('Hey Taylor')
        ->assertSee('Jeffrey Way');
});
```

<a name="navigation"></a>
### Навігація

Метод `visit` дозволяє перейти до заданого URI у вашому застосунку:

```php
$browser->visit('/login');
```

Метод `visitRoute` дозволяє перейти до [іменованого маршруту](/docs/{{version}}/routing#named-routes):

```php
$browser->visitRoute($routeName, $parameters);
```

Ви можете переходити «назад» і «вперед» методами `back` та `forward`:

```php
$browser->back();

$browser->forward();
```

Метод `refresh` дозволяє оновити сторінку:

```php
$browser->refresh();
```

<a name="resizing-browser-windows"></a>
### Зміна розміру вікна браузера

Метод `resize` дозволяє змінити розмір вікна браузера:

```php
$browser->resize(1920, 1080);
```

Метод `maximize` дозволяє розгорнути вікно браузера на весь екран:

```php
$browser->maximize();
```

Метод `fitContent` змінить розмір вікна браузера так, щоб він відповідав розміру вмісту:

```php
$browser->fitContent();
```

Коли тест провалюється, Dusk автоматично змінює розмір браузера під вміст перед знімком екрана. Ви можете вимкнути цю поведінку, викликавши у своєму тесті метод `disableFitOnFailure`:

```php
$browser->disableFitOnFailure();
```

Метод `move` дозволяє перемістити вікно браузера в інше місце екрана:

```php
$browser->move($x = 100, $y = 100);
```

<a name="browser-macros"></a>
### Макроси браузера

Якщо ви хочете визначити власний метод браузера, який можна перевикористовувати в різних тестах, скористайтеся методом `macro` класу `Browser`. Зазвичай цей метод викликають у методі `boot` [сервіс-провайдера](/docs/{{version}}/providers):

```php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Laravel\Dusk\Browser;

class DuskServiceProvider extends ServiceProvider
{
    /**
     * Register Dusk's browser macros.
     */
    public function boot(): void
    {
        Browser::macro('scrollToElement', function (string $element = null) {
            $this->script("$('html, body').animate({ scrollTop: $('$element').offset().top }, 0);");

            return $this;
        });
    }
}
```

Функція `macro` приймає першим аргументом ім'я, а другим - замикання. Замикання макроса виконається, коли ви викличете макрос як метод на екземплярі `Browser`:

```php
$this->browse(function (Browser $browser) use ($user) {
    $browser->visit('/pay')
        ->scrollToElement('#credit-card-details')
        ->assertSee('Enter Credit Card Details');
});
```

<a name="authentication"></a>
### Автентифікація

Часто ви тестуватимете сторінки, які вимагають автентифікації. Щоб не взаємодіяти з екраном входу вашого застосунку в кожному тесті, скористайтеся методом Dusk `loginAs`. Метод `loginAs` приймає первинний ключ вашої моделі, придатної до автентифікації, або сам її екземпляр:

```php
use App\Models\User;
use Laravel\Dusk\Browser;

$this->browse(function (Browser $browser) {
    $browser->loginAs(User::find(1))
        ->visit('/home');
});
```

> [!WARNING]
> Після використання методу `loginAs` сесія користувача зберігатиметься для всіх тестів у межах файлу.

<a name="cookies"></a>
### Cookie

Метод `cookie` дозволяє отримати чи задати значення зашифрованого cookie. За замовчуванням усі створені Laravel cookie зашифровані:

```php
$browser->cookie('name');

$browser->cookie('name', 'Taylor');
```

Метод `plainCookie` дозволяє отримати чи задати значення незашифрованого cookie:

```php
$browser->plainCookie('name');

$browser->plainCookie('name', 'Taylor');
```

Метод `deleteCookie` дозволяє видалити заданий cookie:

```php
$browser->deleteCookie('name');
```

<a name="executing-javascript"></a>
### Виконання JavaScript

Метод `script` дозволяє виконати у браузері довільні інструкції JavaScript:

```php
$browser->script('document.documentElement.scrollTop = 0');

$browser->script([
    'document.body.scrollTop = 0',
    'document.documentElement.scrollTop = 0',
]);

$output = $browser->script('return window.location.pathname');
```

<a name="taking-a-screenshot"></a>
### Знімок екрана

Метод `screenshot` дозволяє зробити знімок екрана й зберегти його із заданим іменем файлу. Усі знімки зберігаються в каталозі `tests/Browser/screenshots`:

```php
$browser->screenshot('filename');
```

Метод `responsiveScreenshots` дозволяє зробити серію знімків на різних брейкпоїнтах:

```php
$browser->responsiveScreenshots('filename');
```

Метод `screenshotElement` дозволяє зробити знімок конкретного елемента на сторінці:

```php
$browser->screenshotElement('#selector', 'filename');
```

<a name="storing-console-output-to-disk"></a>
### Збереження виводу консолі на диск

Метод `storeConsoleLog` дозволяє записати вивід консолі поточного браузера на диск під заданим іменем файлу. Вивід консолі зберігається в каталозі `tests/Browser/console`:

```php
$browser->storeConsoleLog('filename');
```

<a name="storing-page-source-to-disk"></a>
### Збереження коду сторінки на диск

Метод `storeSource` дозволяє записати код поточної сторінки на диск під заданим іменем файлу. Код сторінки зберігається в каталозі `tests/Browser/source`:

```php
$browser->storeSource('filename');
```

<a name="interacting-with-elements"></a>
## Взаємодія з елементами

<a name="dusk-selectors"></a>
### Селектори Dusk

Вибір хороших CSS-селекторів для взаємодії з елементами - одна з найскладніших частин написання тестів Dusk. З часом зміни у фронтенді можуть зламати ваші тести через CSS-селектори на кшталт такого:

```html
// HTML...

<button>Login</button>
```

```php
// Test...

$browser->click('.login-page .container div > button');
```

Селектори Dusk дозволяють зосередитися на написанні ефективних тестів, а не на запам'ятовуванні CSS-селекторів. Щоб визначити селектор, додайте до свого HTML-елемента атрибут `dusk`. Далі, взаємодіючи з браузером Dusk, додайте до селектора префікс `@`, щоб працювати з цим елементом у тесті:

```html
// HTML...

<button dusk="login-button">Login</button>
```

```php
// Test...

$browser->click('@login-button');
```

За бажання ви можете змінити HTML-атрибут, який використовує селектор Dusk, методом `selectorHtmlAttribute`. Зазвичай цей метод викликають у методі `boot` вашого `AppServiceProvider`:

```php
use Laravel\Dusk\Dusk;

Dusk::selectorHtmlAttribute('data-dusk');
```

<a name="text-values-and-attributes"></a>
### Текст, значення та атрибути

<a name="retrieving-setting-values"></a>
#### Отримання та задавання значень

Dusk надає кілька методів для роботи з поточним значенням, видимим текстом та атрибутами елементів на сторінці. Наприклад, щоб отримати «значення» елемента, який відповідає заданому CSS- чи Dusk-селектору, скористайтеся методом `value`:

```php
// Retrieve the value...
$value = $browser->value('selector');

// Set the value...
$browser->value('selector', 'value');
```

Метод `inputValue` дозволяє отримати «значення» елемента input із заданим іменем поля:

```php
$value = $browser->inputValue('field');
```

<a name="retrieving-text"></a>
#### Отримання тексту

Метод `text` дозволяє отримати видимий текст елемента, який відповідає заданому селектору:

```php
$text = $browser->text('selector');
```

<a name="retrieving-attributes"></a>
#### Отримання атрибутів

Нарешті, метод `attribute` дозволяє отримати значення атрибута елемента, який відповідає заданому селектору:

```php
$attribute = $browser->attribute('selector', 'value');
```

<a name="interacting-with-forms"></a>
### Робота з формами

<a name="typing-values"></a>
#### Введення значень

Dusk надає різні методи для роботи з формами й полями введення. Спершу погляньмо на приклад введення тексту в поле:

```php
$browser->type('email', 'taylor@laravel.com');
```

Зверніть увагу: хоча метод за потреби приймає CSS-селектор, передавати його до методу `type` не обов'язково. Якщо CSS-селектор не задано, Dusk шукатиме поле `input` чи `textarea` із заданим атрибутом `name`.

Щоб додати текст до поля, не очищаючи його вмісту, скористайтеся методом `append`:

```php
$browser->type('tags', 'foo')
    ->append('tags', ', bar, baz');
```

Очистити значення поля можна методом `clear`:

```php
$browser->clear('email');
```

Ви можете вказати Dusk вводити текст повільно методом `typeSlowly`. За замовчуванням Dusk робить паузу 100 мілісекунд між натисканнями клавіш. Щоб змінити цей час, передайте потрібну кількість мілісекунд третім аргументом методу:

```php
$browser->typeSlowly('mobile', '+1 (202) 555-5555');

$browser->typeSlowly('mobile', '+1 (202) 555-5555', 300);
```

Метод `appendSlowly` дозволяє повільно додавати текст:

```php
$browser->type('tags', 'foo')
    ->appendSlowly('tags', ', bar, baz');
```

<a name="dropdowns"></a>
#### Випадні списки

Щоб обрати значення в елементі `select`, скористайтеся методом `select`. Як і методу `type`, методу `select` не потрібен повний CSS-селектор. Передаючи значення методу `select`, передавайте саме значення опції, а не видимий текст:

```php
$browser->select('size', 'Large');
```

Ви можете обрати випадкову опцію, не передаючи другого аргументу:

```php
$browser->select('size');
```

Передавши масив другим аргументом до методу `select`, ви можете вказати методу обрати кілька опцій:

```php
$browser->select('categories', ['Art', 'Music']);
```

<a name="checkboxes"></a>
#### Чекбокси

Щоб «поставити галочку» в чекбоксі, скористайтеся методом `check`. Як і багатьом іншим методам для полів введення, повний CSS-селектор тут не потрібен. Якщо збігу за CSS-селектором не знайдено, Dusk шукатиме чекбокс із відповідним атрибутом `name`:

```php
$browser->check('terms');
```

Метод `uncheck` дозволяє «зняти галочку» з чекбокса:

```php
$browser->uncheck('terms');
```

<a name="radio-buttons"></a>
#### Радіокнопки

Щоб «обрати» опцію `radio`, скористайтеся методом `radio`. Як і багатьом іншим методам для полів введення, повний CSS-селектор тут не потрібен. Якщо збігу за CSS-селектором не знайдено, Dusk шукатиме поле `radio` з відповідними атрибутами `name` та `value`:

```php
$browser->radio('size', 'large');
```

<a name="attaching-files"></a>
### Прикріплення файлів

Метод `attach` дозволяє прикріпити файл до елемента `file`. Як і багатьом іншим методам для полів введення, повний CSS-селектор тут не потрібен. Якщо збігу за CSS-селектором не знайдено, Dusk шукатиме поле `file` з відповідним атрибутом `name`:

```php
$browser->attach('photo', __DIR__.'/photos/mountains.png');
```

> [!WARNING]
> Функція attach вимагає, щоб на вашому сервері було встановлено й увімкнено розширення PHP `Zip`.

<a name="pressing-buttons"></a>
### Натискання кнопок

Метод `press` дозволяє клацнути по кнопці на сторінці. Аргументом методу `press` може бути або видимий текст кнопки, або CSS- чи Dusk-селектор:

```php
$browser->press('Login');
```

Надсилаючи форми, багато застосунків вимикають кнопку надсилання після натискання й вмикають її знову, коли HTTP-запит завершується. Щоб натиснути кнопку й дочекатися, доки її знову ввімкнуть, скористайтеся методом `pressAndWaitFor`:

```php
// Press the button and wait a maximum of 5 seconds for it to be enabled...
$browser->pressAndWaitFor('Save');

// Press the button and wait a maximum of 1 second for it to be enabled...
$browser->pressAndWaitFor('Save', 1);
```

<a name="clicking-links"></a>
### Клацання по посиланнях

Щоб клацнути по посиланню, скористайтеся методом `clickLink` на екземплярі браузера. Метод `clickLink` клацне по посиланню із заданим видимим текстом:

```php
$browser->clickLink($linkText);
```

Метод `seeLink` дозволяє визначити, чи видиме на сторінці посилання із заданим видимим текстом:

```php
if ($browser->seeLink($linkText)) {
    // ...
}
```

> [!WARNING]
> Ці методи працюють через jQuery. Якщо jQuery на сторінці немає, Dusk автоматично впровадить його, щоб він був доступний на час тесту.

<a name="using-the-keyboard"></a>
### Використання клавіатури

Метод `keys` дозволяє передавати елементу складніші послідовності введення, ніж дозволяє метод `type`. Наприклад, ви можете вказати Dusk утримувати клавіші-модифікатори під час введення значень. У цьому прикладі клавішу `shift` буде утримувано, доки в елемент, що відповідає заданому селектору, вводиться `taylor`. Після `taylor` буде введено `swift` уже без модифікаторів:

```php
$browser->keys('selector', ['{shift}', 'taylor'], 'swift');
```

Ще один корисний випадок для методу `keys` - надсилання комбінації «гарячих клавіш» до основного CSS-селектора вашого застосунку:

```php
$browser->keys('.app', ['{command}', 'j']);
```

> [!NOTE]
> Усі клавіші-модифікатори на кшталт `{command}` беруться у фігурні дужки `{}` і відповідають константам, визначеним у класі `Facebook\WebDriver\WebDriverKeys`, який можна [знайти на GitHub](https://github.com/php-webdriver/php-webdriver/blob/master/lib/WebDriverKeys.php).

<a name="fluent-keyboard-interactions"></a>
#### Плавна робота з клавіатурою

Dusk також надає метод `withKeyboard`, який дозволяє плавно виконувати складні взаємодії з клавіатурою через клас `Laravel\Dusk\Keyboard`. Клас `Keyboard` надає методи `press`, `release`, `type` та `pause`:

```php
use Laravel\Dusk\Keyboard;

$browser->withKeyboard(function (Keyboard $keyboard) {
    $keyboard->press('c')
        ->pause(1000)
        ->release('c')
        ->type(['c', 'e', 'o']);
});
```

<a name="keyboard-macros"></a>
#### Макроси клавіатури

Якщо ви хочете визначити власні взаємодії з клавіатурою, які легко перевикористовувати в усьому наборі тестів, скористайтеся методом `macro`, який надає клас `Keyboard`. Зазвичай цей метод викликають у методі `boot` [сервіс-провайдера](/docs/{{version}}/providers):

```php
<?php

namespace App\Providers;

use Facebook\WebDriver\WebDriverKeys;
use Illuminate\Support\ServiceProvider;
use Laravel\Dusk\Keyboard;
use Laravel\Dusk\OperatingSystem;

class DuskServiceProvider extends ServiceProvider
{
    /**
     * Register Dusk's browser macros.
     */
    public function boot(): void
    {
        Keyboard::macro('copy', function (string $element = null) {
            $this->type([
                OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'c',
            ]);

            return $this;
        });

        Keyboard::macro('paste', function (string $element = null) {
            $this->type([
                OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'v',
            ]);

            return $this;
        });
    }
}
```

Функція `macro` приймає першим аргументом ім'я, а другим - замикання. Замикання макроса виконається, коли ви викличете макрос як метод на екземплярі `Keyboard`:

```php
$browser->click('@textarea')
    ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->copy())
    ->click('@another-textarea')
    ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->paste());
```

<a name="using-the-mouse"></a>
### Використання миші

<a name="clicking-on-elements"></a>
#### Клацання по елементах

Метод `click` дозволяє клацнути по елементу, який відповідає заданому CSS- чи Dusk-селектору:

```php
$browser->click('.selector');
```

Метод `clickAtXPath` дозволяє клацнути по елементу, який відповідає заданому виразу XPath:

```php
$browser->clickAtXPath('//div[@class = "selector"]');
```

Метод `clickAtPoint` дозволяє клацнути по найвищому елементу за заданою парою координат відносно видимої області браузера:

```php
$browser->clickAtPoint($x = 0, $y = 0);
```

Метод `doubleClick` дозволяє симулювати подвійне клацання мишею:

```php
$browser->doubleClick();

$browser->doubleClick('.selector');
```

Метод `rightClick` дозволяє симулювати клацання правою кнопкою миші:

```php
$browser->rightClick();

$browser->rightClick('.selector');
```

Метод `clickAndHold` дозволяє симулювати натискання й утримування кнопки миші. Наступний виклик методу `releaseMouse` скасує цю поведінку й відпустить кнопку:

```php
$browser->clickAndHold('.selector');

$browser->clickAndHold()
    ->pause(1000)
    ->releaseMouse();
```

Метод `controlClick` дозволяє симулювати у браузері подію `ctrl+click`:

```php
$browser->controlClick();

$browser->controlClick('.selector');
```

Методи `clickWhenVisible` чи `clickWhenEnabled` дозволяють дочекатися готовності елемента, перш ніж клацнути по ньому рівно один раз:

```php
$browser->clickWhenVisible('@save-button');
$browser->clickWhenEnabled('@submit-button');
```

<a name="mouseover"></a>
#### Наведення миші

Метод `mouseover` стане в пригоді, коли вам треба навести мишу на елемент, що відповідає заданому CSS- чи Dusk-селектору:

```php
$browser->mouseover('.selector');
```

<a name="drag-drop"></a>
#### Перетягування

Метод `drag` дозволяє перетягнути елемент, що відповідає заданому селектору, до іншого елемента:

```php
$browser->drag('.from-selector', '.to-selector');
```

Або ж ви можете перетягнути елемент в одному напрямку:

```php
$browser->dragLeft('.selector', $pixels = 10);
$browser->dragRight('.selector', $pixels = 10);
$browser->dragUp('.selector', $pixels = 10);
$browser->dragDown('.selector', $pixels = 10);
```

Нарешті, ви можете перетягнути елемент на заданий зсув:

```php
$browser->dragOffset('.selector', $x = 10, $y = 10);
```

<a name="javascript-dialogs"></a>
### Діалоги JavaScript

Dusk надає різні методи для роботи з діалогами JavaScript. Наприклад, метод `waitForDialog` дозволяє дочекатися появи діалогу JavaScript. Цей метод приймає необов'язковий аргумент - скільки секунд чекати на появу діалогу:

```php
$browser->waitForDialog($seconds = null);
```

Метод `assertDialogOpened` дозволяє перевірити, що діалог було показано і що він містить задане повідомлення:

```php
$browser->assertDialogOpened('Dialog message');
```

Якщо діалог JavaScript містить поле введення, ви можете скористатися методом `typeInDialog`, щоб ввести туди значення:

```php
$browser->typeInDialog('Hello World');
```

Щоб закрити відкритий діалог JavaScript натисканням кнопки «OK», викличте метод `acceptDialog`:

```php
$browser->acceptDialog();
```

Щоб закрити відкритий діалог JavaScript натисканням кнопки «Cancel», викличте метод `dismissDialog`:

```php
$browser->dismissDialog();
```

<a name="interacting-with-iframes"></a>
### Робота з вбудованими фреймами

Якщо вам потрібно взаємодіяти з елементами всередині iframe, скористайтеся методом `withinFrame`. Усі взаємодії з елементами всередині замикання, переданого методу `withinFrame`, будуть обмежені контекстом указаного iframe:

```php
$browser->withinFrame('#credit-card-details', function ($browser) {
    $browser->type('input[name="cardnumber"]', '4242424242424242')
        ->type('input[name="exp-date"]', '1224')
        ->type('input[name="cvc"]', '123')
        ->press('Pay');
});
```

<a name="scoping-selectors"></a>
### Обмеження селекторів

Іноді вам може знадобитися виконати кілька операцій, обмеживши їх усі заданим селектором. Наприклад, ви можете захотіти перевірити, що певний текст є лише в таблиці, а потім клацнути по кнопці всередині цієї таблиці. Для цього скористайтеся методом `with`. Усі операції всередині замикання, переданого методу `with`, будуть обмежені початковим селектором:

```php
$browser->with('.table', function (Browser $table) {
    $table->assertSee('Hello World')
        ->clickLink('Delete');
});
```

Іноді вам може знадобитися виконати твердження поза межами поточного обмеження. Для цього скористайтеся методами `elsewhere` та `elsewhereWhenAvailable`:

```php
$browser->with('.table', function (Browser $table) {
    // Current scope is `body .table`...

    $browser->elsewhere('.page-title', function (Browser $title) {
        // Current scope is `body .page-title`...
        $title->assertSee('Hello World');
    });

    $browser->elsewhereWhenAvailable('.page-title', function (Browser $title) {
        // Current scope is `body .page-title`...
        $title->assertSee('Hello World');
    });
});
```

<a name="waiting-for-elements"></a>
### Очікування елементів

Тестуючи застосунки, які активно використовують JavaScript, часто доводиться «чекати» на доступність певних елементів чи даних, перш ніж рухатися далі. Dusk робить це дуже просто. За допомогою різних методів ви можете дочекатися, доки елементи стануть видимими на сторінці, або навіть доки заданий вираз JavaScript не набуде значення `true`.

<a name="waiting"></a>
#### Очікування

Якщо вам просто треба призупинити тест на задану кількість мілісекунд, скористайтеся методом `pause`:

```php
$browser->pause(1000);
```

Якщо тест треба призупинити лише за умови, що задана умова істинна (`true`), скористайтеся методом `pauseIf`:

```php
$browser->pauseIf(App::environment('production'), 1000);
```

Так само, якщо тест треба призупинити, доки задана умова не стане істинною (`true`), скористайтеся методом `pauseUnless`:

```php
$browser->pauseUnless(App::environment('testing'), 1000);
```

<a name="waiting-for-selectors"></a>
#### Очікування селекторів

Метод `waitFor` дозволяє призупинити виконання тесту, доки на сторінці не з'явиться елемент, що відповідає заданому CSS- чи Dusk-селектору. За замовчуванням тест буде призупинено максимум на п'ять секунд, після чого буде кинуто виняток. За потреби ви можете передати власний поріг очікування другим аргументом методу:

```php
// Wait a maximum of five seconds for the selector...
$browser->waitFor('.selector');

// Wait a maximum of one second for the selector...
$browser->waitFor('.selector', 1);
```

Ви також можете дочекатися, доки елемент, що відповідає заданому селектору, не міститиме заданий текст:

```php
// Wait a maximum of five seconds for the selector to contain the given text...
$browser->waitForTextIn('.selector', 'Hello World');

// Wait a maximum of one second for the selector to contain the given text...
$browser->waitForTextIn('.selector', 'Hello World', 1);
```

Ви також можете дочекатися, доки елемент, що відповідає заданому селектору, зникне зі сторінки:

```php
// Wait a maximum of five seconds until the selector is missing...
$browser->waitUntilMissing('.selector');

// Wait a maximum of one second until the selector is missing...
$browser->waitUntilMissing('.selector', 1);
```

Або ж ви можете дочекатися, доки елемент, що відповідає заданому селектору, стане увімкненим чи вимкненим:

```php
// Wait a maximum of five seconds until the selector is enabled...
$browser->waitUntilEnabled('.selector');

// Wait a maximum of one second until the selector is enabled...
$browser->waitUntilEnabled('.selector', 1);

// Wait a maximum of five seconds until the selector is disabled...
$browser->waitUntilDisabled('.selector');

// Wait a maximum of one second until the selector is disabled...
$browser->waitUntilDisabled('.selector', 1);
```

<a name="scoping-selectors-when-available"></a>
#### Обмеження селекторів за доступністю

Іноді вам може знадобитися дочекатися появи елемента, що відповідає заданому селектору, а потім із ним попрацювати. Наприклад, дочекатися появи модального вікна й натиснути в ньому кнопку «OK». Для цього скористайтеся методом `whenAvailable`. Усі операції з елементами всередині заданого замикання будуть обмежені початковим селектором:

```php
$browser->whenAvailable('.modal', function (Browser $modal) {
    $modal->assertSee('Hello World')
        ->press('OK');
});
```

<a name="waiting-for-text"></a>
#### Очікування тексту

Метод `waitForText` дозволяє дочекатися, доки на сторінці не з'явиться заданий текст:

```php
// Wait a maximum of five seconds for the text...
$browser->waitForText('Hello World');

// Wait a maximum of one second for the text...
$browser->waitForText('Hello World', 1);
```

Метод `waitUntilMissingText` дозволяє дочекатися, доки показаний текст не зникне зі сторінки:

```php
// Wait a maximum of five seconds for the text to be removed...
$browser->waitUntilMissingText('Hello World');

// Wait a maximum of one second for the text to be removed...
$browser->waitUntilMissingText('Hello World', 1);
```

<a name="waiting-for-links"></a>
#### Очікування посилань

Метод `waitForLink` дозволяє дочекатися, доки на сторінці не з'явиться заданий текст посилання:

```php
// Wait a maximum of five seconds for the link...
$browser->waitForLink('Create');

// Wait a maximum of one second for the link...
$browser->waitForLink('Create', 1);
```

<a name="waiting-for-inputs"></a>
#### Очікування полів введення

Метод `waitForInput` дозволяє дочекатися, доки задане поле введення стане видимим на сторінці:

```php
// Wait a maximum of five seconds for the input...
$browser->waitForInput($field);

// Wait a maximum of one second for the input...
$browser->waitForInput($field, 1);
```

<a name="waiting-on-the-page-location"></a>
#### Очікування розташування сторінки

Коли ви робите твердження щодо шляху на кшталт `$browser->assertPathIs('/home')`, воно може провалитися, якщо `window.location.pathname` оновлюється асинхронно. Скористайтеся методом `waitForLocation`, щоб дочекатися, доки розташування набуде заданого значення:

```php
$browser->waitForLocation('/secret');
```

Метод `waitForLocation` можна також використати, щоб дочекатися, доки поточне розташування вікна стане повним URL:

```php
$browser->waitForLocation('https://example.com/path');
```

Ви також можете дочекатися розташування [іменованого маршруту](/docs/{{version}}/routing#named-routes):

```php
$browser->waitForRoute($routeName, $parameters);
```

<a name="waiting-for-page-reloads"></a>
#### Очікування перезавантаження сторінки

Якщо після виконання дії вам треба дочекатися перезавантаження сторінки, скористайтеся методом `waitForReload`:

```php
use Laravel\Dusk\Browser;

$browser->waitForReload(function (Browser $browser) {
    $browser->press('Submit');
})
->assertSee('Success!');
```

Оскільки потреба дочекатися перезавантаження зазвичай виникає після натискання кнопки, для зручності ви можете скористатися методом `clickAndWaitForReload`:

```php
$browser->clickAndWaitForReload('.selector')
    ->assertSee('something');
```

<a name="waiting-on-javascript-expressions"></a>
#### Очікування виразів JavaScript

Іноді вам може знадобитися призупинити виконання тесту, доки заданий вираз JavaScript не набуде значення `true`. Легко зробити це можна методом `waitUntil`. Передаючи цьому методу вираз, вам не треба додавати ключове слово `return` чи крапку з комою в кінці:

```php
// Wait a maximum of five seconds for the expression to be true...
$browser->waitUntil('App.data.servers.length > 0');

// Wait a maximum of one second for the expression to be true...
$browser->waitUntil('App.data.servers.length > 0', 1);
```

<a name="waiting-on-vue-expressions"></a>
#### Очікування виразів Vue

Методи `waitUntilVue` та `waitUntilVueIsNot` дозволяють дочекатися, доки атрибут [компонента Vue](https://vuejs.org) набуде заданого значення:

```php
// Wait until the component attribute contains the given value...
$browser->waitUntilVue('user.name', 'Taylor', '@user');

// Wait until the component attribute doesn't contain the given value...
$browser->waitUntilVueIsNot('user.name', null, '@user');
```

<a name="waiting-for-javascript-events"></a>
#### Очікування подій JavaScript

Метод `waitForEvent` дозволяє призупинити виконання тесту, доки не станеться подія JavaScript:

```php
$browser->waitForEvent('load');
```

Слухач події прикріплюється до поточного обмеження, яким за замовчуванням є елемент `body`. Якщо ви користуєтеся обмеженим селектором, слухач буде прикріплено до відповідного елемента:

```php
$browser->with('iframe', function (Browser $iframe) {
    // Wait for the iframe's load event...
    $iframe->waitForEvent('load');
});
```

Ви також можете передати селектор другим аргументом до методу `waitForEvent`, щоб прикріпити слухач до конкретного елемента:

```php
$browser->waitForEvent('load', '.selector');
```

Ви також можете чекати на події об'єктів `document` та `window`:

```php
// Wait until the document is scrolled...
$browser->waitForEvent('scroll', 'document');

// Wait a maximum of five seconds until the window is resized...
$browser->waitForEvent('resize', 'window', 5);
```

<a name="waiting-with-a-callback"></a>
#### Очікування з колбеком

Багато методів очікування в Dusk спираються на метод `waitUsing`. Ви можете скористатися ним напряму, щоб дочекатися, доки задане замикання поверне `true`. Метод `waitUsing` приймає максимальну кількість секунд очікування, інтервал перевірки замикання, саме замикання та необов'язкове повідомлення про помилку:

```php
$browser->waitUsing(10, 1, function () use ($something) {
    return $something->isReady();
}, "Something wasn't ready in time.");
```

<a name="scrolling-an-element-into-view"></a>
### Прокручування до елемента

Іноді ви не зможете клацнути по елементу, бо він поза видимою областю браузера. Метод `scrollIntoView` прокрутить вікно браузера, доки елемент за заданим селектором не потрапить у поле зору:

```php
$browser->scrollIntoView('.selector')
    ->click('.selector');
```

<a name="available-assertions"></a>
## Доступні твердження

Dusk надає різні твердження, які ви можете робити щодо свого застосунку. Усі доступні твердження задокументовані в списку нижче:

<style>
    .collection-method-list > p {
        columns: 10.8em 3; -moz-columns: 10.8em 3; -webkit-columns: 10.8em 3;
    }

    .collection-method-list a {
        display: block;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
</style>

<div class="collection-method-list" markdown="1">

[assertTitle](#assert-title)
[assertTitleContains](#assert-title-contains)
[assertUrlIs](#assert-url-is)
[assertSchemeIs](#assert-scheme-is)
[assertSchemeIsNot](#assert-scheme-is-not)
[assertHostIs](#assert-host-is)
[assertHostIsNot](#assert-host-is-not)
[assertPortIs](#assert-port-is)
[assertPortIsNot](#assert-port-is-not)
[assertPathBeginsWith](#assert-path-begins-with)
[assertPathEndsWith](#assert-path-ends-with)
[assertPathContains](#assert-path-contains)
[assertPathIs](#assert-path-is)
[assertPathIsNot](#assert-path-is-not)
[assertRouteIs](#assert-route-is)
[assertQueryStringHas](#assert-query-string-has)
[assertQueryStringMissing](#assert-query-string-missing)
[assertFragmentIs](#assert-fragment-is)
[assertFragmentBeginsWith](#assert-fragment-begins-with)
[assertFragmentIsNot](#assert-fragment-is-not)
[assertHasCookie](#assert-has-cookie)
[assertHasPlainCookie](#assert-has-plain-cookie)
[assertCookieMissing](#assert-cookie-missing)
[assertPlainCookieMissing](#assert-plain-cookie-missing)
[assertCookieValue](#assert-cookie-value)
[assertPlainCookieValue](#assert-plain-cookie-value)
[assertSee](#assert-see)
[assertDontSee](#assert-dont-see)
[assertSeeIn](#assert-see-in)
[assertDontSeeIn](#assert-dont-see-in)
[assertSeeAnythingIn](#assert-see-anything-in)
[assertSeeNothingIn](#assert-see-nothing-in)
[assertCount](#assert-count)
[assertScript](#assert-script)
[assertSourceHas](#assert-source-has)
[assertSourceMissing](#assert-source-missing)
[assertSeeLink](#assert-see-link)
[assertDontSeeLink](#assert-dont-see-link)
[assertInputValue](#assert-input-value)
[assertInputValueIsNot](#assert-input-value-is-not)
[assertChecked](#assert-checked)
[assertNotChecked](#assert-not-checked)
[assertIndeterminate](#assert-indeterminate)
[assertRadioSelected](#assert-radio-selected)
[assertRadioNotSelected](#assert-radio-not-selected)
[assertSelected](#assert-selected)
[assertNotSelected](#assert-not-selected)
[assertSelectHasOptions](#assert-select-has-options)
[assertSelectMissingOptions](#assert-select-missing-options)
[assertSelectHasOption](#assert-select-has-option)
[assertSelectMissingOption](#assert-select-missing-option)
[assertValue](#assert-value)
[assertValueIsNot](#assert-value-is-not)
[assertAttribute](#assert-attribute)
[assertAttributeMissing](#assert-attribute-missing)
[assertAttributeContains](#assert-attribute-contains)
[assertAttributeDoesntContain](#assert-attribute-doesnt-contain)
[assertAriaAttribute](#assert-aria-attribute)
[assertDataAttribute](#assert-data-attribute)
[assertVisible](#assert-visible)
[assertPresent](#assert-present)
[assertNotPresent](#assert-not-present)
[assertMissing](#assert-missing)
[assertInputPresent](#assert-input-present)
[assertInputMissing](#assert-input-missing)
[assertDialogOpened](#assert-dialog-opened)
[assertEnabled](#assert-enabled)
[assertDisabled](#assert-disabled)
[assertButtonEnabled](#assert-button-enabled)
[assertButtonDisabled](#assert-button-disabled)
[assertFocused](#assert-focused)
[assertNotFocused](#assert-not-focused)
[assertAuthenticated](#assert-authenticated)
[assertGuest](#assert-guest)
[assertAuthenticatedAs](#assert-authenticated-as)
[assertVue](#assert-vue)
[assertVueIsNot](#assert-vue-is-not)
[assertVueContains](#assert-vue-contains)
[assertVueDoesntContain](#assert-vue-doesnt-contain)

</div>

<a name="assert-title"></a>
#### assertTitle

Перевіряє, що заголовок сторінки збігається із заданим текстом:

```php
$browser->assertTitle($title);
```

<a name="assert-title-contains"></a>
#### assertTitleContains

Перевіряє, що заголовок сторінки містить заданий текст:

```php
$browser->assertTitleContains($title);
```

<a name="assert-url-is"></a>
#### assertUrlIs

Перевіряє, що поточний URL (без рядка запиту) збігається із заданим рядком:

```php
$browser->assertUrlIs($url);
```

<a name="assert-scheme-is"></a>
#### assertSchemeIs

Перевіряє, що схема поточного URL збігається із заданою:

```php
$browser->assertSchemeIs($scheme);
```

<a name="assert-scheme-is-not"></a>
#### assertSchemeIsNot

Перевіряє, що схема поточного URL не збігається із заданою:

```php
$browser->assertSchemeIsNot($scheme);
```

<a name="assert-host-is"></a>
#### assertHostIs

Перевіряє, що хост поточного URL збігається із заданим:

```php
$browser->assertHostIs($host);
```

<a name="assert-host-is-not"></a>
#### assertHostIsNot

Перевіряє, що хост поточного URL не збігається із заданим:

```php
$browser->assertHostIsNot($host);
```

<a name="assert-port-is"></a>
#### assertPortIs

Перевіряє, що порт поточного URL збігається із заданим:

```php
$browser->assertPortIs($port);
```

<a name="assert-port-is-not"></a>
#### assertPortIsNot

Перевіряє, що порт поточного URL не збігається із заданим:

```php
$browser->assertPortIsNot($port);
```

<a name="assert-path-begins-with"></a>
#### assertPathBeginsWith

Перевіряє, що шлях поточного URL починається із заданого:

```php
$browser->assertPathBeginsWith('/home');
```

<a name="assert-path-ends-with"></a>
#### assertPathEndsWith

Перевіряє, що шлях поточного URL закінчується заданим:

```php
$browser->assertPathEndsWith('/home');
```

<a name="assert-path-contains"></a>
#### assertPathContains

Перевіряє, що шлях поточного URL містить заданий:

```php
$browser->assertPathContains('/home');
```

<a name="assert-path-is"></a>
#### assertPathIs

Перевіряє, що поточний шлях збігається із заданим:

```php
$browser->assertPathIs('/home');
```

<a name="assert-path-is-not"></a>
#### assertPathIsNot

Перевіряє, що поточний шлях не збігається із заданим:

```php
$browser->assertPathIsNot('/home');
```

<a name="assert-route-is"></a>
#### assertRouteIs

Перевіряє, що поточний URL збігається з URL заданого [іменованого маршруту](/docs/{{version}}/routing#named-routes):

```php
$browser->assertRouteIs($name, $parameters);
```

<a name="assert-query-string-has"></a>
#### assertQueryStringHas

Перевіряє, що заданий параметр рядка запиту присутній:

```php
$browser->assertQueryStringHas($name);
```

Перевіряє, що заданий параметр рядка запиту присутній і має задане значення:

```php
$browser->assertQueryStringHas($name, $value);
```

<a name="assert-query-string-missing"></a>
#### assertQueryStringMissing

Перевіряє, що заданого параметра рядка запиту немає:

```php
$browser->assertQueryStringMissing($name);
```

<a name="assert-fragment-is"></a>
#### assertFragmentIs

Перевіряє, що поточний хеш-фрагмент URL збігається із заданим:

```php
$browser->assertFragmentIs('anchor');
```

<a name="assert-fragment-begins-with"></a>
#### assertFragmentBeginsWith

Перевіряє, що поточний хеш-фрагмент URL починається із заданого:

```php
$browser->assertFragmentBeginsWith('anchor');
```

<a name="assert-fragment-is-not"></a>
#### assertFragmentIsNot

Перевіряє, що поточний хеш-фрагмент URL не збігається із заданим:

```php
$browser->assertFragmentIsNot('anchor');
```

<a name="assert-has-cookie"></a>
#### assertHasCookie

Перевіряє, що заданий зашифрований cookie присутній:

```php
$browser->assertHasCookie($name);
```

<a name="assert-has-plain-cookie"></a>
#### assertHasPlainCookie

Перевіряє, що заданий незашифрований cookie присутній:

```php
$browser->assertHasPlainCookie($name);
```

<a name="assert-cookie-missing"></a>
#### assertCookieMissing

Перевіряє, що заданого зашифрованого cookie немає:

```php
$browser->assertCookieMissing($name);
```

<a name="assert-plain-cookie-missing"></a>
#### assertPlainCookieMissing

Перевіряє, що заданого незашифрованого cookie немає:

```php
$browser->assertPlainCookieMissing($name);
```

<a name="assert-cookie-value"></a>
#### assertCookieValue

Перевіряє, що зашифрований cookie має задане значення:

```php
$browser->assertCookieValue($name, $value);
```

<a name="assert-plain-cookie-value"></a>
#### assertPlainCookieValue

Перевіряє, що незашифрований cookie має задане значення:

```php
$browser->assertPlainCookieValue($name, $value);
```

<a name="assert-see"></a>
#### assertSee

Перевіряє, що заданий текст присутній на сторінці:

```php
$browser->assertSee($text);
```

<a name="assert-dont-see"></a>
#### assertDontSee

Перевіряє, що заданого тексту немає на сторінці:

```php
$browser->assertDontSee($text);
```

<a name="assert-see-in"></a>
#### assertSeeIn

Перевіряє, що заданий текст присутній усередині селектора:

```php
$browser->assertSeeIn($selector, $text);
```

<a name="assert-dont-see-in"></a>
#### assertDontSeeIn

Перевіряє, що заданого тексту немає всередині селектора:

```php
$browser->assertDontSeeIn($selector, $text);
```

<a name="assert-see-anything-in"></a>
#### assertSeeAnythingIn

Перевіряє, що всередині селектора є будь-який текст:

```php
$browser->assertSeeAnythingIn($selector);
```

<a name="assert-see-nothing-in"></a>
#### assertSeeNothingIn

Перевіряє, що всередині селектора немає жодного тексту:

```php
$browser->assertSeeNothingIn($selector);
```

<a name="assert-count"></a>
#### assertCount

Перевіряє, що елементи, які відповідають заданому селектору, трапляються вказану кількість разів:

```php
$browser->assertCount($selector, $count);
```

<a name="assert-script"></a>
#### assertScript

Перевіряє, що заданий вираз JavaScript обчислюється в задане значення:

```php
$browser->assertScript('window.isLoaded')
    ->assertScript('document.readyState', 'complete');
```

<a name="assert-source-has"></a>
#### assertSourceHas

Перевіряє, що заданий код присутній на сторінці:

```php
$browser->assertSourceHas($code);
```

<a name="assert-source-missing"></a>
#### assertSourceMissing

Перевіряє, що заданого коду немає на сторінці:

```php
$browser->assertSourceMissing($code);
```

<a name="assert-see-link"></a>
#### assertSeeLink

Перевіряє, що задане посилання присутнє на сторінці:

```php
$browser->assertSeeLink($linkText);
```

<a name="assert-dont-see-link"></a>
#### assertDontSeeLink

Перевіряє, що заданого посилання немає на сторінці:

```php
$browser->assertDontSeeLink($linkText);
```

<a name="assert-input-value"></a>
#### assertInputValue

Перевіряє, що задане поле введення має задане значення:

```php
$browser->assertInputValue($field, $value);
```

<a name="assert-input-value-is-not"></a>
#### assertInputValueIsNot

Перевіряє, що задане поле введення не має заданого значення:

```php
$browser->assertInputValueIsNot($field, $value);
```

<a name="assert-checked"></a>
#### assertChecked

Перевіряє, що заданий чекбокс позначено:

```php
$browser->assertChecked($field);
```

<a name="assert-not-checked"></a>
#### assertNotChecked

Перевіряє, що заданий чекбокс не позначено:

```php
$browser->assertNotChecked($field);
```

<a name="assert-indeterminate"></a>
#### assertIndeterminate

Перевіряє, що заданий чекбокс перебуває в невизначеному стані:

```php
$browser->assertIndeterminate($field);
```

<a name="assert-radio-selected"></a>
#### assertRadioSelected

Перевіряє, що задану радіокнопку обрано:

```php
$browser->assertRadioSelected($field, $value);
```

<a name="assert-radio-not-selected"></a>
#### assertRadioNotSelected

Перевіряє, що задану радіокнопку не обрано:

```php
$browser->assertRadioNotSelected($field, $value);
```

<a name="assert-selected"></a>
#### assertSelected

Перевіряє, що в заданому випадному списку обрано задане значення:

```php
$browser->assertSelected($field, $value);
```

<a name="assert-not-selected"></a>
#### assertNotSelected

Перевіряє, що в заданому випадному списку не обрано заданого значення:

```php
$browser->assertNotSelected($field, $value);
```

<a name="assert-select-has-options"></a>
#### assertSelectHasOptions

Перевіряє, що заданий масив значень доступний для вибору:

```php
$browser->assertSelectHasOptions($field, $values);
```

<a name="assert-select-missing-options"></a>
#### assertSelectMissingOptions

Перевіряє, що заданий масив значень недоступний для вибору:

```php
$browser->assertSelectMissingOptions($field, $values);
```

<a name="assert-select-has-option"></a>
#### assertSelectHasOption

Перевіряє, що задане значення доступне для вибору в заданому полі:

```php
$browser->assertSelectHasOption($field, $value);
```

<a name="assert-select-missing-option"></a>
#### assertSelectMissingOption

Перевіряє, що задане значення недоступне для вибору:

```php
$browser->assertSelectMissingOption($field, $value);
```

<a name="assert-value"></a>
#### assertValue

Перевіряє, що елемент, який відповідає заданому селектору, має задане значення:

```php
$browser->assertValue($selector, $value);
```

<a name="assert-value-is-not"></a>
#### assertValueIsNot

Перевіряє, що елемент, який відповідає заданому селектору, не має заданого значення:

```php
$browser->assertValueIsNot($selector, $value);
```

<a name="assert-attribute"></a>
#### assertAttribute

Перевіряє, що елемент, який відповідає заданому селектору, має задане значення у вказаному атрибуті:

```php
$browser->assertAttribute($selector, $attribute, $value);
```

<a name="assert-attribute-missing"></a>
#### assertAttributeMissing

Перевіряє, що в елемента, який відповідає заданому селектору, немає вказаного атрибута:

```php
$browser->assertAttributeMissing($selector, $attribute);
```

<a name="assert-attribute-contains"></a>
#### assertAttributeContains

Перевіряє, що елемент, який відповідає заданому селектору, містить задане значення у вказаному атрибуті:

```php
$browser->assertAttributeContains($selector, $attribute, $value);
```

<a name="assert-attribute-doesnt-contain"></a>
#### assertAttributeDoesntContain

Перевіряє, що елемент, який відповідає заданому селектору, не містить заданого значення у вказаному атрибуті:

```php
$browser->assertAttributeDoesntContain($selector, $attribute, $value);
```

<a name="assert-aria-attribute"></a>
#### assertAriaAttribute

Перевіряє, що елемент, який відповідає заданому селектору, має задане значення у вказаному атрибуті aria:

```php
$browser->assertAriaAttribute($selector, $attribute, $value);
```

Наприклад, для розмітки `<button aria-label="Add"></button>` ви можете зробити твердження щодо атрибута `aria-label` ось так:

```php
$browser->assertAriaAttribute('button', 'label', 'Add');
```

<a name="assert-data-attribute"></a>
#### assertDataAttribute

Перевіряє, що елемент, який відповідає заданому селектору, має задане значення у вказаному атрибуті data:

```php
$browser->assertDataAttribute($selector, $attribute, $value);
```

Наприклад, для розмітки `<tr id="row-1" data-content="attendees"></tr>` ви можете зробити твердження щодо атрибута `data-content` ось так:

```php
$browser->assertDataAttribute('#row-1', 'content', 'attendees');
```

<a name="assert-visible"></a>
#### assertVisible

Перевіряє, що елемент, який відповідає заданому селектору, видимий:

```php
$browser->assertVisible($selector);
```

<a name="assert-present"></a>
#### assertPresent

Перевіряє, що елемент, який відповідає заданому селектору, присутній у коді сторінки:

```php
$browser->assertPresent($selector);
```

<a name="assert-not-present"></a>
#### assertNotPresent

Перевіряє, що елемента, який відповідає заданому селектору, немає в коді сторінки:

```php
$browser->assertNotPresent($selector);
```

<a name="assert-missing"></a>
#### assertMissing

Перевіряє, що елемент, який відповідає заданому селектору, не видимий:

```php
$browser->assertMissing($selector);
```

<a name="assert-input-present"></a>
#### assertInputPresent

Перевіряє, що поле введення із заданим іменем присутнє:

```php
$browser->assertInputPresent($name);
```

<a name="assert-input-missing"></a>
#### assertInputMissing

Перевіряє, що поля введення із заданим іменем немає в коді сторінки:

```php
$browser->assertInputMissing($name);
```

<a name="assert-dialog-opened"></a>
#### assertDialogOpened

Перевіряє, що діалог JavaScript із заданим повідомленням було відкрито:

```php
$browser->assertDialogOpened($message);
```

<a name="assert-enabled"></a>
#### assertEnabled

Перевіряє, що задане поле увімкнене:

```php
$browser->assertEnabled($field);
```

<a name="assert-disabled"></a>
#### assertDisabled

Перевіряє, що задане поле вимкнене:

```php
$browser->assertDisabled($field);
```

<a name="assert-button-enabled"></a>
#### assertButtonEnabled

Перевіряє, що задана кнопка увімкнена:

```php
$browser->assertButtonEnabled($button);
```

<a name="assert-button-disabled"></a>
#### assertButtonDisabled

Перевіряє, що задана кнопка вимкнена:

```php
$browser->assertButtonDisabled($button);
```

<a name="assert-focused"></a>
#### assertFocused

Перевіряє, що задане поле у фокусі:

```php
$browser->assertFocused($field);
```

<a name="assert-not-focused"></a>
#### assertNotFocused

Перевіряє, що задане поле не у фокусі:

```php
$browser->assertNotFocused($field);
```

<a name="assert-authenticated"></a>
#### assertAuthenticated

Перевіряє, що користувач автентифікований:

```php
$browser->assertAuthenticated();
```

<a name="assert-guest"></a>
#### assertGuest

Перевіряє, що користувач не автентифікований:

```php
$browser->assertGuest();
```

<a name="assert-authenticated-as"></a>
#### assertAuthenticatedAs

Перевіряє, що користувач автентифікований як заданий користувач:

```php
$browser->assertAuthenticatedAs($user);
```

<a name="assert-vue"></a>
#### assertVue

Dusk дозволяє навіть робити твердження щодо стану даних [компонента Vue](https://vuejs.org). Уявіть, наприклад, що ваш застосунок містить такий компонент Vue:

    // HTML...

    <profile dusk="profile-component"></profile>

    // Component Definition...

    Vue.component('profile', {
        template: '<div>{{ user.name }}</div>',

        data: function () {
            return {
                user: {
                    name: 'Taylor'
                }
            };
        }
    });

Ви можете зробити твердження щодо стану компонента Vue ось так:

```php tab=Pest
test('vue', function () {
    $this->browse(function (Browser $browser) {
        $browser->visit('/')
            ->assertVue('user.name', 'Taylor', '@profile-component');
    });
});
```

```php tab=PHPUnit
/**
 * A basic Vue test example.
 */
public function test_vue(): void
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/')
            ->assertVue('user.name', 'Taylor', '@profile-component');
    });
}
```

<a name="assert-vue-is-not"></a>
#### assertVueIsNot

Перевіряє, що задана властивість даних компонента Vue не збігається із заданим значенням:

```php
$browser->assertVueIsNot($property, $value, $componentSelector = null);
```

<a name="assert-vue-contains"></a>
#### assertVueContains

Перевіряє, що задана властивість даних компонента Vue є масивом і містить задане значення:

```php
$browser->assertVueContains($property, $value, $componentSelector = null);
```

<a name="assert-vue-doesnt-contain"></a>
#### assertVueDoesntContain

Перевіряє, що задана властивість даних компонента Vue є масивом і не містить заданого значення:

```php
$browser->assertVueDoesntContain($property, $value, $componentSelector = null);
```

<a name="pages"></a>
## Сторінки

Іноді тести вимагають виконати послідовно кілька складних дій. Через це тести стає важче читати й розуміти. Сторінки Dusk дозволяють описати виразні дії, які потім можна виконати на заданій сторінці одним методом. Сторінки також дозволяють описати скорочення для поширених селекторів вашого застосунку або окремої сторінки.

<a name="generating-pages"></a>
### Генерування сторінок

Щоб згенерувати об'єкт сторінки, виконайте артизан-команду `dusk:page`. Усі об'єкти сторінок потраплять до каталогу `tests/Browser/Pages` вашого застосунку:

```shell
php artisan dusk:page Login
```

<a name="configuring-pages"></a>
### Налаштування сторінок

За замовчуванням сторінки мають три методи: `url`, `assert` та `elements`. Методи `url` та `assert` ми розглянемо зараз. Метод `elements` буде [докладніше розглянуто нижче](#shorthand-selectors).

<a name="the-url-method"></a>
#### Метод `url`

Метод `url` має повертати шлях URL, що представляє сторінку. Dusk використовуватиме цей URL, переходячи до сторінки у браузері:

```php
/**
 * Get the URL for the page.
 */
public function url(): string
{
    return '/login';
}
```

<a name="the-assert-method"></a>
#### Метод `assert`

Метод `assert` може містити будь-які твердження, потрібні, щоб переконатися, що браузер справді на заданій сторінці. Розміщувати щось у цьому методі не обов'язково; проте ви вільні робити такі твердження за бажання. Ці твердження виконуватимуться автоматично при переході до сторінки:

```php
/**
 * Assert that the browser is on the page.
 */
public function assert(Browser $browser): void
{
    $browser->assertPathIs($this->url());
}
```

<a name="navigating-to-pages"></a>
### Перехід до сторінок

Коли сторінку визначено, ви можете перейти до неї методом `visit`:

```php
use Tests\Browser\Pages\Login;

$browser->visit(new Login);
```

Іноді ви вже перебуваєте на заданій сторінці, і вам треба «завантажити» її селектори й методи до поточного контексту тесту. Так буває, коли ви натискаєте кнопку й вас перенаправляє на сторінку без явного переходу до неї. У такому разі скористайтеся методом `on`, щоб завантажити сторінку:

```php
use Tests\Browser\Pages\CreatePlaylist;

$browser->visit('/dashboard')
    ->clickLink('Create Playlist')
    ->on(new CreatePlaylist)
    ->assertSee('@create');
```

<a name="shorthand-selectors"></a>
### Скорочені селектори

Метод `elements` у класах сторінок дозволяє описати швидкі й легкі для запам'ятовування скорочення для будь-якого CSS-селектора на вашій сторінці. Наприклад, визначмо скорочення для поля «email» на сторінці входу застосунку:

```php
/**
 * Get the element shortcuts for the page.
 *
 * @return array<string, string>
 */
public function elements(): array
{
    return [
        '@email' => 'input[name=email]',
    ];
}
```

Коли скорочення визначено, ви можете використовувати скорочений селектор усюди, де зазвичай використовували б повний CSS-селектор:

```php
$browser->type('@email', 'taylor@laravel.com');
```

<a name="global-shorthand-selectors"></a>
#### Глобальні скорочені селектори

Після встановлення Dusk базовий клас `Page` потрапить до вашого каталогу `tests/Browser/Pages`. Цей клас містить метод `siteElements`, який дозволяє описати глобальні скорочені селектори, доступні на кожній сторінці вашого застосунку:

```php
/**
 * Get the global element shortcuts for the site.
 *
 * @return array<string, string>
 */
public static function siteElements(): array
{
    return [
        '@element' => '#selector',
    ];
}
```

<a name="page-methods"></a>
### Методи сторінок

Окрім стандартних методів сторінок, ви можете визначити додаткові методи, якими користуватиметеся у своїх тестах. Уявімо, наприклад, що ми будуємо застосунок для керування музикою. Поширеною дією на одній зі сторінок може бути створення плейлиста. Замість того щоб переписувати логіку створення плейлиста в кожному тесті, ви можете визначити метод `createPlaylist` у класі сторінки:

```php
<?php

namespace Tests\Browser\Pages;

use Laravel\Dusk\Browser;
use Laravel\Dusk\Page;

class Dashboard extends Page
{
    // Other page methods...

    /**
     * Create a new playlist.
     */
    public function createPlaylist(Browser $browser, string $name): void
    {
        $browser->type('name', $name)
            ->check('share')
            ->press('Create Playlist');
    }
}
```

Коли метод визначено, ви можете скористатися ним у будь-якому тесті, що використовує цю сторінку. Екземпляр браузера автоматично передається першим аргументом до власних методів сторінки:

```php
use Tests\Browser\Pages\Dashboard;

$browser->visit(new Dashboard)
    ->createPlaylist('My Playlist')
    ->assertSee('My Playlist');
```

<a name="components"></a>
## Компоненти

Компоненти схожі на «об'єкти сторінок» Dusk, але призначені для частин UI та функціональності, які перевикористовуються в усьому застосунку, - наприклад, панель навігації чи вікно сповіщень. Тому компоненти не прив'язані до конкретних URL.

<a name="generating-components"></a>
### Генерування компонентів

Щоб згенерувати компонент, виконайте артизан-команду `dusk:component`. Нові компоненти потрапляють до каталогу `tests/Browser/Components`:

```shell
php artisan dusk:component DatePicker
```

Як показано вище, «вибір дати» - приклад компонента, який може траплятися в усьому застосунку на різних сторінках. Писати вручну логіку автоматизації браузера для вибору дати в десятках тестів стає обтяжливо. Натомість ми можемо описати компонент Dusk, який представлятиме вибір дати, і інкапсулювати цю логіку в ньому:

```php
<?php

namespace Tests\Browser\Components;

use Laravel\Dusk\Browser;
use Laravel\Dusk\Component as BaseComponent;

class DatePicker extends BaseComponent
{
    /**
     * Get the root selector for the component.
     */
    public function selector(): string
    {
        return '.date-picker';
    }

    /**
     * Assert that the browser page contains the component.
     */
    public function assert(Browser $browser): void
    {
        $browser->assertVisible($this->selector());
    }

    /**
     * Get the element shortcuts for the component.
     *
     * @return array<string, string>
     */
    public function elements(): array
    {
        return [
            '@date-field' => 'input.datepicker-input',
            '@year-list' => 'div > div.datepicker-years',
            '@month-list' => 'div > div.datepicker-months',
            '@day-list' => 'div > div.datepicker-days',
        ];
    }

    /**
     * Select the given date.
     */
    public function selectDate(Browser $browser, int $year, int $month, int $day): void
    {
        $browser->click('@date-field')
            ->within('@year-list', function (Browser $browser) use ($year) {
                $browser->click($year);
            })
            ->within('@month-list', function (Browser $browser) use ($month) {
                $browser->click($month);
            })
            ->within('@day-list', function (Browser $browser) use ($day) {
                $browser->click($day);
            });
    }
}
```

<a name="using-components"></a>
### Використання компонентів

Коли компонент визначено, ми можемо легко обрати дату у виборі дати з будь-якого тесту. І якщо логіка вибору дати зміниться, нам треба буде оновити лише компонент:

```php tab=Pest
<?php

use Illuminate\Foundation\Testing\DatabaseMigrations;
use Tests\Browser\Components\DatePicker;

pest()->use(DatabaseMigrations::class);

test('basic example', function () {
    $this->browse(function (Browser $browser) {
        $browser->visit('/')
            ->within(new DatePicker, function (Browser $browser) {
                $browser->selectDate(2019, 1, 30);
            })
            ->assertSee('January');
    });
});
```

```php tab=PHPUnit
<?php

namespace Tests\Browser;

use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\Browser\Components\DatePicker;
use Tests\DuskTestCase;

class ExampleTest extends DuskTestCase
{
    /**
     * A basic component test example.
     */
    public function test_basic_example(): void
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/')
                ->within(new DatePicker, function (Browser $browser) {
                    $browser->selectDate(2019, 1, 30);
                })
                ->assertSee('January');
        });
    }
}
```

Метод `component` дозволяє отримати екземпляр браузера, обмежений заданим компонентом:

```php
$datePicker = $browser->component(new DatePickerComponent);

$datePicker->selectDate(2019, 1, 30);

$datePicker->assertSee('January');
```

<a name="continuous-integration"></a>
## Неперервна інтеграція

> [!WARNING]
> Більшість конфігурацій неперервної інтеграції для Dusk очікують, що ваш застосунок Laravel віддається вбудованим сервером розробки PHP на порту 8000. Тому, перш ніж рухатися далі, переконайтеся, що у вашому середовищі неперервної інтеграції змінна оточення `APP_URL` має значення `http://127.0.0.1:8000`.

<a name="running-tests-on-heroku-ci"></a>
### Heroku CI

Щоб запускати тести Dusk на [Heroku CI](https://www.heroku.com/continuous-integration), додайте до свого файлу `app.json` для Heroku такий buildpack для Google Chrome і скрипти:

```json
{
  "environments": {
    "test": {
      "buildpacks": [
        { "url": "heroku/php" },
        { "url": "https://github.com/heroku/heroku-buildpack-chrome-for-testing" }
      ],
      "scripts": {
        "test-setup": "cp .env.testing .env",
        "test": "nohup bash -c './vendor/laravel/dusk/bin/chromedriver-linux --port=9515 > /dev/null 2>&1 &' && nohup bash -c 'php artisan serve --no-reload > /dev/null 2>&1 &' && php artisan dusk"
      }
    }
  }
}
```

<a name="running-tests-on-travis-ci"></a>
### Travis CI

Щоб запускати ваші тести Dusk на [Travis CI](https://travis-ci.org), скористайтеся такою конфігурацією `.travis.yml`. Оскільки Travis CI не є графічним середовищем, нам знадобиться кілька додаткових кроків, щоб запустити браузер Chrome. Крім того, ми скористаємося `php artisan serve`, щоб запустити вбудований вебсервер PHP:

```yaml
language: php

php:
  - 8.2

addons:
  chrome: stable

install:
  - cp .env.testing .env
  - travis_retry composer install --no-interaction --prefer-dist
  - php artisan key:generate
  - php artisan dusk:chrome-driver

before_script:
  - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost &
  - php artisan serve --no-reload &

script:
  - php artisan dusk
```

<a name="running-tests-on-github-actions"></a>
### GitHub Actions

Якщо ви запускаєте свої тести Dusk через [GitHub Actions](https://github.com/features/actions), можете взяти за відправну точку такий конфігураційний файл. Як і у випадку з TravisCI, ми скористаємося командою `php artisan serve`, щоб запустити вбудований вебсервер PHP:

```yaml
name: CI
on: [push]
jobs:

  dusk-php:
    runs-on: ubuntu-latest
    env:
      APP_URL: "http://127.0.0.1:8000"
      DB_USERNAME: root
      DB_PASSWORD: root
      MAIL_MAILER: log
    steps:
      - uses: actions/checkout@v5
      - name: Prepare The Environment
        run: cp .env.example .env
      - name: Create Database
        run: |
          sudo systemctl start mysql
          mysql --user="root" --password="root" -e "CREATE DATABASE \`my-database\` character set UTF8mb4 collate utf8mb4_bin;"
      - name: Install Composer Dependencies
        run: composer install --no-progress --prefer-dist --optimize-autoloader
      - name: Generate Application Key
        run: php artisan key:generate
      - name: Upgrade Chrome Driver
        run: php artisan dusk:chrome-driver --detect
      - name: Start Chrome Driver
        run: ./vendor/laravel/dusk/bin/chromedriver-linux --port=9515 &
      - name: Run Laravel Server
        run: php artisan serve --no-reload &
      - name: Run Dusk Tests
        run: php artisan dusk
      - name: Upload Screenshots
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: screenshots
          path: tests/Browser/screenshots
      - name: Upload Console Logs
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: console
          path: tests/Browser/console
```

<a name="running-tests-on-chipper-ci"></a>
### Chipper CI

Якщо ви запускаєте свої тести Dusk через [Chipper CI](https://chipperci.com), можете взяти за відправну точку такий конфігураційний файл. Ми скористаємося вбудованим сервером PHP, щоб запустити Laravel і слухати запити:

```yaml
# file .chipperci.yml
version: 1

environment:
  php: 8.2
  node: 16

# Include Chrome in the build environment
services:
  - dusk

# Build all commits
on:
   push:
      branches: .*

pipeline:
  - name: Setup
    cmd: |
      cp -v .env.example .env
      composer install --no-interaction --prefer-dist --optimize-autoloader
      php artisan key:generate

      # Create a dusk env file, ensuring APP_URL uses BUILD_HOST
      cp -v .env .env.dusk.ci
      sed -i "s@APP_URL=.*@APP_URL=http://$BUILD_HOST:8000@g" .env.dusk.ci

  - name: Compile Assets
    cmd: |
      npm ci --no-audit
      npm run build

  - name: Browser Tests
    cmd: |
      php -S [::0]:8000 -t public 2>server.log &
      sleep 2
      php artisan dusk:chrome-driver $CHROME_DRIVER
      php artisan dusk --env=ci
```

Щоб дізнатися більше про запуск тестів Dusk на Chipper CI, зокрема про роботу з базами даних, зверніться до [офіційної документації Chipper CI](https://chipperci.com/docs/testing/laravel-dusk-new/).

---

Джерело: https://laravelukraine.com/docs/13.x/database-testing

# Тестування бази даних

- [Вступ](#introduction)
    - [Скидання бази даних після кожного тесту](#resetting-the-database-after-each-test)
- [Фабрики моделей](#model-factories)
- [Запуск сідерів](#running-seeders)
- [Доступні твердження](#available-assertions)

<a name="introduction"></a>
## Вступ

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

<a name="resetting-the-database-after-each-test"></a>
### Скидання бази даних після кожного тесту

Перш ніж рухатися далі, обговорімо, як скидати вашу базу даних після кожного тесту, щоб дані з попереднього тесту не заважали наступним. Про це подбає трейт `Illuminate\Foundation\Testing\RefreshDatabase`, що входить до Laravel. Просто застосуйте цей трейт до свого тестового класу:

```php tab=Pest
<?php

use Illuminate\Foundation\Testing\RefreshDatabase;

pest()->use(RefreshDatabase::class);

test('basic example', function () {
    $response = $this->get('/');

    // ...
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    /**
     * A basic functional test example.
     */
    public function test_basic_example(): void
    {
        $response = $this->get('/');

        // ...
    }
}
```

Трейт `Illuminate\Foundation\Testing\RefreshDatabase` не мігрує вашу базу даних, якщо схема актуальна. Натомість він лише виконує тест у межах транзакції бази даних. Тому будь-які записи, додані до бази тест-кейсами, які не використовують цей трейт, можуть залишитися в базі.

Якщо ви хочете повністю скинути базу даних, скористайтеся натомість трейтами `Illuminate\Foundation\Testing\DatabaseMigrations` чи `Illuminate\Foundation\Testing\DatabaseTruncation`. Проте обидва ці варіанти значно повільніші за трейт `RefreshDatabase`.

<a name="model-factories"></a>
## Фабрики моделей

Під час тестування вам може знадобитися вставити в базу кілька записів перед виконанням тесту. Замість того щоб вручну вказувати значення кожного стовпця при створенні цих тестових даних, Laravel дозволяє описати набір атрибутів за замовчуванням для кожної з ваших [моделей Eloquent](/docs/{{version}}/eloquent) через [фабрики моделей](/docs/{{version}}/eloquent-factories).

Щоб дізнатися більше про створення й використання фабрик моделей, зверніться до повної [документації про фабрики моделей](/docs/{{version}}/eloquent-factories). Коли фабрику моделі визначено, ви можете скористатися нею у своєму тесті, щоб створювати моделі:

```php tab=Pest
use App\Models\User;

test('models can be instantiated', function () {
    $user = User::factory()->create();

    // ...
});
```

```php tab=PHPUnit
use App\Models\User;

public function test_models_can_be_instantiated(): void
{
    $user = User::factory()->create();

    // ...
}
```

<a name="running-seeders"></a>
## Запуск сідерів

Якщо ви хочете скористатися [сідерами бази даних](/docs/{{version}}/seeding), щоб наповнити базу під час функціонального тесту, викличте метод `seed`. За замовчуванням метод `seed` виконає `DatabaseSeeder`, який має запустити всі інші ваші сідери. Або ж ви можете передати методу `seed` ім'я конкретного класу сідера:

```php tab=Pest
<?php

use Database\Seeders\OrderStatusSeeder;
use Database\Seeders\TransactionStatusSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

pest()->use(RefreshDatabase::class);

test('orders can be created', function () {
    // Run the DatabaseSeeder...
    $this->seed();

    // Run a specific seeder...
    $this->seed(OrderStatusSeeder::class);

    // ...

    // Run an array of specific seeders...
    $this->seed([
        OrderStatusSeeder::class,
        TransactionStatusSeeder::class,
        // ...
    ]);
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Database\Seeders\OrderStatusSeeder;
use Database\Seeders\TransactionStatusSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    /**
     * Test creating a new order.
     */
    public function test_orders_can_be_created(): void
    {
        // Run the DatabaseSeeder...
        $this->seed();

        // Run a specific seeder...
        $this->seed(OrderStatusSeeder::class);

        // ...

        // Run an array of specific seeders...
        $this->seed([
            OrderStatusSeeder::class,
            TransactionStatusSeeder::class,
            // ...
        ]);
    }
}
```

Або ж ви можете вказати Laravel автоматично наповнювати базу перед кожним тестом, що використовує трейт `RefreshDatabase`. Для цього додайте атрибут `Seed` до вашого базового тестового класу:

```php
<?php

namespace Tests;

use Illuminate\Foundation\Testing\Attributes\Seed;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

#[Seed]
abstract class TestCase extends BaseTestCase
{
}
```

Коли присутній атрибут `Seed`, перед кожним тестом, що використовує трейт `RefreshDatabase`, буде запущено клас `Database\Seeders\DatabaseSeeder`. Проте ви можете вказати конкретний сідер, який слід виконати, застосувавши до вашого тестового класу атрибут `Seeder`:

```php
<?php

namespace Tests\Feature;

use Database\Seeders\OrderStatusSeeder;
use Illuminate\Foundation\Testing\Attributes\Seeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

#[Seeder(OrderStatusSeeder::class)]
class OrderTest extends TestCase
{
    use RefreshDatabase;

    // ...
}
```

<a name="available-assertions"></a>
## Доступні твердження

Laravel надає кілька тверджень щодо бази даних для ваших функціональних тестів на [Pest](https://pestphp.com) чи [PHPUnit](https://phpunit.de). Кожне з них ми розглянемо нижче.

<a name="assert-database-count"></a>
#### assertDatabaseCount

Перевіряє, що таблиця в базі даних містить задану кількість записів:

```php
$this->assertDatabaseCount('users', 5);
```

<a name="assert-database-empty"></a>
#### assertDatabaseEmpty

Перевіряє, що таблиця в базі даних не містить записів:

```php
$this->assertDatabaseEmpty('users');
```

<a name="assert-database-has"></a>
#### assertDatabaseHas

Перевіряє, що таблиця в базі даних містить записи, які відповідають заданим умовам запиту ключ / значення:

```php
$this->assertDatabaseHas('users', [
    'email' => 'sally@example.com',
]);
```

<a name="assert-database-missing"></a>
#### assertDatabaseMissing

Перевіряє, що таблиця в базі даних не містить записів, які відповідають заданим умовам запиту ключ / значення:

```php
$this->assertDatabaseMissing('users', [
    'email' => 'sally@example.com',
]);
```

<a name="assert-deleted"></a>
#### assertSoftDeleted

Метод `assertSoftDeleted` дозволяє перевірити, що задану модель Eloquent було «м'яко видалено»:

```php
$this->assertSoftDeleted($user);
```

<a name="assert-not-deleted"></a>
#### assertNotSoftDeleted

Метод `assertNotSoftDeleted` дозволяє перевірити, що задану модель Eloquent не було «м'яко видалено»:

```php
$this->assertNotSoftDeleted($user);
```

<a name="assert-model-exists"></a>
#### assertModelExists

Перевіряє, що задана модель чи колекція моделей існує в базі даних:

```php
use App\Models\User;

$user = User::factory()->create();

$this->assertModelExists($user);
```

<a name="assert-model-missing"></a>
#### assertModelMissing

Перевіряє, що заданої моделі чи колекції моделей немає в базі даних:

```php
use App\Models\User;

$user = User::factory()->create();

$user->delete();

$this->assertModelMissing($user);
```

<a name="expects-database-query-count"></a>
#### expectsDatabaseQueryCount

Метод `expectsDatabaseQueryCount` можна викликати на початку тесту, щоб указати загальну кількість запитів до бази даних, яку ви очікуєте під час прогону. Якщо фактична кількість виконаних запитів не збігається точно з очікуваною, тест провалиться:

```php
$this->expectsDatabaseQueryCount(5);

// Test...
```

---

Джерело: https://laravelukraine.com/docs/13.x/mocking

# Мокування

- [Вступ](#introduction)
- [Мокування об'єктів](#mocking-objects)
- [Мокування фасадів](#mocking-facades)
    - [Шпигуни фасадів](#facade-spies)
- [Робота з часом](#interacting-with-time)

<a name="introduction"></a>
## Вступ

Тестуючи застосунки Laravel, ви можете захотіти «замокати» певні частини вашого застосунку, щоб вони не виконувалися насправді під час конкретного тесту. Наприклад, тестуючи контролер, який відправляє подію, ви можете замокати слухачів події, щоб вони не виконувалися під час тесту. Це дозволяє перевірити лише HTTP-відповідь контролера, не переймаючись виконанням слухачів, адже їх можна протестувати окремим тест-кейсом.

Laravel надає корисні методи для мокування подій, завдань та інших фасадів одразу з коробки. Ці хелпери здебільшого є зручним шаром над Mockery, тож вам не доведеться вручну писати складні виклики його методів.

<a name="mocking-objects"></a>
## Мокування об'єктів

Мокуючи об'єкт, який буде впроваджено у ваш застосунок через [сервіс-контейнер](/docs/{{version}}/container) Laravel, вам треба прив'язати замокований екземпляр до контейнера як прив'язку `instance`. Це вкаже контейнеру використовувати ваш замокований екземпляр об'єкта замість того, щоб конструювати об'єкт самому:

```php tab=Pest
use App\Service;
use Mockery;
use Mockery\MockInterface;

test('something can be mocked', function () {
    $this->instance(
        Service::class,
        Mockery::mock(Service::class, function (MockInterface $mock) {
            $mock->expects('process');
        })
    );
});
```

```php tab=PHPUnit
use App\Service;
use Mockery;
use Mockery\MockInterface;

public function test_something_can_be_mocked(): void
{
    $this->instance(
        Service::class,
        Mockery::mock(Service::class, function (MockInterface $mock) {
            $mock->expects('process');
        })
    );
}
```

Щоб зробити це зручніше, скористайтеся методом `mock`, який надає базовий клас тест-кейса Laravel. Наприклад, наступний приклад рівносильний наведеному вище:

```php
use App\Service;
use Mockery\MockInterface;

$mock = $this->mock(Service::class, function (MockInterface $mock) {
    $mock->expects('process');
});
```

Коли вам треба замокати лише кілька методів об'єкта, скористайтеся методом `partialMock`. Методи, які не замоковані, при виклику виконуватимуться як зазвичай:

```php
use App\Service;
use Mockery\MockInterface;

$mock = $this->partialMock(Service::class, function (MockInterface $mock) {
    $mock->expects('process');
});
```

Так само, якщо ви хочете [шпигувати](http://docs.mockery.io/en/latest/reference/spies.html) за об'єктом, базовий клас тест-кейса Laravel пропонує метод `spy` - зручну обгортку над методом `Mockery::spy`. Шпигуни схожі на моки; проте шпигуни записують усі взаємодії між собою та кодом, який тестується, дозволяючи робити твердження вже після виконання коду:

```php
use App\Service;

$spy = $this->spy(Service::class);

// ...

$spy->shouldHaveReceived('process');
```

<a name="mocking-facades"></a>
## Мокування фасадів

На відміну від традиційних викликів статичних методів, [фасади](/docs/{{version}}/facades) (зокрема й [фасади реального часу](/docs/{{version}}/facades#real-time-facades)) можна мокати. Це велика перевага над традиційними статичними методами, яка дає вам таку саму тестованість, як і при звичайному впровадженні залежностей. Під час тестування вам часто може знадобитися замокати виклик фасада Laravel, що відбувається в одному з ваших контролерів. Розгляньмо, наприклад, таку дію контролера:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Cache;

class UserController extends Controller
{
    /**
     * Retrieve a list of all users of the application.
     */
    public function index(): array
    {
        $value = Cache::get('key');

        return [
            // ...
        ];
    }
}
```

Ми можемо замокати виклик фасада `Cache` методом `expects`, який поверне екземпляр мока [Mockery](https://github.com/padraic/mockery). Оскільки фасади насправді розв'язуються й керуються [сервіс-контейнером](/docs/{{version}}/container) Laravel, вони куди тестованіші за звичайний статичний клас. Наприклад, замокаймо наш виклик методу `get` фасада `Cache`:

```php tab=Pest
<?php

use Illuminate\Support\Facades\Cache;

test('get index', function () {
    Cache::expects('get')
        ->with('key')
        ->andReturn('value');

    $response = $this->get('/users');

    // ...
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Illuminate\Support\Facades\Cache;
use Tests\TestCase;

class UserControllerTest extends TestCase
{
    public function test_get_index(): void
    {
        Cache::expects('get')
            ->with('key')
            ->andReturn('value');

        $response = $this->get('/users');

        // ...
    }
}
```

> [!WARNING]
> Вам не слід мокати фасад `Request`. Натомість передавайте потрібні дані до [методів тестування HTTP](/docs/{{version}}/http-tests) на кшталт `get` і `post` під час прогону тесту. Так само замість мокування фасада `Config` викликайте у своїх тестах метод `Config::set`.

<a name="facade-spies"></a>
### Шпигуни фасадів

Якщо ви хочете [шпигувати](http://docs.mockery.io/en/latest/reference/spies.html) за фасадом, викличте на ньому метод `spy`. Шпигуни схожі на моки; проте шпигуни записують усі взаємодії між собою та кодом, який тестується, дозволяючи робити твердження вже після виконання коду:

```php tab=Pest
<?php

use Illuminate\Support\Facades\Cache;

test('values are stored in cache', function () {
    Cache::spy();

    $response = $this->get('/');

    $response->assertStatus(200);

    Cache::shouldHaveReceived('put')->with('name', 'Taylor', 10);
});
```

```php tab=PHPUnit
use Illuminate\Support\Facades\Cache;

public function test_values_are_stored_in_cache(): void
{
    Cache::spy();

    $response = $this->get('/');

    $response->assertStatus(200);

    Cache::shouldHaveReceived('put')->with('name', 'Taylor', 10);
}
```

<a name="interacting-with-time"></a>
## Робота з часом

Під час тестування вам іноді може знадобитися змінити час, який повертають хелпери на кшталт `now` чи `Illuminate\Support\Carbon::now()`. На щастя, базовий клас функціональних тестів Laravel містить хелпери, що дозволяють маніпулювати поточним часом:

```php tab=Pest
test('time can be manipulated', function () {
    // Travel into the future...
    $this->travel(5)->milliseconds();
    $this->travel(5)->seconds();
    $this->travel(5)->minutes();
    $this->travel(5)->hours();
    $this->travel(5)->days();
    $this->travel(5)->weeks();
    $this->travel(5)->years();

    // Travel into the past...
    $this->travel(-5)->hours();

    // Travel to an explicit time...
    $this->travelTo(now()->minus(hours: 6));

    // Return back to the present time...
    $this->travelBack();
});
```

```php tab=PHPUnit
public function test_time_can_be_manipulated(): void
{
    // Travel into the future...
    $this->travel(5)->milliseconds();
    $this->travel(5)->seconds();
    $this->travel(5)->minutes();
    $this->travel(5)->hours();
    $this->travel(5)->days();
    $this->travel(5)->weeks();
    $this->travel(5)->years();

    // Travel into the past...
    $this->travel(-5)->hours();

    // Travel to an explicit time...
    $this->travelTo(now()->minus(hours: 6));

    // Return back to the present time...
    $this->travelBack();
}
```

Ви також можете передати різним методам подорожі в часі замикання. Його буде викликано із часом, замороженим на вказаній позначці. Коли замикання виконається, час піде далі як зазвичай:

```php
$this->travel(5)->days(function () {
    // Test something five days into the future...
});

$this->travelTo(now()->minus(days: 10), function () {
    // Test something during a given moment...
});
```

Метод `freezeTime` дозволяє заморозити поточний час. Так само метод `freezeSecond` заморозить поточний час, але на початку поточної секунди:

```php
use Illuminate\Support\Carbon;

// Freeze time and resume normal time after executing closure...
$this->freezeTime(function (Carbon $time) {
    // ...
});

// Freeze time at the current second and resume normal time after executing closure...
$this->freezeSecond(function (Carbon $time) {
    // ...
})
```

Як і слід очікувати, всі розглянуті вище методи передусім корисні для тестування поведінки застосунку, чутливої до часу, - наприклад, блокування неактивних дописів на форумі:

```php tab=Pest
use App\Models\Thread;

test('forum threads lock after one week of inactivity', function () {
    $thread = Thread::factory()->create();

    $this->travel(1)->week();

    expect($thread->isLockedByInactivity())->toBeTrue();
});
```

```php tab=PHPUnit
use App\Models\Thread;

public function test_forum_threads_lock_after_one_week_of_inactivity()
{
    $thread = Thread::factory()->create();

    $this->travel(1)->week();

    $this->assertTrue($thread->isLockedByInactivity());
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/billing

# Laravel Cashier (Stripe)

- [Вступ](#introduction)
- [Оновлення Cashier](#upgrading-cashier)
- [Встановлення](#installation)
- [Конфігурація](#configuration)
    - [Модель з білінгом](#billable-model)
    - [API-ключі](#api-keys)
    - [Конфігурація валюти](#currency-configuration)
    - [Конфігурація податків](#tax-configuration)
    - [Логування](#logging)
    - [Використання власних моделей](#using-custom-models)
- [Швидкий старт](#quickstart)
    - [Продаж продуктів](#quickstart-selling-products)
    - [Продаж підписок](#quickstart-selling-subscriptions)
- [Клієнти](#customers)
    - [Отримання клієнтів](#retrieving-customers)
    - [Створення клієнтів](#creating-customers)
    - [Оновлення клієнтів](#updating-customers)
    - [Баланси](#balances)
    - [Податкові номери](#tax-ids)
    - [Синхронізація даних клієнта зі Stripe](#syncing-customer-data-with-stripe)
    - [Портал білінгу](#billing-portal)
- [Платіжні методи](#payment-methods)
    - [Збереження платіжних методів](#storing-payment-methods)
    - [Отримання платіжних методів](#retrieving-payment-methods)
    - [Наявність платіжного методу](#payment-method-presence)
    - [Оновлення платіжного методу за замовчуванням](#updating-the-default-payment-method)
    - [Додавання платіжних методів](#adding-payment-methods)
    - [Видалення платіжних методів](#deleting-payment-methods)
- [Підписки](#subscriptions)
    - [Створення підписок](#creating-subscriptions)
    - [Перевірка стану підписки](#checking-subscription-status)
    - [Зміна цін](#changing-prices)
    - [Кількість у підписці](#subscription-quantity)
    - [Підписки з кількома продуктами](#subscriptions-with-multiple-products)
    - [Кілька підписок](#multiple-subscriptions)
    - [Білінг за використанням](#usage-based-billing)
    - [Податки на підписки](#subscription-taxes)
    - [Опорна дата підписки](#subscription-anchor-date)
    - [Скасування підписок](#cancelling-subscriptions)
    - [Відновлення підписок](#resuming-subscriptions)
- [Пробні періоди підписок](#subscription-trials)
    - [З платіжним методом наперед](#with-payment-method-up-front)
    - [Без платіжного методу наперед](#without-payment-method-up-front)
    - [Продовження пробних періодів](#extending-trials)
- [Обробка вебхуків Stripe](#handling-stripe-webhooks)
    - [Визначення обробників подій вебхуків](#defining-webhook-event-handlers)
    - [Перевірка підписів вебхуків](#verifying-webhook-signatures)
- [Разові списання](#single-charges)
    - [Просте списання](#simple-charge)
    - [Списання з рахунком](#charge-with-invoice)
    - [Створення Payment Intent](#creating-payment-intents)
    - [Повернення коштів за списаннями](#refunding-charges)
- [Рахунки](#invoices)
    - [Отримання рахунків](#retrieving-invoices)
    - [Майбутні рахунки](#upcoming-invoices)
    - [Попередній перегляд рахунків за підпискою](#previewing-subscription-invoices)
    - [Генерація PDF-рахунків](#generating-invoice-pdfs)
- [Checkout](#checkout)
    - [Оформлення продуктів](#product-checkouts)
    - [Оформлення разових списань](#single-charge-checkouts)
    - [Оформлення підписок](#subscription-checkouts)
    - [Збір податкових номерів](#collecting-tax-ids)
    - [Гостьове оформлення](#guest-checkouts)
- [Обробка невдалих платежів](#handling-failed-payments)
    - [Підтвердження платежів](#confirming-payments)
- [Strong Customer Authentication (SCA)](#strong-customer-authentication)
    - [Платежі, що потребують додаткового підтвердження](#payments-requiring-additional-confirmation)
    - [Сповіщення про позасесійні платежі](#off-session-payment-notifications)
- [Stripe SDK](#stripe-sdk)
- [Тестування](#testing)

<a name="introduction"></a>
## Вступ

[Laravel Cashier Stripe](https://github.com/laravel/cashier-stripe) дає виразний, плавний інтерфейс до сервісів білінгу підписок [Stripe](https://stripe.com). Він бере на себе майже весь шаблонний код білінгу підписок, який ви боїтеся писати. Окрім базового керування підписками, Cashier уміє працювати з купонами, змінювати підписки, обробляти «кількості» в підписках, пільгові періоди після скасування і навіть генерувати PDF-рахунки.

<a name="upgrading-cashier"></a>
## Оновлення Cashier

Оновлюючись до нової версії Cashier, важливо уважно переглянути [посібник з оновлення](https://github.com/laravel/cashier-stripe/blob/16.x/UPGRADE.md).

> [!WARNING]
> Щоб уникнути ламких змін, Cashier використовує фіксовану версію API Stripe. Cashier 16 використовує версію API Stripe `2025-06-30.basil`. Версію API Stripe оновлюватимуть у мінорних випусках, щоб скористатися новими можливостями й покращеннями Stripe.

<a name="installation"></a>
## Встановлення

Спершу встановіть пакет Cashier для Stripe за допомогою менеджера пакетів Composer:

```shell
composer require laravel/cashier
```

Після встановлення пакета опублікуйте міграції Cashier артизан-командою `vendor:publish`:

```shell
php artisan vendor:publish --tag="cashier-migrations"
```

Далі виконайте міграції своєї бази даних:

```shell
php artisan migrate
```

Міграції Cashier додадуть кілька колонок до вашої таблиці `users`. Вони також створять нову таблицю `subscriptions` для зберігання всіх підписок ваших клієнтів і таблицю `subscription_items` для підписок з кількома цінами.

За бажанням ви також можете опублікувати конфігураційний файл Cashier артизан-командою `vendor:publish`:

```shell
php artisan vendor:publish --tag="cashier-config"
```

Насамкінець, щоб Cashier належно обробляв усі події Stripe, не забудьте [налаштувати обробку вебхуків Cashier](#handling-stripe-webhooks).

> [!WARNING]
> Stripe рекомендує, щоб будь-яка колонка для зберігання ідентифікаторів Stripe була чутливою до регістру. Тому вам слід подбати, щоб для колонки `stripe_id` було задано порівняння `utf8_bin`, якщо ви використовуєте MySQL. Докладніше про це - у [документації Stripe](https://stripe.com/docs/upgrades#what-changes-does-stripe-consider-to-be-backwards-compatible).

<a name="configuration"></a>
## Конфігурація

<a name="billable-model"></a>
### Модель з білінгом

Перш ніж користуватися Cashier, додайте трейт `Billable` до визначення своєї моделі з білінгом. Зазвичай це буде модель `App\Models\User`. Цей трейт надає різні методи, що дозволяють виконувати типові завдання білінгу: створювати підписки, застосовувати купони й оновлювати інформацію про платіжний метод:

```php
use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}
```

Cashier припускає, що вашою моделлю з білінгом буде клас `App\Models\User`, який постачається з Laravel. Якщо ви хочете змінити це, вкажіть іншу модель методом `useCustomerModel`. Зазвичай цей метод слід викликати в методі `boot` вашого класу `AppServiceProvider`:

```php
use App\Models\Cashier\User;
use Laravel\Cashier\Cashier;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Cashier::useCustomerModel(User::class);
}
```

> [!WARNING]
> Якщо ви використовуєте модель, відмінну від наданої Laravel моделі `App\Models\User`, вам потрібно буде опублікувати й змінити надані [міграції Cashier](#installation) відповідно до імені таблиці вашої альтернативної моделі.

<a name="api-keys"></a>
### API-ключі

Далі вам слід налаштувати свої API-ключі Stripe у файлі `.env` вашого застосунку. Отримати API-ключі Stripe можна з панелі керування Stripe:

```ini
STRIPE_KEY=your-stripe-key
STRIPE_SECRET=your-stripe-secret
STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret
```

> [!WARNING]
> Вам слід подбати, щоб змінну оточення `STRIPE_WEBHOOK_SECRET` було визначено у файлі `.env` вашого застосунку, оскільки вона використовується, щоб пересвідчитися, що вхідні вебхуки справді надходять від Stripe.

<a name="currency-configuration"></a>
### Конфігурація валюти

Валюта Cashier за замовчуванням - долар США (USD). Ви можете змінити валюту за замовчуванням, встановивши змінну оточення `CASHIER_CURRENCY` у файлі `.env` вашого застосунку:

```ini
CASHIER_CURRENCY=eur
```

Окрім налаштування валюти Cashier, ви також можете вказати локаль, яка використовуватиметься для форматування грошових значень для показу в рахунках. Внутрішньо Cashier використовує [клас PHP `NumberFormatter`](https://www.php.net/manual/en/class.numberformatter.php), щоб задати локаль валюти:

```ini
CASHIER_CURRENCY_LOCALE=nl_BE
```

> [!WARNING]
> Щоб використовувати локалі, відмінні від `en`, переконайтеся, що PHP-розширення `ext-intl` встановлено й налаштовано на вашому сервері.

<a name="tax-configuration"></a>
### Конфігурація податків

Завдяки [Stripe Tax](https://stripe.com/tax) можна автоматично обчислювати податки для всіх рахунків, згенерованих Stripe. Увімкнути автоматичне обчислення податків можна, викликавши метод `calculateTaxes` у методі `boot` класу `App\Providers\AppServiceProvider` вашого застосунку:

```php
use Laravel\Cashier\Cashier;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Cashier::calculateTaxes();
}
```

Щойно обчислення податків увімкнено, усі нові підписки й усі разові рахунки, які будуть згенеровані, отримають автоматичне обчислення податків.

Щоб ця можливість працювала належно, платіжні дані вашого клієнта - ім'я, адреса й податковий номер - мають бути синхронізовані зі Stripe. Для цього ви можете скористатися методами [синхронізації даних клієнта](#syncing-customer-data-with-stripe) і [податкових номерів](#tax-ids), які пропонує Cashier.

<a name="logging"></a>
### Логування

Cashier дозволяє вказати канал логування, який використовуватиметься для запису фатальних помилок Stripe. Задати канал логування можна, визначивши змінну оточення `CASHIER_LOGGER` у файлі `.env` вашого застосунку:

```ini
CASHIER_LOGGER=stack
```

Винятки, згенеровані API-викликами до Stripe, логуватимуться через канал логування вашого застосунку за замовчуванням.

<a name="using-custom-models"></a>
### Використання власних моделей

Ви можете розширювати моделі, які Cashier використовує внутрішньо, визначивши власну модель і розширивши відповідну модель Cashier:

```php
use Laravel\Cashier\Subscription as CashierSubscription;

class Subscription extends CashierSubscription
{
    // ...
}
```

Визначивши свою модель, ви можете вказати Cashier використовувати вашу власну модель через клас `Laravel\Cashier\Cashier`. Зазвичай повідомляти Cashier про ваші власні моделі слід у методі `boot` класу `App\Providers\AppServiceProvider` вашого застосунку:

```php
use App\Models\Cashier\Subscription;
use App\Models\Cashier\SubscriptionItem;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Cashier::useSubscriptionModel(Subscription::class);
    Cashier::useSubscriptionItemModel(SubscriptionItem::class);
}
```

<a name="quickstart"></a>
## Швидкий старт

<a name="quickstart-selling-products"></a>
### Продаж продуктів

> [!NOTE]
> Перш ніж користуватися Stripe Checkout, вам слід визначити продукти з фіксованими цінами у своїй панелі Stripe. Крім того, вам слід [налаштувати обробку вебхуків Cashier](#handling-stripe-webhooks).

Пропонувати білінг продуктів і підписок через ваш застосунок може здаватися страшним. Однак завдяки Cashier і [Stripe Checkout](https://stripe.com/payments/checkout) ви можете легко побудувати сучасні, надійні платіжні інтеграції.

Щоб списувати кошти з клієнтів за неперіодичні продукти з разовим списанням, ми скористаємося Cashier, щоб направити клієнтів до Stripe Checkout, де вони нададуть свої платіжні дані й підтвердять покупку. Щойно платіж буде здійснено через Checkout, клієнта буде перенаправлено на обрану вами URL-адресу успіху у вашому застосунку:

```php
use Illuminate\Http\Request;

Route::get('/checkout', function (Request $request) {
    $stripePriceId = 'price_deluxe_album';

    $quantity = 1;

    return $request->user()->checkout([$stripePriceId => $quantity], [
        'success_url' => route('checkout-success'),
        'cancel_url' => route('checkout-cancel'),
    ]);
})->name('checkout');

Route::view('/checkout/success', 'checkout.success')->name('checkout-success');
Route::view('/checkout/cancel', 'checkout.cancel')->name('checkout-cancel');
```

Як бачите в наведеному вище прикладі, ми скористаємося наданим Cashier методом `checkout`, щоб перенаправити клієнта до Stripe Checkout для заданого «ідентифікатора ціни». У Stripe «ціни» - це [визначені ціни для конкретних продуктів](https://stripe.com/docs/products-prices/how-products-and-prices-work).

За потреби метод `checkout` автоматично створить клієнта у Stripe і зв'яже цей запис клієнта Stripe з відповідним користувачем у базі даних вашого застосунку. Після завершення сесії оформлення клієнта буде перенаправлено на спеціальну сторінку успіху чи скасування, де ви можете показати йому інформаційне повідомлення.

<a name="providing-meta-data-to-stripe-checkout"></a>
#### Передавання метаданих до Stripe Checkout

Продаючи продукти, зазвичай відстежують завершені замовлення й куплені продукти через моделі `Cart` і `Order`, визначені у вашому власному застосунку. Перенаправляючи клієнтів до Stripe Checkout для завершення покупки, вам може знадобитися передати наявний ідентифікатор замовлення, щоб пов'язати завершену покупку з відповідним замовленням, коли клієнта буде перенаправлено назад до вашого застосунку.

Щоб досягти цього, передайте до методу `checkout` масив `metadata`. Уявімо, що в нашому застосунку створюється незавершене замовлення `Order`, коли користувач починає процес оформлення. Пам'ятайте: моделі `Cart` і `Order` у цьому прикладі є ілюстративними і не надаються Cashier. Ви можете реалізувати ці концепції відповідно до потреб власного застосунку:

```php
use App\Models\Cart;
use App\Models\Order;
use Illuminate\Http\Request;

Route::get('/cart/{cart}/checkout', function (Request $request, Cart $cart) {
    $order = Order::create([
        'cart_id' => $cart->id,
        'price_ids' => $cart->price_ids,
        'status' => 'incomplete',
    ]);

    return $request->user()->checkout($order->price_ids, [
        'success_url' => route('checkout-success').'?session_id={CHECKOUT_SESSION_ID}',
        'cancel_url' => route('checkout-cancel'),
        'metadata' => ['order_id' => $order->id],
    ]);
})->name('checkout');
```

Як бачите в наведеному вище прикладі, коли користувач починає процес оформлення, ми передаємо до методу `checkout` усі пов'язані з кошиком / замовленням ідентифікатори цін Stripe. Звісно, ваш застосунок відповідає за прив'язку цих позицій до «кошика» чи замовлення, коли клієнт їх додає. Ми також передаємо ID замовлення до сесії Stripe Checkout через масив `metadata`. Насамкінець ми додали шаблонну змінну `CHECKOUT_SESSION_ID` до маршруту успіху Checkout. Коли Stripe перенаправить клієнтів назад до вашого застосунку, цю шаблонну змінну буде автоматично заповнено ID сесії Checkout.

Далі побудуймо маршрут успіху Checkout. Це маршрут, на який користувачів буде перенаправлено після завершення покупки через Stripe Checkout. У цьому маршруті ми можемо отримати ID сесії Stripe Checkout і пов'язаний екземпляр Stripe Checkout, щоб звернутися до переданих нами метаданих і відповідно оновити замовлення клієнта:

```php
use App\Models\Order;
use Illuminate\Http\Request;
use Laravel\Cashier\Cashier;

Route::get('/checkout/success', function (Request $request) {
    $sessionId = $request->get('session_id');

    if ($sessionId === null) {
        return;
    }

    $session = Cashier::stripe()->checkout->sessions->retrieve($sessionId);

    if ($session->payment_status !== 'paid') {
        return;
    }

    $orderId = $session['metadata']['order_id'] ?? null;

    $order = Order::findOrFail($orderId);

    $order->update(['status' => 'completed']);

    return view('checkout-success', ['order' => $order]);
})->name('checkout-success');
```

Докладніше про [дані, які містить об'єкт сесії Checkout](https://stripe.com/docs/api/checkout/sessions/object), дивіться в документації Stripe.

<a name="quickstart-selling-subscriptions"></a>
### Продаж підписок

> [!NOTE]
> Перш ніж користуватися Stripe Checkout, вам слід визначити продукти з фіксованими цінами у своїй панелі Stripe. Крім того, вам слід [налаштувати обробку вебхуків Cashier](#handling-stripe-webhooks).

Пропонувати білінг продуктів і підписок через ваш застосунок може здаватися страшним. Однак завдяки Cashier і [Stripe Checkout](https://stripe.com/payments/checkout) ви можете легко побудувати сучасні, надійні платіжні інтеграції.

Щоб дізнатися, як продавати підписки за допомогою Cashier і Stripe Checkout, розгляньмо простий сценарій сервісу підписок з базовим місячним (`price_basic_monthly`) і річним (`price_basic_yearly`) планом. Ці дві ціни можна згрупувати під продуктом «Basic» (`pro_basic`) у нашій панелі Stripe. Крім того, наш сервіс підписок може пропонувати план Expert як `pro_expert`.

Спершу з'ясуймо, як клієнт може підписатися на наші сервіси. Звісно, можна уявити, що клієнт натисне кнопку «subscribe» для плану Basic на сторінці цін нашого застосунку. Ця кнопка чи посилання має направити користувача до маршруту Laravel, який створює сесію Stripe Checkout для обраного плану:

```php
use Illuminate\Http\Request;

Route::get('/subscription-checkout', function (Request $request) {
    return $request->user()
        ->newSubscription('default', 'price_basic_monthly')
        ->trialDays(5)
        ->allowPromotionCodes()
        ->checkout([
            'success_url' => route('your-success-route'),
            'cancel_url' => route('your-cancel-route'),
        ]);
});
```

Як бачите в наведеному вище прикладі, ми перенаправимо клієнта до сесії Stripe Checkout, яка дозволить йому підписатися на наш план Basic. Після успішного оформлення чи скасування клієнта буде перенаправлено назад на URL, який ми передали до методу `checkout`. Щоб знати, коли підписка справді почалася (оскільки деяким платіжним методам потрібно кілька секунд на обробку), нам також потрібно [налаштувати обробку вебхуків Cashier](#handling-stripe-webhooks).

Тепер, коли клієнти можуть починати підписки, нам потрібно обмежити певні частини нашого застосунку так, щоб доступ до них мали лише підписані користувачі. Звісно, ми завжди можемо визначити поточний стан підписки користувача методом `subscribed`, який надає трейт `Billable` з Cashier:

```blade
@if ($user->subscribed())
    <p>You are subscribed.</p>
@endif
```

Ми навіть легко можемо визначити, чи підписаний користувач на конкретний продукт чи ціну:

```blade
@if ($user->subscribedToProduct('pro_basic'))
    <p>You are subscribed to our Basic product.</p>
@endif

@if ($user->subscribedToPrice('price_basic_monthly'))
    <p>You are subscribed to our monthly Basic plan.</p>
@endif
```

<a name="quickstart-building-a-subscribed-middleware"></a>
#### Створення middleware для підписаних

Для зручності ви можете створити [middleware](/docs/{{version}}/middleware), який визначає, чи надійшов вхідний запит від підписаного користувача. Щойно цей `middleware` буде визначено, ви зможете легко призначити його маршруту, щоб не пускати до нього непідписаних користувачів:

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class Subscribed
{
    /**
     * Handle an incoming request.
     */
    public function handle(Request $request, Closure $next): Response
    {
        if (! $request->user()?->subscribed()) {
            // Redirect user to billing page and ask them to subscribe...
            return redirect('/billing');
        }

        return $next($request);
    }
}
```

Щойно `middleware` визначено, ви можете призначити його маршруту:

```php
use App\Http\Middleware\Subscribed;

Route::get('/dashboard', function () {
    // ...
})->middleware([Subscribed::class]);
```

<a name="quickstart-allowing-customers-to-manage-their-billing-plan"></a>
#### Дозвіл клієнтам керувати своїм планом білінгу

Звісно, клієнти можуть захотіти змінити свій план підписки на інший продукт чи «рівень». Найпростіший спосіб це дозволити - направити клієнтів до [порталу білінгу для клієнтів](https://stripe.com/docs/no-code/customer-portal) від Stripe, який надає розміщений інтерфейс, де клієнти можуть завантажувати рахунки, оновлювати платіжний метод і змінювати плани підписки.

Спершу визначте у своєму застосунку посилання чи кнопку, що направляє користувачів до маршруту Laravel, який ми використаємо для ініціації сесії порталу білінгу:

```blade
<a href="{{ route('billing') }}">
    Billing
</a>
```

Далі визначмо маршрут, який ініціює сесію порталу білінгу для клієнтів Stripe і перенаправляє користувача до порталу. Метод `redirectToBillingPortal` приймає URL, на який користувачів слід повернути, коли вони вийдуть з порталу:

```php
use Illuminate\Http\Request;

Route::get('/billing', function (Request $request) {
    return $request->user()->redirectToBillingPortal(route('dashboard'));
})->middleware(['auth'])->name('billing');
```

> [!NOTE]
> Доки ви налаштували обробку вебхуків Cashier, Cashier автоматично підтримуватиме пов'язані з ним таблиці бази даних вашого застосунку синхронізованими, аналізуючи вхідні вебхуки від Stripe. Так, наприклад, коли користувач скасує свою підписку через портал білінгу для клієнтів Stripe, Cashier отримає відповідний вебхук і позначить підписку як «скасовану» в базі даних вашого застосунку.

<a name="customers"></a>
## Клієнти

<a name="retrieving-customers"></a>
### Отримання клієнтів

Ви можете отримати клієнта за його Stripe ID методом `Cashier::findBillable`. Цей метод поверне екземпляр моделі з білінгом:

```php
use Laravel\Cashier\Cashier;

$user = Cashier::findBillable($stripeId);
```

<a name="creating-customers"></a>
### Створення клієнтів

Іноді ви можете захотіти створити клієнта Stripe, не починаючи підписки. Зробити це можна методом `createAsStripeCustomer`:

```php
$stripeCustomer = $user->createAsStripeCustomer();
```

Щойно клієнта створено у Stripe, ви можете почати підписку пізніше. Ви можете передати необов'язковий масив `$options`, щоб указати будь-які додаткові [параметри створення клієнта, які підтримує API Stripe](https://stripe.com/docs/api/customers/create):

```php
$stripeCustomer = $user->createAsStripeCustomer($options);
```

Ви можете скористатися методом `asStripeCustomer`, якщо хочете повернути об'єкт клієнта Stripe для моделі з білінгом:

```php
$stripeCustomer = $user->asStripeCustomer();
```

Метод `createOrGetStripeCustomer` можна використати, якщо ви хочете отримати об'єкт клієнта Stripe для певної моделі з білінгом, але не впевнені, чи є ця модель уже клієнтом у Stripe. Цей метод створить нового клієнта у Stripe, якщо такого ще немає:

```php
$stripeCustomer = $user->createOrGetStripeCustomer();
```

<a name="updating-customers"></a>
### Оновлення клієнтів

Іноді ви можете захотіти оновити клієнта Stripe напряму, додавши інформацію. Зробити це можна методом `updateStripeCustomer`. Цей метод приймає масив [параметрів оновлення клієнта, які підтримує API Stripe](https://stripe.com/docs/api/customers/update):

```php
$stripeCustomer = $user->updateStripeCustomer($options);
```

<a name="balances"></a>
### Баланси

Stripe дозволяє кредитувати чи дебетувати «баланс» клієнта. Згодом цей баланс буде зараховано чи списано в нових рахунках. Щоб перевірити загальний баланс клієнта, скористайтеся методом `balance`, доступним на вашій моделі з білінгом. Метод `balance` поверне відформатований рядок з представленням балансу у валюті клієнта:

```php
$balance = $user->balance();
```

Щоб кредитувати баланс клієнта, передайте значення до методу `creditBalance`. За бажанням ви також можете передати опис:

```php
$user->creditBalance(500, 'Premium customer top-up.');
```

Передавання значення до методу `debitBalance` дебетує баланс клієнта:

```php
$user->debitBalance(300, 'Bad usage penalty.');
```

Метод `applyBalance` створить для клієнта нові транзакції балансу. Ви можете отримати ці записи транзакцій методом `balanceTransactions`, що може бути корисно, щоб надати клієнту журнал зарахувань і списань для перегляду:

```php
// Retrieve all transactions...
$transactions = $user->balanceTransactions();

foreach ($transactions as $transaction) {
    // Transaction amount...
    $amount = $transaction->amount(); // $2.31

    // Retrieve the related invoice when available...
    $invoice = $transaction->invoice();
}
```

<a name="tax-ids"></a>
### Податкові номери

Cashier пропонує простий спосіб керувати податковими номерами клієнта. Наприклад, метод `taxIds` можна використати, щоб отримати колекцією всі [податкові номери](https://stripe.com/docs/api/customer_tax_ids/object), призначені клієнту:

```php
$taxIds = $user->taxIds();
```

Ви також можете отримати конкретний податковий номер клієнта за його ідентифікатором:

```php
$taxId = $user->findTaxId('txi_belgium');
```

Ви можете створити новий податковий номер, передавши дійсний [тип](https://stripe.com/docs/api/customer_tax_ids/object#tax_id_object-type) і значення до методу `createTaxId`:

```php
$taxId = $user->createTaxId('eu_vat', 'BE0123456789');
```

Метод `createTaxId` одразу додасть VAT ID до облікового запису клієнта. [Перевірку VAT ID також виконує Stripe](https://stripe.com/docs/invoicing/customer/tax-ids#validation); однак це асинхронний процес. Ви можете отримувати сповіщення про оновлення перевірки, підписавшись на подію вебхука `customer.tax_id.updated` і перевіряючи [параметр `verification` податкового номера](https://stripe.com/docs/api/customer_tax_ids/object#tax_id_object-verification). Докладніше про обробку вебхуків дивіться в [документації щодо визначення обробників вебхуків](#handling-stripe-webhooks).

Ви можете видалити податковий номер методом `deleteTaxId`:

```php
$user->deleteTaxId('txi_belgium');
```

<a name="syncing-customer-data-with-stripe"></a>
### Синхронізація даних клієнта зі Stripe

Зазвичай, коли користувачі вашого застосунку оновлюють своє ім'я, адресу електронної пошти чи іншу інформацію, яку також зберігає Stripe, вам слід повідомити Stripe про ці оновлення. Так копія інформації у Stripe буде синхронізована з вашим застосунком.

Щоб автоматизувати це, визначте на своїй моделі з білінгом слухача події, який реагує на подію `updated` моделі. Далі в цьому слухачі викличте на моделі метод `syncStripeCustomerDetails`:

```php
use App\Models\User;
use function Illuminate\Events\queueable;

/**
 * The "booted" method of the model.
 */
protected static function booted(): void
{
    static::updated(queueable(function (User $customer) {
        if ($customer->hasStripeId()) {
            $customer->syncStripeCustomerDetails();
        }
    }));
}
```

Тепер щоразу, коли вашу модель клієнта буде оновлено, її інформація синхронізуватиметься зі Stripe. Для зручності Cashier автоматично синхронізує інформацію про вашого клієнта зі Stripe під час початкового створення клієнта.

Ви можете налаштувати, які колонки використовуються для синхронізації інформації про клієнта зі Stripe, перевизначивши різні методи, які надає Cashier. Наприклад, ви можете перевизначити метод `stripeName`, щоб указати, який атрибут слід вважати «іменем» клієнта, коли Cashier синхронізує інформацію про клієнта зі Stripe:

```php
/**
 * Get the customer name that should be synced to Stripe.
 */
public function stripeName(): string|null
{
    return $this->company_name;
}
```

Так само ви можете перевизначити методи `stripeEmail`, `stripePhone` (максимум 20 символів), `stripeAddress` і `stripePreferredLocales`. Ці методи синхронізуватимуть інформацію з відповідними параметрами клієнта під час [оновлення об'єкта клієнта Stripe](https://stripe.com/docs/api/customers/update). Якщо ви хочете повністю контролювати процес синхронізації інформації про клієнта, перевизначте метод `syncStripeCustomerDetails`.

<a name="billing-portal"></a>
### Портал білінгу

Stripe пропонує [простий спосіб налаштувати портал білінгу](https://stripe.com/docs/billing/subscriptions/customer-portal), щоб ваш клієнт міг керувати своєю підпискою, платіжними методами й переглядати історію білінгу. Ви можете перенаправити своїх користувачів до порталу білінгу, викликавши метод `redirectToBillingPortal` на моделі з білінгом з контролера чи маршруту:

```php
use Illuminate\Http\Request;

Route::get('/billing-portal', function (Request $request) {
    return $request->user()->redirectToBillingPortal();
});
```

За замовчуванням, коли користувач завершить керування своєю підпискою, він зможе повернутися до маршруту `home` вашого застосунку за посиланням у порталі білінгу Stripe. Ви можете вказати власний URL, на який слід повернути користувача, передавши URL аргументом до методу `redirectToBillingPortal`:

```php
use Illuminate\Http\Request;

Route::get('/billing-portal', function (Request $request) {
    return $request->user()->redirectToBillingPortal(route('billing'));
});
```

Якщо ви хочете згенерувати URL до порталу білінгу без створення HTTP-відповіді з перенаправленням, викличте метод `billingPortalUrl`:

```php
$url = $request->user()->billingPortalUrl(route('billing'));
```

<a name="payment-methods"></a>
## Платіжні методи

<a name="storing-payment-methods"></a>
### Збереження платіжних методів

Щоб створювати підписки чи виконувати «разові» списання через Stripe, вашому застосунку потрібно безпечно збирати платіжні дані від клієнта. Підхід до цього різниться залежно від того, чи плануєте ви зберігати платіжний метод для майбутніх підписок, чи одразу обробити разове списання, тож розглянемо обидва варіанти нижче.

[Payment Element](https://stripe.com/docs/payments/payment-element) від Stripe можна використати для підтримки кількох платіжних методів: карток, Apple Pay, Google Pay та iDEAL.

<a name="payment-element-for-subscriptions"></a>
#### Payment Element для підписок

Спершу створіть Setup Intent і передайте його до свого представлення:

```php
return view('subscribe', [
    'intent' => $user->createSetupIntent()
]);
```

Змонтуйте Payment Element, використовуючи `client_secret` з Setup Intent:

```html
<div id="payment-element"></div>
<button id="submit">Subscribe</button>

<script src="https://js.stripe.com/v3/"></script>
<script>
    const stripe = Stripe('stripe-public-key');

    const elements = stripe.elements({
        clientSecret: '{{ $intent->client_secret }}'
    });

    const paymentElement = elements.create('payment');

    paymentElement.mount('#payment-element');

    document.getElementById('submit').addEventListener('click', async () => {
        const { error } = await stripe.confirmSetup({
            elements,
            confirmParams: {
                return_url: '{{ route("subscription.complete") }}',
            },
        });

        if (error) {
            // Display "error.message" to the user...
        }
    });
</script>
```

Після того як Stripe перенаправить на ваш `return_url`, ID `setup_intent` буде доступний як параметр рядка запиту. Ви можете скористатися цим значенням, щоб отримати платіжний метод і створити підписку:

```php
use Illuminate\Http\Request;

Route::get('/subscription/complete', function (Request $request) {
    $setupIntent = $request->user()->findSetupIntent(
        $request->setup_intent
    );

    $paymentMethod = $setupIntent->payment_method;

    $request->user()
        ->newSubscription('default', 'price_xxx')
        ->create($paymentMethod);

    return redirect('/dashboard');
})->name('subscription.complete');
```

Якщо ви використовуєте Payment Element, щоб оновити платіжний метод клієнта за замовчуванням, а не створити підписку, передайте ідентифікатор платіжного методу до методу [`updateDefaultPaymentMethod`](#updating-the-default-payment-method).

<a name="payment-element-for-single-charges"></a>
#### Payment Element для разових списань

Для разових платежів створіть Payment Intent методом `pay` з Cashier. Зазвичай вам слід зберегти ID Payment Intent у відповідному замовленні вашого застосунку, щоб замовлення можна було знайти після того, як Stripe перенаправить клієнта назад до вашого застосунку. Наведений нижче приклад припускає, що ваш застосунок має модель `Order` з колонками `user_id`, `amount`, `status` і `stripe_payment_intent_id`:

```php
use App\Models\Order;
use Illuminate\Http\Request;

Route::post('/pay', function (Request $request) {
    $amount = 1000;

    $payment = $request->user()->pay($amount);

    $order = Order::create([
        'user_id' => $request->user()->id,
        'amount' => $amount,
        'status' => 'pending',
        'stripe_payment_intent_id' => $payment->id,
    ]);

    return view('checkout', [
        'clientSecret' => $payment->client_secret,
        'order' => $order,
    ]);
});
```

Далі змонтуйте Payment Element і підтвердьте платіж:

```html
<div id="payment-element"></div>
<button id="submit">Pay Now</button>

<script src="https://js.stripe.com/v3/"></script>
<script>
    const stripe = Stripe('stripe-public-key');

    const elements = stripe.elements({
        clientSecret: '{{ $clientSecret }}'
    });

    const paymentElement = elements.create('payment');

    paymentElement.mount('#payment-element');

    document.getElementById('submit').addEventListener('click', async () => {
        const { error } = await stripe.confirmPayment({
            elements,
            confirmParams: {
                return_url: '{{ route("payment.complete") }}',
            },
        });

        if (error) {
            // Display "error.message" to the user...
        }
    });
</script>
```

Після перенаправлення ви можете скористатися параметром рядка запиту `payment_intent`, щоб знайти відповідне замовлення й Payment Intent. Перш ніж виконувати замовлення, вам слід перевірити, що воно належить автентифікованому клієнту, а Payment Intent належить автентифікованому клієнту й успішно завершився:

```php
use App\Models\Order;
use Illuminate\Http\Request;

Route::get('/payment/complete', function (Request $request) {
    $order = Order::where('user_id', $request->user()->id)
        ->where('stripe_payment_intent_id', $request->payment_intent)
        ->firstOrFail();

    $paymentIntent = $request->user()
        ->stripe()
        ->paymentIntents
        ->retrieve($request->payment_intent);

    if ($paymentIntent->customer === $request->user()->stripe_id &&
        $paymentIntent->status === 'succeeded') {
        $order->update(['status' => 'paid']);

        // Fulfill the order...
    }

    return redirect('/dashboard');
})->name('payment.complete');
```

<a name="retrieving-payment-methods"></a>
### Отримання платіжних методів

Метод `paymentMethods` на екземплярі моделі з білінгом повертає колекцію екземплярів `Laravel\Cashier\PaymentMethod`:

```php
$paymentMethods = $user->paymentMethods();
```

За замовчуванням цей метод поверне платіжні методи всіх типів. Щоб отримати платіжні методи конкретного типу, передайте `type` аргументом до методу:

```php
$paymentMethods = $user->paymentMethods('sepa_debit');
```

Щоб отримати платіжний метод клієнта за замовчуванням, скористайтеся методом `defaultPaymentMethod`:

```php
$paymentMethod = $user->defaultPaymentMethod();
```

Ви можете отримати конкретний платіжний метод, прикріплений до моделі з білінгом, методом `findPaymentMethod`:

```php
$paymentMethod = $user->findPaymentMethod($paymentMethodId);
```

<a name="payment-method-presence"></a>
### Наявність платіжного методу

Щоб визначити, чи має модель з білінгом прикріплений до облікового запису платіжний метод за замовчуванням, викличте метод `hasDefaultPaymentMethod`:

```php
if ($user->hasDefaultPaymentMethod()) {
    // ...
}
```

Ви можете скористатися методом `hasPaymentMethod`, щоб визначити, чи має модель з білінгом принаймні один прикріплений до облікового запису платіжний метод:

```php
if ($user->hasPaymentMethod()) {
    // ...
}
```

Цей метод визначить, чи має модель з білінгом хоч якийсь платіжний метод. Щоб визначити, чи існує для моделі платіжний метод конкретного типу, передайте `type` аргументом до методу:

```php
if ($user->hasPaymentMethod('sepa_debit')) {
    // ...
}
```

<a name="updating-the-default-payment-method"></a>
### Оновлення платіжного методу за замовчуванням

Метод `updateDefaultPaymentMethod` можна використати, щоб оновити інформацію про платіжний метод клієнта за замовчуванням. Цей метод приймає ідентифікатор платіжного методу Stripe і призначить новий платіжний метод як платіжний метод для білінгу за замовчуванням:

```php
$user->updateDefaultPaymentMethod($paymentMethod);
```

Щоб синхронізувати інформацію про ваш платіжний метод за замовчуванням з інформацією про платіжний метод клієнта за замовчуванням у Stripe, скористайтеся методом `updateDefaultPaymentMethodFromStripe`:

```php
$user->updateDefaultPaymentMethodFromStripe();
```

> [!WARNING]
> Платіжний метод клієнта за замовчуванням можна використовувати лише для виставлення рахунків і створення нових підписок. Через обмеження, накладені Stripe, його не можна використовувати для разових списань.

<a name="adding-payment-methods"></a>
### Додавання платіжних методів

Щоб додати новий платіжний метод, викличте метод `addPaymentMethod` на моделі з білінгом, передавши ідентифікатор платіжного методу:

```php
$user->addPaymentMethod($paymentMethod);
```

> [!NOTE]
> Щоб дізнатися, як отримати ідентифікатори платіжних методів, перегляньте [документацію щодо збереження платіжних методів](#storing-payment-methods).

<a name="deleting-payment-methods"></a>
### Видалення платіжних методів

Щоб видалити платіжний метод, викличте метод `delete` на екземплярі `Laravel\Cashier\PaymentMethod`, який хочете видалити:

```php
$paymentMethod->delete();
```

Метод `deletePaymentMethod` видалить конкретний платіжний метод з моделі з білінгом:

```php
$user->deletePaymentMethod('pm_visa');
```

Метод `deletePaymentMethods` видалить усю інформацію про платіжні методи для моделі з білінгом:

```php
$user->deletePaymentMethods();
```

За замовчуванням цей метод видалить платіжні методи всіх типів. Щоб видалити платіжні методи конкретного типу, передайте `type` аргументом до методу:

```php
$user->deletePaymentMethods('sepa_debit');
```

> [!WARNING]
> Якщо користувач має активну підписку, ваш застосунок не має дозволяти йому видаляти платіжний метод за замовчуванням.

<a name="subscriptions"></a>
## Підписки

Підписки дають спосіб налаштувати періодичні платежі для ваших клієнтів. Підписки Stripe, якими керує Cashier, підтримують кілька цін підписки, кількості в підписці, пробні періоди тощо.

<a name="creating-subscriptions"></a>
### Створення підписок

Щоб створити підписку, спершу отримайте екземпляр вашої моделі з білінгом, яким зазвичай буде екземпляр `App\Models\User`. Отримавши екземпляр моделі, ви можете скористатися методом `newSubscription`, щоб створити підписку для моделі:

```php
use Illuminate\Http\Request;

Route::post('/user/subscribe', function (Request $request) {
    $request->user()->newSubscription(
        'default', 'price_monthly'
    )->create($request->paymentMethodId);

    // ...
});
```

Першим аргументом методу `newSubscription` має бути внутрішній тип підписки. Якщо ваш застосунок пропонує лише одну підписку, ви можете назвати її `default` чи `primary`. Цей тип підписки призначений лише для внутрішнього використання застосунком і не має показуватися користувачам. Крім того, він не повинен містити пробілів, і його ніколи не слід змінювати після створення підписки. Другий аргумент - конкретна ціна, на яку підписується користувач. Це значення має відповідати ідентифікатору ціни у Stripe.

Метод `create`, який приймає [ідентифікатор платіжного методу Stripe](#storing-payment-methods) чи об'єкт `PaymentMethod` зі Stripe, розпочне підписку, а також оновить вашу базу даних, записавши ID клієнта Stripe для моделі з білінгом та іншу релевантну платіжну інформацію.

> [!WARNING]
> Передавання ідентифікатора платіжного методу безпосередньо до методу `create` підписки також автоматично додасть його до збережених платіжних методів користувача.

<a name="collecting-recurring-payments-via-invoice-emails"></a>
#### Збір періодичних платежів через рахунки на пошту

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

```php
$user->newSubscription('default', 'price_monthly')->createAndSendInvoice();
```

Час, який клієнт має на оплату рахунка, перш ніж його підписку буде скасовано, визначається опцією `days_until_due`. За замовчуванням це 30 днів; однак за бажанням ви можете вказати для цієї опції конкретне значення:

```php
$user->newSubscription('default', 'price_monthly')->createAndSendInvoice([], [
    'days_until_due' => 30
]);
```

<a name="subscription-quantities"></a>
#### Кількості

Якщо ви хочете задати конкретну [кількість](https://stripe.com/docs/billing/subscriptions/quantities) для ціни під час створення підписки, викличте метод `quantity` на білдері підписки перед її створенням:

```php
$user->newSubscription('default', 'price_monthly')
    ->quantity(5)
    ->create($paymentMethod);
```

<a name="additional-details"></a>
#### Додаткові деталі

Якщо ви хочете вказати додаткові опції [клієнта](https://stripe.com/docs/api/customers/create) чи [підписки](https://stripe.com/docs/api/subscriptions/create), які підтримує Stripe, передайте їх другим і третім аргументами до методу `create`:

```php
$user->newSubscription('default', 'price_monthly')->create($paymentMethod, [
    'email' => $email,
], [
    'metadata' => ['note' => 'Some extra information.'],
]);
```

<a name="coupons"></a>
#### Купони

Якщо ви хочете застосувати купон під час створення підписки, скористайтеся методом `withCoupon`:

```php
$user->newSubscription('default', 'price_monthly')
    ->withCoupon('code')
    ->create($paymentMethod);
```

Або ж, якщо ви хочете застосувати [промокод Stripe](https://stripe.com/docs/billing/subscriptions/discounts/codes), скористайтеся методом `withPromotionCode`:

```php
$user->newSubscription('default', 'price_monthly')
    ->withPromotionCode('promo_code_id')
    ->create($paymentMethod);
```

Заданий ID промокоду має бути API ID зі Stripe, призначеним промокоду, а не промокодом, який бачить клієнт. Якщо вам потрібно знайти ID промокоду за промокодом, який бачить клієнт, скористайтеся методом `findPromotionCode`:

```php
// Find a promotion code ID by its customer facing code...
$promotionCode = $user->findPromotionCode('SUMMERSALE');

// Find an active promotion code ID by its customer facing code...
$promotionCode = $user->findActivePromotionCode('SUMMERSALE');
```

У наведеному вище прикладі повернутий об'єкт `$promotionCode` є екземпляром `Laravel\Cashier\PromotionCode`. Цей клас декорує об'єкт `Stripe\PromotionCode`, що лежить в основі. Ви можете отримати купон, пов'язаний з промокодом, викликавши метод `coupon`:

```php
$coupon = $user->findPromotionCode('SUMMERSALE')->coupon();
```

Екземпляр купона дозволяє визначити розмір знижки й те, чи є купон фіксованою знижкою, чи знижкою у відсотках:

```php
if ($coupon->isPercentage()) {
    return $coupon->percentOff().'%'; // 21.5%
} else {
    return $coupon->amountOff(); // $5.99
}
```

Ви також можете отримати знижки, які наразі застосовані до клієнта чи підписки:

```php
$discount = $billable->discount();

$discount = $subscription->discount();
```

Повернуті екземпляри `Laravel\Cashier\Discount` декорують екземпляр об'єкта `Stripe\Discount`, що лежить в основі. Ви можете отримати купон, пов'язаний із цією знижкою, викликавши метод `coupon`:

```php
$coupon = $subscription->discount()->coupon();
```

Якщо ви хочете застосувати новий купон чи промокод до клієнта чи підписки, зробіть це методами `applyCoupon` чи `applyPromotionCode`:

```php
$billable->applyCoupon('coupon_id');
$billable->applyPromotionCode('promotion_code_id');

$subscription->applyCoupon('coupon_id');
$subscription->applyPromotionCode('promotion_code_id');
```

Пам'ятайте: вам слід використовувати API ID зі Stripe, призначений промокоду, а не промокод, який бачить клієнт. До клієнта чи підписки одночасно можна застосувати лише один купон чи промокод.

Докладніше про це дивіться в документації Stripe щодо [купонів](https://stripe.com/docs/billing/subscriptions/coupons) і [промокодів](https://stripe.com/docs/billing/subscriptions/coupons/codes).

<a name="adding-subscriptions"></a>
#### Додавання підписок

Якщо ви хочете додати підписку клієнту, який уже має платіжний метод за замовчуванням, викличте метод `add` на білдері підписки:

```php
use App\Models\User;

$user = User::find(1);

$user->newSubscription('default', 'price_monthly')->add();
```

<a name="creating-subscriptions-from-the-stripe-dashboard"></a>
#### Створення підписок з панелі Stripe

Ви також можете створювати підписки просто з панелі Stripe. У такому разі Cashier синхронізує щойно додані підписки й призначить їм тип `default`. Щоб змінити тип підписки, який призначається створеним у панелі підпискам, [визначте обробники подій вебхуків](#defining-webhook-event-handlers).

Крім того, через панель Stripe можна створити лише один тип підписки. Якщо ваш застосунок пропонує кілька підписок різних типів, через панель Stripe можна додати лише один тип підписки.

Насамкінець вам слід завжди стежити за тим, щоб додавати лише одну активну підписку на кожен тип підписки, який пропонує ваш застосунок. Якщо клієнт має дві підписки `default`, Cashier використовуватиме лише останню додану, навіть попри те, що обидві синхронізуватимуться з базою даних вашого застосунку.

<a name="checking-subscription-status"></a>
### Перевірка стану підписки

Щойно клієнт підписався на ваш застосунок, ви можете легко перевіряти стан його підписки різними зручними методами. По-перше, метод `subscribed` повертає `true`, якщо клієнт має активну підписку, навіть якщо вона наразі в межах пробного періоду. Метод `subscribed` приймає тип підписки першим аргументом:

```php
if ($user->subscribed('default')) {
    // ...
}
```

Метод `subscribed` також чудово підходить для [middleware маршруту](/docs/{{version}}/middleware), дозволяючи фільтрувати доступ до маршрутів і контролерів на основі стану підписки користувача:

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class EnsureUserIsSubscribed
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        if ($request->user() && ! $request->user()->subscribed('default')) {
            // This user is not a paying customer...
            return redirect('/billing');
        }

        return $next($request);
    }
}
```

Якщо ви хочете визначити, чи користувач досі в межах пробного періоду, скористайтеся методом `onTrial`. Цей метод може бути корисним, щоб визначити, чи слід показати користувачеві попередження про те, що він досі на пробному періоді:

```php
if ($user->subscription('default')->onTrial()) {
    // ...
}
```

Метод `subscribedToProduct` можна використати, щоб визначити, чи підписаний користувач на певний продукт на основі заданого ідентифікатора продукту Stripe. У Stripe продукти - це набори цін. У цьому прикладі ми визначимо, чи підписка користувача `default` активно підписана на продукт «premium» застосунку. Заданий ідентифікатор продукту Stripe має відповідати одному з ідентифікаторів ваших продуктів у панелі Stripe:

```php
if ($user->subscribedToProduct('prod_premium', 'default')) {
    // ...
}
```

Передавши до методу `subscribedToProduct` масив, ви можете визначити, чи підписка користувача `default` активно підписана на продукт «basic» чи «premium» застосунку:

```php
if ($user->subscribedToProduct(['prod_basic', 'prod_premium'], 'default')) {
    // ...
}
```

Метод `subscribedToPrice` можна використати, щоб визначити, чи відповідає підписка клієнта заданому ID ціни:

```php
if ($user->subscribedToPrice('price_basic_monthly', 'default')) {
    // ...
}
```

Метод `recurring` можна використати, щоб визначити, чи має користувач наразі підписку і чи вийшов він уже за межі пробного періоду:

```php
if ($user->subscription('default')->recurring()) {
    // ...
}
```

> [!WARNING]
> Якщо користувач має дві підписки того самого типу, метод `subscription` завжди повертатиме найновішу з них. Наприклад, користувач може мати два записи підписок з типом `default`; однак одна з них може бути старою, простроченою, а інша - поточною, активною. Завжди повертатиметься найновіша підписка, тоді як старіші зберігаються в базі даних для історичного огляду.

<a name="cancelled-subscription-status"></a>
#### Стан скасованої підписки

Щоб визначити, чи був користувач колись активним підписником, але скасував свою підписку, скористайтеся методом `canceled`:

```php
if ($user->subscription('default')->canceled()) {
    // ...
}
```

Ви також можете визначити, чи скасував користувач підписку, але досі перебуває в «пільговому періоді», доки підписка повністю не спливе. Наприклад, якщо користувач скасує 5 березня підписку, яка спочатку мала спливти 10 березня, він перебуватиме в «пільговому періоді» до 10 березня. Зауважте, що метод `subscribed` протягом цього часу все ще повертає `true`:

```php
if ($user->subscription('default')->onGracePeriod()) {
    // ...
}
```

Щоб визначити, чи скасував користувач підписку і чи вийшов він уже за межі «пільгового періоду», скористайтеся методом `ended`:

```php
if ($user->subscription('default')->ended()) {
    // ...
}
```

<a name="incomplete-and-past-due-status"></a>
#### Стани incomplete і past due

Якщо підписка після створення потребує додаткової платіжної дії, її буде позначено як `incomplete`. Стани підписок зберігаються в колонці `stripe_status` таблиці бази даних `subscriptions` у Cashier.

Так само, якщо додаткова платіжна дія потрібна під час зміни цін, підписку буде позначено як `past_due`. Коли ваша підписка в будь-якому з цих станів, вона не буде активною, доки клієнт не підтвердить свій платіж. Визначити, чи має підписка незавершений платіж, можна методом `hasIncompletePayment` на моделі з білінгом чи на екземплярі підписки:

```php
if ($user->hasIncompletePayment('default')) {
    // ...
}

if ($user->subscription('default')->hasIncompletePayment()) {
    // ...
}
```

Коли підписка має незавершений платіж, вам слід направити користувача на сторінку підтвердження платежу в Cashier, передавши ідентифікатор `latestPayment`. Отримати цей ідентифікатор можна методом `latestPayment`, доступним на екземплярі підписки:

```html
<a href="{{ route('cashier.payment', $subscription->latestPayment()->id) }}">
    Please confirm your payment.
</a>
```

Якщо ви хочете, щоб підписка все ще вважалася активною у стані `past_due` чи `incomplete`, скористайтеся методами `keepPastDueSubscriptionsActive` і `keepIncompleteSubscriptionsActive`, які надає Cashier. Зазвичай ці методи слід викликати в методі `register` вашого `App\Providers\AppServiceProvider`:

```php
use Laravel\Cashier\Cashier;

/**
 * Register any application services.
 */
public function register(): void
{
    Cashier::keepPastDueSubscriptionsActive();
    Cashier::keepIncompleteSubscriptionsActive();
}
```

> [!WARNING]
> Коли підписка у стані `incomplete`, її не можна змінити, доки платіж не буде підтверджено. Тому методи `swap` і `updateQuantity` видадуть виняток, коли підписка у стані `incomplete`.

<a name="subscription-scopes"></a>
#### Скопи підписок

Більшість станів підписки також доступні як скопи запитів, тож ви можете легко шукати у своїй базі даних підписки в певному стані:

```php
// Get all active subscriptions...
$subscriptions = Subscription::query()->active()->get();

// Get all of the canceled subscriptions for a user...
$subscriptions = $user->subscriptions()->canceled()->get();
```

Повний список доступних скопів наведено нижче:

```php
Subscription::query()->active();
Subscription::query()->canceled();
Subscription::query()->ended();
Subscription::query()->incomplete();
Subscription::query()->notCanceled();
Subscription::query()->notOnGracePeriod();
Subscription::query()->notOnTrial();
Subscription::query()->onGracePeriod();
Subscription::query()->onTrial();
Subscription::query()->pastDue();
Subscription::query()->recurring();
```

<a name="changing-prices"></a>
### Зміна цін

Після того як клієнт підписався на ваш застосунок, він може час від часу хотіти перейти на нову ціну підписки. Щоб перевести клієнта на нову ціну, передайте ідентифікатор ціни Stripe до методу `swap`. Під час зміни цін припускається, що користувач хотів би повторно активувати свою підписку, якщо її було раніше скасовано. Заданий ідентифікатор ціни має відповідати ідентифікатору ціни Stripe, доступному в панелі Stripe:

```php
use App\Models\User;

$user = App\Models\User::find(1);

$user->subscription('default')->swap('price_yearly');
```

Якщо клієнт на пробному періоді, пробний період буде збережено. Крім того, якщо для підписки існує «кількість», її також буде збережено.

Якщо ви хочете змінити ціни й скасувати будь-який пробний період, на якому наразі перебуває клієнт, викличте метод `skipTrial`:

```php
$user->subscription('default')
    ->skipTrial()
    ->swap('price_yearly');
```

Якщо ви хочете змінити ціни й одразу виставити рахунок клієнту, не чекаючи наступного розрахункового циклу, скористайтеся методом `swapAndInvoice`:

```php
$user = User::find(1);

$user->subscription('default')->swapAndInvoice('price_yearly');
```

<a name="prorations"></a>
#### Пропорційний перерахунок

За замовчуванням Stripe робить пропорційний перерахунок платежів під час зміни цін. Метод `noProrate` можна використати, щоб оновити ціну підписки без пропорційного перерахунку платежів:

```php
$user->subscription('default')->noProrate()->swap('price_yearly');
```

Докладніше про пропорційний перерахунок підписок дивіться в [документації Stripe](https://stripe.com/docs/billing/subscriptions/prorations).

> [!WARNING]
> Виконання методу `noProrate` перед методом `swapAndInvoice` не вплине на пропорційний перерахунок. Рахунок буде виставлено в будь-якому разі.

<a name="subscription-quantity"></a>
### Кількість у підписці

Іноді на підписки впливає «кількість». Наприклад, застосунок для керування проєктами може стягувати $10 на місяць за проєкт. Ви можете скористатися методами `incrementQuantity` і `decrementQuantity`, щоб легко збільшити чи зменшити кількість у підписці:

```php
use App\Models\User;

$user = User::find(1);

$user->subscription('default')->incrementQuantity();

// Add five to the subscription's current quantity...
$user->subscription('default')->incrementQuantity(5);

$user->subscription('default')->decrementQuantity();

// Subtract five from the subscription's current quantity...
$user->subscription('default')->decrementQuantity(5);
```

Як альтернативу ви можете задати конкретну кількість методом `updateQuantity`:

```php
$user->subscription('default')->updateQuantity(10);
```

Метод `noProrate` можна використати, щоб оновити кількість у підписці без пропорційного перерахунку платежів:

```php
$user->subscription('default')->noProrate()->updateQuantity(10);
```

Докладніше про кількості в підписках дивіться в [документації Stripe](https://stripe.com/docs/subscriptions/quantities).

<a name="quantities-for-subscription-with-multiple-products"></a>
#### Кількості для підписок з кількома продуктами

Якщо ваша підписка є [підпискою з кількома продуктами](#subscriptions-with-multiple-products), вам слід передати ID ціни, кількість якої ви хочете збільшити чи зменшити, другим аргументом методів increment / decrement:

```php
$user->subscription('default')->incrementQuantity(1, 'price_chat');
```

<a name="subscriptions-with-multiple-products"></a>
### Підписки з кількома продуктами

[Підписка з кількома продуктами](https://stripe.com/docs/billing/subscriptions/multiple-products) дозволяє призначити одній підписці кілька продуктів білінгу. Наприклад, уявіть, що ви створюєте застосунок «служби підтримки» з базовою ціною підписки $10 на місяць, але пропонуєте додатковий продукт живого чату за додаткові $15 на місяць. Інформація про підписки з кількома продуктами зберігається в таблиці бази даних `subscription_items` у Cashier.

Ви можете вказати кілька продуктів для певної підписки, передавши масив цін другим аргументом до методу `newSubscription`:

```php
use Illuminate\Http\Request;

Route::post('/user/subscribe', function (Request $request) {
    $request->user()->newSubscription('default', [
        'price_monthly',
        'price_chat',
    ])->create($request->paymentMethodId);

    // ...
});
```

У наведеному вище прикладі клієнт матиме дві ціни, прив'язані до його підписки `default`. Обидві ціни стягуватимуться у відповідні розрахункові інтервали. За потреби ви можете скористатися методом `quantity`, щоб указати конкретну кількість для кожної ціни:

```php
$user = User::find(1);

$user->newSubscription('default', ['price_monthly', 'price_chat'])
    ->quantity(5, 'price_chat')
    ->create($paymentMethod);
```

Якщо ви хочете додати ще одну ціну до наявної підписки, викличте метод `addPrice` підписки:

```php
$user = User::find(1);

$user->subscription('default')->addPrice('price_chat');
```

Наведений вище приклад додасть нову ціну, і клієнту буде виставлено рахунок за неї в наступному розрахунковому циклі. Якщо ви хочете виставити рахунок клієнту негайно, скористайтеся методом `addPriceAndInvoice`:

```php
$user->subscription('default')->addPriceAndInvoice('price_chat');
```

Якщо ви хочете додати ціну з конкретною кількістю, передайте кількість другим аргументом методів `addPrice` чи `addPriceAndInvoice`:

```php
$user = User::find(1);

$user->subscription('default')->addPrice('price_chat', 5);
```

Ви можете прибрати ціни з підписок методом `removePrice`:

```php
$user->subscription('default')->removePrice('price_chat');
```

> [!WARNING]
> Ви не можете прибрати останню ціну в підписці. Натомість вам слід просто скасувати підписку.

<a name="swapping-prices"></a>
#### Зміна цін

Ви також можете змінювати ціни, прив'язані до підписки з кількома продуктами. Наприклад, уявіть, що клієнт має підписку `price_basic` з додатковим продуктом `price_chat`, і ви хочете підвищити клієнта з ціни `price_basic` до `price_pro`:

```php
use App\Models\User;

$user = User::find(1);

$user->subscription('default')->swap(['price_pro', 'price_chat']);
```

Під час виконання наведеного вище прикладу елемент підписки з `price_basic`, що лежить в основі, буде видалено, а той, що з `price_chat`, - збережено. Крім того, буде створено новий елемент підписки для `price_pro`.

Ви також можете вказати опції елемента підписки, передавши до методу `swap` масив пар ключ / значення. Наприклад, вам може знадобитися вказати кількості для цін підписки:

```php
$user = User::find(1);

$user->subscription('default')->swap([
    'price_pro' => ['quantity' => 5],
    'price_chat'
]);
```

Якщо ви хочете змінити одну ціну в підписці, зробіть це методом `swap` на самому елементі підписки. Такий підхід особливо корисний, якщо ви хочете зберегти всі наявні метадані інших цін підписки:

```php
$user = User::find(1);

$user->subscription('default')
    ->findItemOrFail('price_basic')
    ->swap('price_pro');
```

<a name="proration"></a>
#### Пропорційний перерахунок

За замовчуванням Stripe робитиме пропорційний перерахунок платежів під час додавання чи прибирання цін у підписці з кількома продуктами. Якщо ви хочете скоригувати ціну без пропорційного перерахунку, додайте метод `noProrate` ланцюжком до своєї операції з ціною:

```php
$user->subscription('default')->noProrate()->removePrice('price_chat');
```

<a name="swapping-quantities"></a>
#### Кількості

Якщо ви хочете оновити кількості для окремих цін підписки, зробіть це [наявними методами кількості](#subscription-quantity), передавши ID ціни додатковим аргументом методу:

```php
$user = User::find(1);

$user->subscription('default')->incrementQuantity(5, 'price_chat');

$user->subscription('default')->decrementQuantity(3, 'price_chat');

$user->subscription('default')->updateQuantity(10, 'price_chat');
```

> [!WARNING]
> Коли підписка має кілька цін, атрибути `stripe_price` і `quantity` на моделі `Subscription` будуть `null`. Щоб звернутися до атрибутів окремих цін, скористайтеся зв'язком `items`, доступним на моделі `Subscription`.

<a name="subscription-items"></a>
#### Елементи підписки

Коли підписка має кілька цін, вона матиме кілька «елементів» підписки, збережених у таблиці `subscription_items` вашої бази даних. Ви можете звернутися до них через зв'язок `items` на підписці:

```php
use App\Models\User;

$user = User::find(1);

$subscriptionItem = $user->subscription('default')->items->first();

// Retrieve the Stripe price and quantity for a specific item...
$stripePrice = $subscriptionItem->stripe_price;
$quantity = $subscriptionItem->quantity;
```

Ви також можете отримати конкретну ціну методом `findItemOrFail`:

```php
$user = User::find(1);

$subscriptionItem = $user->subscription('default')->findItemOrFail('price_chat');
```

<a name="multiple-subscriptions"></a>
### Кілька підписок

Stripe дозволяє вашим клієнтам мати кілька підписок одночасно. Наприклад, ви можете керувати спортзалом, який пропонує підписку на плавання й підписку на важку атлетику, і кожна підписка може мати різну ціну. Звісно, клієнти мають мати змогу підписатися на один чи обидва плани.

Коли ваш застосунок створює підписки, ви можете передати тип підписки до методу `newSubscription`. Типом може бути будь-який рядок, що представляє тип підписки, яку починає користувач:

```php
use Illuminate\Http\Request;

Route::post('/swimming/subscribe', function (Request $request) {
    $request->user()->newSubscription('swimming')
        ->price('price_swimming_monthly')
        ->create($request->paymentMethodId);

    // ...
});
```

У цьому прикладі ми розпочали для клієнта місячну підписку на плавання. Однак згодом він може захотіти перейти на річну підписку. Коригуючи підписку клієнта, ми можемо просто змінити ціну в підписці `swimming`:

```php
$user->subscription('swimming')->swap('price_swimming_yearly');
```

Звісно, ви також можете скасувати підписку повністю:

```php
$user->subscription('swimming')->cancel();
```

<a name="usage-based-billing"></a>
### Білінг за використанням

[Білінг за використанням](https://stripe.com/docs/billing/subscriptions/metered-billing) дозволяє стягувати з клієнтів плату на основі того, як вони користувалися продуктом протягом розрахункового циклу. Наприклад, ви можете стягувати плату на основі кількості текстових повідомлень чи листів, які вони надсилають щомісяця.

Щоб почати користуватися білінгом за використанням, вам спершу потрібно створити у своїй панелі Stripe новий продукт з [моделлю білінгу за використанням](https://docs.stripe.com/billing/subscriptions/usage-based/implementation-guide) і [лічильником](https://docs.stripe.com/billing/subscriptions/usage-based/recording-usage#configure-meter). Створивши лічильник, збережіть пов'язане ім'я події та ID лічильника, які знадобляться вам для звітування й отримання використання. Далі скористайтеся методом `meteredPrice`, щоб додати ID лічильної ціни до підписки клієнта:

```php
use Illuminate\Http\Request;

Route::post('/user/subscribe', function (Request $request) {
    $request->user()->newSubscription('default')
        ->meteredPrice('price_metered')
        ->create($request->paymentMethodId);

    // ...
});
```

Ви також можете розпочати лічильну підписку через [Stripe Checkout](#checkout):

```php
$checkout = Auth::user()
    ->newSubscription('default', [])
    ->meteredPrice('price_metered')
    ->checkout();

return view('your-checkout-view', [
    'checkout' => $checkout,
]);
```

<a name="reporting-usage"></a>
#### Звітування про використання

Коли ваш клієнт користується вашим застосунком, ви звітуватимете про його використання до Stripe, щоб рахунок був точним. Щоб відзвітувати про використання лічильної події, скористайтеся методом `reportMeterEvent` на своїй моделі `Billable`:

```php
$user = User::find(1);

$user->reportMeterEvent('emails-sent');
```

За замовчуванням до розрахункового періоду додається «кількість використання» 1. Як альтернативу ви можете передати конкретний обсяг «використання», який слід додати до використання клієнта за розрахунковий період:

```php
$user = User::find(1);

$user->reportMeterEvent('emails-sent', quantity: 15);
```

Щоб отримати зведення подій клієнта для лічильника, скористайтеся методом `meterEventSummaries` екземпляра `Billable`:

```php
$user = User::find(1);

$meterUsage = $user->meterEventSummaries($meterId);

$meterUsage->first()->aggregated_value; // 10
```

Докладніше про зведення подій лічильника дивіться в [документації Stripe щодо об'єкта Meter Event Summary](https://docs.stripe.com/api/billing/meter-event_summary/object).

Щоб [перелічити всі лічильники](https://docs.stripe.com/api/billing/meter/list), скористайтеся методом `meters` екземпляра `Billable`:

```php
$user = User::find(1);

$user->meters();
```

<a name="subscription-taxes"></a>
### Податки на підписки

> [!WARNING]
> Замість обчислювати податкові ставки вручну, ви можете [автоматично обчислювати податки за допомогою Stripe Tax](#tax-configuration)

Щоб указати податкові ставки, які користувач сплачує за підпискою, реалізуйте на своїй моделі з білінгом метод `taxRates` і поверніть масив з ID податкових ставок Stripe. Ви можете визначити ці податкові ставки [у своїй панелі Stripe](https://dashboard.stripe.com/test/tax-rates):

```php
/**
 * The tax rates that should apply to the customer's subscriptions.
 *
 * @return array<int, string>
 */
public function taxRates(): array
{
    return ['txr_id'];
}
```

Метод `taxRates` дозволяє застосовувати податкову ставку до кожного клієнта окремо, що може бути корисно для бази користувачів, яка охоплює кілька країн і податкових ставок.

Якщо ви пропонуєте підписки з кількома продуктами, ви можете визначити різні податкові ставки для кожної ціни, реалізувавши на своїй моделі з білінгом метод `priceTaxRates`:

```php
/**
 * The tax rates that should apply to the customer's subscriptions.
 *
 * @return array<string, array<int, string>>
 */
public function priceTaxRates(): array
{
    return [
        'price_monthly' => ['txr_id'],
    ];
}
```

> [!WARNING]
> Метод `taxRates` застосовується лише до списань за підписками. Якщо ви користуєтеся Cashier для «разових» списань, вам потрібно буде вказати податкову ставку вручну в той момент.

<a name="syncing-tax-rates"></a>
#### Синхронізація податкових ставок

Коли ви змінюєте жорстко задані ID податкових ставок, які повертає метод `taxRates`, податкові налаштування наявних підписок користувача залишаться незмінними. Якщо ви хочете оновити значення податку для наявних підписок новими значеннями `taxRates`, викличте метод `syncTaxRates` на екземплярі підписки користувача:

```php
$user->subscription('default')->syncTaxRates();
```

Це також синхронізує податкові ставки елементів для підписки з кількома продуктами. Якщо ваш застосунок пропонує підписки з кількома продуктами, переконайтеся, що ваша модель з білінгом реалізує метод `priceTaxRates`, [описаний вище](#subscription-taxes).

<a name="tax-exemption"></a>
#### Звільнення від податку

Cashier також пропонує методи `isNotTaxExempt`, `isTaxExempt` і `reverseChargeApplies`, щоб визначити, чи звільнений клієнт від податку. Ці методи викликатимуть API Stripe, щоб визначити статус звільнення клієнта від податку:

```php
use App\Models\User;

$user = User::find(1);

$user->isTaxExempt();
$user->isNotTaxExempt();
$user->reverseChargeApplies();
```

> [!WARNING]
> Ці методи також доступні на будь-якому об'єкті `Laravel\Cashier\Invoice`. Однак, викликані на об'єкті `Invoice`, вони визначатимуть статус звільнення на момент створення рахунка.

<a name="subscription-anchor-date"></a>
### Опорна дата підписки

За замовчуванням опорною датою розрахункового циклу є дата створення підписки або, якщо використовується пробний період, дата його завершення. Якщо ви хочете змінити опорну дату білінгу, скористайтеся методом `anchorBillingCycleOn`:

```php
use Illuminate\Http\Request;

Route::post('/user/subscribe', function (Request $request) {
    $anchor = Carbon::parse('first day of next month');

    $request->user()->newSubscription('default', 'price_monthly')
        ->anchorBillingCycleOn($anchor->startOfDay())
        ->create($request->paymentMethodId);

    // ...
});
```

Докладніше про керування розрахунковими циклами підписок дивіться в [документації Stripe щодо розрахункового циклу](https://stripe.com/docs/billing/subscriptions/billing-cycle)

<a name="cancelling-subscriptions"></a>
### Скасування підписок

Щоб скасувати підписку, викличте метод `cancel` на підписці користувача:

```php
$user->subscription('default')->cancel();
```

Коли підписку скасовано, Cashier автоматично встановить колонку `ends_at` у вашій таблиці бази даних `subscriptions`. Ця колонка використовується, щоб знати, коли метод `subscribed` має почати повертати `false`.

Наприклад, якщо клієнт скасує підписку 1 березня, але її завершення було заплановане лише на 5 березня, метод `subscribed` продовжуватиме повертати `true` до 5 березня. Так зроблено тому, що користувачеві зазвичай дозволено користуватися застосунком до кінця його розрахункового циклу.

Визначити, чи користувач скасував підписку, але досі перебуває в «пільговому періоді», можна методом `onGracePeriod`:

```php
if ($user->subscription('default')->onGracePeriod()) {
    // ...
}
```

Якщо ви хочете скасувати підписку негайно, викличте метод `cancelNow` на підписці користувача:

```php
$user->subscription('default')->cancelNow();
```

Якщо ви хочете скасувати підписку негайно і виставити рахунок за будь-яке невиставлене лічильне використання чи нові / очікувані позиції пропорційного перерахунку, викличте метод `cancelNowAndInvoice` на підписці користувача:

```php
$user->subscription('default')->cancelNowAndInvoice();
```

Ви також можете скасувати підписку в конкретний момент часу:

```php
$user->subscription('default')->cancelAt(
    now()->plus(days: 10)
);
```

Насамкінець вам слід завжди скасовувати підписки користувача, перш ніж видаляти пов'язану модель користувача:

```php
$user->subscription('default')->cancelNow();

$user->delete();
```

<a name="resuming-subscriptions"></a>
### Відновлення підписок

Якщо клієнт скасував свою підписку, а ви хочете її відновити, викличте на підписці метод `resume`. Щоб відновити підписку, клієнт має досі перебувати в «пільговому періоді»:

```php
$user->subscription('default')->resume();
```

Якщо клієнт скасує підписку, а потім відновить її до того, як вона повністю спливе, кошти з нього не спишуть одразу. Натомість його підписку буде повторно активовано, і рахунок виставлять за початковим розрахунковим циклом.

<a name="subscription-trials"></a>
## Пробні періоди підписок

<a name="with-payment-method-up-front"></a>
### З платіжним методом наперед

Якщо ви хочете пропонувати клієнтам пробні періоди, водночас збираючи інформацію про платіжний метод наперед, скористайтеся методом `trialDays` під час створення підписок:

```php
use Illuminate\Http\Request;

Route::post('/user/subscribe', function (Request $request) {
    $request->user()->newSubscription('default', 'price_monthly')
        ->trialDays(10)
        ->create($request->paymentMethodId);

    // ...
});
```

Цей метод встановить дату завершення пробного періоду в записі підписки в базі даних і вкаже Stripe не починати стягувати кошти з клієнта до цієї дати. Використовуючи метод `trialDays`, Cashier перезапише будь-який пробний період за замовчуванням, налаштований для ціни у Stripe.

> [!WARNING]
> Якщо підписку клієнта не буде скасовано до дати завершення пробного періоду, кошти з нього спишуть одразу після його спливання, тож обов'язково повідомляйте своїх користувачів про дату завершення пробного періоду.

Метод `trialUntil` дозволяє передати екземпляр `DateTime`, який вказує, коли пробний період має завершитися:

```php
use Illuminate\Support\Carbon;

$user->newSubscription('default', 'price_monthly')
    ->trialUntil(Carbon::now()->plus(days: 10))
    ->create($paymentMethod);
```

Визначити, чи користувач у межах пробного періоду, можна методом `onTrial` екземпляра користувача або методом `onTrial` екземпляра підписки. Два наведені нижче приклади рівнозначні:

```php
if ($user->onTrial('default')) {
    // ...
}

if ($user->subscription('default')->onTrial()) {
    // ...
}
```

Ви можете скористатися методом `endTrial`, щоб негайно завершити пробний період підписки:

```php
$user->subscription('default')->endTrial();
```

Щоб визначити, чи наявний пробний період сплив, скористайтеся методом `hasExpiredTrial`:

```php
if ($user->hasExpiredTrial('default')) {
    // ...
}

if ($user->subscription('default')->hasExpiredTrial()) {
    // ...
}
```

<a name="defining-trial-days-in-stripe-cashier"></a>
#### Визначення днів пробного періоду у Stripe / Cashier

Ви можете визначити, скільки днів пробного періоду отримують ваші ціни, у панелі Stripe або завжди передавати їх явно через Cashier. Якщо ви обрали визначати дні пробного періоду для своїх цін у Stripe, майте на увазі, що нові підписки, включно з новими підписками клієнта, який мав підписку в минулому, завжди отримуватимуть пробний період, доки ви явно не викличете метод `skipTrial()`.

<a name="without-payment-method-up-front"></a>
### Без платіжного методу наперед

Якщо ви хочете пропонувати пробні періоди, не збираючи інформацію про платіжний метод користувача наперед, ви можете встановити колонку `trial_ends_at` у записі користувача на бажану дату завершення пробного періоду. Зазвичай це роблять під час реєстрації користувача:

```php
use App\Models\User;

$user = User::create([
    // ...
    'trial_ends_at' => now()->plus(days: 10),
]);
```

> [!WARNING]
> Обов'язково додайте [приведення до дати](/docs/{{version}}/eloquent-mutators#date-casting) для атрибута `trial_ends_at` у визначенні класу вашої моделі з білінгом.

Cashier називає такий тип пробного періоду «загальним пробним періодом», оскільки він не прив'язаний до жодної наявної підписки. Метод `onTrial` на екземплярі моделі з білінгом поверне `true`, якщо поточна дата не перевищує значення `trial_ends_at`:

```php
if ($user->onTrial()) {
    // User is within their trial period...
}
```

Щойно ви будете готові створити для користувача справжню підписку, скористайтеся методом `newSubscription` як зазвичай:

```php
$user = User::find(1);

$user->newSubscription('default', 'price_monthly')->create($paymentMethod);
```

Щоб отримати дату завершення пробного періоду користувача, скористайтеся методом `trialEndsAt`. Цей метод поверне екземпляр дати Carbon, якщо користувач на пробному періоді, або `null`, якщо ні. Ви також можете передати необов'язковий параметр типу підписки, якщо хочете отримати дату завершення пробного періоду для конкретної підписки, відмінної від типової:

```php
if ($user->onTrial()) {
    $trialEndsAt = $user->trialEndsAt('main');
}
```

Ви також можете скористатися методом `onGenericTrial`, якщо хочете дізнатися саме те, що користувач перебуває в «загальному» пробному періоді і ще не створив справжньої підписки:

```php
if ($user->onGenericTrial()) {
    // User is within their "generic" trial period...
}
```

<a name="extending-trials"></a>
### Продовження пробних періодів

Метод `extendTrial` дозволяє продовжити пробний період підписки після її створення. Якщо пробний період уже сплив і з клієнта вже стягують плату за підписку, ви все одно можете запропонувати йому продовжений пробний період. Час, проведений у межах пробного періоду, буде вирахувано з наступного рахунка клієнта:

```php
use App\Models\User;

$subscription = User::find(1)->subscription('default');

// End the trial 7 days from now...
$subscription->extendTrial(
    now()->plus(days: 7)
);

// Add an additional 5 days to the trial...
$subscription->extendTrial(
    $subscription->trial_ends_at->plus(days: 5)
);
```

<a name="handling-stripe-webhooks"></a>
## Обробка вебхуків Stripe

> [!NOTE]
> Ви можете скористатися [Stripe CLI](https://stripe.com/docs/stripe-cli), щоб тестувати вебхуки під час локальної розробки.

Stripe може сповіщати ваш застосунок про різні події через вебхуки. За замовчуванням сервіс-провайдер Cashier автоматично реєструє маршрут, що вказує на контролер вебхуків Cashier. Цей контролер оброблятиме всі вхідні запити вебхуків.

За замовчуванням контролер вебхуків Cashier автоматично оброблятиме скасування підписок із занадто великою кількістю невдалих списань (як визначено у ваших налаштуваннях Stripe), оновлення клієнтів, видалення клієнтів, оновлення підписок і зміни платіжного методу; однак, як ми невдовзі побачимо, ви можете розширити цей контролер, щоб обробляти будь-яку подію вебхука Stripe, яку забажаєте.

Щоб ваш застосунок міг обробляти вебхуки Stripe, обов'язково налаштуйте URL вебхука в панелі керування Stripe. За замовчуванням контролер вебхуків Cashier відповідає за шляхом URL `/stripe/webhook`. Повний список усіх вебхуків, які вам слід увімкнути в панелі керування Stripe:

- `customer.subscription.created`
- `customer.subscription.updated`
- `customer.subscription.deleted`
- `customer.updated`
- `customer.deleted`
- `payment_method.automatically_updated`
- `invoice.payment_action_required`
- `invoice.payment_succeeded`

Для зручності Cashier містить артизан-команду `cashier:webhook`. Ця команда створить у Stripe вебхук, який слухає всі події, потрібні Cashier:

```shell
php artisan cashier:webhook
```

За замовчуванням створений вебхук вказуватиме на URL, визначений змінною оточення `APP_URL`, і маршрут `cashier.webhook`, що входить до Cashier. Ви можете передати опцію `--url` під час виклику команди, якщо хочете використати інший URL:

```shell
php artisan cashier:webhook --url "https://example.com/stripe/webhook"
```

Створений вебхук використовуватиме версію API Stripe, з якою сумісна ваша версія Cashier. Якщо ви хочете використати іншу версію Stripe, передайте опцію `--api-version`:

```shell
php artisan cashier:webhook --api-version="2019-12-03"
```

Після створення вебхук одразу стане активним. Якщо ви хочете створити вебхук, але залишити його вимкненим, доки не будете готові, передайте опцію `--disabled` під час виклику команди:

```shell
php artisan cashier:webhook --disabled
```

> [!WARNING]
> Обов'язково захистіть вхідні запити вебхуків Stripe за допомогою `middleware` [перевірки підпису вебхука](#verifying-webhook-signatures), що входить до Cashier.

<a name="webhooks-csrf-protection"></a>
#### Вебхуки й захист від CSRF

Оскільки вебхуки Stripe мають обходити [захист від CSRF](/docs/{{version}}/csrf) у Laravel, вам слід подбати, щоб Laravel не намагався перевіряти CSRF-токен для вхідних вебхуків Stripe. Щоб досягти цього, виключіть `stripe/*` із захисту від CSRF у файлі `bootstrap/app.php` вашого застосунку:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->preventRequestForgery(except: [
        'stripe/*',
    ]);
})
```

<a name="defining-webhook-event-handlers"></a>
### Визначення обробників подій вебхуків

Cashier автоматично обробляє скасування підписок за невдалих списань та інші поширені події вебхуків Stripe. Однак, якщо у вас є додаткові події вебхуків, які ви хочете обробляти, ви можете зробити це, слухаючи такі події, які диспетчеризує Cashier:

- `Laravel\Cashier\Events\WebhookReceived`
- `Laravel\Cashier\Events\WebhookHandled`

Обидві події містять повні дані вебхука Stripe. Наприклад, якщо ви хочете обробити вебхук `invoice.payment_succeeded`, зареєструйте [слухача](/docs/{{version}}/events#defining-listeners), який оброблятиме подію:

```php
<?php

namespace App\Listeners;

use Laravel\Cashier\Events\WebhookReceived;

class StripeEventListener
{
    /**
     * Handle received Stripe webhooks.
     */
    public function handle(WebhookReceived $event): void
    {
        if ($event->payload['type'] === 'invoice.payment_succeeded') {
            // Handle the incoming event...
        }
    }
}
```

<a name="verifying-webhook-signatures"></a>
### Перевірка підписів вебхуків

Щоб захистити свої вебхуки, ви можете скористатися [підписами вебхуків Stripe](https://stripe.com/docs/webhooks/signatures). Для зручності Cashier автоматично містить `middleware`, який перевіряє, що вхідний запит вебхука Stripe є дійсним.

Щоб увімкнути перевірку вебхуків, переконайтеся, що змінну оточення `STRIPE_WEBHOOK_SECRET` встановлено у файлі `.env` вашого застосунку. `secret` вебхука можна отримати з панелі вашого облікового запису Stripe.

<a name="single-charges"></a>
## Разові списання

<a name="simple-charge"></a>
### Просте списання

Якщо ви хочете зробити разове списання з клієнта за ідентифікатором платіжного методу, скористайтеся методом `charge` на екземплярі моделі з білінгом. Якщо вам потрібно зібрати платіжні дані від клієнта, перш ніж обробляти разове списання, дивіться документацію щодо [Payment Element для разових списань](#payment-element-for-single-charges):

```php
use Illuminate\Http\Request;

Route::post('/purchase', function (Request $request) {
    $payment = $request->user()->charge(
        100, $request->paymentMethodId
    );

    // ...
});
```

Метод `charge` приймає масив третім аргументом, що дозволяє передати будь-які опції до створення Payment Intent у Stripe, яке лежить в основі. Докладніше про доступні вам опції під час створення Payment Intent дивіться в [документації Stripe](https://stripe.com/docs/api/payment_intents/create):

```php
$user->charge(100, $paymentMethod, [
    'custom_option' => $value,
]);
```

Ви також можете скористатися методом `charge` без наявного клієнта чи користувача. Для цього викличте метод `charge` на новому екземплярі моделі з білінгом вашого застосунку:

```php
use App\Models\User;

$payment = (new User)->charge(100, $paymentMethod);
```

Метод `charge` видасть виняток, якщо списання не вдасться. Якщо списання успішне, метод поверне екземпляр `Laravel\Cashier\Payment`:

```php
try {
    $payment = $user->charge(100, $paymentMethod);
} catch (Exception $e) {
    // ...
}
```

> [!WARNING]
> Метод `charge` приймає суму платежу в найменшій одиниці валюти, яку використовує ваш застосунок. Наприклад, якщо клієнти платять у доларах США, суми слід вказувати в центах.

<a name="charge-with-invoice"></a>
### Списання з рахунком

Іноді вам може знадобитися зробити разове списання й запропонувати клієнту PDF-рахунок. Метод `invoicePrice` дозволяє зробити саме це. Наприклад, виставмо клієнту рахунок за п'ять нових футболок:

```php
$user->invoicePrice('price_tshirt', 5);
```

Рахунок буде одразу списано з платіжного методу користувача за замовчуванням. Метод `invoicePrice` також приймає масив третім аргументом. Цей масив містить опції білінгу для позиції рахунка. Четвертий аргумент, який приймає метод, - теж масив, що має містити опції білінгу для самого рахунка:

```php
$user->invoicePrice('price_tshirt', 5, [
    'discounts' => [
        ['coupon' => 'SUMMER21SALE']
    ],
], [
    'default_tax_rates' => ['txr_id'],
]);
```

Так само як і `invoicePrice`, ви можете скористатися методом `tabPrice`, щоб створити разове списання за кілька позицій (до 250 позицій на рахунок), додавши їх до «рахунку» клієнта, а потім виставивши рахунок. Наприклад, ми можемо виставити клієнту рахунок за п'ять футболок і два кухлі:

```php
$user->tabPrice('price_tshirt', 5);
$user->tabPrice('price_mug', 2);
$user->invoice();
```

Як альтернативу ви можете скористатися методом `invoiceFor`, щоб зробити «разове» списання з платіжного методу клієнта за замовчуванням:

```php
$user->invoiceFor('One Time Fee', 500);
```

Хоча метод `invoiceFor` доступний для використання, рекомендується користуватися методами `invoicePrice` і `tabPrice` із заздалегідь визначеними цінами. Так ви матимете доступ до кращої аналітики й даних у своїй панелі Stripe щодо продажів у розрізі продуктів.

> [!WARNING]
> Методи `invoice`, `invoicePrice` та `invoiceFor` створять рахунок Stripe, який повторюватиме невдалі спроби білінгу. Якщо ви не хочете, щоб рахунки повторювали невдалі списання, вам потрібно буде закрити їх через API Stripe після першого невдалого списання.

<a name="creating-payment-intents"></a>
### Створення Payment Intent

Ви можете створити новий payment intent у Stripe, викликавши метод `pay` на екземплярі моделі з білінгом. Виклик цього методу створить payment intent, загорнутий в екземпляр `Laravel\Cashier\Payment`:

```php
use Illuminate\Http\Request;

Route::post('/pay', function (Request $request) {
    $payment = $request->user()->pay(
        $request->get('amount')
    );

    return $payment->client_secret;
});
```

Створивши payment intent, ви можете повернути client secret до фронтенду свого застосунку, щоб користувач міг завершити платіж у своєму браузері. Докладніше про побудову повних платіжних потоків з payment intent у Stripe дивіться в [документації Stripe](https://stripe.com/docs/payments/accept-a-payment?platform=web).

Використовуючи метод `pay`, клієнту будуть доступні платіжні методи за замовчуванням, увімкнені у вашій панелі Stripe. Як альтернативу, якщо ви хочете дозволити лише певні платіжні методи, скористайтеся методом `payWith`:

```php
use Illuminate\Http\Request;

Route::post('/pay', function (Request $request) {
    $payment = $request->user()->payWith(
        $request->get('amount'), ['card', 'bancontact']
    );

    return $payment->client_secret;
});
```

> [!WARNING]
> Методи `pay` і `payWith` приймають суму платежу в найменшій одиниці валюти, яку використовує ваш застосунок. Наприклад, якщо клієнти платять у доларах США, суми слід вказувати в центах.

<a name="refunding-charges"></a>
### Повернення коштів за списаннями

Якщо вам потрібно повернути кошти за платежем Stripe, скористайтеся методом `refund`. Цей метод приймає ID Payment Intent зі Stripe першим аргументом:

```php
$payment = $user->charge(100, $paymentMethodId);

$user->refund($payment->id);
```

<a name="invoices"></a>
## Рахунки

<a name="retrieving-invoices"></a>
### Отримання рахунків

Ви можете легко отримати масив рахунків моделі з білінгом методом `invoices`. Метод `invoices` повертає колекцію екземплярів `Laravel\Cashier\Invoice`:

```php
$invoices = $user->invoices();
```

Якщо ви хочете включити до результатів очікувані рахунки, скористайтеся методом `invoicesIncludingPending`:

```php
$invoices = $user->invoicesIncludingPending();
```

Ви можете скористатися методом `findInvoice`, щоб отримати конкретний рахунок за його ID:

```php
$invoice = $user->findInvoice($invoiceId);
```

<a name="displaying-invoice-information"></a>
#### Показ інформації про рахунок

Перелічуючи рахунки клієнта, ви можете скористатися методами рахунка, щоб показати релевантну інформацію про нього. Наприклад, ви можете захотіти перелічити кожен рахунок у таблиці, дозволивши користувачеві легко завантажити будь-який з них:

```blade
<table>
    @foreach ($invoices as $invoice)
        <tr>
            <td>{{ $invoice->date()->toFormattedDateString() }}</td>
            <td>{{ $invoice->total() }}</td>
            <td><a href="/user/invoice/{{ $invoice->id }}">Download</a></td>
        </tr>
    @endforeach
</table>
```

<a name="upcoming-invoices"></a>
### Майбутні рахунки

Щоб отримати майбутній рахунок клієнта, скористайтеся методом `upcomingInvoice`:

```php
$invoice = $user->upcomingInvoice();
```

Так само, якщо клієнт має кілька підписок, ви можете отримати майбутній рахунок для конкретної підписки:

```php
$invoice = $user->subscription('default')->upcomingInvoice();
```

<a name="previewing-subscription-invoices"></a>
### Попередній перегляд рахунків за підпискою

За допомогою методу `previewInvoice` ви можете переглянути рахунок перед зміною цін. Це дозволить визначити, як виглядатиме рахунок вашого клієнта після певної зміни цін:

```php
$invoice = $user->subscription('default')->previewInvoice('price_yearly');
```

Ви можете передати до методу `previewInvoice` масив цін, щоб переглянути рахунки з кількома новими цінами:

```php
$invoice = $user->subscription('default')->previewInvoice(['price_yearly', 'price_metered']);
```

<a name="generating-invoice-pdfs"></a>
### Генерація PDF-рахунків

Перш ніж генерувати PDF-рахунки, вам слід встановити через Composer бібліотеку Dompdf - рендерер рахунків за замовчуванням у Cashier:

```shell
composer require dompdf/dompdf
```

З маршруту чи контролера ви можете скористатися методом `downloadInvoice`, щоб згенерувати PDF-завантаження певного рахунка. Цей метод автоматично згенерує належну HTTP-відповідь, потрібну для завантаження рахунка:

```php
use Illuminate\Http\Request;

Route::get('/user/invoice/{invoice}', function (Request $request, string $invoiceId) {
    return $request->user()->downloadInvoice($invoiceId);
});
```

За замовчуванням усі дані в рахунку беруться з даних клієнта й рахунка, збережених у Stripe. Ім'я файлу базується на значенні вашої конфігурації `app.name`. Однак ви можете змінити частину цих даних, передавши масив другим аргументом до методу `downloadInvoice`. Цей масив дозволяє налаштувати інформацію на кшталт деталей вашої компанії та продукту:

```php
return $request->user()->downloadInvoice($invoiceId, [
    'vendor' => 'Your Company',
    'product' => 'Your Product',
    'street' => 'Main Str. 1',
    'location' => '2000 Antwerp, Belgium',
    'phone' => '+32 499 00 00 00',
    'email' => 'info@example.com',
    'url' => 'https://example.com',
    'vendorVat' => 'BE123456789',
]);
```

Метод `downloadInvoice` також дозволяє задати власне ім'я файлу третім аргументом. До цього імені автоматично додасться суфікс `.pdf`:

```php
return $request->user()->downloadInvoice($invoiceId, [], 'my-invoice');
```

<a name="custom-invoice-render"></a>
#### Власний рендерер рахунків

Cashier також дозволяє використовувати власний рендерер рахунків. За замовчуванням Cashier використовує реалізацію `DompdfInvoiceRenderer`, яка застосовує PHP-бібліотеку [dompdf](https://github.com/dompdf/dompdf) для генерації рахунків Cashier. Однак ви можете використати будь-який рендерер, реалізувавши інтерфейс `Laravel\Cashier\Contracts\InvoiceRenderer`. Наприклад, ви можете захотіти рендерити PDF-рахунок через API-виклик до стороннього сервісу рендерингу PDF:

```php
use Illuminate\Support\Facades\Http;
use Laravel\Cashier\Contracts\InvoiceRenderer;
use Laravel\Cashier\Invoice;

class ApiInvoiceRenderer implements InvoiceRenderer
{
    /**
     * Render the given invoice and return the raw PDF bytes.
     */
    public function render(Invoice $invoice, array $data = [], array $options = []): string
    {
        $html = $invoice->view($data)->render();

        return Http::get('https://example.com/html-to-pdf', ['html' => $html])->get()->body();
    }
}
```

Щойно ви реалізували контракт рендерера рахунків, вам слід оновити значення конфігурації `cashier.invoices.renderer` у конфігураційному файлі `config/cashier.php` вашого застосунку. Це значення має бути іменем класу вашої власної реалізації рендерера.

<a name="checkout"></a>
## Checkout

Cashier Stripe також підтримує [Stripe Checkout](https://stripe.com/payments/checkout). Stripe Checkout знімає біль від створення власних сторінок для приймання платежів, надаючи готову, розміщену сторінку оплати.

Наведена нижче документація містить інформацію про те, як почати користуватися Stripe Checkout з Cashier. Щоб дізнатися більше про Stripe Checkout, вам також варто переглянути [власну документацію Stripe щодо Checkout](https://stripe.com/docs/payments/checkout).

<a name="product-checkouts"></a>
### Оформлення продуктів

Ви можете виконати оформлення для наявного продукту, створеного у вашій панелі Stripe, методом `checkout` на моделі з білінгом. Метод `checkout` ініціює нову сесію Stripe Checkout. За замовчуванням від вас вимагається передати Stripe Price ID:

```php
use Illuminate\Http\Request;

Route::get('/product-checkout', function (Request $request) {
    return $request->user()->checkout('price_tshirt');
});
```

За потреби ви також можете вказати кількість продукту:

```php
use Illuminate\Http\Request;

Route::get('/product-checkout', function (Request $request) {
    return $request->user()->checkout(['price_tshirt' => 15]);
});
```

Коли клієнт відвідає цей маршрут, його буде перенаправлено на сторінку Checkout від Stripe. За замовчуванням, коли користувач успішно завершує чи скасовує покупку, його буде перенаправлено до маршруту `home` вашого застосунку, але ви можете вказати власні URL зворотного виклику опціями `success_url` і `cancel_url`:

```php
use Illuminate\Http\Request;

Route::get('/product-checkout', function (Request $request) {
    return $request->user()->checkout(['price_tshirt' => 1], [
        'success_url' => route('your-success-route'),
        'cancel_url' => route('your-cancel-route'),
    ]);
});
```

Визначаючи опцію оформлення `success_url`, ви можете вказати Stripe додавати ID сесії оформлення як параметр рядка запиту під час виклику вашого URL. Для цього додайте буквальний рядок `{CHECKOUT_SESSION_ID}` до рядка запиту `success_url`. Stripe замінить цей плейсхолдер фактичним ID сесії оформлення:

```php
use Illuminate\Http\Request;
use Stripe\Checkout\Session;
use Stripe\Customer;

Route::get('/product-checkout', function (Request $request) {
    return $request->user()->checkout(['price_tshirt' => 1], [
        'success_url' => route('checkout-success').'?session_id={CHECKOUT_SESSION_ID}',
        'cancel_url' => route('checkout-cancel'),
    ]);
});

Route::get('/checkout-success', function (Request $request) {
    $checkoutSession = $request->user()->stripe()->checkout->sessions->retrieve($request->get('session_id'));

    return view('checkout.success', ['checkoutSession' => $checkoutSession]);
})->name('checkout-success');
```

<a name="checkout-promotion-codes"></a>
#### Промокоди

За замовчуванням Stripe Checkout не дозволяє [промокоди, які активує користувач](https://stripe.com/docs/billing/subscriptions/discounts/codes). На щастя, увімкнути їх для вашої сторінки Checkout нескладно. Для цього викличте метод `allowPromotionCodes`:

```php
use Illuminate\Http\Request;

Route::get('/product-checkout', function (Request $request) {
    return $request->user()
        ->allowPromotionCodes()
        ->checkout('price_tshirt');
});
```

<a name="single-charge-checkouts"></a>
### Оформлення разових списань

Ви також можете виконати просте списання за спонтанний продукт, не створений у вашій панелі Stripe. Для цього скористайтеся методом `checkoutCharge` на моделі з білінгом і передайте йому суму до списання, назву продукту й необов'язкову кількість. Коли клієнт відвідає цей маршрут, його буде перенаправлено на сторінку Checkout від Stripe:

```php
use Illuminate\Http\Request;

Route::get('/charge-checkout', function (Request $request) {
    return $request->user()->checkoutCharge(1200, 'T-Shirt', 5);
});
```

> [!WARNING]
> Використовуючи метод `checkoutCharge`, Stripe завжди створюватиме новий продукт і ціну у вашій панелі Stripe. Тому ми рекомендуємо створювати продукти заздалегідь у панелі Stripe і використовувати натомість метод `checkout`.

<a name="subscription-checkouts"></a>
### Оформлення підписок

> [!WARNING]
> Використання Stripe Checkout для підписок вимагає увімкнути вебхук `customer.subscription.created` у вашій панелі Stripe. Цей вебхук створить запис підписки у вашій базі даних і збереже всі релевантні елементи підписки.

Ви також можете скористатися Stripe Checkout, щоб ініціювати підписки. Визначивши свою підписку методами білдера підписок у Cashier, викличте метод `checkout`. Коли клієнт відвідає цей маршрут, його буде перенаправлено на сторінку Checkout від Stripe:

```php
use Illuminate\Http\Request;

Route::get('/subscription-checkout', function (Request $request) {
    return $request->user()
        ->newSubscription('default', 'price_monthly')
        ->checkout();
});
```

Так само як і з оформленням продуктів, ви можете налаштувати URL успіху та скасування:

```php
use Illuminate\Http\Request;

Route::get('/subscription-checkout', function (Request $request) {
    return $request->user()
        ->newSubscription('default', 'price_monthly')
        ->checkout([
            'success_url' => route('your-success-route'),
            'cancel_url' => route('your-cancel-route'),
        ]);
});
```

Звісно, ви також можете увімкнути промокоди для оформлення підписок:

```php
use Illuminate\Http\Request;

Route::get('/subscription-checkout', function (Request $request) {
    return $request->user()
        ->newSubscription('default', 'price_monthly')
        ->allowPromotionCodes()
        ->checkout();
});
```

> [!WARNING]
> На жаль, Stripe Checkout не підтримує всі опції білінгу підписок під час їх започаткування. Використання методу `anchorBillingCycleOn` на білдері підписок, налаштування поведінки пропорційного перерахунку чи поведінки платежу не матиме жодного ефекту під час сесій Stripe Checkout. Перегляньте [документацію API Stripe Checkout Session](https://stripe.com/docs/api/checkout/sessions/create), щоб дізнатися, які параметри доступні.

<a name="stripe-checkout-trial-periods"></a>
#### Stripe Checkout і пробні періоди

Звісно, ви можете визначити пробний період, створюючи підписку, яку буде завершено через Stripe Checkout:

```php
$checkout = Auth::user()->newSubscription('default', 'price_monthly')
    ->trialDays(3)
    ->checkout();
```

Однак пробний період має тривати щонайменше 48 годин - це мінімальний час пробного періоду, який підтримує Stripe Checkout.

<a name="stripe-checkout-subscriptions-and-webhooks"></a>
#### Підписки й вебхуки

Пам'ятайте: Stripe і Cashier оновлюють стани підписок через вебхуки, тож існує ймовірність, що підписка ще не буде активною, коли клієнт повернеться до застосунку після введення платіжних даних. Щоб обробити цей сценарій, ви можете захотіти показати повідомлення, яке інформує користувача, що його платіж чи підписка в обробці.

<a name="collecting-tax-ids"></a>
### Збір податкових номерів

Checkout також підтримує збір податкового номера клієнта. Щоб увімкнути це для сесії оформлення, викличте метод `collectTaxIds` під час її створення:

```php
$checkout = $user->collectTaxIds()->checkout('price_tshirt');
```

Коли цей метод викликано, клієнту стане доступним новий чекбокс, який дозволяє вказати, що він купує як компанія. Якщо так, він матиме змогу надати свій податковий номер.

> [!WARNING]
> Якщо ви вже налаштували [автоматичний збір податків](#tax-configuration) у сервіс-провайдері свого застосунку, ця можливість буде увімкнена автоматично, і викликати метод `collectTaxIds` не потрібно.

<a name="guest-checkouts"></a>
### Гостьове оформлення

За допомогою методу `Checkout::guest` ви можете ініціювати сесії оформлення для гостей вашого застосунку, які не мають «облікового запису»:

```php
use Illuminate\Http\Request;
use Laravel\Cashier\Checkout;

Route::get('/product-checkout', function (Request $request) {
    return Checkout::guest()->create('price_tshirt', [
        'success_url' => route('your-success-route'),
        'cancel_url' => route('your-cancel-route'),
    ]);
});
```

Так само як і під час створення сесій оформлення для наявних користувачів, ви можете скористатися додатковими методами, доступними на екземплярі `Laravel\Cashier\CheckoutBuilder`, щоб налаштувати гостьову сесію оформлення:

```php
use Illuminate\Http\Request;
use Laravel\Cashier\Checkout;

Route::get('/product-checkout', function (Request $request) {
    return Checkout::guest()
        ->withPromotionCode('promo-code')
        ->create('price_tshirt', [
            'success_url' => route('your-success-route'),
            'cancel_url' => route('your-cancel-route'),
        ]);
});
```

Після завершення гостьового оформлення Stripe може надіслати подію вебхука `checkout.session.completed`, тож обов'язково [налаштуйте свій вебхук Stripe](https://dashboard.stripe.com/webhooks), щоб ця подія справді надсилалася до вашого застосунку. Щойно вебхук увімкнено в панелі Stripe, ви можете [обробити його за допомогою Cashier](#handling-stripe-webhooks). Об'єкт у даних вебхука буде [об'єктом оформлення](https://stripe.com/docs/api/checkout/sessions/object), який ви можете оглянути, щоб виконати замовлення свого клієнта.

<a name="handling-failed-payments"></a>
## Обробка невдалих платежів

Іноді платежі за підписками чи разові списання можуть не вдатися. Коли це стається, Cashier видасть виняток `Laravel\Cashier\Exceptions\IncompletePayment`, який повідомляє вас про це. Перехопивши цей виняток, ви маєте два варіанти дій.

По-перше, ви можете перенаправити клієнта на спеціальну сторінку підтвердження платежу, що входить до Cashier. Ця сторінка вже має пов'язаний іменований маршрут, зареєстрований через сервіс-провайдер Cashier. Тож ви можете перехопити виняток `IncompletePayment` і перенаправити користувача на сторінку підтвердження платежу:

```php
use Laravel\Cashier\Exceptions\IncompletePayment;

try {
    $subscription = $user->newSubscription('default', 'price_monthly')
        ->create($paymentMethod);
} catch (IncompletePayment $exception) {
    return redirect()->route(
        'cashier.payment',
        [$exception->payment->id, 'redirect' => route('home')]
    );
}
```

На сторінці підтвердження платежу клієнту буде запропоновано ввести дані своєї картки ще раз і виконати будь-які додаткові дії, потрібні Stripe, як-от підтвердження «3D Secure». Після підтвердження платежу користувача буде перенаправлено на URL, указаний у параметрі `redirect` вище. Під час перенаправлення до URL буде додано змінні рядка запиту `message` (рядок) і `success` (ціле число). Сторінка оплати наразі підтримує такі типи платіжних методів:

<div class="content-list" markdown="1">

- Credit Cards
- Alipay
- Bancontact
- BECS Direct Debit
- EPS
- Giropay
- iDEAL
- SEPA Direct Debit

</div>

Як альтернативу ви можете дозволити Stripe обробляти підтвердження платежу за вас. У цьому разі замість перенаправлення на сторінку підтвердження платежу ви можете [налаштувати автоматичні листи білінгу Stripe](https://dashboard.stripe.com/account/billing/automatic) у своїй панелі Stripe. Однак, якщо виняток `IncompletePayment` перехоплено, вам усе одно слід повідомити користувача, що він отримає лист з подальшими інструкціями щодо підтвердження платежу.

Платіжні винятки можуть видаватися такими методами: `charge`, `invoiceFor` та `invoice` на моделях, що використовують трейт `Billable`. Працюючи з підписками, метод `create` на `SubscriptionBuilder`, а також методи `incrementAndInvoice` і `swapAndInvoice` на моделях `Subscription` і `SubscriptionItem` можуть видавати винятки незавершеного платежу.

Визначити, чи має наявна підписка незавершений платіж, можна методом `hasIncompletePayment` на моделі з білінгом чи на екземплярі підписки:

```php
if ($user->hasIncompletePayment('default')) {
    // ...
}

if ($user->subscription('default')->hasIncompletePayment()) {
    // ...
}
```

Ви можете з'ясувати конкретний стан незавершеного платежу, оглянувши властивість `payment` на екземплярі винятку:

```php
use Laravel\Cashier\Exceptions\IncompletePayment;

try {
    $user->charge(1000, 'pm_card_threeDSecure2Required');
} catch (IncompletePayment $exception) {
    // Get the payment intent status...
    $exception->payment->status;

    // Check specific conditions...
    if ($exception->payment->requiresPaymentMethod()) {
        // ...
    } elseif ($exception->payment->requiresConfirmation()) {
        // ...
    }
}
```

<a name="confirming-payments"></a>
### Підтвердження платежів

Деякі платіжні методи потребують додаткових даних для підтвердження платежів. Наприклад, платіжні методи SEPA потребують додаткових даних «mandate» під час процесу оплати. Ви можете передати ці дані до Cashier методом `withPaymentConfirmationOptions`:

```php
$subscription->withPaymentConfirmationOptions([
    'mandate_data' => '...',
])->swap('price_xxx');
```

Ви можете переглянути [документацію API Stripe](https://stripe.com/docs/api/payment_intents/confirm), щоб побачити всі опції, які приймаються під час підтвердження платежів.

<a name="strong-customer-authentication"></a>
## Strong Customer Authentication

Якщо ваш бізнес чи хтось із ваших клієнтів базується в Європі, вам потрібно дотримуватися правил Strong Customer Authentication (SCA) від ЄС. Ці правила запровадив Європейський Союз у вересні 2019 року, щоб запобігти платіжному шахрайству. На щастя, Stripe і Cashier готові до створення застосунків, сумісних із SCA.

> [!WARNING]
> Перш ніж почати, перегляньте [посібник Stripe щодо PSD2 і SCA](https://stripe.com/guides/strong-customer-authentication), а також їхню [документацію щодо нових API SCA](https://stripe.com/docs/strong-customer-authentication).

<a name="payments-requiring-additional-confirmation"></a>
### Платежі, що потребують додаткового підтвердження

Правила SCA часто вимагають додаткової перевірки для підтвердження й обробки платежу. Коли це стається, Cashier видасть виняток `Laravel\Cashier\Exceptions\IncompletePayment`, який повідомляє вас, що потрібна додаткова перевірка. Докладніше про обробку цих винятків дивіться в документації щодо [обробки невдалих платежів](#handling-failed-payments).

Екрани підтвердження платежу, які показують Stripe чи Cashier, можуть бути адаптовані до платіжного потоку конкретного банку чи емітента картки і можуть містити додаткове підтвердження картки, тимчасове невелике списання, окрему автентифікацію пристрою чи інші форми перевірки.

<a name="incomplete-and-past-due-state"></a>
#### Стани incomplete і past due

Коли платіж потребує додаткового підтвердження, підписка залишатиметься у стані `incomplete` чи `past_due`, як указано в її колонці бази даних `stripe_status`. Cashier автоматично активує підписку клієнта, щойно підтвердження платежу буде завершено, а ваш застосунок отримає від Stripe сповіщення про це через вебхук.

Докладніше про стани `incomplete` і `past_due` дивіться в [нашій додатковій документації щодо цих станів](#incomplete-and-past-due-status).

<a name="off-session-payment-notifications"></a>
### Сповіщення про позасесійні платежі

Оскільки правила SCA вимагають від клієнтів час від часу підтверджувати свої платіжні дані навіть тоді, коли їхня підписка активна, Cashier може надіслати клієнту сповіщення, коли потрібне позасесійне підтвердження платежу. Наприклад, це може статися під час поновлення підписки. Сповіщення про платіж у Cashier можна увімкнути, встановивши змінну оточення `CASHIER_PAYMENT_NOTIFICATION` у клас сповіщення. За замовчуванням це сповіщення вимкнено. Звісно, Cashier містить клас сповіщення, який ви можете використати для цього, але за бажанням ви можете надати власний клас сповіщення:

```ini
CASHIER_PAYMENT_NOTIFICATION=Laravel\Cashier\Notifications\ConfirmPayment
```

Щоб сповіщення про позасесійне підтвердження платежу доставлялися, переконайтеся, що [вебхуки Stripe налаштовано](#handling-stripe-webhooks) для вашого застосунку і що вебхук `invoice.payment_action_required` увімкнено у вашій панелі Stripe. Крім того, ваша модель `Billable` має також використовувати трейт `Illuminate\Notifications\Notifiable` з Laravel.

> [!WARNING]
> Сповіщення надсилатимуться навіть тоді, коли клієнти вручну роблять платіж, що потребує додаткового підтвердження. На жаль, Stripe не може знати, що платіж було зроблено вручну чи «поза сесією». Але клієнт просто побачить повідомлення «Payment Successful», якщо відвідає сторінку оплати після того, як уже підтвердив свій платіж. Клієнту не дозволять випадково підтвердити той самий платіж двічі й отримати випадкове друге списання.

<a name="stripe-sdk"></a>
## Stripe SDK

Багато об'єктів Cashier є обгортками навколо об'єктів Stripe SDK. Якщо ви хочете працювати з об'єктами Stripe напряму, ви можете зручно отримати їх методом `asStripe`:

```php
$stripeSubscription = $subscription->asStripeSubscription();

$stripeSubscription->application_fee_percent = 5;

$stripeSubscription->save();
```

Ви також можете скористатися методом `updateStripeSubscription`, щоб оновити підписку Stripe напряму:

```php
$subscription->updateStripeSubscription(['application_fee_percent' => 5]);
```

Ви можете викликати метод `stripe` на класі `Cashier`, якщо хочете використати клієнт `Stripe\StripeClient` напряму. Наприклад, цим методом можна звернутися до екземпляра `StripeClient` і отримати список цін зі свого облікового запису Stripe:

```php
use Laravel\Cashier\Cashier;

$prices = Cashier::stripe()->prices->all();
```

<a name="testing"></a>
## Тестування

Тестуючи застосунок, що використовує Cashier, ви можете мокати фактичні HTTP-запити до API Stripe; однак це вимагатиме від вас частково перереалізувати власну поведінку Cashier. Тому ми рекомендуємо дозволити вашим тестам звертатися до справжнього API Stripe. Хоча це повільніше, воно дає більше впевненості, що ваш застосунок працює як очікується, а будь-які повільні тести можна винести до окремої тестової групи Pest / PHPUnit.

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

Для початку додайте **тестову** версію свого секрету Stripe до файлу `phpunit.xml`:

```xml
<env name="STRIPE_SECRET" value="sk_test_<your-key>"/>
```

Тепер щоразу, коли ви взаємодіятимете з Cashier під час тестування, він надсилатиме справжні API-запити до вашого тестового середовища Stripe. Для зручності вам слід заздалегідь наповнити свій тестовий обліковий запис Stripe підписками / цінами, які ви можете використовувати під час тестування.

> [!NOTE]
> Щоб протестувати різноманітні сценарії білінгу, як-от відмови й збої кредитних карток, ви можете скористатися широким набором [тестових номерів карток і токенів](https://stripe.com/docs/testing), які надає Stripe.

---

Джерело: https://laravelukraine.com/docs/13.x/cashier-paddle

# Laravel Cashier (Paddle)

- [Вступ](#introduction)
- [Оновлення Cashier](#upgrading-cashier)
- [Встановлення](#installation)
    - [Paddle Sandbox](#paddle-sandbox)
- [Конфігурація](#configuration)
    - [Модель з білінгом](#billable-model)
    - [API-ключі](#api-keys)
    - [Paddle JS](#paddle-js)
    - [Конфігурація валюти](#currency-configuration)
    - [Заміна моделей за замовчуванням](#overriding-default-models)
- [Швидкий старт](#quickstart)
    - [Продаж продуктів](#quickstart-selling-products)
    - [Продаж підписок](#quickstart-selling-subscriptions)
- [Сесії оформлення](#checkout-sessions)
    - [Overlay-оформлення](#overlay-checkout)
    - [Вбудоване оформлення](#inline-checkout)
    - [Гостьове оформлення](#guest-checkouts)
- [Попередній перегляд цін](#price-previews)
    - [Попередній перегляд цін для клієнта](#customer-price-previews)
    - [Знижки](#price-discounts)
- [Клієнти](#customers)
    - [Значення за замовчуванням для клієнта](#customer-defaults)
    - [Отримання клієнтів](#retrieving-customers)
    - [Створення клієнтів](#creating-customers)
- [Підписки](#subscriptions)
    - [Створення підписок](#creating-subscriptions)
    - [Перевірка стану підписки](#checking-subscription-status)
    - [Разові списання за підпискою](#subscription-single-charges)
    - [Оновлення платіжної інформації](#updating-payment-information)
    - [Зміна планів](#changing-plans)
    - [Кількість у підписці](#subscription-quantity)
    - [Підписки з кількома продуктами](#subscriptions-with-multiple-products)
    - [Кілька підписок](#multiple-subscriptions)
    - [Призупинення підписок](#pausing-subscriptions)
    - [Скасування підписок](#canceling-subscriptions)
- [Пробні періоди підписок](#subscription-trials)
    - [З платіжним методом наперед](#with-payment-method-up-front)
    - [Без платіжного методу наперед](#without-payment-method-up-front)
    - [Продовження чи активація пробного періоду](#extend-or-activate-a-trial)
- [Обробка вебхуків Paddle](#handling-paddle-webhooks)
    - [Визначення обробників подій вебхуків](#defining-webhook-event-handlers)
    - [Перевірка підписів вебхуків](#verifying-webhook-signatures)
- [Разові списання](#single-charges)
    - [Списання за продукти](#charging-for-products)
    - [Повернення коштів за транзакціями](#refunding-transactions)
    - [Кредитування транзакцій](#crediting-transactions)
- [Транзакції](#transactions)
    - [Минулі та майбутні платежі](#past-and-upcoming-payments)
- [Тестування](#testing)

<a name="introduction"></a>
## Вступ

> [!WARNING]
> Ця документація стосується інтеграції Cashier Paddle 2.x з Paddle Billing. Якщо ви досі користуєтеся Paddle Classic, вам слід використовувати [Cashier Paddle 1.x](https://github.com/laravel/cashier-paddle/tree/1.x).

[Laravel Cashier Paddle](https://github.com/laravel/cashier-paddle) дає виразний, плавний інтерфейс до сервісів білінгу підписок [Paddle](https://paddle.com). Він бере на себе майже весь шаблонний код білінгу підписок, якого ви так боїтеся. Окрім базового керування підписками, Cashier уміє: змінювати підписки, працювати з «кількостями» в підписках, призупиняти підписки, обробляти пільгові періоди після скасування тощо.

Перш ніж заглиблюватися в Cashier Paddle, радимо також переглянути [концептуальні посібники](https://developer.paddle.com/concepts/overview) та [документацію API](https://developer.paddle.com/api-reference/overview) від Paddle.

<a name="upgrading-cashier"></a>
## Оновлення Cashier

Оновлюючись до нової версії Cashier, важливо уважно переглянути [посібник з оновлення](https://github.com/laravel/cashier-paddle/blob/master/UPGRADE.md).

<a name="installation"></a>
## Встановлення

Спершу встановіть пакет Cashier для Paddle за допомогою менеджера пакетів Composer:

```shell
composer require laravel/cashier-paddle
```

Далі вам слід опублікувати файли міграцій Cashier артизан-командою `vendor:publish`:

```shell
php artisan vendor:publish --tag="cashier-migrations"
```

Потім виконайте міграції бази даних вашого застосунку. Міграції Cashier створять нову таблицю `customers`. Крім того, буде створено нові таблиці `subscriptions` і `subscription_items` для зберігання всіх підписок ваших клієнтів. Насамкінець буде створено нову таблицю `transactions` для зберігання всіх транзакцій Paddle, пов'язаних з вашими клієнтами:

```shell
php artisan migrate
```

> [!WARNING]
> Щоб Cashier належно обробляв усі події Paddle, не забудьте [налаштувати обробку вебхуків Cashier](#handling-paddle-webhooks).

<a name="paddle-sandbox"></a>
### Paddle Sandbox

Під час локальної розробки та на staging вам слід [зареєструвати обліковий запис Paddle Sandbox](https://sandbox-login.paddle.com/signup). Цей обліковий запис дасть вам пісочницю для тестування й розробки застосунків без реальних платежів. Ви можете скористатися [тестовими номерами карток](https://developer.paddle.com/concepts/payment-methods/credit-debit-card#test-payment-method) Paddle, щоб змоделювати різні платіжні сценарії.

Використовуючи середовище Paddle Sandbox, вам слід встановити змінну оточення `PADDLE_SANDBOX` у `true` у файлі `.env` вашого застосунку:

```ini
PADDLE_SANDBOX=true
```

Завершивши розробку застосунку, ви можете [подати заявку на обліковий запис вендора Paddle](https://paddle.com). Перш ніж ваш застосунок потрапить у продакшен, Paddle має схвалити домен вашого застосунку.

<a name="configuration"></a>
## Конфігурація

<a name="billable-model"></a>
### Модель з білінгом

Перш ніж користуватися Cashier, вам потрібно додати трейт `Billable` до визначення вашої моделі користувача. Цей трейт надає різні методи, що дозволяють виконувати типові завдання білінгу, як-от створення підписок і оновлення інформації про платіжний метод:

```php
use Laravel\Paddle\Billable;

class User extends Authenticatable
{
    use Billable;
}
```

Якщо у вас є сутності з білінгом, які не є користувачами, ви можете додати трейт і до цих класів:

```php
use Illuminate\Database\Eloquent\Model;
use Laravel\Paddle\Billable;

class Team extends Model
{
    use Billable;
}
```

<a name="api-keys"></a>
### API-ключі

Далі вам слід налаштувати свої ключі Paddle у файлі `.env` вашого застосунку. Отримати API-ключі Paddle можна з панелі керування Paddle:

```ini
PADDLE_CLIENT_SIDE_TOKEN=your-paddle-client-side-token
PADDLE_API_KEY=your-paddle-api-key
PADDLE_RETAIN_KEY=your-paddle-retain-key
PADDLE_WEBHOOK_SECRET="your-paddle-webhook-secret"
PADDLE_SANDBOX=true
```

Змінну оточення `PADDLE_SANDBOX` слід встановити в `true`, коли ви використовуєте [середовище Sandbox від Paddle](#paddle-sandbox). Змінну `PADDLE_SANDBOX` слід встановити в `false`, якщо ви розгортаєте застосунок у продакшені й використовуєте живе вендорське середовище Paddle.

`PADDLE_RETAIN_KEY` необов'язковий, і його слід встановлювати, лише якщо ви використовуєте Paddle із [Retain](https://developer.paddle.com/concepts/retain/overview).

<a name="paddle-js"></a>
### Paddle JS

Paddle покладається на власну JavaScript-бібліотеку, щоб запустити віджет оформлення Paddle. Завантажити цю бібліотеку можна, розмістивши Blade-директиву `@paddleJS` безпосередньо перед закривальним тегом `</head>` у макеті вашого застосунку:

```blade
<head>
    ...

    @paddleJS
</head>
```

<a name="currency-configuration"></a>
### Конфігурація валюти

Ви можете вказати локаль, яка використовуватиметься для форматування грошових значень для показу в рахунках. Внутрішньо Cashier використовує [клас PHP `NumberFormatter`](https://www.php.net/manual/en/class.numberformatter.php), щоб задати локаль валюти:

```ini
CASHIER_CURRENCY_LOCALE=nl_BE
```

> [!WARNING]
> Щоб використовувати локалі, відмінні від `en`, переконайтеся, що PHP-розширення `ext-intl` встановлено й налаштовано на вашому сервері.

<a name="overriding-default-models"></a>
### Заміна моделей за замовчуванням

Ви можете розширювати моделі, які Cashier використовує внутрішньо, визначивши власну модель і розширивши відповідну модель Cashier:

```php
use Laravel\Paddle\Subscription as CashierSubscription;

class Subscription extends CashierSubscription
{
    // ...
}
```

Визначивши свою модель, ви можете вказати Cashier використовувати вашу власну модель через клас `Laravel\Paddle\Cashier`. Зазвичай повідомляти Cashier про ваші власні моделі слід у методі `boot` класу `App\Providers\AppServiceProvider` вашого застосунку:

```php
use App\Models\Cashier\Subscription;
use App\Models\Cashier\Transaction;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Cashier::useSubscriptionModel(Subscription::class);
    Cashier::useTransactionModel(Transaction::class);
}
```

<a name="quickstart"></a>
## Швидкий старт

<a name="quickstart-selling-products"></a>
### Продаж продуктів

> [!NOTE]
> Перш ніж користуватися Paddle Checkout, вам слід визначити продукти з фіксованими цінами у своїй панелі Paddle. Крім того, вам слід [налаштувати обробку вебхуків Paddle](#handling-paddle-webhooks).

Пропонувати білінг продуктів і підписок через ваш застосунок може здаватися страшним. Однак завдяки Cashier та [Checkout Overlay від Paddle](https://developer.paddle.com/concepts/sell/overlay-checkout) ви можете легко побудувати сучасні, надійні платіжні інтеграції.

Щоб списувати кошти з клієнтів за неперіодичні продукти з разовим списанням, ми скористаємося Cashier для списання через Checkout Overlay від Paddle, де клієнти нададуть свої платіжні дані й підтвердять покупку. Щойно платіж буде здійснено через Checkout Overlay, клієнта буде перенаправлено на обрану вами URL-адресу успіху у вашому застосунку:

```php
use Illuminate\Http\Request;

Route::get('/buy', function (Request $request) {
    $checkout = $request->user()->checkout('pri_deluxe_album')
        ->returnTo(route('dashboard'));

    return view('buy', ['checkout' => $checkout]);
})->name('checkout');
```

Як бачите в наведеному вище прикладі, ми скористаємося наданим Cashier методом `checkout`, щоб створити об'єкт оформлення й показати клієнту Checkout Overlay від Paddle для заданого «ідентифікатора ціни». У Paddle «ціни» - це [визначені ціни для конкретних продуктів](https://developer.paddle.com/build/products/create-products-prices).

За потреби метод `checkout` автоматично створить клієнта в Paddle і зв'яже цей запис клієнта Paddle з відповідним користувачем у базі даних вашого застосунку. Після завершення сесії оформлення клієнта буде перенаправлено на спеціальну сторінку успіху, де ви можете показати йому інформаційне повідомлення.

У представленні `buy` ми додамо кнопку для показу Checkout Overlay. Blade-компонент `paddle-button` входить до Cashier Paddle; однак ви також можете [відрендерити overlay-оформлення вручну](#manually-rendering-an-overlay-checkout):

```html
<x-paddle-button :checkout="$checkout" class="px-8 py-4">
    Buy Product
</x-paddle-button>
```

<a name="providing-meta-data-to-paddle-checkout"></a>
#### Передавання метаданих до Paddle Checkout

Продаючи продукти, зазвичай відстежують завершені замовлення й куплені продукти через моделі `Cart` і `Order`, визначені у вашому власному застосунку. Перенаправляючи клієнтів до Checkout Overlay від Paddle для завершення покупки, вам може знадобитися передати наявний ідентифікатор замовлення, щоб пов'язати завершену покупку з відповідним замовленням, коли клієнта буде перенаправлено назад до вашого застосунку.

Щоб досягти цього, передайте до методу `checkout` масив власних даних. Уявімо, що в нашому застосунку створюється незавершене замовлення `Order`, коли користувач починає процес оформлення. Пам'ятайте: моделі `Cart` і `Order` у цьому прикладі є ілюстративними і не надаються Cashier. Ви можете реалізувати ці концепції відповідно до потреб власного застосунку:

```php
use App\Models\Cart;
use App\Models\Order;
use Illuminate\Http\Request;

Route::get('/cart/{cart}/checkout', function (Request $request, Cart $cart) {
    $order = Order::create([
        'cart_id' => $cart->id,
        'price_ids' => $cart->price_ids,
        'status' => 'incomplete',
    ]);

    $checkout = $request->user()->checkout($order->price_ids)
        ->customData(['order_id' => $order->id]);

    return view('billing', ['checkout' => $checkout]);
})->name('checkout');
```

Як бачите в наведеному вище прикладі, коли користувач починає процес оформлення, ми передаємо до методу `checkout` усі пов'язані з кошиком / замовленням ідентифікатори цін Paddle. Звісно, ваш застосунок відповідає за прив'язку цих позицій до «кошика» чи замовлення, коли клієнт їх додає. Ми також передаємо ID замовлення до Checkout Overlay від Paddle методом `customData`.

Звісно, ви, найімовірніше, захочете позначити замовлення як «завершене», щойно клієнт завершить процес оформлення. Щоб досягти цього, ви можете слухати вебхуки, які надсилає Paddle і які Cashier здіймає у вигляді подій, і зберігати інформацію про замовлення у своїй базі даних.

Для початку слухайте подію `TransactionCompleted`, яку диспетчеризує Cashier. Зазвичай реєструвати слухача події слід у методі `boot` `AppServiceProvider` вашого застосунку:

```php
use App\Listeners\CompleteOrder;
use Illuminate\Support\Facades\Event;
use Laravel\Paddle\Events\TransactionCompleted;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Event::listen(TransactionCompleted::class, CompleteOrder::class);
}
```

У цьому прикладі слухач `CompleteOrder` міг би виглядати так:

```php
namespace App\Listeners;

use App\Models\Order;
use Laravel\Paddle\Cashier;
use Laravel\Paddle\Events\TransactionCompleted;

class CompleteOrder
{
    /**
     * Handle the incoming Cashier webhook event.
     */
    public function handle(TransactionCompleted $event): void
    {
        $orderId = $event->payload['data']['custom_data']['order_id'] ?? null;

        $order = Order::findOrFail($orderId);

        $order->update(['status' => 'completed']);
    }
}
```

Докладніше про [дані, які містить подія `transaction.completed`](https://developer.paddle.com/webhooks/transactions/transaction-completed), дивіться в документації Paddle.

<a name="quickstart-selling-subscriptions"></a>
### Продаж підписок

> [!NOTE]
> Перш ніж користуватися Paddle Checkout, вам слід визначити продукти з фіксованими цінами у своїй панелі Paddle. Крім того, вам слід [налаштувати обробку вебхуків Paddle](#handling-paddle-webhooks).

Пропонувати білінг продуктів і підписок через ваш застосунок може здаватися страшним. Однак завдяки Cashier та [Checkout Overlay від Paddle](https://developer.paddle.com/concepts/sell/overlay-checkout) ви можете легко побудувати сучасні, надійні платіжні інтеграції.

Щоб дізнатися, як продавати підписки за допомогою Cashier і Checkout Overlay від Paddle, розгляньмо простий сценарій сервісу підписок з базовим місячним (`price_basic_monthly`) і річним (`price_basic_yearly`) планом. Ці дві ціни можна згрупувати під продуктом «Basic» (`pro_basic`) у нашій панелі Paddle. Крім того, наш сервіс підписок може пропонувати план «Expert» як `pro_expert`.

Спершу з'ясуймо, як клієнт може підписатися на наші сервіси. Звісно, можна уявити, що клієнт натисне кнопку «subscribe» для плану Basic на сторінці цін нашого застосунку. Ця кнопка викличе Checkout Overlay від Paddle для обраного плану. Для початку ініціюймо сесію оформлення методом `checkout`:

```php
use Illuminate\Http\Request;

Route::get('/subscribe', function (Request $request) {
    $checkout = $request->user()->checkout('price_basic_monthly')
        ->returnTo(route('dashboard'));

    return view('subscribe', ['checkout' => $checkout]);
})->name('subscribe');
```

У представленні `subscribe` ми додамо кнопку для показу Checkout Overlay. Blade-компонент `paddle-button` входить до Cashier Paddle; однак ви також можете [відрендерити overlay-оформлення вручну](#manually-rendering-an-overlay-checkout):

```html
<x-paddle-button :checkout="$checkout" class="px-8 py-4">
    Subscribe
</x-paddle-button>
```

Тепер, коли натиснуто кнопку Subscribe, клієнт зможе ввести свої платіжні дані й розпочати підписку. Щоб знати, коли підписка справді почалася (оскільки деяким платіжним методам потрібно кілька секунд на обробку), вам також слід [налаштувати обробку вебхуків Cashier](#handling-paddle-webhooks).

Тепер, коли клієнти можуть починати підписки, нам потрібно обмежити певні частини нашого застосунку так, щоб доступ до них мали лише підписані користувачі. Звісно, ми завжди можемо визначити поточний стан підписки користувача методом `subscribed`, який надає трейт `Billable` з Cashier:

```blade
@if ($user->subscribed())
    <p>You are subscribed.</p>
@endif
```

Ми навіть легко можемо визначити, чи підписаний користувач на конкретний продукт чи ціну:

```blade
@if ($user->subscribedToProduct('pro_basic'))
    <p>You are subscribed to our Basic product.</p>
@endif

@if ($user->subscribedToPrice('price_basic_monthly'))
    <p>You are subscribed to our monthly Basic plan.</p>
@endif
```

<a name="quickstart-building-a-subscribed-middleware"></a>
#### Створення middleware для підписаних

Для зручності ви можете створити [middleware](/docs/{{version}}/middleware), який визначає, чи надійшов вхідний запит від підписаного користувача. Щойно цей `middleware` буде визначено, ви зможете легко призначити його маршруту, щоб не пускати до нього непідписаних користувачів:

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class Subscribed
{
    /**
     * Handle an incoming request.
     */
    public function handle(Request $request, Closure $next): Response
    {
        if (! $request->user()?->subscribed()) {
            // Redirect user to billing page and ask them to subscribe...
            return redirect('/subscribe');
        }

        return $next($request);
    }
}
```

Щойно `middleware` визначено, ви можете призначити його маршруту:

```php
use App\Http\Middleware\Subscribed;

Route::get('/dashboard', function () {
    // ...
})->middleware([Subscribed::class]);
```

<a name="quickstart-allowing-customers-to-manage-their-billing-plan"></a>
#### Дозвіл клієнтам керувати своїм планом білінгу

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

```php
use Illuminate\Http\Request;

Route::put('/subscription/{price}/swap', function (Request $request, $price) {
    $user->subscription()->swap($price); // With "$price" being "price_basic_yearly" for this example.

    return redirect()->route('dashboard');
})->name('subscription.swap');
```

Окрім зміни планів, вам також потрібно буде дозволити клієнтам скасовувати підписку. Як і зі зміною планів, надайте кнопку, що веде до такого маршруту:

```php
use Illuminate\Http\Request;

Route::put('/subscription/cancel', function (Request $request, $price) {
    $user->subscription()->cancel();

    return redirect()->route('dashboard');
})->name('subscription.cancel');
```

І тепер вашу підписку буде скасовано наприкінці її розрахункового періоду.

> [!NOTE]
> Доки ви налаштували обробку вебхуків Cashier, Cashier автоматично підтримуватиме пов'язані з ним таблиці бази даних вашого застосунку синхронізованими, аналізуючи вхідні вебхуки від Paddle. Так, наприклад, коли ви скасуєте підписку клієнта через панель Paddle, Cashier отримає відповідний вебхук і позначить підписку як «скасовану» в базі даних вашого застосунку.

<a name="checkout-sessions"></a>
## Сесії оформлення

Більшість операцій білінгу для клієнтів виконуються через «оформлення» за допомогою [віджета Checkout Overlay](https://developer.paddle.com/build/checkout/build-overlay-checkout) від Paddle або за допомогою [вбудованого оформлення](https://developer.paddle.com/build/checkout/build-branded-inline-checkout).

Перш ніж обробляти платежі оформлення через Paddle, вам слід визначити [посилання на оплату за замовчуванням](https://developer.paddle.com/build/transactions/default-payment-link#set-default-link) для вашого застосунку в панелі налаштувань оформлення Paddle.

<a name="overlay-checkout"></a>
### Overlay-оформлення

Перш ніж показувати віджет Checkout Overlay, вам потрібно згенерувати сесію оформлення за допомогою Cashier. Сесія оформлення повідомить віджету оформлення, яку операцію білінгу слід виконати:

```php
use Illuminate\Http\Request;

Route::get('/buy', function (Request $request) {
    $checkout = $user->checkout('pri_34567')
        ->returnTo(route('dashboard'));

    return view('billing', ['checkout' => $checkout]);
});
```

Cashier містить [Blade-компонент](/docs/{{version}}/blade#components) `paddle-button`. Ви можете передати сесію оформлення до цього компонента як «проп». Далі, коли цю кнопку буде натиснуто, з'явиться віджет оформлення Paddle:

```html
<x-paddle-button :checkout="$checkout" class="px-8 py-4">
    Subscribe
</x-paddle-button>
```

За замовчуванням це покаже віджет зі стандартною стилізацією Paddle. Ви можете налаштувати віджет, додавши до компонента [атрибути, які підтримує Paddle](https://developer.paddle.com/paddlejs/html-data-attributes), як-от атрибут `data-theme='light'`:

```html
<x-paddle-button :checkout="$checkout" class="px-8 py-4" data-theme="light">
    Subscribe
</x-paddle-button>
```

Віджет оформлення Paddle асинхронний. Щойно користувач створить підписку у віджеті, Paddle надішле вашому застосунку вебхук, щоб ви могли належно оновити стан підписки в базі даних вашого застосунку. Тому важливо, щоб ви правильно [налаштували вебхуки](#handling-paddle-webhooks) для врахування змін стану з боку Paddle.

> [!WARNING]
> Після зміни стану підписки затримка отримання відповідного вебхука зазвичай мінімальна, але вам слід урахувати це у своєму застосунку, зважаючи на те, що підписка вашого користувача може бути недоступною одразу після завершення оформлення.

<a name="manually-rendering-an-overlay-checkout"></a>
#### Ручний рендеринг overlay-оформлення

Ви також можете відрендерити overlay-оформлення вручну, не використовуючи вбудовані Blade-компоненти Laravel. Для початку згенеруйте сесію оформлення [як показано в попередніх прикладах](#overlay-checkout):

```php
use Illuminate\Http\Request;

Route::get('/buy', function (Request $request) {
    $checkout = $user->checkout('pri_34567')
        ->returnTo(route('dashboard'));

    return view('billing', ['checkout' => $checkout]);
});
```

Далі ви можете скористатися Paddle.js, щоб ініціалізувати оформлення. У цьому прикладі ми створимо посилання з класом `paddle_button`. Paddle.js виявить цей клас і покаже overlay-оформлення, коли посилання буде натиснуто:

```blade
<?php
$items = $checkout->getItems();
$customer = $checkout->getCustomer();
$custom = $checkout->getCustomData();
?>

<a
    href='#!'
    class='paddle_button'
    data-items='{!! json_encode($items) !!}'
    @if ($customer) data-customer-id='{{ $customer->paddle_id }}' @endif
    @if ($custom) data-custom-data='{{ json_encode($custom) }}' @endif
    @if ($returnUrl = $checkout->getReturnUrl()) data-success-url='{{ $returnUrl }}' @endif
>
    Buy Product
</a>
```

<a name="inline-checkout"></a>
### Вбудоване оформлення

Якщо ви не хочете користуватися віджетом оформлення в стилі «overlay» від Paddle, Paddle також пропонує можливість показати віджет вбудовано. Хоча цей підхід не дозволяє змінювати HTML-поля оформлення, він дає змогу вбудувати віджет у ваш застосунок.

Щоб вам було легко почати з вбудованим оформленням, Cashier містить Blade-компонент `paddle-checkout`. Для початку вам слід [згенерувати сесію оформлення](#overlay-checkout):

```php
use Illuminate\Http\Request;

Route::get('/buy', function (Request $request) {
    $checkout = $user->checkout('pri_34567')
        ->returnTo(route('dashboard'));

    return view('billing', ['checkout' => $checkout]);
});
```

Далі ви можете передати сесію оформлення до атрибута `checkout` компонента:

```blade
<x-paddle-checkout :checkout="$checkout" class="w-full" />
```

Щоб змінити висоту компонента вбудованого оформлення, передайте Blade-компоненту атрибут `height`:

```blade
<x-paddle-checkout :checkout="$checkout" class="w-full" height="500" />
```

Докладніше про можливості налаштування вбудованого оформлення дивіться в [посібнику Paddle щодо вбудованого оформлення](https://developer.paddle.com/build/checkout/build-branded-inline-checkout) і [доступних налаштуваннях оформлення](https://developer.paddle.com/build/checkout/set-up-checkout-default-settings).

<a name="manually-rendering-an-inline-checkout"></a>
#### Ручний рендеринг вбудованого оформлення

Ви також можете відрендерити вбудоване оформлення вручну, не використовуючи вбудовані Blade-компоненти Laravel. Для початку згенеруйте сесію оформлення [як показано в попередніх прикладах](#inline-checkout):

```php
use Illuminate\Http\Request;

Route::get('/buy', function (Request $request) {
    $checkout = $user->checkout('pri_34567')
        ->returnTo(route('dashboard'));

    return view('billing', ['checkout' => $checkout]);
});
```

Далі ви можете скористатися Paddle.js, щоб ініціалізувати оформлення. У цьому прикладі ми продемонструємо це за допомогою [Alpine.js](https://github.com/alpinejs/alpine); однак ви можете змінити цей приклад під свій фронтенд-стек:

```blade
<?php
$options = $checkout->options();

$options['settings']['frameTarget'] = 'paddle-checkout';
$options['settings']['frameInitialHeight'] = 366;
?>

<div class="paddle-checkout" x-data="{}" x-init="
    Paddle.Checkout.open(@json($options));
">
</div>
```

<a name="guest-checkouts"></a>
### Гостьове оформлення

Іноді вам може знадобитися створити сесію оформлення для користувачів, яким не потрібен обліковий запис у вашому застосунку. Для цього скористайтеся методом `guest`:

```php
use Illuminate\Http\Request;
use Laravel\Paddle\Checkout;

Route::get('/buy', function (Request $request) {
    $checkout = Checkout::guest(['pri_34567'])
        ->returnTo(route('home'));

    return view('billing', ['checkout' => $checkout]);
});
```

Далі ви можете передати сесію оформлення до Blade-компонентів [кнопки Paddle](#overlay-checkout) чи [вбудованого оформлення](#inline-checkout).

<a name="price-previews"></a>
## Попередній перегляд цін

Paddle дозволяє налаштовувати ціни для кожної валюти, тобто фактично задавати різні ціни для різних країн. Cashier Paddle дозволяє отримати всі ці ціни методом `previewPrices`. Цей метод приймає ID цін, для яких ви хочете отримати ціни:

```php
use Laravel\Paddle\Cashier;

$prices = Cashier::previewPrices(['pri_123', 'pri_456']);
```

Валюту буде визначено на основі IP-адреси запиту; однак ви можете за бажанням указати конкретну країну, для якої отримати ціни:

```php
use Laravel\Paddle\Cashier;

$prices = Cashier::previewPrices(['pri_123', 'pri_456'], ['address' => [
    'country_code' => 'BE',
    'postal_code' => '1234',
]]);
```

Отримавши ціни, ви можете показати їх як завгодно:

```blade
<ul>
    @foreach ($prices as $price)
        <li>{{ $price->product['name'] }} - {{ $price->total() }}</li>
    @endforeach
</ul>
```

Ви також можете показати проміжну суму й суму податку окремо:

```blade
<ul>
    @foreach ($prices as $price)
        <li>{{ $price->product['name'] }} - {{ $price->subtotal() }} (+ {{ $price->tax() }} tax)</li>
    @endforeach
</ul>
```

Докладніше дивіться в [документації API Paddle щодо попереднього перегляду цін](https://developer.paddle.com/api-reference/pricing-preview/preview-prices).

<a name="customer-price-previews"></a>
### Попередній перегляд цін для клієнта

Якщо користувач уже є клієнтом і ви хочете показати ціни, що застосовуються саме до нього, отримайте ціни безпосередньо з екземпляра клієнта:

```php
use App\Models\User;

$prices = User::find(1)->previewPrices(['pri_123', 'pri_456']);
```

Внутрішньо Cashier використає ID клієнта, щоб отримати ціни в його валюті. Так, наприклад, користувач зі США побачить ціни в доларах США, а користувач з Бельгії - у євро. Якщо відповідної валюти не знайдено, буде використано валюту продукту за замовчуванням. Усі ціни продукту чи плану підписки можна налаштувати в панелі керування Paddle.

<a name="price-discounts"></a>
### Знижки

Ви також можете показувати ціни зі знижкою. Викликаючи метод `previewPrices`, передайте ID знижки через опцію `discount_id`:

```php
use Laravel\Paddle\Cashier;

$prices = Cashier::previewPrices(['pri_123', 'pri_456'], [
    'discount_id' => 'dsc_123'
]);
```

Далі покажіть обчислені ціни:

```blade
<ul>
    @foreach ($prices as $price)
        <li>{{ $price->product['name'] }} - {{ $price->total() }}</li>
    @endforeach
</ul>
```

<a name="customers"></a>
## Клієнти

<a name="customer-defaults"></a>
### Значення за замовчуванням для клієнта

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

```php
/**
 * Get the customer's name to associate with Paddle.
 */
public function paddleName(): string|null
{
    return $this->name;
}

/**
 * Get the customer's email address to associate with Paddle.
 */
public function paddleEmail(): string|null
{
    return $this->email;
}
```

Ці значення за замовчуванням використовуватимуться для кожної дії в Cashier, яка генерує [сесію оформлення](#checkout-sessions).

<a name="retrieving-customers"></a>
### Отримання клієнтів

Ви можете отримати клієнта за його Paddle Customer ID методом `Cashier::findBillable`. Цей метод поверне екземпляр моделі з білінгом:

```php
use Laravel\Paddle\Cashier;

$user = Cashier::findBillable($customerId);
```

<a name="creating-customers"></a>
### Створення клієнтів

Іноді ви можете захотіти створити клієнта Paddle, не починаючи підписки. Зробити це можна методом `createAsCustomer`:

```php
$customer = $user->createAsCustomer();
```

Повертається екземпляр `Laravel\Paddle\Customer`. Щойно клієнта створено в Paddle, ви можете почати підписку пізніше. Ви можете передати необов'язковий масив `$options`, щоб указати будь-які додаткові [параметри створення клієнта, які підтримує API Paddle](https://developer.paddle.com/api-reference/customers/create-customer):

```php
$customer = $user->createAsCustomer($options);
```

<a name="subscriptions"></a>
## Підписки

<a name="creating-subscriptions"></a>
### Створення підписок

Щоб створити підписку, спершу отримайте з бази даних екземпляр вашої моделі з білінгом, яким зазвичай буде екземпляр `App\Models\User`. Отримавши екземпляр моделі, ви можете скористатися методом `subscribe`, щоб створити сесію оформлення для моделі:

```php
use Illuminate\Http\Request;

Route::get('/user/subscribe', function (Request $request) {
    $checkout = $request->user()->subscribe($premium = 'pri_123', 'default')
        ->returnTo(route('home'));

    return view('billing', ['checkout' => $checkout]);
});
```

Перший аргумент методу `subscribe` - конкретна ціна, на яку підписується користувач. Це значення має відповідати ідентифікатору ціни в Paddle. Метод `returnTo` приймає URL, на який буде перенаправлено вашого користувача після успішного завершення оформлення. Другим аргументом методу `subscribe` має бути внутрішній «тип» підписки. Якщо ваш застосунок пропонує лише одну підписку, ви можете назвати її `default` чи `primary`. Цей тип підписки призначений лише для внутрішнього використання застосунком і не має показуватися користувачам. Крім того, він не повинен містити пробілів, і його ніколи не слід змінювати після створення підписки.

Ви також можете передати масив власних метаданих щодо підписки методом `customData`:

```php
$checkout = $request->user()->subscribe($premium = 'pri_123', 'default')
    ->customData(['key' => 'value'])
    ->returnTo(route('home'));
```

Щойно сесію оформлення підписки створено, її можна передати до [Blade-компонента](#overlay-checkout) `paddle-button`, який входить до Cashier Paddle:

```blade
<x-paddle-button :checkout="$checkout" class="px-8 py-4">
    Subscribe
</x-paddle-button>
```

Після того як користувач завершить оформлення, Paddle надішле вебхук `subscription_created`. Cashier отримає цей вебхук і налаштує підписку для вашого клієнта. Щоб переконатися, що всі вебхуки належно отримуються й обробляються вашим застосунком, переконайтеся, що ви правильно [налаштували обробку вебхуків](#handling-paddle-webhooks).

<a name="checking-subscription-status"></a>
### Перевірка стану підписки

Щойно користувач підписався на ваш застосунок, ви можете перевіряти стан його підписки різними зручними методами. По-перше, метод `subscribed` повертає `true`, якщо користувач має дійсну підписку, навіть якщо вона наразі в межах пробного періоду:

```php
if ($user->subscribed()) {
    // ...
}
```

Якщо ваш застосунок пропонує кілька підписок, ви можете вказати підписку під час виклику методу `subscribed`:

```php
if ($user->subscribed('default')) {
    // ...
}
```

Метод `subscribed` також чудово підходить для [middleware маршруту](/docs/{{version}}/middleware), дозволяючи фільтрувати доступ до маршрутів і контролерів на основі стану підписки користувача:

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class EnsureUserIsSubscribed
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        if ($request->user() && ! $request->user()->subscribed()) {
            // This user is not a paying customer...
            return redirect('/billing');
        }

        return $next($request);
    }
}
```

Якщо ви хочете визначити, чи користувач досі в межах пробного періоду, скористайтеся методом `onTrial`. Цей метод може бути корисним, щоб визначити, чи слід показати користувачеві попередження про те, що він досі на пробному періоді:

```php
if ($user->subscription()->onTrial()) {
    // ...
}
```

Метод `subscribedToPrice` можна використати, щоб визначити, чи підписаний користувач на певний план на основі заданого ID ціни Paddle. У цьому прикладі ми визначимо, чи підписка користувача `default` активно підписана на місячну ціну:

```php
if ($user->subscribedToPrice($monthly = 'pri_123', 'default')) {
    // ...
}
```

Метод `recurring` можна використати, щоб визначити, чи має користувач наразі активну підписку і чи вийшов він уже за межі пробного чи пільгового періоду:

```php
if ($user->subscription()->recurring()) {
    // ...
}
```

<a name="canceled-subscription-status"></a>
#### Стан скасованої підписки

Щоб визначити, чи був користувач колись активним підписником, але скасував свою підписку, скористайтеся методом `canceled`:

```php
if ($user->subscription()->canceled()) {
    // ...
}
```

Ви також можете визначити, чи скасував користувач підписку, але досі перебуває в «пільговому періоді», доки підписка повністю не спливе. Наприклад, якщо користувач скасує 5 березня підписку, яка спочатку мала спливти 10 березня, він перебуватиме в «пільговому періоді» до 10 березня. Крім того, метод `subscribed` протягом цього часу все ще повертатиме `true`:

```php
if ($user->subscription()->onGracePeriod()) {
    // ...
}
```

<a name="past-due-status"></a>
#### Стан простроченої оплати

Якщо платіж за підпискою не пройде, її буде позначено як `past_due`. Коли ваша підписка в цьому стані, вона не буде активною, доки клієнт не оновить свою платіжну інформацію. Визначити, чи підписка прострочена, можна методом `pastDue` на екземплярі підписки:

```php
if ($user->subscription()->pastDue()) {
    // ...
}
```

Коли підписка прострочена, вам слід указати користувачеві [оновити свою платіжну інформацію](#updating-payment-information).

Якщо ви хочете, щоб підписки все ще вважалися дійсними у стані `past_due`, скористайтеся методом `keepPastDueSubscriptionsActive`, який надає Cashier. Зазвичай цей метод слід викликати в методі `register` вашого `AppServiceProvider`:

```php
use Laravel\Paddle\Cashier;

/**
 * Register any application services.
 */
public function register(): void
{
    Cashier::keepPastDueSubscriptionsActive();
}
```

> [!WARNING]
> Коли підписка у стані `past_due`, її не можна змінити, доки платіжну інформацію не буде оновлено. Тому методи `swap` і `updateQuantity` видадуть виняток, коли підписка у стані `past_due`.

<a name="subscription-scopes"></a>
#### Скопи підписок

Більшість станів підписки також доступні як скопи запитів, тож ви можете легко шукати у своїй базі даних підписки в певному стані:

```php
// Get all valid subscriptions...
$subscriptions = Subscription::query()->valid()->get();

// Get all of the canceled subscriptions for a user...
$subscriptions = $user->subscriptions()->canceled()->get();
```

Повний список доступних скопів наведено нижче:

```php
Subscription::query()->valid();
Subscription::query()->onTrial();
Subscription::query()->expiredTrial();
Subscription::query()->notOnTrial();
Subscription::query()->active();
Subscription::query()->recurring();
Subscription::query()->pastDue();
Subscription::query()->paused();
Subscription::query()->notPaused();
Subscription::query()->onPausedGracePeriod();
Subscription::query()->notOnPausedGracePeriod();
Subscription::query()->canceled();
Subscription::query()->notCanceled();
Subscription::query()->onGracePeriod();
Subscription::query()->notOnGracePeriod();
```

<a name="subscription-single-charges"></a>
### Разові списання за підпискою

Разові списання за підпискою дозволяють стягнути з підписників одноразову плату понад їхні підписки. Викликаючи метод `charge`, ви маєте вказати один чи кілька ID цін:

```php
// Charge a single price...
$response = $user->subscription()->charge('pri_123');

// Charge multiple prices at once...
$response = $user->subscription()->charge(['pri_123', 'pri_456']);
```

Метод `charge` фактично не стягне кошти з клієнта до наступного розрахункового інтервалу його підписки. Якщо ви хочете виставити рахунок клієнту негайно, скористайтеся натомість методом `chargeAndInvoice`:

```php
$response = $user->subscription()->chargeAndInvoice('pri_123');
```

<a name="updating-payment-information"></a>
### Оновлення платіжної інформації

Paddle завжди зберігає платіжний метод для кожної підписки. Якщо ви хочете оновити платіжний метод за замовчуванням для підписки, вам слід перенаправити клієнта на розміщену в Paddle сторінку оновлення платіжного методу методом `redirectToUpdatePaymentMethod` на моделі підписки:

```php
use Illuminate\Http\Request;

Route::get('/update-payment-method', function (Request $request) {
    $user = $request->user();

    return $user->subscription()->redirectToUpdatePaymentMethod();
});
```

Коли користувач завершить оновлення своєї інформації, Paddle надішле вебхук `subscription_updated`, і деталі підписки буде оновлено в базі даних вашого застосунку.

<a name="changing-plans"></a>
### Зміна планів

Після того як користувач підписався на ваш застосунок, він може час від часу хотіти перейти на новий план підписки. Щоб оновити план підписки для користувача, передайте ідентифікатор ціни Paddle до методу `swap` підписки:

```php
use App\Models\User;

$user = User::find(1);

$user->subscription()->swap($premium = 'pri_456');
```

Якщо ви хочете змінити план і одразу виставити рахунок користувачеві, не чекаючи наступного розрахункового циклу, скористайтеся методом `swapAndInvoice`:

```php
$user = User::find(1);

$user->subscription()->swapAndInvoice($premium = 'pri_456');
```

<a name="prorations"></a>
#### Пропорційний перерахунок

За замовчуванням Paddle робить пропорційний перерахунок платежів під час зміни планів. Метод `noProrate` можна використати, щоб оновити підписки без пропорційного перерахунку платежів:

```php
$user->subscription('default')->noProrate()->swap($premium = 'pri_456');
```

Якщо ви хочете вимкнути пропорційний перерахунок і одразу виставити рахунок клієнтам, скористайтеся методом `swapAndInvoice` у поєднанні з `noProrate`:

```php
$user->subscription('default')->noProrate()->swapAndInvoice($premium = 'pri_456');
```

Або ж, щоб не стягувати з клієнта плату за зміну підписки, скористайтеся методом `doNotBill`:

```php
$user->subscription('default')->doNotBill()->swap($premium = 'pri_456');
```

Докладніше про політики пропорційного перерахунку в Paddle дивіться в [документації Paddle щодо пропорційного перерахунку](https://developer.paddle.com/concepts/subscriptions/proration).

<a name="subscription-quantity"></a>
### Кількість у підписці

Іноді на підписки впливає «кількість». Наприклад, застосунок для керування проєктами може стягувати $10 на місяць за проєкт. Щоб легко збільшити чи зменшити кількість у підписці, скористайтеся методами `incrementQuantity` і `decrementQuantity`:

```php
$user = User::find(1);

$user->subscription()->incrementQuantity();

// Add five to the subscription's current quantity...
$user->subscription()->incrementQuantity(5);

$user->subscription()->decrementQuantity();

// Subtract five from the subscription's current quantity...
$user->subscription()->decrementQuantity(5);
```

Як альтернативу ви можете задати конкретну кількість методом `updateQuantity`:

```php
$user->subscription()->updateQuantity(10);
```

Метод `noProrate` можна використати, щоб оновити кількість у підписці без пропорційного перерахунку платежів:

```php
$user->subscription()->noProrate()->updateQuantity(10);
```

<a name="quantities-for-subscription-with-multiple-products"></a>
#### Кількості для підписок з кількома продуктами

Якщо ваша підписка є [підпискою з кількома продуктами](#subscriptions-with-multiple-products), вам слід передати ID ціни, кількість якої ви хочете збільшити чи зменшити, другим аргументом методів increment / decrement:

```php
$user->subscription()->incrementQuantity(1, 'price_chat');
```

<a name="subscriptions-with-multiple-products"></a>
### Підписки з кількома продуктами

[Підписка з кількома продуктами](https://developer.paddle.com/build/subscriptions/add-remove-products-prices-addons) дозволяє призначити одній підписці кілька продуктів білінгу. Наприклад, уявіть, що ви створюєте застосунок «служби підтримки» з базовою ціною підписки $10 на місяць, але пропонуєте додатковий продукт живого чату за додаткові $15 на місяць.

Створюючи сесії оформлення підписки, ви можете вказати кілька продуктів для певної підписки, передавши масив цін першим аргументом методу `subscribe`:

```php
use Illuminate\Http\Request;

Route::post('/user/subscribe', function (Request $request) {
    $checkout = $request->user()->subscribe([
        'price_monthly',
        'price_chat',
    ]);

    return view('billing', ['checkout' => $checkout]);
});
```

У наведеному вище прикладі клієнт матиме дві ціни, прив'язані до його підписки `default`. Обидві ціни стягуватимуться у відповідні розрахункові інтервали. За потреби ви можете передати асоціативний масив пар ключ / значення, щоб указати конкретну кількість для кожної ціни:

```php
$user = User::find(1);

$checkout = $user->subscribe('default', ['price_monthly', 'price_chat' => 5]);
```

Якщо ви хочете додати ще одну ціну до наявної підписки, вам потрібно скористатися методом `swap` підписки. Викликаючи метод `swap`, вам слід також включити поточні ціни й кількості підписки:

```php
$user = User::find(1);

$user->subscription()->swap(['price_chat', 'price_original' => 2]);
```

Наведений вище приклад додасть нову ціну, але клієнту не буде виставлено рахунок за неї до наступного розрахункового циклу. Якщо ви хочете виставити рахунок клієнту негайно, скористайтеся методом `swapAndInvoice`:

```php
$user->subscription()->swapAndInvoice(['price_chat', 'price_original' => 2]);
```

Ви можете прибрати ціни з підписок методом `swap`, опустивши ціну, яку хочете прибрати:

```php
$user->subscription()->swap(['price_original' => 2]);
```

> [!WARNING]
> Ви не можете прибрати останню ціну в підписці. Натомість вам слід просто скасувати підписку.

<a name="multiple-subscriptions"></a>
### Кілька підписок

Paddle дозволяє вашим клієнтам мати кілька підписок одночасно. Наприклад, ви можете керувати спортзалом, який пропонує підписку на плавання й підписку на важку атлетику, і кожна підписка може мати різну ціну. Звісно, клієнти мають мати змогу підписатися на один чи обидва плани.

Коли ваш застосунок створює підписки, ви можете передати тип підписки до методу `subscribe` другим аргументом. Типом може бути будь-який рядок, що представляє тип підписки, яку починає користувач:

```php
use Illuminate\Http\Request;

Route::post('/swimming/subscribe', function (Request $request) {
    $checkout = $request->user()->subscribe($swimmingMonthly = 'pri_123', 'swimming');

    return view('billing', ['checkout' => $checkout]);
});
```

У цьому прикладі ми розпочали для клієнта місячну підписку на плавання. Однак згодом він може захотіти перейти на річну підписку. Коригуючи підписку клієнта, ми можемо просто змінити ціну в підписці `swimming`:

```php
$user->subscription('swimming')->swap($swimmingYearly = 'pri_456');
```

Звісно, ви також можете скасувати підписку повністю:

```php
$user->subscription('swimming')->cancel();
```

<a name="pausing-subscriptions"></a>
### Призупинення підписок

Щоб призупинити підписку, викличте метод `pause` на підписці користувача:

```php
$user->subscription()->pause();
```

Коли підписку призупинено, Cashier автоматично встановить колонку `paused_at` у вашій базі даних. Ця колонка використовується, щоб визначити, коли метод `paused` має почати повертати `true`. Наприклад, якщо клієнт призупинить підписку 1 березня, але поновлення підписки було заплановане лише на 5 березня, метод `paused` продовжуватиме повертати `false` до 5 березня. Так відбувається тому, що користувачеві зазвичай дозволено користуватися застосунком до кінця його розрахункового циклу.

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

```php
$user->subscription()->pauseNow();
```

За допомогою методу `pauseUntil` ви можете призупинити підписку до конкретного моменту часу:

```php
$user->subscription()->pauseUntil(now()->plus(months: 1));
```

Або ж ви можете скористатися методом `pauseNowUntil`, щоб негайно призупинити підписку до заданого моменту часу:

```php
$user->subscription()->pauseNowUntil(now()->plus(months: 1));
```

Визначити, чи користувач призупинив підписку, але досі перебуває в «пільговому періоді», можна методом `onPausedGracePeriod`:

```php
if ($user->subscription()->onPausedGracePeriod()) {
    // ...
}
```

Щоб відновити призупинену підписку, викличте на ній метод `resume`:

```php
$user->subscription()->resume();
```

> [!WARNING]
> Підписку не можна змінювати, доки її призупинено. Якщо ви хочете перейти на інший план чи оновити кількості, спершу потрібно відновити підписку.

<a name="canceling-subscriptions"></a>
### Скасування підписок

Щоб скасувати підписку, викличте метод `cancel` на підписці користувача:

```php
$user->subscription()->cancel();
```

Коли підписку скасовано, Cashier автоматично встановить колонку `ends_at` у вашій базі даних. Ця колонка використовується, щоб визначити, коли метод `subscribed` має почати повертати `false`. Наприклад, якщо клієнт скасує підписку 1 березня, але завершення підписки було заплановане лише на 5 березня, метод `subscribed` продовжуватиме повертати `true` до 5 березня. Так зроблено тому, що користувачеві зазвичай дозволено користуватися застосунком до кінця його розрахункового циклу.

Визначити, чи користувач скасував підписку, але досі перебуває в «пільговому періоді», можна методом `onGracePeriod`:

```php
if ($user->subscription()->onGracePeriod()) {
    // ...
}
```

Якщо ви хочете скасувати підписку негайно, викличте на ній метод `cancelNow`:

```php
$user->subscription()->cancelNow();
```

Щоб зупинити скасування підписки, яка перебуває в пільговому періоді, викличте метод `stopCancelation`:

```php
$user->subscription()->stopCancelation();
```

> [!WARNING]
> Підписки Paddle не можна відновити після скасування. Якщо ваш клієнт захоче відновити свою підписку, йому доведеться створити нову.

<a name="subscription-trials"></a>
## Пробні періоди підписок

<a name="with-payment-method-up-front"></a>
### З платіжним методом наперед

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

```php
use Illuminate\Http\Request;

Route::get('/user/subscribe', function (Request $request) {
    $checkout = $request->user()
        ->subscribe('pri_monthly')
        ->returnTo(route('home'));

    return view('billing', ['checkout' => $checkout]);
});
```

Коли ваш застосунок отримає подію `subscription_created`, Cashier встановить дату завершення пробного періоду в записі підписки в базі даних вашого застосунку, а також вкаже Paddle не починати стягувати кошти з клієнта до цієї дати.

> [!WARNING]
> Якщо підписку клієнта не буде скасовано до дати завершення пробного періоду, кошти з нього спишуть одразу після його спливання, тож обов'язково повідомляйте своїх користувачів про дату завершення пробного періоду.

Визначити, чи користувач у межах пробного періоду, можна методом `onTrial` на екземплярі користувача:

```php
if ($user->onTrial()) {
    // ...
}
```

Щоб визначити, чи наявний пробний період сплив, скористайтеся методом `hasExpiredTrial`:

```php
if ($user->hasExpiredTrial()) {
    // ...
}
```

Щоб визначити, чи користувач на пробному періоді для конкретного типу підписки, передайте тип до методів `onTrial` чи `hasExpiredTrial`:

```php
if ($user->onTrial('default')) {
    // ...
}

if ($user->hasExpiredTrial('default')) {
    // ...
}
```

<a name="without-payment-method-up-front"></a>
### Без платіжного методу наперед

Якщо ви хочете пропонувати пробні періоди, не збираючи інформацію про платіжний метод користувача наперед, ви можете встановити колонку `trial_ends_at` у записі клієнта, прив'язаному до вашого користувача, на бажану дату завершення пробного періоду. Зазвичай це роблять під час реєстрації користувача:

```php
use App\Models\User;

$user = User::create([
    // ...
]);

$user->createAsCustomer([
    'trial_ends_at' => now()->plus(days: 10)
]);
```

Cashier називає такий тип пробного періоду «загальним пробним періодом», оскільки він не прив'язаний до жодної наявної підписки. Метод `onTrial` на екземплярі `User` поверне `true`, якщо поточна дата не перевищує значення `trial_ends_at`:

```php
if ($user->onTrial()) {
    // User is within their trial period...
}
```

Щойно ви будете готові створити для користувача справжню підписку, скористайтеся методом `subscribe` як зазвичай:

```php
use Illuminate\Http\Request;

Route::get('/user/subscribe', function (Request $request) {
    $checkout = $request->user()
        ->subscribe('pri_monthly')
        ->returnTo(route('home'));

    return view('billing', ['checkout' => $checkout]);
});
```

Щоб отримати дату завершення пробного періоду користувача, скористайтеся методом `trialEndsAt`. Цей метод поверне екземпляр дати Carbon, якщо користувач на пробному періоді, або `null`, якщо ні. Ви також можете передати необов'язковий параметр типу підписки, якщо хочете отримати дату завершення пробного періоду для конкретної підписки, відмінної від типової:

```php
if ($user->onTrial('default')) {
    $trialEndsAt = $user->trialEndsAt();
}
```

Ви можете скористатися методом `onGenericTrial`, якщо хочете дізнатися саме те, що користувач перебуває в «загальному» пробному періоді і ще не створив справжньої підписки:

```php
if ($user->onGenericTrial()) {
    // User is within their "generic" trial period...
}
```

<a name="extend-or-activate-a-trial"></a>
### Продовження чи активація пробного періоду

Ви можете продовжити наявний пробний період підписки, викликавши метод `extendTrial` і вказавши момент часу, коли пробний період має завершитися:

```php
$user->subscription()->extendTrial(now()->plus(days: 5));
```

Або ж ви можете негайно активувати підписку, завершивши її пробний період, викликавши на ній метод `activate`:

```php
$user->subscription()->activate();
```

<a name="handling-paddle-webhooks"></a>
## Обробка вебхуків Paddle

Paddle може сповіщати ваш застосунок про різні події через вебхуки. За замовчуванням сервіс-провайдер Cashier реєструє маршрут, що вказує на контролер вебхуків Cashier. Цей контролер оброблятиме всі вхідні запити вебхуків.

За замовчуванням цей контролер автоматично оброблятиме скасування підписок із занадто великою кількістю невдалих списань, оновлення підписок і зміни платіжного методу; однак, як ми невдовзі побачимо, ви можете розширити цей контролер, щоб обробляти будь-яку подію вебхука Paddle, яку забажаєте.

Щоб ваш застосунок міг обробляти вебхуки Paddle, обов'язково [налаштуйте URL вебхука в панелі керування Paddle](https://vendors.paddle.com/notifications-v2). За замовчуванням контролер вебхуків Cashier відповідає за шляхом URL `/paddle/webhook`. Повний список усіх вебхуків, які вам слід увімкнути в панелі керування Paddle:

- Customer Updated
- Transaction Completed
- Transaction Updated
- Subscription Created
- Subscription Updated
- Subscription Paused
- Subscription Canceled

> [!WARNING]
> Обов'язково захистіть вхідні запити за допомогою `middleware` [перевірки підпису вебхука](/docs/{{version}}/cashier-paddle#verifying-webhook-signatures), що входить до Cashier.

<a name="webhooks-csrf-protection"></a>
#### Вебхуки й захист від CSRF

Оскільки вебхуки Paddle мають обходити [захист від CSRF](/docs/{{version}}/csrf) у Laravel, вам слід подбати, щоб Laravel не намагався перевіряти CSRF-токен для вхідних вебхуків Paddle. Щоб досягти цього, виключіть `paddle/*` із захисту від CSRF у файлі `bootstrap/app.php` вашого застосунку:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->preventRequestForgery(except: [
        'paddle/*',
    ]);
})
```

<a name="webhooks-local-development"></a>
#### Вебхуки й локальна розробка

Щоб Paddle міг надсилати вашому застосунку вебхуки під час локальної розробки, вам потрібно буде відкрити свій застосунок через сервіс спільного доступу до сайтів, як-от [Ngrok](https://ngrok.com/) чи [Expose](https://expose.dev/docs/introduction). Якщо ви розробляєте застосунок локально за допомогою [Laravel Sail](/docs/{{version}}/sail), ви можете скористатися [командою спільного доступу до сайту](/docs/{{version}}/sail#sharing-your-site) в Sail.

<a name="defining-webhook-event-handlers"></a>
### Визначення обробників подій вебхуків

Cashier автоматично обробляє скасування підписки за невдалих списань та інші поширені вебхуки Paddle. Однак, якщо у вас є додаткові події вебхуків, які ви хочете обробляти, ви можете зробити це, слухаючи такі події, які диспетчеризує Cashier:

- `Laravel\Paddle\Events\WebhookReceived`
- `Laravel\Paddle\Events\WebhookHandled`

Обидві події містять повні дані вебхука Paddle. Наприклад, якщо ви хочете обробити вебхук `transaction.billed`, зареєструйте [слухача](/docs/{{version}}/events#defining-listeners), який оброблятиме подію:

```php
<?php

namespace App\Listeners;

use Laravel\Paddle\Events\WebhookReceived;

class PaddleEventListener
{
    /**
     * Handle received Paddle webhooks.
     */
    public function handle(WebhookReceived $event): void
    {
        if ($event->payload['event_type'] === 'transaction.billed') {
            // Handle the incoming event...
        }
    }
}
```

Cashier також випромінює події, присвячені типу отриманого вебхука. Окрім повних даних від Paddle, вони також містять релевантні моделі, які було використано для обробки вебхука, як-от модель з білінгом, підписку чи квитанцію:

<div class="content-list" markdown="1">

- `Laravel\Paddle\Events\CustomerUpdated`
- `Laravel\Paddle\Events\TransactionCompleted`
- `Laravel\Paddle\Events\TransactionUpdated`
- `Laravel\Paddle\Events\SubscriptionCreated`
- `Laravel\Paddle\Events\SubscriptionUpdated`
- `Laravel\Paddle\Events\SubscriptionPaused`
- `Laravel\Paddle\Events\SubscriptionCanceled`

</div>

Ви також можете перевизначити вбудований маршрут вебхука за замовчуванням, визначивши змінну оточення `CASHIER_WEBHOOK` у файлі `.env` вашого застосунку. Це значення має бути повним URL до вашого маршруту вебхука і має збігатися з URL, заданим у вашій панелі керування Paddle:

```ini
CASHIER_WEBHOOK=https://example.com/my-paddle-webhook-url
```

<a name="verifying-webhook-signatures"></a>
### Перевірка підписів вебхуків

Щоб захистити свої вебхуки, ви можете скористатися [підписами вебхуків Paddle](https://developer.paddle.com/webhooks/signature-verification). Для зручності Cashier автоматично містить `middleware`, який перевіряє, що вхідний запит вебхука Paddle є дійсним.

Щоб увімкнути перевірку вебхуків, переконайтеся, що змінну оточення `PADDLE_WEBHOOK_SECRET` визначено у файлі `.env` вашого застосунку. Секрет вебхука можна отримати з панелі вашого облікового запису Paddle.

<a name="single-charges"></a>
## Разові списання

<a name="charging-for-products"></a>
### Списання за продукти

Якщо ви хочете ініціювати купівлю продукту для клієнта, скористайтеся методом `checkout` на екземплярі моделі з білінгом, щоб згенерувати сесію оформлення для покупки. Метод `checkout` приймає один чи кілька ID цін. За потреби можна скористатися асоціативним масивом, щоб указати кількість продукту, який купують:

```php
use Illuminate\Http\Request;

Route::get('/buy', function (Request $request) {
    $checkout = $request->user()->checkout(['pri_tshirt', 'pri_socks' => 5]);

    return view('buy', ['checkout' => $checkout]);
});
```

Згенерувавши сесію оформлення, ви можете скористатися наданим Cashier [Blade-компонентом](#overlay-checkout) `paddle-button`, щоб дозволити користувачеві переглянути віджет оформлення Paddle і завершити покупку:

```blade
<x-paddle-button :checkout="$checkout" class="px-8 py-4">
    Buy
</x-paddle-button>
```

Сесія оформлення має метод `customData`, який дозволяє передати будь-які власні дані до створення транзакції, що лежить в основі. Докладніше про доступні вам опції під час передавання власних даних дивіться в [документації Paddle](https://developer.paddle.com/build/transactions/custom-data):

```php
$checkout = $user->checkout('pri_tshirt')
    ->customData([
        'custom_option' => $value,
    ]);
```

<a name="refunding-transactions"></a>
### Повернення коштів за транзакціями

Повернення коштів за транзакціями поверне повернуту суму на платіжний метод вашого клієнта, який використовувався під час покупки. Якщо вам потрібно повернути кошти за покупку в Paddle, скористайтеся методом `refund` на моделі `Cashier\Paddle\Transaction`. Цей метод приймає причину як перший аргумент, а також один чи кілька ID цін для повернення з необов'язковими сумами у вигляді асоціативного масиву. Отримати транзакції для певної моделі з білінгом можна методом `transactions`.

Наприклад, уявімо, що ми хочемо повернути кошти за конкретною транзакцією для цін `pri_123` і `pri_456`. Ми хочемо повністю повернути `pri_123`, але повернути лише два долари за `pri_456`:

```php
use App\Models\User;

$user = User::find(1);

$transaction = $user->transactions()->first();

$response = $transaction->refund('Accidental charge', [
    'pri_123', // Fully refund this price...
    'pri_456' => 200, // Only partially refund this price...
]);
```

Наведений вище приклад повертає кошти за конкретні позиції транзакції. Якщо ви хочете повернути кошти за всю транзакцію, просто вкажіть причину:

```php
$response = $transaction->refund('Accidental charge');
```

Докладніше про повернення коштів дивіться в [документації Paddle щодо повернень](https://developer.paddle.com/build/transactions/create-transaction-adjustments).

> [!WARNING]
> Повернення коштів завжди має бути схвалене Paddle, перш ніж буде оброблене повністю.

<a name="crediting-transactions"></a>
### Кредитування транзакцій

Так само як і повертати кошти, ви можете кредитувати транзакції. Кредитування транзакцій додасть кошти на баланс клієнта, щоб їх можна було використати для майбутніх покупок. Кредитувати можна лише транзакції, зібрані вручну, а не автоматично зібрані транзакції (як-от підписки), оскільки Paddle обробляє кредити за підписками автоматично:

```php
$transaction = $user->transactions()->first();

// Credit a specific line item fully...
$response = $transaction->credit('Compensation', 'pri_123');
```

Докладніше [дивіться в документації Paddle щодо кредитування](https://developer.paddle.com/build/transactions/create-transaction-adjustments).

> [!WARNING]
> Кредити можна застосувати лише до зібраних вручну транзакцій. Автоматично зібрані транзакції кредитує сам Paddle.

<a name="transactions"></a>
## Транзакції

Ви можете легко отримати масив транзакцій моделі з білінгом через властивість `transactions`:

```php
use App\Models\User;

$user = User::find(1);

$transactions = $user->transactions;
```

Транзакції представляють платежі за ваші продукти й покупки і супроводжуються рахунками. У базі даних вашого застосунку зберігаються лише завершені транзакції.

Перелічуючи транзакції клієнта, ви можете скористатися методами екземпляра транзакції, щоб показати релевантну платіжну інформацію. Наприклад, ви можете захотіти перелічити кожну транзакцію в таблиці, дозволивши користувачеві легко завантажити будь-який із рахунків:

```html
<table>
    @foreach ($transactions as $transaction)
        <tr>
            <td>{{ $transaction->billed_at->toFormattedDateString() }}</td>
            <td>{{ $transaction->total() }}</td>
            <td>{{ $transaction->tax() }}</td>
            <td><a href="{{ route('download-invoice', $transaction->id) }}" target="_blank">Download</a></td>
        </tr>
    @endforeach
</table>
```

Маршрут `download-invoice` може виглядати так:

```php
use Illuminate\Http\Request;
use Laravel\Paddle\Transaction;

Route::get('/download-invoice/{transaction}', function (Request $request, Transaction $transaction) {
    return $transaction->redirectToInvoicePdf();
})->name('download-invoice');
```

<a name="past-and-upcoming-payments"></a>
### Минулі та майбутні платежі

Ви можете скористатися методами `lastPayment` і `nextPayment`, щоб отримати й показати минулі чи майбутні платежі клієнта за періодичними підписками:

```php
use App\Models\User;

$user = User::find(1);

$subscription = $user->subscription();

$lastPayment = $subscription->lastPayment();
$nextPayment = $subscription->nextPayment();
```

Обидва ці методи повернуть екземпляр `Laravel\Paddle\Payment`; однак `lastPayment` поверне `null`, коли транзакції ще не синхронізовано вебхуками, а `nextPayment` поверне `null`, коли розрахунковий цикл завершився (наприклад, коли підписку скасовано):

```blade
Next payment: {{ $nextPayment->amount() }} due on {{ $nextPayment->date()->format('d/m/Y') }}
```

<a name="testing"></a>
## Тестування

Під час тестування вам слід вручну перевірити свій потік білінгу, щоб переконатися, що ваша інтеграція працює як очікується.

Для автоматизованих тестів, зокрема тих, що виконуються в CI-середовищі, ви можете скористатися [HTTP-клієнтом Laravel](/docs/{{version}}/http-client#testing), щоб підробити HTTP-виклики до Paddle. Хоча це не тестує фактичні відповіді від Paddle, воно дає спосіб тестувати ваш застосунок, не викликаючи API Paddle насправді.

---

Джерело: https://laravelukraine.com/docs/13.x/dusk

# Laravel Dusk

- [Вступ](#introduction)
- [Встановлення](#installation)
    - [Керування встановленням ChromeDriver](#managing-chromedriver-installations)
    - [Використання інших браузерів](#using-other-browsers)
- [Перші кроки](#getting-started)
    - [Генерування тестів](#generating-tests)
    - [Скидання бази даних після кожного тесту](#resetting-the-database-after-each-test)
    - [Запуск тестів](#running-tests)
    - [Робота із середовищем](#environment-handling)
- [Основи роботи з браузером](#browser-basics)
    - [Створення браузерів](#creating-browsers)
    - [Навігація](#navigation)
    - [Зміна розміру вікна браузера](#resizing-browser-windows)
    - [Макроси браузера](#browser-macros)
    - [Автентифікація](#authentication)
    - [Cookie](#cookies)
    - [Виконання JavaScript](#executing-javascript)
    - [Знімок екрана](#taking-a-screenshot)
    - [Збереження виводу консолі на диск](#storing-console-output-to-disk)
    - [Збереження коду сторінки на диск](#storing-page-source-to-disk)
- [Взаємодія з елементами](#interacting-with-elements)
    - [Селектори Dusk](#dusk-selectors)
    - [Текст, значення та атрибути](#text-values-and-attributes)
    - [Робота з формами](#interacting-with-forms)
    - [Прикріплення файлів](#attaching-files)
    - [Натискання кнопок](#pressing-buttons)
    - [Клацання по посиланнях](#clicking-links)
    - [Використання клавіатури](#using-the-keyboard)
    - [Використання миші](#using-the-mouse)
    - [Діалоги JavaScript](#javascript-dialogs)
    - [Робота з вбудованими фреймами](#interacting-with-iframes)
    - [Обмеження селекторів](#scoping-selectors)
    - [Очікування елементів](#waiting-for-elements)
    - [Прокручування до елемента](#scrolling-an-element-into-view)
- [Доступні твердження](#available-assertions)
- [Сторінки](#pages)
    - [Генерування сторінок](#generating-pages)
    - [Налаштування сторінок](#configuring-pages)
    - [Перехід до сторінок](#navigating-to-pages)
    - [Скорочені селектори](#shorthand-selectors)
    - [Методи сторінок](#page-methods)
- [Компоненти](#components)
    - [Генерування компонентів](#generating-components)
    - [Використання компонентів](#using-components)
- [Неперервна інтеграція](#continuous-integration)
    - [Heroku CI](#running-tests-on-heroku-ci)
    - [Travis CI](#running-tests-on-travis-ci)
    - [GitHub Actions](#running-tests-on-github-actions)
    - [Chipper CI](#running-tests-on-chipper-ci)

<a name="introduction"></a>
## Вступ

> [!WARNING]
> [Pest 4](https://pestphp.com/) тепер містить автоматизоване тестування у браузері, яке суттєво виграє в продуктивності та зручності порівняно з Laravel Dusk. Для нових проєктів ми рекомендуємо тестувати у браузері саме через Pest.

[Laravel Dusk](https://github.com/laravel/dusk) надає виразний і простий у користуванні API для автоматизації браузера й тестування. За замовчуванням Dusk не вимагає встановлювати JDK чи Selenium на ваш комп'ютер. Натомість Dusk використовує окремо встановлений [ChromeDriver](https://sites.google.com/chromium.org/driver). Проте ви вільні скористатися будь-яким іншим сумісним із Selenium драйвером.

<a name="installation"></a>
## Встановлення

Для початку встановіть [Google Chrome](https://www.google.com/chrome) і додайте до свого проєкту Composer-залежність `laravel/dusk`:

```shell
composer require laravel/dusk --dev
```

> [!WARNING]
> Якщо ви реєструєте сервіс-провайдер Dusk вручну, **ніколи** не реєструйте його у продакшн-середовищі: це може призвести до того, що будь-хто зможе автентифікуватися у вашому застосунку.

Після встановлення пакета Dusk виконайте артизан-команду `dusk:install`. Команда `dusk:install` створить каталог `tests/Browser`, приклад тесту Dusk і встановить бінарник Chrome Driver для вашої операційної системи:

```shell
php artisan dusk:install
```

Далі задайте змінну оточення `APP_URL` у файлі `.env` вашого застосунку. Це значення має збігатися з URL, за яким ви звертаєтеся до застосунку у браузері.

> [!NOTE]
> Якщо ви користуєтеся [Laravel Sail](/docs/{{version}}/sail) для керування локальним середовищем розробки, зверніться також до документації Sail щодо [налаштування й запуску тестів Dusk](/docs/{{version}}/sail#laravel-dusk).

<a name="managing-chromedriver-installations"></a>
### Керування встановленням ChromeDriver

Якщо ви хочете встановити версію ChromeDriver, відмінну від тієї, яку встановлює Laravel Dusk командою `dusk:install`, скористайтеся командою `dusk:chrome-driver`:

```shell
# Install the latest version of ChromeDriver for your OS...
php artisan dusk:chrome-driver

# Install a given version of ChromeDriver for your OS...
php artisan dusk:chrome-driver 86

# Install a given version of ChromeDriver for all supported OSs...
php artisan dusk:chrome-driver --all

# Install the version of ChromeDriver that matches the detected version of Chrome / Chromium for your OS...
php artisan dusk:chrome-driver --detect
```

> [!WARNING]
> Dusk вимагає, щоб бінарники `chromedriver` були виконуваними. Якщо у вас виникають проблеми із запуском Dusk, переконайтеся, що бінарники виконувані, такою командою: `chmod -R 0755 vendor/laravel/dusk/bin/`.

<a name="using-other-browsers"></a>
### Використання інших браузерів

За замовчуванням Dusk запускає ваші браузерні тести в Google Chrome через окремо встановлений [ChromeDriver](https://sites.google.com/chromium.org/driver). Проте ви можете запустити власний сервер Selenium і виконувати тести в будь-якому браузері.

Для початку відкрийте файл `tests/DuskTestCase.php` - це базовий тест-кейс Dusk для вашого застосунку. У ньому ви можете прибрати виклик методу `startChromeDriver`. Це зупинить автоматичний запуск ChromeDriver у Dusk:

```php
/**
 * Prepare for Dusk test execution.
 *
 * @beforeClass
 */
public static function prepare(): void
{
    // static::startChromeDriver();
}
```

Далі ви можете змінити метод `driver`, щоб підключитися до потрібних вам URL і порту. Крім того, ви можете змінити «бажані можливості» (desired capabilities), які передаються до WebDriver:

```php
use Facebook\WebDriver\Remote\RemoteWebDriver;

/**
 * Create the RemoteWebDriver instance.
 */
protected function driver(): RemoteWebDriver
{
    return RemoteWebDriver::create(
        'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs()
    );
}
```

<a name="getting-started"></a>
## Перші кроки

<a name="generating-tests"></a>
### Генерування тестів

Щоб згенерувати тест Dusk, скористайтеся артизан-командою `dusk:make`. Згенерований тест потрапить до каталогу `tests/Browser`:

```shell
php artisan dusk:make LoginTest
```

<a name="resetting-the-database-after-each-test"></a>
### Скидання бази даних після кожного тесту

Більшість ваших тестів працюватимуть зі сторінками, які отримують дані з бази даних застосунку; проте ваші тести Dusk ніколи не повинні використовувати трейт `RefreshDatabase`. Трейт `RefreshDatabase` спирається на транзакції бази даних, які не застосовні й недоступні між HTTP-запитами. Натомість у вас є два варіанти: трейт `DatabaseMigrations` і трейт `DatabaseTruncation`.

<a name="reset-migrations"></a>
#### Використання міграцій бази даних

Трейт `DatabaseMigrations` запускатиме ваші міграції перед кожним тестом. Проте видалення й повторне створення таблиць для кожного тесту зазвичай повільніше за їх очищення:

```php tab=Pest
<?php

use Illuminate\Foundation\Testing\DatabaseMigrations;

pest()->use(DatabaseMigrations::class);

//
```

```php tab=PHPUnit
<?php

namespace Tests\Browser;

use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;

class ExampleTest extends DuskTestCase
{
    use DatabaseMigrations;

    //
}
```

> [!WARNING]
> Бази даних SQLite в пам'яті не можна використовувати під час прогону тестів Dusk. Оскільки браузер виконується у власному процесі, він не матиме доступу до баз даних у пам'яті інших процесів.

<a name="reset-truncation"></a>
#### Використання очищення бази даних

Трейт `DatabaseTruncation` мігрує вашу базу даних на першому тесті, щоб переконатися, що таблиці створено належним чином. Проте в наступних тестах таблиці бази просто очищатимуться - це швидше, ніж щоразу проганяти всі міграції:

```php tab=Pest
<?php

use Illuminate\Foundation\Testing\DatabaseTruncation;

pest()->use(DatabaseTruncation::class);

//
```

```php tab=PHPUnit
<?php

namespace Tests\Browser;

use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTruncation;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;

class ExampleTest extends DuskTestCase
{
    use DatabaseTruncation;

    //
}
```

За замовчуванням цей трейт очищує всі таблиці, окрім `migrations`. Якщо ви хочете вказати, які саме таблиці слід очищати, визначте у своєму тестовому класі властивість `$tablesToTruncate`:

> [!NOTE]
> Якщо ви користуєтеся Pest, визначайте властивості чи методи в базовому класі `DuskTestCase` або в будь-якому класі, який успадковує ваш тестовий файл.

```php
/**
 * Indicates which tables should be truncated.
 *
 * @var array
 */
protected $tablesToTruncate = ['users'];
```

Або ж ви можете визначити у своєму тестовому класі властивість `$exceptTables`, щоб указати, які таблиці слід виключити з очищення:

```php
/**
 * Indicates which tables should be excluded from truncation.
 *
 * @var array
 */
protected $exceptTables = ['users'];
```

Щоб указати підключення до баз даних, чиї таблиці слід очищати, визначте у своєму тестовому класі властивість `$connectionsToTruncate`:

```php
/**
 * Indicates which connections should have their tables truncated.
 *
 * @var array
 */
protected $connectionsToTruncate = ['mysql'];
```

Якщо ви хочете виконати код до або після очищення бази даних, визначте у своєму тестовому класі методи `beforeTruncatingDatabase` чи `afterTruncatingDatabase`:

```php
/**
 * Perform any work that should take place before the database has started truncating.
 */
protected function beforeTruncatingDatabase(): void
{
    //
}

/**
 * Perform any work that should take place after the database has finished truncating.
 */
protected function afterTruncatingDatabase(): void
{
    //
}
```

<a name="running-tests"></a>
### Запуск тестів

Щоб запустити ваші браузерні тести, виконайте артизан-команду `dusk`:

```shell
php artisan dusk
```

Якщо під час останнього запуску команди `dusk` у вас були провалені тести, ви можете заощадити час, спершу перезапустивши саме їх командою `dusk:fails`:

```shell
php artisan dusk:fails
```

Команда `dusk` приймає будь-який аргумент, який зазвичай приймає раннер тестів Pest / PHPUnit, - наприклад, щоб запустити лише тести певної [групи](https://docs.phpunit.de/en/10.5/annotations.html#group):

```shell
php artisan dusk --group=foo
```

> [!NOTE]
> Якщо ви користуєтеся [Laravel Sail](/docs/{{version}}/sail) для керування локальним середовищем розробки, зверніться до документації Sail щодо [налаштування й запуску тестів Dusk](/docs/{{version}}/sail#laravel-dusk).

<a name="manually-starting-chromedriver"></a>
#### Ручний запуск ChromeDriver

За замовчуванням Dusk автоматично намагається запустити ChromeDriver. Якщо у вашій системі це не працює, ви можете запустити ChromeDriver вручну перед виконанням команди `dusk`. Якщо ви вирішите запускати ChromeDriver вручну, закоментуйте такий рядок у файлі `tests/DuskTestCase.php`:

```php
/**
 * Prepare for Dusk test execution.
 *
 * @beforeClass
 */
public static function prepare(): void
{
    // static::startChromeDriver();
}
```

Крім того, якщо ви запускаєте ChromeDriver на порту, відмінному від 9515, змініть метод `driver` того самого класу, щоб указати правильний порт:

```php
use Facebook\WebDriver\Remote\RemoteWebDriver;

/**
 * Create the RemoteWebDriver instance.
 */
protected function driver(): RemoteWebDriver
{
    return RemoteWebDriver::create(
        'http://localhost:9515', DesiredCapabilities::chrome()
    );
}
```

<a name="environment-handling"></a>
### Робота із середовищем

Щоб змусити Dusk використовувати власний файл оточення під час прогону тестів, створіть у корені проєкту файл `.env.dusk.{environment}`. Наприклад, якщо ви запускатимете команду `dusk` із середовища `local`, створіть файл `.env.dusk.local`.

Під час прогону тестів Dusk створить резервну копію вашого файлу `.env` і перейменує ваше оточення Dusk на `.env`. Коли тести завершаться, ваш файл `.env` буде відновлено.

<a name="browser-basics"></a>
## Основи роботи з браузером

<a name="creating-browsers"></a>
### Створення браузерів

Для початку напишімо тест, який перевіряє, що ми можемо увійти до нашого застосунку. Згенерувавши тест, ми можемо змінити його так, щоб перейти на сторінку входу, ввести облікові дані й натиснути кнопку «Login». Щоб створити екземпляр браузера, викличте метод `browse` усередині вашого тесту Dusk:

```php tab=Pest
<?php

use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;

pest()->use(DatabaseMigrations::class);

test('basic example', function () {
    $user = User::factory()->create([
        'email' => 'taylor@laravel.com',
    ]);

    $this->browse(function (Browser $browser) use ($user) {
        $browser->visit('/login')
            ->type('email', $user->email)
            ->type('password', 'password')
            ->press('Login')
            ->assertPathIs('/home');
    });
});
```

```php tab=PHPUnit
<?php

namespace Tests\Browser;

use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;

class ExampleTest extends DuskTestCase
{
    use DatabaseMigrations;

    /**
     * A basic browser test example.
     */
    public function test_basic_example(): void
    {
        $user = User::factory()->create([
            'email' => 'taylor@laravel.com',
        ]);

        $this->browse(function (Browser $browser) use ($user) {
            $browser->visit('/login')
                ->type('email', $user->email)
                ->type('password', 'password')
                ->press('Login')
                ->assertPathIs('/home');
        });
    }
}
```

Як видно з прикладу вище, метод `browse` приймає замикання. Dusk автоматично передасть до нього екземпляр браузера - це головний об'єкт для взаємодії з вашим застосунком і тверджень щодо нього.

<a name="creating-multiple-browsers"></a>
#### Створення кількох браузерів

Іноді для належного виконання тесту вам може знадобитися кілька браузерів. Наприклад, вони можуть знадобитися, щоб протестувати екран чату, який працює через вебсокети. Щоб створити кілька браузерів, просто додайте більше аргументів-браузерів до сигнатури замикання, переданого методу `browse`:

```php
$this->browse(function (Browser $first, Browser $second) {
    $first->loginAs(User::find(1))
        ->visit('/home')
        ->waitForText('Message');

    $second->loginAs(User::find(2))
        ->visit('/home')
        ->waitForText('Message')
        ->type('message', 'Hey Taylor')
        ->press('Send');

    $first->waitForText('Hey Taylor')
        ->assertSee('Jeffrey Way');
});
```

<a name="navigation"></a>
### Навігація

Метод `visit` дозволяє перейти до заданого URI у вашому застосунку:

```php
$browser->visit('/login');
```

Метод `visitRoute` дозволяє перейти до [іменованого маршруту](/docs/{{version}}/routing#named-routes):

```php
$browser->visitRoute($routeName, $parameters);
```

Ви можете переходити «назад» і «вперед» методами `back` та `forward`:

```php
$browser->back();

$browser->forward();
```

Метод `refresh` дозволяє оновити сторінку:

```php
$browser->refresh();
```

<a name="resizing-browser-windows"></a>
### Зміна розміру вікна браузера

Метод `resize` дозволяє змінити розмір вікна браузера:

```php
$browser->resize(1920, 1080);
```

Метод `maximize` дозволяє розгорнути вікно браузера на весь екран:

```php
$browser->maximize();
```

Метод `fitContent` змінить розмір вікна браузера так, щоб він відповідав розміру вмісту:

```php
$browser->fitContent();
```

Коли тест провалюється, Dusk автоматично змінює розмір браузера під вміст перед знімком екрана. Ви можете вимкнути цю поведінку, викликавши у своєму тесті метод `disableFitOnFailure`:

```php
$browser->disableFitOnFailure();
```

Метод `move` дозволяє перемістити вікно браузера в інше місце екрана:

```php
$browser->move($x = 100, $y = 100);
```

<a name="browser-macros"></a>
### Макроси браузера

Якщо ви хочете визначити власний метод браузера, який можна перевикористовувати в різних тестах, скористайтеся методом `macro` класу `Browser`. Зазвичай цей метод викликають у методі `boot` [сервіс-провайдера](/docs/{{version}}/providers):

```php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Laravel\Dusk\Browser;

class DuskServiceProvider extends ServiceProvider
{
    /**
     * Register Dusk's browser macros.
     */
    public function boot(): void
    {
        Browser::macro('scrollToElement', function (string $element = null) {
            $this->script("$('html, body').animate({ scrollTop: $('$element').offset().top }, 0);");

            return $this;
        });
    }
}
```

Функція `macro` приймає першим аргументом ім'я, а другим - замикання. Замикання макроса виконається, коли ви викличете макрос як метод на екземплярі `Browser`:

```php
$this->browse(function (Browser $browser) use ($user) {
    $browser->visit('/pay')
        ->scrollToElement('#credit-card-details')
        ->assertSee('Enter Credit Card Details');
});
```

<a name="authentication"></a>
### Автентифікація

Часто ви тестуватимете сторінки, які вимагають автентифікації. Щоб не взаємодіяти з екраном входу вашого застосунку в кожному тесті, скористайтеся методом Dusk `loginAs`. Метод `loginAs` приймає первинний ключ вашої моделі, придатної до автентифікації, або сам її екземпляр:

```php
use App\Models\User;
use Laravel\Dusk\Browser;

$this->browse(function (Browser $browser) {
    $browser->loginAs(User::find(1))
        ->visit('/home');
});
```

> [!WARNING]
> Після використання методу `loginAs` сесія користувача зберігатиметься для всіх тестів у межах файлу.

<a name="cookies"></a>
### Cookie

Метод `cookie` дозволяє отримати чи задати значення зашифрованого cookie. За замовчуванням усі створені Laravel cookie зашифровані:

```php
$browser->cookie('name');

$browser->cookie('name', 'Taylor');
```

Метод `plainCookie` дозволяє отримати чи задати значення незашифрованого cookie:

```php
$browser->plainCookie('name');

$browser->plainCookie('name', 'Taylor');
```

Метод `deleteCookie` дозволяє видалити заданий cookie:

```php
$browser->deleteCookie('name');
```

<a name="executing-javascript"></a>
### Виконання JavaScript

Метод `script` дозволяє виконати у браузері довільні інструкції JavaScript:

```php
$browser->script('document.documentElement.scrollTop = 0');

$browser->script([
    'document.body.scrollTop = 0',
    'document.documentElement.scrollTop = 0',
]);

$output = $browser->script('return window.location.pathname');
```

<a name="taking-a-screenshot"></a>
### Знімок екрана

Метод `screenshot` дозволяє зробити знімок екрана й зберегти його із заданим іменем файлу. Усі знімки зберігаються в каталозі `tests/Browser/screenshots`:

```php
$browser->screenshot('filename');
```

Метод `responsiveScreenshots` дозволяє зробити серію знімків на різних брейкпоїнтах:

```php
$browser->responsiveScreenshots('filename');
```

Метод `screenshotElement` дозволяє зробити знімок конкретного елемента на сторінці:

```php
$browser->screenshotElement('#selector', 'filename');
```

<a name="storing-console-output-to-disk"></a>
### Збереження виводу консолі на диск

Метод `storeConsoleLog` дозволяє записати вивід консолі поточного браузера на диск під заданим іменем файлу. Вивід консолі зберігається в каталозі `tests/Browser/console`:

```php
$browser->storeConsoleLog('filename');
```

<a name="storing-page-source-to-disk"></a>
### Збереження коду сторінки на диск

Метод `storeSource` дозволяє записати код поточної сторінки на диск під заданим іменем файлу. Код сторінки зберігається в каталозі `tests/Browser/source`:

```php
$browser->storeSource('filename');
```

<a name="interacting-with-elements"></a>
## Взаємодія з елементами

<a name="dusk-selectors"></a>
### Селектори Dusk

Вибір хороших CSS-селекторів для взаємодії з елементами - одна з найскладніших частин написання тестів Dusk. З часом зміни у фронтенді можуть зламати ваші тести через CSS-селектори на кшталт такого:

```html
// HTML...

<button>Login</button>
```

```php
// Test...

$browser->click('.login-page .container div > button');
```

Селектори Dusk дозволяють зосередитися на написанні ефективних тестів, а не на запам'ятовуванні CSS-селекторів. Щоб визначити селектор, додайте до свого HTML-елемента атрибут `dusk`. Далі, взаємодіючи з браузером Dusk, додайте до селектора префікс `@`, щоб працювати з цим елементом у тесті:

```html
// HTML...

<button dusk="login-button">Login</button>
```

```php
// Test...

$browser->click('@login-button');
```

За бажання ви можете змінити HTML-атрибут, який використовує селектор Dusk, методом `selectorHtmlAttribute`. Зазвичай цей метод викликають у методі `boot` вашого `AppServiceProvider`:

```php
use Laravel\Dusk\Dusk;

Dusk::selectorHtmlAttribute('data-dusk');
```

<a name="text-values-and-attributes"></a>
### Текст, значення та атрибути

<a name="retrieving-setting-values"></a>
#### Отримання та задавання значень

Dusk надає кілька методів для роботи з поточним значенням, видимим текстом та атрибутами елементів на сторінці. Наприклад, щоб отримати «значення» елемента, який відповідає заданому CSS- чи Dusk-селектору, скористайтеся методом `value`:

```php
// Retrieve the value...
$value = $browser->value('selector');

// Set the value...
$browser->value('selector', 'value');
```

Метод `inputValue` дозволяє отримати «значення» елемента input із заданим іменем поля:

```php
$value = $browser->inputValue('field');
```

<a name="retrieving-text"></a>
#### Отримання тексту

Метод `text` дозволяє отримати видимий текст елемента, який відповідає заданому селектору:

```php
$text = $browser->text('selector');
```

<a name="retrieving-attributes"></a>
#### Отримання атрибутів

Нарешті, метод `attribute` дозволяє отримати значення атрибута елемента, який відповідає заданому селектору:

```php
$attribute = $browser->attribute('selector', 'value');
```

<a name="interacting-with-forms"></a>
### Робота з формами

<a name="typing-values"></a>
#### Введення значень

Dusk надає різні методи для роботи з формами й полями введення. Спершу погляньмо на приклад введення тексту в поле:

```php
$browser->type('email', 'taylor@laravel.com');
```

Зверніть увагу: хоча метод за потреби приймає CSS-селектор, передавати його до методу `type` не обов'язково. Якщо CSS-селектор не задано, Dusk шукатиме поле `input` чи `textarea` із заданим атрибутом `name`.

Щоб додати текст до поля, не очищаючи його вмісту, скористайтеся методом `append`:

```php
$browser->type('tags', 'foo')
    ->append('tags', ', bar, baz');
```

Очистити значення поля можна методом `clear`:

```php
$browser->clear('email');
```

Ви можете вказати Dusk вводити текст повільно методом `typeSlowly`. За замовчуванням Dusk робить паузу 100 мілісекунд між натисканнями клавіш. Щоб змінити цей час, передайте потрібну кількість мілісекунд третім аргументом методу:

```php
$browser->typeSlowly('mobile', '+1 (202) 555-5555');

$browser->typeSlowly('mobile', '+1 (202) 555-5555', 300);
```

Метод `appendSlowly` дозволяє повільно додавати текст:

```php
$browser->type('tags', 'foo')
    ->appendSlowly('tags', ', bar, baz');
```

<a name="dropdowns"></a>
#### Випадні списки

Щоб обрати значення в елементі `select`, скористайтеся методом `select`. Як і методу `type`, методу `select` не потрібен повний CSS-селектор. Передаючи значення методу `select`, передавайте саме значення опції, а не видимий текст:

```php
$browser->select('size', 'Large');
```

Ви можете обрати випадкову опцію, не передаючи другого аргументу:

```php
$browser->select('size');
```

Передавши масив другим аргументом до методу `select`, ви можете вказати методу обрати кілька опцій:

```php
$browser->select('categories', ['Art', 'Music']);
```

<a name="checkboxes"></a>
#### Чекбокси

Щоб «поставити галочку» в чекбоксі, скористайтеся методом `check`. Як і багатьом іншим методам для полів введення, повний CSS-селектор тут не потрібен. Якщо збігу за CSS-селектором не знайдено, Dusk шукатиме чекбокс із відповідним атрибутом `name`:

```php
$browser->check('terms');
```

Метод `uncheck` дозволяє «зняти галочку» з чекбокса:

```php
$browser->uncheck('terms');
```

<a name="radio-buttons"></a>
#### Радіокнопки

Щоб «обрати» опцію `radio`, скористайтеся методом `radio`. Як і багатьом іншим методам для полів введення, повний CSS-селектор тут не потрібен. Якщо збігу за CSS-селектором не знайдено, Dusk шукатиме поле `radio` з відповідними атрибутами `name` та `value`:

```php
$browser->radio('size', 'large');
```

<a name="attaching-files"></a>
### Прикріплення файлів

Метод `attach` дозволяє прикріпити файл до елемента `file`. Як і багатьом іншим методам для полів введення, повний CSS-селектор тут не потрібен. Якщо збігу за CSS-селектором не знайдено, Dusk шукатиме поле `file` з відповідним атрибутом `name`:

```php
$browser->attach('photo', __DIR__.'/photos/mountains.png');
```

> [!WARNING]
> Функція attach вимагає, щоб на вашому сервері було встановлено й увімкнено розширення PHP `Zip`.

<a name="pressing-buttons"></a>
### Натискання кнопок

Метод `press` дозволяє клацнути по кнопці на сторінці. Аргументом методу `press` може бути або видимий текст кнопки, або CSS- чи Dusk-селектор:

```php
$browser->press('Login');
```

Надсилаючи форми, багато застосунків вимикають кнопку надсилання після натискання й вмикають її знову, коли HTTP-запит завершується. Щоб натиснути кнопку й дочекатися, доки її знову ввімкнуть, скористайтеся методом `pressAndWaitFor`:

```php
// Press the button and wait a maximum of 5 seconds for it to be enabled...
$browser->pressAndWaitFor('Save');

// Press the button and wait a maximum of 1 second for it to be enabled...
$browser->pressAndWaitFor('Save', 1);
```

<a name="clicking-links"></a>
### Клацання по посиланнях

Щоб клацнути по посиланню, скористайтеся методом `clickLink` на екземплярі браузера. Метод `clickLink` клацне по посиланню із заданим видимим текстом:

```php
$browser->clickLink($linkText);
```

Метод `seeLink` дозволяє визначити, чи видиме на сторінці посилання із заданим видимим текстом:

```php
if ($browser->seeLink($linkText)) {
    // ...
}
```

> [!WARNING]
> Ці методи працюють через jQuery. Якщо jQuery на сторінці немає, Dusk автоматично впровадить його, щоб він був доступний на час тесту.

<a name="using-the-keyboard"></a>
### Використання клавіатури

Метод `keys` дозволяє передавати елементу складніші послідовності введення, ніж дозволяє метод `type`. Наприклад, ви можете вказати Dusk утримувати клавіші-модифікатори під час введення значень. У цьому прикладі клавішу `shift` буде утримувано, доки в елемент, що відповідає заданому селектору, вводиться `taylor`. Після `taylor` буде введено `swift` уже без модифікаторів:

```php
$browser->keys('selector', ['{shift}', 'taylor'], 'swift');
```

Ще один корисний випадок для методу `keys` - надсилання комбінації «гарячих клавіш» до основного CSS-селектора вашого застосунку:

```php
$browser->keys('.app', ['{command}', 'j']);
```

> [!NOTE]
> Усі клавіші-модифікатори на кшталт `{command}` беруться у фігурні дужки `{}` і відповідають константам, визначеним у класі `Facebook\WebDriver\WebDriverKeys`, який можна [знайти на GitHub](https://github.com/php-webdriver/php-webdriver/blob/master/lib/WebDriverKeys.php).

<a name="fluent-keyboard-interactions"></a>
#### Плавна робота з клавіатурою

Dusk також надає метод `withKeyboard`, який дозволяє плавно виконувати складні взаємодії з клавіатурою через клас `Laravel\Dusk\Keyboard`. Клас `Keyboard` надає методи `press`, `release`, `type` та `pause`:

```php
use Laravel\Dusk\Keyboard;

$browser->withKeyboard(function (Keyboard $keyboard) {
    $keyboard->press('c')
        ->pause(1000)
        ->release('c')
        ->type(['c', 'e', 'o']);
});
```

<a name="keyboard-macros"></a>
#### Макроси клавіатури

Якщо ви хочете визначити власні взаємодії з клавіатурою, які легко перевикористовувати в усьому наборі тестів, скористайтеся методом `macro`, який надає клас `Keyboard`. Зазвичай цей метод викликають у методі `boot` [сервіс-провайдера](/docs/{{version}}/providers):

```php
<?php

namespace App\Providers;

use Facebook\WebDriver\WebDriverKeys;
use Illuminate\Support\ServiceProvider;
use Laravel\Dusk\Keyboard;
use Laravel\Dusk\OperatingSystem;

class DuskServiceProvider extends ServiceProvider
{
    /**
     * Register Dusk's browser macros.
     */
    public function boot(): void
    {
        Keyboard::macro('copy', function (string $element = null) {
            $this->type([
                OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'c',
            ]);

            return $this;
        });

        Keyboard::macro('paste', function (string $element = null) {
            $this->type([
                OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'v',
            ]);

            return $this;
        });
    }
}
```

Функція `macro` приймає першим аргументом ім'я, а другим - замикання. Замикання макроса виконається, коли ви викличете макрос як метод на екземплярі `Keyboard`:

```php
$browser->click('@textarea')
    ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->copy())
    ->click('@another-textarea')
    ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->paste());
```

<a name="using-the-mouse"></a>
### Використання миші

<a name="clicking-on-elements"></a>
#### Клацання по елементах

Метод `click` дозволяє клацнути по елементу, який відповідає заданому CSS- чи Dusk-селектору:

```php
$browser->click('.selector');
```

Метод `clickAtXPath` дозволяє клацнути по елементу, який відповідає заданому виразу XPath:

```php
$browser->clickAtXPath('//div[@class = "selector"]');
```

Метод `clickAtPoint` дозволяє клацнути по найвищому елементу за заданою парою координат відносно видимої області браузера:

```php
$browser->clickAtPoint($x = 0, $y = 0);
```

Метод `doubleClick` дозволяє симулювати подвійне клацання мишею:

```php
$browser->doubleClick();

$browser->doubleClick('.selector');
```

Метод `rightClick` дозволяє симулювати клацання правою кнопкою миші:

```php
$browser->rightClick();

$browser->rightClick('.selector');
```

Метод `clickAndHold` дозволяє симулювати натискання й утримування кнопки миші. Наступний виклик методу `releaseMouse` скасує цю поведінку й відпустить кнопку:

```php
$browser->clickAndHold('.selector');

$browser->clickAndHold()
    ->pause(1000)
    ->releaseMouse();
```

Метод `controlClick` дозволяє симулювати у браузері подію `ctrl+click`:

```php
$browser->controlClick();

$browser->controlClick('.selector');
```

Методи `clickWhenVisible` чи `clickWhenEnabled` дозволяють дочекатися готовності елемента, перш ніж клацнути по ньому рівно один раз:

```php
$browser->clickWhenVisible('@save-button');
$browser->clickWhenEnabled('@submit-button');
```

<a name="mouseover"></a>
#### Наведення миші

Метод `mouseover` стане в пригоді, коли вам треба навести мишу на елемент, що відповідає заданому CSS- чи Dusk-селектору:

```php
$browser->mouseover('.selector');
```

<a name="drag-drop"></a>
#### Перетягування

Метод `drag` дозволяє перетягнути елемент, що відповідає заданому селектору, до іншого елемента:

```php
$browser->drag('.from-selector', '.to-selector');
```

Або ж ви можете перетягнути елемент в одному напрямку:

```php
$browser->dragLeft('.selector', $pixels = 10);
$browser->dragRight('.selector', $pixels = 10);
$browser->dragUp('.selector', $pixels = 10);
$browser->dragDown('.selector', $pixels = 10);
```

Нарешті, ви можете перетягнути елемент на заданий зсув:

```php
$browser->dragOffset('.selector', $x = 10, $y = 10);
```

<a name="javascript-dialogs"></a>
### Діалоги JavaScript

Dusk надає різні методи для роботи з діалогами JavaScript. Наприклад, метод `waitForDialog` дозволяє дочекатися появи діалогу JavaScript. Цей метод приймає необов'язковий аргумент - скільки секунд чекати на появу діалогу:

```php
$browser->waitForDialog($seconds = null);
```

Метод `assertDialogOpened` дозволяє перевірити, що діалог було показано і що він містить задане повідомлення:

```php
$browser->assertDialogOpened('Dialog message');
```

Якщо діалог JavaScript містить поле введення, ви можете скористатися методом `typeInDialog`, щоб ввести туди значення:

```php
$browser->typeInDialog('Hello World');
```

Щоб закрити відкритий діалог JavaScript натисканням кнопки «OK», викличте метод `acceptDialog`:

```php
$browser->acceptDialog();
```

Щоб закрити відкритий діалог JavaScript натисканням кнопки «Cancel», викличте метод `dismissDialog`:

```php
$browser->dismissDialog();
```

<a name="interacting-with-iframes"></a>
### Робота з вбудованими фреймами

Якщо вам потрібно взаємодіяти з елементами всередині iframe, скористайтеся методом `withinFrame`. Усі взаємодії з елементами всередині замикання, переданого методу `withinFrame`, будуть обмежені контекстом указаного iframe:

```php
$browser->withinFrame('#credit-card-details', function ($browser) {
    $browser->type('input[name="cardnumber"]', '4242424242424242')
        ->type('input[name="exp-date"]', '1224')
        ->type('input[name="cvc"]', '123')
        ->press('Pay');
});
```

<a name="scoping-selectors"></a>
### Обмеження селекторів

Іноді вам може знадобитися виконати кілька операцій, обмеживши їх усі заданим селектором. Наприклад, ви можете захотіти перевірити, що певний текст є лише в таблиці, а потім клацнути по кнопці всередині цієї таблиці. Для цього скористайтеся методом `with`. Усі операції всередині замикання, переданого методу `with`, будуть обмежені початковим селектором:

```php
$browser->with('.table', function (Browser $table) {
    $table->assertSee('Hello World')
        ->clickLink('Delete');
});
```

Іноді вам може знадобитися виконати твердження поза межами поточного обмеження. Для цього скористайтеся методами `elsewhere` та `elsewhereWhenAvailable`:

```php
$browser->with('.table', function (Browser $table) {
    // Current scope is `body .table`...

    $browser->elsewhere('.page-title', function (Browser $title) {
        // Current scope is `body .page-title`...
        $title->assertSee('Hello World');
    });

    $browser->elsewhereWhenAvailable('.page-title', function (Browser $title) {
        // Current scope is `body .page-title`...
        $title->assertSee('Hello World');
    });
});
```

<a name="waiting-for-elements"></a>
### Очікування елементів

Тестуючи застосунки, які активно використовують JavaScript, часто доводиться «чекати» на доступність певних елементів чи даних, перш ніж рухатися далі. Dusk робить це дуже просто. За допомогою різних методів ви можете дочекатися, доки елементи стануть видимими на сторінці, або навіть доки заданий вираз JavaScript не набуде значення `true`.

<a name="waiting"></a>
#### Очікування

Якщо вам просто треба призупинити тест на задану кількість мілісекунд, скористайтеся методом `pause`:

```php
$browser->pause(1000);
```

Якщо тест треба призупинити лише за умови, що задана умова істинна (`true`), скористайтеся методом `pauseIf`:

```php
$browser->pauseIf(App::environment('production'), 1000);
```

Так само, якщо тест треба призупинити, доки задана умова не стане істинною (`true`), скористайтеся методом `pauseUnless`:

```php
$browser->pauseUnless(App::environment('testing'), 1000);
```

<a name="waiting-for-selectors"></a>
#### Очікування селекторів

Метод `waitFor` дозволяє призупинити виконання тесту, доки на сторінці не з'явиться елемент, що відповідає заданому CSS- чи Dusk-селектору. За замовчуванням тест буде призупинено максимум на п'ять секунд, після чого буде кинуто виняток. За потреби ви можете передати власний поріг очікування другим аргументом методу:

```php
// Wait a maximum of five seconds for the selector...
$browser->waitFor('.selector');

// Wait a maximum of one second for the selector...
$browser->waitFor('.selector', 1);
```

Ви також можете дочекатися, доки елемент, що відповідає заданому селектору, не міститиме заданий текст:

```php
// Wait a maximum of five seconds for the selector to contain the given text...
$browser->waitForTextIn('.selector', 'Hello World');

// Wait a maximum of one second for the selector to contain the given text...
$browser->waitForTextIn('.selector', 'Hello World', 1);
```

Ви також можете дочекатися, доки елемент, що відповідає заданому селектору, зникне зі сторінки:

```php
// Wait a maximum of five seconds until the selector is missing...
$browser->waitUntilMissing('.selector');

// Wait a maximum of one second until the selector is missing...
$browser->waitUntilMissing('.selector', 1);
```

Або ж ви можете дочекатися, доки елемент, що відповідає заданому селектору, стане увімкненим чи вимкненим:

```php
// Wait a maximum of five seconds until the selector is enabled...
$browser->waitUntilEnabled('.selector');

// Wait a maximum of one second until the selector is enabled...
$browser->waitUntilEnabled('.selector', 1);

// Wait a maximum of five seconds until the selector is disabled...
$browser->waitUntilDisabled('.selector');

// Wait a maximum of one second until the selector is disabled...
$browser->waitUntilDisabled('.selector', 1);
```

<a name="scoping-selectors-when-available"></a>
#### Обмеження селекторів за доступністю

Іноді вам може знадобитися дочекатися появи елемента, що відповідає заданому селектору, а потім із ним попрацювати. Наприклад, дочекатися появи модального вікна й натиснути в ньому кнопку «OK». Для цього скористайтеся методом `whenAvailable`. Усі операції з елементами всередині заданого замикання будуть обмежені початковим селектором:

```php
$browser->whenAvailable('.modal', function (Browser $modal) {
    $modal->assertSee('Hello World')
        ->press('OK');
});
```

<a name="waiting-for-text"></a>
#### Очікування тексту

Метод `waitForText` дозволяє дочекатися, доки на сторінці не з'явиться заданий текст:

```php
// Wait a maximum of five seconds for the text...
$browser->waitForText('Hello World');

// Wait a maximum of one second for the text...
$browser->waitForText('Hello World', 1);
```

Метод `waitUntilMissingText` дозволяє дочекатися, доки показаний текст не зникне зі сторінки:

```php
// Wait a maximum of five seconds for the text to be removed...
$browser->waitUntilMissingText('Hello World');

// Wait a maximum of one second for the text to be removed...
$browser->waitUntilMissingText('Hello World', 1);
```

<a name="waiting-for-links"></a>
#### Очікування посилань

Метод `waitForLink` дозволяє дочекатися, доки на сторінці не з'явиться заданий текст посилання:

```php
// Wait a maximum of five seconds for the link...
$browser->waitForLink('Create');

// Wait a maximum of one second for the link...
$browser->waitForLink('Create', 1);
```

<a name="waiting-for-inputs"></a>
#### Очікування полів введення

Метод `waitForInput` дозволяє дочекатися, доки задане поле введення стане видимим на сторінці:

```php
// Wait a maximum of five seconds for the input...
$browser->waitForInput($field);

// Wait a maximum of one second for the input...
$browser->waitForInput($field, 1);
```

<a name="waiting-on-the-page-location"></a>
#### Очікування розташування сторінки

Коли ви робите твердження щодо шляху на кшталт `$browser->assertPathIs('/home')`, воно може провалитися, якщо `window.location.pathname` оновлюється асинхронно. Скористайтеся методом `waitForLocation`, щоб дочекатися, доки розташування набуде заданого значення:

```php
$browser->waitForLocation('/secret');
```

Метод `waitForLocation` можна також використати, щоб дочекатися, доки поточне розташування вікна стане повним URL:

```php
$browser->waitForLocation('https://example.com/path');
```

Ви також можете дочекатися розташування [іменованого маршруту](/docs/{{version}}/routing#named-routes):

```php
$browser->waitForRoute($routeName, $parameters);
```

<a name="waiting-for-page-reloads"></a>
#### Очікування перезавантаження сторінки

Якщо після виконання дії вам треба дочекатися перезавантаження сторінки, скористайтеся методом `waitForReload`:

```php
use Laravel\Dusk\Browser;

$browser->waitForReload(function (Browser $browser) {
    $browser->press('Submit');
})
->assertSee('Success!');
```

Оскільки потреба дочекатися перезавантаження зазвичай виникає після натискання кнопки, для зручності ви можете скористатися методом `clickAndWaitForReload`:

```php
$browser->clickAndWaitForReload('.selector')
    ->assertSee('something');
```

<a name="waiting-on-javascript-expressions"></a>
#### Очікування виразів JavaScript

Іноді вам може знадобитися призупинити виконання тесту, доки заданий вираз JavaScript не набуде значення `true`. Легко зробити це можна методом `waitUntil`. Передаючи цьому методу вираз, вам не треба додавати ключове слово `return` чи крапку з комою в кінці:

```php
// Wait a maximum of five seconds for the expression to be true...
$browser->waitUntil('App.data.servers.length > 0');

// Wait a maximum of one second for the expression to be true...
$browser->waitUntil('App.data.servers.length > 0', 1);
```

<a name="waiting-on-vue-expressions"></a>
#### Очікування виразів Vue

Методи `waitUntilVue` та `waitUntilVueIsNot` дозволяють дочекатися, доки атрибут [компонента Vue](https://vuejs.org) набуде заданого значення:

```php
// Wait until the component attribute contains the given value...
$browser->waitUntilVue('user.name', 'Taylor', '@user');

// Wait until the component attribute doesn't contain the given value...
$browser->waitUntilVueIsNot('user.name', null, '@user');
```

<a name="waiting-for-javascript-events"></a>
#### Очікування подій JavaScript

Метод `waitForEvent` дозволяє призупинити виконання тесту, доки не станеться подія JavaScript:

```php
$browser->waitForEvent('load');
```

Слухач події прикріплюється до поточного обмеження, яким за замовчуванням є елемент `body`. Якщо ви користуєтеся обмеженим селектором, слухач буде прикріплено до відповідного елемента:

```php
$browser->with('iframe', function (Browser $iframe) {
    // Wait for the iframe's load event...
    $iframe->waitForEvent('load');
});
```

Ви також можете передати селектор другим аргументом до методу `waitForEvent`, щоб прикріпити слухач до конкретного елемента:

```php
$browser->waitForEvent('load', '.selector');
```

Ви також можете чекати на події об'єктів `document` та `window`:

```php
// Wait until the document is scrolled...
$browser->waitForEvent('scroll', 'document');

// Wait a maximum of five seconds until the window is resized...
$browser->waitForEvent('resize', 'window', 5);
```

<a name="waiting-with-a-callback"></a>
#### Очікування з колбеком

Багато методів очікування в Dusk спираються на метод `waitUsing`. Ви можете скористатися ним напряму, щоб дочекатися, доки задане замикання поверне `true`. Метод `waitUsing` приймає максимальну кількість секунд очікування, інтервал перевірки замикання, саме замикання та необов'язкове повідомлення про помилку:

```php
$browser->waitUsing(10, 1, function () use ($something) {
    return $something->isReady();
}, "Something wasn't ready in time.");
```

<a name="scrolling-an-element-into-view"></a>
### Прокручування до елемента

Іноді ви не зможете клацнути по елементу, бо він поза видимою областю браузера. Метод `scrollIntoView` прокрутить вікно браузера, доки елемент за заданим селектором не потрапить у поле зору:

```php
$browser->scrollIntoView('.selector')
    ->click('.selector');
```

<a name="available-assertions"></a>
## Доступні твердження

Dusk надає різні твердження, які ви можете робити щодо свого застосунку. Усі доступні твердження задокументовані в списку нижче:

<style>
    .collection-method-list > p {
        columns: 10.8em 3; -moz-columns: 10.8em 3; -webkit-columns: 10.8em 3;
    }

    .collection-method-list a {
        display: block;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
</style>

<div class="collection-method-list" markdown="1">

[assertTitle](#assert-title)
[assertTitleContains](#assert-title-contains)
[assertUrlIs](#assert-url-is)
[assertSchemeIs](#assert-scheme-is)
[assertSchemeIsNot](#assert-scheme-is-not)
[assertHostIs](#assert-host-is)
[assertHostIsNot](#assert-host-is-not)
[assertPortIs](#assert-port-is)
[assertPortIsNot](#assert-port-is-not)
[assertPathBeginsWith](#assert-path-begins-with)
[assertPathEndsWith](#assert-path-ends-with)
[assertPathContains](#assert-path-contains)
[assertPathIs](#assert-path-is)
[assertPathIsNot](#assert-path-is-not)
[assertRouteIs](#assert-route-is)
[assertQueryStringHas](#assert-query-string-has)
[assertQueryStringMissing](#assert-query-string-missing)
[assertFragmentIs](#assert-fragment-is)
[assertFragmentBeginsWith](#assert-fragment-begins-with)
[assertFragmentIsNot](#assert-fragment-is-not)
[assertHasCookie](#assert-has-cookie)
[assertHasPlainCookie](#assert-has-plain-cookie)
[assertCookieMissing](#assert-cookie-missing)
[assertPlainCookieMissing](#assert-plain-cookie-missing)
[assertCookieValue](#assert-cookie-value)
[assertPlainCookieValue](#assert-plain-cookie-value)
[assertSee](#assert-see)
[assertDontSee](#assert-dont-see)
[assertSeeIn](#assert-see-in)
[assertDontSeeIn](#assert-dont-see-in)
[assertSeeAnythingIn](#assert-see-anything-in)
[assertSeeNothingIn](#assert-see-nothing-in)
[assertCount](#assert-count)
[assertScript](#assert-script)
[assertSourceHas](#assert-source-has)
[assertSourceMissing](#assert-source-missing)
[assertSeeLink](#assert-see-link)
[assertDontSeeLink](#assert-dont-see-link)
[assertInputValue](#assert-input-value)
[assertInputValueIsNot](#assert-input-value-is-not)
[assertChecked](#assert-checked)
[assertNotChecked](#assert-not-checked)
[assertIndeterminate](#assert-indeterminate)
[assertRadioSelected](#assert-radio-selected)
[assertRadioNotSelected](#assert-radio-not-selected)
[assertSelected](#assert-selected)
[assertNotSelected](#assert-not-selected)
[assertSelectHasOptions](#assert-select-has-options)
[assertSelectMissingOptions](#assert-select-missing-options)
[assertSelectHasOption](#assert-select-has-option)
[assertSelectMissingOption](#assert-select-missing-option)
[assertValue](#assert-value)
[assertValueIsNot](#assert-value-is-not)
[assertAttribute](#assert-attribute)
[assertAttributeMissing](#assert-attribute-missing)
[assertAttributeContains](#assert-attribute-contains)
[assertAttributeDoesntContain](#assert-attribute-doesnt-contain)
[assertAriaAttribute](#assert-aria-attribute)
[assertDataAttribute](#assert-data-attribute)
[assertVisible](#assert-visible)
[assertPresent](#assert-present)
[assertNotPresent](#assert-not-present)
[assertMissing](#assert-missing)
[assertInputPresent](#assert-input-present)
[assertInputMissing](#assert-input-missing)
[assertDialogOpened](#assert-dialog-opened)
[assertEnabled](#assert-enabled)
[assertDisabled](#assert-disabled)
[assertButtonEnabled](#assert-button-enabled)
[assertButtonDisabled](#assert-button-disabled)
[assertFocused](#assert-focused)
[assertNotFocused](#assert-not-focused)
[assertAuthenticated](#assert-authenticated)
[assertGuest](#assert-guest)
[assertAuthenticatedAs](#assert-authenticated-as)
[assertVue](#assert-vue)
[assertVueIsNot](#assert-vue-is-not)
[assertVueContains](#assert-vue-contains)
[assertVueDoesntContain](#assert-vue-doesnt-contain)

</div>

<a name="assert-title"></a>
#### assertTitle

Перевіряє, що заголовок сторінки збігається із заданим текстом:

```php
$browser->assertTitle($title);
```

<a name="assert-title-contains"></a>
#### assertTitleContains

Перевіряє, що заголовок сторінки містить заданий текст:

```php
$browser->assertTitleContains($title);
```

<a name="assert-url-is"></a>
#### assertUrlIs

Перевіряє, що поточний URL (без рядка запиту) збігається із заданим рядком:

```php
$browser->assertUrlIs($url);
```

<a name="assert-scheme-is"></a>
#### assertSchemeIs

Перевіряє, що схема поточного URL збігається із заданою:

```php
$browser->assertSchemeIs($scheme);
```

<a name="assert-scheme-is-not"></a>
#### assertSchemeIsNot

Перевіряє, що схема поточного URL не збігається із заданою:

```php
$browser->assertSchemeIsNot($scheme);
```

<a name="assert-host-is"></a>
#### assertHostIs

Перевіряє, що хост поточного URL збігається із заданим:

```php
$browser->assertHostIs($host);
```

<a name="assert-host-is-not"></a>
#### assertHostIsNot

Перевіряє, що хост поточного URL не збігається із заданим:

```php
$browser->assertHostIsNot($host);
```

<a name="assert-port-is"></a>
#### assertPortIs

Перевіряє, що порт поточного URL збігається із заданим:

```php
$browser->assertPortIs($port);
```

<a name="assert-port-is-not"></a>
#### assertPortIsNot

Перевіряє, що порт поточного URL не збігається із заданим:

```php
$browser->assertPortIsNot($port);
```

<a name="assert-path-begins-with"></a>
#### assertPathBeginsWith

Перевіряє, що шлях поточного URL починається із заданого:

```php
$browser->assertPathBeginsWith('/home');
```

<a name="assert-path-ends-with"></a>
#### assertPathEndsWith

Перевіряє, що шлях поточного URL закінчується заданим:

```php
$browser->assertPathEndsWith('/home');
```

<a name="assert-path-contains"></a>
#### assertPathContains

Перевіряє, що шлях поточного URL містить заданий:

```php
$browser->assertPathContains('/home');
```

<a name="assert-path-is"></a>
#### assertPathIs

Перевіряє, що поточний шлях збігається із заданим:

```php
$browser->assertPathIs('/home');
```

<a name="assert-path-is-not"></a>
#### assertPathIsNot

Перевіряє, що поточний шлях не збігається із заданим:

```php
$browser->assertPathIsNot('/home');
```

<a name="assert-route-is"></a>
#### assertRouteIs

Перевіряє, що поточний URL збігається з URL заданого [іменованого маршруту](/docs/{{version}}/routing#named-routes):

```php
$browser->assertRouteIs($name, $parameters);
```

<a name="assert-query-string-has"></a>
#### assertQueryStringHas

Перевіряє, що заданий параметр рядка запиту присутній:

```php
$browser->assertQueryStringHas($name);
```

Перевіряє, що заданий параметр рядка запиту присутній і має задане значення:

```php
$browser->assertQueryStringHas($name, $value);
```

<a name="assert-query-string-missing"></a>
#### assertQueryStringMissing

Перевіряє, що заданого параметра рядка запиту немає:

```php
$browser->assertQueryStringMissing($name);
```

<a name="assert-fragment-is"></a>
#### assertFragmentIs

Перевіряє, що поточний хеш-фрагмент URL збігається із заданим:

```php
$browser->assertFragmentIs('anchor');
```

<a name="assert-fragment-begins-with"></a>
#### assertFragmentBeginsWith

Перевіряє, що поточний хеш-фрагмент URL починається із заданого:

```php
$browser->assertFragmentBeginsWith('anchor');
```

<a name="assert-fragment-is-not"></a>
#### assertFragmentIsNot

Перевіряє, що поточний хеш-фрагмент URL не збігається із заданим:

```php
$browser->assertFragmentIsNot('anchor');
```

<a name="assert-has-cookie"></a>
#### assertHasCookie

Перевіряє, що заданий зашифрований cookie присутній:

```php
$browser->assertHasCookie($name);
```

<a name="assert-has-plain-cookie"></a>
#### assertHasPlainCookie

Перевіряє, що заданий незашифрований cookie присутній:

```php
$browser->assertHasPlainCookie($name);
```

<a name="assert-cookie-missing"></a>
#### assertCookieMissing

Перевіряє, що заданого зашифрованого cookie немає:

```php
$browser->assertCookieMissing($name);
```

<a name="assert-plain-cookie-missing"></a>
#### assertPlainCookieMissing

Перевіряє, що заданого незашифрованого cookie немає:

```php
$browser->assertPlainCookieMissing($name);
```

<a name="assert-cookie-value"></a>
#### assertCookieValue

Перевіряє, що зашифрований cookie має задане значення:

```php
$browser->assertCookieValue($name, $value);
```

<a name="assert-plain-cookie-value"></a>
#### assertPlainCookieValue

Перевіряє, що незашифрований cookie має задане значення:

```php
$browser->assertPlainCookieValue($name, $value);
```

<a name="assert-see"></a>
#### assertSee

Перевіряє, що заданий текст присутній на сторінці:

```php
$browser->assertSee($text);
```

<a name="assert-dont-see"></a>
#### assertDontSee

Перевіряє, що заданого тексту немає на сторінці:

```php
$browser->assertDontSee($text);
```

<a name="assert-see-in"></a>
#### assertSeeIn

Перевіряє, що заданий текст присутній усередині селектора:

```php
$browser->assertSeeIn($selector, $text);
```

<a name="assert-dont-see-in"></a>
#### assertDontSeeIn

Перевіряє, що заданого тексту немає всередині селектора:

```php
$browser->assertDontSeeIn($selector, $text);
```

<a name="assert-see-anything-in"></a>
#### assertSeeAnythingIn

Перевіряє, що всередині селектора є будь-який текст:

```php
$browser->assertSeeAnythingIn($selector);
```

<a name="assert-see-nothing-in"></a>
#### assertSeeNothingIn

Перевіряє, що всередині селектора немає жодного тексту:

```php
$browser->assertSeeNothingIn($selector);
```

<a name="assert-count"></a>
#### assertCount

Перевіряє, що елементи, які відповідають заданому селектору, трапляються вказану кількість разів:

```php
$browser->assertCount($selector, $count);
```

<a name="assert-script"></a>
#### assertScript

Перевіряє, що заданий вираз JavaScript обчислюється в задане значення:

```php
$browser->assertScript('window.isLoaded')
    ->assertScript('document.readyState', 'complete');
```

<a name="assert-source-has"></a>
#### assertSourceHas

Перевіряє, що заданий код присутній на сторінці:

```php
$browser->assertSourceHas($code);
```

<a name="assert-source-missing"></a>
#### assertSourceMissing

Перевіряє, що заданого коду немає на сторінці:

```php
$browser->assertSourceMissing($code);
```

<a name="assert-see-link"></a>
#### assertSeeLink

Перевіряє, що задане посилання присутнє на сторінці:

```php
$browser->assertSeeLink($linkText);
```

<a name="assert-dont-see-link"></a>
#### assertDontSeeLink

Перевіряє, що заданого посилання немає на сторінці:

```php
$browser->assertDontSeeLink($linkText);
```

<a name="assert-input-value"></a>
#### assertInputValue

Перевіряє, що задане поле введення має задане значення:

```php
$browser->assertInputValue($field, $value);
```

<a name="assert-input-value-is-not"></a>
#### assertInputValueIsNot

Перевіряє, що задане поле введення не має заданого значення:

```php
$browser->assertInputValueIsNot($field, $value);
```

<a name="assert-checked"></a>
#### assertChecked

Перевіряє, що заданий чекбокс позначено:

```php
$browser->assertChecked($field);
```

<a name="assert-not-checked"></a>
#### assertNotChecked

Перевіряє, що заданий чекбокс не позначено:

```php
$browser->assertNotChecked($field);
```

<a name="assert-indeterminate"></a>
#### assertIndeterminate

Перевіряє, що заданий чекбокс перебуває в невизначеному стані:

```php
$browser->assertIndeterminate($field);
```

<a name="assert-radio-selected"></a>
#### assertRadioSelected

Перевіряє, що задану радіокнопку обрано:

```php
$browser->assertRadioSelected($field, $value);
```

<a name="assert-radio-not-selected"></a>
#### assertRadioNotSelected

Перевіряє, що задану радіокнопку не обрано:

```php
$browser->assertRadioNotSelected($field, $value);
```

<a name="assert-selected"></a>
#### assertSelected

Перевіряє, що в заданому випадному списку обрано задане значення:

```php
$browser->assertSelected($field, $value);
```

<a name="assert-not-selected"></a>
#### assertNotSelected

Перевіряє, що в заданому випадному списку не обрано заданого значення:

```php
$browser->assertNotSelected($field, $value);
```

<a name="assert-select-has-options"></a>
#### assertSelectHasOptions

Перевіряє, що заданий масив значень доступний для вибору:

```php
$browser->assertSelectHasOptions($field, $values);
```

<a name="assert-select-missing-options"></a>
#### assertSelectMissingOptions

Перевіряє, що заданий масив значень недоступний для вибору:

```php
$browser->assertSelectMissingOptions($field, $values);
```

<a name="assert-select-has-option"></a>
#### assertSelectHasOption

Перевіряє, що задане значення доступне для вибору в заданому полі:

```php
$browser->assertSelectHasOption($field, $value);
```

<a name="assert-select-missing-option"></a>
#### assertSelectMissingOption

Перевіряє, що задане значення недоступне для вибору:

```php
$browser->assertSelectMissingOption($field, $value);
```

<a name="assert-value"></a>
#### assertValue

Перевіряє, що елемент, який відповідає заданому селектору, має задане значення:

```php
$browser->assertValue($selector, $value);
```

<a name="assert-value-is-not"></a>
#### assertValueIsNot

Перевіряє, що елемент, який відповідає заданому селектору, не має заданого значення:

```php
$browser->assertValueIsNot($selector, $value);
```

<a name="assert-attribute"></a>
#### assertAttribute

Перевіряє, що елемент, який відповідає заданому селектору, має задане значення у вказаному атрибуті:

```php
$browser->assertAttribute($selector, $attribute, $value);
```

<a name="assert-attribute-missing"></a>
#### assertAttributeMissing

Перевіряє, що в елемента, який відповідає заданому селектору, немає вказаного атрибута:

```php
$browser->assertAttributeMissing($selector, $attribute);
```

<a name="assert-attribute-contains"></a>
#### assertAttributeContains

Перевіряє, що елемент, який відповідає заданому селектору, містить задане значення у вказаному атрибуті:

```php
$browser->assertAttributeContains($selector, $attribute, $value);
```

<a name="assert-attribute-doesnt-contain"></a>
#### assertAttributeDoesntContain

Перевіряє, що елемент, який відповідає заданому селектору, не містить заданого значення у вказаному атрибуті:

```php
$browser->assertAttributeDoesntContain($selector, $attribute, $value);
```

<a name="assert-aria-attribute"></a>
#### assertAriaAttribute

Перевіряє, що елемент, який відповідає заданому селектору, має задане значення у вказаному атрибуті aria:

```php
$browser->assertAriaAttribute($selector, $attribute, $value);
```

Наприклад, для розмітки `<button aria-label="Add"></button>` ви можете зробити твердження щодо атрибута `aria-label` ось так:

```php
$browser->assertAriaAttribute('button', 'label', 'Add');
```

<a name="assert-data-attribute"></a>
#### assertDataAttribute

Перевіряє, що елемент, який відповідає заданому селектору, має задане значення у вказаному атрибуті data:

```php
$browser->assertDataAttribute($selector, $attribute, $value);
```

Наприклад, для розмітки `<tr id="row-1" data-content="attendees"></tr>` ви можете зробити твердження щодо атрибута `data-content` ось так:

```php
$browser->assertDataAttribute('#row-1', 'content', 'attendees');
```

<a name="assert-visible"></a>
#### assertVisible

Перевіряє, що елемент, який відповідає заданому селектору, видимий:

```php
$browser->assertVisible($selector);
```

<a name="assert-present"></a>
#### assertPresent

Перевіряє, що елемент, який відповідає заданому селектору, присутній у коді сторінки:

```php
$browser->assertPresent($selector);
```

<a name="assert-not-present"></a>
#### assertNotPresent

Перевіряє, що елемента, який відповідає заданому селектору, немає в коді сторінки:

```php
$browser->assertNotPresent($selector);
```

<a name="assert-missing"></a>
#### assertMissing

Перевіряє, що елемент, який відповідає заданому селектору, не видимий:

```php
$browser->assertMissing($selector);
```

<a name="assert-input-present"></a>
#### assertInputPresent

Перевіряє, що поле введення із заданим іменем присутнє:

```php
$browser->assertInputPresent($name);
```

<a name="assert-input-missing"></a>
#### assertInputMissing

Перевіряє, що поля введення із заданим іменем немає в коді сторінки:

```php
$browser->assertInputMissing($name);
```

<a name="assert-dialog-opened"></a>
#### assertDialogOpened

Перевіряє, що діалог JavaScript із заданим повідомленням було відкрито:

```php
$browser->assertDialogOpened($message);
```

<a name="assert-enabled"></a>
#### assertEnabled

Перевіряє, що задане поле увімкнене:

```php
$browser->assertEnabled($field);
```

<a name="assert-disabled"></a>
#### assertDisabled

Перевіряє, що задане поле вимкнене:

```php
$browser->assertDisabled($field);
```

<a name="assert-button-enabled"></a>
#### assertButtonEnabled

Перевіряє, що задана кнопка увімкнена:

```php
$browser->assertButtonEnabled($button);
```

<a name="assert-button-disabled"></a>
#### assertButtonDisabled

Перевіряє, що задана кнопка вимкнена:

```php
$browser->assertButtonDisabled($button);
```

<a name="assert-focused"></a>
#### assertFocused

Перевіряє, що задане поле у фокусі:

```php
$browser->assertFocused($field);
```

<a name="assert-not-focused"></a>
#### assertNotFocused

Перевіряє, що задане поле не у фокусі:

```php
$browser->assertNotFocused($field);
```

<a name="assert-authenticated"></a>
#### assertAuthenticated

Перевіряє, що користувач автентифікований:

```php
$browser->assertAuthenticated();
```

<a name="assert-guest"></a>
#### assertGuest

Перевіряє, що користувач не автентифікований:

```php
$browser->assertGuest();
```

<a name="assert-authenticated-as"></a>
#### assertAuthenticatedAs

Перевіряє, що користувач автентифікований як заданий користувач:

```php
$browser->assertAuthenticatedAs($user);
```

<a name="assert-vue"></a>
#### assertVue

Dusk дозволяє навіть робити твердження щодо стану даних [компонента Vue](https://vuejs.org). Уявіть, наприклад, що ваш застосунок містить такий компонент Vue:

    // HTML...

    <profile dusk="profile-component"></profile>

    // Component Definition...

    Vue.component('profile', {
        template: '<div>{{ user.name }}</div>',

        data: function () {
            return {
                user: {
                    name: 'Taylor'
                }
            };
        }
    });

Ви можете зробити твердження щодо стану компонента Vue ось так:

```php tab=Pest
test('vue', function () {
    $this->browse(function (Browser $browser) {
        $browser->visit('/')
            ->assertVue('user.name', 'Taylor', '@profile-component');
    });
});
```

```php tab=PHPUnit
/**
 * A basic Vue test example.
 */
public function test_vue(): void
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/')
            ->assertVue('user.name', 'Taylor', '@profile-component');
    });
}
```

<a name="assert-vue-is-not"></a>
#### assertVueIsNot

Перевіряє, що задана властивість даних компонента Vue не збігається із заданим значенням:

```php
$browser->assertVueIsNot($property, $value, $componentSelector = null);
```

<a name="assert-vue-contains"></a>
#### assertVueContains

Перевіряє, що задана властивість даних компонента Vue є масивом і містить задане значення:

```php
$browser->assertVueContains($property, $value, $componentSelector = null);
```

<a name="assert-vue-doesnt-contain"></a>
#### assertVueDoesntContain

Перевіряє, що задана властивість даних компонента Vue є масивом і не містить заданого значення:

```php
$browser->assertVueDoesntContain($property, $value, $componentSelector = null);
```

<a name="pages"></a>
## Сторінки

Іноді тести вимагають виконати послідовно кілька складних дій. Через це тести стає важче читати й розуміти. Сторінки Dusk дозволяють описати виразні дії, які потім можна виконати на заданій сторінці одним методом. Сторінки також дозволяють описати скорочення для поширених селекторів вашого застосунку або окремої сторінки.

<a name="generating-pages"></a>
### Генерування сторінок

Щоб згенерувати об'єкт сторінки, виконайте артизан-команду `dusk:page`. Усі об'єкти сторінок потраплять до каталогу `tests/Browser/Pages` вашого застосунку:

```shell
php artisan dusk:page Login
```

<a name="configuring-pages"></a>
### Налаштування сторінок

За замовчуванням сторінки мають три методи: `url`, `assert` та `elements`. Методи `url` та `assert` ми розглянемо зараз. Метод `elements` буде [докладніше розглянуто нижче](#shorthand-selectors).

<a name="the-url-method"></a>
#### Метод `url`

Метод `url` має повертати шлях URL, що представляє сторінку. Dusk використовуватиме цей URL, переходячи до сторінки у браузері:

```php
/**
 * Get the URL for the page.
 */
public function url(): string
{
    return '/login';
}
```

<a name="the-assert-method"></a>
#### Метод `assert`

Метод `assert` може містити будь-які твердження, потрібні, щоб переконатися, що браузер справді на заданій сторінці. Розміщувати щось у цьому методі не обов'язково; проте ви вільні робити такі твердження за бажання. Ці твердження виконуватимуться автоматично при переході до сторінки:

```php
/**
 * Assert that the browser is on the page.
 */
public function assert(Browser $browser): void
{
    $browser->assertPathIs($this->url());
}
```

<a name="navigating-to-pages"></a>
### Перехід до сторінок

Коли сторінку визначено, ви можете перейти до неї методом `visit`:

```php
use Tests\Browser\Pages\Login;

$browser->visit(new Login);
```

Іноді ви вже перебуваєте на заданій сторінці, і вам треба «завантажити» її селектори й методи до поточного контексту тесту. Так буває, коли ви натискаєте кнопку й вас перенаправляє на сторінку без явного переходу до неї. У такому разі скористайтеся методом `on`, щоб завантажити сторінку:

```php
use Tests\Browser\Pages\CreatePlaylist;

$browser->visit('/dashboard')
    ->clickLink('Create Playlist')
    ->on(new CreatePlaylist)
    ->assertSee('@create');
```

<a name="shorthand-selectors"></a>
### Скорочені селектори

Метод `elements` у класах сторінок дозволяє описати швидкі й легкі для запам'ятовування скорочення для будь-якого CSS-селектора на вашій сторінці. Наприклад, визначмо скорочення для поля «email» на сторінці входу застосунку:

```php
/**
 * Get the element shortcuts for the page.
 *
 * @return array<string, string>
 */
public function elements(): array
{
    return [
        '@email' => 'input[name=email]',
    ];
}
```

Коли скорочення визначено, ви можете використовувати скорочений селектор усюди, де зазвичай використовували б повний CSS-селектор:

```php
$browser->type('@email', 'taylor@laravel.com');
```

<a name="global-shorthand-selectors"></a>
#### Глобальні скорочені селектори

Після встановлення Dusk базовий клас `Page` потрапить до вашого каталогу `tests/Browser/Pages`. Цей клас містить метод `siteElements`, який дозволяє описати глобальні скорочені селектори, доступні на кожній сторінці вашого застосунку:

```php
/**
 * Get the global element shortcuts for the site.
 *
 * @return array<string, string>
 */
public static function siteElements(): array
{
    return [
        '@element' => '#selector',
    ];
}
```

<a name="page-methods"></a>
### Методи сторінок

Окрім стандартних методів сторінок, ви можете визначити додаткові методи, якими користуватиметеся у своїх тестах. Уявімо, наприклад, що ми будуємо застосунок для керування музикою. Поширеною дією на одній зі сторінок може бути створення плейлиста. Замість того щоб переписувати логіку створення плейлиста в кожному тесті, ви можете визначити метод `createPlaylist` у класі сторінки:

```php
<?php

namespace Tests\Browser\Pages;

use Laravel\Dusk\Browser;
use Laravel\Dusk\Page;

class Dashboard extends Page
{
    // Other page methods...

    /**
     * Create a new playlist.
     */
    public function createPlaylist(Browser $browser, string $name): void
    {
        $browser->type('name', $name)
            ->check('share')
            ->press('Create Playlist');
    }
}
```

Коли метод визначено, ви можете скористатися ним у будь-якому тесті, що використовує цю сторінку. Екземпляр браузера автоматично передається першим аргументом до власних методів сторінки:

```php
use Tests\Browser\Pages\Dashboard;

$browser->visit(new Dashboard)
    ->createPlaylist('My Playlist')
    ->assertSee('My Playlist');
```

<a name="components"></a>
## Компоненти

Компоненти схожі на «об'єкти сторінок» Dusk, але призначені для частин UI та функціональності, які перевикористовуються в усьому застосунку, - наприклад, панель навігації чи вікно сповіщень. Тому компоненти не прив'язані до конкретних URL.

<a name="generating-components"></a>
### Генерування компонентів

Щоб згенерувати компонент, виконайте артизан-команду `dusk:component`. Нові компоненти потрапляють до каталогу `tests/Browser/Components`:

```shell
php artisan dusk:component DatePicker
```

Як показано вище, «вибір дати» - приклад компонента, який може траплятися в усьому застосунку на різних сторінках. Писати вручну логіку автоматизації браузера для вибору дати в десятках тестів стає обтяжливо. Натомість ми можемо описати компонент Dusk, який представлятиме вибір дати, і інкапсулювати цю логіку в ньому:

```php
<?php

namespace Tests\Browser\Components;

use Laravel\Dusk\Browser;
use Laravel\Dusk\Component as BaseComponent;

class DatePicker extends BaseComponent
{
    /**
     * Get the root selector for the component.
     */
    public function selector(): string
    {
        return '.date-picker';
    }

    /**
     * Assert that the browser page contains the component.
     */
    public function assert(Browser $browser): void
    {
        $browser->assertVisible($this->selector());
    }

    /**
     * Get the element shortcuts for the component.
     *
     * @return array<string, string>
     */
    public function elements(): array
    {
        return [
            '@date-field' => 'input.datepicker-input',
            '@year-list' => 'div > div.datepicker-years',
            '@month-list' => 'div > div.datepicker-months',
            '@day-list' => 'div > div.datepicker-days',
        ];
    }

    /**
     * Select the given date.
     */
    public function selectDate(Browser $browser, int $year, int $month, int $day): void
    {
        $browser->click('@date-field')
            ->within('@year-list', function (Browser $browser) use ($year) {
                $browser->click($year);
            })
            ->within('@month-list', function (Browser $browser) use ($month) {
                $browser->click($month);
            })
            ->within('@day-list', function (Browser $browser) use ($day) {
                $browser->click($day);
            });
    }
}
```

<a name="using-components"></a>
### Використання компонентів

Коли компонент визначено, ми можемо легко обрати дату у виборі дати з будь-якого тесту. І якщо логіка вибору дати зміниться, нам треба буде оновити лише компонент:

```php tab=Pest
<?php

use Illuminate\Foundation\Testing\DatabaseMigrations;
use Tests\Browser\Components\DatePicker;

pest()->use(DatabaseMigrations::class);

test('basic example', function () {
    $this->browse(function (Browser $browser) {
        $browser->visit('/')
            ->within(new DatePicker, function (Browser $browser) {
                $browser->selectDate(2019, 1, 30);
            })
            ->assertSee('January');
    });
});
```

```php tab=PHPUnit
<?php

namespace Tests\Browser;

use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\Browser\Components\DatePicker;
use Tests\DuskTestCase;

class ExampleTest extends DuskTestCase
{
    /**
     * A basic component test example.
     */
    public function test_basic_example(): void
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/')
                ->within(new DatePicker, function (Browser $browser) {
                    $browser->selectDate(2019, 1, 30);
                })
                ->assertSee('January');
        });
    }
}
```

Метод `component` дозволяє отримати екземпляр браузера, обмежений заданим компонентом:

```php
$datePicker = $browser->component(new DatePickerComponent);

$datePicker->selectDate(2019, 1, 30);

$datePicker->assertSee('January');
```

<a name="continuous-integration"></a>
## Неперервна інтеграція

> [!WARNING]
> Більшість конфігурацій неперервної інтеграції для Dusk очікують, що ваш застосунок Laravel віддається вбудованим сервером розробки PHP на порту 8000. Тому, перш ніж рухатися далі, переконайтеся, що у вашому середовищі неперервної інтеграції змінна оточення `APP_URL` має значення `http://127.0.0.1:8000`.

<a name="running-tests-on-heroku-ci"></a>
### Heroku CI

Щоб запускати тести Dusk на [Heroku CI](https://www.heroku.com/continuous-integration), додайте до свого файлу `app.json` для Heroku такий buildpack для Google Chrome і скрипти:

```json
{
  "environments": {
    "test": {
      "buildpacks": [
        { "url": "heroku/php" },
        { "url": "https://github.com/heroku/heroku-buildpack-chrome-for-testing" }
      ],
      "scripts": {
        "test-setup": "cp .env.testing .env",
        "test": "nohup bash -c './vendor/laravel/dusk/bin/chromedriver-linux --port=9515 > /dev/null 2>&1 &' && nohup bash -c 'php artisan serve --no-reload > /dev/null 2>&1 &' && php artisan dusk"
      }
    }
  }
}
```

<a name="running-tests-on-travis-ci"></a>
### Travis CI

Щоб запускати ваші тести Dusk на [Travis CI](https://travis-ci.org), скористайтеся такою конфігурацією `.travis.yml`. Оскільки Travis CI не є графічним середовищем, нам знадобиться кілька додаткових кроків, щоб запустити браузер Chrome. Крім того, ми скористаємося `php artisan serve`, щоб запустити вбудований вебсервер PHP:

```yaml
language: php

php:
  - 8.2

addons:
  chrome: stable

install:
  - cp .env.testing .env
  - travis_retry composer install --no-interaction --prefer-dist
  - php artisan key:generate
  - php artisan dusk:chrome-driver

before_script:
  - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost &
  - php artisan serve --no-reload &

script:
  - php artisan dusk
```

<a name="running-tests-on-github-actions"></a>
### GitHub Actions

Якщо ви запускаєте свої тести Dusk через [GitHub Actions](https://github.com/features/actions), можете взяти за відправну точку такий конфігураційний файл. Як і у випадку з TravisCI, ми скористаємося командою `php artisan serve`, щоб запустити вбудований вебсервер PHP:

```yaml
name: CI
on: [push]
jobs:

  dusk-php:
    runs-on: ubuntu-latest
    env:
      APP_URL: "http://127.0.0.1:8000"
      DB_USERNAME: root
      DB_PASSWORD: root
      MAIL_MAILER: log
    steps:
      - uses: actions/checkout@v5
      - name: Prepare The Environment
        run: cp .env.example .env
      - name: Create Database
        run: |
          sudo systemctl start mysql
          mysql --user="root" --password="root" -e "CREATE DATABASE \`my-database\` character set UTF8mb4 collate utf8mb4_bin;"
      - name: Install Composer Dependencies
        run: composer install --no-progress --prefer-dist --optimize-autoloader
      - name: Generate Application Key
        run: php artisan key:generate
      - name: Upgrade Chrome Driver
        run: php artisan dusk:chrome-driver --detect
      - name: Start Chrome Driver
        run: ./vendor/laravel/dusk/bin/chromedriver-linux --port=9515 &
      - name: Run Laravel Server
        run: php artisan serve --no-reload &
      - name: Run Dusk Tests
        run: php artisan dusk
      - name: Upload Screenshots
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: screenshots
          path: tests/Browser/screenshots
      - name: Upload Console Logs
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: console
          path: tests/Browser/console
```

<a name="running-tests-on-chipper-ci"></a>
### Chipper CI

Якщо ви запускаєте свої тести Dusk через [Chipper CI](https://chipperci.com), можете взяти за відправну точку такий конфігураційний файл. Ми скористаємося вбудованим сервером PHP, щоб запустити Laravel і слухати запити:

```yaml
# file .chipperci.yml
version: 1

environment:
  php: 8.2
  node: 16

# Include Chrome in the build environment
services:
  - dusk

# Build all commits
on:
   push:
      branches: .*

pipeline:
  - name: Setup
    cmd: |
      cp -v .env.example .env
      composer install --no-interaction --prefer-dist --optimize-autoloader
      php artisan key:generate

      # Create a dusk env file, ensuring APP_URL uses BUILD_HOST
      cp -v .env .env.dusk.ci
      sed -i "s@APP_URL=.*@APP_URL=http://$BUILD_HOST:8000@g" .env.dusk.ci

  - name: Compile Assets
    cmd: |
      npm ci --no-audit
      npm run build

  - name: Browser Tests
    cmd: |
      php -S [::0]:8000 -t public 2>server.log &
      sleep 2
      php artisan dusk:chrome-driver $CHROME_DRIVER
      php artisan dusk --env=ci
```

Щоб дізнатися більше про запуск тестів Dusk на Chipper CI, зокрема про роботу з базами даних, зверніться до [офіційної документації Chipper CI](https://chipperci.com/docs/testing/laravel-dusk-new/).

---

Джерело: https://laravelukraine.com/docs/13.x/envoy

# Laravel Envoy

- [Вступ](#introduction)
- [Встановлення](#installation)
- [Написання завдань](#writing-tasks)
    - [Визначення завдань](#defining-tasks)
    - [Кілька серверів](#multiple-servers)
    - [Підготовка](#setup)
    - [Змінні](#variables)
    - [Сценарії](#stories)
    - [Хуки](#completion-hooks)
- [Запуск завдань](#running-tasks)
    - [Підтвердження виконання завдання](#confirming-task-execution)
- [Сповіщення](#notifications)
    - [Slack](#slack)
    - [Discord](#discord)
    - [Telegram](#telegram)
    - [Microsoft Teams](#microsoft-teams)

<a name="introduction"></a>
## Вступ

[Laravel Envoy](https://github.com/laravel/envoy) - інструмент для виконання типових завдань на ваших віддалених серверах. Через синтаксис у стилі [Blade](/docs/{{version}}/blade) ви можете легко описати завдання для розгортання, артизан-команди тощо. Наразі Envoy підтримує лише macOS і Linux. Проте підтримки Windows можна досягти через [WSL2](https://docs.microsoft.com/en-us/windows/wsl/install-win10).

<a name="installation"></a>
## Встановлення

Спершу встановіть Envoy у свій проєкт через менеджер пакетів Composer:

```shell
composer require laravel/envoy --dev
```

Коли Envoy встановлено, його бінарник буде доступний у каталозі `vendor/bin` вашого застосунку:

```shell
php vendor/bin/envoy
```

<a name="writing-tasks"></a>
## Написання завдань

<a name="defining-tasks"></a>
### Визначення завдань

Завдання - базовий будівельний блок Envoy. Завдання описують команди оболонки, які мають виконатися на ваших віддалених серверах при виклику завдання. Наприклад, ви можете описати завдання, що виконує команду `php artisan queue:restart` на всіх серверах-воркерах черг вашого застосунку.

Усі ваші завдання Envoy слід описувати у файлі `Envoy.blade.php` у корені застосунку. Ось приклад для початку:

```blade
@servers(['web' => ['user@192.168.1.1'], 'workers' => ['user@192.168.1.2']])

@task('restart-queues', ['on' => 'workers'])
    cd /home/user/example.com
    php artisan queue:restart
@endtask
```

Як бачите, на початку файлу описано масив `@servers` - завдяки цьому ви можете посилатися на ці сервери через опцію `on` у оголошеннях завдань. Оголошення `@servers` завжди має бути в одному рядку. Усередині оголошень `@task` розміщуйте команди оболонки, які мають виконатися на ваших серверах при виклику завдання.

<a name="local-tasks"></a>
#### Локальні завдання

Ви можете змусити скрипт виконуватися на вашому комп'ютері, вказавши IP-адресу сервера `127.0.0.1`:

```blade
@servers(['localhost' => '127.0.0.1'])
```

<a name="importing-envoy-tasks"></a>
#### Імпорт завдань Envoy

За допомогою директиви `@import` ви можете імпортувати інші файли Envoy, щоб їхні сценарії та завдання додалися до ваших. Після імпорту ви можете виконувати їхні завдання так, ніби вони описані у вашому власному файлі Envoy:

```blade
@import('vendor/package/Envoy.blade.php')
```

<a name="multiple-servers"></a>
### Кілька серверів

Envoy дозволяє легко запустити завдання на кількох серверах. Спершу додайте додаткові сервери до оголошення `@servers`. Кожному серверу слід дати унікальне ім'я. Коли додаткові сервери описано, ви можете перелічити кожен із них у масиві `on` завдання:

```blade
@servers(['web-1' => '192.168.1.1', 'web-2' => '192.168.1.2'])

@task('deploy', ['on' => ['web-1', 'web-2']])
    cd /home/user/example.com
    git pull origin {{ $branch }}
    php artisan migrate --force
@endtask
```

<a name="parallel-execution"></a>
#### Паралельне виконання

За замовчуванням завдання виконуються на кожному сервері послідовно. Іншими словами, завдання завершиться на першому сервері, перш ніж почне виконуватися на другому. Якщо ви хочете запустити завдання на кількох серверах паралельно, додайте до його оголошення опцію `parallel`:

```blade
@servers(['web-1' => '192.168.1.1', 'web-2' => '192.168.1.2'])

@task('deploy', ['on' => ['web-1', 'web-2'], 'parallel' => true])
    cd /home/user/example.com
    git pull origin {{ $branch }}
    php artisan migrate --force
@endtask
```

<a name="setup"></a>
### Підготовка

Іноді вам може знадобитися виконати довільний PHP-код перед запуском завдань Envoy. Скористайтеся директивою `@setup`, щоб описати блок PHP-коду, який має виконатися перед вашими завданнями:

```blade
@setup
    $now = new DateTime;
@endsetup
```

Якщо перед виконанням завдання вам треба підключити інші файли PHP, скористайтеся директивою `@include` на початку вашого файлу `Envoy.blade.php`:

```blade
@include('vendor/autoload.php')

@task('restart-queues')
    # ...
@endtask
```

<a name="variables"></a>
### Змінні

За потреби ви можете передавати завданням Envoy аргументи, вказавши їх у командному рядку під час виклику:

```shell
php vendor/bin/envoy run deploy --branch=master
```

Звертатися до опцій у своїх завданнях можна через синтаксис «echo» у Blade. Ви також можете описувати всередині завдань конструкції `if` і цикли Blade. Наприклад, перевірмо наявність змінної `$branch` перед виконанням команди `git pull`:

```blade
@servers(['web' => ['user@192.168.1.1']])

@task('deploy', ['on' => 'web'])
    cd /home/user/example.com

    @if ($branch)
        git pull origin {{ $branch }}
    @endif

    php artisan migrate --force
@endtask
```

<a name="stories"></a>
### Сценарії

Сценарії групують набір завдань під одним зручним іменем. Наприклад, сценарій `deploy` може запускати завдання `update-code` та `install-dependencies`, перелічені в його визначенні:

```blade
@servers(['web' => ['user@192.168.1.1']])

@story('deploy')
    update-code
    install-dependencies
@endstory

@task('update-code')
    cd /home/user/example.com
    git pull origin master
@endtask

@task('install-dependencies')
    cd /home/user/example.com
    composer install
@endtask
```

Коли сценарій написано, ви можете викликати його так само, як і завдання:

```shell
php vendor/bin/envoy run deploy
```

<a name="completion-hooks"></a>
### Хуки

Під час виконання завдань і сценаріїв запускається низка хуків. Envoy підтримує такі типи хуків: `@before`, `@after`, `@error`, `@success` та `@finished`. Увесь код у цих хуках інтерпретується як PHP і виконується локально, а не на віддалених серверах, з якими працюють ваші завдання.

Ви можете описати скільки завгодно хуків кожного типу. Вони виконуватимуться в тому порядку, у якому йдуть у вашому скрипті Envoy.

<a name="hook-before"></a>
#### `@before`

Перед виконанням кожного завдання виконаються всі хуки `@before`, зареєстровані у вашому скрипті Envoy. Хуки `@before` отримують ім'я завдання, яке буде виконано:

```blade
@before
    if ($task === 'deploy') {
        // ...
    }
@endbefore
```

<a name="completion-after"></a>
#### `@after`

Після виконання кожного завдання виконаються всі хуки `@after`, зареєстровані у вашому скрипті Envoy. Хуки `@after` отримують ім'я виконаного завдання:

```blade
@after
    if ($task === 'deploy') {
        // ...
    }
@endafter
```

<a name="completion-error"></a>
#### `@error`

Після кожного провалу завдання (вихід із кодом статусу більшим за `0`) виконаються всі хуки `@error`, зареєстровані у вашому скрипті Envoy. Хуки `@error` отримують ім'я виконаного завдання:

```blade
@error
    if ($task === 'deploy') {
        // ...
    }
@enderror
```

<a name="completion-success"></a>
#### `@success`

Якщо всі завдання виконалися без помилок, виконаються всі хуки `@success`, зареєстровані у вашому скрипті Envoy:

```blade
@success
    // ...
@endsuccess
```

<a name="completion-finished"></a>
#### `@finished`

Після виконання всіх завдань (незалежно від статусу виходу) виконаються всі хуки `@finished`. Хуки `@finished` отримують код статусу завершеного завдання, який може бути `null` або цілим числом (`integer`), більшим за `0` чи рівним йому:

```blade
@finished
    if ($exitCode > 0) {
        // There were errors in one of the tasks...
    }
@endfinished
```

<a name="running-tasks"></a>
## Запуск завдань

Щоб запустити завдання чи сценарій, описаний у файлі `Envoy.blade.php` вашого застосунку, виконайте команду Envoy `run`, передавши ім'я потрібного завдання чи сценарію. Envoy виконає завдання й показуватиме вивід із ваших віддалених серверів у процесі:

```shell
php vendor/bin/envoy run deploy
```

<a name="confirming-task-execution"></a>
### Підтвердження виконання завдання

Якщо ви хочете отримувати запит на підтвердження перед запуском певного завдання на серверах, додайте до його оголошення директиву `confirm`. Ця опція особливо корисна для руйнівних операцій:

```blade
@task('deploy', ['on' => 'web', 'confirm' => true])
    cd /home/user/example.com
    git pull origin {{ $branch }}
    php artisan migrate
@endtask
```

<a name="notifications"></a>
## Сповіщення

<a name="slack"></a>
### Slack

Envoy підтримує надсилання сповіщень у [Slack](https://slack.com) після виконання кожного завдання. Директива `@slack` приймає URL хука Slack та ім'я каналу / користувача. Отримати URL вебхука можна, створивши інтеграцію «Incoming WebHooks» у панелі керування Slack.

Передайте повний URL вебхука першим аргументом директиви `@slack`. Другим аргументом має бути ім'я каналу (`#channel`) чи ім'я користувача (`@user`):

```blade
@finished
    @slack('webhook-url', '#bots')
@endfinished
```

За замовчуванням сповіщення Envoy надсилатимуть у канал повідомлення з описом виконаного завдання. Проте ви можете перезаписати це повідомлення власним, передавши директиві `@slack` третій аргумент:

```blade
@finished
    @slack('webhook-url', '#bots', 'Hello, Slack.')
@endfinished
```

<a name="discord"></a>
### Discord

Envoy також підтримує надсилання сповіщень у [Discord](https://discord.com) після виконання кожного завдання. Директива `@discord` приймає URL хука Discord і повідомлення. Отримати URL вебхука можна, створивши «Webhook» у налаштуваннях сервера й обравши канал, до якого він публікуватиме. Передайте повний URL вебхука до директиви `@discord`:

```blade
@finished
    @discord('discord-webhook-url')
@endfinished
```

<a name="telegram"></a>
### Telegram

Envoy також підтримує надсилання сповіщень у [Telegram](https://telegram.org) після виконання кожного завдання. Директива `@telegram` приймає ID бота Telegram і ID чату. Отримати ID бота можна, створивши нового бота через [BotFather](https://t.me/botfather). Отримати дійсний ID чату можна через [@username_to_id_bot](https://t.me/username_to_id_bot). Передайте повні ID бота та ID чату до директиви `@telegram`:

```blade
@finished
    @telegram('bot-id','chat-id')
@endfinished
```

<a name="microsoft-teams"></a>
### Microsoft Teams

Envoy також підтримує надсилання сповіщень у [Microsoft Teams](https://www.microsoft.com/en-us/microsoft-teams) після виконання кожного завдання. Директива `@microsoftTeams` приймає вебхук Teams (обов'язково), повідомлення, колір теми (success, info, warning, error) і масив опцій. Отримати вебхук Teams можна, створивши новий [вхідний вебхук](https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook). API Teams має багато інших атрибутів для налаштування вашого блоку повідомлення - як-от заголовок, підсумок і секції. Докладніше читайте в [документації Microsoft Teams](https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using?tabs=cURL#example-of-connector-message). Передайте повний URL вебхука до директиви `@microsoftTeams`:

```blade
@finished
    @microsoftTeams('webhook-url')
@endfinished
```

---

Джерело: https://laravelukraine.com/docs/13.x/fortify

# Laravel Fortify

- [Вступ](#introduction)
    - [Що таке Fortify?](#what-is-fortify)
    - [Коли варто використовувати Fortify?](#when-should-i-use-fortify)
- [Встановлення](#installation)
    - [Можливості Fortify](#fortify-features)
    - [Вимкнення представлень](#disabling-views)
- [Автентифікація](#authentication)
    - [Налаштування автентифікації користувачів](#customizing-user-authentication)
    - [Налаштування конвеєра автентифікації](#customizing-the-authentication-pipeline)
    - [Налаштування перенаправлень](#customizing-authentication-redirects)
- [Двофакторна автентифікація](#two-factor-authentication)
    - [Увімкнення двофакторної автентифікації](#enabling-two-factor-authentication)
    - [Автентифікація з двофакторною автентифікацією](#authenticating-with-two-factor-authentication)
    - [Вимкнення двофакторної автентифікації](#disabling-two-factor-authentication)
- [Passkeys](#passkeys)
    - [Увімкнення passkeys](#enabling-passkeys)
    - [JavaScript-клієнт](#passkeys-javascript-client)
    - [Автентифікація через passkeys](#authenticating-with-passkeys)
    - [Підтвердження пароля через passkeys](#confirming-password-with-passkeys)
    - [Реєстрація passkeys](#registering-passkeys)
    - [Видалення passkeys](#deleting-passkeys)
- [Реєстрація](#registration)
    - [Налаштування реєстрації](#customizing-registration)
- [Скидання пароля](#password-reset)
    - [Запит посилання для скидання пароля](#requesting-a-password-reset-link)
    - [Скидання пароля](#resetting-the-password)
    - [Налаштування скидання паролів](#customizing-password-resets)
- [Підтвердження електронної пошти](#email-verification)
    - [Захист маршрутів](#protecting-routes)
- [Підтвердження пароля](#password-confirmation)

<a name="introduction"></a>
## Вступ

[Laravel Fortify](https://github.com/laravel/fortify) - це незалежна від фронтенду реалізація бекенду автентифікації для Laravel. Fortify реєструє маршрути й контролери, потрібні для всіх можливостей автентифікації Laravel, - зокрема входу, реєстрації, скидання пароля, підтвердження пошти тощо. Після встановлення Fortify ви можете виконати артизан-команду `route:list`, щоб побачити зареєстровані ним маршрути.

Оскільки Fortify не має власного інтерфейсу користувача, він призначений для роботи з вашим власним інтерфейсом, який робить запити до зареєстрованих ним маршрутів. Далі в цій документації ми детально розберемо, як саме робити запити до цих маршрутів.

> [!NOTE]
> Пам'ятайте: Fortify - пакет, покликаний дати вам фору в реалізації можливостей автентифікації Laravel. **Користуватися ним не обов'язково.** Ви завжди вільні працювати із сервісами автентифікації Laravel вручну, дотримуючись документації з [автентифікації](/docs/{{version}}/authentication), [скидання пароля](/docs/{{version}}/passwords) та [підтвердження пошти](/docs/{{version}}/verification).

<a name="what-is-fortify"></a>
### Що таке Fortify?

Як згадувалося раніше, Laravel Fortify - це незалежна від фронтенду реалізація бекенду автентифікації для Laravel. Fortify реєструє маршрути й контролери, потрібні для всіх можливостей автентифікації Laravel, - зокрема входу, реєстрації, скидання пароля, підтвердження пошти тощо.

**Щоб користуватися можливостями автентифікації Laravel, Fortify не потрібен.** Ви завжди вільні працювати із сервісами автентифікації Laravel вручну, дотримуючись документації з [автентифікації](/docs/{{version}}/authentication), [скидання пароля](/docs/{{version}}/passwords) та [підтвердження пошти](/docs/{{version}}/verification).

Якщо ви новачок у Laravel, вам, можливо, варто дослідити [наші стартові набори застосунку](/docs/{{version}}/starter-kits). Стартові набори Laravel використовують Fortify всередині, щоб дати каркас автентифікації з інтерфейсом на [Tailwind CSS](https://tailwindcss.com). Це дозволяє вивчити можливості автентифікації Laravel і освоїтися з ними.

По суті, Laravel Fortify бере маршрути й контролери з наших стартових наборів і пропонує їх як пакет без інтерфейсу користувача. Тож ви можете швидко створити бекенд-реалізацію шару автентифікації свого застосунку, не прив'язуючись до жодних поглядів на фронтенд.

<a name="when-should-i-use-fortify"></a>
### Коли варто використовувати Fortify?

Ви можете замислитися, коли саме доречно брати Laravel Fortify. По-перше, якщо ви користуєтеся одним зі [стартових наборів застосунку](/docs/{{version}}/starter-kits) Laravel, встановлювати Laravel Fortify не потрібно: усі стартові набори Laravel використовують Fortify і вже дають повну реалізацію автентифікації.

Якщо ви не користуєтеся стартовим набором, а вашому застосунку потрібні можливості автентифікації, у вас є два варіанти: реалізувати їх вручну або скористатися Laravel Fortify як бекенд-реалізацією.

Якщо ви оберете Fortify, ваш інтерфейс робитиме запити до маршрутів автентифікації Fortify, описаних у цій документації, щоб автентифікувати й реєструвати користувачів.

Якщо ж ви вирішите працювати із сервісами автентифікації Laravel вручну, а не через Fortify, дотримуйтеся документації з [автентифікації](/docs/{{version}}/authentication), [скидання пароля](/docs/{{version}}/passwords) та [підтвердження пошти](/docs/{{version}}/verification).

<a name="laravel-fortify-and-laravel-sanctum"></a>
#### Laravel Fortify і Laravel Sanctum

Деякі розробники плутаються в різниці між [Laravel Sanctum](/docs/{{version}}/sanctum) і Laravel Fortify. Оскільки ці два пакети розв'язують різні, хоч і пов'язані задачі, Laravel Fortify та Laravel Sanctum не є взаємовиключними чи конкурентними.

Laravel Sanctum опікується лише керуванням API-токенами та автентифікацією наявних користувачів через сесійні cookie чи токени. Sanctum не надає маршрутів, що обробляють реєстрацію користувачів, скидання пароля тощо.

Якщо ви намагаєтеся вручну побудувати шар автентифікації для застосунку, який пропонує API чи слугує бекендом для односторінкового застосунку, цілком імовірно, що ви використаєте і Laravel Fortify (для реєстрації, скидання пароля тощо), і Laravel Sanctum (керування API-токенами, сесійна автентифікація).

<a name="installation"></a>
## Встановлення

Для початку встановіть Fortify через менеджер пакетів Composer:

```shell
composer require laravel/fortify
```

Далі опублікуйте ресурси Fortify артизан-командою `fortify:install`:

```shell
php artisan fortify:install
```

Ця команда опублікує дії Fortify до вашого каталогу `app/Actions`, який буде створено, якщо його немає. Крім того, буде опубліковано `FortifyServiceProvider`, конфігураційний файл і всі потрібні міграції бази даних.

Далі виконайте міграції:

```shell
php artisan migrate
```

<a name="fortify-features"></a>
### Можливості Fortify

Конфігураційний файл `fortify` містить масив конфігурації `features`. Цей масив визначає, які бекенд-маршрути й можливості Fortify відкриватиме за замовчуванням. Ми радимо вмикати лише такі можливості - базові для більшості застосунків Laravel:

```php
'features' => [
    Features::registration(),
    Features::resetPasswords(),
    Features::emailVerification(),
],
```

<a name="disabling-views"></a>
### Вимкнення представлень

За замовчуванням Fortify визначає маршрути, які повертають представлення, - як-от екран входу чи реєстрації. Проте якщо ви будуєте односторінковий застосунок на JavaScript, ці маршрути вам можуть бути не потрібні. Тому ви можете вимкнути їх цілком, встановивши значення конфігурації `views` у файлі `config/fortify.php` вашого застосунку в `false`:

```php
'views' => false,
```

<a name="disabling-views-and-password-reset"></a>
#### Вимкнення представлень і скидання пароля

Якщо ви вимкнете представлення Fortify, але реалізовуватимете скидання пароля у своєму застосунку, вам усе одно слід визначити маршрут з іменем `password.reset`, що відповідає за показ представлення «reset password». Це потрібно, бо сповіщення Laravel `Illuminate\Auth\Notifications\ResetPassword` генеруватиме URL скидання пароля саме через іменований маршрут `password.reset`.

<a name="authentication"></a>
## Автентифікація

Для початку нам треба вказати Fortify, як повертати наше представлення «login». Пам'ятайте: Fortify - headless-бібліотека автентифікації. Якщо вам потрібна готова фронтенд-реалізація можливостей автентифікації Laravel, скористайтеся [стартовим набором застосунку](/docs/{{version}}/starter-kits).

Усю логіку рендерингу представлень автентифікації можна налаштувати відповідними методами класу `Laravel\Fortify\Fortify`. Зазвичай цей метод викликають у методі `boot` класу `App\Providers\FortifyServiceProvider` вашого застосунку. Fortify подбає про визначення маршруту `/login`, який повертає це представлення:

```php
use Laravel\Fortify\Fortify;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Fortify::loginView(function () {
        return view('auth.login');
    });

    // ...
}
```

Ваш шаблон входу має містити форму, що робить POST-запит до `/login`. Ендпоїнт `/login` очікує рядок `email` / `username` і `password`. Ім'я поля пошти / імені користувача має збігатися зі значенням `username` у конфігураційному файлі `config/fortify.php`. Крім того, можна передати булеве поле `remember`, щоб вказати, що користувач хоче скористатися функціональністю «запам'ятати мене» від Laravel.

Якщо спроба входу успішна, Fortify перенаправить вас на URI, налаштований опцією `home` у конфігураційному файлі `fortify` вашого застосунку. Якщо запит на вхід був XHR-запитом, буде повернено HTTP-відповідь 200.

Якщо запит не був успішним, користувача буде перенаправлено назад на екран входу, а помилки валідації стануть доступні через спільну [змінну Blade-шаблону](/docs/{{version}}/validation#quick-displaying-the-validation-errors) `$errors`. Або ж, у випадку XHR-запиту, помилки валідації буде повернено з HTTP-відповіддю 422.

<a name="customizing-user-authentication"></a>
### Налаштування автентифікації користувачів

Fortify автоматично знаходить і автентифікує користувача за наданими обліковими даними й гардом автентифікації, налаштованим для вашого застосунку. Проте іноді вам може знадобитися повний контроль над тим, як перевіряються облікові дані й як дістаються користувачі. На щастя, Fortify дозволяє легко цього досягти методом `Fortify::authenticateUsing`.

Цей метод приймає замикання, яке отримує вхідний HTTP-запит. Замикання відповідає за валідацію облікових даних із запиту й повернення відповідного екземпляра користувача. Якщо облікові дані недійсні або користувача не знайдено, замикання має повернути `null` чи `false`. Зазвичай цей метод викликають у методі `boot` вашого `FortifyServiceProvider`:

```php
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Laravel\Fortify\Fortify;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Fortify::authenticateUsing(function (Request $request) {
        $user = User::where('email', $request->email)->first();

        if ($user &&
            Hash::check($request->password, $user->password)) {
            return $user;
        }
    });

    // ...
}
```

<a name="authentication-guard"></a>
#### Гард автентифікації

Ви можете налаштувати гард автентифікації, який використовує Fortify, у конфігураційному файлі `fortify` вашого застосунку. Проте переконайтеся, що налаштований гард реалізує `Illuminate\Contracts\Auth\StatefulGuard`. Якщо ви намагаєтеся автентифікувати SPA через Laravel Fortify, скористайтеся стандартним гардом Laravel `web` у поєднанні з [Laravel Sanctum](https://laravel.com/docs/sanctum).

<a name="customizing-the-authentication-pipeline"></a>
### Налаштування конвеєра автентифікації

Laravel Fortify автентифікує запити на вхід через конвеєр викликаних класів. За бажання ви можете описати власний конвеєр класів, через який проходитимуть запити на вхід. Кожен клас має містити метод `__invoke`, який отримує вхідний екземпляр `Illuminate\Http\Request` і, як у [`middleware`](/docs/{{version}}/middleware), змінну `$next`, що викликається, щоб передати запит наступному класу конвеєра.

Щоб описати власний конвеєр, скористайтеся методом `Fortify::authenticateThrough`. Цей метод приймає замикання, яке має повернути масив класів, через які слід пропустити запит на вхід. Зазвичай цей метод викликають у методі `boot` класу `App\Providers\FortifyServiceProvider` вашого застосунку.

Наведений нижче приклад містить стандартне визначення конвеєра, яке можна взяти за відправну точку для власних змін:

```php
use Laravel\Fortify\Actions\AttemptToAuthenticate;
use Laravel\Fortify\Actions\CanonicalizeUsername;
use Laravel\Fortify\Actions\EnsureLoginIsNotThrottled;
use Laravel\Fortify\Actions\PrepareAuthenticatedSession;
use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable;
use Laravel\Fortify\Features;
use Laravel\Fortify\Fortify;
use Illuminate\Http\Request;

Fortify::authenticateThrough(function (Request $request) {
    return array_filter([
            config('fortify.limiters.login') ? null : EnsureLoginIsNotThrottled::class,
            config('fortify.lowercase_usernames') ? CanonicalizeUsername::class : null,
            Features::enabled(Features::twoFactorAuthentication()) ? RedirectIfTwoFactorAuthenticatable::class : null,
            AttemptToAuthenticate::class,
            PrepareAuthenticatedSession::class,
    ]);
});
```

#### Обмеження спроб автентифікації

За замовчуванням Fortify обмежує спроби автентифікації через `middleware` `EnsureLoginIsNotThrottled`. Це `middleware` обмежує спроби за унікальною комбінацією імені користувача та IP-адреси.

Деяким застосункам може знадобитися інший підхід до обмеження спроб автентифікації - наприклад, лише за IP-адресою. Тому Fortify дозволяє задати власний [обмежувач частоти](/docs/{{version}}/routing#rate-limiting) через опцію конфігурації `fortify.limiters.login`. Звісно, ця опція розташована в конфігураційному файлі `config/fortify.php` вашого застосунку.

> [!NOTE]
> Поєднання обмеження спроб, [двофакторної автентифікації](/docs/{{version}}/fortify#two-factor-authentication) і зовнішнього брандмауера вебзастосунків (WAF) дасть найнадійніший захист вашим справжнім користувачам.

<a name="customizing-authentication-redirects"></a>
### Налаштування перенаправлень

Якщо спроба входу успішна, Fortify перенаправить вас на URI, налаштований опцією `home` у конфігураційному файлі `fortify` вашого застосунку. Якщо запит на вхід був XHR-запитом, буде повернено HTTP-відповідь 200. Після виходу користувача із застосунку його буде перенаправлено на URI `/`.

Якщо вам потрібне складніше налаштування цієї поведінки, ви можете прив'язати реалізації контрактів `LoginResponse` та `LogoutResponse` до [сервіс-контейнера](/docs/{{version}}/container) Laravel. Зазвичай це роблять у методі `register` класу `App\Providers\FortifyServiceProvider` вашого застосунку:

```php
use Laravel\Fortify\Contracts\LogoutResponse;

/**
 * Register any application services.
 */
public function register(): void
{
    $this->app->instance(LogoutResponse::class, new class implements LogoutResponse {
        public function toResponse($request)
        {
            return redirect('/');
        }
    });
}
```

<a name="two-factor-authentication"></a>
## Двофакторна автентифікація

Коли можливість двофакторної автентифікації Fortify увімкнено, користувач має ввести шестизначний числовий токен під час автентифікації. Цей токен генерується як одноразовий пароль на основі часу (TOTP), який можна отримати з будь-якого сумісного з TOTP мобільного застосунку автентифікації - як-от Google Authenticator.

Перш ніж почати, переконайтеся, що модель `App\Models\User` вашого застосунку використовує трейт `Laravel\Fortify\TwoFactorAuthenticatable`:

```php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Fortify\TwoFactorAuthenticatable;

class User extends Authenticatable
{
    use Notifiable, TwoFactorAuthenticatable;
}
```

Далі вам слід побудувати у своєму застосунку екран, де користувачі зможуть керувати налаштуваннями двофакторної автентифікації. Цей екран має дозволяти вмикати й вимикати двофакторну автентифікацію, а також перегенеровувати коди відновлення.

> За замовчуванням масив `features` конфігураційного файлу `fortify` вказує, що налаштування двофакторної автентифікації Fortify вимагають підтвердження пароля перед зміною. Тому, перш ніж рухатися далі, ваш застосунок має реалізувати можливість [підтвердження пароля](#password-confirmation) від Fortify.

<a name="enabling-two-factor-authentication"></a>
### Увімкнення двофакторної автентифікації

Щоб почати вмикати двофакторну автентифікацію, ваш застосунок має зробити POST-запит до ендпоїнта `/user/two-factor-authentication`, визначеного Fortify. Якщо запит успішний, користувача буде перенаправлено назад на попередній URL, а сесійна змінна `status` набуде значення `two-factor-authentication-enabled`. Ви можете відстежити цю сесійну змінну `status` у своїх шаблонах, щоб показати відповідне повідомлення про успіх. Якщо запит був XHR-запитом, буде повернено HTTP-відповідь `200`.

Обравши увімкнення двофакторної автентифікації, користувач має ще «підтвердити» її налаштування, ввівши дійсний код. Тож ваше повідомлення про успіх має пояснити користувачеві, що підтвердження двофакторної автентифікації ще потрібне:

```html
@if (session('status') == 'two-factor-authentication-enabled')
    <div class="mb-4 font-medium text-sm">
        Please finish configuring two-factor authentication below.
    </div>
@endif
```

Далі вам слід показати QR-код двофакторної автентифікації, щоб користувач відсканував його у своєму застосунку-автентифікаторі. Якщо ви рендерите фронтенд застосунку через Blade, отримати SVG із QR-кодом можна методом `twoFactorQrCodeSvg`, доступним на екземплярі користувача:

```php
$request->user()->twoFactorQrCodeSvg();
```

Якщо ви будуєте фронтенд на JavaScript, ви можете зробити XHR GET-запит до ендпоїнта `/user/two-factor-qr-code`, щоб отримати QR-код двофакторної автентифікації користувача. Цей ендпоїнт поверне JSON-об'єкт із ключем `svg`.

<a name="confirming-two-factor-authentication"></a>
#### Підтвердження двофакторної автентифікації

Окрім показу QR-коду двофакторної автентифікації, вам слід дати текстове поле, куди користувач введе дійсний код автентифікації, щоб «підтвердити» налаштування. Цей код має бути переданий застосунку Laravel POST-запитом до ендпоїнта `/user/confirmed-two-factor-authentication`, визначеного Fortify.

Якщо запит успішний, користувача буде перенаправлено назад на попередній URL, а сесійна змінна `status` набуде значення `two-factor-authentication-confirmed`:

```html
@if (session('status') == 'two-factor-authentication-confirmed')
    <div class="mb-4 font-medium text-sm">
        Two-factor authentication confirmed and enabled successfully.
    </div>
@endif
```

Якщо запит до ендпоїнта підтвердження двофакторної автентифікації був XHR-запитом, буде повернено HTTP-відповідь `200`.

<a name="displaying-the-recovery-codes"></a>
#### Показ кодів відновлення

Вам також слід показати коди відновлення двофакторної автентифікації користувача. Ці коди дозволяють автентифікуватися, якщо користувач втратить доступ до свого мобільного пристрою. Якщо ви рендерите фронтенд застосунку через Blade, до кодів відновлення можна дістатися через екземпляр автентифікованого користувача:

```php
(array) $request->user()->recoveryCodes();
```

Якщо ви будуєте фронтенд на JavaScript, ви можете зробити XHR GET-запит до ендпоїнта `/user/two-factor-recovery-codes`. Цей ендпоїнт поверне JSON-масив із кодами відновлення користувача.

Щоб перегенерувати коди відновлення користувача, ваш застосунок має зробити POST-запит до ендпоїнта `/user/two-factor-recovery-codes`.

<a name="authenticating-with-two-factor-authentication"></a>
### Автентифікація з двофакторною автентифікацією

Під час автентифікації Fortify автоматично перенаправить користувача на екран двофакторного виклику вашого застосунку. Проте якщо ваш застосунок робить XHR-запит на вхід, JSON-відповідь після успішної спроби автентифікації міститиме JSON-об'єкт із булевою властивістю `two_factor`. Перевіряйте це значення, щоб знати, чи слід перенаправляти на екран двофакторного виклику.

Щоб почати реалізацію двофакторної автентифікації, нам треба вказати Fortify, як повертати наше представлення двофакторного виклику. Усю логіку рендерингу представлень автентифікації Fortify можна налаштувати відповідними методами класу `Laravel\Fortify\Fortify`. Зазвичай цей метод викликають у методі `boot` класу `App\Providers\FortifyServiceProvider` вашого застосунку:

```php
use Laravel\Fortify\Fortify;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Fortify::twoFactorChallengeView(function () {
        return view('auth.two-factor-challenge');
    });

    // ...
}
```

Fortify подбає про визначення маршруту `/two-factor-challenge`, який повертає це представлення. Ваш шаблон `two-factor-challenge` має містити форму, що робить POST-запит до ендпоїнта `/two-factor-challenge`. Дія `/two-factor-challenge` очікує поле `code` з дійсним TOTP-токеном або поле `recovery_code` з одним із кодів відновлення користувача.

Якщо спроба входу успішна, Fortify перенаправить користувача на URI, налаштований опцією `home` у конфігураційному файлі `fortify` вашого застосунку. Якщо запит на вхід був XHR-запитом, буде повернено HTTP-відповідь 204.

Якщо запит не був успішним, користувача буде перенаправлено назад на екран двофакторного виклику, а помилки валідації стануть доступні через спільну [змінну Blade-шаблону](/docs/{{version}}/validation#quick-displaying-the-validation-errors) `$errors`. Або ж, у випадку XHR-запиту, помилки валідації буде повернено з HTTP-відповіддю 422.

<a name="disabling-two-factor-authentication"></a>
### Вимкнення двофакторної автентифікації

Щоб вимкнути двофакторну автентифікацію, ваш застосунок має зробити DELETE-запит до ендпоїнта `/user/two-factor-authentication`. Пам'ятайте: ендпоїнти двофакторної автентифікації Fortify вимагають [підтвердження пароля](#password-confirmation) перед викликом.

<a name="passkeys"></a>
## Passkeys

Fortify підтримує автентифікацію через passkeys на основі WebAuthn. Passkeys дозволяють користувачам автентифікуватися без паролів - через платформні автентифікатори на кшталт Face ID, Touch ID, Windows Hello чи апаратні ключі безпеки.

<a name="enabling-passkeys"></a>
### Увімкнення passkeys

Для початку переконайтеся, що можливість `passkeys` увімкнено в конфігураційному файлі `fortify` вашого застосунку:

```php
use Laravel\Fortify\Features;

'features' => [
    // ...
    Features::passkeys([
        'confirmPassword' => true,
    ]),
],
```

Опція `confirmPassword` визначає, чи вимагає Fortify [підтвердження пароля](#password-confirmation) перед реєстрацією чи видаленням passkeys.

Далі переконайтеся, що модель `App\Models\User` вашого застосунку реалізує `Laravel\Fortify\Contracts\PasskeyUser` і використовує трейт `Laravel\Fortify\PasskeyAuthenticatable`:

```php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Fortify\Contracts\PasskeyUser;
use Laravel\Fortify\PasskeyAuthenticatable;

class User extends Authenticatable implements PasskeyUser
{
    use Notifiable, PasskeyAuthenticatable;
}
```

Опції конфігурації passkeys у Fortify можна налаштувати через масив конфігурації `passkeys` у файлі `config/fortify.php` вашого застосунку:

```php
'passkeys' => [
    'relying_party_id' => parse_url(config('app.url'), PHP_URL_HOST),
    'allowed_origins' => [config('app.url')],
    'user_handle_secret' => config('app.key'),
    'timeout' => 60000,
],
```

> [!NOTE]
> Fortify обгортає Composer-пакет `laravel/passkeys` і налаштовує його за вас. Якщо ви користуєтеся можливістю passkeys у Fortify, налаштовуйте passkeys через файл `config/fortify.php` вашого застосунку. Публікувати конфігураційний файл `laravel/passkeys` не потрібно, і будь-які задані там значення буде перевизначено Fortify.

`relying_party_id` має збігатися з доменом вашого застосунку. Масив `allowed_origins` перелічує origin браузера, з яких можна завершити реєстрацію та автентифікацію через passkey. `user_handle_secret` використовується для виведення непрозорих ідентифікаторів користувачів, що гарантує впізнавання того самого користувача між реєстраціями passkeys. Опція `timeout` визначає, як довго можуть лишатися активними операції реєстрації та автентифікації passkey.

Fortify застосовує окремий обмежувач частоти passkeys до своїх маршрутів входу, підтвердження та реєстрації passkey. За потреби ви можете налаштувати його через опцію конфігурації `fortify.limiters.passkeys` і відповідне визначення `RateLimiter::for(...)`.

<a name="passkeys-javascript-client"></a>
### JavaScript-клієнт

Якщо ви будуєте власний фронтенд - зокрема застосунок на Blade зі скриптами на боці браузера, - ви можете скористатися офіційним пакетом [`@laravel/passkeys`](https://www.npmjs.com/package/@laravel/passkeys). Цей пакет обробляє церемонії WebAuthn у браузері й надсилає запити до ендпоїнтів passkey у Fortify.

Встановіть пакет через npm:

```shell
npm install @laravel/passkeys
```

Далі ви можете ініціювати реєстрацію та перевірку passkey зі свого фронтенду:

```js
import { Passkeys } from "@laravel/passkeys";

await Passkeys.register({ name: "MacBook Pro" });
await Passkeys.verify();
```

Якщо ваш застосунок використовує власні URI ендпоїнтів passkey, ви можете перевизначити маршрути для кожного виклику окремо:

```js
await Passkeys.verify({
    routes: {
        options: "/passkeys/confirm/options",
        submit: "/passkeys/confirm",
    },
});

await Passkeys.register({
    name: "MacBook Pro",
    routes: {
        options: "/user/passkeys/options",
        submit: "/user/passkeys",
    },
});
```

Пакет також надає хелпери для React, Vue і Svelte через `@laravel/passkeys/react`, `@laravel/passkeys/vue` та `@laravel/passkeys/svelte`.

<a name="authenticating-with-passkeys"></a>
### Автентифікація через passkeys

Щоб автентифікувати користувача через passkey, ваш застосунок має спершу зробити GET-запит до ендпоїнта `/passkeys/login/options`. Цей ендпоїнт повертає опції виклику WebAuthn, які ваш фронтенд має передати до `navigator.credentials.get(...)`.

Коли браузер поверне облікові дані, ваш застосунок має зробити POST-запит до `/passkeys/login` з даними цих облікових даних. Ви також можете додати булеве поле `remember`.

Якщо запит успішний, Fortify виконає вхід користувача в налаштований гард і поверне або:

<div class="content-list" markdown="1">

- Відповідь-перенаправлення до потрібного місця призначення для звичайних запитів.
- HTTP-відповідь `200` із JSON-даними, що містять ключ `redirect`, для XHR-запитів.

</div>

<a name="confirming-password-with-passkeys"></a>
### Підтвердження пароля через passkeys

Для автентифікованих сесій Fortify надає ендпоїнти підтвердження через passkey, які задовольняють вимогу Laravel щодо підтвердження пароля для поточної сесії.

Щоб підтвердити через passkey, ваш застосунок має спершу зробити GET-запит до `/passkeys/confirm/options`. Цей ендпоїнт повертає опції виклику WebAuthn, які ваш фронтенд має передати до `navigator.credentials.get(...)`.

Коли браузер поверне облікові дані, ваш застосунок має зробити POST-запит до `/passkeys/confirm` з даними цих облікових даних.

Якщо запит успішний, Fortify позначає поточну сесію як таку, де пароль підтверджено, і повертає або:

<div class="content-list" markdown="1">

- Відповідь-перенаправлення до потрібного місця призначення для звичайних запитів.
- HTTP-відповідь `200` із JSON-даними, що містять ключ `redirect`, для XHR-запитів.

</div>

<a name="registering-passkeys"></a>
### Реєстрація passkeys

Щоб зареєструвати passkey для автентифікованого користувача, ваш застосунок має спершу зробити GET-запит до `/user/passkeys/options`. Цей ендпоїнт повертає опції створення WebAuthn, які ваш фронтенд має передати до `navigator.credentials.create(...)`.

Коли браузер поверне облікові дані, ваш застосунок має зробити POST-запит до `/user/passkeys` з полем `name` і полем `credential`, що містить серіалізований об'єкт [`PublicKeyCredential`](https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential), повернений `navigator.credentials.create(...)`.

Якщо запит успішний, Fortify поверне або:

<div class="content-list" markdown="1">

- Відповідь-перенаправлення назад зі статусом `passkey-registered` у сесії для звичайних запитів.
- HTTP-відповідь `200` із JSON-даними, що містять ключ `status`, а також `id` та `name` щойно зареєстрованого passkey.

</div>

<a name="deleting-passkeys"></a>
### Видалення passkeys

Щоб видалити passkey, ваш застосунок має зробити DELETE-запит до `/user/passkeys/{passkey}`.

Якщо запит успішний, Fortify поверне або:

<div class="content-list" markdown="1">

- Відповідь-перенаправлення назад зі статусом `passkey-deleted` у сесії для звичайних запитів.
- HTTP-відповідь `200` із JSON-даними, що містять ключ `status`, для XHR-запитів.

</div>

<a name="registration"></a>
## Реєстрація

Щоб почати реалізацію реєстрації у нашому застосунку, нам треба вказати Fortify, як повертати наше представлення «register». Пам'ятайте: Fortify - headless-бібліотека автентифікації. Якщо вам потрібна готова фронтенд-реалізація можливостей автентифікації Laravel, скористайтеся [стартовим набором застосунку](/docs/{{version}}/starter-kits).

Усю логіку рендерингу представлень Fortify можна налаштувати відповідними методами класу `Laravel\Fortify\Fortify`. Зазвичай цей метод викликають у методі `boot` вашого класу `App\Providers\FortifyServiceProvider`:

```php
use Laravel\Fortify\Fortify;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Fortify::registerView(function () {
        return view('auth.register');
    });

    // ...
}
```

Fortify подбає про визначення маршруту `/register`, який повертає це представлення. Ваш шаблон `register` має містити форму, що робить POST-запит до ендпоїнта `/register`, визначеного Fortify.

Ендпоїнт `/register` очікує рядок `name`, рядкову адресу пошти / ім'я користувача, а також поля `password` і `password_confirmation`. Ім'я поля пошти / імені користувача має збігатися зі значенням конфігурації `username`, заданим у конфігураційному файлі `fortify` вашого застосунку.

Якщо спроба реєстрації успішна, Fortify перенаправить користувача на URI, налаштований опцією `home` у конфігураційному файлі `fortify` вашого застосунку. Якщо запит був XHR-запитом, буде повернено HTTP-відповідь 201.

Якщо запит не був успішним, користувача буде перенаправлено назад на екран реєстрації, а помилки валідації стануть доступні через спільну [змінну Blade-шаблону](/docs/{{version}}/validation#quick-displaying-the-validation-errors) `$errors`. Або ж, у випадку XHR-запиту, помилки валідації буде повернено з HTTP-відповіддю 422.

<a name="customizing-registration"></a>
### Налаштування реєстрації

Процес валідації та створення користувача можна налаштувати, змінивши дію `App\Actions\Fortify\CreateNewUser`, згенеровану під час встановлення Laravel Fortify.

<a name="password-reset"></a>
## Скидання пароля

<a name="requesting-a-password-reset-link"></a>
### Запит посилання для скидання пароля

Щоб почати реалізацію скидання пароля у нашому застосунку, нам треба вказати Fortify, як повертати наше представлення «forgot password». Пам'ятайте: Fortify - headless-бібліотека автентифікації. Якщо вам потрібна готова фронтенд-реалізація можливостей автентифікації Laravel, скористайтеся [стартовим набором застосунку](/docs/{{version}}/starter-kits).

Усю логіку рендерингу представлень Fortify можна налаштувати відповідними методами класу `Laravel\Fortify\Fortify`. Зазвичай цей метод викликають у методі `boot` класу `App\Providers\FortifyServiceProvider` вашого застосунку:

```php
use Laravel\Fortify\Fortify;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Fortify::requestPasswordResetLinkView(function () {
        return view('auth.forgot-password');
    });

    // ...
}
```

Fortify подбає про визначення ендпоїнта `/forgot-password`, який повертає це представлення. Ваш шаблон `forgot-password` має містити форму, що робить POST-запит до ендпоїнта `/forgot-password`.

Ендпоїнт `/forgot-password` очікує рядкове поле `email`. Ім'я цього поля / стовпця бази даних має збігатися зі значенням конфігурації `email` у конфігураційному файлі `fortify` вашого застосунку.

<a name="handling-the-password-reset-link-request-response"></a>
#### Обробка відповіді на запит посилання для скидання пароля

Якщо запит посилання для скидання пароля успішний, Fortify перенаправить користувача назад на ендпоїнт `/forgot-password` і надішле йому лист із безпечним посиланням для скидання пароля. Якщо запит був XHR-запитом, буде повернено HTTP-відповідь 200.

Після перенаправлення назад на ендпоїнт `/forgot-password` після успішного запиту сесійна змінна `status` дозволяє показати стан спроби запиту посилання.

Значення сесійної змінної `$status` збігатиметься з одним із рядків перекладу, визначених у [мовному файлі](/docs/{{version}}/localization) `passwords` вашого застосунку. Якщо ви хочете змінити це значення, а мовні файли Laravel ще не опубліковано, зробіть це артизан-командою `lang:publish`:

```html
@if (session('status'))
    <div class="mb-4 font-medium text-sm text-green-600">
        {{ session('status') }}
    </div>
@endif
```

Якщо запит не був успішним, користувача буде перенаправлено назад на екран запиту посилання для скидання пароля, а помилки валідації стануть доступні через спільну [змінну Blade-шаблону](/docs/{{version}}/validation#quick-displaying-the-validation-errors) `$errors`. Або ж, у випадку XHR-запиту, помилки валідації буде повернено з HTTP-відповіддю 422.

<a name="resetting-the-password"></a>
### Скидання пароля

Щоб завершити реалізацію скидання пароля у нашому застосунку, нам треба вказати Fortify, як повертати наше представлення «reset password».

Усю логіку рендерингу представлень Fortify можна налаштувати відповідними методами класу `Laravel\Fortify\Fortify`. Зазвичай цей метод викликають у методі `boot` класу `App\Providers\FortifyServiceProvider` вашого застосунку:

```php
use Laravel\Fortify\Fortify;
use Illuminate\Http\Request;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Fortify::resetPasswordView(function (Request $request) {
        return view('auth.reset-password', ['request' => $request]);
    });

    // ...
}
```

Fortify подбає про визначення маршруту, що показує це представлення. Ваш шаблон `reset-password` має містити форму, що робить POST-запит до `/reset-password`.

Ендпоїнт `/reset-password` очікує рядкове поле `email`, поле `password`, поле `password_confirmation` і приховане поле `token`, що містить значення `request()->route('token')`. Ім'я поля «email» / стовпця бази даних має збігатися зі значенням конфігурації `email`, заданим у конфігураційному файлі `fortify` вашого застосунку.

<a name="handling-the-password-reset-response"></a>
#### Обробка відповіді на скидання пароля

Якщо запит на скидання пароля успішний, Fortify перенаправить назад на маршрут `/login`, щоб користувач міг увійти з новим паролем. Крім того, буде встановлено сесійну змінну `status`, щоб ви могли показати успішний стан скидання на своєму екрані входу:

```blade
@if (session('status'))
    <div class="mb-4 font-medium text-sm text-green-600">
        {{ session('status') }}
    </div>
@endif
```

Якщо запит був XHR-запитом, буде повернено HTTP-відповідь 200.

Якщо запит не був успішним, користувача буде перенаправлено назад на екран скидання пароля, а помилки валідації стануть доступні через спільну [змінну Blade-шаблону](/docs/{{version}}/validation#quick-displaying-the-validation-errors) `$errors`. Або ж, у випадку XHR-запиту, помилки валідації буде повернено з HTTP-відповіддю 422.

<a name="customizing-password-resets"></a>
### Налаштування скидання паролів

Процес скидання пароля можна налаштувати, змінивши дію `App\Actions\ResetUserPassword`, згенеровану під час встановлення Laravel Fortify.

<a name="email-verification"></a>
## Підтвердження електронної пошти

Після реєстрації ви можете захотіти, щоб користувачі підтвердили свою адресу електронної пошти, перш ніж продовжити роботу із застосунком. Для початку переконайтеся, що можливість `emailVerification` увімкнено в масиві `features` вашого конфігураційного файлу `fortify`. Далі переконайтеся, що ваш клас `App\Models\User` реалізує інтерфейс `Illuminate\Contracts\Auth\MustVerifyEmail`.

Коли ці два кроки виконано, новозареєстровані користувачі отримуватимуть лист із проханням підтвердити свою адресу. Проте нам треба вказати Fortify, як показувати екран підтвердження пошти, який повідомляє користувачеві, що треба перейти за посиланням із листа.

Усю логіку рендерингу представлень Fortify можна налаштувати відповідними методами класу `Laravel\Fortify\Fortify`. Зазвичай цей метод викликають у методі `boot` класу `App\Providers\FortifyServiceProvider` вашого застосунку:

```php
use Laravel\Fortify\Fortify;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Fortify::verifyEmailView(function () {
        return view('auth.verify-email');
    });

    // ...
}
```

Fortify подбає про визначення маршруту, який показує це представлення, коли вбудоване `middleware` Laravel `verified` перенаправляє користувача на ендпоїнт `/email/verify`.

Ваш шаблон `verify-email` має містити інформаційне повідомлення з проханням натиснути посилання для підтвердження, надіслане на адресу користувача.

<a name="resending-email-verification-links"></a>
#### Повторне надсилання посилань для підтвердження пошти

За бажання ви можете додати до шаблону `verify-email` кнопку, що запускає POST-запит до ендпоїнта `/email/verification-notification`. Коли цей ендпоїнт отримує запит, користувачеві надсилається новий лист із посиланням для підтвердження - на випадок, якщо попереднє було випадково видалено чи загублено.

Якщо запит на повторне надсилання листа успішний, Fortify перенаправить користувача назад на ендпоїнт `/email/verify` із сесійною змінною `status`, що дозволить показати інформаційне повідомлення про успіх операції. Якщо запит був XHR-запитом, буде повернено HTTP-відповідь 202:

```blade
@if (session('status') == 'verification-link-sent')
    <div class="mb-4 font-medium text-sm text-green-600">
        A new email verification link has been emailed to you!
    </div>
@endif
```

<a name="protecting-routes"></a>
### Захист маршрутів

Щоб указати, що маршрут чи група маршрутів вимагає підтвердженої адреси електронної пошти, додайте до маршруту вбудоване `middleware` Laravel `verified`. Аліас `middleware` `verified` реєструється Laravel автоматично й слугує аліасом для `middleware` `Illuminate\Auth\Middleware\EnsureEmailIsVerified`:

```php
Route::get('/dashboard', function () {
    // ...
})->middleware(['verified']);
```

<a name="password-confirmation"></a>
## Підтвердження пароля

Будуючи застосунок, ви час від часу матимете дії, які вимагають від користувача підтвердити свій пароль, перш ніж дію буде виконано. Зазвичай такі маршрути захищають вбудованим `middleware` Laravel `password.confirm`.

Щоб почати реалізацію підтвердження пароля, нам треба вказати Fortify, як повертати представлення «password confirmation» нашого застосунку. Пам'ятайте: Fortify - headless-бібліотека автентифікації. Якщо вам потрібна готова фронтенд-реалізація можливостей автентифікації Laravel, скористайтеся [стартовим набором застосунку](/docs/{{version}}/starter-kits).

Усю логіку рендерингу представлень Fortify можна налаштувати відповідними методами класу `Laravel\Fortify\Fortify`. Зазвичай цей метод викликають у методі `boot` класу `App\Providers\FortifyServiceProvider` вашого застосунку:

```php
use Laravel\Fortify\Fortify;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Fortify::confirmPasswordView(function () {
        return view('auth.confirm-password');
    });

    // ...
}
```

Fortify подбає про визначення ендпоїнта `/user/confirm-password`, який повертає це представлення. Ваш шаблон `confirm-password` має містити форму, що робить POST-запит до ендпоїнта `/user/confirm-password`. Ендпоїнт `/user/confirm-password` очікує поле `password` із поточним паролем користувача.

Якщо пароль збігається з поточним паролем користувача, Fortify перенаправить його на маршрут, до якого він намагався дістатися. Якщо запит був XHR-запитом, буде повернено HTTP-відповідь 201.

Якщо запит не був успішним, користувача буде перенаправлено назад на екран підтвердження пароля, а помилки валідації стануть доступні через спільну змінну Blade-шаблону `$errors`. Або ж, у випадку XHR-запиту, помилки валідації буде повернено з HTTP-відповіддю 422.

---

Джерело: https://laravelukraine.com/docs/13.x/folio

# Laravel Folio

- [Вступ](#introduction)
- [Встановлення](#installation)
    - [Шляхи сторінок / URI](#page-paths-uris)
    - [Маршрутизація за піддоменом](#subdomain-routing)
- [Створення маршрутів](#creating-routes)
    - [Вкладені маршрути](#nested-routes)
    - [Індексні маршрути](#index-routes)
- [Параметри маршруту](#route-parameters)
- [Прив'язка моделей до маршруту](#route-model-binding)
    - [М'яко видалені моделі](#soft-deleted-models)
- [Хуки рендерингу](#render-hooks)
- [Іменовані маршрути](#named-routes)
- [Middleware](#middleware)
- [Кешування маршрутів](#route-caching)

<a name="introduction"></a>
## Вступ

[Laravel Folio](https://github.com/laravel/folio) - це потужний посторінковий маршрутизатор, створений, щоб спростити маршрутизацію в Laravel-застосунках. З Laravel Folio створити маршрут так само легко, як створити Blade-шаблон у каталозі `resources/views/pages` вашого застосунку.

Наприклад, щоб створити сторінку, доступну за URL `/greeting`, просто створіть файл `greeting.blade.php` у каталозі `resources/views/pages` вашого застосунку:

```blade
<div>
    Hello World
</div>
```

<a name="installation"></a>
## Встановлення

Для початку встановіть Folio у свій проєкт за допомогою менеджера пакетів Composer:

```shell
composer require laravel/folio
```

Після встановлення Folio ви можете виконати артизан-команду `folio:install`, яка встановить сервіс-провайдер Folio у ваш застосунок. Цей провайдер реєструє каталог, у якому Folio шукатиме маршрути / сторінки:

```shell
php artisan folio:install
```

<a name="page-paths-uris"></a>
### Шляхи сторінок / URI

За замовчуванням Folio віддає сторінки з каталогу `resources/views/pages` вашого застосунку, але ви можете змінити ці каталоги в методі `boot` вашого сервіс-провайдера Folio.

Наприклад, іноді буває зручно вказати кілька шляхів Folio у тому самому Laravel-застосунку. Можливо, ви захочете мати окремий каталог сторінок Folio для «адмінки» вашого застосунку, а для решти сторінок використовувати інший каталог.

Зробити це можна за допомогою методів `Folio::path` і `Folio::uri`. Метод `path` реєструє каталог, який Folio скануватиме в пошуках сторінок під час маршрутизації вхідних HTTP-запитів, а метод `uri` задає «базовий URI» для цього каталогу сторінок:

```php
use Laravel\Folio\Folio;

Folio::path(resource_path('views/pages/guest'))->uri('/');

Folio::path(resource_path('views/pages/admin'))
    ->uri('/admin')
    ->middleware([
        '*' => [
            'auth',
            'verified',

            // ...
        ],
    ]);
```

<a name="subdomain-routing"></a>
### Маршрутизація за піддоменом

Ви також можете маршрутизувати до сторінок на основі піддомену вхідного запиту. Наприклад, ви можете захотіти спрямувати запити з `admin.example.com` до іншого каталогу сторінок, ніж решта ваших сторінок Folio. Зробити це можна, викликавши метод `domain` після виклику методу `Folio::path`:

```php
use Laravel\Folio\Folio;

Folio::domain('admin.example.com')
    ->path(resource_path('views/pages/admin'));
```

Метод `domain` також дозволяє захоплювати частини домену чи піддомену як параметри. Ці параметри будуть передані до шаблону вашої сторінки:

```php
use Laravel\Folio\Folio;

Folio::domain('{account}.example.com')
    ->path(resource_path('views/pages/admin'));
```

<a name="creating-routes"></a>
## Створення маршрутів

Створити маршрут Folio можна, розмістивши Blade-шаблон у будь-якому з підключених до Folio каталогів. За замовчуванням Folio підключає каталог `resources/views/pages`, але ви можете змінити ці каталоги в методі `boot` вашого сервіс-провайдера Folio.

Щойно Blade-шаблон опиниться в підключеному до Folio каталозі, ви одразу зможете відкрити його у браузері. Наприклад, сторінка, розміщена в `pages/schedule.blade.php`, буде доступна у браузері за адресою `http://example.com/schedule`.

Щоб швидко переглянути список усіх ваших сторінок / маршрутів Folio, скористайтеся артизан-командою `folio:list`:

```shell
php artisan folio:list
```

<a name="nested-routes"></a>
### Вкладені маршрути

Вкладений маршрут створюється створенням одного чи кількох каталогів усередині одного з каталогів Folio. Наприклад, щоб створити сторінку, доступну за `/user/profile`, створіть шаблон `profile.blade.php` у каталозі `pages/user`:

```shell
php artisan folio:page user/profile

# pages/user/profile.blade.php → /user/profile
```

<a name="index-routes"></a>
### Індексні маршрути

Іноді вам може знадобитися зробити певну сторінку «індексом» каталогу. Якщо розмістити шаблон `index.blade.php` у каталозі Folio, будь-які запити до кореня цього каталогу будуть спрямовані на цю сторінку:

```shell
php artisan folio:page index
# pages/index.blade.php → /

php artisan folio:page users/index
# pages/users/index.blade.php → /users
```

<a name="route-parameters"></a>
## Параметри маршруту

Часто вам потрібно, щоб сегменти URL вхідного запиту передавалися до сторінки, аби ви могли з ними працювати. Наприклад, вам може знадобитися доступ до «ID» користувача, чий профіль показується. Для цього візьміть сегмент імені файлу сторінки у квадратні дужки:

```shell
php artisan folio:page "users/[id]"

# pages/users/[id].blade.php → /users/1
```

Захоплені сегменти доступні як змінні у вашому Blade-шаблоні:

```html
<div>
    User {{ $id }}
</div>
```

Щоб захопити кілька сегментів, додайте перед узятим у дужки сегментом три крапки `...`:

```shell
php artisan folio:page "users/[...ids]"

# pages/users/[...ids].blade.php → /users/1/2/3
```

Коли захоплюється кілька сегментів, вони передаються до сторінки як масив:

```html
<ul>
    @foreach ($ids as $id)
        <li>User {{ $id }}</li>
    @endforeach
</ul>
```

<a name="route-model-binding"></a>
## Прив'язка моделей до маршруту

Якщо сегмент-підстановка в імені файлу шаблону сторінки відповідає одній з Eloquent-моделей вашого застосунку, Folio автоматично скористається можливостями прив'язки моделей до маршруту в Laravel і спробує передати до сторінки знайдений екземпляр моделі:

```shell
php artisan folio:page "users/[User]"

# pages/users/[User].blade.php → /users/1
```

Захоплені моделі доступні як змінні у вашому Blade-шаблоні. Ім'я змінної моделі буде переведене в «camel case»:

```html
<div>
    User {{ $user->id }}
</div>
```

#### Зміна ключа

Іноді вам може знадобитися знаходити прив'язані Eloquent-моделі за колонкою, відмінною від `id`. Для цього вкажіть колонку в імені файлу сторінки. Наприклад, сторінка з іменем файлу `[Post:slug].blade.php` шукатиме прив'язану модель за колонкою `slug`, а не `id`.

У Windows для відокремлення імені моделі від ключа слід використовувати `-`: `[Post-slug].blade.php`.

#### Розташування моделі

За замовчуванням Folio шукатиме вашу модель у каталозі `app/Models` вашого застосунку. Однак за потреби ви можете вказати повністю кваліфіковане ім'я класу моделі в імені файлу шаблону:

```shell
php artisan folio:page "users/[.App.Models.User]"

# pages/users/[.App.Models.User].blade.php → /users/1
```

<a name="soft-deleted-models"></a>
### М'яко видалені моделі

За замовчуванням м'яко видалені моделі не знаходяться під час неявної прив'язки моделей. Однак, якщо хочете, ви можете вказати Folio знаходити м'яко видалені моделі, викликавши функцію `withTrashed` у шаблоні сторінки:

```php
<?php

use function Laravel\Folio\{withTrashed};

withTrashed();

?>

<div>
    User {{ $user->id }}
</div>
```

<a name="render-hooks"></a>
## Хуки рендерингу

За замовчуванням Folio повертає вміст Blade-шаблону сторінки як відповідь на вхідний запит. Однак ви можете змінити відповідь, викликавши функцію `render` у шаблоні сторінки.

Функція `render` приймає замикання, яке отримає екземпляр `View`, що його рендерить Folio, - це дозволяє додати до представлення додаткові дані або повністю змінити відповідь. Окрім екземпляра `View`, до замикання `render` також передаються будь-які додаткові параметри маршруту чи прив'язані моделі:

```php
<?php

use App\Models\Post;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;

use function Laravel\Folio\render;

render(function (View $view, Post $post) {
    if (! Auth::user()->can('view', $post)) {
        return response('Unauthorized', 403);
    }

    return $view->with('photos', $post->author->photos);
}); ?>

<div>
    {{ $post->content }}
</div>

<div>
    This author has also taken {{ count($photos) }} photos.
</div>
```

<a name="named-routes"></a>
## Іменовані маршрути

Задати ім'я маршруту певної сторінки можна за допомогою функції `name`:

```php
<?php

use function Laravel\Folio\name;

name('users.index');
```

Так само як і з іменованими маршрутами Laravel, ви можете скористатися функцією `route`, щоб згенерувати URL до сторінок Folio, яким призначено ім'я:

```blade
<a href="{{ route('users.index') }}">
    All Users
</a>
```

Якщо сторінка має параметри, просто передайте їхні значення до функції `route`:

```php
route('users.show', ['user' => $user]);
```

<a name="middleware"></a>
## Middleware

Застосувати `middleware` до конкретної сторінки можна, викликавши функцію `middleware` у шаблоні сторінки:

```php
<?php

use function Laravel\Folio\{middleware};

middleware(['auth', 'verified']);

?>

<div>
    Dashboard
</div>
```

Або, щоб призначити `middleware` групі сторінок, додайте метод `middleware` ланцюжком після виклику методу `Folio::path`.

Щоб указати, до яких саме сторінок слід застосувати `middleware`, ключами масиву `middleware` можуть бути відповідні URL-шаблони сторінок, до яких їх треба застосувати. Символ `*` можна використовувати як символ-підстановку:

```php
use Laravel\Folio\Folio;

Folio::path(resource_path('views/pages'))->middleware([
    'admin/*' => [
        'auth',
        'verified',

        // ...
    ],
]);
```

До масиву `middleware` можна додавати замикання, щоб визначити вбудовані анонімні `middleware`:

```php
use Closure;
use Illuminate\Http\Request;
use Laravel\Folio\Folio;

Folio::path(resource_path('views/pages'))->middleware([
    'admin/*' => [
        'auth',
        'verified',

        function (Request $request, Closure $next) {
            // ...

            return $next($request);
        },
    ],
]);
```

<a name="route-caching"></a>
## Кешування маршрутів

Використовуючи Folio, вам завжди варто користуватися [можливостями кешування маршрутів Laravel](/docs/{{version}}/routing#route-caching). Folio слухає артизан-команду `route:cache`, щоб гарантувати, що визначення сторінок і імена маршрутів Folio будуть належно закешовані для максимальної продуктивності.

---

Джерело: https://laravelukraine.com/docs/13.x/head

# Laravel Head

- [Вступ](#introduction)
- [Встановлення](#installation)
- [Швидкий старт](#quickstart)
- [Пріоритет розв'язання](#resolution-precedence)
- [Визначення метаданих](#defining-metadata)
    - [Значення за замовчуванням](#defaults)
    - [Метадані маршруту](#route-metadata)
    - [Метадані під час виконання](#runtime-metadata)
    - [Сторінки помилок](#error-pages)
- [Open Graph](#open-graph)
    - [Картки X / Twitter](#twitter-cards)
- [Кольори теми](#theme-colors)
- [Метадані застосунку та іконки](#app-metadata-and-icons)
- [Прогресивні вебзастосунки](#progressive-web-apps)
- [Продуктивність і виявлення](#performance-and-discovery)
- [Власні теги](#custom-tags)
- [Схеми](#schemas)
    - [Хлібні крихти](#breadcrumbs)
    - [Часті питання](#faqs)
    - [Власні схеми](#custom-schemas)
- [Рендеринг](#rendering)
    - [Blade](#blade)
    - [Livewire](#livewire)
    - [Inertia](#inertia)

<a name="introduction"></a>
## Вступ

[Laravel Head](https://github.com/laravel/head) надає плавний API для керування елементом `<head>` документа вашого застосунку: заголовком і мета-тегами, метаданими Open Graph, канонічними URL, директивами для роботів, підказками продуктивності та структурованими даними. Він працює з Blade, Livewire та Inertia.

<a name="installation"></a>
## Встановлення

Laravel Head встановлюється через менеджер пакетів Composer:

```shell
composer require laravel/head
```

<a name="quickstart"></a>
## Швидкий старт

Зареєструйте загальносайтові значення за замовчуванням у сервіс-провайдері:

```php
use Laravel\Head\Facades\Head;
use Laravel\Head\HeadBuilder;

Head::defaults(fn (HeadBuilder $head) => $head
    ->title('Laravel', suffix: ' - Laravel')
    ->description('Build something great.'));
```

Задайте метадані конкретної сторінки під час виконання:

```php
Head::title($post->title)
    ->description($post->description);
```

Відрендеріть розв'язані теги у своєму макеті:

```blade
<head>
    @head
</head>
```

<a name="resolution-precedence"></a>
## Пріоритет розв'язання

Метадані сторінки розв'язуються з п'яти шарів, наведених від найнижчого пріоритету до найвищого:

1. Значення сторінки за замовчуванням
2. Метадані групи маршрутів
3. Метадані маршруту
4. Метадані під час виконання
5. Метадані помилки

Вищі шари заміщують нижчі поле за полем. Наприклад, заголовок, заданий під час виконання, заміщує заголовок маршруту, не чіпаючи його опис. Розділи нижче описують, як задати метадані на кожному шарі. Про рендеринг розв'язаних метаданих у Blade, Livewire та Inertia читайте в розділі [Рендеринг](#rendering).

<a name="defining-metadata"></a>
## Визначення метаданих

Laravel Head дозволяє визначати метадані через загальносайтові значення за замовчуванням, метадані маршруту, виклики під час виконання та визначення для сторінок помилок.

<a name="defaults"></a>
### Значення за замовчуванням

Зареєструйте значення сторінки за замовчуванням у сервіс-провайдері:

```php
use Laravel\Head\Enums\OgType;
use Laravel\Head\Facades\Head;
use Laravel\Head\HeadBuilder;

Head::defaults(function (HeadBuilder $head) {
    $head
        ->title('Laravel', suffix: ' - Laravel')
        ->description('Build something great.')
        ->canonical()
        ->og(siteName: 'Laravel', type: OgType::Website)
        ->searchableByRobots()
        ->preconnect('https://fonts.example.com');
});
```

Значення за замовчуванням - найнижчий шар метаданих сторінки. Якщо жоден маршрут, виклик під час виконання чи метадані помилки не задають заголовка, `Laravel` рендериться як є. Коли заголовок сторінки задає вищий шар, успадкований суфікс застосовується, тож `Head::title('About')` відрендерить `About - Laravel`. Передайте `exact: true` для заголовків, які мають ігнорувати успадкований префікс або суфікс.

Виклик `Head::canonical()` рендерить канонічний URL на основі URL поточного запиту. Щоб задати URL явно, передайте рядок, наприклад `Head::canonical('/about')`. Канонічні URL за замовчуванням нормалізуються до `https`; передайте `forceHttps: false`, щоб зберегти схему запиту.

Директиви для роботів можна передати сирим рядком, як case-и енума `RobotsRule` або списком, що змішує обидві форми. Списки рендеряться як директиви через кому, тож `Head::robots([RobotsRule::NoIndex, RobotsRule::NoFollow])` дає `noindex, nofollow`.

Для зручності метод `searchableByRobots` рендерить `all`, а метод `hiddenFromRobots` - `none`.

<a name="route-metadata"></a>
### Метадані маршруту

Ви можете визначати метадані безпосередньо на маршрутах - це особливо зручно для напівстатичних сторінок, метадані яких відомі заздалегідь.

<a name="routes-and-groups"></a>
#### Маршрути та групи

```php
Route::view('/contact', 'contact')
    ->name('contact')
    ->withHead(
        title: 'Contact Us',
        description: 'Get in touch.',
    );
```

Спільні метадані маршрутів можна застосувати до групи в будь-якій позиції ланцюжка:

```php
Route::withHead(robots: 'noindex, nofollow')
    ->prefix('admin')
    ->name('admin.')
    ->group(function () {
        Route::get('/dashboard', DashboardController::class)
            ->name('dashboard')
            ->withHead(title: 'Dashboard');
    });
```

Метадані можна визначати також для ресурсних і одиничних маршрутів:

```php
Route::resource('posts', PostController::class)->withHead(
    robots: 'index, follow',
);

Route::singleton('profile', ProfileController::class)->withHead(
    title: 'Your Profile',
);
```

Метод `withHead` зберігає звичайні масиви через нативний API метаданих маршрутів Laravel. Він рівнозначний виклику методу `metadata` з атрибутами, вкладеними під ключ `head`, тож метадані лишаються сумісними з кешованими маршрутами.

Іменовані аргументи навмисно обмежені вбудованими властивостями маршрутів Laravel Head, щоб редактори та статичний аналіз ловили помилки в назвах. Атрибути маршрутів, зареєстровані власними білдерами тегів, передаються через `extensions`:

```php
Route::get('/article', ArticleController::class)->withHead(
    title: 'Article',
    extensions: ['readingTime' => 4],
);
```

<a name="supported-properties"></a>
#### Підтримувані властивості

Підтримувані властивості маршруту мають ті самі назви, що й методи плавного білдера:

| Категорія | Властивості |
| --- | --- |
| Документ | `title`, `description`, `canonical`, `robots` |
| Метадані застосунку | `themeColor`, `applicationName`, `colorScheme`, `referrer`, `viewport`, `appleWebAppTitle`, `webAppCapable`, `appleWebAppStatusBarStyle` |
| Соціальні мережі | `og`, `ogImage`, `ogVideo`, `ogAudio`, `twitter`, `twitterImage` |
| Продуктивність | `preload`, `prefetch`, `preconnect`, `dnsPrefetch` |
| Виявлення | `alternates`, `feed`, `icon`, `favicon`, `appleTouchIcon`, `appleTouchStartupImage`, `maskIcon`, `manifest` |
| Структуровані дані | `schema` |
| Власні теги | `meta`, `link` |

Назви вкладених опцій використовують той самий `camelCase`, що й плавний API, наприклад `forceHttps`, `siteName` і `secureUrl`.

Повторювані властивості - `ogImage`, `preload`, `feed`, `schema`, `icon` та `appleTouchStartupImage` - приймають або одне значення, або список.

<a name="runtime-metadata"></a>
### Метадані під час виконання

Коли значення невідоме до надходження запиту - наприклад, заголовок допису, який переглядають, - його можна задати під час виконання:

```php
use Laravel\Head\Facades\Head;

public function __invoke(Post $post): Response
{
    Head::title($post->title);

    // ...
}
```

Виклики під час виконання через фасад `Head` перекривають метадані маршруту для даних, що залежать від запиту. Найчастіше такі виклики роблять у контролерах і діях:

```php
use App\Models\Post;
use Laravel\Head\Facades\Head;

public function show(Post $post)
{
    Head::title($post->title)
        ->description($post->description);

    return view('posts.show', ['post' => $post]);
}
```

Кілька викликів під час виконання зливаються в порядку їх виконання. Для полів з одним значенням - заголовка, опису, канонічного URL і директив для роботів - перемагає пізніший виклик. Повторювані поля зберігають кілька записів, але повторне додавання того самого ключа оновлює попередній запис. Для методу `ogImage` ключем є URL:

```php
Head::ogImage('/images/cover.jpg', alt: 'Draft cover')
    ->ogImage('/images/gallery.jpg', alt: 'Gallery image')
    ->ogImage('/images/cover.jpg', alt: 'Final cover', width: 1200, height: 630);
```

```html
<meta property="og:image" content="/images/cover.jpg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Final cover">
<meta property="og:image" content="/images/gallery.jpg">
<meta property="og:image:alt" content="Gallery image">
```

Медіа Open Graph, успадковані зі значень за замовчуванням, працюють як запасний варіант. Коли метадані маршруту, виконання чи помилки визначають власне медіа того самого типу, медіа за замовчуванням заміщується, а не зливається, тож `og:image` сторінки має перевагу над загальносайтовим зображенням.

Умовні метадані визначаються плавно через методи `when` та `unless`:

```php
Head::title($post->title)
    ->when($post->isDraft(), fn ($head) => $head->hiddenFromRobots());
```

<a name="error-pages"></a>
### Сторінки помилок

Зазвичай метадані помилок реєструють у методі `boot` класу `AppServiceProvider` вашого застосунку:

```php
use Laravel\Head\ErrorPages;
use Laravel\Head\Facades\Head;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Head::errors(function (ErrorPages $errors) {
        $errors->defaults(robots: 'noindex, follow');

        $errors->status(
            404,
            title: 'Page Not Found',
            description: 'The page you are looking for could not be found.',
        );
    });
}
```

Методи `defaults` і `status` також приймають той самий колбек плавного білдера, що й `Head::defaults()`:

```php
use Laravel\Head\ErrorPages;
use Laravel\Head\Facades\Head;
use Laravel\Head\HeadBuilder;

Head::errors(function (ErrorPages $errors) {
    $errors->status(404, fn (HeadBuilder $head) => $head
        ->title('Page Not Found')
        ->description('The page you are looking for could not be found.'));
});
```

Коли відповідь рендериться для зареєстрованого статусу помилки, ці метадані мають перевагу над усіма іншими шарами.

Laravel автоматично визначає статус відповіді під час рендерингу представлення помилки або виконання хука фази відповіді, як-от метод `handleExceptionsUsing()` з Inertia. Якщо ви рендерите відповідь з помилкою всередині колбека `$exceptions->render()`, викличте `Head::status(404)` перед рендерингом, щоб метадані помилки застосувалися.

<a name="open-graph"></a>
## Open Graph

Властивості Open Graph задаються методом `og`. Повторюване медіа додається методами верхнього рівня, які приймають іменовані аргументи напряму:

```php
use Laravel\Head\Enums\ImageType;
use Laravel\Head\Enums\OgType;

Head::og(type: OgType::Article, title: $post->title)
    ->ogImage($post->hero_image_url)
    ->ogImage(
        $post->gallery_image_url,
        alt: $post->gallery_image_alt,
        width: 1200,
        height: 630,
        type: ImageType::Jpeg,
    );
```

Методи `ogImage`, `ogVideo` та `ogAudio` приймають URL першим аргументом, а також необов'язкові іменовані аргументи, як-от `alt`, `width`, `height`, `type` і `secureUrl` - там, де їх підтримує специфікація Open Graph.

MIME-типи зображень можна передавати як case-и енума `ImageType` усюди, де API приймає `type` зображення: `ImageType::Svg`, `ImageType::Png`, `ImageType::Jpeg` та `ImageType::Webp`.

> [!NOTE]
> `title` і `description` документа автоматично заповнюють відсутні значення `og:title` та `og:description`.

Для одного зображення Open Graph без інших атрибутів можна передати іменований аргумент `image` до методу `og`:

```php
Head::og(
    type: OgType::Website,
    title: $page->title,
    description: $page->description,
    image: $page->og_image_url,
);
```

Виклики `og(image: ...)` та `ogImage(...)` пишуть в один і той самий список зображень, тож користуйтеся тим, що виразніше в конкретному місці. Для власних розширень Open Graph, як-от властивостей товару чи статті, скористайтеся методом [`meta`](#custom-tags).

<a name="twitter-cards"></a>
### Картки X / Twitter

Щоб рендерити картки X / Twitter з того самого заголовка, опису та зображення, що й Open Graph, зареєструйте `twitter()` у значеннях за замовчуванням:

```php
use Laravel\Head\Enums\TwitterCard;
use Laravel\Head\Facades\Head;
use Laravel\Head\HeadBuilder;

Head::defaults(fn (HeadBuilder $head) => $head->twitter(
    card: TwitterCard::SummaryWithLargeImage,
));
```

Потім задайте метадані рівня сторінки:

```php
Head::title('Introducing Laravel Head')
    ->description('A fluent API for Laravel document head metadata.')
    ->ogImage('https://example.com/social.jpg', alt: 'Introducing Laravel Head');
```

Це відрендерить відповідні теги Twitter:

```html
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Introducing Laravel Head">
<meta name="twitter:description" content="A fluent API for Laravel document head metadata.">
<meta name="twitter:image" content="https://example.com/social.jpg">
<meta name="twitter:image:alt" content="Introducing Laravel Head">
```

Окремі сторінки можна налаштувати явними значеннями Twitter:

```php
Head::twitter(title: $post->social_title)
    ->twitterImage($post->social_image_url, alt: $post->title);
```

Метадані маршруту приймають `twitter` і `twitterImage`.

<a name="theme-colors"></a>
## Кольори теми

Кольори теми задаються глобально, для маршруту або під час виконання:

```php
Head::themeColor('#0f172a');
```

Це рендерить тег `<meta name="theme-color">`. Для кольорів теми, прив'язаних до медіа, скористайтеся енумом `Media`:

```php
use Laravel\Head\Enums\Media;

Head::themeColor('#ffffff', media: Media::Light)
    ->themeColor('#111827', media: Media::Dark);
```

Енум `Media` містить також `Portrait` і `Landscape`. Аргумент `media` приймає й власний рядок медіазапиту.

Метадані маршруту підтримують один колір теми через той самий ключ у `camelCase`:

```php
Route::view('/dashboard', 'dashboard')->withHead(
    themeColor: '#0f172a',
);
```

<a name="app-metadata-and-icons"></a>
## Метадані застосунку та іконки

Laravel Head містить методи для поширених метаданих браузера та застосунку:

```php
use Laravel\Head\Enums\ImageType;
use Laravel\Head\Enums\Media;

Head::applicationName('Laravel')
    ->colorScheme('light dark')
    ->referrer('strict-origin-when-cross-origin')
    ->viewport('width=device-width, initial-scale=1')
    ->appleWebAppTitle('Laravel')
    ->webAppCapable()
    ->appleWebAppStatusBarStyle('black')
    ->favicon('/favicon.svg', type: ImageType::Svg)
    ->icon('/favicon-32x32.png', type: ImageType::Png, sizes: '32x32')
    ->appleTouchIcon('/apple-touch-icon.png', sizes: '180x180')
    ->appleTouchStartupImage('/launch.png', media: Media::Portrait)
    ->maskIcon('/safari-pinned-tab.svg', color: '#111827')
    ->manifest('/site.webmanifest');
```

Метод `favicon` - псевдонім методу `icon` і приймає ті самі аргументи `type`, `sizes` та `media`.

Метадані маршруту використовують ті самі назви:

```php
use Laravel\Head\Enums\ImageType;
use Laravel\Head\Enums\Media;

Route::view('/dashboard', 'dashboard')->withHead(
    applicationName: 'Laravel',
    colorScheme: 'light dark',
    appleWebAppTitle: 'Laravel',
    webAppCapable: true,
    appleWebAppStatusBarStyle: 'black',
    favicon: [
        ['href' => '/favicon.svg', 'type' => ImageType::Svg],
        ['href' => '/favicon-32x32.png', 'type' => ImageType::Png, 'sizes' => '32x32'],
    ],
    appleTouchIcon: ['href' => '/apple-touch-icon.png', 'sizes' => '180x180'],
    appleTouchStartupImage: ['href' => '/launch.png', 'media' => Media::Portrait],
    manifest: '/site.webmanifest',
);
```

<a name="progressive-web-apps"></a>
## Прогресивні вебзастосунки

Метод `pwa` налаштовує поширені теги `<head>`, потрібні для вебзастосунку, який можна встановити:

```php
Head::pwa(
    name: 'Laravel',
    manifest: '/site.webmanifest',
    themeColor: '#0f172a',
    appleTouchIcon: '/apple-touch-icon.png',
    appleWebAppStatusBarStyle: 'black',
);
```

Це рендерить назву застосунку, посилання на маніфест вебзастосунку та метадані автономного режиму для iOS. Якщо їх передано, рендеряться також колір теми, стиль рядка стану Apple та іконка Apple touch. Створення маніфесту вебзастосунку й реєстрація service worker лишаються відповідальністю вашого застосунку.

Метод `pwa` можна використовувати у значеннях за замовчуванням або метаданих під час виконання. Метадані маршруту підтримують окремі властивості, показані вище.

<a name="performance-and-discovery"></a>
## Продуктивність і виявлення

Laravel Head рендерить підказки продуктивності, посилання пагінації, локальні альтернативи та виявлення стрічок:

```php
Head::preload(asset('fonts/inter.woff2'), as: 'font', crossorigin: true)
    ->prefetch(asset('images/next.webp'))
    ->preconnect('https://cdn.example.com')
    ->dnsPrefetch('https://analytics.example.com')
    ->paginate($posts)
    ->alternates([
        'en' => 'https://example.com/en/about',
        'fr' => 'https://example.com/fr/about',
        'x-default' => 'https://example.com/about',
    ])
    ->feed('/feed', title: 'Laravel RSS')
    ->feed('/feed.atom', type: 'atom', title: 'Laravel Atom');
```

Для локальних ресурсів `preloadAsset()` і `prefetchAsset()` розв'язують URL через хелпер `asset()` і визначають атрибут `as` за розширенням файлу. Попереднє завантаження шрифтів автоматично додає `crossorigin`, якого специфікація preload вимагає навіть для шрифтів того самого джерела:

```php
Head::preloadAsset('fonts/inter.woff2')
    ->prefetchAsset('images/next.webp');
```

```html
<link rel="preload" href="https://example.com/fonts/inter.woff2" as="font" crossorigin>
<link rel="prefetch" href="https://example.com/images/next.webp" as="image">
```

Атрибут `as` можна передати явно, щоб перекрити автовизначення. Метод `preloadAsset` кине виняток, коли атрибут `as` не вдається визначити за розширенням, бо браузери ігнорують preload без цього атрибута; метод `prefetchAsset` просто його опустить.

<a name="custom-tags"></a>
## Власні теги

Для тегів без окремого методу скористайтеся `meta()` та `link()`:

```php
Head::meta('format-detection', 'telephone=no')
    ->meta('article:author', $post->author->name)
    ->link('search', '/opensearch.xml', [
        'type' => 'application/opensearchdescription+xml',
        'title' => 'Laravel Search',
    ])
    ->link('me', 'https://social.example.com/@laravel');
```

До мета-тега можна додати медіазапит, коли браузер має застосовувати тег лише за відповідних умов:

```php
use Laravel\Head\Enums\Media;

Head::meta('theme-color', '#ffffff', media: Media::Light)
    ->meta('theme-color', '#111827', media: Media::Dark);
```

Метод `meta` використовує атрибут `name` для звичайних мета-тегів. Для ключів, які зазвичай вживають атрибут `property` - як-от Open Graph (`og:`) чи метадані статті (`article:`), - метод перемикається автоматично:

```php
Head::meta('description', 'About Laravel')
    ->meta('og:title', 'About Laravel');
```

```html
<meta name="description" content="About Laravel">
<meta property="og:title" content="About Laravel">
```

Щоб обрати атрибут явно, передайте `property: true` або `property: false`.

<a name="schemas"></a>
## Схеми

Вбудовані білдери схем покривають поширені типи JSON-LD:

```php
use Laravel\Head\Enums\OfferAvailability;
use Laravel\Head\Facades\Schema;

Head::schema(
    Schema::product()
        ->name($product->name)
        ->offers(
            Schema::offer()
                ->price($product->price)
                ->currency('USD')
                ->availability(OfferAvailability::InStock)
        )
);
```

Вбудовані фабричні методи - `article`, `blogPosting`, `product`, `offer`, `brand`, `breadcrumbs`, `faq`, `organization`, `person`, `webPage` та `webSite`. Невідомі фабричні методи створюють узагальнений об'єкт схеми, тож ви все одно можете виразити власні типи schema.org.

Коли дані схеми JSON-LD некоректні, Laravel Head кидає виняток у непродакшн-середовищах і пише попередження в лог на продакшні.

<a name="breadcrumbs"></a>
### Хлібні крихти

Елементи хлібних крихт додаються по одному або пакетом. Позиції призначаються автоматично в порядку додавання:

```php
Head::schema(
    Schema::breadcrumbs()->items([
        'Home' => route('home'),
        'Shop' => route('shop.index'),
        'Shoes' => route('shop.category', 'shoes'),
    ])
);
```

Щоб додати одну хлібну крихту, скористайтеся методом `item`:

```php
Schema::breadcrumbs()
    ->item('Home', route('home'))
    ->item('Shop', route('shop.index'));
```

<a name="faqs"></a>
### Часті питання

Записи FAQ працюють за тим самим принципом. Додавайте їх по одному методом `question` або пакетом методом `questions`:

```php
Head::schema(
    Schema::faq()->questions([
        'What is Laravel Head?' => 'A fluent API for managing the document head.',
        'Is it free?' => 'Yes, it is open source.',
    ])
);
```

<a name="custom-schemas"></a>
### Власні схеми

Власні типи схем можна зареєструвати явно:

```php
use DateTimeInterface;
use Laravel\Head\Facades\Schema;
use Laravel\Head\Schema\SchemaObject;
use Laravel\Head\SchemaType;

#[SchemaType('JobPosting')]
class JobPosting extends SchemaObject
{
    public function title(string $title): static
    {
        return $this->set('title', $title);
    }

    public function datePosted(DateTimeInterface|string $date): static
    {
        return $this->date('datePosted', $date);
    }
}

Schema::register(JobPosting::class);

Head::schema(
    Schema::jobPosting()
        ->title('Senior Laravel Developer')
        ->datePosted(now())
);
```

<a name="rendering"></a>
## Рендеринг

Laravel Head розв'язує метадані сторінки в теги для поточної відповіді. Те, як ці теги рендеряться, залежить від стека вашого застосунку.

HTML-рендерер живить директиву `@head` і відрендерені елементи, якими Laravel Head ділиться з Inertia через проп `head`. Рендерер масиву живить `Head::toArray()` для застосунків, яким потрібні розв'язані метадані у вигляді структурованих даних.

<a name="blade"></a>
### Blade

Рендеріть накопичені теги в `<head>` свого макета директивою `@head`:

```blade
<head>
    <meta charset="utf-8">
    @head
</head>
```

Директива `@head` рендериться синхронно, тому метадані сторінки слід визначати до рендерингу макета.

<a name="livewire"></a>
### Livewire

Застосунки на Livewire використовують ту саму директиву `@head` у макеті документа:

```blade
<head>
    @head
</head>

<body>
    {{ $slot }}

    @livewireScripts
</body>
```

Жодних специфічних для Livewire налаштувань не потрібно. Метадані Laravel Head розв'язуються на кожен запит, а резолвер має область видимості запиту. Тому кожен перехід через `wire:navigate` завантажує свіжий документ, вивід `@head` якого відповідає метаданим маршруту призначення. Сторінки, відвідані через `wire:navigate`, отримують належні метадані маршруту, виконання та помилок без коду для head на рівні компонента.

<a name="inertia"></a>
### Inertia

Використовуйте ту саму директиву `@head` у кореневому шаблоні Inertia, поруч із власними компонентами Inertia:

```blade
<html>
<head>
    <meta charset="utf-8">
    @head

    @viteReactRefresh
    @vite(['resources/css/app.css', 'resources/js/app.tsx'])
    <x-inertia::head />
</head>
<body>
    <x-inertia::app />
</body>
</html>
```

Коли Inertia встановлено, Laravel Head автоматично ділиться керованим сторінкою head як масивом відрендерених рядків елементів під пропом `head` у кожному об'єкті сторінки:

```json
{
    "props": {
        "head": [
            "<title data-inertia=\"title\">Dashboard - Laravel</title>",
            "<meta data-inertia=\"description\" name=\"description\" content=\"Your application overview.\">"
        ]
    }
}
```

Увімкніть опцію `serverHead` в Inertia там, де ваш застосунок викликає `createInertiaApp()`. Опція доступна в Inertia 3.5 і новіших:

```js
createInertiaApp({
    // ...
    serverHead: true,
});
```

Кожен керований сторінкою елемент має стабільний ключ `data-inertia`. Директива `@head` рендерить початковий документ, після чого Inertia переймає ці елементи й тримає їх синхронізованими під час звичайних переходів, [миттєвих переходів](https://inertiajs.com/docs/v3/the-basics/instant-visits) і навігації вперед-назад. Елементи присутні в початковій HTML-відповіді, тож пошукові краулери й боти попереднього перегляду посилань читають їх без виконання JavaScript. Клієнтський компонент `<Head>` не потрібен.

Це працює як із [рендерингом на боці сервера (SSR)](https://inertiajs.com/docs/v3/advanced/server-side-rendering), так і без нього. Якщо ваш застосунок має окрему точку входу для SSR, увімкніть `serverHead` і там. Laravel Head автоматично усуває дублікати керованих сторінкою елементів між `@head` і `<x-inertia::head />` незалежно від їхнього порядку, зберігаючи інші елементи head, створені JavaScript-SSR.

> [!NOTE]
> Додаючи Laravel Head до наявного застосунку на Inertia, приберіть будь-які колбеки заголовка з `resources/js/app.tsx` і `resources/js/ssr.tsx`, щоб Laravel Head керував остаточним заголовком документа, і перенесіть теги, якими керує [компонент `<Head>`](https://inertiajs.com/docs/v3/the-basics/title-and-meta) з Inertia, до Laravel Head, щоб вони ніколи не визначали той самий елемент удвох.

Проп `head` не входить до відповідей часткового перезавантаження, тож Inertia зберігає head останньої повної сторінки. Миттєві переходи так само зберігають поточний head, доки не надійде фонова відповідь. Якщо ваш застосунок уже використовує проп `head`, змініть його назву в сервіс-провайдері:

```php
use Laravel\Head\Facades\Head;

public function boot(): void
{
    Head::inertia(prop: '_head');
}
```

Потім вкажіть Inertia той самий проп через `serverHead: '_head'`.

<a name="static-inertia-tags"></a>
#### Статичні теги Inertia

Більшість тегів мають жити у значеннях за замовчуванням, метаданих маршруту чи метаданих під час виконання, щоб Laravel Head розв'язував правильне значення для кожної сторінки. Глобальні значення Inertia використовуйте лише для тегів документа, які рендеряться в першій HTML-відповіді й лишаються незмінними до кінця сесії.

Реєструйте їх у сервіс-провайдері через `Head::inertiaGlobals()`:

```php
use Laravel\Head\Facades\Head;
use Laravel\Head\HeadBuilder;

Head::inertiaGlobals(function (HeadBuilder $head) {
    $head
        ->viewport('width=device-width, initial-scale=1')
        ->colorScheme('light dark')
        ->icon('/favicon.svg', type: 'image/svg+xml')
        ->appleTouchIcon('/apple-touch-icon.png', sizes: '180x180')
        ->manifest('/site.webmanifest');
});
```

Глобальні значення Inertia не входять до пропа `head`, рендеряться без атрибутів власності `data-inertia` і ніколи не оновлюються після першої відповіді. Вони пасують для стабільних підказок браузера: viewport, колірної схеми, фавіконок, touch-іконок і маніфестів. Якщо тег специфічний для сторінки, важливий для SEO або може бути перекритий пізніше, розміщуйте його у `defaults`, метаданих маршруту чи метаданих під час виконання.

Застосунки, яким потрібні розв'язані метадані у вигляді структурованих даних, а не відрендерених тегів, можуть викликати `Head::toArray()`. Повернуті дані містять заголовки, значення Open Graph, схеми JSON-LD та інші розв'язані метадані.

---

Джерело: https://laravelukraine.com/docs/13.x/homestead

# Laravel Homestead

- [Вступ](#introduction)
- [Встановлення й налаштування](#installation-and-setup)
    - [Перші кроки](#first-steps)
    - [Налаштування Homestead](#configuring-homestead)
    - [Налаштування сайтів Nginx](#configuring-nginx-sites)
    - [Налаштування сервісів](#configuring-services)
    - [Запуск Vagrant-боксу](#launching-the-vagrant-box)
    - [Встановлення для окремого проєкту](#per-project-installation)
    - [Встановлення додаткового ПЗ](#installing-optional-features)
    - [Аліаси](#aliases)
- [Оновлення Homestead](#updating-homestead)
- [Щоденне використання](#daily-usage)
    - [Підключення через SSH](#connecting-via-ssh)
    - [Додавання нових сайтів](#adding-additional-sites)
    - [Змінні оточення](#environment-variables)
    - [Порти](#ports)
    - [Версії PHP](#php-versions)
    - [Підключення до баз даних](#connecting-to-databases)
    - [Резервні копії бази даних](#database-backups)
    - [Налаштування розкладу cron](#configuring-cron-schedules)
    - [Налаштування Mailpit](#configuring-mailpit)
    - [Налаштування Minio](#configuring-minio)
    - [Laravel Dusk](#laravel-dusk)
    - [Спільний доступ до вашого середовища](#sharing-your-environment)
- [Налагодження та профілювання](#debugging-and-profiling)
    - [Налагодження вебзапитів за допомогою Xdebug](#debugging-web-requests)
    - [Налагодження CLI-застосунків](#debugging-cli-applications)
    - [Профілювання застосунків за допомогою Blackfire](#profiling-applications-with-blackfire)
- [Мережеві інтерфейси](#network-interfaces)
- [Розширення Homestead](#extending-homestead)
- [Налаштування, специфічні для провайдера](#provider-specific-settings)
    - [VirtualBox](#provider-specific-virtualbox)

<a name="introduction"></a>
## Вступ

> [!WARNING]
> Laravel Homestead - це застарілий пакет, який більше активно не підтримується. Як сучасну альтернативу можна використати [Laravel Sail](/docs/{{version}}/sail).

Laravel прагне зробити приємним увесь досвід розробки на PHP, включно з вашим локальним середовищем розробки. [Laravel Homestead](https://github.com/laravel/homestead) - це офіційний, заздалегідь зібраний Vagrant-бокс, який дає вам чудове середовище розробки, не вимагаючи встановлювати PHP, вебсервер чи будь-яке інше серверне ПЗ на вашу локальну машину.

[Vagrant](https://www.vagrantup.com) дає простий та елегантний спосіб керувати віртуальними машинами й розгортати їх. Vagrant-бокси повністю одноразові. Якщо щось піде не так, ви можете знищити й перестворити бокс за кілька хвилин!

Homestead працює на будь-якій системі Windows, macOS чи Linux і містить Nginx, PHP, MySQL, PostgreSQL, Redis, Memcached, Node і все інше ПЗ, потрібне для розробки чудових Laravel-застосунків.

> [!WARNING]
> Якщо ви користуєтеся Windows, вам може знадобитися увімкнути апаратну віртуалізацію (VT-x). Зазвичай це можна зробити через BIOS. Якщо ви використовуєте Hyper-V в UEFI-системі, вам, можливо, також доведеться вимкнути Hyper-V, щоб отримати доступ до VT-x.

<a name="included-software"></a>
### Включене ПЗ

<style>
    #software-list > ul {
        column-count: 2; -moz-column-count: 2; -webkit-column-count: 2;
        column-gap: 5em; -moz-column-gap: 5em; -webkit-column-gap: 5em;
        line-height: 1.9;
    }
</style>

<div id="software-list" markdown="1">

- Ubuntu 22.04
- Git
- PHP 8.3
- PHP 8.2
- PHP 8.1
- PHP 8.0
- PHP 7.4
- PHP 7.3
- PHP 7.2
- PHP 7.1
- PHP 7.0
- PHP 5.6
- Nginx
- MySQL 8.0
- lmm
- Sqlite3
- PostgreSQL 15
- Composer
- Docker
- Node (With Yarn, Bower, Grunt, and Gulp)
- Redis
- Memcached
- Beanstalkd
- Mailpit
- avahi
- ngrok
- Xdebug
- XHProf / Tideways / XHGui
- wp-cli

</div>

<a name="optional-software"></a>
### Додаткове ПЗ

<style>
    #software-list > ul {
        column-count: 2; -moz-column-count: 2; -webkit-column-count: 2;
        column-gap: 5em; -moz-column-gap: 5em; -webkit-column-gap: 5em;
        line-height: 1.9;
    }
</style>

<div id="software-list" markdown="1">

- Apache
- Blackfire
- Cassandra
- Chronograf
- CouchDB
- Crystal & Lucky Framework
- Elasticsearch
- EventStoreDB
- Flyway
- Gearman
- Go
- Grafana
- InfluxDB
- Logstash
- MariaDB
- Meilisearch
- MinIO
- MongoDB
- Neo4j
- Oh My Zsh
- Open Resty
- PM2
- Python
- R
- RabbitMQ
- Rust
- RVM (Ruby Version Manager)
- Solr
- TimescaleDB
- Trader <small>(PHP extension)</small>
- Webdriver & Laravel Dusk Utilities

</div>

<a name="installation-and-setup"></a>
## Встановлення й налаштування

<a name="first-steps"></a>
### Перші кроки

Перш ніж запускати середовище Homestead, вам потрібно встановити [Vagrant](https://developer.hashicorp.com/vagrant/downloads), а також один із таких підтримуваних провайдерів:

- [VirtualBox 6.1.x](https://www.virtualbox.org/wiki/Download_Old_Builds_6_1)
- [Parallels](https://www.parallels.com/products/desktop/)

Усі ці програмні пакети мають прості візуальні інсталятори для всіх популярних операційних систем.

Щоб скористатися провайдером Parallels, вам потрібно встановити [плагін Parallels для Vagrant](https://github.com/Parallels/vagrant-parallels). Він безкоштовний.

<a name="installing-homestead"></a>
#### Встановлення Homestead

Встановити Homestead можна, склонувавши репозиторій Homestead на вашу хост-машину. Варто склонувати репозиторій у теку `Homestead` у вашому «домашньому» каталозі, адже віртуальна машина Homestead буде хостом для всіх ваших Laravel-застосунків. У цій документації ми називатимемо цей каталог вашим «каталогом Homestead»:

```shell
git clone https://github.com/laravel/homestead.git ~/Homestead
```

Склонувавши репозиторій Laravel Homestead, вам слід перейти на гілку `release`. Ця гілка завжди містить останній стабільний випуск Homestead:

```shell
cd ~/Homestead

git checkout release
```

Далі виконайте команду `bash init.sh` з каталогу Homestead, щоб створити конфігураційний файл `Homestead.yaml`. У файлі `Homestead.yaml` ви налаштовуватимете всі параметри своєї інсталяції Homestead. Цей файл буде розміщено в каталозі Homestead:

```shell
# macOS / Linux...
bash init.sh

# Windows...
init.bat
```

<a name="configuring-homestead"></a>
### Налаштування Homestead

<a name="setting-your-provider"></a>
#### Вибір провайдера

Ключ `provider` у вашому файлі `Homestead.yaml` вказує, який провайдер Vagrant слід використовувати: `virtualbox` чи `parallels`:

    provider: virtualbox

> [!WARNING]
> Якщо ви користуєтеся Apple Silicon, потрібен провайдер Parallels.

<a name="configuring-shared-folders"></a>
#### Налаштування спільних тек

Властивість `folders` файлу `Homestead.yaml` перелічує всі теки, якими ви хочете поділитися зі своїм середовищем Homestead. Коли файли в цих теках змінюються, вони синхронізуватимуться між вашою локальною машиною та віртуальним середовищем Homestead. Ви можете налаштувати скільки завгодно спільних тек:

```yaml
folders:
    - map: ~/code/project1
      to: /home/vagrant/project1
```

> [!WARNING]
> Користувачам Windows не варто використовувати синтаксис шляху `~/`, натомість слід указувати повний шлях до свого проєкту, наприклад `C:\Users\user\Code\project1`.

Вам завжди слід зіставляти окремі застосунки з власними теками, а не мапити один великий каталог, що містить усі ваші застосунки. Коли ви мапите теку, віртуальна машина має відстежувати весь дисковий ввід-вивід для *кожного* файлу в цій теці. Якщо у теці багато файлів, ви можете зіткнутися зі зниженою продуктивністю:

```yaml
folders:
    - map: ~/code/project1
      to: /home/vagrant/project1
    - map: ~/code/project2
      to: /home/vagrant/project2
```

> [!WARNING]
> Ніколи не монтуйте `.` (поточний каталог), використовуючи Homestead. Через це Vagrant не змапить поточну теку до `/vagrant`, що зламає додаткові можливості й призведе до несподіваних результатів під час розгортання.

Щоб увімкнути [NFS](https://developer.hashicorp.com/vagrant/docs/synced-folders/nfs), додайте до свого мапінгу теки опцію `type`:

```yaml
folders:
    - map: ~/code/project1
      to: /home/vagrant/project1
      type: "nfs"
```

> [!WARNING]
> Використовуючи NFS у Windows, варто розглянути встановлення плагіна [vagrant-winnfsd](https://github.com/winnfsd/vagrant-winnfsd). Цей плагін підтримуватиме правильні дозволи користувача / групи для файлів і каталогів усередині віртуальної машини Homestead.

Ви також можете передати будь-які опції, які підтримують [синхронізовані теки](https://developer.hashicorp.com/vagrant/docs/synced-folders/basic_usage) Vagrant, перелічивши їх під ключем `options`:

```yaml
folders:
    - map: ~/code/project1
      to: /home/vagrant/project1
      type: "rsync"
      options:
          rsync__args: ["--verbose", "--archive", "--delete", "-zz"]
          rsync__exclude: ["node_modules"]
```

<a name="configuring-nginx-sites"></a>
### Налаштування сайтів Nginx

Не знайомі з Nginx? Не біда. Властивість `sites` вашого файлу `Homestead.yaml` дозволяє легко змапити «домен» на теку у вашому середовищі Homestead. Приклад конфігурації сайту є у файлі `Homestead.yaml`. Знову ж таки, ви можете додати до свого середовища Homestead скільки завгодно сайтів. Homestead може бути зручним віртуалізованим середовищем для кожного Laravel-застосунку, над яким ви працюєте:

```yaml
sites:
    - map: homestead.test
      to: /home/vagrant/project1/public
```

Якщо ви зміните властивість `sites` після розгортання віртуальної машини Homestead, виконайте в терміналі команду `vagrant reload --provision`, щоб оновити конфігурацію Nginx на віртуальній машині.

> [!WARNING]
> Скрипти Homestead написані так, щоб бути максимально ідемпотентними. Однак, якщо ви стикаєтеся з проблемами під час розгортання, знищте й перезберіть машину командою `vagrant destroy && vagrant up`.

<a name="hostname-resolution"></a>
#### Резолвінг імен хостів

Homestead публікує імена хостів через `mDNS` для автоматичного резолвінгу хостів. Якщо ви встановите `hostname: homestead` у файлі `Homestead.yaml`, хост буде доступний за адресою `homestead.local`. macOS, iOS і десктопні дистрибутиви Linux підтримують `mDNS` за замовчуванням. Якщо ви користуєтеся Windows, вам потрібно встановити [Bonjour Print Services for Windows](https://support.apple.com/kb/DL999?viewlocale=en_US&locale=en_US).

Автоматичні імена хостів найкраще працюють для [встановлення для окремого проєкту](#per-project-installation). Якщо ви хостите кілька сайтів на одному екземплярі Homestead, ви можете додати «домени» ваших вебсайтів до файлу `hosts` на своїй машині. Файл `hosts` перенаправлятиме запити до ваших сайтів Homestead у вашу віртуальну машину Homestead. У macOS і Linux цей файл розташований у `/etc/hosts`. У Windows - у `C:\Windows\System32\drivers\etc\hosts`. Рядки, які ви додаєте до цього файлу, виглядатимуть так:

```text
192.168.56.56  homestead.test
```

Переконайтеся, що вказана IP-адреса - це та, яку задано у вашому файлі `Homestead.yaml`. Щойно ви додасте домен до файлу `hosts` і запустите Vagrant-бокс, ви зможете відкрити сайт у своєму браузері:

```shell
http://homestead.test
```

<a name="configuring-services"></a>
### Налаштування сервісів

Homestead запускає кілька сервісів за замовчуванням; однак ви можете налаштувати, які сервіси вмикати чи вимикати під час розгортання. Наприклад, ви можете увімкнути PostgreSQL і вимкнути MySQL, змінивши опцію `services` у своєму файлі `Homestead.yaml`:

```yaml
services:
    - enabled:
        - "postgresql"
    - disabled:
        - "mysql"
```

Указані сервіси буде запущено чи зупинено відповідно до їхнього порядку в директивах `enabled` і `disabled`.

<a name="launching-the-vagrant-box"></a>
### Запуск Vagrant-боксу

Щойно ви відредагуєте `Homestead.yaml` на свій смак, виконайте команду `vagrant up` зі свого каталогу Homestead. Vagrant завантажить віртуальну машину й автоматично налаштує ваші спільні теки та сайти Nginx.

Щоб знищити машину, скористайтеся командою `vagrant destroy`.

<a name="per-project-installation"></a>
### Встановлення для окремого проєкту

Замість встановлювати Homestead глобально й ділити ту саму віртуальну машину Homestead між усіма своїми проєктами, ви можете налаштувати окремий екземпляр Homestead для кожного проєкту, яким керуєте. Встановлення Homestead для окремого проєкту може бути корисним, якщо ви хочете постачати `Vagrantfile` разом зі своїм проєктом, щоб інші учасники проєкту могли виконати `vagrant up` одразу після клонування репозиторію проєкту.

Встановити Homestead у свій проєкт можна за допомогою менеджера пакетів Composer:

```shell
composer require laravel/homestead --dev
```

Щойно Homestead буде встановлено, викличте команду `make` з Homestead, щоб згенерувати `Vagrantfile` і файл `Homestead.yaml` для вашого проєкту. Ці файли буде розміщено в корені вашого проєкту. Команда `make` автоматично налаштує директиви `sites` і `folders` у файлі `Homestead.yaml`:

```shell
# macOS / Linux...
php vendor/bin/homestead make

# Windows...
vendor\\bin\\homestead make
```

Далі виконайте в терміналі команду `vagrant up` і відкрийте свій проєкт за адресою `http://homestead.test` у браузері. Пам'ятайте: вам усе одно потрібно буде додати запис для `homestead.test` (чи для обраного вами домену) до файлу `/etc/hosts`, якщо ви не користуєтеся автоматичним [резолвінгом імен хостів](#hostname-resolution).

<a name="installing-optional-features"></a>
### Встановлення додаткового ПЗ

Додаткове ПЗ встановлюється через опцію `features` у вашому файлі `Homestead.yaml`. Більшість можливостей вмикаються чи вимикаються булевим значенням, а деякі допускають кілька параметрів конфігурації:

```yaml
features:
    - blackfire:
        server_id: "server_id"
        server_token: "server_value"
        client_id: "client_id"
        client_token: "client_value"
    - cassandra: true
    - chronograf: true
    - couchdb: true
    - crystal: true
    - dragonflydb: true
    - elasticsearch:
        version: 7.9.0
    - eventstore: true
        version: 21.2.0
    - flyway: true
    - gearman: true
    - golang: true
    - grafana: true
    - influxdb: true
    - logstash: true
    - mariadb: true
    - meilisearch: true
    - minio: true
    - mongodb: true
    - neo4j: true
    - ohmyzsh: true
    - openresty: true
    - pm2: true
    - python: true
    - r-base: true
    - rabbitmq: true
    - rustc: true
    - rvm: true
    - solr: true
    - timescaledb: true
    - trader: true
    - webdriver: true
```

<a name="elasticsearch"></a>
#### Elasticsearch

Ви можете вказати підтримувану версію Elasticsearch, яка має бути точним номером версії (major.minor.patch). Встановлення за замовчуванням створить кластер з іменем 'homestead'. Ніколи не віддавайте Elasticsearch більше ніж половину пам'яті операційної системи, тож переконайтеся, що ваша віртуальна машина Homestead має щонайменше вдвічі більше пам'яті, ніж виділено Elasticsearch.

> [!NOTE]
> Загляньте в [документацію Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/current), щоб дізнатися, як налаштувати конфігурацію.

<a name="mariadb"></a>
#### MariaDB

Увімкнення MariaDB прибере MySQL і встановить MariaDB. Ваш застосунок має використовувати драйвер бази даних `mariadb`, а не `mysql`: обидва підключаться, але лише драйвер `mariadb` генерує SQL, специфічний для MariaDB, як-от запити на векторну відстань.

<a name="mongodb"></a>
#### MongoDB

Встановлення MongoDB за замовчуванням встановить ім'я користувача бази даних `homestead`, а відповідний пароль - `secret`.

<a name="neo4j"></a>
#### Neo4j

Встановлення Neo4j за замовчуванням встановить ім'я користувача бази даних `homestead`, а відповідний пароль - `secret`. Щоб відкрити браузер Neo4j, перейдіть за адресою `http://homestead.test:7474` у своєму вебпереглядачі. Порти `7687` (Bolt), `7474` (HTTP) і `7473` (HTTPS) готові обслуговувати запити від клієнта Neo4j.

<a name="aliases"></a>
### Аліаси

Ви можете додати Bash-аліаси до своєї віртуальної машини Homestead, змінивши файл `aliases` у своєму каталозі Homestead:

```shell
alias c='clear'
alias ..='cd ..'
```

Після оновлення файлу `aliases` вам слід перерозгорнути віртуальну машину Homestead командою `vagrant reload --provision`. Це гарантує, що ваші нові аліаси будуть доступні на машині.

<a name="updating-homestead"></a>
## Оновлення Homestead

Перш ніж починати оновлення Homestead, переконайтеся, що ви прибрали свою поточну віртуальну машину, виконавши таку команду у своєму каталозі Homestead:

```shell
vagrant destroy
```

Далі вам потрібно оновити вихідний код Homestead. Якщо ви клонували репозиторій, виконайте такі команди там, де ви його спочатку склонували:

```shell
git fetch

git pull origin release
```

Ці команди підтягують останній код Homestead з репозиторію GitHub, отримують останні теги, а потім переходять на останній тегований випуск. Останню стабільну версію можна знайти на [сторінці випусків Homestead на GitHub](https://github.com/laravel/homestead/releases).

Якщо ви встановили Homestead через файл `composer.json` свого проєкту, переконайтеся, що ваш файл `composer.json` містить `"laravel/homestead": "^12"`, і оновіть залежності:

```shell
composer update
```

Далі вам слід оновити Vagrant-бокс командою `vagrant box update`:

```shell
vagrant box update
```

Після оновлення Vagrant-боксу виконайте команду `bash init.sh` з каталогу Homestead, щоб оновити додаткові конфігураційні файли Homestead. Вас запитають, чи хочете ви перезаписати наявні файли `Homestead.yaml`, `after.sh` і `aliases`:

```shell
# macOS / Linux...
bash init.sh

# Windows...
init.bat
```

Насамкінець вам потрібно буде перестворити свою віртуальну машину Homestead, щоб скористатися останньою інсталяцією Vagrant:

```shell
vagrant up
```

<a name="daily-usage"></a>
## Щоденне використання

<a name="connecting-via-ssh"></a>
### Підключення через SSH

Ви можете підключитися до своєї віртуальної машини через SSH, виконавши в терміналі команду `vagrant ssh` зі свого каталогу Homestead.

<a name="adding-additional-sites"></a>
### Додавання нових сайтів

Щойно ваше середовище Homestead розгорнуто й працює, ви можете захотіти додати нові сайти Nginx для інших своїх Laravel-проєктів. В одному середовищі Homestead можна запускати скільки завгодно Laravel-проєктів. Щоб додати новий сайт, додайте його до свого файлу `Homestead.yaml`.

```yaml
sites:
    - map: homestead.test
      to: /home/vagrant/project1/public
    - map: another.test
      to: /home/vagrant/project2/public
```

> [!WARNING]
> Перш ніж додавати сайт, переконайтеся, що ви налаштували [мапінг теки](#configuring-shared-folders) для каталогу проєкту.

Якщо Vagrant не керує вашим файлом «hosts» автоматично, вам, можливо, доведеться додати новий сайт і до цього файлу. У macOS і Linux цей файл розташований у `/etc/hosts`. У Windows - у `C:\Windows\System32\drivers\etc\hosts`:

```text
192.168.56.56  homestead.test
192.168.56.56  another.test
```

Щойно сайт буде додано, виконайте в терміналі команду `vagrant reload --provision` зі свого каталогу Homestead.

<a name="site-types"></a>
#### Типи сайтів

Homestead підтримує кілька «типів» сайтів, що дозволяє легко запускати проєкти, не засновані на Laravel. Наприклад, ми можемо легко додати до Homestead застосунок Statamic, скориставшись типом сайту `statamic`:

```yaml
sites:
    - map: statamic.test
      to: /home/vagrant/my-symfony-project/web
      type: "statamic"
```

Доступні типи сайтів: `apache`, `apache-proxy`, `apigility`, `expressive`, `laravel` (за замовчуванням), `proxy` (для nginx), `silverstripe`, `statamic`, `symfony2`, `symfony4` і `zf`.

<a name="site-parameters"></a>
#### Параметри сайту

Ви можете додати до свого сайту додаткові значення `fastcgi_param` для Nginx через директиву сайту `params`:

```yaml
sites:
    - map: homestead.test
      to: /home/vagrant/project1/public
      params:
          - key: FOO
            value: BAR
```

<a name="environment-variables"></a>
### Змінні оточення

Ви можете визначити глобальні змінні оточення, додавши їх до свого файлу `Homestead.yaml`:

```yaml
variables:
    - key: APP_ENV
      value: local
    - key: FOO
      value: bar
```

Після оновлення файлу `Homestead.yaml` обов'язково перерозгорніть машину командою `vagrant reload --provision`. Це оновить конфігурацію PHP-FPM для всіх встановлених версій PHP, а також оновить оточення для користувача `vagrant`.

<a name="ports"></a>
### Порти

За замовчуванням до вашого середовища Homestead прокидаються такі порти:

<div class="content-list" markdown="1">

- **HTTP:** 8000 &rarr; Forwards To 80
- **HTTPS:** 44300 &rarr; Forwards To 443

</div>

<a name="forwarding-additional-ports"></a>
#### Прокидання додаткових портів

За бажанням ви можете прокинути до Vagrant-боксу додаткові порти, визначивши запис конфігурації `ports` у своєму файлі `Homestead.yaml`. Після оновлення файлу `Homestead.yaml` обов'язково перерозгорніть машину командою `vagrant reload --provision`:

```yaml
ports:
    - send: 50000
      to: 5000
    - send: 7777
      to: 777
      protocol: udp
```

Нижче наведено список додаткових портів сервісів Homestead, які ви можете захотіти змапити зі своєї хост-машини на Vagrant-бокс:

<div class="content-list" markdown="1">

- **SSH:** 2222 &rarr; To 22
- **ngrok UI:** 4040 &rarr; To 4040
- **MySQL:** 33060 &rarr; To 3306
- **PostgreSQL:** 54320 &rarr; To 5432
- **MongoDB:** 27017 &rarr; To 27017
- **Mailpit:** 8025 &rarr; To 8025
- **Minio:** 9600 &rarr; To 9600

</div>

<a name="php-versions"></a>
### Версії PHP

Homestead підтримує роботу кількох версій PHP на одній віртуальній машині. Ви можете вказати, яку версію PHP використовувати для конкретного сайту, у своєму файлі `Homestead.yaml`. Доступні версії PHP: "5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2" і "8.3" (за замовчуванням):

```yaml
sites:
    - map: homestead.test
      to: /home/vagrant/project1/public
      php: "7.1"
```

[Усередині вашої віртуальної машини Homestead](#connecting-via-ssh) ви можете використовувати будь-яку з підтримуваних версій PHP через CLI:

```shell
php5.6 artisan list
php7.0 artisan list
php7.1 artisan list
php7.2 artisan list
php7.3 artisan list
php7.4 artisan list
php8.0 artisan list
php8.1 artisan list
php8.2 artisan list
php8.3 artisan list
```

Ви можете змінити версію PHP за замовчуванням, яку використовує CLI, виконавши такі команди всередині своєї віртуальної машини Homestead:

```shell
php56
php70
php71
php72
php73
php74
php80
php81
php82
php83
```

<a name="connecting-to-databases"></a>
### Підключення до баз даних

База даних `homestead` налаштована як для MySQL, так і для PostgreSQL «з коробки». Щоб підключитися до своєї бази MySQL чи PostgreSQL з клієнта бази даних на хост-машині, підключайтеся до `127.0.0.1` на порт `33060` (MySQL) чи `54320` (PostgreSQL). Ім'я користувача й пароль для обох баз - `homestead` / `secret`.

> [!WARNING]
> Ці нестандартні порти слід використовувати лише під час підключення до баз даних з вашої хост-машини. У конфігураційному файлі `database` вашого Laravel-застосунку ви використовуватимете стандартні порти 3306 і 5432, оскільки Laravel працює _всередині_ віртуальної машини.

<a name="database-backups"></a>
### Резервні копії бази даних

Homestead може автоматично створювати резервні копії вашої бази даних, коли віртуальну машину Homestead знищують. Щоб скористатися цією можливістю, вам потрібен Vagrant 2.1.0 чи новіший. Або ж, якщо у вас старіша версія Vagrant, вам потрібно встановити плагін `vagrant-triggers`. Щоб увімкнути автоматичне резервне копіювання бази даних, додайте до свого файлу `Homestead.yaml` такий рядок:

```yaml
backup: true
```

Після налаштування Homestead експортуватиме ваші бази даних до каталогів `.backup/mysql_backup` і `.backup/postgres_backup` під час виконання команди `vagrant destroy`. Ці каталоги можна знайти в теці, куди ви встановили Homestead, або в корені вашого проєкту, якщо ви користуєтеся методом [встановлення для окремого проєкту](#per-project-installation).

<a name="configuring-cron-schedules"></a>
### Налаштування розкладу cron

Laravel дає зручний спосіб [планувати cron-завдання](/docs/{{version}}/scheduling), запускаючи щохвилини єдину артизан-команду `schedule:run`. Команда `schedule:run` перегляне розклад завдань, визначений у вашому файлі `routes/console.php`, щоб з'ясувати, які заплановані завдання виконати.

Якщо ви хочете, щоб команда `schedule:run` виконувалася для сайту Homestead, встановіть опцію `schedule` в `true` під час визначення сайту:

```yaml
sites:
    - map: homestead.test
      to: /home/vagrant/project1/public
      schedule: true
```

Cron-завдання для сайту буде визначено в каталозі `/etc/cron.d` віртуальної машини Homestead.

<a name="configuring-mailpit"></a>
### Налаштування Mailpit

[Mailpit](https://github.com/axllent/mailpit) дозволяє перехоплювати вашу вихідну пошту й переглядати її, фактично не надсилаючи листи одержувачам. Для початку оновіть файл `.env` свого застосунку, щоб використовувати такі поштові налаштування:

```ini
MAIL_MAILER=smtp
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
```

Щойно Mailpit буде налаштовано, ви зможете відкрити панель Mailpit за адресою `http://localhost:8025`.

<a name="configuring-minio"></a>
### Налаштування Minio

[Minio](https://github.com/minio/minio) - це сервер об'єктного сховища з відкритим кодом і API, сумісним з Amazon S3. Щоб установити Minio, оновіть свій файл `Homestead.yaml`, додавши такий параметр конфігурації до секції [features](#installing-optional-features):

    minio: true

За замовчуванням Minio доступний на порту 9600. Ви можете відкрити панель керування Minio за адресою `http://localhost:9600`. Ключ доступу за замовчуванням - `homestead`, а секретний ключ за замовчуванням - `secretkey`. Звертаючись до Minio, завжди використовуйте регіон `us-east-1`.

Щоб користуватися Minio, переконайтеся, що ваш файл `.env` містить такі опції:

```ini
AWS_USE_PATH_STYLE_ENDPOINT=true
AWS_ENDPOINT=http://localhost:9600
AWS_ACCESS_KEY_ID=homestead
AWS_SECRET_ACCESS_KEY=secretkey
AWS_DEFAULT_REGION=us-east-1
```

Щоб створити «S3»-бакети на базі Minio, додайте до свого файлу `Homestead.yaml` директиву `buckets`. Визначивши свої бакети, виконайте в терміналі команду `vagrant reload --provision`:

```yaml
buckets:
    - name: your-bucket
      policy: public
    - name: your-private-bucket
      policy: none
```

Підтримувані значення `policy`: `none`, `download`, `upload` і `public`.

<a name="laravel-dusk"></a>
### Laravel Dusk

Щоб запускати тести [Laravel Dusk](/docs/{{version}}/dusk) усередині Homestead, увімкніть [можливість webdriver](#installing-optional-features) у своїй конфігурації Homestead:

```yaml
features:
    - webdriver: true
```

Після увімкнення можливості `webdriver` виконайте в терміналі команду `vagrant reload --provision`.

<a name="sharing-your-environment"></a>
### Спільний доступ до вашого середовища

Іноді ви можете захотіти поділитися тим, над чим зараз працюєте, з колегами чи клієнтом. Vagrant має вбудовану підтримку цього через команду `vagrant share`; однак вона не працюватиме, якщо у вашому файлі `Homestead.yaml` налаштовано кілька сайтів.

Щоб розв'язати цю проблему, Homestead містить власну команду `share`. Для початку [підключіться через SSH до своєї віртуальної машини Homestead](#connecting-via-ssh) командою `vagrant ssh` і виконайте команду `share homestead.test`. Ця команда відкриє спільний доступ до сайту `homestead.test` з вашого конфігураційного файлу `Homestead.yaml`. Замість `homestead.test` ви можете підставити будь-який інший налаштований сайт:

```shell
share homestead.test
```

Після виконання команди ви побачите екран Ngrok з журналом активності та публічно доступними URL для сайту зі спільним доступом. Якщо ви хочете вказати власний регіон, піддомен чи іншу опцію виконання Ngrok, додайте їх до своєї команди `share`:

```shell
share homestead.test -region=eu -subdomain=laravel
```

Якщо вам потрібно поділитися вмістом через HTTPS, а не HTTP, скористайтеся командою `sshare` замість `share`.

> [!WARNING]
> Пам'ятайте: Vagrant за своєю природою небезпечний, і, виконуючи команду `share`, ви відкриваєте свою віртуальну машину в інтернет.

<a name="debugging-and-profiling"></a>
## Налагодження та профілювання

<a name="debugging-web-requests"></a>
### Налагодження вебзапитів за допомогою Xdebug

Homestead підтримує покрокове налагодження через [Xdebug](https://xdebug.org). Наприклад, ви можете відкрити сторінку у браузері, і PHP підключиться до вашої IDE, дозволяючи оглядати й змінювати код, що виконується.

За замовчуванням Xdebug уже запущено й готово приймати з'єднання. Якщо вам потрібно увімкнути Xdebug у CLI, виконайте команду `sudo phpenmod xdebug` усередині своєї віртуальної машини Homestead. Далі дотримуйтеся інструкцій вашої IDE, щоб увімкнути налагодження. Насамкінець налаштуйте свій браузер запускати Xdebug через розширення чи [букмарклет](https://www.jetbrains.com/phpstorm/marklets/).

> [!WARNING]
> Xdebug змушує PHP працювати значно повільніше. Щоб вимкнути Xdebug, виконайте `sudo phpdismod xdebug` усередині своєї віртуальної машини Homestead і перезапустіть сервіс FPM.

<a name="autostarting-xdebug"></a>
#### Автозапуск Xdebug

Налагоджуючи функціональні тести, які роблять запити до вебсервера, простіше запускати налагодження автоматично, ніж змінювати тести так, щоб вони передавали власний заголовок чи cookie для запуску налагодження. Щоб змусити Xdebug стартувати автоматично, змініть файл `/etc/php/7.x/fpm/conf.d/20-xdebug.ini` усередині своєї віртуальної машини Homestead і додайте таку конфігурацію:

```ini
; If Homestead.yaml contains a different subnet for the IP address, this address may be different...
xdebug.client_host = 192.168.10.1
xdebug.mode = debug
xdebug.start_with_request = yes
```

<a name="debugging-cli-applications"></a>
### Налагодження CLI-застосунків

Щоб налагодити PHP CLI-застосунок, скористайтеся shell-аліасом `xphp` усередині своєї віртуальної машини Homestead:

```shell
xphp /path/to/script
```

<a name="profiling-applications-with-blackfire"></a>
### Профілювання застосунків за допомогою Blackfire

[Blackfire](https://blackfire.io/docs/introduction) - це сервіс для профілювання вебзапитів і CLI-застосунків. Він пропонує інтерактивний інтерфейс, який показує дані профілювання у графах викликів і на часових шкалах. Його створено для використання в розробці, staging і продакшені, без накладних витрат для кінцевих користувачів. Крім того, Blackfire перевіряє продуктивність, якість і безпеку коду та налаштувань `php.ini`.

[Blackfire Player](https://blackfire.io/docs/player/index) - це застосунок з відкритим кодом для вебкраулінгу, вебтестування та вебскрапінгу, який може працювати разом з Blackfire, щоб скриптувати сценарії профілювання.

Щоб увімкнути Blackfire, скористайтеся налаштуванням «features» у своєму конфігураційному файлі Homestead:

```yaml
features:
    - blackfire:
        server_id: "server_id"
        server_token: "server_value"
        client_id: "client_id"
        client_token: "client_value"
```

Облікові дані сервера й клієнта Blackfire [потребують облікового запису Blackfire](https://blackfire.io/signup). Blackfire пропонує різні способи профілювати застосунок, зокрема CLI-інструмент і розширення для браузера. Будь ласка, [перегляньте документацію Blackfire, щоб дізнатися більше](https://blackfire.io/docs/php/integrations/laravel/index).

<a name="network-interfaces"></a>
## Мережеві інтерфейси

Властивість `networks` файлу `Homestead.yaml` налаштовує мережеві інтерфейси для вашої віртуальної машини Homestead. Ви можете налаштувати скільки завгодно інтерфейсів:

```yaml
networks:
    - type: "private_network"
      ip: "192.168.10.20"
```

Щоб увімкнути [мостовий](https://developer.hashicorp.com/vagrant/docs/networking/public_network) інтерфейс, налаштуйте параметр `bridge` для мережі й змініть тип мережі на `public_network`:

```yaml
networks:
    - type: "public_network"
      ip: "192.168.10.20"
      bridge: "en1: Wi-Fi (AirPort)"
```

Щоб увімкнути [DHCP](https://developer.hashicorp.com/vagrant/docs/networking/public_network#dhcp), просто приберіть зі своєї конфігурації опцію `ip`:

```yaml
networks:
    - type: "public_network"
      bridge: "en1: Wi-Fi (AirPort)"
```

Щоб змінити пристрій, який використовує мережа, додайте до конфігурації мережі опцію `dev`. Значення `dev` за замовчуванням - `eth0`:

```yaml
networks:
    - type: "public_network"
      ip: "192.168.10.20"
      bridge: "en1: Wi-Fi (AirPort)"
      dev: "enp2s0"
```

<a name="extending-homestead"></a>
## Розширення Homestead

Ви можете розширити Homestead за допомогою скрипта `after.sh` у корені свого каталогу Homestead. У цьому файлі ви можете додати будь-які shell-команди, потрібні для належного налаштування вашої віртуальної машини.

Налаштовуючи Homestead, Ubuntu може запитати вас, чи хочете ви зберегти оригінальну конфігурацію пакета, чи перезаписати її новим конфігураційним файлом. Щоб уникнути цього, використовуйте під час встановлення пакетів таку команду, аби не перезаписати конфігурацію, раніше записану Homestead:

```shell
sudo apt-get -y \
    -o Dpkg::Options::="--force-confdef" \
    -o Dpkg::Options::="--force-confold" \
    install package-name
```

<a name="user-customizations"></a>
### Користувацькі налаштування

Використовуючи Homestead у команді, ви можете захотіти підлаштувати Homestead під свій особистий стиль розробки. Для цього створіть файл `user-customizations.sh` у корені свого каталогу Homestead (у тому самому каталозі, що містить ваш файл `Homestead.yaml`). У цьому файлі ви можете зробити будь-які налаштування; однак `user-customizations.sh` не слід тримати під контролем версій.

<a name="provider-specific-settings"></a>
## Налаштування, специфічні для провайдера

<a name="provider-specific-virtualbox"></a>
### VirtualBox

<a name="natdnshostresolver"></a>
#### `natdnshostresolver`

За замовчуванням Homestead встановлює налаштування `natdnshostresolver` у `on`. Це дозволяє Homestead використовувати налаштування DNS вашої хост-операційної системи. Якщо ви хочете перевизначити цю поведінку, додайте до свого файлу `Homestead.yaml` такі параметри конфігурації:

```yaml
provider: virtualbox
natdnshostresolver: 'off'
```

---

Джерело: https://laravelukraine.com/docs/13.x/horizon

# Laravel Horizon

- [Вступ](#introduction)
- [Встановлення](#installation)
    - [Конфігурація](#configuration)
    - [Авторизація панелі](#dashboard-authorization)
    - [Максимум спроб для завдання](#max-job-attempts)
    - [Тайм-аут завдання](#job-timeout)
    - [Затримка перед повтором](#job-backoff)
    - [Інші опції воркерів](#other-worker-options)
    - [Приглушені завдання](#silenced-jobs)
- [Стратегії балансування](#balancing-strategies)
    - [Автоматичне балансування](#auto-balancing)
    - [Просте балансування](#simple-balancing)
    - [Без балансування](#no-balancing)
- [Оновлення Horizon](#upgrading-horizon)
- [Запуск Horizon](#running-horizon)
    - [Розгортання Horizon](#deploying-horizon)
- [Теги](#tags)
- [Сповіщення](#notifications)
- [Метрики](#metrics)
- [Видалення провалених завдань](#deleting-failed-jobs)
- [Очищення завдань із черг](#clearing-jobs-from-queues)

<a name="introduction"></a>
## Вступ

> [!NOTE]
> Перш ніж заглиблюватися в Laravel Horizon, ознайомтеся з базовими [сервісами черг](/docs/{{version}}/queues) у Laravel. Horizon доповнює черги Laravel додатковими можливостями, які можуть заплутати, якщо ви ще не знайомі з основами черг у Laravel.

[Laravel Horizon](https://github.com/laravel/horizon) надає гарну панель керування та конфігурацію в коді для ваших [черг на Redis](/docs/{{version}}/queues) у Laravel. Horizon дозволяє легко стежити за ключовими метриками системи черг - як-от пропускна здатність, час виконання та провали завдань.

Коли ви користуєтеся Horizon, уся конфігурація воркерів черг зберігається в одному простому конфігураційному файлі. Описавши конфігурацію воркерів у файлі під контролем версій, ви можете легко масштабувати чи змінювати воркери черг вашого застосунку під час розгортання.

<img src="https://laravel.com/img/docs/horizon-example.png">

<a name="installation"></a>
## Встановлення

> [!WARNING]
> Laravel Horizon вимагає, щоб ваші черги працювали на [Redis](https://redis.io). Тому переконайтеся, що підключення черги у вашому конфігураційному файлі `config/queue.php` має значення `redis`. Наразі Horizon не сумісний із Redis Cluster.

Ви можете встановити Horizon у свій проєкт через менеджер пакетів Composer:

```shell
composer require laravel/horizon
```

Після встановлення Horizon опублікуйте його ресурси артизан-командою `horizon:install`:

```shell
php artisan horizon:install
```

<a name="configuration"></a>
### Конфігурація

Після публікації ресурсів Horizon його основний конфігураційний файл буде розташований у `config/horizon.php`. Цей файл дозволяє налаштувати опції воркерів черг вашого застосунку. Кожна опція конфігурації супроводжується описом призначення, тож обов'язково ретельно перегляньте цей файл.

> [!WARNING]
> Horizon використовує всередині підключення Redis на ім'я `horizon`. Це ім'я зарезервоване й не має призначатися іншому підключенню Redis у конфігураційному файлі `database.php` чи як значення опції `use` у файлі `horizon.php`.

<a name="content-security-policy-csp-nonce"></a>
#### Nonce для Content Security Policy (CSP)

Якщо ви хотіли б використовувати [атрибут nonce](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/nonce) на тегах script і style у представленнях Horizon у межах вашої [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP), скористайтеся методом `Horizon::cspNonce`, щоб указати потрібний nonce. Зазвичай цей метод викликають у `middleware`, щоб для кожного запиту призначався новий nonce:

```php
use Closure;
use Illuminate\Http\Request;
use Laravel\Horizon\Horizon;
use Symfony\Component\HttpFoundation\Response;

public function handle(Request $request, Closure $next): Response
{
    Horizon::cspNonce('csp-nonce');

    return $next($request);
}
```

Ви можете додати це `middleware` до опції `middleware` у конфігураційному файлі `config/horizon.php` вашого застосунку:

```php
'middleware' => [
    'web',
    App\Http\Middleware\AddHorizonCspNonce::class,
],
```

<a name="environments"></a>
#### Середовища

Після встановлення головна опція конфігурації Horizon, з якою вам варто ознайомитися, - `environments`. Ця опція є масивом середовищ, у яких працює ваш застосунок, і визначає опції процесів-воркерів для кожного з них. За замовчуванням цей запис містить середовища `production` і `local`. Проте ви вільні додати за потреби й інші:

```php
'environments' => [
    'production' => [
        'supervisor-1' => [
            'maxProcesses' => 10,
            'balanceMaxShift' => 1,
            'balanceCooldown' => 3,
        ],
    ],

    'local' => [
        'supervisor-1' => [
            'maxProcesses' => 3,
        ],
    ],
],
```

Ви також можете визначити середовище-джокер (`*`), яке буде використано, коли не знайдено жодного іншого відповідного середовища:

```php
'environments' => [
    // ...

    '*' => [
        'supervisor-1' => [
            'maxProcesses' => 3,
        ],
    ],
],
```

Коли ви запускаєте Horizon, він використає опції конфігурації процесів-воркерів для того середовища, у якому працює ваш застосунок. Зазвичай середовище визначається значенням [змінної оточення](/docs/{{version}}/configuration#determining-the-current-environment) `APP_ENV`. Наприклад, стандартне середовище Horizon `local` налаштоване запускати три процеси-воркери й автоматично балансувати їхню кількість між чергами. Стандартне середовище `production` налаштоване запускати максимум 10 процесів-воркерів і автоматично балансувати їхню кількість між чергами.

> [!WARNING]
> Переконайтеся, що частина `environments` вашого конфігураційного файлу `horizon` містить запис для кожного [середовища](/docs/{{version}}/configuration#environment-configuration), у якому ви плануєте запускати Horizon.

<a name="supervisors"></a>
#### Супервізори

Як видно зі стандартного конфігураційного файлу Horizon, кожне середовище може містити один чи кілька «супервізорів». За замовчуванням конфігураційний файл описує супервізор як `supervisor-1`; проте ви вільні називати супервізори як завгодно. Кожен супервізор, по суті, відповідає за «нагляд» за групою процесів-воркерів і дбає про балансування воркерів між чергами.

Ви можете додати до заданого середовища додаткові супервізори, якщо хочете описати нову групу процесів-воркерів для цього середовища. Це може знадобитися, якщо ви хочете задати іншу стратегію балансування чи кількість процесів-воркерів для певної черги вашого застосунку.

<a name="maintenance-mode"></a>
#### Режим обслуговування

Доки ваш застосунок перебуває в [режимі обслуговування](/docs/{{version}}/configuration#maintenance-mode), Horizon не оброблятиме завдань із черг, якщо в конфігураційному файлі Horizon опція супервізора `force` не має значення `true`:

```php
'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'force' => true,
        ],
    ],
],
```

<a name="default-values"></a>
#### Значення за замовчуванням

У стандартному конфігураційному файлі Horizon ви помітите опцію `defaults`. Ця опція задає значення за замовчуванням для [супервізорів](#supervisors) вашого застосунку. Стандартні значення супервізора буде об'єднано з конфігурацією супервізора для кожного середовища, тож ви зможете уникнути зайвих повторів під час опису супервізорів.

<a name="dashboard-authorization"></a>
### Авторизація панелі

Панель Horizon доступна за маршрутом `/horizon`. За замовчуванням ви зможете відкрити її лише в середовищі `local`. Проте у вашому файлі `app/Providers/HorizonServiceProvider.php` є визначення [гейта авторизації](/docs/{{version}}/authorization#gates). Цей гейт керує доступом до Horizon у **нелокальних** середовищах. Ви вільні змінювати його як потрібно, щоб обмежити доступ до вашої установки Horizon:

```php
/**
 * Register the Horizon gate.
 *
 * This gate determines who can access Horizon in non-local environments.
 */
protected function gate(): void
{
    Gate::define('viewHorizon', function (User $user) {
        return in_array($user->email, [
            'taylor@laravel.com',
        ]);
    });
}
```

<a name="alternative-authentication-strategies"></a>
#### Альтернативні стратегії автентифікації

Пам'ятайте, що Laravel автоматично впроваджує автентифікованого користувача в замикання гейта. Якщо ваш застосунок захищає Horizon іншим способом - наприклад, обмеженнями за IP, - вашим користувачам Horizon може не знадобитися «входити». Тому вам треба буде змінити сигнатуру замикання вище з `function (User $user)` на `function (User $user = null)`, щоб Laravel не вимагав автентифікації.

<a name="max-job-attempts"></a>
### Максимум спроб для завдання

> [!NOTE]
> Перш ніж налаштовувати ці опції, переконайтеся, що ви знайомі зі стандартними [сервісами черг](/docs/{{version}}/queues#max-job-attempts-and-timeout) у Laravel і з поняттям «спроб».

Ви можете задати максимальну кількість спроб, які може витратити завдання, у конфігурації супервізора:

```php
'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'tries' => 10,
        ],
    ],
],
```

> [!NOTE]
> Ця опція подібна до опції `--tries` під час обробки черг артизан-командою.

Налаштування опції `tries` вкрай важливе, коли ви користуєтеся `middleware` на кшталт `WithoutOverlapping` чи `RateLimited`, адже вони витрачають спроби. Щоб це врахувати, підлаштуйте значення `tries` або на рівні супервізора, або через властивість `$tries` у класі завдання.

Якщо ви не задасте опцію `tries`, Horizon за замовчуванням використає одну спробу - хіба що клас завдання визначає `$tries`, що має перевагу над конфігурацією Horizon.

Значення `tries` чи `$tries`, встановлене в 0, дозволяє необмежену кількість спроб, що ідеально, коли кількість спроб наперед невідома. Щоб запобігти нескінченним провалам, ви можете обмежити кількість дозволених винятків, задавши властивість `$maxExceptions` у класі завдання.

<a name="job-timeout"></a>
### Тайм-аут завдання

Так само ви можете задати значення `timeout` на рівні супервізора - воно визначає, скільки секунд процес-воркер може виконувати завдання, перш ніж його буде примусово завершено. Після завершення завдання буде або повторено, або позначено як провалене - залежно від конфігурації вашої черги:

```php
'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'timeout' => 60,
        ],
    ],
],
```

> [!WARNING]
> Зі стратегією балансування `auto` Horizon вважатиме воркери, що виконують завдання, «завислими» й примусово вб'є їх після тайм-ауту Horizon під час зменшення масштабу. Завжди стежте, щоб тайм-аут Horizon був більшим за будь-який тайм-аут на рівні завдання, інакше завдання можуть бути перервані на середині виконання. Крім того, значення `timeout` завжди має бути щонайменше на кілька секунд меншим за значення `retry_after`, задане у вашому конфігураційному файлі `config/queue.php`. Інакше ваші завдання можуть бути оброблені двічі.

<a name="job-backoff"></a>
### Затримка перед повтором

Ви можете задати значення `backoff` на рівні супервізора, щоб указати, як довго Horizon має чекати, перш ніж повторити завдання, яке зіткнулося з необробленим винятком:

```php
'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'backoff' => 10,
        ],
    ],
],
```

Ви також можете налаштувати «експоненційні» затримки, передавши в `backoff` масив. У цьому прикладі затримка перед повтором становитиме 1 секунду для першої спроби, 5 секунд для другої, 10 секунд для третьої й 10 секунд для кожної наступної, якщо спроби ще лишилися:

```php
'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'backoff' => [1, 5, 10],
        ],
    ],
],
```

<a name="other-worker-options"></a>
### Інші опції воркерів

Окрім `tries`, `timeout` і `backoff`, кожен супервізор приймає ще кілька опцій, які керують поведінкою його процесів-воркерів і тим, коли їх автоматично перезапускати. Періодичний перезапуск воркерів - хороша практика для довготривалих процесів, адже вона допомагає захиститися від витоків пам'яті:

```php
'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'memory' => 128,
            'maxJobs' => 1000,
            'maxTime' => 3600,
            'sleep' => 3,
            'rest' => 0,
            'nice' => 0,
        ],
    ],
],
```

<div class="content-list" markdown="1">

- `memory` визначає максимальний обсяг пам'яті в мегабайтах, який може спожити один процес-воркер, перш ніж його буде перезапущено. За замовчуванням це значення - `128`.
- `maxJobs` визначає кількість завдань, які воркер має обробити перед перезапуском. Значення `0` означає, що воркери не перезапускатимуться за кількістю оброблених завдань. За замовчуванням це значення - `0`.
- `maxTime` визначає кількість секунд, які воркер має пропрацювати перед перезапуском. Значення `0` означає, що воркери не перезапускатимуться за часом. За замовчуванням це значення - `0`.
- `sleep` визначає кількість секунд, які воркер має почекати за відсутності завдань, перш ніж знову опитати чергу. За замовчуванням це значення - `3`.
- `rest` визначає кількість секунд паузи між обробкою кожного завдання. За замовчуванням це значення - `0`.
- `nice` визначає «люб'язність» (пріоритет планування) процесів-воркерів. Вище значення дає процесу нижчий пріоритет. За замовчуванням це значення - `0`.

</div>

<a name="silenced-jobs"></a>
### Приглушені завдання

Іноді вам можуть бути нецікаві певні завдання, які відправляє ваш застосунок чи сторонні пакети. Замість того щоб вони займали місце у вашому списку «Completed Jobs», ви можете їх приглушити. Для початку додайте ім'я класу завдання до опції конфігурації `silenced` у файлі `horizon` вашого застосунку:

```php
'silenced' => [
    App\Jobs\ProcessPodcast::class,
],
```

Окрім приглушення окремих класів завдань, Horizon підтримує також приглушення за [тегами](#tags). Це стане в пригоді, якщо ви хочете сховати кілька завдань зі спільним тегом:

```php
'silenced_tags' => [
    'notifications'
],
```

Або ж завдання, яке ви хочете приглушити, може реалізувати інтерфейс `Laravel\Horizon\Contracts\Silenced`. Якщо завдання реалізує цей інтерфейс, його буде приглушено автоматично, навіть якщо його немає в масиві конфігурації `silenced`:

```php
use Laravel\Horizon\Contracts\Silenced;

class ProcessPodcast implements ShouldQueue, Silenced
{
    use Queueable;

    // ...
}
```

<a name="balancing-strategies"></a>
## Стратегії балансування

Кожен супервізор може обробляти одну чи кілька черг, але, на відміну від стандартної системи черг Laravel, Horizon дозволяє обрати одну з трьох стратегій балансування воркерів: `auto`, `simple` та `false`.

<a name="auto-balancing"></a>
### Автоматичне балансування

Стратегія `auto`, яка є стандартною, підлаштовує кількість процесів-воркерів на чергу відповідно до її поточного навантаження. Наприклад, якщо у вашій черзі `notifications` 1000 завдань в очікуванні, а черга `default` порожня, Horizon виділить більше воркерів на чергу `notifications`, доки та не спорожніє.

Зі стратегією `auto` ви можете також налаштувати опції конфігурації `minProcesses` та `maxProcesses`:

<div class="content-list" markdown="1">

- `minProcesses` визначає мінімальну кількість процесів-воркерів на чергу. Це значення має бути більшим за 1 або дорівнювати йому.
- `maxProcesses` визначає максимальну загальну кількість процесів-воркерів, до якої Horizon може масштабуватися по всіх чергах. Зазвичай це значення має бути більшим за кількість черг, помножену на `minProcesses`. Щоб супервізор не породжував жодних процесів, ви можете задати тут 0.

</div>

Наприклад, ви можете налаштувати Horizon тримати щонайменше один процес на чергу й масштабуватися до 10 процесів-воркерів загалом:

```php
'environments' => [
    'production' => [
        'supervisor-1' => [
            'connection' => 'redis',
            'queue' => ['default', 'notifications'],
            'balance' => 'auto',
            'autoScalingStrategy' => 'time',
            'minProcesses' => 1,
            'maxProcesses' => 10,
            'balanceMaxShift' => 1,
            'balanceCooldown' => 3,
        ],
    ],
],
```

Опція конфігурації `autoScalingStrategy` визначає, як Horizon призначатиме чергам більше процесів-воркерів. Ви можете обрати одну з трьох стратегій:

<div class="content-list" markdown="1">

- Стратегія `time` призначатиме воркерів за загальним оцінюваним часом, потрібним, щоб розібрати чергу.
- Стратегія `size` призначатиме воркерів за загальною кількістю завдань у черзі.
- Стратегія `log` призначатиме воркерів за логарифмом кількості завдань у черзі. Це запобігає тому, щоб істотно більша черга отримувала непропорційно велику частку воркерів.

</div>

Значення конфігурації `balanceMaxShift` і `balanceCooldown` визначають, як швидко Horizon масштабуватиметься під потребу у воркерах. У прикладі вище щотри секунди створюватиметься чи знищуватиметься максимум один новий процес. Ви вільні підлаштувати ці значення під потреби вашого застосунку.

<a name="auto-queue-priorities"></a>
#### Пріоритети черг і автоматичне балансування

Зі стратегією балансування `auto` Horizon не забезпечує суворого пріоритету між чергами. Порядок черг у конфігурації супервізора не впливає на те, як призначаються процеси-воркери. Натомість Horizon спирається на обрану `autoScalingStrategy`, щоб динамічно розподіляти процеси-воркери за навантаженням черг.

Наприклад, у наведеній нижче конфігурації черга high не має пріоритету над чергою default, попри те що стоїть у списку першою:

```php
'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'queue' => ['high', 'default'],
            'minProcesses' => 1,
            'maxProcesses' => 10,
        ],
    ],
],
```

Якщо вам потрібен відносний пріоритет між чергами, ви можете описати кілька супервізорів і явно розподілити обчислювальні ресурси:

```php
'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'queue' => ['default'],
            'minProcesses' => 1,
            'maxProcesses' => 10,
        ],
        'supervisor-2' => [
            // ...
            'queue' => ['images'],
            'minProcesses' => 1,
            'maxProcesses' => 1,
        ],
    ],
],
```

У цьому прикладі черга default може масштабуватися до 10 процесів, тоді як черга `images` обмежена одним процесом. Така конфігурація гарантує, що ваші черги масштабуватимуться незалежно.

> [!NOTE]
> Відправляючи ресурсомісткі завдання, іноді найкраще призначити їм окрему чергу з обмеженим значенням `maxProcesses`. Інакше ці завдання можуть з'їсти надмірно багато ресурсів CPU й перевантажити вашу систему.

<a name="simple-balancing"></a>
### Просте балансування

Стратегія `simple` розподіляє процеси-воркери рівномірно між указаними чергами. З цією стратегією Horizon не масштабує кількість процесів-воркерів автоматично, а використовує фіксовану кількість:

```php
'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'queue' => ['default', 'notifications'],
            'balance' => 'simple',
            'processes' => 10,
        ],
    ],
],
```

У прикладі вище Horizon призначить кожній черзі по 5 процесів, розділивши загальні 10 порівну.

Якщо ви хочете керувати кількістю процесів-воркерів для кожної черги окремо, опишіть кілька супервізорів:

```php
'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'queue' => ['default'],
            'balance' => 'simple',
            'processes' => 10,
        ],
        'supervisor-notifications' => [
            // ...
            'queue' => ['notifications'],
            'balance' => 'simple',
            'processes' => 2,
        ],
    ],
],
```

За такої конфігурації Horizon призначить 10 процесів черзі `default` і 2 процеси черзі `notifications`.

<a name="no-balancing"></a>
### Без балансування

Коли опція `balance` має значення `false`, Horizon обробляє черги строго в тому порядку, у якому їх перелічено, - подібно до стандартної системи черг Laravel. Проте він усе одно масштабуватиме кількість процесів-воркерів, якщо завдання почнуть накопичуватися:

```php
'environments' => [
    'production' => [
        'supervisor-1' => [
            // ...
            'queue' => ['default', 'notifications'],
            'balance' => false,
            'minProcesses' => 1,
            'maxProcesses' => 10,
        ],
    ],
],
```

У прикладі вище завдання з черги `default` завжди матимуть пріоритет над завданнями з черги `notifications`. Наприклад, якщо в `default` 1000 завдань, а в `notifications` лише 10, Horizon повністю обробить усі завдання `default`, перш ніж візьметься за `notifications`.

Ви можете керувати здатністю Horizon масштабувати процеси-воркери через опції `minProcesses` та `maxProcesses`:

<div class="content-list" markdown="1">

- `minProcesses` визначає мінімальну загальну кількість процесів-воркерів. Це значення має бути більшим за 1 або дорівнювати йому.
- `maxProcesses` визначає максимальну загальну кількість процесів-воркерів, до якої Horizon може масштабуватися.

</div>

<a name="upgrading-horizon"></a>
## Оновлення Horizon

Оновлюючись до нової мажорної версії Horizon, обов'язково уважно перегляньте [посібник з оновлення](https://github.com/laravel/horizon/blob/master/UPGRADE.md).

<a name="running-horizon"></a>
## Запуск Horizon

Коли ви налаштували супервізори й воркери в конфігураційному файлі `config/horizon.php` вашого застосунку, запустіть Horizon артизан-командою `horizon`. Ця єдина команда запустить усі налаштовані процеси-воркери для поточного середовища:

```shell
php artisan horizon
```

Ви можете призупинити процес Horizon і знову дозволити йому обробляти завдання артизан-командами `horizon:pause` та `horizon:continue`:

```shell
php artisan horizon:pause

php artisan horizon:continue
```

Ви також можете призупиняти й продовжувати роботу окремих [супервізорів](#supervisors) Horizon артизан-командами `horizon:pause-supervisor` та `horizon:continue-supervisor`:

```shell
php artisan horizon:pause-supervisor supervisor-1

php artisan horizon:continue-supervisor supervisor-1
```

Перевірити поточний стан процесу Horizon можна артизан-командою `horizon:status`:

```shell
php artisan horizon:status
```

Перевірити поточний стан окремого [супервізора](#supervisors) Horizon можна артизан-командою `horizon:supervisor-status`:

```shell
php artisan horizon:supervisor-status supervisor-1
```

Ви можете плавно завершити процес Horizon артизан-командою `horizon:terminate`. Усі завдання, які наразі обробляються, буде завершено, після чого Horizon зупиниться:

```shell
php artisan horizon:terminate
```

<a name="automatically-restarting-horizon"></a>
#### Автоматичний перезапуск Horizon

Під час локальної розробки ви можете запускати команду `horizon:listen`. Із командою `horizon:listen` вам не доведеться вручну перезапускати Horizon, щоб підхопити оновлений код. Перш ніж користуватися цією можливістю, переконайтеся, що у вашому локальному середовищі розробки встановлено [Node](https://nodejs.org). Крім того, встановіть у своєму проєкті бібліотеку стеження за файлами [Chokidar](https://github.com/paulmillr/chokidar):

```shell
npm install --save-dev chokidar
```

Коли Chokidar встановлено, ви можете запускати Horizon командою `horizon:listen`:

```shell
php artisan horizon:listen
```

Працюючи в Docker чи Vagrant, скористайтеся опцією `--poll`:

```shell
php artisan horizon:listen --poll
```

Ви можете налаштувати каталоги й файли, за якими слід стежити, через опцію конфігурації `watch` у файлі `config/horizon.php` вашого застосунку:

```php
'watch' => [
    'app',
    'bootstrap',
    'config',
    'database',
    'public/**/*.php',
    'resources/**/*.php',
    'routes',
    'composer.lock',
    '.env',
],
```

<a name="deploying-horizon"></a>
### Розгортання Horizon

Коли ви готові розгорнути Horizon на справжньому сервері вашого застосунку, налаштуйте монітор процесів, який стежитиме за командою `php artisan horizon` і перезапускатиме її, якщо та несподівано завершиться. Не хвилюйтеся - нижче ми розповімо, як встановити монітор процесів.

Під час розгортання застосунку вам слід дати процесу Horizon команду завершитися, щоб монітор процесів перезапустив його вже з вашими змінами в коді:

```shell
php artisan horizon:terminate
```

<a name="installing-supervisor"></a>
#### Встановлення Supervisor

Supervisor - це монітор процесів для Linux, який автоматично перезапустить ваш процес `horizon`, якщо той зупиниться. Щоб встановити Supervisor на Ubuntu, скористайтеся такою командою. Якщо ви не на Ubuntu, ви, найімовірніше, зможете встановити Supervisor через менеджер пакетів вашої операційної системи:

```shell
sudo apt-get install supervisor
```

> [!NOTE]
> Якщо налаштування Supervisor власноруч видається надто складним, розгляньте [Laravel Cloud](https://cloud.laravel.com), який може керувати фоновими процесами ваших застосунків Laravel.

<a name="supervisor-configuration"></a>
#### Конфігурація Supervisor

Конфігураційні файли Supervisor зазвичай зберігаються в каталозі `/etc/supervisor/conf.d` вашого сервера. У цьому каталозі ви можете створити будь-яку кількість файлів, які вказують supervisor, як стежити за вашими процесами. Створімо, наприклад, файл `horizon.conf`, який запускає процес `horizon` і стежить за ним:

```ini
[program:horizon]
process_name=%(program_name)s
command=php /home/forge/example.com/artisan horizon
autostart=true
autorestart=true
user=forge
redirect_stderr=true
stdout_logfile=/home/forge/example.com/horizon.log
stopwaitsecs=3600
```

Описуючи конфігурацію Supervisor, переконайтеся, що значення `stopwaitsecs` більше за кількість секунд, які займає ваше найдовше завдання. Інакше Supervisor може вбити завдання до того, як його буде оброблено.

> [!WARNING]
> Хоча наведені вище приклади підходять для серверів на Ubuntu, розташування й розширення конфігураційних файлів Supervisor можуть відрізнятися в інших серверних операційних системах. Докладніше читайте в документації до вашого сервера.

<a name="starting-supervisor"></a>
#### Запуск Supervisor

Коли конфігураційний файл створено, ви можете оновити конфігурацію Supervisor і запустити процеси під наглядом такими командами:

```shell
sudo supervisorctl reread

sudo supervisorctl update

sudo supervisorctl start horizon
```

> [!NOTE]
> Докладніше про роботу з Supervisor читайте в [документації Supervisor](http://supervisord.org/index.html).

<a name="tags"></a>
## Теги

Horizon дозволяє призначати завданням «теги» - зокрема поштовим класам, подіям бродкастингу, сповіщенням і слухачам подій у черзі. Ба більше, Horizon розумно й автоматично проставляє теги більшості завдань залежно від моделей Eloquent, прикріплених до завдання. Погляньте, наприклад, на таке завдання:

```php
<?php

namespace App\Jobs;

use App\Models\Video;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;

class RenderVideo implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new job instance.
     */
    public function __construct(
        public Video $video,
    ) {}

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        // ...
    }
}
```

Якщо це завдання поставлено в чергу з екземпляром `App\Models\Video`, чий атрибут `id` дорівнює `1`, воно автоматично отримає тег `App\Models\Video:1`. Так стається тому, що Horizon шукає у властивостях завдання будь-які моделі Eloquent. Якщо їх знайдено, Horizon розумно проставляє тег за іменем класу моделі та її первинним ключем:

```php
use App\Jobs\RenderVideo;
use App\Models\Video;

$video = Video::find(1);

RenderVideo::dispatch($video);
```

<a name="manually-tagging-jobs"></a>
#### Ручне проставляння тегів завданням

Якщо ви хочете задати теги для одного зі своїх об'єктів у черзі вручну, визначте в його класі метод `tags`:

```php
class RenderVideo implements ShouldQueue
{
    /**
     * Get the tags that should be assigned to the job.
     *
     * @return array<int, string>
     */
    public function tags(): array
    {
        return ['render', 'video:'.$this->video->id];
    }
}
```

<a name="manually-tagging-event-listeners"></a>
#### Ручне проставляння тегів слухачам подій

Отримуючи теги для слухача подій у черзі, Horizon автоматично передасть екземпляр події до методу `tags`, тож ви зможете додати дані події до тегів:

```php
class SendRenderNotifications implements ShouldQueue
{
    /**
     * Get the tags that should be assigned to the listener.
     *
     * @return array<int, string>
     */
    public function tags(VideoRendered $event): array
    {
        return ['video:'.$event->video->id];
    }
}
```

<a name="notifications"></a>
## Сповіщення

> [!WARNING]
> Налаштовуючи Horizon на надсилання сповіщень у Slack чи SMS, перегляньте [передумови для відповідного каналу сповіщень](/docs/{{version}}/notifications).

Якщо ви хочете отримувати сповіщення, коли в одній із ваших черг довгий час очікування, скористайтеся методами `Horizon::routeMailNotificationsTo`, `Horizon::routeSlackNotificationsTo` та `Horizon::routeSmsNotificationsTo`. Викликати ці методи можна в методі `boot` вашого `App\Providers\HorizonServiceProvider`:

```php
/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    parent::boot();

    Horizon::routeSmsNotificationsTo('15556667777');
    Horizon::routeMailNotificationsTo('example@example.com');
    Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel');
}
```

<a name="configuring-notification-wait-time-thresholds"></a>
#### Налаштування порогів часу очікування для сповіщень

Ви можете налаштувати, скільки секунд вважається «довгим очікуванням», у конфігураційному файлі `config/horizon.php` вашого застосунку. Опція конфігурації `waits` у цьому файлі дозволяє задати поріг довгого очікування для кожної комбінації підключення / черги. Для не описаних комбінацій підключення / черги поріг за замовчуванням становитиме 60 секунд:

```php
'waits' => [
    'redis:critical' => 30,
    'redis:default' => 60,
    'redis:batch' => 120,
],
```

Поріг черги, встановлений у `0`, вимкне сповіщення про довге очікування для цієї черги.

<a name="metrics"></a>
## Метрики

Horizon містить панель метрик, яка показує інформацію про час очікування й пропускну здатність ваших завдань і черг. Щоб наповнити цю панель, налаштуйте артизан-команду Horizon `snapshot` на запуск щоп'ять хвилин у файлі `routes/console.php` вашого застосунку:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('horizon:snapshot')->everyFiveMinutes();
```

Ви можете налаштувати, скільки знімків Horizon зберігає для своїх графіків метрик, через опцію `metrics.trim_snapshots` у конфігураційному файлі `config/horizon.php` вашого застосунку. Оскільки ця опція обмежує кількість знімків, а не їхній вік, період зберігання залежить від того, як часто виконується команда `horizon:snapshot`:

```php
'metrics' => [
    'trim_snapshots' => [
        'job' => 24,
        'queue' => 24,
    ],
],
```

Якщо ви хочете видалити всі дані метрик, викличте артизан-команду `horizon:clear-metrics`:

```shell
php artisan horizon:clear-metrics
```

<a name="deleting-failed-jobs"></a>
## Видалення провалених завдань

Якщо ви хочете видалити провалене завдання, скористайтеся командою `horizon:forget`. Команда `horizon:forget` приймає єдиним аргументом ID чи UUID проваленого завдання:

```shell
php artisan horizon:forget 5
```

Якщо ви хочете видалити всі провалені завдання, додайте до команди `horizon:forget` опцію `--all`:

```shell
php artisan horizon:forget --all
```

<a name="clearing-jobs-from-queues"></a>
## Очищення завдань із черг

Якщо ви хочете видалити всі завдання зі стандартної черги вашого застосунку, скористайтеся артизан-командою `horizon:clear`:

```shell
php artisan horizon:clear
```

Ви можете додати опцію `queue`, щоб видалити завдання з конкретної черги:

```shell
php artisan horizon:clear --queue=emails
```

---

Джерело: https://laravelukraine.com/docs/13.x/mix

# Laravel Mix

- [Вступ](#introduction)

<a name="introduction"></a>
## Вступ

> [!WARNING]
> Laravel Mix - застарілий пакет, який більше активно не підтримується. Як сучасну альтернативу можна використовувати [Vite](/docs/{{version}}/vite).

[Laravel Mix](https://github.com/laravel-mix/laravel-mix) - пакет, розроблений Джеффрі Веєм, творцем [Laracasts](https://laracasts.com), - надає плавний API для опису кроків збірки [webpack](https://webpack.js.org) у вашому застосунку Laravel через кілька поширених препроцесорів CSS і JavaScript.

Іншими словами, Mix робить компіляцію та мініфікацію файлів CSS і JavaScript вашого застосунку елементарною справою. Простими ланцюжками методів ви можете плавно описати свій конвеєр ресурсів. Наприклад:

```js
mix.js('resources/js/app.js', 'public/js')
    .postCss('resources/css/app.css', 'public/css');
```

Якщо ви колись губилися й почувалися приголомшеними, беручись за webpack і компіляцію ресурсів, Laravel Mix вам сподобається. Проте користуватися ним під час розробки застосунку не обов'язково: ви вільні взяти будь-який інший інструмент для конвеєра ресурсів - або не брати жодного.

> [!NOTE]
> У нових установках Laravel на зміну Laravel Mix прийшов Vite. Документацію щодо Mix шукайте на [офіційному сайті Laravel Mix](https://laravel-mix.com/). Якщо ви хочете перейти на Vite, перегляньте наш [посібник з міграції на Vite](https://github.com/laravel/vite-plugin/blob/main/UPGRADE.md#migrating-from-laravel-mix-to-vite).

---

Джерело: https://laravelukraine.com/docs/13.x/octane

# Laravel Octane

- [Вступ](#introduction)
- [Встановлення](#installation)
- [Вимоги до сервера](#server-prerequisites)
    - [FrankenPHP](#frankenphp)
    - [RoadRunner](#roadrunner)
    - [Swoole](#swoole)
- [Обслуговування вашого застосунку](#serving-your-application)
    - [Обслуговування застосунку через HTTPS](#serving-your-application-via-https)
    - [Обслуговування застосунку через Nginx](#serving-your-application-via-nginx)
    - [Відстеження змін у файлах](#watching-for-file-changes)
    - [Визначення кількості воркерів](#specifying-the-worker-count)
    - [Визначення максимальної кількості запитів](#specifying-the-max-request-count)
    - [Визначення максимального часу виконання](#specifying-the-max-execution-time)
    - [Перезавантаження воркерів](#reloading-the-workers)
    - [Зупинка сервера](#stopping-the-server)
- [Впровадження залежностей і Octane](#dependency-injection-and-octane)
    - [Впровадження контейнера](#container-injection)
    - [Впровадження запиту](#request-injection)
    - [Впровадження репозиторію конфігурації](#configuration-repository-injection)
- [Керування витоками пам'яті](#managing-memory-leaks)
- [Паралельні завдання](#concurrent-tasks)
- [Тіки та інтервали](#ticks-and-intervals)
- [Кеш Octane](#the-octane-cache)
    - [Інтервали кешу](#cache-intervals)
- [Таблиці](#tables)

<a name="introduction"></a>
## Вступ

[Laravel Octane](https://github.com/laravel/octane) різко підвищує продуктивність вашого застосунку, обслуговуючи його за допомогою потужних серверів застосунків, зокрема [FrankenPHP](https://frankenphp.dev/), [Open Swoole](https://openswoole.com/), [Swoole](https://github.com/swoole/swoole-src) і [RoadRunner](https://roadrunner.dev). Octane завантажує ваш застосунок один раз, тримає його в пам'яті, а потім подає йому запити на надзвуковій швидкості.

<a name="installation"></a>
## Встановлення

Octane можна встановити через менеджер пакетів Composer:

```shell
composer require laravel/octane
```

Після встановлення Octane ви можете виконати артизан-команду `octane:install`, яка встановить конфігураційний файл Octane у ваш застосунок:

```shell
php artisan octane:install
```

<a name="server-prerequisites"></a>
## Вимоги до сервера

<a name="frankenphp"></a>
### FrankenPHP

[FrankenPHP](https://frankenphp.dev) - це сервер PHP-застосунків, написаний на Go, який підтримує сучасні вебможливості на кшталт early hints, стиснення Brotli та Zstandard. Коли ви встановлюєте Octane і обираєте FrankenPHP як свій сервер, Octane автоматично завантажить і встановить бінарник FrankenPHP за вас.

<a name="frankenphp-via-laravel-sail"></a>
#### FrankenPHP через Laravel Sail

Якщо ви плануєте розробляти застосунок за допомогою [Laravel Sail](/docs/{{version}}/sail), виконайте такі команди, щоб установити Octane і FrankenPHP:

```shell
./vendor/bin/sail up

./vendor/bin/sail composer require laravel/octane
```

Далі скористайтеся артизан-командою `octane:install`, щоб установити бінарник FrankenPHP:

```shell
./vendor/bin/sail artisan octane:install --server=frankenphp
```

Насамкінець додайте змінну оточення `SUPERVISOR_PHP_COMMAND` до визначення сервісу `laravel.test` у файлі `docker-compose.yml` вашого застосунку. Ця змінна оточення міститиме команду, якою Sail обслуговуватиме ваш застосунок через Octane замість девсервера PHP:

```yaml
services:
  laravel.test:
    environment:
      SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=frankenphp --host=0.0.0.0 --admin-port=2019 --port='${APP_PORT:-80}'" # [tl! add]
      XDG_CONFIG_HOME:  /var/www/html/config # [tl! add]
      XDG_DATA_HOME:  /var/www/html/data # [tl! add]
```

Щоб увімкнути HTTPS, HTTP/2 і HTTP/3, застосуйте натомість такі зміни:

```yaml
services:
  laravel.test:
    ports:
        - '${APP_PORT:-80}:80'
        - '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
        - '443:443' # [tl! add]
        - '443:443/udp' # [tl! add]
    environment:
      SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --host=localhost --port=443 --admin-port=2019 --https" # [tl! add]
      XDG_CONFIG_HOME:  /var/www/html/config # [tl! add]
      XDG_DATA_HOME:  /var/www/html/data # [tl! add]
```

Зазвичай до вашого Sail-застосунку на FrankenPHP слід звертатися через `https://localhost`, оскільки використання `https://127.0.0.1` потребує додаткового налаштування і [не рекомендується](https://frankenphp.dev/docs/known-issues/#using-https127001-with-docker).

<a name="frankenphp-via-docker"></a>
#### FrankenPHP через Docker

Використання офіційних Docker-образів FrankenPHP може дати кращу продуктивність і доступ до додаткових розширень, яких немає у статичних інсталяціях FrankenPHP. Крім того, офіційні Docker-образи дозволяють запускати FrankenPHP на платформах, які він нативно не підтримує, наприклад на Windows. Офіційні Docker-образи FrankenPHP підходять як для локальної розробки, так і для продакшену.

Ви можете взяти наведений нижче Dockerfile за відправну точку для контейнеризації вашого Laravel-застосунку на FrankenPHP:

```dockerfile
FROM dunglas/frankenphp

RUN install-php-extensions \
    pcntl
    # Add other PHP extensions here...

COPY . /app

ENTRYPOINT ["php", "artisan", "octane:frankenphp"]
```

Далі, під час розробки, ви можете скористатися таким файлом Docker Compose для запуску застосунку:

```yaml
# compose.yaml
services:
  frankenphp:
    build:
      context: .
    entrypoint: php artisan octane:frankenphp --workers=1 --max-requests=1
    ports:
      - "8000:8000"
    volumes:
      - .:/app
```

Якщо до команди `php artisan octane:start` явно передано опцію `--log-level`, Octane використовуватиме нативний логер FrankenPHP і, якщо не налаштовано інакше, видаватиме структуровані JSON-логи.

Докладніше про запуск FrankenPHP з Docker дивіться в [офіційній документації FrankenPHP](https://frankenphp.dev/docs/docker/).

<a name="frankenphp-caddyfile"></a>
#### Власна конфігурація Caddyfile

Використовуючи FrankenPHP, ви можете вказати власний Caddyfile за допомогою опції `--caddyfile` під час запуску Octane:

```shell
php artisan octane:start --server=frankenphp --caddyfile=/path/to/your/Caddyfile
```

Це дозволяє налаштувати FrankenPHP поза межами параметрів за замовчуванням: додати власний `middleware`, налаштувати складнішу маршрутизацію чи задати власні директиви. Докладніше про синтаксис Caddyfile і параметри конфігурації дивіться в [офіційній документації Caddy](https://caddyserver.com/docs/caddyfile).

<a name="roadrunner"></a>
### RoadRunner

[RoadRunner](https://roadrunner.dev) працює на бінарнику RoadRunner, написаному на Go. Під час першого запуску сервера Octane на базі RoadRunner Octane запропонує завантажити й установити бінарник RoadRunner за вас.

<a name="roadrunner-via-laravel-sail"></a>
#### RoadRunner через Laravel Sail

Якщо ви плануєте розробляти застосунок за допомогою [Laravel Sail](/docs/{{version}}/sail), виконайте такі команди, щоб установити Octane і RoadRunner:

```shell
./vendor/bin/sail up

./vendor/bin/sail composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http
```

Далі запустіть оболонку Sail і скористайтеся виконуваним файлом `rr`, щоб отримати останню Linux-збірку бінарника RoadRunner:

```shell
./vendor/bin/sail shell

# Within the Sail shell...
./vendor/bin/rr get-binary
```

Потім додайте змінну оточення `SUPERVISOR_PHP_COMMAND` до визначення сервісу `laravel.test` у файлі `docker-compose.yml` вашого застосунку. Ця змінна оточення міститиме команду, якою Sail обслуговуватиме ваш застосунок через Octane замість девсервера PHP:

```yaml
services:
  laravel.test:
    environment:
      SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=roadrunner --host=0.0.0.0 --rpc-port=6001 --port='${APP_PORT:-80}'" # [tl! add]
```

Насамкінець переконайтеся, що бінарник `rr` є виконуваним, і зберіть свої образи Sail:

```shell
chmod +x ./rr

./vendor/bin/sail build --no-cache
```

<a name="swoole"></a>
### Swoole

Якщо ви плануєте використовувати сервер застосунків Swoole для обслуговування вашого застосунку на Laravel Octane, вам потрібно встановити PHP-розширення Swoole. Зазвичай це робиться через PECL:

```shell
pecl install swoole
```

<a name="openswoole"></a>
#### Open Swoole

Якщо ви хочете використовувати сервер застосунків Open Swoole для обслуговування вашого застосунку на Laravel Octane, вам потрібно встановити PHP-розширення Open Swoole. Зазвичай це робиться через PECL:

```shell
pecl install openswoole
```

Використання Laravel Octane з Open Swoole дає ту саму функціональність, що й Swoole: паралельні завдання, тіки та інтервали.

<a name="swoole-via-laravel-sail"></a>
#### Swoole через Laravel Sail

> [!WARNING]
> Перш ніж обслуговувати застосунок на Octane через Sail, переконайтеся, що у вас остання версія Laravel Sail, і виконайте `./vendor/bin/sail build --no-cache` у кореневому каталозі вашого застосунку.

Як альтернативу ви можете розробляти свій застосунок на Octane зі Swoole за допомогою [Laravel Sail](/docs/{{version}}/sail) - офіційного середовища розробки Laravel на базі Docker. Laravel Sail містить розширення Swoole за замовчуванням. Однак вам усе одно потрібно буде підправити файл `docker-compose.yml`, який використовує Sail.

Для початку додайте змінну оточення `SUPERVISOR_PHP_COMMAND` до визначення сервісу `laravel.test` у файлі `docker-compose.yml` вашого застосунку. Ця змінна оточення міститиме команду, якою Sail обслуговуватиме ваш застосунок через Octane замість девсервера PHP:

```yaml
services:
  laravel.test:
    environment:
      SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=swoole --host=0.0.0.0 --port='${APP_PORT:-80}'" # [tl! add]
```

Насамкінець зберіть свої образи Sail:

```shell
./vendor/bin/sail build --no-cache
```

<a name="swoole-configuration"></a>
#### Конфігурація Swoole

Swoole підтримує кілька додаткових параметрів конфігурації, які ви можете за потреби додати до свого конфігураційного файлу `octane`. Оскільки їх рідко доводиться змінювати, у файлі конфігурації за замовчуванням їх немає:

```php
'swoole' => [
    'options' => [
        'log_file' => storage_path('logs/swoole_http.log'),
        'package_max_length' => 10 * 1024 * 1024,
    ],
],
```

<a name="serving-your-application"></a>
## Обслуговування вашого застосунку

Сервер Octane запускається артизан-командою `octane:start`. За замовчуванням ця команда використовуватиме сервер, указаний у параметрі конфігурації `server` вашого конфігураційного файлу `octane`:

```shell
php artisan octane:start
```

За замовчуванням Octane запустить сервер на порту 8000, тож ви зможете відкрити свій застосунок у браузері за адресою `http://localhost:8000`.

<a name="keeping-octane-running-in-production"></a>
#### Підтримання роботи Octane у продакшені

Якщо ви розгортаєте свій застосунок на Octane у продакшені, вам слід використовувати монітор процесів на кшталт Supervisor, щоб сервер Octane продовжував працювати. Приклад конфігураційного файлу Supervisor для Octane може виглядати так:

```ini
[program:octane]
process_name=%(program_name)s_%(process_num)02d
command=php /home/forge/example.com/artisan octane:start --server=frankenphp --host=127.0.0.1 --port=8000
autostart=true
autorestart=true
user=forge
redirect_stderr=true
stdout_logfile=/home/forge/example.com/storage/logs/octane.log
stopwaitsecs=3600
```

<a name="serving-your-application-via-https"></a>
### Обслуговування застосунку через HTTPS

За замовчуванням застосунки, що працюють через Octane, генерують посилання з префіксом `http://`. Змінну оточення `OCTANE_HTTPS`, яка використовується в конфігураційному файлі `config/octane.php` вашого застосунку, можна встановити в `true`, коли ви обслуговуєте застосунок через HTTPS. Коли це значення конфігурації дорівнює `true`, Octane вкаже Laravel додавати до всіх згенерованих посилань префікс `https://`:

```php
'https' => env('OCTANE_HTTPS', false),
```

<a name="serving-your-application-via-nginx"></a>
### Обслуговування застосунку через Nginx

> [!NOTE]
> Якщо ви ще не готові керувати власною конфігурацією сервера або не почуваєтеся впевнено, налаштовуючи всі різноманітні сервіси, потрібні для роботи надійного застосунку на Laravel Octane, погляньте на [Laravel Cloud](https://cloud.laravel.com), який пропонує повністю керовану підтримку Laravel Octane.

У продакшен-середовищах вам слід обслуговувати застосунок на Octane за традиційним вебсервером на кшталт Nginx чи Apache. Це дозволить вебсерверу віддавати ваші статичні ассети, як-от зображення та стилі, а також керувати термінацією вашого SSL-сертифіката.

У наведеному нижче прикладі конфігурації Nginx віддаватиме статичні ассети сайту й проксіюватиме запити до сервера Octane, що працює на порту 8000:

```nginx
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    listen [::]:80;
    server_name domain.com;
    server_tokens off;
    root /home/forge/domain.com/public;

    index index.php;

    charset utf-8;

    location /index.php {
        try_files /not_exists @octane;
    }

    location / {
        try_files $uri $uri/ @octane;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/domain.com-error.log error;

    error_page 404 /index.php;

    location @octane {
        set $suffix "";

        if ($uri = /index.php) {
            set $suffix ?$query_string;
        }

        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header Scheme $scheme;
        proxy_set_header SERVER_PORT $server_port;
        proxy_set_header REMOTE_ADDR $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        proxy_pass http://127.0.0.1:8000$suffix;
    }
}
```

<a name="watching-for-file-changes"></a>
### Відстеження змін у файлах

Оскільки ваш застосунок завантажується в пам'ять один раз під час запуску сервера Octane, будь-які зміни у файлах застосунку не з'являться, коли ви оновите сторінку у браузері. Наприклад, визначення маршрутів, додані до файлу `routes/web.php`, не діятимуть, доки сервер не буде перезапущено. Для зручності ви можете скористатися прапорцем `--watch`, щоб Octane автоматично перезапускав сервер за будь-яких змін у файлах вашого застосунку:

```shell
php artisan octane:start --watch
```

Перш ніж скористатися цією можливістю, переконайтеся, що у вашому локальному середовищі розробки встановлено [Node](https://nodejs.org). Крім того, вам слід установити у своєму проєкті бібліотеку відстеження файлів [Chokidar](https://github.com/paulmillr/chokidar):

```shell
npm install --save-dev chokidar
```

Налаштувати каталоги та файли, які слід відстежувати, можна за допомогою параметра конфігурації `watch` у конфігураційному файлі `config/octane.php` вашого застосунку.

<a name="specifying-the-worker-count"></a>
### Визначення кількості воркерів

За замовчуванням Octane запустить по одному воркеру запитів застосунку на кожне ядро процесора вашої машини. Ці воркери потім використовуватимуться для обслуговування вхідних HTTP-запитів, що надходять до вашого застосунку. Ви можете вручну вказати, скільки воркерів запускати, за допомогою опції `--workers` під час виклику команди `octane:start`:

```shell
php artisan octane:start --workers=4
```

Якщо ви використовуєте сервер застосунків Swoole, ви також можете вказати, скільки [«воркерів завдань»](#concurrent-tasks) слід запустити:

```shell
php artisan octane:start --workers=4 --task-workers=6
```

<a name="specifying-the-max-request-count"></a>
### Визначення максимальної кількості запитів

Щоб запобігти випадковим витокам пам'яті, Octane коректно перезапускає кожного воркера після того, як той обробив 500 запитів. Змінити це число можна за допомогою опції `--max-requests`:

```shell
php artisan octane:start --max-requests=250
```

<a name="specifying-the-max-execution-time"></a>
### Визначення максимального часу виконання

За замовчуванням Laravel Octane встановлює максимальний час виконання вхідних запитів у 30 секунд через параметр `max_execution_time` у конфігураційному файлі `config/octane.php` вашого застосунку:

```php
'max_execution_time' => 30,
```

Це налаштування визначає максимальну кількість секунд, протягом яких вхідному запиту дозволено виконуватися, перш ніж його буде припинено. Значення `0` повністю вимикає обмеження часу виконання. Цей параметр конфігурації особливо корисний для застосунків, які обробляють довготривалі запити: завантаження файлів, обробку даних чи API-виклики до зовнішніх сервісів.

> [!WARNING]
> Змінивши конфігурацію `max_execution_time`, ви маєте перезапустити сервер Octane, щоб зміни набули чинності.

<a name="reloading-the-workers"></a>
### Перезавантаження воркерів

Ви можете коректно перезапустити воркери застосунку на сервері Octane командою `octane:reload`. Зазвичай це слід робити після розгортання, щоб щойно розгорнутий код завантажився в пам'ять і використовувався для обслуговування наступних запитів:

```shell
php artisan octane:reload
```

<a name="stopping-the-server"></a>
### Зупинка сервера

Зупинити сервер Octane можна артизан-командою `octane:stop`:

```shell
php artisan octane:stop
```

<a name="checking-the-server-status"></a>
#### Перевірка стану сервера

Перевірити поточний стан сервера Octane можна артизан-командою `octane:status`:

```shell
php artisan octane:status
```

<a name="dependency-injection-and-octane"></a>
## Впровадження залежностей і Octane

Оскільки Octane завантажує ваш застосунок один раз і тримає його в пам'яті, обслуговуючи запити, під час розробки застосунку варто врахувати кілька застережень. Наприклад, методи `register` і `boot` сервіс-провайдерів вашого застосунку будуть виконані лише один раз, коли воркер запитів початково завантажується. На наступних запитах повторно використовуватиметься той самий екземпляр застосунку.

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

Octane автоматично скидає між запитами будь-який стан самого фреймворку. Однак Octane не завжди знає, як скинути глобальний стан, створений вашим застосунком. Тому вам варто розуміти, як будувати застосунок так, щоб він дружив з Octane. Нижче ми розглянемо найпоширеніші ситуації, які можуть спричинити проблеми під час використання Octane.

<a name="container-injection"></a>
### Впровадження контейнера

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

```php
use App\Service;
use Illuminate\Contracts\Foundation\Application;

/**
 * Register any application services.
 */
public function register(): void
{
    $this->app->singleton(Service::class, function (Application $app) {
        return new Service($app);
    });
}
```

У цьому прикладі, якщо екземпляр `Service` буде створено під час завантаження застосунку, контейнер буде впроваджено в сервіс, і той самий контейнер утримуватиметься екземпляром `Service` на наступних запитах. Для вашого конкретного застосунку це **може** й не бути проблемою; однак це може призвести до того, що в контейнері несподівано бракуватиме прив'язок, доданих пізніше в циклі завантаження або наступним запитом.

Як обхідний шлях ви можете або перестати реєструвати прив'язку як синглтон, або впровадити в сервіс замикання-резолвер контейнера, яке завжди повертає поточний екземпляр контейнера:

```php
use App\Service;
use Illuminate\Container\Container;
use Illuminate\Contracts\Foundation\Application;

$this->app->bind(Service::class, function (Application $app) {
    return new Service($app);
});

$this->app->singleton(Service::class, function () {
    return new Service(fn () => Container::getInstance());
});
```

Глобальний хелпер `app` і метод `Container::getInstance()` завжди повертатимуть найновішу версію контейнера застосунку.

<a name="request-injection"></a>
### Впровадження запиту

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

```php
use App\Service;
use Illuminate\Contracts\Foundation\Application;

/**
 * Register any application services.
 */
public function register(): void
{
    $this->app->singleton(Service::class, function (Application $app) {
        return new Service($app['request']);
    });
}
```

У цьому прикладі, якщо екземпляр `Service` буде створено під час завантаження застосунку, HTTP-запит буде впроваджено в сервіс, і той самий запит утримуватиметься екземпляром `Service` на наступних запитах. Тому всі заголовки, вхідні дані й дані рядка запиту будуть неправильними, як і решта даних запиту.

Як обхідний шлях ви можете або перестати реєструвати прив'язку як синглтон, або впровадити в сервіс замикання-резолвер запиту, яке завжди повертає поточний екземпляр запиту. Або ж - і це найбільш рекомендований підхід - просто передавати потрібну вашому об'єкту конкретну інформацію із запиту до одного з методів об'єкта під час виконання:

```php
use App\Service;
use Illuminate\Contracts\Foundation\Application;

$this->app->bind(Service::class, function (Application $app) {
    return new Service($app['request']);
});

$this->app->singleton(Service::class, function (Application $app) {
    return new Service(fn () => $app['request']);
});

// Or...

$service->method($request->input('name'));
```

Глобальний хелпер `request` завжди повертатиме запит, який застосунок обробляє зараз, тому його безпечно використовувати у вашому застосунку.

> [!WARNING]
> Вказувати тип `Illuminate\Http\Request` у методах контролерів і замиканнях маршрутів цілком припустимо.

<a name="configuration-repository-injection"></a>
### Впровадження репозиторію конфігурації

Загалом вам слід уникати впровадження екземпляра репозиторію конфігурації до конструкторів інших об'єктів. Наприклад, наступна прив'язка впроваджує репозиторій конфігурації в об'єкт, прив'язаний як синглтон:

```php
use App\Service;
use Illuminate\Contracts\Foundation\Application;

/**
 * Register any application services.
 */
public function register(): void
{
    $this->app->singleton(Service::class, function (Application $app) {
        return new Service($app->make('config'));
    });
}
```

У цьому прикладі, якщо значення конфігурації зміняться між запитами, цей сервіс не матиме доступу до нових значень, бо залежить від початкового екземпляра репозиторію.

Як обхідний шлях ви можете або перестати реєструвати прив'язку як синглтон, або впровадити в клас замикання-резолвер репозиторію конфігурації:

```php
use App\Service;
use Illuminate\Container\Container;
use Illuminate\Contracts\Foundation\Application;

$this->app->bind(Service::class, function (Application $app) {
    return new Service($app->make('config'));
});

$this->app->singleton(Service::class, function () {
    return new Service(fn () => Container::getInstance()->make('config'));
});
```

Глобальний `config` завжди повертатиме найновішу версію репозиторію конфігурації, тому його безпечно використовувати у вашому застосунку.

<a name="managing-memory-leaks"></a>
### Керування витоками пам'яті

Пам'ятайте: Octane тримає ваш застосунок у пам'яті між запитами, тож додавання даних до статичного масиву призведе до витоку пам'яті. Наприклад, у наведеному нижче контролері є витік пам'яті, бо кожен запит до застосунку й далі додаватиме дані до статичного масиву `$data`:

```php
use App\Service;
use Illuminate\Http\Request;
use Illuminate\Support\Str;

/**
 * Handle an incoming request.
 */
public function index(Request $request): array
{
    Service::$data[] = Str::random(10);

    return [
        // ...
    ];
}
```

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

<a name="concurrent-tasks"></a>
## Паралельні завдання

> [!WARNING]
> Ця можливість потребує [Swoole](#swoole).

Використовуючи Swoole, ви можете виконувати операції паралельно за допомогою легких фонових завдань. Зробити це можна методом `concurrently` в Octane. Ви можете поєднати цей метод з деструктуризацією масивів PHP, щоб отримати результати кожної операції:

```php
use App\Models\User;
use App\Models\Server;
use Laravel\Octane\Facades\Octane;

[$users, $servers] = Octane::concurrently([
    fn () => User::all(),
    fn () => Server::all(),
]);
```

Паралельні завдання, які обробляє Octane, використовують «воркери завдань» Swoole і виконуються в зовсім іншому процесі, ніж вхідний запит. Кількість воркерів, доступних для обробки паралельних завдань, визначається директивою `--task-workers` команди `octane:start`:

```shell
php artisan octane:start --workers=4 --task-workers=6
```

Викликаючи метод `concurrently`, не передавайте понад 1024 завдання - через обмеження, накладені системою завдань Swoole.

<a name="ticks-and-intervals"></a>
## Тіки та інтервали

> [!WARNING]
> Ця можливість потребує [Swoole](#swoole).

Використовуючи Swoole, ви можете зареєструвати операції-«тіки», які виконуватимуться кожні задані кілька секунд. Зареєструвати колбеки «тіків» можна методом `tick`. Першим аргументом методу `tick` має бути рядок з іменем тікера. Другим аргументом має бути callable, який викликатиметься із заданим інтервалом.

У цьому прикладі ми зареєструємо замикання, яке викликатиметься кожні 10 секунд. Зазвичай метод `tick` слід викликати в методі `boot` одного з сервіс-провайдерів вашого застосунку:

```php
Octane::tick('simple-ticker', fn () => ray('Ticking...'))
    ->seconds(10);
```

За допомогою методу `immediate` ви можете вказати Octane викликати колбек тіка одразу під час початкового завантаження сервера Octane, а далі кожні N секунд:

```php
Octane::tick('simple-ticker', fn () => ray('Ticking...'))
    ->seconds(10)
    ->immediate();
```

<a name="the-octane-cache"></a>
## Кеш Octane

> [!WARNING]
> Ця можливість потребує [Swoole](#swoole).

Використовуючи Swoole, ви можете скористатися драйвером кешу Octane, який дає швидкість читання й запису до 2 мільйонів операцій за секунду. Тому цей драйвер кешу - чудовий вибір для застосунків, яким потрібна екстремальна швидкість читання / запису від шару кешування.

Цей драйвер кешу працює на [таблицях Swoole](https://www.swoole.co.uk/docs/modules/swoole-table). Усі дані, збережені в кеші, доступні всім воркерам на сервері. Однак закешовані дані буде очищено під час перезапуску сервера:

```php
Cache::store('octane')->put('framework', 'Laravel', 30);
```

> [!NOTE]
> Максимальну кількість записів, дозволену в кеші Octane, можна визначити в конфігураційному файлі `octane` вашого застосунку.

<a name="cache-intervals"></a>
### Інтервали кешу

Окрім звичних методів, які надає система кешування Laravel, драйвер кешу Octane пропонує кеші на основі інтервалів. Ці кеші автоматично оновлюються із заданим інтервалом і мають реєструватися в методі `boot` одного з сервіс-провайдерів вашого застосунку. Наприклад, наведений нижче кеш оновлюватиметься кожні п'ять секунд:

```php
use Illuminate\Support\Str;

Cache::store('octane')->interval('random', function () {
    return Str::random(10);
}, seconds: 5);
```

<a name="tables"></a>
## Таблиці

> [!WARNING]
> Ця можливість потребує [Swoole](#swoole).

Використовуючи Swoole, ви можете визначати власні довільні [таблиці Swoole](https://www.swoole.co.uk/docs/modules/swoole-table) і працювати з ними. Таблиці Swoole дають екстремальну пропускну здатність, і дані в цих таблицях доступні всім воркерам на сервері. Однак дані в них буде втрачено під час перезапуску сервера.

Таблиці слід визначати в масиві конфігурації `tables` вашого конфігураційного файлу `octane`. Приклад таблиці, яка допускає максимум 1000 рядків, уже налаштований для вас. Максимальний розмір рядкових колонок можна задати, вказавши розмір колонки після її типу, як показано нижче:

```php
'tables' => [
    'example:1000' => [
        'name' => 'string:1000',
        'votes' => 'int',
    ],
],
```

Щоб звернутися до таблиці, скористайтеся методом `Octane::table`:

```php
use Laravel\Octane\Facades\Octane;

Octane::table('example')->set('uuid', [
    'name' => 'Nuno Maduro',
    'votes' => 1000,
]);

return Octane::table('example')->get('uuid');
```

> [!WARNING]
> Таблиці Swoole підтримують такі типи колонок: `string`, `int` і `float`.

---

Джерело: https://laravelukraine.com/docs/13.x/passport

# Laravel Passport

- [Вступ](#introduction)
    - [Passport чи Sanctum?](#passport-or-sanctum)
- [Встановлення](#installation)
    - [Розгортання Passport](#deploying-passport)
    - [Оновлення Passport](#upgrading-passport)
- [Конфігурація](#configuration)
    - [Час життя токенів](#token-lifetimes)
    - [Заміна моделей за замовчуванням](#overriding-default-models)
    - [Заміна маршрутів](#overriding-routes)
- [Authorization Code Grant](#authorization-code-grant)
    - [Керування клієнтами](#managing-clients)
    - [Запит токенів](#requesting-tokens)
    - [Керування токенами](#managing-tokens)
    - [Оновлення токенів](#refreshing-tokens)
    - [Відкликання токенів](#revoking-tokens)
    - [Очищення токенів](#purging-tokens)
- [Authorization Code Grant з PKCE](#code-grant-pkce)
    - [Створення клієнта](#creating-a-auth-pkce-grant-client)
    - [Запит токенів](#requesting-auth-pkce-grant-tokens)
- [Device Authorization Grant](#device-authorization-grant)
    - [Створення клієнта Device Authorization Grant](#creating-a-device-authorization-grant-client)
    - [Запит токенів](#requesting-device-authorization-grant-tokens)
- [Password Grant](#password-grant)
    - [Створення клієнта Password Grant](#creating-a-password-grant-client)
    - [Запит токенів](#requesting-password-grant-tokens)
    - [Запит усіх скопів](#requesting-all-scopes)
    - [Зміна провайдера користувачів](#customizing-the-user-provider)
    - [Зміна поля username](#customizing-the-username-field)
    - [Зміна валідації пароля](#customizing-the-password-validation)
- [Implicit Grant](#implicit-grant)
- [Client Credentials Grant](#client-credentials-grant)
    - [Отримання токенів](#retrieving-tokens)
- [Персональні токени доступу](#personal-access-tokens)
    - [Створення клієнта персонального доступу](#creating-a-personal-access-client)
    - [Зміна провайдера користувачів](#customizing-the-user-provider-for-pat)
    - [Керування персональними токенами доступу](#managing-personal-access-tokens)
- [Захист маршрутів](#protecting-routes)
    - [Через middleware](#via-middleware)
    - [Передавання токена доступу](#passing-the-access-token)
- [Скопи токенів](#token-scopes)
    - [Визначення скопів](#defining-scopes)
    - [Скоп за замовчуванням](#default-scope)
    - [Призначення скопів токенам](#assigning-scopes-to-tokens)
    - [Перевірка скопів](#checking-scopes)
- [Автентифікація SPA](#spa-authentication)
- [Події](#events)
- [Тестування](#testing)

<a name="introduction"></a>
## Вступ

[Laravel Passport](https://github.com/laravel/passport) дає повну реалізацію сервера OAuth2 для вашого Laravel-застосунку за лічені хвилини. Passport побудований на [League OAuth2 server](https://github.com/thephpleague/oauth2-server), який підтримують Andy Millington і Simon Hamp.

> [!NOTE]
> Ця документація припускає, що ви вже знайомі з OAuth2. Якщо ви нічого не знаєте про OAuth2, перш ніж продовжувати, ознайомтеся із загальною [термінологією](https://oauth2.thephpleague.com/terminology/) та можливостями OAuth2.

<a name="passport-or-sanctum"></a>
### Passport чи Sanctum?

Перш ніж почати, вам, можливо, варто визначити, що краще підійде вашому застосунку: Laravel Passport чи [Laravel Sanctum](/docs/{{version}}/sanctum). Якщо вашому застосунку конче потрібна підтримка OAuth2, вам слід використовувати Laravel Passport.

Однак, якщо ви намагаєтеся автентифікувати односторінковий застосунок, мобільний застосунок або видавати API-токени, вам слід використовувати [Laravel Sanctum](/docs/{{version}}/sanctum). Laravel Sanctum не підтримує OAuth2; однак він дає значно простіший досвід розробки автентифікації API.

<a name="installation"></a>
## Встановлення

Встановити Laravel Passport можна артизан-командою `install:api`:

```shell
php artisan install:api --passport
```

Ця команда опублікує й виконає міграції бази даних, потрібні для створення таблиць, у яких ваш застосунок зберігатиме OAuth2-клієнтів і токени доступу. Команда також створить ключі шифрування, потрібні для генерації безпечних токенів доступу.

Після виконання команди `install:api` додайте трейт `Laravel\Passport\HasApiTokens` та інтерфейс `Laravel\Passport\Contracts\OAuthenticatable` до своєї моделі `App\Models\User`. Цей трейт надасть вашій моделі кілька хелпер-методів, які дозволяють оглядати токен і скопи автентифікованого користувача:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable implements OAuthenticatable
{
    use HasApiTokens, HasFactory, Notifiable;
}
```

Насамкінець у конфігураційному файлі `config/auth.php` вашого застосунку вам слід визначити гард автентифікації `api` і встановити опцію `driver` у `passport`. Це вкаже вашому застосунку використовувати `TokenGuard` з Passport під час автентифікації вхідних API-запитів:

```php
'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'api' => [
        'driver' => 'passport',
        'provider' => 'users',
    ],
],
```

<a name="deploying-passport"></a>
### Розгортання Passport

Розгортаючи Passport на сервери свого застосунку вперше, вам, найімовірніше, потрібно буде виконати команду `passport:keys`. Ця команда генерує ключі шифрування, потрібні Passport для генерації токенів доступу. Згенеровані ключі зазвичай не тримають під контролем версій:

```shell
php artisan passport:keys
```

За потреби ви можете визначити шлях, з якого слід завантажувати ключі Passport. Зробити це можна методом `Passport::loadKeysFrom`. Зазвичай цей метод слід викликати з методу `boot` класу `App\Providers\AppServiceProvider` вашого застосунку:

```php
/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Passport::loadKeysFrom(__DIR__.'/../secrets/oauth');
}
```

<a name="loading-keys-from-the-environment"></a>
#### Завантаження ключів з оточення

Як альтернативу ви можете опублікувати конфігураційний файл Passport артизан-командою `vendor:publish`:

```shell
php artisan vendor:publish --tag=passport-config
```

Після публікації конфігураційного файлу ви можете завантажити ключі шифрування свого застосунку, визначивши їх як змінні оточення:

```ini
PASSPORT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
<private key here>
-----END RSA PRIVATE KEY-----"

PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
<public key here>
-----END PUBLIC KEY-----"
```

<a name="upgrading-passport"></a>
### Оновлення Passport

Оновлюючись до нової мажорної версії Passport, важливо уважно переглянути [посібник з оновлення](https://github.com/laravel/passport/blob/master/UPGRADE.md).

<a name="configuration"></a>
## Конфігурація

<a name="token-lifetimes"></a>
### Час життя токенів

За замовчуванням Passport видає довгоживучі токени доступу, які спливають через рік. Якщо ви хочете налаштувати довший / коротший час життя токена, скористайтеся методами `tokensExpireIn`, `refreshTokensExpireIn` і `personalAccessTokensExpireIn`. Ці методи слід викликати з методу `boot` класу `App\Providers\AppServiceProvider` вашого застосунку:

```php
use Carbon\CarbonInterval;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Passport::tokensExpireIn(CarbonInterval::days(15));
    Passport::refreshTokensExpireIn(CarbonInterval::days(30));
    Passport::personalAccessTokensExpireIn(CarbonInterval::months(6));
}
```

> [!WARNING]
> Колонки `expires_at` у таблицях бази даних Passport доступні лише для читання і призначені лише для показу. Видаючи токени, Passport зберігає інформацію про строк дії всередині підписаних і зашифрованих токенів. Якщо вам потрібно зробити токен недійсним, вам слід [відкликати його](#revoking-tokens).

<a name="overriding-default-models"></a>
### Заміна моделей за замовчуванням

Ви можете розширювати моделі, які Passport використовує внутрішньо, визначивши власну модель і розширивши відповідну модель Passport:

```php
use Laravel\Passport\Client as PassportClient;

class Client extends PassportClient
{
    // ...
}
```

Визначивши свою модель, ви можете вказати Passport використовувати вашу власну модель через клас `Laravel\Passport\Passport`. Зазвичай повідомляти Passport про ваші власні моделі слід у методі `boot` класу `App\Providers\AppServiceProvider` вашого застосунку:

```php
use App\Models\Passport\AuthCode;
use App\Models\Passport\Client;
use App\Models\Passport\DeviceCode;
use App\Models\Passport\RefreshToken;
use App\Models\Passport\Token;
use Laravel\Passport\Passport;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Passport::useTokenModel(Token::class);
    Passport::useRefreshTokenModel(RefreshToken::class);
    Passport::useAuthCodeModel(AuthCode::class);
    Passport::useClientModel(Client::class);
    Passport::useDeviceCodeModel(DeviceCode::class);
}
```

<a name="overriding-routes"></a>
### Заміна маршрутів

Іноді ви можете захотіти змінити маршрути, визначені Passport. Щоб досягти цього, спершу вам потрібно проігнорувати маршрути, зареєстровані Passport, додавши `Passport::ignoreRoutes` до методу `register` `AppServiceProvider` вашого застосунку:

```php
use Laravel\Passport\Passport;

/**
 * Register any application services.
 */
public function register(): void
{
    Passport::ignoreRoutes();
}
```

Далі ви можете скопіювати маршрути, визначені Passport у [його файлі маршрутів](https://github.com/laravel/passport/blob/master/routes/web.php), до файлу `routes/web.php` свого застосунку і змінити їх на свій смак:

```php
Route::group([
    'as' => 'passport.',
    'prefix' => config('passport.path', 'oauth'),
    'namespace' => '\Laravel\Passport\Http\Controllers',
], function () {
    // Passport routes...
});
```

<a name="authorization-code-grant"></a>
## Authorization Code Grant

Використання OAuth2 через коди авторизації - це те, з чим знайома більшість розробників. Коли використовуються коди авторизації, клієнтський застосунок перенаправляє користувача на ваш сервер, де той або схвалить, або відхилить запит на видачу токена доступу клієнту.

Для початку нам потрібно вказати Passport, як повертати наше «авторизаційне» представлення.

Усю логіку рендерингу авторизаційного представлення можна змінити відповідними методами, доступними через клас `Laravel\Passport\Passport`. Зазвичай цей метод слід викликати з методу `boot` класу `App\Providers\AppServiceProvider` вашого застосунку:

```php
use Inertia\Inertia;
use Laravel\Passport\Passport;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    // By providing a view name...
    Passport::authorizationView('auth.oauth.authorize');

    // By providing a closure...
    Passport::authorizationView(
        fn ($parameters) => Inertia::render('Auth/OAuth/Authorize', [
            'request' => $parameters['request'],
            'authToken' => $parameters['authToken'],
            'client' => $parameters['client'],
            'user' => $parameters['user'],
            'scopes' => $parameters['scopes'],
        ])
    );
}
```

Passport автоматично визначить маршрут `/oauth/authorize`, який повертає це представлення. Ваш шаблон `auth.oauth.authorize` має містити форму, що робить POST-запит до маршруту `passport.authorizations.approve`, щоб схвалити авторизацію, і форму, що робить DELETE-запит до маршруту `passport.authorizations.deny`, щоб відхилити авторизацію. Маршрути `passport.authorizations.approve` і `passport.authorizations.deny` очікують поля `state`, `client_id` та `auth_token`.

<a name="managing-clients"></a>
### Керування клієнтами

Розробникам, які створюють застосунки, що мають взаємодіяти з API вашого застосунку, потрібно буде зареєструвати свій застосунок у вашому, створивши «клієнта». Зазвичай це полягає в наданні імені їхнього застосунку та URI, на який ваш застосунок може перенаправити після того, як користувачі схвалять їхній запит на авторизацію.

<a name="managing-first-party-clients"></a>
#### Власні клієнти

Найпростіший спосіб створити клієнта - артизан-команда `passport:client`. Цю команду можна використовувати для створення власних клієнтів або для тестування вашої функціональності OAuth2. Коли ви виконаєте команду `passport:client`, Passport запитає у вас більше інформації про вашого клієнта і надасть вам ID та секрет клієнта:

```shell
php artisan passport:client
```

Якщо ви хочете дозволити своєму клієнту кілька URI перенаправлення, вкажіть їх списком через кому, коли команда `passport:client` запитає URI. Будь-які URI, що містять коми, слід закодувати як URI:

```shell
https://third-party-app.com/callback,https://example.com/oauth/redirect
```

<a name="managing-third-party-clients"></a>
#### Сторонні клієнти

Оскільки користувачі вашого застосунку не зможуть скористатися командою `passport:client`, ви можете застосувати метод `createAuthorizationCodeGrantClient` класу `Laravel\Passport\ClientRepository`, щоб зареєструвати клієнта для заданого користувача:

```php
use App\Models\User;
use Laravel\Passport\ClientRepository;

$user = User::find($userId);

// Creating an OAuth app client that belongs to the given user...
$client = app(ClientRepository::class)->createAuthorizationCodeGrantClient(
    user: $user,
    name: 'Example App',
    redirectUris: ['https://third-party-app.com/callback'],
    confidential: false,
    enableDeviceFlow: true
);

// Retrieving all the OAuth app clients that belong to the user...
$clients = $user->oauthApps()->get();
```

Метод `createAuthorizationCodeGrantClient` повертає екземпляр `Laravel\Passport\Client`. Ви можете показати користувачеві `$client->id` як ID клієнта і `$client->plainSecret` як секрет клієнта.

<a name="requesting-tokens"></a>
### Запит токенів

<a name="requesting-tokens-redirecting-for-authorization"></a>
#### Перенаправлення для авторизації

Щойно клієнта створено, розробники можуть використовувати свій ID і секрет клієнта, щоб запитати код авторизації й токен доступу у вашого застосунку. Спершу застосунок-споживач має зробити запит на перенаправлення до маршруту `/oauth/authorize` вашого застосунку, ось так:

```php
use Illuminate\Http\Request;
use Illuminate\Support\Str;

Route::get('/redirect', function (Request $request) {
    $request->session()->put('state', $state = Str::random(40));

    $query = http_build_query([
        'client_id' => 'your-client-id',
        'redirect_uri' => 'https://third-party-app.com/callback',
        'response_type' => 'code',
        'scope' => 'user:read orders:create',
        'state' => $state,
        // 'prompt' => '', // "none", "consent", or "login"
    ]);

    return redirect('https://passport-app.test/oauth/authorize?'.$query);
});
```

Параметр `prompt` можна використати, щоб задати поведінку автентифікації застосунку Passport.

Якщо значення `prompt` - `none`, Passport завжди видаватиме помилку автентифікації, коли користувач ще не автентифікований у застосунку Passport. Якщо значення - `consent`, Passport завжди показуватиме екран схвалення авторизації, навіть якщо всі скопи вже раніше було надано застосунку-споживачу. Коли значення - `login`, застосунок Passport завжди пропонуватиме користувачеві повторно увійти до застосунку, навіть якщо той уже має наявну сесію.

Якщо значення `prompt` не надано, користувачеві буде запропоновано авторизацію лише тоді, коли він раніше не авторизував доступ застосунку-споживачу до запитаних скопів.

> [!NOTE]
> Пам'ятайте: маршрут `/oauth/authorize` уже визначено Passport. Вам не потрібно визначати цей маршрут вручну.

<a name="approving-the-request"></a>
#### Схвалення запиту

Отримуючи запити на авторизацію, Passport автоматично відповідатиме на основі значення параметра `prompt` (якщо він присутній) і може показати користувачеві шаблон, що дозволяє схвалити чи відхилити запит на авторизацію. Якщо користувач схвалить запит, його буде перенаправлено назад на `redirect_uri`, указаний застосунком-споживачем. `redirect_uri` має збігатися з URL `redirect`, указаним під час створення клієнта.

Іноді ви можете захотіти пропустити запит на авторизацію, наприклад авторизуючи власного клієнта. Досягти цього можна, [розширивши модель `Client`](#overriding-default-models) і визначивши метод `skipsAuthorization`. Якщо `skipsAuthorization` поверне `true`, клієнта буде схвалено, а користувача одразу перенаправлено назад на `redirect_uri` - хіба що застосунок-споживач явно встановив параметр `prompt` під час перенаправлення для авторизації:

```php
<?php

namespace App\Models\Passport;

use Illuminate\Contracts\Auth\Authenticatable;
use Laravel\Passport\Client as BaseClient;

class Client extends BaseClient
{
    /**
     * Determine if the client should skip the authorization prompt.
     *
     * @param  \Laravel\Passport\Scope[]  $scopes
     */
    public function skipsAuthorization(Authenticatable $user, array $scopes): bool
    {
        return $this->firstParty();
    }
}
```

<a name="requesting-tokens-converting-authorization-codes-to-access-tokens"></a>
#### Обмін кодів авторизації на токени доступу

Якщо користувач схвалить запит на авторизацію, його буде перенаправлено назад до застосунку-споживача. Споживач має спершу звірити параметр `state` зі значенням, збереженим перед перенаправленням. Якщо параметр state збігається, споживач має надіслати `POST`-запит до вашого застосунку, щоб запитати токен доступу. Запит має містити код авторизації, виданий вашим застосунком, коли користувач схвалив запит на авторизацію:

```php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;

Route::get('/callback', function (Request $request) {
    $state = $request->session()->pull('state');

    throw_unless(
        strlen($state) > 0 && $state === $request->state,
        InvalidArgumentException::class,
        'Invalid state value.'
    );

    $response = Http::asForm()->post('https://passport-app.test/oauth/token', [
        'grant_type' => 'authorization_code',
        'client_id' => 'your-client-id',
        'client_secret' => 'your-client-secret',
        'redirect_uri' => 'https://third-party-app.com/callback',
        'code' => $request->code,
    ]);

    return $response->json();
});
```

Цей маршрут `/oauth/token` поверне JSON-відповідь з атрибутами `access_token`, `refresh_token` та `expires_in`. Атрибут `expires_in` містить кількість секунд до спливання токена доступу.

> [!NOTE]
> Як і маршрут `/oauth/authorize`, маршрут `/oauth/token` визначено за вас у Passport. Визначати цей маршрут вручну не потрібно.

<a name="managing-tokens"></a>
### Керування токенами

Ви можете отримати авторизовані токени користувача методом `tokens` трейта `Laravel\Passport\HasApiTokens`. Наприклад, це можна використати, щоб запропонувати вашим користувачам панель для відстеження їхніх з'єднань зі сторонніми застосунками:

```php
use App\Models\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Date;
use Laravel\Passport\Token;

$user = User::find($userId);

// Retrieving all of the valid tokens for the user...
$tokens = $user->tokens()
    ->where('revoked', false)
    ->where('expires_at', '>', Date::now())
    ->get();

// Retrieving all the user's connections to third-party OAuth app clients...
$connections = $tokens->load('client')
    ->reject(fn (Token $token) => $token->client->firstParty())
    ->groupBy('client_id')
    ->map(fn (Collection $tokens) => [
        'client' => $tokens->first()->client,
        'scopes' => $tokens->pluck('scopes')->flatten()->unique()->values()->all(),
        'tokens_count' => $tokens->count(),
    ])
    ->values();
```

<a name="refreshing-tokens"></a>
### Оновлення токенів

Якщо ваш застосунок видає короткоживучі токени доступу, користувачам потрібно буде оновлювати свої токени доступу через refresh-токен, наданий їм під час видачі токена доступу:

```php
use Illuminate\Support\Facades\Http;

$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
    'grant_type' => 'refresh_token',
    'refresh_token' => 'the-refresh-token',
    'client_id' => 'your-client-id',
    'client_secret' => 'your-client-secret', // Required for confidential clients only...
    'scope' => 'user:read orders:create',
]);

return $response->json();
```

Цей маршрут `/oauth/token` поверне JSON-відповідь з атрибутами `access_token`, `refresh_token` та `expires_in`. Атрибут `expires_in` містить кількість секунд до спливання токена доступу.

<a name="revoking-tokens"></a>
### Відкликання токенів

Ви можете відкликати токен методом `revoke` на моделі `Laravel\Passport\Token`. Відкликати refresh-токен токена можна методом `revoke` на моделі `Laravel\Passport\RefreshToken`:

```php
use Laravel\Passport\Passport;
use Laravel\Passport\Token;

$token = Passport::token()->find($tokenId);

// Revoke an access token...
$token->revoke();

// Revoke the token's refresh token...
$token->refreshToken?->revoke();

// Revoke all of the user's tokens...
User::find($userId)->tokens()->each(function (Token $token) {
    $token->revoke();
    $token->refreshToken?->revoke();
});
```

<a name="purging-tokens"></a>
### Очищення токенів

Коли токени відкликано або вони сплили, ви можете захотіти прибрати їх з бази даних. Артизан-команда `passport:purge`, що входить до Passport, може зробити це за вас:

```shell
# Purge revoked and expired tokens, auth codes, and device codes...
php artisan passport:purge

# Only purge tokens expired for more than 6 hours...
php artisan passport:purge --hours=6

# Only purge revoked tokens, auth codes, and device codes...
php artisan passport:purge --revoked

# Only purge expired tokens, auth codes, and device codes...
php artisan passport:purge --expired
```

Ви також можете налаштувати [заплановане завдання](/docs/{{version}}/scheduling) у файлі `routes/console.php` свого застосунку, щоб автоматично прибирати токени за розкладом:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('passport:purge')->hourly();
```

<a name="code-grant-pkce"></a>
## Authorization Code Grant з PKCE

Authorization Code grant з «Proof Key for Code Exchange» (PKCE) - це безпечний спосіб автентифікувати односторінкові чи мобільні застосунки для доступу до вашого API. Цей grant слід використовувати, коли ви не можете гарантувати, що секрет клієнта зберігатиметься конфіденційно, або щоб зменшити ризик перехоплення коду авторизації зловмисником. Комбінація «code verifier» і «code challenge» замінює секрет клієнта під час обміну коду авторизації на токен доступу.

<a name="creating-a-auth-pkce-grant-client"></a>
### Створення клієнта

Перш ніж ваш застосунок зможе видавати токени через authorization code grant з PKCE, вам потрібно створити клієнта з увімкненим PKCE. Зробити це можна артизан-командою `passport:client` з опцією `--public`:

```shell
php artisan passport:client --public
```

<a name="requesting-auth-pkce-grant-tokens"></a>
### Запит токенів

<a name="code-verifier-code-challenge"></a>
#### Code Verifier і Code Challenge

Оскільки цей grant авторизації не передбачає секрету клієнта, розробникам потрібно буде згенерувати комбінацію code verifier і code challenge, щоб запитати токен.

Code verifier має бути випадковим рядком довжиною від 43 до 128 символів, що містить літери, цифри та символи `"-"`, `"."`, `"_"`, `"~"`, як визначено в [специфікації RFC 7636](https://tools.ietf.org/html/rfc7636).

Code challenge має бути рядком у кодуванні Base64 із символами, безпечними для URL та імен файлів. Кінцеві символи `'='` слід прибрати, і в ньому не має бути переносів рядків, пробілів чи інших додаткових символів.

```php
$encoded = base64_encode(hash('sha256', $codeVerifier, true));

$codeChallenge = strtr(rtrim($encoded, '='), '+/', '-_');
```

<a name="code-grant-pkce-redirecting-for-authorization"></a>
#### Перенаправлення для авторизації

Щойно клієнта створено, ви можете використати ID клієнта та згенеровані code verifier і code challenge, щоб запитати код авторизації й токен доступу у вашого застосунку. Спершу застосунок-споживач має зробити запит на перенаправлення до маршруту `/oauth/authorize` вашого застосунку:

```php
use Illuminate\Http\Request;
use Illuminate\Support\Str;

Route::get('/redirect', function (Request $request) {
    $request->session()->put('state', $state = Str::random(40));

    $request->session()->put(
        'code_verifier', $codeVerifier = Str::random(128)
    );

    $codeChallenge = strtr(rtrim(
        base64_encode(hash('sha256', $codeVerifier, true))
    , '='), '+/', '-_');

    $query = http_build_query([
        'client_id' => 'your-client-id',
        'redirect_uri' => 'https://third-party-app.com/callback',
        'response_type' => 'code',
        'scope' => 'user:read orders:create',
        'state' => $state,
        'code_challenge' => $codeChallenge,
        'code_challenge_method' => 'S256',
        // 'prompt' => '', // "none", "consent", or "login"
    ]);

    return redirect('https://passport-app.test/oauth/authorize?'.$query);
});
```

<a name="code-grant-pkce-converting-authorization-codes-to-access-tokens"></a>
#### Обмін кодів авторизації на токени доступу

Якщо користувач схвалить запит на авторизацію, його буде перенаправлено назад до застосунку-споживача. Споживач має звірити параметр `state` зі значенням, збереженим перед перенаправленням, як і у стандартному Authorization Code Grant.

Якщо параметр state збігається, споживач має надіслати `POST`-запит до вашого застосунку, щоб запитати токен доступу. Запит має містити код авторизації, виданий вашим застосунком, коли користувач схвалив запит на авторизацію, разом із початково згенерованим code verifier:

```php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;

Route::get('/callback', function (Request $request) {
    $state = $request->session()->pull('state');

    $codeVerifier = $request->session()->pull('code_verifier');

    throw_unless(
        strlen($state) > 0 && $state === $request->state,
        InvalidArgumentException::class
    );

    $response = Http::asForm()->post('https://passport-app.test/oauth/token', [
        'grant_type' => 'authorization_code',
        'client_id' => 'your-client-id',
        'redirect_uri' => 'https://third-party-app.com/callback',
        'code_verifier' => $codeVerifier,
        'code' => $request->code,
    ]);

    return $response->json();
});
```

<a name="device-authorization-grant"></a>
## Device Authorization Grant

Device authorization grant в OAuth2 дозволяє пристроям без браузера чи з обмеженим введенням, як-от телевізорам та ігровим консолям, отримати токен доступу, обмінявши «device code». Використовуючи device flow, клієнт на пристрої вкаже користувачеві скористатися другим пристроєм, наприклад комп'ютером чи смартфоном, і підключитися до вашого сервера, де той введе наданий «user code» і схвалить або відхилить запит на доступ.

Для початку нам потрібно вказати Passport, як повертати наші представлення «user code» та «авторизація».

Усю логіку рендерингу авторизаційного представлення можна змінити відповідними методами, доступними через клас `Laravel\Passport\Passport`. Зазвичай цей метод слід викликати з методу `boot` класу `App\Providers\AppServiceProvider` вашого застосунку.

```php
use Inertia\Inertia;
use Laravel\Passport\Passport;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    // By providing a view name...
    Passport::deviceUserCodeView('auth.oauth.device.user-code');
    Passport::deviceAuthorizationView('auth.oauth.device.authorize');

    // By providing a closure...
    Passport::deviceUserCodeView(
        fn ($parameters) => Inertia::render('Auth/OAuth/Device/UserCode')
    );

    Passport::deviceAuthorizationView(
        fn ($parameters) => Inertia::render('Auth/OAuth/Device/Authorize', [
            'request' => $parameters['request'],
            'authToken' => $parameters['authToken'],
            'client' => $parameters['client'],
            'user' => $parameters['user'],
            'scopes' => $parameters['scopes'],
        ])
    );

    // ...
}
```

Passport автоматично визначить маршрути, які повертають ці представлення. Ваш шаблон `auth.oauth.device.user-code` має містити форму, що робить GET-запит до маршруту `passport.device.authorizations.authorize`. Маршрут `passport.device.authorizations.authorize` очікує параметр рядка запиту `user_code`.

Ваш шаблон `auth.oauth.device.authorize` має містити форму, що робить POST-запит до маршруту `passport.device.authorizations.approve`, щоб схвалити авторизацію, і форму, що робить DELETE-запит до маршруту `passport.device.authorizations.deny`, щоб відхилити авторизацію. Маршрути `passport.device.authorizations.approve` і `passport.device.authorizations.deny` очікують поля `state`, `client_id` та `auth_token`.

<a name="creating-a-device-authorization-grant-client"></a>
### Створення клієнта Device Authorization Grant

Перш ніж ваш застосунок зможе видавати токени через device authorization grant, вам потрібно створити клієнта з увімкненим device flow. Зробити це можна артизан-командою `passport:client` з опцією `--device`. Ця команда створить власного клієнта з увімкненим device flow і надасть вам ID та секрет клієнта:

```shell
php artisan passport:client --device
```

Крім того, ви можете скористатися методом `createDeviceAuthorizationGrantClient` класу `ClientRepository`, щоб зареєструвати стороннього клієнта, що належить заданому користувачеві:

```php
use App\Models\User;
use Laravel\Passport\ClientRepository;

$user = User::find($userId);

$client = app(ClientRepository::class)->createDeviceAuthorizationGrantClient(
    user: $user,
    name: 'Example Device',
    confidential: false,
);
```

<a name="requesting-device-authorization-grant-tokens"></a>
### Запит токенів

<a name="device-code"></a>
#### Запит device code

Щойно клієнта створено, розробники можуть використовувати свій ID клієнта, щоб запитати device code у вашого застосунку. Спершу пристрій-споживач має зробити `POST`-запит до маршруту `/oauth/device/code` вашого застосунку, щоб запитати device code:

```php
use Illuminate\Support\Facades\Http;

$response = Http::asForm()->post('https://passport-app.test/oauth/device/code', [
    'client_id' => 'your-client-id',
    'scope' => 'user:read orders:create',
]);

return $response->json();
```

Це поверне JSON-відповідь з атрибутами `device_code`, `user_code`, `verification_uri`, `interval` та `expires_in`. Атрибут `expires_in` містить кількість секунд до спливання device code. Атрибут `interval` містить кількість секунд, які пристрій-споживач має чекати між запитами, опитуючи маршрут `/oauth/token`, щоб уникнути помилок обмеження частоти.

> [!NOTE]
> Пам'ятайте: маршрут `/oauth/device/code` уже визначено Passport. Вам не потрібно визначати цей маршрут вручну.

<a name="user-code"></a>
#### Показ verification URI та user code

Щойно запит на device code виконано, пристрій-споживач має вказати користувачеві скористатися іншим пристроєм, відвідати наданий `verification_uri` і ввести `user_code`, щоб схвалити запит на авторизацію.

<a name="polling-token-request"></a>
#### Запит токена опитуванням

Оскільки користувач надаватиме (чи відхилятиме) доступ з окремого пристрою, пристрій-споживач має опитувати маршрут `/oauth/token` вашого застосунку, щоб визначити, коли користувач відповів на запит. Пристрій-споживач має використовувати мінімальний інтервал опитування `interval`, наданий у JSON-відповіді під час запиту device code, щоб уникнути помилок обмеження частоти:

```php
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Sleep;

$interval = 5;

do {
    Sleep::for($interval)->seconds();

    $response = Http::asForm()->post('https://passport-app.test/oauth/token', [
        'grant_type' => 'urn:ietf:params:oauth:grant-type:device_code',
        'client_id' => 'your-client-id',
        'client_secret' => 'your-client-secret', // Required for confidential clients only...
        'device_code' => 'the-device-code',
    ]);

    if ($response->json('error') === 'slow_down') {
        $interval += 5;
    }
} while (in_array($response->json('error'), ['authorization_pending', 'slow_down']));

return $response->json();
```

Якщо користувач схвалив запит на авторизацію, це поверне JSON-відповідь з атрибутами `access_token`, `refresh_token` та `expires_in`. Атрибут `expires_in` містить кількість секунд до спливання токена доступу.

<a name="password-grant"></a>
## Password Grant

> [!WARNING]
> Ми більше не рекомендуємо використовувати токени password grant. Натомість вам слід обрати [тип grant, який наразі рекомендує OAuth2 Server](https://oauth2.thephpleague.com/authorization-server/which-grant/).

Password grant в OAuth2 дозволяє іншим вашим власним клієнтам, наприклад мобільному застосунку, отримати токен доступу за адресою електронної пошти / іменем користувача й паролем. Це дозволяє безпечно видавати токени доступу вашим власним клієнтам, не змушуючи користувачів проходити весь потік перенаправлень з кодом авторизації OAuth2.

Щоб увімкнути password grant, викличте метод `enablePasswordGrant` у методі `boot` класу `App\Providers\AppServiceProvider` вашого застосунку:

```php
/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Passport::enablePasswordGrant();
}
```

<a name="creating-a-password-grant-client"></a>
### Створення клієнта Password Grant

Перш ніж ваш застосунок зможе видавати токени через password grant, вам потрібно створити клієнта password grant. Зробити це можна артизан-командою `passport:client` з опцією `--password`.

```shell
php artisan passport:client --password
```

<a name="requesting-password-grant-tokens"></a>
### Запит токенів

Щойно ви увімкнули grant і створили клієнта password grant, ви можете запитати токен доступу, надіславши `POST`-запит до маршруту `/oauth/token` з адресою електронної пошти й паролем користувача. Пам'ятайте: цей маршрут уже зареєстровано Passport, тож визначати його вручну не потрібно. Якщо запит буде успішним, ви отримаєте `access_token` і `refresh_token` у JSON-відповіді від сервера:

```php
use Illuminate\Support\Facades\Http;

$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
    'grant_type' => 'password',
    'client_id' => 'your-client-id',
    'client_secret' => 'your-client-secret', // Required for confidential clients only...
    'username' => 'taylor@laravel.com',
    'password' => 'my-password',
    'scope' => 'user:read orders:create',
]);

return $response->json();
```

> [!NOTE]
> Пам'ятайте: за замовчуванням токени доступу довгоживучі. Однак за потреби ви можете [налаштувати максимальний час життя токена доступу](#configuration).

<a name="requesting-all-scopes"></a>
### Запит усіх скопів

Використовуючи password grant чи client credentials grant, ви можете захотіти авторизувати токен для всіх скопів, які підтримує ваш застосунок. Зробити це можна, запитавши скоп `*`. Якщо ви запитаєте скоп `*`, метод `can` на екземплярі токена завжди повертатиме `true`. Цей скоп можна призначити лише токену, виданому через grant `password` чи `client_credentials`:

```php
use Illuminate\Support\Facades\Http;

$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
    'grant_type' => 'password',
    'client_id' => 'your-client-id',
    'client_secret' => 'your-client-secret', // Required for confidential clients only...
    'username' => 'taylor@laravel.com',
    'password' => 'my-password',
    'scope' => '*',
]);
```

<a name="customizing-the-user-provider"></a>
### Зміна провайдера користувачів

Якщо ваш застосунок використовує більше одного [провайдера користувачів для автентифікації](/docs/{{version}}/authentication#introduction), ви можете вказати, який провайдер користувачів використовує клієнт password grant, передавши опцію `--provider` під час створення клієнта командою `artisan passport:client --password`. Задане ім'я провайдера має відповідати дійсному провайдеру, визначеному в конфігураційному файлі `config/auth.php` вашого застосунку. Далі ви можете [захистити свій маршрут за допомогою middleware](#multiple-authentication-guards), щоб авторизованими були лише користувачі з указаного провайдера гарда.

<a name="customizing-the-username-field"></a>
### Зміна поля username

Автентифікуючись через password grant, Passport використовуватиме атрибут `email` вашої автентифіковної моделі як «username». Однак ви можете змінити цю поведінку, визначивши на своїй моделі метод `findForPassport`:

```php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\Bridge\Client;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable implements OAuthenticatable
{
    use HasApiTokens, Notifiable;

    /**
     * Find the user instance for the given username.
     */
    public function findForPassport(string $username, Client $client): User
    {
        return $this->where('username', $username)->first();
    }
}
```

<a name="customizing-the-password-validation"></a>
### Зміна валідації пароля

Автентифікуючись через password grant, Passport використовуватиме атрибут `password` вашої моделі для перевірки наданого пароля. Якщо ваша модель не має атрибута `password` або ви хочете змінити логіку валідації пароля, визначте на своїй моделі метод `validateForPassportPasswordGrant`:

```php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable implements OAuthenticatable
{
    use HasApiTokens, Notifiable;

    /**
     * Validate the password of the user for the Passport password grant.
     */
    public function validateForPassportPasswordGrant(string $password): bool
    {
        return Hash::check($password, $this->password);
    }
}
```

<a name="implicit-grant"></a>
## Implicit Grant

> [!WARNING]
> Ми більше не рекомендуємо використовувати токени implicit grant. Натомість вам слід обрати [тип grant, який наразі рекомендує OAuth2 Server](https://oauth2.thephpleague.com/authorization-server/which-grant/).

Implicit grant схожий на authorization code grant; однак токен повертається клієнту без обміну коду авторизації. Цей grant найчастіше використовують для JavaScript- чи мобільних застосунків, де облікові дані клієнта не можна зберігати безпечно. Щоб увімкнути цей grant, викличте метод `enableImplicitGrant` у методі `boot` класу `App\Providers\AppServiceProvider` вашого застосунку:

```php
/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Passport::enableImplicitGrant();
}
```

Перш ніж ваш застосунок зможе видавати токени через implicit grant, вам потрібно створити клієнта implicit grant. Зробити це можна артизан-командою `passport:client` з опцією `--implicit`.

```shell
php artisan passport:client --implicit
```

Щойно grant увімкнено, а implicit-клієнта створено, розробники можуть використовувати свій ID клієнта, щоб запитати токен доступу у вашого застосунку. Застосунок-споживач має зробити запит на перенаправлення до маршруту `/oauth/authorize` вашого застосунку, ось так:

```php
use Illuminate\Http\Request;

Route::get('/redirect', function (Request $request) {
    $request->session()->put('state', $state = Str::random(40));

    $query = http_build_query([
        'client_id' => 'your-client-id',
        'redirect_uri' => 'https://third-party-app.com/callback',
        'response_type' => 'token',
        'scope' => 'user:read orders:create',
        'state' => $state,
        // 'prompt' => '', // "none", "consent", or "login"
    ]);

    return redirect('https://passport-app.test/oauth/authorize?'.$query);
});
```

> [!NOTE]
> Пам'ятайте: маршрут `/oauth/authorize` уже визначено Passport. Вам не потрібно визначати цей маршрут вручну.

<a name="client-credentials-grant"></a>
## Client Credentials Grant

Client credentials grant підходить для автентифікації між машинами. Наприклад, ви можете використати цей grant у запланованому завданні, яке виконує обслуговування через API.

Перш ніж ваш застосунок зможе видавати токени через client credentials grant, вам потрібно створити клієнта client credentials grant. Зробити це можна опцією `--client` артизан-команди `passport:client`:

```shell
php artisan passport:client --client
```

Далі призначте маршруту `middleware` `Laravel\Passport\Http\Middleware\EnsureClientIsResourceOwner`:

```php
use Laravel\Passport\Http\Middleware\EnsureClientIsResourceOwner;

Route::get('/orders', function (Request $request) {
    // Access token is valid and the client is resource owner...
})->middleware(EnsureClientIsResourceOwner::class);
```

Щоб обмежити доступ до маршруту конкретними скопами, передайте список потрібних скопів до методу `using`:

```php
Route::get('/orders', function (Request $request) {
    // Access token is valid, the client is resource owner, and has both "servers:read" and "servers:create" scopes...
})->middleware(EnsureClientIsResourceOwner::using('servers:read', 'servers:create'));
```

> [!WARNING]
> [Сервер OAuth2, що лежить в основі](https://oauth2.thephpleague.com/database-setup/#:~:text=Please%20note%20that,the%20bearer%20token.), встановлює claim `sub` токена в ідентифікатор клієнта для токенів client credentials. За замовчуванням Passport використовує UUID для клієнтів, тож це не може конфліктувати з цілочисловим первинним ключем користувача. Однак, якщо ви встановили `Passport::$clientUuids` у `false`, токен client credentials може ненавмисно знайти користувача, чий ID збігається з ID клієнта. У таких випадках цей `middleware` не може гарантувати, що вхідний токен є токеном client credentials.

<a name="retrieving-tokens"></a>
### Отримання токенів

Щоб отримати токен за допомогою цього типу grant, зробіть запит до ендпоїнта `oauth/token`:

```php
use Illuminate\Support\Facades\Http;

$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
    'grant_type' => 'client_credentials',
    'client_id' => 'your-client-id',
    'client_secret' => 'your-client-secret',
    'scope' => 'servers:read servers:create',
]);

return $response->json()['access_token'];
```

<a name="personal-access-tokens"></a>
## Персональні токени доступу

Іноді ваші користувачі можуть захотіти видати токени доступу самим собі, не проходячи звичний потік перенаправлень з кодом авторизації. Дозвіл користувачам видавати собі токени через UI вашого застосунку може бути корисним, щоб вони могли поекспериментувати з вашим API, або ж бути простішим підходом до видачі токенів доступу загалом.

> [!NOTE]
> Якщо ваш застосунок використовує Passport переважно для видачі персональних токенів доступу, розгляньте [Laravel Sanctum](/docs/{{version}}/sanctum) - легку власну бібліотеку Laravel для видачі API-токенів доступу.

<a name="creating-a-personal-access-client"></a>
### Створення клієнта персонального доступу

Перш ніж ваш застосунок зможе видавати персональні токени доступу, вам потрібно створити клієнта персонального доступу. Зробити це можна, виконавши артизан-команду `passport:client` з опцією `--personal`. Якщо ви вже виконували команду `passport:install`, виконувати цю команду не потрібно:

```shell
php artisan passport:client --personal
```

<a name="customizing-the-user-provider-for-pat"></a>
### Зміна провайдера користувачів

Якщо ваш застосунок використовує більше одного [провайдера користувачів для автентифікації](/docs/{{version}}/authentication#introduction), ви можете вказати, який провайдер користувачів використовує клієнт personal access grant, передавши опцію `--provider` під час створення клієнта командою `artisan passport:client --personal`. Задане ім'я провайдера має відповідати дійсному провайдеру, визначеному в конфігураційному файлі `config/auth.php` вашого застосунку. Далі ви можете [захистити свій маршрут за допомогою middleware](#multiple-authentication-guards), щоб авторизованими були лише користувачі з указаного провайдера гарда.

<a name="managing-personal-access-tokens"></a>
### Керування персональними токенами доступу

Щойно ви створили клієнта персонального доступу, ви можете видавати токени для заданого користувача методом `createToken` на екземплярі моделі `App\Models\User`. Метод `createToken` приймає ім'я токена як перший аргумент і необов'язковий масив [скопів](#token-scopes) як другий:

```php
use App\Models\User;
use Illuminate\Support\Facades\Date;
use Laravel\Passport\Token;

$user = User::find($userId);

// Creating a token without scopes...
$token = $user->createToken('My Token')->accessToken;

// Creating a token with scopes...
$token = $user->createToken('My Token', ['user:read', 'orders:create'])->accessToken;

// Creating a token with all scopes...
$token = $user->createToken('My Token', ['*'])->accessToken;

// Retrieving all the valid personal access tokens that belong to the user...
$tokens = $user->tokens()
    ->with('client')
    ->where('revoked', false)
    ->where('expires_at', '>', Date::now())
    ->get()
    ->filter(fn (Token $token) => $token->client->hasGrantType('personal_access'));
```

<a name="protecting-routes"></a>
## Захист маршрутів

<a name="via-middleware"></a>
### Через middleware

Passport містить [гард автентифікації](/docs/{{version}}/authentication#adding-custom-guards), який перевірятиме токени доступу у вхідних запитах. Щойно ви налаштували гард `api` на використання драйвера `passport`, вам достатньо вказати `middleware` `auth:api` на будь-яких маршрутах, що мають вимагати дійсний токен доступу:

```php
Route::get('/user', function () {
    // Only API authenticated users may access this route...
})->middleware('auth:api');
```

> [!WARNING]
> Якщо ви використовуєте [client credentials grant](#client-credentials-grant), для захисту своїх маршрутів вам слід використовувати [`middleware` `Laravel\Passport\Http\Middleware\EnsureClientIsResourceOwner`](#client-credentials-grant), а не `middleware` `auth:api`.

<a name="multiple-authentication-guards"></a>
#### Кілька гардів автентифікації

Якщо ваш застосунок автентифікує різні типи користувачів, які, можливо, використовують цілком різні Eloquent-моделі, вам, найімовірніше, потрібно буде визначити конфігурацію гарда для кожного типу провайдера користувачів у своєму застосунку. Це дозволяє захищати запити, призначені для конкретних провайдерів користувачів. Наприклад, за такої конфігурації гардів у конфігураційному файлі `config/auth.php`:

```php
'guards' => [
    'api' => [
        'driver' => 'passport',
        'provider' => 'users',
    ],

    'api-customers' => [
        'driver' => 'passport',
        'provider' => 'customers',
    ],
],
```

Наведений нижче маршрут використовуватиме гард `api-customers`, який використовує провайдер користувачів `customers`, для автентифікації вхідних запитів:

```php
Route::get('/customer', function () {
    // ...
})->middleware('auth:api-customers');
```

> [!NOTE]
> Докладніше про використання кількох провайдерів користувачів з Passport дивіться в [документації щодо персональних токенів доступу](#customizing-the-user-provider-for-pat) і [документації щодо password grant](#customizing-the-user-provider).

<a name="passing-the-access-token"></a>
### Передавання токена доступу

Викликаючи маршрути, захищені Passport, споживачі API вашого застосунку мають указувати свій токен доступу як `Bearer`-токен у заголовку `Authorization` свого запиту. Наприклад, використовуючи фасад `Http`:

```php
use Illuminate\Support\Facades\Http;

$response = Http::withHeaders([
    'Accept' => 'application/json',
    'Authorization' => "Bearer $accessToken",
])->get('https://passport-app.test/api/user');

return $response->json();
```

<a name="token-scopes"></a>
## Скопи токенів

Скопи дозволяють вашим API-клієнтам запитувати конкретний набір дозволів, коли вони запитують авторизацію для доступу до облікового запису. Наприклад, якщо ви створюєте застосунок електронної комерції, не всім споживачам API знадобиться можливість оформлювати замовлення. Натомість ви можете дозволити споживачам запитувати авторизацію лише на доступ до статусів відправлення замовлень. Іншими словами, скопи дозволяють користувачам вашого застосунку обмежувати дії, які сторонній застосунок може виконувати від їхнього імені.

<a name="defining-scopes"></a>
### Визначення скопів

Ви можете визначити скопи свого API методом `Passport::tokensCan` у методі `boot` класу `App\Providers\AppServiceProvider` вашого застосунку. Метод `tokensCan` приймає масив імен скопів та їхніх описів. Опис скопу може бути будь-яким, і його буде показано користувачам на екрані схвалення авторизації:

```php
/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Passport::tokensCan([
        'user:read' => 'Retrieve the user info',
        'orders:create' => 'Place orders',
        'orders:read:status' => 'Check order status',
    ]);
}
```

<a name="default-scope"></a>
### Скоп за замовчуванням

Якщо клієнт не запитує жодних конкретних скопів, ви можете налаштувати свій сервер Passport додавати до токена скопи за замовчуванням методом `defaultScopes`. Зазвичай цей метод слід викликати з методу `boot` класу `App\Providers\AppServiceProvider` вашого застосунку:

```php
use Laravel\Passport\Passport;

Passport::tokensCan([
    'user:read' => 'Retrieve the user info',
    'orders:create' => 'Place orders',
    'orders:read:status' => 'Check order status',
]);

Passport::defaultScopes([
    'user:read',
    'orders:create',
]);
```

<a name="assigning-scopes-to-tokens"></a>
### Призначення скопів токенам

<a name="when-requesting-authorization-codes"></a>
#### Під час запиту кодів авторизації

Запитуючи токен доступу через authorization code grant, споживачі мають указати бажані скопи як параметр рядка запиту `scope`. Параметр `scope` має бути списком скопів, розділених пробілами:

```php
Route::get('/redirect', function () {
    $query = http_build_query([
        'client_id' => 'your-client-id',
        'redirect_uri' => 'https://third-party-app.com/callback',
        'response_type' => 'code',
        'scope' => 'user:read orders:create',
    ]);

    return redirect('https://passport-app.test/oauth/authorize?'.$query);
});
```

<a name="when-issuing-personal-access-tokens"></a>
#### Під час видачі персональних токенів доступу

Якщо ви видаєте персональні токени доступу методом `createToken` моделі `App\Models\User`, ви можете передати масив бажаних скопів другим аргументом методу:

```php
$token = $user->createToken('My Token', ['orders:create'])->accessToken;
```

<a name="checking-scopes"></a>
### Перевірка скопів

Passport містить два `middleware`, якими можна перевірити, що вхідний запит автентифіковано токеном, якому надано певний скоп.

<a name="check-for-all-scopes"></a>
#### Перевірка всіх скопів

`middleware` `Laravel\Passport\Http\Middleware\CheckToken` можна призначити маршруту, щоб перевірити, що токен доступу вхідного запиту має всі перелічені скопи:

```php
use Laravel\Passport\Http\Middleware\CheckToken;

Route::get('/orders', function () {
    // Access token has both "orders:read" and "orders:create" scopes...
})->middleware(['auth:api', CheckToken::using('orders:read', 'orders:create')]);
```

<a name="check-for-any-scopes"></a>
#### Перевірка будь-якого зі скопів

`middleware` `Laravel\Passport\Http\Middleware\CheckTokenForAnyScope` можна призначити маршруту, щоб перевірити, що токен доступу вхідного запиту має *принаймні один* з перелічених скопів:

```php
use Laravel\Passport\Http\Middleware\CheckTokenForAnyScope;

Route::get('/orders', function () {
    // Access token has either "orders:read" or "orders:create" scope...
})->middleware(['auth:api', CheckTokenForAnyScope::using('orders:read', 'orders:create')]);
```

<a name="scope-attributes"></a>
#### Атрибути скопів

Якщо ваш застосунок використовує [атрибути middleware контролерів](/docs/{{version}}/controllers#middleware-attributes), ви можете скористатися атрибутом `Laravel\Passport\Attributes\AuthorizeToken` як зручним скороченням для `middleware` скопів у Passport:

```php
<?php

namespace App\Http\Controllers;

use Laravel\Passport\Attributes\AuthorizeToken;

#[AuthorizeToken('orders:read')]
#[AuthorizeToken('orders:create', only: ['store'])]
class OrderController
{
    #[AuthorizeToken(['orders:read', 'orders:create'], anyScope: true)]
    public function index()
    {
        // Access token has either "orders:read" or "orders:create" scope...
    }

    public function store()
    {
        // Access token has both "orders:read" and "orders:create" scopes...
    }
}
```

За замовчуванням атрибут `AuthorizeToken` вимагає всі задані скопи. Якщо ви передасте `anyScope: true`, запит буде авторизовано, коли токен має принаймні один із заданих скопів.

<a name="checking-scopes-on-a-token-instance"></a>
#### Перевірка скопів на екземплярі токена

Щойно запит, автентифікований токеном доступу, потрапив до вашого застосунку, ви все ще можете перевірити, чи має токен певний скоп, методом `tokenCan` на автентифікованому екземплярі `App\Models\User`:

```php
use Illuminate\Http\Request;

Route::get('/orders', function (Request $request) {
    if ($request->user()->tokenCan('orders:create')) {
        // ...
    }
});
```

<a name="additional-scope-methods"></a>
#### Додаткові методи для скопів

Метод `scopeIds` поверне масив усіх визначених ID / імен:

```php
use Laravel\Passport\Passport;

Passport::scopeIds();
```

Метод `scopes` поверне масив усіх визначених скопів як екземплярів `Laravel\Passport\Scope`:

```php
Passport::scopes();
```

Метод `scopesFor` поверне масив екземплярів `Laravel\Passport\Scope`, що відповідають заданим ID / іменам:

```php
Passport::scopesFor(['user:read', 'orders:create']);
```

Ви можете визначити, чи задано певний скоп, методом `hasScope`:

```php
Passport::hasScope('orders:create');
```

<a name="spa-authentication"></a>
## Автентифікація SPA

Створюючи API, буває надзвичайно корисно мати змогу споживати власний API зі свого JavaScript-застосунку. Такий підхід до розробки API дозволяє вашому власному застосунку споживати той самий API, яким ви ділитеся зі світом. Той самий API можуть споживати ваш вебзастосунок, мобільні застосунки, сторонні застосунки та будь-які SDK, які ви можете публікувати в різних менеджерах пакетів.

Зазвичай, якщо ви хочете споживати свій API зі свого JavaScript-застосунку, вам потрібно було б вручну надсилати токен доступу застосунку й передавати його з кожним запитом до вашого застосунку. Однак Passport містить `middleware`, який може впоратися з цим за вас. Усе, що вам потрібно зробити, - додати `middleware` `CreateFreshApiToken` до групи `middleware` `web` у файлі `bootstrap/app.php` вашого застосунку:

```php
use Laravel\Passport\Http\Middleware\CreateFreshApiToken;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->web(append: [
        CreateFreshApiToken::class,
    ]);
})
```

> [!WARNING]
> Переконайтеся, що `middleware` `CreateFreshApiToken` є останнім `middleware` у вашому стеку `middleware`.

Цей `middleware` додасть до ваших вихідних відповідей cookie `laravel_token`. Цей cookie містить зашифрований JWT, який Passport використовуватиме для автентифікації API-запитів з вашого JavaScript-застосунку. Час життя JWT дорівнює значенню вашої конфігурації `session.lifetime`. Тепер, оскільки браузер автоматично надсилатиме цей cookie з усіма наступними запитами, ви можете робити запити до API свого застосунку, не передаючи токен доступу явно:

```js
axios.get('/api/user')
    .then(response => {
        console.log(response.data);
    });
```

<a name="customizing-the-cookie-name"></a>
#### Зміна імені cookie

За потреби ви можете змінити ім'я cookie `laravel_token` методом `Passport::cookie`. Зазвичай цей метод слід викликати з методу `boot` класу `App\Providers\AppServiceProvider` вашого застосунку:

```php
/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Passport::cookie('custom_name');
}
```

<a name="csrf-protection"></a>
#### Захист від CSRF

Використовуючи цей спосіб автентифікації, вам потрібно буде подбати, щоб до ваших запитів було включено дійсний заголовок з CSRF-токеном. Стандартний JavaScript-каркас Laravel, що входить до скелета застосунку й усіх стартових наборів, містить екземпляр [Axios](https://github.com/axios/axios), який автоматично використовуватиме зашифроване значення cookie `XSRF-TOKEN`, щоб надсилати заголовок `X-XSRF-TOKEN` у запитах з того самого джерела.

> [!NOTE]
> Якщо ви вирішите надсилати заголовок `X-CSRF-TOKEN` замість `X-XSRF-TOKEN`, вам потрібно буде використовувати незашифрований токен, який надає `csrf_token()`.

<a name="events"></a>
## Події

Passport здіймає події під час видачі токенів доступу й refresh-токенів. Ви можете [слухати ці події](/docs/{{version}}/events), щоб прибирати чи відкликати інші токени доступу у своїй базі даних:

<div class="overflow-auto">

| Event Name                                    |
| --------------------------------------------- |
| `Laravel\Passport\Events\AccessTokenCreated`  |
| `Laravel\Passport\Events\AccessTokenRevoked`  |
| `Laravel\Passport\Events\RefreshTokenCreated` |

</div>

<a name="testing"></a>
## Тестування

Метод `actingAs` у Passport можна використати, щоб указати поточного автентифікованого користувача, а також його скопи. Перший аргумент методу `actingAs` - екземпляр користувача, а другий - масив скопів, які слід надати токену користувача:

```php tab=Pest
use App\Models\User;
use Laravel\Passport\Passport;

test('orders can be created', function () {
    Passport::actingAs(
        User::factory()->create(),
        ['orders:create']
    );

    $response = $this->post('/api/orders');

    $response->assertStatus(201);
});
```

```php tab=PHPUnit
use App\Models\User;
use Laravel\Passport\Passport;

public function test_orders_can_be_created(): void
{
    Passport::actingAs(
        User::factory()->create(),
        ['orders:create']
    );

    $response = $this->post('/api/orders');

    $response->assertStatus(201);
}
```

Метод `actingAsClient` у Passport можна використати, щоб указати поточного автентифікованого клієнта, а також його скопи. Перший аргумент методу `actingAsClient` - екземпляр клієнта, а другий - масив скопів, які слід надати токену клієнта:

```php tab=Pest
use Laravel\Passport\Client;
use Laravel\Passport\Passport;

test('servers can be retrieved', function () {
    Passport::actingAsClient(
        Client::factory()->create(),
        ['servers:read']
    );

    $response = $this->get('/api/servers');

    $response->assertStatus(200);
});
```

```php tab=PHPUnit
use Laravel\Passport\Client;
use Laravel\Passport\Passport;

public function test_servers_can_be_retrieved(): void
{
    Passport::actingAsClient(
        Client::factory()->create(),
        ['servers:read']
    );

    $response = $this->get('/api/servers');

    $response->assertStatus(200);
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/pennant

# Laravel Pennant

- [Вступ](#introduction)
- [Встановлення](#installation)
- [Конфігурація](#configuration)
- [Визначення можливостей](#defining-features)
    - [Можливості на основі класів](#class-based-features)
- [Перевірка можливостей](#checking-features)
    - [Умовне виконання](#conditional-execution)
    - [Трейт `HasFeatures`](#the-has-features-trait)
    - [Blade-директива](#blade-directive)
    - [Middleware](#middleware)
    - [Перехоплення перевірок можливостей](#intercepting-feature-checks)
    - [Кеш у пам'яті](#in-memory-cache)
- [Скоп](#scope)
    - [Визначення скопу](#specifying-the-scope)
    - [Глобальний скоп](#global-scope)
    - [Скоп за замовчуванням](#default-scope)
    - [Скоп, що допускає null](#nullable-scope)
    - [Ідентифікація скопу](#identifying-scope)
    - [Серіалізація скопу](#serializing-scope)
- [Багатші значення можливостей](#rich-feature-values)
- [Отримання кількох можливостей](#retrieving-multiple-features)
- [Жадібне завантаження](#eager-loading)
- [Оновлення значень](#updating-values)
    - [Масові оновлення](#bulk-updates)
    - [Очищення можливостей](#purging-features)
- [Тестування](#testing)
- [Додавання власних драйверів Pennant](#adding-custom-pennant-drivers)
    - [Реалізація драйвера](#implementing-the-driver)
    - [Реєстрація драйвера](#registering-the-driver)
    - [Визначення можливостей ззовні](#defining-features-externally)
- [Події](#events)

<a name="introduction"></a>
## Вступ

[Laravel Pennant](https://github.com/laravel/pennant) - це простий і легкий пакет для feature-прапорців, без зайвого. Feature-прапорці дозволяють упевнено викочувати нові можливості застосунку поступово, проводити A/B-тестування нових дизайнів інтерфейсу, доповнювати стратегію trunk-based development тощо.

<a name="installation"></a>
## Встановлення

Спершу встановіть Pennant у свій проєкт за допомогою менеджера пакетів Composer:

```shell
composer require laravel/pennant
```

Далі вам слід опублікувати конфігураційний файл і файли міграцій Pennant артизан-командою `vendor:publish`:

```shell
php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"
```

Насамкінець виконайте міграції бази даних вашого застосунку. Це створить таблицю `features`, яку Pennant використовує для роботи свого драйвера `database`:

```shell
php artisan migrate
```

<a name="configuration"></a>
## Конфігурація

Після публікації ассетів Pennant його конфігураційний файл буде розташовано в `config/pennant.php`. Цей конфігураційний файл дозволяє вказати механізм зберігання за замовчуванням, який Pennant використовуватиме для зберігання обчислених значень feature-прапорців.

Pennant підтримує зберігання обчислених значень feature-прапорців у масиві в пам'яті через драйвер `array`. Або ж Pennant може зберігати обчислені значення feature-прапорців постійно в реляційній базі даних через драйвер `database` - механізм зберігання, який Pennant використовує за замовчуванням.

<a name="defining-features"></a>
## Визначення можливостей

Щоб визначити можливість, скористайтеся методом `define`, який надає фасад `Feature`. Вам потрібно буде вказати ім'я можливості, а також замикання, яке буде викликано для обчислення початкового значення можливості.

Зазвичай можливості визначаються в сервіс-провайдері за допомогою фасада `Feature`. Замикання отримає «скоп» для перевірки можливості. Найчастіше скоп - це поточний автентифікований користувач. У цьому прикладі ми визначимо можливість для поступового викочування нового API для користувачів нашого застосунку:

```php
<?php

namespace App\Providers;

use App\Models\User;
use Illuminate\Support\Lottery;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Feature::define('new-api', fn (User $user) => match (true) {
            $user->isInternalTeamMember() => true,
            $user->isHighTrafficCustomer() => false,
            default => Lottery::odds(1 / 100),
        });
    }
}
```

Як бачите, для нашої можливості маємо такі правила:

- Усі члени внутрішньої команди мають користуватися новим API.
- Клієнти з високим трафіком не мають користуватися новим API.
- В інших випадках можливість має призначатися користувачам випадково з імовірністю активації 1 до 100.

Коли можливість `new-api` перевіряється для певного користувача вперше, результат замикання буде збережено драйвером сховища. Наступного разу, коли можливість перевірятиметься для того самого користувача, значення буде взято зі сховища, а замикання не викликатиметься.

Для зручності, якщо визначення можливості повертає лише лотерею, ви можете взагалі опустити замикання:

    Feature::define('site-redesign', Lottery::odds(1, 1000));

<a name="class-based-features"></a>
### Можливості на основі класів

Pennant також дозволяє визначати можливості на основі класів. На відміну від визначень можливостей на основі замикань, реєструвати можливість на основі класу в сервіс-провайдері не потрібно. Щоб створити можливість на основі класу, скористайтеся артизан-командою `pennant:feature`. За замовчуванням клас можливості буде розміщено в каталозі `app/Features` вашого застосунку:

```shell
php artisan pennant:feature NewApi
```

Пишучи клас можливості, вам потрібно визначити лише метод `resolve`, який буде викликано для обчислення початкового значення можливості для заданого скопу. Знову ж таки, скопом зазвичай буде поточний автентифікований користувач:

```php
<?php

namespace App\Features;

use App\Models\User;
use Illuminate\Support\Lottery;

class NewApi
{
    /**
     * Resolve the feature's initial value.
     */
    public function resolve(User $user): mixed
    {
        return match (true) {
            $user->isInternalTeamMember() => true,
            $user->isHighTrafficCustomer() => false,
            default => Lottery::odds(1 / 100),
        };
    }
}
```

Якщо ви хочете вручну отримати екземпляр можливості на основі класу, викличте метод `instance` на фасаді `Feature`:

```php
use App\Features\NewApi;
use Laravel\Pennant\Feature;

$instance = Feature::instance(NewApi::class);
```

> [!NOTE]
> Класи можливостей створюються через [контейнер](/docs/{{version}}/container), тож за потреби ви можете впроваджувати залежності до конструктора класу можливості.

#### Зміна збереженого імені можливості

За замовчуванням Pennant зберігатиме повністю кваліфіковане ім'я класу можливості. Якщо ви хочете відв'язати збережене ім'я можливості від внутрішньої структури застосунку, додайте до класу можливості атрибут `Name`. Значення цього атрибута буде збережено замість імені класу:

```php
<?php

namespace App\Features;

use Laravel\Pennant\Attributes\Name;

#[Name('new-api')]
class NewApi
{
    // ...
}
```

<a name="checking-features"></a>
## Перевірка можливостей

Щоб визначити, чи можливість активна, скористайтеся методом `active` на фасаді `Feature`. За замовчуванням можливості перевіряються для поточного автентифікованого користувача:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Feature;

class PodcastController
{
    /**
     * Display a listing of the resource.
     */
    public function index(Request $request): Response
    {
        return Feature::active('new-api')
            ? $this->resolveNewApiResponse($request)
            : $this->resolveLegacyApiResponse($request);
    }

    // ...
}
```

Хоча за замовчуванням можливості перевіряються для поточного автентифікованого користувача, ви легко можете перевірити можливість для іншого користувача чи [скопу](#scope). Для цього скористайтеся методом `for`, який надає фасад `Feature`:

```php
return Feature::for($user)->active('new-api')
    ? $this->resolveNewApiResponse($request)
    : $this->resolveLegacyApiResponse($request);
```

Pennant також пропонує кілька додаткових зручних методів, які можуть стати в пригоді, коли ви визначаєте, активна можливість чи ні:

```php
// Determine if all of the given features are active...
Feature::allAreActive(['new-api', 'site-redesign']);

// Determine if any of the given features are active...
Feature::someAreActive(['new-api', 'site-redesign']);

// Determine if a feature is inactive...
Feature::inactive('new-api');

// Determine if all of the given features are inactive...
Feature::allAreInactive(['new-api', 'site-redesign']);

// Determine if any of the given features are inactive...
Feature::someAreInactive(['new-api', 'site-redesign']);
```

> [!NOTE]
> Використовуючи Pennant поза HTTP-контекстом, наприклад в артизан-команді чи завданні з черги, вам зазвичай слід [явно вказувати скоп можливості](#specifying-the-scope). Як альтернативу ви можете визначити [скоп за замовчуванням](#default-scope), який враховує як автентифіковані HTTP-контексти, так і неавтентифіковані.

<a name="checking-class-based-features"></a>
#### Перевірка можливостей на основі класів

Для можливостей на основі класів під час перевірки можливості слід передавати ім'я класу:

```php
<?php

namespace App\Http\Controllers;

use App\Features\NewApi;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Feature;

class PodcastController
{
    /**
     * Display a listing of the resource.
     */
    public function index(Request $request): Response
    {
        return Feature::active(NewApi::class)
            ? $this->resolveNewApiResponse($request)
            : $this->resolveLegacyApiResponse($request);
    }

    // ...
}
```

<a name="conditional-execution"></a>
### Умовне виконання

Метод `when` можна використати, щоб плавно виконати задане замикання, якщо можливість активна. Крім того, можна передати друге замикання, яке буде виконано, якщо можливість неактивна:

```php
<?php

namespace App\Http\Controllers;

use App\Features\NewApi;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Feature;

class PodcastController
{
    /**
     * Display a listing of the resource.
     */
    public function index(Request $request): Response
    {
        return Feature::when(NewApi::class,
            fn () => $this->resolveNewApiResponse($request),
            fn () => $this->resolveLegacyApiResponse($request),
        );
    }

    // ...
}
```

Метод `unless` є протилежністю методу `when` і виконує перше замикання, якщо можливість неактивна:

```php
return Feature::unless(NewApi::class,
    fn () => $this->resolveLegacyApiResponse($request),
    fn () => $this->resolveNewApiResponse($request),
);
```

<a name="the-has-features-trait"></a>
### Трейт `HasFeatures`

Трейт `HasFeatures` з Pennant можна додати до моделі `User` вашого застосунку (чи до будь-якої іншої моделі, що має можливості), щоб отримати зручний і плавний спосіб перевіряти можливості безпосередньо з моделі:

```php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Pennant\Concerns\HasFeatures;

class User extends Authenticatable
{
    use HasFeatures;

    // ...
}
```

Щойно трейт буде додано до вашої моделі, ви зможете легко перевіряти можливості, викликаючи метод `features`:

```php
if ($user->features()->active('new-api')) {
    // ...
}
```

Звісно, метод `features` дає доступ до багатьох інших зручних методів для роботи з можливостями:

```php
// Values...
$value = $user->features()->value('purchase-button');
$values = $user->features()->values(['new-api', 'purchase-button']);

// State...
$user->features()->active('new-api');
$user->features()->allAreActive(['new-api', 'server-api']);
$user->features()->someAreActive(['new-api', 'server-api']);

$user->features()->inactive('new-api');
$user->features()->allAreInactive(['new-api', 'server-api']);
$user->features()->someAreInactive(['new-api', 'server-api']);

// Conditional execution...
$user->features()->when('new-api',
    fn () => /* ... */,
    fn () => /* ... */,
);

$user->features()->unless('new-api',
    fn () => /* ... */,
    fn () => /* ... */,
);
```

<a name="blade-directive"></a>
### Blade-директива

Щоб перевірка можливостей у Blade була безшовною, Pennant пропонує директиви `@feature` і `@featureany`:

```blade
@feature('site-redesign')
    <!-- 'site-redesign' is active -->
@else
    <!-- 'site-redesign' is inactive -->
@endfeature

@featureany(['site-redesign', 'beta'])
    <!-- 'site-redesign' or `beta` is active -->
@endfeatureany
```

<a name="middleware"></a>
### Middleware

Pennant також містить [middleware](/docs/{{version}}/middleware), який можна використати, щоб перевірити, чи має поточний автентифікований користувач доступ до можливості, ще до того, як маршрут буде викликано. Ви можете призначити цей `middleware` маршруту й указати можливості, потрібні для доступу до нього. Якщо будь-яка з указаних можливостей неактивна для поточного автентифікованого користувача, маршрут поверне HTTP-відповідь `400 Bad Request`. До статичного методу `using` можна передати кілька можливостей.

```php
use Illuminate\Support\Facades\Route;
use Laravel\Pennant\Middleware\EnsureFeaturesAreActive;

Route::get('/api/servers', function () {
    // ...
})->middleware(EnsureFeaturesAreActive::using('new-api', 'servers-api'));
```

<a name="customizing-the-response"></a>
#### Зміна відповіді

Якщо ви хочете змінити відповідь, яку повертає `middleware`, коли одна з перелічених можливостей неактивна, скористайтеся методом `whenInactive`, який надає `middleware` `EnsureFeaturesAreActive`. Зазвичай цей метод слід викликати в методі `boot` одного із сервіс-провайдерів вашого застосунку:

```php
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Laravel\Pennant\Middleware\EnsureFeaturesAreActive;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    EnsureFeaturesAreActive::whenInactive(
        function (Request $request, array $features) {
            return new Response(status: 403);
        }
    );

    // ...
}
```

<a name="intercepting-feature-checks"></a>
### Перехоплення перевірок можливостей

Іноді буває корисно виконати певні перевірки в пам'яті, перш ніж діставати збережене значення можливості. Уявіть, що ви розробляєте новий API за feature-прапорцем і хочете мати змогу вимкнути новий API, не втративши жодного з обчислених значень можливості у сховищі. Якщо ви помітите баг у новому API, ви зможете легко вимкнути його для всіх, окрім членів внутрішньої команди, виправити баг, а потім знову увімкнути новий API для користувачів, які раніше мали доступ до можливості.

Досягти цього можна методом `before` у [можливості на основі класу](#class-based-features). Якщо метод `before` присутній, він завжди виконується в пам'яті перед отриманням значення зі сховища. Якщо метод поверне значення, відмінне від `null`, воно буде використане замість збереженого значення можливості на час цього запиту:

```php
<?php

namespace App\Features;

use App\Models\User;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Lottery;

class NewApi
{
    /**
     * Run an always-in-memory check before the stored value is retrieved.
     */
    public function before(User $user): mixed
    {
        if (Config::get('features.new-api.disabled')) {
            return $user->isInternalTeamMember();
        }
    }

    /**
     * Resolve the feature's initial value.
     */
    public function resolve(User $user): mixed
    {
        return match (true) {
            $user->isInternalTeamMember() => true,
            $user->isHighTrafficCustomer() => false,
            default => Lottery::odds(1 / 100),
        };
    }
}
```

Ви також можете скористатися цією можливістю, щоб запланувати глобальне викочування можливості, яка раніше була за feature-прапорцем:

```php
<?php

namespace App\Features;

use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Config;

class NewApi
{
    /**
     * Run an always-in-memory check before the stored value is retrieved.
     */
    public function before(User $user): mixed
    {
        if (Config::get('features.new-api.disabled')) {
            return $user->isInternalTeamMember();
        }

        if (Carbon::parse(Config::get('features.new-api.rollout-date'))->isPast()) {
            return true;
        }
    }

    // ...
}
```

<a name="in-memory-cache"></a>
### Кеш у пам'яті

Перевіряючи можливість, Pennant створить кеш результату в пам'яті. Якщо ви використовуєте драйвер `database`, це означає, що повторна перевірка того самого feature-прапорця в межах одного запиту не спричинить додаткових запитів до бази даних. Це також гарантує, що можливість матиме однаковий результат протягом усього запиту.

Якщо вам потрібно вручну скинути кеш у пам'яті, скористайтеся методом `flushCache`, який надає фасад `Feature`:

```php
Feature::flushCache();
```

<a name="scope"></a>
## Скоп

<a name="specifying-the-scope"></a>
### Визначення скопу

Як ми вже обговорювали, можливості зазвичай перевіряються для поточного автентифікованого користувача. Однак це не завжди відповідає вашим потребам. Тому скоп, для якого ви хочете перевірити певну можливість, можна вказати методом `for` фасада `Feature`:

```php
return Feature::for($user)->active('new-api')
    ? $this->resolveNewApiResponse($request)
    : $this->resolveLegacyApiResponse($request);
```

Звісно, скопи можливостей не обмежуються «користувачами». Уявіть, що ви створили новий досвід білінгу, який викочуєте цілим командам, а не окремим користувачам. Можливо, ви хочете, щоб найстаріші команди отримували його повільніше, ніж новіші. Ваше замикання для обчислення можливості могло б виглядати приблизно так:

```php
use App\Models\Team;
use Illuminate\Support\Carbon;
use Illuminate\Support\Lottery;
use Laravel\Pennant\Feature;

Feature::define('billing-v2', function (Team $team) {
    if ($team->created_at->isAfter(new Carbon('1st Jan, 2023'))) {
        return true;
    }

    if ($team->created_at->isAfter(new Carbon('1st Jan, 2019'))) {
        return Lottery::odds(1 / 100);
    }

    return Lottery::odds(1 / 1000);
});
```

Ви помітите, що визначене нами замикання очікує не `User`, а модель `Team`. Щоб визначити, чи активна ця можливість для команди користувача, передайте команду до методу `for`, який надає фасад `Feature`:

```php
if (Feature::for($user->team)->active('billing-v2')) {
    return redirect('/billing/v2');
}

// ...
```

<a name="global-scope"></a>
### Глобальний скоп

Щоб перевірити функцію або взаємодіяти з нею, використовуючи глобальний скоп, незалежно від налаштованого резолвера скопу за замовчуванням, використовуйте метод `globally`. Це корисно для загальноапплікаційних feature-прапорців, наприклад для тимчасового увімкнення режиму обслуговування або розгортання функції для всіх користувачів:

```php
Feature::globally()->active('new-api');

Feature::globally()->activate('new-api');
```

<a name="default-scope"></a>
### Скоп за замовчуванням

Ви також можете змінити скоп за замовчуванням, який Pennant використовує для перевірки можливостей. Наприклад, можливо, усі ваші можливості перевіряються для команди поточного автентифікованого користувача, а не для самого користувача. Замість того щоб щоразу під час перевірки можливості викликати `Feature::for($user->team)`, ви можете вказати команду як скоп за замовчуванням. Зазвичай це слід робити в одному із сервіс-провайдерів вашого застосунку:

```php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Feature::resolveScopeUsing(fn ($driver) => Auth::user()?->team);

        // ...
    }
}
```

Якщо скоп не передано явно через метод `for`, перевірка можливості тепер використовуватиме команду поточного автентифікованого користувача як скоп за замовчуванням:

```php
Feature::active('billing-v2');

// Is now equivalent to...

Feature::for($user->team)->active('billing-v2');
```

<a name="nullable-scope"></a>
### Скоп, що допускає null

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

Тож, якщо скоп, який ви передаєте до можливості, потенційно може бути `null`, і ви хочете, щоб резолвер значення можливості все ж викликався, врахуйте це у визначенні своєї можливості. Скоп `null` може виникнути, якщо ви перевіряєте можливість в артизан-команді, завданні з черги чи на неавтентифікованому маршруті. Оскільки в цих контекстах автентифікованого користувача зазвичай немає, скопом за замовчуванням буде `null`.

Якщо ви не завжди [явно вказуєте скоп можливості](#specifying-the-scope), вам слід переконатися, що тип скопу «допускає null», і обробити значення скопу `null` у логіці визначення вашої можливості:

```php
use App\Models\User;
use Illuminate\Support\Lottery;
use Laravel\Pennant\Feature;

Feature::define('new-api', fn (User $user) => match (true) {// [tl! remove]
Feature::define('new-api', fn (User|null $user) => match (true) {// [tl! add]
    $user === null => true,// [tl! add]
    $user->isInternalTeamMember() => true,
    $user->isHighTrafficCustomer() => false,
    default => Lottery::odds(1 / 100),
});
```

<a name="identifying-scope"></a>
### Ідентифікація скопу

Вбудовані драйвери сховища `array` і `database` у Pennant знають, як правильно зберігати ідентифікатори скопу для всіх типів даних PHP, а також для Eloquent-моделей. Однак, якщо ваш застосунок використовує сторонній драйвер Pennant, той драйвер може не знати, як правильно зберегти ідентифікатор Eloquent-моделі чи інших власних типів у вашому застосунку.

З огляду на це Pennant дозволяє форматувати значення скопу для зберігання, реалізувавши контракт `FeatureScopeable` на об'єктах вашого застосунку, які використовуються як скопи Pennant.

Наприклад, уявіть, що ви використовуєте два різні драйвери можливостей в одному застосунку: вбудований драйвер `database` і сторонній драйвер «Flag Rocket». Драйвер «Flag Rocket» не знає, як правильно зберегти Eloquent-модель. Натомість йому потрібен екземпляр `FlagRocketUser`. Реалізувавши метод `toFeatureIdentifier`, визначений контрактом `FeatureScopeable`, ми можемо налаштувати придатне до зберігання значення скопу, яке передається кожному драйверу, що використовує наш застосунок:

```php
<?php

namespace App\Models;

use FlagRocket\FlagRocketUser;
use Illuminate\Database\Eloquent\Model;
use Laravel\Pennant\Contracts\FeatureScopeable;

class User extends Model implements FeatureScopeable
{
    /**
     * Cast the object to a feature scope identifier for the given driver.
     */
    public function toFeatureIdentifier(string $driver): mixed
    {
        return match($driver) {
            'database' => $this,
            'flag-rocket' => FlagRocketUser::fromId($this->flag_rocket_id),
        };
    }
}
```

<a name="serializing-scope"></a>
### Серіалізація скопу

За замовчуванням Pennant використовуватиме повністю кваліфіковане ім'я класу, зберігаючи можливість, пов'язану з Eloquent-моделлю. Якщо ви вже використовуєте [morph-мапу Eloquent](/docs/{{version}}/eloquent-relationships#custom-polymorphic-types), ви можете зробити так, щоб Pennant теж використовував morph-мапу і відв'язав збережену можливість від структури вашого застосунку.

Щоб досягти цього, після визначення morph-мапи Eloquent у сервіс-провайдері викличте метод `useMorphMap` фасада `Feature`:

```php
use Illuminate\Database\Eloquent\Relations\Relation;
use Laravel\Pennant\Feature;

Relation::enforceMorphMap([
    'post' => 'App\Models\Post',
    'video' => 'App\Models\Video',
]);

Feature::useMorphMap();
```

<a name="rich-feature-values"></a>
## Багатші значення можливостей

Досі ми переважно показували можливості в бінарному стані, тобто вони або «активні», або «неактивні», але Pennant також дозволяє зберігати багатші значення.

Наприклад, уявіть, що ви тестуєте три нові кольори для кнопки «Buy now» у своєму застосунку. Замість того щоб повертати з визначення можливості `true` чи `false`, ви можете повернути рядок:

```php
use Illuminate\Support\Arr;
use Laravel\Pennant\Feature;

Feature::define('purchase-button', fn (User $user) => Arr::random([
    'blue-sapphire',
    'seafoam-green',
    'tart-orange',
]));
```

Отримати значення можливості `purchase-button` можна методом `value`:

```php
$color = Feature::value('purchase-button');
```

Blade-директива, що входить до Pennant, також дозволяє легко умовно рендерити вміст залежно від поточного значення можливості:

```blade
@feature('purchase-button', 'blue-sapphire')
    <!-- 'blue-sapphire' is active -->
@elsefeature('purchase-button', 'seafoam-green')
    <!-- 'seafoam-green' is active -->
@elsefeature('purchase-button', 'tart-orange')
    <!-- 'tart-orange' is active -->
@endfeature
```

> [!NOTE]
> Використовуючи багатші значення, важливо знати, що можливість вважається «активною», коли вона має будь-яке значення, відмінне від `false`.

Під час виклику [умовного методу `when`](#conditional-execution) багатше значення можливості буде передано до першого замикання:

```php
Feature::when('purchase-button',
    fn ($color) => /* ... */,
    fn () => /* ... */,
);
```

Так само, під час виклику умовного методу `unless` багатше значення можливості буде передано до необов'язкового другого замикання:

```php
Feature::unless('purchase-button',
    fn () => /* ... */,
    fn ($color) => /* ... */,
);
```

<a name="retrieving-multiple-features"></a>
## Отримання кількох можливостей

Метод `values` дозволяє отримати кілька можливостей для заданого скопу:

```php
Feature::values(['billing-v2', 'purchase-button']);

// [
//     'billing-v2' => false,
//     'purchase-button' => 'blue-sapphire',
// ]
```

Або ж ви можете скористатися методом `all`, щоб отримати значення всіх визначених можливостей для заданого скопу:

```php
Feature::all();

// [
//     'billing-v2' => false,
//     'purchase-button' => 'blue-sapphire',
//     'site-redesign' => true,
// ]
```

Однак можливості на основі класів реєструються динамічно і невідомі Pennant, доки їх явно не перевірять. Це означає, що можливості на основі класів вашого застосунку можуть не з'явитися в результатах методу `all`, якщо їх ще не перевіряли протягом поточного запиту.

Якщо ви хочете, щоб класи можливостей завжди потрапляли до результатів методу `all`, скористайтеся можливостями виявлення в Pennant. Для початку викличте метод `discover` в одному із сервіс-провайдерів вашого застосунку:

```php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Feature::discover();

        // ...
    }
}
```

Метод `discover` зареєструє всі класи можливостей у каталозі `app/Features` вашого застосунку. Тепер метод `all` включатиме ці класи до своїх результатів незалежно від того, чи перевірялися вони протягом поточного запиту:

```php
Feature::all();

// [
//     'App\Features\NewApi' => true,
//     'billing-v2' => false,
//     'purchase-button' => 'blue-sapphire',
//     'site-redesign' => true,
// ]
```

<a name="eager-loading"></a>
## Жадібне завантаження

Хоча Pennant тримає в пам'яті кеш усіх обчислених можливостей для одного запиту, проблеми з продуктивністю все одно можливі. Щоб полегшити це, Pennant дає змогу жадібно завантажувати значення можливостей.

Щоб проілюструвати це, уявіть, що ми перевіряємо, чи активна можливість, у циклі:

```php
use Laravel\Pennant\Feature;

foreach ($users as $user) {
    if (Feature::for($user)->active('notifications-beta')) {
        $user->notify(new RegistrationSuccess);
    }
}
```

Якщо припустити, що ми використовуємо драйвер database, цей код виконає запит до бази даних для кожного користувача в циклі - потенційно сотні запитів. Однак за допомогою методу `load` у Pennant ми можемо усунути це потенційне вузьке місце продуктивності, жадібно завантаживши значення можливостей для колекції користувачів чи скопів:

```php
Feature::for($users)->load(['notifications-beta']);

foreach ($users as $user) {
    if (Feature::for($user)->active('notifications-beta')) {
        $user->notify(new RegistrationSuccess);
    }
}
```

Щоб завантажити значення можливостей лише тоді, коли їх ще не завантажено, скористайтеся методом `loadMissing`:

```php
Feature::for($users)->loadMissing([
    'new-api',
    'purchase-button',
    'notifications-beta',
]);
```

Завантажити всі визначені можливості можна методом `loadAll`:

```php
Feature::for($users)->loadAll();
```

<a name="updating-values"></a>
## Оновлення значень

Коли значення можливості обчислюється вперше, драйвер, що лежить в основі, збереже результат у сховищі. Часто це потрібно, щоб забезпечити узгоджений досвід для ваших користувачів між запитами. Однак іноді ви можете захотіти вручну оновити збережене значення можливості.

Для цього скористайтеся методами `activate` і `deactivate`, щоб перемкнути можливість «увімкнено» чи «вимкнено»:

```php
use Laravel\Pennant\Feature;

// Activate the feature for the default scope...
Feature::activate('new-api');

// Deactivate the feature for the given scope...
Feature::for($user->team)->deactivate('billing-v2');
```

Також можна вручну встановити багатше значення для можливості, передавши другий аргумент до методу `activate`:

```php
Feature::activate('purchase-button', 'seafoam-green');
```

Щоб указати Pennant забути збережене значення можливості, скористайтеся методом `forget`. Коли можливість буде перевірено знову, Pennant обчислить її значення з визначення можливості:

```php
Feature::forget('purchase-button');
```

<a name="bulk-updates"></a>
### Масові оновлення

Щоб оновити збережені значення можливостей масово, скористайтеся методами `activateForEveryone` і `deactivateForEveryone`.

Наприклад, уявіть, що ви тепер упевнені в стабільності можливості `new-api` і визначилися з найкращим кольором `'purchase-button'` для свого процесу оформлення замовлення - ви можете відповідно оновити збережене значення для всіх користувачів:

```php
use Laravel\Pennant\Feature;

Feature::activateForEveryone('new-api');

Feature::activateForEveryone('purchase-button', 'seafoam-green');
```

Як альтернативу ви можете вимкнути можливість для всіх користувачів:

```php
Feature::deactivateForEveryone('new-api');
```

> [!NOTE]
> Це оновить лише обчислені значення можливостей, збережені драйвером сховища Pennant. Вам також потрібно буде оновити визначення можливості у своєму застосунку.

<a name="purging-features"></a>
### Очищення можливостей

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

Прибрати всі збережені значення можливості можна методом `purge`:

```php
// Purging a single feature...
Feature::purge('new-api');

// Purging multiple features...
Feature::purge(['new-api', 'purchase-button']);
```

Якщо ви хочете прибрати зі сховища _всі_ можливості, викличте метод `purge` без аргументів:

```php
Feature::purge();
```

Оскільки очищення можливостей може бути корисним як частина вашого конвеєра розгортання, Pennant містить артизан-команду `pennant:purge`, яка прибере зі сховища вказані можливості:

```shell
php artisan pennant:purge new-api

php artisan pennant:purge new-api purchase-button
```

Також можна прибрати всі можливості, _окрім_ тих, що є в заданому списку. Наприклад, уявіть, що ви хотіли прибрати всі можливості, але залишити у сховищі значення можливостей «new-api» і «purchase-button». Щоб зробити це, передайте ці імена можливостей до опції `--except`:

```shell
php artisan pennant:purge --except=new-api --except=purchase-button
```

Для зручності команда `pennant:purge` також підтримує прапорець `--except-registered`. Цей прапорець означає, що слід прибрати всі можливості, окрім тих, що явно зареєстровані в сервіс-провайдері:

```shell
php artisan pennant:purge --except-registered
```

<a name="testing"></a>
## Тестування

Тестуючи код, який взаємодіє з feature-прапорцями, найпростіший спосіб керувати значенням, яке повертає feature-прапорець у ваших тестах, - просто перевизначити можливість. Наприклад, уявіть, що у вас в одному із сервіс-провайдерів застосунку визначено таку можливість:

```php
use Illuminate\Support\Arr;
use Laravel\Pennant\Feature;

Feature::define('purchase-button', fn () => Arr::random([
    'blue-sapphire',
    'seafoam-green',
    'tart-orange',
]));
```

Щоб змінити значення, яке повертає можливість, у ваших тестах, перевизначте можливість на початку тесту. Наведений нижче тест завжди проходитиме, навіть попри те, що реалізація `Arr::random()` усе ще присутня в сервіс-провайдері:

```php tab=Pest
use Laravel\Pennant\Feature;

test('it can control feature values', function () {
    Feature::define('purchase-button', 'seafoam-green');

    expect(Feature::value('purchase-button'))->toBe('seafoam-green');
});
```

```php tab=PHPUnit
use Laravel\Pennant\Feature;

public function test_it_can_control_feature_values()
{
    Feature::define('purchase-button', 'seafoam-green');

    $this->assertSame('seafoam-green', Feature::value('purchase-button'));
}
```

Той самий підхід можна застосувати до можливостей на основі класів:

```php tab=Pest
use Laravel\Pennant\Feature;

test('it can control feature values', function () {
    Feature::define(NewApi::class, true);

    expect(Feature::value(NewApi::class))->toBeTrue();
});
```

```php tab=PHPUnit
use App\Features\NewApi;
use Laravel\Pennant\Feature;

public function test_it_can_control_feature_values()
{
    Feature::define(NewApi::class, true);

    $this->assertTrue(Feature::value(NewApi::class));
}
```

Якщо ваша можливість повертає екземпляр `Lottery`, є кілька корисних [хелперів для тестування](/docs/{{version}}/helpers#testing-lotteries).

<a name="store-configuration"></a>
#### Конфігурація сховища

Ви можете налаштувати сховище, яке Pennant використовуватиме під час тестування, визначивши змінну оточення `PENNANT_STORE` у файлі `phpunit.xml` вашого застосунку:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true">
    <!-- ... -->
    <php>
        <env name="PENNANT_STORE" value="array"/>
        <!-- ... -->
    </php>
</phpunit>
```

<a name="adding-custom-pennant-drivers"></a>
## Додавання власних драйверів Pennant

<a name="implementing-the-driver"></a>
#### Реалізація драйвера

Якщо жоден з наявних драйверів сховища Pennant не задовольняє потреби вашого застосунку, ви можете написати власний драйвер сховища. Ваш власний драйвер має реалізовувати інтерфейс `Laravel\Pennant\Contracts\Driver`:

```php
<?php

namespace App\Extensions;

use Laravel\Pennant\Contracts\Driver;

class RedisFeatureDriver implements Driver
{
    public function define(string $feature, callable $resolver): void {}
    public function defined(): array {}
    public function getAll(array $features): array {}
    public function get(string $feature, mixed $scope): mixed {}
    public function set(string $feature, mixed $scope, mixed $value): void {}
    public function setForAllScopes(string $feature, mixed $value): void {}
    public function delete(string $feature, mixed $scope): void {}
    public function purge(array|null $features): void {}
}
```

Тепер нам залишається лише реалізувати кожен із цих методів через підключення Redis. Приклад того, як реалізувати кожен із цих методів, дивіться в `Laravel\Pennant\Drivers\DatabaseDriver` у [вихідному коді Pennant](https://github.com/laravel/pennant/blob/1.x/src/Drivers/DatabaseDriver.php)

> [!NOTE]
> Laravel не постачається з каталогом для ваших розширень. Ви можете розміщувати їх де завгодно. У цьому прикладі ми створили каталог `Extensions`, щоб розмістити в ньому `RedisFeatureDriver`.

<a name="registering-the-driver"></a>
#### Реєстрація драйвера

Щойно ваш драйвер буде реалізовано, можна зареєструвати його в Laravel. Щоб додати до Pennant додаткові драйвери, скористайтеся методом `extend`, який надає фасад `Feature`. Викликати метод `extend` слід у методі `boot` одного із [сервіс-провайдерів](/docs/{{version}}/providers) вашого застосунку:

```php
<?php

namespace App\Providers;

use App\Extensions\RedisFeatureDriver;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Feature::extend('redis', function (Application $app) {
            return new RedisFeatureDriver($app->make('redis'), $app->make('events'), []);
        });
    }
}
```

Щойно драйвер буде зареєстровано, ви можете використовувати драйвер `redis` у конфігураційному файлі `config/pennant.php` вашого застосунку:

```php
'stores' => [

    'redis' => [
        'driver' => 'redis',
        'connection' => null,
    ],

    // ...

],
```

<a name="defining-features-externally"></a>
### Визначення можливостей ззовні

Якщо ваш драйвер є обгорткою навколо сторонньої платформи feature-прапорців, ви, найімовірніше, визначатимете можливості на самій платформі, а не методом `Feature::define` у Pennant. У такому разі ваш власний драйвер має також реалізовувати інтерфейс `Laravel\Pennant\Contracts\DefinesFeaturesExternally`:

```php
<?php

namespace App\Extensions;

use Laravel\Pennant\Contracts\Driver;
use Laravel\Pennant\Contracts\DefinesFeaturesExternally;

class FeatureFlagServiceDriver implements Driver, DefinesFeaturesExternally
{
    /**
     * Get the features defined for the given scope.
     */
    public function definedFeaturesForScope(mixed $scope): array {}

    /* ... */
}
```

Метод `definedFeaturesForScope` має повертати список імен можливостей, визначених для наданого скопу.

<a name="events"></a>
## Події

Pennant диспетчеризує різноманітні події, які можуть стати в пригоді для відстеження feature-прапорців у вашому застосунку.

### `Laravel\Pennant\Events\FeatureRetrieved`

Ця подія диспетчеризується щоразу, коли [перевіряється можливість](#checking-features). Вона може бути корисною для створення й відстеження метрик використання feature-прапорця у вашому застосунку.

### `Laravel\Pennant\Events\FeatureResolved`

Ця подія диспетчеризується, коли значення можливості обчислюється для конкретного скопу вперше.

### `Laravel\Pennant\Events\UnknownFeatureResolved`

Ця подія диспетчеризується, коли невідома можливість обчислюється для конкретного скопу вперше. Слухати цю подію може бути корисно, якщо ви мали намір прибрати feature-прапорець, але випадково залишили розкидані посилання на нього у своєму застосунку:

```php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use Laravel\Pennant\Events\UnknownFeatureResolved;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Event::listen(function (UnknownFeatureResolved $event) {
            Log::error("Resolving unknown feature [{$event->feature}].");
        });
    }
}
```

### `Laravel\Pennant\Events\DynamicallyRegisteringFeatureClass`

Ця подія диспетчеризується, коли [можливість на основі класу](#class-based-features) динамічно перевіряється вперше протягом запиту.

### `Laravel\Pennant\Events\UnexpectedNullScopeEncountered`

Ця подія диспетчеризується, коли скоп `null` передається до визначення можливості, яке [не підтримує null](#nullable-scope).

Ця ситуація обробляється коректно, і можливість поверне `false`. Однак, якщо ви хочете відмовитися від цієї коректної поведінки за замовчуванням, зареєструйте слухача для цієї події в методі `boot` `AppServiceProvider` вашого застосунку:

```php
use Illuminate\Support\Facades\Log;
use Laravel\Pennant\Events\UnexpectedNullScopeEncountered;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Event::listen(UnexpectedNullScopeEncountered::class, fn () => abort(500));
}
```

### `Laravel\Pennant\Events\FeatureUpdated`

Ця подія диспетчеризується під час оновлення можливості для скопу, зазвичай через виклик `activate` чи `deactivate`.

### `Laravel\Pennant\Events\FeatureUpdatedForAllScopes`

Ця подія диспетчеризується під час оновлення можливості для всіх скопів, зазвичай через виклик `activateForEveryone` чи `deactivateForEveryone`.

### `Laravel\Pennant\Events\FeatureDeleted`

Ця подія диспетчеризується під час видалення можливості для скопу, зазвичай через виклик `forget`.

### `Laravel\Pennant\Events\FeaturesPurged`

Ця подія диспетчеризується під час очищення конкретних можливостей.

### `Laravel\Pennant\Events\AllFeaturesPurged`

Ця подія диспетчеризується під час очищення всіх можливостей.

---

Джерело: https://laravelukraine.com/docs/13.x/pint

# Laravel Pint

- [Вступ](#introduction)
- [Встановлення](#installation)
- [Запуск Pint](#running-pint)
- [Налаштування Pint](#configuring-pint)
    - [Пресети](#presets)
    - [Правила](#rules)
    - [Виключення файлів і каталогів](#excluding-files-or-folders)
- [Неперервна інтеграція](#continuous-integration)
    - [GitHub Actions](#running-tests-on-github-actions)

<a name="introduction"></a>
## Вступ

[Laravel Pint](https://github.com/laravel/pint) - це самобутній інструмент виправлення стилю коду PHP для мінімалістів. Pint побудований на [PHP CS Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) і дозволяє легко тримати стиль вашого коду чистим і послідовним.

Pint автоматично встановлюється в усі нові застосунки Laravel, тож ви можете користуватися ним одразу. За замовчуванням Pint не потребує жодної конфігурації й виправлятиме проблеми стилю у вашому коді, дотримуючись самобутнього стилю кодування Laravel.

<a name="installation"></a>
## Встановлення

Pint входить до останніх випусків фреймворку Laravel, тож встановлювати його зазвичай не потрібно. Проте для старіших застосунків ви можете встановити Laravel Pint через Composer:

```shell
composer require laravel/pint --dev
```

<a name="running-pint"></a>
## Запуск Pint

Ви можете доручити Pint виправити проблеми стилю коду, викликавши бінарник `pint`, доступний у каталозі `vendor/bin` вашого проєкту:

```shell
./vendor/bin/pint
```

Якщо ви хочете запустити Pint у паралельному режимі (експериментальному) для кращої продуктивності, скористайтеся опцією `--parallel`:

```shell
./vendor/bin/pint --parallel
```

Паралельний режим також дозволяє вказати максимальну кількість процесів через опцію `--max-processes`. Якщо цю опцію не задано, Pint використає всі доступні ядра на вашій машині:

```shell
./vendor/bin/pint --parallel --max-processes=4
```

Ви також можете запустити Pint на конкретних файлах чи каталогах:

```shell
./vendor/bin/pint app/Models

./vendor/bin/pint app/Models/User.php
```

За замовчуванням Pint не форматує Blade-шаблони. Якщо ви хочете форматувати також і ваші `.blade.php` файли, скористайтеся опцією `--blade`, яка вмикає правило [`Pint/laravel_blade`](#laravel-blade) для поточного запуску без зміни вашого файлу `pint.json`:

```shell
./vendor/bin/pint --blade
```

Pint покаже докладний список усіх файлів, які він оновив. Побачити ще більше деталей про зміни Pint можна, додавши до виклику опцію `-v`:

```shell
./vendor/bin/pint -v
```

Якщо ви хочете, щоб Pint просто перевірив ваш код на помилки стилю, не змінюючи файлів, скористайтеся опцією `--test`. Pint поверне ненульовий код виходу, якщо знайде хоч одну помилку стилю:

```shell
./vendor/bin/pint --test
```

Якщо ви хочете, щоб Pint змінював лише файли, які, за даними Git, відрізняються від указаної гілки, скористайтеся опцією `--diff=[branch]`. Це можна ефективно застосувати у вашому CI-середовищі (наприклад, у GitHub actions), щоб заощадити час, перевіряючи лише нові чи змінені файли:

```shell
./vendor/bin/pint --diff=main
```

Якщо ви хочете, щоб Pint змінював лише файли з незакомміченими змінами за даними Git, скористайтеся опцією `--dirty`:

```shell
./vendor/bin/pint --dirty
```

Якщо ви хочете, щоб Pint виправляв файли з помилками стилю, але водночас завершувався з ненульовим кодом виходу, коли якісь помилки було виправлено, скористайтеся опцією `--repair`:

```shell
./vendor/bin/pint --repair
```

<a name="configuring-pint"></a>
## Налаштування Pint

Як згадувалося раніше, Pint не потребує жодної конфігурації. Проте якщо ви хочете налаштувати пресети, правила чи каталоги для перевірки, створіть файл `pint.json` у кореневому каталозі вашого проєкту:

```json
{
    "preset": "laravel"
}
```

Крім того, якщо ви хочете використати `pint.json` з певного каталогу, додайте до виклику Pint опцію `--config`:

```shell
./vendor/bin/pint --config vendor/my-company/coding-style/pint.json
```

<a name="presets"></a>
### Пресети

Пресети визначають набір правил, за якими виправляються проблеми стилю у вашому коді. За замовчуванням Pint використовує пресет `laravel`, який виправляє проблеми за самобутнім стилем кодування Laravel. Проте ви можете вказати інший пресет, передавши Pint опцію `--preset`:

```shell
./vendor/bin/pint --preset psr12
```

За бажання ви можете задати пресет і у файлі `pint.json` вашого проєкту:

```json
{
    "preset": "psr12"
}
```

Наразі Pint підтримує такі пресети: `laravel`, `per`, `psr12`, `symfony` та `empty`.

<a name="rules"></a>
### Правила

Правила - це настанови щодо стилю, за якими Pint виправлятиме проблеми у вашому коді. Як зазначено вище, пресети - це наперед визначені групи правил, які мають чудово пасувати більшості проєктів на PHP, тож зазвичай вам не доведеться перейматися окремими правилами всередині них.

Проте за бажання ви можете вмикати чи вимикати конкретні правила у своєму файлі `pint.json` або скористатися пресетом `empty` й описати правила з нуля:

```json
{
    "preset": "laravel",
    "rules": {
        "simplified_null_return": true,
        "array_indentation": false,
        "new_with_parentheses": {
            "anonymous_class": true,
            "named_class": true
        }
    }
}
```

Pint побудований на [PHP CS Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer). Тому ви можете скористатися будь-яким з його правил, щоб виправити проблеми стилю у своєму проєкті: [PHP CS Fixer Configurator](https://mlocati.github.io/php-cs-fixer-configurator).

<a name="custom-rules"></a>
#### Власні правила

Окрім правил PHP CS Fixer, Pint надає власні правила з префіксом `Pint/`. За замовчуванням вони вимкнені, але ви можете ввімкнути їх у своєму файлі `pint.json`.

<a name="laravel-blade"></a>
##### `Pint/laravel_blade`

Це правило форматує ваші Blade-шаблони, застосовуючи послідовні відступи, пробіли та форматування атрибутів до ваших `.blade.php` файлів. За замовчуванням Pint не форматує Blade-файли, тому ви повинні увімкнути це правило у вашому файлі `pint.json`:

```json
{
    "preset": "laravel",
    "rules": {
        "Pint/laravel_blade": true
    }
}
```

Після увімкнення Pint форматуватиме ваші Blade-шаблони на додачу до ваших PHP-файлів щоразу, коли запускається:

```shell
./vendor/bin/pint
```

Як альтернатива, якщо ви хочете увімкнути це правило для одного запуску без зміни вашого файлу `pint.json`, ви можете використати опцію `--blade`:

```shell
./vendor/bin/pint --blade
```

Під капотом це правило використовує [Prettier](https://prettier.io) разом із плагінами `prettier-plugin-blade` та `prettier-plugin-tailwindcss`, тому на вашій машині має бути встановлений [Node.js](https://nodejs.org). Під час першого запуску Pint із увімкненим цим правилом Pint виявить відсутні залежності Prettier і запропонує вам встановити їх.

> [!NOTE]
> Це правило автоматично пропускає файли, які зазвичай покладаються на власне форматування, наприклад гайдлайни [Laravel Boost](https://github.com/laravel/boost) та представлення електронних листів, розташовані в каталогах `resources/views/emails` і `resources/views/mail`.

<a name="phpdoc-type-annotations-only"></a>
##### `Pint/phpdoc_type_annotations_only`

Це правило прибирає з вашого коду всі коментарі та прозу з докблоків, лишаючи тільки рядки з анотаціями `@` - як-от `@param`, `@return`, `@var`, `@phpstan-type` тощо:

```php
/**
 * Get the posts for the user. [tl! remove]
 * [tl! remove]
 * @return HasMany<Post, $this>
 */
public function posts(): HasMany
```

Однорядкові коментарі та блокові коментарі без анотацій `@` прибираються цілком. Якщо ви хочете зберегти конкретний коментар, додайте до нього префікс `@note`, `@warning` чи `@todo`:

```php
// @note This comment will be preserved.
```

Щоб увімкнути це правило, додайте його до свого файлу `pint.json`:

```json
{
    "preset": "laravel",
    "rules": {
        "Pint/phpdoc_type_annotations_only": true
    }
}
```

> [!NOTE]
> Це правило автоматично пропускає файли в каталозі `config`, адже конфігураційні файли зазвичай спираються на коментарі як на документацію.

<a name="excluding-files-or-folders"></a>
### Виключення файлів і каталогів

За замовчуванням Pint перевіряє всі файли `.php` у вашому проєкті, окрім тих, що в каталозі `vendor`. Якщо ви хочете виключити більше каталогів, скористайтеся опцією конфігурації `exclude`:

```json
{
    "exclude": [
        "my-specific/folder"
    ]
}
```

Якщо ви хочете виключити всі файли, чиї імена відповідають заданому шаблону, скористайтеся опцією конфігурації `notName`:

```json
{
    "notName": [
        "*-my-file.php"
    ]
}
```

Якщо ви хочете виключити файл, указавши точний шлях до нього, скористайтеся опцією конфігурації `notPath`:

```json
{
    "notPath": [
        "path/to/excluded-file.php"
    ]
}
```

<a name="continuous-integration"></a>
## Неперервна інтеграція

<a name="running-tests-on-github-actions"></a>
### GitHub Actions

Щоб автоматизувати лінтинг вашого проєкту через Laravel Pint, ви можете налаштувати [GitHub Actions](https://github.com/features/actions) запускати Pint щоразу, коли новий код потрапляє на GitHub. Спершу обов'язково надайте робочим процесам дозвіл «Read and write permissions» у GitHub за адресою **Settings > Actions > General > Workflow permissions**. Далі створіть файл `.github/workflows/lint.yml` з таким вмістом:

```yaml
name: Fix Code Style

on: [push]

jobs:
  lint:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: true
      matrix:
        php: [8.4]

    steps:
      - name: Checkout code
        uses: actions/checkout@v5

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
          tools: pint

      - name: Run Pint
        run: pint

      - name: Commit linted files
        uses: stefanzweifel/git-auto-commit-action@v6
```

---

Джерело: https://laravelukraine.com/docs/13.x/precognition

# Precognition

- [Вступ](#introduction)
- [Жива валідація](#live-validation)
    - [Через Vue](#using-vue)
    - [Через React](#using-react)
    - [Через Alpine і Blade](#using-alpine)
    - [Налаштування Axios](#configuring-axios)
- [Валідація масивів](#validating-arrays)
- [Налаштування правил валідації](#customizing-validation-rules)
- [Обробка завантаження файлів](#handling-file-uploads)
- [Керування побічними ефектами](#managing-side-effects)
- [Тестування](#testing)

<a name="introduction"></a>
## Вступ

Laravel Precognition дозволяє передбачити результат майбутнього HTTP-запиту. Одне з головних застосувань Precognition - «жива» валідація у вашому фронтенді на JavaScript без дублювання правил валідації з бекенду застосунку.

Коли Laravel отримує «прекогнітивний запит», він виконає все `middleware` маршруту й розв'яже залежності його контролера, зокрема провалідує [запити форм](/docs/{{version}}/validation#form-request-validation), - але не виконуватиме сам метод контролера.

> [!NOTE]
> Починаючи з Inertia 2.3, підтримка Precognition вбудована. Докладніше читайте в [документації Inertia про форми](https://inertiajs.com/forms). Раніші версії Inertia потребують Precognition 0.x.

<a name="live-validation"></a>
## Жива валідація

<a name="using-vue"></a>
### Через Vue

За допомогою Laravel Precognition ви можете дати користувачам живу валідацію, не дублюючи правила валідації у своєму фронтенді на Vue. Щоб показати, як це працює, побудуймо форму створення нових користувачів у нашому застосунку.

Спершу, щоб увімкнути Precognition для маршруту, додайте до його визначення `middleware` `HandlePrecognitiveRequests`. Вам також слід створити [запит форми](/docs/{{version}}/validation#form-request-validation), який міститиме правила валідації маршруту:

```php
use App\Http\Requests\StoreUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;

Route::post('/users', function (StoreUserRequest $request) {
    // ...
})->middleware([HandlePrecognitiveRequests::class]);
```

Далі встановіть фронтенд-хелпери Laravel Precognition для Vue через NPM:

```shell
npm install laravel-precognition-vue
```

Коли пакет Laravel Precognition встановлено, ви можете створити об'єкт форми функцією `useForm` із Precognition, передавши HTTP-метод (`post`), цільовий URL (`/users`) і початкові дані форми.

Далі, щоб увімкнути живу валідацію, викликайте метод форми `validate` на події `change` кожного поля, передаючи ім'я поля:

```vue
<script setup>
import { useForm } from 'laravel-precognition-vue';

const form = useForm('post', '/users', {
    name: '',
    email: '',
});

const submit = () => form.submit();
</script>

<template>
    <form @submit.prevent="submit">
        <label for="name">Name</label>
        <input
            id="name"
            v-model="form.name"
            @change="form.validate('name')"
        />
        <div v-if="form.invalid('name')">
            {{ form.errors.name }}
        </div>

        <label for="email">Email</label>
        <input
            id="email"
            type="email"
            v-model="form.email"
            @change="form.validate('email')"
        />
        <div v-if="form.invalid('email')">
            {{ form.errors.email }}
        </div>

        <button :disabled="form.processing">
            Create User
        </button>
    </form>
</template>
```

Тепер, поки користувач заповнює форму, Precognition даватиме результати живої валідації на основі правил із запиту форми маршруту. Коли поля форми змінюються, до вашого застосунку Laravel надсилатиметься відкладений (debounced) «прекогнітивний» запит валідації. Ви можете налаштувати час відкладання, викликавши функцію форми `setValidationTimeout`:

```js
form.setValidationTimeout(3000);
```

Доки запит валідації в дорозі, властивість форми `validating` дорівнюватиме `true`:

```html
<div v-if="form.validating">
    Validating...
</div>
```

Будь-які помилки валідації, повернені під час запиту валідації чи надсилання форми, автоматично заповнять об'єкт форми `errors`:

```html
<div v-if="form.invalid('email')">
    {{ form.errors.email }}
</div>
```

Визначити, чи має форма помилки, можна через властивість форми `hasErrors`:

```html
<div v-if="form.hasErrors">
    <!-- ... -->
</div>
```

Ви також можете визначити, чи пройшло поле валідацію, передавши його ім'я до функцій форми `valid` та `invalid` відповідно:

```html
<span v-if="form.valid('email')">
    ✅
</span>

<span v-else-if="form.invalid('email')">
    ❌
</span>
```

> [!WARNING]
> Поле форми буде позначено як дійсне чи недійсне лише після того, як воно зміниться й надійде відповідь валідації.

Якщо ви валідуєте через Precognition лише частину полів форми, вам може знадобитися вручну очистити помилки. Зробити це можна функцією форми `forgetError`:

```html
<input
    id="avatar"
    type="file"
    @change="(e) => {
        form.avatar = e.target.files[0]

        form.forgetError('avatar')
    }"
>
```

Як ми бачили, ви можете підключитися до події `change` поля й валідувати окремі поля в міру взаємодії користувача з ними; проте вам може знадобитися провалідувати поля, з якими користувач ще не взаємодіяв. Так буває при побудові «майстра», де ви хочете провалідувати всі видимі поля - незалежно від того, чи торкався їх користувач, - перш ніж переходити до наступного кроку.

Щоб зробити це через Precognition, викличте метод `validate`, передавши імена потрібних полів у ключ конфігурації `only`. Обробити результат валідації можна колбеками `onSuccess` чи `onValidationError`:

```html
<button
    type="button"
    @click="form.validate({
        only: ['name', 'email', 'phone'],
        onSuccess: (response) => nextStep(),
        onValidationError: (response) => /* ... */,
    })"
>Next Step</button>
```

Звісно, ви можете виконувати код і у відповідь на результат надсилання форми. Функція форми `submit` повертає проміс запиту Axios. Це зручний спосіб дістатися даних відповіді, скинути поля форми після успішного надсилання чи обробити невдалий запит:

```js
const submit = () => form.submit()
    .then(response => {
        form.reset();

        alert('User created.');
    })
    .catch(error => {
        alert('An error occurred.');
    });
```

Визначити, чи запит на надсилання форми в дорозі, можна за властивістю форми `processing`:

```html
<button :disabled="form.processing">
    Submit
</button>
```

<a name="using-react"></a>
### Через React

За допомогою Laravel Precognition ви можете дати користувачам живу валідацію, не дублюючи правила валідації у своєму фронтенді на React. Щоб показати, як це працює, побудуймо форму створення нових користувачів у нашому застосунку.

Спершу, щоб увімкнути Precognition для маршруту, додайте до його визначення `middleware` `HandlePrecognitiveRequests`. Вам також слід створити [запит форми](/docs/{{version}}/validation#form-request-validation), який міститиме правила валідації маршруту:

```php
use App\Http\Requests\StoreUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;

Route::post('/users', function (StoreUserRequest $request) {
    // ...
})->middleware([HandlePrecognitiveRequests::class]);
```

Далі встановіть фронтенд-хелпери Laravel Precognition для React через NPM:

```shell
npm install laravel-precognition-react
```

Коли пакет Laravel Precognition встановлено, ви можете створити об'єкт форми функцією `useForm` із Precognition, передавши HTTP-метод (`post`), цільовий URL (`/users`) і початкові дані форми.

Щоб увімкнути живу валідацію, слухайте події `change` і `blur` кожного поля. В обробнику `change` задавайте дані форми функцією `setData`, передаючи ім'я поля та нове значення. Далі, в обробнику `blur`, викликайте метод форми `validate`, передаючи ім'я поля:

```jsx
import { useForm } from 'laravel-precognition-react';

export default function Form() {
    const form = useForm('post', '/users', {
        name: '',
        email: '',
    });

    const submit = (e) => {
        e.preventDefault();

        form.submit();
    };

    return (
        <form onSubmit={submit}>
            <label htmlFor="name">Name</label>
            <input
                id="name"
                value={form.data.name}
                onChange={(e) => form.setData('name', e.target.value)}
                onBlur={() => form.validate('name')}
            />
            {form.invalid('name') && <div>{form.errors.name}</div>}

            <label htmlFor="email">Email</label>
            <input
                id="email"
                value={form.data.email}
                onChange={(e) => form.setData('email', e.target.value)}
                onBlur={() => form.validate('email')}
            />
            {form.invalid('email') && <div>{form.errors.email}</div>}

            <button disabled={form.processing}>
                Create User
            </button>
        </form>
    );
};
```

Тепер, поки користувач заповнює форму, Precognition даватиме результати живої валідації на основі правил із запиту форми маршруту. Коли поля форми змінюються, до вашого застосунку Laravel надсилатиметься відкладений (debounced) «прекогнітивний» запит валідації. Ви можете налаштувати час відкладання, викликавши функцію форми `setValidationTimeout`:

```js
form.setValidationTimeout(3000);
```

Доки запит валідації в дорозі, властивість форми `validating` дорівнюватиме `true`:

```jsx
{form.validating && <div>Validating...</div>}
```

Будь-які помилки валідації, повернені під час запиту валідації чи надсилання форми, автоматично заповнять об'єкт форми `errors`:

```jsx
{form.invalid('email') && <div>{form.errors.email}</div>}
```

Визначити, чи має форма помилки, можна через властивість форми `hasErrors`:

```jsx
{form.hasErrors && <div><!-- ... --></div>}
```

Ви також можете визначити, чи пройшло поле валідацію, передавши його ім'я до функцій форми `valid` та `invalid` відповідно:

```jsx
{form.valid('email') && <span>✅</span>}

{form.invalid('email') && <span>❌</span>}
```

> [!WARNING]
> Поле форми буде позначено як дійсне чи недійсне лише після того, як воно зміниться й надійде відповідь валідації.

Якщо ви валідуєте через Precognition лише частину полів форми, вам може знадобитися вручну очистити помилки. Зробити це можна функцією форми `forgetError`:

```jsx
<input
    id="avatar"
    type="file"
    onChange={(e) => {
        form.setData('avatar', e.target.files[0]);

        form.forgetError('avatar');
    }}
>
```

Як ми бачили, ви можете підключитися до події `blur` поля й валідувати окремі поля в міру взаємодії користувача з ними; проте вам може знадобитися провалідувати поля, з якими користувач ще не взаємодіяв. Так буває при побудові «майстра», де ви хочете провалідувати всі видимі поля - незалежно від того, чи торкався їх користувач, - перш ніж переходити до наступного кроку.

Щоб зробити це через Precognition, викличте метод `validate`, передавши імена потрібних полів у ключ конфігурації `only`. Обробити результат валідації можна колбеками `onSuccess` чи `onValidationError`:

```jsx
<button
    type="button"
    onClick={() => form.validate({
        only: ['name', 'email', 'phone'],
        onSuccess: (response) => nextStep(),
        onValidationError: (response) => /* ... */,
    })}
>Next Step</button>
```

Звісно, ви можете виконувати код і у відповідь на результат надсилання форми. Функція форми `submit` повертає проміс запиту Axios. Це зручний спосіб дістатися даних відповіді, скинути поля форми після успішного надсилання чи обробити невдалий запит:

```js
const submit = (e) => {
    e.preventDefault();

    form.submit()
        .then(response => {
            form.reset();

            alert('User created.');
        })
        .catch(error => {
            alert('An error occurred.');
        });
};
```

Визначити, чи запит на надсилання форми в дорозі, можна за властивістю форми `processing`:

```html
<button disabled={form.processing}>
    Submit
</button>
```

<a name="using-alpine"></a>
### Через Alpine і Blade

За допомогою Laravel Precognition ви можете дати користувачам живу валідацію, не дублюючи правила валідації у своєму фронтенді на Alpine. Щоб показати, як це працює, побудуймо форму створення нових користувачів у нашому застосунку.

Спершу, щоб увімкнути Precognition для маршруту, додайте до його визначення `middleware` `HandlePrecognitiveRequests`. Вам також слід створити [запит форми](/docs/{{version}}/validation#form-request-validation), який міститиме правила валідації маршруту:

```php
use App\Http\Requests\CreateUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;

Route::post('/users', function (CreateUserRequest $request) {
    // ...
})->middleware([HandlePrecognitiveRequests::class]);
```

Далі встановіть фронтенд-хелпери Laravel Precognition для Alpine через NPM:

```shell
npm install laravel-precognition-alpine
```

Далі зареєструйте плагін Precognition в Alpine у своєму файлі `resources/js/app.js`:

```js
import Alpine from 'alpinejs';
import Precognition from 'laravel-precognition-alpine';

window.Alpine = Alpine;

Alpine.plugin(Precognition);
Alpine.start();
```

Коли пакет Laravel Precognition встановлено й зареєстровано, ви можете створити об'єкт форми через «магію» `$form` із Precognition, передавши HTTP-метод (`post`), цільовий URL (`/users`) і початкові дані форми.

Щоб увімкнути живу валідацію, прив'яжіть дані форми до відповідного поля, а потім слухайте подію `change` кожного поля. В обробнику `change` викликайте метод форми `validate`, передаючи ім'я поля:

```html
<form x-data="{
    form: $form('post', '/register', {
        name: '',
        email: '',
    }),
}">
    @csrf
    <label for="name">Name</label>
    <input
        id="name"
        name="name"
        x-model="form.name"
        @change="form.validate('name')"
    />
    <template x-if="form.invalid('name')">
        <div x-text="form.errors.name"></div>
    </template>

    <label for="email">Email</label>
    <input
        id="email"
        name="email"
        x-model="form.email"
        @change="form.validate('email')"
    />
    <template x-if="form.invalid('email')">
        <div x-text="form.errors.email"></div>
    </template>

    <button :disabled="form.processing">
        Create User
    </button>
</form>
```

Тепер, поки користувач заповнює форму, Precognition даватиме результати живої валідації на основі правил із запиту форми маршруту. Коли поля форми змінюються, до вашого застосунку Laravel надсилатиметься відкладений (debounced) «прекогнітивний» запит валідації. Ви можете налаштувати час відкладання, викликавши функцію форми `setValidationTimeout`:

```js
form.setValidationTimeout(3000);
```

Доки запит валідації в дорозі, властивість форми `validating` дорівнюватиме `true`:

```html
<template x-if="form.validating">
    <div>Validating...</div>
</template>
```

Будь-які помилки валідації, повернені під час запиту валідації чи надсилання форми, автоматично заповнять об'єкт форми `errors`:

```html
<template x-if="form.invalid('email')">
    <div x-text="form.errors.email"></div>
</template>
```

Визначити, чи має форма помилки, можна через властивість форми `hasErrors`:

```html
<template x-if="form.hasErrors">
    <div><!-- ... --></div>
</template>
```

Ви також можете визначити, чи пройшло поле валідацію, передавши його ім'я до функцій форми `valid` та `invalid` відповідно:

```html
<template x-if="form.valid('email')">
    <span>✅</span>
</template>

<template x-if="form.invalid('email')">
    <span>❌</span>
</template>
```

> [!WARNING]
> Поле форми буде позначено як дійсне чи недійсне лише після того, як воно зміниться й надійде відповідь валідації.

Як ми бачили, ви можете підключитися до події `change` поля й валідувати окремі поля в міру взаємодії користувача з ними; проте вам може знадобитися провалідувати поля, з якими користувач ще не взаємодіяв. Так буває при побудові «майстра», де ви хочете провалідувати всі видимі поля - незалежно від того, чи торкався їх користувач, - перш ніж переходити до наступного кроку.

Щоб зробити це через Precognition, викличте метод `validate`, передавши імена потрібних полів у ключ конфігурації `only`. Обробити результат валідації можна колбеками `onSuccess` чи `onValidationError`:

```html
<button
    type="button"
    @click="form.validate({
        only: ['name', 'email', 'phone'],
        onSuccess: (response) => nextStep(),
        onValidationError: (response) => /* ... */,
    })"
>Next Step</button>
```

Визначити, чи запит на надсилання форми в дорозі, можна за властивістю форми `processing`:

```html
<button :disabled="form.processing">
    Submit
</button>
```

<a name="repopulating-old-form-data"></a>
#### Повторне заповнення старих даних форми

У наведеному вище прикладі створення користувача ми використовуємо Precognition для живої валідації; проте саму форму надсилаємо традиційно, на бік сервера. Тож форму слід заповнити «старими» даними й помилками валідації, поверненими після серверного надсилання:

```html
<form x-data="{
    form: $form('post', '/register', {
        name: '{{ old('name') }}',
        email: '{{ old('email') }}',
    }).setErrors({{ Js::from($errors->messages()) }}),
}">
```

Або ж, якщо ви хочете надсилати форму через XHR, скористайтеся функцією форми `submit`, яка повертає проміс запиту Axios:

```html
<form
    x-data="{
        form: $form('post', '/register', {
            name: '',
            email: '',
        }),
        submit() {
            this.form.submit()
                .then(response => {
                    this.form.reset();

                    alert('User created.')
                })
                .catch(error => {
                    alert('An error occurred.');
                });
        },
    }"
    @submit.prevent="submit"
>
```

<a name="configuring-axios"></a>
### Налаштування Axios

Бібліотеки валідації Precognition використовують HTTP-клієнт [Axios](https://github.com/axios/axios), щоб надсилати запити до бекенду вашого застосунку. Для зручності екземпляр Axios можна налаштувати, якщо цього вимагає ваш застосунок. Наприклад, користуючись бібліотекою `laravel-precognition-vue`, ви можете додати додаткові заголовки до кожного вихідного запиту у файлі `resources/js/app.js` вашого застосунку:

```js
import { client } from 'laravel-precognition-vue';

client.axios().defaults.headers.common['Authorization'] = authToken;
```

Або ж, якщо у вас уже є налаштований екземпляр Axios, ви можете вказати Precognition використовувати саме його:

```js
import Axios from 'axios';
import { client } from 'laravel-precognition-vue';

window.axios = Axios.create()
window.axios.defaults.headers.common['Authorization'] = authToken;

client.use(window.axios)
```

<a name="validating-arrays"></a>
## Валідація масивів

Ви можете скористатися символами підстановки, щоб валідувати поля всередині масивів чи вкладених об'єктів. Кожен `*` відповідає одному сегменту шляху:

```js
// Validate email for all users in an array...
form.validate('users.*.email');

// Validate all fields in a profile object...
form.validate('profile.*');

// Validate all fields for all users...
form.validate('users.*.*');
```

<a name="customizing-validation-rules"></a>
## Налаштування правил валідації

Правила валідації, які виконуються під час прекогнітивного запиту, можна налаштувати методом запиту `isPrecognitive`.

Наприклад, у формі створення користувача ми можемо захотіти перевіряти пароль на «невикритість» лише при остаточному надсиланні форми. Для прекогнітивних запитів валідації ми просто перевірятимемо, що пароль обов'язковий і має щонайменше 8 символів. Методом `isPrecognitive` ми можемо налаштувати правила, визначені в нашому запиті форми:

```php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Password;

class StoreUserRequest extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'password' => [
                'required',
                $this->isPrecognitive()
                    ? Password::min(8)
                    : Password::min(8)->uncompromised(),
            ],
            // ...
        ];
    }
}
```

<a name="handling-file-uploads"></a>
## Обробка завантаження файлів

За замовчуванням Laravel Precognition не завантажує й не валідує файли під час прекогнітивного запиту валідації. Це гарантує, що великі файли не завантажуватимуться зайвий раз кілька разів.

Через таку поведінку вам слід переконатися, що ваш застосунок [налаштовує відповідні правила валідації запиту форми](#customizing-validation-rules) так, щоб поле було обов'язковим лише при повному надсиланні форми:

```php
/**
 * Get the validation rules that apply to the request.
 *
 * @return array
 */
public function rules()
{
    return [
        'avatar' => [
            ...$this->isPrecognitive() ? [] : ['required'],
            'image',
            'mimes:jpg,png',
            'dimensions:ratio=3/2',
        ],
        // ...
    ];
}
```

Якщо ви хочете включати файли до кожного запиту валідації, викличте функцію `validateFiles` на екземплярі форми на боці клієнта:

```js
form.validateFiles();
```

<a name="managing-side-effects"></a>
## Керування побічними ефектами

Додаючи до маршруту `middleware` `HandlePrecognitiveRequests`, подумайте, чи немає в _іншому_ `middleware` побічних ефектів, які слід пропустити під час прекогнітивного запиту.

Наприклад, у вас може бути `middleware`, яке рахує загальну кількість «взаємодій» кожного користувача з вашим застосунком, - але ви можете не хотіти, щоб прекогнітивні запити рахувалися як взаємодія. Щоб цього досягти, перевіряйте метод запиту `isPrecognitive` перед збільшенням лічильника:

```php
<?php

namespace App\Http\Middleware;

use App\Facades\Interaction;
use Closure;
use Illuminate\Http\Request;

class InteractionMiddleware
{
    /**
     * Handle an incoming request.
     */
    public function handle(Request $request, Closure $next): mixed
    {
        if (! $request->isPrecognitive()) {
            Interaction::incrementFor($request->user());
        }

        return $next($request);
    }
}
```

<a name="testing"></a>
## Тестування

Якщо ви хочете робити прекогнітивні запити у своїх тестах, `TestCase` у Laravel містить хелпер `withPrecognition`, який додасть заголовок запиту `Precognition`.

Крім того, якщо ви хочете перевірити, що прекогнітивний запит був успішним - тобто не повернув помилок валідації, - скористайтеся методом `assertSuccessfulPrecognition` на відповіді:

```php tab=Pest
it('validates registration form with precognition', function () {
    $response = $this->withPrecognition()
        ->post('/register', [
            'name' => 'Taylor Otwell',
        ]);

    $response->assertSuccessfulPrecognition();

    expect(User::count())->toBe(0);
});
```

```php tab=PHPUnit
public function test_it_validates_registration_form_with_precognition()
{
    $response = $this->withPrecognition()
        ->post('/register', [
            'name' => 'Taylor Otwell',
        ]);

    $response->assertSuccessfulPrecognition();
    $this->assertSame(0, User::count());
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/prompts

# Prompts

- [Вступ](#introduction)
- [Встановлення](#installation)
- [Доступні промпти](#available-prompts)
    - [Text](#text)
    - [Textarea](#textarea)
    - [Number](#number)
    - [Password](#password)
    - [Confirm](#confirm)
    - [Select](#select)
    - [Multi-select](#multiselect)
    - [Suggest](#suggest)
    - [Search](#search)
    - [Multi-search](#multisearch)
    - [Pause](#pause)
    - [Autocomplete](#autocomplete)
- [Перетворення вводу перед валідацією](#transforming-input-before-validation)
- [Форми](#forms)
- [Інформаційні повідомлення](#informational-messages)
- [Виноски](#callouts)
- [Таблиці](#tables)
- [Spin](#spin)
- [Індикатор прогресу](#progress)
- [Task](#task)
- [Stream](#stream)
- [Заголовок термінала](#terminal-title)
- [Очищення термінала](#clear)
- [Що врахувати щодо термінала](#terminal-considerations)
- [Непідтримувані середовища та запасні варіанти](#fallbacks)
- [Тестування](#testing)

<a name="introduction"></a>
## Вступ

[Laravel Prompts](https://github.com/laravel/prompts) - це PHP-пакет для додавання гарних і зручних форм до ваших консольних застосунків, з можливостями як у браузері - зокрема текстом-підказкою та валідацією.

<img src="https://laravel.com/img/docs/prompts-example.png">

Laravel Prompts чудово пасує для отримання вводу користувача у ваших [консольних командах Artisan](/docs/{{version}}/artisan#writing-commands), але його можна використовувати і в будь-якому консольному проєкті на PHP.

> [!NOTE]
> Laravel Prompts підтримує macOS, Linux і Windows із WSL. Докладніше читайте в нашій документації про [непідтримувані середовища та запасні варіанти](#fallbacks).

<a name="installation"></a>
## Встановлення

Laravel Prompts уже входить до останнього випуску Laravel.

Laravel Prompts можна також встановити в інші ваші проєкти на PHP через менеджер пакетів Composer:

```shell
composer require laravel/prompts
```

<a name="available-prompts"></a>
## Доступні промпти

<a name="text"></a>
### Text

Функція `text` поставить користувачеві задане запитання, прийме його ввід і поверне його:

```php
use function Laravel\Prompts\text;

$name = text('What is your name?');
```

Ви також можете додати текст-підказку, значення за замовчуванням та інформаційну підказку:

```php
$name = text(
    label: 'What is your name?',
    placeholder: 'E.g. Taylor Otwell',
    default: $user?->name,
    hint: 'This will be displayed on your profile.'
);
```

<a name="text-required"></a>
#### Обов'язкові значення

Якщо ви вимагаєте, щоб значення було введено, передайте аргумент `required`:

```php
$name = text(
    label: 'What is your name?',
    required: true
);
```

Якщо ви хочете змінити повідомлення про помилку валідації, передайте рядок:

```php
$name = text(
    label: 'What is your name?',
    required: 'Your name is required.'
);
```

<a name="text-validation"></a>
#### Додаткова валідація

Нарешті, якщо ви хочете виконати додаткову логіку валідації, передайте замикання в аргумент `validate`:

```php
$name = text(
    label: 'What is your name?',
    validate: fn (string $value) => match (true) {
        strlen($value) < 3 => 'The name must be at least 3 characters.',
        strlen($value) > 255 => 'The name must not exceed 255 characters.',
        default => null
    }
);
```

Замикання отримає введене значення й може повернути повідомлення про помилку або `null`, якщо валідація пройшла.

Або ж ви можете скористатися силою [валідатора](/docs/{{version}}/validation) Laravel. Для цього передайте в аргумент `validate` масив з іменем атрибута та потрібними правилами валідації:

```php
$name = text(
    label: 'What is your name?',
    validate: ['name' => 'required|max:255|unique:users']
);
```

<a name="textarea"></a>
### Textarea

Функція `textarea` поставить користувачеві задане запитання, прийме його ввід через багаторядкове поле й поверне його:

```php
use function Laravel\Prompts\textarea;

$story = textarea('Tell me a story.');
```

Ви також можете додати текст-підказку, значення за замовчуванням та інформаційну підказку:

```php
$story = textarea(
    label: 'Tell me a story.',
    placeholder: 'This is a story about...',
    hint: 'This will be displayed on your profile.'
);
```

<a name="textarea-required"></a>
#### Обов'язкові значення

Якщо ви вимагаєте, щоб значення було введено, передайте аргумент `required`:

```php
$story = textarea(
    label: 'Tell me a story.',
    required: true
);
```

Якщо ви хочете змінити повідомлення про помилку валідації, передайте рядок:

```php
$story = textarea(
    label: 'Tell me a story.',
    required: 'A story is required.'
);
```

<a name="textarea-validation"></a>
#### Додаткова валідація

Нарешті, якщо ви хочете виконати додаткову логіку валідації, передайте замикання в аргумент `validate`:

```php
$story = textarea(
    label: 'Tell me a story.',
    validate: fn (string $value) => match (true) {
        strlen($value) < 250 => 'The story must be at least 250 characters.',
        strlen($value) > 10000 => 'The story must not exceed 10,000 characters.',
        default => null
    }
);
```

Замикання отримає введене значення й може повернути повідомлення про помилку або `null`, якщо валідація пройшла.

Або ж ви можете скористатися силою [валідатора](/docs/{{version}}/validation) Laravel. Для цього передайте в аргумент `validate` масив з іменем атрибута та потрібними правилами валідації:

```php
$story = textarea(
    label: 'Tell me a story.',
    validate: ['story' => 'required|max:10000']
);
```

<a name="number"></a>
### Number

Функція `number` поставить користувачеві задане запитання, прийме його числовий ввід і поверне його. Функція `number` дозволяє користувачеві змінювати число клавішами зі стрілками вгору й вниз:

```php
use function Laravel\Prompts\number;

$number = number('How many copies would you like?');
```

Ви також можете додати текст-підказку, значення за замовчуванням та інформаційну підказку:

```php
$name = number(
    label: 'How many copies would you like?',
    placeholder: '5',
    default: 1,
    hint: 'This will be determine how many copies to create.'
);
```

<a name="number-required"></a>
#### Обов'язкові значення

Якщо ви вимагаєте, щоб значення було введено, передайте аргумент `required`:

```php
$copies = number(
    label: 'How many copies would you like?',
    required: true
);
```

Якщо ви хочете змінити повідомлення про помилку валідації, передайте рядок:

```php
$copies = number(
    label: 'How many copies would you like?',
    required: 'A number of copies is required.'
);
```

<a name="number-validation"></a>
#### Додаткова валідація

Нарешті, якщо ви хочете виконати додаткову логіку валідації, передайте замикання в аргумент `validate`:

```php
$copies = number(
    label: 'How many copies would you like?',
    validate: fn (?int $value) => match (true) {
        $value < 1 => 'At least one copy is required.',
        $value > 100 => 'You may not create more than 100 copies.',
        default => null
    }
);
```

Замикання отримає введене значення й може повернути повідомлення про помилку або `null`, якщо валідація пройшла.

Або ж ви можете скористатися силою [валідатора](/docs/{{version}}/validation) Laravel. Для цього передайте в аргумент `validate` масив з іменем атрибута та потрібними правилами валідації:

```php
$copies = number(
    label: 'How many copies would you like?',
    validate: ['copies' => 'required|integer|min:1|max:100']
);
```

<a name="password"></a>
### Password

Функція `password` схожа на функцію `text`, але ввід користувача маскується під час набору в консолі. Це стає в пригоді, коли ви запитуєте чутливу інформацію - як-от паролі:

```php
use function Laravel\Prompts\password;

$password = password('What is your password?');
```

Ви також можете додати текст-підказку та інформаційну підказку:

```php
$password = password(
    label: 'What is your password?',
    placeholder: 'password',
    hint: 'Minimum 8 characters.'
);
```

<a name="password-required"></a>
#### Обов'язкові значення

Якщо ви вимагаєте, щоб значення було введено, передайте аргумент `required`:

```php
$password = password(
    label: 'What is your password?',
    required: true
);
```

Якщо ви хочете змінити повідомлення про помилку валідації, передайте рядок:

```php
$password = password(
    label: 'What is your password?',
    required: 'The password is required.'
);
```

<a name="password-validation"></a>
#### Додаткова валідація

Нарешті, якщо ви хочете виконати додаткову логіку валідації, передайте замикання в аргумент `validate`:

```php
$password = password(
    label: 'What is your password?',
    validate: fn (string $value) => match (true) {
        strlen($value) < 8 => 'The password must be at least 8 characters.',
        default => null
    }
);
```

Замикання отримає введене значення й може повернути повідомлення про помилку або `null`, якщо валідація пройшла.

Або ж ви можете скористатися силою [валідатора](/docs/{{version}}/validation) Laravel. Для цього передайте в аргумент `validate` масив з іменем атрибута та потрібними правилами валідації:

```php
$password = password(
    label: 'What is your password?',
    validate: ['password' => 'min:8']
);
```

<a name="confirm"></a>
### Confirm

Якщо вам треба запитати в користувача підтвердження «так чи ні», скористайтеся функцією `confirm`. Користувачі можуть скористатися клавішами зі стрілками або натиснути `y` чи `n`, щоб обрати відповідь. Ця функція поверне `true` або `false`.

```php
use function Laravel\Prompts\confirm;

$confirmed = confirm('Do you accept the terms?');
```

Ви також можете додати значення за замовчуванням, власні написи для «Yes» і «No» та інформаційну підказку:

```php
$confirmed = confirm(
    label: 'Do you accept the terms?',
    default: false,
    yes: 'I accept',
    no: 'I decline',
    hint: 'The terms must be accepted to continue.'
);
```

<a name="confirm-required"></a>
#### Вимога відповіді «Yes»

За потреби ви можете зобов'язати користувачів обрати «Yes», передавши аргумент `required`:

```php
$confirmed = confirm(
    label: 'Do you accept the terms?',
    required: true
);
```

Якщо ви хочете змінити повідомлення про помилку валідації, передайте рядок:

```php
$confirmed = confirm(
    label: 'Do you accept the terms?',
    required: 'You must accept the terms to continue.'
);
```

<a name="select"></a>
### Select

Якщо вам потрібно, щоб користувач обрав із наперед визначеного набору варіантів, скористайтеся функцією `select`:

```php
use function Laravel\Prompts\select;

$role = select(
    label: 'What role should the user have?',
    options: ['Member', 'Contributor', 'Owner']
);
```

Ви також можете вказати варіант за замовчуванням та інформаційну підказку:

```php
$role = select(
    label: 'What role should the user have?',
    options: ['Member', 'Contributor', 'Owner'],
    default: 'Owner',
    hint: 'The role may be changed at any time.'
);
```

Ви також можете передати в аргумент `options` асоціативний масив, щоб повертався ключ обраного варіанта, а не його значення:

```php
$role = select(
    label: 'What role should the user have?',
    options: [
        'member' => 'Member',
        'contributor' => 'Contributor',
        'owner' => 'Owner',
    ],
    default: 'owner'
);
```

До п'яти варіантів буде показано, перш ніж список почне прокручуватися. Ви можете змінити це, передавши аргумент `scroll`:

```php
$role = select(
    label: 'Which category would you like to assign?',
    options: Category::pluck('name', 'id'),
    scroll: 10
);
```

<a name="select-info"></a>
#### Додаткова інформація

Аргумент `info` дозволяє показувати додаткову інформацію про поточний підсвічений варіант. Якщо передати замикання, воно отримає значення підсвіченого варіанта й має повернути рядок або `null`:

```php
$role = select(
    label: 'What role should the user have?',
    options: [
        'member' => 'Member',
        'contributor' => 'Contributor',
        'owner' => 'Owner',
    ],
    info: fn (string $value) => match ($value) {
        'member' => 'Can view and comment.',
        'contributor' => 'Can view, comment, and edit.',
        'owner' => 'Full access to all resources.',
        default => null,
    }
);
```

Ви також можете передати в аргумент `info` статичний рядок, якщо інформація не залежить від підсвіченого варіанта:

```php
$role = select(
    label: 'What role should the user have?',
    options: ['Member', 'Contributor', 'Owner'],
    info: 'The role may be changed at any time.'
);
```

<a name="select-validation"></a>
#### Додаткова валідація

На відміну від інших функцій-промптів, функція `select` не приймає аргумент `required`, адже не обрати нічого неможливо. Проте ви можете передати замикання в аргумент `validate`, якщо вам треба показати варіант, але завадити його вибору:

```php
$role = select(
    label: 'What role should the user have?',
    options: [
        'member' => 'Member',
        'contributor' => 'Contributor',
        'owner' => 'Owner',
    ],
    validate: fn (string $value) =>
        $value === 'owner' && User::where('role', 'owner')->exists()
            ? 'An owner already exists.'
            : null
);
```

Якщо аргумент `options` - асоціативний масив, замикання отримає обраний ключ, інакше - обране значення. Замикання може повернути повідомлення про помилку або `null`, якщо валідація пройшла.

<a name="multiselect"></a>
### Multi-select

Якщо вам потрібно, щоб користувач міг обрати кілька варіантів, скористайтеся функцією `multiselect`:

```php
use function Laravel\Prompts\multiselect;

$permissions = multiselect(
    label: 'What permissions should be assigned?',
    options: ['Read', 'Create', 'Update', 'Delete']
);
```

Ви також можете вказати варіанти за замовчуванням та інформаційну підказку:

```php
use function Laravel\Prompts\multiselect;

$permissions = multiselect(
    label: 'What permissions should be assigned?',
    options: ['Read', 'Create', 'Update', 'Delete'],
    default: ['Read', 'Create'],
    hint: 'Permissions may be updated at any time.'
);
```

Ви також можете передати в аргумент `options` асоціативний масив, щоб повертати ключі обраних варіантів, а не їхні значення:

```php
$permissions = multiselect(
    label: 'What permissions should be assigned?',
    options: [
        'read' => 'Read',
        'create' => 'Create',
        'update' => 'Update',
        'delete' => 'Delete',
    ],
    default: ['read', 'create']
);
```

До п'яти варіантів буде показано, перш ніж список почне прокручуватися. Ви можете змінити це, передавши аргумент `scroll`:

```php
$categories = multiselect(
    label: 'What categories should be assigned?',
    options: Category::pluck('name', 'id'),
    scroll: 10
);
```

<a name="multiselect-info"></a>
#### Додаткова інформація

Аргумент `info` дозволяє показувати додаткову інформацію про поточний підсвічений варіант. Якщо передати замикання, воно отримає значення підсвіченого варіанта й має повернути рядок або `null`:

```php
$permissions = multiselect(
    label: 'What permissions should be assigned?',
    options: [
        'read' => 'Read',
        'create' => 'Create',
        'update' => 'Update',
        'delete' => 'Delete',
    ],
    info: fn (string $value) => match ($value) {
        'read' => 'View resources and their properties.',
        'create' => 'Create new resources.',
        'update' => 'Modify existing resources.',
        'delete' => 'Permanently remove resources.',
        default => null,
    }
);
```

<a name="multiselect-required"></a>
#### Вимога значення

За замовчуванням користувач може обрати нуль чи більше варіантів. Ви можете передати аргумент `required`, щоб вимагати щонайменше один:

```php
$categories = multiselect(
    label: 'What categories should be assigned?',
    options: Category::pluck('name', 'id'),
    required: true
);
```

Якщо ви хочете змінити повідомлення про помилку валідації, передайте рядок в аргумент `required`:

```php
$categories = multiselect(
    label: 'What categories should be assigned?',
    options: Category::pluck('name', 'id'),
    required: 'You must select at least one category'
);
```

<a name="multiselect-validation"></a>
#### Додаткова валідація

Ви можете передати замикання в аргумент `validate`, якщо вам треба показати варіант, але завадити його вибору:

```php
$permissions = multiselect(
    label: 'What permissions should the user have?',
    options: [
        'read' => 'Read',
        'create' => 'Create',
        'update' => 'Update',
        'delete' => 'Delete',
    ],
    validate: fn (array $values) => ! in_array('read', $values)
        ? 'All users require the read permission.'
        : null
);
```

Якщо аргумент `options` - асоціативний масив, замикання отримає обрані ключі, інакше - обрані значення. Замикання може повернути повідомлення про помилку або `null`, якщо валідація пройшла.

<a name="suggest"></a>
### Suggest

Функція `suggest` дозволяє додати автодоповнення для можливих варіантів. Користувач і далі може ввести будь-яку відповідь, незалежно від підказок автодоповнення:

```php
use function Laravel\Prompts\suggest;

$name = suggest('What is your name?', ['Taylor', 'Dayle']);
```

Або ж ви можете передати замикання другим аргументом до функції `suggest`. Замикання викликатиметься щоразу, коли користувач вводить символ. Воно має приймати рядковий параметр із уже введеним текстом і повертати масив варіантів для автодоповнення:

```php
$name = suggest(
    label: 'What is your name?',
    options: fn ($value) => collect(['Taylor', 'Dayle'])
        ->filter(fn ($name) => Str::contains($name, $value, ignoreCase: true))
)
```

Ви також можете додати текст-підказку, значення за замовчуванням та інформаційну підказку:

```php
$name = suggest(
    label: 'What is your name?',
    options: ['Taylor', 'Dayle'],
    placeholder: 'E.g. Taylor',
    default: $user?->name,
    hint: 'This will be displayed on your profile.'
);
```

<a name="suggest-info"></a>
#### Додаткова інформація

Аргумент `info` дозволяє показувати додаткову інформацію про поточний підсвічений варіант. Якщо передати замикання, воно отримає значення підсвіченого варіанта й має повернути рядок або `null`:

```php
$name = suggest(
    label: 'What is your name?',
    options: ['Taylor', 'Dayle'],
    info: fn (string $value) => match ($value) {
        'Taylor' => 'Administrator',
        'Dayle' => 'Contributor',
        default => null,
    }
);
```

<a name="suggest-required"></a>
#### Обов'язкові значення

Якщо ви вимагаєте, щоб значення було введено, передайте аргумент `required`:

```php
$name = suggest(
    label: 'What is your name?',
    options: ['Taylor', 'Dayle'],
    required: true
);
```

Якщо ви хочете змінити повідомлення про помилку валідації, передайте рядок:

```php
$name = suggest(
    label: 'What is your name?',
    options: ['Taylor', 'Dayle'],
    required: 'Your name is required.'
);
```

<a name="suggest-validation"></a>
#### Додаткова валідація

Нарешті, якщо ви хочете виконати додаткову логіку валідації, передайте замикання в аргумент `validate`:

```php
$name = suggest(
    label: 'What is your name?',
    options: ['Taylor', 'Dayle'],
    validate: fn (string $value) => match (true) {
        strlen($value) < 3 => 'The name must be at least 3 characters.',
        strlen($value) > 255 => 'The name must not exceed 255 characters.',
        default => null
    }
);
```

Замикання отримає введене значення й може повернути повідомлення про помилку або `null`, якщо валідація пройшла.

Або ж ви можете скористатися силою [валідатора](/docs/{{version}}/validation) Laravel. Для цього передайте в аргумент `validate` масив з іменем атрибута та потрібними правилами валідації:

```php
$name = suggest(
    label: 'What is your name?',
    options: ['Taylor', 'Dayle'],
    validate: ['name' => 'required|min:3|max:255']
);
```

<a name="search"></a>
### Search

Якщо у вас багато варіантів на вибір, функція `search` дозволяє користувачеві ввести пошуковий запит, щоб відфільтрувати результати, а вже потім обрати варіант клавішами зі стрілками:

```php
use function Laravel\Prompts\search;

$id = search(
    label: 'Search for the user that should receive the mail',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : []
);
```

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

Фільтруючи масив, коли ви маєте намір повертати значення, скористайтеся функцією `array_values` або методом колекції `values`, щоб масив не став асоціативним:

```php
$names = collect(['Taylor', 'Abigail']);

$selected = search(
    label: 'Search for the user that should receive the mail',
    options: fn (string $value) => $names
        ->filter(fn ($name) => Str::contains($name, $value, ignoreCase: true))
        ->values()
        ->all(),
);
```

Ви також можете додати текст-підказку та інформаційну підказку:

```php
$id = search(
    label: 'Search for the user that should receive the mail',
    placeholder: 'E.g. Taylor Otwell',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    hint: 'The user will receive an email immediately.'
);
```

До п'яти варіантів буде показано, перш ніж список почне прокручуватися. Ви можете змінити це, передавши аргумент `scroll`:

```php
$id = search(
    label: 'Search for the user that should receive the mail',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    scroll: 10
);
```

<a name="search-info"></a>
#### Додаткова інформація

Аргумент `info` дозволяє показувати додаткову інформацію про поточний підсвічений варіант. Якщо передати замикання, воно отримає значення підсвіченого варіанта й має повернути рядок або `null`:

```php
$id = search(
    label: 'Search for the user that should receive the mail',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    info: fn (int $userId) => User::find($userId)?->email
);
```

<a name="search-validation"></a>
#### Додаткова валідація

Якщо ви хочете виконати додаткову логіку валідації, передайте замикання в аргумент `validate`:

```php
$id = search(
    label: 'Search for the user that should receive the mail',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    validate: function (int|string $value) {
        $user = User::findOrFail($value);

        if ($user->opted_out) {
            return 'This user has opted-out of receiving mail.';
        }
    }
);
```

Якщо замикання `options` повертає асоціативний масив, замикання валідації отримає обраний ключ, інакше - обране значення. Замикання може повернути повідомлення про помилку або `null`, якщо валідація пройшла.

<a name="multisearch"></a>
### Multi-search

Якщо у вас багато варіантів для пошуку й користувачеві треба обрати кілька, функція `multisearch` дозволяє ввести пошуковий запит, щоб відфільтрувати результати, а вже потім обрати варіанти клавішами зі стрілками й пробілом:

```php
use function Laravel\Prompts\multisearch;

$ids = multisearch(
    'Search for users who should receive the mail',
    fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : []
);
```

Замикання отримає текст, уже введений користувачем, і має повернути масив варіантів. Якщо ви повернете асоціативний масив, буде повернено ключі обраних варіантів; інакше - їхні значення.

Фільтруючи масив, коли ви маєте намір повертати значення, скористайтеся функцією `array_values` або методом колекції `values`, щоб масив не став асоціативним:

```php
$names = collect(['Taylor', 'Abigail']);

$selected = multisearch(
    label: 'Search for users who should receive the mail',
    options: fn (string $value) => $names
        ->filter(fn ($name) => Str::contains($name, $value, ignoreCase: true))
        ->values()
        ->all(),
);
```

Ви також можете додати текст-підказку та інформаційну підказку:

```php
$ids = multisearch(
    label: 'Search for users who should receive the mail',
    placeholder: 'E.g. Taylor Otwell',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    hint: 'The user will receive an email immediately.'
);
```

До п'яти варіантів буде показано, перш ніж список почне прокручуватися. Ви можете змінити це, передавши аргумент `scroll`:

```php
$ids = multisearch(
    label: 'Search for the users that should receive the mail',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    scroll: 10
);
```

<a name="multisearch-info"></a>
#### Додаткова інформація

Аргумент `info` дозволяє показувати додаткову інформацію про поточний підсвічений варіант. Якщо передати замикання, воно отримає значення підсвіченого варіанта й має повернути рядок або `null`:

```php
$ids = multisearch(
    label: 'Search for the users that should receive the mail',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    info: fn (int $userId) => User::find($userId)?->email
);
```

<a name="multisearch-required"></a>
#### Вимога значення

За замовчуванням користувач може обрати нуль чи більше варіантів. Ви можете передати аргумент `required`, щоб вимагати щонайменше один:

```php
$ids = multisearch(
    label: 'Search for the users that should receive the mail',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    required: true
);
```

Якщо ви хочете змінити повідомлення про помилку валідації, передайте рядок в аргумент `required`:

```php
$ids = multisearch(
    label: 'Search for the users that should receive the mail',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    required: 'You must select at least one user.'
);
```

<a name="multisearch-validation"></a>
#### Додаткова валідація

Якщо ви хочете виконати додаткову логіку валідації, передайте замикання в аргумент `validate`:

```php
$ids = multisearch(
    label: 'Search for the users that should receive the mail',
    options: fn (string $value) => strlen($value) > 0
        ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
        : [],
    validate: function (array $values) {
        $optedOut = User::whereLike('name', '%a%')->findMany($values);

        if ($optedOut->isNotEmpty()) {
            return $optedOut->pluck('name')->join(', ', ', and ').' have opted out.';
        }
    }
);
```

Якщо замикання `options` повертає асоціативний масив, замикання валідації отримає обрані ключі; інакше - обрані значення. Замикання може повернути повідомлення про помилку або `null`, якщо валідація пройшла.

<a name="pause"></a>
### Pause

Функція `pause` дозволяє показати користувачеві інформаційний текст і зачекати, доки він підтвердить бажання рухатися далі, натиснувши клавішу Enter / Return:

```php
use function Laravel\Prompts\pause;

pause('Press ENTER to continue.');
```

<a name="autocomplete"></a>
### Autocomplete

Функція `autocomplete` дозволяє додати вбудоване автодоповнення для можливих варіантів. Поки користувач набирає текст, підказки, що відповідають його вводу, з'являтимуться як примарний текст, який можна прийняти натисканням `Tab` чи стрілки вправо:

```php
use function Laravel\Prompts\autocomplete;

$name = autocomplete(
    label: 'What is your name?',
    options: ['Taylor', 'Dayle', 'Jess', 'Nuno', 'Tim']
);
```

Ви також можете додати текст-підказку, значення за замовчуванням та інформаційну підказку:

```php
$name = autocomplete(
    label: 'What is your name?',
    options: ['Taylor', 'Dayle', 'Jess', 'Nuno', 'Tim'],
    placeholder: 'E.g. Taylor',
    default: $user?->name,
    hint: 'Use tab to accept, up/down to cycle.'
);
```

<a name="autocomplete-closure"></a>
#### Динамічні варіанти

Ви також можете передати замикання, щоб динамічно генерувати варіанти за вводом користувача. Замикання викликатиметься щоразу, коли користувач вводить символ, і має повертати масив варіантів для автодоповнення:

```php
$file = autocomplete(
    label: 'Which file?',
    options: fn (string $value) => collect($files)
        ->filter(fn ($file) => str_starts_with(strtolower($file), strtolower($value)))
        ->values()
        ->all(),
);
```

<a name="autocomplete-required"></a>
#### Обов'язкові значення

Якщо ви вимагаєте, щоб значення було введено, передайте аргумент `required`:

```php
$name = autocomplete(
    label: 'What is your name?',
    options: ['Taylor', 'Dayle', 'Jess', 'Nuno', 'Tim'],
    required: true
);
```

Якщо ви хочете змінити повідомлення про помилку валідації, передайте рядок:

```php
$name = autocomplete(
    label: 'What is your name?',
    options: ['Taylor', 'Dayle', 'Jess', 'Nuno', 'Tim'],
    required: 'Your name is required.'
);
```

<a name="autocomplete-validation"></a>
#### Додаткова валідація

Нарешті, якщо ви хочете виконати додаткову логіку валідації, передайте замикання в аргумент `validate`:

```php
$name = autocomplete(
    label: 'What is your name?',
    options: ['Taylor', 'Dayle', 'Jess', 'Nuno', 'Tim'],
    validate: fn (string $value) => match (true) {
        strlen($value) < 3 => 'The name must be at least 3 characters.',
        strlen($value) > 255 => 'The name must not exceed 255 characters.',
        default => null
    }
);
```

Замикання отримає введене значення й може повернути повідомлення про помилку або `null`, якщо валідація пройшла.

<a name="transforming-input-before-validation"></a>
## Перетворення вводу перед валідацією

Іноді вам може знадобитися перетворити ввід промпту, перш ніж відбудеться валідація. Наприклад, ви можете захотіти прибрати пробіли з переданих рядків. Для цього багато функцій-промптів надають аргумент `transform`, який приймає замикання:

```php
$name = text(
    label: 'What is your name?',
    transform: fn (string $value) => trim($value),
    validate: fn (string $value) => match (true) {
        strlen($value) < 3 => 'The name must be at least 3 characters.',
        strlen($value) > 255 => 'The name must not exceed 255 characters.',
        default => null
    }
);
```

<a name="forms"></a>
## Форми

Часто у вас буде кілька промптів, які показуються послідовно, щоб зібрати інформацію перед подальшими діями. Ви можете скористатися функцією `form`, щоб створити згрупований набір промптів для заповнення:

```php
use function Laravel\Prompts\form;

$responses = form()
    ->text('What is your name?', required: true)
    ->password('What is your password?', validate: ['password' => 'min:8'])
    ->confirm('Do you accept the terms?')
    ->submit();
```

Метод `submit` поверне масив із числовими індексами, що містить усі відповіді з промптів форми. Проте ви можете дати кожному промпту ім'я через аргумент `name`. Коли ім'я задано, до відповіді цього промпту можна звертатися за цим іменем:

```php
use App\Models\User;
use function Laravel\Prompts\form;

$responses = form()
    ->text('What is your name?', required: true, name: 'name')
    ->password(
        label: 'What is your password?',
        validate: ['password' => 'min:8'],
        name: 'password'
    )
    ->confirm('Do you accept the terms?')
    ->submit();

User::create([
    'name' => $responses['name'],
    'password' => $responses['password'],
]);
```

Головна перевага функції `form` - можливість повернутися до попередніх промптів форми через `CTRL + U`. Це дозволяє користувачеві виправити помилки чи змінити вибір, не скасовуючи й не починаючи форму заново.

Якщо вам потрібен тонший контроль над промптом у формі, викличте метод `add` замість того, щоб напряму викликати одну з функцій-промптів. Методу `add` передаються всі попередні відповіді користувача:

```php
use function Laravel\Prompts\form;
use function Laravel\Prompts\outro;
use function Laravel\Prompts\text;

$responses = form()
    ->text('What is your name?', required: true, name: 'name')
    ->add(function ($responses) {
        return text("How old are you, {$responses['name']}?");
    }, name: 'age')
    ->submit();

outro("Your name is {$responses['name']} and you are {$responses['age']} years old.");
```

<a name="informational-messages"></a>
## Інформаційні повідомлення

Функції `note`, `info`, `warning`, `error` та `alert` дозволяють показувати інформаційні повідомлення:

```php
use function Laravel\Prompts\info;

info('Package installed successfully.');
```

<a name="callouts"></a>
## Виноски

Функція `callout` показує повідомлення в рамці із заголовком і вмістом. Виноски стають у пригоді, щоб показати важливу інформацію, яка має вирізнятися, - як-от підсумки розгортання, деталі помилок чи оновлення статусу:

```php
use function Laravel\Prompts\callout;

callout(
    label: 'Environment Configured',
    content: 'Your application is running in production mode with 4 workers.',
);
```

Ви можете передати `warning` чи `error` в аргумент `type`, щоб змінити візуальний стиль виноски:

```php
callout(
    label: 'Deprecation Notice',
    content: 'The `--prefer-stable` flag will be removed in v4.0. Use `--stability=stable` instead.',
    type: 'warning',
);

callout(
    label: 'Database Connection Failed',
    content: 'Could not connect to MySQL on 127.0.0.1:3306.',
    type: 'error',
);
```

Аргумент `info` додає до виноски рядок-підвал, що стає в пригоді для показу метаданих на кшталт ID чи часових міток:

```php
callout(
    label: 'Deployment Summary',
    content: 'Your application was deployed to production.',
    info: 'deploy-id: d4f8a2c',
);
```

<a name="callout-rich-content"></a>
#### Насичений вміст

Замість рядка ви можете передати масив рядків та елементів, щоб побудувати насичені структуровані виноски. Клас `Element` надає фабричні методи для створення заголовків, марковані та нумеровані списки, списки «ключ - значення» та посилання:

```php
use Laravel\Prompts\Elements\Element;

use function Laravel\Prompts\callout;

callout('Deployment Summary', [
    'Your application was deployed to production at 2024-03-15 14:32 UTC.',
    Element::heading('What Changed'),
    Element::bulletedList([
        'Migrated 3 pending database migrations',
        'Cleared and rebuilt route cache',
        'Restarted 4 queue workers',
    ]),
    Element::heading('Next Steps'),
    Element::numberedList([
        'Verify the health check endpoint at /up',
        'Monitor error rates for the next 15 minutes',
        'Confirm background jobs are processing',
    ]),
]);
```

Ви також можете скористатися `Element::keyValueList`, щоб показати дані з підписами:

```php
callout('Database Connection Failed', [
    'Could not connect to the database server.',
    Element::keyValueList([
        'Host' => '127.0.0.1',
        'Port' => '3306',
        'Database' => 'forge',
        'Status' => 'Connection refused',
    ]),
], type: 'error');
```

Метод `Element::link` створює клікабельне гіперпосилання в терміналах, які підтримують [OSC 8](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda). Ви можете передати сам URL або URL із власним підписом:

```php
callout('Server Health Check', [
    'Multiple services are reporting degraded performance.',
    Element::heading('Affected Services'),
    'Look here: '.Element::link('https://example.com/health', 'Health Dashboard'),
    Element::link('https://example.com/health'),
]);
```

Якщо підпис не задано, як текст посилання буде показано сам URL.

<a name="tables"></a>
## Таблиці

Функція `table` дозволяє легко показати кілька рядків і стовпців даних. Усе, що вам треба, - передати імена стовпців і дані таблиці:

```php
use function Laravel\Prompts\table;

table(
    headers: ['Name', 'Email'],
    rows: User::all(['name', 'email'])->toArray()
);
```

<a name="spin"></a>
## Spin

Функція `spin` показує спінер разом із необов'язковим повідомленням, доки виконується заданий колбек. Вона слугує індикатором тривалих процесів і повертає результати колбека після завершення:

```php
use function Laravel\Prompts\spin;

$response = spin(
    callback: fn () => Http::get('http://example.com'),
    message: 'Fetching response...'
);
```

> [!WARNING]
> Функція `spin` вимагає розширення PHP [PCNTL](https://www.php.net/manual/en/book.pcntl.php), щоб анімувати спінер. Коли це розширення недоступне, натомість з'явиться статична версія спінера.

<a name="progress"></a>
## Індикатори прогресу

Для тривалих завдань буває корисно показати індикатор прогресу, який повідомляє користувачам, наскільки завдання виконане. За допомогою функції `progress` Laravel покаже індикатор прогресу й просуватиме його на кожній ітерації по заданому ітерабельному значенню:

```php
use function Laravel\Prompts\progress;

$users = progress(
    label: 'Updating users',
    steps: User::all(),
    callback: fn ($user) => $this->performTask($user)
);
```

Функція `progress` працює як функція map і поверне масив із результатами кожної ітерації вашого колбека.

Колбек може також приймати екземпляр `Laravel\Prompts\Progress`, що дозволяє змінювати підпис і підказку на кожній ітерації:

```php
$users = progress(
    label: 'Updating users',
    steps: User::all(),
    callback: function ($user, $progress) {
        $progress
            ->label("Updating {$user->name}")
            ->hint("Created on {$user->created_at}");

        return $this->performTask($user);
    },
    hint: 'This may take some time.'
);
```

Іноді вам може знадобитися ручніший контроль над просуванням індикатора. Спершу задайте загальну кількість кроків, які пройде процес. Далі просувайте індикатор методом `advance` після обробки кожного елемента:

```php
$progress = progress(label: 'Updating users', steps: 10);

$users = User::all();

$progress->start();

foreach ($users as $user) {
    $this->performTask($user);

    $progress->advance();
}

$progress->finish();
```

<a name="task"></a>
## Task

Функція `task` показує підписане завдання зі спінером і прокручуваною областю живого виводу, доки виконується заданий колбек. Вона ідеальна, щоб обгорнути тривалі процеси - як-от встановлення залежностей чи скрипти розгортання, - даючи змогу бачити в реальному часі, що відбувається:

```php
use function Laravel\Prompts\task;

task(
    label: 'Installing dependencies',
    callback: function ($logger) {
        // Long-running process...
    }
);
```

Колбек отримує екземпляр `Logger`, яким ви можете показувати рядки логу, повідомлення про статус і потоковий текст в області виводу завдання.

> [!WARNING]
> Функція `task` вимагає розширення PHP [PCNTL](https://www.php.net/manual/en/book.pcntl.php), щоб анімувати спінер. Коли це розширення недоступне, натомість з'явиться статична версія завдання.

<a name="task-logging"></a>
#### Запис рядків логу

Метод `line` пише один рядок логу до прокручуваної області виводу завдання:

```php
task(
    label: 'Installing dependencies',
    callback: function ($logger) {
        $logger->line('Resolving packages...');
        // ...
        $logger->line('Downloading laravel/framework');
        // ...
    }
);
```

<a name="task-status-messages"></a>
#### Повідомлення про статус

Ви можете скористатися методами `success`, `warning` та `error`, щоб показувати повідомлення про статус. Вони з'являються як стабільні підсвічені повідомлення над прокручуваною областю логу:

```php
task(
    label: 'Deploying application',
    callback: function ($logger) {
        $logger->line('Pulling latest changes...');
        // ...
        $logger->success('Changes pulled!');

        $logger->line('Running migrations...');
        // ...
        $logger->warning('No new migrations to run.');

        $logger->line('Clearing cache...');
        // ...
        $logger->success('Cache cleared!');
    }
);
```

<a name="task-label"></a>
#### Оновлення підпису

Метод `label` дозволяє оновлювати підпис завдання, доки воно виконується:

```php
task(
    label: 'Starting deployment...',
    callback: function ($logger) {
        $logger->label('Pulling latest changes...');
        // ...
        $logger->label('Running migrations...');
        // ...
        $logger->label('Clearing cache...');
        // ...
    }
);
```

<a name="task-sub-label"></a>
#### Показ підпідпису

Метод `subLabel` показує притлумлений рядок під головним підписом завдання, що стає в пригоді, щоб повідомляти тимчасовий статус - як-от крок, який виконується зараз. Передайте порожній рядок, щоб очистити підпідпис:

```php
task(
    label: 'Deploying',
    callback: function ($logger) {
        $logger->subLabel('Building assets...');
        // ...
        $logger->subLabel('Running migrations...');
        // ...
        $logger->subLabel('');
    }
);
```

Ви також можете задати початковий підпідпис через аргумент `subLabel`:

```php
task(
    label: 'Deploying',
    callback: function ($logger) {
        // ...
    },
    subLabel: 'Preparing...'
);
```

<a name="task-streaming"></a>
#### Потоковий текст

Для процесів, які видають вивід поступово - як-от згенеровані AI відповіді, - метод `partial` дозволяє транслювати текст слово за словом чи порція за порцією. Коли потік завершено, викличте `commitPartial`, щоб фіналізувати вивід:

```php
task(
    label: 'Generating response...',
    callback: function ($logger) {
        foreach ($words as $word) {
            $logger->partial($word . ' ');
        }

        $logger->commitPartial();
    }
);
```

<a name="task-limit"></a>
#### Налаштування ліміту виводу

За замовчуванням завдання показує до 10 рядків прокручуваного виводу. Ви можете змінити це через аргумент `limit`:

```php
task(
    label: 'Installing dependencies',
    callback: function ($logger) {
        // ...
    },
    limit: 20
);
```

<a name="task-keep-summary"></a>
#### Збереження підсумку

За замовчуванням вивід завдання стирається, щойно колбек завершується. Якщо ви хочете лишити повідомлення про статус на екрані після завершення завдання, передайте аргумент `keepSummary`:

```php
task(
    label: 'Deploying',
    callback: function ($logger) {
        $logger->success('Assets built');
        // ...
        $logger->success('Migrations complete');
    },
    keepSummary: true,
);
```

<a name="stream"></a>
## Stream

Функція `stream` показує текст, який транслюється в термінал, - ідеально для показу згенерованого AI вмісту чи будь-якого тексту, що надходить поступово:

```php
use function Laravel\Prompts\stream;

$stream = stream();

foreach ($words as $word) {
    $stream->append($word . ' ');
    usleep(25_000); // Simulate delay between chunks...
}

$stream->close();
```

Метод `append` додає текст до потоку, рендерячи його з ефектом поступової появи. Коли весь вміст передано, викличте метод `close`, щоб фіналізувати вивід і повернути курсор.

<a name="terminal-title"></a>
## Заголовок термінала

Функція `title` оновлює заголовок вікна чи вкладки термінала користувача:

```php
use function Laravel\Prompts\title;

title('Installing Dependencies');
```

Щоб скинути заголовок термінала до стандартного, передайте порожній рядок:

```php
title('');
```

<a name="clear"></a>
## Очищення термінала

Функція `clear` дозволяє очистити термінал користувача:

```php
use function Laravel\Prompts\clear;

clear();
```

<a name="terminal-considerations"></a>
## Що врахувати щодо термінала

<a name="terminal-width"></a>
#### Ширина термінала

Якщо довжина будь-якого підпису, варіанта чи повідомлення про помилку валідації перевищує кількість «стовпців» у терміналі користувача, її буде автоматично обрізано. Подумайте про скорочення цих рядків, якщо ваші користувачі можуть працювати у вузьких терміналах. Зазвичай безпечна максимальна довжина - 74 символи, щоб підтримати 80-символьний термінал.

<a name="terminal-height"></a>
#### Висота термінала

Для будь-яких промптів, що приймають аргумент `scroll`, задане значення буде автоматично зменшено під висоту термінала користувача - з урахуванням місця для повідомлення про помилку валідації.

<a name="fallbacks"></a>
## Непідтримувані середовища та запасні варіанти

Laravel Prompts підтримує macOS, Linux і Windows із WSL. Через обмеження Windows-версії PHP наразі неможливо користуватися Laravel Prompts у Windows поза WSL.

Тому Laravel Prompts підтримує запасний варіант з альтернативною реалізацією - як-от [Symfony Console Question Helper](https://symfony.com/doc/current/components/console/helpers/questionhelper.html).

> [!NOTE]
> Коли ви користуєтеся Laravel Prompts разом із фреймворком Laravel, запасні варіанти для кожного промпту вже налаштовані за вас і автоматично вмикатимуться в непідтримуваних середовищах.

<a name="fallback-conditions"></a>
#### Умови запасного варіанта

Якщо ви не користуєтеся Laravel або хочете налаштувати, коли саме застосовується запасна поведінка, передайте булеве значення статичному методу `fallbackWhen` класу `Prompt`:

```php
use Laravel\Prompts\Prompt;

Prompt::fallbackWhen(
    ! $input->isInteractive() || windows_os() || app()->runningUnitTests()
);
```

<a name="fallback-behavior"></a>
#### Запасна поведінка

Якщо ви не користуєтеся Laravel або хочете налаштувати запасну поведінку, передайте замикання статичному методу `fallbackUsing` кожного класу промпту:

```php
use Laravel\Prompts\TextPrompt;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;

TextPrompt::fallbackUsing(function (TextPrompt $prompt) use ($input, $output) {
    $question = (new Question($prompt->label, $prompt->default ?: null))
        ->setValidator(function ($answer) use ($prompt) {
            if ($prompt->required && $answer === null) {
                throw new \RuntimeException(
                    is_string($prompt->required) ? $prompt->required : 'Required.'
                );
            }

            if ($prompt->validate) {
                $error = ($prompt->validate)($answer ?? '');

                if ($error) {
                    throw new \RuntimeException($error);
                }
            }

            return $answer;
        });

    return (new SymfonyStyle($input, $output))
        ->askQuestion($question);
});
```

Запасні варіанти треба налаштовувати окремо для кожного класу промпту. Замикання отримає екземпляр класу промпту й має повернути тип, відповідний для цього промпту.

<a name="testing"></a>
## Тестування

Laravel надає різні методи, щоб перевірити, що ваша команда показує очікувані повідомлення Prompts:

```php tab=Pest
test('report generation', function () {
    $this->artisan('report:generate')
        ->expectsPromptsInfo('Welcome to the application!')
        ->expectsPromptsWarning('This action cannot be undone')
        ->expectsPromptsError('Something went wrong')
        ->expectsPromptsAlert('Important notice!')
        ->expectsPromptsIntro('Starting process...')
        ->expectsPromptsOutro('Process completed!')
        ->expectsPromptsTable(
            headers: ['Name', 'Email'],
            rows: [
                ['Taylor Otwell', 'taylor@example.com'],
                ['Jason Beggs', 'jason@example.com'],
            ]
        )
        ->assertExitCode(0);
});
```

```php tab=PHPUnit
public function test_report_generation(): void
{
    $this->artisan('report:generate')
        ->expectsPromptsInfo('Welcome to the application!')
        ->expectsPromptsWarning('This action cannot be undone')
        ->expectsPromptsError('Something went wrong')
        ->expectsPromptsAlert('Important notice!')
        ->expectsPromptsIntro('Starting process...')
        ->expectsPromptsOutro('Process completed!')
        ->expectsPromptsTable(
            headers: ['Name', 'Email'],
            rows: [
                ['Taylor Otwell', 'taylor@example.com'],
                ['Jason Beggs', 'jason@example.com'],
            ]
        )
        ->assertExitCode(0);
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/pulse

# Laravel Pulse

- [Вступ](#introduction)
- [Встановлення](#installation)
    - [Конфігурація](#configuration)
- [Панель](#dashboard)
    - [Авторизація](#dashboard-authorization)
    - [Налаштування](#dashboard-customization)
    - [Отримання користувачів](#dashboard-resolving-users)
    - [Картки](#dashboard-cards)
- [Захоплення записів](#capturing-entries)
    - [Рекордери](#recorders)
    - [Фільтрація](#filtering)
- [Продуктивність](#performance)
    - [Використання іншої бази даних](#using-a-different-database)
    - [Приймання через Redis](#ingest)
    - [Семплювання](#sampling)
    - [Обрізання](#trimming)
    - [Обробка винятків Pulse](#pulse-exceptions)
- [Власні картки](#custom-cards)
    - [Компоненти карток](#custom-card-components)
    - [Стилізація](#custom-card-styling)
    - [Захоплення й агрегація даних](#custom-card-data)

<a name="introduction"></a>
## Вступ

[Laravel Pulse](https://github.com/laravel/pulse) дає змогу одним поглядом оцінити продуктивність і використання вашого застосунку. З Pulse ви можете відстежувати вузькі місця на кшталт повільних завдань і ендпоїнтів, знаходити найактивніших користувачів тощо.

Для докладного налагодження окремих подій погляньте на [Laravel Telescope](/docs/{{version}}/telescope).

<a name="installation"></a>
## Встановлення

> [!WARNING]
> Власна реалізація сховища Pulse наразі потребує бази даних MySQL, MariaDB чи PostgreSQL. Якщо ви використовуєте інший рушій бази даних, вам знадобиться окрема база MySQL, MariaDB чи PostgreSQL для даних Pulse.

Встановити Pulse можна через менеджер пакетів Composer:

```shell
composer require laravel/pulse
```

Далі вам слід опублікувати конфігураційний файл і файли міграцій Pulse артизан-командою `vendor:publish`:

```shell
php artisan vendor:publish --provider="Laravel\Pulse\PulseServiceProvider"
```

Насамкінець виконайте команду `migrate`, щоб створити таблиці, потрібні для зберігання даних Pulse:

```shell
php artisan migrate
```

Щойно міграції бази даних Pulse буде виконано, ви зможете відкрити панель Pulse за маршрутом `/pulse`.

> [!NOTE]
> Якщо ви не хочете зберігати дані Pulse в основній базі даних вашого застосунку, ви можете [вказати виділене підключення до бази даних](#using-a-different-database).

<a name="configuration"></a>
### Конфігурація

Багатьма параметрами конфігурації Pulse можна керувати через змінні оточення. Щоб побачити доступні параметри, зареєструвати нові рекордери чи налаштувати розширені опції, ви можете опублікувати конфігураційний файл `config/pulse.php`:

```shell
php artisan vendor:publish --tag=pulse-config
```

<a name="dashboard"></a>
## Панель

<a name="dashboard-authorization"></a>
### Авторизація

Панель Pulse доступна за маршрутом `/pulse`. За замовчуванням ви зможете відкрити цю панель лише в середовищі `local`, тож для продакшен-середовищ вам потрібно буде налаштувати авторизацію, змінивши гейт авторизації `'viewPulse'`. Зробити це можна у файлі `app/Providers/AppServiceProvider.php` вашого застосунку:

```php
use App\Models\User;
use Illuminate\Support\Facades\Gate;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Gate::define('viewPulse', function (User $user) {
        return $user->isAdmin();
    });

    // ...
}
```

<a name="dashboard-customization"></a>
### Налаштування

Картки та компонування панелі Pulse можна налаштувати, опублікувавши представлення панелі. Представлення панелі буде опубліковано в `resources/views/vendor/pulse/dashboard.blade.php`:

```shell
php artisan vendor:publish --tag=pulse-dashboard
```

Панель працює на [Livewire](https://livewire.laravel.com/) і дозволяє налаштовувати картки та компонування без потреби перезбирати JavaScript-ассети.

У цьому файлі за рендеринг панелі відповідає компонент `<x-pulse>`, який надає сітку для карток. Якщо ви хочете, щоб панель займала всю ширину екрана, передайте компоненту проп `full-width`:

```blade
<x-pulse full-width>
    ...
</x-pulse>
```

За замовчуванням компонент `<x-pulse>` створить сітку з 12 колонок, але ви можете змінити це пропом `cols`:

```blade
<x-pulse cols="16">
    ...
</x-pulse>
```

Кожна картка приймає пропи `cols` і `rows` для керування простором і розташуванням:

```blade
<livewire:pulse.usage cols="4" rows="2" />
```

Більшість карток також приймають проп `expand`, щоб показати картку повністю замість прокручування:

```blade
<livewire:pulse.slow-queries expand />
```

<a name="dashboard-resolving-users"></a>
### Отримання користувачів

Для карток, які показують інформацію про ваших користувачів, як-от картка Application Usage, Pulse записуватиме лише ID користувача. Під час рендерингу панелі Pulse візьме поля `name` і `email` з вашої моделі `Authenticatable` за замовчуванням і показуватиме аватари через вебсервіс Gravatar.

Ви можете змінити ці поля й аватар, викликавши метод `Pulse::user` у класі `App\Providers\AppServiceProvider` вашого застосунку.

Метод `user` приймає замикання, яке отримає модель `Authenticatable` для показу і має повернути масив з інформацією `name`, `extra` та `avatar` для користувача:

```php
use Laravel\Pulse\Facades\Pulse;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Pulse::user(fn ($user) => [
        'name' => $user->name,
        'extra' => $user->email,
        'avatar' => $user->avatar_url,
    ]);

    // ...
}
```

> [!NOTE]
> Ви можете повністю змінити те, як захоплюється й отримується автентифікований користувач, реалізувавши контракт `Laravel\Pulse\Contracts\ResolvesUsers` і прив'язавши його в [сервіс-контейнері](/docs/{{version}}/container#binding-a-singleton) Laravel.

<a name="dashboard-cards"></a>
### Картки

<a name="servers-card"></a>
#### Сервери

Картка `<livewire:pulse.servers />` показує використання системних ресурсів для всіх серверів, на яких запущено команду `pulse:check`. Докладніше про звітування про системні ресурси дивіться в документації щодо [рекордера серверів](#servers-recorder).

Якщо ви заміните сервер у своїй інфраструктурі, ви можете захотіти, щоб неактивний сервер перестав показуватися на панелі Pulse через певний час. Зробити це можна пропом `ignore-after`, який приймає кількість секунд, після яких неактивні сервери слід прибрати з панелі Pulse. Як альтернативу ви можете передати рядок з відносним часом, наприклад `1 hour` чи `3 days and 1 hour`:

```blade
<livewire:pulse.servers ignore-after="3 hours" />
```

<a name="application-usage-card"></a>
#### Використання застосунку

Картка `<livewire:pulse.usage />` показує топ-10 користувачів, які роблять запити до вашого застосунку, диспетчеризують завдання та стикаються з повільними запитами.

Якщо ви хочете бачити всі метрики використання на екрані одночасно, ви можете додати картку кілька разів і вказати атрибут `type`:

```blade
<livewire:pulse.usage type="requests" />
<livewire:pulse.usage type="slow_requests" />
<livewire:pulse.usage type="jobs" />
```

Щоб дізнатися, як налаштувати спосіб, у який Pulse отримує та показує інформацію про користувачів, зверніться до нашої документації щодо [отримання користувачів](#dashboard-resolving-users).

> [!NOTE]
> Якщо ваш застосунок отримує багато запитів чи диспетчеризує багато завдань, ви можете захотіти увімкнути [семплювання](#sampling). Докладніше дивіться в документації [рекордера запитів користувачів](#user-requests-recorder), [рекордера завдань користувачів](#user-jobs-recorder) і [рекордера повільних завдань](#slow-jobs-recorder).

<a name="exceptions-card"></a>
#### Винятки

Картка `<livewire:pulse.exceptions />` показує частоту та свіжість винятків, що трапляються у вашому застосунку. За замовчуванням винятки групуються за класом винятку й місцем, де він стався. Докладніше дивіться в документації [рекордера винятків](#exceptions-recorder).

<a name="queues-card"></a>
#### Черги

Картка `<livewire:pulse.queues />` показує пропускну здатність черг у вашому застосунку, включно з кількістю завдань у черзі, в обробці, оброблених, повернутих і невдалих. Докладніше дивіться в документації [рекордера черг](#queues-recorder).

<a name="slow-requests-card"></a>
#### Повільні запити

Картка `<livewire:pulse.slow-requests />` показує вхідні запити до вашого застосунку, які перевищують налаштований поріг, що за замовчуванням становить 1 000 мс. Докладніше дивіться в документації [рекордера повільних запитів](#slow-requests-recorder).

<a name="slow-jobs-card"></a>
#### Повільні завдання

Картка `<livewire:pulse.slow-jobs />` показує завдання з черги у вашому застосунку, які перевищують налаштований поріг, що за замовчуванням становить 1 000 мс. Докладніше дивіться в документації [рекордера повільних завдань](#slow-jobs-recorder).

<a name="slow-queries-card"></a>
#### Повільні запити до бази

Картка `<livewire:pulse.slow-queries />` показує запити до бази даних у вашому застосунку, які перевищують налаштований поріг, що за замовчуванням становить 1 000 мс.

За замовчуванням повільні запити до бази групуються за SQL-запитом (без прив'язок) і місцем, де вони сталися, але ви можете не захоплювати місце, якщо хочете групувати лише за SQL-запитом.

Якщо ви стикаєтеся з проблемами продуктивності рендерингу через підсвічування синтаксису надзвичайно великих SQL-запитів, ви можете вимкнути підсвічування, додавши проп `without-highlighting`:

```blade
<livewire:pulse.slow-queries without-highlighting />
```

Докладніше дивіться в документації [рекордера повільних запитів до бази](#slow-queries-recorder).

<a name="slow-outgoing-requests-card"></a>
#### Повільні вихідні запити

Картка `<livewire:pulse.slow-outgoing-requests />` показує вихідні запити, зроблені через [HTTP-клієнт](/docs/{{version}}/http-client) Laravel, які перевищують налаштований поріг, що за замовчуванням становить 1 000 мс.

За замовчуванням записи групуються за повним URL. Однак ви можете захотіти нормалізувати чи згрупувати схожі вихідні запити за допомогою регулярних виразів. Докладніше дивіться в документації [рекордера повільних вихідних запитів](#slow-outgoing-requests-recorder).

<a name="cache-card"></a>
#### Кеш

Картка `<livewire:pulse.cache />` показує статистику влучань і промахів кешу для вашого застосунку - як загалом, так і за окремими ключами.

За замовчуванням записи групуються за ключем. Однак ви можете захотіти нормалізувати чи згрупувати схожі ключі за допомогою регулярних виразів. Докладніше дивіться в документації [рекордера взаємодій з кешем](#cache-interactions-recorder).

<a name="capturing-entries"></a>
## Захоплення записів

Більшість рекордерів Pulse автоматично захоплюють записи на основі подій фреймворку, які диспетчеризує Laravel. Однак [рекордер серверів](#servers-recorder) і деякі сторонні картки мають регулярно опитувати інформацію. Щоб користуватися цими картками, вам потрібно запустити демон `pulse:check` на всіх ваших окремих серверах застосунку:

```shell
php artisan pulse:check
```

> [!NOTE]
> Щоб процес `pulse:check` постійно працював у фоні, вам слід використовувати монітор процесів на кшталт Supervisor, аби команда не припиняла роботу.

Оскільки команда `pulse:check` - це довготривалий процес, вона не побачить змін у вашій кодовій базі без перезапуску. Вам слід коректно перезапускати команду, викликаючи команду `pulse:restart` під час розгортання вашого застосунку:

```shell
php artisan pulse:restart
```

> [!NOTE]
> Pulse використовує [кеш](/docs/{{version}}/cache) для зберігання сигналів перезапуску, тож перед використанням цієї можливості переконайтеся, що драйвер кешу правильно налаштований для вашого застосунку.

<a name="recorders"></a>
### Рекордери

Рекордери відповідають за захоплення записів з вашого застосунку для збереження в базі даних Pulse. Рекордери реєструються й налаштовуються в секції `recorders` [конфігураційного файлу Pulse](#configuration).

<a name="cache-interactions-recorder"></a>
#### Взаємодії з кешем

Рекордер `CacheInteractions` захоплює інформацію про влучання та промахи [кешу](/docs/{{version}}/cache) у вашому застосунку для показу на картці [Кеш](#cache-card).

За бажанням ви можете скоригувати [частоту семплювання](#sampling) та шаблони ігнорованих ключів.

Ви також можете налаштувати групування ключів, щоб схожі ключі групувалися в один запис. Наприклад, ви можете захотіти прибрати унікальні ID з ключів, що кешують той самий тип інформації. Групи налаштовуються регулярним виразом, який «знаходить і замінює» частини ключа. Приклад є в конфігураційному файлі:

```php
Recorders\CacheInteractions::class => [
    // ...
    'groups' => [
        // '/:\d+/' => ':*',
    ],
],
```

Використано буде перший шаблон, який збігся. Якщо жоден шаблон не збігається, ключ буде захоплено як є.

<a name="exceptions-recorder"></a>
#### Винятки

Рекордер `Exceptions` захоплює інформацію про придатні до звітування винятки, що трапляються у вашому застосунку, для показу на картці [Винятки](#exceptions-card).

За бажанням ви можете скоригувати [частоту семплювання](#sampling) та шаблони ігнорованих винятків. Ви також можете налаштувати, чи захоплювати місце, звідки походить виняток. Захоплене місце буде показано на панелі Pulse, що може допомогти відстежити походження винятку; однак, якщо той самий виняток трапляється в кількох місцях, він з'явиться кілька разів - по одному для кожного унікального місця.

<a name="queues-recorder"></a>
#### Черги

Рекордер `Queues` захоплює інформацію про черги вашого застосунку для показу на картці [Черги](#queues-card).

За бажанням ви можете скоригувати [частоту семплювання](#sampling) та шаблони ігнорованих завдань.

<a name="slow-jobs-recorder"></a>
#### Повільні завдання

Рекордер `SlowJobs` захоплює інформацію про повільні завдання у вашому застосунку для показу на картці [Повільні завдання](#slow-jobs-recorder).

За бажанням ви можете скоригувати поріг повільного завдання, [частоту семплювання](#sampling) та шаблони ігнорованих завдань.

Деякі завдання можуть виконуватися довше за інші - і це очікувано. У таких випадках ви можете налаштувати пороги для окремих завдань:

```php
Recorders\SlowJobs::class => [
    // ...
    'threshold' => [
        '#^App\\Jobs\\GenerateYearlyReports$#' => 5000,
        'default' => env('PULSE_SLOW_JOBS_THRESHOLD', 1000),
    ],
],
```

Якщо жоден шаблон регулярного виразу не збігається з іменем класу завдання, буде використано значення `'default'`.

<a name="slow-outgoing-requests-recorder"></a>
#### Повільні вихідні запити

Рекордер `SlowOutgoingRequests` захоплює інформацію про вихідні HTTP-запити, зроблені через [HTTP-клієнт](/docs/{{version}}/http-client) Laravel, які перевищують налаштований поріг, для показу на картці [Повільні вихідні запити](#slow-outgoing-requests-card).

За бажанням ви можете скоригувати поріг повільного вихідного запиту, [частоту семплювання](#sampling) та шаблони ігнорованих URL.

Деякі вихідні запити можуть виконуватися довше за інші - і це очікувано. У таких випадках ви можете налаштувати пороги для окремих запитів:

```php
Recorders\SlowOutgoingRequests::class => [
    // ...
    'threshold' => [
        '#backup.zip$#' => 5000,
        'default' => env('PULSE_SLOW_OUTGOING_REQUESTS_THRESHOLD', 1000),
    ],
],
```

Якщо жоден шаблон регулярного виразу не збігається з URL запиту, буде використано значення `'default'`.

Ви також можете налаштувати групування URL, щоб схожі URL групувалися в один запис. Наприклад, ви можете захотіти прибрати унікальні ID зі шляхів URL або групувати лише за доменом. Групи налаштовуються регулярним виразом, який «знаходить і замінює» частини URL. Кілька прикладів є в конфігураційному файлі:

```php
Recorders\SlowOutgoingRequests::class => [
    // ...
    'groups' => [
        // '#^https://api\.github\.com/repos/.*$#' => 'api.github.com/repos/*',
        // '#^https?://([^/]*).*$#' => '\1',
        // '#/\d+#' => '/*',
    ],
],
```

Використано буде перший шаблон, який збігся. Якщо жоден шаблон не збігається, URL буде захоплено як є.

<a name="slow-queries-recorder"></a>
#### Повільні запити до бази

Рекордер `SlowQueries` захоплює будь-які запити до бази даних у вашому застосунку, які перевищують налаштований поріг, для показу на картці [Повільні запити до бази](#slow-queries-card).

За бажанням ви можете скоригувати поріг повільного запиту до бази, [частоту семплювання](#sampling) та шаблони ігнорованих запитів. Ви також можете налаштувати, чи захоплювати місце запиту. Захоплене місце буде показано на панелі Pulse, що може допомогти відстежити походження запиту; однак, якщо той самий запит робиться в кількох місцях, він з'явиться кілька разів - по одному для кожного унікального місця.

Деякі запити до бази можуть виконуватися довше за інші - і це очікувано. У таких випадках ви можете налаштувати пороги для окремих запитів:

```php
Recorders\SlowQueries::class => [
    // ...
    'threshold' => [
        '#^insert into `yearly_reports`#' => 5000,
        'default' => env('PULSE_SLOW_QUERIES_THRESHOLD', 1000),
    ],
],
```

Якщо жоден шаблон регулярного виразу не збігається з SQL запиту, буде використано значення `'default'`.

<a name="slow-requests-recorder"></a>
#### Повільні запити

Рекордер `Requests` захоплює інформацію про запити до вашого застосунку для показу на картках [Повільні запити](#slow-requests-card) і [Використання застосунку](#application-usage-card).

За бажанням ви можете скоригувати поріг повільного маршруту, [частоту семплювання](#sampling) та ігноровані шляхи.

Деякі запити можуть виконуватися довше за інші - і це очікувано. У таких випадках ви можете налаштувати пороги для окремих запитів:

```php
Recorders\SlowRequests::class => [
    // ...
    'threshold' => [
        '#^/admin/#' => 5000,
        'default' => env('PULSE_SLOW_REQUESTS_THRESHOLD', 1000),
    ],
],
```

Якщо жоден шаблон регулярного виразу не збігається з URL запиту, буде використано значення `'default'`.

<a name="servers-recorder"></a>
#### Сервери

Рекордер `Servers` захоплює використання процесора, пам'яті та сховища серверами, на яких працює ваш застосунок, для показу на картці [Сервери](#servers-card). Цей рекордер потребує, щоб [команда pulse:check](#capturing-entries) працювала на кожному сервері, за яким ви хочете стежити.

Кожен сервер, що звітує, повинен мати унікальне ім'я. За замовчуванням Pulse використовуватиме значення, яке повертає функція PHP `gethostname`. Якщо ви хочете змінити це, встановіть змінну оточення `PULSE_SERVER_NAME`:

```env
PULSE_SERVER_NAME=load-balancer
```

Конфігураційний файл Pulse також дозволяє налаштувати каталоги, за якими ведеться спостереження.

<a name="user-jobs-recorder"></a>
#### Завдання користувачів

Рекордер `UserJobs` захоплює інформацію про користувачів, які диспетчеризують завдання у вашому застосунку, для показу на картці [Використання застосунку](#application-usage-card).

За бажанням ви можете скоригувати [частоту семплювання](#sampling) та шаблони ігнорованих завдань.

<a name="user-requests-recorder"></a>
#### Запити користувачів

Рекордер `UserRequests` захоплює інформацію про користувачів, які роблять запити до вашого застосунку, для показу на картці [Використання застосунку](#application-usage-card).

За бажанням ви можете скоригувати [частоту семплювання](#sampling) та шаблони ігнорованих URL.

<a name="filtering"></a>
### Фільтрація

Як ми вже бачили, багато [рекордерів](#recorders) дають змогу через конфігурацію «ігнорувати» вхідні записи на основі їхнього значення, наприклад URL запиту. Але іноді буває корисно відфільтрувати записи за іншими чинниками, скажімо за поточним автентифікованим користувачем. Щоб відфільтрувати такі записи, передайте замикання до методу `filter` в Pulse. Зазвичай метод `filter` слід викликати в методі `boot` `AppServiceProvider` вашого застосунку:

```php
use Illuminate\Support\Facades\Auth;
use Laravel\Pulse\Entry;
use Laravel\Pulse\Facades\Pulse;
use Laravel\Pulse\Value;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Pulse::filter(function (Entry|Value $entry) {
        return Auth::user()->isNotAdmin();
    });

    // ...
}
```

<a name="performance"></a>
## Продуктивність

Pulse спроєктований так, щоб вбудовуватися в наявний застосунок без потреби в додатковій інфраструктурі. Однак для високонавантажених застосунків є кілька способів усунути будь-який вплив, який Pulse може мати на продуктивність вашого застосунку.

<a name="using-a-different-database"></a>
### Використання іншої бази даних

Для високонавантажених застосунків ви можете віддати перевагу виділеному підключенню до бази даних для Pulse, щоб не впливати на базу даних вашого застосунку.

Ви можете змінити [підключення до бази даних](/docs/{{version}}/database#configuration), яке використовує Pulse, встановивши змінну оточення `PULSE_DB_CONNECTION`.

```env
PULSE_DB_CONNECTION=pulse
```

<a name="ingest"></a>
### Приймання через Redis

> [!WARNING]
> Приймання через Redis потребує Redis 6.2 чи новішої версії та `phpredis` або `predis` як налаштованого клієнтського драйвера Redis у застосунку.

За замовчуванням Pulse зберігатиме записи безпосередньо до [налаштованого підключення до бази даних](#using-a-different-database) після того, як HTTP-відповідь буде надіслано клієнту або завдання буде оброблено; однак ви можете скористатися драйвером приймання через Redis, щоб натомість надсилати записи до Redis-стріму. Це вмикається налаштуванням змінної оточення `PULSE_INGEST_DRIVER`:

```ini
PULSE_INGEST_DRIVER=redis
```

За замовчуванням Pulse використовуватиме ваше [підключення Redis](/docs/{{version}}/redis#configuration) за замовчуванням, але ви можете змінити це через змінну оточення `PULSE_REDIS_CONNECTION`:

```ini
PULSE_REDIS_CONNECTION=pulse
```

> [!WARNING]
> Використовуючи драйвер приймання через Redis, ваша інсталяція Pulse завжди має використовувати інше підключення Redis, ніж ваша черга на Redis, якщо така є.

Використовуючи приймання через Redis, вам потрібно буде запустити команду `pulse:work`, щоб стежити за стрімом і переносити записи з Redis до таблиць бази даних Pulse.

```shell
php artisan pulse:work
```

> [!NOTE]
> Щоб процес `pulse:work` постійно працював у фоні, вам слід використовувати монітор процесів на кшталт Supervisor, аби воркер Pulse не припиняв роботу.

Оскільки команда `pulse:work` - це довготривалий процес, вона не побачить змін у вашій кодовій базі без перезапуску. Вам слід коректно перезапускати команду, викликаючи команду `pulse:restart` під час розгортання вашого застосунку:

```shell
php artisan pulse:restart
```

> [!NOTE]
> Pulse використовує [кеш](/docs/{{version}}/cache) для зберігання сигналів перезапуску, тож перед використанням цієї можливості переконайтеся, що драйвер кешу правильно налаштований для вашого застосунку.

<a name="sampling"></a>
### Семплювання

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

Натомість ви можете увімкнути «семплювання» для певних рекордерів даних Pulse. Наприклад, встановлення частоти семплювання `0.1` для рекордера [Запити користувачів](#user-requests-recorder) означатиме, що ви записуєте лише приблизно 10% запитів до вашого застосунку. На панелі значення буде масштабовано вгору й додано префікс `~`, щоб позначити, що це наближення.

Загалом, чим більше записів ви маєте для конкретної метрики, тим нижче ви можете безпечно встановити частоту семплювання, не жертвуючи занадто великою точністю.

<a name="trimming"></a>
### Обрізання

Pulse автоматично обрізатиме збережені записи, щойно вони вийдуть за межі вікна панелі. Обрізання відбувається під час приймання даних за лотерейною системою, яку можна налаштувати в [конфігураційному файлі](#configuration) Pulse.

<a name="pulse-exceptions"></a>
### Обробка винятків Pulse

Якщо під час захоплення даних Pulse станеться виняток, наприклад не вдасться підключитися до бази даних сховища, Pulse мовчки завершиться невдачею, щоб не вплинути на ваш застосунок.

Якщо ви хочете змінити спосіб обробки цих винятків, передайте замикання до методу `handleExceptionsUsing`:

```php
use Laravel\Pulse\Facades\Pulse;
use Illuminate\Support\Facades\Log;

Pulse::handleExceptionsUsing(function ($e) {
    Log::debug('An exception happened in Pulse', [
        'message' => $e->getMessage(),
        'stack' => $e->getTraceAsString(),
    ]);
});
```

<a name="custom-cards"></a>
## Власні картки

Pulse дозволяє створювати власні картки, щоб показувати дані, релевантні для конкретних потреб вашого застосунку. Pulse використовує [Livewire](https://livewire.laravel.com), тож перед створенням своєї першої власної картки вам, можливо, варто [переглянути його документацію](https://livewire.laravel.com/docs).

<a name="custom-card-components"></a>
### Компоненти карток

Створення власної картки в Laravel Pulse починається з розширення базового Livewire-компонента `Card` і визначення відповідного представлення:

```php
namespace App\Livewire\Pulse;

use Laravel\Pulse\Livewire\Card;
use Livewire\Attributes\Lazy;

#[Lazy]
class TopSellers extends Card
{
    public function render()
    {
        return view('livewire.pulse.top-sellers');
    }
}
```

Коли ви користуєтеся можливістю [лінивого завантаження](https://livewire.laravel.com/docs/lazy) Livewire, компонент `Card` автоматично надасть заглушку, яка враховує атрибути `cols` і `rows`, передані вашому компоненту.

Пишучи відповідне представлення для вашої картки Pulse, ви можете скористатися Blade-компонентами Pulse задля узгодженого вигляду:

```blade
<x-pulse::card :cols="$cols" :rows="$rows" :class="$class" wire:poll.5s="">
    <x-pulse::card-header name="Top Sellers">
        <x-slot:icon>
            ...
        </x-slot:icon>
    </x-pulse::card-header>

    <x-pulse::scroll :expand="$expand">
        ...
    </x-pulse::scroll>
</x-pulse::card>
```

Змінні `$cols`, `$rows`, `$class` і `$expand` слід передати до відповідних Blade-компонентів, щоб компонування картки можна було налаштувати з представлення панелі. Ви також можете додати до свого представлення атрибут `wire:poll.5s=""`, щоб картка оновлювалася автоматично.

Щойно ви визначите свій Livewire-компонент і шаблон, картку можна додати до вашого [представлення панелі](#dashboard-customization):

```blade
<x-pulse>
    ...

    <livewire:pulse.top-sellers cols="4" />
</x-pulse>
```

> [!NOTE]
> Якщо ваша картка входить до складу пакета, вам потрібно буде зареєструвати компонент у Livewire методом `Livewire::component`.

<a name="custom-card-styling"></a>
### Стилізація

Якщо вашій картці потрібна додаткова стилізація понад класи й компоненти, що входять до Pulse, є кілька варіантів підключення власного CSS для ваших карток.

<a name="custom-card-styling-vite"></a>
#### Інтеграція з Laravel Vite

Якщо ваша власна картка живе в кодовій базі вашого застосунку і ви користуєтеся [інтеграцією з Vite](/docs/{{version}}/vite) у Laravel, ви можете оновити свій файл `vite.config.js`, додавши окрему точку входу CSS для вашої картки:

```js
laravel({
    input: [
        'resources/css/pulse/top-sellers.css',
        // ...
    ],
}),
```

Далі ви можете скористатися Blade-директивою `@vite` у своєму [представленні панелі](#dashboard-customization), вказавши точку входу CSS для вашої картки:

```blade
<x-pulse>
    @vite('resources/css/pulse/top-sellers.css')

    ...
</x-pulse>
```

<a name="custom-card-styling-css"></a>
#### CSS-файли

Для інших випадків, зокрема для карток Pulse у складі пакета, ви можете вказати Pulse завантажити додаткові таблиці стилів, визначивши на своєму Livewire-компоненті метод `css`, який повертає шлях до вашого CSS-файлу:

```php
class TopSellers extends Card
{
    // ...

    protected function css()
    {
        return __DIR__.'/../../dist/top-sellers.css';
    }
}
```

Коли цю картку буде додано на панель, Pulse автоматично вставить вміст цього файлу в тег `<style>`, тож його не потрібно публікувати в каталог `public`.

<a name="custom-card-styling-tailwind"></a>
#### Tailwind CSS

Використовуючи Tailwind CSS, вам слід створити окрему точку входу CSS. Наведений нижче приклад виключає базові стилі [Preflight](https://tailwindcss.com/docs/preflight) з Tailwind, які Pulse уже містить, і обмежує Tailwind CSS-селектором, щоб уникнути конфліктів із класами Tailwind у Pulse:

```css
@import "tailwindcss/theme.css";

@custom-variant dark (&:where(.dark, .dark *));
@source "./../../views/livewire/pulse/top-sellers.blade.php";

@theme {
  /* ... */
}

#top-sellers {
  @import "tailwindcss/utilities.css" source(none);
}
```

Вам також потрібно буде додати до представлення вашої картки атрибут `id` чи `class`, який збігається з CSS-селектором у вашій точці входу:

```blade
<x-pulse::card id="top-sellers" :cols="$cols" :rows="$rows" class="$class">
    ...
</x-pulse::card>
```

<a name="custom-card-data"></a>
### Захоплення й агрегація даних

Власні картки можуть отримувати та показувати дані звідки завгодно; однак ви можете захотіти скористатися потужною та ефективною системою запису й агрегації даних у Pulse.

<a name="custom-card-data-capture"></a>
#### Захоплення записів

Pulse дозволяє записувати «записи» методом `Pulse::record`:

```php
use Laravel\Pulse\Facades\Pulse;

Pulse::record('user_sale', $user->id, $sale->amount)
    ->sum()
    ->count();
```

Перший аргумент, переданий методу `record`, - це `type` запису, який ви записуєте, а другий - `key`, що визначає, як слід групувати агреговані дані. Для більшості методів агрегації вам також потрібно буде вказати `value` для агрегації. У наведеному вище прикладі значення, яке агрегується, - це `$sale->amount`. Далі ви можете викликати один чи кілька методів агрегації (як-от `sum`), щоб Pulse захоплював попередньо агреговані значення у «бакети» для ефективного отримання згодом.

Доступні методи агрегації:

* `avg`
* `count`
* `max`
* `min`
* `sum`

> [!NOTE]
> Створюючи пакет із карткою, який захоплює ID поточного автентифікованого користувача, вам слід використовувати метод `Pulse::resolveAuthenticatedUserId()`, який враховує будь-які [налаштування резолвера користувачів](#dashboard-resolving-users), зроблені в застосунку.

<a name="custom-card-data-retrieval"></a>
#### Отримання агрегованих даних

Розширюючи Livewire-компонент `Card` з Pulse, ви можете скористатися методом `aggregate`, щоб отримати агреговані дані за період, який переглядається на панелі:

```php
class TopSellers extends Card
{
    public function render()
    {
        return view('livewire.pulse.top-sellers', [
            'topSellers' => $this->aggregate('user_sale', ['sum', 'count'])
        ]);
    }
}
```

Метод `aggregate` повертає колекцію PHP-об'єктів `stdClass`. Кожен об'єкт міститиме захоплену раніше властивість `key` разом із ключами для кожного із запитаних агрегатів:

```blade
@foreach ($topSellers as $seller)
    {{ $seller->key }}
    {{ $seller->sum }}
    {{ $seller->count }}
@endforeach
```

Pulse отримуватиме дані переважно з попередньо агрегованих бакетів; тому вказані агрегати мають бути захоплені заздалегідь методом `Pulse::record`. Найстаріший бакет зазвичай частково виходитиме за межі періоду, тож Pulse агрегує найстаріші записи, щоб заповнити прогалину й дати точне значення за весь період, не агрегуючи весь період на кожен запит опитування.

Ви також можете отримати загальне значення для певного типу методом `aggregateTotal`. Наприклад, наведений нижче метод отримав би суму всіх продажів користувачів, не групуючи їх за користувачем.

```php
$total = $this->aggregateTotal('user_sale', 'sum');
```

<a name="custom-card-displaying-users"></a>
#### Показ користувачів

Працюючи з агрегатами, які записують ID користувача як ключ, ви можете перетворити ці ключі на записи користувачів методом `Pulse::resolveUsers`:

```php
$aggregates = $this->aggregate('user_sale', ['sum', 'count']);

$users = Pulse::resolveUsers($aggregates->pluck('key'));

return view('livewire.pulse.top-sellers', [
    'sellers' => $aggregates->map(fn ($aggregate) => (object) [
        'user' => $users->find($aggregate->key),
        'sum' => $aggregate->sum,
        'count' => $aggregate->count,
    ])
]);
```

Метод `find` повертає об'єкт із ключами `name`, `extra` та `avatar`, який ви за бажанням можете передати напряму до Blade-компонента `<x-pulse::user-card>`:

```blade
<x-pulse::user-card :user="{{ $seller->user }}" :stats="{{ $seller->sum }}" />
```

<a name="custom-recorders"></a>
#### Власні рекордери

Автори пакетів можуть захотіти надати класи рекордерів, щоб користувачі могли налаштовувати захоплення даних.

Рекордери реєструються в секції `recorders` конфігураційного файлу `config/pulse.php` застосунку:

```php
[
    // ...
    'recorders' => [
        Acme\Recorders\Deployments::class => [
            // ...
        ],

        // ...
    ],
]
```

Рекордери можуть слухати події, якщо вказати властивість `$listen`. Pulse автоматично зареєструє слухачів і викличе метод `record` рекордера:

```php
<?php

namespace Acme\Recorders;

use Acme\Events\Deployment;
use Illuminate\Support\Facades\Config;
use Laravel\Pulse\Facades\Pulse;

class Deployments
{
    /**
     * The events to listen for.
     *
     * @var array<int, class-string>
     */
    public array $listen = [
        Deployment::class,
    ];

    /**
     * Record the deployment.
     */
    public function record(Deployment $event): void
    {
        $config = Config::get('pulse.recorders.'.static::class);

        Pulse::record(
            // ...
        );
    }
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/reverb

# Laravel Reverb

- [Вступ](#introduction)
- [Встановлення](#installation)
- [Конфігурація](#configuration)
    - [Облікові дані застосунку](#application-credentials)
    - [Дозволені джерела](#allowed-origins)
    - [Додаткові застосунки](#additional-applications)
    - [SSL](#ssl)
- [Запуск сервера](#running-server)
    - [Налагодження](#debugging)
    - [Перезапуск](#restarting)
- [Моніторинг](#monitoring)
- [Reverb у продакшені](#production)
    - [Відкриті файли](#open-files)
    - [Цикл подій](#event-loop)
    - [Вебсервер](#web-server)
    - [Порти](#ports)
    - [Керування процесами](#process-management)
    - [Масштабування](#scaling)
- [Події](#events)

<a name="introduction"></a>
## Вступ

[Laravel Reverb](https://github.com/laravel/reverb) приносить блискавично швидку та масштабовану real-time комунікацію через WebSocket безпосередньо до вашого Laravel-застосунку і безшовно інтегрується з наявним набором [інструментів бродкастингу подій](/docs/{{version}}/broadcasting) Laravel.

<a name="installation"></a>
## Встановлення

Встановити Reverb можна артизан-командою `install:broadcasting`:

```shell
php artisan install:broadcasting
```

<a name="configuration"></a>
## Конфігурація

Під капотом артизан-команда `install:broadcasting` запустить команду `reverb:install`, яка встановить Reverb з розумним набором параметрів конфігурації за замовчуванням. Якщо ви захочете щось змінити в конфігурації, зробіть це через змінні оточення Reverb або через конфігураційний файл `config/reverb.php`.

<a name="application-credentials"></a>
### Облікові дані застосунку

Щоб установити з'єднання з Reverb, клієнт і сервер мають обмінятися набором облікових даних «застосунку» Reverb. Ці облікові дані налаштовуються на сервері й використовуються для перевірки запиту від клієнта. Визначити їх можна за допомогою таких змінних оточення:

```ini
REVERB_APP_ID=my-app-id
REVERB_APP_KEY=my-app-key
REVERB_APP_SECRET=my-app-secret
```

<a name="allowed-origins"></a>
### Дозволені джерела

Ви також можете визначити джерела, з яких можуть надходити клієнтські запити, змінивши значення `allowed_origins` у секції `apps` конфігураційного файлу `config/reverb.php`. Будь-які запити з джерела, якого немає у списку дозволених, будуть відхилені. Дозволити всі джерела можна за допомогою `*`:

```php
'apps' => [
    [
        'app_id' => 'my-app-id',
        'allowed_origins' => ['laravel.com'],
        // ...
    ]
]
```

<a name="additional-applications"></a>
### Додаткові застосунки

Зазвичай Reverb надає WebSocket-сервер для того застосунку, у якому його встановлено. Однак одна інсталяція Reverb може обслуговувати більше ніж один застосунок.

Наприклад, ви можете захотіти підтримувати один Laravel-застосунок, який через Reverb забезпечує WebSocket-з'єднання для кількох застосунків. Цього можна досягти, визначивши кілька `apps` у конфігураційному файлі `config/reverb.php` вашого застосунку:

```php
'apps' => [
    [
        'app_id' => 'my-app-one',
        // ...
    ],
    [
        'app_id' => 'my-app-two',
        // ...
    ],
],
```

<a name="ssl"></a>
### SSL

У більшості випадків захищені WebSocket-з'єднання обробляє вищестоящий вебсервер (Nginx тощо), перш ніж запит буде пропрокси́йовано до вашого сервера Reverb.

Однак іноді буває корисно, наприклад під час локальної розробки, щоб сервер Reverb обробляв захищені з'єднання напряму. Якщо ви користуєтеся можливістю захищених сайтів [Laravel Herd](https://herd.laravel.com) або використовуєте [Laravel Valet](/docs/{{version}}/valet) і виконали [команду secure](/docs/{{version}}/valet#securing-sites) для свого застосунку, ви можете скористатися згенерованим Herd / Valet сертифікатом для вашого сайту, щоб захистити з'єднання Reverb. Для цього встановіть змінну оточення `REVERB_HOST` на ім'я хоста вашого сайту або явно передайте опцію hostname під час запуску сервера Reverb:

```shell
php artisan reverb:start --host="0.0.0.0" --port=8080 --hostname="laravel.test"
```

Оскільки домени Herd і Valet резолвляться в `localhost`, після виконання наведеної вище команди ваш сервер Reverb буде доступний через захищений протокол WebSocket (`wss`) за адресою `wss://laravel.test:8080`.

Ви також можете вибрати сертифікат вручну, визначивши опції `tls` у конфігураційному файлі `config/reverb.php` вашого застосунку. У масиві опцій `tls` можна вказати будь-яку з опцій, які підтримують [SSL-контексти PHP](https://www.php.net/manual/en/context.ssl.php):

```php
'options' => [
    'tls' => [
        'local_cert' => '/path/to/cert.pem'
    ],
],
```

<a name="running-server"></a>
## Запуск сервера

Сервер Reverb запускається артизан-командою `reverb:start`:

```shell
php artisan reverb:start
```

За замовчуванням сервер Reverb запускається на `0.0.0.0:8080`, тобто доступний з усіх мережевих інтерфейсів.

Якщо вам потрібно вказати власний хост чи порт, зробіть це за допомогою опцій `--host` і `--port` під час запуску сервера:

```shell
php artisan reverb:start --host=127.0.0.1 --port=9000
```

Або ж ви можете визначити змінні оточення `REVERB_SERVER_HOST` і `REVERB_SERVER_PORT` у конфігураційному файлі `.env` вашого застосунку.

Змінні оточення `REVERB_SERVER_HOST` і `REVERB_SERVER_PORT` не слід плутати з `REVERB_HOST` і `REVERB_PORT`. Перші вказують хост і порт, на яких запускається сам сервер Reverb, а друга пара повідомляє Laravel, куди надсилати бродкаст-повідомлення. Наприклад, у продакшен-середовищі ви можете спрямовувати запити з публічного імені хоста Reverb на порту `443` до сервера Reverb, що працює на `0.0.0.0:8080`. У такому сценарії ваші змінні оточення були б визначені так:

```ini
REVERB_SERVER_HOST=0.0.0.0
REVERB_SERVER_PORT=8080

REVERB_HOST=ws.laravel.com
REVERB_PORT=443
```

<a name="debugging"></a>
### Налагодження

Задля продуктивності Reverb за замовчуванням не виводить жодної налагоджувальної інформації. Якщо ви хочете бачити потік даних, що проходить через ваш сервер Reverb, додайте до команди `reverb:start` опцію `--debug`:

```shell
php artisan reverb:start --debug
```

<a name="restarting"></a>
### Перезапуск

Оскільки Reverb - це довготривалий процес, зміни у вашому коді не застосуються, доки ви не перезапустите сервер артизан-командою `reverb:restart`.

Команда `reverb:restart` гарантує, що всі з'єднання будуть коректно завершені, перш ніж сервер зупиниться. Якщо ви запускаєте Reverb під менеджером процесів на кшталт Supervisor, після завершення всіх з'єднань менеджер процесів автоматично перезапустить сервер:

```shell
php artisan reverb:restart
```

<a name="monitoring"></a>
## Моніторинг

За Reverb можна стежити через інтеграцію з [Laravel Pulse](/docs/{{version}}/pulse). Увімкнувши інтеграцію Reverb з Pulse, ви зможете відстежувати кількість з'єднань і повідомлень, які обробляє ваш сервер.

Щоб увімкнути інтеграцію, спершу переконайтеся, що ви [встановили Pulse](/docs/{{version}}/pulse#installation). Далі додайте будь-які з рекордерів Reverb до конфігураційного файлу `config/pulse.php` вашого застосунку:

```php
use Laravel\Reverb\Pulse\Recorders\ReverbConnections;
use Laravel\Reverb\Pulse\Recorders\ReverbMessages;

'recorders' => [
    ReverbConnections::class => [
        'sample_rate' => 1,
    ],

    ReverbMessages::class => [
        'sample_rate' => 1,
    ],

    // ...
],
```

Далі додайте картки Pulse для кожного рекордера до вашої [панелі Pulse](/docs/{{version}}/pulse#dashboard-customization):

```blade
<x-pulse>
    <livewire:reverb.connections cols="full" />
    <livewire:reverb.messages cols="full" />
    ...
</x-pulse>
```

Активність з'єднань записується шляхом періодичного опитування нових оновлень. Щоб ця інформація коректно відображалася на панелі Pulse, ви маєте запустити демон `pulse:check` на вашому сервері Reverb. Якщо ви запускаєте Reverb у [горизонтально масштабованій](#scaling) конфігурації, цей демон слід запускати лише на одному з ваших серверів.

<a name="production"></a>
## Reverb у продакшені

Через довготривалу природу WebSocket-серверів вам, можливо, доведеться дещо оптимізувати ваш сервер і хостинг-середовище, щоб ваш сервер Reverb міг ефективно обробляти оптимальну кількість з'єднань для доступних на сервері ресурсів.

> [!NOTE]
> [Laravel Cloud](https://cloud.laravel.com) пропонує повністю керовану WebSocket-інфраструктуру на базі кластерів Laravel Reverb, що дозволяє масштабувати й випускати застосунки з Reverb, не керуючи інфраструктурою.

<a name="open-files"></a>
### Відкриті файли

Кожне WebSocket-з'єднання тримається в пам'яті, доки не від'єднається клієнт або сервер. У Unix і Unix-подібних середовищах кожне з'єднання представлене файлом. Однак часто існують обмеження на кількість дозволених відкритих файлів як на рівні операційної системи, так і на рівні застосунку.

<a name="operating-system"></a>
#### Операційна система

В операційній системі на базі Unix дозволену кількість відкритих файлів можна дізнатися командою `ulimit`:

```shell
ulimit -n
```

Ця команда покаже обмеження на відкриті файли для різних користувачів. Змінити ці значення можна, відредагувавши файл `/etc/security/limits.conf`. Наприклад, збільшення максимальної кількості відкритих файлів до 10 000 для користувача `forge` виглядало б так:

```ini
# /etc/security/limits.conf
forge        soft  nofile  10000
forge        hard  nofile  10000
```

<a name="event-loop"></a>
### Цикл подій

Під капотом Reverb використовує цикл подій ReactPHP для керування WebSocket-з'єднаннями на сервері. За замовчуванням цей цикл подій працює на `stream_select`, який не потребує жодних додаткових розширень. Однак `stream_select` зазвичай обмежений 1 024 відкритими файлами. Тому, якщо ви плануєте обробляти понад 1 000 одночасних з'єднань, вам знадобиться альтернативний цикл подій, не обмежений тими самими рамками.

Reverb автоматично перемкнеться на цикл на базі `ext-uv`, коли той доступний. Це розширення PHP можна встановити через PECL:

```shell
pecl install uv
```

<a name="web-server"></a>
### Вебсервер

У більшості випадків Reverb працює на порту вашого сервера, не зверненому до вебу. Тож, щоб спрямувати трафік до Reverb, вам слід налаштувати зворотний проксі. Припускаючи, що Reverb працює на хості `0.0.0.0` і порту `8080`, а ваш сервер використовує вебсервер Nginx, зворотний проксі для вашого сервера Reverb можна визначити такою конфігурацією сайту Nginx:

```nginx
server {
    ...

    location / {
        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header Scheme $scheme;
        proxy_set_header SERVER_PORT $server_port;
        proxy_set_header REMOTE_ADDR $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";

        proxy_pass http://0.0.0.0:8080;
    }

    ...
}
```

> [!WARNING]
> Reverb слухає WebSocket-з'єднання на `/app` і обробляє API-запити на `/apps`. Переконайтеся, що вебсервер, який обробляє запити до Reverb, може обслуговувати обидва ці URI. Якщо ви користуєтеся [Laravel Forge](https://forge.laravel.com) для керування серверами, ваш сервер Reverb буде правильно налаштований за замовчуванням.

Зазвичай вебсервери налаштовані обмежувати кількість дозволених з'єднань, щоб запобігти перевантаженню сервера. Щоб збільшити кількість дозволених з'єднань на вебсервері Nginx до 10 000, слід оновити значення `worker_rlimit_nofile` і `worker_connections` у файлі `nginx.conf`:

```nginx
user forge;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
worker_rlimit_nofile 10000;

events {
  worker_connections 10000;
  multi_accept on;
}
```

Наведена вище конфігурація дозволить породжувати до 10 000 воркерів Nginx на процес. Крім того, вона встановлює обмеження Nginx на відкриті файли в 10 000.

<a name="ports"></a>
### Порти

Операційні системи на базі Unix зазвичай обмежують кількість портів, які можна відкрити на сервері. Побачити поточний дозволений діапазон можна такою командою:

```shell
cat /proc/sys/net/ipv4/ip_local_port_range
# 32768	60999
```

Наведений вище вивід показує, що сервер може обробити максимум 28 231 (60 999 - 32 768) з'єднань, оскільки кожне з'єднання потребує вільного порту. Хоча для збільшення кількості дозволених з'єднань ми рекомендуємо [горизонтальне масштабування](#scaling), ви можете збільшити кількість доступних відкритих портів, змінивши дозволений діапазон портів у конфігураційному файлі `/etc/sysctl.conf` вашого сервера.

<a name="process-management"></a>
### Керування процесами

У більшості випадків вам слід використовувати менеджер процесів на кшталт Supervisor, щоб сервер Reverb працював безперервно. Якщо ви запускаєте Reverb через Supervisor, оновіть налаштування `minfds` у файлі `supervisor.conf` вашого сервера, щоб Supervisor міг відкрити файли, потрібні для обробки з'єднань із вашим сервером Reverb:

```ini
[supervisord]
...
minfds=10000
```

<a name="scaling"></a>
### Масштабування

Якщо вам потрібно обробляти більше з'єднань, ніж дозволяє один сервер, ви можете масштабувати сервер Reverb горизонтально. Використовуючи можливості publish / subscribe Redis, Reverb може керувати з'єднаннями на кількох серверах. Коли один із серверів Reverb вашого застосунку отримує повідомлення, він через Redis опублікує вхідне повідомлення для всіх інших серверів.

Щоб увімкнути горизонтальне масштабування, встановіть змінну оточення `REVERB_SCALING_ENABLED` у `true` в конфігураційному файлі `.env` вашого застосунку:

```env
REVERB_SCALING_ENABLED=true
```

Далі вам потрібен виділений, центральний сервер Redis, з яким взаємодіятимуть усі сервери Reverb. Reverb використовуватиме [підключення Redis, налаштоване для вашого застосунку за замовчуванням](/docs/{{version}}/redis#configuration), щоб публікувати повідомлення для всіх ваших серверів Reverb.

Щойно ви увімкнете опцію масштабування Reverb і налаштуєте сервер Redis, вам достатньо виконати команду `reverb:start` на кількох серверах, здатних взаємодіяти з вашим сервером Redis. Ці сервери Reverb слід розмістити за балансувальником навантаження, який рівномірно розподіляє вхідні запити між серверами.

<a name="events"></a>
## Події

Reverb диспетчеризує внутрішні події протягом життєвого циклу з'єднання та обробки повідомлень. Ви можете [слухати ці події](/docs/{{version}}/events), щоб виконувати дії, коли керується з'єднаннями чи обмінюється повідомленнями.

Reverb диспетчеризує такі події:

#### `Laravel\Reverb\Events\ChannelCreated`

Диспетчеризується, коли створюється канал. Зазвичай це відбувається, коли перше з'єднання підписується на конкретний канал. Подія отримує екземпляр `Laravel\Reverb\Protocols\Pusher\Channel`.

#### `Laravel\Reverb\Events\ChannelRemoved`

Диспетчеризується, коли канал видаляється. Зазвичай це відбувається, коли останнє з'єднання відписується від каналу. Подія отримує екземпляр `Laravel\Reverb\Protocols\Pusher\Channel`.

#### `Laravel\Reverb\Events\ConnectionPruned`

Диспетчеризується, коли сервер прибирає застаріле з'єднання. Подія отримує екземпляр `Laravel\Reverb\Contracts\Connection`.

#### `Laravel\Reverb\Events\MessageReceived`

Диспетчеризується, коли отримано повідомлення від клієнтського з'єднання. Подія отримує екземпляр `Laravel\Reverb\Contracts\Connection` і сирий рядок `$message`.

#### `Laravel\Reverb\Events\MessageSent`

Диспетчеризується, коли повідомлення надіслано до клієнтського з'єднання. Подія отримує екземпляр `Laravel\Reverb\Contracts\Connection` і сирий рядок `$message`.

---

Джерело: https://laravelukraine.com/docs/13.x/sail

# Laravel Sail

- [Вступ](#introduction)
- [Встановлення й налаштування](#installation)
    - [Перезбирання образів Sail](#rebuilding-sail-images)
    - [Налаштування аліаса в оболонці](#configuring-a-shell-alias)
- [Запуск і зупинка Sail](#starting-and-stopping-sail)
- [Виконання команд](#executing-sail-commands)
    - [Виконання команд PHP](#executing-php-commands)
    - [Виконання команд Composer](#executing-composer-commands)
    - [Виконання артизан-команд](#executing-artisan-commands)
    - [Виконання команд Node / NPM](#executing-node-npm-commands)
- [Робота з базами даних](#interacting-with-sail-databases)
    - [MySQL](#mysql)
    - [MongoDB](#mongodb)
    - [Redis](#redis)
    - [Valkey](#valkey)
    - [Meilisearch](#meilisearch)
    - [Typesense](#typesense)
- [Зберігання файлів](#file-storage)
- [Запуск тестів](#running-tests)
    - [Laravel Dusk](#laravel-dusk)
- [Перегляд листів](#previewing-emails)
- [CLI контейнера](#sail-container-cli)
- [Версії PHP](#sail-php-versions)
    - [Додаткові розширення PHP](#sail-php-extensions)
- [Версії Node](#sail-node-versions)
- [Публічний доступ до вашого сайту](#sharing-your-site)
- [Налагодження через Xdebug](#debugging-with-xdebug)
    - [Xdebug у CLI](#xdebug-cli-usage)
    - [Xdebug у браузері](#xdebug-browser-usage)
- [Налаштування](#sail-customization)

<a name="introduction"></a>
## Вступ

[Laravel Sail](https://github.com/laravel/sail) - це легкий інтерфейс командного рядка для роботи зі стандартним середовищем розробки Laravel на Docker. Sail дає чудову відправну точку, щоб будувати застосунок Laravel на PHP, MySQL і Redis без попереднього досвіду з Docker.

По суті, Sail - це файл `compose.yaml` і скрипт `sail`, що лежить у корені вашого проєкту. Скрипт `sail` надає CLI зі зручними методами для роботи з контейнерами Docker, описаними у файлі `compose.yaml`.

Laravel Sail підтримується на macOS, Linux і Windows (через [WSL2](https://docs.microsoft.com/en-us/windows/wsl/about)).

<a name="installation"></a>
## Встановлення й налаштування

Ви можете встановити Sail через менеджер пакетів Composer:

```shell
composer require laravel/sail --dev
```

Після встановлення Sail виконайте артизан-команду `sail:install`. Ця команда опублікує файл `compose.yaml` до кореня вашого застосунку й додасть до вашого файлу `.env` змінні оточення, потрібні для підключення до сервісів Docker:

```shell
php artisan sail:install
```

Нарешті, ви можете запустити Sail. Щоб дізнатися більше про роботу із Sail, читайте цю документацію далі:

```shell
./vendor/bin/sail up
```

> [!WARNING]
> Якщо ви користуєтеся Docker Desktop для Linux, скористайтеся контекстом Docker `default`, виконавши таку команду: `docker context use default`. Крім того, якщо ви натрапите на помилки прав доступу до файлів усередині контейнерів, вам може знадобитися встановити змінну оточення `SUPERVISOR_PHP_USER` у значення `root`.

<a name="adding-additional-services"></a>
#### Додавання додаткових сервісів

Якщо ви хочете додати ще один сервіс до наявної установки Sail, виконайте артизан-команду `sail:add`:

```shell
php artisan sail:add
```

<a name="using-devcontainers"></a>
#### Використання Devcontainers

Якщо ви хочете розробляти всередині [Devcontainer](https://code.visualstudio.com/docs/remote/containers), додайте до команди `sail:install` опцію `--devcontainer`. Опція `--devcontainer` вкаже команді `sail:install` опублікувати до кореня вашого застосунку стандартний файл `.devcontainer/devcontainer.json `:

```shell
php artisan sail:install --devcontainer
```

<a name="rebuilding-sail-images"></a>
### Перезбирання образів Sail

Іноді вам може знадобитися повністю перезібрати образи Sail, щоб переконатися, що всі пакети й програми в образі актуальні. Зробити це можна командою `build`:

```shell
docker compose down -v

sail build --no-cache

sail up
```

<a name="configuring-a-shell-alias"></a>
### Налаштування аліаса в оболонці

За замовчуванням команди Sail викликаються через скрипт `vendor/bin/sail`, який входить до всіх нових застосунків Laravel:

```shell
./vendor/bin/sail up
```

Проте, замість того щоб раз у раз набирати `vendor/bin/sail`, ви можете налаштувати аліас в оболонці, який дозволить виконувати команди Sail простіше:

```shell
alias sail='sh $([ -f sail ] && echo sail || echo vendor/bin/sail)'
```

Щоб аліас був доступний завжди, додайте його до конфігураційного файлу вашої оболонки в домашньому каталозі - як-от `~/.zshrc` чи `~/.bashrc`, - а потім перезапустіть оболонку.

Коли аліас налаштовано, ви можете виконувати команди Sail, просто набираючи `sail`. Далі в цій документації приклади припускають, що ви налаштували цей аліас:

```shell
sail up
```

<a name="starting-and-stopping-sail"></a>
## Запуск і зупинка Sail

Файл `compose.yaml` у Laravel Sail описує різні контейнери Docker, які працюють разом, щоб допомогти вам будувати застосунки Laravel. Кожен із цих контейнерів - запис у конфігурації `services` вашого файлу `compose.yaml`. Контейнер `laravel.test` - головний контейнер застосунку, який його й віддаватиме.

Перш ніж запускати Sail, переконайтеся, що на вашому комп'ютері не працюють інші вебсервери чи бази даних. Щоб запустити всі контейнери Docker, описані у файлі `compose.yaml` вашого застосунку, виконайте команду `up`:

```shell
sail up
```

Щоб запустити всі контейнери Docker у фоні, стартуйте Sail у «відчепленому» (detached) режимі:

```shell
sail up -d
```

Коли контейнери застосунку запущено, ви можете відкрити проєкт у браузері за адресою http://localhost.

Щоб зупинити всі контейнери, просто натисніть Control + C. Або, якщо контейнери працюють у фоні, скористайтеся командою `stop`:

```shell
sail stop
```

<a name="executing-sail-commands"></a>
## Виконання команд

Коли ви користуєтеся Laravel Sail, ваш застосунок виконується всередині контейнера Docker і ізольований від вашого комп'ютера. Проте Sail дає зручний спосіб запускати різні команди щодо вашого застосунку - як-от довільні команди PHP, артизан-команди, команди Composer і Node / NPM.

**Читаючи документацію Laravel, ви часто бачитимете згадки команд Composer, Artisan і Node / NPM без Sail.** Ті приклади припускають, що ці інструменти встановлено на вашому комп'ютері. Якщо ви користуєтеся Sail як локальним середовищем розробки Laravel, виконуйте ці команди через Sail:

```shell
# Running Artisan commands locally...
php artisan queue:work

# Running Artisan commands within Laravel Sail...
sail artisan queue:work
```

<a name="executing-php-commands"></a>
### Виконання команд PHP

Команди PHP виконуються командою `php`. Звісно, вони виконуватимуться тією версією PHP, яку налаштовано для вашого застосунку. Щоб дізнатися більше про версії PHP, доступні в Laravel Sail, зверніться до [документації про версії PHP](#sail-php-versions):

```shell
sail php --version

sail php script.php
```

<a name="executing-composer-commands"></a>
### Виконання команд Composer

Команди Composer виконуються командою `composer`. Контейнер застосунку в Laravel Sail містить встановлений Composer:

```shell
sail composer require laravel/sanctum
```

<a name="executing-artisan-commands"></a>
### Виконання артизан-команд

Артизан-команди Laravel виконуються командою `artisan`:

```shell
sail artisan queue:work
```

<a name="executing-node-npm-commands"></a>
### Виконання команд Node / NPM

Команди Node виконуються командою `node`, а команди NPM - командою `npm`:

```shell
sail node --version

sail npm run dev
```

За бажання ви можете скористатися Yarn замість NPM:

```shell
sail yarn
```

<a name="interacting-with-sail-databases"></a>
## Робота з базами даних

<a name="mysql"></a>
### MySQL

Як ви могли помітити, файл `compose.yaml` вашого застосунку містить запис для контейнера MySQL. Цей контейнер використовує [том Docker](https://docs.docker.com/storage/volumes/), тож дані у вашій базі зберігаються навіть при зупинці й перезапуску контейнерів.

Крім того, під час першого запуску контейнер MySQL створить для вас дві бази даних. Перша названа за значенням вашої змінної оточення `DB_DATABASE` і призначена для локальної розробки. Друга - окрема тестова база на ім'я `testing`, яка гарантує, що ваші тести не заважатимуть даним розробки.

Коли контейнери запущено, ви можете підключитися до екземпляра MySQL у своєму застосунку, встановивши змінну оточення `DB_HOST` у файлі `.env` вашого застосунку в значення `mysql`.

Щоб підключитися до бази MySQL вашого застосунку зі свого комп'ютера, скористайтеся графічним застосунком для керування базами - як-от [TablePlus](https://tableplus.com). За замовчуванням база MySQL доступна на `localhost`, порт 3306, а облікові дані відповідають значенням ваших змінних оточення `DB_USERNAME` та `DB_PASSWORD`. Або ж ви можете підключитися як користувач `root`, чиїм паролем також є значення змінної оточення `DB_PASSWORD`.

<a name="mongodb"></a>
### MongoDB

Якщо під час встановлення Sail ви обрали сервіс [MongoDB](https://www.mongodb.com/), файл `compose.yaml` вашого застосунку містить запис для контейнера [MongoDB Atlas Local](https://www.mongodb.com/docs/atlas/cli/current/atlas-cli-local-cloud/), який надає документну базу MongoDB з можливостями Atlas - як-от [пошукові індекси](https://www.mongodb.com/docs/atlas/atlas-search/). Цей контейнер використовує [том Docker](https://docs.docker.com/storage/volumes/), тож дані у вашій базі зберігаються навіть при зупинці й перезапуску контейнерів.

Коли контейнери запущено, ви можете підключитися до екземпляра MongoDB у своєму застосунку, встановивши змінну оточення `MONGODB_URI` у файлі `.env` вашого застосунку в значення `mongodb://mongodb:27017`. Автентифікацію за замовчуванням вимкнено, але ви можете задати змінні оточення `MONGODB_USERNAME` та `MONGODB_PASSWORD`, щоб увімкнути її перед запуском контейнера `mongodb`. Далі додайте облікові дані до рядка підключення:

```ini
MONGODB_USERNAME=user
MONGODB_PASSWORD=laravel
MONGODB_URI=mongodb://${MONGODB_USERNAME}:${MONGODB_PASSWORD}@mongodb:27017
```

Для безшовної інтеграції MongoDB з вашим застосунком можна встановити [офіційний пакет, який підтримує MongoDB](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/).

Щоб підключитися до бази MongoDB вашого застосунку зі свого комп'ютера, скористайтеся графічним інтерфейсом - як-от [Compass](https://www.mongodb.com/products/tools/compass). За замовчуванням база MongoDB доступна на `localhost`, порт `27017`.

<a name="redis"></a>
### Redis

Файл `compose.yaml` вашого застосунку містить також запис для контейнера [Redis](https://redis.io). Цей контейнер використовує [том Docker](https://docs.docker.com/storage/volumes/), тож дані у вашому екземплярі Redis зберігаються навіть при зупинці й перезапуску контейнерів. Коли контейнери запущено, ви можете підключитися до екземпляра Redis у своєму застосунку, встановивши змінну оточення `REDIS_HOST` у файлі `.env` вашого застосунку в значення `redis`.

Щоб підключитися до бази Redis вашого застосунку зі свого комп'ютера, скористайтеся графічним застосунком для керування базами - як-от [TablePlus](https://tableplus.com). За замовчуванням база Redis доступна на `localhost`, порт 6379.

<a name="valkey"></a>
### Valkey

Якщо під час встановлення Sail ви обрали сервіс Valkey, файл `compose.yaml` вашого застосунку міститиме запис для [Valkey](https://valkey.io/). Цей контейнер використовує [том Docker](https://docs.docker.com/storage/volumes/), тож дані у вашому екземплярі Valkey зберігаються навіть при зупинці й перезапуску контейнерів. Ви можете підключитися до цього контейнера у своєму застосунку, встановивши змінну оточення `REDIS_HOST` у файлі `.env` вашого застосунку в значення `valkey`.

Щоб підключитися до бази Valkey вашого застосунку зі свого комп'ютера, скористайтеся графічним застосунком для керування базами - як-от [TablePlus](https://tableplus.com). За замовчуванням база Valkey доступна на `localhost`, порт 6379.

<a name="meilisearch"></a>
### Meilisearch

Якщо під час встановлення Sail ви обрали сервіс [Meilisearch](https://www.meilisearch.com), файл `compose.yaml` вашого застосунку міститиме запис для цього потужного пошукового рушія, інтегрованого з [Laravel Scout](/docs/{{version}}/scout). Коли контейнери запущено, ви можете підключитися до екземпляра Meilisearch у своєму застосунку, встановивши змінну оточення `MEILISEARCH_HOST` у значення `http://meilisearch:7700`.

Зі свого комп'ютера ви можете відкрити вебпанель адміністрування Meilisearch, перейшовши у браузері за адресою `http://localhost:7700`.

<a name="typesense"></a>
### Typesense

Якщо під час встановлення Sail ви обрали сервіс [Typesense](https://typesense.org), файл `compose.yaml` вашого застосунку міститиме запис для цього блискавично швидкого пошукового рушія з відкритим кодом, який нативно інтегрований з [Laravel Scout](/docs/{{version}}/scout#typesense). Коли контейнери запущено, ви можете підключитися до екземпляра Typesense у своєму застосунку, задавши такі змінні оточення:

```ini
TYPESENSE_HOST=typesense
TYPESENSE_PORT=8108
TYPESENSE_PROTOCOL=http
TYPESENSE_API_KEY=xyz
```

Зі свого комп'ютера ви можете звертатися до API Typesense за адресою `http://localhost:8108`.

<a name="file-storage"></a>
## Зберігання файлів

Якщо ви плануєте зберігати файли в Amazon S3, коли ваш застосунок працює у продакшн-середовищі, вам, можливо, варто встановити сервіс [RustFS](https://rustfs.com) під час встановлення Sail. RustFS надає сумісний із S3 API, яким ви можете розробляти локально через драйвер файлового сховища `s3` у Laravel, не створюючи «тестових» бакетів у своєму продакшн-середовищі S3. Якщо ви оберете RustFS під час встановлення Sail, до файлу `compose.yaml` вашого застосунку буде додано секцію конфігурації RustFS.

За замовчуванням конфігураційний файл `filesystems` вашого застосунку вже містить конфігурацію диска `s3`. Окрім роботи з Amazon S3, цей диск можна використовувати з будь-яким сумісним із S3 сервісом зберігання файлів - як-от RustFS, - просто змінивши відповідні змінні оточення, що керують його конфігурацією. Наприклад, для RustFS конфігурація змінних оточення файлової системи має мати такий вигляд:

```ini
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=sail
AWS_SECRET_ACCESS_KEY=password
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=local
AWS_ENDPOINT=http://rustfs:9000
AWS_USE_PATH_STYLE_ENDPOINT=true
```

<a name="running-tests"></a>
## Запуск тестів

Laravel надає чудову підтримку тестування одразу з коробки, і ви можете скористатися командою Sail `test`, щоб запустити [функціональні та юніт-тести](/docs/{{version}}/testing) свого застосунку. Будь-які опції CLI, які приймають Pest / PHPUnit, можна передати й команді `test`:

```shell
sail test

sail test --group orders
```

Команда Sail `test` рівносильна виконанню артизан-команди `test`:

```shell
sail artisan test
```

За замовчуванням Sail створить окрему базу `testing`, щоб ваші тести не заважали поточному стану вашої бази даних. У стандартній установці Laravel Sail також налаштує ваш файл `phpunit.xml` на використання цієї бази під час прогону тестів:

```xml
<env name="DB_DATABASE" value="testing"/>
```

<a name="laravel-dusk"></a>
### Laravel Dusk

[Laravel Dusk](/docs/{{version}}/dusk) надає виразний і простий у користуванні API для автоматизації браузера й тестування. Завдяки Sail ви можете запускати ці тести, не встановлюючи Selenium чи інші інструменти на своєму комп'ютері. Для початку розкоментуйте сервіс Selenium у файлі `compose.yaml` вашого застосунку:

```yaml
selenium:
    image: 'selenium/standalone-chrome'
    extra_hosts:
      - 'host.docker.internal:host-gateway'
    volumes:
        - '/dev/shm:/dev/shm'
    networks:
        - sail
```

Далі переконайтеся, що сервіс `laravel.test` у файлі `compose.yaml` вашого застосунку має запис `depends_on` для `selenium`:

```yaml
depends_on:
    - mysql
    - redis
    - selenium
```

Нарешті, ви можете запустити свій набір тестів Dusk, стартувавши Sail і виконавши команду `dusk`:

```shell
sail dusk
```

<a name="selenium-on-apple-silicon"></a>
#### Selenium на Apple Silicon

Якщо ваш комп'ютер має чип Apple Silicon, ваш сервіс `selenium` має використовувати образ `selenium/standalone-chromium`:

```yaml
selenium:
    image: 'selenium/standalone-chromium'
    extra_hosts:
        - 'host.docker.internal:host-gateway'
    volumes:
        - '/dev/shm:/dev/shm'
    networks:
        - sail
```

<a name="previewing-emails"></a>
## Перегляд листів

Стандартний файл `compose.yaml` у Laravel Sail містить запис сервісу для [Mailpit](https://github.com/axllent/mailpit). Mailpit перехоплює листи, надіслані вашим застосунком під час локальної розробки, і надає зручний вебінтерфейс, щоб ви могли переглядати їх у браузері. Із Sail стандартний хост Mailpit - `mailpit`, доступний на порту 1025:

```ini
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_ENCRYPTION=null
```

Коли Sail працює, ви можете відкрити вебінтерфейс Mailpit за адресою http://localhost:8025

Якщо ви вирішили встановити сервіс [Mailtrap Local](https://github.com/mailtrap/mailtrap-local) під час інсталяції Sail або додали його пізніше за допомогою Artisan-команди `sail:add`, файл `compose.yaml` вашого застосунку міститиме запис для цього перехоплювача листів замість Mailpit. Стандартний хост Mailtrap Local - `mailtrap-local`, доступний на порту 3535:

```ini
MAIL_HOST=mailtrap-local
MAIL_PORT=3535
```

Коли Sail працює, ви можете відкрити вебінтерфейс Mailtrap Local за адресою http://localhost:3550

<a name="sail-container-cli"></a>
## CLI контейнера

Іноді вам може знадобитися відкрити сеанс Bash усередині контейнера вашого застосунку. Скористайтеся командою `shell`, щоб підключитися до контейнера застосунку, - так ви зможете оглянути його файли та встановлені сервіси, а також виконувати довільні команди оболонки всередині контейнера:

```shell
sail shell

sail root-shell
```

Щоб розпочати новий сеанс [Laravel Tinker](https://github.com/laravel/tinker), виконайте команду `tinker`:

```shell
sail tinker
```

<a name="sail-php-versions"></a>
## Версії PHP

Наразі Sail підтримує роботу вашого застосунку на PHP 8.5, 8.4, 8.3, 8.2, 8.1 чи PHP 8.0. Стандартна версія PHP у Sail наразі - PHP 8.5. Щоб змінити версію PHP, якою віддається ваш застосунок, оновіть визначення `build` контейнера `laravel.test` у файлі `compose.yaml` вашого застосунку:

```yaml
# PHP 8.5
context: ./vendor/laravel/sail/runtimes/8.5

# PHP 8.4
context: ./vendor/laravel/sail/runtimes/8.4

# PHP 8.3
context: ./vendor/laravel/sail/runtimes/8.3

# PHP 8.2
context: ./vendor/laravel/sail/runtimes/8.2

# PHP 8.1
context: ./vendor/laravel/sail/runtimes/8.1

# PHP 8.0
context: ./vendor/laravel/sail/runtimes/8.0
```

Крім того, вам, можливо, варто оновити ім'я `image`, щоб воно відображало версію PHP, яку використовує ваш застосунок. Ця опція також визначена у файлі `compose.yaml` вашого застосунку:

```yaml
image: sail-8.2/app
```

Оновивши файл `compose.yaml` вашого застосунку, перезберіть образи контейнерів:

```shell
sail build --no-cache

sail up
```

<a name="sail-php-extensions"></a>
### Додаткові розширення PHP

Образи середовища виконання Sail містять поширений набір розширень PHP. Якщо вашому застосунку потрібні додаткові розширення, ви можете встановити їх під час збирання образу, додавши до сервісу `laravel.test` у файлі `compose.yaml` вашого застосунку аргумент збірки `PHP_EXTENSIONS` зі значеннями через пробіл:

```yaml
build:
    args:
        WWWGROUP: '${WWWGROUP}'
        PHP_EXTENSIONS: 'gmp imagick'
```

Оновивши файл `compose.yaml` вашого застосунку, перезберіть образи контейнерів.

<a name="sail-node-versions"></a>
## Версії Node

За замовчуванням Sail встановлює Node 24. Щоб змінити версію Node, яка встановлюється під час збирання образів, оновіть визначення `build.args` сервісу `laravel.test` у файлі `compose.yaml` вашого застосунку:

```yaml
build:
    args:
        WWWGROUP: '${WWWGROUP}'
        NODE_VERSION: '18'
```

Оновивши файл `compose.yaml` вашого застосунку, перезберіть образи контейнерів:

```shell
sail build --no-cache

sail up
```

<a name="sharing-your-site"></a>
## Публічний доступ до вашого сайту

Іноді вам може знадобитися відкрити свій сайт публічно, щоб показати його колезі чи протестувати інтеграції з вебхуками. Щоб поділитися сайтом, скористайтеся командою `share`. Виконавши її, ви отримаєте випадкову адресу `laravel-sail.site`, за якою можна звертатися до вашого застосунку:

```shell
sail share
```

Відкриваючи сайт командою `share`, налаштуйте довірені проксі вашого застосунку методом `middleware` `trustProxies` у файлі `bootstrap/app.php`. Інакше хелпери генерування URL - як-от `url` і `route` - не зможуть визначити правильний HTTP-хост для генерування URL:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->trustProxies(at: '*');
})
```

Якщо ви хочете обрати піддомен для свого відкритого сайту, додайте опцію `subdomain` до команди `share`:

```shell
sail share --subdomain=my-sail-site
```

> [!NOTE]
> Команда `share` працює на [Expose](https://github.com/beyondcode/expose) - сервісі тунелювання з відкритим кодом від [BeyondCode](https://beyondco.de).

<a name="debugging-with-xdebug"></a>
## Налагодження через Xdebug

Конфігурація Docker у Laravel Sail містить підтримку [Xdebug](https://xdebug.org/) - популярного й потужного налагоджувача для PHP. Щоб увімкнути Xdebug, переконайтеся, що ви [опублікували конфігурацію Sail](#sail-customization). Далі додайте до файлу `.env` вашого застосунку такі змінні, щоб налаштувати Xdebug:

```ini
SAIL_XDEBUG_MODE=develop,debug,coverage
```

Далі переконайтеся, що ваш опублікований файл `php.ini` містить таку конфігурацію, щоб Xdebug активувався у вказаних режимах:

```ini
[xdebug]
xdebug.mode=${XDEBUG_MODE}
```

Змінивши файл `php.ini`, не забудьте перезібрати образи Docker, щоб ваші зміни набрали чинності:

```shell
sail build --no-cache
```

#### Налаштування IP хоста на Linux

Усередині змінна оточення `XDEBUG_CONFIG` має значення `client_host=host.docker.internal`, щоб Xdebug був належно налаштований для Mac і Windows (WSL2). Якщо ваш комп'ютер працює на Linux і ви користуєтеся Docker 20.10+, `host.docker.internal` доступний, і ручне налаштування не потрібне.

Для версій Docker, старіших за 20.10, `host.docker.internal` на Linux не підтримується, тож вам доведеться задати IP хоста вручну. Для цього налаштуйте статичний IP для свого контейнера, описавши власну мережу у файлі `compose.yaml`:

```yaml
networks:
  custom_network:
    ipam:
      config:
        - subnet: 172.20.0.0/16

services:
  laravel.test:
    networks:
      custom_network:
        ipv4_address: 172.20.0.2
```

Задавши статичний IP, визначте змінну SAIL_XDEBUG_CONFIG у файлі .env вашого застосунку:

```ini
SAIL_XDEBUG_CONFIG="client_host=172.20.0.2"
```

<a name="xdebug-cli-usage"></a>
### Xdebug у CLI

Команда `sail debug` дозволяє розпочати сеанс налагодження під час виконання артизан-команди:

```shell
# Run an Artisan command without Xdebug...
sail artisan migrate

# Run an Artisan command with Xdebug...
sail debug migrate
```

<a name="xdebug-browser-usage"></a>
### Xdebug у браузері

Щоб налагоджувати застосунок, працюючи з ним через веббраузер, дотримуйтеся [інструкцій Xdebug](https://xdebug.org/docs/step_debug#web-application) щодо запуску сеансу Xdebug із браузера.

Якщо ви користуєтеся PhpStorm, перегляньте документацію JetBrains про [налагодження без конфігурації](https://www.jetbrains.com/help/phpstorm/zero-configuration-debugging.html).

> [!WARNING]
> Laravel Sail покладається на `artisan serve`, щоб віддавати ваш застосунок. Команда `artisan serve` приймає змінні `XDEBUG_CONFIG` та `XDEBUG_MODE` лише починаючи з Laravel 8.53.0. Старіші версії Laravel (8.52.0 і нижче) не підтримують цих змінних і не приймуть налагоджувальних підключень.

<a name="sail-customization"></a>
## Налаштування

Оскільки Sail - це просто Docker, ви вільні налаштувати в ньому майже все. Щоб опублікувати власні Dockerfile Sail, виконайте команду `sail:publish`:

```shell
sail artisan sail:publish
```

Після виконання цієї команди Dockerfile та інші конфігураційні файли, які використовує Laravel Sail, потраплять до каталогу `docker` у корені вашого застосунку. Налаштувавши свою установку Sail, вам, можливо, варто змінити ім'я образу для контейнера застосунку у файлі `compose.yaml`. Після цього перезберіть контейнери застосунку командою `build`. Присвоєння унікального імені образу застосунку особливо важливе, якщо ви розробляєте через Sail кілька застосунків Laravel на одній машині:

```shell
sail build --no-cache
```

---

Джерело: https://laravelukraine.com/docs/13.x/sanctum

# Laravel Sanctum

- [Вступ](#introduction)
    - [Як це працює](#how-it-works)
- [Встановлення](#installation)
- [Конфігурація](#configuration)
    - [Перевизначення стандартних моделей](#overriding-default-models)
- [Автентифікація через API-токени](#api-token-authentication)
    - [Видача API-токенів](#issuing-api-tokens)
    - [Можливості токенів](#token-abilities)
    - [Захист маршрутів](#protecting-routes)
    - [Відкликання токенів](#revoking-tokens)
    - [Термін дії токенів](#token-expiration)
- [Автентифікація SPA](#spa-authentication)
    - [Конфігурація](#spa-configuration)
    - [Автентифікація](#spa-authenticating)
    - [Захист маршрутів](#protecting-spa-routes)
    - [Авторизація приватних каналів бродкастингу](#authorizing-private-broadcast-channels)
- [Автентифікація мобільних застосунків](#mobile-application-authentication)
    - [Видача API-токенів](#issuing-mobile-api-tokens)
    - [Захист маршрутів](#protecting-mobile-api-routes)
    - [Відкликання токенів](#revoking-mobile-api-tokens)
- [Тестування](#testing)

<a name="introduction"></a>
## Вступ

[Laravel Sanctum](https://github.com/laravel/sanctum) надає легку як пір'їнка систему автентифікації для SPA (односторінкових застосунків), мобільних застосунків і простих API на токенах. Sanctum дозволяє кожному користувачеві вашого застосунку генерувати кілька API-токенів для свого облікового запису. Цим токенам можна надати можливості / скопи, які визначають, які дії дозволено виконувати з токеном.

<a name="how-it-works"></a>
### Як це працює

Laravel Sanctum існує, щоб розв'язати дві окремі проблеми. Обговорімо кожну з них, перш ніж заглиблюватися в бібліотеку.

<a name="how-it-works-api-tokens"></a>
#### API-токени

По-перше, Sanctum - це простий пакет, яким ви можете видавати API-токени своїм користувачам без складнощів OAuth. Ця можливість надихнута GitHub та іншими застосунками, які видають «персональні токени доступу». Уявіть, наприклад, що в «налаштуваннях облікового запису» вашого застосунку є екран, де користувач може згенерувати API-токен. Sanctum дозволяє генерувати такі токени й керувати ними. Зазвичай вони мають дуже довгий термін дії (роки), але користувач може будь-коли відкликати їх вручну.

Laravel Sanctum реалізує цю можливість, зберігаючи API-токени користувачів в одній таблиці бази даних і автентифікуючи вхідні HTTP-запити через заголовок `Authorization`, який має містити дійсний API-токен.

<a name="how-it-works-spa-authentication"></a>
#### Автентифікація SPA

По-друге, Sanctum пропонує простий спосіб автентифікувати односторінкові застосунки (SPA), яким треба спілкуватися з API на Laravel. Такі SPA можуть жити в тому самому репозиторії, що й ваш застосунок Laravel, або в цілком окремому - наприклад, SPA, створений на Next.js чи Nuxt.

Для цієї можливості Sanctum не використовує жодних токенів. Натомість Sanctum спирається на вбудовані сервіси сесійної автентифікації Laravel на основі cookie. Зазвичай для цього Sanctum використовує гард автентифікації `web`. Це дає переваги захисту від CSRF, сесійної автентифікації, а також захищає від витоку облікових даних через XSS.

Sanctum намагатиметься автентифікувати через cookie лише тоді, коли вхідний запит надходить із фронтенду вашого власного SPA. Перевіряючи вхідний HTTP-запит, Sanctum спершу шукає cookie автентифікації, а якщо його немає - перевіряє заголовок `Authorization` на наявність дійсного API-токена.

> [!NOTE]
> Цілком нормально користуватися Sanctum лише для автентифікації через API-токени або лише для автентифікації SPA. Використання Sanctum не зобов'язує вас застосовувати обидві його можливості.

<a name="installation"></a>
## Встановлення

Ви можете встановити Laravel Sanctum артизан-командою `install:api`:

```shell
php artisan install:api
```

Далі, якщо ви плануєте автентифікувати через Sanctum ваш SPA, зверніться до розділу [Автентифікація SPA](#spa-authentication) цієї документації.

<a name="configuration"></a>
## Конфігурація

<a name="overriding-default-models"></a>
### Перевизначення стандартних моделей

Хоча зазвичай це не потрібно, ви вільні розширити модель `PersonalAccessToken`, яку Sanctum використовує всередині:

```php
use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;

class PersonalAccessToken extends SanctumPersonalAccessToken
{
    // ...
}
```

Далі ви можете вказати Sanctum використовувати вашу модель через метод `usePersonalAccessTokenModel`, який надає Sanctum. Зазвичай цей метод викликають у методі `boot` файлу `AppServiceProvider` вашого застосунку:

```php
use App\Models\Sanctum\PersonalAccessToken;
use Laravel\Sanctum\Sanctum;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
}
```

<a name="api-token-authentication"></a>
## Автентифікація через API-токени

> [!NOTE]
> Вам не слід автентифікувати власний SPA через API-токени. Натомість скористайтеся вбудованими [можливостями автентифікації SPA](#spa-authentication) у Sanctum.

<a name="issuing-api-tokens"></a>
### Видача API-токенів

Sanctum дозволяє видавати API-токени / персональні токени доступу, якими можна автентифікувати API-запити до вашого застосунку. Роблячи запити з API-токеном, передавайте його в заголовку `Authorization` як токен `Bearer`.

Щоб почати видавати токени користувачам, ваша модель User має використовувати трейт `Laravel\Sanctum\HasApiTokens`:

```php
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;
}
```

Щоб видати токен, скористайтеся методом `createToken`. Метод `createToken` повертає екземпляр `Laravel\Sanctum\NewAccessToken`. API-токени хешуються через SHA-256 перед збереженням у вашій базі даних, але ви можете отримати значення токена відкритим текстом через властивість `plainTextToken` екземпляра `NewAccessToken`. Це значення слід показати користувачеві одразу після створення токена:

```php
use Illuminate\Http\Request;

Route::post('/tokens/create', function (Request $request) {
    $token = $request->user()->createToken($request->token_name);

    return ['token' => $token->plainTextToken];
});
```

Ви можете дістатися всіх токенів користувача через зв'язок Eloquent `tokens`, який надає трейт `HasApiTokens`:

```php
foreach ($user->tokens as $token) {
    // ...
}
```

<a name="token-abilities"></a>
### Можливості токенів

Sanctum дозволяє призначати токенам «можливості» (abilities). Можливості слугують схожій меті, що й «скопи» в OAuth. Ви можете передати масив рядків-можливостей другим аргументом до методу `createToken`:

```php
return $user->createToken('token-name', ['server:update'])->plainTextToken;
```

Обробляючи вхідний запит, автентифікований через Sanctum, ви можете визначити, чи має токен задану можливість, методами `tokenCan` чи `tokenCant`:

```php
if ($user->tokenCan('server:update')) {
    // ...
}

if ($user->tokenCant('server:update')) {
    // ...
}
```

<a name="token-ability-middleware"></a>
#### Middleware для можливостей токенів

Sanctum також містить два `middleware`, якими можна перевірити, що вхідний запит автентифіковано токеном із заданою можливістю. Для початку визначте у файлі `bootstrap/app.php` вашого застосунку такі аліаси `middleware`:

```php
use Laravel\Sanctum\Http\Middleware\CheckAbilities;
use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->alias([
        'abilities' => CheckAbilities::class,
        'ability' => CheckForAnyAbility::class,
    ]);
})
```

`middleware` `abilities` можна призначити маршруту, щоб перевірити, що токен вхідного запиту має **усі** перелічені можливості:

```php
Route::get('/orders', function () {
    // Token has both "check-status" and "place-orders" abilities...
})->middleware(['auth:sanctum', 'abilities:check-status,place-orders']);
```

`middleware` `ability` можна призначити маршруту, щоб перевірити, що токен вхідного запиту має *хоча б одну* з перелічених можливостей:

```php
Route::get('/orders', function () {
    // Token has the "check-status" or "place-orders" ability...
})->middleware(['auth:sanctum', 'ability:check-status,place-orders']);
```

<a name="first-party-ui-initiated-requests"></a>
#### Запити, ініційовані власним UI

Для зручності метод `tokenCan` завжди повертатиме `true`, якщо вхідний автентифікований запит надійшов із вашого власного SPA й ви користуєтеся вбудованою [автентифікацією SPA](#spa-authentication) у Sanctum.

Проте це не означає, що ваш застосунок зобов'язаний дозволити користувачеві виконати дію. Зазвичай [політики авторизації](/docs/{{version}}/authorization#creating-policies) вашого застосунку визначатимуть і те, чи надано токену дозвіл виконувати ці можливості, і те, чи має право виконати дію сам користувач.

Наприклад, якщо уявити застосунок для керування серверами, це може означати перевірку того, що токену дозволено оновлювати сервери **і** що сервер належить користувачеві:

```php
return $request->user()->id === $server->user_id &&
       $request->user()->tokenCan('server:update');
```

Спершу може здатися дивним, що метод `tokenCan` можна викликати й він завжди повертає `true` для запитів, ініційованих власним UI; проте зручно завжди мати змогу припускати, що API-токен доступний і його можна перевірити методом `tokenCan`. Завдяки такому підходу ви можете завжди викликати метод `tokenCan` у політиках авторизації вашого застосунку, не переймаючись тим, чи запит надійшов з UI вашого застосунку, чи його ініціював хтось зі сторонніх споживачів вашого API.

<a name="protecting-routes"></a>
### Захист маршрутів

Щоб захистити маршрути так, аби всі вхідні запити мали бути автентифікованими, додайте гард автентифікації `sanctum` до ваших захищених маршрутів у файлах `routes/web.php` та `routes/api.php`. Цей гард гарантуватиме, що вхідні запити або автентифіковані як stateful-запити через cookie, або містять заголовок із дійсним API-токеном, якщо запит надійшов від третьої сторони.

Ви можете замислитися, чому ми радимо автентифікувати гардом `sanctum` і маршрути у файлі `routes/web.php`. Пам'ятайте: Sanctum спершу спробує автентифікувати вхідні запити через звичайний сесійний cookie автентифікації Laravel. Якщо цього cookie немає, Sanctum спробує автентифікувати запит токеном із заголовка `Authorization`. До того ж автентифікація всіх запитів через Sanctum гарантує, що ми завжди зможемо викликати метод `tokenCan` на екземплярі поточного автентифікованого користувача:

```php
use Illuminate\Http\Request;

Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:sanctum');
```

<a name="revoking-tokens"></a>
### Відкликання токенів

Ви можете «відкликати» токени, видаливши їх зі своєї бази даних через зв'язок `tokens`, який надає трейт `Laravel\Sanctum\HasApiTokens`:

```php
// Revoke all tokens...
$user->tokens()->delete();

// Revoke the token that was used to authenticate the current request...
$request->user()->currentAccessToken()->delete();

// Revoke a specific token...
$user->tokens()->where('id', $tokenId)->delete();
```

<a name="token-expiration"></a>
### Термін дії токенів

За замовчуванням токени Sanctum ніколи не спливають, і їх можна знеструмити лише [відкликанням](#revoking-tokens). Проте якщо ви хочете задати час дії API-токенів вашого застосунку, зробіть це через опцію конфігурації `expiration` у вашому конфігураційному файлі `sanctum`. Ця опція визначає кількість хвилин, після яких виданий токен вважатиметься простроченим:

```php
'expiration' => 525600,
```

Якщо ви хочете задати термін дії кожного токена окремо, передайте його третім аргументом до методу `createToken`:

```php
return $user->createToken(
    'token-name', ['*'], now()->plus(weeks: 1)
)->plainTextToken;
```

Якщо ви налаштували термін дії токенів для свого застосунку, вам, можливо, варто також [запланувати завдання](/docs/{{version}}/scheduling), яке прибиратиме прострочені токени. На щастя, Sanctum містить артизан-команду `sanctum:prune-expired` саме для цього. Наприклад, ви можете налаштувати заплановане завдання, яке видалятиме всі записи токенів, прострочених щонайменше 24 години:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('sanctum:prune-expired --hours=24')->daily();
```

<a name="spa-authentication"></a>
## Автентифікація SPA

Sanctum також існує, щоб дати простий спосіб автентифікувати односторінкові застосунки (SPA), яким треба спілкуватися з API на Laravel. Такі SPA можуть жити в тому самому репозиторії, що й ваш застосунок Laravel, або в цілком окремому.

Для цієї можливості Sanctum не використовує жодних токенів. Натомість Sanctum спирається на вбудовані сервіси сесійної автентифікації Laravel на основі cookie. Такий підхід до автентифікації дає переваги захисту від CSRF, сесійної автентифікації, а також захищає від витоку облікових даних через XSS.

> [!WARNING]
> Щоб автентифікація працювала, ваш SPA та API мають бути на одному домені верхнього рівня. Проте вони можуть бути на різних піддоменах. Крім того, надсилайте із запитом заголовок `Accept: application/json` і заголовок `Referer` чи `Origin`.

<a name="spa-configuration"></a>
### Конфігурація

<a name="configuring-your-first-party-domains"></a>
#### Налаштування власних доменів

Спершу вам треба вказати, з яких доменів ваш SPA робитиме запити. Налаштувати ці домени можна через опцію конфігурації `stateful` у вашому конфігураційному файлі `sanctum`. Ця настройка визначає, які домени підтримуватимуть «stateful»-автентифікацію через сесійні cookie Laravel під час запитів до вашого API.

Щоб допомогти вам налаштувати власні stateful-домени, Sanctum надає дві допоміжні функції, які можна включити до конфігурації. Перша, `Sanctum::currentApplicationUrlWithPort()`, поверне поточний URL застосунку зі змінної оточення `APP_URL`, а `Sanctum::currentRequestHost()` вставить до списку stateful-доменів плейсхолдер, який під час виконання буде замінено на хост із поточного запиту, - тож усі запити з тим самим доменом вважатимуться stateful.

> [!WARNING]
> Якщо ви звертаєтеся до застосунку за URL із портом (`127.0.0.1:8000`), обов'язково вкажіть номер порту разом із доменом.

<a name="sanctum-middleware"></a>
#### Middleware Sanctum

Далі вам слід указати Laravel, що вхідні запити з вашого SPA можуть автентифікуватися через сесійні cookie Laravel, водночас дозволивши запитам від третіх сторін чи мобільних застосунків автентифікуватися через API-токени. Легко зробити це можна викликом методу `middleware` `statefulApi` у файлі `bootstrap/app.php` вашого застосунку:

```php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->statefulApi();
})
```

<a name="cors-and-cookies"></a>
#### CORS і cookie

Якщо у вас виникають проблеми з автентифікацією у вашому застосунку з SPA, що працює на окремому піддомені, ви, найімовірніше, неправильно налаштували CORS (Cross-Origin Resource Sharing) або сесійні cookie.

Конфігураційний файл `config/cors.php` за замовчуванням не публікується. Якщо вам треба налаштувати опції CORS у Laravel, опублікуйте повний конфігураційний файл `cors` артизан-командою `config:publish`:

```shell
php artisan config:publish cors
```

Далі переконайтеся, що конфігурація CORS вашого застосунку повертає заголовок `Access-Control-Allow-Credentials` зі значенням `True`. Це можна зробити, встановивши опцію `supports_credentials` у вашому файлі `config/cors.php` у значення `true`.

Крім того, увімкніть опції `withCredentials` та `withXSRFToken` на глобальному екземплярі `axios` вашого застосунку. Зробити це можна у файлі `resources/js/app.js`. Якщо ви не користуєтеся Axios для HTTP-запитів із фронтенду, виконайте рівнозначну конфігурацію у своєму HTTP-клієнті:

```js
axios.defaults.withCredentials = true;
axios.defaults.withXSRFToken = true;
```

Нарешті, переконайтеся, що конфігурація домену сесійних cookie вашого застосунку підтримує будь-який піддомен вашого кореневого домену. Для цього додайте до домену провідну крапку `.` у вашому конфігураційному файлі `config/session.php`:

```php
'domain' => '.domain.com',
```

<a name="spa-authenticating"></a>
### Автентифікація

<a name="csrf-protection"></a>
#### Захист від CSRF

Щоб автентифікувати ваш SPA, сторінка «входу» вашого SPA має спершу зробити запит до ендпоїнта `/sanctum/csrf-cookie`, щоб ініціалізувати захист від CSRF:

```js
axios.get('/sanctum/csrf-cookie').then(response => {
    // Login...
});
```

Під час цього запиту Laravel установить cookie `XSRF-TOKEN` з поточним CSRF-токеном. Далі цей токен слід декодувати з URL і передавати в заголовку `X-XSRF-TOKEN` у наступних запитах - деякі HTTP-клієнти на кшталт Axios чи Angular HttpClient зроблять це за вас автоматично. Якщо ваша JavaScript-бібліотека не встановлює це значення сама, вам доведеться вручну задати заголовок `X-XSRF-TOKEN` так, щоб він відповідав URL-декодованому значенню cookie `XSRF-TOKEN`, встановленого цим маршрутом.

<a name="logging-in"></a>
#### Вхід

Коли захист від CSRF ініціалізовано, зробіть запит `POST` до маршруту `/login` вашого застосунку Laravel. Цей маршрут `/login` можна [реалізувати вручну](/docs/{{version}}/authentication#authenticating-users) або через headless-пакет автентифікації на кшталт [Laravel Fortify](/docs/{{version}}/fortify).

Якщо запит на вхід успішний, вас буде автентифіковано, і наступні запити до маршрутів вашого застосунку автоматично автентифікуватимуться через сесійний cookie, який застосунок Laravel видав вашому клієнту. До того ж, оскільки ваш застосунок уже звернувся до маршруту `/sanctum/csrf-cookie`, наступні запити мають автоматично отримувати захист від CSRF, доки ваш JavaScript HTTP-клієнт надсилає значення cookie `XSRF-TOKEN` у заголовку `X-XSRF-TOKEN`.

Звісно, якщо сесія користувача спливе через брак активності, наступні запити до застосунку Laravel можуть отримати HTTP-помилку 401 чи 419. У такому разі перенаправте користувача на сторінку входу вашого SPA.

Оскільки такий підхід до автентифікації SPA базується на сесіях, ви можете користуватися стандартними сервісами автентифікації Laravel, зокрема функціональністю [«запам'ятати мене»](/docs/{{version}}/authentication#remembering-users).

> [!WARNING]
> Ви вільні написати власний ендпоїнт `/login`; проте переконайтеся, що він автентифікує користувача через стандартні [сервіси сесійної автентифікації, які надає Laravel](/docs/{{version}}/authentication#authenticating-users). Зазвичай це означає використання гарда автентифікації `web`.

<a name="protecting-spa-routes"></a>
### Захист маршрутів

Щоб захистити маршрути так, аби всі вхідні запити мали бути автентифікованими, додайте гард автентифікації `sanctum` до ваших API-маршрутів у файлі `routes/api.php`. Цей гард гарантуватиме, що вхідні запити або автентифіковані як stateful-запити з вашого SPA, або містять заголовок із дійсним API-токеном, якщо запит надійшов від третьої сторони:

```php
use Illuminate\Http\Request;

Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:sanctum');
```

<a name="authorizing-private-broadcast-channels"></a>
### Авторизація приватних каналів бродкастингу

Якщо вашому SPA треба автентифікуватися в [приватних каналах чи каналах присутності](/docs/{{version}}/broadcasting#authorizing-channels), приберіть запис `channels` з методу `withRouting` у файлі `bootstrap/app.php` вашого застосунку. Натомість викличте метод `withBroadcasting`, щоб указати правильне `middleware` для маршрутів бродкастингу вашого застосунку:

```php
return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        // ...
    )
    ->withBroadcasting(
        __DIR__.'/../routes/channels.php',
        ['prefix' => 'api', 'middleware' => ['api', 'auth:sanctum']],
    )
    ->create();
```

Далі, щоб запити авторизації Pusher були успішними, вам треба передати власний `authorizer` для Pusher під час ініціалізації [Laravel Echo](/docs/{{version}}/broadcasting#client-side-installation). Це дозволить вашому застосунку налаштувати Pusher на використання екземпляра `axios`, [належно налаштованого для міждоменних запитів](#cors-and-cookies):

```js
window.Echo = new Echo({
    broadcaster: "pusher",
    cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
    encrypted: true,
    key: import.meta.env.VITE_PUSHER_APP_KEY,
    authorizer: (channel, options) => {
        return {
            authorize: (socketId, callback) => {
                axios.post('/api/broadcasting/auth', {
                    socket_id: socketId,
                    channel_name: channel.name
                })
                .then(response => {
                    callback(false, response.data);
                })
                .catch(error => {
                    callback(true, error);
                });
            }
        };
    },
})
```

<a name="mobile-application-authentication"></a>
## Автентифікація мобільних застосунків

Ви також можете автентифікувати токенами Sanctum запити вашого мобільного застосунку до вашого API. Процес автентифікації запитів мобільного застосунку схожий на автентифікацію сторонніх API-запитів; проте є невеликі відмінності в тому, як ви видаватимете API-токени.

<a name="issuing-mobile-api-tokens"></a>
### Видача API-токенів

Для початку створіть маршрут, який приймає пошту / ім'я користувача, пароль та ім'я пристрою, а потім обмінює ці облікові дані на новий токен Sanctum. «Ім'я пристрою», передане цьому ендпоїнту, потрібне лише для інформації й може бути будь-яким. Загалом ім'я пристрою має бути таким, яке користувач упізнає, - наприклад, «Nuno's iPhone 17».

Зазвичай ви робитимете запит до цього ендпоїнта з екрана «входу» вашого мобільного застосунку. Ендпоїнт поверне API-токен відкритим текстом, який потім можна зберегти на мобільному пристрої й використовувати для наступних API-запитів:

```php
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;

Route::post('/sanctum/token', function (Request $request) {
    $request->validate([
        'email' => 'required|email',
        'password' => 'required',
        'device_name' => 'required',
    ]);

    $user = User::where('email', $request->email)->first();

    if (! $user || ! Hash::check($request->password, $user->password)) {
        throw ValidationException::withMessages([
            'email' => ['The provided credentials are incorrect.'],
        ]);
    }

    return $user->createToken($request->device_name)->plainTextToken;
});
```

Коли мобільний застосунок робить із цим токеном API-запит до вашого застосунку, він має передавати токен у заголовку `Authorization` як токен `Bearer`.

> [!NOTE]
> Видаючи токени для мобільного застосунку, ви так само вільні вказати [можливості токенів](#token-abilities).

<a name="protecting-mobile-api-routes"></a>
### Захист маршрутів

Як уже описано раніше, ви можете захистити маршрути так, аби всі вхідні запити мали бути автентифікованими, додавши до них гард автентифікації `sanctum`:

```php
Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:sanctum');
```

<a name="revoking-mobile-api-tokens"></a>
### Відкликання токенів

Щоб дозволити користувачам відкликати API-токени, видані мобільним пристроям, ви можете вивести їх за іменами разом із кнопкою «Revoke» у розділі «налаштування облікового запису» інтерфейсу вашого вебзастосунку. Коли користувач натисне «Revoke», ви можете видалити токен із бази даних. Пам'ятайте: до API-токенів користувача можна дістатися через зв'язок `tokens`, який надає трейт `Laravel\Sanctum\HasApiTokens`:

```php
// Revoke all tokens...
$user->tokens()->delete();

// Revoke a specific token...
$user->tokens()->where('id', $tokenId)->delete();
```

<a name="testing"></a>
## Тестування

Під час тестування метод `Sanctum::actingAs` дозволяє автентифікувати користувача й указати, які можливості слід надати його токену:

```php tab=Pest
use App\Models\User;
use Laravel\Sanctum\Sanctum;

test('task list can be retrieved', function () {
    Sanctum::actingAs(
        User::factory()->create(),
        ['view-tasks']
    );

    $response = $this->get('/api/task');

    $response->assertOk();
});
```

```php tab=PHPUnit
use App\Models\User;
use Laravel\Sanctum\Sanctum;

public function test_task_list_can_be_retrieved(): void
{
    Sanctum::actingAs(
        User::factory()->create(),
        ['view-tasks']
    );

    $response = $this->get('/api/task');

    $response->assertOk();
}
```

Якщо ви хочете надати токену всі можливості, включіть `*` до списку можливостей, переданого методу `actingAs`:

```php
Sanctum::actingAs(
    User::factory()->create(),
    ['*']
);
```

---

Джерело: https://laravelukraine.com/docs/13.x/scout

# Laravel Scout

- [Вступ](#introduction)
- [Встановлення](#installation)
    - [Черги](#queueing)
- [Передумови для драйверів](#driver-prerequisites)
    - [Algolia](#algolia)
    - [Meilisearch](#meilisearch)
    - [Typesense](#typesense)
    - [Turbopuffer](#turbopuffer)
- [Конфігурація](#configuration)
    - [Налаштування даних для пошуку](#configuring-searchable-data)
- [Рушії database і collection](#database-and-collection-engines)
    - [Рушій database](#database-engine)
    - [Рушій collection](#collection-engine)
- [Конфігурація сторонніх рушіїв](#third-party-engine-configuration)
    - [Налаштування індексів моделей](#configuring-model-indexes)
    - [Algolia](#algolia-configuration)
    - [Meilisearch](#meilisearch-configuration)
    - [Typesense](#typesense-configuration)
    - [Turbopuffer](#turbopuffer-configuration)
- [Індексування у сторонніх рушіях](#indexing)
    - [Пакетний імпорт](#batch-import)
    - [Додавання записів](#adding-records)
    - [Оновлення записів](#updating-records)
    - [Видалення записів](#removing-records)
    - [Призупинення індексування](#pausing-indexing)
    - [Умовна індексованість екземплярів моделей](#conditionally-searchable-model-instances)
- [Пошук](#searching)
    - [Умови where](#where-clauses)
    - [Семантичний пошук](#semantic-search)
    - [Пагінація](#pagination)
    - [М'яке видалення](#soft-deleting)
    - [Налаштування пошуку в рушіях](#customizing-engine-searches)
- [Власні рушії](#custom-engines)

<a name="introduction"></a>
## Вступ

[Laravel Scout](https://github.com/laravel/scout) надає просте рішення на драйверах, щоб додати повнотекстовий пошук до ваших [моделей Eloquent](/docs/{{version}}/eloquent). Через спостерігачів моделей Scout автоматично тримає ваші пошукові індекси синхронізованими із записами Eloquent.

Scout постачається із вбудованим рушієм `database`, який використовує повнотекстові індекси MySQL / PostgreSQL та умови `LIKE`, щоб шукати у вашій наявній базі даних - жодного зовнішнього сервісу не потрібно. Для більшості застосунків цього цілком достатньо. Огляд усіх можливостей пошуку в Laravel дивіться в [документації про пошук](/docs/{{version}}/search).

Scout також містить драйвери для [Algolia](https://www.algolia.com/), [Meilisearch](https://www.meilisearch.com), [Typesense](https://typesense.org) і [Turbopuffer](https://turbopuffer.com) - на випадок, коли вам потрібні стійкість до одруківок, фасетна фільтрація, векторний пошук чи геопошук у великих масштабах. Для локальної розробки доступний також драйвер «collection», а ще ви вільні писати [власні рушії](#custom-engines).

<a name="installation"></a>
## Встановлення

Спершу встановіть Scout через менеджер пакетів Composer:

```shell
composer require laravel/scout
```

Після встановлення Scout опублікуйте його конфігураційний файл артизан-командою `vendor:publish`. Ця команда опублікує файл `scout.php` до каталогу `config` вашого застосунку:

```shell
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
```

Нарешті, додайте трейт `Laravel\Scout\Searchable` до моделі, яку ви хочете зробити придатною для пошуку. Цей трейт зареєструє спостерігача моделі, який автоматично триматиме модель синхронізованою з вашим пошуковим драйвером:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Post extends Model
{
    use Searchable;
}
```

<a name="queueing"></a>
### Черги

Якщо ви користуєтеся рушієм, відмінним від `database` чи `collection`, вам варто серйозно подумати про налаштування [драйвера черги](/docs/{{version}}/queues), перш ніж працювати з бібліотекою. Запущений воркер черги дозволить Scout ставити в чергу всі операції синхронізації даних моделі з пошуковими індексами, що суттєво покращить час відгуку вебінтерфейсу вашого застосунку.

Коли ви налаштували драйвер черги, встановіть опцію `queue` у вашому конфігураційному файлі `config/scout.php` у значення `true`:

```php
'queue' => true,
```

Навіть коли опція `queue` має значення `false`, важливо пам'ятати, що деякі драйвери Scout - як-от Algolia та Meilisearch - завжди індексують записи асинхронно. Іншими словами, навіть якщо операція індексування завершилася у вашому застосунку Laravel, сам пошуковий рушій може не одразу відобразити нові й оновлені записи.

Щоб указати підключення та чергу, які використовують завдання Scout, задайте опцію конфігурації `queue` масивом:

```php
'queue' => [
    'connection' => 'redis',
    'queue' => 'scout'
],
```

Звісно, якщо ви змінюєте підключення та чергу для завдань Scout, вам слід запустити воркер черги, який оброблятиме завдання на цьому підключенні й у цій черзі:

```shell
php artisan queue:work redis --queue=scout
```

<a name="unique-jobs"></a>
#### Унікальні завдання

У застосунках з інтенсивним записом ви можете захотіти завадити Scout ставити в чергу дублікати завдань для тих самих записів моделі. Ви можете ввімкнути унікальні завдання індексування, зареєструвавши класи завдань `MakeSearchableUniquely` та `RemoveFromSearchUniquely` - зазвичай у методі `boot` сервіс-провайдера:

```php
use Laravel\Scout\Jobs\MakeSearchableUniquely;
use Laravel\Scout\Jobs\RemoveFromSearchUniquely;
use Laravel\Scout\Scout;

Scout::makeSearchableUsing(MakeSearchableUniquely::class);
Scout::removeFromSearchUsing(RemoveFromSearchUniquely::class);
```

Ці завдання використовують [блокування унікальних завдань](/docs/{{version}}/queues#unique-jobs) у Laravel, щоб не відправляти дублікати операцій індексування для тих самих записів моделі, доки відповідне завдання вже стоїть у черзі.

<a name="driver-prerequisites"></a>
## Передумови для драйверів

<a name="algolia"></a>
### Algolia

Користуючись драйвером Algolia, налаштуйте свої облікові дані Algolia `id` та `secret` у конфігураційному файлі `config/scout.php`. Коли облікові дані налаштовано, вам також треба встановити PHP SDK для Algolia через менеджер пакетів Composer:

```shell
composer require algolia/algoliasearch-client-php
```

<a name="meilisearch"></a>
### Meilisearch

[Meilisearch](https://www.meilisearch.com) - це швидкий пошуковий рушій з відкритим кодом, який підтримує повнотекстовий, семантичний і гібридний пошук. Якщо ви не знаєте, як встановити Meilisearch на своїй машині, скористайтеся [Laravel Sail](/docs/{{version}}/sail#meilisearch) - офіційно підтримуваним середовищем розробки Laravel на Docker. Для продакшена ви можете [розгорнути Meilisearch самостійно](https://www.meilisearch.com/docs/learn/self_hosted/getting_started_with_self_hosted_meilisearch) або скористатися [Meilisearch Cloud](https://www.meilisearch.com/cloud).

Користуючись драйвером Meilisearch, вам треба встановити PHP SDK для Meilisearch через менеджер пакетів Composer:

```shell
composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle
```

Далі задайте змінну оточення `SCOUT_DRIVER`, а також облікові дані Meilisearch `host` і `key` у файлі `.env` вашого застосунку:

```ini
SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_KEY=masterKey
```

Докладніше про Meilisearch читайте в [документації Meilisearch](https://www.meilisearch.com/docs/getting_started/overview).

<a name="typesense"></a>
### Typesense

[Typesense](https://typesense.org) - блискавично швидкий пошуковий рушій з відкритим кодом, який підтримує пошук за ключовими словами, семантичний пошук, геопошук і векторний пошук.

Ви можете [хостити Typesense самостійно](https://typesense.org/docs/guide/install-typesense.html#option-2-local-machine-self-hosting) або скористатися [Typesense Cloud](https://cloud.typesense.org).

Щоб почати роботу з Typesense у Scout, встановіть PHP SDK для Typesense через менеджер пакетів Composer:

```shell
composer require typesense/typesense-php
```

Далі задайте змінну оточення `SCOUT_DRIVER`, а також хост і ключ API Typesense у файлі .env вашого застосунку:

```ini
SCOUT_DRIVER=typesense
TYPESENSE_API_KEY=masterKey
TYPESENSE_HOST=localhost
```

Якщо ви користуєтеся [Laravel Sail](/docs/{{version}}/sail), вам, можливо, доведеться підлаштувати змінну оточення `TYPESENSE_HOST` під ім'я контейнера Docker. Ви також можете за бажання вказати порт, шлях і протокол вашої установки:

```ini
TYPESENSE_PORT=8108
TYPESENSE_PATH=
TYPESENSE_PROTOCOL=http
```

Додаткові налаштування та визначення схем для ваших колекцій Typesense можна знайти в конфігураційному файлі `config/scout.php` вашого застосунку. Докладніше про Typesense читайте в [документації Typesense](https://typesense.org/docs/guide/#quick-start).

<a name="turbopuffer"></a>
### Turbopuffer

[Turbopuffer](https://turbopuffer.com) - це пошуковий рушій, який підтримує повнотекстовий, семантичний і гібридний пошук. Щоб скористатися драйвером Turbopuffer, установіть змінну оточення `SCOUT_DRIVER` і вкажіть свій ключ API Turbopuffer:

```ini
SCOUT_DRIVER=turbopuffer
TURBOPUFFER_API_KEY=tpuf_...
TURBOPUFFER_REGION=gcp-us-central1
```

Змінна оточення `TURBOPUFFER_REGION` необов'язкова й типово має значення `gcp-us-central1`.

<a name="configuration"></a>
## Конфігурація

<a name="configuring-searchable-data"></a>
### Налаштування даних для пошуку

За замовчуванням до пошукового індексу потрапляє вся форма `toArray` заданої моделі. Якщо ви хочете налаштувати дані, які синхронізуються з індексом, перевизначте на моделі метод `toSearchableArray`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Post extends Model
{
    use Searchable;

    /**
     * Get the indexable data array for the model.
     *
     * @return array<string, mixed>
     */
    public function toSearchableArray(): array
    {
        $array = $this->toArray();

        // Customize the data array...

        return $array;
    }
}
```

<a name="configuring-search-engines-per-model"></a>
#### Налаштування рушіїв для моделей

Під час пошуку Scout зазвичай використовує стандартний пошуковий рушій, указаний у конфігураційному файлі `scout` вашого застосунку. Проте рушій для конкретної моделі можна змінити, перевизначивши на ній метод `searchableUsing`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Engines\Engine;
use Laravel\Scout\Scout;
use Laravel\Scout\Searchable;

class User extends Model
{
    use Searchable;

    /**
     * Get the engine used to index the model.
     */
    public function searchableUsing(): Engine
    {
        return Scout::engine('meilisearch');
    }
}
```

<a name="database-and-collection-engines"></a>
## Рушії database і collection

<a name="database-engine"></a>
### Рушій database

> [!WARNING]
> Наразі рушій database підтримує MySQL і PostgreSQL - обидві бази надають швидке повнотекстове індексування стовпців.

Рушій `database` використовує повнотекстові індекси MySQL / PostgreSQL та умови `LIKE`, щоб шукати безпосередньо у вашій наявній базі даних. Для багатьох застосунків це найпростіший і найпрактичніший спосіб додати пошук - жодного зовнішнього сервісу чи додаткової інфраструктури не потрібно.

Щоб скористатися рушієм database, встановіть змінну оточення `SCOUT_DRIVER` у значення `database`:

```ini
SCOUT_DRIVER=database
```

Коли рушій налаштовано, ви можете [описати дані для пошуку](#configuring-searchable-data) і почати [виконувати пошукові запити](#searching) до своїх моделей. На відміну від сторонніх рушіїв, рушій database не потребує окремого етапу індексування - він шукає безпосередньо у таблицях вашої бази даних.

<a name="database-semantic-and-hybrid-search"></a>
#### Семантичний і гібридний пошук

Рушій database підтримує семантичний і гібридний пошук, коли ви користуєтеся PostgreSQL із розширенням `pgvector`. Для початку додайте до таблиці вашої моделі nullable-стовпець вектора та повнотекстовий індекс. Стовпець вектора має бути nullable, бо Scout зберігає ембединг уже після того, як модель збережено:

```php
Schema::ensureVectorExtensionExists();

Schema::table('articles', function (Blueprint $table) {
    // ...

    $table->vector('embedding', dimensions: 1536)->nullable();
    $table->vectorIndex('embedding');
    $table->fullText(['title', 'body']);
});
```

Далі опишіть на моделі метод `toSearchableEmbedding`. Цей метод може повертати вихідний текст, який Scout має перетворити на ембединг, або вже готовий масив ембединга. Типово Scout зберігає ембединги у стовпці `embedding`; щоб використати інший стовпець, опишіть на моделі метод `searchableEmbeddingColumn`.

#### Налаштування стратегій пошуку в базі даних

За замовчуванням рушій database виконає запит `LIKE` до кожного атрибута моделі, який ви [налаштували для пошуку](#configuring-searchable-data). Проте ви можете призначити конкретним стовпцям ефективніші стратегії пошуку. Атрибут `SearchUsingFullText` використовуватиме для цього стовпця повнотекстовий індекс вашої бази, а `SearchUsingPrefix` шукатиме лише збіг на початку рядків (`example%`), а не по всьому рядку (`%example%`).

Щоб задати цю поведінку, застосуйте PHP-атрибути до методу `toSearchableArray` вашої моделі. Стовпці без атрибута й далі використовуватимуть стандартну стратегію `LIKE`:

```php
use Laravel\Scout\Attributes\SearchUsingFullText;
use Laravel\Scout\Attributes\SearchUsingPrefix;

/**
 * Get the indexable data array for the model.
 *
 * @return array<string, mixed>
 */
#[SearchUsingPrefix(['id', 'email'])]
#[SearchUsingFullText(['bio'])]
public function toSearchableArray(): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'bio' => $this->bio,
    ];
}
```

> [!WARNING]
> Перш ніж указувати, що стовпець має використовувати повнотекстові обмеження запиту, переконайтеся, що йому призначено [повнотекстовий індекс](/docs/{{version}}/migrations#available-index-types).

<a name="collection-engine"></a>
### Рушій collection

Рушій «collection» призначений для швидких прототипів, украй малих наборів даних (кілька сотень записів) чи прогону тестів. Він дістає з вашої бази всі можливі записи й фільтрує їх у PHP хелпером `Str::is`, тож не потребує ані індексування, ані специфічних можливостей бази даних. Для будь-чого серйознішого за тривіальні випадки скористайтеся натомість [рушієм database](#database-engine).

Щоб скористатися рушієм collection, просто встановіть змінну оточення `SCOUT_DRIVER` у значення `collection` або вкажіть драйвер `collection` безпосередньо в конфігураційному файлі `scout` вашого застосунку:

```ini
SCOUT_DRIVER=collection
```

Коли ви обрали драйвер collection, можете починати [виконувати пошукові запити](#searching) до своїх моделей. Індексування в пошуковому рушії - як-от те, що потрібне для наповнення індексів Algolia, Meilisearch чи Typesense, - із рушієм collection не потрібне.

#### Відмінності від рушія database

Якщо рушій database ефективно знаходить відповідні записи через повнотекстові індекси та умови `LIKE`, то рушій collection дістає всі записи й фільтрує їх у PHP. Рушій collection - найпереносніший варіант, адже працює з усіма реляційними базами, які підтримує Laravel (зокрема SQLite та SQL Server); проте він значно менш ефективний за рушій database і не має використовуватися з великими наборами даних.

<a name="third-party-engine-configuration"></a>
## Конфігурація сторонніх рушіїв

Наведені далі опції конфігурації стосуються лише сторонніх пошукових рушіїв - як-от Algolia, Meilisearch чи Typesense. Якщо ви користуєтеся [рушієм database](#database-engine), цей розділ можна пропустити.

<a name="configuring-model-indexes"></a>
### Налаштування індексів моделей

Коли ви користуєтеся стороннім рушієм, кожна модель Eloquent синхронізується із заданим пошуковим «індексом», що містить усі придатні для пошуку записи цієї моделі. За замовчуванням кожна модель зберігатиметься в індексі, ім'я якого збігається зі звичним іменем «таблиці» моделі. Зазвичай це форма множини від імені моделі; проте ви вільні змінити індекс моделі, перевизначивши на ній метод `searchableAs`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Post extends Model
{
    use Searchable;

    /**
     * Get the name of the index associated with the model.
     */
    public function searchableAs(): string
    {
        return 'posts_index';
    }
}
```

> [!NOTE]
> Метод `searchableAs` не має жодного впливу під час використання рушія database, який завжди шукає безпосередньо в таблиці бази даних моделі.

<a name="configuring-the-model-id"></a>
#### Налаштування ID моделі

За замовчуванням Scout використовує первинний ключ моделі як її унікальний ID / ключ, що зберігається в пошуковому індексі. Якщо вам треба змінити цю поведінку зі стороннім рушієм, перевизначте на моделі методи `getScoutKey` та `getScoutKeyName`:

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class User extends Model
{
    use Searchable;

    /**
     * Get the value used to index the model.
     */
    public function getScoutKey(): mixed
    {
        return $this->email;
    }

    /**
     * Get the key name used to index the model.
     */
    public function getScoutKeyName(): mixed
    {
        return 'email';
    }
}
```

> [!NOTE]
> Методи `getScoutKey` та `getScoutKeyName` не мають жодного впливу під час використання рушія database, який завжди використовує первинний ключ моделі.

<a name="algolia-configuration"></a>
### Algolia

<a name="algolia-index-settings"></a>
#### Налаштування індексів

Іноді вам може знадобитися задати додаткові налаштування для ваших індексів Algolia. Хоча керувати ними можна через інтерфейс Algolia, іноді ефективніше описати бажаний стан конфігурації індексу просто в конфігураційному файлі `config/scout.php` вашого застосунку.

Такий підхід дозволяє розгортати ці налаштування через автоматизований пайплайн розгортання вашого застосунку, уникаючи ручного налаштування й забезпечуючи узгодженість між середовищами. Ви можете налаштувати атрибути для фільтрації, ранжування, фасети чи [будь-які інші підтримувані налаштування](https://www.algolia.com/doc/rest-api/search/#tag/Indices/operation/setSettings).

Для початку додайте налаштування для кожного індексу в конфігураційному файлі `config/scout.php` вашого застосунку:

```php
use App\Models\User;
use App\Models\Flight;

'algolia' => [
    'id' => env('ALGOLIA_APP_ID', ''),
    'secret' => env('ALGOLIA_SECRET', ''),
    'index-settings' => [
        User::class => [
            'searchableAttributes' => ['id', 'name', 'email'],
            'attributesForFaceting'=> ['filterOnly(email)'],
            // Other settings fields...
        ],
        Flight::class => [
            'searchableAttributes'=> ['id', 'destination'],
        ],
    ],
],
```

Якщо модель, що лежить в основі заданого індексу, підтримує м'яке видалення (soft delete) і включена до масиву `index-settings`, Scout автоматично додасть до цього індексу підтримку фасетів для м'яко видалених моделей. Якщо для індексу такої моделі вам більше нічого описувати у фасетах, просто додайте до масиву `index-settings` порожній запис для цієї моделі:

```php
'index-settings' => [
    Flight::class => []
],
```

Налаштувавши індекси вашого застосунку, ви маєте викликати артизан-команду `scout:sync-index-settings`. Ця команда повідомить Algolia про поточні налаштування ваших індексів. Для зручності вам, можливо, варто зробити цю команду частиною процесу розгортання:

```shell
php artisan scout:sync-index-settings
```

<a name="algolia-identifying-users"></a>
#### Ідентифікація користувачів

Scout дозволяє автоматично ідентифікувати користувачів під час роботи з Algolia. Прив'язка автентифікованого користувача до пошукових операцій може стати в пригоді при перегляді пошукової аналітики в панелі Algolia. Увімкнути ідентифікацію користувачів можна, задавши змінну оточення `SCOUT_IDENTIFY` у значення `true` у файлі `.env` вашого застосунку:

```ini
SCOUT_IDENTIFY=true
```

Увімкнення цієї можливості також передаватиме до Algolia IP-адресу запиту та первинний ідентифікатор автентифікованого користувача, тож ці дані буде пов'язано з кожним пошуковим запитом користувача.

<a name="meilisearch-configuration"></a>
### Meilisearch

<a name="meilisearch-index-settings"></a>
#### Налаштування індексів

Meilisearch вимагає наперед описати налаштування пошуку для індексу - як-от атрибути для фільтрації, атрибути для сортування та [інші підтримувані поля налаштувань](https://docs.meilisearch.com/reference/api/settings.html).

Атрибути для фільтрації - це будь-які атрибути, за якими ви плануєте фільтрувати через метод `where` у Scout, а атрибути для сортування - ті, за якими ви плануєте сортувати через метод `orderBy`. Щоб описати налаштування індексів, підлаштуйте частину `index-settings` запису `meilisearch` у конфігураційному файлі `scout` вашого застосунку:

```php
use App\Models\User;
use App\Models\Flight;

'meilisearch' => [
    'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
    'key' => env('MEILISEARCH_KEY', null),
    'index-settings' => [
        User::class => [
            'filterableAttributes'=> ['id', 'name', 'email'],
            'sortableAttributes' => ['created_at'],
            // Other settings fields...
        ],
        Flight::class => [
            'filterableAttributes'=> ['id', 'destination'],
            'sortableAttributes' => ['updated_at'],
        ],
    ],
],
```

Якщо модель, що лежить в основі заданого індексу, підтримує м'яке видалення і включена до масиву `index-settings`, Scout автоматично додасть до цього індексу підтримку фільтрації м'яко видалених моделей. Якщо для індексу такої моделі вам більше нічого описувати в атрибутах фільтрації чи сортування, просто додайте до масиву `index-settings` порожній запис для цієї моделі:

```php
'index-settings' => [
    Flight::class => []
],
```

Налаштувавши індекси вашого застосунку, ви маєте викликати артизан-команду `scout:sync-index-settings`. Ця команда повідомить Meilisearch про поточні налаштування ваших індексів. Для зручності вам, можливо, варто зробити цю команду частиною процесу розгортання:

```shell
php artisan scout:sync-index-settings
```

<a name="meilisearch-semantic-and-hybrid-search"></a>
#### Семантичний і гібридний пошук

Щоб користуватися семантичним чи гібридним пошуком у Meilisearch, налаштуйте ембедер у параметрах індексу та параметри ембедингів для кожної моделі, доступної для пошуку:

```php
'meilisearch' => [
    // ...
    'index-settings' => [
        Article::class => [
            'embedders' => [
                'default' => [
                    'source' => 'userProvided',
                    'dimensions' => 1536,
                ],
            ],
        ],
    ],
    'model-settings' => [
        Article::class => [
            'embedding' => [
                'embedder' => 'default',
                'dimensions' => 1536,
            ],
        ],
    ],
],
```

Метод моделі `toSearchableEmbedding` може повертати вихідний текст, який Scout перетворює на ембединг за допомогою [Laravel AI SDK](/docs/{{version}}/ai-sdk), або вже готовий масив ембединга. Оновивши конфігурацію, виконайте команду `scout:sync-index-settings`.

Альтернативно, можна використовувати нативні ембединги Meilisearch, встановивши `driver` ембедингу на `meilisearch`. У цьому режимі Meilisearch генерує ембединги документів і запитів за допомогою налаштованого ембедера, тому параметр `dimensions` і метод `toSearchableEmbedding` не потрібні:

```php
'meilisearch' => [
    'index-settings' => [
        Article::class => [
            'embedders' => [
                'default' => [
                    'source' => 'openAi',
                    'apiKey' => env('OPENAI_API_KEY'),
                    'model' => 'text-embedding-3-small',
                    'documentTemplate' => 'An article titled {{ doc.title }}: {{ doc.body }}',
                ],
            ],
        ],
    ],
    'model-settings' => [
        Article::class => [
            'embedding' => [
                'embedder' => 'default',
                'driver' => 'meilisearch',
            ],
        ],
    ],
],
```

При використанні нативних ембедингів Scout не генерує й не додає вектори до індексованих документів. Ви все одно можете надати заздалегідь обчислений вектор запиту за допомогою параметра пошуку `vector`. Як і з іншими параметрами індексу, виконайте команду `scout:sync-index-settings` після оновлення конфігурації ембедера.

Детальнішу інформацію про доступні джерела ембедерів та їхні параметри дивіться в [документації Meilisearch з гібридного пошуку](https://www.meilisearch.com/docs/capabilities/hybrid_search/getting_started).

<a name="meilisearch-data-types"></a>
#### Типи даних для пошуку

Meilisearch виконуватиме операції фільтрації (`>`, `<` тощо) лише над даними правильного типу. Налаштовуючи дані для пошуку, переконайтеся, що числові значення приведено до відповідного типу:

```php
public function toSearchableArray()
{
    return [
        'id' => (int) $this->id,
        'name' => $this->name,
        'price' => (float) $this->price,
    ];
}
```

<a name="typesense-configuration"></a>
### Typesense

<a name="typesense-searchable-data"></a>
#### Підготовка даних для пошуку

Користуючись Typesense, ваші придатні до пошуку моделі мають визначати метод `toSearchableArray`, який приводить первинний ключ моделі до рядка, а дату створення - до UNIX-мітки часу:

```php
/**
 * Get the indexable data array for the model.
 *
 * @return array<string, mixed>
 */
public function toSearchableArray(): array
{
    return array_merge($this->toArray(),[
        'id' => (string) $this->id,
        'created_at' => $this->created_at->timestamp,
    ]);
}
```

Вам також слід описати схеми колекцій Typesense у файлі `config/scout.php` вашого застосунку. Схема колекції описує типи даних кожного поля, доступного для пошуку через Typesense. Докладніше про всі доступні опції схеми читайте в [документації Typesense](https://typesense.org/docs/latest/api/collections.html#schema-parameters).

Якщо вам треба змінити схему колекції Typesense після того, як її описано, ви можете або виконати `scout:flush` і `scout:import`, що видалить усі проіндексовані дані й перестворить схему, або скористатися API Typesense, щоб змінити схему колекції, не видаляючи проіндексованих даних.

Якщо ваша придатна до пошуку модель підтримує м'яке видалення, опишіть поле `__soft_deleted` у відповідній схемі Typesense у конфігураційному файлі `config/scout.php` вашого застосунку:

```php
User::class => [
    'collection-schema' => [
        'fields' => [
            // ...
            [
                'name' => '__soft_deleted',
                'type' => 'int32',
                'optional' => true,
            ],
        ],
    ],
],
```

<a name="typesense-embeddings"></a>
#### Ембединги

Щоб увімкнути семантичний і гібридний пошук, визначте налаштування `embedding` і векторне поле в конфігурації Typesense моделі. За замовчуванням Scout використовує [Laravel AI SDK](/docs/{{version}}/ai-sdk) для генерації ембедингів:

```php
use App\Models\Article;

'model-settings' => [
    Article::class => [
        'collection-schema' => [
            'fields' => [
                ['name' => 'title', 'type' => 'string'],
                ['name' => 'embedding', 'type' => 'float[]', 'num_dim' => 1536],
            ],
        ],
        'search-parameters' => ['query_by' => 'title'],
        'embedding' => [
            'attribute' => 'embedding',
            'dimensions' => 1536,
        ],
    ],
],
```

Метод `toSearchableEmbedding` вашої моделі повинен повертати вихідний текст, який Scout має перетворити на ембединг, або попередньо обчислений масив ембедингу:

```php
public function toSearchableEmbedding(): string|array
{
    return $this->title.' '.$this->body;
}
```

<a name="typesense-dynamic-search-parameters"></a>
#### Динамічні параметри пошуку

Typesense дозволяє змінювати ваші [параметри пошуку](https://typesense.org/docs/latest/api/search.html#search-parameters) динамічно під час виконання пошуку - через метод `options`:

```php
use App\Models\Todo;

Todo::search('Groceries')->options([
    'query_by' => 'title, description'
])->get();
```

<a name="turbopuffer-configuration"></a>
### Turbopuffer

Turbopuffer потребує схеми та атрибутів для пошуку для кожної моделі. Опишіть їх у масиві `model-settings` вашої конфігурації `turbopuffer` у конфігураційному файлі `scout`:

```php
use App\Models\Article;

'turbopuffer' => [
    // ...
    'model-settings' => [
        Article::class => [
            'searchable-attributes' => [
                'title' => 3,
                'body' => 1,
            ],
            'schema' => [
                'title' => ['type' => 'string', 'full_text_search' => true],
                'body' => ['type' => 'string', 'full_text_search' => true],
                'status' => ['type' => 'string'],
            ],
        ],
    ],
],
```

Числові значення, призначені `searchable-attributes`, - це відносні ваги BM25. У прикладі вище збіги в заголовку статті додають до оцінки втричі більше, ніж збіги в тілі.

Щоб увімкнути семантичний і гібридний пошук, додайте до конфігурації моделі параметр `embedding` і схему вектора:

```php
'turbopuffer' => [
    // ...
    'model-settings' => [
        Article::class => [
            'searchable-attributes' => [
                'title' => 3,
                'body' => 1,
            ],
            'embedding' => [
                'attribute' => 'embedding',
                'dimensions' => 1536,
            ],
            'schema' => [
                'title' => ['type' => 'string', 'full_text_search' => true],
                'body' => ['type' => 'string', 'full_text_search' => true],
                'embedding' => ['type' => '[1536]f32', 'ann' => true],
            ],
        ],
    ],
],
```

Метод вашої моделі `toSearchableEmbedding` має повертати вихідний текст, який Scout перетворить на ембединг, або вже готовий масив ембединга. Ембединги з вихідного тексту Scout генерує за допомогою [Laravel AI SDK](/docs/{{version}}/ai-sdk).

Як альтернативу ви можете скористатися нативними ембедингами Turbopuffer, не встановлюючи Laravel AI SDK і не описуючи метод `toSearchableEmbedding`. Установіть драйвер ембедингів у значення `turbopuffer` і налаштуйте схему `embed` на вихідному атрибуті:

```php
'embedding' => [
    'driver' => 'turbopuffer',
    'attribute' => 'embedding_text',
],

'schema' => [
    // ...
    'embedding_text' => [
        'type' => 'string',
        'embed' => [
            'model' => 'voyage/voyage-4',
            'dimensions' => 1024,
            'attribute' => 'embedding',
        ],
    ],
],
```

Вихідний атрибут має бути серед того, що повертає метод моделі `toSearchableArray`.

<a name="indexing"></a>
## Індексування у сторонніх рушіях

> [!NOTE]
> Описані в цьому розділі можливості індексування стосуються передусім сторонніх рушіїв (Algolia, Meilisearch, Typesense чи Turbopuffer). Рушій database шукає безпосередньо у ваших таблицях, тож не потребує ручного керування індексами.

<a name="batch-import"></a>
### Пакетний імпорт

Якщо ви встановлюєте Scout у наявний проєкт, у вас, можливо, вже є записи в базі даних, які треба імпортувати до індексів. Scout надає артизан-команду `scout:import`, якою можна імпортувати всі наявні записи до ваших пошукових індексів:

```shell
php artisan scout:import "App\Models\Post"
```

Команда `scout:queue-import` дозволяє імпортувати всі ваші наявні записи через [завдання в черзі](/docs/{{version}}/queues):

```shell
php artisan scout:queue-import "App\Models\Post" --chunk=500
```

Команда `flush` дозволяє прибрати всі записи моделі з ваших пошукових індексів:

```shell
php artisan scout:flush "App\Models\Post"
```

<a name="modifying-the-import-query"></a>
#### Зміна запиту для імпорту

Якщо ви хочете змінити запит, яким дістаються всі ваші моделі для пакетного імпорту, визначте на моделі метод `makeAllSearchableUsing`. Це чудове місце, щоб додати будь-яке жадібне завантаження зв'язків, потрібне перед імпортом ваших моделей:

```php
use Illuminate\Database\Eloquent\Builder;

/**
 * Modify the query used to retrieve models when making all of the models searchable.
 */
protected function makeAllSearchableUsing(Builder $query): Builder
{
    return $query->with('author');
}
```

> [!WARNING]
> Метод `makeAllSearchableUsing` може бути незастосовним, коли пакетний імпорт моделей іде через чергу. Зв'язки [не відновлюються](/docs/{{version}}/queues#handling-relationships), коли колекції моделей обробляються завданнями.

<a name="adding-records"></a>
### Додавання записів

Коли ви додали трейт `Laravel\Scout\Searchable` до моделі, вам залишається лише зберегти (`save`) чи створити (`create`) екземпляр моделі - і його буде автоматично додано до вашого пошукового індексу. Якщо ви налаштували Scout [на використання черг](#queueing), цю операцію виконає у фоні ваш воркер черги:

```php
use App\Models\Order;

$order = new Order;

// ...

$order->save();
```

<a name="adding-records-via-query"></a>
#### Додавання записів через запит

Якщо ви хочете додати до пошукового індексу колекцію моделей через запит Eloquent, додайте до нього ланцюжком метод `searchable`. Метод `searchable` [розіб'є результати](/docs/{{version}}/eloquent#chunking-results) запиту на порції й додасть записи до вашого пошукового індексу. І знову ж: якщо ви налаштували Scout на використання черг, усі порції буде імпортовано у фоні вашими воркерами:

```php
use App\Models\Order;

Order::where('price', '>', 100)->searchable();
```

Ви також можете викликати метод `searchable` на екземплярі зв'язку Eloquent:

```php
$user->orders()->searchable();
```

Або ж, якщо у вас уже є колекція моделей Eloquent у пам'яті, викличте метод `searchable` на екземплярі колекції, щоб додати екземпляри моделей до відповідного індексу:

```php
$orders->searchable();
```

> [!NOTE]
> Метод `searchable` можна вважати операцією «upsert». Іншими словами, якщо запис моделі вже є у вашому індексі, його буде оновлено. Якщо його в індексі немає, його буде додано.

<a name="updating-records"></a>
### Оновлення записів

Щоб оновити придатну до пошуку модель, вам треба лише змінити властивості її екземпляра й зберегти (`save`) модель до бази даних. Scout автоматично збереже зміни до вашого пошукового індексу:

```php
use App\Models\Order;

$order = Order::find(1);

// Update the order...

$order->save();
```

Ви також можете викликати метод `searchable` на екземплярі запиту Eloquent, щоб оновити колекцію моделей. Якщо моделей у вашому пошуковому індексі немає, їх буде створено:

```php
Order::where('price', '>', 100)->searchable();
```

Якщо ви хочете оновити записи пошукового індексу для всіх моделей у зв'язку, викличте `searchable` на екземплярі зв'язку:

```php
$user->orders()->searchable();
```

Або ж, якщо у вас уже є колекція моделей Eloquent у пам'яті, викличте метод `searchable` на екземплярі колекції, щоб оновити екземпляри моделей у відповідному індексі:

```php
$orders->searchable();
```

<a name="modifying-records-before-importing"></a>
#### Зміна записів перед імпортом

Іноді вам може знадобитися підготувати колекцію моделей, перш ніж робити їх придатними до пошуку. Наприклад, ви можете захотіти жадібно завантажити зв'язок, щоб його дані ефективно потрапили до пошукового індексу. Для цього визначте у відповідній моделі метод `makeSearchableUsing`:

```php
use Illuminate\Database\Eloquent\Collection;

/**
 * Modify the collection of models being made searchable.
 */
public function makeSearchableUsing(Collection $models): Collection
{
    return $models->load('author');
}
```

<a name="conditionally-updating-the-search-index"></a>
#### Умовне оновлення пошукового індексу

За замовчуванням Scout переіндексує оновлену модель незалежно від того, які атрибути було змінено. Якщо ви хочете змінити цю поведінку, визначте на моделі метод `searchIndexShouldBeUpdated`:

```php
/**
 * Determine if the search index should be updated.
 */
public function searchIndexShouldBeUpdated(): bool
{
    return $this->wasRecentlyCreated || $this->wasChanged(['title', 'body']);
}
```

<a name="removing-records"></a>
### Видалення записів

Щоб прибрати запис із вашого індексу, просто видаліть (`delete`) модель із бази даних. Це працює навіть із моделями, що використовують [м'яке видалення](/docs/{{version}}/eloquent#soft-deleting):

```php
use App\Models\Order;

$order = Order::find(1);

$order->delete();
```

Якщо ви не хочете діставати модель перед видаленням запису, скористайтеся методом `unsearchable` на екземплярі запиту Eloquent:

```php
Order::where('price', '>', 100)->unsearchable();
```

Якщо ви хочете прибрати записи пошукового індексу для всіх моделей у зв'язку, викличте `unsearchable` на екземплярі зв'язку:

```php
$user->orders()->unsearchable();
```

Або ж, якщо у вас уже є колекція моделей Eloquent у пам'яті, викличте метод `unsearchable` на екземплярі колекції, щоб прибрати екземпляри моделей із відповідного індексу:

```php
$orders->unsearchable();
```

Щоб прибрати з відповідного індексу всі записи моделі, викличте метод `removeAllFromSearch`:

```php
Order::removeAllFromSearch();
```

<a name="pausing-indexing"></a>
### Призупинення індексування

Іноді вам може знадобитися виконати пакет операцій Eloquent над моделлю, не синхронізуючи її дані з пошуковим індексом. Зробити це можна методом `withoutSyncingToSearch`. Цей метод приймає єдине замикання, яке буде виконано одразу. Жодна операція з моделлю всередині замикання не потрапить до індексу моделі:

```php
use App\Models\Order;

Order::withoutSyncingToSearch(function () {
    // Perform model actions...
});
```

<a name="conditionally-searchable-model-instances"></a>
### Умовна індексованість екземплярів моделей

Іноді вам може знадобитися робити модель придатною до пошуку лише за певних умов. Уявіть, наприклад, що у вас є модель `App\Models\Post`, яка може бути в одному з двох станів: «draft» і «published». Ви можете захотіти, щоб до пошуку потрапляли лише «published»-дописи. Для цього визначте на моделі метод `shouldBeSearchable`:

```php
/**
 * Determine if the model should be searchable.
 */
public function shouldBeSearchable(): bool
{
    return $this->isPublished();
}
```

Метод `shouldBeSearchable` застосовується лише тоді, коли ви працюєте з моделями через методи `save` і `create`, запити чи зв'язки. Пряме додавання моделей чи колекцій до пошуку методом `searchable` перевизначить результат методу `shouldBeSearchable`.

> [!WARNING]
> Метод `shouldBeSearchable` незастосовний із рушієм Scout «database», адже всі дані для пошуку завжди зберігаються в базі. Щоб досягти схожої поведінки з рушієм database, скористайтеся натомість [умовами where](#where-clauses).

<a name="searching"></a>
## Пошук

Почати пошук по моделі можна методом `search`. Метод search приймає єдиний рядок, за яким шукатимуться ваші моделі. Далі додайте до пошукового запиту ланцюжком метод `get`, щоб отримати моделі Eloquent, які відповідають заданому запиту:

```php
use App\Models\Order;

$orders = Order::search('Star Trek')->get();
```

Оскільки пошук у Scout повертає колекцію моделей Eloquent, ви можете навіть повертати результати просто з маршруту чи контролера - їх буде автоматично перетворено на JSON:

```php
use App\Models\Order;
use Illuminate\Http\Request;

Route::get('/search', function (Request $request) {
    return Order::search($request->search)->get();
});
```

Якщо ви хочете отримати сирі результати пошуку до їх перетворення на моделі Eloquent, скористайтеся методом `raw`:

```php
$orders = Order::search('Star Trek')->raw();
```

<a name="semantic-search"></a>
### Семантичний пошук

Рушії database, Meilisearch, Typesense і Turbopuffer підтримують семантичний пошук, який добирає записи за змістом запиту. Коли ембединги генерує Scout, семантичний і гібридний пошук потребують [Laravel AI SDK](/docs/{{version}}/ai-sdk). [Нативні ембединги Typesense](#typesense-embeddings), [нативні ембединги Turbopuffer](#turbopuffer-configuration) і заздалегідь обчислені вектори запиту Laravel AI SDK не потребують.

Налаштувавши ембединги для обраного рушія, викличте на пошуковому запиті метод `semantic`:

```php
$articles = Article::search('staying cool in the summer')
    ->semantic()
    ->get();
```

Ви можете задати мінімальний поріг схожості, якщо обраний рушій це підтримує:

```php
$articles = Article::search('renewable energy storage')
    ->semantic(minSimilarity: 0.6)
    ->get();
```

Щоб поєднати повнотекстовий і семантичний пошук, скористайтеся методом `hybrid`. Два його перші аргументи керують відносними вагами текстових і семантичних результатів:

```php
$articles = Article::search('renewable energy storage')
    ->hybrid(textWeight: 1, semanticWeight: 2)
    ->get();
```

<a name="custom-indexes"></a>
#### Власні індекси

Шукаючи через сторонні рушії, пошукові запити зазвичай виконуються в індексі, вказаному методом моделі [searchableAs](#configuring-model-indexes). Проте ви можете скористатися методом `within`, щоб указати інший індекс для пошуку:

```php
$orders = Order::search('Star Trek')
    ->within('tv_shows_popularity_desc')
    ->get();
```

<a name="where-clauses"></a>
### Умови where

Scout дозволяє додавати до пошукових запитів умови «where». Наприклад, базові перевірки на рівність стають у пригоді, щоб обмежити пошук за ID власника:

```php
use App\Models\Order;

$orders = Order::search('Star Trek')->where('user_id', 1)->get();
```

Ви також можете скористатися операторами порівняння `=`, `!=`, `<`, `>`, `>=`, `<=`, щоб будувати складніші запити:

```php
Order::search('Star Trek')
  ->where('status', '=', 'completed')
  ->where('is_refunded', '!=', true)
  ->where('total_price', '>', 100)
  ->where('shipping_cost', '<', 20)
  ->where('discount_percent', '>=', 10)
  ->where('item_count', '<=', 5)
  ->get();
```

Крім того, метод `whereIn` дозволяє перевірити, що значення заданого стовпця міститься в заданому масиві:

```php
$orders = Order::search('Star Trek')->whereIn(
    'status', ['open', 'paid']
)->get();
```

Метод `whereNotIn` перевіряє, що значення заданого стовпця не міститься в заданому масиві:

```php
$orders = Order::search('Star Trek')->whereNotIn(
    'status', ['closed']
)->get();
```

> [!WARNING]
> Якщо ваш застосунок використовує Meilisearch, вам треба налаштувати [атрибути для фільтрації](#meilisearch-index-settings), перш ніж користуватися умовами «where» у Scout.

<a name="customizing-the-eloquent-results-query"></a>
#### Налаштування запиту результатів Eloquent

Коли Scout отримає від пошукового рушія вашого застосунку список відповідних моделей Eloquent, Eloquent дістає всі ці моделі за їхніми первинними ключами. Ви можете налаштувати цей запит методом `query`. Метод `query` приймає замикання, яке отримає аргументом екземпляр конструктора запитів Eloquent:

```php
use App\Models\Order;
use Illuminate\Database\Eloquent\Builder;

$orders = Order::search('Star Trek')
    ->query(fn (Builder $query) => $query->with('invoices'))
    ->get();
```

Зі стороннім рушієм цей колбек викликається вже після того, як потрібні моделі отримано з пошукового рушія, тож його не варто використовувати для «фільтрації» результатів - скористайтеся натомість [умовами where у Scout](#where-clauses). Проте з рушієм database обмеження методу `query` застосовуються безпосередньо до запиту в базу, тож ви можете використовувати його й для фільтрації.

<a name="pagination"></a>
### Пагінація

Окрім отримання колекції моделей, ви можете розбити результати пошуку на сторінки методом `paginate`. Цей метод поверне екземпляр `Illuminate\Pagination\LengthAwarePaginator` - так само, як і при [пагінації звичайного запиту Eloquent](/docs/{{version}}/pagination):

```php
use App\Models\Order;

$orders = Order::search('Star Trek')->paginate();
```

Ви можете вказати, скільки моделей отримувати на сторінку, передавши кількість першим аргументом до методу `paginate`:

```php
$orders = Order::search('Star Trek')->paginate(15);
```

З рушієм database ви можете також скористатися методом `simplePaginate`. На відміну від `paginate`, який дістає загальну кількість відповідних записів, щоб показати номери сторінок, `simplePaginate` лише визначає, чи є результати за межами поточної сторінки, - що ефективніше для великих наборів даних, де вам потрібні лише посилання «попередня» й «наступна»:

```php
$orders = Order::search('Star Trek')->simplePaginate(15);
```

Отримавши результати, ви можете показати їх і відрендерити посилання на сторінки через [Blade](/docs/{{version}}/blade) - так само, як при пагінації звичайного запиту Eloquent:

```html
<div class="container">
    @foreach ($orders as $order)
        {{ $order->price }}
    @endforeach
</div>

{{ $orders->links() }}
```

Звісно, якщо ви хочете отримати результати пагінації як JSON, поверніть екземпляр пагінатора просто з маршруту чи контролера:

```php
use App\Models\Order;
use Illuminate\Http\Request;

Route::get('/orders', function (Request $request) {
    return Order::search($request->input('query'))->paginate(15);
});
```

> [!WARNING]
> Оскільки пошукові рушії не знають про визначення глобальних скопів вашої моделі Eloquent, вам не слід використовувати глобальні скопи в застосунках, що покладаються на пагінацію Scout. Або ж вам треба відтворити обмеження глобального скопа під час пошуку через Scout.

<a name="soft-deleting"></a>
### М'яке видалення

Якщо ваші проіндексовані моделі використовують [м'яке видалення](/docs/{{version}}/eloquent#soft-deleting) і вам треба шукати серед м'яко видалених моделей, встановіть опцію `soft_delete` у конфігураційному файлі `config/scout.php` у значення `true`:

```php
'soft_delete' => true,
```

Коли ця опція має значення `true`, Scout не прибиратиме м'яко видалені моделі з пошукового індексу. Натомість він встановить прихований атрибут `__soft_deleted` на проіндексованому записі. Далі ви можете скористатися методами `withTrashed` чи `onlyTrashed`, щоб діставати м'яко видалені записи під час пошуку:

```php
use App\Models\Order;

// Include trashed records when retrieving results...
$orders = Order::search('Star Trek')->withTrashed()->get();

// Only include trashed records when retrieving results...
$orders = Order::search('Star Trek')->onlyTrashed()->get();
```

> [!NOTE]
> Коли м'яко видалену модель остаточно видаляють через `forceDelete`, Scout автоматично прибирає її з пошукового індексу.

<a name="customizing-engine-searches"></a>
### Налаштування пошуку в рушіях

Якщо вам потрібне складне налаштування пошукової поведінки рушія, ви можете передати замикання другим аргументом до методу `search`. Наприклад, цим колбеком можна додати геодані до ваших опцій пошуку, перш ніж запит буде передано до Algolia:

```php
use Algolia\AlgoliaSearch\SearchIndex;
use App\Models\Order;

Order::search(
    'Star Trek',
    function (SearchIndex $algolia, string $query, array $options) {
        $options['body']['query']['bool']['filter']['geo_distance'] = [
            'distance' => '1000km',
            'location' => ['lat' => 36, 'lon' => 111],
        ];

        return $algolia->search($query, $options);
    }
)->get();
```

<a name="custom-engines"></a>
## Власні рушії

<a name="writing-the-engine"></a>
#### Написання рушія

Якщо жоден із вбудованих пошукових рушіїв Scout вам не пасує, ви можете написати власний і зареєструвати його в Scout. Ваш рушій має успадковувати абстрактний клас `Laravel\Scout\Engines\Engine`. Цей абстрактний клас містить вісім методів, які має реалізувати ваш рушій:

```php
use Laravel\Scout\Builder;

abstract public function update($models);
abstract public function delete($models);
abstract public function search(Builder $builder);
abstract public function paginate(Builder $builder, $perPage, $page);
abstract public function mapIds($results);
abstract public function map(Builder $builder, $results, $model);
abstract public function getTotalCount($results);
abstract public function flush($model);
```

Вам може бути корисно переглянути реалізації цих методів у класі `Laravel\Scout\Engines\AlgoliaEngine`. Цей клас стане хорошою відправною точкою, щоб зрозуміти, як реалізувати кожен із них у власному рушії.

<a name="registering-the-engine"></a>
#### Реєстрація рушія

Написавши власний рушій, ви можете зареєструвати його в Scout методом `extend` менеджера рушіїв Scout. Менеджер рушіїв Scout можна розв'язати із сервіс-контейнера Laravel. Викликати метод `extend` слід у методі `boot` вашого класу `App\Providers\AppServiceProvider` чи будь-якого іншого сервіс-провайдера вашого застосунку:

```php
use App\ScoutExtensions\MySqlSearchEngine;
use Laravel\Scout\EngineManager;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    resolve(EngineManager::class)->extend('mysql', function () {
        return new MySqlSearchEngine;
    });
}
```

Коли ваш рушій зареєстровано, ви можете вказати його як стандартний `driver` Scout у конфігураційному файлі `config/scout.php` вашого застосунку:

```php
'driver' => 'mysql',
```

---

Джерело: https://laravelukraine.com/docs/13.x/socialite

# Laravel Socialite

- [Вступ](#introduction)
- [Встановлення](#installation)
- [Оновлення Socialite](#upgrading-socialite)
- [Конфігурація](#configuration)
- [Автентифікація](#authentication)
    - [Маршрутизація](#routing)
    - [Автентифікація та збереження](#authentication-and-storage)
    - [Скопи доступу](#access-scopes)
    - [Скопи ботів Slack](#slack-bot-scopes)
    - [Необов'язкові параметри](#optional-parameters)
- [Отримання даних користувача](#retrieving-user-details)
- [Тестування](#testing)

<a name="introduction"></a>
## Вступ

Окрім звичайної автентифікації через форму, Laravel надає простий і зручний спосіб автентифікуватися через провайдери OAuth за допомогою [Laravel Socialite](https://github.com/laravel/socialite). Наразі Socialite підтримує автентифікацію через Facebook, X, LinkedIn, Google, GitHub, GitLab, Bitbucket і Slack.

> [!NOTE]
> Адаптери для інших платформ доступні на сайті [Socialite Providers](https://socialiteproviders.com/), який розвиває спільнота.

<a name="installation"></a>
## Встановлення

Щоб почати роботу із Socialite, додайте пакет до залежностей вашого проєкту через менеджер пакетів Composer:

```shell
composer require laravel/socialite
```

<a name="upgrading-socialite"></a>
## Оновлення Socialite

Оновлюючись до нової мажорної версії Socialite, обов'язково уважно перегляньте [посібник з оновлення](https://github.com/laravel/socialite/blob/master/UPGRADE.md).

<a name="configuration"></a>
## Конфігурація

Перш ніж користуватися Socialite, вам треба додати облікові дані для провайдерів OAuth, які використовує ваш застосунок. Зазвичай ці облікові дані можна отримати, створивши «застосунок розробника» в панелі керування сервісу, через який ви автентифікуватиметеся.

Ці облікові дані слід розмістити в конфігураційному файлі `config/services.php` вашого застосунку під ключем `facebook`, `x`, `linkedin-openid`, `google`, `github`, `gitlab`, `bitbucket`, `slack` чи `slack-openid` - залежно від того, які провайдери потрібні вашому застосунку:

```php
'github' => [
    'client_id' => env('GITHUB_CLIENT_ID'),
    'client_secret' => env('GITHUB_CLIENT_SECRET'),
    'redirect' => 'http://example.com/callback-url',
],
```

> [!NOTE]
> Якщо опція `redirect` містить відносний шлях, його буде автоматично перетворено на повний URL.

<a name="authentication"></a>
## Автентифікація

<a name="routing"></a>
### Маршрутизація

Щоб автентифікувати користувачів через провайдер OAuth, вам знадобляться два маршрути: один - для перенаправлення користувача до провайдера, другий - для отримання зворотного виклику від провайдера після автентифікації. Наведені нижче приклади демонструють реалізацію обох маршрутів:

```php
use Laravel\Socialite\Socialite;

Route::get('/auth/redirect', function () {
    return Socialite::driver('github')->redirect();
});

Route::get('/auth/callback', function () {
    $user = Socialite::driver('github')->user();

    // $user->token
});
```

Метод `redirect`, який надає фасад `Socialite`, дбає про перенаправлення користувача до провайдера OAuth, а метод `user` перевіряє вхідний запит і отримує дані користувача від провайдера після того, як той схвалив запит на автентифікацію.

<a name="authentication-and-storage"></a>
### Автентифікація та збереження

Отримавши користувача від провайдера OAuth, ви можете визначити, чи існує він у базі даних вашого застосунку, і [автентифікувати його](/docs/{{version}}/authentication#authenticate-a-user-instance). Якщо користувача у вашій базі немає, зазвичай ви створюєте новий запис, що представлятиме його:

```php
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Socialite;

Route::get('/auth/callback', function () {
    $githubUser = Socialite::driver('github')->user();

    $user = User::updateOrCreate([
        'github_id' => $githubUser->id,
    ], [
        'name' => $githubUser->name,
        'email' => $githubUser->email,
        'github_token' => $githubUser->token,
        'github_refresh_token' => $githubUser->refreshToken,
    ]);

    Auth::login($user);

    return redirect('/dashboard');
});
```

> [!NOTE]
> Щоб дізнатися більше про те, які дані користувача доступні від конкретних провайдерів OAuth, зверніться до документації про [отримання даних користувача](#retrieving-user-details).

<a name="access-scopes"></a>
### Скопи доступу

Перед перенаправленням користувача ви можете скористатися методом `scopes`, щоб указати «скопи» (scopes), які слід включити до запиту на автентифікацію. Цей метод об'єднає всі раніше вказані скопи з тими, які ви задаєте:

```php
use Laravel\Socialite\Socialite;

return Socialite::driver('github')
    ->scopes(['read:user', 'public_repo'])
    ->redirect();
```

Ви можете перезаписати всі наявні скопи в запиті на автентифікацію методом `setScopes`:

```php
return Socialite::driver('github')
    ->setScopes(['read:user', 'public_repo'])
    ->redirect();
```

<a name="slack-bot-scopes"></a>
### Скопи ботів Slack

API Slack надає [різні типи токенів доступу](https://api.slack.com/authentication/token-types), кожен зі своїм набором [скопів дозволів](https://api.slack.com/scopes). Socialite сумісний з обома такими типами токенів доступу Slack:

<div class="content-list" markdown="1">

- Bot (з префіксом `xoxb-`)
- User (з префіксом `xoxp-`)

</div>

За замовчуванням драйвер `slack` генеруватиме токен `user`, а виклик методу `user` цього драйвера поверне дані користувача.

Токени ботів передусім корисні, якщо ваш застосунок надсилатиме сповіщення до зовнішніх робочих просторів Slack, що належать вашим користувачам. Щоб згенерувати токен бота, викличте метод `asBotUser`, перш ніж перенаправляти користувача до Slack для автентифікації:

```php
return Socialite::driver('slack')
    ->asBotUser()
    ->setScopes(['chat:write', 'chat:write.public', 'chat:write.customize'])
    ->redirect();
```

Крім того, ви маєте викликати метод `asBotUser` перед викликом методу `user` після того, як Slack поверне користувача до вашого застосунку:

```php
$user = Socialite::driver('slack')->asBotUser()->user();
```

Під час генерування токена бота метод `user` усе одно поверне екземпляр `Laravel\Socialite\Two\User`; проте заповненою буде лише властивість `token`. Цей токен можна зберегти, щоб [надсилати сповіщення до робочих просторів Slack автентифікованого користувача](/docs/{{version}}/notifications#notifying-external-slack-workspaces).

<a name="optional-parameters"></a>
### Необов'язкові параметри

Низка провайдерів OAuth підтримує інші необов'язкові параметри в запиті на перенаправлення. Щоб додати такі параметри до запиту, викличте метод `with` з асоціативним масивом:

```php
use Laravel\Socialite\Socialite;

return Socialite::driver('google')
    ->with(['hd' => 'example.com'])
    ->redirect();
```

> [!WARNING]
> Користуючись методом `with`, стежте, щоб не передати зарезервованих ключових слів на кшталт `state` чи `response_type`.

<a name="retrieving-user-details"></a>
## Отримання даних користувача

Після того як користувача перенаправлено назад на маршрут зворотного виклику вашого застосунку, ви можете отримати його дані методом `user` у Socialite. Об'єкт користувача, який повертає метод `user`, надає різні властивості й методи, якими ви можете скористатися, щоб зберегти інформацію про користувача у власній базі даних.

Набір доступних властивостей і методів цього об'єкта різниться залежно від того, чи підтримує провайдер OAuth, через який ви автентифікуєтеся, OAuth 1.0 чи OAuth 2.0:

```php
use Laravel\Socialite\Socialite;

Route::get('/auth/callback', function () {
    $user = Socialite::driver('github')->user();

    // OAuth 2.0 providers...
    $token = $user->token;
    $refreshToken = $user->refreshToken;
    $expiresIn = $user->expiresIn;

    // OAuth 1.0 providers...
    $token = $user->token;
    $tokenSecret = $user->tokenSecret;

    // All providers...
    $user->getId();
    $user->getNickname();
    $user->getName();
    $user->getEmail();
    $user->getAvatar();
});
```

<a name="retrieving-user-details-from-a-token-oauth2"></a>
#### Отримання даних користувача за токеном

Якщо ви вже маєте дійсний токен доступу користувача, ви можете отримати його дані методом `userFromToken` у Socialite:

```php
use Laravel\Socialite\Socialite;

$user = Socialite::driver('github')->userFromToken($token);
```

Якщо ви користуєтеся Facebook Limited Login через застосунок для iOS, Facebook поверне токен OIDC замість токена доступу. Щоб отримати дані користувача з токена OIDC, передайте методу `userFromToken` nonce, використаний для ініціації входу:

```php
$user = Socialite::driver('facebook')->userFromToken($token, $nonce);
```

<a name="stateless-authentication"></a>
#### Автентифікація без збереження стану

Метод `stateless` дозволяє вимкнути перевірку стану сесії. Це стає в пригоді, коли ви додаєте соціальну автентифікацію до stateless-API, який не використовує сесій на основі cookie:

```php
use Laravel\Socialite\Socialite;

return Socialite::driver('google')->stateless()->user();
```

<a name="testing"></a>
## Тестування

Laravel Socialite надає зручний спосіб тестувати потоки автентифікації OAuth, не роблячи справжніх запитів до провайдерів. Метод `fake` дозволяє підробити поведінку провайдера OAuth і задати дані користувача, які має бути повернено.

<a name="faking-the-redirect"></a>
#### Підроблення перенаправлення

Щоб перевірити, що ваш застосунок правильно перенаправляє користувачів до провайдера OAuth, викличте метод `fake` перед запитом до вашого маршруту перенаправлення. Тоді Socialite поверне перенаправлення на фіктивний URL авторизації замість справжнього провайдера OAuth:

```php
use Laravel\Socialite\Socialite;

test('user is redirected to github', function () {
    Socialite::fake('github');

    $response = $this->get('/auth/github/redirect');

    $response->assertRedirect();
});
```

<a name="faking-the-callback"></a>
#### Підроблення зворотного виклику

Щоб протестувати маршрут зворотного виклику вашого застосунку, викличте метод `fake` і передайте екземпляр `User`, який має бути повернено, коли застосунок запитає в провайдера дані користувача. Екземпляр `User` можна створити методом `fake`:

```php
use Laravel\Socialite\Socialite;
use Laravel\Socialite\Two\User;

test('user can login with github', function () {
    Socialite::fake('github', User::fake([
        'id' => 'github-123',
        'name' => 'Jason Beggs',
        'email' => 'jason@example.com',
    ]));

    $response = $this->get('/auth/github/callback');

    $response->assertRedirect('/dashboard');

    $this->assertDatabaseHas('users', [
        'name' => 'Jason Beggs',
        'email' => 'jason@example.com',
        'github_id' => 'github-123',
    ]);
});
```

За замовчуванням екземпляр `User` міститиме фіктивні значення токенів OAuth. За потреби ви можете перевизначити ці значення, передавши методу `fake` додаткові атрибути:

```php
$fakeUser = User::fake([
    'id' => 'github-123',
    'name' => 'Jason Beggs',
    'email' => 'jason@example.com',
    'token' => 'fake-token',
    'refreshToken' => 'fake-refresh-token',
    'expiresIn' => 3600,
    'approvedScopes' => ['read', 'write'],
]);
```

Користувачів OAuth 1 можна підробити через клас `Laravel\Socialite\One\User`.

---

Джерело: https://laravelukraine.com/docs/13.x/telescope

# Laravel Telescope

- [Вступ](#introduction)
- [Встановлення](#installation)
    - [Встановлення лише для локальної розробки](#local-only-installation)
    - [Конфігурація](#configuration)
    - [Очищення даних](#data-pruning)
    - [Авторизація панелі](#dashboard-authorization)
- [Оновлення Telescope](#upgrading-telescope)
- [Фільтрація](#filtering)
    - [Записи](#filtering-entries)
    - [Пакети](#filtering-batches)
- [Теги](#tagging)
- [Доступні спостерігачі](#available-watchers)
    - [Спостерігач пакетів](#batch-watcher)
    - [Спостерігач кешу](#cache-watcher)
    - [Спостерігач команд](#command-watcher)
    - [Спостерігач дампів](#dump-watcher)
    - [Спостерігач подій](#event-watcher)
    - [Спостерігач винятків](#exception-watcher)
    - [Спостерігач гейтів](#gate-watcher)
    - [Спостерігач HTTP-клієнта](#http-client-watcher)
    - [Спостерігач завдань](#job-watcher)
    - [Спостерігач логів](#log-watcher)
    - [Спостерігач пошти](#mail-watcher)
    - [Спостерігач моделей](#model-watcher)
    - [Спостерігач сповіщень](#notification-watcher)
    - [Спостерігач запитів до бази](#query-watcher)
    - [Спостерігач Redis](#redis-watcher)
    - [Спостерігач запитів](#request-watcher)
    - [Спостерігач планувальника](#schedule-watcher)
    - [Спостерігач представлень](#view-watcher)
- [Показ аватарів користувачів](#displaying-user-avatars)

<a name="introduction"></a>
## Вступ

[Laravel Telescope](https://github.com/laravel/telescope) стане чудовим супутником вашого локального середовища розробки на Laravel. Telescope дає змогу зазирнути всередину запитів, що надходять до вашого застосунку, а також винятків, записів логу, запитів до бази даних, завдань у черзі, пошти, сповіщень, операцій із кешем, запланованих завдань, дампів змінних тощо.

<img src="https://laravel.com/img/docs/telescope-example.png">

<a name="installation"></a>
## Встановлення

Ви можете встановити Telescope у свій проєкт Laravel через менеджер пакетів Composer:

```shell
composer require laravel/telescope
```

Після встановлення Telescope опублікуйте його ресурси й міграції артизан-командою `telescope:install`. Далі виконайте команду `migrate`, щоб створити таблиці, потрібні для зберігання даних Telescope:

```shell
php artisan telescope:install

php artisan migrate
```

Нарешті, ви можете відкрити панель Telescope за маршрутом `/telescope`.

<a name="local-only-installation"></a>
### Встановлення лише для локальної розробки

Якщо ви плануєте користуватися Telescope лише для локальної розробки, встановіть його з прапорцем `--dev`:

```shell
composer require laravel/telescope --dev

php artisan telescope:install

php artisan migrate
```

Виконавши `telescope:install`, приберіть реєстрацію сервіс-провайдера `TelescopeServiceProvider` з конфігураційного файлу `bootstrap/providers.php` вашого застосунку. Натомість зареєструйте сервіс-провайдери Telescope вручну в методі `register` вашого класу `App\Providers\AppServiceProvider`. Перед реєстрацією провайдерів ми переконаємося, що поточне середовище - `local`:

```php
/**
 * Register any application services.
 */
public function register(): void
{
    if ($this->app->environment('local') && class_exists(\Laravel\Telescope\TelescopeServiceProvider::class)) {
        $this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class);
        $this->app->register(TelescopeServiceProvider::class);
    }
}
```

Нарешті, вам також слід завадити [автовиявленню](/docs/{{version}}/packages#package-discovery) пакета Telescope, додавши до свого файлу `composer.json` таке:

```json
"extra": {
    "laravel": {
        "dont-discover": [
            "laravel/telescope"
        ]
    }
},
```

<a name="configuration"></a>
### Конфігурація

Після публікації ресурсів Telescope його основний конфігураційний файл буде розташований у `config/telescope.php`. Цей файл дозволяє налаштувати [опції спостерігачів](#available-watchers). Кожна опція конфігурації супроводжується описом призначення, тож обов'язково ретельно перегляньте цей файл.

За бажання ви можете цілком вимкнути збирання даних Telescope через опцію конфігурації `enabled`:

```php
'enabled' => env('TELESCOPE_ENABLED', true),
```

<a name="content-security-policy-csp-nonce"></a>
#### Nonce політики безпеки вмісту (CSP)

Якщо ви хочете використовувати [атрибут nonce](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/nonce) на тегах script і style, що використовуються в представленнях Telescope, як частину вашої [політики безпеки вмісту](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP), ви можете скористатися методом `Telescope::cspNonce`, щоб вказати nonce для використання. Цей метод зазвичай слід викликати всередині `middleware`, щоб новий nonce призначався для кожного запиту:

```php
use Closure;
use Illuminate\Http\Request;
use Laravel\Telescope\Telescope;
use Symfony\Component\HttpFoundation\Response;

public function handle(Request $request, Closure $next): Response
{
    Telescope::cspNonce('csp-nonce');

    return $next($request);
}
```

Ви можете додати це `middleware` до опції `middleware` у конфігураційному файлі вашого застосунку `config/telescope.php`:

```php
'middleware' => [
    'web',
    App\Http\Middleware\AddTelescopeCspNonce::class,
    Authorize::class,
],
```

<a name="data-pruning"></a>
### Очищення даних

Без очищення таблиця `telescope_entries` може дуже швидко накопичити записи. Щоб цьому запобігти, [заплануйте](/docs/{{version}}/scheduling) щоденний запуск артизан-команди `telescope:prune`:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('telescope:prune')->daily();
```

За замовчуванням буде видалено всі записи, старші за 24 години. Ви можете скористатися опцією `hours` під час виклику команди, щоб визначити, як довго зберігати дані Telescope. Наприклад, така команда видалить усі записи, створені понад 48 годин тому:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('telescope:prune --hours=48')->daily();
```

<a name="dashboard-authorization"></a>
### Авторизація панелі

Панель Telescope доступна за маршрутом `/telescope`. За замовчуванням ви зможете відкрити її лише в середовищі `local`. У вашому файлі `app/Providers/TelescopeServiceProvider.php` є визначення [гейта авторизації](/docs/{{version}}/authorization#gates). Цей гейт керує доступом до Telescope у **нелокальних** середовищах. Ви вільні змінювати його як потрібно, щоб обмежити доступ до вашої установки Telescope:

```php
use App\Models\User;

/**
 * Register the Telescope gate.
 *
 * This gate determines who can access Telescope in non-local environments.
 */
protected function gate(): void
{
    Gate::define('viewTelescope', function (User $user) {
        return in_array($user->email, [
            'taylor@laravel.com',
        ]);
    });
}
```

> [!WARNING]
> Обов'язково змініть у продакшн-середовищі змінну оточення `APP_ENV` на `production`. Інакше ваша установка Telescope буде публічно доступною.

<a name="upgrading-telescope"></a>
## Оновлення Telescope

Оновлюючись до нової мажорної версії Telescope, обов'язково уважно перегляньте [посібник з оновлення](https://github.com/laravel/telescope/blob/master/UPGRADE.md).

Крім того, оновлюючись до будь-якої нової версії Telescope, повторно опублікуйте його ресурси:

```shell
php artisan telescope:publish
```

Щоб тримати ресурси актуальними й уникнути проблем у майбутніх оновленнях, ви можете додати команду `vendor:publish --tag=laravel-assets` до скриптів `post-update-cmd` у файлі `composer.json` вашого застосунку:

```json
{
    "scripts": {
        "post-update-cmd": [
            "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
        ]
    }
}
```

<a name="filtering"></a>
## Фільтрація

<a name="filtering-entries"></a>
### Записи

Ви можете фільтрувати дані, які записує Telescope, через замикання `filter`, визначене у вашому класі `App\Providers\TelescopeServiceProvider`. За замовчуванням це замикання записує всі дані в середовищі `local`, а в решті середовищ - винятки, провалені завдання, заплановані завдання й дані з відстежуваними тегами:

```php
use Laravel\Telescope\IncomingEntry;
use Laravel\Telescope\Telescope;

/**
 * Register any application services.
 */
public function register(): void
{
    $this->hideSensitiveRequestDetails();

    Telescope::filter(function (IncomingEntry $entry) {
        if ($this->app->environment('local')) {
            return true;
        }

        return $entry->isReportableException() ||
            $entry->isFailedJob() ||
            $entry->isScheduledTask() ||
            $entry->isSlowQuery() ||
            $entry->hasMonitoredTag();
    });
}
```

<a name="filtering-batches"></a>
### Пакети

Якщо замикання `filter` фільтрує дані для окремих записів, то метод `filterBatch` дозволяє зареєструвати замикання, яке фільтрує всі дані для заданого запиту чи консольної команди. Якщо замикання поверне `true`, Telescope запише всі записи:

```php
use Illuminate\Support\Collection;
use Laravel\Telescope\IncomingEntry;
use Laravel\Telescope\Telescope;

/**
 * Register any application services.
 */
public function register(): void
{
    $this->hideSensitiveRequestDetails();

    Telescope::filterBatch(function (Collection $entries) {
        if ($this->app->environment('local')) {
            return true;
        }

        return $entries->contains(function (IncomingEntry $entry) {
            return $entry->isReportableException() ||
                $entry->isFailedJob() ||
                $entry->isScheduledTask() ||
                $entry->isSlowQuery() ||
                $entry->hasMonitoredTag();
            });
    });
}
```

<a name="tagging"></a>
## Теги

Telescope дозволяє шукати записи за «тегом». Часто теги - це імена класів моделей Eloquent чи ID автентифікованих користувачів, які Telescope додає до записів автоматично. Іноді вам може знадобитися прикріпити до записів власні теги. Для цього скористайтеся методом `Telescope::tag`. Метод `tag` приймає замикання, яке має повернути масив тегів. Повернені замиканням теги буде об'єднано з тими, які Telescope додав би до запису автоматично. Зазвичай метод `tag` викликають у методі `register` вашого класу `App\Providers\TelescopeServiceProvider`:

```php
use Laravel\Telescope\EntryType;
use Laravel\Telescope\IncomingEntry;
use Laravel\Telescope\Telescope;

/**
 * Register any application services.
 */
public function register(): void
{
    $this->hideSensitiveRequestDetails();

    Telescope::tag(function (IncomingEntry $entry) {
        return $entry->type === EntryType::REQUEST
            ? ['status:'.$entry->content['response_status']]
            : [];
    });
}
```

<a name="available-watchers"></a>
## Доступні спостерігачі

«Спостерігачі» (watchers) Telescope збирають дані застосунку під час виконання запиту чи консольної команди. Ви можете налаштувати список спостерігачів, які хочете ввімкнути, у своєму конфігураційному файлі `config/telescope.php`:

```php
'watchers' => [
    Watchers\CacheWatcher::class => true,
    Watchers\CommandWatcher::class => true,
    // ...
],
```

Деякі спостерігачі дозволяють також задати додаткові опції налаштування:

```php
'watchers' => [
    Watchers\QueryWatcher::class => [
        'enabled' => env('TELESCOPE_QUERY_WATCHER', true),
        'slow' => 100,
    ],
    // ...
],
```

<a name="batch-watcher"></a>
### Спостерігач пакетів

Спостерігач пакетів записує інформацію про [пакети](/docs/{{version}}/queues#job-batching) у чергах, зокрема дані про завдання та підключення.

<a name="cache-watcher"></a>
### Спостерігач кешу

Спостерігач кешу записує дані, коли ключ кешу знайдено, не знайдено, оновлено чи забуто.

<a name="command-watcher"></a>
### Спостерігач команд

Спостерігач команд записує аргументи, опції, код виходу та вивід щоразу, коли виконується артизан-команда. Якщо ви хочете виключити певні команди із запису, вкажіть їх в опції `ignore` у своєму файлі `config/telescope.php`:

```php
'watchers' => [
    Watchers\CommandWatcher::class => [
        'enabled' => env('TELESCOPE_COMMAND_WATCHER', true),
        'ignore' => ['key:generate'],
    ],
    // ...
],
```

<a name="dump-watcher"></a>
### Спостерігач дампів

Спостерігач дампів записує й показує ваші дампи змінних у Telescope. У Laravel змінні можна дампити глобальною функцією `dump`. Щоб дамп було записано, вкладка спостерігача дампів має бути відкрита у браузері - інакше спостерігач ігноруватиме дампи.

<a name="event-watcher"></a>
### Спостерігач подій

Спостерігач подій записує дані, слухачів і дані бродкастингу для всіх [подій](/docs/{{version}}/events), відправлених вашим застосунком. Внутрішні події фреймворку Laravel спостерігач подій ігнорує.

<a name="exception-watcher"></a>
### Спостерігач винятків

Спостерігач винятків записує дані та стек викликів для всіх винятків вашого застосунку, які підлягають звітуванню.

<a name="gate-watcher"></a>
### Спостерігач гейтів

Спостерігач гейтів записує дані та результат перевірок [гейтів і політик](/docs/{{version}}/authorization) у вашому застосунку. Якщо ви хочете виключити певні можливості із запису, вкажіть їх в опції `ignore_abilities` у своєму файлі `config/telescope.php`:

```php
'watchers' => [
    Watchers\GateWatcher::class => [
        'enabled' => env('TELESCOPE_GATE_WATCHER', true),
        'ignore_abilities' => ['viewNova'],
    ],
    // ...
],
```

<a name="http-client-watcher"></a>
### Спостерігач HTTP-клієнта

Спостерігач HTTP-клієнта записує вихідні [запити HTTP-клієнта](/docs/{{version}}/http-client), які робить ваш застосунок.

<a name="job-watcher"></a>
### Спостерігач завдань

Спостерігач завдань записує дані та статус усіх [завдань](/docs/{{version}}/queues), відправлених вашим застосунком.

<a name="log-watcher"></a>
### Спостерігач логів

Спостерігач логів записує [дані логу](/docs/{{version}}/logging) для всіх записів, які створює ваш застосунок.

За замовчуванням Telescope записує лише логи рівня `error` і вище. Проте ви можете змінити опцію `level` у конфігураційному файлі `config/telescope.php` вашого застосунку, щоб змінити цю поведінку:

```php
'watchers' => [
    Watchers\LogWatcher::class => [
        'enabled' => env('TELESCOPE_LOG_WATCHER', true),
        'level' => 'debug',
    ],

    // ...
],
```

<a name="mail-watcher"></a>
### Спостерігач пошти

Спостерігач пошти дозволяє переглянути у браузері попередній вигляд [листів](/docs/{{version}}/mail), надісланих вашим застосунком, разом із пов'язаними даними. Ви також можете завантажити лист як файл `.eml`.

<a name="model-watcher"></a>
### Спостерігач моделей

Спостерігач моделей записує зміни моделей щоразу, коли відправляється [подія моделі](/docs/{{version}}/eloquent#events) Eloquent. Ви можете вказати, які саме події моделей слід записувати, через опцію `events` цього спостерігача:

```php
'watchers' => [
    Watchers\ModelWatcher::class => [
        'enabled' => env('TELESCOPE_MODEL_WATCHER', true),
        'events' => ['eloquent.created*', 'eloquent.updated*'],
    ],
    // ...
],
```

Якщо ви хочете записувати кількість моделей, створених із даних під час заданого запиту, увімкніть опцію `hydrations`:

```php
'watchers' => [
    Watchers\ModelWatcher::class => [
        'enabled' => env('TELESCOPE_MODEL_WATCHER', true),
        'events' => ['eloquent.created*', 'eloquent.updated*'],
        'hydrations' => true,
    ],
    // ...
],
```

<a name="notification-watcher"></a>
### Спостерігач сповіщень

Спостерігач сповіщень записує всі [сповіщення](/docs/{{version}}/notifications), надіслані вашим застосунком. Якщо сповіщення спричиняє надсилання листа й у вас увімкнено спостерігач пошти, лист також буде доступний для перегляду на екрані спостерігача пошти.

<a name="query-watcher"></a>
### Спостерігач запитів до бази

Спостерігач запитів записує сирий SQL, прив'язки та час виконання всіх запитів, які виконує ваш застосунок. Спостерігач також позначає тегом `slow` усі запити, повільніші за 100 мілісекунд. Ви можете змінити поріг повільного запиту через опцію `slow` цього спостерігача:

```php
'watchers' => [
    Watchers\QueryWatcher::class => [
        'enabled' => env('TELESCOPE_QUERY_WATCHER', true),
        'slow' => 50,
    ],
    // ...
],
```

<a name="redis-watcher"></a>
### Спостерігач Redis

Спостерігач Redis записує всі команди [Redis](/docs/{{version}}/redis), які виконує ваш застосунок. Якщо ви використовуєте Redis для кешування, спостерігач Redis записуватиме й команди кешу.

<a name="request-watcher"></a>
### Спостерігач запитів

Спостерігач запитів записує запит, заголовки, сесію та дані відповіді для всіх запитів, які обробляє застосунок. Ви можете обмежити обсяг записуваних даних відповіді опцією `size_limit` (у кілобайтах):

```php
'watchers' => [
    Watchers\RequestWatcher::class => [
        'enabled' => env('TELESCOPE_REQUEST_WATCHER', true),
        'size_limit' => env('TELESCOPE_RESPONSE_SIZE_LIMIT', 64),
    ],
    // ...
],
```

<a name="schedule-watcher"></a>
### Спостерігач планувальника

Спостерігач планувальника записує команду та вивід усіх [запланованих завдань](/docs/{{version}}/scheduling), які виконує ваш застосунок.

<a name="view-watcher"></a>
### Спостерігач представлень

Спостерігач представлень записує ім'я, шлях, дані та «композери» [представлення](/docs/{{version}}/views), використані під час рендерингу.

<a name="displaying-user-avatars"></a>
## Показ аватарів користувачів

Панель Telescope показує аватар користувача, який був автентифікований на момент збереження запису. За замовчуванням Telescope отримує аватари через вебсервіс Gravatar. Проте ви можете налаштувати URL аватара, зареєструвавши колбек у вашому класі `App\Providers\TelescopeServiceProvider`. Колбек отримає ID та адресу електронної пошти користувача й має повернути URL зображення аватара:

```php
use App\Models\User;
use Laravel\Telescope\Telescope;

/**
 * Register any application services.
 */
public function register(): void
{
    // ...

    Telescope::avatar(function (?string $id, ?string $email) {
        return ! is_null($id)
            ? '/avatars/'.User::find($id)->avatar_path
            : '/generic-avatar.jpg';
    });
}
```

---

Джерело: https://laravelukraine.com/docs/13.x/valet

# Laravel Valet

- [Вступ](#introduction)
- [Встановлення](#installation)
    - [Оновлення Valet](#upgrading-valet)
- [Віддача сайтів](#serving-sites)
    - [Команда «park»](#the-park-command)
    - [Команда «link»](#the-link-command)
    - [Захист сайтів через TLS](#securing-sites)
    - [Віддача сайту за замовчуванням](#serving-a-default-site)
    - [Версії PHP для окремих сайтів](#per-site-php-versions)
- [Публічний доступ до сайтів](#sharing-sites)
    - [Доступ до сайтів у локальній мережі](#sharing-sites-on-your-local-network)
- [Змінні оточення для окремих сайтів](#site-specific-environment-variables)
- [Проксіювання сервісів](#proxying-services)
- [Власні драйвери Valet](#custom-valet-drivers)
    - [Локальні драйвери](#local-drivers)
- [Інші команди Valet](#other-valet-commands)
- [Каталоги й файли Valet](#valet-directories-and-files)
    - [Доступ до диска](#disk-access)

<a name="introduction"></a>
## Вступ

> [!NOTE]
> Шукаєте ще простіший спосіб розробляти застосунки Laravel на macOS чи Windows? Погляньте на [Laravel Herd](https://herd.laravel.com). Herd містить усе потрібне, щоб почати розробку на Laravel, - зокрема Valet, PHP і Composer.

[Laravel Valet](https://github.com/laravel/valet) - середовище розробки для мінімалістів на macOS. Laravel Valet налаштовує ваш Mac так, щоб [Nginx](https://www.nginx.com/) завжди працював у фоні від старту системи. Далі, через [DnsMasq](https://en.wikipedia.org/wiki/Dnsmasq), Valet проксіює всі запити на домен `*.test` до сайтів, встановлених на вашій машині.

Іншими словами, Valet - це блискавично швидке середовище розробки Laravel, яке з'їдає приблизно 7 МБ RAM. Valet не є повною заміною [Sail](/docs/{{version}}/sail) чи [Homestead](/docs/{{version}}/homestead), але стане чудовою альтернативою, якщо вам потрібна гнучка основа, ви цінуєте граничну швидкість або працюєте на машині з обмеженою кількістю RAM.

Одразу з коробки Valet підтримує - зокрема, але не лише:

<style>
    #valet-support > ul {
        column-count: 3; -moz-column-count: 3; -webkit-column-count: 3;
        line-height: 1.9;
    }
</style>

<div id="valet-support" markdown="1">

- [Laravel](https://laravel.com)
- [Bedrock](https://roots.io/bedrock/)
- [CakePHP 3](https://cakephp.org)
- [ConcreteCMS](https://www.concretecms.com/)
- [Contao](https://contao.org/en/)
- [Craft](https://craftcms.com)
- [Drupal](https://www.drupal.org/)
- [ExpressionEngine](https://www.expressionengine.com/)
- [Jigsaw](https://jigsaw.tighten.co)
- [Joomla](https://www.joomla.org/)
- [Katana](https://github.com/themsaid/katana)
- [Kirby](https://getkirby.com/)
- [Magento](https://magento.com/)
- [OctoberCMS](https://octobercms.com/)
- [Sculpin](https://sculpin.io/)
- [Slim](https://www.slimframework.com)
- [Statamic](https://statamic.com)
- Статичний HTML
- [Symfony](https://symfony.com)
- [WordPress](https://wordpress.org)
- [Zend](https://framework.zend.com)

</div>

Проте ви можете розширити Valet власними [драйверами](#custom-valet-drivers).

<a name="installation"></a>
## Встановлення

> [!WARNING]
> Valet вимагає macOS і [Homebrew](https://brew.sh/). Перед встановленням переконайтеся, що жодна інша програма - як-от Apache чи Nginx - не займає порт 80 на вашій машині.

Для початку переконайтеся, що Homebrew оновлено, командою `update`:

```shell
brew update
```

Далі встановіть PHP через Homebrew:

```shell
brew install php
```

Після встановлення PHP ви готові встановити [менеджер пакетів Composer](https://getcomposer.org). Крім того, переконайтеся, що каталог `$HOME/.composer/vendor/bin` є у «PATH» вашої системи. Коли Composer встановлено, ви можете встановити Laravel Valet як глобальний пакет Composer:

```shell
composer global require laravel/valet
```

Нарешті, виконайте команду Valet `install`. Вона налаштує й встановить Valet і DnsMasq. Крім того, демони, від яких залежить Valet, буде налаштовано на запуск разом із системою:

```shell
valet install
```

Коли Valet встановлено, спробуйте пропінгувати будь-який домен `*.test` у терміналі - наприклад, командою `ping foobar.test`. Якщо Valet встановлено правильно, ви побачите, що цей домен відповідає на `127.0.0.1`.

Valet автоматично запускатиме потрібні йому сервіси при кожному старті машини.

<a name="php-versions"></a>
#### Версії PHP

> [!NOTE]
> Замість того щоб змінювати глобальну версію PHP, ви можете вказати Valet використовувати різні версії PHP для окремих сайтів через [команду](#per-site-php-versions) `isolate`.

Valet дозволяє перемикати версії PHP командою `valet use php@version`. Valet встановить указану версію PHP через Homebrew, якщо її ще не встановлено:

```shell
valet use php@8.2

valet use php
```

Ви також можете створити файл `.valetrc` у корені свого проєкту. Файл `.valetrc` має містити версію PHP, яку слід використовувати для сайту:

```shell
php=php@8.2
```

Коли цей файл створено, ви можете просто виконати команду `valet use` - і вона визначить бажану версію PHP для сайту, прочитавши цей файл.

> [!WARNING]
> Valet віддає сайти лише однією версією PHP за раз, навіть якщо у вас встановлено кілька версій PHP.

<a name="database"></a>
#### База даних

Якщо вашому застосунку потрібна база даних, погляньте на [DBngin](https://dbngin.com) - безкоштовний універсальний інструмент керування базами, що містить MySQL, PostgreSQL і Redis. Коли DBngin встановлено, ви можете підключитися до бази на `127.0.0.1` з іменем користувача `root` і порожнім рядком як паролем.

<a name="resetting-your-installation"></a>
#### Скидання вашої установки

Якщо у вас проблеми з правильною роботою Valet, виконання команди `composer global require laravel/valet` з наступним `valet install` скине вашу установку й може розв'язати різні проблеми. У рідкісних випадках може знадобитися «жорстке скидання» Valet: виконайте `valet uninstall --force`, а потім `valet install`.

<a name="upgrading-valet"></a>
### Оновлення Valet

Ви можете оновити свою установку Valet, виконавши в терміналі команду `composer global require laravel/valet`. Після оновлення варто запустити команду `valet install`, щоб Valet за потреби вніс додаткові оновлення до ваших конфігураційних файлів.

<a name="upgrading-to-valet-4"></a>
#### Оновлення до Valet 4

Якщо ви оновлюєтеся з Valet 3 до Valet 4, виконайте такі кроки, щоб оновити установку правильно:

<div class="content-list" markdown="1">

- Якщо ви додавали файли `.valetphprc`, щоб задати версію PHP для сайту, перейменуйте кожен файл `.valetphprc` на `.valetrc`. Далі додайте `php=` перед наявним вмістом файлу `.valetrc`.
- Оновіть усі власні драйвери, щоб вони відповідали простору імен, розширенню, підказкам типів і типам, що повертаються, у новій системі драйверів. Як приклад можна взяти [SampleValetDriver](https://github.com/laravel/valet/blob/d7787c025e60abc24a5195dc7d4c5c6f2d984339/cli/stubs/SampleValetDriver.php) від Valet.
- Якщо ви віддаєте сайти на PHP 7.1 - 7.4, обов'язково встановіть через Homebrew також версію PHP 8.0 чи вище: Valet використовуватиме її для деяких своїх скриптів, навіть якщо вона не є вашою основною прив'язаною версією.

</div>

<a name="serving-sites"></a>
## Віддача сайтів

Коли Valet встановлено, ви готові віддавати свої застосунки Laravel. Valet надає для цього дві команди: `park` і `link`.

<a name="the-park-command"></a>
### Команда `park`

Команда `park` реєструє на вашій машині каталог, що містить ваші застосунки. Коли каталог «припарковано» у Valet, усі каталоги всередині нього стануть доступні у браузері за адресою `http://<directory-name>.test`:

```shell
cd ~/Sites

valet park
```

Ось і все. Тепер будь-який застосунок, створений у вашому «припаркованому» каталозі, автоматично віддаватиметься за угодою `http://<directory-name>.test`. Тож якщо ваш припаркований каталог містить каталог «laravel», застосунок усередині нього буде доступний за адресою `http://laravel.test`. Крім того, Valet автоматично дозволяє звертатися до сайту через піддомени-джокери (`http://foo.laravel.test`).

<a name="the-link-command"></a>
### Команда `link`

Команда `link` також дозволяє віддавати ваші застосунки Laravel. Вона стає в пригоді, коли ви хочете віддати один сайт у каталозі, а не весь каталог:

```shell
cd ~/Sites/laravel

valet link
```

Коли застосунок прив'язано до Valet командою `link`, ви можете звертатися до нього за іменем його каталогу. Тож сайт, прив'язаний у прикладі вище, буде доступний за адресою `http://laravel.test`. Крім того, Valet автоматично дозволяє звертатися до сайту через піддомени-джокери (`http://foo.laravel.test`).

Якщо ви хочете віддавати застосунок за іншим іменем хоста, передайте це ім'я команді `link`. Наприклад, ви можете виконати таку команду, щоб застосунок став доступний за адресою `http://application.test`:

```shell
cd ~/Sites/laravel

valet link application
```

Звісно, командою `link` можна віддавати застосунки й на піддоменах:

```shell
valet link api.application
```

Ви можете виконати команду `links`, щоб побачити список усіх ваших прив'язаних каталогів:

```shell
valet links
```

Команда `unlink` дозволяє знищити символічне посилання для сайту:

```shell
cd ~/Sites/laravel

valet unlink
```

<a name="securing-sites"></a>
### Захист сайтів через TLS

За замовчуванням Valet віддає сайти через HTTP. Проте якщо ви хочете віддавати сайт через шифрований TLS із HTTP/2, скористайтеся командою `secure`. Наприклад, якщо Valet віддає ваш сайт на домені `laravel.test`, виконайте таку команду, щоб захистити його:

```shell
valet secure laravel
```

Щоб «зняти захист» із сайту й повернутися до звичайного HTTP, скористайтеся командою `unsecure`. Як і команда `secure`, вона приймає ім'я хоста, з якого ви хочете зняти захист:

```shell
valet unsecure laravel
```

<a name="serving-a-default-site"></a>
### Віддача сайту за замовчуванням

Іноді вам може знадобитися налаштувати Valet віддавати сайт «за замовчуванням» замість `404` при зверненні до невідомого домену `test`. Для цього додайте до конфігураційного файлу `~/.config/valet/config.json` опцію `default` зі шляхом до сайту, який має слугувати сайтом за замовчуванням:

    "default": "/Users/Sally/Sites/example-site",

<a name="per-site-php-versions"></a>
### Версії PHP для окремих сайтів

За замовчуванням Valet віддає ваші сайти через глобально встановлений PHP. Проте якщо вам треба підтримувати кілька версій PHP для різних сайтів, скористайтеся командою `isolate`, щоб указати, яку версію PHP має використовувати конкретний сайт. Команда `isolate` налаштовує Valet використовувати вказану версію PHP для сайту, що лежить у вашому поточному робочому каталозі:

```shell
cd ~/Sites/example-site

valet isolate php@8.0
```

Якщо ім'я сайту не збігається з іменем каталогу, що його містить, вкажіть ім'я сайту опцією `--site`:

```shell
valet isolate php@8.0 --site="site-name"
```

Для зручності ви можете скористатися командами `valet php`, `composer` та `which-php`, щоб проксіювати виклики до відповідного PHP CLI чи інструмента, зважаючи на налаштовану для сайту версію PHP:

```shell
valet php
valet composer
valet which-php
```

Ви можете виконати команду `isolated`, щоб побачити список усіх ваших ізольованих сайтів і їхніх версій PHP:

```shell
valet isolated
```

Щоб повернути сайт до глобально встановленої у Valet версії PHP, викличте команду `unisolate` з кореневого каталогу сайту:

```shell
valet unisolate
```

<a name="sharing-sites"></a>
## Публічний доступ до сайтів

Valet містить команду, щоб відкрити ваші локальні сайти світові, - це простий спосіб протестувати сайт на мобільних пристроях або показати його колегам і клієнтам.

Одразу з коробки Valet підтримує відкриття сайтів через ngrok чи Expose. Перш ніж відкривати сайт, оновіть конфігурацію Valet командою `share-tool`, указавши `ngrok`, `expose` чи `cloudflared`:

```shell
valet share-tool ngrok
```

Якщо ви оберете інструмент, який не встановлено через Homebrew (для ngrok і cloudflared) чи Composer (для Expose), Valet автоматично запропонує його встановити. Звісно, обидва інструменти вимагають автентифікації у вашому обліковому записі ngrok чи Expose, перш ніж ви зможете відкривати сайти.

Щоб відкрити сайт, перейдіть у терміналі до його каталогу й виконайте команду Valet `share`. Публічно доступний URL потрапить до вашого буфера обміну - його можна одразу вставити у браузер чи надіслати команді:

```shell
cd ~/Sites/laravel

valet share
```

Щоб припинити відкритий доступ до сайту, натисніть `Control + C`.

> [!WARNING]
> Якщо ви користуєтеся власним DNS-сервером (як-от `1.1.1.1`), відкриття через ngrok може працювати некоректно. Якщо це ваш випадок, відкрийте системні налаштування Mac, перейдіть до налаштувань мережі, відкрийте додаткові налаштування, перейдіть на вкладку DNS і додайте `127.0.0.1` як перший DNS-сервер.

<a name="sharing-sites-via-ngrok"></a>
#### Відкриття сайтів через Ngrok

Щоб відкрити сайт через ngrok, вам треба [створити обліковий запис ngrok](https://dashboard.ngrok.com/signup) і [налаштувати токен автентифікації](https://dashboard.ngrok.com/get-started/your-authtoken). Коли токен у вас є, оновіть конфігурацію Valet цим токеном:

```shell
valet set-ngrok-token YOUR_TOKEN_HERE
```

> [!NOTE]
> Ви можете передати команді share додаткові параметри ngrok - наприклад, `valet share --region=eu`. Докладніше читайте в [документації ngrok](https://ngrok.com/docs).

<a name="sharing-sites-via-expose"></a>
#### Відкриття сайтів через Expose

Щоб відкрити сайт через Expose, вам треба [створити обліковий запис Expose](https://expose.dev/register) та [автентифікуватися в Expose через свій токен](https://expose.dev/docs/getting-started/getting-your-token).

Інформацію про додаткові параметри командного рядка, які він підтримує, шукайте в [документації Expose](https://expose.dev/docs).

<a name="sharing-sites-on-your-local-network"></a>
### Доступ до сайтів у локальній мережі

За замовчуванням Valet обмежує вхідний трафік внутрішнім інтерфейсом `127.0.0.1`, щоб ваша машина для розробки не наражалася на загрози з інтернету.

Якщо ви хочете дозволити іншим пристроям у локальній мережі звертатися до сайтів Valet на вашій машині за її IP-адресою (наприклад, `192.168.1.10/application.test`), вам треба вручну відредагувати відповідний конфігураційний файл Nginx для цього сайту й прибрати обмеження в директиві `listen`. Приберіть префікс `127.0.0.1:` у директиві `listen` для портів 80 і 443.

Якщо ви не виконували `valet secure` для проєкту, ви можете відкрити мережевий доступ до всіх не-HTTPS сайтів, відредагувавши файл `/usr/local/etc/nginx/valet/valet.conf`. Проте якщо ви віддаєте сайт проєкту через HTTPS (тобто виконали для нього `valet secure`), редагуйте файл `~/.config/valet/Nginx/app-name.test`.

Оновивши конфігурацію Nginx, виконайте команду `valet restart`, щоб зміни набрали чинності.

<a name="site-specific-environment-variables"></a>
## Змінні оточення для окремих сайтів

Деякі застосунки на інших фреймворках можуть залежати від серверних змінних оточення, але не давати способу налаштувати ці змінні всередині проєкту. Valet дозволяє задати змінні оточення для окремих сайтів, додавши файл `.valet-env.php` у корінь вашого проєкту. Цей файл має повертати масив пар «сайт / змінна оточення», які буде додано до глобального масиву `$_SERVER` для кожного вказаного в ньому сайту:

```php
<?php

return [
    // Set $_SERVER['key'] to "value" for the laravel.test site...
    'laravel' => [
        'key' => 'value',
    ],

    // Set $_SERVER['key'] to "value" for all sites...
    '*' => [
        'key' => 'value',
    ],
];
```

<a name="proxying-services"></a>
## Проксіювання сервісів

Іноді вам може знадобитися проксіювати домен Valet до іншого сервісу на вашій машині. Наприклад, вам час від часу треба запускати Valet, водночас маючи окремий сайт у Docker; проте Valet і Docker не можуть одночасно займати порт 80.

Щоб це розв'язати, скористайтеся командою `proxy`, щоб створити проксі. Наприклад, ви можете проксіювати весь трафік із `http://elasticsearch.test` до `http://127.0.0.1:9200`:

```shell
# Proxy over HTTP...
valet proxy elasticsearch http://127.0.0.1:9200

# Proxy over TLS + HTTP/2...
valet proxy elasticsearch http://127.0.0.1:9200 --secure
```

Прибрати проксі можна командою `unproxy`:

```shell
valet unproxy elasticsearch
```

Команда `proxies` дозволяє побачити список усіх проксійованих конфігурацій сайтів:

```shell
valet proxies
```

<a name="custom-valet-drivers"></a>
## Власні драйвери Valet

Ви можете написати власний «драйвер» Valet, щоб віддавати застосунки PHP на фреймворку чи CMS, які Valet не підтримує нативно. Під час встановлення Valet створюється каталог `~/.config/valet/Drivers`, що містить файл `SampleValetDriver.php`. Цей файл містить приклад реалізації драйвера, який демонструє, як написати власний. Написання драйвера вимагає реалізувати лише три методи: `serves`, `isStaticFile` та `frontControllerPath`.

Усі три методи отримують аргументами значення `$sitePath`, `$siteName` та `$uri`. `$sitePath` - повний шлях до сайту, який віддається на вашій машині, - наприклад, `/Users/Lisa/Sites/my-project`. `$siteName` - частина домену з «хостом» / «іменем сайту» (`my-project`). `$uri` - URI вхідного запиту (`/foo/bar`).

Коли ви завершите свій драйвер Valet, покладіть його до каталогу `~/.config/valet/Drivers`, дотримуючись угоди іменування `FrameworkValetDriver.php`. Наприклад, якщо ви пишете власний драйвер valet для WordPress, ім'я файлу має бути `WordPressValetDriver.php`.

Погляньмо на приклад реалізації кожного методу, який має містити ваш драйвер Valet.

<a name="the-serves-method"></a>
#### Метод `serves`

Метод `serves` має повертати `true`, якщо ваш драйвер повинен обробити вхідний запит. Інакше метод має повертати `false`. Тож усередині цього методу вам слід спробувати визначити, чи містить заданий `$sitePath` проєкт того типу, який ви намагаєтеся віддавати.

Уявімо, наприклад, що ми пишемо `WordPressValetDriver`. Наш метод `serves` міг би мати такий вигляд:

```php
/**
 * Determine if the driver serves the request.
 */
public function serves(string $sitePath, string $siteName, string $uri): bool
{
    return is_dir($sitePath.'/wp-admin');
}
```

<a name="the-isstaticfile-method"></a>
#### Метод `isStaticFile`

Метод `isStaticFile` має визначити, чи стосується вхідний запит «статичного» файлу - як-от зображення чи стилю. Якщо файл статичний, метод має повернути повний шлях до нього на диску. Якщо вхідний запит не стосується статичного файлу, метод має повернути `false`:

```php
/**
 * Determine if the incoming request is for a static file.
 *
 * @return string|false
 */
public function isStaticFile(string $sitePath, string $siteName, string $uri)
{
    if (file_exists($staticFilePath = $sitePath.'/public/'.$uri)) {
        return $staticFilePath;
    }

    return false;
}
```

> [!WARNING]
> Метод `isStaticFile` буде викликано лише тоді, коли метод `serves` повертає `true` для вхідного запиту, а URI запиту не дорівнює `/`.

<a name="the-frontcontrollerpath-method"></a>
#### Метод `frontControllerPath`

Метод `frontControllerPath` має повертати повний шлях до «фронт-контролера» вашого застосунку - зазвичай це файл «index.php» чи його аналог:

```php
/**
 * Get the fully resolved path to the application's front controller.
 */
public function frontControllerPath(string $sitePath, string $siteName, string $uri): string
{
    return $sitePath.'/public/index.php';
}
```

<a name="local-drivers"></a>
### Локальні драйвери

Якщо ви хочете описати власний драйвер Valet для одного застосунку, створіть файл `LocalValetDriver.php` у кореневому каталозі застосунку. Ваш драйвер може успадковувати базовий клас `ValetDriver` або наявний драйвер для конкретного застосунку - як-от `LaravelValetDriver`:

```php
use Valet\Drivers\LaravelValetDriver;

class LocalValetDriver extends LaravelValetDriver
{
    /**
     * Determine if the driver serves the request.
     */
    public function serves(string $sitePath, string $siteName, string $uri): bool
    {
        return true;
    }

    /**
     * Get the fully resolved path to the application's front controller.
     */
    public function frontControllerPath(string $sitePath, string $siteName, string $uri): string
    {
        return $sitePath.'/public_html/index.php';
    }
}
```

<a name="other-valet-commands"></a>
## Інші команди Valet

<div class="overflow-auto">

| Команда | Опис |
| --- | --- |
| `valet list` | Показує список усіх команд Valet. |
| `valet diagnose` | Виводить діагностику, що допомагає налагоджувати Valet. |
| `valet directory-listing` | Визначає поведінку показу вмісту каталогів. За замовчуванням «off», що віддає для каталогів сторінку 404. |
| `valet forget` | Виконайте цю команду з «припаркованого» каталогу, щоб прибрати його зі списку припаркованих. |
| `valet log` | Показує список логів, які пишуть сервіси Valet. |
| `valet paths` | Показує всі ваші «припарковані» шляхи. |
| `valet restart` | Перезапускає демони Valet. |
| `valet start` | Запускає демони Valet. |
| `valet stop` | Зупиняє демони Valet. |
| `valet trust` | Додає файли sudoers для Brew і Valet, щоб команди Valet виконувалися без запиту пароля. |
| `valet uninstall` | Видаляє Valet: показує інструкції для ручного видалення. Додайте опцію `--force`, щоб агресивно видалити всі ресурси Valet. |

</div>

<a name="valet-directories-and-files"></a>
## Каталоги й файли Valet

Наведена нижче інформація про каталоги й файли може стати в пригоді, коли ви розбираєтеся з проблемами у вашому середовищі Valet:

#### `~/.config/valet`

Містить усю конфігурацію Valet. Вам, можливо, варто зробити резервну копію цього каталогу.

#### `~/.config/valet/dnsmasq.d/`

Цей каталог містить конфігурацію DNSMasq.

#### `~/.config/valet/Drivers/`

Цей каталог містить драйвери Valet. Драйвери визначають, як віддається конкретний фреймворк чи CMS.

#### `~/.config/valet/Nginx/`

Цей каталог містить усі конфігурації сайтів Nginx у Valet. Ці файли перезбираються під час виконання команд `install` і `secure`.

#### `~/.config/valet/Sites/`

Цей каталог містить усі символічні посилання на ваші [прив'язані проєкти](#the-link-command).

#### `~/.config/valet/config.json`

Цей файл - головний конфігураційний файл Valet.

#### `~/.config/valet/valet.sock`

Цей файл - сокет PHP-FPM, який використовує встановлений Valet Nginx. Він існуватиме, лише якщо PHP працює належно.

#### `~/.config/valet/Log/fpm-php.www.log`

Цей файл - користувацький лог помилок PHP.

#### `~/.config/valet/Log/nginx-error.log`

Цей файл - користувацький лог помилок Nginx.

#### `/usr/local/var/log/php-fpm.log`

Цей файл - системний лог помилок PHP-FPM.

#### `/usr/local/var/log/nginx`

Цей каталог містить логи доступу та помилок Nginx.

#### `/usr/local/etc/php/X.X/conf.d`

Цей каталог містить файли `*.ini` з різними налаштуваннями конфігурації PHP.

#### `/usr/local/etc/php/X.X/php-fpm.d/valet-fpm.conf`

Цей файл - конфігураційний файл пулу PHP-FPM.

#### `~/.composer/vendor/laravel/valet/cli/stubs/secure.valet.conf`

Цей файл - стандартна конфігурація Nginx, яка використовується для створення SSL-сертифікатів для ваших сайтів.

<a name="disk-access"></a>
### Доступ до диска

Починаючи з macOS 10.14, [доступ до деяких файлів і каталогів обмежено за замовчуванням](https://manuals.info.apple.com/MANUALS/1000/MA1902/en_US/apple-platform-security-guide.pdf). Ці обмеження стосуються каталогів Desktop, Documents і Downloads. Крім того, обмежено доступ до мережевих і знімних томів. Тому Valet рекомендує тримати каталоги ваших сайтів поза цими захищеними розташуваннями.

Проте якщо ви хочете віддавати сайти з одного з таких розташувань, вам треба надати Nginx «Full Disk Access». Інакше ви можете натрапити на помилки сервера чи іншу непередбачувану поведінку Nginx - особливо при віддачі статичних ресурсів. Зазвичай macOS автоматично запропонує надати Nginx повний доступ до цих розташувань. Або ж ви можете зробити це вручну через `System Preferences` > `Security & Privacy` > `Privacy`, обравши `Full Disk Access`. Далі увімкніть усі записи `nginx` у головній панелі вікна.