Конфигурация и передача Laravel-портала
Оглавление серии «Laravel 13 и PHP: портал «Заявка»»
Портал «Заявка» теперь связывает маршрут, Blade, Eloquent, сессию, policy, формы, обсуждение, транзакцию, очередь, уведомление, PDF и JSON. Последний результат серии — передача этой системы как понятного исходного проекта с условиями выпуска. Добавим контролируемое скачивание фоновой квитанции и зафиксируем окружение. Никакие действия размещения, установки, миграции или перезапуска в этой работе не выполнялись.
Важно передать и ограничения. Это кабинет одного владельца с подготовленным учебным аккаунтом, без регистрации организации, административных ролей и повторного открытия заявки. Очередь выполняет вспомогательную квитанцию после commit, но не обещает критическую доставку при любом отказе. Архив содержит последовательные исходные изменения на официальном каркасе, а composer.lock и vendor требуют реальной установки в выбранной среде.
Частная квитанция
Создадим полный app/Http/Controllers/TicketReceiptController.php. Сначала проверяется policy view, затем закрытое состояние и существование файла. Внутренний путь вычисляется по id, а не берётся из query клиента. Отсутствие квитанции получает понятный 409 о неготовом результате, хотя заявка уже может быть успешно закрыта.
<?php
namespace App\Http\Controllers;
use App\Models\Ticket;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
class TicketReceiptController extends Controller
{
public function __invoke(Ticket $ticket)
{
Gate::authorize('view', $ticket);
abort_unless($ticket->status === 'closed', 409, 'Заявка ещё не закрыта.');
$path = "receipts/{$ticket->id}.txt";
abort_unless(Storage::disk('local')->exists($path), 409, 'Квитанция пока недоступна.');
return Storage::disk('local')->download($path, "ticket-{$ticket->id}.txt");
}
}
Добавьте импорт контроллера и маршрут ниже. В show перед обсуждением добавляется ссылка для closed. Полные итоговые routes и шаблон архивного состояния содержат эти изменения.
Route::get('/tickets/{ticket}/receipt', TicketReceiptController::class)->whereNumber('ticket')->middleware('auth')->name('receipt.download');
@if($ticket->status === 'closed')<a href="{{ route('receipt.download', $ticket) }}">Квитанция после фоновой подготовки</a>@endif
Ожидаемый готовый файл выдаётся только владельцу. До обработки очереди ссылка может вернуть неготовность; исчезновение файла из-за другой причины также означает недоступность результата, поэтому текст не утверждает точный диагноз worker. Если нужен статус failed или pending, его следует хранить отдельно и обновлять процессом подготовки. Само exists различает лишь наличие текущего файла, а не всю историю задания.
Окружение выпуска
Ниже полный .env.production.example. Он не содержит ключа или действующих секретов. SQLite оставлен учебным профилем; для рабочей общей базы надо выбрать серверную СУБД и проверить миграции и поведение условных обновлений. HTTPS отражён в APP_URL и secure cookie, debug выключен.
APP_NAME=Заявка
APP_ENV=production
APP_KEY=
APP_DEBUG=false
APP_URL=https://portal.example.test
DB_CONNECTION=sqlite
SESSION_DRIVER=file
SESSION_SECURE_COOKIE=true
QUEUE_CONNECTION=database
FILESYSTEM_DISK=local
MAIL_MAILER=log
Документация deployment Laravel описывает серверные требования и кеши конфигурации. Для нашего проекта корень веб-сервера должен указывать на public. storage и bootstrap/cache требуют необходимых прав записи процесса, а частный local-диск должен сохраняться между выпусками. Копирование нового кода не является основанием удалить документы пользователей и квитанции.
Создадим полный RELEASE.md. Это договор следующего исполнителя, а не журнал прошедшей проверки.
# Передача учебного портала
Профиль: PHP 8.4.25; laravel/framework 13.35.0; официальный каркас из base/README.md.
Example execution: not_run. Composer/PHP/миграции/HTTP/SQL/worker не запускались.
Получить каркас, наложить base и lesson-01 ... lesson-16. README и snippets не применять.
После собственной установки сохранить реальный composer.lock; vendor отсутствует в архиве.
Создать отдельную учебную базу; применить стандартные и четыре новые миграции.
LAB_PASSWORD и local seeder использовать только для localhost, не для production.
Выдать свой APP_KEY; внешняя среда HTTPS, APP_DEBUG=false, secure session cookies.
Корень сервера public; частные local attachments/receipts не публиковать.
Отдельно организовать database worker, failed_jobs и перезапуск worker после смены кода.
Согласовать worker timeout и queue retry_after. Кеш конфигурации строить для целевой среды.
Сверить: свой/чужой список и карточка; вход/выход; неверная форма; закрытое редактирование;
повтор close без новой closure; ожидающий/готовый receipt; личные notifications;
одиночное PDF-вложение и защищённое скачивание; JSON без внутренних путей.
Нет регистрации/reset/почтовой доставки/ролей организаций/transactional outbox/write API.
Откат кода не является восстановлением данных; down может удалять пользовательские строки.
Кеш и источник настройки
env следует читать в конфигурационных файлах, а приложение — через config. Наш local seeder читает LAB_PASSWORD до учебной подготовки и не является кодом production-запроса. После кеширования конфигурации прямые обращения env в произвольном сервисе могут перестать соответствовать ожиданию разработчика. Поэтому не переносите этот учебный способ задания пароля в постоянную бизнес-логику.
Кеш маршрутов и представлений относится к выбранному окружению. Его нельзя скопировать из одной машины с другими путями и настройками, не определив порядок подготовки. В курсе такие команды описаны как последующие действия исполнителя, а не запускались. Отсутствие новых ошибок в редакторе не доказывает пригодность кеша или наличие PHP-расширений. Проверка реальной среды остаётся материальной границей передачи.
Очередь и частное хранение
Долгоживущий worker должен получить новый код при выпуске, иначе HTTP и job могут работать по разным версиям. Также нужны наблюдение failed_jobs и понятный способ повторить допустимую задачу после исправления ресурса. Квитанция использует предсказуемый путь и неизменяемую закрытую запись, но afterCommit не устраняет промежуток доставки при отказе процесса. Для критического документа нужно отдельное устойчивое намерение в базе.
Частные файлы не входят в web root и требуют policy при выдаче. В нескольких экземплярах сервера локальный диск одной машины не становится общим автоматически. Если запрос скачивания попадает на другой узел, файла может не быть. Для такой среды понадобится общий диск или объектное хранилище с согласованной выдачей. Учебный профиль ориентирован на одну локальную установку и не обещает горизонтальное масштабирование одним изменением APP_URL.
Проверяемый результат и откат
Полезный неправильный случай — объявить выпуск удачным по странице списка, не проверив чужую карточку, повтор close и неготовую квитанцию. Эти сценарии соответствуют главным причинам архитектуры: право объекта, атомарная история и отделение фонового результата. В RELEASE они перечислены как ожидаемая самостоятельная приёмка. Автоматические тесты и реальные запросы не выполнялись по границам текущей работы.
Rollback миграции может удалить tickets, comments или путь вложения. Откат версии кода не возвращает удалённое содержимое базы и диска. Поэтому перед изменением структуры нужен собственный план резервирования и восстановления, согласованный с рабочими данными. Не предлагайте down как универсальную кнопку отмены. В нашей серии обратные методы описывают структуру учебных миграций, а не безопасное восстановление любой истории.
Завершённый исходник позволяет объяснить каждую границу: сессия задаёт человека, policy проверяет заявку, Form Request ограничивает намерение, SQL-условие защищает переход, transaction связывает историю, очередь отделяет работу, resource ограничивает JSON. Дальнейшее расширение должно сохранять эти связи и вводить новый результат явно. Тогда портал можно развивать без смешения пользовательского ввода, текущей записи и неподтверждённого фонового эффекта.
База, исходники и пользовательские файлы
Выпуск содержит три разных набора: код приложения, строки базы и частные файлы. Новый архив исходников заменяет только первый. Миграции изменяют второй по явному плану, а диск третьего должен сохраняться и резервироваться отдельно. Если развернуть новый код в чистом каталоге без доступа к старому local storage, карточки могут ссылаться на исчезнувшие PDF. Это инфраструктурное расхождение, не ошибка policy.
Для восстановления также недостаточно иметь только копию базы: attachment_path не содержит сам документ. Квитанцию в нынешнем учебном договоре можно подготовить повторно из закрытой заявки, а оригинальный PDF без отдельной копии восстановить нельзя. Такая разница влияет на резервирование. Согласованный план передачи должен перечислять реально сохраняемые ресурсы и не обещать одинаковую обратимость любого результата.
После выбора серверной СУБД необходимо повторно оценить условные update и правила внешних ключей в её среде. Изменение DB_CONNECTION не является доказанной миграцией платформы. Серия передаёт понятные SQL-намерения, но фактическая совместимость окружения остаётся задачей следующей самостоятельной подготовки.
Для самостоятельного воспроизведения результата «Зафиксировать окружение, миграции и границы выпуска» выберите lesson-16 в архиве исходников. Статус выполнения этого состояния: not_run.