add initial solution (made with copilot)
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
namespace Core;
|
||||
|
||||
/**
|
||||
* Simple service container / dependency injector.
|
||||
*
|
||||
* - bind($abstract, $concrete): registra una factory, callable o nome classe.
|
||||
* - singleton($abstract, $concrete): come bind ma mantiene l'istanza.
|
||||
* - make($abstract): risolve e restituisce l'istanza.
|
||||
*
|
||||
* Nota: usa reflection per risolvere le dipendenze del costruttore.
|
||||
*/
|
||||
class Container {
|
||||
/** @var array<string,mixed> */
|
||||
private $bindings = [];
|
||||
|
||||
/** @var array<string,mixed> */
|
||||
private $instances = [];
|
||||
|
||||
/**
|
||||
* Bind an abstract name (interface or key) to a concrete implementation.
|
||||
* $concrete can be:
|
||||
* - a callable: function(Container $c) { return new Foo(); }
|
||||
* - a string class name: 'App\\Foo'
|
||||
*
|
||||
* @param string $abstract
|
||||
* @param callable|string $concrete
|
||||
* @return void
|
||||
*/
|
||||
public function bind(string $abstract, $concrete): void {
|
||||
$this->bindings[$abstract] = $concrete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind as singleton. The first resolved instance is cached and returned afterwards.
|
||||
*
|
||||
* @param string $abstract
|
||||
* @param callable|string $concrete
|
||||
* @return void
|
||||
*/
|
||||
public function singleton(string $abstract, $concrete): void {
|
||||
$this->bindings[$abstract] = $concrete;
|
||||
// mark as singleton with null placeholder
|
||||
$this->instances[$abstract] = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an abstract to an instance.
|
||||
*
|
||||
* @param string $abstract
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function make(string $abstract) {
|
||||
// return existing singleton instance if already created
|
||||
if (array_key_exists($abstract, $this->instances) && $this->instances[$abstract] !== null) {
|
||||
return $this->instances[$abstract];
|
||||
}
|
||||
|
||||
if (!isset($this->bindings[$abstract])) {
|
||||
// if no binding, try to instantiate the abstract directly if it's a class
|
||||
if (class_exists($abstract)) {
|
||||
$object = $this->build($abstract);
|
||||
} else {
|
||||
throw new \Exception("No binding found for [{$abstract}]");
|
||||
}
|
||||
} else {
|
||||
$concrete = $this->bindings[$abstract];
|
||||
|
||||
if (is_callable($concrete)) {
|
||||
// factory receives the container
|
||||
$object = $concrete($this);
|
||||
} elseif (is_string($concrete) && class_exists($concrete)) {
|
||||
$object = $this->build($concrete);
|
||||
} else {
|
||||
throw new \Exception("Invalid binding for [{$abstract}]");
|
||||
}
|
||||
}
|
||||
|
||||
// if abstract was registered as singleton, cache the instance
|
||||
if (array_key_exists($abstract, $this->instances)) {
|
||||
$this->instances[$abstract] = $object;
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an instance of the given class resolving constructor dependencies.
|
||||
*
|
||||
* @param string $class
|
||||
* @return object
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function build(string $class) {
|
||||
$reflector = new \ReflectionClass($class);
|
||||
|
||||
if (!$reflector->isInstantiable()) {
|
||||
throw new \Exception("Class {$class} is not instantiable");
|
||||
}
|
||||
|
||||
$constructor = $reflector->getConstructor();
|
||||
if (is_null($constructor)) {
|
||||
return new $class();
|
||||
}
|
||||
|
||||
$params = $constructor->getParameters();
|
||||
$dependencies = [];
|
||||
|
||||
foreach ($params as $param) {
|
||||
$type = $param->getType();
|
||||
|
||||
// If parameter has a class/interface type, resolve it from container
|
||||
if ($type && !$type->isBuiltin()) {
|
||||
$depClass = $type->getName();
|
||||
$dependencies[] = $this->make($depClass);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If default value is available, use it
|
||||
if ($param->isDefaultValueAvailable()) {
|
||||
$dependencies[] = $param->getDefaultValue();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cannot resolve the dependency
|
||||
$name = $param->getName();
|
||||
throw new \Exception("Unresolvable dependency [\${$name}] in class {$class}");
|
||||
}
|
||||
|
||||
return $reflector->newInstanceArgs($dependencies);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user