Passed
Branch master (ecd953)
by Dan
01:47
created

ConfigurableServiceLocator::locate()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 16
nc 6
nop 2
dl 0
loc 28
ccs 17
cts 17
cp 1
crap 6
rs 9.1111
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 18
    public function __construct(array $config)
37
    {
38 18
        $this->validateConfig($config);
39 15
        $this->config = $config;
40 15
    }
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 18
    private function validateConfig(array $config)
89
    {
90 18
        foreach ($config as $id => $service) {
91 7
            if (!isset($service['class'])) {
92 1
                throw new ContainerException(sprintf('Configured service `%s` has no `class` value.', $id));
93
            }
94
95 6
            if (isset($service['arguments'])) {
96 5
                $this->validateArguments($service['arguments'], $id);
97
            }
98
        }
99 15
    }
100
101
    /**
102
     * Ensure the arguments config is in a valid format.
103
     *
104
     * @param array  $arguments
105
     * @param string $id
106
     *
107
     * @throws ContainerException
108
     */
109 5
    private function validateArguments(array $arguments, string $id)
110
    {
111 5
        foreach ($arguments as $argument) {
112 5
            $this->validateArgumentProperties($argument, $id);
113 4
            $this->validateArgumentType($argument['type'], $id);
114
        }
115 3
    }
116
117
    /**
118
     * Ensure the argument has valid properties.
119
     *
120
     * @param array  $argument
121
     * @param string $id
122
     *
123
     * @throws ContainerException
124
     */
125 5
    private function validateArgumentProperties(array $argument, string $id)
126
    {
127 5
        if (!isset($argument['type']) || !isset($argument['value'])) {
128 1
            throw new ContainerException(sprintf(
129 1
                'Configuration for service `%s` must have `type` and `value` values.',
130 1
                $id
131
            ));
132
        }
133 4
    }
134
135
    /**
136
     * Ensure the argument type is valid.
137
     *
138
     * @param string $type
139
     * @param string $id
140
     *
141
     * @throws ContainerException
142
     */
143 4
    private function validateArgumentType(string $type, string $id)
144
    {
145 4
        if (false === array_search($type, self::ARGUMENT_TYPES)) {
146 1
            throw new ContainerException(sprintf(
147 1
                'Unknown argument type `%s` for service `%s`. Accepted values are `scalar` and `service`.',
148 1
                $type,
149 1
                $id
150
            ));
151
        }
152 3
    }
153
154
    /**
155
     * @param string $id
156
     *
157
     * @throws CircularReferenceException
158
     */
159 9
    private function validateRequest(string $id)
160
    {
161 9
        if (false !== array_search($id, $this->locateCalls)) {
162 1
            throw new CircularReferenceException(sprintf('Circular dependency reference detected for `%s`', $id));
163
        }
164
165 9
        $this->locateCalls[] = $id;
166 9
    }
167
}
168