You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
122 lines
3.5 KiB
122 lines
3.5 KiB
<?php
|
|
|
|
namespace App;
|
|
|
|
use Psr\Container\ContainerInterface;
|
|
use Shared\Log;
|
|
use Twig\Environment;
|
|
use Twig\Loader\FilesystemLoader;
|
|
|
|
class Container implements ContainerInterface
|
|
{
|
|
private array $entries = [];
|
|
|
|
const SINGLETON = 'singleton';
|
|
const TRANSIENT = 'transient';
|
|
|
|
public function get(string $id)
|
|
{
|
|
if ($this->has($id)) {
|
|
$entry = $this->entries[$id];
|
|
|
|
if ($entry['lifecycle'] === self::SINGLETON) {
|
|
if ($entry['instance'] === null) {
|
|
$entry['instance'] = $this->resolve($entry['concrete']);
|
|
}
|
|
return $entry['instance'];
|
|
}
|
|
|
|
if (is_callable($entry['concrete'])) {
|
|
return $entry['concrete']($this);
|
|
}
|
|
|
|
return $this->resolve($entry['concrete']);
|
|
}
|
|
|
|
return $this->resolve($id);
|
|
}
|
|
|
|
|
|
|
|
public function has(string $id): bool
|
|
{
|
|
return isset($this->entries[$id]);
|
|
}
|
|
|
|
public function set(string $id, callable|string $concrete, string $lifecycle = self::TRANSIENT): void
|
|
{
|
|
$this->entries[$id] = [
|
|
'concrete' => $concrete,
|
|
'lifecycle' => $lifecycle,
|
|
'instance' => null
|
|
];
|
|
}
|
|
|
|
public function resolve(string $id)
|
|
{
|
|
// 1. Inspect the class that we are trying to get from the container
|
|
try {
|
|
$reflectionClass = new \ReflectionClass($id);
|
|
} catch (\ReflectionException $e) {
|
|
throw new \Exception($e->getMessage(), $e->getCode(), $e);
|
|
}
|
|
if (!$reflectionClass->isInstantiable()) {
|
|
throw new \Exception('Class "' . $id . '" is not instantiable');
|
|
}
|
|
|
|
// 2. Inspect the constructor of the class
|
|
$constructor = $reflectionClass->getConstructor();
|
|
|
|
if (!$constructor) {
|
|
return new $id;
|
|
}
|
|
|
|
// 3. Inspect the constructor parameters (dependencies)
|
|
$parameters = $constructor->getParameters();
|
|
|
|
if (!$parameters) {
|
|
return new $id;
|
|
}
|
|
|
|
// 4. If the constructor parameter is a class then try to resolve that class using the container
|
|
$dependencies = array_map(
|
|
function (\ReflectionParameter $param) use ($id) {
|
|
$name = $param->getName();
|
|
$type = $param->getType();
|
|
|
|
// Check for a default value
|
|
if ($param->isDefaultValueAvailable()) {
|
|
return $param->getDefaultValue();
|
|
}
|
|
|
|
if (!$type) {
|
|
throw new \Exception(
|
|
'Failed to resolve class "' . $id . '" because param "' . $name . '" is missing a type hint'
|
|
);
|
|
}
|
|
|
|
if ($type instanceof \ReflectionUnionType) {
|
|
throw new \Exception(
|
|
'Failed to resolve class "' . $id . '" because of union type for param "' . $name . '"'
|
|
);
|
|
}
|
|
|
|
if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) {
|
|
return $this->get($type->getName());
|
|
}
|
|
|
|
throw new \Exception(
|
|
'Failed to resolve class "' . $id . '" because invalid param "' . $name . '"'
|
|
);
|
|
},
|
|
$parameters
|
|
);
|
|
|
|
return $reflectionClass->newInstanceArgs($dependencies);
|
|
}
|
|
|
|
public function getAllRegisteredClassNames(): array
|
|
{
|
|
return array_keys($this->entries);
|
|
}
|
|
} |