Completed
Branch feature/interfacesupport (1f790d)
by Dan
01:46
created

ConfigurableServiceLocator::validateRequest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 2
rs 10
c 0
b 0
f 0
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 the arguments config is in a valid format.
126
     *
127
     * @param array  $arguments
128
     * @param string $id
129
     *
130
     * @throws ContainerException
131
     */
132 4
    private function validateArguments(array $arguments, string $id)
133
    {
134 4
        foreach ($arguments as $argument) {
135 4
            $this->validateArgumentProperties($argument, $id);
136 3
            $this->validateArgumentType($argument['type'], $id);
137
        }
138 2
    }
139
140
    /**
141
     * Ensure the argument has valid properties.
142
     *
143
     * @param array  $argument
144
     * @param string $id
145
     *
146
     * @throws ContainerException
147
     */
148 4
    private function validateArgumentProperties(array $argument, string $id)
149
    {
150 4
        if (!isset($argument['type']) || !isset($argument['value'])) {
151 1
            throw new ContainerException(sprintf(
152 1
                'Configuration for service `%s` must have `type` and `value` values.',
153 1
                $id
154
            ));
155
        }
156 3
    }
157
158
    /**
159
     * Ensure the argument type is valid.
160
     *
161
     * @param string $type
162
     * @param string $id
163
     *
164
     * @throws ContainerException
165
     */
166 3
    private function validateArgumentType(string $type, string $id)
167
    {
168 3
        if (false === array_search($type, self::ARGUMENT_TYPES)) {
169 1
            throw new ContainerException(sprintf(
170 1
                'Unknown argument type `%s` for service `%s`. Accepted values are `scalar` and `service`.',
171 1
                $type,
172 1
                $id
173
            ));
174
        }
175 2
    }
176
177
    /**
178
     * @param string $id
179
     *
180
     * @throws CircularReferenceException
181
     */
182 10
    private function validateRequest(string $id)
183
    {
184 10
        if (false !== array_search($id, $this->locateCalls)) {
185 1
            throw new CircularReferenceException(sprintf('Circular dependency reference detected for `%s`', $id));
186
        }
187
188 10
        $this->locateCalls[] = $id;
189 10
    }
190
}
191