zappy-base/src/DIContainer.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)
);
}
}
}