Completed
Push — master ( 4fc76a...f82471 )
by Paul
06:24 queued 02:47
created

App   F

Complexity

Total Complexity 80

Size/Duplication

Total Lines 658
Duplicated Lines 0.76 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 23
Bugs 2 Features 0
Metric Value
wmc 80
c 23
b 2
f 0
lcom 1
cbo 12
dl 5
loc 658
rs 3.7116

33 Methods

Rating   Name   Duplication   Size   Complexity  
A getVersion() 0 4 1
A getEnvironment() 0 4 1
A isEnvironment() 0 10 3
A isDebug() 0 4 1
A getRootDir() 0 8 2
A getServiceManager() 0 4 1
A getContainer() 0 4 1
A getModuleManager() 0 8 2
A getModules() 0 4 1
A locateResource() 0 4 1
A getStartTime() 0 4 2
A getCacheDir() 0 4 1
A getLogDir() 0 4 1
A getCharset() 0 4 1
A getConfigManager() 0 9 2
A loadConfig() 0 6 1
A getConfig() 0 8 2
A serialize() 0 4 1
A unserialize() 0 6 1
A getAppParameters() 0 15 1
A getEnvParameters() 0 11 3
A buildServiceManager() 0 9 1
C __construct() 0 15 10
A setOption() 0 16 3
A getOption() 0 10 2
A __clone() 0 9 2
B boot() 0 29 5
A run() 0 14 4
C dispatch() 0 37 7
A getName() 0 8 2
B handleRouting() 5 24 5
A log() 0 10 4
B enableDebug() 0 16 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like App 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 App, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This file is part of the PPI Framework.
4
 *
5
 * @copyright  Copyright (c) 2011-2015 Paul Dragoonis <[email protected]>
6
 * @license    http://opensource.org/licenses/mit-license.php MIT
7
 *
8
 * @link       http://www.ppi.io
9
 */
10
11
namespace PPI\Framework;
12
13
use PPI\Framework\Config\ConfigManager;
14
use PPI\Framework\Debug\ExceptionHandler;
15
use PPI\Framework\Http\Request as HttpRequest;
16
use PPI\Framework\Http\Response as HttpResponse;
17
use PPI\Framework\ServiceManager\ServiceManagerBuilder;
18
use Symfony\Component\ClassLoader\DebugClassLoader;
19
use Symfony\Component\Debug\ErrorHandler;
20
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
21
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
22
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
23
24
/**
25
 * The PPI App bootstrap class.
26
 *
27
 * This class sets various app settings, and allows you to override classes used in the bootup process.
28
 *
29
 * @author     Paul Dragoonis <[email protected]>
30
 * @author     Vítor Brandão <[email protected]>
31
 */
