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

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace App\Framework\Tokenizer\ValueObjects;
/**
* State tracking for smart discovery tokenization
* Tracks what we've found so far to enable early stopping
*/
final class DiscoveryState
{
public bool $namespaceFound = false;
public int $namespaceLineFound = 0;
public bool $classFound = false;
public int $classLineFound = 0;
public bool $classBodyStarted = false;
public int $useStatementsCount = 0;
public int $functionsCount = 0;
public int $attributesCount = 0;
public int $docCommentsCount = 0;
/**
* Check if we have found significant structural content
*/
public function hasSignificantContent(): bool
{
return $this->namespaceFound ||
$this->classFound ||
$this->useStatementsCount > 0 ||
$this->functionsCount > 0;
}
/**
* Get a summary of what we've discovered
*/
public function getSummary(): array
{
return [
'namespace' => $this->namespaceFound,
'class' => $this->classFound,
'uses' => $this->useStatementsCount,
'functions' => $this->functionsCount,
'attributes' => $this->attributesCount,
'lines_processed' => max($this->namespaceLineFound, $this->classLineFound),
];
}
}

View File

@@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace App\Framework\Tokenizer\ValueObjects;
/**
* Represents a single token with full context
*/
final readonly class Token
{
public function __construct(
public TokenType $type,
public string $value,
public int $line,
public int $position,
public int $id,
public TokenContext $context
) {
}
/**
* Check if token is a specific PHP token type
*/
public function is(int|array $tokenId): bool
{
if (is_array($tokenId)) {
return in_array($this->id, $tokenId, true);
}
return $this->id === $tokenId;
}
/**
* Check if token is structural (class, function, namespace, etc.)
*/
public function isStructural(): bool
{
return $this->is([
T_CLASS, T_INTERFACE, T_TRAIT, T_ENUM,
T_FUNCTION, T_NAMESPACE, T_USE,
T_EXTENDS, T_IMPLEMENTS,
T_PUBLIC, T_PROTECTED, T_PRIVATE,
T_STATIC, T_FINAL, T_ABSTRACT, T_READONLY,
]);
}
/**
* Check if token is an attribute
*/
public function isAttribute(): bool
{
return $this->is(T_ATTRIBUTE) ||
($this->context->isInAttribute && $this->is(T_STRING));
}
/**
* Check if token is a doc comment
*/
public function isDocComment(): bool
{
return $this->is(T_DOC_COMMENT);
}
/**
* Check if token is a keyword
*/
public function isKeyword(): bool
{
return $this->type === TokenType::KEYWORD;
}
/**
* Check if token is an identifier (class name, function name, etc.)
*/
public function isIdentifier(): bool
{
return in_array($this->type, [
TokenType::CLASS_NAME,
TokenType::FUNCTION_NAME,
TokenType::METHOD_NAME,
TokenType::PROPERTY_NAME,
TokenType::CONSTANT_NAME,
], true);
}
/**
* Get token length
*/
public function getLength(): int
{
return strlen($this->value);
}
/**
* Get end position
*/
public function getEndPosition(): int
{
return $this->position + $this->getLength();
}
/**
* Check if this token contains a specific position
*/
public function containsPosition(int $position): bool
{
return $position >= $this->position && $position < $this->getEndPosition();
}
/**
* Get a clean version of the value (trimmed, no quotes, etc.)
*/
public function getCleanValue(): string
{
return match($this->type) {
TokenType::STRING_LITERAL => trim($this->value, '"\''),
TokenType::DOC_COMMENT => trim($this->value, '/*'),
default => trim($this->value)
};
}
}

View File

