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
This commit is contained in:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -1,326 +1,386 @@
<?php
declare(strict_types=1);
namespace App\Framework\SyntaxHighlighter\Formatters;
use App\Framework\SyntaxHighlighter\Token;
use App\Framework\SyntaxHighlighter\TokenType;
use App\Framework\Tokenizer\ValueObjects\Token;
use App\Framework\Tokenizer\ValueObjects\TokenCollection;
use App\Framework\Tokenizer\ValueObjects\TokenType;
class HtmlFormatter implements FormatterInterface {
private array $theme = [
'keyword' => '#0000FF',
'variable' => '#800080',
'string' => '#008000',
'identifier' => '#000000',
'number' => '#FF0000',
'comment' => '#808080',
'doc-comment' => '#008080',
'php-tag' => '#000080',
'operator' => '#000080',
'background' => '#F8F8F8',
'border' => '#DDDDDD',
'line-number' => '#999999',
];
/**
* HTML formatter for the modern tokenizer
*/
final class HtmlFormatter implements FormatterInterface
{
private static bool $cssOutput = false;
private static bool $cssOutputted = false;
private array $customTheme = [];
/**
* Format tokens as HTML
*/
public function format(TokenCollection|array $tokens, array $options = []): string
{
// Handle both old array format and new TokenCollection
if (is_array($tokens)) {
// Legacy support for old Token format
return $this->formatLegacy($tokens, $options);
}
$includeCss = $options['includeCss'] ?? false;
$lineNumbers = $options['lineNumbers'] ?? true;
$lineOffset = $options['lineOffset'] ?? 0;
$highlightLines = $options['highlightLines'] ?? [];
$wrapInPre = $options['wrapInPre'] ?? true;
$html = '';
// Include CSS if requested and not already output
if ($includeCss && ! self::$cssOutput) {
$html .= $this->getCss($options);
self::$cssOutput = true;
}
// Start wrapper
if ($wrapInPre) {
$html .= '<pre class="syntax-highlight"><code>';
}
// Group tokens by line for line number display
$tokensByLine = $tokens->groupByLine();
foreach ($tokensByLine as $lineNum => $lineTokens) {
$displayLineNum = $lineNum + $lineOffset;
$isHighlighted = in_array($displayLineNum, $highlightLines, true);
// Add line wrapper
$lineClass = $isHighlighted ? 'line highlighted' : 'line';
$html .= sprintf('<div class="%s">', $lineClass);
// Add line number
if ($lineNumbers) {
$html .= sprintf('<span class="line-number">%d</span>', $displayLineNum);
}
// Add tokens
$html .= '<span class="code">';
foreach ($lineTokens as $token) {
$html .= $this->formatSingle($token, $options);
}
$html .= '</span></div>';
}
// End wrapper
if ($wrapInPre) {
$html .= '</code></pre>';
}
return $html;
}
/**
* Format a single token
*/
public function formatSingle(Token $token, array $options = []): string
{
// Skip whitespace tokens unless explicitly requested
if ($token->type === TokenType::WHITESPACE && ! ($options['showWhitespace'] ?? false)) {
return htmlspecialchars($token->value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
$cssClass = $token->type->getCssClass();
$escapedValue = htmlspecialchars($token->value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
return sprintf('<span class="%s">%s</span>', $cssClass, $escapedValue);
}
/**
* Get CSS for syntax highlighting
*/
public function getCss(array $options = []): string
{
// Use custom theme if set via setTheme()
if (! empty($this->customTheme)) {
return $this->generateCustomThemeCss($this->customTheme);
}
$theme = $options['theme'] ?? 'default';
return match($theme) {
'dark' => $this->getDarkThemeCss(),
'light' => $this->getLightThemeCss(),
default => $this->getDefaultThemeCss()
};
}
/**
* Get formatter name
*/
public function getName(): string
{
return 'html';
}
/**
* Check if formatter supports option
*/
public function supportsOption(string $option): bool
{
return in_array($option, [
'includeCss',
'lineNumbers',
'lineOffset',
'highlightLines',
'wrapInPre',
'showWhitespace',
'theme',
], true);
}
/**
* Reset CSS output flag
*/
public static function resetCssOutput(): void
{
self::$cssOutput = false;
}
/**
* Set custom theme colors (backward compatibility)
*/
public function setTheme(array $theme): self
{
$this->customTheme = $theme;
public function setTheme(array $theme): self {
$this->theme = array_merge($this->theme, $theme);
return $this;
}
public function format(array $tokens, array $options = []): string {
$showLineNumbers = $options['lineNumbers'] ?? true;
$startLine = $options['startLine'] ?? 1;
$lineOffset = $options['lineOffset'] ?? 0; // Neuer Offset Parameter
$highlightLines = $options['highlightLines'] ?? [];
$cssClasses = $options['cssClasses'] ?? true;
$includeCss = $options['includeCss'] ?? true;
$language = $options['language'] ?? 'php';
$lines = $this->groupTokensByLines($tokens);
$output = [];
foreach ($lines as $lineNumber => $lineTokens) {
// Berücksichtigung von startLine und lineOffset
$actualLineNumber = $lineNumber + $startLine - 1 + $lineOffset;
$isHighlighted = in_array($actualLineNumber, $highlightLines);
$lineContent = $this->formatLine($lineTokens, $cssClasses);
$output[] = rtrim($this->wrapLine(
$lineContent,
$actualLineNumber,
$showLineNumbers,
$isHighlighted
));
/**
* Legacy format method for old Token format
*/
private function formatLegacy(array $tokens, array $options = []): string
{
$html = '';
foreach ($tokens as $token) {
if (is_object($token) && property_exists($token, 'type') && property_exists($token, 'value')) {
// Map old TokenType to CSS class
$cssClass = 'token-' . str_replace('_', '-', strtolower($token->type->name));
$escapedValue = htmlspecialchars($token->value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$html .= sprintf('<span class="%s">%s</span>', $cssClass, $escapedValue);
}
}
$codeOutput = $this->wrapOutput(implode("\n", $output), $options);
// CSS automatisch einbinden (nur einmal pro Request)
if ($includeCss && !self::$cssOutputted) {
$css = $this->generateCss($options);
self::$cssOutputted = true;
return $css . "\n" . $codeOutput;
}
return $codeOutput;
return $html;
}
private function generateCss(array $options = []): string {
$tabSize = $options['tabSize'] ?? 4;
$fontSize = $options['fontSize'] ?? '14px';
$lineHeight = $options['lineHeight'] ?? '1.5';
return sprintf(
'<style>%s</style>',
$this->getCssRules($tabSize, $fontSize, $lineHeight)
);
}
private function getCssRules(int $tabSize, string $fontSize, string $lineHeight): string {
return <<<CSS
.syntax-highlighter {
border-radius: 6px;
overflow-x: auto;
margin: 1em auto;
position: relative;
background-color: {$this->theme['background']};
border: 1px solid {$this->theme['border']};
max-width: fit-content;
cursor: text;
}
.syntax-highlighter code {
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: {$fontSize};
line-height: {$lineHeight};
padding: 16px;
display: inline-flex;
flex-direction: column;
white-space: pre;
tab-size: {$tabSize};
color: inherit;
background: none;
margin: 0;
}
.syntax-highlighter .line {
display: inline-flex;
min-height: {$lineHeight}em;
width: 100%;
&:hover .line-number {
font-weight: bold;
}
&:hover {
background-color: rgba(255, 255, 0, 0.1);
}
}
.syntax-highlighter .line.highlighted {
background-color: rgba(255, 255, 0, 0.1);
}
.syntax-highlighter .line-number {
padding-right: 16px;
min-width: 40px;
text-align: right;
border-right: 1px solid {$this->theme['border']};
margin-right: 16px;
user-select: none;
color: {$this->theme['line-number']};
}
.syntax-highlighter .code {
flex: 1;
padding-left: 8px;
}
/* Token Styles */
.syntax-highlighter .token-keyword {
color: {$this->theme['keyword']};
font-weight: bold;
}
.syntax-highlighter .token-variable {
color: {$this->theme['variable']};
}
.syntax-highlighter .token-string {
color: {$this->theme['string']};
}
.syntax-highlighter .token-comment {
color: {$this->theme['comment']};
font-style: italic;
}
.syntax-highlighter .token-doc-comment {
color: {$this->theme['doc-comment']};
font-style: italic;
}
.syntax-highlighter .token-number {
color: {$this->theme['number']};
}
.syntax-highlighter .token-php-tag {
color: {$this->theme['php-tag']};
font-weight: bold;
}
.syntax-highlighter .token-operator {
color: {$this->theme['operator']};
}
.syntax-highlighter .token-identifier {
color: {$this->theme['identifier']};
}
/* Copy Button */
.syntax-highlighter .copy-btn {
position: absolute;
top: 8px;
right: 8px;
background: rgba(0, 0, 0, 0.8);
color: white;
border: none;
padding: 4px 8px;
/**
* Get default theme CSS
*/
private function getDefaultThemeCss(): string
{
return <<<'CSS'
<style>
.syntax-highlight {
background: #2d2d2d;
color: #f8f8f2;
padding: 1em;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
opacity: 0;
transition: opacity 0.2s;
overflow-x: auto;
font-family: 'Fira Code', 'Consolas', monospace;
font-size: 14px;
line-height: 1.5;
}
.syntax-highlighter:hover .copy-btn {
opacity: 1;
.syntax-highlight .line {
display: flex;
min-height: 1.5em;
}
.syntax-highlighter .copy-btn:hover {
background: rgba(0, 0, 0, 0.9);
.syntax-highlight .line.highlighted {
background: rgba(255, 255, 100, 0.1);
}
.syntax-highlight .line-number {
color: #666;
padding-right: 1em;
user-select: none;
min-width: 3em;
text-align: right;
}
.syntax-highlight .code {
flex: 1;
}
/* Token colors */
.token-keyword { color: #ff79c6; font-weight: bold; }
.token-class-name { color: #8be9fd; }
.token-interface-name { color: #8be9fd; }
.token-trait-name { color: #8be9fd; }
.token-enum-name { color: #8be9fd; }
.token-namespace-name { color: #50fa7b; }
.token-function-name { color: #f1fa8c; }
.token-method-name { color: #f1fa8c; }
.token-property-name { color: #bd93f9; }
.token-constant-name { color: #ff5555; }
.token-variable { color: #f8f8f2; }
.token-parameter { color: #ffb86c; }
.token-string-literal { color: #50fa7b; }
.token-number-literal { color: #bd93f9; }
.token-boolean-literal { color: #bd93f9; }
.token-null-literal { color: #bd93f9; }
.token-comment { color: #6272a4; font-style: italic; }
.token-doc-comment { color: #6272a4; font-style: italic; }
.token-doc-tag { color: #ff79c6; font-style: italic; }
.token-doc-type { color: #8be9fd; font-style: italic; }
.token-doc-variable { color: #ffb86c; font-style: italic; }
.token-operator { color: #ff79c6; }
.token-punctuation { color: #f8f8f2; }
.token-attribute { color: #ff79c6; }
.token-attribute-name { color: #f1fa8c; }
.token-type-hint { color: #8be9fd; }
.token-php-tag { color: #ff79c6; }
.token-error { color: #ff5555; background: rgba(255, 85, 85, 0.1); }
</style>
CSS;
}
public static function resetCssOutput(): void {
self::$cssOutputted = false;
/**
* Get dark theme CSS
*/
private function getDarkThemeCss(): string
{
return $this->getDefaultThemeCss(); // Dark is default
}
public function getCss(array $options = []): string {
return $this->generateCss($options);
/**
* Get light theme CSS
*/
private function getLightThemeCss(): string
{
return <<<'CSS'
<style>
.syntax-highlight {
background: #fafafa;
color: #383a42;
padding: 1em;
border: 1px solid #e1e4e8;
border-radius: 4px;
overflow-x: auto;
font-family: 'Fira Code', 'Consolas', monospace;
font-size: 14px;
line-height: 1.5;
}
.syntax-highlight .line {
display: flex;
min-height: 1.5em;
}
.syntax-highlight .line.highlighted {
background: rgba(255, 251, 0, 0.2);
}
.syntax-highlight .line-number {
color: #969896;
padding-right: 1em;
user-select: none;
min-width: 3em;
text-align: right;
}
/* Light theme token colors */
.token-keyword { color: #a626a4; font-weight: bold; }
.token-class-name { color: #c18401; }
.token-function-name { color: #4078f2; }
.token-method-name { color: #4078f2; }
.token-property-name { color: #986801; }
.token-constant-name { color: #986801; }
.token-variable { color: #383a42; }
.token-parameter { color: #e45649; }
.token-string-literal { color: #50a14f; }
.token-number-literal { color: #986801; }
.token-comment { color: #a0a1a7; font-style: italic; }
.token-operator { color: #a626a4; }
.token-attribute { color: #a626a4; }
.token-type-hint { color: #c18401; }
.token-error { color: #e45649; background: rgba(228, 86, 73, 0.1); }
</style>
CSS;
}
private function groupTokensByLines(array $tokens): array {
$lines = [];
$currentLine = 1;
/**
* Generate custom theme CSS from theme array
*/
private function generateCustomThemeCss(array $theme): string
{
$background = $theme['background'] ?? '#2b2b2b';
$color = $theme['identifier'] ?? '#a9b7c6';
$border = $theme['border'] ?? '#555555';
$lineNumber = $theme['line-number'] ?? '#606366';
foreach ($tokens as $token) {
if (!isset($lines[$currentLine])) {
$lines[$currentLine] = [];
}
// Build CSS with custom colors
$css = "<style>\n";
$css .= ".syntax-highlight {\n";
$css .= " background: {$background};\n";
$css .= " color: {$color};\n";
$css .= " padding: 1em;\n";
$css .= " border: 1px solid {$border};\n";
$css .= " border-radius: 4px;\n";
$css .= " overflow-x: auto;\n";
$css .= " font-family: 'Fira Code', 'Consolas', monospace;\n";
$css .= " font-size: 14px;\n";
$css .= " line-height: 1.5;\n";
$css .= "}\n\n";
// Spezielle Behandlung für reine Newline-Token
if ($token->value === "\n") {
$currentLine++;
continue;
}
$css .= ".syntax-highlight .line { display: flex; min-height: 1.5em; }\n";
$css .= ".syntax-highlight .line.highlighted { background: rgba(255, 255, 100, 0.1); }\n";
$css .= ".syntax-highlight .line-number {\n";
$css .= " color: {$lineNumber};\n";
$css .= " padding-right: 1em;\n";
$css .= " user-select: none;\n";
$css .= " min-width: 3em;\n";
$css .= " text-align: right;\n";
$css .= "}\n";
$css .= ".syntax-highlight .code { flex: 1; }\n\n";
if (str_contains($token->value, "\n")) {
$parts = explode("\n", $token->value);
// Map theme keys to CSS token classes
$tokenMapping = [
'keyword' => 'token-keyword',
'variable' => 'token-variable',
'string' => 'token-string-literal',
'number' => 'token-number-literal',
'comment' => 'token-comment',
'doc-comment' => 'token-doc-comment',
'php-tag' => 'token-php-tag',
'operator' => 'token-operator',
'constant' => 'token-constant-name',
'method-call' => 'token-method-name',
'property' => 'token-property-name',
'class-name' => 'token-class-name',
];
// Ersten Teil zur aktuellen Zeile hinzufügen
if ($parts[0] !== '') {
$lines[$currentLine][] = new Token(
$token->type,
$parts[0],
$currentLine,
$token->column
);
}
// Für jeden Newline eine neue Zeile erstellen
for ($i = 1; $i < count($parts); $i++) {
$currentLine++;
if (!isset($lines[$currentLine])) {
$lines[$currentLine] = [];
}
// Nur nicht-leere Teile hinzufügen
if ($parts[$i] !== '') {
$lines[$currentLine][] = new Token(
$token->type,
$parts[$i],
$currentLine,
$token->column
);
}
}
} else {
$lines[$currentLine][] = $token;
// Generate token-specific styles
foreach ($tokenMapping as $themeKey => $cssClass) {
if (isset($theme[$themeKey])) {
$css .= ".{$cssClass} { color: {$theme[$themeKey]}; }\n";
}
}
return $lines;
}
private function formatLine(array $tokens, bool $useCssClasses): string {
$content = '';
foreach ($tokens as $token) {
$content .= $this->formatToken($token, $useCssClasses);
// Special cases with additional styling
if (isset($theme['keyword'])) {
$css .= ".token-keyword { color: {$theme['keyword']}; font-weight: bold; }\n";
}
if (isset($theme['comment'])) {
$css .= ".token-comment { color: {$theme['comment']}; font-style: italic; }\n";
}
if (isset($theme['doc-comment'])) {
$css .= ".token-doc-comment { color: {$theme['doc-comment']}; font-style: italic; }\n";
}
return $content;
}
$css .= "</style>";
private function formatToken(Token $token, bool $useCssClasses): string {
$escapedValue = htmlspecialchars($token->value);
if ($token->type === TokenType::WHITESPACE) {
return $escapedValue;
}
if ($useCssClasses) {
return sprintf('<span class="%s">%s</span>',
$token->type->getCssClass(), $escapedValue);
} else {
$color = $this->theme[$token->type->value] ?? $this->theme['identifier'];
return sprintf('<span style="color: %s;">%s</span>',
$color, $escapedValue);
}
}
private function wrapLine(string $content, int $lineNumber, bool $showLineNumbers, bool $isHighlighted): string {
$lineClass = $isHighlighted ? 'line highlighted' : 'line';
if ($showLineNumbers) {
$lineNumberSpan = sprintf(
'<span class="line-number">%d</span>',
$lineNumber
);
return sprintf('<div class="%s">%s<span class="code">%s</span></div>',
$lineClass, $lineNumberSpan, $content);
}
return sprintf('<div class="%s">%s</div>', $lineClass, $content);
}
private function wrapOutput(string $content, array $options): string {
$containerClass = $options['containerClass'] ?? 'syntax-highlighter';
$language = $options['language'] ?? 'php';
$copyButton = $options['copyButton'] ?? false;
$copyButtonHtml = $copyButton ?
'<button class="copy-btn" onclick="navigator.clipboard.writeText(this.nextElementSibling.querySelector(\'code\').textContent)">Copy</button>' : '';
return sprintf(
'<pre class="%s">%s<code class="language-%s">%s</code></pre>',
$containerClass,
$copyButtonHtml,
$language,
$content
);
return $css;
}
}