Completed
Push — master ( 5ea837...9dec3c )
by Paul
9s
created

App   D

Complexity

Total Complexity 74

Size/Duplication

Total Lines 638
Duplicated Lines 0.78 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 32
Bugs 3 Features 1
Metric Value
wmc 74
c 32
b 3
f 1
lcom 1
cbo 13
dl 5
loc 638
rs 4.2446

31 Methods

Rating   Name   Duplication   Size   Complexity  
C __construct() 0 15 10
A __clone() 0 9 2
B boot() 0 33 6
B run() 0 31 6
C dispatch() 0 45 7
A getName() 0 8 2
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 setSymfonyKernel() 0 4 1
A unserialize() 0 6 1
A getAppParameters() 0 15 1
A getEnvParameters() 0 11 3
A buildServiceManager() 0 9 1
B handleRouting() 5 24 5
A log() 0 10 4

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-2016 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\Http\Request as HttpRequest;
15
use PPI\Framework\Http\Response as HttpResponse;
16
use PPI\Framework\Router\ChainRouter;
17
use PPI\Framework\ServiceManager\ServiceManager;
18
use PPI\Framework\ServiceManager\ServiceManagerBuilder;
19
use PPI\Framework\Debug\ExceptionHandler;
20
use Symfony\Component\Debug\Debug;
21
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
22
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
23
use Symfony\Component\HttpKernel\KernelInterface;
24
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
25
26
/**
27
 * The PPI App bootstrap class.
28
 *
29
 * This class sets various app settings, and allows you to override classes used in the bootup process.
30
 *
31
 * @author     Paul Dragoonis <[email protected]>
32
 * @author     Vítor Brandão <[email protected]>
33
 */
