Tworzymy wzorzec service container. Kontynuacja lekcji o refleksji oraz grafie zależności. Do dzieła.
Na początek klasa z konstruktorem przyjmującym builtiny:
class Person {
public function __construct(public string $name, public int $age)
{
}
}
Teraz klasa z konstruktorem przyjmującym obiekty, co service container ma umieć samemu ogarniać:
class Couple {
public function __construct(public Person $person1, public Person $person2)
{
}
}
Teraz klasa z konstruktorem bezargumentowym, co również ma umieć ogarniać:
class Parameterless {
public string $name;
public int $age;
public function __construct()
{
$this->name = 'Prototype';
$this->age = 30;
}
}
Wreszcie klasa bez konstruktora, co również ma umieć service container ogarniać:
class NoConstructor {
public string $name;
public int $age;
}
Teraz kontener z możliwością zapisywania do rejestru przypadków takich jak ten pierwszy (Person):
class Container {
private array $registry = [];
public function set(string $name, Closure $value): void
{
$this->registry[$name] = $value;
}
}
$container = new Container;
$container->set(Person::class, function(){
return new Person("Prototype Name", 30);
});
Teraz odczyt z rejestru:
class Container {
private array $registry = [];
public function set(string $name, Closure $value): void
{
$this->registry[$name] = $value;
}
public function get(string $className) : object|bool {
if (array_key_exists($className, $this->registry)) {
return $this->registry[$className]();
}
return false;
}
$container = new Container;
$container->set(Person::class, function(){
return new Person("Prototype Name", 30);
});
$person = $container->get(Person::class);
print_r($person);
//Person Object ( [name] => Prototype Name [age] => 30 )
Teraz obsługa klas bez konstruktora i bez wpisu w rejestrze:
class Container {
//(...)
public function get(string $className) : object|bool {
if (array_key_exists($className, $this->registry))
return $this->registry[$className]();
$reflector = new ReflectionClass($className);
$constructor = $reflector->getConstructor();
$dependencies = [];
if ($constructor === null)
return new $className;
return false;
}
}
Teraz obsługa klas, których konstruktor nie ma arguemntów:
class Container {
private array $registry = [];
public function set(string $name, Closure $value): void
{
$this->registry[$name] = $value;
}
public function get(string $className) : object|bool {
if (array_key_exists($className, $this->registry))
return $this->registry[$className]();
$reflector = new ReflectionClass($className);
$constructor = $reflector->getConstructor();
$dependencies = [];
if ($constructor === null)
return new $className;
$parameters = $constructor->getParameters();
if(count($parameters) === 0)
return new $className;
return false;
}
}
Teraz obsługa klas bez wpisu w rejestrze i z argumentami-obiektami w sposób rekurencyjny i wywalając niepotrzebne przypadki:
class Container {
//(...)
public function get(string $className) : object|bool {
if (array_key_exists($className, $this->registry))
return $this->registry[$className]();
$reflector = new ReflectionClass($className);
$constructor = $reflector->getConstructor();
$dependencies = [];
if ($constructor === null)
return new $className;
$parameters = $constructor->getParameters();
if(count($parameters) === 0)
return new $className;
foreach ($constructor->getParameters() as $parameter) {
$type = $parameter->getType();
if ($type === null)
return false;
if ( ! ($type instanceof ReflectionNamedType))
return false;
if ($type->isBuiltIn())
return false;
$dependencies[] = $this->get((string) $type);
}
return new $className(...$dependencies);
}
}
Testujemy rejestr oraz graf zależności:
$container = new Container;
$container->set(Person::class, function(){
return new Person("Prototype Name", 30);
});
$person = $container->get(Person::class);
print_r($person);
//Person Object ( [name] => Prototype Name [age] => 30 )
$couple = $container->get(Couple::class);
print_r($couple);
//Couple Object (
// [person1] => Person Object ( [name] => Prototype Name [age] => 30 )
// [person2] => Person Object ( [name] => Prototype Name [age] => 30 )
//)
Jak widać jedno i drugie działa. Teraz inne przypadki:
$parameterless = $container->get(Parameterless::class);
print_r($parameterless);
//Parameterless Object ( [name] => Prototype [age] => 30 )
$noconstructor = $container->get(NoConstructor::class);
print_r($noconstructor);
//NoConstructor Object ( )
Też w porządku. Udało nam się stworzyć potężny mechanizm – gratulacje!