Completed
Pull Request — master (#149)
by Paul
03:16 queued 14s
created

App::buildServiceManager()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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