Files
michaelschiemer/src/Framework/QrCode/QrCodeGenerator.php
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

187 lines
5.4 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\QrCode;
use InvalidArgumentException;
/**
* QR Code Generator
*
* Main facade for generating QR codes from data.
* Orchestrates encoding, matrix generation, and rendering.
*/
final readonly class QrCodeGenerator
{
public function __construct(
private DataEncoder $dataEncoder,
private MatrixGenerator $matrixGenerator,
private SvgRenderer $svgRenderer
) {
}
/**
* Create QR code generator with default components
*/
public static function create(): self
{
return new self(
dataEncoder: new DataEncoder(),
matrixGenerator: new MatrixGenerator(),
svgRenderer: new SvgRenderer()
);
}
/**
* Create QR code generator for TOTP URLs
*/
public static function forTotp(): self
{
return new self(
dataEncoder: new DataEncoder(),
matrixGenerator: new MatrixGenerator(),
svgRenderer: SvgRenderer::withModuleSize(4)->withQuietZone(2)
);
}
/**
* Generate QR code as SVG string
*/
public function generateSvg(
string $data,
?ErrorCorrectionLevel $errorLevel = null,
?QrCodeVersion $version = null
): string {
$matrix = $this->generateMatrix($data, $errorLevel, $version);
return $this->svgRenderer->render($matrix);
}
/**
* Generate QR code as SVG data URI
*/
public function generateDataUri(
string $data,
?ErrorCorrectionLevel $errorLevel = null,
?QrCodeVersion $version = null
): string {
$matrix = $this->generateMatrix($data, $errorLevel, $version);
return $this->svgRenderer->renderAsDataUri($matrix);
}
/**
* Generate QR code matrix
*/
public function generateMatrix(
string $data,
?ErrorCorrectionLevel $errorLevel = null,
?QrCodeVersion $version = null
): QrCodeMatrix {
if (empty($data)) {
throw new InvalidArgumentException('QR code data cannot be empty');
}
$errorLevel = $errorLevel ?? ErrorCorrectionLevel::forTotp();
$version = $version ?? $this->determineOptimalVersion($data, $errorLevel);
// Encode data into bit stream
$encodedData = $this->dataEncoder->encode($data, $version, $errorLevel);
// Generate matrix from encoded data
return $this->matrixGenerator->generateMatrix($encodedData, $version, $errorLevel);
}
/**
* Create generator with custom renderer
*/
public function withRenderer(SvgRenderer $renderer): self
{
return new self(
dataEncoder: $this->dataEncoder,
matrixGenerator: $this->matrixGenerator,
svgRenderer: $renderer
);
}
/**
* Get recommended configuration for data
*/
public function analyzeData(string $data): array
{
if (empty($data)) {
throw new InvalidArgumentException('Cannot analyze empty data');
}
$mode = DataMode::detectForData($data);
$errorLevel = ErrorCorrectionLevel::forTotp();
$version = $this->determineOptimalVersion($data, $errorLevel);
return [
'data_length' => strlen($data),
'detected_mode' => $mode,
'recommended_error_level' => $errorLevel,
'optimal_version' => $version->getVersion(),
'matrix_size' => $version->getModuleCount(),
'data_capacity' => $version->getDataCapacity($errorLevel),
'efficiency' => $mode->getEfficiency(),
'capacity_used' => round((strlen($data) / $version->getDataCapacity($errorLevel)) * 100, 1),
];
}
/**
* Generate QR code for TOTP URI
*/
public function generateTotpQrCode(string $totpUri): string
{
// Validate TOTP URI format
if (! str_starts_with($totpUri, 'otpauth://totp/')) {
throw new InvalidArgumentException('Invalid TOTP URI format');
}
// Use appropriate settings for TOTP (allow larger versions)
$errorLevel = ErrorCorrectionLevel::M; // Medium error correction for TOTP
return $this->generateSvg($totpUri, $errorLevel, null); // Auto-detect version
}
/**
* Determine the optimal QR code version for given data
*/
private function determineOptimalVersion(string $data, ErrorCorrectionLevel $errorLevel): QrCodeVersion
{
$dataLength = strlen($data);
// Try to find the smallest version that can fit the data
return QrCodeVersion::forDataLength($dataLength, $errorLevel);
}
/**
* Get generator configuration
*/
public function getConfiguration(): array
{
return [
'encoder' => DataEncoder::class,
'matrix_generator' => MatrixGenerator::class,
'renderer' => $this->svgRenderer->getConfiguration(),
];
}
/**
* Validate data before encoding
*/
private function validateData(string $data): void
{
if (strlen($data) > 2953) { // Max capacity for version 40, error level L
throw new InvalidArgumentException('Data too large for QR code: ' . strlen($data) . ' bytes');
}
// Check for null bytes or other problematic characters
if (strpos($data, "\0") !== false) {
throw new InvalidArgumentException('Data contains null bytes');
}
}
}