@@ -0,0 +1,245 @@
<?php
declare(strict_types=1);
namespace App\Framework\Tokenizer\ValueObjects;
use ArrayIterator;
use Countable;
use IteratorAggregate;
use Traversable;
/**
* Collection of tokens with utility methods
*/
final readonly class TokenCollection implements IteratorAggregate, Countable
{
/**
* @param array<Token> $tokens
*/
public function __construct(
private array $tokens = []
) {
}
/**
* Get iterator for the collection
*/
public function getIterator(): Traversable
{
return new ArrayIterator($this->tokens);
}
/**
* Count tokens in collection
*/
public function count(): int
{
return count($this->tokens);
}
/**
* Get all tokens as array
* @return array<Token>
*/
public function toArray(): array
{
return $this->tokens;
}
/**
* Get token at index
*/
public function get(int $index): ?Token
{
return $this->tokens[$index] ?? null;
}
/**
* Filter tokens by predicate
*/
public function filter(callable $predicate): self
{
return new self(array_values(array_filter($this->tokens, $predicate)));
}
/**
* Filter by token type
*/
public function filterByType(TokenType ...$types): self
{
return $this->filter(fn (Token $token) => in_array($token->type, $types, true));
}
/**
* Filter by PHP token ID
*/
public function filterById(int ...$ids): self
{
return $this->filter(fn (Token $token) => in_array($token->id, $ids, true));
}
/**
* Get only structural tokens
*/
public function getStructural(): self
{
return $this->filter(fn (Token $token) => $token->isStructural());
}
/**
* Get only identifiers
*/
public function getIdentifiers(): self
{
return $this->filter(fn (Token $token) => $token->isIdentifier());
}
/**
* Get tokens in line range
*/
public function getInLineRange(int $startLine, int $endLine): self
{
return $this->filter(
fn (Token $token) =>
$token->line >= $startLine && $token->line <= $endLine
);
}
/**
* Get tokens at specific line
*/
public function getAtLine(int $line): self
{
return $this->filter(fn (Token $token) => $token->line === $line);
}
/**
* Find first token matching predicate
*/
public function findFirst(callable $predicate): ?Token
{
foreach ($this->tokens as $token) {
if ($predicate($token)) {
return $token;
}
}
return null;
}
/**
* Find first token of type
*/
public function findFirstOfType(TokenType $type): ?Token
{
return $this->findFirst(fn (Token $token) => $token->type === $type);
}
/**
* Map tokens to another form
*/
public function map(callable $mapper): array
{
return array_map($mapper, $this->tokens);
}
/**
* Extract all values
*/
public function getValues(): array
{
return $this->map(fn (Token $token) => $token->value);
}
/**
* Extract all clean values
*/
public function getCleanValues(): array
{
return $this->map(fn (Token $token) => $token->getCleanValue());
}
/**
* Get tokens grouped by type
* @return array<string, array<Token>>
*/
public function groupByType(): array
{
$groups = [];
foreach ($this->tokens as $token) {
$type = $token->type->value;
if (! isset($groups[$type])) {
$groups[$type] = [];
}
$groups[$type][] = $token;
}
return $groups;
}
/**
* Get tokens grouped by line
* @return array<int, array<Token>>
*/
public function groupByLine(): array
{
$groups = [];
foreach ($this->tokens as $token) {
if (! isset($groups[$token->line])) {
$groups[$token->line] = [];
}
$groups[$token->line][] = $token;
}
return $groups;
}
/**
* Check if collection is empty
*/
public function isEmpty(): bool
{
return empty($this->tokens);
}
/**
* Get first token
*/
public function first(): ?Token
{
return $this->tokens[0] ?? null;
}
/**
* Get last token
*/
public function last(): ?Token
{
return empty($this->tokens) ? null : $this->tokens[count($this->tokens) - 1];
}
/**
* Slice collection
*/
public function slice(int $offset, ?int $length = null): self
{
return new self(array_slice($this->tokens, $offset, $length));
}
/**
* Merge with another collection
*/
public function merge(self $other): self
{
return new self(array_merge($this->tokens, $other->tokens));
}
/**
* Convert to string (concatenate all values)
*/
public function toString(): string
{
return implode('', $this->getValues());
}
}

View File

