vcard4reseller/backend/src/Command/RenderCardCommand.php
Thomas Peterson 67e4353c8d Skalierbarkeit: Druck-Assets in S3-Object-Storage (Flysystem)
Macht die App-Nodes zustandslos (horizontal skalierbar): Hintergrund-PDFs
und Schriften liegen nicht mehr lokal, sondern im S3-kompatiblen Object
Storage (Flysystem + async-aws). In der DB stehen Storage-Keys.

- flysystem-bundle + async-aws (Storage "card_assets"), env-getrieben
  (S3_ENDPOINT/REGION/BUCKET/KEY/SECRET/PATH_STYLE) → lokal MinIO, prod Hetzner OS
- CardAssetUploadController: Upload/Read/Delete über Storage; GET streamt PDF
- CardPdfRenderer: liest Hintergrund (FPDI StreamReader) & Schriften (Temp-Datei) aus S3
- docker-compose: minio + minio-init (Bucket) + zweiter App-Node php2 (Profil scale-test)
- app:render-card Command für den Cross-Node-Nachweis

Verifiziert: Upload über Node 1 → identisches PDF-Render (51897 B, mit
Hintergrund) auf Node 2, der nur DB + Object Storage liest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 20:56:51 +02:00

63 lines
2.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Command;
use App\Repository\CardTemplateRepository;
use App\Repository\EmployeeRepository;
use App\Service\CardPdfRenderer;
use App\Service\CardTemplateFactory;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Test-Utility: rendert die Visitenkarte eines Mitarbeiters und schreibt sie
* nach /tmp. Dient dem Skalierungs-Nachweis (auf mehreren Nodes ausführen
* jeder Node liest dieselbe DB + denselben Object Storage).
*/
#[AsCommand(name: 'app:render-card', description: 'Rendert eine Visitenkarte (Skalierungs-Test).')]
final class RenderCardCommand extends Command
{
public function __construct(
private readonly EmployeeRepository $employees,
private readonly CardTemplateRepository $templates,
private readonly CardTemplateFactory $factory,
private readonly CardPdfRenderer $renderer,
) {
parent::__construct();
}
protected function configure(): void
{
$this->addArgument('slug', InputArgument::REQUIRED, 'Employee-Slug');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$employee = $this->employees->findOneBy(['slug' => $input->getArgument('slug')]);
if (null === $employee) {
$output->writeln('<error>Mitarbeiter nicht gefunden.</error>');
return Command::FAILURE;
}
$template = $this->templates->findCardForCompany($employee->getCompany()) ?? $this->factory->default();
$pdf = $this->renderer->render($employee, $template);
$file = sprintf('/tmp/render-%s.pdf', gethostname());
file_put_contents($file, $pdf);
$output->writeln(sprintf(
'Node %s: %d bytes, Hintergrund=%s, Datei=%s',
gethostname(),
strlen($pdf),
$template->getBackgroundPath() ? 'ja' : 'nein',
$file,
));
return Command::SUCCESS;
}
}