Passed
Branch develop (ff897e)
by Peter
01:35
created

DependencyContainer::handleReflectionParameter()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

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