fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
use App\Framework\Core\ValueObjects\ClassName;
|
||||
use App\Framework\Reflection\CachedReflectionProvider;
|
||||
use App\Framework\ReflectionLegacy\CachedReflectionProvider;
|
||||
|
||||
echo "=== Debug VisitClass Method ===" . PHP_EOL;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ use App\Framework\DI\InitializerMapper;
|
||||
use App\Framework\Discovery\UnifiedDiscoveryService;
|
||||
use App\Framework\Discovery\ValueObjects\DiscoveryConfiguration;
|
||||
use App\Framework\Filesystem\FileSystemService;
|
||||
use App\Framework\Reflection\CachedReflectionProvider;
|
||||
use App\Framework\ReflectionLegacy\CachedReflectionProvider;
|
||||
use App\Framework\Serializer\Json\JsonSerializer;
|
||||
|
||||
echo "=== Testing ALL Initializer Discovery ===\n\n";
|
||||
|
||||
42
tests/debug/test-auth-capture.php
Normal file
42
tests/debug/test-auth-capture.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
use App\Framework\HttpClient\AuthConfig;
|
||||
use App\Framework\HttpClient\AuthType;
|
||||
use App\Framework\HttpClient\ClientOptions;
|
||||
|
||||
// Test 1: Create AuthConfig directly
|
||||
echo "Test 1: Direct AuthConfig creation\n";
|
||||
$auth = AuthConfig::basic('testuser', 'testpass');
|
||||
var_dump($auth);
|
||||
echo "Auth type: " . ($auth->type->value ?? 'NULL') . "\n";
|
||||
echo "Auth type is null: " . (is_null($auth->type) ? 'YES' : 'NO') . "\n\n";
|
||||
|
||||
// Test 2: Create ClientOptions with auth
|
||||
echo "Test 2: ClientOptions with auth\n";
|
||||
$options = new ClientOptions(
|
||||
timeout: 10,
|
||||
auth: $auth
|
||||
);
|
||||
var_dump($options->auth);
|
||||
echo "Options auth type: " . ($options->auth->type->value ?? 'NULL') . "\n";
|
||||
echo "Options auth type is null: " . (is_null($options->auth->type) ? 'YES' : 'NO') . "\n\n";
|
||||
|
||||
// Test 3: ClientOptions with() method
|
||||
echo "Test 3: ClientOptions with() method\n";
|
||||
$baseOptions = new ClientOptions(timeout: 10);
|
||||
$newOptions = $baseOptions->with(['auth' => $auth]);
|
||||
var_dump($newOptions->auth);
|
||||
echo "New options auth type: " . ($newOptions->auth->type->value ?? 'NULL') . "\n";
|
||||
echo "New options auth type is null: " . (is_null($newOptions->auth->type) ? 'YES' : 'NO') . "\n\n";
|
||||
|
||||
// Test 4: Check if readonly classes preserve properties
|
||||
echo "Test 4: Readonly class property preservation\n";
|
||||
$options1 = new ClientOptions(timeout: 10, auth: $auth);
|
||||
$options2 = $options1; // Assignment
|
||||
echo "Are they the same object? " . (($options1 === $options2) ? 'YES' : 'NO') . "\n";
|
||||
echo "Options1 auth type: " . ($options1->auth->type->value ?? 'NULL') . "\n";
|
||||
echo "Options2 auth type: " . ($options2->auth->type->value ?? 'NULL') . "\n";
|
||||
@@ -20,7 +20,7 @@ use App\Framework\DI\InitializerMapper;
|
||||
use App\Framework\Discovery\UnifiedDiscoveryService;
|
||||
use App\Framework\Discovery\ValueObjects\DiscoveryConfiguration;
|
||||
use App\Framework\Filesystem\FileSystemService;
|
||||
use App\Framework\Reflection\CachedReflectionProvider;
|
||||
use App\Framework\ReflectionLegacy\CachedReflectionProvider;
|
||||
use App\Framework\Serializer\Json\JsonSerializer;
|
||||
|
||||
echo "=== Testing Context Filtering in Web Context ===\n\n";
|
||||
|
||||
@@ -21,7 +21,7 @@ use App\Framework\Discovery\Results\InterfaceRegistry;
|
||||
use App\Framework\Discovery\ValueObjects\DiscoveredAttribute;
|
||||
use App\Framework\LiveComponents\DataProviderResolver;
|
||||
use App\Framework\LiveComponents\ValueObjects\ComponentId;
|
||||
use App\Framework\Reflection\WrappedReflectionClass;
|
||||
use App\Framework\ReflectionLegacy\WrappedReflectionClass;
|
||||
|
||||
echo "\nMinimal DataProvider Resolution Test\n";
|
||||
echo str_repeat('=', 70) . "\n\n";
|
||||
|
||||
@@ -13,7 +13,7 @@ use App\Framework\DI\InitializerMapper;
|
||||
use App\Framework\Discovery\UnifiedDiscoveryService;
|
||||
use App\Framework\Discovery\ValueObjects\DiscoveryConfiguration;
|
||||
use App\Framework\Filesystem\FileSystemService;
|
||||
use App\Framework\Reflection\CachedReflectionProvider;
|
||||
use App\Framework\ReflectionLegacy\CachedReflectionProvider;
|
||||
use App\Framework\Serializer\Json\JsonSerializer;
|
||||
use Tests\Debug\DebugInitializer;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
use App\Framework\Core\ValueObjects\ClassName;
|
||||
use App\Framework\Discovery\Analysis\DependencyAnalyzer;
|
||||
use App\Framework\Reflection\CachedReflectionProvider;
|
||||
use App\Framework\ReflectionLegacy\CachedReflectionProvider;
|
||||
|
||||
echo "=== Testing Dependency Graph Analysis System ===\n\n";
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
use App\Framework\DI\InitializerDependencyGraph;
|
||||
use App\Framework\Reflection\CachedReflectionProvider;
|
||||
use App\Framework\ReflectionLegacy\CachedReflectionProvider;
|
||||
|
||||
echo "=== Testing InitializerDependencyGraph API ===\n\n";
|
||||
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
use App\Framework\ErrorHandling\DummyTemplateRenderer;
|
||||
use App\Framework\ErrorHandling\View\ErrorTemplateRenderer;
|
||||
use App\Framework\Exception\ErrorHandlerContext;
|
||||
use App\Framework\Exception\ExceptionContext;
|
||||
use App\Framework\Exception\RequestContext;
|
||||
use App\Framework\Exception\SystemContext;
|
||||
use App\Framework\Http\RequestId;
|
||||
use App\Framework\UserAgent\UserAgent;
|
||||
|
||||
// Erstelle eine Test-Exception
|
||||
$testException = new \RuntimeException('Test error for enhanced debug page', 500);
|
||||
|
||||
// Erstelle ErrorHandlerContext mit Test-Daten
|
||||
$exceptionContext = ExceptionContext::empty()
|
||||
->withOperation('test_operation', 'TestComponent')
|
||||
->withData([
|
||||
'exception_message' => $testException->getMessage(),
|
||||
'exception_file' => $testException->getFile(),
|
||||
'exception_line' => $testException->getLine(),
|
||||
'original_exception' => $testException,
|
||||
]);
|
||||
|
||||
$requestContext = RequestContext::create(
|
||||
clientIp: '127.0.0.1',
|
||||
userAgent: UserAgent::fromString('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'),
|
||||
requestMethod: 'GET',
|
||||
requestUri: '/test-error',
|
||||
hostIp: '127.0.0.1',
|
||||
hostname: 'localhost',
|
||||
protocol: 'HTTP/1.1',
|
||||
port: '80',
|
||||
requestId: new RequestId()
|
||||
);
|
||||
|
||||
$systemContext = new SystemContext(
|
||||
memoryUsage: '25 MB',
|
||||
executionTime: 0.25, // 250ms
|
||||
phpVersion: PHP_VERSION,
|
||||
frameworkVersion: '1.0.0-dev',
|
||||
environment: ['test_system_data' => 'value']
|
||||
);
|
||||
|
||||
$metadata = [
|
||||
'exception_class' => get_class($testException),
|
||||
'error_level' => 'ERROR',
|
||||
'http_status' => 500,
|
||||
];
|
||||
|
||||
$errorHandlerContext = ErrorHandlerContext::create(
|
||||
$exceptionContext,
|
||||
$requestContext,
|
||||
$systemContext,
|
||||
$metadata
|
||||
);
|
||||
|
||||
// Erstelle Template Renderer
|
||||
$templateRenderer = new DummyTemplateRenderer();
|
||||
|
||||
// Erstelle Error Template Renderer
|
||||
$errorRenderer = new ErrorTemplateRenderer($templateRenderer);
|
||||
|
||||
// Rendere die Enhanced Debug Page
|
||||
try {
|
||||
$html = $errorRenderer->renderFromHandlerContext($errorHandlerContext, true);
|
||||
|
||||
// Speichere in temporäre Datei zum Betrachten im Browser
|
||||
$outputFile = __DIR__ . '/../tmp/enhanced-error-page-test.html';
|
||||
file_put_contents($outputFile, $html);
|
||||
|
||||
echo "✅ Enhanced Error Page erfolgreich gerendert!\n";
|
||||
echo "📁 Gespeichert in: {$outputFile}\n";
|
||||
echo "🌐 Öffne die Datei im Browser, um das Ergebnis zu sehen.\n";
|
||||
echo "\nHTML Länge: " . strlen($html) . " Zeichen\n";
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
echo "❌ Fehler beim Rendern der Enhanced Error Page:\n";
|
||||
echo "Fehler: " . $e->getMessage() . "\n";
|
||||
echo "Datei: " . $e->getFile() . "\n";
|
||||
echo "Zeile: " . $e->getLine() . "\n";
|
||||
echo "\nStack Trace:\n" . $e->getTraceAsString() . "\n";
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
use App\Framework\ErrorAggregation\ErrorEvent;
|
||||
use App\Framework\DateTime\Clock;
|
||||
use App\Framework\Core\ValueObjects\Timestamp;
|
||||
use App\Framework\Exception\Core\DatabaseErrorCode;
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
use App\Framework\Exception\ExceptionContext;
|
||||
use App\Framework\Exception\RequestContext;
|
||||
use App\Framework\Exception\SystemContext;
|
||||
use App\Framework\Exception\ErrorHandlerContext;
|
||||
|
||||
// Create test clock
|
||||
$clock = new class implements Clock {
|
||||
public function now(): \DateTimeImmutable { return new \DateTimeImmutable(); }
|
||||
public function fromTimestamp(Timestamp $timestamp): \DateTimeImmutable { return $timestamp->toDateTime(); }
|
||||
public function fromString(string $dateTime, ?string $format = null): \DateTimeImmutable { return new \DateTimeImmutable($dateTime); }
|
||||
public function today(): \DateTimeImmutable { return new \DateTimeImmutable('today'); }
|
||||
public function yesterday(): \DateTimeImmutable { return new \DateTimeImmutable('yesterday'); }
|
||||
public function tomorrow(): \DateTimeImmutable { return new \DateTimeImmutable('tomorrow'); }
|
||||
public function time(): Timestamp { return Timestamp::now(); }
|
||||
};
|
||||
|
||||
echo "Creating exception and context...\n";
|
||||
$exception = FrameworkException::create(
|
||||
DatabaseErrorCode::QUERY_FAILED,
|
||||
'Test database query failed'
|
||||
);
|
||||
|
||||
$exceptionContext = ExceptionContext::empty()
|
||||
->withOperation('test_operation', 'TestComponent')
|
||||
->withData(['test_key' => 'test_value']);
|
||||
|
||||
$requestContext = RequestContext::fromGlobals();
|
||||
$systemContext = SystemContext::current();
|
||||
|
||||
$errorHandlerContext = ErrorHandlerContext::create(
|
||||
$exceptionContext,
|
||||
$requestContext,
|
||||
$systemContext,
|
||||
['http_status' => 500]
|
||||
);
|
||||
|
||||
echo "Creating ErrorEvent directly...\n";
|
||||
try {
|
||||
$errorEvent = ErrorEvent::fromErrorHandlerContext($errorHandlerContext, $clock);
|
||||
echo "SUCCESS: ErrorEvent created\n";
|
||||
echo " ID: " . $errorEvent->id->toString() . "\n";
|
||||
echo " Service: " . $errorEvent->service . "\n";
|
||||
echo " Component: " . $errorEvent->component . "\n";
|
||||
echo " Operation: " . $errorEvent->operation . "\n";
|
||||
echo " Error Message: " . $errorEvent->errorMessage . "\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo "ERROR creating ErrorEvent: " . $e->getMessage() . "\n";
|
||||
echo "Exception class: " . get_class($e) . "\n";
|
||||
echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
use App\Framework\ErrorAggregation\ErrorAggregator;
|
||||
use App\Framework\ErrorAggregation\Storage\InMemoryErrorStorage;
|
||||
use App\Framework\Queue\InMemoryQueue;
|
||||
use App\Framework\Cache\Cache;
|
||||
use App\Framework\Cache\CacheIdentifier;
|
||||
use App\Framework\Cache\CacheItem;
|
||||
use App\Framework\Cache\CacheKey;
|
||||
use App\Framework\Cache\CacheResult;
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\DateTime\Clock;
|
||||
use App\Framework\Core\ValueObjects\Timestamp;
|
||||
use App\Framework\Exception\Core\DatabaseErrorCode;
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
use App\Framework\Exception\ExceptionContext;
|
||||
use App\Framework\Exception\RequestContext;
|
||||
use App\Framework\Exception\SystemContext;
|
||||
use App\Framework\Exception\ErrorHandlerContext;
|
||||
|
||||
// Create test cache
|
||||
$cache = new class implements Cache {
|
||||
private array $data = [];
|
||||
|
||||
public function get(CacheIdentifier ...$identifiers): CacheResult
|
||||
{
|
||||
$hits = [];
|
||||
$misses = [];
|
||||
foreach ($identifiers as $identifier) {
|
||||
$key = $identifier->toString();
|
||||
if (isset($this->data[$key])) {
|
||||
$value = $this->data[$key];
|
||||
$hits[$key] = $value instanceof CacheItem ? $value->value : $value;
|
||||
} else {
|
||||
$misses[] = $key;
|
||||
}
|
||||
}
|
||||
return new CacheResult($hits, $misses);
|
||||
}
|
||||
|
||||
public function set(CacheItem ...$items): bool
|
||||
{
|
||||
foreach ($items as $item) {
|
||||
$this->data[$item->key->toString()] = $item;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function has(CacheIdentifier ...$identifiers): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($identifiers as $identifier) {
|
||||
$key = (string) $identifier;
|
||||
$result[$key] = isset($this->data[$key]);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function forget(CacheIdentifier ...$identifiers): bool
|
||||
{
|
||||
foreach ($identifiers as $identifier) {
|
||||
$key = (string) $identifier;
|
||||
unset($this->data[$key]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function clear(): bool
|
||||
{
|
||||
$this->data = [];
|
||||
return true;
|
||||
}
|
||||
|
||||
public function remember(CacheKey $key, callable $callback, ?Duration $ttl = null): CacheItem
|
||||
{
|
||||
$keyStr = $key->toString();
|
||||
if (isset($this->data[$keyStr])) {
|
||||
return $this->data[$keyStr];
|
||||
}
|
||||
$value = $callback();
|
||||
$item = CacheItem::forSetting($key, $value, $ttl);
|
||||
$this->data[$keyStr] = $item;
|
||||
return $item;
|
||||
}
|
||||
};
|
||||
|
||||
// Create test clock
|
||||
$clock = new class implements Clock {
|
||||
public function now(): \DateTimeImmutable { return new \DateTimeImmutable(); }
|
||||
public function fromTimestamp(Timestamp $timestamp): \DateTimeImmutable { return $timestamp->toDateTime(); }
|
||||
public function fromString(string $dateTime, ?string $format = null): \DateTimeImmutable { return new \DateTimeImmutable($dateTime); }
|
||||
public function today(): \DateTimeImmutable { return new \DateTimeImmutable('today'); }
|
||||
public function yesterday(): \DateTimeImmutable { return new \DateTimeImmutable('yesterday'); }
|
||||
public function tomorrow(): \DateTimeImmutable { return new \DateTimeImmutable('tomorrow'); }
|
||||
public function time(): Timestamp { return Timestamp::now(); }
|
||||
};
|
||||
|
||||
echo "Creating ErrorAggregator...\n";
|
||||
$storage = new InMemoryErrorStorage();
|
||||
$alertQueue = new InMemoryQueue();
|
||||
|
||||
$errorAggregator = new ErrorAggregator(
|
||||
storage: $storage,
|
||||
cache: $cache,
|
||||
clock: $clock,
|
||||
alertQueue: $alertQueue
|
||||
);
|
||||
|
||||
echo "Creating exception and context...\n";
|
||||
$exception = FrameworkException::create(
|
||||
DatabaseErrorCode::QUERY_FAILED,
|
||||
'Test database query failed'
|
||||
);
|
||||
|
||||
$exceptionContext = ExceptionContext::empty()
|
||||
->withOperation('test_operation', 'TestComponent')
|
||||
->withData(['test_key' => 'test_value']);
|
||||
|
||||
$requestContext = RequestContext::fromGlobals();
|
||||
$systemContext = SystemContext::current();
|
||||
|
||||
$errorHandlerContext = ErrorHandlerContext::create(
|
||||
$exceptionContext,
|
||||
$requestContext,
|
||||
$systemContext,
|
||||
['http_status' => 500]
|
||||
);
|
||||
|
||||
echo "Processing error...\n";
|
||||
try {
|
||||
$errorAggregator->processError($errorHandlerContext);
|
||||
echo "SUCCESS: Error processed without exception\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo "ERROR processing error: " . $e->getMessage() . "\n";
|
||||
echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
|
||||
}
|
||||
|
||||
echo "\nGetting recent events...\n";
|
||||
$recentEvents = $errorAggregator->getRecentEvents(10);
|
||||
echo "Recent events count: " . count($recentEvents) . "\n";
|
||||
|
||||
echo "\nGetting active patterns...\n";
|
||||
$activePatterns = $errorAggregator->getActivePatterns(10);
|
||||
echo "Active patterns count: " . count($activePatterns) . "\n";
|
||||
|
||||
if (count($recentEvents) > 0) {
|
||||
echo "\nFirst event details:\n";
|
||||
$event = $recentEvents[0];
|
||||
echo " Error message: " . $event->errorMessage . "\n";
|
||||
echo " Component: " . $event->component . "\n";
|
||||
echo " Operation: " . $event->operation . "\n";
|
||||
}
|
||||
|
||||
if (count($activePatterns) > 0) {
|
||||
echo "\nFirst pattern details:\n";
|
||||
$pattern = $activePatterns[0];
|
||||
echo " Component: " . $pattern->component . "\n";
|
||||
echo " Occurrence count: " . $pattern->occurrenceCount . "\n";
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
use App\Framework\ErrorHandling\ErrorLogger;
|
||||
use App\Framework\Exception\ErrorCode;
|
||||
use App\Framework\Exception\ErrorHandlerContext;
|
||||
use App\Framework\Exception\ExceptionContext;
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
use App\Framework\Exception\RequestContext;
|
||||
use App\Framework\Exception\SystemContext;
|
||||
|
||||
echo "=== Testing Advanced Error Context System ===\n\n";
|
||||
|
||||
echo "1. Testing Basic FrameworkException with ErrorCode:\n";
|
||||
|
||||
try {
|
||||
$exception = FrameworkException::create(
|
||||
ErrorCode::DB_CONNECTION_FAILED,
|
||||
"Connection to database failed"
|
||||
)->withData([
|
||||
'host' => 'localhost',
|
||||
'port' => 3306,
|
||||
'database' => 'test_db',
|
||||
])->withOperation('database.connect', 'DatabaseManager');
|
||||
|
||||
throw $exception;
|
||||
} catch (FrameworkException $e) {
|
||||
echo " ✅ Exception created with ErrorCode\n";
|
||||
echo " • Message: {$e->getMessage()}\n";
|
||||
echo " • Error Code: {$e->getErrorCode()?->value}\n";
|
||||
echo " • Category: {$e->getErrorCode()?->getCategory()}\n";
|
||||
echo " • Recoverable: " . ($e->isRecoverable() ? 'Yes' : 'No') . "\n";
|
||||
echo " • Retry After: " . ($e->getRetryAfter() ?? 'None') . " seconds\n";
|
||||
echo " • Recovery Hint: {$e->getRecoveryHint()}\n";
|
||||
echo " • Operation: {$e->getContext()->operation}\n";
|
||||
echo " • Component: {$e->getContext()->component}\n\n";
|
||||
}
|
||||
|
||||
echo "2. Testing ExceptionContext fluent interface:\n";
|
||||
|
||||
try {
|
||||
$context = ExceptionContext::forOperation('user.create', 'UserService')
|
||||
->withData([
|
||||
'user_id' => 'user_123',
|
||||
'email' => 'test@example.com',
|
||||
])
|
||||
->withDebug([
|
||||
'validation_errors' => ['email_already_exists'],
|
||||
'retry_count' => 1,
|
||||
])
|
||||
->withMetadata([
|
||||
'security_event' => false,
|
||||
'business_critical' => true,
|
||||
]);
|
||||
|
||||
echo " ✅ ExceptionContext created with fluent interface\n";
|
||||
echo " • Operation: {$context->operation}\n";
|
||||
echo " • Component: {$context->component}\n";
|
||||
echo " • Data keys: " . implode(', ', array_keys($context->data)) . "\n";
|
||||
echo " • Debug keys: " . implode(', ', array_keys($context->debug)) . "\n";
|
||||
echo " • Metadata keys: " . implode(', ', array_keys($context->metadata)) . "\n\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo " ❌ Error: {$e->getMessage()}\n\n";
|
||||
}
|
||||
|
||||
echo "3. Testing ErrorHandlerContext creation:\n";
|
||||
|
||||
try {
|
||||
$exceptionContext = ExceptionContext::forOperation('payment.process', 'PaymentService')
|
||||
->withData([
|
||||
'amount' => 99.99,
|
||||
'currency' => 'EUR',
|
||||
'customer_id' => 'cust_123',
|
||||
])
|
||||
->withMetadata([
|
||||
'security_event' => true,
|
||||
'security_level' => 'WARN',
|
||||
'security_description' => 'Suspicious payment attempt',
|
||||
]);
|
||||
|
||||
$requestContext = RequestContext::fromGlobals();
|
||||
$systemContext = SystemContext::current();
|
||||
|
||||
$errorContext = ErrorHandlerContext::create(
|
||||
$exceptionContext,
|
||||
$requestContext,
|
||||
$systemContext,
|
||||
['transaction_id' => 'tx_456']
|
||||
);
|
||||
|
||||
echo " ✅ ErrorHandlerContext created\n";
|
||||
echo " • Request method: " . ($errorContext->request->requestMethod ?? 'CLI') . "\n";
|
||||
echo " • Client IP: " . ($errorContext->request->clientIp ?? 'localhost') . "\n";
|
||||
echo " • Memory usage: {$errorContext->system->memoryUsage}\n";
|
||||
echo " • PHP version: {$errorContext->system->phpVersion}\n";
|
||||
echo " • Security event: " . ($errorContext->exception->metadata['security_event'] ? 'Yes' : 'No') . "\n\n";
|
||||
|
||||
// Test security event format
|
||||
echo " 📊 Security Event Format:\n";
|
||||
$securityEvent = $errorContext->toSecurityEventFormat('test-app');
|
||||
foreach ($securityEvent as $key => $value) {
|
||||
if (is_string($value) || is_numeric($value)) {
|
||||
echo " • {$key}: {$value}\n";
|
||||
}
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
echo " ❌ Error: {$e->getMessage()}\n\n";
|
||||
}
|
||||
|
||||
echo "4. Testing Error Logging:\n";
|
||||
|
||||
try {
|
||||
// Create an exception with security context
|
||||
$exception = FrameworkException::create(
|
||||
ErrorCode::SEC_UNAUTHORIZED_ACCESS,
|
||||
"Unauthorized access attempt detected"
|
||||
)->withData([
|
||||
'endpoint' => '/admin/dashboard',
|
||||
'user_agent' => 'curl/7.68.0',
|
||||
'ip_address' => '192.168.1.100',
|
||||
])->withMetadata([
|
||||
'security_event' => true,
|
||||
'security_level' => 'ERROR',
|
||||
'security_description' => 'Unauthorized admin access attempt',
|
||||
]);
|
||||
|
||||
$errorContext = ErrorHandlerContext::fromException($exception, [
|
||||
'alert_sent' => true,
|
||||
'blocked' => true,
|
||||
]);
|
||||
|
||||
$errorLogger = new ErrorLogger();
|
||||
|
||||
echo " ✅ Created security exception and error context\n";
|
||||
echo " • Exception type: " . get_class($exception) . "\n";
|
||||
echo " • Error code: {$exception->getErrorCode()?->value}\n";
|
||||
echo " • Security event: " . ($errorContext->exception->metadata['security_event'] ? 'Yes' : 'No') . "\n";
|
||||
|
||||
// Test logging data format
|
||||
$loggingData = $errorContext->forLogging();
|
||||
echo " • Logging data keys: " . implode(', ', array_keys($loggingData)) . "\n\n";
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
echo " ❌ Error: {$e->getMessage()}\n\n";
|
||||
}
|
||||
|
||||
echo "5. Testing ErrorCode system:\n";
|
||||
|
||||
try {
|
||||
$errorCodes = [
|
||||
ErrorCode::DB_CONNECTION_FAILED,
|
||||
ErrorCode::AUTH_TOKEN_EXPIRED,
|
||||
ErrorCode::SEC_XSS_ATTEMPT,
|
||||
ErrorCode::VAL_BUSINESS_RULE_VIOLATION,
|
||||
ErrorCode::HTTP_RATE_LIMIT_EXCEEDED,
|
||||
];
|
||||
|
||||
echo " 📋 ErrorCode Analysis:\n";
|
||||
foreach ($errorCodes as $code) {
|
||||
echo " • {$code->value}:\n";
|
||||
echo " - Category: {$code->getCategory()}\n";
|
||||
echo " - Description: {$code->getDescription()}\n";
|
||||
echo " - Recoverable: " . ($code->isRecoverable() ? 'Yes' : 'No') . "\n";
|
||||
echo " - Retry after: " . ($code->getRetryAfterSeconds() ?? 'None') . " seconds\n";
|
||||
echo " - Recovery hint: {$code->getRecoveryHint()}\n\n";
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
echo " ❌ Error: {$e->getMessage()}\n\n";
|
||||
}
|
||||
|
||||
echo "=== Advanced Error Context System Test Completed ===\n";
|
||||
@@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
use App\Framework\Queue\Exceptions\JobNotFoundException;
|
||||
use App\Framework\Queue\ValueObjects\JobId;
|
||||
use App\Framework\ErrorHandling\ErrorHandler;
|
||||
use App\Framework\DI\DefaultContainer;
|
||||
use App\Framework\Http\ResponseEmitter;
|
||||
use App\Framework\Http\RequestIdGenerator;
|
||||
|
||||
echo "=== Testing ErrorHandler Enhancements ===\n\n";
|
||||
|
||||
// Create minimal container for ErrorHandler
|
||||
$container = new DefaultContainer();
|
||||
$emitter = new ResponseEmitter();
|
||||
$requestIdGenerator = new RequestIdGenerator();
|
||||
|
||||
$errorHandler = new ErrorHandler(
|
||||
$emitter,
|
||||
$container,
|
||||
$requestIdGenerator,
|
||||
null,
|
||||
true // Debug mode to see recovery hints
|
||||
);
|
||||
|
||||
// Test 1: Create a JobNotFoundException (uses QUEUE007 error code)
|
||||
echo "Test 1: JobNotFoundException with ErrorCode integration\n";
|
||||
echo str_repeat('-', 60) . "\n";
|
||||
|
||||
try {
|
||||
$jobId = JobId::fromString('test-job-123');
|
||||
throw JobNotFoundException::byId($jobId);
|
||||
} catch (JobNotFoundException $e) {
|
||||
echo "Exception: " . get_class($e) . "\n";
|
||||
echo "Message: " . $e->getMessage() . "\n";
|
||||
echo "Error Code: " . $e->getErrorCode()->getValue() . "\n";
|
||||
echo "Category: " . $e->getErrorCode()->getCategory() . "\n";
|
||||
echo "Severity: " . $e->getErrorCode()->getSeverity()->value . "\n";
|
||||
echo "Is Recoverable: " . ($e->getErrorCode()->isRecoverable() ? 'Yes' : 'No') . "\n";
|
||||
echo "Recovery Hint: " . $e->getErrorCode()->getRecoveryHint() . "\n";
|
||||
|
||||
// Test ErrorHandler metadata creation
|
||||
echo "\nTesting ErrorHandler metadata generation...\n";
|
||||
$response = $errorHandler->createHttpResponse($e);
|
||||
|
||||
echo "HTTP Status Code would be: " . $response->status->value . "\n";
|
||||
echo "Response created successfully!\n";
|
||||
}
|
||||
|
||||
echo "\n" . str_repeat('=', 60) . "\n\n";
|
||||
|
||||
// Test 2: Test with different error code that has Retry-After
|
||||
echo "Test 2: Testing with error code that has Retry-After\n";
|
||||
echo str_repeat('-', 60) . "\n";
|
||||
|
||||
use App\Framework\Queue\Exceptions\ChainNotFoundException;
|
||||
|
||||
try {
|
||||
throw ChainNotFoundException::byId('chain-123');
|
||||
} catch (ChainNotFoundException $e) {
|
||||
echo "Exception: " . get_class($e) . "\n";
|
||||
echo "Error Code: " . $e->getErrorCode()->getValue() . "\n";
|
||||
echo "Retry After: " . ($e->getErrorCode()->getRetryAfterSeconds() ?? 'null') . " seconds\n";
|
||||
|
||||
$response = $errorHandler->createHttpResponse($e);
|
||||
echo "HTTP Status Code: " . $response->status->value . "\n";
|
||||
echo "Response headers would include Retry-After if applicable\n";
|
||||
}
|
||||
|
||||
echo "\n✅ All ErrorHandler enhancement tests completed!\n";
|
||||
@@ -1,194 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Test Script for ErrorHandling Module
|
||||
*
|
||||
* Tests the current error handling system with various exception types
|
||||
* to verify handler registration, priority execution, and response generation.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
use App\Framework\ErrorHandling\Handlers\{
|
||||
ValidationErrorHandler,
|
||||
DatabaseErrorHandler,
|
||||
HttpErrorHandler,
|
||||
FallbackErrorHandler
|
||||
};
|
||||
use App\Framework\ErrorHandling\{ErrorHandlerManager, ErrorHandlerRegistry};
|
||||
use App\Framework\Validation\Exceptions\ValidationException;
|
||||
use App\Framework\Validation\ValidationResult;
|
||||
use App\Framework\Database\Exception\DatabaseException;
|
||||
use App\Framework\Http\Exception\HttpException;
|
||||
use App\Framework\Http\Status;
|
||||
use App\Framework\Exception\ExceptionContext;
|
||||
use App\Framework\Logging\Logger;
|
||||
use App\Framework\Logging\ValueObjects\LogContext;
|
||||
use App\Framework\Logging\LogLevel;
|
||||
|
||||
echo "=== ErrorHandling Module Test ===\n\n";
|
||||
|
||||
// Setup Logger (mock for testing)
|
||||
$logger = new class implements Logger {
|
||||
public function debug(string $message, ?LogContext $context = null): void {
|
||||
echo "📝 [Logger::debug] {$message}\n";
|
||||
}
|
||||
|
||||
public function info(string $message, ?LogContext $context = null): void {
|
||||
echo "📝 [Logger::info] {$message}\n";
|
||||
}
|
||||
|
||||
public function notice(string $message, ?LogContext $context = null): void {
|
||||
echo "📝 [Logger::notice] {$message}\n";
|
||||
}
|
||||
|
||||
public function warning(string $message, ?LogContext $context = null): void {
|
||||
echo "📝 [Logger::warning] {$message}\n";
|
||||
}
|
||||
|
||||
public function error(string $message, ?LogContext $context = null): void {
|
||||
echo "📝 [Logger::error] {$message}\n";
|
||||
if ($context && $context->structured) {
|
||||
echo " Context: " . print_r($context->structured, true) . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
public function critical(string $message, ?LogContext $context = null): void {
|
||||
echo "📝 [Logger::critical] {$message}\n";
|
||||
}
|
||||
|
||||
public function alert(string $message, ?LogContext $context = null): void {
|
||||
echo "📝 [Logger::alert] {$message}\n";
|
||||
}
|
||||
|
||||
public function emergency(string $message, ?LogContext $context = null): void {
|
||||
echo "📝 [Logger::emergency] {$message}\n";
|
||||
}
|
||||
|
||||
public function log(LogLevel $level, string $message, ?LogContext $context = null): void {
|
||||
echo "📝 [Logger::{$level->value}] {$message}\n";
|
||||
}
|
||||
};
|
||||
|
||||
// Setup ErrorHandlerManager
|
||||
$registry = new ErrorHandlerRegistry();
|
||||
$manager = new ErrorHandlerManager($registry);
|
||||
|
||||
// Register handlers in priority order
|
||||
echo "Registering handlers...\n";
|
||||
$manager->register(new ValidationErrorHandler());
|
||||
$manager->register(new DatabaseErrorHandler($logger));
|
||||
$manager->register(new HttpErrorHandler());
|
||||
$manager->register(new FallbackErrorHandler($logger));
|
||||
echo "✅ All handlers registered\n\n";
|
||||
|
||||
// Test 1: ValidationException
|
||||
echo "--- Test 1: ValidationException ---\n";
|
||||
try {
|
||||
$validationResult = new ValidationResult();
|
||||
$validationResult->addErrors('email', ['Email is required', 'Email format is invalid']);
|
||||
$validationResult->addErrors('password', ['Password must be at least 8 characters']);
|
||||
|
||||
$exception = new ValidationException($validationResult);
|
||||
|
||||
$result = $manager->handleException($exception);
|
||||
|
||||
echo "✅ Handled: " . ($result->handled ? 'Yes' : 'No') . "\n";
|
||||
echo " Message: {$result->message}\n";
|
||||
echo " Status Code: {$result->statusCode}\n";
|
||||
echo " Error Type: {$result->data['error_type']}\n";
|
||||
echo " Errors: " . print_r($result->data['errors'], true) . "\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo "❌ Error: {$e->getMessage()}\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 2: DatabaseException
|
||||
echo "--- Test 2: DatabaseException ---\n";
|
||||
try {
|
||||
$exception = DatabaseException::fromContext(
|
||||
'Connection failed: Too many connections',
|
||||
ExceptionContext::empty()
|
||||
);
|
||||
|
||||
$result = $manager->handleException($exception);
|
||||
|
||||
echo "✅ Handled: " . ($result->handled ? 'Yes' : 'No') . "\n";
|
||||
echo " Message: {$result->message}\n";
|
||||
echo " Status Code: {$result->statusCode}\n";
|
||||
echo " Error Type: {$result->data['error_type']}\n";
|
||||
echo " Retry After: {$result->data['retry_after']} seconds\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo "❌ Error: {$e->getMessage()}\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 3: HttpException
|
||||
echo "--- Test 3: HttpException (404 Not Found) ---\n";
|
||||
try {
|
||||
$exception = new HttpException(
|
||||
'Resource not found',
|
||||
Status::NOT_FOUND,
|
||||
headers: ['X-Resource-Type' => 'User']
|
||||
);
|
||||
|
||||
$result = $manager->handleException($exception);
|
||||
|
||||
echo "✅ Handled: " . ($result->handled ? 'Yes' : 'No') . "\n";
|
||||
echo " Message: {$result->message}\n";
|
||||
echo " Status Code: {$result->statusCode}\n";
|
||||
echo " Error Type: {$result->data['error_type']}\n";
|
||||
echo " Headers: " . print_r($result->data['headers'], true) . "\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo "❌ Error: {$e->getMessage()}\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 4: Generic RuntimeException (Fallback)
|
||||
echo "--- Test 4: Generic RuntimeException (Fallback Handler) ---\n";
|
||||
try {
|
||||
$exception = new \RuntimeException('Something unexpected happened');
|
||||
|
||||
$result = $manager->handleException($exception);
|
||||
|
||||
echo "✅ Handled: " . ($result->handled ? 'Yes' : 'No') . "\n";
|
||||
echo " Message: {$result->message}\n";
|
||||
echo " Status Code: {$result->statusCode}\n";
|
||||
echo " Error Type: {$result->data['error_type']}\n";
|
||||
echo " Exception Class: {$result->data['exception_class']}\n";
|
||||
echo " Is Final: " . ($result->isFinal ? 'Yes' : 'No') . "\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo "❌ Error: {$e->getMessage()}\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 5: PDOException (Database Handler)
|
||||
echo "--- Test 5: PDOException ---\n";
|
||||
try {
|
||||
$exception = new \PDOException('SQLSTATE[HY000] [2002] Connection refused');
|
||||
|
||||
$result = $manager->handleException($exception);
|
||||
|
||||
echo "✅ Handled: " . ($result->handled ? 'Yes' : 'No') . "\n";
|
||||
echo " Message: {$result->message}\n";
|
||||
echo " Status Code: {$result->statusCode}\n";
|
||||
echo " Error Type: {$result->data['error_type']}\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo "❌ Error: {$e->getMessage()}\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Test 6: Handler Priority Order
|
||||
echo "--- Test 6: Handler Priority Verification ---\n";
|
||||
$handlers = $manager->getHandlers();
|
||||
echo "Registered handlers in priority order:\n";
|
||||
foreach ($handlers as $index => $handler) {
|
||||
$priority = $handler->getPriority();
|
||||
$name = $handler->getName();
|
||||
echo " " . ($index + 1) . ". {$name} (Priority: {$priority->value})\n";
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
echo "=== All Tests Completed ===\n";
|
||||
@@ -1,202 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
use App\Framework\Cache\Cache;
|
||||
use App\Framework\Cache\CacheIdentifier;
|
||||
use App\Framework\Cache\CacheKey;
|
||||
use App\Framework\Cache\CacheItem;
|
||||
use App\Framework\Cache\CacheResult;
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\DateTime\SystemClock;
|
||||
use App\Framework\ErrorAggregation\ErrorAggregator;
|
||||
use App\Framework\ErrorAggregation\Storage\InMemoryErrorStorage;
|
||||
use App\Framework\ErrorHandling\ErrorHandler;
|
||||
use App\Framework\ErrorReporting\ErrorReporter;
|
||||
use App\Framework\ErrorReporting\Storage\InMemoryErrorReportStorage;
|
||||
use App\Framework\Exception\Core\DatabaseErrorCode;
|
||||
use App\Framework\Exception\FrameworkException;
|
||||
use App\Framework\Http\RequestIdGenerator;
|
||||
use App\Framework\Http\ResponseEmitter;
|
||||
use App\Framework\DI\DefaultContainer;
|
||||
use App\Framework\Queue\InMemoryQueue;
|
||||
|
||||
// Create all dependencies
|
||||
$container = new DefaultContainer();
|
||||
$emitter = new ResponseEmitter();
|
||||
$requestIdGenerator = new RequestIdGenerator();
|
||||
|
||||
// Error Aggregation setup
|
||||
$errorStorage = new InMemoryErrorStorage();
|
||||
$cache = new class implements Cache {
|
||||
private array $data = [];
|
||||
|
||||
public function get(CacheIdentifier ...$identifiers): CacheResult
|
||||
{
|
||||
$items = [];
|
||||
foreach ($identifiers as $identifier) {
|
||||
$keyStr = $identifier->toString();
|
||||
if (isset($this->data[$keyStr])) {
|
||||
$items[] = $this->data[$identifier->toString()];
|
||||
} else {
|
||||
$items[] = CacheItem::miss($identifier instanceof CacheKey ? $identifier : CacheKey::fromString($identifier->toString()));
|
||||
}
|
||||
}
|
||||
|
||||
return count($items) === 1
|
||||
? CacheResult::single($items[0])
|
||||
: CacheResult::multiple($items);
|
||||
}
|
||||
|
||||
public function set(CacheItem ...$items): bool
|
||||
{
|
||||
foreach ($items as $item) {
|
||||
$this->data[$item->key->toString()] = $item;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function has(CacheIdentifier ...$identifiers): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($identifiers as $identifier) {
|
||||
$result[] = isset($this->data[$identifier->toString()]);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function forget(CacheIdentifier ...$identifiers): bool
|
||||
{
|
||||
foreach ($identifiers as $identifier) {
|
||||
unset($this->data[$identifier->toString()]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function clear(): bool
|
||||
{
|
||||
$this->data = [];
|
||||
return true;
|
||||
}
|
||||
|
||||
public function remember(CacheKey $key, callable $callback, ?Duration $ttl = null): CacheItem
|
||||
{
|
||||
$keyStr = $key->toString();
|
||||
if (isset($this->data[$keyStr])) {
|
||||
return $this->data[$keyStr];
|
||||
}
|
||||
|
||||
$value = $callback();
|
||||
$item = $ttl ? CacheItem::forSetting($key, $value, $ttl) : CacheItem::miss($key);
|
||||
$this->data[$keyStr] = $item;
|
||||
return $item;
|
||||
}
|
||||
};
|
||||
$clock = new SystemClock();
|
||||
$alertQueue = new InMemoryQueue();
|
||||
|
||||
$errorAggregator = new ErrorAggregator(
|
||||
storage: $errorStorage,
|
||||
cache: $cache,
|
||||
clock: $clock,
|
||||
alertQueue: $alertQueue,
|
||||
logger: null,
|
||||
batchSize: 100,
|
||||
maxRetentionDays: 90
|
||||
);
|
||||
|
||||
// Error Reporting setup
|
||||
$errorReportStorage = new InMemoryErrorReportStorage();
|
||||
$reportQueue = new InMemoryQueue();
|
||||
|
||||
$errorReporter = new ErrorReporter(
|
||||
storage: $errorReportStorage,
|
||||
clock: $clock,
|
||||
logger: null,
|
||||
queue: $reportQueue,
|
||||
asyncProcessing: false,
|
||||
processors: [],
|
||||
filters: []
|
||||
);
|
||||
|
||||
// Create ErrorHandler
|
||||
$errorHandler = new ErrorHandler(
|
||||
emitter: $emitter,
|
||||
container: $container,
|
||||
requestIdGenerator: $requestIdGenerator,
|
||||
errorAggregator: $errorAggregator,
|
||||
errorReporter: $errorReporter,
|
||||
logger: null,
|
||||
isDebugMode: true,
|
||||
securityHandler: null
|
||||
);
|
||||
|
||||
echo "=== Testing Error Pipeline ===\n\n";
|
||||
|
||||
// Create test exception
|
||||
$exception = FrameworkException::create(
|
||||
DatabaseErrorCode::QUERY_FAILED,
|
||||
'Test database error'
|
||||
);
|
||||
|
||||
echo "1. Creating ErrorHandlerContext manually...\n";
|
||||
$errorHandlerContext = (new \ReflectionClass($errorHandler))->getMethod('createErrorHandlerContext')->invoke($errorHandler, $exception, null);
|
||||
echo " Context created successfully\n";
|
||||
echo " ExceptionContext: operation={$errorHandlerContext->exception->operation}, component={$errorHandlerContext->exception->component}\n";
|
||||
|
||||
echo "\n2. Testing ErrorEvent::fromErrorHandlerContext...\n";
|
||||
try {
|
||||
$errorEvent = \App\Framework\ErrorAggregation\ErrorEvent::fromErrorHandlerContext($errorHandlerContext, $clock);
|
||||
echo " ErrorEvent created successfully\n";
|
||||
echo " Event ID: {$errorEvent->id}\n";
|
||||
echo " Event message: {$errorEvent->errorMessage}\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo " EXCEPTION in fromErrorHandlerContext: " . $e->getMessage() . "\n";
|
||||
echo " AT: " . $e->getFile() . ":" . $e->getLine() . "\n";
|
||||
echo " TRACE:\n";
|
||||
foreach ($e->getTrace() as $frame) {
|
||||
$file = $frame['file'] ?? 'unknown';
|
||||
$line = $frame['line'] ?? 0;
|
||||
$function = $frame['function'] ?? 'unknown';
|
||||
echo " $file:$line - $function()\n";
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "\n3. Testing ErrorAggregator.processError directly...\n";
|
||||
try {
|
||||
$errorAggregator->processError($errorHandlerContext);
|
||||
echo " processError completed successfully\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo " EXCEPTION in processError: " . $e->getMessage() . "\n";
|
||||
echo " AT: " . $e->getFile() . ":" . $e->getLine() . "\n";
|
||||
throw $e;
|
||||
}
|
||||
|
||||
echo "\n3. Creating HTTP response from exception...\n";
|
||||
$response = $errorHandler->createHttpResponse($exception);
|
||||
|
||||
echo "\n4. Checking ErrorAggregator storage...\n";
|
||||
$events = $errorStorage->getRecentEvents(10);
|
||||
echo " Events stored: " . count($events) . "\n";
|
||||
if (count($events) > 0) {
|
||||
echo " First event message: " . $events[0]->message . "\n";
|
||||
echo " First event severity: " . $events[0]->severity->value . "\n";
|
||||
} else {
|
||||
echo " No events stored!\n";
|
||||
}
|
||||
|
||||
echo "\n3. Checking ErrorReporter storage...\n";
|
||||
$reports = $errorReportStorage->findRecent(10);
|
||||
echo " Reports stored: " . count($reports) . "\n";
|
||||
if (count($reports) > 0) {
|
||||
echo " First report message: " . $reports[0]->message . "\n";
|
||||
echo " First report exception: " . $reports[0]->exception . "\n";
|
||||
echo " First report level: " . $reports[0]->level . "\n";
|
||||
} else {
|
||||
echo " No reports stored!\n";
|
||||
}
|
||||
|
||||
echo "\n=== Test Complete ===\n";
|
||||
@@ -1,135 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Test script for ErrorReportingConfig integration
|
||||
*
|
||||
* Verifies that ErrorReportingConfig correctly loads from environment
|
||||
* and applies environment-specific defaults.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
use App\Framework\Config\Environment;
|
||||
use App\Framework\Config\EnvironmentType;
|
||||
use App\Framework\ErrorReporting\ErrorReportingConfig;
|
||||
|
||||
echo "=== Testing ErrorReportingConfig Integration ===\n\n";
|
||||
|
||||
// Test 1: Development Environment
|
||||
echo "Test 1: Development Environment Configuration\n";
|
||||
echo "=============================================\n";
|
||||
|
||||
$devEnv = new Environment(['APP_ENV' => 'development']);
|
||||
$devConfig = ErrorReportingConfig::fromEnvironment($devEnv);
|
||||
|
||||
echo "✓ Config loaded from environment\n";
|
||||
echo " - enabled: " . ($devConfig->enabled ? 'true' : 'false') . "\n";
|
||||
echo " - asyncProcessing: " . ($devConfig->asyncProcessing ? 'true' : 'false') . " (expected: false for dev)\n";
|
||||
echo " - filterLevels: " . (empty($devConfig->filterLevels) ? 'ALL' : implode(', ', $devConfig->filterLevels)) . "\n";
|
||||
echo " - maxStackTraceDepth: {$devConfig->maxStackTraceDepth} (expected: 30)\n";
|
||||
echo " - sanitizeSensitiveData: " . ($devConfig->sanitizeSensitiveData ? 'true' : 'false') . " (expected: false)\n";
|
||||
echo " - maxReportsPerMinute: {$devConfig->maxReportsPerMinute} (expected: 1000)\n\n";
|
||||
|
||||
// Test 2: Production Environment
|
||||
echo "Test 2: Production Environment Configuration\n";
|
||||
echo "===========================================\n";
|
||||
|
||||
$prodEnv = new Environment(['APP_ENV' => 'production']);
|
||||
$prodConfig = ErrorReportingConfig::fromEnvironment($prodEnv);
|
||||
|
||||
echo "✓ Config loaded from environment\n";
|
||||
echo " - enabled: " . ($prodConfig->enabled ? 'true' : 'false') . "\n";
|
||||
echo " - asyncProcessing: " . ($prodConfig->asyncProcessing ? 'true' : 'false') . " (expected: true)\n";
|
||||
echo " - filterLevels: " . (empty($prodConfig->filterLevels) ? 'ALL' : implode(', ', $prodConfig->filterLevels)) . " (expected: error, critical, alert, emergency)\n";
|
||||
echo " - maxStackTraceDepth: {$prodConfig->maxStackTraceDepth} (expected: 15)\n";
|
||||
echo " - sanitizeSensitiveData: " . ($prodConfig->sanitizeSensitiveData ? 'true' : 'false') . " (expected: true)\n";
|
||||
echo " - maxReportsPerMinute: {$prodConfig->maxReportsPerMinute} (expected: 30)\n\n";
|
||||
|
||||
// Test 3: Staging Environment
|
||||
echo "Test 3: Staging Environment Configuration\n";
|
||||
echo "========================================\n";
|
||||
|
||||
$stagingEnv = new Environment(['APP_ENV' => 'staging']);
|
||||
$stagingConfig = ErrorReportingConfig::fromEnvironment($stagingEnv);
|
||||
|
||||
echo "✓ Config loaded from environment\n";
|
||||
echo " - enabled: " . ($stagingConfig->enabled ? 'true' : 'false') . "\n";
|
||||
echo " - asyncProcessing: " . ($stagingConfig->asyncProcessing ? 'true' : 'false') . " (expected: true)\n";
|
||||
echo " - filterLevels: " . (empty($stagingConfig->filterLevels) ? 'ALL' : implode(', ', $stagingConfig->filterLevels)) . " (expected: warning and above)\n";
|
||||
echo " - maxStackTraceDepth: {$stagingConfig->maxStackTraceDepth} (expected: 20)\n";
|
||||
echo " - analyticsRetentionDays: {$stagingConfig->analyticsRetentionDays} (expected: 14)\n\n";
|
||||
|
||||
// Test 4: Environment Variable Overrides
|
||||
echo "Test 4: Environment Variable Overrides\n";
|
||||
echo "=====================================\n";
|
||||
|
||||
$overrideEnv = new Environment([
|
||||
'APP_ENV' => 'production',
|
||||
'ERROR_REPORTING_ENABLED' => 'false',
|
||||
'ERROR_REPORTING_ASYNC' => 'false',
|
||||
'ERROR_REPORTING_FILTER_LEVELS' => 'critical,emergency',
|
||||
'ERROR_REPORTING_MAX_STACK_DEPTH' => '5',
|
||||
'ERROR_REPORTING_SAMPLING_RATE' => '50'
|
||||
]);
|
||||
|
||||
$overrideConfig = ErrorReportingConfig::fromEnvironment($overrideEnv);
|
||||
|
||||
echo "✓ Config with environment overrides\n";
|
||||
echo " - enabled: " . ($overrideConfig->enabled ? 'true' : 'false') . " (override: false)\n";
|
||||
echo " - asyncProcessing: " . ($overrideConfig->asyncProcessing ? 'true' : 'false') . " (override: false)\n";
|
||||
echo " - filterLevels: " . implode(', ', $overrideConfig->filterLevels) . " (override: critical, emergency)\n";
|
||||
echo " - maxStackTraceDepth: {$overrideConfig->maxStackTraceDepth} (override: 5)\n";
|
||||
echo " - samplingRate: {$overrideConfig->samplingRate} (override: 50)\n\n";
|
||||
|
||||
// Test 5: Helper Methods
|
||||
echo "Test 5: Helper Methods\n";
|
||||
echo "====================\n";
|
||||
|
||||
$testConfig = ErrorReportingConfig::fromEnvironment($prodEnv);
|
||||
|
||||
// shouldReportLevel
|
||||
$shouldReportError = $testConfig->shouldReportLevel('error');
|
||||
$shouldReportDebug = $testConfig->shouldReportLevel('debug');
|
||||
|
||||
echo "✓ shouldReportLevel()\n";
|
||||
echo " - 'error' level: " . ($shouldReportError ? 'REPORT' : 'SKIP') . " (expected: REPORT)\n";
|
||||
echo " - 'debug' level: " . ($shouldReportDebug ? 'REPORT' : 'SKIP') . " (expected: SKIP in production)\n\n";
|
||||
|
||||
// shouldReportException
|
||||
$normalException = new \RuntimeException('Test error');
|
||||
$shouldReport = $testConfig->shouldReportException($normalException);
|
||||
|
||||
echo "✓ shouldReportException()\n";
|
||||
echo " - RuntimeException: " . ($shouldReport ? 'REPORT' : 'SKIP') . " (expected: REPORT)\n\n";
|
||||
|
||||
// shouldSample
|
||||
$samples = 0;
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
if ($testConfig->shouldSample()) {
|
||||
$samples++;
|
||||
}
|
||||
}
|
||||
|
||||
echo "✓ shouldSample()\n";
|
||||
echo " - Sampling rate: {$testConfig->samplingRate}%\n";
|
||||
echo " - Samples in 100 attempts: {$samples} (expected: ~{$testConfig->samplingRate})\n\n";
|
||||
|
||||
// Test 6: Direct Environment Type
|
||||
echo "Test 6: Direct Environment Type Configuration\n";
|
||||
echo "============================================\n";
|
||||
|
||||
$directConfig = ErrorReportingConfig::forEnvironment(EnvironmentType::DEV, $devEnv);
|
||||
|
||||
echo "✓ Config created directly with EnvironmentType::DEV\n";
|
||||
echo " - asyncProcessing: " . ($directConfig->asyncProcessing ? 'true' : 'false') . " (expected: false)\n";
|
||||
echo " - maxReportsPerMinute: {$directConfig->maxReportsPerMinute} (expected: 1000)\n\n";
|
||||
|
||||
// Validation
|
||||
echo "=== Validation Results ===\n";
|
||||
echo "✓ All environment configurations loaded successfully\n";
|
||||
echo "✓ Environment-specific defaults applied correctly\n";
|
||||
echo "✓ Environment variable overrides work as expected\n";
|
||||
echo "✓ Helper methods function correctly\n";
|
||||
echo "✓ ErrorReportingConfig integration: PASSED\n";
|
||||
@@ -24,7 +24,7 @@ use App\Framework\Logging\DefaultLogger;
|
||||
use App\Framework\Logging\Handlers\ConsoleHandler;
|
||||
use App\Framework\Logging\LogLevel;
|
||||
use App\Framework\Performance\MemoryMonitor;
|
||||
use App\Framework\Reflection\CachedReflectionProvider;
|
||||
use App\Framework\ReflectionLegacy\CachedReflectionProvider;
|
||||
use App\Framework\Serializer\Php\PhpSerializer;
|
||||
|
||||
echo "🎯 Testing Discovery Event System\n";
|
||||
@@ -52,7 +52,7 @@ try {
|
||||
// Register optional services in container
|
||||
$container->singleton(\App\Framework\Logging\Logger::class, $logger);
|
||||
$container->singleton(\App\Framework\Performance\MemoryMonitor::class, $memoryMonitor);
|
||||
$container->singleton(\App\Framework\Reflection\ReflectionProvider::class, $reflectionProvider);
|
||||
$container->singleton(\App\Framework\ReflectionLegacy\ReflectionProvider::class, $reflectionProvider);
|
||||
$container->singleton(\App\Framework\Filesystem\FileSystemService::class, $fileSystemService);
|
||||
$container->singleton(\App\Framework\Core\Events\EventDispatcher::class, $eventDispatcher);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use App\Framework\Core\PathProvider;
|
||||
use App\Framework\DateTime\SystemClock;
|
||||
use App\Framework\DI\DefaultContainer;
|
||||
use App\Framework\Discovery\DiscoveryServiceBootstrapper;
|
||||
use App\Framework\Reflection\CachedReflectionProvider;
|
||||
use App\Framework\ReflectionLegacy\CachedReflectionProvider;
|
||||
use App\Framework\Serializer\Json\JsonSerializer;
|
||||
|
||||
echo "=== Testing Initializer Execution ===\n\n";
|
||||
@@ -37,7 +37,7 @@ try {
|
||||
$container->instance(\App\Framework\Serializer\Serializer::class, $serializer);
|
||||
$container->instance(\App\Framework\Cache\Cache::class, $cache);
|
||||
$container->instance(\App\Framework\DateTime\Clock::class, $clock);
|
||||
$container->instance(\App\Framework\Reflection\ReflectionProvider::class, new CachedReflectionProvider());
|
||||
$container->instance(\App\Framework\ReflectionLegacy\ReflectionProvider::class, new CachedReflectionProvider());
|
||||
|
||||
echo "Current execution context: " . ExecutionContext::detect()->getType()->value . "\n\n";
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ use App\Framework\Logging\DefaultLogger;
|
||||
use App\Framework\Logging\Handlers\ConsoleHandler;
|
||||
use App\Framework\Logging\LogLevel;
|
||||
use App\Framework\Performance\MemoryMonitor;
|
||||
use App\Framework\Reflection\CachedReflectionProvider;
|
||||
use App\Framework\ReflectionLegacy\CachedReflectionProvider;
|
||||
use App\Framework\Serializer\Php\PhpSerializer;
|
||||
|
||||
echo "🧠 Testing Memory Management System for Discovery Module\n";
|
||||
@@ -50,7 +50,7 @@ try {
|
||||
// Register optional services in container
|
||||
$container->singleton(\App\Framework\Logging\Logger::class, $logger);
|
||||
$container->singleton(\App\Framework\Performance\MemoryMonitor::class, $memoryMonitor);
|
||||
$container->singleton(\App\Framework\Reflection\ReflectionProvider::class, $reflectionProvider);
|
||||
$container->singleton(\App\Framework\ReflectionLegacy\ReflectionProvider::class, $reflectionProvider);
|
||||
$container->singleton(\App\Framework\Filesystem\FileSystemService::class, $fileSystemService);
|
||||
|
||||
echo "✅ Dependencies initialized\n";
|
||||
|
||||
@@ -22,7 +22,7 @@ use App\Framework\Logging\DefaultLogger;
|
||||
use App\Framework\Logging\Handlers\ConsoleHandler;
|
||||
use App\Framework\Logging\LogLevel;
|
||||
use App\Framework\Performance\MemoryMonitor;
|
||||
use App\Framework\Reflection\CachedReflectionProvider;
|
||||
use App\Framework\ReflectionLegacy\CachedReflectionProvider;
|
||||
use App\Framework\Serializer\Php\PhpSerializer;
|
||||
|
||||
echo "🔍 Testing New DI System for Discovery Module\n";
|
||||
@@ -47,7 +47,7 @@ try {
|
||||
// Register optional services in container
|
||||
$container->singleton(\App\Framework\Logging\Logger::class, $logger);
|
||||
$container->singleton(\App\Framework\Performance\MemoryMonitor::class, $memoryMonitor);
|
||||
$container->singleton(\App\Framework\Reflection\ReflectionProvider::class, $reflectionProvider);
|
||||
$container->singleton(\App\Framework\ReflectionLegacy\ReflectionProvider::class, $reflectionProvider);
|
||||
$container->singleton(\App\Framework\Filesystem\FileSystemService::class, $fileSystemService);
|
||||
|
||||
echo "✅ Dependencies initialized\n";
|
||||
|
||||
123
tests/debug/test-reflection-performance.php
Normal file
123
tests/debug/test-reflection-performance.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Performance Test: Native PHP Reflection vs Cached Reflection
|
||||
*
|
||||
* This test measures the actual performance difference between
|
||||
* native PHP reflection and the cached reflection provider.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
use App\Framework\Core\ValueObjects\ClassName;
|
||||
use App\Framework\ReflectionLegacy\CachedReflectionProvider;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
|
||||
// Test class
|
||||
class TestClass {
|
||||
public function __construct(
|
||||
private string $param1,
|
||||
private int $param2,
|
||||
private ?object $param3 = null
|
||||
) {}
|
||||
|
||||
public function testMethod(string $arg1, int $arg2): void {}
|
||||
}
|
||||
|
||||
$iterations = 1000;
|
||||
$className = TestClass::class;
|
||||
|
||||
echo "=== Reflection Performance Test ===\n\n";
|
||||
echo "Testing $iterations iterations for class: $className\n\n";
|
||||
|
||||
// Test 1: Native ReflectionClass
|
||||
$start = microtime(true);
|
||||
for ($i = 0; $i < $iterations; $i++) {
|
||||
$reflection = new ReflectionClass($className);
|
||||
$methods = $reflection->getMethods();
|
||||
$constructor = $reflection->getConstructor();
|
||||
if ($constructor) {
|
||||
$params = $constructor->getParameters();
|
||||
}
|
||||
}
|
||||
$nativeTime = microtime(true) - $start;
|
||||
$nativeTimeMs = ($nativeTime * 1000);
|
||||
$nativeTimePerOp = ($nativeTimeMs / $iterations);
|
||||
|
||||
echo "1. Native ReflectionClass:\n";
|
||||
echo " Total: " . round($nativeTimeMs, 2) . " ms\n";
|
||||
echo " Per operation: " . round($nativeTimePerOp, 4) . " ms\n";
|
||||
echo " Per operation: " . round($nativeTimePerOp * 1000, 2) . " microseconds\n\n";
|
||||
|
||||
// Test 2: Native ReflectionMethod (most common operation)
|
||||
$start = microtime(true);
|
||||
for ($i = 0; $i < $iterations; $i++) {
|
||||
$reflection = new ReflectionMethod($className, 'testMethod');
|
||||
$params = $reflection->getParameters();
|
||||
foreach ($params as $param) {
|
||||
$type = $param->getType();
|
||||
$name = $param->getName();
|
||||
$hasDefault = $param->isDefaultValueAvailable();
|
||||
}
|
||||
}
|
||||
$nativeMethodTime = microtime(true) - $start;
|
||||
$nativeMethodTimeMs = ($nativeMethodTime * 1000);
|
||||
$nativeMethodTimePerOp = ($nativeMethodTimeMs / $iterations);
|
||||
|
||||
echo "2. Native ReflectionMethod (getParameters):\n";
|
||||
echo " Total: " . round($nativeMethodTimeMs, 2) . " ms\n";
|
||||
echo " Per operation: " . round($nativeMethodTimePerOp, 4) . " ms\n";
|
||||
echo " Per operation: " . round($nativeMethodTimePerOp * 1000, 2) . " microseconds\n\n";
|
||||
|
||||
// Test 3: Cached ReflectionProvider (first run - no cache)
|
||||
$provider = new CachedReflectionProvider();
|
||||
$classNameObj = ClassName::create($className);
|
||||
|
||||
$start = microtime(true);
|
||||
for ($i = 0; $i < $iterations; $i++) {
|
||||
$class = $provider->getClass($classNameObj);
|
||||
$methods = $provider->getMethods($classNameObj);
|
||||
$params = $provider->getMethodParameters($classNameObj, '__construct');
|
||||
}
|
||||
$cachedFirstTime = microtime(true) - $start;
|
||||
$cachedFirstTimeMs = ($cachedFirstTime * 1000);
|
||||
$cachedFirstTimePerOp = ($cachedFirstTimeMs / $iterations);
|
||||
|
||||
echo "3. Cached ReflectionProvider (first run - no cache):\n";
|
||||
echo " Total: " . round($cachedFirstTimeMs, 2) . " ms\n";
|
||||
echo " Per operation: " . round($cachedFirstTimePerOp, 4) . " ms\n";
|
||||
echo " Per operation: " . round($cachedFirstTimePerOp * 1000, 2) . " microseconds\n\n";
|
||||
|
||||
// Test 4: Cached ReflectionProvider (cached - second run)
|
||||
$start = microtime(true);
|
||||
for ($i = 0; $i < $iterations; $i++) {
|
||||
$class = $provider->getClass($classNameObj);
|
||||
$methods = $provider->getMethods($classNameObj);
|
||||
$params = $provider->getMethodParameters($classNameObj, '__construct');
|
||||
}
|
||||
$cachedSecondTime = microtime(true) - $start;
|
||||
$cachedSecondTimeMs = ($cachedSecondTime * 1000);
|
||||
$cachedSecondTimePerOp = ($cachedSecondTimeMs / $iterations);
|
||||
|
||||
echo "4. Cached ReflectionProvider (cached - second run):\n";
|
||||
echo " Total: " . round($cachedSecondTimeMs, 2) . " ms\n";
|
||||
echo " Per operation: " . round($cachedSecondTimePerOp, 4) . " ms\n";
|
||||
echo " Per operation: " . round($cachedSecondTimePerOp * 1000, 2) . " microseconds\n\n";
|
||||
|
||||
// Comparison
|
||||
echo "=== Comparison ===\n";
|
||||
$speedup = $nativeTimePerOp / $cachedSecondTimePerOp;
|
||||
echo "Speedup (cached vs native): " . round($speedup, 2) . "x\n";
|
||||
echo "Overhead (cached first vs native): " . round(($cachedFirstTimePerOp / $nativeTimePerOp - 1) * 100, 1) . "%\n\n";
|
||||
|
||||
// Reality check: How many reflection calls per request?
|
||||
echo "=== Reality Check ===\n";
|
||||
echo "Typical request might call getMethodParameters() 10-50 times\n";
|
||||
echo "Native: " . round($nativeMethodTimePerOp * 50, 2) . " ms for 50 calls\n";
|
||||
echo "Cached: " . round($cachedSecondTimePerOp * 50, 2) . " ms for 50 calls\n";
|
||||
echo "Difference: " . round(($nativeMethodTimePerOp - $cachedSecondTimePerOp) * 50, 2) . " ms\n";
|
||||
echo "Is this significant? " . (($nativeMethodTimePerOp - $cachedSecondTimePerOp) * 50 > 1 ? "YES (>1ms)" : "NO (<1ms)") . "\n";
|
||||
|
||||
@@ -11,7 +11,7 @@ use App\Framework\Filesystem\FileScanner;
|
||||
use App\Framework\Mcp\Tools\SecurityAuditTools;
|
||||
use App\Framework\Mcp\Tools\SecurityConfigurationTools;
|
||||
use App\Framework\Mcp\Tools\SecurityMonitoringTools;
|
||||
use App\Framework\Reflection\CachedReflectionProvider;
|
||||
use App\Framework\ReflectionLegacy\CachedReflectionProvider;
|
||||
use App\Framework\Router\CompiledRoutes;
|
||||
|
||||
echo "=== Testing Security Audit MCP Tools ===\n\n";
|
||||
|
||||
@@ -10,7 +10,7 @@ use App\Framework\Core\PathProvider;
|
||||
use App\Framework\DateTime\SystemClock;
|
||||
use App\Framework\DI\DefaultContainer;
|
||||
use App\Framework\Discovery\DiscoveryServiceBootstrapper;
|
||||
use App\Framework\Reflection\CachedReflectionProvider;
|
||||
use App\Framework\ReflectionLegacy\CachedReflectionProvider;
|
||||
use App\Framework\Serializer\Json\JsonSerializer;
|
||||
|
||||
echo "=== Testing Initializer-Created Services ===\n\n";
|
||||
@@ -33,7 +33,7 @@ try {
|
||||
$container->instance(\App\Framework\Serializer\Serializer::class, $serializer);
|
||||
$container->instance(\App\Framework\Cache\Cache::class, $cache);
|
||||
$container->instance(\App\Framework\DateTime\Clock::class, $clock);
|
||||
$container->instance(\App\Framework\Reflection\ReflectionProvider::class, new CachedReflectionProvider());
|
||||
$container->instance(\App\Framework\ReflectionLegacy\ReflectionProvider::class, new CachedReflectionProvider());
|
||||
|
||||
// Bootstrap the system (this runs all initializers)
|
||||
$bootstrapper = new DiscoveryServiceBootstrapper($container, $clock);
|
||||
|
||||
@@ -17,7 +17,7 @@ use App\Framework\Core\PathProvider;
|
||||
use App\Framework\DateTime\SystemClock;
|
||||
use App\Framework\DI\DefaultContainer;
|
||||
use App\Framework\Discovery\DiscoveryServiceBootstrapper;
|
||||
use App\Framework\Reflection\CachedReflectionProvider;
|
||||
use App\Framework\ReflectionLegacy\CachedReflectionProvider;
|
||||
use App\Framework\Serializer\Json\JsonSerializer;
|
||||
|
||||
echo "=== Testing Web Context Initializer Execution ===\n\n";
|
||||
@@ -43,7 +43,7 @@ try {
|
||||
$container->instance(\App\Framework\Serializer\Serializer::class, $serializer);
|
||||
$container->instance(\App\Framework\Cache\Cache::class, $cache);
|
||||
$container->instance(\App\Framework\DateTime\Clock::class, $clock);
|
||||
$container->instance(\App\Framework\Reflection\ReflectionProvider::class, new CachedReflectionProvider());
|
||||
$container->instance(\App\Framework\ReflectionLegacy\ReflectionProvider::class, new CachedReflectionProvider());
|
||||
|
||||
// Create bootstrapper
|
||||
$bootstrapper = new DiscoveryServiceBootstrapper($container, $clock);
|
||||
|
||||
912
tests/debug/test_token_classification.php
Normal file
912
tests/debug/test_token_classification.php
Normal file
@@ -0,0 +1,912 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
use App\Framework\Tokenizer\PhpTokenizer;
|
||||
use App\Framework\Tokenizer\ValueObjects\Token;
|
||||
use App\Framework\Tokenizer\ValueObjects\TokenType;
|
||||
|
||||
/**
|
||||
* Test script for token classification
|
||||
* Tests various PHP structures and compares expected vs actual token types
|
||||
*/
|
||||
|
||||
final class TokenClassificationTest
|
||||
{
|
||||
private PhpTokenizer $tokenizer;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->tokenizer = new PhpTokenizer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all test cases
|
||||
*/
|
||||
public function runAll(): void
|
||||
{
|
||||
$testCases = $this->getTestCases();
|
||||
$total = count($testCases);
|
||||
$passed = 0;
|
||||
$failed = 0;
|
||||
|
||||
echo "=== Token Classification Test Suite ===\n\n";
|
||||
echo "Running {$total} test cases...\n\n";
|
||||
|
||||
foreach ($testCases as $index => $testCase) {
|
||||
echo str_repeat('=', 80) . "\n";
|
||||
echo "Test Case " . ($index + 1) . ": {$testCase['name']}\n";
|
||||
echo str_repeat('=', 80) . "\n\n";
|
||||
|
||||
$result = $this->runTestCase($testCase);
|
||||
|
||||
if ($result['passed']) {
|
||||
$passed++;
|
||||
echo "✅ PASSED\n\n";
|
||||
} else {
|
||||
$failed++;
|
||||
echo "❌ FAILED\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo str_repeat('=', 80) . "\n";
|
||||
echo "Summary: {$passed}/{$total} passed, {$failed}/{$total} failed\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a single test case
|
||||
*/
|
||||
private function runTestCase(array $testCase): array
|
||||
{
|
||||
$code = $testCase['code'];
|
||||
$expected = $testCase['expected'];
|
||||
$expectedMetadata = $testCase['expectedMetadata'] ?? null;
|
||||
|
||||
echo "Code:\n";
|
||||
$codeLines = explode("\n", $code);
|
||||
foreach ($codeLines as $i => $line) {
|
||||
echo sprintf(" %2d: %s\n", $i + 1, $line);
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Tokenize the code
|
||||
$tokens = $this->tokenizer->tokenize($code);
|
||||
|
||||
// Build actual token map
|
||||
$actual = $this->buildTokenMap($tokens);
|
||||
|
||||
// Compare expected vs actual
|
||||
$differences = $this->compareTokens($expected, $actual);
|
||||
|
||||
// Check metadata if expected
|
||||
$metadataDifferences = [];
|
||||
if ($expectedMetadata !== null) {
|
||||
$metadataDifferences = $this->compareMetadata($tokens, $expectedMetadata);
|
||||
}
|
||||
|
||||
// Display expected tokens
|
||||
echo "Expected Tokens:\n";
|
||||
foreach ($expected as $lineNum => $lineTokens) {
|
||||
foreach ($lineTokens as $tokenValue => $expectedType) {
|
||||
echo sprintf(" Line %d: '%s' → %s\n", $lineNum, $tokenValue, $expectedType);
|
||||
}
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Display actual tokens
|
||||
echo "Actual Tokens:\n";
|
||||
foreach ($actual as $lineNum => $lineTokens) {
|
||||
foreach ($lineTokens as $tokenValue => $actualType) {
|
||||
echo sprintf(" Line %d: '%s' → %s\n", $lineNum, $tokenValue, $actualType->value);
|
||||
}
|
||||
}
|
||||
echo "\n";
|
||||
|
||||
// Display differences
|
||||
if (empty($differences) && empty($metadataDifferences)) {
|
||||
echo "Differences: None - All tokens and metadata match!\n";
|
||||
return ['passed' => true, 'differences' => []];
|
||||
}
|
||||
|
||||
echo "Differences:\n";
|
||||
foreach ($differences as $diff) {
|
||||
if ($diff['match']) {
|
||||
echo sprintf(" ✅ Line %d: '%s' → %s (correct)\n",
|
||||
$diff['line'],
|
||||
$diff['token'],
|
||||
$diff['expected']
|
||||
);
|
||||
} else {
|
||||
echo sprintf(" ❌ Line %d: '%s' → Expected %s, got %s\n",
|
||||
$diff['line'],
|
||||
$diff['token'],
|
||||
$diff['expected'],
|
||||
$diff['actual']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Display metadata differences
|
||||
if (!empty($metadataDifferences)) {
|
||||
echo "\nMetadata Differences:\n";
|
||||
foreach ($metadataDifferences as $diff) {
|
||||
if ($diff['match']) {
|
||||
echo sprintf(" ✅ Line %d: '%s' → %s (correct)\n",
|
||||
$diff['line'],
|
||||
$diff['token'],
|
||||
$diff['expected']
|
||||
);
|
||||
} else {
|
||||
echo sprintf(" ❌ Line %d: '%s' → Expected %s, got %s\n",
|
||||
$diff['line'],
|
||||
$diff['token'],
|
||||
$diff['expected'],
|
||||
$diff['actual']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$allDifferences = array_merge($differences, $metadataDifferences);
|
||||
return [
|
||||
'passed' => empty(array_filter($allDifferences, fn($d) => !$d['match'])),
|
||||
'differences' => $allDifferences
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build token map from TokenCollection
|
||||
*/
|
||||
private function buildTokenMap($tokens): array
|
||||
{
|
||||
$map = [];
|
||||
|
||||
foreach ($tokens as $token) {
|
||||
$line = $token->line;
|
||||
$value = trim($token->value);
|
||||
|
||||
// Skip whitespace-only tokens and PHP tags
|
||||
if ($token->type === TokenType::WHITESPACE ||
|
||||
$token->type === TokenType::PHP_TAG ||
|
||||
empty($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($map[$line])) {
|
||||
$map[$line] = [];
|
||||
}
|
||||
|
||||
// Use token value as key, token type as value
|
||||
$map[$line][$value] = $token->type;
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare metadata for tokens
|
||||
*/
|
||||
private function compareMetadata($tokens, array $expectedMetadata): array
|
||||
{
|
||||
$differences = [];
|
||||
|
||||
// Build token lookup by line and value
|
||||
$tokenLookup = [];
|
||||
foreach ($tokens as $token) {
|
||||
$line = $token->line;
|
||||
$value = trim($token->value);
|
||||
|
||||
if (empty($value) || $token->type === TokenType::WHITESPACE || $token->type === TokenType::PHP_TAG) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($tokenLookup[$line])) {
|
||||
$tokenLookup[$line] = [];
|
||||
}
|
||||
|
||||
$tokenLookup[$line][$value] = $token;
|
||||
}
|
||||
|
||||
// Compare expected metadata
|
||||
foreach ($expectedMetadata as $lineNum => $lineMetadata) {
|
||||
foreach ($lineMetadata as $tokenValue => $expectedMeta) {
|
||||
$token = $tokenLookup[$lineNum][$tokenValue] ?? null;
|
||||
|
||||
if ($token === null) {
|
||||
$differences[] = [
|
||||
'line' => $lineNum,
|
||||
'token' => $tokenValue,
|
||||
'expected' => json_encode($expectedMeta),
|
||||
'actual' => 'TOKEN_NOT_FOUND',
|
||||
'match' => false
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
$actualMeta = $token->metadata;
|
||||
|
||||
// Check each expected metadata property
|
||||
foreach ($expectedMeta as $property => $expectedValue) {
|
||||
$actualValue = $actualMeta?->$property ?? null;
|
||||
|
||||
if ($property === 'isBuiltIn') {
|
||||
$actualValue = $actualMeta?->isBuiltIn ?? false;
|
||||
}
|
||||
|
||||
$match = $actualValue === $expectedValue;
|
||||
|
||||
if (!$match) {
|
||||
$differences[] = [
|
||||
'line' => $lineNum,
|
||||
'token' => $tokenValue,
|
||||
'expected' => "{$property}: " . ($expectedValue === true ? 'true' : ($expectedValue === false ? 'false' : $expectedValue)),
|
||||
'actual' => "{$property}: " . ($actualValue === true ? 'true' : ($actualValue === false ? 'false' : ($actualValue ?? 'null'))),
|
||||
'match' => false
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $differences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare expected and actual tokens
|
||||
*/
|
||||
private function compareTokens(array $expected, array $actual): array
|
||||
{
|
||||
$differences = [];
|
||||
$allLines = array_unique(array_merge(array_keys($expected), array_keys($actual)));
|
||||
|
||||
foreach ($allLines as $lineNum) {
|
||||
$expectedTokens = $expected[$lineNum] ?? [];
|
||||
$actualTokens = $actual[$lineNum] ?? [];
|
||||
|
||||
// Check all expected tokens
|
||||
foreach ($expectedTokens as $tokenValue => $expectedTypeName) {
|
||||
$expectedType = TokenType::tryFrom($expectedTypeName);
|
||||
$actualType = $actualTokens[$tokenValue] ?? null;
|
||||
|
||||
$differences[] = [
|
||||
'line' => $lineNum,
|
||||
'token' => $tokenValue,
|
||||
'expected' => $expectedTypeName,
|
||||
'actual' => $actualType?->value ?? 'NOT_FOUND',
|
||||
'match' => $actualType === $expectedType
|
||||
];
|
||||
}
|
||||
|
||||
// Check for unexpected tokens (optional - could be verbose)
|
||||
foreach ($actualTokens as $tokenValue => $actualType) {
|
||||
if (!isset($expectedTokens[$tokenValue])) {
|
||||
// This token wasn't expected - could log as info
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $differences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all test cases
|
||||
*/
|
||||
private function getTestCases(): array
|
||||
{
|
||||
return array_merge(
|
||||
// Test Cases for Classes
|
||||
$this->getClassTestCases(),
|
||||
// Test Cases for Enums
|
||||
$this->getEnumTestCases(),
|
||||
// Test Cases for Attributes
|
||||
$this->getAttributeTestCases(),
|
||||
// Test Cases for Methods and Properties
|
||||
$this->getMethodPropertyTestCases(),
|
||||
// Test Cases for Metadata
|
||||
$this->getMetadataTestCases()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get test cases for classes
|
||||
*/
|
||||
private function getClassTestCases(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'name' => 'Simple Class',
|
||||
'code' => '<?php class MyClass {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'class' => 'keyword_other',
|
||||
'MyClass' => 'class_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Final Class',
|
||||
'code' => '<?php final class MyClass {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'final' => 'keyword_modifier',
|
||||
'class' => 'keyword_other',
|
||||
'MyClass' => 'class_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Abstract Class',
|
||||
'code' => '<?php abstract class MyClass {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'abstract' => 'keyword_modifier',
|
||||
'class' => 'keyword_other',
|
||||
'MyClass' => 'class_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Readonly Class',
|
||||
'code' => '<?php readonly class MyClass {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'readonly' => 'keyword_modifier',
|
||||
'class' => 'keyword_other',
|
||||
'MyClass' => 'class_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Class with Extends',
|
||||
'code' => '<?php class Child extends Parent {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'class' => 'keyword_other',
|
||||
'Child' => 'class_name',
|
||||
'extends' => 'keyword_other',
|
||||
'Parent' => 'class_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Class with Implements',
|
||||
'code' => '<?php class MyClass implements MyInterface {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'class' => 'keyword_other',
|
||||
'MyClass' => 'class_name',
|
||||
'implements' => 'keyword_other',
|
||||
'MyInterface' => 'interface_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Class with Multiple Modifiers',
|
||||
'code' => '<?php final readonly class MyClass {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'final' => 'keyword_modifier',
|
||||
'readonly' => 'keyword_modifier',
|
||||
'class' => 'keyword_other',
|
||||
'MyClass' => 'class_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Class with Extends and Implements',
|
||||
'code' => '<?php class Child extends Parent implements MyInterface {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'class' => 'keyword_other',
|
||||
'Child' => 'class_name',
|
||||
'extends' => 'keyword_other',
|
||||
'Parent' => 'class_name',
|
||||
'implements' => 'keyword_other',
|
||||
'MyInterface' => 'interface_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get test cases for enums
|
||||
*/
|
||||
private function getEnumTestCases(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'name' => 'Pure Enum',
|
||||
'code' => '<?php enum Status {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'enum' => 'keyword_other',
|
||||
'Status' => 'enum_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Backed Enum (String)',
|
||||
'code' => '<?php enum Status: string {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'enum' => 'keyword_other',
|
||||
'Status' => 'enum_name',
|
||||
':' => 'operator',
|
||||
'string' => 'keyword_type',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Backed Enum (Int)',
|
||||
'code' => '<?php enum Status: int {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'enum' => 'keyword_other',
|
||||
'Status' => 'enum_name',
|
||||
':' => 'operator',
|
||||
'int' => 'keyword_type',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Enum with Implements',
|
||||
'code' => '<?php enum Status implements MyInterface {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'enum' => 'keyword_other',
|
||||
'Status' => 'enum_name',
|
||||
'implements' => 'keyword_other',
|
||||
'MyInterface' => 'interface_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Final Enum',
|
||||
'code' => '<?php final enum Status {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'final' => 'keyword_modifier',
|
||||
'enum' => 'keyword_other',
|
||||
'Status' => 'enum_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Backed Enum with Implements',
|
||||
'code' => '<?php enum Status: string implements MyInterface {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'enum' => 'keyword_other',
|
||||
'Status' => 'enum_name',
|
||||
':' => 'operator',
|
||||
'string' => 'keyword_type',
|
||||
'implements' => 'keyword_other',
|
||||
'MyInterface' => 'interface_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get test cases for attributes
|
||||
*/
|
||||
private function getAttributeTestCases(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'name' => 'Simple Attribute',
|
||||
'code' => '<?php #[Route] class MyClass {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'#' => 'attribute',
|
||||
'[' => 'bracket',
|
||||
'Route' => 'attribute_name',
|
||||
']' => 'bracket',
|
||||
'class' => 'keyword_other',
|
||||
'MyClass' => 'class_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Attribute with Parameter',
|
||||
'code' => '<?php #[Route(\'/api\')] class MyClass {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'#' => 'attribute',
|
||||
'[' => 'bracket',
|
||||
'Route' => 'attribute_name',
|
||||
'(' => 'parenthesis',
|
||||
'\'/api\'' => 'string_literal',
|
||||
')' => 'parenthesis',
|
||||
']' => 'bracket',
|
||||
'class' => 'keyword_other',
|
||||
'MyClass' => 'class_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Attribute with Named Parameters',
|
||||
'code' => '<?php #[Route(path: \'/api\', method: \'GET\')] class MyClass {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'#' => 'attribute',
|
||||
'[' => 'bracket',
|
||||
'Route' => 'attribute_name',
|
||||
'(' => 'parenthesis',
|
||||
'path' => 'attribute_name',
|
||||
':' => 'operator',
|
||||
'\'/api\'' => 'string_literal',
|
||||
',' => 'punctuation',
|
||||
'method' => 'attribute_name',
|
||||
':' => 'operator',
|
||||
'\'GET\'' => 'string_literal',
|
||||
')' => 'parenthesis',
|
||||
']' => 'bracket',
|
||||
'class' => 'keyword_other',
|
||||
'MyClass' => 'class_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Multiple Attributes',
|
||||
'code' => '<?php #[Route, Auth] class MyClass {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'#' => 'attribute',
|
||||
'[' => 'bracket',
|
||||
'Route' => 'attribute_name',
|
||||
',' => 'punctuation',
|
||||
'Auth' => 'attribute_name',
|
||||
']' => 'bracket',
|
||||
'class' => 'keyword_other',
|
||||
'MyClass' => 'class_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Attribute with Multiple Parameters',
|
||||
'code' => '<?php #[Route(\'/api\', \'POST\')] class MyClass {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'#' => 'attribute',
|
||||
'[' => 'bracket',
|
||||
'Route' => 'attribute_name',
|
||||
'(' => 'parenthesis',
|
||||
'\'/api\'' => 'string_literal',
|
||||
',' => 'punctuation',
|
||||
'\'POST\'' => 'string_literal',
|
||||
')' => 'parenthesis',
|
||||
']' => 'bracket',
|
||||
'class' => 'keyword_other',
|
||||
'MyClass' => 'class_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Attribute with Class Parameter',
|
||||
'code' => '<?php #[Route(MyClass::class)] class MyClass {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'#' => 'attribute',
|
||||
'[' => 'bracket',
|
||||
'Route' => 'attribute_name',
|
||||
'(' => 'parenthesis',
|
||||
'MyClass' => 'class_name',
|
||||
'::' => 'operator',
|
||||
'class' => 'keyword_other',
|
||||
')' => 'parenthesis',
|
||||
']' => 'bracket',
|
||||
'class' => 'keyword_other',
|
||||
'MyClass' => 'class_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Attribute on Method',
|
||||
'code' => '<?php class MyClass { #[Route] public function test() {} }',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'class' => 'keyword_other',
|
||||
'MyClass' => 'class_name',
|
||||
'{' => 'brace',
|
||||
],
|
||||
2 => [
|
||||
'#' => 'attribute',
|
||||
'[' => 'bracket',
|
||||
'Route' => 'attribute_name',
|
||||
']' => 'bracket',
|
||||
'public' => 'keyword_modifier',
|
||||
'function' => 'keyword_other',
|
||||
'test' => 'method_name',
|
||||
'(' => 'parenthesis',
|
||||
')' => 'parenthesis',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get test cases for methods and properties
|
||||
*/
|
||||
private function getMethodPropertyTestCases(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'name' => 'Static Method Call',
|
||||
'code' => '<?php MyClass::staticMethod();',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'MyClass' => 'class_name',
|
||||
'::' => 'operator',
|
||||
'staticMethod' => 'static_method_name',
|
||||
'(' => 'parenthesis',
|
||||
')' => 'parenthesis',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Instance Method Call',
|
||||
'code' => '<?php $obj->instanceMethod();',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'$obj' => 'variable',
|
||||
'->' => 'operator',
|
||||
'instanceMethod' => 'instance_method_name',
|
||||
'(' => 'parenthesis',
|
||||
')' => 'parenthesis',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Property Access',
|
||||
'code' => '<?php $obj->property;',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'$obj' => 'variable',
|
||||
'->' => 'operator',
|
||||
'property' => 'instance_property_name',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Static Property Access',
|
||||
'code' => '<?php MyClass::$staticProperty;',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'MyClass' => 'class_name',
|
||||
'::' => 'operator',
|
||||
'$staticProperty' => 'variable',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Constructor Call',
|
||||
'code' => '<?php new MyClass();',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'new' => 'keyword_other',
|
||||
'MyClass' => 'constructor_name',
|
||||
'(' => 'parenthesis',
|
||||
')' => 'parenthesis',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Method Definition',
|
||||
'code' => '<?php public function myMethod() {}',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'public' => 'keyword_modifier',
|
||||
'function' => 'keyword_other',
|
||||
'myMethod' => 'function_name',
|
||||
'(' => 'parenthesis',
|
||||
')' => 'parenthesis',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Method Definition in Class',
|
||||
'code' => '<?php class MyClass { public function myMethod() {} }',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'class' => 'keyword_other',
|
||||
'MyClass' => 'class_name',
|
||||
'{' => 'brace',
|
||||
],
|
||||
2 => [
|
||||
'public' => 'keyword_modifier',
|
||||
'function' => 'keyword_other',
|
||||
'myMethod' => 'method_name',
|
||||
'(' => 'parenthesis',
|
||||
')' => 'parenthesis',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Constructor Definition',
|
||||
'code' => '<?php class MyClass { public function __construct() {} }',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'class' => 'keyword_other',
|
||||
'MyClass' => 'class_name',
|
||||
'{' => 'brace',
|
||||
],
|
||||
2 => [
|
||||
'public' => 'keyword_modifier',
|
||||
'function' => 'keyword_other',
|
||||
'__construct' => 'constructor_name',
|
||||
'(' => 'parenthesis',
|
||||
')' => 'parenthesis',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Static Method Definition',
|
||||
'code' => '<?php class MyClass { public static function staticMethod() {} }',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'class' => 'keyword_other',
|
||||
'MyClass' => 'class_name',
|
||||
'{' => 'brace',
|
||||
],
|
||||
2 => [
|
||||
'public' => 'keyword_modifier',
|
||||
'static' => 'keyword_modifier',
|
||||
'function' => 'keyword_other',
|
||||
'staticMethod' => 'method_name',
|
||||
'(' => 'parenthesis',
|
||||
')' => 'parenthesis',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Nullsafe Operator Method Call',
|
||||
'code' => '<?php $obj?->method();',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'$obj' => 'variable',
|
||||
'?->' => 'operator',
|
||||
'method' => 'instance_method_name',
|
||||
'(' => 'parenthesis',
|
||||
')' => 'parenthesis',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get test cases for metadata
|
||||
*/
|
||||
private function getMetadataTestCases(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'name' => 'Built-in Function Call',
|
||||
'code' => '<?php count($array);',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'count' => 'function_name',
|
||||
'$array' => 'variable',
|
||||
],
|
||||
],
|
||||
'expectedMetadata' => [
|
||||
1 => [
|
||||
'count' => [
|
||||
'functionName' => 'count',
|
||||
'isBuiltIn' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Built-in Function strlen',
|
||||
'code' => '<?php strlen($str);',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'strlen' => 'function_name',
|
||||
'$str' => 'variable',
|
||||
],
|
||||
],
|
||||
'expectedMetadata' => [
|
||||
1 => [
|
||||
'strlen' => [
|
||||
'functionName' => 'strlen',
|
||||
'isBuiltIn' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Built-in Function array_map',
|
||||
'code' => '<?php array_map($callback, $array);',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'array_map' => 'function_name',
|
||||
'$callback' => 'variable',
|
||||
'$array' => 'variable',
|
||||
],
|
||||
],
|
||||
'expectedMetadata' => [
|
||||
1 => [
|
||||
'array_map' => [
|
||||
'functionName' => 'array_map',
|
||||
'isBuiltIn' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'User Function Call',
|
||||
'code' => '<?php myFunction();',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'myFunction' => 'function_name',
|
||||
'(' => 'parenthesis',
|
||||
')' => 'parenthesis',
|
||||
],
|
||||
],
|
||||
'expectedMetadata' => [
|
||||
1 => [
|
||||
'myFunction' => [
|
||||
'functionName' => 'myFunction',
|
||||
'isBuiltIn' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Static Method Call with Metadata',
|
||||
'code' => '<?php MyClass::staticMethod();',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'MyClass' => 'class_name',
|
||||
'::' => 'operator',
|
||||
'staticMethod' => 'static_method_name',
|
||||
],
|
||||
],
|
||||
'expectedMetadata' => [
|
||||
1 => [
|
||||
'MyClass' => [
|
||||
'className' => 'MyClass',
|
||||
],
|
||||
'staticMethod' => [
|
||||
'functionName' => 'staticMethod',
|
||||
'className' => 'MyClass',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Instance Method Call with Metadata',
|
||||
'code' => '<?php $obj->instanceMethod();',
|
||||
'expected' => [
|
||||
1 => [
|
||||
'$obj' => 'variable',
|
||||
'->' => 'operator',
|
||||
'instanceMethod' => 'instance_method_name',
|
||||
],
|
||||
],
|
||||
'expectedMetadata' => [
|
||||
1 => [
|
||||
'instanceMethod' => [
|
||||
'functionName' => 'instanceMethod',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Run tests if executed directly
|
||||
if (php_sapi_name() === 'cli') {
|
||||
$test = new TokenClassificationTest();
|
||||
$test->runAll();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user