32
class App implements AppInterface
33
{
34
    /**
35
     * Version string.
36
     *
37
     * @var string
38
     */
39
    const VERSION = '2.1.0-DEV';
40
41
    /**
42
     * @var bool
43
     */
44
    protected $booted = false;
45
46
    /**
47
     * @var bool
48
     */
49
    protected $debug;
50
51
    /**
52
     * Application environment: "dev|development" vs "prod|production".
53
     *
54
     * @var string
55
     */
56
    protected $environment;
57
58
    /**
59
     * @var \Psr\Log\LoggerInterface
60
     */
61
    protected $logger;
62
63
    /**
64
     * Unix timestamp with microseconds.
65
     *
66
     * @var float
67
     */
68
    protected $startTime;
69
70
    /**
71
     * Configuration loader.
72
     *
73
     * @var \PPI\Framework\Config\ConfigManager
74
     */
75
    protected $configManager;
76
77
    /**
78
     * The Module Manager.
79
     *
80
     * @var \Zend\ModuleManager\ModuleManager
81
     */
82
    protected $moduleManager;
83
84
    /**
85
     * @param int $errorReportingLevel The level of error reporting you want
86
     */
87
    protected $errorReportingLevel;
88
89
    /**
90
     * @var null|array
91
     */
92
    protected $matchedRoute;
93
94
    /**
95
     * @var \PPI\Framework\Module\Controller\ControllerResolver
96
     */
97
    protected $resolver;
98
99
    /**
100
     * @var string
101
     */
102
    protected $name;
103
104
    /**
105
     * Path to the application root dir aka the "app" directory.
106
     *
107
     * @var null|string
108
     */
109
    protected $rootDir;
110
111
    /**
112
     * Service Manager.
113
     *
114
     * @var \PPI\Framework\ServiceManager\ServiceManager
115
     */
116
    protected $serviceManager;
117
118
    /**
119
     * App constructor.
120
     *
121
     * @param array $options
122
     */
123
    public function __construct(array $options = array())
124
    {
125
        // Default options
126
        $this->environment = isset($options['environment']) && $options['environment'] ? (string) $options['environment'] : 'prod';
127
        $this->debug = isset($options['debug']) && null !== $options['debug'] ? (boolean) $options['debug'] : false;
128
        $this->rootDir = isset($options['rootDir']) && $options['rootDir'] ? (string) $options['rootDir'] : $this->getRootDir();
129
        $this->name = isset($options['name']) && $options['name'] ? (string) $options['name'] : $this->getName();
130
131
        if ($this->debug) {
132
            $this->startTime = microtime(true);
133
            $this->enableDebug();
134
        } else {
135
            ini_set('display_errors', 0);
136
        }
137
    }
138
139
    /**
140
     * Set an App option.
141
     *
142
     * @param $option
143
     * @param $value
144
     *
145
     * @throws \RuntimeException
146
     *
147
     * @return $this
148
     */
149
    public function setOption($option, $value)
150
    {
151
        if (true === $this->booted) {
152
            throw new \RuntimeException('Setting App options after boot() is now allowed');
153
        }
154
155
        // "root_dir" to "rootDir"
156
        $property = preg_replace('/_(.?)/e', "strtoupper('$1')", $option);
157
        if (!property_exists($this, $property)) {
158
            throw new \RuntimeException(sprintf('App property "%s" (option "%s") does not exist', $property, $option));
159
        }
160
161
        $this->$property = $value;
162
163
        return $this;
164
    }
165
166
    /**
167
     * Get an App option.
168
     *
169
     * @param $option
170
     *
171
     * @throws \RuntimeException
172
     *
173
     * @return string
174
     */
175
    public function getOption($option)
176
    {
177
        // "root_dir" to "rootDir"
178
        $property = preg_replace('/_(.?)/e', "strtoupper('$1')", $option);
179
        if (!property_exists($this, $property)) {
180
            throw new \RuntimeException(sprintf('App property "%s" (option "%s") does not exist', $property, $option));
181
        }
182
183
        return $property;
184
    }
185
186
    public function __clone()
187
    {
188
        if ($this->debug) {
189
            $this->startTime = microtime(true);
190
        }
191
192
        $this->booted = false;
193
        $this->serviceManager = null;
194
    }
195
196
    /**
197
     * Run the boot process, load our modules and their dependencies.
198
     *
199
     * This method is automatically called by dispatch(), but you can use it
200
     * to build all services when not handling a request.
201
     *
202
     * @return $this
203
     */
204
    public function boot()
205
    {
206
        if (true === $this->booted) {
207
            return $this;
208
        }
209
210
        $this->serviceManager = $this->buildServiceManager();
211
        $this->log('debug', sprintf('Booting %s ...', $this->name));
212
213
        // Loading our Modules
214
        $this->getModuleManager()->loadModules();
215
        if ($this->debug) {
216
            $modules = $this->getModuleManager()->getModules();
217
            $this->log('debug', sprintf('All modules online (%d): "%s"', count($modules), implode('", "', $modules)));
218
        }
219
220
        // Lets get all the services our of our modules and start setting them in the ServiceManager
221
        $moduleServices = $this->serviceManager->get('ModuleDefaultListener')->getServices();
222
        foreach ($moduleServices as $key => $service) {
223
            $this->serviceManager->setFactory($key, $service);
224
        }
225
226
        $this->booted = true;
227
        if ($this->debug) {
228
            $this->log('debug', sprintf('%s has booted (in %.3f secs)', $this->name, microtime(true) - $this->startTime));
229
        }
230
231
        return $this;
232
    }
233
234
    /**
235
     * Run the application and send the response.
236
     *
237
     * @param HttpRequest|null $request
238
     * @param HttpRequest|null $request
239
     * @throws \Exception
240
     *
241
     * @return Response
242
     */
243
    public function run(HttpRequest $request = null, HttpResponse $response = null)
244
    {
245
        if (false === $this->booted) {
246
            $this->boot();
247
        }
248
249
        $request = $request ?: HttpRequest::createFromGlobals();
250
        $response = $response ?: new HttpResponse();
251
252
        $response = $this->dispatch($request, $response);
0 ignored issues
show
Compatibility introduced by
$request of type object<Symfony\Component\HttpFoundation\Request> is not a sub-type of object<PPI\Framework\Http\Request>. It seems like you assume a child class of the class Symfony\Component\HttpFoundation\Request to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
253
        $response->send();
254
255
        return $response;
256
    }
257
258
    /**
259
     * Decide on a route to use and dispatch our module's controller action.
260
     *
261
     * @param HttpRequest $request
262
     * @param HttpResponse $response
263
     * @throws \Exception
264
     *
265
     * @return Response
266
     */
267
    public function dispatch(HttpRequest $request, HttpResponse $response)
268
    {
269
        if (false === $this->booted) {
270
            $this->boot();
271
        }
272
273
        // Routing
274
        $routeParams = $this->handleRouting($request);
275
        $request->attributes->add($routeParams);
276
277
        // Resolve our Controller
278
        $resolver = $this->serviceManager->get('ControllerResolver');
279
        if (false === $controller = $resolver->getController($request)) {
280
            throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s".', $request->getPathInfo()));
281
        }
282
283
        $controllerArguments = $resolver->getArguments($request, $controller);
284
285
        $result = call_user_func_array(
286
            $controller,
287
            $controllerArguments
288
        );
289
290
        if ($result === null) {
291
            throw new \Exception('Your action returned null. It must always return something');
292
        } elseif (is_string($result)) {
293
            $response->setContent($result);
294
        } else if ($result instanceof SymfonyResponse || $response instanceof HttpResponse) {
295
            $response = $result;
296
        } else {
297
            throw new \Exception('Invalid response type returned from controller');
298
        }
299
300
        $this->response = $response;
0 ignored issues
show
Bug introduced by
The property response does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
301
302
        return $response;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $response; (object|integer|double|null|array|boolean) is incompatible with the return type documented by PPI\Framework\App::dispatch of type PPI\Framework\Response|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
303
    }
304
305
    /**
306
     * Gets the name of the application.
307
     *
308
     * @return string The application name
309
     *
310
     * @api
311
     */
312
    public function getName()
313
    {
314
        if (null === $this->name) {
315
            $this->name = preg_replace('/[^a-zA-Z0-9_]+/', '', basename($this->rootDir));
316
        }
317
318
        return $this->name;
319
    }
320
321
    /**
322
     * Gets the version of the application.
323
     *
324
     * @return string The application version
325
     *
326
     * @api
327
     */
328
    public function getVersion()
329
    {
330
        return self::VERSION;
331
    }
332
333
    /**
334
     * Get the environment mode the application is in.
335
     *
336
     * @return string The current environment
337
     *
338
     * @api
339
     */
340
    public function getEnvironment()
341
    {
342
        return $this->environment;
343
    }
344
345
    /**
346
     * @param $env
347
     *
348
     * @return bool
349
     */
350
    public function isEnvironment($env)
351
    {
352
        if ('development' == $env) {
353
            $env = 'dev';
354
        } elseif ('production' == $env) {
355
            $env = 'prod';
356
        }
357
358
        return $this->getEnvironment() == $env;
359
    }
360
361
    /**
362
     * Checks if debug mode is enabled.
363
     *
364
     * @return bool true if debug mode is enabled, false otherwise
365
     *
366
     * @api
367
     */
368
    public function isDebug()
369
    {
370
        return $this->debug;
371
    }
372
373
    /**
374
     * Gets the application root dir.
375
     *
376
     * @return string The application root dir
377
     *
378
     * @api
379
     */
380
    public function getRootDir()
381
    {
382
        if (null === $this->rootDir) {
383
            $this->rootDir = realpath(getcwd() . '/app');
384
        }
385
386
        return $this->rootDir;
387
    }
388
389
    /**
390
     * Get the service manager.
391
     *
392
     * @return ServiceManager\ServiceManager
393
     */
394
    public function getServiceManager()
395
    {
396
        return $this->serviceManager;
397
    }
398
399
    /**
400
     * @note Added for compatibility with Symfony's HttpKernel\Kernel.
401
     *
402
     * @return null|ServiceManager\ServiceManager
403
     */
404
    public function getContainer()
405
    {
406
        return $this->serviceManager;
407
    }
408
409
    /**
410
     * Returns the Module Manager.
411
     *
412
     * @return \Zend\ModuleManager\ModuleManager
413
     */
414
    public function getModuleManager()
415
    {
416
        if (null === $this->moduleManager) {
417
            $this->moduleManager = $this->serviceManager->get('ModuleManager');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->serviceManager->get('ModuleManager') can also be of type array. However, the property $moduleManager is declared as type object<Zend\ModuleManager\ModuleManager>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
418
        }
419
420
        return $this->moduleManager;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->moduleManager; of type object|array adds the type array to the return on line 420 which is incompatible with the return type documented by PPI\Framework\App::getModuleManager of type Zend\ModuleManager\ModuleManager.
Loading history...
421
    }
422
423
    /**
424
     * Get an array of the loaded modules.
425
     *
426
     * @return array An array of Module objects, keyed by module name
427
     */
428
    public function getModules()
429
    {
430
        return $this->getModuleManager()->getLoadedModules(true);
431
    }
432
433
    /**
434
     * @see PPI\Framework\Module\ModuleManager::locateResource()
435
     *
436
     * @param string $name  A resource name to locate
437
     * @param string $dir   A directory where to look for the resource first
438
     * @param bool   $first Whether to return the first path or paths for all matching bundles
439
     *
440
     * @throws \InvalidArgumentException if the file cannot be found or the name is not valid
441
     * @throws \RuntimeException         if the name contains invalid/unsafe
442
     * @throws \RuntimeException         if a custom resource is hidden by a resource in a derived bundle
443
     *
444
     * @return string|array The absolute path of the resource or an array if $first is false
445
     */
446
    public function locateResource($name, $dir = null, $first = true)
447
    {
448
        return $this->getModuleManager()->locateResource($name, $dir, $first);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Zend\ModuleManager\ModuleManager as the method locateResource() does only exist in the following sub-classes of Zend\ModuleManager\ModuleManager: PPI\Framework\Module\ModuleManager. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
449
    }
450
451
    /**
452
     * Gets the request start time (not available if debug is disabled).
453
     *
454
     * @return int The request start timestamp
455
     *
456
     * @api
457
     */
458
    public function getStartTime()
459
    {
460
        return $this->debug ? $this->startTime : -INF;
461
    }
462
463
    /**
464
     * Gets the cache directory.
465
     *
466
     * @return string The cache directory
467
     *
468
     * @api
469
     */
470
    public function getCacheDir()
471
    {
472
        return $this->rootDir . '/cache/' . $this->environment;
473
    }
474
475
    /**
476
     * Gets the log directory.
477
     *
478
     * @return string The log directory
479
     *
480
     * @api
481
     */
482
    public function getLogDir()
483
    {
484
        return $this->rootDir . '/logs';
485
    }
486
487
    /**
488
     * Gets the charset of the application.
489
     *
490
     * @return string The charset
491
     *
492
     * @api
493
     */
494
    public function getCharset()
495
    {
496
        return 'UTF-8';
497
    }
498
499
    /**
500
     * Returns a ConfigManager instance.
501
     *
502
     * @return \PPI\Framework\Config\ConfigManager
503
     */
504
    public function getConfigManager()
505
    {
506
        if (null === $this->configManager) {
507
            $cachePath = $this->getCacheDir() . '/application-config-cache.' . $this->getName() . '.php';
508
            $this->configManager = new ConfigManager($cachePath, !$this->debug, $this->rootDir . '/config');
509
        }
510
511
        return $this->configManager;
512
    }
513
514
    /**
515
     * Loads a configuration file or PHP array.
516
     *
517
     * @param  $resource
518
     * @param null $type
519
     *
520
     * @return App The current instance
521
     */
522
    public function loadConfig($resource, $type = null)
523
    {
524
        $this->getConfigManager()->addConfig($resource, $type);
525
526
        return $this;
527
    }
528
529
    /**
530
     * Returns the application configuration.
531
     *
532
     * @throws \RuntimeException
533
     *
534
     * @return array|object
535
     */
536
    public function getConfig()
537
    {
538
        if (!$this->booted) {
539
            throw new \RuntimeException('The "Config" service is only available after the App boot()');
540
        }
541
542
        return $this->serviceManager->get('Config');
543
    }
544
545
    public function serialize()
546
    {
547
        return serialize(array($this->environment, $this->debug));
548
    }
549
550
    public function unserialize($data)
551
    {
552
        list($environment, $debug) = unserialize($data);
553
554
        $this->__construct($environment, $debug);
0 ignored issues
show
Unused Code introduced by
The call to App::__construct() has too many arguments starting with $debug.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
555
    }
556
557
    /**
558
     * Returns the application parameters.
559
     *
560
     * @return array An array of application parameters
561
     */
562
    protected function getAppParameters()
563
    {
564
        return array_merge(
565
            array(
566
                'app.root_dir' => $this->rootDir,
567
                'app.environment' => $this->environment,
568
                'app.debug' => $this->debug,
569
                'app.name' => $this->name,
570
                'app.cache_dir' => $this->getCacheDir(),
571
                'app.logs_dir' => $this->getLogDir(),
572
                'app.charset' => $this->getCharset(),
573
            ),
574
            $this->getEnvParameters()
575
        );
576
    }
577
578
    /**
579
     * Gets the environment parameters.
580
     *
581
     * Only the parameters starting with "PPI__" are considered.
582
     *
583
     * @return array An array of parameters
584
     */
585
    protected function getEnvParameters()
0 ignored issues
show
Coding Style introduced by
getEnvParameters uses the super-global variable $_SERVER 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...
586
    {
587
        $parameters = array();
588
        foreach ($_SERVER as $key => $value) {
589
            if (0 === strpos($key, 'PPI__')) {
590
                $parameters[strtolower(str_replace('__', '.', substr($key, 5)))] = $value;
591
            }
592
        }
593
594
        return $parameters;
595
    }
596
597
    /**
598
     * Creates and initializes a ServiceManager instance.
599
     *
600
     * @return ServiceManager The compiled service manager
601
     */
602
    protected function buildServiceManager()
603
    {
604
        // ServiceManager creation
605
        $serviceManager = new ServiceManagerBuilder($this->getConfigManager()->getMergedConfig());
606
        $serviceManager->build($this->getAppParameters());
607
        $serviceManager->set('app', $this);
608
609
        return $serviceManager;
610
    }
611
612
    /**
613
     * Perform the matching of a route and return a set of routing parameters if a valid one is found.
614
     * Otherwise exceptions get thrown.
615
     *
616
     * @param HttpRequest $request
617
     *
618
     * @throws \Exception
619
     *
620
     * @return array
621
     */
622
    protected function handleRouting(HttpRequest $request)
623
    {
624
        $this->router = $this->serviceManager->get('Router');
0 ignored issues
show
Bug introduced by
The property router does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
625
        $this->router->warmUp($this->getCacheDir());
626
627
        try {
628
            // Lets load up our router and match the appropriate route
629
            $parameters = $this->router->matchRequest($request);
630 View Code Duplication
            if (!empty($parameters)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
631
                if (null !== $this->logger) {
632
                    $this->logger->info(sprintf('Matched route "%s" (parameters: %s)', $parameters['_route'], $this->router->parametersToString($parameters)));
633
                }
634
            }
635
        } catch (ResourceNotFoundException $e) {
636
            $routeUri = $this->router->generate('Framework_404');
637
            $parameters = $this->router->matchRequest($request::create($routeUri));
638
        } catch (\Exception $e) {
639
            throw $e;
640
        }
641
642
        $parameters['_route_params'] = $parameters;
643
644
        return $parameters;
645
    }
646
647
    /**
648
     * Logs with an arbitrary level.
649
     *
650
     * @param mixed  $level
651
     * @param string $message
652
     * @param array  $context
653
     */
654
    protected function log($level, $message, array $context = array())
655
    {
656
        if (null === $this->logger && $this->getServiceManager()->has('logger')) {
657
            $this->logger = $this->getServiceManager()->get('logger');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getServiceManager()->get('logger') can also be of type array. However, the property $logger is declared as type object<Psr\Log\LoggerInterface>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
658
        }
659
660
        if ($this->logger) {
661
            $this->logger->log($level, $message, $context);
662
        }
663
    }
664
665
    /**
666
     * Enables the debug tools.
667
     *
668
     * This method registers an error handler and an exception handler.
669
     *
670
     * If the Symfony ClassLoader component is available, a special
671
     * class loader is also registered.
672
     */
673
    protected function enableDebug()
674
    {
675
        error_reporting(-1);
676
677
        ErrorHandler::register($this->errorReportingLevel);
678
        if ('cli' !== php_sapi_name()) {
679
            $handler = ExceptionHandler::register();
680
            $handler->setAppVersion($this->getVersion());
681
        } elseif (!ini_get('log_errors') || ini_get('error_log')) {
682
            ini_set('display_errors', 1);
683
        }
684
685
        if (class_exists('Symfony\Component\ClassLoader\DebugClassLoader')) {
686
            DebugClassLoader::enable();
687
        }
688
    }
689
}
690