mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-09-25 20:01:56 +02:00
Move Core/Container into XcVm\Core\Container (ServiceContainer) and
Core/Container/Psr into XcVm\Core\Container\Psr (ContainerInterface,
ContainerExceptionInterface, NotFoundExceptionInterface, NotFoundException).
- Namespace ServiceContainer + the 4 PSR-11 interfaces/classes.
- ServiceContainer: import the PSR siblings (use ...\Psr\ContainerInterface,
NotFoundException); migrated deps (DatabaseHandler/ModuleInterface/Request/
SettingsManager) keep their use; still-global exceptions/decorators qualified
with leading backslash (\ContainerException, \XcVmException, \Exception, ...).
- Rewrite 'use ServiceContainer;' → FQCN (incl. module fixtures); add use to
referrers across src/ and tests/; fix leading-backslash \ServiceContainer refs.
- Core/Exception/Container/ContainerException: import the moved PSR
ContainerExceptionInterface.
- Router: class_exists('ServiceContainer') → ::class. InterfaceContractTest boot
param-type → FQCN. Repaired use-inserter mis-placement in the two namespace()
fixture tests.
- phpstan-baseline.neon regenerated (292→292).
Verified: php -l clean; PHPStan no errors; PHPUnit 295/295; ServiceContainer
resolves and implements XcVm\Core\Container\Psr\ContainerInterface.
156 lines
5.5 KiB
PHP
156 lines
5.5 KiB
PHP
<?php
|
|
|
|
use XcVm\Core\Container\Psr\NotFoundException;
|
|
use XcVm\Core\Container\Psr\NotFoundExceptionInterface;
|
|
use XcVm\Core\Container\Psr\ContainerExceptionInterface;
|
|
use XcVm\Core\Container\Psr\ContainerInterface;
|
|
use XcVm\Core\Container\ServiceContainer;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class ServiceContainerTest extends TestCase {
|
|
|
|
protected function setUp(): void {
|
|
ServiceContainer::resetInstance();
|
|
}
|
|
|
|
protected function tearDown(): void {
|
|
ServiceContainer::resetInstance();
|
|
}
|
|
|
|
// ── PSR-11 compliance ──────────────────────────────────────
|
|
|
|
public function testGetReturnsRegisteredValue(): void {
|
|
$c = ServiceContainer::getInstance();
|
|
$c->set('foo', 'bar');
|
|
|
|
$this->assertSame('bar', $c->get('foo'));
|
|
}
|
|
|
|
public function testGetThrowsNotFoundExceptionForMissingService(): void {
|
|
$c = ServiceContainer::getInstance();
|
|
|
|
$this->expectException(NotFoundException::class);
|
|
$c->get('does-not-exist');
|
|
}
|
|
|
|
public function testNotFoundExceptionImplementsContainerInterface(): void {
|
|
$this->assertInstanceOf(NotFoundExceptionInterface::class, new NotFoundException());
|
|
$this->assertInstanceOf(ContainerExceptionInterface::class, new NotFoundException());
|
|
}
|
|
|
|
public function testHasReturnsTrueForRegisteredService(): void {
|
|
$c = ServiceContainer::getInstance();
|
|
$c->set('exists', 42);
|
|
|
|
$this->assertTrue($c->has('exists'));
|
|
}
|
|
|
|
public function testHasReturnsFalseForMissingService(): void {
|
|
$c = ServiceContainer::getInstance();
|
|
|
|
$this->assertFalse($c->has('missing'));
|
|
}
|
|
|
|
public function testContainerImplementsContainerInterface(): void {
|
|
$this->assertInstanceOf(ContainerInterface::class, ServiceContainer::getInstance());
|
|
}
|
|
|
|
// ── Factory / singleton ────────────────────────────────────
|
|
|
|
public function testFactoryCallableIsCalledOnce(): void {
|
|
$c = ServiceContainer::getInstance();
|
|
$calls = 0;
|
|
$c->set('obj', function () use (&$calls) {
|
|
$calls++;
|
|
return new stdClass();
|
|
});
|
|
|
|
$a = $c->get('obj');
|
|
$b = $c->get('obj');
|
|
|
|
$this->assertSame(1, $calls);
|
|
$this->assertSame($a, $b);
|
|
}
|
|
|
|
public function testFactoryMethodReturnsNewInstanceEachTime(): void {
|
|
$c = ServiceContainer::getInstance();
|
|
$c->factory('fresh', fn() => new stdClass());
|
|
|
|
$a = $c->get('fresh');
|
|
$b = $c->get('fresh');
|
|
|
|
$this->assertNotSame($a, $b);
|
|
}
|
|
|
|
// ── Circular dependency detection ──────────────────────────
|
|
|
|
public function testCircularDependencyThrowsRuntimeException(): void {
|
|
$c = ServiceContainer::getInstance();
|
|
$c->set('a', function ($c) {
|
|
return $c->get('b');
|
|
});
|
|
$c->set('b', function ($c) {
|
|
return $c->get('a');
|
|
});
|
|
|
|
$this->expectException(RuntimeException::class);
|
|
$this->expectExceptionMessageMatches('/цикл/u');
|
|
$c->get('a');
|
|
}
|
|
|
|
// ── Decoration ─────────────────────────────────────────────
|
|
|
|
public function testDecorateWrapsService(): void {
|
|
$c = ServiceContainer::getInstance();
|
|
$c->set('value', fn() => 'original');
|
|
$c->decorate('value', fn($inner, $c) => strtoupper($inner));
|
|
|
|
$this->assertSame('ORIGINAL', $c->get('value'));
|
|
}
|
|
|
|
public function testDecoratePriorityOrderIsHighestOutermost(): void {
|
|
$c = ServiceContainer::getInstance();
|
|
$c->set('str', fn() => 'base');
|
|
$c->decorate('str', fn($inner, $c) => "[low:{$inner}]", priority: 10);
|
|
$c->decorate('str', fn($inner, $c) => "[high:{$inner}]", priority: 50);
|
|
|
|
// highest priority (50) wraps outermost → called first by caller
|
|
$this->assertSame('[high:[low:base]]', $c->get('str'));
|
|
}
|
|
|
|
public function testDecorateProtectedServiceThrowsRuntimeException(): void {
|
|
$c = ServiceContainer::getInstance();
|
|
|
|
foreach (['db', 'settings', 'config', 'auth'] as $protected) {
|
|
try {
|
|
$c->decorate($protected, fn($inner) => $inner);
|
|
$this->fail("Expected RuntimeException for protected service '{$protected}'");
|
|
} catch (RuntimeException $e) {
|
|
$this->assertStringContainsString($protected, $e->getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
public function testDecorateUnregisteredServiceThrowsRuntimeException(): void {
|
|
$c = ServiceContainer::getInstance();
|
|
|
|
$this->expectException(RuntimeException::class);
|
|
$c->decorate('not-registered', fn($inner) => $inner);
|
|
}
|
|
|
|
// ── getOrDefault ───────────────────────────────────────────
|
|
|
|
public function testGetOrDefaultReturnsFallbackForMissingService(): void {
|
|
$c = ServiceContainer::getInstance();
|
|
|
|
$this->assertSame('fallback', $c->getOrDefault('missing', 'fallback'));
|
|
}
|
|
|
|
public function testGetOrDefaultReturnServiceWhenPresent(): void {
|
|
$c = ServiceContainer::getInstance();
|
|
$c->set('key', 'value');
|
|
|
|
$this->assertSame('value', $c->getOrDefault('key', 'fallback'));
|
|
}
|
|
}
|