HTTP Відповіді

Створення відповідей

Рядки та масиви

Всі маршрути та контролери повинні повертати відповідь, яка буде відправлена назад до браузера користувача. Laravel надає кілька різних способів повернення відповідей. Найбільш базовою відповіддю є повернення рядка з маршруту або контролера. Фреймворк автоматично перетворить рядок у повну HTTP-відповідь:

Route::get('/', function () {
    return 'Hello World';
});

Окрім повернення рядків з ваших маршрутів та контролерів, ви також можете повертати масиви. Фреймворк автоматично перетворить масив у JSON-відповідь:

Route::get('/', function () {
    return [1, 2, 3];
});

Чи знаєте ви, що ви також можете повертати Eloquent колекції з ваших маршрутів або контролерів? Вони автоматично будуть конвертовані в JSON. Спробуйте!

Об'єкти Відповіді

Зазвичай, ви не будете просто повертати прості рядки або масиви з ваших дій маршруту. Натомість, ви будете повертати повні екземпляри Illuminate\Http\Response або представлення.

Повернення повного екземпляра Response дозволяє налаштувати код статусу HTTP та заголовки відповіді. Екземпляр Response успадковується від класу Symfony\Component\HttpFoundation\Response, який надає різноманітні методи для створення HTTP-відповідей:

Route::get('/home', function () {
    return response('Hello World', 200)
        ->header('Content-Type', 'text/plain');
});

Eloquent Моделі та Колекції

Ви також можете повертати моделі та колекції Eloquent ORM безпосередньо з ваших маршрутів та контролерів. Коли ви це робите, Laravel автоматично конвертує моделі та колекції у JSON-відповіді, враховуючи приховані атрибути моделі:

use App\Models\User;
 
Route::get('/user/{user}', function (User $user) {
    return $user;
});

Додавання заголовків до відповідей

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

return response($content)
    ->header('Content-Type', $type)
    ->header('X-Header-One', 'Header Value')
    ->header('X-Header-Two', 'Header Value');

Або ви можете використовувати метод withHeaders, щоб вказати масив заголовків, які будуть додані до відповіді:

return response($content)
    ->withHeaders([
        'Content-Type' => $type,
        'X-Header-One' => 'Header Value',
        'X-Header-Two' => 'Header Value',
    ]);

Middleware контроль кешування

Laravel включає middleware cache.headers, яке може бути використане для швидкого встановлення заголовка Cache-Control для групи маршрутів. Директиви повинні бути надані, використовуючи "snake case" еквівалент відповідної директиви cache-control і повинні бути розділені крапкою з комою. Якщо etag вказано в списку директив, MD5 хеш вмісту відповіді буде автоматично встановлено як ідентифікатор ETag:

Route::middleware('cache.headers:public;max_age=2628000;etag')->group(function () {
    Route::get('/privacy', function () {
        // ...
    });
 
    Route::get('/terms', function () {
        // ...
    });
});

Прикріплення Cookies до Відповідей

Ви можете прикріпити cookie до вихідного екземпляра Illuminate\Http\Response, використовуючи метод cookie. Ви повинні передати ім'я, значення та кількість хвилин, протягом яких cookie вважатиметься дійсним, цьому методу:

return response('Hello World')->cookie(
    'name', 'value', $minutes
);

Метод cookie також приймає кілька додаткових аргументів, які використовуються рідше. Зазвичай ці аргументи мають таке ж призначення та значення, як і аргументи, які були б передані до нативного методу PHP setcookie:

return response('Hello World')->cookie(
    'name', 'value', $minutes, $path, $domain, $secure, $httpOnly
);

Якщо ви хочете переконатися, що cookie відправляється з вихідним відгуком, але у вас ще немає екземпляра цього відгуку, ви можете використовувати фасад Cookie для "черги" cookie для приєднання до відгуку, коли він буде відправлений. Метод queue приймає аргументи, необхідні для створення екземпляра cookie. Ці cookie будуть приєднані до вихідного відгуку перед тим, як він буде відправлений до браузера:

use Illuminate\Support\Facades\Cookie;
 
Cookie::queue('name', 'value', $minutes);

Якщо ви хочете створити екземпляр Symfony\Component\HttpFoundation\Cookie, який можна буде прикріпити до екземпляра відповіді пізніше, ви можете використовувати глобальний хелпер cookie. Цей cookie не буде відправлений назад клієнту, якщо він не прикріплений до екземпляра відповіді:

$cookie = cookie('name', 'value', $minutes);
 
return response('Hello World')->cookie($cookie);

Дострокове завершення терміну дії Cookies

