Completed
Push — master ( a88edb...19b57a )
by Oleg
08:57
created

Micro::terminate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 8
c 1
b 0
f 0
rs 9.4285
cc 2
eloc 5
nc 2
nop 0
1
<?php /** Micro */
2
3
namespace Micro;
4
5
use Micro\Base\Dispatcher;
6
use Micro\Base\DispatcherInjector;
7
use Micro\Base\Exception;
8
use Micro\Base\FatalError;
9
use Micro\Base\IInjector;
10
use Micro\Base\Injector;
11
use Micro\Cli\Console;
12
use Micro\Cli\Consoles\DefaultConsoleCommand;
13
use Micro\Mvc\Controllers\IController;
14
use Micro\Resolver\ConsoleResolverInjector;
15
use Micro\Resolver\IResolver;
16
use Micro\Resolver\ResolverInjector;
17
use Micro\Web\ResponseInjector;
18
use Psr\Http\Message\ResponseInterface;
19
use Psr\Http\Message\ServerRequestInterface;
20
21
22
/**
23
 * Micro class file.
24
 *
25
 * Base class for initialize MicroPHP, used as bootstrap framework.
26
 *
27
 * @author Oleg Lunegov <[email protected]>
28
 * @link https://github.com/lugnsk/micro
29
 * @copyright Copyright (c) 2013 Oleg Lunegov
30
 * @license https://github.com/linpax/microphp-framework/blob/master/LICENSE
31
 * @package Micro
32
 * @version 1.0
33
 * @since 1.0
34
 */
