1000) { throw new \RuntimeException('Content too long for a QR code (max 1000 characters).'); } // error-correction: L ~7%, M ~15%, Q ~25%, H ~30% $level = strtoupper((string) ($params['level'] ?? 'M')); if (! in_array($level, ['L', 'M', 'Q', 'H'], true)) { $level = 'M'; } $size = (int) ($params['size'] ?? 300); $size = min(1000, max(120, $size)); $options = new QROptions([ 'outputType' => QRCode::OUTPUT_IMAGE_PNG, 'eccLevel' => constant(QRCode::class . '::ECC_' . $level), 'scale' => max(3, (int) round($size / 33)), 'imageBase64' => false, 'quality' => 90, ]); [, $out] = self::output($job, 'png'); $png = (new QRCode($options))->render($content); if (! str_starts_with((string) $png, "\x89PNG")) { throw new \RuntimeException('QR rendering failed.'); } file_put_contents($out, $png); return ['file' => basename($out), 'name' => 'qr-code.png', 'ext' => 'png']; } private static function output(array $job, string $ext): array { $dir = \App\Libraries\Pipeline::storageDir(); $file = \App\Libraries\Pipeline::safeName($job['id'], $ext); return [$file, $dir . '/' . $file]; } }