Completed
Branch feature/interfacesupport (e1bea3)
by Dan
03:10
created

validateInterfaceDefinition()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 2
dl 0
loc 4
ccs 3
cts 3
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 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
                return $this->validateClassDefinition($definition, $id);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->validateClassDefinition($definition, $id) targeting danmurf\DependencyInject...lidateClassDefinition() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
93
            }
94
95 3
            if (isset($definition['interface'])) {
96 2
                return $this->validateInterfaceDefinition($definition, $id);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->validateInterface...ition($definition, $id) targeting danmurf\DependencyInject...teInterfaceDefinition() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
97
            }
98
99 1
            throw new ContainerException(sprintf('Configured service `%s` has no `class` or `interface` value.', $id));
100
            if (!isset($definition['class'])) {
0 ignored issues
show
Unused Code introduced by
IfNode is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
101
                throw new ContainerException(sprintf('Configured service `%s` has no `class` value.', $id));
102
            }
103
104
            if (isset($definition['arguments'])) {
105
            }
106
        }
107 11
    }
108
109
    /**
110
     * Ensure a class definied service has valid config.
111
     *
112
     * @param array  $definition
113
     * @param string $id
114
     */
115 6
    private function validateClassDefinition(array $definition, string $id)
116
    {
117 6
        if (isset($definition['arguments'])) {
118 4
            $this->validateArguments($definition['arguments'], $id);
119
        }
120 4
    }
121
122
    /**
123
     * Ensure an interface defined service has valid config.
124
     *
125
     * @param array  $definition
126
     * @param string $id
127
     */
128 2
    private function validateInterfaceDefinition(array $definition, string $id)
129
    {
130 2
        if (!isset($definition['service'])) {
131 1
            throw new ContainerException(sprintf('Configured interface definition `%s` has no `service` value.', $id));
132
        }
133 1
    }
134
135
    /**
136
     * Ensure the arguments config is in a valid format.
137
     *
138
     * @param array  $arguments
139
     * @param string $id
140
     *
141
     * @throws ContainerException
142
     */
143 4
    private function validateArguments(array $arguments, string $id)
144
    {
145 4
        foreach ($arguments as $argument) {
146 4
            $this->validateArgumentProperties($argument, $id);
147 3
            $this->validateArgumentType($argument['type'], $id);
148
        }
149 2
    }
150
151
    /**
152
     * Ensure the argument has valid properties.
153
     *
154
     * @param array  $argument
155
     * @param string $id
156
     *
157
     * @throws ContainerException
158
     */
159 4
    private function validateArgumentProperties(array $argument, string $id)
160
    {
161 4
        if (!isset($argument['type']) || !isset($argument['value'])) {
162 1
            throw new ContainerException(sprintf(
163 1
                'Configuration for service `%s` must have `type` and `value` values.',
164 1
                $id
165
            ));
166
        }
167 3
    }
168
169
    /**
170
     * Ensure the argument type is valid.
171
     *
172
     * @param string $type
173
     * @param string $id
174
     *
175
     * @throws ContainerException
176
     */
177 3
    private function validateArgumentType(string $type, string $id)
178
    {
179 3
        if (false === array_search($type, self::ARGUMENT_TYPES)) {
180 1
            throw new ContainerException(sprintf(
181 1
                'Unknown argument type `%s` for service `%s`. Accepted values are `scalar` and `service`.',
182 1
                $type,
183 1
                $id
184
            ));
185
        }
186 2
    }
187
188
    /**
189
     * @param string $id
190
     *
191
     * @throws CircularReferenceException
192
     */
193 9
    private function validateRequest(string $id)
194
    {
195 9
        if (false !== array_search($id, $this->locateCalls)) {
196 1
            throw new CircularReferenceException(sprintf('Circular dependency reference detected for `%s`', $id));
197
        }
198
199 9
        $this->locateCalls[] = $id;
200 9
    }
201
}
202