34
class App implements AppInterface
35
{
36
    /**
37
     * Version string.
38
     *
39
     * @var string
40
     */
41
    const VERSION = '2.2.0-DEV';
42
43
    /**
44
     * @var bool
45
     */
46
    protected $booted = false;
47
48
    /**
49
     * @var bool
50
     */
51
    protected $debug;
52
53
    /**
54
     * Application environment: "dev|development" vs "prod|production".
55
     *
56
     * @var string
57
     */
58
    protected $environment;
59
60
    /**
61
     * @var \Psr\Log\LoggerInterface
62
     */
63
    protected $logger;
64
65
    /**
66
     * Unix timestamp with microseconds.
67
     *
68
     * @var float
69
     */
70
    protected $startTime;
71
72
    /**
73
     * Configuration loader.
74
     *
75
     * @var \PPI\Framework\Config\ConfigManager
76
     */
77
    protected $configManager;
78
79
    /**
80
     * The Module Manager.
81
     *
82
     * @var \Zend\ModuleManager\ModuleManager
83
     */
84
    protected $moduleManager;
85
86
    /**
87
     * @param int $errorReportingLevel The level of error reporting you want
88
     */
89
    protected $errorReportingLevel;
90
91
    /**
92
     * @var null|array
93
     */
94
    protected $matchedRoute;
95
96
    /**
97
     * @var \PPI\Framework\Module\Controller\ControllerResolver
98
     */
99
    protected $resolver;
100
101
    /**
102
     * @var string
103
     */
104
    protected $name;
105
106
    /**
107
     * Path to the application root dir aka the "app" directory.
108
     *
109
     * @var null|string
110
     */
111
    protected $rootDir;
112
113
    /**
114
     * Service Manager.
115
     *
116
     * @var \PPI\Framework\ServiceManager\ServiceManager
117
     */
118
    protected $serviceManager;
119
120
    /**
121
     * @var ChainRouter
122
     */
123
    private $router;
124
125
    /**
126
     * @var KernelInterface
127
     */
128
    private $symfonyKernel;
129
130
    /**
131
     * App constructor.
132
     *
133
     * @param array $options
134
     */
135
    public function __construct(array $options = array())
136
    {
137
        // Default options
138
        $this->environment = isset($options['environment']) && $options['environment'] ? (string) $options['environment'] : 'prod';
139
        $this->debug = isset($options['debug']) && null !== $options['debug'] ? (boolean) $options['debug'] : false;
140
        $this->rootDir = isset($options['rootDir']) && $options['rootDir'] ? (string) $options['rootDir'] : $this->getRootDir();
141
        $this->name = isset($options['name']) && $options['name'] ? (string) $options['name'] : $this->getName();
142
143
        if ($this->debug) {
144
            $this->startTime = microtime(true);
145
            Debug::enable();
146
        } else {
147
            ini_set('display_errors', 0);
148
        }
149
    }
150
151
    public function __clone()
152
    {
153
        if ($this->debug) {
154
            $this->startTime = microtime(true);
155
        }
156
157
        $this->booted = false;
158
        $this->serviceManager = null;
159
    }
160
161
    /**
162
     * Run the boot process, load our modules and their dependencies.
163
     *
164
     * This method is automatically called by dispatch(), but you can use it
165
     * to build all services when not handling a request.
166
     *
167
     * @return $this
168
     */
169
    public function boot()
170
    {
171
        if (true === $this->booted) {
172
            return $this;
173
        }
174
175
        if($this->isDebug()) {
176
            ExceptionHandler::register(true, 'UTF-8', 'PPI Framework', self::VERSION, true);
0 ignored issues
show
Unused Code introduced by
The call to ExceptionHandler::register() has too many arguments starting with self::VERSION.

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...
177
        }
178
179
        $this->serviceManager = $this->buildServiceManager();
180
        $this->log('debug', sprintf('Booting %s ...', $this->name));
181
182
        // Loading our Modules
183
        $this->getModuleManager()->loadModules();
184
        if ($this->debug) {
185
            $modules = $this->getModuleManager()->getModules();
186
            $this->log('debug', sprintf('All modules online (%d): "%s"', count($modules), implode('", "', $modules)));
187
        }
188
189
        // Lets get all the services our of our modules and start setting them in the ServiceManager
190
        $moduleServices = $this->serviceManager->get('ModuleDefaultListener')->getServices();
191
        foreach ($moduleServices as $key => $service) {
192
            $this->serviceManager->setFactory($key, $service);
193
        }
194
195
        $this->booted = true;
196
        if ($this->debug) {
197
            $this->log('debug', sprintf('%s has booted (in %.3f secs)', $this->name, microtime(true) - $this->startTime));
198
        }
199
200
        return $this;
201
    }
202
203
    /**
204
     * Run the application and send the response.
205
     *
206
     * @param HttpRequest|null  $request
207
     * @param HttpResponse|null $response
208
     *
209
     * @throws \Exception
210
     *
211
     * @return HttpResponse
212
     */
213
    public function run(HttpRequest $request = null, HttpResponse $response = null)
214
    {
215
        if (false === $this->booted) {
216
            $this->boot();
217
        }
218
219
        if (null === $request) {
220
            $request = HttpRequest::createFromGlobals();
221
        }
222
223
        if (null === $response) {
224
            $response = new HttpResponse();
225
        }
226
227
        // Create a copy of request, as it's by-ref passed into $this->dispatch() and gets modified.
228
        $cleanRequest = clone $request;
229
        try {
230
            $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...
231
        } catch (ResourceNotFoundException $e) {
232
233
            if($this->symfonyKernel === null) {
234
                throw $e;
235
            }
236
            $response = $this->symfonyKernel->handle($cleanRequest);
237
        }
238
239
240
        $response->send();
241
242
        return $response;
243
    }
244
245
    /**
246
     * Decide on a route to use and dispatch our module's controller action.
247
     *
248
     * @param HttpRequest  $request
249
     * @param HttpResponse $response
250
     *
251
     * @throws \Exception
252
     *
253
     * @return HttpResponse
254
     */
255
    public function dispatch(HttpRequest $request, HttpResponse $response)
256
    {
257
        if (false === $this->booted) {
258
            $this->boot();
259
        }
260
261
262
        // cache like a mother fucker
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
263
//        if(!$this->hasRouteInCache($request)) {
264
            $routeParams = $this->handleRouting($request);
265
//            $this->setRouteInCache($request, $routeParams);
0 ignored issues
show
Unused Code Comprehensibility introduced by
73% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
266
            // @todo - move these 2 lines to setRouteInCache()
0 ignored issues
show
Unused Code Comprehensibility introduced by
46% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
267
//            $routingCache = $this->serviceManager->get('RoutingCache');
268
//            $routingCache->set($request->getPathInfo(), $routeParams);
269
//        } else {
270
//            $routeParams = $this->getRouteFromCache($request);
271
//        }
272
273
        $request->attributes->add($routeParams);
274
275
        // Resolve our Controller
276
        $resolver = $this->serviceManager->get('ControllerResolver');
277
        if (false === $controller = $resolver->getController($request)) {
278
            throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s".', $request->getPathInfo()));
279
        }
280
281
        $controllerArguments = $resolver->getArguments($request, $controller);
282
283
        $result = call_user_func_array(
284
            $controller,
285
            $controllerArguments
286
        );
287
288
        if ($result === null) {
289
            throw new \Exception('Your action returned null. It must always return something');
290
        } elseif (is_string($result)) {
291
            $response->setContent($result);
292
        } elseif ($result instanceof SymfonyResponse || $response instanceof HttpResponse) {
293
            $response = $result;
294
        } else {
295
            throw new \Exception('Invalid response type returned from controller');
296
        }
297
298
        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\Http\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...
299
    }
300
301
    /**
302
     * Gets the name of the application.
303
     *
304
     * @return string The application name
305
     *
306
     * @api
307
     */
308
    public function getName()
309
    {
310
        if (null === $this->name) {
311
            $this->name = preg_replace('/[^a-zA-Z0-9_]+/', '', basename($this->rootDir));
312
        }
313
314
        return $this->name;
315
    }
316
317
    /**
318
     * Gets the version of the application.
319
     *
320
     * @return string The application version
321
     *
322
     * @api
323
     */
324
    public function getVersion()
325
    {
326
        return self::VERSION;
327
    }
328
329
    /**
330
     * Get the environment mode the application is in.
331
     *
332
     * @return string The current environment
333
     *
334
     * @api
335
     */
336
    public function getEnvironment()
337
    {
338
        return $this->environment;
339
    }
340
341
    /**
342
     * @param $env
343
     *
344
     * @return bool
345
     */
346
    public function isEnvironment($env)
347
    {
348
        if ('development' == $env) {
349
            $env = 'dev';
350
        } elseif ('production' == $env) {
351
            $env = 'prod';
352
        }
353
354
        return $this->getEnvironment() == $env;
355
    }
356
357
    /**
358
     * Checks if debug mode is enabled.
359
     *
360
     * @return bool true if debug mode is enabled, false otherwise
361
     *
362
     * @api
363
     */
364
    public function isDebug()
365
    {
366
        return $this->debug;
367
    }
368
369
    /**
370
     * Gets the application root dir.
371
     *
372
     * @return string The application root dir
373
     *
374
     * @api
375
     */
376
    public function getRootDir()
377
    {
378
        if (null === $this->rootDir) {
379
            $this->rootDir = realpath(getcwd() . '/app');
380
        }
381
382
        return $this->rootDir;
383
    }
384
385
    /**
386
     * Get the service manager.
387
     *
388
     * @return ServiceManager
389
     */
390
    public function getServiceManager()
391
    {
392
        return $this->serviceManager;
393
    }
394
395
    /**
396
     * @note Added for compatibility with Symfony's HttpKernel\Kernel.
397
     *
398
     * @return null|ServiceManager
399
     */
400
    public function getContainer()
401
    {
402
        return $this->serviceManager;
403
    }
404
405
    /**
406
     * Returns the Module Manager.
407
     *
408
     * @return \Zend\ModuleManager\ModuleManager
409
     */
410
    public function getModuleManager()
411
    {
412
        if (null === $this->moduleManager) {
413
            $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...
414
        }
415
416
        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 416 which is incompatible with the return type documented by PPI\Framework\App::getModuleManager of type Zend\ModuleManager\ModuleManager.
Loading history...
417
    }
418
419
    /**
420
     * Get an array of the loaded modules.
421
     *
422
     * @return array An array of Module objects, keyed by module name
423
     */
424
    public function getModules()
425
    {
426
        return $this->getModuleManager()->getLoadedModules(true);
427
    }
428
429
    /**
430
     * @see PPI\Framework\Module\ModuleManager::locateResource()
431
     *
432
     * @param string $name  A resource name to locate
433
     * @param string $dir   A directory where to look for the resource first
434
     * @param bool   $first Whether to return the first path or paths for all matching bundles
435
     *
436
     * @throws \InvalidArgumentException if the file cannot be found or the name is not valid
437
     * @throws \RuntimeException         if the name contains invalid/unsafe
438
     * @throws \RuntimeException         if a custom resource is hidden by a resource in a derived bundle
439
     *
440
     * @return string|array The absolute path of the resource or an array if $first is false
441
     */
442
    public function locateResource($name, $dir = null, $first = true)
443
    {
444
        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...
445
    }
446
447
    /**
448
     * Gets the request start time (not available if debug is disabled).
449
     *
450
     * @return int The request start timestamp
451
     *
452
     * @api
453
     */
454
    public function getStartTime()
455
    {
456
        return $this->debug ? $this->startTime : -INF;
457
    }
458
459
    /**
460
     * Gets the cache directory.
461
     *
462
     * @return string The cache directory
463
     *
464
     * @api
465
     */
466
    public function getCacheDir()
467
    {
468
        return $this->rootDir . '/cache/' . $this->environment;
469
    }
470
471
    /**
472
     * Gets the log directory.
473
     *
474
     * @return string The log directory
475
     *
476
     * @api
477
     */
478
    public function getLogDir()
479
    {
480
        return $this->rootDir . '/logs';
481
    }
482
483
    /**
484
     * Gets the charset of the application.
485
     *
486
     * @return string The charset
487
     *
488
     * @api
489
     */
490
    public function getCharset()
491
    {
492
        return 'UTF-8';
493
    }
494
495
    /**
496
     * Returns a ConfigManager instance.
497
     *
498
     * @return \PPI\Framework\Config\ConfigManager
499
     */
500
    public function getConfigManager()
501
    {
502
        if (null === $this->configManager) {
503
            $cachePath = $this->getCacheDir() . '/application-config-cache.' . $this->getName() . '.php';
504
            $this->configManager = new ConfigManager($cachePath, !$this->debug, $this->rootDir . '/config');
505
        }
506
507
        return $this->configManager;
508
    }
509
510
    /**
511
     * Loads a configuration file or PHP array.
512
     *
513
     * @param  $resource
514
     * @param null $type
515
     *
516
     * @return App The current instance
517
     */
518
    public function loadConfig($resource, $type = null)
519
    {
520
        $this->getConfigManager()->addConfig($resource, $type);
521
522
        return $this;
523
    }
524
525
    /**
526
     * Returns the application configuration.
527
     *
528
     * @throws \RuntimeException
529
     *
530
     * @return array|object
531
     */
532
    public function getConfig()
533
    {
534
        if (!$this->booted) {
535
            throw new \RuntimeException('The "Config" service is only available after the App boot()');
536
        }
537
538
        return $this->serviceManager->get('Config');
539
    }
540
541
    /**
542
     * @return string
543
     */
544
    public function serialize()
545
    {
546
        return serialize(array($this->environment, $this->debug));
547
    }
548
549
    /**
550
     * @param KernelInterface $kernel
551
     */
552
    public function setSymfonyKernel(KernelInterface $kernel)
553
    {
554
        $this->symfonyKernel = $kernel;
555
    }
556
557
    public function unserialize($data)
558
    {
559
        list($environment, $debug) = unserialize($data);
560
561
        $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...
562
    }
563
564
    /**
565
     * Returns the application parameters.
566
     *
567
     * @return array An array of application parameters
568
     */
569
    protected function getAppParameters()
570
    {
571
        return array_merge(
572
            array(
573
                'app.root_dir' => $this->rootDir,
574
                'app.environment' => $this->environment,
575
                'app.debug' => $this->debug,
576
                'app.name' => $this->name,
577
                'app.cache_dir' => $this->getCacheDir(),
578
                'app.logs_dir' => $this->getLogDir(),
579
                'app.charset' => $this->getCharset(),
580
            ),
581
            $this->getEnvParameters()
582
        );
583
    }
584
585
    /**
586
     * Gets the environment parameters.
587
     *
588
     * Only the parameters starting with "PPI__" are considered.
589
     *
590
     * @return array An array of parameters
591
     */
592
    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...
593
    {
594
        $parameters = array();
595
        foreach ($_SERVER as $key => $value) {
596
            if (0 === strpos($key, 'PPI__')) {
597
                $parameters[strtolower(str_replace('__', '.', substr($key, 5)))] = $value;
598
            }
599
        }
600
601
        return $parameters;
602
    }
603
604
    /**
605
     * Creates and initializes a ServiceManager instance.
606
     *
607
     * @return ServiceManager The compiled service manager
608
     */
609
    protected function buildServiceManager()
610
    {
611
        // ServiceManager creation
612
        $serviceManager = new ServiceManagerBuilder($this->getConfigManager()->getMergedConfig());
613
        $serviceManager->build($this->getAppParameters());
614
        $serviceManager->set('app', $this);
615
616
        return $serviceManager;
617
    }
618
619
    /**
620
     * Perform the matching of a route and return a set of routing parameters if a valid one is found.
621
     * Otherwise exceptions get thrown.
622
     *
623
     * @param HttpRequest $request
624
     *
625
     * @throws \Exception
626
     *
627
     * @return array
628
     */
629
    protected function handleRouting(HttpRequest $request)
630
    {
631
        $this->router = $this->serviceManager->get('Router');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->serviceManager->get('Router') can also be of type array. However, the property $router is declared as type object<PPI\Framework\Router\ChainRouter>. 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...
632
        $this->router->warmUp($this->getCacheDir());
633
634
        try {
635
            // Lets load up our router and match the appropriate route
636
            $parameters = $this->router->matchRequest($request);
637 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...
638
                if (null !== $this->logger) {
639
                    $this->logger->info(sprintf('Matched route "%s" (parameters: %s)', $parameters['_route'], $this->router->parametersToString($parameters)));
640
                }
641
            }
642
        } catch (ResourceNotFoundException $e) {
643
            $routeUri = $this->router->generate('Framework_404');
644
            $parameters = $this->router->matchRequest($request::create($routeUri));
645
        } catch (\Exception $e) {
646
            throw $e;
647
        }
648
649
        $parameters['_route_params'] = $parameters;
650
651
        return $parameters;
652
    }
653
654
    /**
655
     * Logs with an arbitrary level.
656
     *
657
     * @param mixed  $level
658
     * @param string $message
659
     * @param array  $context
660
     */
661
    protected function log($level, $message, array $context = array())
662
    {
663
        if (null === $this->logger && $this->getServiceManager()->has('logger')) {
664
            $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...
665
        }
666
667
        if ($this->logger) {
668
            $this->logger->log($level, $message, $context);
669
        }
670
    }
671
}
672