Ви можете видалити cookie, встановивши його термін дії за допомогою методу withoutCookie у вихідному відповіді:

return response('Hello World')->withoutCookie('name');

Якщо у вас ще немає екземпляра вихідного відповіді, ви можете використовувати метод expire фасаду Cookie для завершення терміну дії cookie:

Cookie::expire('name');

Cookies та шифрування

За замовчуванням, завдяки middleware Illuminate\Cookie\Middleware\EncryptCookies, всі cookies, згенеровані Laravel, шифруються та підписуються, щоб їх не можна було змінити або прочитати клієнтом. Якщо ви хочете вимкнути шифрування для підмножини cookies, згенерованих вашим застосунком, ви можете використовувати метод encryptCookies у файлі bootstrap/app.php вашого застосунку:

->withMiddleware(function (Middleware $middleware) {
    $middleware->encryptCookies(except: [
        'cookie_name',
    ]);
})

Перенаправлення

Відповіді перенаправлення є екземплярами класу Illuminate\Http\RedirectResponse і містять необхідні заголовки для перенаправлення користувача на інший URL. Існує кілька способів створення екземпляра RedirectResponse. Найпростіший метод - використовувати глобальний хелпер redirect:

Route::get('/dashboard', function () {
    return redirect('/home/dashboard');
});

Іноді ви можете захотіти перенаправити користувача на попереднє місце, наприклад, коли надіслана форма є недійсною. Ви можете зробити це, використовуючи глобальну функцію-хелпер back. Оскільки ця функція використовує сесію, переконайтеся, що маршрут, який викликає функцію back, використовує групу middleware web:

Route::post('/user/profile', function () {
// Валідувати запит...
 
return back()->withInput();
});

Перенаправлення на іменовані маршрути

Коли ви викликаєте хелпер redirect без параметрів, повертається екземпляр Illuminate\Routing\Redirector, що дозволяє викликати будь-який метод на екземплярі Redirector. Наприклад, щоб згенерувати RedirectResponse до іменованого маршруту, ви можете використовувати метод route:

return redirect()->route('login');

Якщо ваш маршрут має параметри, ви можете передати їх як другий аргумент методу route:

// Для маршруту з таким URI: /profile/{id}
 
return redirect()->route('profile', ['id' => 1]);

Заповнення параметрів за допомогою моделей Eloquent

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

// Для маршруту з таким URI: /profile/{id}
 
return redirect()->route('profile', [$user]);

Якщо ви хочете налаштувати значення, яке розміщується в параметрі маршруту, ви можете вказати стовпець у визначенні параметра маршруту (/profile/{id:slug}) або ви можете перевизначити метод getRouteKey у вашій Eloquent моделі:

/**
* Отримати значення ключа маршруту моделі.
*/
public function getRouteKey(): mixed
{
return $this->slug;
}

Перенаправлення до методів контролера

Ви також можете створювати перенаправлення до дій контролера. Для цього передайте контролер і назву дії до методу action:

use App\Http\Controllers\UserController;
 
return redirect()->action([UserController::class, 'index']);

Якщо ваш маршрут контролера вимагає параметрів, ви можете передати їх як другий аргумент методу action:

return redirect()->action(
    [UserController::class, 'profile'], ['id' => 1]
);

Перенаправлення на зовнішні домени

Іноді вам може знадобитися перенаправити на домен за межами вашого застосунку. Ви можете зробити це, викликавши метод away, який створює RedirectResponse без додаткового кодування URL, валідації або перевірки:

return redirect()->away('https://www.google.com');

Перенаправлення з тимчасовими даними сесії

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

Route::post('/user/profile', function () {
    // ...
 
    return redirect('/dashboard')->with('status', 'Profile updated!');
});

Після того, як користувача буде перенаправлено, ви можете відобразити спалахнуте повідомлення з сесії. Наприклад, використовуючи синтаксис Blade:

@if (session('status'))
    <div class="alert alert-success">
        {{ session('status') }}
    </div>
@endif

Перенаправлення з введеними даними

Ви можете використовувати метод withInput, наданий екземпляром RedirectResponse, щоб зберегти в сесії дані введення поточного запиту перед перенаправленням користувача на нове місце. Це зазвичай робиться, якщо користувач зіткнувся з помилкою валідації. Після того, як введення було збережено в сесії, ви можете легко отримати його під час наступного запиту, щоб знову заповнити форму:

return back()->withInput();

Інші типи відповідей

The response хелпер може бути використаний для генерації інших типів екземплярів відповіді. Коли хелпер response викликається без аргументів, повертається реалізація Illuminate\Contracts\Routing\ResponseFactory контракту. Цей контракт надає декілька корисних методів для генерації відповідей.

