DependencyContainer::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 11
cts 11
cp 1
rs 9.6
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
namespace King23\DI;
4
5
use King23\DI\Exception\AlreadyRegisteredException;
6
use King23\DI\Exception\NotFoundException;
7
8
/**
9
 * Class DependencyContainer
10
 * @package King23\DI
11
 */
12
class DependencyContainer implements ContainerInterface
13
{
14
    protected $injectors = [];
15
16
    /**
17
     * Constructor
18
     */
19 10
    public function __construct()
20
    {
21 10
        $that = $this;
22
        // we register ourselves, so this container can be injected too
23
        /** @noinspection PhpUnhandledExceptionInspection */
24 10
        $this->register(
25 10
            \Psr\Container\ContainerInterface::class,
26
            function () use ($that) {
27 1
                return $that;
28
            }
29 10
        );
30
31
        /** @noinspection PhpUnhandledExceptionInspection */
32 10
        $this->register(
33 10
            ContainerInterface::class,
34
            function () use ($that) {
35 1
                return $that;
36
            }
37 10
        );
38 10
    }
39
40
    /**
41
     * check if for $interface there is an existing service to inject
42
     *
43
     * @param $interface
44
     * @return bool
45
     */
46 8
    protected function hasServiceFor($interface)
47
    {
48 8
        return isset($this->injectors[$interface]);
49
    }
50
51
    /**
52
     * gets a service instance for $interface - singletoned!
53
     *
54
     * @param string $interface
55
     * @return mixed
56
     */
57 4
    protected function getServiceInstanceFor($interface)
58
    {
59 4
        return $this->injectors[$interface]();
60
    }
61
62
    /**
63
     * register an service implementation as a singleton (shared instance)
64
     *
65
     * @param $interface
66
     * @param callable $implementation
67
     * @throws AlreadyRegisteredException
68
     */
69 10
    public function register($interface, callable $implementation)
70
    {
71 10
        if (isset($this->injectors[$interface])) {
72 1
            throw new AlreadyRegisteredException("Error: for $interface there is already an implementation registered");
73
        }
74
75
        // wraps the implementation in a singleton
76 3
        $this->injectors[$interface] = function () use ($implementation) {
77 3
            static $instance;
78 3
            if (is_null($instance)) {
79 3
                $instance = $implementation();
80 3
            }
81
82 3
            return $instance;
83
        };
84 10
    }
85
86
    /**
87
     * register a service implementation as a factory
88
     *
89
     * @param $interface
90
     * @param callable $implementation
91
     * @throws AlreadyRegisteredException
92
     */
93 2
    public function registerFactory($interface, callable $implementation)
94
    {
95 2
        if (isset($this->injectors[$interface])) {
96 1
            throw new AlreadyRegisteredException("Error: for $interface there is already an implementation registered");
97
        }
98
99 1
        $this->injectors[$interface] = $implementation;
100 1
    }
101
102
    /**
103
     * @param string $classname fully qualified classname
104
     * @return object
105
     * @throws NotFoundException
106
     * @throws \ReflectionException
107
     */
108 8
    public function get($classname)
109
    {
110
        // first we check if this is maybe a known service
111 8
        if ($this->hasServiceFor($classname)) {
112 1
            return $this->getServiceInstanceFor($classname);
113
        }
114
115
        // alright, this was not one of our known services, so we assume
116
        // we are supposed to play factory for $classname
117
118
        // lets see if the class actually exists first
119 8
        if (!class_exists($classname, true) && !interface_exists($classname, true)) {
120 1
            throw new NotFoundException("Class/Interface not found: '$classname'");
121
        }
122
123 7
        $reflector = new \ReflectionClass($classname);
124
125 7
        $args = [];
126
127 7
        $constructor = $reflector->getConstructor();
128
129
        // if there is no constructor, we don't need to inject anything
130 7
        if (!is_null($constructor)) {
131
            /** @var \ReflectionParameter $parameter */
132 7
            foreach ($constructor->getParameters() as $parameter) {
133 7
                $args[] = $this->handleReflectionParameter($parameter, $classname);
134 4
            }
135 4
        }
136
137 5
        if ($reflector->isInterface()) {
138 1
            throw new NotFoundException("no Injector registered for interface: '$classname'");
139
        }
140
141 4
        return $reflector->newInstanceArgs($args);
142
    }
143
144
    /**
145
     * Returns true if the container can return an entry for the given identifier.
146
     * Returns false otherwise.
147
     *
148
     * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
149
     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
150
     *
151
     * @param string $id Identifier of the entry to look for.
152
     *
153
     * @return bool
154
     */
155 6
    public function has($id)
156
    {
157
        // if we have a service registered, we can assume true
158 6
        if($this->hasServiceFor($id)) {
159 3
            return true;
160
        }
161
162
        // if we don't have as service registered, we might still pull one from the hat
163
        // so lets at least check if the class would be available
164 5
        return class_exists($id, true);
165
    }
166
167
    /**
168
     * @param \ReflectionParameter $parameter
169
     * @param string $classname
170
     * @return mixed|object
171
     * @throws NotFoundException
172
     * @throws \ReflectionException
173
     */
174 7
    private function handleReflectionParameter(\ReflectionParameter $parameter, $classname)
175
    {
176
        try {
177 7
            if (is_null($parameter->getClass())) {
178 1
                throw new NotFoundException("parameters for constructor contains field without typehint");
179
            }
180 7
        } catch (\ReflectionException $reflectionException) {
181 1
            throw new NotFoundException("can't reflect parameter '{$parameter->name}' of '$classname'", 0, $reflectionException);
182
        }
183 5
        $paramClass = $parameter->getClass()->getName();
184 5
        if ($this->hasServiceFor($paramClass)) {
185 4
            return $this->getServiceInstanceFor($paramClass);
186
        } else {
187 1
            return $this->get($paramClass);
188
        }
189
    }
190
}
191