195 lines
No EOL
6.1 KiB
PHP
195 lines
No EOL
6.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Foundation\Core\DependencyInjection;
|
|
|
|
use DI\Container;
|
|
use Psr\Container\ContainerInterface;
|
|
use function DI\create;
|
|
|
|
class InflectableContainer implements ContainerInterface
|
|
{
|
|
private array $inflections = [];
|
|
/** @var AttributeProcessorInterface[] */
|
|
private array $attributeProcessors = [];
|
|
|
|
public function __construct(private readonly Container $phpDiContainer)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* @template T of object
|
|
* @param string $id
|
|
* @return ($id is class-string<T> ? T : mixed)
|
|
*/
|
|
public function get(string $id): mixed
|
|
{
|
|
$instance = $this->phpDiContainer->get($id);
|
|
|
|
// Only apply inflections to objects, not arrays or primitives
|
|
if (is_object($instance)) {
|
|
return $this->applyInflections($instance);
|
|
}
|
|
|
|
return $instance;
|
|
}
|
|
|
|
public function has(string $id): bool
|
|
{
|
|
return $this->phpDiContainer->has($id);
|
|
}
|
|
|
|
public function set(string $name, mixed $value): void
|
|
{
|
|
$this->phpDiContainer->set($name, $value);
|
|
}
|
|
|
|
/**
|
|
* @template T of object
|
|
* @param class-string<T> $name
|
|
* @param array $parameters
|
|
* @return T
|
|
*/
|
|
public function make(string $name, array $parameters = []): object
|
|
{
|
|
$instance = $this->phpDiContainer->make($name, $parameters);
|
|
|
|
return $this->applyInflections($instance);
|
|
}
|
|
|
|
public function inflect(string $class): InflectionHelper
|
|
{
|
|
return new InflectionHelper($this, $class);
|
|
}
|
|
|
|
public function register(string $abstract, array $dependencies = []): object
|
|
{
|
|
if ($dependencies === []) {
|
|
// Simple registration with no dependencies
|
|
$this->phpDiContainer->set($abstract, create($abstract));
|
|
} else {
|
|
// Registration with explicit dependencies
|
|
$this->phpDiContainer->set(
|
|
$abstract,
|
|
function (InflectableContainer $container) use ($abstract, $dependencies): object {
|
|
$resolvedDependencies = [];
|
|
foreach ($dependencies as $dependency) {
|
|
$resolvedDependencies[] = $container->get($dependency);
|
|
}
|
|
|
|
return new $abstract(...$resolvedDependencies);
|
|
},
|
|
);
|
|
}
|
|
|
|
// Process attributes for the registered class
|
|
$this->processAttributes($abstract);
|
|
|
|
return $this->get($abstract);
|
|
}
|
|
|
|
public function bind(string $abstract, string $concrete, array $dependencies = []): object
|
|
{
|
|
if ($dependencies === []) {
|
|
// Simple interface-to-implementation binding
|
|
$this->phpDiContainer->set($abstract, create($concrete));
|
|
} else {
|
|
// Interface-to-implementation binding with explicit dependencies
|
|
$this->phpDiContainer->set(
|
|
$abstract,
|
|
function (InflectableContainer $container) use ($concrete, $dependencies): object {
|
|
$resolvedDependencies = [];
|
|
foreach ($dependencies as $dependency) {
|
|
$resolvedDependencies[] = $container->get($dependency);
|
|
}
|
|
|
|
return new $concrete(...$resolvedDependencies);
|
|
},
|
|
);
|
|
}
|
|
|
|
return $this->get($abstract);
|
|
}
|
|
|
|
public function addAttributeProcessor(AttributeProcessorInterface $processor): void
|
|
{
|
|
$this->attributeProcessors[] = $processor;
|
|
}
|
|
|
|
public function registerInflection(string $targetClass, string $method, array $params): void
|
|
{
|
|
$this->inflections[$targetClass][] = [
|
|
'method' => $method,
|
|
'params' => $params,
|
|
];
|
|
}
|
|
|
|
private function applyInflections(object $instance): object
|
|
{
|
|
$instanceClass = $instance::class;
|
|
|
|
// Apply direct class inflections
|
|
$this->applyDirectInflections($instance, $instanceClass);
|
|
|
|
// Apply interface/parent class inflections
|
|
$this->applyInheritanceInflections($instance);
|
|
|
|
return $instance;
|
|
}
|
|
|
|
private function applyDirectInflections(object $instance, string $instanceClass): void
|
|
{
|
|
if (isset($this->inflections[$instanceClass])) {
|
|
foreach ($this->inflections[$instanceClass] as $inflection) {
|
|
$resolvedParams = $this->resolveParameters($inflection['params']);
|
|
$methodName = $inflection['method'];
|
|
// @phpstan-ignore-next-line Dynamic method call is intentional for inflection
|
|
$instance->$methodName(...$resolvedParams);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function applyInheritanceInflections(object $instance): void
|
|
{
|
|
foreach ($this->inflections as $targetClass => $inflections) {
|
|
if ($instance instanceof $targetClass) {
|
|
foreach ($inflections as $inflection) {
|
|
$resolvedParams = $this->resolveParameters($inflection['params']);
|
|
$methodName = $inflection['method'];
|
|
// @phpstan-ignore-next-line Dynamic method call is intentional for inflection
|
|
$instance->$methodName(...$resolvedParams);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private function resolveParameters(array $params): array
|
|
{
|
|
$resolved = [];
|
|
foreach ($params as $param) {
|
|
if (is_string($param) && $this->isResolvableClass($param)) {
|
|
$resolved[] = $this->phpDiContainer->get($param);
|
|
} else {
|
|
$resolved[] = $param;
|
|
}
|
|
}
|
|
|
|
return $resolved;
|
|
}
|
|
|
|
private function processAttributes(string $className): void
|
|
{
|
|
foreach ($this->attributeProcessors as $processor) {
|
|
if ($processor->canProcess($className)) {
|
|
$processor->process($className);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function isResolvableClass(string $param): bool
|
|
{
|
|
// Check if it's a class name (contains backslash or ends with ::class pattern)
|
|
return class_exists($param) || interface_exists($param) || $this->phpDiContainer->has($param);
|
|
}
|
|
} |