GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — 3.0 ( 06e746...c52ac9 )
by Vermeulen
04:55
created

Application   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 677
Duplicated Lines 1.33 %

Coupling/Cohesion

Components 2
Dependencies 11

Importance

Changes 14
Bugs 0 Features 1
Metric Value
wmc 57
c 14
b 0
f 1
lcom 2
cbo 11
dl 9
loc 677
rs 5.4229

37 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
A getInstance() 9 9 2
A getCli() 0 4 1
A getComposerLoader() 0 4 1
A getConfig() 0 4 1
A getErrors() 0 4 1
A getCtrlRouterInfos() 0 4 1
A getMemcached() 0 4 1
A getModules() 0 4 1
A getModuleForName() 0 4 1
A getOptions() 0 4 1
A getRequest() 0 4 1
A getRunSteps() 0 4 1
A getSubjectList() 0 4 1
A initSystem() 0 16 1
A initOptions() 0 14 1
A initConstants() 0 16 1
A initComposerLoader() 0 7 1
A initSubjectList() 0 4 1
A initConfig() 0 5 1
A initRequest() 0 5 1
A initSession() 0 12 2
A initErrors() 0 4 1
A initCli() 0 4 1
B initRunTasks() 0 24 3
A initModules() 0 4 1
A addComposerNamespaces() 0 6 1
A declareRunSteps() 0 12 1
A run() 0 7 1
B loadMemcached() 0 34 5
A loadAllModules() 0 17 3
A runAllCoreModules() 0 13 4
A runAllAppModules() 0 12 4
A runModule() 0 7 1
A runCliFile() 0 12 2
B initCtrlRouterLink() 0 25 2
A runCtrlRouterLink() 0 12 3

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 Application 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 Application, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace BFW;
4
5
use \Exception;
6
use \BFW\Helpers\Constants;
7
8
/**
9
 * Application class
10
 * Manage all BFW application
11
 * Load and init components, modules, ...
12
 */
