Passed
Push — master ( 71df61...271814 )
by Oleg
04:35
created

Micro   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 428
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 15

Importance

Changes 18
Bugs 4 Features 2
Metric Value
wmc 50
c 18
b 4
f 2
lcom 1
cbo 15
dl 0
loc 428
rs 7.2559

20 Methods

Rating   Name   Duplication   Size   Complexity  
A getCharset() 0 4 1
A getInjectorClass() 0 4 1
A getConfig() 0 4 1
B initialize() 0 23 4
A addListener() 0 8 2
A sendSignal() 0 4 1
A __construct() 0 17 2
A __clone() 0 8 2
A run() 0 14 3
C doRun() 0 49 10
A getAppDir() 0 8 2
A isDebug() 0 4 1
A getStartTime() 0 4 1
C getResolver() 0 26 7
B doException() 0 39 6
A getWebDir() 0 4 1
A terminate() 0 8 2
A getLogDir() 0 4 1
A getCacheDir() 0 4 1
A getEnvironment() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Micro often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Micro, and based on these observations, apply Extract Interface, too.

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\IOutput;
18
use Micro\Web\IRequest;
19
use Micro\Web\IResponse;
20
use Micro\Web\RequestInjector;
21
use Micro\Web\Response;
22
use Micro\Web\ResponseInjector;
23
24
/**
25
 * Micro class file.
26
 *
27
 * Base class for initialize MicroPHP, used as bootstrap framework.
28
 *
29
 * @author Oleg Lunegov <[email protected]>
30
 * @link https://github.com/lugnsk/micro
31
 * @copyright Copyright &copy; 2013 Oleg Lunegov
32
 * @license /LICENSE
33
 * @package micro
34
 * @version 1.0
35
 * @since 1.0
36
 */
