Passed
Branch develop (c6f8d0)
by Peter
04:03
created

DependencyContainer::has()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 11
c 0
b 0
f 0
ccs 4
cts 4
cp 1
rs 9.9
cc 2
nc 2
nop 1
crap 2
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
                try {
126 7
                    if (is_null($parameter->getClass())) {
127 1
                        throw new NotFoundException("parameters for constructor contains field without typehint");
128
                    }
129 7
                } catch (\ReflectionException $reflectionException) {
130 1
                    throw new NotFoundException("can't reflect parameter '{$parameter->name}' of '$classname'", 0, $reflectionException);
131
                }
132 5
                $paramClass = $parameter->getClass()->getName();
133 5
                if ($this->hasServiceFor($paramClass)) {
134 4
                    $args[] = $this->getServiceInstanceFor($paramClass);
135 4
                } else {
136 1
                    $args[] = $this->get($paramClass);
137
                }
138 4
            }
139 4
        }
140
141 5
        if ($reflector->isInterface()) {
142 1
            throw new NotFoundException("no Injector registered for interface: '$classname'");
143
        }
144
145 4
        return $reflector->newInstanceArgs($args);
146
    }
147
148
    /**
149
     * Returns true if the container can return an entry for the given identifier.
150
     * Returns false otherwise.
151
     *
152
     * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
153
     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
154
     *
155
     * @param string $id Identifier of the entry to look for.
156
     *
157
     * @return bool
158
     */
159 6
    public function has($id)
160
    {
161
        // if we have a service registered, we can assume true
162 6
        if($this->hasServiceFor($id)) {
163 3
            return true;
164
        }
165
166
        // if we don't have as service registered, we might still pull one from the hat
167
        // so lets at least check if the class would be available
168 5
        return class_exists($id, true);
169
    }
170
}
171