35
class Micro
36
{
37
    /** @const string VERSION Version framework */
38
    const VERSION = '1.1';
39
40
    /** @var string $appDir */
41
    protected $appDir;
42
    /** @var string $webDir */
43
    protected $webDir;
44
45
    /** @var bool $loaded Micro loaded flag */
46
    private $loaded;
47
    /** @var bool $debug Debug-mode flag */
48
    private $debug = true;
49
    /** @var string $environment Application environment */
50
    private $environment = 'devel';
51
    /** @var float $startTime Time of start framework */
52
    private $startTime;
53
54
55
    /**
56
     * Initialize application
57
     *
58
     * @access public
59
     *
60
     * @param string $environment Application environment: devel , production , test, other
61
     * @param bool $debug Debug-mode flag
62
     *
63
     * @result void
64
     */
65
    public function __construct($environment = 'devel', $debug = true)
66
    {
67
        $this->webDir = getenv('DOCUMENT_ROOT');
68
        $this->environment = (string)$environment;
69
        $this->debug = (bool)$debug;
70
        $this->loaded = false;
71
72
        ini_set('display_errors', (integer)$this->debug);
73
        ini_set('log_errors', (integer)$this->debug);
74
75
        FatalError::register();
76
77
        if ($this->debug) {
78
            ini_set('error_reporting', -1);
79
            $this->startTime = microtime(true);
80
        }
81
    }
82
83
    /**
84
     * Clone application
85
     *
86
     * @access public
87
     *
88
     * @return void
89
     */
90
    public function __clone()
91
    {
92
        if ($this->debug) {
93
            $this->startTime = microtime(true);
94
        }
95
96
        $this->loaded = false;
97
    }
98
99
    /**
100
     * Running application
101
     *
102
     * @access public
103
     *
104
     * @param ServerRequestInterface $request Request object
105
     *
106
     * @return \Micro\Web\IOutput|ResponseInterface
107
     * @throws \Exception
108
     * @throws \Micro\Base\Exception
109
     */
110
    public function run(ServerRequestInterface $request)
111
    {
112
        try {
113
            return $this->doRun($request);
114
        } catch (\Exception $e) {
115
            if ($this->debug) {
116
                (new DispatcherInjector)->build()->signal('kernel.exception', ['exception' => $e]);
117
118
                throw $e;
119
            }
120
121
            return $this->doException($e);
122
        }
123
    }
124
125
    /**
126
     * Starting ...
127
     *
128
     * @access private
129
     *
130
     * @param ServerRequestInterface $request
131
     *
132
     * @return ResponseInterface|\Micro\Web\IOutput
133
     * @throws \Micro\Base\Exception|\RuntimeException|\InvalidArgumentException
134
     */
135
    private function doRun(ServerRequestInterface $request)
136
    {
137
        $params = $request->getServerParams();
138
        $isAjax = strtolower(
139
                filter_var(!empty($params['HTTP_X_REQUESTED_WITH']) ? $params['HTTP_X_REQUESTED_WITH'] : null)
140
            ) === 'xmlhttprequest';
141
142
        if (!$this->loaded) {
143
            $this->initialize($request);
144
145
            $this->addListener('kernel.kill', function () use ($isAjax) {
146
                if ($this->isDebug() && !$this->isCli() && !$isAjax) {
147
                    echo '<div class="debug_timer">', (microtime(true) - $this->getStartTime()), '</div>';
148
                }
149
150
                if (false === $this->loaded) {
151
                    return;
152
                }
153
154
                $this->loaded = false;
155
            });
156
        }
157
158
        if (($output = $this->sendSignal('kernel.request', [])) instanceof ResponseInterface) {
159
            return $output;
160
        }
161
162
        /** @var IResolver $resolver */
163
        $resolver = $this->getResolver();
164
        if (($output = $this->sendSignal('kernel.router', ['resolver' => $resolver])) instanceof ResponseInterface) {
165
            return $output;
166
        }
167
168
        /** @var IController|Console $app */
169
        $app = $resolver->getApplication();
170
        if (($output = $this->sendSignal('kernel.controller', ['application' => $app])) instanceof ResponseInterface) {
171
            return $output;
172
        }
173
174
        $output = $app->action((string)$resolver->getAction());
175
        if (!($output instanceof ResponseInterface)) {
176
            $response = (new ResponseInjector)->build();
177
            $stream = $response->getBody();
178
            $stream->write((string)$output);
179
            $output = $response->withBody($stream);
180
        }
181
182
        $this->sendSignal('kernel.response', ['output' => $output]);
183
184
        return $output;
185
    }
186
187
    /**
188
     * Initialization
189
     *
190
     * @access protected
191
     * @param ServerRequestInterface $request
192
     * @return void
193
     * @throws Exception
194
     */
195
    protected function initialize(ServerRequestInterface $request)
196
    {
197
        $class = $this->getInjectorClass();
198
        if (!$class || !class_exists($class)) {
199
            $class = '\Micro\Base\Injector';
200
        }
201
202
        /** @var IInjector $inject */
203
        $inject = new $class($this->getConfig());
204
        $inject->addRequirement('kernel', $this);
205
        $inject->addRequirement('request', $request);
206
207
        $dispatcherInjector = new DispatcherInjector;
208
        try {
209
            $dispatcher = $dispatcherInjector->build();
210
        } catch (Exception $e) {
211
            $dispatcher = new Dispatcher;
212
            $dispatcherInjector->addRequirement('dispatcher', $dispatcher);
213
        }
214
        $dispatcher->signal('kernel.boot', ['injector' => $inject]);
215
216
        $this->loaded = true;
217
    }
218
219
    /**
220
     * Get full class name
221
     * @return string
222
     */
223
    protected function getInjectorClass()
224
    {
225
        return '';
226
    }
227
228
    /**
229
     * Default config path
230
     *
231
     * @return string
232
     */
233
    protected function getConfig()
234
    {
235
        return $this->getAppDir().'/configs/index.php';
236
    }
237
238
    /**
239
     * Get application directory
240
     *
241
     * @return string
242
     */
243
    public function getAppDir()
244
    {
245
        if (!$this->appDir) {
246
            $this->appDir = realpath(dirname((new \ReflectionObject($this))->getFileName()));
247
        }
248
249
        return $this->appDir;
250
    }
251
252
    /**
253
     * Add listener on event
254
     *
255
     * @access public
256
     *
257
     * @param string $listener listener name
258
     * @param \Closure $event ['Object', 'method'] or callable
259
     * @param int|null $prior priority
260
     *
261
     * @return boolean|null
262
     * @throws Exception
263
     */
264
    protected function addListener($listener, $event, $prior = null)
265
    {
266
        if (!is_string($listener)) {
267
            return false;
268
        }
269
270
        return (new DispatcherInjector)->build()->addListener($listener, $event, $prior);
271
    }
272
273
    /**
274
     * Get status of debug
275
     *
276
     * @access public
277
     *
278
     * @return bool
279
     */
280
    public function isDebug()
281
    {
282
        return $this->debug;
283
    }
284
285
    public function isCli()
286
    {
287
        return PHP_SAPI === 'cli';
288
    }
289
290
    /**
291
     * Get start time
292
     *
293
     * @access public
294
     *
295
     * @return double
296
     */
297
    public function getStartTime()
298
    {
299
        return $this->startTime;
300
    }
301
302
    /**
303
     * Send signal to dispatcher
304
     *
305
     * @param string $signal
306
     * @param $params
307
     * @return mixed
308
     * @throws Exception
309
     */
310
    protected function sendSignal($signal, $params)
311
    {
312
        return (new DispatcherInjector)->build()->signal($signal, $params);
313
    }
314
315
    /**
316
     * Get resolver
317
     *
318
     * @access protected
319
     *
320
     * @return IResolver
321
     * @throws \Micro\Base\Exception
322
     */
323
    protected function getResolver()
324
    {
325
        try {
326
            if ($this->isCli()) {
327
                $resolver = (new ConsoleResolverInjector)->build();
328
            } else {
329
                $resolver = (new ResolverInjector)->build();
330
            }
331
        } catch (Exception $e) {
332
            $class = $this->isCli() ? '\Micro\Resolver\ConsoleResolver' : '\Micro\Resolver\HMVCResolver';
333
            $resolver = new $class;
334
        }
335
336
        if (is_string($resolver) && is_subclass_of($resolver, '\Micro\Resolver\IResolver')) {
337
            $resolver = new $resolver();
338
        }
339
340
        if (!($resolver instanceof IResolver)) {
341
            throw new Exception('Resolver is not implement an IResolver');
342
        }
343
344
        return $resolver;
345
    }
346
347
    /**
348
     * Do exception
349
     *
350
     * @access private
351
     *
352
     * @param \Exception $e Exception
353
     *
354
     * @return \Micro\Web\IOutput|ResponseInterface
355
     * @throws \Micro\Base\Exception
356
     */
357
    private function doException(\Exception $e)
0 ignored issues
show
Coding Style introduced by
doException uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
358
    {
359
        $output = $this->isCli() ? new DefaultConsoleCommand([]) : (new ResponseInjector)->build();
360
361
        if ($this->isCli()) { // Render CLI error
362
            $output->data = '"Error #'.$e->getCode().' - '.$e->getMessage().'"';
363
            $output->execute();
0 ignored issues
show
Bug introduced by
The method execute does only exist in Micro\Cli\Consoles\DefaultConsoleCommand, but not in Psr\Http\Message\ResponseInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
364
365
            return $output;
366
        }
367
368
        $errorController = (new Injector)->param('errorController');
369
        $errorAction = (new Injector)->param('errorAction');
370
371
        if (!$errorController || !$errorAction) { // render SAPI error not configured
372
            $stream = $output->getBody();
0 ignored issues
show
Bug introduced by
The method getBody does only exist in Psr\Http\Message\ResponseInterface, but not in Micro\Cli\Consoles\DefaultConsoleCommand.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
373
            $stream->write('Option `errorController` or `errorAction` not configured');
374
375
            return $output->withBody($stream);
0 ignored issues
show
Bug introduced by
The method withBody does only exist in Psr\Http\Message\ResponseInterface, but not in Micro\Cli\Consoles\DefaultConsoleCommand.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
376
        }
377
378
        // Render SAPI error
379
        $_POST['error'] = $e;
380
381
        $controller = $errorController;
382
383
        /** @var \Micro\Mvc\Controllers\IController $result */
384
        $result = new $controller(false);
385
        $result = $result->action($errorAction);
386
387
        if ($result instanceof ResponseInterface) {
388
            return $result;
389
        }
390
391
        $stream = $output->getBody();
392
        $stream->write((string)$result);
393
394
        return $output->withBody($stream);
395
    }
396
397
    /**
398
     * Get web root directory
399
     *
400
     * @return string
401
     */
402
    public function getWebDir()
403
    {
404
        return $this->webDir;
405
    }
406
407
    /**
408
     * Terminate application
409
     *
410
     * @access public
411
     *
412
     * @return void
413
     */
414
    public function terminate()
415
    {
416
        try {
417
            (new DispatcherInjector)->build()->signal('kernel.kill', []);
418
        } catch (Exception $e) {
419
            (new Dispatcher)->signal('kernel.kill', []);
420
        }
421
    }
422
423
    /**
424
     * Get character set
425
     *
426
     * @access public
427
     *
428
     * @return string
429
     */
430
    public function getCharset()
431
    {
432
        return 'UTF-8';
433
    }
434
435
    /**
436
     * Get logs directory
437
     *
438
     * @return string
439
     */
440
    public function getLogDir()
441
    {
442
        return $this->getAppDir().'/logs';
443
    }
444
445
    /**
446
     * Get cache directory
447
     *
448
     * @return string
449
     */
450
    public function getCacheDir()
451
    {
452
        return $this->getAppDir().'/cache/'.$this->getEnvironment();
453
    }
454
455
    /**
456
     * Get environment name
457
     *
458
     * @access public
459
     *
460
     * @return string
461
     */
462
    public function getEnvironment()
463
    {
464
        return $this->environment;
465
    }
466
467
    public function send(ResponseInterface $response)
468
    {
469
        header(
470
            'HTTP/' . $response->getProtocolVersion() . ' ' .
471
            $response->getStatusCode() . ' ' .
472
            $response->getReasonPhrase()
473
        );
474
475
        foreach ($response->getHeaders() as $header => $values) {
476
            header($header . ': ' . implode(', ', $values));
477
        }
478
479
        printf($response->getBody());
480
    }
481
}
482