Completed
Push — master ( 6411fa...6ee6e0 )
by Jesse
02:41
created

AutoWiring::resolveThe()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 4
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Stratadox\Di;
6
7
use Closure;
8
use Psr\Container\ContainerInterface as PsrContainerInterface;
9
use ReflectionClass as Reflected;
10
use ReflectionException;
11
use ReflectionType;
12
13
final class AutoWiring implements ContainerInterface, PsrContainerInterface
14
{
15
    private $container;
16
    private $links;
17
18
    private function __construct(ContainerInterface $container, array $links)
19
    {
20
        $this->container = $container;
21
        $this->links = $links;
22
    }
23
24
    public static function the(ContainerInterface $container) : self
25
    {
26
        return new self($container, []);
27
    }
28
29
    public function link(string $interface, string $class) : self
30
    {
31
        if (!is_a($class, $interface, true)) {
32
            throw InvalidServiceType::serviceIsNotOfType($class, $interface);
33
        }
34
        return new self($this->container, [$interface => $class] + $this->links);
35
    }
36
37
    public function get($theService, string $type = '')
38
    {
39
        if (!$this->container->has($theService)) {
40
            $this->resolve($theService);
41
        }
42
        return $this->container->get($theService);
43
    }
44
45
    public function has($theService) : bool
46
    {
47
        return class_exists($theService)
48
            || isset($this->links[$theService])
49
            || $this->container->has($theService);
50
    }
51
52
    public function set(string $service, Closure $factory, bool $cache = true)
53
    {
54
        $this->container->set($service, $factory, $cache);
55
    }
56
57
    public function forget(string $service)
58
    {
59
        $this->container->forget($service);
60
    }
61
62
    private function resolve(string $service)
63
    {
64
        try {
65
            $this->resolveThe(new Reflected($service));
66
        } catch (ReflectionException $exception) {
67
            throw ServiceNotFound::noServiceNamed($service);
68
        }
69
    }
70
71
    private function resolveThe(Reflected $service)
72
    {
73
        if ($service->isAbstract() || $service->isInterface()) {
74
            $this->resolveAbstract($service);
75
        } else {
76
            $this->resolveClass($service);
77
        }
78
    }
79
80
    private function resolveAbstract(Reflected $service) : void
81
    {
82
        $name = $service->getName();
83
        if (!isset($this->links[$name])) {
84
            throw CannotResolveAbstractType::noLinkDefinedFor($service);
85
        }
86
        $class = $this->links[$name];
87
        $this->resolveClass(new Reflected($class));
88
        $this->container->set($name, function () use ($class) {
89
            return $this->container->get($class);
90
        });
91
    }
92
93
    private function resolveClass(Reflected $service) : void
94
    {
95
        $name = $service->getName();
96
        $constructor = $service->getConstructor();
97
        $dependencies = [];
98
        if (isset($constructor)) {
99
            foreach ($constructor->getParameters() as $parameter) {
100
                $dependencies[] = $this->handleDependency($parameter->getType());
101
            }
102
        }
103
        $container = $this->container;
104
        $container->set($name, function () use ($name, $dependencies, $container) {
105
            $parameters = [];
106
            foreach ($dependencies as $dependency) {
107
                $parameters[] = $container->get($dependency);
108
            }
109
            return new $name(...$parameters);
110
        });
111
    }
112
113
    private function handleDependency(ReflectionType $theType) : string
114
    {
115
        if ($theType->isBuiltin()) {
116
            throw CannotAutoWireBuiltInTypes::cannotResolve($theType);
117
        }
118
        $theDependency = (string) $theType;
119
        if (!$this->container->has($theDependency)) {
120
            $this->resolve($theDependency);
121
        }
122
        return $theDependency;
123
    }
124
}
125