118 lines
3.5 KiB
PHP
118 lines
3.5 KiB
PHP
<?php
|
|
|
|
namespace Ardent\Undercurrent\Container;
|
|
|
|
use Ardent\Undercurrent\Logger\LogContainer;
|
|
use Ardent\Undercurrent\Logger\LoggerInterface;
|
|
use Exception;
|
|
use ReflectionClass;
|
|
use ReflectionMethod;
|
|
|
|
class GenericContainer implements ContainerInterface
|
|
{
|
|
private array $definitions = [];
|
|
|
|
private array $instances = [];
|
|
|
|
private array $aliases = [];
|
|
|
|
public function add(
|
|
string $className,
|
|
?callable $definition = null,
|
|
bool $singleton = true
|
|
): self
|
|
{
|
|
if (!$definition) {
|
|
$definition = fn() => $this->autowire($className);
|
|
}
|
|
|
|
if (isset($this->definitions[$className])) {
|
|
throw new Exception(sprintf('Class %s already defined', $className));
|
|
}
|
|
|
|
$this->definitions[$className] = [
|
|
'definition' => $definition,
|
|
'singleton' => $singleton,
|
|
];
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function alias(string $alias, string $className): self
|
|
{
|
|
if (isset($this->aliases[$alias])) {
|
|
throw new Exception(sprintf('Class %s already defined', $className));
|
|
}
|
|
|
|
$this->aliases[$alias] = $className;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function get(string $className): object
|
|
{
|
|
if (isset($this->aliases[$className])) {
|
|
$className = $this->aliases[$className];
|
|
}
|
|
if (!isset($this->definitions[$className])) {
|
|
throw new ClassNotFoundException($className);
|
|
}
|
|
|
|
$definition = $this->definitions[$className]['definition'];
|
|
|
|
if ($this->definitions[$className]['singleton']) {
|
|
if (isset($this->instances[$className])) {
|
|
return $this->instances[$className];
|
|
}
|
|
$instance = $definition($this);
|
|
if ($className !== LogContainer::class) {
|
|
$logger = $this->get(LoggerInterface::class);
|
|
$logger->add(sprintf('Created singleton instance of %s', $className));
|
|
}
|
|
$this->instances[$className] = $instance;
|
|
} else {
|
|
$instance = $definition($this);
|
|
}
|
|
|
|
return $instance;
|
|
}
|
|
|
|
public function call(string $className, string $methodName, array $parameters = []): mixed
|
|
{
|
|
$params = $this->methodParams($className, $methodName, $parameters) + $parameters;
|
|
return $this->get($className)->$methodName(...$params);
|
|
}
|
|
|
|
private function autowire(string $className): ?object
|
|
{
|
|
$reflection = new ReflectionClass($className);
|
|
$reflectionMethod = $reflection->getConstructor();
|
|
if (!$reflectionMethod) {
|
|
return new $className();
|
|
}
|
|
|
|
return new $className(...$this->methodParams($className, $reflectionMethod->getName()));
|
|
}
|
|
|
|
private function methodParams(string $className, string $methodName, array $exceptParams = []): array
|
|
{
|
|
$reflectionMethod = new ReflectionMethod($className, $methodName);
|
|
|
|
$params = [];
|
|
foreach ($reflectionMethod->getParameters() as $parameter) {
|
|
if (in_array($parameter->getName(), array_keys($exceptParams))) {
|
|
continue;
|
|
}
|
|
$type = $parameter->getType();
|
|
if (!$type) {
|
|
throw new Exception('Cannot autowire parameter without type');
|
|
}
|
|
$params[] = $this->get($type->getName());
|
|
}
|
|
|
|
return $params;
|
|
}
|
|
} |