Completed
Push — master ( bc3898...bd8c0c )
by Mihail
04:18
created

BootManager::__construct()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 1
Metric Value
c 1
b 1
f 1
dl 0
loc 24
rs 8.6845
cc 4
eloc 14
nc 6
nop 1
1
<?php
2
3
namespace Ffcms\Core\Managers;
4
5
6
use Ffcms\Core\App;
7
use Ffcms\Core\Debug\DebugMeasure;
8
use Ffcms\Core\Helper\FileSystem\Directory;
9
use Ffcms\Core\Helper\FileSystem\File;
10
use Ffcms\Core\Helper\Type\Obj;
11
use Ffcms\Core\Helper\Type\Str;
12
13
/**
14
 * Class BootManager. Manage auto executed boot methods in widgets and applications.
15
 * @package Ffcms\Core\Managers
16
 */
17
class BootManager
18
{
19
    use DebugMeasure;
20
21
    CONST CACHE_TREE_TIME = 120;
22
23
    private $loader;
24
    private $appRoots = [];
25
    private $widgetRoots = [];
26
27
    private $objects = [];
28
29
    /**
30
     * BootManager constructor. Pass composer loader inside
31
     * @param bool|object $loader
32
     */
33
    public function __construct($loader = false)
34
    {
35
        $this->startMeasure(__METHOD__);
36
        // pass loader inside
37
        $this->loader = $loader;
38
        if ($this->loader !== false) {
39
            $this->parseComposerLoader();
40
        }
41
42
        // check if cache is enabled
43
        if (App::$Cache !== null) {
44
            // try to get bootable class map from cache, or initialize parsing
45
            $cache = App::$Cache->getItem('boot.' . env_name . '.class.map');
46
            if (!$cache->isHit()) {
47
                $this->compileBootableClasses();
48
                $cache->set($this->objects)->expiresAfter(static::CACHE_TREE_TIME);
49
                App::$Cache->save($cache);
50
            }
51
        } else {
52
            $this->compileBootableClasses();
53
        }
54
55
        $this->stopMeasure(__METHOD__);
56
    }
57
58
    /**
59
     * Find app's and widgets root directories over composer psr loader
60
     */
61
    private function parseComposerLoader(): void
62
    {
63
        // get composer autoload map
64
        $map = $this->loader->getPrefixes();
65
        if (Obj::isArray($map)) {
66
            // get all available apps root dirs by psr loader for apps
67
            if (array_key_exists('Apps\\', $map)) {
68
                foreach ($map['Apps\\'] as $appPath) {
69
                    $this->appRoots[] = $appPath;
70
                }
71
            }
72
73
            // get Widgets map
74
            if (array_key_exists('Widgets\\', $map)) {
75
                // get all available root dirs by psr loader for widgets
76
                foreach ($map['Widgets\\'] as $widgetPath) {
77
                    $this->widgetRoots[] = $widgetPath;
78
                }
79
            }
80
        }
81
82
        // set default root path if not found anything else
83
        if (count($this->appRoots) < 1) {
84
            $this->appRoots = [root];
85
        }
86
87
        if (count($this->widgetRoots) < 1) {
88
            $this->widgetRoots = [root];
89
        }
90
    }
91
92
    /**
93
     * Find all bootatble instances and set it to object map
94
     * @return void
95
     */
96
    public function compileBootableClasses(): void
97
    {
98
        // list app root's
99
        foreach ($this->appRoots as $app) {
100
            $app .= '/Apps/Controller/' . env_name;
101
            $files = File::listFiles($app, ['.php'], true);
102
            foreach ($files as $file) {
103
                // define full class name with namespace
104
                $class = 'Apps\Controller\\' . env_name . '\\' . Str::cleanExtension($file);
105
                // check if class exists (must be loaded over autoloader), boot method exist and this is controller instanceof
106
                if (class_exists($class) && method_exists($class, 'boot') && is_a($class, 'Ffcms\Core\Arch\Controller', true)) {
107
                    $this->objects[] = $class;
108
                }
109
            }
110
        }
111
112
        // list widget root's
113
        foreach ($this->widgetRoots as $widget) {
114
            $widget .= '/Widgets/' . env_name;
115
            // widgets are packed in directory, classname should be the same with root directory name
116
            $dirs = Directory::scan($widget, GLOB_ONLYDIR, true);
117
            if (!Obj::isArray($dirs)) {
118
                continue;
119
            }
120
            foreach ($dirs as $instance) {
0 ignored issues
show
Bug introduced by
The expression $dirs of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
121
                $class = 'Widgets\\' . env_name . '\\' . $instance . '\\' . $instance;
122
                if (class_exists($class) && method_exists($class, 'boot') && is_a($class, 'Ffcms\Core\Arch\Widget', true)) {
123
                    $this->objects[] = $class;
124
                }
125
            }
126
        }
127
    }
128
129
    /**
130
     * Call bootable methods in apps and widgets
131
     * @return bool
132
     */
133
    public function run(): bool
134
    {
135
        $this->startMeasure(__METHOD__);
136
137
        if (!Obj::isArray($this->objects)) {
138
            return false;
139
        }
140
141
        foreach ($this->objects as $class) {
142
            forward_static_call([$class, 'boot']);
143
        }
144
145
        $this->stopMeasure(__METHOD__);
146
        return true;
147
    }
148
}