Completed
Branch feature/interfacesupport (2c39b6)
by Dan
01:53
created

ConfigurableServiceLocator   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 181
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 53
dl 0
loc 181
ccs 61
cts 61
cp 1
rs 10
c 0
b 0
f 0
wmc 24

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A locate() 0 28 6
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
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 20
    public function __construct(array $config)
37
    {
38 20
        $this->validateConfig($config);
39 16
        $this->config = $config;
40 16
    }
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 9
    public function locate($id, ContainerInterface $container)
52
    {
53 9
        $this->validateRequest($id);
54
55 9
        if (!isset($this->config[$id])) {
56 5
            throw new NotFoundException(sprintf('Unable to locate service `%s` from configuration.', $id));
57
        }
58
59 4
        $class = new ReflectionClass($this->config[$id]['class']);
60
61 4
        if (!isset($this->config[$id]['arguments'])) {
62 1
            return $class->newInstance();
63
        }
64
65 3
        $args = [];
66 3
        foreach ($this->config[$id]['arguments'] as $argumentConfig) {
67 3
            switch ($argumentConfig['type']) {
68 3
                case self::ARGUMENT_TYPE_SCALAR:
69 1
                    $args[] = $argumentConfig['value'];
70 1
                    break;
71
72 2
                case self::ARGUMENT_TYPE_SERVICE:
73 2
                    $args[] = $container->get($argumentConfig['value']);
74 2
                    break;
75
            }
76
        }
77
78 2
        return $class->newInstanceArgs($args);
79
    }
80
81
    /**
82
     * Ensure the config is in a valid format.
83
     *
84
     * @param array $config
85
     *
86
     * @throws ContainerException
87
     */
88 20
    private function validateConfig(array $config)
89
    {
90 20
        foreach ($config as $id => $definition) {
91 9
            if (isset($definition['class'])) {
92 6
                $this->validateClassDefinition($definition, $id);
93
94 4
                return;
95
            }
96
97 3
            if (isset($definition['interface'])) {
98 2
                $this->validateInterfaceDefinition($definition, $id);
99
100 1
                return;
101
            }
102
103 1
            throw new ContainerException(sprintf('Configured service `%s` has no `class` or `interface` value.', $id));
104
        }
105 11
    }
106
107
    /**
108
     * Ensure a class definied service has valid config.
109
     *
110
     * @param array  $definition
111
     * @param string $id
112
     */
113 6
    private function validateClassDefinition(array $definition, string $id)
114
    {
115 6
        if (isset($definition['arguments'])) {
116 4
            $this->validateArguments($definition['arguments'], $id);
117
        }
118 4
    }
119
120
    /**
121
     * Ensure an interface defined service has valid config.
122
     *
123
     * @param array  $definition
124
     * @param string $id
125
     */
126 2
    private function validateInterfaceDefinition(array $definition, string $id)
127
    {
128 2
        if (!isset($definition['service'])) {
129 1
            throw new ContainerException(sprintf('Configured interface definition `%s` has no `service` value.', $id));
130
        }
131 1
    }
132
133
    /**
134
     * Ensure the arguments config is in a valid format.
135
     *
136
     * @param array  $arguments
137
     * @param string $id
138
     *
139
     * @throws ContainerException
140
     */
141 4
    private function validateArguments(array $arguments, string $id)
142
    {
143 4
        foreach ($arguments as $argument) {
144 4
            $this->validateArgumentProperties($argument, $id);
145 3
            $this->validateArgumentType($argument['type'], $id);
146
        }
147 2
    }
148
149
    /**
150
     * Ensure the argument has valid properties.
151
     *
152
     * @param array  $argument
153
     * @param string $id
154
     *
155
     * @throws ContainerException
156
     */
157 4
    private function validateArgumentProperties(array $argument, string $id)
158
    {
159 4
        if (!isset($argument['type']) || !isset($argument['value'])) {
160 1
            throw new ContainerException(sprintf(
161 1
                'Configuration for service `%s` must have `type` and `value` values.',
162 1
                $id
163
            ));
164
        }
165 3
    }
166
167
    /**
168
     * Ensure the argument type is valid.
169
     *
170
     * @param string $type
171
     * @param string $id
172
     *
173
     * @throws ContainerException
174
     */
175 3
    private function validateArgumentType(string $type, string $id)
176
    {
177 3
        if (false === array_search($type, self::ARGUMENT_TYPES)) {
178 1
            throw new ContainerException(sprintf(
179 1
                'Unknown argument type `%s` for service `%s`. Accepted values are `scalar` and `service`.',
180 1
                $type,
181 1
                $id
182
            ));
183
        }
184 2
    }
185
186
    /**
187
     * @param string $id
188
     *
189
     * @throws CircularReferenceException
190
     */
191 9
    private function validateRequest(string $id)
192
    {
193 9
        if (false !== array_search($id, $this->locateCalls)) {
194 1
            throw new CircularReferenceException(sprintf('Circular dependency reference detected for `%s`', $id));
195
        }
196
197 9
        $this->locateCalls[] = $id;
198 9
    }
199
}
200