Passed
Branch feature/php7.1 (6b75ed)
by Andy
04:06
created

Container::resolveParameter()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 27
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 12
nc 3
nop 1
dl 0
loc 27
ccs 12
cts 12
cp 1
crap 5
rs 9.5555
c 0
b 0
f 0
1
<?php
2
3
namespace Palmtree\Container;
4
5
use Palmtree\Container\Definition\Definition;
6
use Palmtree\Container\Exception\ParameterNotFoundException;
7
use Palmtree\Container\Exception\ServiceNotFoundException;
8
use Palmtree\Container\Exception\ServiceNotPublicException;
9
use Psr\Container\ContainerInterface;
10
11
class Container implements ContainerInterface
12
{
13
    /** Regex for single parameters e.g '%my.parameter%' */
14
    const PATTERN_PARAMETER = '/^%([^%\s]+)%$/';
15
    /** Regex for multiple parameters in a string */
16
    const PATTERN_MULTI_PARAMETER = '/%%|%([^%\s]+)%/';
17
18
    /** @var Definition[] */
19
    private $definitions = [];
20
    /** @var mixed[] */
21
    private $services = [];
22
    /** @var array */
23
    private $parameters = [];
24
25
    /** @var array */
26
    private $envCache = [];
27
28 25
    public function __construct(array $definitions = [], array $parameters = [])
29
    {
30 25
        foreach ($definitions as $key => $definitionArgs) {
31 22
            $this->addDefinition($key, Definition::fromYaml($definitionArgs));
32
        }
33
34 25
        $this->parameters = $parameters;
35 25
        $this->parameters = $this->resolveArgs($this->parameters);
36 25
    }
37
38
    /**
39
     * Instantiates non-lazy services.
40
     */
41 24
    public function instantiateServices()
42
    {
43 24
        foreach ($this->definitions as $key => $definition) {
44 22
            if (!$definition->isLazy()) {
45 22
                $this->services[$key] = $this->create($definition);
46
            }
47
        }
48 24
    }
49
50
    /**
51
     * Returns whether a service with the given key exists within the container.
52
     *
53
     * @param string $key
54
     *
55
     * @return bool
56
     */
57 23
    public function has($key)
58
    {
59 23
        return isset($this->services[$key]);
60
    }
61
62
    /**
63
     * Returns a service object with the given key.
64
     *
65
     * @param string $key
66
     *
67
     * @return mixed
68
     * @throws ServiceNotFoundException
69
     * @throws ServiceNotPublicException
70
     */
71 23
    public function get($key)
72
    {
73 23
        if (!$this->has($key)) {
74 22
            if (!$this->hasDefinition($key)) {
75 1
                throw new ServiceNotFoundException($key);
76
            }
77
78 22
            $this->services[$key] = $this->create($this->definitions[$key]);
79
        }
80
81 23
        if (!$this->definitions[$key]->isPublic()) {
82 21
            throw new ServiceNotPublicException($key);
83
        }
84
85 23
        return $this->services[$key];
86
    }
87
88
    /**
89
     * Returns whether a definition with the given key exists within the container.
90
     *
91
     * @param string $key
92
     *
93
     * @return bool
94
     */
95 22
    public function hasDefinition(string $key): bool
96
    {
97 22
        return isset($this->definitions[$key]);
98
    }
99
100 23
    public function addDefinition(string $key, Definition $definition)
101
    {
102 23
        $this->definitions[$key] = $definition;
103 23
    }
104
105
    /**
106
     * Returns a parameter with the given key or a default value if given.
107
     *
108
     * @param string $key
109
     * @param mixed  $default
110
     *
111
     * @return mixed
112
     * @throws ParameterNotFoundException
113
     */
114 22
    public function getParameter(string $key, $default = null)
115
    {
116 22
        if (!$this->hasParameter($key)) {
117 2
            if (func_num_args() < 2) {
118 1
                throw new ParameterNotFoundException($key);
119
            }
120
121 1
            return $default;
122
        }
123
124 22
        return $this->parameters[$key];
125
    }
126
127
    /**
128
     * Sets a parameter within the container.
129
     *
130
     * @param string $key
131
     * @param mixed  $value
132
     *
133
     * @throws ParameterNotFoundException
134
     * @throws ServiceNotFoundException
135
     */
136 21
    public function setParameter(string $key, $value)
137
    {
138 21
        $this->parameters[$key] = $this->resolveArg($value);
139 21
    }
140
141
    /**
142
     * Returns whether a parameter with the given key exists within the container.
143
     *
144
     * @param string $key
145
     *
146
     * @return bool
147
     */
148 22
    public function hasParameter(string $key): bool
149
    {
150 22
        return isset($this->parameters[$key]) || array_key_exists($key, $this->parameters);
151
    }
152
153
    /**
154
     * @param string $key
155
     *
156
     * @return mixed
157
     * @throws ServiceNotFoundException
158
     */
159 21
    private function inject(string $key)
160
    {
161
        try {
162 21
            $this->get($key);
163 21
        } catch (ServiceNotPublicException $e) {
164
            // Ensures the service is created. Private services are allowed to be injected.
165
        }
166
167 21
        return $this->services[$key];
168
    }
169
170
    /**
171
     * Creates a service as defined by the Definition object.
172
     *
173
     * @param Definition $definition
174
     *
175
     * @return mixed
176
     */
177 23
    private function create(Definition $definition)
178
    {
179 23
        $args = $this->resolveArgs($definition->getArguments());
180
181 23
        if ($factory = $definition->getFactory()) {
182 21
            list($class, $method) = $factory;
183 21
            $class   = $this->resolveArg($class);
184 21
            $method  = $this->resolveArg($method);
185 21
            $service = $class::$method(...$args);
186
        } else {
187 23
            $class   = $this->resolveArg($definition->getClass());
188 23
            $service = new $class(...$args);
189
        }
190
191 23
        foreach ($definition->getMethodCalls() as $methodCall) {
192 22
            $methodName = $methodCall->getName();
193 22
            $methodArgs = $this->resolveArgs($methodCall->getArguments());
194 22
            $service->$methodName(...$methodArgs);
195
        }
196
197 23
        return $service;
198
    }
199
200 25
    private function resolveArgs(array $args): array
201
    {
202 25
        foreach ($args as $key => &$arg) {
203 23
            if (is_array($arg)) {
204 22
                $arg = $this->resolveArgs($arg);
205
            } else {
206 23
                $arg = $this->resolveArg($arg);
207
            }
208
        }
209
210 25
        return $args;
211
    }
212
213
    /**
214
     * @param mixed $arg
215
     *
216
     * @return mixed
217
     * @throws ServiceNotFoundException
218
     */
219 24
    private function resolveArg($arg)
220
    {
221 24
        if (!is_string($arg)) {
222 21
            return $arg;
223
        }
224
225 24
        if (strrpos($arg, '@') === 0) {
226 21
            return $this->inject(substr($arg, 1));
227
        }
228
229 24
        return $this->resolveParameter($arg);
230
    }
231
232
    /**
233
     * @param string $arg
234
     *
235
     * @return mixed
236
     * @throws ParameterNotFoundException
237
     */
238 24
    private function resolveParameter($arg)
239
    {
240
        // Resolve a single parameter value e.g %my_param%
241
        // Used for non-string values (boolean, integer etc)
242 24
        if (preg_match(self::PATTERN_PARAMETER, $arg, $matches)) {
243 21
            $envKey = $this->getEnvParameterKey($matches[1]);
244
245 21
            if (!is_null($envKey)) {
246 21
                return $this->getEnv($envKey);
247
            }
248
249 21
            return $this->getParameter($matches[1]);
250
        }
251
252
        // Resolve multiple parameters in a string e.g /%parent_dir%/somewhere/%child_dir%
253
        return preg_replace_callback(self::PATTERN_MULTI_PARAMETER, function ($matches) {
254
            // Skip %% to allow escaping percent signs
255 21
            if (!isset($matches[1])) {
256 21
                return '%';
257
            }
258
259 21
            if ($envKey = $this->getEnvParameterKey($matches[1])) {
260 21
                return $this->getEnv($envKey);
261
            }
262
263 21
            return $this->getParameter($matches[1]);
264 24
        }, $arg);
265
    }
266
267 21
    private function getEnvParameterKey(string $value): ?string
268
    {
269 21
        if (strpos($value, 'env(') === 0 && substr($value, -1) === ')' && $value !== 'env()') {
270 21
            return substr($value, 4, -1);
271
        }
272
273 21
        return null;
274
    }
275
276
    /**
277
     * @param string $key
278
     *
279
     * @return string|bool
280
     */
281 21
    private function getEnv(string $key)
282
    {
283 21
        if (isset($this->envCache[$key]) || array_key_exists($key, $this->envCache)) {
284 21
            return $this->envCache[$key];
285
        }
286
287 21
        $envVar = getenv($key);
288
289 21
        if (!$envVar) {
290
            try {
291 20
                $envVar = $this->resolveArg($this->getParameter("env($key)"));
292
            } catch (ParameterNotFoundException $exception) {
293
                // do nothing
294
            }
295
        }
296
297 21
        $this->envCache[$key] = $envVar;
298
299 21
        return $this->envCache[$key];
300
    }
301
}
302