Completed
Push — master ( a09c74...b4437a )
by Gabor
03:33
created

WebApplication::getContainer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
/**
3
 * WebHemi.
4
 *
5
 * PHP version 5.6
6
 *
7
 * @copyright 2012 - 2016 Gixx-web (http://www.gixx-web.com)
8
 * @license   https://opensource.org/licenses/MIT The MIT License (MIT)
9
 *
10
 * @link      http://www.gixx-web.com
11
 */
12
namespace WebHemi\Application\Web;
13
14
use InvalidArgumentException;
15
use WebHemi\Adapter\DependencyInjection\DependencyInjectionAdapterInterface;
16
use WebHemi\Adapter\Http\ResponseInterface;
17
use WebHemi\Adapter\Http\ServerRequestInterface;
18
use WebHemi\Adapter\Http\HttpAdapterInterface;
19
use WebHemi\Adapter\Renderer\RendererAdapterInterface;
20
use WebHemi\Adapter\Router\RouterAdapterInterface;
21
use WebHemi\Application\ApplicationInterface;
22
use WebHemi\Application\EnvironmentManager;
23
use WebHemi\Middleware\DispatcherMiddleware;
24
use WebHemi\Middleware\FinalMiddleware;
25
use WebHemi\Middleware\MiddlewareInterface;
26
use WebHemi\Middleware\Pipeline\MiddlewarePipelineInterface;
27
28
/**
29
 * Class WebApplication.
30
 */