@@ -0,0 +1,208 @@
<?php
declare(strict_types=1);
namespace App\Framework\Tokenizer\ValueObjects;
/**
* Context information for a token
*/
final class TokenContext
{
public function __construct(
public readonly bool $isInClass = false,
public readonly bool $isInFunction = false,
public readonly bool $isInNamespace = false,
public readonly bool $isInAttribute = false,
public readonly bool $isInDocComment = false,
public readonly ?string $currentClass = null,
public readonly ?string $currentFunction = null,
public readonly ?string $currentNamespace = null,
public readonly array $scopeStack = [],
public readonly int $nestingLevel = 0
) {
}
/**
* Create a new context with updated values
*/
public function with(array $updates): self
{
return new self(
isInClass: $updates['isInClass'] ?? $this->isInClass,
isInFunction: $updates['isInFunction'] ?? $this->isInFunction,
isInNamespace: $updates['isInNamespace'] ?? $this->isInNamespace,
isInAttribute: $updates['isInAttribute'] ?? $this->isInAttribute,
isInDocComment: $updates['isInDocComment'] ?? $this->isInDocComment,
currentClass: $updates['currentClass'] ?? $this->currentClass,
currentFunction: $updates['currentFunction'] ?? $this->currentFunction,
currentNamespace: $updates['currentNamespace'] ?? $this->currentNamespace,
scopeStack: $updates['scopeStack'] ?? $this->scopeStack,
nestingLevel: $updates['nestingLevel'] ?? $this->nestingLevel
);
}
/**
* Enter a new scope
*/
public function enterScope(string $type, ?string $name = null): self
{
$newStack = $this->scopeStack;
$newStack[] = ['type' => $type, 'name' => $name];
$updates = [
'scopeStack' => $newStack,
'nestingLevel' => $this->nestingLevel + 1,
];
// Use match for cleaner, type-safe scope handling
$scopeUpdates = match($type) {
'class', 'interface', 'trait', 'enum' => [
'isInClass' => true,
'currentClass' => $name,
],
'function', 'method' => [
'isInFunction' => true,
'currentFunction' => $name,
],
'namespace' => [
'isInNamespace' => true,
'currentNamespace' => $name,
],
'attribute' => [
'isInAttribute' => true,
],
'doccomment' => [
'isInDocComment' => true,
],
default => []
};
return $this->with([...$updates, ...$scopeUpdates]);
}
/**
* Exit current scope
*/
public function exitScope(): self
{
if (empty($this->scopeStack)) {
return $this;
}
$newStack = $this->scopeStack;
$exitedScope = array_pop($newStack);
$updates = [
'scopeStack' => $newStack,
'nestingLevel' => max(0, $this->nestingLevel - 1),
];
// Update context based on remaining stack
if (empty($newStack)) {
$updates = [
...$updates,
'isInClass' => false,
'isInFunction' => false,
'isInAttribute' => false,
'isInDocComment' => false,
'currentClass' => null,
'currentFunction' => null,
];
} else {
// Check what scopes we're still in
$stillInClass = false;
$stillInFunction = false;
$stillInAttribute = false;
$stillInDocComment = false;
$currentClass = null;
$currentFunction = null;
foreach ($newStack as $scope) {
$scopeType = $scope['type'];
match($scopeType) {
'class', 'interface', 'trait', 'enum' => ($stillInClass = true) && ($currentClass = $scope['name']),
'function', 'method' => ($stillInFunction = true) && ($currentFunction = $scope['name']),
'attribute' => $stillInAttribute = true,
'doccomment' => $stillInDocComment = true,
default => null
};
}
$updates = [
...$updates,
'isInClass' => $stillInClass,
'isInFunction' => $stillInFunction,
'isInAttribute' => $stillInAttribute,
'isInDocComment' => $stillInDocComment,
'currentClass' => $currentClass,
'currentFunction' => $currentFunction,
];
}
return $this->with($updates);
}
/**
* Get the current scope type
*/
public function getCurrentScopeType(): ?string
{
if (empty($this->scopeStack)) {
return null;
}
$currentScope = $this->scopeStack[array_key_last($this->scopeStack)];
return $currentScope['type'] ?? null;
}
/**
* Check if we're in a specific scope type
*/
public function isInScopeType(string $type): bool
{
return match($type) {
'class' => $this->isInClass,
'function' => $this->isInFunction,
'namespace' => $this->isInNamespace,
'attribute' => $this->isInAttribute,
'doccomment' => $this->isInDocComment,
default => false
};
}
/**
* Get fully qualified name for current context
*/
public function getFullyQualifiedName(?string $name = null): string
{
$parts = array_filter([
$this->currentNamespace,
$this->currentClass,
$name,
]);
return implode('\\', $parts);
}
/**
* Clone the context
*/
public function clone(): self
{
return new self(
isInClass: $this->isInClass,
isInFunction: $this->isInFunction,
isInNamespace: $this->isInNamespace,
isInAttribute: $this->isInAttribute,
isInDocComment: $this->isInDocComment,
currentClass: $this->currentClass,
currentFunction: $this->currentFunction,
currentNamespace: $this->currentNamespace,
scopeStack: $this->scopeStack,
nestingLevel: $this->nestingLevel
);
}
}

