AutoWiring   A
last analyzed

Complexity

Total Complexity 25

Size/Duplication

Total Lines 115
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 25
eloc 50
dl 0
loc 115
rs 10
c 0
b 0
f 0

12 Methods

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