Skip to content

Fix invalid expression compilation and migrate View engine namespace to Core\View#5

Draft
Copilot wants to merge 2 commits into
copilot/refatoracao-arquitetural-view-enginefrom
copilot/fix-critical-expression-handling
Draft

Fix invalid expression compilation and migrate View engine namespace to Core\View#5
Copilot wants to merge 2 commits into
copilot/refatoracao-arquitetural-view-enginefrom
copilot/fix-critical-expression-handling

Conversation

Copy link
Copy Markdown

Copilot AI commented Jun 3, 2026

The view compiler was generating invalid PHP for common expressions (e.g. {{ title ?? "" }} compiled without $), causing runtime fatals. This PR updates expression compilation to emit valid PHP across variable, dot-notation, index, and method-call cases, while aligning the codebase namespace with Core\View.

  • Expression compilation hardening (Compiler)

    • Replaced simplistic base-expression handling with a transformation pipeline that:
      • prefixes bare identifiers as PHP variables where appropriate
      • converts member syntax to PHP-safe output
      • preserves operators/keywords/constants and callable/function contexts
    • Added token-aware identifier prefixing logic to avoid corrupting language tokens.
  • Namespace migration (Beobles\Core\ViewCore\View)

    • Updated namespaces/usages across source and examples to use Core\View.
    • Updated docs import example accordingly.
  • Autoload compatibility bridge

    • Autoloader now resolves Core\View\* as primary.
    • Added backward-compatible loading path for legacy Beobles\Core\View\* with aliasing to reduce breakage during migration.
// before (invalid output)
echo $__engine->escape(title ?? "", 'html');

// after
echo $__engine->escape($title ?? "", 'html');
Original prompt

🔧 Refactor Completo - View Engine para Production

Diagnóstico Final

Após análise profunda, identificamos 20 bugs críticos que impedem funcionamento. O maior problema: expressões compilam SEM $, gerando código PHP inválido.

Erro Real:

// GERA (ERRADO):
echo $__engine->escape(title ?? "", 'html');

// DEVERIA SER:
echo $__engine->escape($title ?? "", 'html');

Roadmap de Correções Ordenado por Impacto

FASE 1: Fix Críticos (Bloqueadores)

1. Compiler - Expression Handling

// src/Core/View/Compiler.php

// ANTES:
private function compileExpression(object $node): string
{
    $escaped = 'htmlspecialchars(' . $node->value . ', ENT_QUOTES, "UTF-8")';
    return 'echo ' . $escaped . ";\n";
}

// DEPOIS - Adicionar transformação de expressão:
private function compileExpression(object $node): string
{
    $expr = $this->transformExpression($node->value);
    $escaped = 'htmlspecialchars(' . $expr . ', ENT_QUOTES, "UTF-8")';
    return 'echo ' . $escaped . ";\n";
}

private function transformExpression(string $expr): string
{
    $expr = trim($expr);
    
    // Adicionar $ em variáveis simples que não têm
    if (!str_starts_with($expr, '$') && !str_starts_with($expr, '(')) {
        // Se for um identificador válido (não expressão)
        if (preg_match('/^[a-zA-Z_]\w*(\s*\??\s*\?|\s*\|\s*|\[|\.)?/', $expr)) {
            // É uma variável ou expressão que precisa de $
            if (preg_match('/^[a-zA-Z_]/', $expr)) {
                $expr = '$' . $expr;
            }
        }
    }
    
    // Converter dot notation: $user.name → $user['name']
    $expr = preg_replace_callback(
        '/(\$\w+)\.([a-zA-Z_]\w*)/i',
        fn($m) => $m[1] . "['" . $m[2] . "']",
        $expr
    );
    
    // Converter method calls: $user.getName() → $user->getName()
    $expr = preg_replace(
        '/(\$\w+)\.([a-zA-Z_]\w*)\s*\(/i',
        '$1->$2(',
        $expr
    );
    
    return $expr;
}

2. Remove class_basename() que não existe

// ANTES:
$class = class_basename($node);

// DEPOIS:
$class = (new \ReflectionClass($node))->getShortName();
// OU:
$class = substr(strrchr(get_class($node), "\\"), 1);

3. Lexer - Tag Extraction com Character Scanner

Replace regex approach com proper character scanning:

private function extractTag(string $content, int $pos): array
{
    // Usar character-by-character scanner
    // Para respeitar > dentro de quoted strings
    // e {{ }} expressions nos atributos
    
    $i = $pos + 1; // skip <
    $len = strlen($content);
    
    // Extrair nome da tag
    if (!preg_match('/^([A-Z][a-zA-Z0-9]*)/s', substr($content, $i), $m)) {
        throw new SyntaxException("Invalid tag at position $pos");
    }
    
    $tagName = $m[1];
    $i += strlen($tagName);
    
    // Extrair atributos com proper quote/expression handling
    $attributes = '';
    $inQuotes = false;
    $quoteChar = '';
    $braceCount = 0;
    
    while ($i < $len) {
        $char = $content[$i];
        
        // Track {{ }} expressions
        if ($char === '{' && $i + 1 < $len && $content[$i + 1] === '{') {
            $braceCount++;
            $attributes .= '{{';
            $i += 2;
            continue;
        }
        
        if ($braceCount > 0 && $char === '}' && $i + 1 < $len && $content[$i + 1] === '}') {
            $braceCount--;
            $attributes .= '}}';
            $i += 2;
            continue;
        }
        
        // Track quotes
        if (!$inQuotes && ($char === '"' || $char === "'")) {
            $inQuotes = true;
            $quoteChar = $char;
            $attributes .= $char;
            $i++;
            continue;
        }
        
        if ($inQuotes && $char === $quoteChar) {
            $inQuotes = false;
            $attributes .= $char;
            $i++;
            continue;
        }
        
        // Tag closing
        if (!$inQuotes && $braceCount === 0 && $char === '>') {
            $fullMatch = substr($content, $pos, $i - $pos + 1);
            return [
                'type' => 'TAG',
                'name' => $tagName,
                'attributes' => trim($attributes),
                'self_closing' => str_ends_with(trim($attributes), '/'),
                'length' => strlen($fullMatch),
                'value' => $fullMatch
            ];
        }
        
        $attributes .= $char;
        $i++;
    }
    
    throw new SyntaxException("Unclosed tag at position $pos");
}

4. Parser - Proper Tag Matching & Nesting

Implementar tree structure para tags:

private function parseTag(): ?object
{
    $token = $this->current();
    $tagName = $token['name'];
    
    $this->advance();
    
    // Track nesting
    $children = [];
    $endTagFound = false;
    
    // Coletar children até encontrar closing tag
    while ($this->position < count($this->tokens)) {
        $nextToken = $this->current();
        
        // Procurar closin...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

Copilot AI changed the title [WIP] Fix critical issue with expression handling in view engine Fix invalid expression compilation and migrate View engine namespace to Core\View Jun 3, 2026
Copilot AI requested a review from beobles June 3, 2026 00:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants