Completed
Branch feature/interfacesupport (20314c)
by Dan
01:58
created

ConfigurableServiceLocator   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 185
Duplicated Lines 0 %

Test Coverage

Coverage 93.55%

Importance

Changes 0
Metric Value
eloc 54
dl 0
loc 185
ccs 58
cts 62
cp 0.9355
rs 10
c 0
b 0
f 0
wmc 25

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A validateArgumentType() 0 7 2
A validateInterfaceDefinition() 0 4 2
A validateRequest() 0 7 2
A validateClassDefinition() 0 4 2
A validateArguments() 0 5 2
A validateArgumentProperties() 0 6 3
B locate() 0 32 7
A validateConfig() 0 16 4
1
<?php
2
3
namespace danmurf\DependencyInjection;
4
5
use danmurf\DependencyInjection\Exception\CircularReferenceException;
6
use danmurf\DependencyInjection\Exception\ContainerException;
7
use danmurf\DependencyInjection\Exception\NotFoundException;
8
use Psr\Container\ContainerInterface;
9
use ReflectionClass;
10
11
/**
12
 * Service locator which can get find and build services using
13
 * pre-defined configuration.
14
 *
15
 * @author Dan Murfitt <[email protected]>
16
 */
17
class ConfigurableServiceLocator implements ServiceLocatorInterface
18
{
19
    const ARGUMENT_TYPE_SCALAR = 'scalar';
20
    const ARGUMENT_TYPE_SERVICE = 'service';
21
22
    const ARGUMENT_TYPES = [
23
            self::ARGUMENT_TYPE_SCALAR,
24
            self::ARGUMENT_TYPE_SERVICE,
25
        ];
26
27
    /** @var array */
28
    private $config = [];
29
30
    /** @var array */
31
    private $locateCalls = [];
32
33
    /**
34
     * @param array $config
35
     */
36 21
    public function __construct(array $config)
37
    {
38 21
        $this->validateConfig($config);
39 17
        $this->config = $config;
40 17
    }
41
42
    /**
43
     * Get a new instance of a service.
44
     *
45
     * @param string             $id        The service id
46
     * @param ContainerInterface $container The container to get service dependencies from
47
     *
48
     * @throws ContainerException
49
     * @throws NotFoundException
50
     */
51 10
    public function locate($id, ContainerInterface $container)
52
    {
53 10
        $this->validateRequest($id);
54
55 10
        if (!isset($this->config[$id])) {
56 5
            throw new NotFoundException(sprintf('Unable to locate service `%s` from configuration.', $id));
57
        }
58
59 5
        if (isset($this->config[$id]['service'])) {
60 1
            return $this->locate($this->config[$id]['service'], $container);
61
        }
62
63 5
        $class = new ReflectionClass($this->config[$id]['class']);
64
65 5
        if (!isset($this->config[$id]['arguments'])) {
66 2
            return $class->newInstance();
67
        }
68
69 3
        $args = [];
70 3
        foreach ($this->config[$id]['arguments'] as $argumentConfig) {
71 3
            switch ($argumentConfig['type']) {
72 3
                case self::ARGUMENT_TYPE_SCALAR:
73 1
                    $args[] = $argumentConfig['value'];
74 1
                    break;
75
76 2
                case self::ARGUMENT_TYPE_SERVICE:
77 2
                    $args[] = $container->get($argumentConfig['value']);
78 2
                    break;
79
            }
80
        }
81
82 2
        return $class->newInstanceArgs($args);
83
    }
84
85
    /**
86
     * Ensure the config is in a valid format.
87
     *
88
     * @param array $config
89
     *
90
     * @throws ContainerException
91
     */
92 21
    private function validateConfig(array $config)
93
    {
94 21
        foreach ($config as $id => $definition) {
95 10
            if (isset($definition['class'])) {
96
                // Class service definition
97 6
                $this->validateClassDefinition($definition, $id);
98
99 4
                return;
100
            }
101
102 4
            if (isset($definition['service'])) {
103
                // Interface service mapping
104 2
                return;
105
            }
106
107 2
            throw new ContainerException(sprintf('Configured service `%s` has no `class` or `interface` value.', $id));
108
        }
109 11
    }
110
111
    /**
112
     * Ensure a class definied service has valid config.
113
     *
114
     * @param array  $definition
115
     * @param string $id
116
     */
117 6
    private function validateClassDefinition(array $definition, string $id)
118
    {
119 6
        if (isset($definition['arguments'])) {
120 4
            $this->validateArguments($definition['arguments'], $id);
121
        }
122 4
    }
123
124
    /**
125
     * Ensure an interface defined service has valid config.
126
     *
127
     * @param array  $definition
128
     * @param string $id
129
     */
130
    private function validateInterfaceDefinition(array $definition, string $id)
0 ignored issues
show
Unused Code introduced by
The method validateInterfaceDefinition() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
131
    {
132
        if (!isset($definition['service'])) {
133
            throw new ContainerException(sprintf('Configured interface definition `%s` has no `service` value.', $id));
134
        }
135
    }
136
137
    /**
138
     * Ensure the arguments config is in a valid format.
139
     *
140
     * @param array  $arguments
141
     * @param string $id
142
     *
143
     * @throws ContainerException
144
     */
145 4
    private function validateArguments(array $arguments, string $id)
146
    {
147 4
        foreach ($arguments as $argument) {
148 4
            $this->validateArgumentProperties($argument, $id);
149 3
            $this->validateArgumentType($argument['type'], $id);
150
        }
151 2
    }
152
153
    /**
154
     * Ensure the argument has valid properties.
155
     *
156
     * @param array  $argument
157
     * @param string $id
158
     *
159
     * @throws ContainerException
160
     */
161 4
    private function validateArgumentProperties(array $argument, string $id)
162
    {
163 4
        if (!isset($argument['type']) || !isset($argument['value'])) {
164 1
            throw new ContainerException(sprintf(
165 1
                'Configuration for service `%s` must have `type` and `value` values.',
166 1
                $id
167
            ));
168
        }
169 3
    }
170
171
    /**
172
     * Ensure the argument type is valid.
173
     *
174
     * @param string $type
175
     * @param string $id
176
     *
177
     * @throws ContainerException
178
     */
179 3
    private function validateArgumentType(string $type, string $id)
180
    {
181 3
        if (false === array_search($type, self::ARGUMENT_TYPES)) {
182 1
            throw new ContainerException(sprintf(
183 1
                'Unknown argument type `%s` for service `%s`. Accepted values are `scalar` and `service`.',
184 1
                $type,
185 1
                $id
186
            ));
187
        }
188 2
    }
189
190
    /**
191
     * @param string $id
192
     *
193
     * @throws CircularReferenceException
194
     */
195 10
    private function validateRequest(string $id)
196
    {
197 10
        if (false !== array_search($id, $this->locateCalls)) {
198 1
            throw new CircularReferenceException(sprintf('Circular dependency reference detected for `%s`', $id));
199
        }
200
201 10
        $this->locateCalls[] = $id;
202 10
    }
203
}
204