13
class Application
14
{
15
    /**
16
     * @const ERR_MEMCACHED_NOT_CLASS_DEFINED Exception code if memcache(d) is
17
     * enabled but the class to use is not defined.
18
     */
19
    const ERR_MEMCACHED_NOT_CLASS_DEFINED = 1301001;
20
    
21
    /**
22
     * @const ERR_MEMCACHED_CLASS_NOT_FOUND Exception code if the memcache(d)
23
     * class is not found.
24
     */
25
    const ERR_MEMCACHED_CLASS_NOT_FOUND = 1301002;
26
    
27
    /**
28
     * @const ERR_MEMCACHED_NOT_IMPLEMENT_INTERFACE Exception code the
29
     * memcache(d) class not implement the interface.
30
     */
31
    const ERR_MEMCACHED_NOT_IMPLEMENT_INTERFACE = 1301003;
32
    
33
    /**
34
     * @var \BFW\Application|null $instance Application instance (Singleton)
35
     */
36
    protected static $instance = null;
37
38
    /**
39
     * @var string $rootDir Path to the application project directory
40
     */
41
    protected $rootDir = '';
42
43
    /**
44
     * @var \BFW\Config $config Config's instance for BFW
45
     */
46
    protected $config;
47
48
    /**
49
     * @var \BFW\Core\Options $options Option's instance for the core
50
     */
51
    protected $options;
52
53
    /**
54
     * @var \Composer\Autoload\ClassLoader $composerLoader Loader used by
55
     *  composer.
56
     */
57
    protected $composerLoader;
58
59
    /**
60
     * @var array[] $runSteps All steps used for run the application
61
     */
62
    protected $runSteps = [];
63
64
    /**
65
     * @var Object $memcached The class used to connect to memcache(d) server.
66
     * The class name should be declared into config file.
67
     */
68
    protected $memcached;
69
70
    /**
71
     * @var \BFW\Request $request Informations about the http request
72
     */
73
    protected $request;
74
75
    /**
76
     * @var \BFW\Modules $modules System who manage all modules
77
     */
78
    protected $modules;
79
    
80
    /**
81
     * @var \BFW\Core\Errors $errors System who manage personal errors page
82
     */
83
    protected $errors;
84
    
85
    /**
86
     * @var \BFW\Core\Cli $cli Cli system
87
     */
88
    protected $cli;
89
    
90
    /**
91
     * @var \BFW\SubjectList $subjectList System who manage subjects list
92
     */
93
    protected $subjectList;
94
    
95
    /**
96
     * @var \stdClass $ctrlRouterInfos Infos from router for controller system
97
     */
98
    protected $ctrlRouterInfos;
99
100
    /**
101
     * Constructor
102
     * Init output buffering
103
     * Declare run steps
104
     * Set UTF-8 header
105
     * 
106
     * protected for Singleton pattern
107
     */
108
    protected function __construct()
109
    {
110
        //Start the output buffering
111
        ob_start();
112
113
        $this->declareRunSteps();
114
115
        //Defaut http header. Define here add possiblity to override him
116
        header('Content-Type: text/html; charset=utf-8');
117
        
118
        //Default charset to UTF-8. Define here add possiblity to override him
119
        ini_set('default_charset', 'UTF-8');
120
    }
121
122
    /**
123
     * Get the Application instance (Singleton pattern)
124
     * 
125
     * @return \BFW\Application The current instance of this class
126
     */
127 View Code Duplication
    public static function getInstance()
128
    {
129
        if (self::$instance === null) {
130
            $calledClass = get_called_class(); //Autorize extends this class
131
            self::$instance = new $calledClass;
132
        }
133
134
        return self::$instance;
135
    }
136
137
    /**
138
     * Getter to access to cli property
139
     * 
140
     * @return \BFW\Core\Cli
141
     */
142
    public function getCli()
143
    {
144
        return $this->cli;
145
    }
146
147
    /**
148
     * Getter to access to composerLoader property
149
     * 
150
     * @return \Composer\Autoload\ClassLoader The composer class loader
151
     */
152
    public function getComposerLoader()
153
    {
154
        return $this->composerLoader;
155
    }
156
157
    /**
158
     * Getter to access to the config instance
159
     * 
160
     * @return \BFW\Config
161
     */
162
    public function getConfig()
163
    {
164
        return $this->config;
165
    }
166
    
167
    /**
168
     * Getter to access to the errors instance
169
     * 
170
     * @return \BFW\Errors
171
     */
172
    public function getErrors()
173
    {
174
        return $this->errors;
175
    }
176
    
177
    /**
178
     * Getter to access to the ctrlRouterInfos property
179
     * 
180
     * @return null|\stdClass
181
     */
182
    public function getCtrlRouterInfos()
183
    {
184
        return $this->ctrlRouterInfos;
185
    }
186
    
187
    /**
188
     * Getter to access to memcache instance
189
     * 
190
     * @return Object|null
191
     */
192
    public function getMemcached()
193
    {
194
        return $this->memcached;
195
    }
196
    
197
    /**
198
     * Getter to access to modules system
199
     * 
200
     * @return \BFW\Modules
201
     */
202
    public function getModules()
203
    {
204
        return $this->modules;
205
    }
206
    
207
    /**
208
     * Getter to access to a module
209
     * 
210
     * @param string $moduleName The module name to access
211
     * 
212
     * @return \BFW\Module
213
     */
214
    public function getModuleForName($moduleName)
215
    {
216
        return $this->modules->getModuleForName($moduleName);
217
    }
218
219
    /**
220
     * Getter to access to the options system
221
     * 
222
     * @return mixed
223
     */
224
    public function getOptions()
225
    {
226
        return $this->options;
227
    }
228
    
229
    /**
230
     * Getter to access to the Request instance
231
     * 
232
     * @return \BFW\Request
233
     */
234
    public function getRequest()
235
    {
236
        return $this->request;
237
    }
238
    
239
    /**
240
     * Getter to access to the run step array
241
     * 
242
     * @return array
243
     */
244
    public function getRunSteps()
245
    {
246
        return $this->runSteps;
247
    }
248
249
    /**
250
     * Getter to access to the subjects list
251
     * 
252
     * @return \BFW\SubjectList
253
     */
254
    public function getSubjectList()
255
    {
256
        return $this->subjectList;
257
    }
258
    
259
    /**
260
     * Initialize all components
261
     * 
262
     * @param array $options Options passed to application
263
     * 
264
     * @return void
265
     */
266
    public function initSystem($options)
267
    {
268
        $this->initOptions($options);
269
        $this->initConstants();
270
        $this->initComposerLoader();
271
        $this->initSubjectList();
272
        $this->initConfig();
273
        $this->initRequest();
274
        $this->initSession();
275
        $this->initErrors();
276
        $this->initCli();
277
        $this->initRunTasks();
278
        $this->initModules();
279
        
280
        return $this;
281
    }
282
283
    /**
284
     * Initialize options with the class \BFW\Core\Options
285
     * 
286
     * @param array $options The option passed when initialize this class
287
     */
288
    protected function initOptions($options)
289
    {
290
        $defaultOptions = [
291
            'rootDir'    => null,
292
            'vendorDir'  => null,
293
            'runSession' => true
294
        ];
295
296
        $this->options = new \BFW\Core\Options($defaultOptions, $options);
297
        $this->options
298
            ->searchPaths()
299
            ->checkPaths()
300
        ;
301
    }
302
303
    /**
304
     * Initialize all constants used by framework
305
     * Use helper Constants::create to allow override of constants
306
     * 
307
     * @return void
308
     */
309
    protected function initConstants()
310
    {
311
        Constants::create('ROOT_DIR', $this->options->getValue('rootDir'));
312
313
        Constants::create('APP_DIR', ROOT_DIR.'app/');
314
        Constants::create('SRC_DIR', ROOT_DIR.'src/');
315
        Constants::create('WEB_DIR', ROOT_DIR.'web/');
316
317
        Constants::create('CONFIG_DIR', APP_DIR.'config/');
318
        Constants::create('MODULES_DIR', APP_DIR.'modules/');
319
320
        Constants::create('CLI_DIR', SRC_DIR.'cli/');
321
        Constants::create('CTRL_DIR', SRC_DIR.'controllers/');
322
        Constants::create('MODELES_DIR', SRC_DIR.'modeles/');
323
        Constants::create('VIEW_DIR', SRC_DIR.'view/');
324
    }
325
326
    /**
327
     * Initialize composer loader
328
     * Obtain the composerLoader instance
329
     * Call addComposerNamespaces method to add Application namespaces
330
     * 
331
     * @return void
332
     */
333
    protected function initComposerLoader()
334
    {
335
        $this->composerLoader = require(
336
            $this->options->getValue('vendorDir').'autoload.php'
337
        );
338
        $this->addComposerNamespaces();
339
    }
340
    
341
    /**
342
     * Initialize the subjectList object
343
     * 
344
     * @return void
345
     */
346
    protected function initSubjectList()
347
    {
348
        $this->subjectList = new \BFW\SubjectList;
349
    }
350
351
    /**
352
     * Initialize the property config with \BFW\Config instance
353
     * The config class will search all file in "bfw" directory and load files
354
     * 
355
     * @return void
356
     */
357
    protected function initConfig()
358
    {
359
        $this->config = new \BFW\Config('bfw');
360
        $this->config->loadFiles();
361
    }
362
363
    /**
364
     * Initialize request property with the \BFW\Request class
365
     * 
366
     * @return void
367
     */
368
    protected function initRequest()
369
    {
370
        $this->request = \BFW\Request::getInstance();
371
        $this->request->runDetect();
372
    }
373
374
    /**
375
     * Initiliaze php session if option "runSession" is not (bool) false
376
     * 
377
     * @return void
378
     */
379
    protected function initSession()
380
    {
381
        if ($this->options->getValue('runSession') === false) {
382
            return;
383
        }
384
385
        //Destroy session cookie if browser quit
386
        session_set_cookie_params(0);
387
388
        //Run session
389
        session_start();
390
    }
391
392
    /**
393
     * Initialize errors property with the \BFW\Core\Errors class
394
     * 
395
     * @return void
396
     */
397
    protected function initErrors()
398
    {
399
        $this->errors = new \BFW\Core\Errors;
400
    }
401
402
    /**
403
     * Initialize cli property with the \BFW\Core\Cli class
404
     * 
405
     * @return void
406
     */
407
    protected function initCli()
408
    {
409
        $this->cli = new \BFW\Core\Cli;
410
    }
411
    
412
    /**
413
     * Initialize taskers
414
     * 
415
     * @return void
416
     */
417
    protected function initRunTasks()
418
    {
419
        $stepsToRun = [];
420
        $closureNb  = 0;
421
        
422
        foreach ($this->runSteps as $step) {
423
            if ($step instanceof \Closure) {
424
                $stepName = 'closure_'.$closureNb;
425
                $closureNb++;
426
            } else {
427
                $stepName = $step[1];
428
            }
429
            
430
            //To keep methods to run protected
431
            $stepsToRun[$stepName] = (object) [
432
                'callback' => function() use ($step) {
433
                    $step();
434
                }
435
            ];
436
        }
437
        
438
        $runTasks = new \BFW\RunTasks($stepsToRun, 'BfwApp');
439
        $this->subjectList->addSubject($runTasks, 'ApplicationTasks');
440
    }
441
442
    /**
443
     * Initialize modules property with the \BFW\Modules class
444
     * 
445
     * @return void
446
     */
447
    protected function initModules()
448
    {
449
        $this->modules = new \BFW\Modules;
450
    }
451
452
    /**
453
     * Add namespaces used by a BFW Application to composer
454
     * 
455
     * @return void
456
     */
457
    protected function addComposerNamespaces()
458
    {
459
        $this->composerLoader->addPsr4('Controller\\', CTRL_DIR);
460
        $this->composerLoader->addPsr4('Modules\\', MODULES_DIR);
461
        $this->composerLoader->addPsr4('Modeles\\', MODELES_DIR);
462
    }
463
464
    /**
465
     * Declare all steps to run the application
466
     * 
467
     * @return void
468
     */
469
    protected function declareRunSteps()
470
    {
471
        $this->runSteps = [
472
            [$this, 'loadMemcached'],
473
            [$this, 'loadAllModules'],
474
            [$this, 'runAllCoreModules'],
475
            [$this, 'runAllAppModules'],
476
            [$this, 'runCliFile'],
477
            [$this, 'initCtrlRouterLink'],
478
            [$this, 'runCtrlRouterLink']
479
        ];
480
    }
481
482
    /**
483
     * Run the application
484
     * 
485
     * @return void
486
     */
487
    public function run()
488
    {
489
        $runTasks = $this->subjectList->getSubjectForName('ApplicationTasks');
490
        
491
        $runTasks->run();
492
        $runTasks->sendNotify('bfw_run_done');
0 ignored issues
show
Bug introduced by
The method sendNotify() does not exist on SplSubject. Did you maybe mean notify()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
493
    }
494
495
    /**
496
     * Connect to memcache(d) server with the class declared in config file
497
     * 
498
     * @return Object
499
     * 
500
     * @throws Exception If memcached is enabled but no class is define. Or if
501
     *  The class declared into the config is not found.
502
     */
503
    protected function loadMemcached()
504
    {
505
        $memcachedConfig = $this->config->getValue('memcached');
506
507
        if ($memcachedConfig['enabled'] === false) {
508
            return;
509
        }
510
511
        $class = $memcachedConfig['class'];
512
        if (empty($class)) {
513
            throw new Exception(
514
                'Memcached is active but no class is define',
515
                $this::ERR_MEMCACHED_NOT_CLASS_DEFINED
516
            );
517
        }
518
519
        if (class_exists($class) === false) {
520
            throw new Exception(
521
                'Memcache class '.$class.' not found.',
522
                $this::ERR_MEMCACHED_CLASS_NOT_FOUND
523
            );
524
        }
525
526
        $this->memcached = new $class;
527
        
528
        if (!($this->memcached instanceof \BFW\Memcache\MemcacheInterface)) {
529
            throw new Exception(
530
                'Memcache class '.$class.' not implement the interface.',
531
                $this::ERR_MEMCACHED_NOT_IMPLEMENT_INTERFACE
532
            );
533
        }
534
        
535
        $this->memcached->connectToServers();
536
    }
537
538
    /**
539
     * Read all directories in modules directory and add each module to Modules
540
     * class.
541
     * Generate the load tree.
542
     * Not initialize modules !
543
     * 
544
     * @return void
545
     */
546
    protected function loadAllModules()
547
    {
548
        $listModules = array_diff(scandir(MODULES_DIR), ['.', '..']);
549
550
        foreach ($listModules as $moduleName) {
551
            $modulePath = realpath(MODULES_DIR.$moduleName); //Symlink
552
553
            if (!is_dir($modulePath)) {
554
                continue;
555
            }
556
557
            $this->modules->addModule($moduleName);
558
        }
559
560
        $this->modules->readNeedMeDependencies();
561
        $this->modules->generateTree();
562
    }
563
564
    /**
565
     * Load core modules defined into config bfw file.
566
     * Only module for controller, router, database and template only.
567
     * 
568
     * @return void
569
     */
570
    protected function runAllCoreModules()
571
    {
572
        foreach ($this->config->getValue('modules') as $moduleInfos) {
573
            $moduleName    = $moduleInfos['name'];
574
            $moduleEnabled = $moduleInfos['enabled'];
575
576
            if (empty($moduleName) || $moduleEnabled === false) {
577
                continue;
578
            }
579
580
            $this->runModule($moduleName);
581
        }
582
    }
583
584
    /**
585
     * Load all modules (except core).
586
     * Get the load tree, read him and load all modules with the order
587
     * declared into the tree.
588
     * 
589
     * @return void
590
     */
591
    protected function runAllAppModules()
592
    {
593
        $tree = $this->modules->getLoadTree();
594
595
        foreach ($tree as $firstLine) {
596
            foreach ($firstLine as $secondLine) {
597
                foreach ($secondLine as $moduleName) {
598
                    $this->runModule($moduleName);
599
                }
600
            }
601
        }
602
    }
603
604
    /**
605
     * Load a module
606
     * 
607
     * @param string $moduleName The module's name to load
608
     * 
609
     * @return void
610
     */
611
    protected function runModule($moduleName)
612
    {
613
        $this->subjectList->getSubjectForName('ApplicationTasks')
0 ignored issues
show
Bug introduced by
The method sendNotify() does not exist on SplSubject. Did you maybe mean notify()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
614
            ->sendNotify('BfwApp_load_module_'.$moduleName);
615
        
616
        $this->modules->getModuleForName($moduleName)->runModule();
617
    }
618
619
    /**
620
     * Run the cli file if we're in cli mode
621
     * 
622
     * @return void
623
     * 
624
     * @throws Exception If no file is specified or if the file not exist.
625
     */
626
    protected function runCliFile()
627
    {
628
        if (PHP_SAPI !== 'cli') {
629
            return;
630
        }
631
632
        $this->subjectList->getSubjectForName('ApplicationTasks')
0 ignored issues
show
Bug introduced by
The method sendNotify() does not exist on SplSubject. Did you maybe mean notify()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
633
            ->sendNotify('run_cli_file');
634
        
635
        $fileToExec = $this->cli->obtainFileFromArg();
636
        $this->cli->run($fileToExec);
637
    }
638
    
639
    /**
640
     * Create a new observer to controller and router module.
641
     * 
642
     * @return void
643
     */
644
    protected function initCtrlRouterLink()
645
    {
646
        if (PHP_SAPI === 'cli') {
647
            return;
648
        }
649
650
        //Others properties can be dynamically added by modules
651
        $this->ctrlRouterInfos = (object) [
652
            'isFound' => false
653
        ];
654
        
655
        $ctrlRouterTask = new RunTasks(
656
            [
657
                'searchRoute' => (object) [
658
                    'context' => $this->ctrlRouterInfos
659
                ]
660
            ],
661
            'ctrlRouterLink'
662
        );
663
        
664
        $this->subjectList->addSubject($ctrlRouterTask, 'ctrlRouterLink');
665
        
666
        $runTasks = $this->subjectList->getSubjectForName('ApplicationTasks');
667
        $runTasks->sendNotify('bfw_ctrlRouterLink_subject_added');
0 ignored issues
show
Bug introduced by
The method sendNotify() does not exist on SplSubject. Did you maybe mean notify()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
668
    }
669
    
670
    /**
671
     * Execute the ctrlRouter task to find the route and the controller.
672
     * If nothing is found (context object), return an 404 error.
673
     * Not executed in cli.
674
     * 
675
     * @return void
676
     */
677
    protected function runCtrlRouterLink()
678
    {
679
        if (PHP_SAPI === 'cli') {
680
            return;
681
        }
682
        
683
        $this->subjectList->getSubjectForName('ctrlRouterLink')->run();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface SplSubject as the method run() does only exist in the following implementations of said interface: BFW\RunTasks.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
684
        
685
        if ($this->ctrlRouterInfos->isFound === false) {
686
            http_response_code(404);
687
        }
688
    }
689
}
690