37
class Micro
38
{
39
    /** @const string VERSION Version framework */
40
    const VERSION = '1.1';
41
42
    /** @var string $appDir */
43
    protected $appDir;
44
    /** @var string $webDir */
45
    protected $webDir;
46
47
    /** @var bool $loaded Micro loaded flag */
48
    private $loaded;
49
    /** @var bool $debug Debug-mode flag */
50
    private $debug = true;
51
    /** @var string $environment Application environment */
52
    private $environment = 'devel';
53
    /** @var float $startTime Time of start framework */
54
    private $startTime;
55
56
57
    /**
58
     * Initialize application
59
     *
60
     * @access public
61
     *
62
     * @param string $environment Application environment: devel , production , test, other
63
     * @param bool $debug Debug-mode flag
64
     *
65
     * @result void
66
     */
67
    public function __construct($environment = 'devel', $debug = true)
68
    {
69
        $this->webDir = getenv('DOCUMENT_ROOT');
70
        $this->environment = (string)$environment;
71
        $this->debug = (bool)$debug;
72
        $this->loaded = false;
73
74
        ini_set('display_errors', (integer)$this->debug);
75
        ini_set('log_errors', (integer)$this->debug);
76
77
        FatalError::register();
78
79
        if ($this->debug) {
80
            ini_set('error_reporting', -1);
81
            $this->startTime = microtime(true);
82
        }
83
    }
84
85
    /**
86
     * Clone application
87
     *
88
     * @access public
89
     *
90
     * @return void
91
     */
92
    public function __clone()
93
    {
94
        if ($this->debug) {
95
            $this->startTime = microtime(true);
96
        }
97
98
        $this->loaded = false;
99
    }
100
101
    /**
102
     * Running application
103
     *
104
     * @access public
105
     *
106
     * @param IRequest $request Request object
107
     *
108
     * @return \Micro\Web\IOutput|\Micro\Web\IResponse
109
     * @throws \Exception
110
     * @throws \Micro\Base\Exception
111
     */
112
    public function run(IRequest $request)
113
    {
114
        try {
115
            return $this->doRun($request);
116
        } catch (\Exception $e) {
117
            if ($this->debug) {
118
                (new DispatcherInjector)->build()->signal('kernel.exception', ['exception' => $e]);
119
120
                throw $e;
121
            }
122
123
            return $this->doException($e);
124
        }
125
    }
126
127
    /**
128
     * Starting ...
129
     *
130
     * @access private
131
     *
132
     * @param IRequest $request
133
     *
134
     * @return \Micro\Web\IResponse|\Micro\Web\IOutput
135
     * @throws \Micro\Base\Exception
136
     */
137
    private function doRun(IRequest $request)
138
    {
139
140
        if (!$this->loaded) {
141
            $this->initialize($request);
142
143
            $this->addListener('kernel.kill', function() {
144
                /** @var IRequest $request */
145
                $request = (new RequestInjector)->build();
146
147
                if ($this->isDebug() && !$request->isCli() && !$request->isAjax()) {
148
                    echo '<div class="debug_timer">', (microtime(true) - $this->getStartTime()), '</div>';
149
                }
150
151
                if (false === $this->loaded) {
152
                    return;
153
                }
154
155
                $this->loaded = false;
156
            });
157
        }
158
159
        if (($output = $this->sendSignal('kernel.request', [])) instanceof IResponse) {
160
            return $output;
161
        }
162
163
        /** @var IResolver $resolver */
164
        $resolver = $this->getResolver();
165
        if (($output = $this->sendSignal('kernel.router', ['resolver' => $resolver])) instanceof IResponse) {
166
            return $output;
167
        }
168
169
        /** @var IController|Console $app */
170
        $app = $resolver->getApplication();
171
        if (($output = $this->sendSignal('kernel.controller', ['application' => $app])) instanceof IResponse) {
172
            return $output;
173
        }
174
175
        $output = $app->action((string)$resolver->getAction());
176
        if (!$output instanceof IOutput) {
177
            $response = (new ResponseInjector)->build();
178
            $response->setBody((string)$output);
179
            $output = $response;
180
        }
181
182
        $this->sendSignal('kernel.response', ['output' => $output]);
183
184
        return $output;
185
    }
186
187
    /**
188
     * Initialization
189
     *
190
     * @access protected
191
     * @param IRequest $request
192
     * @return void
193
     * @throws Exception
194
     */
195
    protected function initialize(IRequest $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
    /**
286
     * Get start time
287
     *
288
     * @access public
289
     *
290
     * @return double
291
     */
292
    public function getStartTime()
293
    {
294
        return $this->startTime;
295
    }
296
297
    /**
298
     * Send signal to dispatcher
299
     *
300
     * @param string $signal
301
     * @param $params
302
     * @return mixed
303
     * @throws Exception
304
     */
305
    protected function sendSignal($signal, $params)
306
    {
307
        return (new DispatcherInjector)->build()->signal($signal, $params);
308
    }
309
310
    /**
311
     * Get resolver
312
     *
313
     * @access protected
314
     *
315
     * @return IResolver
316
     * @throws \Micro\Base\Exception
317
     */
318
    protected function getResolver()
319
    {
320
        /** @var IRequest $request */
321
        $request = (new RequestInjector)->build();
322
323
        try {
324
            if ($request->isCli()) {
325
                $resolver = (new ConsoleResolverInjector)->build();
326
            } else {
327
                $resolver = (new ResolverInjector)->build();
328
            }
329
        } catch (Exception $e) {
330
            $class = $request->isCli() ? '\Micro\Resolver\ConsoleResolver' : '\Micro\Resolver\HMVCResolver';
331
            $resolver = new $class;
332
        }
333
334
        if (is_string($resolver) && is_subclass_of($resolver, '\Micro\Resolver\IResolver')) {
335
            $resolver = new $resolver();
336
        }
337
338
        if (!$resolver instanceof IResolver) {
339
            throw new Exception('Resolver is not implement an IResolver');
340
        }
341
342
        return $resolver;
343
    }
344
345
    /**
346
     * Do exception
347
     *
348
     * @access private
349
     *
350
     * @param \Exception $e Exception
351
     *
352
     * @return \Micro\Web\IOutput|\Micro\Web\IResponse
353
     * @throws \Micro\Base\Exception
354
     */
355
    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...
356
    {
357
        /** @var IRequest $request */
358
        $request = (new RequestInjector)->build();
359
360
        $output = $request->isCli() ? new DefaultConsoleCommand([]) : new Response();
361
362
        if ($request->isCli()) {
363
            $output->data = '"Error #'.$e->getCode().' - '.$e->getMessage().'"';
364
            $output->execute();
0 ignored issues
show
Bug introduced by
The method execute does only exist in Micro\Cli\Consoles\DefaultConsoleCommand, but not in Micro\Web\Response.

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...
365
366
            return $output;
367
        }
368
369
        $errorController = (new Injector)->param('errorController');
370
        $errorAction = (new Injector)->param('errorAction');
371
372
        if (!$errorController || !$errorAction) {
373
            $output->setBody('Option `errorController` or `errorAction` not configured');
0 ignored issues
show
Bug introduced by
The method setBody does only exist in Micro\Web\Response, 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...
374
375
            return $output;
376
        }
377
378
        $_POST['error'] = $e;
379
380
        $controller = $errorController;
381
382
        /** @var \Micro\Mvc\Controllers\IController $result */
383
        $result = new $controller(false);
384
        $result = $result->action($errorAction);
385
386
        if ($result instanceof IOutput) {
387
            return $result;
388
        }
389
390
        $output->setBody((string)$result);
391
392
        return $output;
393
    }
394
395
    /**
396
     * Get web root directory
397
     *
398
     * @return string
399
     */
400
    public function getWebDir()
401
    {
402
        return $this->webDir;
403
    }
404
405
    /**
406
     * Terminate application
407
     *
408
     * @access public
409
     *
410
     * @return void
411
     */
412
    public function terminate()
413
    {
414
        try {
415
            (new DispatcherInjector)->build()->signal('kernel.kill', []);
416
        } catch (Exception $e) {
417
            (new Dispatcher)->signal('kernel.kill', []);
418
        }
419
    }
420
421
    /**
422
     * Get character set
423
     *
424
     * @access public
425
     *
426
     * @return string
427
     */
428
    public function getCharset()
429
    {
430
        return 'UTF-8';
431
    }
432
433
    /**
434
     * Get logs directory
435
     *
436
     * @return string
437
     */
438
    public function getLogDir()
439
    {
440
        return $this->getAppDir().'/logs';
441
    }
442
443
    /**
444
     * Get cache directory
445
     *
446
     * @return string
447
     */
448
    public function getCacheDir()
449
    {
450
        return $this->getAppDir().'/cache/'.$this->getEnvironment();
451
    }
452
453
    /**
454
     * Get environment name
455
     *
456
     * @access public
457
     *
458
     * @return string
459
     */
460
    public function getEnvironment()
461
    {
462
        return $this->environment;
463
    }
464
}
465