Completed
Push — master ( 3bac70...6aee2b )
by Mihail
02:55
created

DIContainer::call()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
nc 1
nop 2
1
<?php declare(strict_types=1);
2
3
/*
4
 * This file is part of the Koded package.
5
 *
6
 * (c) Mihail Binev <[email protected]>
7
 *
8
 * Please view the LICENSE distributed with this source code
9
 * for the full copyright and license information.
10
 *
11
 */
12
13
namespace Koded;
14
15
use Psr\Container\ContainerInterface;
16
use Throwable;
17
18
interface DIModule
19
{
20
    public function configure(DIContainer $injector): void;
21
}
22
23
final class DIContainer implements ContainerInterface
24
{
25
    public const SINGLETONS = 'singletons';
26
    public const BINDINGS   = 'bindings';
27
    public const NAMED      = 'named';
28
29
    private $reflection;
30
    private $inProgress = [];
31
32
    private $singletons = [];
33
    private $bindings   = [];
34
    private $named      = [];
35
36
    private $interfaces = [];
37
38 39
    public function __construct(DIModule ...$modules)
39
    {
40 39
        $this->reflection = new DIReflector;
41
        $this->interfaces = array_filter(get_declared_interfaces(), function(string $name) {
42 39
            return false === strpos($name, '\\');
43 39
        });
44 39
        $this->interfaces = array_flip($this->interfaces);
45
46 39
        foreach ((array)$modules as $module) {
47 9
            $module->configure($this);
48
        }
49 39
    }
50
51 1
    public function __clone()
52
    {
53 1
        throw DIException::forCloningNotAllowed();
54
    }
55
56 39
    public function __destruct()
57
    {
58 39
        $this->reflection = null;
59
60 39
        $this->singletons = [];
61 39
        $this->interfaces = [];
62 39
        $this->bindings   = [];
63 39
        $this->named      = [];
64 39
    }
65
66 4
    public function __invoke(callable $callable, array $arguments = [])
67
    {
68 4
        return call_user_func_array($callable, $this->reflection->processMethodArguments(
69 4
            $this, $this->reflection->newMethodFromCallable($callable), $arguments
70
        ));
71
    }
72
73 27
    public function inject(string $class, array $arguments = []): ?object
74
    {
75 27
        $binding = $this->getFromBindings($class);
76
77 26
        if (isset($this->singletons[$binding])) {
78 3
            return $this->singletons[$binding];
79
        }
80
81 26
        if (isset($this->inProgress[$binding])) {
82 1
            throw DIException::forCircularDependency($binding);
83
        }
84 26
        $this->inProgress[$binding] = true;
85
86
        try {
87 26
            return $this->newInstance($binding, $arguments);
88
        } finally {
89 26
            unset($this->inProgress[$binding]);
90
        }
91
    }
92
93 9
    public function singleton(string $class, array $arguments = []): object
94
    {
95 9
        return $this->singletons[$class] = $this->inject($class, $arguments);
96
    }
97
98 2
    public function share(object $instance): DIContainer
99
    {
100 2
        $class = get_class($instance);
101 2
        $this->mapInterfaces($class, $class);
102 2
        $this->singletons[$class] = $instance;
103
104 2
        return $this;
105
    }
106
107 11
    public function bind(string $interface, string $class): DIContainer
108
    {
109 11
        $this->assertEmpty($class, 'class');
110 11
        $this->assertEmpty($interface, 'interface');
111
112 11
        if ('$' === $class[0]) {
113 1
            $this->bindings[$interface] = $interface;
114 1
            $this->bindings[$class]     = $interface;
115
        } else {
116 10
            $this->bindings[$interface] = $class;
117 10
            $this->bindings[$class]     = $class;
118
        }
119
120 11
        return $this;
121
    }
122
123 13
    public function named(string $name, $value): DIContainer
124
    {
125 13
        if (1 !== preg_match('/\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $name)) {
126 7
            throw DIException::forInvalidParameterName();
127
        }
128 6
        $this->named[$name] = $value;
129
130 6
        return $this;
131
    }
132
133
    /**
134
     * @internal
135
     */
136 19
    public function getStorage(): array
137
    {
138
        return [
139 19
            self::SINGLETONS => $this->singletons,
140 19
            self::BINDINGS   => $this->bindings,
141 19
            self::NAMED      => $this->named,
142
        ];
143
    }
144
145
    /**
146
     * @inheritDoc
147
     */
148 6
    public function has($id): bool
149
    {
150 6
        $this->assertEmpty($id, 'dependency');
151
152 6
        return isset($this->bindings[$id]) || isset($this->named[$id]);
153
    }
154
155
    /**
156
     * @inheritDoc
157
     */
158 4
    public function get($id)
159
    {
160 4
        if (false === $this->has($id)) {
161 1
            throw DIInstanceNotFound::for($id);
162
        }
163
164 3
        $dependency = $this->getFromBindings($id);
165
166 3
        return $this->singletons[$dependency]
167 1
            ?? $this->named[$dependency]
168 3
            ?? $this->inject($dependency);
169
    }
170
171 26
    private function newInstance(string $class, array $arguments): object
172
    {
173
        try {
174 26
            $this->bindings[$class] = $class;
175 26
            return $this->reflection->newInstance($this, $class, $arguments);
176 6
        } catch (Throwable $e) {
177 6
            throw $e;
178
        }
179
    }
180
181 27
    private function getFromBindings(string $dependency): string
182
    {
183 27
        $this->assertEmpty($dependency, 'class/interface');
184
185 26
        return $this->bindings[$dependency] ?? $dependency;
186
    }
187
188 29
    private function assertEmpty(string $value, string $type): void
189
    {
190 29
        if (empty($value)) {
191 1
            throw DIException::forEmptyName($type);
192
        }
193 28
    }
194
195 2
    private function mapInterfaces(string $dependency, string $class): void
196
    {
197 2
        foreach ((@class_implements($dependency, false) ?: []) as $implements) {
198 1
            if (false === isset($this->interfaces[$implements])) {
199 1
                $this->bindings[$implements] = $class;
200
            }
201
        }
202 2
    }
203
}
204