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!