View File

@@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
namespace App\Framework\Tokenizer\ValueObjects;
/**
* Token type enumeration for semantic classification
*/
enum TokenType: string
{
// Structural
case KEYWORD = 'keyword';
case CLASS_NAME = 'class_name';
case INTERFACE_NAME = 'interface_name';
case TRAIT_NAME = 'trait_name';
case ENUM_NAME = 'enum_name';
case NAMESPACE_NAME = 'namespace_name';
// Identifiers
case FUNCTION_NAME = 'function_name';
case METHOD_NAME = 'method_name';
case PROPERTY_NAME = 'property_name';
case CONSTANT_NAME = 'constant_name';
case VARIABLE = 'variable';
case PARAMETER = 'parameter';
// Literals
case STRING_LITERAL = 'string_literal';
case NUMBER_LITERAL = 'number_literal';
case BOOLEAN_LITERAL = 'boolean_literal';
case NULL_LITERAL = 'null_literal';
// Comments
case DOC_COMMENT = 'doc_comment';
case DOC_TAG = 'doc_tag';
case DOC_TYPE = 'doc_type';
case DOC_VARIABLE = 'doc_variable';
case DOC_TEXT = 'doc_text';
case COMMENT = 'comment';
// Operators and syntax
case OPERATOR = 'operator';
case PUNCTUATION = 'punctuation';
case BRACKET = 'bracket';
case PARENTHESIS = 'parenthesis';
case BRACE = 'brace';
case SEMICOLON = 'semicolon';
// Attributes
case ATTRIBUTE = 'attribute';
case ATTRIBUTE_NAME = 'attribute_name';
case ATTRIBUTE_ARGUMENT = 'attribute_argument';
// Types
case TYPE_HINT = 'type_hint';
case RETURN_TYPE = 'return_type';
case UNION_TYPE = 'union_type';
case INTERSECTION_TYPE = 'intersection_type';
// Special
case WHITESPACE = 'whitespace';
case PHP_TAG = 'php_tag';
case HTML = 'html';
case DEFAULT = 'default';
case ERROR = 'error';
/**
* Get CSS class for syntax highlighting
*/
public function getCssClass(): string
{
return 'token-' . str_replace('_', '-', $this->value);
}
/**
* Get color for terminal output
*/
public function getTerminalColor(): string
{
return match($this) {
self::KEYWORD => "\033[35m", // Magenta
self::CLASS_NAME,
self::INTERFACE_NAME,
self::TRAIT_NAME,
self::ENUM_NAME => "\033[36m", // Cyan
self::FUNCTION_NAME,
self::METHOD_NAME => "\033[33m", // Yellow
self::VARIABLE,
self::PARAMETER => "\033[37m", // White
self::STRING_LITERAL => "\033[32m", // Green
self::NUMBER_LITERAL => "\033[34m", // Blue
self::COMMENT,
self::DOC_COMMENT => "\033[90m", // Gray
self::ATTRIBUTE,
self::ATTRIBUTE_NAME => "\033[95m", // Light Magenta
self::OPERATOR => "\033[31m", // Red
default => "\033[0m" // Reset
};
}
/**
* Check if this is a structural token type
*/
public function isStructural(): bool
{
return in_array($this, [
self::CLASS_NAME,
self::INTERFACE_NAME,
self::TRAIT_NAME,
self::ENUM_NAME,
self::NAMESPACE_NAME,
self::FUNCTION_NAME,
self::METHOD_NAME,
], true);
}
/**
* Check if this is a comment type
*/
public function isComment(): bool
{
return in_array($this, [
self::COMMENT,
self::DOC_COMMENT,
self::DOC_TAG,
self::DOC_TYPE,
self::DOC_VARIABLE,
self::DOC_TEXT,
], true);
}
}