Completed
Pull Request — master (#154)
by Vítor
03:03
created

App   C

Complexity

Total Complexity 73

Size/Duplication

Total Lines 637
Duplicated Lines 0.78 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 27
Bugs 2 Features 0
Metric Value
wmc 73
c 27
b 2
f 0
lcom 1
cbo 10
dl 5
loc 637
rs 5.3576

32 Methods

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

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
Bug introduced by
It seems like $response defined by $this->dispatch($request, $response) on line 254 can be null; however, PPI\Framework\App::dispatch() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

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