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:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View 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)
{
}
}