Passed
Push — master ( 1ef2f8...5ed3c8 )
by Andy
02:03
created

Container::instantiateServices()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 3
nc 3
nop 0
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 4
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 Psr\Container\ContainerInterface;
9
10
class Container implements ContainerInterface
11
{
12
    /** Regex for parameters e.g '%my.parameter%' */
13
    const PATTERN_PARAMETER = '/%%|%([^%\s]+)%/';
14
    /** Regex for environment variable parameters e.g '%env(MY_ENV_VAR)%' */
15
    const PATTERN_ENV_PARAMETER = '/^env\(([^\)]+)\)$/';
16
    /** Regex for services e.g '@myservice' */
17
    const PATTERN_SERVICE = '/^@(.+)$/';
18
19
    /** @var Definition[]|mixed[] */
20
    protected $services = [];
21
    /** @var array */
22
    protected $parameters = [];
23
24
    /** @var array */
25
    protected $envCache = [];
26
27 16
    public function __construct(array $services = [], array $parameters = [])
28
    {
29 16
        foreach ($services as $key => $definitionArgs) {
30 16
            $this->add($key, $definitionArgs);
31
        }
32
33 16
        $this->parameters = $parameters;
34 16
        $this->parameters = $this->resolveArgs($this->parameters);
35 16
    }
36
37
    /**
38
     * Instantiates non-lazy services.
39
     *
40
     * @throws ServiceNotFoundException
41
     */
42 16
    public function instantiateServices()
43
    {
44 16
        foreach ($this->services as $key => $service) {
45 16
            if ($service instanceof Definition && !$service->isLazy()) {
46 16
                $this->get($key);
47
            }
48
        }
49 16
    }
50
51
    /**
52
     * @param string $key
53
     * @param mixed  $default
54
     *
55
     * @return mixed
56
     * @throws ParameterNotFoundException
57
     */
58 16
    public function getParameter($key, $default = null)
59
    {
60 16
        if (!$this->hasParameter($key)) {
61 1
            if (func_num_args() < 2) {
62 1
                throw new ParameterNotFoundException($key);
63
            }
64
65
            return $default;
66
        }
67
68 16
        return $this->parameters[$key];
69
    }
70
71
    /**
72
     * @param $key
73
     * @param $value
74
     *
75
     * @throws ParameterNotFoundException
76
     * @throws ServiceNotFoundException
77
     */
78 16
    public function setParameter($key, $value)
79
    {
80 16
        $this->parameters[$key] = $this->resolveArg($value);
81 16
    }
82
83
    /**
84
     * @param string $key
85
     *
86
     * @return bool
87
     */
88 16
    public function hasParameter($key)
89
    {
90 16
        return isset($this->parameters[$key]) || array_key_exists($key, $this->parameters);
91
    }
92
93
    /**
94
     * @param string $key
95
     *
96
     * @return bool
97
     */
98 16
    public function has($key)
99
    {
100 16
        return isset($this->services[$key]) || array_key_exists($key, $this->services);
101
    }
102
103
    /**
104
     * @param string $key
105
     *
106
     * @return mixed
107
     * @throws ServiceNotFoundException
108
     */
109 16
    public function get($key)
110
    {
111 16
        if (!$this->has($key)) {
112 1
            throw new ServiceNotFoundException($key);
113
        }
114
115 16
        $service = $this->services[$key];
116
117 16
        if ($service instanceof Definition) {
118 16
            $service = $this->create($service);
119
        }
120
121 16
        return $this->services[$key] = $service;
122
    }
123
124
    /**
125
     * @param string $key
126
     * @param array  $definitionArgs
127
     *
128
     * @return Definition
129
     */
130 16
    protected function add($key, array $definitionArgs)
131
    {
132 16
        return $this->services[$key] = Definition::fromYaml($definitionArgs);
133
    }
134
135
    /**
136
     * Creates a service as defined by the Definition object.
137
     *
138
     * @param Definition $definition
139
     *
140
     * @return mixed
141
     */
142 16
    protected function create(Definition $definition)
143
    {
144 16
        $class = $definition->getClass();
145 16
        $args  = $this->resolveArgs($definition->getArguments());
146
147 16
        $service = new $class(...$args);
148
149 16
        foreach ($definition->getMethodCalls() as $methodCall) {
150 16
            $methodName = $methodCall->getName();
151 16
            $methodArgs = $this->resolveArgs($methodCall->getArguments());
152 16
            $service->$methodName(...$methodArgs);
153
        }
154
155 16
        return $service;
156
    }
157
158
    /**
159
     * @param array $args
160
     *
161
     * @return array
162
     */
163 16
    protected function resolveArgs(array $args)
164
    {
165 16
        foreach ($args as $key => &$arg) {
166 16
            if (is_array($arg)) {
167
                $arg = $this->resolveArgs($arg);
168
            } else {
169 16
                $arg = $this->resolveArg($arg);
170
            }
171
        }
172
173 16
        return $args;
174
    }
175
176
    /**
177
     * @param $arg
178
     *
179
     * @return mixed|string
180
     * @throws ParameterNotFoundException
181
     * @throws ServiceNotFoundException
182
     */
183 16
    protected function resolveArg($arg)
184
    {
185 16
        if (is_string($arg)) {
186 16
            if (preg_match(static::PATTERN_SERVICE, $arg, $matches)) {
187 16
                $arg = $this->get($matches[1]);
188
            } else {
189
                // todo: Optimise PATTERN_PARAMETER regex so PATTERN_ENV_PARAMETER preg_match isn't necessary
190 16
                $arg = preg_replace_callback(static::PATTERN_PARAMETER, function ($matches) {
191
                    // Skip %% to allow escaping percent signs
192 16
                    if (!isset($matches[1])) {
193 16
                        return '%';
194 16
                    } elseif (preg_match(static::PATTERN_ENV_PARAMETER, $matches[1], $envMatches)) {
195 16
                        return $this->getEnv($envMatches[1]);
196
                    } else {
197 16
                        return $this->getParameter($matches[1]);
198
                    }
199 16
                }, $arg);
200
            }
201
        }
202
203 16
        return $arg;
204
    }
205
206
    /**
207
     * @param string $key
208
     *
209
     * @return string|bool
210
     */
211 16
    protected function getEnv($key)
212
    {
213 16
        if (isset($this->envCache[$key]) || array_key_exists($key, $this->envCache)) {
214
            return $this->envCache[$key];
215
        }
216
217 16
        $envVar = getenv($key);
218
219 16
        if (!$envVar) {
220
            try {
221 15
                $envVar = $this->getParameter(sprintf('env(%s)', $key));
222
            } catch (ParameterNotFoundException $exception) {
223
                // do nothing
224
            }
225
        }
226
227 16
        $this->envCache[$key] = $this->resolveArg($envVar);
228
229 16
        return $this->envCache[$key];
230
    }
231
}
232