Completed
Push — master ( 5d54c2...47c66e )
by Mr
03:19
created

App::run()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 50
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 24
nc 4
nop 0
dl 0
loc 50
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
namespace DrMVC\Framework;
4
5
use DrMVC\Router\MethodsInterface;
6
use Zend\Diactoros\ServerRequestFactory;
7
use Zend\Diactoros\ServerRequest;
8
use Zend\Diactoros\Response;
9
10
use DrMVC\Config\ConfigInterface;
11
use DrMVC\Router\Router;
12
use DrMVC\Controllers\Error;
13
14
/**
15
 * Class App
16
 * @package DrMVC\Framework
17
 * @method App options(string $pattern, callable $callable): App
18
 * @method App get(string $pattern, callable $callable): App
19
 * @method App head(string $pattern, callable $callable): App
20
 * @method App post(string $pattern, callable $callable): App
21
 * @method App put(string $pattern, callable $callable): App
22
 * @method App delete(string $pattern, callable $callable): App
23
 * @method App trace(string $pattern, callable $callable): App
24
 * @method App connect(string $pattern, callable $callable): App
25
 * @since 3.0
26
 */
27
class App implements AppInterface
28
{
29
    const DEFAULT_ACTION = 'index';
30
31
    /**
32
     * @var ContainersInterface
33
     */
34
    private $_containers;
35
36
    /**
37
     * App constructor.
38
     * @param ConfigInterface $config
39
     */
40
    public function __construct(ConfigInterface $config)
41
    {
42
        // Initiate PSR-11 containers
43
        $this->initContainers();
44
45
        // Save configuration
46
        $this->initConfig($config);
47
48
        // Initiate router
49
        $this
50
            ->initRequest()
51
            ->initResponse()
52
            ->initRouter();
53
    }
54
55
    /**
56
     * Initialize containers object
57
     *
58
     * @return  App
59
     */
60
    private function initContainers(): App
61
    {
62
        if (null === $this->_containers) {
63
            $this->_containers = new Containers();
64
        }
65
        return $this;
66
    }
67
68
    /**
69
     * Put config object into the containers class
70
     *
71
     * @param   ConfigInterface $config
72
     * @return  App
73
     */
74
    private function initConfig(ConfigInterface $config): App
75
    {
76
        $this->containers()->set('config', $config);
0 ignored issues
show
Bug introduced by
$config of type DrMVC\Config\ConfigInterface is incompatible with the type string expected by parameter $object of DrMVC\Framework\ContainersInterface::set(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

76
        $this->containers()->set('config', /** @scrutinizer ignore-type */ $config);
Loading history...
77
        return $this;
78
    }
79
80
    /**
81
     * Initiate PSR-7 request object
82
     *
83
     * @return  App
84
     */
85
    private function initRequest(): App
86
    {
87
        try {
88
            $request = ServerRequestFactory::fromGlobals();
89
            $this->containers()->set('request', $request);
0 ignored issues
show
Bug introduced by
$request of type Zend\Diactoros\ServerRequest is incompatible with the type string expected by parameter $object of DrMVC\Framework\ContainersInterface::set(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

89
            $this->containers()->set('request', /** @scrutinizer ignore-type */ $request);
Loading history...
90
        } catch (\InvalidArgumentException $e) {
91
            new Exception($e);
92
        }
93
        return $this;
94
    }
95
96
    /**
97
     * Initiate PSR-7 response object
98
     *
99
     * @return  App
100
     */
101
    private function initResponse(): App
102
    {
103
        try {
104
            $response = new Response();
105
            $this->containers()->set('response', $response);
0 ignored issues
show
Bug introduced by
$response of type Zend\Diactoros\Response is incompatible with the type string expected by parameter $object of DrMVC\Framework\ContainersInterface::set(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

105
            $this->containers()->set('response', /** @scrutinizer ignore-type */ $response);
Loading history...
106
        } catch (\InvalidArgumentException $e) {
107
            new Exception($e);
108
        }
109
        return $this;
110
    }
111
112
    /**
113
     * Put route into the container of classes
114
     *
115
     * @return  App
116
     */
117
    private function initRouter(): App
118
    {
119
        $request = $this->container('request');
120
        $response = $this->container('response');
121
        $router = new Router($request, $response);
122
        //$router->setError(Error::class);
123
124
        $this->containers()->set('router', $router);
0 ignored issues
show
Bug introduced by
$router of type DrMVC\Router\Router is incompatible with the type string expected by parameter $object of DrMVC\Framework\ContainersInterface::set(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

124
        $this->containers()->set('router', /** @scrutinizer ignore-type */ $router);
Loading history...
125
        return $this;
126
    }
127
128
    /**
129
     * Get custom container by name
130
     *
131
     * @param   string $name
132
     * @return  mixed
133
     */
134
    public function container(string $name)
135
    {
136
        return $this->_containers->get($name);
137
    }
138
139
    /**
140
     * Get all available containers
141
     *
142
     * @return  ContainersInterface
143
     */
144
    public function containers(): ContainersInterface
145
    {
146
        return $this->_containers;
147
    }
148
149
    /**
150
     * Magic method for work with calls
151
     *
152
     * @param   string $method
153
     * @param   array $args
154
     * @return  MethodsInterface
155
     */
156
    public function __call(string $method, array $args): MethodsInterface
157
    {
158
        if (\in_array($method, Router::METHODS, false)) {
159
            $this->container('router')->set([$method], $args);
160
        }
161
        return $this;
162
    }
163
164
    /**
165
     * If any route is provided
166
     *
167
     * @param   string $pattern
168
     * @param   callable|string $callable
169
     * @return  MethodsInterface
170
     */
171
    public function any(string $pattern, $callable): MethodsInterface
172
    {
173
        $this->container('router')->any($pattern, $callable);
174
        return $this;
175
    }
176
177
    /**
178
     * Set the error callback of class
179
     *
180
     * @param   callable|string $callable
181
     * @return  MethodsInterface
182
     */
183
    public function error($callable): MethodsInterface
184
    {
185
        $this->container('router')->error($callable);
186
        return $this;
187
    }
188
189
    /**
190
     * Few methods provided
191
     *
192
     * @param   array $methods
193
     * @param   string $pattern
194
     * @param   callable|string $callable
195
     * @return  MethodsInterface
196
     */
197
    public function map(array $methods, string $pattern, $callable): MethodsInterface
198
    {
199
        $this->container('router')->map($methods, $pattern, $callable);
200
        return $this;
201
    }
202
203
    /**
204
     * Detect action by string name, variable or use default
205
     *
206
     * @param   string $className - eg. MyApp\Index:test
207
     * @param   array $variables
208
     * @return  string
209
     */
210
    private function detectAction(string $className, array $variables = []): string
211
    {
212
        // If class contain method name
213
        if (strpos($className, ':') !== false) {
214
            $classArray = explode(':', $className);
215
            $classAction = end($classArray);
216
        } else {
217
            $classAction = null;
218
        }
219
220
        /*
221
         * Detect what action should be initiated
222
         *
223
         * Priorities:
224
         *  1. If action name in variables
225
         *  2. Action name in line with class name eg. MyApp\Index:test - test here is `action_test` alias
226
         *  3. Default action is index - action_index in result
227
         */
228
        $action = self::DEFAULT_ACTION;
229
        if (null !== $classAction) {
230
            $action = $classAction;
231
        }
232
        if (isset($variables['action'][0])) {
233
            $action = $variables['action'][0];
234
        }
235
236
        return 'action_' . $action;
237
    }
238
239
    /**
240
     * Simple runner should parse query and make work on user's class
241
     *
242
     * @return  mixed
243
     */
244
    public function run()
245
    {
246
        // Extract some important objects
247
        $router = $this->container('router');
248
        $request = $this->container('request');
249
        $response = $this->container('response');
250
251
        // Get current matched route with and extract variables with callback
252
        $route = $router->getRoute();
253
        $variables = $route->getVariables();
254
        $callback = $route->getCallback();
255
256
        // If extracted call back is string
257
        if (\is_string($callback)) {
258
259
            // Then class provided
260
            $class = new $callback();
261
            $action = $this->detectAction($callback, $variables);
262
263
            // TODO: crapcode, rewrite to exceptions
264
            // If method not found in required class
265
            if (!\method_exists($class, $action)) {
266
                // Replace route object to error route
267
                $route = $router->getError();
268
                $variables = $route->getVariables();
269
                $callback = $route->getCallback();
270
271
                // If extracted call back is string
272
                if (\is_string($callback)) {
273
                    // Then class provided
274
                    $class = new $callback();
275
                    $action = $this->detectAction($callback);
276
277
                    // Call required action, with request/response
278
                    $class->$action($request, $response, $variables);
279
                } else {
280
                    // Else simple callback
281
                    $callback($request, $response, $variables);
282
                }
283
                return $response->getBody();
284
            }
285
286
            // Call required action, with request/response
287
            $class->$action($request, $response, $variables);
288
        } else {
289
            // Else simple callback
290
            $callback($request, $response, $variables);
291
        }
292
293
        return $response->getBody();
294
    }
295
296
}
297