- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
257 lines
8.6 KiB
PHP
257 lines
8.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../../vendor/autoload.php';
|
|
|
|
use App\Framework\Core\ValueObjects\ClassName;
|
|
use App\Framework\DI\Attributes\DefaultImplementation;
|
|
use App\Framework\DI\DefaultContainer;
|
|
use App\Framework\DI\DefaultImplementationProcessor;
|
|
use App\Framework\DI\Exceptions\DefaultImplementationException;
|
|
use App\Framework\Discovery\Results\AttributeRegistry;
|
|
use App\Framework\Discovery\Results\DiscoveryRegistry;
|
|
use App\Framework\Discovery\ValueObjects\AttributeTarget;
|
|
use App\Framework\Discovery\ValueObjects\DiscoveredAttribute;
|
|
|
|
// Test Interfaces
|
|
interface UserRepository
|
|
{
|
|
public function findById(string $id): ?object;
|
|
}
|
|
|
|
interface LoggerInterface
|
|
{
|
|
public function log(string $message): void;
|
|
}
|
|
|
|
interface FormattableInterface
|
|
{
|
|
public function format(): string;
|
|
}
|
|
|
|
// Test Classes
|
|
|
|
// Scenario 1: Explicit interface specification
|
|
#[DefaultImplementation(UserRepository::class)]
|
|
final readonly class DatabaseUserRepository implements UserRepository
|
|
{
|
|
public function findById(string $id): ?object
|
|
{
|
|
return (object) ['id' => $id, 'name' => 'Test User'];
|
|
}
|
|
}
|
|
|
|
// Scenario 2: Auto-detect single interface
|
|
#[DefaultImplementation]
|
|
final readonly class FileUserRepository implements UserRepository
|
|
{
|
|
public function findById(string $id): ?object
|
|
{
|
|
return (object) ['id' => $id, 'name' => 'File User'];
|
|
}
|
|
}
|
|
|
|
// Scenario 3: Auto-detect with multiple interfaces (should use first)
|
|
#[DefaultImplementation]
|
|
final readonly class AppLogger implements LoggerInterface, FormattableInterface
|
|
{
|
|
public function log(string $message): void
|
|
{
|
|
echo "[LOG] $message\n";
|
|
}
|
|
|
|
public function format(): string
|
|
{
|
|
return 'AppLogger';
|
|
}
|
|
}
|
|
|
|
// Scenario 4: Invalid - explicit interface not implemented (for error testing)
|
|
#[DefaultImplementation(LoggerInterface::class)]
|
|
final readonly class BadRepository implements UserRepository
|
|
{
|
|
public function findById(string $id): ?object
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Scenario 5: Invalid - no interfaces implemented (for error testing)
|
|
#[DefaultImplementation]
|
|
final readonly class NoInterfaceClass
|
|
{
|
|
public function doSomething(): void {}
|
|
}
|
|
|
|
// Helper function to create DiscoveredAttribute
|
|
function createDiscoveredAttribute(string $className, array $attributeArgs = []): DiscoveredAttribute
|
|
{
|
|
return new DiscoveredAttribute(
|
|
className: ClassName::create($className),
|
|
attributeClass: DefaultImplementation::class,
|
|
target: AttributeTarget::TARGET_CLASS,
|
|
arguments: $attributeArgs
|
|
);
|
|
}
|
|
|
|
// Helper function to create DiscoveryRegistry with specific attributes
|
|
function createRegistryWithAttributes(array $discoveredAttributes): DiscoveryRegistry
|
|
{
|
|
$attributeRegistry = new AttributeRegistry();
|
|
foreach ($discoveredAttributes as $attr) {
|
|
$attributeRegistry->add(DefaultImplementation::class, $attr);
|
|
}
|
|
|
|
return new DiscoveryRegistry(
|
|
attributes: $attributeRegistry,
|
|
interfaces: new \App\Framework\Discovery\Results\InterfaceRegistry(),
|
|
templates: new \App\Framework\Discovery\Results\TemplateRegistry()
|
|
);
|
|
}
|
|
|
|
// Test Runner
|
|
echo "=== DefaultImplementation Attribute Tests ===\n\n";
|
|
|
|
// Test 1: Explicit Interface Specification
|
|
echo "Test 1: Explicit interface specification\n";
|
|
try {
|
|
$container = new DefaultContainer();
|
|
$processor = new DefaultImplementationProcessor($container);
|
|
|
|
$discovered = createDiscoveredAttribute(DatabaseUserRepository::class, ['interface' => UserRepository::class]);
|
|
$registry = createRegistryWithAttributes([$discovered]);
|
|
|
|
$registered = $processor->process($registry);
|
|
|
|
echo "✓ Registered $registered binding(s)\n";
|
|
echo "✓ Container has binding: " . ($container->has(UserRepository::class) ? 'YES' : 'NO') . "\n";
|
|
|
|
$instance = $container->get(UserRepository::class);
|
|
echo "✓ Instance type: " . get_class($instance) . "\n";
|
|
echo "✓ Find user: " . json_encode($instance->findById('123')) . "\n";
|
|
echo "PASSED\n\n";
|
|
} catch (\Exception $e) {
|
|
echo "✗ FAILED: " . $e->getMessage() . "\n\n";
|
|
}
|
|
|
|
// Test 2: Auto-detect Single Interface
|
|
echo "Test 2: Auto-detect single interface\n";
|
|
try {
|
|
$container = new DefaultContainer();
|
|
$processor = new DefaultImplementationProcessor($container);
|
|
|
|
$discovered = createDiscoveredAttribute(FileUserRepository::class, []); // No explicit interface
|
|
$registry = createRegistryWithAttributes([$discovered]);
|
|
|
|
$registered = $processor->process($registry);
|
|
|
|
echo "✓ Registered $registered binding(s)\n";
|
|
echo "✓ Container has binding: " . ($container->has(UserRepository::class) ? 'YES' : 'NO') . "\n";
|
|
|
|
$instance = $container->get(UserRepository::class);
|
|
echo "✓ Instance type: " . get_class($instance) . "\n";
|
|
echo "PASSED\n\n";
|
|
} catch (\Exception $e) {
|
|
echo "✗ FAILED: " . $e->getMessage() . "\n\n";
|
|
}
|
|
|
|
// Test 3: Auto-detect Multiple Interfaces (uses first)
|
|
echo "Test 3: Auto-detect with multiple interfaces\n";
|
|
try {
|
|
$container = new DefaultContainer();
|
|
$processor = new DefaultImplementationProcessor($container);
|
|
|
|
$discovered = createDiscoveredAttribute(AppLogger::class, []);
|
|
$registry = createRegistryWithAttributes([$discovered]);
|
|
|
|
$registered = $processor->process($registry);
|
|
|
|
echo "✓ Registered $registered binding(s)\n";
|
|
echo "✓ Container has LoggerInterface: " . ($container->has(LoggerInterface::class) ? 'YES' : 'NO') . "\n";
|
|
|
|
$instance = $container->get(LoggerInterface::class);
|
|
echo "✓ Instance type: " . get_class($instance) . "\n";
|
|
$instance->log("Test message");
|
|
echo "PASSED\n\n";
|
|
} catch (\Exception $e) {
|
|
echo "✗ FAILED: " . $e->getMessage() . "\n\n";
|
|
}
|
|
|
|
// Test 4: Error - Explicit Interface Not Implemented
|
|
echo "Test 4: Error - explicit interface not implemented\n";
|
|
try {
|
|
$container = new DefaultContainer();
|
|
$processor = new DefaultImplementationProcessor($container);
|
|
|
|
$discovered = createDiscoveredAttribute(BadRepository::class, ['interface' => LoggerInterface::class]);
|
|
$registry = createRegistryWithAttributes([$discovered]);
|
|
|
|
$processor->process($registry);
|
|
echo "✗ FAILED: Should have thrown exception\n\n";
|
|
} catch (DefaultImplementationException $e) {
|
|
echo "✓ Caught expected exception: " . $e->getMessage() . "\n";
|
|
echo "PASSED\n\n";
|
|
}
|
|
|
|
// Test 5: Error - No Interfaces Implemented
|
|
echo "Test 5: Error - no interfaces implemented\n";
|
|
try {
|
|
$container = new DefaultContainer();
|
|
$processor = new DefaultImplementationProcessor($container);
|
|
|
|
$discovered = createDiscoveredAttribute(NoInterfaceClass::class, []);
|
|
$registry = createRegistryWithAttributes([$discovered]);
|
|
|
|
$processor->process($registry);
|
|
echo "✗ FAILED: Should have thrown exception\n\n";
|
|
} catch (DefaultImplementationException $e) {
|
|
echo "✓ Caught expected exception: " . $e->getMessage() . "\n";
|
|
echo "PASSED\n\n";
|
|
}
|
|
|
|
// Test 6: Multiple Bindings
|
|
echo "Test 6: Multiple bindings in one registry\n";
|
|
try {
|
|
$container = new DefaultContainer();
|
|
$processor = new DefaultImplementationProcessor($container);
|
|
|
|
$discovered1 = createDiscoveredAttribute(DatabaseUserRepository::class, ['interface' => UserRepository::class]);
|
|
$discovered2 = createDiscoveredAttribute(AppLogger::class, []);
|
|
|
|
$registry = createRegistryWithAttributes([$discovered1, $discovered2]);
|
|
|
|
$registered = $processor->process($registry);
|
|
|
|
echo "✓ Registered $registered binding(s)\n";
|
|
echo "✓ Has UserRepository: " . ($container->has(UserRepository::class) ? 'YES' : 'NO') . "\n";
|
|
echo "✓ Has LoggerInterface: " . ($container->has(LoggerInterface::class) ? 'YES' : 'NO') . "\n";
|
|
echo "PASSED\n\n";
|
|
} catch (\Exception $e) {
|
|
echo "✗ FAILED: " . $e->getMessage() . "\n\n";
|
|
}
|
|
|
|
// Test 7: Singleton Registration (verify it's actually singleton)
|
|
echo "Test 7: Singleton registration verification\n";
|
|
try {
|
|
$container = new DefaultContainer();
|
|
$processor = new DefaultImplementationProcessor($container);
|
|
|
|
$discovered = createDiscoveredAttribute(DatabaseUserRepository::class, ['interface' => UserRepository::class]);
|
|
$registry = createRegistryWithAttributes([$discovered]);
|
|
|
|
$processor->process($registry);
|
|
|
|
$instance1 = $container->get(UserRepository::class);
|
|
$instance2 = $container->get(UserRepository::class);
|
|
|
|
echo "✓ Instance 1 ID: " . spl_object_id($instance1) . "\n";
|
|
echo "✓ Instance 2 ID: " . spl_object_id($instance2) . "\n";
|
|
echo "✓ Same instance: " . ($instance1 === $instance2 ? 'YES' : 'NO') . "\n";
|
|
echo "PASSED\n\n";
|
|
} catch (\Exception $e) {
|
|
echo "✗ FAILED: " . $e->getMessage() . "\n\n";
|
|
}
|
|
|
|
echo "=== All Tests Complete ===\n";
|