Completed
Push — master ( 98c9c0...eacb7d )
by Andy
01:52
created

Container::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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