31
class WebApplication implements ApplicationInterface
32
{
33
    /** @var DependencyInjectionAdapterInterface */
34
    private $container;
35
    /** @var EnvironmentManager */
36
    private $environmentManager;
37
    /** @var MiddlewarePipelineInterface */
38
    private $pipeline;
39
40
41
    /**
42
     * ApplicationInterface constructor.
43
     *
44
     * @param DependencyInjectionAdapterInterface $container
45
     * @param EnvironmentManager                  $environmentManager
46
     * @param MiddlewarePipelineInterface         $pipeline
47
     */
48 3
    public function __construct(
49
        DependencyInjectionAdapterInterface $container,
50
        EnvironmentManager $environmentManager,
51
        MiddlewarePipelineInterface $pipeline
52
    ) {
53 3
        $this->container = $container;
54 3
        $this->environmentManager = $environmentManager;
55 3
        $this->pipeline = $pipeline;
56 3
    }
57
58
    /**
59
     * Returns the DI Adapter instance.
60
     *
61
     * @return DependencyInjectionAdapterInterface
62
     */
63 3
    public function getContainer()
64
    {
65 3
        return $this->container;
66
    }
67
68
    /**
69
     * Get ready to run the application: set final data for specific services.
70
     *
71
     * @codeCoverageIgnore - Check the EnvironmentManager and Container adapter tests.
72
     */
73
    private function prepare()
74
    {
75
        $this->container
76
            ->setServiceArgument(HttpAdapterInterface::class, $this->environmentManager->getEnvironmentData('GET'))
77
            ->setServiceArgument(HttpAdapterInterface::class, $this->environmentManager->getEnvironmentData('POST'))
78
            ->setServiceArgument(HttpAdapterInterface::class, $this->environmentManager->getEnvironmentData('SERVER'))
79
            ->setServiceArgument(HttpAdapterInterface::class, $this->environmentManager->getEnvironmentData('COOKIE'))
80
            ->setServiceArgument(HttpAdapterInterface::class, $this->environmentManager->getEnvironmentData('FILES'));
81
82
        try {
83
            $theme = $this->environmentManager
84
                ->getApplicationTemplateSettings($this->environmentManager->getSelectedTheme());
85
            $themeResourcePath = $this->environmentManager->getResourcePath();
86
        } catch (InvalidArgumentException $e) {
87
            $theme = $this->environmentManager->getApplicationTemplateSettings(EnvironmentManager::DEFAULT_THEME);
88
            $themeResourcePath = EnvironmentManager::DEFAULT_THEME_RESOURCE_PATH;
89
        }
90
91
        $this->container
92
            ->setServiceArgument(
93
                RendererAdapterInterface::class,
94
                $theme
95
            )
96
            ->setServiceArgument(
97
                RendererAdapterInterface::class,
98
                $themeResourcePath
99
            )
100
            ->setServiceArgument(
101
                RendererAdapterInterface::class,
102
                $this->environmentManager->getSelectedApplicationUri()
103
            );
104
105
        $this->container
106
            ->setServiceArgument(
107
                RouterAdapterInterface::class,
108
                $this->environmentManager->getModuleRouteSettings()
109
            )
110
            ->setServiceArgument(
111
                RouterAdapterInterface::class,
112
                $this->environmentManager->getSelectedApplicationUri()
113
            );
114
    }
115
116
    /**
117
     * Runs the application. This is where the magic happens.
118
     * According tho the environment settings this must build up the middleware pipeline and execute it.
119
     *
120
     * a Pre-Routing Middleware can be; priority < 0:
121
     *  - LockCheck - check if the client IP is banned > S102|S403
122
     *  - Auth - if the user is not logged in, but there's a "Remember me" cookie, then logs in > S102
123
     *
124
     * Routing Middleware is fixed (RoutingMiddleware::class); priority = 0:
125
     *  - A middleware that routes the incoming Request and delegates to the matched middleware. > S102|S404|S405
126
     *    The RouteResult should be attached to the Request.
127
     *    If the Routing is not defined explicitly in the pipeline, then it will be injected with priority 0.
128
     *
129
     * a Post-Routing Middleware can be; priority between 0 and 100:
130
     *  - Acl - checks if the given route is available for the client. Also checks the auth > S102|S401|S403
131
     *  - CacheReader - checks if a suitable response body is cached. > S102|S200
132
     *
133
     * Dispatcher Middleware is fixed (DispatcherMiddleware::class); priority = 100:
134
     *  - A middleware which gets the corresponding Action middleware and applies it > S102
135
     *    If the Dispatcher is not defined explicitly in the pipeline, then it will be injected with priority 100.
136
     *    The Dispatcher should not set the response Status Code to 200 to let Post-Dispatchers to be called.
137
     *
138
     * a Post-Dispatch Middleware can be; priority > 100:
139
     *  - CacheWriter - writes response body into DataStorage (DB, File etc.) > S102
140
     *
141
     * Final Middleware is fixed (FinalMiddleware:class):
142
     *  - This middleware behaves a bit differently. It cannot be ordered, it's always the last called middleware:
143
     *    - when the middleware pipeline reached its end (typically when the Status Code is still 102)
144
     *    - when one item of the middleware pipeline returns with return response (status code is set to 200|40*|500)
145
     *    - when during the pipeline process an Exception is thrown.
146
     *
147
     * When the middleware pipeline is finished the application prints the header and the output.
148
     *
149
     * If a middleware other than the Routing, Dispatcher and Final Middleware has no priority set, it will be
150
     * considered to have priority = 50.
151
     *
152
     * @return void
153
     */
154 2
    public function run()
155
    {
156 2
        $this->prepare();
157
        /** @var HttpAdapterInterface $httpAdapter */
158 2
        $httpAdapter = $this->getContainer()->get(HttpAdapterInterface::class);
159
        /** @var ServerRequestInterface $request */
160 2
        $request = $httpAdapter->getRequest();
161
        /** @var ResponseInterface $response */
162 2
        $response = $httpAdapter->getResponse();
163 2
        $middlewareClass = $this->pipeline->start();
164
165 2
        while ($middlewareClass !== null
166 2
            && $response->getStatusCode() == ResponseInterface::STATUS_PROCESSING
167
        ) {
168
            try {
169
                /** @var MiddlewareInterface $middleware */
170 2
                $middleware = $this->container->get($middlewareClass);
171 2
                $requestAttributes = $request->getAttributes();
172
                // As an extra step if the action middleware is resolved, it is invoked right before the dispatcher.
173
                // Only the container knows how to instantiate it in the right way, and the container must not be
174
                // injected into anz other classes. It seems like a hack but it is by purpose.
175
176 2
                if ($middleware instanceof DispatcherMiddleware
177 2
                    && isset($requestAttributes[ServerRequestInterface::REQUEST_ATTR_RESOLVED_ACTION_CLASS])
178
                ) {
179
                    /** @var MiddlewareInterface $actionMiddleware */
180 2
                    $actionMiddleware = $this->container
181 2
                        ->get($requestAttributes[ServerRequestInterface::REQUEST_ATTR_RESOLVED_ACTION_CLASS]);
182 2
                    $request = $request->withAttribute(
183 2
                        ServerRequestInterface::REQUEST_ATTR_ACTION_MIDDLEWARE,
184
                        $actionMiddleware
185
                    );
186
                }
187 2
                $response = $middleware($request, $response);
188 1
            } catch (\Exception $exception) {
189 1
                $response = $response->withStatus(ResponseInterface::STATUS_INTERNAL_SERVER_ERROR);
190 1
                $request = $request->withAttribute(
191 1
                    ServerRequestInterface::REQUEST_ATTR_MIDDLEWARE_EXCEPTION,
192
                    $exception
193
                );
194
            }
195
196 2
            $middlewareClass = $this->pipeline->next();
197
        };
198
199
        // If there was no error, we mark as ready for output.
200 2
        if ($response->getStatusCode() == ResponseInterface::STATUS_PROCESSING) {
201 1
            $response = $response->withStatus(ResponseInterface::STATUS_OK);
202
        }
203
204
        /** @var FinalMiddleware $finalMiddleware */
205 2
        $finalMiddleware = $this->container->get(FinalMiddleware::class);
206
207
        // Send out headers and content.
208 2
        $finalMiddleware($request, $response);
209 2
    }
210
}
211