Відповіді з представленнями

Якщо вам потрібно контролювати статус і заголовки відповіді, але також потрібно повернути представлення як вміст відповіді, ви повинні використовувати метод view:

return response()
    ->view('hello', $data, 200)
    ->header('Content-Type', $type);

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

JSON Відповіді

Метод json автоматично встановить заголовок Content-Type на application/json, а також перетворить переданий масив у JSON за допомогою PHP-функції json_encode:

return response()->json([
    'name' => 'Abigail',
    'state' => 'CA',
]);

Якщо ви хочете створити JSONP-відповідь, ви можете використовувати метод json у поєднанні з методом withCallback:

return response()
    ->json(['name' => 'Abigail', 'state' => 'CA'])
    ->withCallback($request->input('callback'));

Завантаження файлів

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

return response()->download($pathToFile);
 
return response()->download($pathToFile, $name, $headers);

Symfony HttpFoundation, який керує завантаженням файлів, вимагає, щоб файл, що завантажується, мав ім'я файлу в ASCII.

Файлові відповіді

Метод file може бути використаний для відображення файлу, такого як зображення або PDF, безпосередньо в браузері користувача замість ініціювання завантаження. Цей метод приймає абсолютний шлях до файлу як свій перший аргумент і масив заголовків як свій другий аргумент:

return response()->file($pathToFile);
 
return response()->file($pathToFile, $headers);

Потокові відповіді

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

Route::get('/stream', function () {
return response()->stream(function (): void {
foreach (['developer', 'admin'] as $string) {
echo $string;
ob_flush();
flush();
sleep(2); // Імітувати затримку між фрагментами...
}
}, 200, ['X-Accel-Buffering' => 'no']);
});

Для зручності, якщо замикання, яке ви надаєте методу stream, повертає Generator, Laravel автоматично очистить вихідний буфер між рядками, що повертаються генератором, а також вимкне буферизацію виходу Nginx:

Route::post('/chat', function () {
    return response()->stream(function (): void {
        $stream = OpenAI::client()->chat()->createStreamed(...);
 
        foreach ($stream as $response) {
            yield $response->choices[0];
        }
    });
});

Використання Стрімінгових Відповідей

Потокові відповіді можуть бути використані за допомогою Laravel пакету stream npm, який надає зручний API для взаємодії з потоками відповідей та подій Laravel. Щоб почати, встановіть пакет @laravel/stream-react або @laravel/stream-vue:

npm install @laravel/stream-react
npm install @laravel/stream-vue

Потім, useStream може бути використано для споживання потоку подій. Після надання URL вашого потоку, хук автоматично оновить data з об'єднаною відповіддю, коли вміст повертається з вашого Laravel застосунку:

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>
);
}
<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>

Коли дані відправляються назад до потоку через send, активне з'єднання з потоком скасовується перед відправкою нових даних. Всі запити відправляються як JSON-запити POST.

Оскільки хук useStream робить запит POST до вашого застосунку, потрібен дійсний CSRF токен. Найпростіший спосіб надати CSRF токен - це включити його через тег meta у head макету вашого застосунку.

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

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>;
}
<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>

onResponse викликається після успішної початкової відповіді від потоку, і необроблена Response передається в зворотний виклик. onData викликається при отриманні кожного фрагмента - поточний фрагмент передається в зворотний виклик. onFinish викликається, коли потік завершився і коли виникає помилка під час циклу отримання / читання.

За замовчуванням запит не надсилається до потоку під час ініціалізації. Ви можете передати початкове навантаження до потоку, використовуючи опцію initialInput:

import { useStream } from "@laravel/stream-react";
 
function App() {
const { data } = useStream("chat", {
initialInput: {
message: "Introduce yourself.",
},
});
 
return <div>{data}</div>;
}
<script setup lang="ts">
import { useStream } from "@laravel/stream-vue";
 
const { data } = useStream("chat", {
initialInput: {
message: "Introduce yourself.",
},
});
</script>
 
<template>
<div>{{ data }}</div>
</template>

Щоб скасувати потік вручну, ви можете використовувати метод cancel, що повертається з хука:

import { useStream } from "@laravel/stream-react";
 
function App() {
const { data, cancel } = useStream("chat");
 
return (
<div>
<div>{data}</div>
<button onClick={cancel}>Cancel</button>
</div>
);
}
<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>

