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

- 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:
2025-11-09 14:46:15 +01:00
parent 85c369e846
commit 36ef2a1e2c
1366 changed files with 104925 additions and 28719 deletions

View File

@@ -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;

View File

@@ -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";

View 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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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;

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";
}

View File

@@ -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";
}

View File

@@ -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";
}

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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);

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View 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";

View File

@@ -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";

View File

@@ -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);

View File

@@ -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);

View 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();
}