Перейти к содержимому

Руководство разработчика по Symfony

Пакет Symfony построен вокруг сервисов. Внедрите PdfFactory, вызывайте create() для каждого документа Portable Document Format (PDF) и используйте построители Messenger для асинхронной генерации. Фабрику можно держать сервисом контейнера, поскольку каждый вызов возвращает новый документ.

Используйте это руководство, когда проектируете контроллеры, сервисы, обработчики Messenger или точки расширения на уровне пакета для nextpdf/symfony.

УровеньКому принадлежитОтветственностьЧего здесь быть не должно
КонтроллерПриложениеАвторизуйте запрос, соберите входные данные и верните PdfResponse.Общий PDF-макет для нескольких сценариев использования.
Сервис приложенияПриложениеЗагрузите доменные данные и выберите построитель.Логика компилятора контейнера Symfony.
Сервис-построительПриложениеРеализуйте PdfBuilderInterface для синхронного построения документа или построения через очередь.Объекты запроса, менеджеры сущностей или несериализуемый контекст.
Пакет Symfonynextpdf/symfonyРегистрирует сервисы, дерево конфигурации, необязательный проход расширения, помощники для ответов и объекты передачи данных (DTO) для Messenger.Политика хранения для конкретного арендатора.
Базовый движокnextpdf/nextpdfСоздаёт и сериализует документ.Поведение ответа Symfony или Messenger.
ЭтапПоведениеДействие разработчика
Загрузка пакетаNextPdfBundle::build() регистрирует обнаружение необязательного расширения.Позвольте Symfony обнаружить пакет или зарегистрируйте его в bundles.php.
Загрузка конфигурацииNextPdfExtension::load() обрабатывает конфигурацию nextpdf: и загружает определения сервисов.Задавайте конфигурацию явно и с учётом окружения.
Использование фабрикиPdfFactory::create() возвращает новый настроенный документ.Не храните документы в сервисах.
Вывод контроллераPdfResponse преобразует готовый документ в ответ.Используйте помощник вместо ручной сборки заголовков.
Отправка через MessengerGeneratePdfMessage содержит класс построителя, путь вывода и сериализуемый контекст.Держите контекст минимальным и совместимым со скалярными значениями.
Обработка сообщенияGeneratePdfHandler получает построитель из локатора сервисов и сохраняет документ.Делайте построители детерминированными и идемпотентными.
ПутьНазначение
src/Pdf/Builder/*Сервисы, реализующие PdfBuilderInterface.
src/Pdf/Data/*Небольшие DTO или массивы, используемые как контекст построителя.
src/Pdf/Storage/*Выбор корня хранилища и политика имён выходных файлов.
src/Controller/*Точки входа для синхронных ответов.
tests/Pdf/*Тесты построителей, ответов, Messenger и конфигурации.

Предпочитайте сервисы-построители статическим вспомогательным функциям. Их легко помечать тегами, декорировать, тестировать и использовать из Messenger.

<?php
namespace App\Pdf\Builder;
use NextPDF\Core\Document;
use NextPDF\Symfony\Message\PdfBuilderInterface;
final readonly class InvoicePdfBuilder implements PdfBuilderInterface
{
public function build(Document $document, array $context): Document
{
$document->setTitle((string) $context['title'])
->addPage()
->writeHtml((string) $context['html']);
return $document;
}
}
<?php
namespace App\Controller;
use App\Pdf\Builder\InvoicePdfBuilder;
use NextPDF\Symfony\Http\PdfResponse;
use NextPDF\Symfony\Service\PdfFactory;
final readonly class InvoiceController
{
public function __invoke(
PdfFactory $factory,
InvoicePdfBuilder $builder,
) {
$document = $builder->build($factory->create(), [
'title' => 'Invoice 1234',
'html' => '<h1>Invoice 1234</h1>',
]);
return PdfResponse::download($document, 'invoice-1234.pdf');
}
}

Держите контекст контроллера небольшим. Если построителю нужно много доменных объектов, перенесите оркестрацию в сервис приложения и передайте построителю DTO или нормализованный массив.

GeneratePdfMessage проверяет класс построителя и путь вывода перед отправкой. При запуске обработчик проверяет путь повторно.

<?php
use App\Pdf\Builder\InvoicePdfBuilder;
use NextPDF\Symfony\Message\GeneratePdfMessage;
$bus->dispatch(new GeneratePdfMessage(
builderClass: InvoicePdfBuilder::class,
outputPath: $projectDir . '/var/pdfs/invoice-1234.pdf',
builderContext: [
'title' => 'Invoice 1234',
'html' => '<h1>Invoice 1234</h1>',
],
));

Не помещайте сущности Doctrine, открытые потоки, замыкания, объекты запроса или объекты сервисов в builderContext.

Точка расширенияИспользуйте её дляОграничение
PdfFactory — декорирование сервисаПрименение значений по умолчанию приложения до того, как документы попадут в контроллеры.Нужно сохранять семантику нового документа.
PdfBuilderInterfaceОпределение построителей для документов, которые отправляются в очередь или используются повторно.Должен возвращать Document.
OptionalExtensionPassПодключение необязательных возможностей Artisan или Premium во время компиляции.Доступность определяется состоянием компиляции контейнера, а не состоянием запроса.
Дерево конфигурации SymfonyЗначения по умолчанию, PDF/A, настройки рендерера, подпись, служба меток времени (TSA) и Messenger.Недопустимая конфигурация должна приводить к ошибке во время сборки контейнера.
GeneratePdfHandler — подключение сервисаОграничение набора построителей, доступных для сообщений из очереди.Локатор сервисов должен предоставлять только утверждённые сервисы-построители.
  1. Добавьте сервис-построитель с детерминированными входными данными.
  2. Используйте PdfFactory::create() в контроллере или сервисе.
  3. Добавьте тест ответа для имени файла, типа содержимого и заголовков.
  4. Зарегистрируйте построитель в Messenger, когда тот же документ нужно генерировать асинхронно.
  5. Добавьте тесты недопустимых сообщений для имени класса, пути вывода и формы контекста.
  6. Добавьте тест компиляции контейнера с минимальной и производственной конфигурацией.
  7. Измеряйте время отрисовки и потребление памяти при тех же настройках PHP, что и в производственной среде.
СбойГде его следует обрабатыватьРекомендуемый ответ
Недопустимая конфигурацияКомпиляция контейнера.Прервите развёртывание до того, как трафик дойдёт до приложения.
Отсутствует сервис-построительТесты обработчика Messenger и теги сервисов.Завершите сообщение с ошибкой и оповестите ответственную команду.
Небезопасный путь выводаКонструктор сообщения и политика хранения.Отклоните его до отправки; сохраните проверку в обработчике как дополнительный уровень защиты.
Необязательное расширение недоступноПроход компилятора и поведение фабрики.Отключите необязательную возможность или сделайте установку явной.
Сбой преобразования сервиса или отрисовкиГраница построителя.По умолчанию завершайте с ошибкой, если для сценария не задокументирован запасной вариант.
АспектПо умолчаниюКогда переопределять
Время жизни фабрикиСервис контейнера.Оставьте как есть; фабрика безопасна, потому что создаёт новые документы.
Время жизни документаОдна единица работы.Никогда не используйте его совместно между запросами или сообщениями.
Проверка пути выводаКонструктор сообщения и обработчик.Добавьте ограничения по арендатору или корню хранилища в коде приложения.
Имя файла ответаdocument.pdf.Переопределите его очищенными бизнес-идентификаторами.
Транспорт Messengerasync.Используйте выделенный транспорт, когда работа с PDF ресурсоёмкая.
  • Тесты контейнера компилируют пакет с минимальной и производственной конфигурацией.
  • Тесты ответов проверяют заголовки безопасности и обработку имени файла.
  • Тесты Messenger проверяют, что недопустимые пути и недопустимые имена классов построителей приводят к ошибке до отправки.
  • Тесты обработчика используют реальный сервис-построитель и временный каталог вывода.
  • Тесты построителей отрисовывают репрезентативный документ и сохраняют его с правами доступа файловой системы, близкими к производственным.
  • Тесты необязательных расширений охватывают случаи недоступности Artisan, недоступности Premium и поведение настроенного профиля PDF/A.