Completed
Push — master ( ef610a...dc0762 )
by Gabor
24:45
created

WebApplication::prepare()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 22
ccs 0
cts 4
cp 0
rs 9.2
cc 3
eloc 15
nc 3
nop 0
crap 12
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 Psr\Http\Message\ResponseInterface;
16
use Psr\Http\Message\ServerRequestInterface;
17
use WebHemi\Adapter\DependencyInjection\DependencyInjectionAdapterInterface;
18
use WebHemi\Adapter\Http\HttpAdapterInterface;
19
use WebHemi\Application\ApplicationInterface;
20
use WebHemi\Config\ConfigInterface;
21
use WebHemi\Middleware\DispatcherMiddleware;
22
use WebHemi\Middleware\MiddlewareInvokerInterface;
23
use WebHemi\Middleware\FinalMiddleware;
24
use WebHemi\Middleware\MiddlewareInterface;
25
use WebHemi\Middleware\Pipeline\MiddlewarePipelineInterface;
26
27
/**
28
 * Class WebApplication.
29
 */
30
class WebApplication implements ApplicationInterface
31
{
32
    const MODULE_ADMIN = 'Admin';
33
    const MODULE_SITE = 'Website';
34
35
    /** @var DependencyInjectionAdapterInterface */
36
    private $container;
37
    /** @var ConfigInterface */
38
    private $config;
39
    /** @var MiddlewarePipelineInterface */
40
    private $pipeline;
41
    /** @var array  */
42
    private $environmentData = [
43
        'GET'    => [],
44
        'POST'   => [],
45
        'SERVER' => [],
46
        'COOKIE' => [],
47
        'FILES'  => [],
48
    ];
49
    /** @var string */
50
    private $selectedModule = self::MODULE_SITE;
51
52
    /**
53
     * ApplicationInterface constructor.
54
     *
55
     * @param DependencyInjectionAdapterInterface $container
56
     * @param ConfigInterface                     $config
57
     * @param MiddlewarePipelineInterface         $pipeline
58
     */
59
    public function __construct(
60
        DependencyInjectionAdapterInterface $container,
61
        ConfigInterface $config,
62
        MiddlewarePipelineInterface $pipeline
63
    ) {
64
        $this->container = $container;
65
        $this->config = $config;
66
        $this->pipeline = $pipeline;
67
    }
68
69
    /**
70
     * Returns the DI Adapter instance.
71
     *
72
     * @return DependencyInjectionAdapterInterface
73
     */
74
    public function getContainer()
75
    {
76
        return $this->container;
77
    }
78
79
    /**
80
     * Returns the Configuration.
81
     *
82
     * @return ConfigInterface
83
     */
84
    public function getConfig()
85
    {
86
        return $this->config;
87
    }
88
89
    /**
90
     * Sets application environments according to the super globals.
91
     *
92
     * @param string $key
93
     * @param array  $data
94
     *
95
     * @throws InvalidArgumentException
96
     *
97
     * @return $this
98
     */
99
    public function setEnvironmentData($key, array $data)
100
    {
101
        if (!isset($this->environmentData[$key])) {
102
            throw new InvalidArgumentException(sprintf('The key "%s" is not a valid super global key.', $key));
103
        }
104
105
        $this->environmentData[$key] = $data;
106
107
        return $this;
108
    }
109
110
    /**
111
     * Get ready to run the application: set final data for specific services.
112
     */
113
    private function prepare()
114
    {
115
        $this->container
116
            ->setServiceArgument(HttpAdapterInterface::class, $this->environmentData['SERVER'])
117
            ->setServiceArgument(HttpAdapterInterface::class, $this->environmentData['GET'])
118
            ->setServiceArgument(HttpAdapterInterface::class, $this->environmentData['POST'])
119
            ->setServiceArgument(HttpAdapterInterface::class, $this->environmentData['COOKIE'])
120
            ->setServiceArgument(HttpAdapterInterface::class, $this->environmentData['FILES']);
121
122
        $moduleConfig = $this->config->get('modules/' . $this->selectedModule, ConfigInterface::CONFIG_AS_OBJECT);
123
        $this->container
124
            ->setServiceArgument(FinalMiddleware::class, $moduleConfig);
125
126
        $pipelineConfig = $this->config->get('middleware_pipeline');
127
128
        foreach ($pipelineConfig as $middlewareData) {
0 ignored issues
show
Bug introduced by
The expression $pipelineConfig of type array|object<WebHemi\Config\Config> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
129
            if (!isset($middlewareData['priority'])) {
130
                $middlewareData['priority'] = 50;
131
            }
132
            $this->pipeline->queueMiddleware($middlewareData['class'], $middlewareData['priority']);
133
        }
134
    }
135
136
    /**
137
     * Runs the application. This is where the magic happens.
138
     * According tho the environment settings this must build up the middleware pipeline and execute it.
139
     *
140
     * a Pre-Routing Middleware can be; priority < 0:
141
     *  - LockCheck - check if the client IP is banned > S102|S403
142
     *  - Auth - if the user is not logged in, but there's a "Remember me" cookie, then logs in > S102
143
     *
144
     * Routing Middleware is fixed (RoutingMiddleware::class); priority = 0:
145
     *  - A middleware that routes the incoming Request and delegates to the matched middleware. > S102|S404|S405
146
     *    The RouteResult should be attached to the Request.
147
     *    If the Routing is not defined explicitly in the pipeline, then it will be injected with priority 0.
148
     *
149
     * a Post-Routing Middleware can be; priority between 0 and 100:
150
     *  - Acl - checks if the given route is available for the client. Also checks the auth > S102|S401|S403
151
     *  - CacheReader - checks if a suitable response body is cached. > S102|S200
152
     *
153
     * Dispatcher Middleware is fixed (DispatcherMiddleware::class); priority = 100:
154
     *  - A middleware which gets the corresponding Action middleware and applies it > S102
155
     *    If the Dispatcher is not defined explicitly in the pipeline, then it will be injected with priority 100.
156
     *    The Dispatcher should not set the response Status Code to 200 to let Post-Dispatchers to be called.
157
     *
158
     * a Post-Dispatch Middleware can be; priority > 100:
159
     *  - CacheWriter - writes response body into DataStorage (DB, File etc.) > S102
160
     *
161
     * Final Middleware is fixed (FinalMiddleware:class):
162
     *  - This middleware behaves a bit differently. It cannot be ordered, it's always the last called middleware:
163
     *    - when the middleware pipeline reached its end (typically when the Status Code is still 102)
164
     *    - when one item of the middleware pipeline returns with return response (status code is set to 200|40*|500)
165
     *    - when during the pipeline process an Exception is thrown.
166
     *
167
     * When the middleware pipeline is finished the application prints the header and the output.
168
     *
169
     * If a middleware other than the Routing, Dispatcher and Final Middleware has no priority set, it will be
170
     * considered to have priority = 50.
171
     *
172
     * @return void
173
     */
174
    public function run()
175
    {
176
        $this->prepare();
177
178
        /** @var HttpAdapterInterface $httpAdapter */
179
        $httpAdapter = $this->getContainer()->get(HttpAdapterInterface::class);
180
        /** @var ServerRequestInterface $request */
181
        $request = $httpAdapter->getRequest();
182
        /** @var ResponseInterface $response */
183
        $response = $httpAdapter->getResponse();
184
185
        $middlewareClass = $this->pipeline->start();
186
187
        while ($middlewareClass !== null) {
188
            try {
189
                /** @var MiddlewareInterface $middleware */
190
                $middleware = $this->container->get($middlewareClass);
191
192
                $requestAttributes = $request->getAttributes();
193
                // As an extra step if the action middleware is resolved, it is invoked right before the dispatcher.
194
                if ($middleware instanceof DispatcherMiddleware
195
                    && isset($requestAttributes['resolvedActionMiddleware'])
196
                ) {
197
                    /** @var MiddlewareInterface $actionMiddleware */
198
                    $actionMiddleware = $this->container->get($requestAttributes['resolvedActionMiddleware']);
199
                    $response = $actionMiddleware($request, $response);
200
                }
201
202
                $response = $middleware($request, $response);
203
            } catch (\Exception $exception) {
204
                $response = $response->withStatus(500);
205
                $request = $request->withAttribute('exception', $exception);
206
            }
207
208
            if ($response->getStatusCode() != 102) {
209
                break;
210
            }
211
212
            $middlewareClass = $this->pipeline->next();
213
        };
214
215
        // If there was no error, we mark as ready for output.
216
        if ($response->getStatusCode() == 102) {
217
            $response = $response->withStatus(200);
218
        }
219
220
        /** @var FinalMiddleware $finalMiddleware */
221
        $finalMiddleware = $this->container->get(FinalMiddleware::class);
222
223
        // Send out headers and content.
224
        $finalMiddleware($request, $response);
225
    }
226
}
227