Кожного разу, коли використовується хук useStream, генерується випадковий id для ідентифікації потоку. Цей id відправляється назад на сервер з кожним запитом у заголовку X-STREAM-ID. Коли ви використовуєте той самий потік з декількох компонентів, ви можете читати і записувати в потік, надаючи свій власний id:

// 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>
);
}
<!-- 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>

Потокові JSON-відповіді

Якщо вам потрібно передавати JSON-дані поступово, ви можете скористатися методом streamJson. Цей метод особливо корисний для великих наборів даних, які потрібно надсилати поступово до браузера у форматі, який може бути легко розібраний JavaScript:

use App\Models\User;
 
Route::get('/users.json', function () {
    return response()->streamJson([
        'users' => User::cursor(),
    ]);
});

The useJsonStream хук ідентичний useStream хуку, за винятком того, що він спробує розібрати дані як JSON після завершення потокової передачі:

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>
);
}
<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>

Потоки подій (SSE)

Метод eventStream може бути використаний для повернення потокової відповіді серверних подій (SSE) з використанням типу вмісту text/event-stream. Метод eventStream приймає замикання, яке повинно yield відповідати на потік, коли відповіді стають доступними:

Route::get('/chat', function () {
    return response()->eventStream(function () {
        $stream = OpenAI::client()->chat()->createStreamed(...);
 
        foreach ($stream as $response) {
            yield $response->choices[0];
        }
    });
});

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

use Illuminate\Http\StreamedEvent;
 
yield new StreamedEvent(
    event: 'update',
    data: $response->choices[0],
);

Обробка потоків подій

Потоки подій можуть оброблятися за допомогою Laravel пакету stream npm, який надає зручний API для взаємодії з потоками подій Laravel. Щоб почати, встановіть пакет @laravel/stream-react або @laravel/stream-vue:

npm install @laravel/stream-react
npm install @laravel/stream-vue

Потім, useEventStream може бути застосовано для використання потоку подій. Після надання URL вашого потоку, хук автоматично оновить message з об'єднаною відповіддю, коли повідомлення повертаються з вашого Laravel застосунку:

import { useEventStream } from "@laravel/stream-react";
 
function App() {
const { message } = useEventStream("/chat");
 
return <div>{message}</div>;
}
<script setup lang="ts">
import { useEventStream } from "@laravel/stream-vue";
 
const { message } = useEventStream("/chat");
</script>
 
<template>
  <div>{{ message }}</div>
</template>

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

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>;
}
<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>

Потоки подій також можуть бути вручну використані через об'єкт EventSource на фронтенді вашого застосунку. Метод eventStream автоматично надішле оновлення </stream> до потоку подій, коли потік буде завершено:

const source = new EventSource('/chat');
 
source.addEventListener('update', (event) => {
    if (event.data === '</stream>') {
        source.close();
 
        return;
    }
 
    console.log(event.data);
});

Щоб налаштувати фінальну подію, яка надсилається до потоку подій, ви можете надати екземпляр StreamedEvent аргументу endStreamWith методу eventStream:

return response()->eventStream(function () {
    // ...
}, endStreamWith: new StreamedEvent(event: 'update', data: '</stream>'));

Потокові завантаження

Іноді ви можете захотіти перетворити рядкову відповідь певної операції на відповідь, що завантажується, без необхідності записувати вміст операції на диск. Ви можете використовувати метод streamDownload у цьому випадку. Цей метод приймає зворотний виклик, ім'я файлу та необов'язковий масив заголовків як свої аргументи:

use App\Services\GitHub;
 
return response()->streamDownload(function () {
    echo GitHub::api('repo')
        ->contents()
        ->readme('laravel', 'laravel')['contents'];
}, 'laravel-readme.md');

Макроси Відповідей

Якщо ви хочете визначити власну відповідь, яку можна повторно використовувати в різних маршрутах і контролерах, ви можете використовувати метод macro на фасаді Response. Зазвичай, ви повинні викликати цей метод з методу boot одного з ваших Сервіс-провайдерів застосунку, таких як Сервіс-провайдер App\Providers\AppServiceProvider:

<?php
 
namespace App\Providers;
 
use Illuminate\Support\Facades\Response;
use Illuminate\Support\ServiceProvider;
 
class AppServiceProvider extends ServiceProvider
{
/**
* Ініціалізувати будь-які сервіси застосунку.
*/
public function boot(): void
{
Response::macro('caps', function (string $value) {
return Response::make(strtoupper($value));
});
}
}

Функція macro приймає ім'я як свій перший аргумент і замикання як другий аргумент. Замикання макросу буде виконано при виклику імені макросу з реалізації ResponseFactory або хелпера response:

return response()->caps('foo');