Completed
Pull Request — master (#152)
by Paul
03:25
created

App::setupRouters()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 46
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 46
rs 8.4752
cc 6
eloc 28
nc 6
nop 0
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
// @todo - these need moved to a new class for module->router processing
25
use PPI\Framework\Router\Router as SymfonyRouter;
26
use PPI\Framework\Router\Wrapper\AuraRouterWrapper;
27
use PPI\Framework\Router\Wrapper\SymfonyRouterWrapper;
28
use Aura\Router\Router as AuraRouter;
29
use Illuminate\Http\Request as LaravelRequest;
30
use Illuminate\Routing\Router as LaravelRouter;
31
use Illuminate\Routing\UrlGenerator as LaravelUrlGenerator;
32
use PPI\FastRoute\Wrapper\FastRouteWrapper;
33
use PPI\LaravelRouting\Wrapper\LaravelRouterWrapper;
34
35
/**
36
 * The PPI App bootstrap class.
37
 *
38
 * This class sets various app settings, and allows you to override classes used in the bootup process.
39
 *
40
 * @author     Paul Dragoonis <[email protected]>
41
 * @author     Vítor Brandão <[email protected]>
42
 */
43
class App implements AppInterface
44
{
45
    /**
46
     * Version string.
47
     *
48
     * @var string
49
     */
50
    const VERSION = '2.1.0-DEV';
51
52
    /**
53
     * @var bool
54
     */
55
    protected $booted = false;
56
57
    /**
58
     * @var bool
59
     */
60
    protected $debug;
61
62
    /**
63
     * Application environment: "dev|development" vs "prod|production".
64
     *
65
     * @var string
66
     */
67
    protected $environment;
68
69
    /**
70
     * @var \Psr\Log\LoggerInterface
71
     */
72
    protected $logger;
73
74
    /**
75
     * Unix timestamp with microseconds.
76
     *
77
     * @var float
78
     */
79
    protected $startTime;
80
81
    /**
82
     * Configuration loader.
83
     *
84
     * @var \PPI\Framework\Config\ConfigManager
85
     */
86
    protected $configManager;
87
88
    /**
89
     * The Module Manager.
90
     *
91
     * @var \Zend\ModuleManager\ModuleManager
92
     */
93
    protected $moduleManager;
94
95
    /**
96
     * @param int $errorReportingLevel The level of error reporting you want
97
     */
98
    protected $errorReportingLevel;
99
100
    /**
101
     * @var null|array
102
     */
103
    protected $matchedRoute;
104
105
    /**
106
     * @var \PPI\Framework\Module\Controller\ControllerResolver
107
     */
108
    protected $resolver;
109
110
    /**
111
     * @var string
112
     */
113
    protected $name;
114
115
    /**
116
     * Path to the application root dir aka the "app" directory.
117
     *
118
     * @var null|string
119
     */
120
    protected $rootDir;
121
122
    /**
123
     * Service Manager.
124
     *
125
     * @var \PPI\Framework\ServiceManager\ServiceManager
126
     */
127
    protected $serviceManager;
128
129
    /**
130
     * App constructor.
131
     *
132
     * @param array $options
133
     */
134
    public function __construct(array $options = array())
135
    {
136
        // Default options
137
        $this->environment = isset($options['environment']) && $options['environment'] ? (string) $options['environment'] : 'prod';
138
        $this->debug = isset($options['debug']) && null !== $options['debug'] ? (boolean) $options['debug'] : false;
139
        $this->rootDir = isset($options['rootDir']) && $options['rootDir'] ? (string) $options['rootDir'] : $this->getRootDir();
140
        $this->name = isset($options['name']) && $options['name'] ? (string) $options['name'] : $this->getName();
141
142
        if ($this->debug) {
143
            $this->startTime = microtime(true);
144
            $this->enableDebug();
145
        } else {
146
            ini_set('display_errors', 0);
147
        }
148
    }
149
150
    /**
151
     * Set an App option.
152
     *
153
     * @param $option
154
     * @param $value
155
     *
156
     * @throws \RuntimeException
157
     *
158
     * @return $this
159
     */
160
    public function setOption($option, $value)
161
    {
162
        if (true === $this->booted) {
163
            throw new \RuntimeException('Setting App options after boot() is now allowed');
164
        }
165
166
        // "root_dir" to "rootDir"
167
        $property = preg_replace('/_(.?)/e', "strtoupper('$1')", $option);
168
        if (!property_exists($this, $property)) {
169
            throw new \RuntimeException(sprintf('App property "%s" (option "%s") does not exist', $property, $option));
170
        }
171
172
        $this->$property = $value;
173
174
        return $this;
175
    }
176
177
    /**
178
     * Get an App option.
179
     *
180
     * @param $option
181
     *
182
     * @throws \RuntimeException
183
     *
184
     * @return string
185
     */
186
    public function getOption($option)
187
    {
188
        // "root_dir" to "rootDir"
189
        $property = preg_replace('/_(.?)/e', "strtoupper('$1')", $option);
190
        if (!property_exists($this, $property)) {
191
            throw new \RuntimeException(sprintf('App property "%s" (option "%s") does not exist', $property, $option));
192
        }
193
194
        return $property;
195
    }
196
197
    public function __clone()
198
    {
199
        if ($this->debug) {
200
            $this->startTime = microtime(true);
201
        }
202
203
        $this->booted = false;
204
        $this->serviceManager = null;
205
    }
206
207
    /**
208
     * Run the boot process, load our modules and their dependencies.
209
     *
210
     * This method is automatically called by dispatch(), but you can use it
211
     * to build all services when not handling a request.
212
     *
213
     * @return $this
214
     */
215
    public function boot()
216
    {
217
        if (true === $this->booted) {
218
            return $this;
219
        }
220
221
        $this->serviceManager = $this->buildServiceManager();
222
        $this->log('debug', sprintf('Booting %s ...', $this->name));
223
224
        // Loading our Modules
225
        $this->getModuleManager()->loadModules();
226
        if ($this->debug) {
227
            $modules = $this->getModuleManager()->getModules();
228
            $this->log('debug', sprintf('All modules online (%d): "%s"', count($modules), implode('", "', $modules)));
229
        }
230
231
        // Lets get all the services our of our modules and start setting them in the ServiceManager
232
        $moduleServices = $this->serviceManager->get('ModuleDefaultListener')->getServices();
233
        foreach ($moduleServices as $key => $service) {
234
            $this->serviceManager->setFactory($key, $service);
235
        }
236
237
        $this->booted = true;
238
        if ($this->debug) {
239
            $this->log('debug', sprintf('%s has booted (in %.3f secs)', $this->name, microtime(true) - $this->startTime));
240
        }
241
242
        return $this;
243
    }
244
245
    /**
246
     * Run the application and send the response.
247
     *
248
     * @param HttpRequest|null $request
249
     * @param HttpRequest|null $request
250
     *
251
     * @throws \Exception
252
     *
253
     * @return Response
254
     */
255
    public function run(HttpRequest $request = null, HttpResponse $response = null)
256
    {
257
        if (false === $this->booted) {
258
            $this->boot();
259
        }
260
261
        $request = $request ?: HttpRequest::createFromGlobals();
262
        $response = $response ?: new HttpResponse();
263
264
        $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...
265
        $response->send();
266
267
        return $response;
268
    }
269
270
    /**
271
     * Decide on a route to use and dispatch our module's controller action.
272
     *
273
     * @param HttpRequest  $request
274
     * @param HttpResponse $response
275
     *
276
     * @throws \Exception
277
     *
278
     * @return Response
279
     */
280
    public function dispatch(HttpRequest $request, HttpResponse $response)
281
    {
282
        if (false === $this->booted) {
283
            $this->boot();
284
        }
285
286
        // Routing
287
        $routeParams = $this->handleRouting($request);
288
        $request->attributes->add($routeParams);
289
290
        // Resolve our Controller
291
        $resolver = $this->serviceManager->get('ControllerResolver');
292
        if (false === $controller = $resolver->getController($request)) {
293
            throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s".', $request->getPathInfo()));
294
        }
295
296
        $controllerArguments = $resolver->getArguments($request, $controller);
297
298
        $result = call_user_func_array(
299
            $controller,
300
            $controllerArguments
301
        );
302
303
        if ($result === null) {
304
            throw new \Exception('Your action returned null. It must always return something');
305
        } elseif (is_string($result)) {
306
            $response->setContent($result);
307
        } elseif ($result instanceof SymfonyResponse || $response instanceof HttpResponse) {
308
            $response = $result;
309
        } else {
310
            throw new \Exception('Invalid response type returned from controller');
311
        }
312
313
        $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...
314
315
        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...
316
    }
317
318
    /**
319
     * Gets the name of the application.
320
     *
321
     * @return string The application name
322
     *
323
     * @api
324
     */
325
    public function getName()
326
    {
327
        if (null === $this->name) {
328
            $this->name = preg_replace('/[^a-zA-Z0-9_]+/', '', basename($this->rootDir));
329
        }
330
331
        return $this->name;
332
    }
333
334
    /**
335
     * Gets the version of the application.
336
     *
337
     * @return string The application version
338
     *
339
     * @api
340
     */
341
    public function getVersion()
342
    {
343
        return self::VERSION;
344
    }
345
346
    /**
347
     * Get the environment mode the application is in.
348
     *
349
     * @return string The current environment
350
     *
351
     * @api
352
     */
353
    public function getEnvironment()
354
    {
355
        return $this->environment;
356
    }
357
358
    /**
359
     * @param $env
360
     *
361
     * @return bool
362
     */
363
    public function isEnvironment($env)
364
    {
365
        if ('development' == $env) {
366
            $env = 'dev';
367
        } elseif ('production' == $env) {
368
            $env = 'prod';
369
        }
370
371
        return $this->getEnvironment() == $env;
372
    }
373
374
    /**
375
     * Checks if debug mode is enabled.
376
     *
377
     * @return bool true if debug mode is enabled, false otherwise
378
     *
379
     * @api
380
     */
381
    public function isDebug()
382
    {
383
        return $this->debug;
384
    }
385
386
    /**
387
     * Gets the application root dir.
388
     *
389
     * @return string The application root dir
390
     *
391
     * @api
392
     */
393
    public function getRootDir()
394
    {
395
        if (null === $this->rootDir) {
396
            $this->rootDir = realpath(getcwd() . '/app');
397
        }
398
399
        return $this->rootDir;
400
    }
401
402
    /**
403
     * Get the service manager.
404
     *
405
     * @return ServiceManager\ServiceManager
406
     */
407
    public function getServiceManager()
408
    {
409
        return $this->serviceManager;
410
    }
411
412
    /**
413
     * @note Added for compatibility with Symfony's HttpKernel\Kernel.
414
     *
415
     * @return null|ServiceManager\ServiceManager
416
     */
417
    public function getContainer()
418
    {
419
        return $this->serviceManager;
420
    }
421
422
    /**
423
     * Returns the Module Manager.
424
     *
425
     * @return \Zend\ModuleManager\ModuleManager
426
     */
427
    public function getModuleManager()
428
    {
429
        if (null === $this->moduleManager) {
430
            $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...
431
        }
432
433
        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 433 which is incompatible with the return type documented by PPI\Framework\App::getModuleManager of type Zend\ModuleManager\ModuleManager.
Loading history...
434
    }
435
436
    /**
437
     * Get an array of the loaded modules.
438
     *
439
     * @return array An array of Module objects, keyed by module name
440
     */
441
    public function getModules()
442
    {
443
        return $this->getModuleManager()->getLoadedModules(true);
444
    }
445
446
    /**
447
     * @see PPI\Framework\Module\ModuleManager::locateResource()
448
     *
449
     * @param string $name  A resource name to locate
450
     * @param string $dir   A directory where to look for the resource first
451
     * @param bool   $first Whether to return the first path or paths for all matching bundles
452
     *
453
     * @throws \InvalidArgumentException if the file cannot be found or the name is not valid
454
     * @throws \RuntimeException         if the name contains invalid/unsafe
455
     * @throws \RuntimeException         if a custom resource is hidden by a resource in a derived bundle
456
     *
457
     * @return string|array The absolute path of the resource or an array if $first is false
458
     */
459
    public function locateResource($name, $dir = null, $first = true)
460
    {
461
        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...
462
    }
463
464
    /**
465
     * Gets the request start time (not available if debug is disabled).
466
     *
467
     * @return int The request start timestamp
468
     *
469
     * @api
470
     */
471
    public function getStartTime()
472
    {
473
        return $this->debug ? $this->startTime : -INF;
474
    }
475
476
    /**
477
     * Gets the cache directory.
478
     *
479
     * @return string The cache directory
480
     *
481
     * @api
482
     */
483
    public function getCacheDir()
484
    {
485
        return $this->rootDir . '/cache/' . $this->environment;
486
    }
487
488
    /**
489
     * Gets the log directory.
490
     *
491
     * @return string The log directory
492
     *
493
     * @api
494
     */
495
    public function getLogDir()
496
    {
497
        return $this->rootDir . '/logs';
498
    }
499
500
    /**
501
     * Gets the charset of the application.
502
     *
503
     * @return string The charset
504
     *
505
     * @api
506
     */
507
    public function getCharset()
508
    {
509
        return 'UTF-8';
510
    }
511
512
    /**
513
     * Returns a ConfigManager instance.
514
     *
515
     * @return \PPI\Framework\Config\ConfigManager
516
     */
517
    public function getConfigManager()
518
    {
519
        if (null === $this->configManager) {
520
            $cachePath = $this->getCacheDir() . '/application-config-cache.' . $this->getName() . '.php';
521
            $this->configManager = new ConfigManager($cachePath, !$this->debug, $this->rootDir . '/config');
522
        }
523
524
        return $this->configManager;
525
    }
526
527
    /**
528
     * Loads a configuration file or PHP array.
529
     *
530
     * @param  $resource
531
     * @param null $type
532
     *
533
     * @return App The current instance
534
     */
535
    public function loadConfig($resource, $type = null)
536
    {
537
        $this->getConfigManager()->addConfig($resource, $type);
538
539
        return $this;
540
    }
541
542
    /**
543
     * Returns the application configuration.
544
     *
545
     * @throws \RuntimeException
546
     *
547
     * @return array|object
548
     */
549
    public function getConfig()
550
    {
551
        if (!$this->booted) {
552
            throw new \RuntimeException('The "Config" service is only available after the App boot()');
553
        }
554
555
        return $this->serviceManager->get('Config');
556
    }
557
558
    public function serialize()
559
    {
560
        return serialize(array($this->environment, $this->debug));
561
    }
562
563
    public function unserialize($data)
564
    {
565
        list($environment, $debug) = unserialize($data);
566
567
        $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...
568
    }
569
570
    /**
571
     * Returns the application parameters.
572
     *
573
     * @return array An array of application parameters
574
     */
575
    protected function getAppParameters()
576
    {
577
        return array_merge(
578
            array(
579
                'app.root_dir' => $this->rootDir,
580
                'app.environment' => $this->environment,
581
                'app.debug' => $this->debug,
582
                'app.name' => $this->name,
583
                'app.cache_dir' => $this->getCacheDir(),
584
                'app.logs_dir' => $this->getLogDir(),
585
                'app.charset' => $this->getCharset(),
586
            ),
587
            $this->getEnvParameters()
588
        );
589
    }
590
591
    /**
592
     * Gets the environment parameters.
593
     *
594
     * Only the parameters starting with "PPI__" are considered.
595
     *
596
     * @return array An array of parameters
597
     */
598
    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...
599
    {
600
        $parameters = array();
601
        foreach ($_SERVER as $key => $value) {
602
            if (0 === strpos($key, 'PPI__')) {
603
                $parameters[strtolower(str_replace('__', '.', substr($key, 5)))] = $value;
604
            }
605
        }
606
607
        return $parameters;
608
    }
609
610
    /**
611
     * Creates and initializes a ServiceManager instance.
612
     *
613
     * @return ServiceManager The compiled service manager
614
     */
615
    protected function buildServiceManager()
616
    {
617
        // ServiceManager creation
618
        $serviceManager = new ServiceManagerBuilder($this->getConfigManager()->getMergedConfig());
619
        $serviceManager->build($this->getAppParameters());
620
        $serviceManager->set('app', $this);
621
622
        return $serviceManager;
623
    }
624
625
    /**
626
     * Perform the matching of a route and return a set of routing parameters if a valid one is found.
627
     * Otherwise exceptions get thrown.
628
     *
629
     * @param HttpRequest $request
630
     *
631
     * @throws \Exception
632
     *
633
     * @return array
634
     */
635
    protected function handleRouting(HttpRequest $request)
636
    {
637
        $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...
638
        $this->router->warmUp($this->getCacheDir());
639
640
        try {
641
            // Lets load up our router and match the appropriate route
642
            $parameters = $this->router->matchRequest($request);
643 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...
644
                if (null !== $this->logger) {
645
                    $this->logger->info(sprintf('Matched route "%s" (parameters: %s)', $parameters['_route'], $this->router->parametersToString($parameters)));
646
                }
647
            }
648
        } catch (ResourceNotFoundException $e) {
649
            $routeUri = $this->router->generate('Framework_404');
650
            $parameters = $this->router->matchRequest($request::create($routeUri));
651
        } catch (\Exception $e) {
652
            throw $e;
653
        }
654
655
        $parameters['_route_params'] = $parameters;
656
657
        return $parameters;
658
    }
659
660
    /**
661
     * Logs with an arbitrary level.
662
     *
663
     * @param mixed  $level
664
     * @param string $message
665
     * @param array  $context
666
     */
667
    protected function log($level, $message, array $context = array())
668
    {
669
        if (null === $this->logger && $this->getServiceManager()->has('logger')) {
670
            $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...
671
        }
672
673
        if ($this->logger) {
674
            $this->logger->log($level, $message, $context);
675
        }
676
    }
677
678
    /**
679
     * Enables the debug tools.
680
     *
681
     * This method registers an error handler and an exception handler.
682
     *
683
     * If the Symfony ClassLoader component is available, a special
684
     * class loader is also registered.
685
     */
686
    protected function enableDebug()
687
    {
688
        error_reporting(-1);
689
690
        ErrorHandler::register($this->errorReportingLevel);
691
        if ('cli' !== php_sapi_name()) {
692
            $handler = ExceptionHandler::register();
693
            $handler->setAppVersion($this->getVersion());
694
        } elseif (!ini_get('log_errors') || ini_get('error_log')) {
695
            ini_set('display_errors', 1);
696
        }
697
698
        if (class_exists('Symfony\Component\ClassLoader\DebugClassLoader')) {
699
            DebugClassLoader::enable();
700
        }
701
    }
702
703
    protected function setupRouters()
704
    {
705
706
        $requestContext  = $this->getServiceManager()->get('RouterRequestContext');
707
        $chainRouter->setContext($requestContext);
0 ignored issues
show
Bug introduced by
The variable $chainRouter does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
708
709
        $allModuleRoutes = $this->getServiceManager()->get('ModuleDefaultListener')->getRoutes();
710
711
        // For each module, add a matching instance type to the chain router
712
        foreach ($allModuleRoutes as $moduleName => $moduleRoutingResponse) {
713
            switch (true) {
714
                // @todo - move this to a separate method()
715
                case $moduleRoutingResponse instanceof SymfonyRouteCollection:
0 ignored issues
show
Bug introduced by
The class PPI\Framework\SymfonyRouteCollection does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
716
                    $sfRouter = new SymfonyRouter($requestContext, $moduleRoutingResponse, $routerOptions, $logger);
0 ignored issues
show
Documentation introduced by
$requestContext is of type object|array, but the function expects a object<Symfony\Component\Routing\RequestContext>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Bug introduced by
The variable $routerOptions does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $logger does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
717
                    $sfRouterWrapper = new SymfonyRouterWrapper($sfRouter);
718
                    $chainRouter->add($sfRouterWrapper);
719
                    break;
720
721
                // @todo - move this to a separate method()
722
                case $moduleRoutingResponse instanceof AuraRouter:
0 ignored issues
show
Bug introduced by
The class Aura\Router\Router does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
723
                    $auraRouterWrapper = new AuraRouterWrapper($moduleRoutingResponse);
724
                    $chainRouter->add($auraRouterWrapper);
725
                    break;
726
727
                // @todo - move this to a separate method()
728
                case $moduleRoutingResponse instanceof LaravelRouter:
0 ignored issues
show
Bug introduced by
The class Illuminate\Routing\Router does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
729
                    $laravelRequest = new LaravelRequest();
730
                    $laravelUrlGenerator = new LaravelUrlGenerator($moduleRoutingResponse->getRoutes(), $laravelRequest);
731
                    $laravelRouterWrapper = new LaravelRouterWrapper(
732
                        $moduleRoutingResponse, $laravelRequest, $laravelUrlGenerator
733
                    );
734
                    // @todo - solve this problem
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% 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...
735
//                    $laravelRouterWrapper->setModuleName($this->getName());
736
                    $chainRouter->add($laravelRouterWrapper);
737
                    break;
738
739
                case $moduleRoutingResponse instanceof FastRouteWrapper:
0 ignored issues
show
Bug introduced by
The class PPI\FastRoute\Wrapper\FastRouteWrapper does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
740
                    $chainRouter->add($moduleRoutingResponse);
741
                    break;
742
743
                default:
744
                    throw new \Exception('Unexpected routes value return from module: ' . $moduleName .
745
                        '. found value of type: ' . gettype($moduleRoutingResponse));
746
            }
747
        }
748
    }
749
750
751
}
752