Completed
Pull Request — master (#46)
by
unknown
05:21
created

Kernel::loadConfiguration()   C

Complexity

Conditions 7
Paths 14

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 14
nc 14
nop 0
1
<?php
2
3
namespace Coduo\TuTu;
4
5
use Coduo\PHPMatcher\Factory\SimpleFactory;
6
use Coduo\TuTu\Config\Loader\YamlLoader;
7
use Coduo\TuTu\Config\Resolver;
8
use Coduo\TuTu\Event\PreConfigResolve;
9
use Coduo\TuTu\Event\RequestMatch;
10
use Coduo\TuTu\Extension\Initializer;
11
use Coduo\TuTu\Request\BodyMatchingPolicy;
12
use Coduo\TuTu\Request\ChainMatchingPolicy;
13
use Coduo\TuTu\Request\HeadersMatchingPolicy;
14
use Coduo\TuTu\Request\MethodMatchingPolicy;
15
use Coduo\TuTu\Request\ParameterMatchingPolicy;
16
use Coduo\TuTu\Request\Path\Parser;
17
use Coduo\TuTu\Request\RouteMatchingPolicy;
18
use Coduo\TuTu\Response\Builder;
19
use Symfony\Component\ClassLoader\ClassLoader;
20
use Symfony\Component\EventDispatcher\EventDispatcher;
21
use Symfony\Component\HttpFoundation\Request;
22
use Symfony\Component\HttpFoundation\Response;
23
use Symfony\Component\HttpKernel\HttpKernelInterface;
24
use Symfony\Component\Yaml\Yaml;
25
26
class Kernel implements HttpKernelInterface
27
{
28
    /**
29
     * @var ServiceContainer
30
     */
31
    private $container;
32
33
    /**
34
     * @param ServiceContainer $container
35
     */
36
    public function __construct(ServiceContainer $container)
37
    {
38
        $this->container = $container;
39
        $this->setUpContainer();
40
    }
41
42
    /**
43
     * @param Request $request
44
     * @param int $type
45
     * @param bool $catch
46
     * @return Response
47
     */
48
    public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)
49
    {
50
        try {
51
            $this->loadConfiguration();
52
            $request = $this->dispatchPreConfigResolveEvent($request);
53
            $configElement = $this->container->getService('response.config.resolver')->resolveConfigElement($request);
54
            if (isset($configElement)) {
55
                $this->dispatchRequestMatchEvent($configElement);
56
57
                return $this->container->getService('response.builder')->build($configElement, $request);
58
            }
59
            return $this->container->getService('response.builder')->buildForMismatch($request);
60
        } catch (\Exception $e) {
61
            return $this->container->getService('response.builder')->buildForException($e);
62
        }
63
    }
64
65
    private function setUpContainer()
66
    {
67
        $this->registerClassLoader();
68
        $this->registerPHPMatcher();
69
        $this->registerTwig();
70
        $this->registerEventDispatcher();
71
        $this->registerExtensionInitializer();
72
        $this->registerConfigLoader();
73
        $this->registerRequestMatchingPolicy();
74
        $this->registerConfigResolver();
75
        $this->registerResponseBuilder();
76
    }
77
78
    private function registerClassLoader()
79
    {
80
        $this->container->setStaticDefinition('class_loader', function ($container) {
0 ignored issues
show
Unused Code introduced by
The parameter $container is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
81
            return new ClassLoader();
82
        });
83
    }
84
85
    private function registerPHPMatcher()
86
    {
87
        $this->container->setStaticDefinition('php_matcher', function ($container) {
0 ignored issues
show
Unused Code introduced by
The parameter $container is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
88
            return (new SimpleFactory())->createMatcher();
89
        });
90
    }
91
92
    private function registerTwig()
93
    {
94
        $resourcesPath = $this->container->getParameter('tutu.root_path') . '/resources';
95
96 View Code Duplication
        if ($customPath = getenv('tutu_resources')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
97
            if (!file_exists($customPath)) {
98
                throw new \RuntimeException(sprintf('Custom resources path \"%s\" does not exist.', $customPath));
99
            }
100
            $resourcesPath = $customPath;
101
        }
102
103
        $this->container->setParameter(
104
            'resources_path',
105
            $resourcesPath
106
        );
107
108
        $this->container->setStaticDefinition('twig_loader', function ($container) {
109
            $stringLoader = new \Twig_Loader_String();
110
            $filesystemLoader = new \Twig_Loader_Filesystem();
111
            $filesystemLoader->addPath($container->getParameter('resources_path'), 'resources');
112
            return new \Twig_Loader_Chain([$filesystemLoader, $stringLoader]);
113
        });
114
115
        $this->container->setStaticDefinition('twig' ,function ($container) {
116
            $defaultOptions = [
117
                'cache' => $container->getParameter('tutu.root_path') . '/var/twig',
118
                'globals' => []
119
            ];
120
121
            $options = $container->hasParameter('twig') && is_array($container->getParameter('twig'))
122
                ? array_merge($defaultOptions, $container->getParameter('twig'))
123
                : $defaultOptions;
124
125
            $twig = new \Twig_Environment($container->getService('twig_loader'), $options);
126
127
            if (is_array($options['globals'])) {
128
                foreach ($options['globals'] as $name => $value) {
129
                    $twig->addGlobal(
130
                        $name,
131
                        $this->getValue($value)
132
                    );
133
                }
134
            }
135
136
            return $twig;
137
        });
138
    }
139
140
    private function registerEventDispatcher()
141
    {
142
        $this->container->setStaticDefinition('event_dispatcher' , function ($container) {
143
            $eventDispatcher = new EventDispatcher();
144
            $eventSubscribers = $container->getServicesByTag('event_dispatcher.subscriber');
145
            foreach ($eventSubscribers as $subscriber) {
146
                $eventDispatcher->addSubscriber($subscriber);
147
            }
148
149
            return $eventDispatcher;
150
        });
151
    }
152
153
    private function registerExtensionInitializer()
154
    {
155
        $this->container->setDefinition('extension.initializer', function ($container) {
0 ignored issues
show
Unused Code introduced by
The parameter $container is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
156
            return new Initializer($this->container->getService('class_loader'));
157
        });
158
    }
159
160
    private function registerConfigLoader()
161
    {
162
        $responsesPath = $this->container->getParameter('tutu.root_path') . '/config/responses.yml';
163
164
        if ($customPath = getenv('tutu_responses')) {
165
            if (!file_exists($customPath)) {
166
                throw new \RuntimeException('Custom responses file not found at ' . $customPath);
167
            }
168
            $responsesPath = $customPath;
169
        }
170
171
        $this->container->setParameter(
172
            'responses_file_path',
173
            $responsesPath
174
        );
175
176
        $this->container->setDefinition('response.config.loader.yaml', function ($container) {
177
            return new YamlLoader($container->getParameter('responses_file_path'));
178
        });
179
    }
180
181
    private function registerRequestMatchingPolicy()
182
    {
183
        $this->container->setDefinition('request.matching_policy.method', function($container) {
0 ignored issues
show
Unused Code introduced by
The parameter $container is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
184
            return new MethodMatchingPolicy();
185
        }, ['matching_policy']);
186
        $this->container->setDefinition('request.matching_policy.route', function($container) {
0 ignored issues
show
Unused Code introduced by
The parameter $container is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
187
            return new RouteMatchingPolicy();
188
        }, ['matching_policy']);
189
        $this->container->setDefinition('request.matching_policy.parameter', function($container) {
190
            return new ParameterMatchingPolicy($container->getService('php_matcher'));
191
        }, ['matching_policy']);
192
        $this->container->setDefinition('request.matching_policy.headers', function($container) {
193
            return new HeadersMatchingPolicy($container->getService('php_matcher'));
194
        }, ['matching_policy']);
195
        $this->container->setDefinition('request.matching_policy.body', function($container) {
196
            return new BodyMatchingPolicy($container->getService('php_matcher'));
197
        }, ['matching_policy']);
198
199
200
        $this->container->setDefinition('request.matching_policy', function ($container) {
201
            $matchingPolicy = new ChainMatchingPolicy();
202
            $matchingPolicies = $container->getServicesByTag('matching_policy');
203
            foreach ($matchingPolicies as $policy) {
204
                $matchingPolicy->addMatchingPolicy($policy);
205
            }
206
207
            return $matchingPolicy;
208
        });
209
    }
210
211
    private function registerConfigResolver()
212
    {
213
        $this->container->setStaticDefinition('response.config.resolver', function ($container) {
214
            return new Resolver(
215
                $container->getService('response.config.loader.yaml'),
216
                $container->getService('request.matching_policy')
217
            );
218
        });
219
    }
220
221
    private function registerResponseBuilder()
222
    {
223
        $this->container->setDefinition('request.path.parser', function($container) {
0 ignored issues
show
Unused Code introduced by
The parameter $container is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
224
            return new Parser();
225
        });
226
        $this->container->setDefinition('response.builder', function ($container) {
227
            return new Builder($container->getService('twig'), $container->getService('request.path.parser'));
228
        });
229
    }
230
231
    private function loadConfiguration()
232
    {
233
        $config = $this->parseConfiguration();
234
        if (array_key_exists('autoload', $config)) {
235
            $this->container->getService('class_loader')->addPrefixes($config['autoload']);
236
        }
237
238
        if (array_key_exists('parameters', $config)) {
239
            if (!is_array($config['parameters'])) {
240
                throw new \RuntimeException("Parameters key in config.yml must be a valid array.");
241
            }
242
243
            foreach ($config['parameters'] as $id => $value) {
244
                $this->container->setParameter($id, $value);
245
            }
246
        }
247
248
        if (array_key_exists('extensions', $config)) {
249
            foreach ($config['extensions'] as $extensionClass => $constructorArguments) {
250
                $arguments = $this->getExtensionArgumentsValues($constructorArguments);
251
                $extension = $this->container->getService('extension.initializer')->initialize($extensionClass, $arguments);
252
253
                $extension->load($this->container);
254
            }
255
        }
256
    }
257
258
    private function parseConfiguration()
259
    {
260
        $configFiles = [
261
            sprintf('%s/config/%s', $this->container->getParameter('tutu.root_path'), 'config.yml'),
262
            sprintf('%s/config/%s', $this->container->getParameter('tutu.root_path'), 'config.yml.dist')
263
        ];
264
265 View Code Duplication
        if ($customPath = getenv('tutu_config')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
266
            if (!file_exists($customPath)) {
267
                throw new \RuntimeException('Custom config file not found at '.$customPath);
268
            }
269
270
            $configFiles = (array) $customPath;
271
        }
272
273
        foreach ($configFiles as $filePath) {
274
            if ($filePath && file_exists($filePath) && $config = Yaml::parse(file_get_contents($filePath))) {
275
                return $config;
276
            }
277
        }
278
279
        return [];
280
    }
281
282
    /**
283
     * @param $configElement
284
     */
285
    private function dispatchRequestMatchEvent($configElement)
286
    {
287
        $this->container->getService('event_dispatcher')->dispatch(
288
            Events::REQUEST_MATCH,
289
            new RequestMatch($configElement)
290
        );
291
    }
292
293
    /**
294
     * @param Request $request
295
     * @return Request
296
     */
297
    private function dispatchPreConfigResolveEvent(Request $request)
298
    {
299
        $event = new PreConfigResolve($request);
300
        $this->container->getService('event_dispatcher')->dispatch(
301
            Events::PRE_CONFIG_RESOLVE,
302
            $event
303
        );
304
305
        return $event->getRequest();
306
    }
307
308
    /**
309
     * @param $arguments
310
     * @return array|mixed|void
311
     */
312
    private function getExtensionArgumentsValues($arguments)
313
    {
314
        if (is_null($arguments)) {
315
            return ;
316
        }
317
318
        if (is_array($arguments)) {
319
            $argumentsWithValues = [];
320
            foreach ($arguments as $name => $value) {
321
                $argumentsWithValues[$name] = $this->getValue($value);
322
            }
323
324
            return $argumentsWithValues;
325
        }
326
327
        return $this->getValue($arguments);
328
    }
329
330
    /**
331
     * If value is valid string between "%" characters then it might be a parameter
332
     * so we need to try take it from container.
333
     *
334
     * @param $value
335
     * @return mixed
336
     */
337
    private function getValue($value)
338
    {
339
        if (!is_string($value)) {
340
            return $value;
341
        }
342
343
        if ($value[0] != '%' || $value[strlen($value) - 1] != '%')  {
344
            return $value;
345
        }
346
347
        $parameter = trim($value, '%');
348
        if ($this->container->hasParameter($parameter)) {
349
            return $this->container->getParameter($parameter);
350
        }
351
352
        return $value;
353
    }
354
}
355