Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
239
tests/Framework/DI/DefaultContainerTest.php
Normal file
239
tests/Framework/DI/DefaultContainerTest.php
Normal file
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Framework\DI\Container;
|
||||
use App\Framework\DI\DefaultContainer;
|
||||
use App\Framework\DI\Exceptions\CyclicDependencyException;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->container = new DefaultContainer();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
$this->container->flush();
|
||||
});
|
||||
|
||||
it('registers itself', function () {
|
||||
expect($this->container->has(Container::class))->toBeTrue();
|
||||
expect($this->container->has(DefaultContainer::class))->toBeTrue();
|
||||
expect($this->container->get(Container::class))->toBe($this->container);
|
||||
expect($this->container->get(DefaultContainer::class))->toBe($this->container);
|
||||
});
|
||||
|
||||
it('creates simple class', function () {
|
||||
$instance = $this->container->get(SimpleTestClass::class);
|
||||
|
||||
expect($instance)->toBeInstanceOf(SimpleTestClass::class);
|
||||
});
|
||||
|
||||
it('caches instances for same class', function () {
|
||||
// Note: DefaultContainer caches instances even for non-singletons
|
||||
$instance1 = $this->container->get(SimpleTestClass::class);
|
||||
$instance2 = $this->container->get(SimpleTestClass::class);
|
||||
|
||||
expect($instance1)->toBe($instance2);
|
||||
});
|
||||
|
||||
it('binds string class', function () {
|
||||
$this->container->bind(TestInterface::class, ConcreteTestClass::class);
|
||||
$instance = $this->container->get(TestInterface::class);
|
||||
|
||||
expect($instance)->toBeInstanceOf(ConcreteTestClass::class);
|
||||
});
|
||||
|
||||
it('binds callable', function () {
|
||||
$this->container->bind(TestInterface::class, fn () => new ConcreteTestClass('from-callable'));
|
||||
$instance = $this->container->get(TestInterface::class);
|
||||
|
||||
expect($instance)->toBeInstanceOf(ConcreteTestClass::class);
|
||||
expect($instance->value)->toBe('from-callable');
|
||||
});
|
||||
|
||||
it('binds object', function () {
|
||||
$object = new ConcreteTestClass('bound-object');
|
||||
$this->container->bind(TestInterface::class, $object);
|
||||
$instance = $this->container->get(TestInterface::class);
|
||||
|
||||
expect($instance)->toBe($object);
|
||||
expect($instance->value)->toBe('bound-object');
|
||||
});
|
||||
|
||||
it('returns same instance for singleton', function () {
|
||||
$this->container->singleton(TestInterface::class, fn () => new ConcreteTestClass('singleton'));
|
||||
|
||||
$instance1 = $this->container->get(TestInterface::class);
|
||||
$instance2 = $this->container->get(TestInterface::class);
|
||||
|
||||
expect($instance1)->toBe($instance2);
|
||||
expect($instance1->value)->toBe('singleton');
|
||||
});
|
||||
|
||||
it('calls callable only once for singleton', function () {
|
||||
$callCount = 0;
|
||||
$this->container->singleton(TestInterface::class, function () use (&$callCount) {
|
||||
$callCount++;
|
||||
|
||||
return new ConcreteTestClass("call-{$callCount}");
|
||||
});
|
||||
|
||||
$instance1 = $this->container->get(TestInterface::class);
|
||||
$instance2 = $this->container->get(TestInterface::class);
|
||||
|
||||
expect($instance1)->toBe($instance2);
|
||||
expect($callCount)->toBe(1);
|
||||
expect($instance1->value)->toBe('call-1');
|
||||
});
|
||||
|
||||
it('stores instance directly', function () {
|
||||
$object = new ConcreteTestClass('instance');
|
||||
$this->container->instance(TestInterface::class, $object);
|
||||
|
||||
$instance = $this->container->get(TestInterface::class);
|
||||
|
||||
expect($instance)->toBe($object);
|
||||
});
|
||||
|
||||
it('has returns true for existing class', function () {
|
||||
expect($this->container->has(SimpleTestClass::class))->toBeTrue();
|
||||
});
|
||||
|
||||
it('has returns true for bound class', function () {
|
||||
$this->container->bind(TestInterface::class, ConcreteTestClass::class);
|
||||
|
||||
expect($this->container->has(TestInterface::class))->toBeTrue();
|
||||
});
|
||||
|
||||
it('has returns false for non-existent class', function () {
|
||||
expect($this->container->has('NonExistentClass'))->toBeFalse();
|
||||
});
|
||||
|
||||
it('forgets binding', function () {
|
||||
$this->container->bind(TestInterface::class, ConcreteTestClass::class);
|
||||
expect($this->container->has(TestInterface::class))->toBeTrue();
|
||||
|
||||
$this->container->forget(TestInterface::class);
|
||||
|
||||
expect($this->container->has(TestInterface::class))->toBeFalse();
|
||||
});
|
||||
|
||||
it('forgets singleton and creates new instance', function () {
|
||||
$this->container->singleton(TestInterface::class, fn () => new ConcreteTestClass('singleton'));
|
||||
$instance1 = $this->container->get(TestInterface::class);
|
||||
|
||||
$this->container->forget(TestInterface::class);
|
||||
|
||||
// After forget, the binding is gone
|
||||
expect($this->container->has(TestInterface::class))->toBeFalse();
|
||||
});
|
||||
|
||||
it('resolves dependencies', function () {
|
||||
$this->container->bind(TestInterface::class, ConcreteTestClass::class);
|
||||
$instance = $this->container->get(ClassWithDependency::class);
|
||||
|
||||
expect($instance)->toBeInstanceOf(ClassWithDependency::class);
|
||||
expect($instance->dependency)->toBeInstanceOf(ConcreteTestClass::class);
|
||||
});
|
||||
|
||||
it('throws exception for cyclic dependency', function () {
|
||||
$this->container->bind(CyclicA::class, CyclicA::class);
|
||||
$this->container->bind(CyclicB::class, CyclicB::class);
|
||||
|
||||
$this->container->get(CyclicA::class);
|
||||
})->throws(CyclicDependencyException::class, 'Zyklische Abhängigkeit entdeckt');
|
||||
|
||||
it('returns registered services', function () {
|
||||
$this->container->bind(TestInterface::class, ConcreteTestClass::class);
|
||||
$this->container->singleton(TestInterface::class, fn () => new ConcreteTestClass('singleton'));
|
||||
|
||||
$services = $this->container->getRegisteredServices();
|
||||
|
||||
expect($services)->toContain(TestInterface::class);
|
||||
expect($services)->toContain(Container::class);
|
||||
expect($services)->toContain(DefaultContainer::class);
|
||||
});
|
||||
|
||||
it('flushes all bindings and instances', function () {
|
||||
$this->container->bind(TestInterface::class, ConcreteTestClass::class);
|
||||
$this->container->singleton(TestInterface::class, fn () => new ConcreteTestClass('singleton'));
|
||||
$instance = $this->container->get(TestInterface::class);
|
||||
|
||||
$this->container->flush();
|
||||
|
||||
// Container should re-register itself after flush
|
||||
expect($this->container->has(Container::class))->toBeTrue();
|
||||
expect($this->container->has(DefaultContainer::class))->toBeTrue();
|
||||
|
||||
// Other bindings should be gone
|
||||
expect($this->container->has(TestInterface::class))->toBeFalse();
|
||||
|
||||
// New instance should be different after flush
|
||||
$newInstance = $this->container->get(SimpleTestClass::class);
|
||||
expect($newInstance)->toBeInstanceOf(SimpleTestClass::class);
|
||||
});
|
||||
|
||||
it('has method invoker available', function () {
|
||||
expect($this->container->invoker)->not->toBeNull();
|
||||
expect($this->container->invoker)->toBeInstanceOf(\App\Framework\DI\MethodInvoker::class);
|
||||
});
|
||||
|
||||
it('resolves complex dependency chain', function () {
|
||||
$this->container->bind(TestInterface::class, ConcreteTestClass::class);
|
||||
$instance = $this->container->get(ComplexDependencyClass::class);
|
||||
|
||||
expect($instance)->toBeInstanceOf(ComplexDependencyClass::class);
|
||||
expect($instance->classWithDep)->toBeInstanceOf(ClassWithDependency::class);
|
||||
expect($instance->classWithDep->dependency)->toBeInstanceOf(ConcreteTestClass::class);
|
||||
expect($instance->simple)->toBeInstanceOf(SimpleTestClass::class);
|
||||
});
|
||||
|
||||
// Test helper classes
|
||||
interface TestInterface
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
class SimpleTestClass
|
||||
{
|
||||
public function __construct(public string $value = 'default')
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class ConcreteTestClass implements TestInterface
|
||||
{
|
||||
public function __construct(public string $value = 'default')
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class ClassWithDependency
|
||||
{
|
||||
public function __construct(public TestInterface $dependency)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class ComplexDependencyClass
|
||||
{
|
||||
public function __construct(
|
||||
public ClassWithDependency $classWithDep,
|
||||
public SimpleTestClass $simple
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
// Cyclic dependency test classes
|
||||
class CyclicA
|
||||
{
|
||||
public function __construct(public CyclicB $b)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class CyclicB
|
||||
{
|
||||
public function __construct(public CyclicA $a)
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user