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