Passed
Push — 3.0 ( 02fb2f...68382e )
by Vermeulen
01:45
created

Module.php$0 ➔ __call()   A

Complexity

Conditions 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
c 0
b 0
f 0
cc 3
rs 9.7998
1
<?php
2
3
namespace BFW;
4
5
use \Exception;
6
7
/**
8
 * Class to manage a module
9
 */
10
class Module
11
{
12
    /**
13
     * @const ERR_FILE_NOT_FOUND Exception code if the file is not found.
14
     */
15
    const ERR_FILE_NOT_FOUND = 1104001;
16
    
17
    /**
18
     * @const ERR_JSON_PARSE Exception code if the parse of a json file fail.
19
     */
20
    const ERR_JSON_PARSE = 1104002;
21
    
22
    /**
23
     * @const ERR_RUNNER_FILE_NOT_FOUND Exception code if the runner file to
24
     * execute is not found.
25
     */
26
    const ERR_RUNNER_FILE_NOT_FOUND = 1104003;
27
    
28
    /**
29
     * @const ERR_METHOD_NOT_EXIST Exception code if the use call an unexisting
30
     * method.
31
     */
32
    const ERR_METHOD_NOT_EXIST = 1104004;
33
34
    /**
35
     * @var string $name Module's name
36
     */
37
    protected $name = '';
38
39
    /**
40
     * @var \BFW\Config|null $config Config object for this module
41
     */
42
    protected $config;
43
44
    /**
45
     * @var \stdClass|null $loadInfos All informations about how to run the module
46
     */
47
    protected $loadInfos;
48
49
    /**
50
     * @var object $status Load and run status
51
     */
52
    protected $status;
53
54
    /**
55
     * Constructor
56
     * 
57
     * @param string $name Module name
58
     */
59
    public function __construct(string $name)
60
    {
61
        \BFW\Application::getInstance()
62
            ->getMonolog()
63
            ->getLogger()
64
            ->debug('New module declared', ['name' => $name])
65
        ;
66
        
67
        $this->name   = $name;
68
        $this->status = new class {
69
            public $load = false;
70
            public $run  = false;
71
        };
72
    }
73
    
74
    /**
75
     * PHP Magic method, called when we call an unexisting method
76
     * This method allow the module to add dynamic method on fly (issue #88)
77
     * 
78
     * @param string $name The method name
79
     * @param array $arguments The argument passed to the method
80
     * 
81
     * @return mixed
82
     */
83
    public function __call(string $name, array $arguments)
84
    {
85
        if (
86
            property_exists($this, $name) === true &&
87
            is_callable($this->$name) === true
88
        ) {
89
            //Temp var because $this->$name(...$arguments) create ininite loop
90
            $fct = $this->$name;
91
            return $fct(...$arguments);
92
        }
93
        
94
        throw new Exception(
95
            'The method '.$name.' not exist in module class for '.$this->name,
96
            self::ERR_METHOD_NOT_EXIST
97
        );
98
    }
99
    
100
    /**
101
     * Load informations about the module
102
     * 
103
     * @return void
104
     */
105
    public function loadModule()
106
    {
107
        \BFW\Application::getInstance()
108
            ->getMonolog()
109
            ->getLogger()
110
            ->debug('Load module', ['name' => $this->name])
111
        ;
112
        
113
        $this->loadConfig();
114
        $this->obtainLoadInfos();
115
116
        $this->status->load = true;
117
    }
118
119
    /**
120
     * Get installation informations
121
     * 
122
     * @param string $sourceFiles Path to module source (in vendor)
123
     * 
124
     * @return \stdClass
125
     */
126
    public static function installInfos(string $sourceFiles): \stdClass
127
    {
128
        $currentClass = get_called_class(); //Allow extends
129
        return $currentClass::readJsonFile(
130
            $sourceFiles.'/bfwModulesInfos.json'
131
        );
132
    }
133
134
    /**
135
     * Get the module's name
136
     * 
137
     * @return string
138
     */
139
    public function getName(): string
140
    {
141
        return $this->name;
142
    }
143
144
    /**
145
     * Get the Config object which have config for this module
146
     * 
147
     * @return \BFW\Config|null
148
     */
149
    public function getConfig()
150
    {
151
        return $this->config;
152
    }
153
154
    /**
155
     * Get the load informations
156
     * 
157
     * @return \stdClass|null
158
     */
159
    public function getLoadInfos()
160
    {
161
        return $this->loadInfos;
162
    }
163
164
    /**
165
     * Get the status object for this module
166
     * 
167
     * @return object
168
     */
169
    public function getStatus()
170
    {
171
        return $this->status;
172
    }
173
174
    /**
175
     * Return the load status
176
     * 
177
     * @return boolean
178
     */
179
    public function isLoaded(): bool
180
    {
181
        return $this->status->load;
182
    }
183
184
    /**
185
     * Return the run status
186
     * 
187
     * @return boolean
188
     */
189
    public function isRun(): bool
190
    {
191
        return $this->status->run;
192
    }
193
194
    /**
195
     * Instantiate the Config object to obtains module's configuration
196
     * 
197
     * @return void
198
     */
199
    protected function loadConfig()
200
    {
201
        if (!file_exists(CONFIG_DIR.$this->name)) {
202
            return;
203
        }
204
205
        $this->config = new \BFW\Config($this->name);
206
        $this->config->loadFiles();
207
    }
208
209
    /**
210
     * Save loaded informations from json file into the loadInfos property
211
     * 
212
     * @return void
213
     */
214
    protected function obtainLoadInfos()
215
    {
216
        $currentClass = get_called_class(); //Allow extends
217
        
218
        $this->loadInfos = $currentClass::readJsonFile(
219
            MODULES_DIR.$this->name
220
            .'/module.json'
221
        );
222
    }
223
224
    /**
225
     * Read and parse a json file
226
     * 
227
     * @param string $jsonFilePath : The path to the file to read
228
     * 
229
     * @return mixed Json parsed datas
230
     * 
231
     * @throws \Exception If the file is not found or for a json parser error
232
     */
233
    protected static function readJsonFile(string $jsonFilePath)
234
    {
235
        if (!file_exists($jsonFilePath)) {
236
            throw new Exception(
237
                'File '.$jsonFilePath.' not found.',
238
                self::ERR_FILE_NOT_FOUND
239
            );
240
        }
241
242
        $infos = json_decode(file_get_contents($jsonFilePath));
243
        if ($infos === null) {
244
            throw new Exception(
245
                json_last_error_msg(),
246
                self::ERR_JSON_PARSE
247
            );
248
        }
249
250
        return $infos;
251
    }
252
    
253
    /**
254
     * Add a dependency to the module
255
     * Used for needMe property in module infos
256
     * 
257
     * @param string $dependencyName The dependency name to add
258
     * 
259
     * @return $this
0 ignored issues
show
Documentation Bug introduced by
The doc comment $this at position 0 could not be parsed: '$this' is only available from within classes.
Loading history...
260
     */
261
    public function addDependency(string $dependencyName): self
262
    {
263
        if (!property_exists($this->loadInfos, 'require')) {
264
            $this->loadInfos->require = [];
265
        }
266
        
267
        if (!is_array($this->loadInfos->require)) {
268
            $this->loadInfos->require = [$this->loadInfos->require];
269
        }
270
        
271
        $this->loadInfos->require[] = $dependencyName;
272
        
273
        return $this;
274
    }
275
276
    /**
277
     * Get path to the runner file
278
     * 
279
     * @return string
280
     * 
281
     * @throws \Exception If the file not exists
282
     */
283
    protected function obtainRunnerFile(): string
284
    {
285
        $moduleInfos = $this->loadInfos;
286
        $runnerFile  = '';
287
288
        if (property_exists($moduleInfos, 'runner')) {
289
            $runnerFile = (string) $moduleInfos->runner;
290
        }
291
292
        if (empty($runnerFile)) {
293
            return '';
294
        }
295
296
        $runnerFile = MODULES_DIR.$this->name.'/'.$runnerFile;
297
        if (!file_exists($runnerFile)) {
298
            throw new Exception(
299
                'Runner file for module '.$this->name.' not found.',
300
                $this::ERR_RUNNER_FILE_NOT_FOUND
301
            );
302
        }
303
304
        return $runnerFile;
305
    }
306
307
    /**
308
     * Run the module in a closure
309
     * 
310
     * @return void
311
     */
312
    public function runModule()
313
    {
314
        if ($this->status->run === true) {
315
            return;
316
        }
317
        
318
        $runnerFile   = $this->obtainRunnerFile();
319
        $initFunction = function() use ($runnerFile) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
320
            if (empty($runnerFile)) {
321
                return;
322
            }
323
            
324
            require(realpath($runnerFile));
325
        };
326
327
        $this->status->run = true;
328
        $initFunction();
329
    }
330
}
331