156 lines
4.3 KiB
PHP
156 lines
4.3 KiB
PHP
<?php
|
|
|
|
namespace CrocWork\Zappy;
|
|
|
|
class DIContainer
|
|
{
|
|
protected array $definitions = [];
|
|
protected array $services = [];
|
|
protected array $initializedServices = [];
|
|
|
|
|
|
public function get(string $id)
|
|
{
|
|
return $this->resolve($id);
|
|
}
|
|
|
|
public function add(string $id, callable $factory): DIContainer
|
|
{
|
|
if(isset($this->definitions[$id])) {
|
|
return $this;
|
|
}
|
|
|
|
$this->definitions[$id] = $factory;
|
|
return $this;
|
|
}
|
|
|
|
|
|
|
|
public function resolve(string $id)
|
|
{
|
|
if (key_exists($id, $this->definitions)) {
|
|
$factory = $this->definitions[$id];
|
|
return $factory($this);
|
|
}
|
|
|
|
if ($this->provides($id)) {
|
|
$this->initialize($id);
|
|
|
|
if (!key_exists($id, $this->definitions)) {
|
|
throw new \Exception(sprintf('Service does not properly provide (%s)', $id));
|
|
}
|
|
|
|
$factory = $this->definitions[$id];
|
|
return $factory($this);
|
|
}
|
|
|
|
throw new \Exception(sprintf('Definition (%s) has not been added to the container', $id));
|
|
}
|
|
|
|
public function has(string $id): bool
|
|
{
|
|
if (key_exists($id, $this->definitions) || $this->provides($id)) {
|
|
return true;
|
|
}
|
|
|
|
if ($this->provides($id)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
private function make(string $class)
|
|
{
|
|
try {
|
|
$reflector = new \ReflectionClass($class);
|
|
} catch (\ReflectionException $e) {
|
|
throw new \Exception("Target class [$class] does not exist.", 0, $e);
|
|
}
|
|
|
|
// If the type is not instantiable, such as an Interface or Abstract Class
|
|
if (! $reflector->isInstantiable()) {
|
|
throw new \Exception("Target [$class] is not instantiable.");
|
|
}
|
|
|
|
$constructor = $reflector->getConstructor();
|
|
|
|
// If there are no constructor, that means there are no dependencies
|
|
if ($constructor === null) {
|
|
return new $class;
|
|
}
|
|
|
|
$parameters = $constructor->getParameters();
|
|
$dependencies = [];
|
|
|
|
foreach ($parameters as $parameter) {
|
|
$type = $parameter->getType();
|
|
|
|
if (! $type instanceof \ReflectionNamedType || $type->isBuiltin()) {
|
|
// Resolve a non-class hinted primitive dependency.
|
|
if ($parameter->isDefaultValueAvailable()) {
|
|
$dependencies[] = $parameter->getDefaultValue();
|
|
} else if ($parameter->isVariadic()) {
|
|
$dependencies[] = [];
|
|
} else {
|
|
throw new \Exception("Unresolvable dependency [$parameter] in class {$parameter->getDeclaringClass()->getName()}");
|
|
}
|
|
}
|
|
|
|
$name = $type->getName();
|
|
|
|
// Resolve a class based dependency from the container.
|
|
try {
|
|
$dependency = $this->get($name);
|
|
$dependencies[] = $dependency;
|
|
} catch (\Exception $e) {
|
|
if ($parameter->isOptional()) {
|
|
$dependencies[] = $parameter->getDefaultValue();
|
|
} else {
|
|
$dependency = $this->make($parameter->getType()->getName());
|
|
//$this->register($name, $dependency);
|
|
$dependencies[] = $dependency;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $reflector->newInstanceArgs($dependencies);
|
|
}
|
|
|
|
protected function provides(string $id): bool
|
|
{
|
|
foreach($this->services as $service) {
|
|
if($service->provides($id)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
protected function initialize(string $id): void
|
|
{
|
|
$initialized = false;
|
|
|
|
foreach ($this->services as $service) {
|
|
if (in_array($service->getId(), $this->initializedServices, true)) {
|
|
$initialized = true;
|
|
continue;
|
|
}
|
|
|
|
if ($service->provides($id)) {
|
|
$service->initialize();
|
|
$this->initializedServices[] = $service->getId();
|
|
$initialized = true;
|
|
}
|
|
}
|
|
|
|
if (!$initialized) {
|
|
throw new \Exception(
|
|
sprintf('(%s) is not provided by a service provider', $id)
|
|
);
|
|
}
|
|
}
|
|
} |