UnderCurrent/src/Container/GenericContainer.php
2023-08-15 21:21:24 +02:00

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;
}
}