ModuleManager::activateModule()   B
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 14
cts 14
cp 1
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 14
nc 3
nop 2
crap 3
1
<?php
2
/*
3
 * The MIT License (MIT)
4
 *
5
 * Copyright (c) 2015 zepi
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in
15
 * all copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
 * THE SOFTWARE.
24
 *
25
 */
26
27
/**
28
 * The ModuleManager manages all modules in the framework. Please do
29
 * not create an instance of the module manager. Use the framework to get 
30
 * the ModuleManager instance.
31
 * 
32
 * @package Zepi\Turbo\Manager
33
 * @author Matthias Zobrist <[email protected]>
34
 * @copyright Copyright (c) 2015 zepi
35
 */
36
37
namespace Zepi\Turbo\Manager;
38
39
use \Zepi\Turbo\Framework;
40
use \Zepi\Turbo\Backend\ObjectBackendAbstract;
41
use \Zepi\Turbo\Exception;
42
use \Zepi\Turbo\Module\ModuleAbstract;
43
44
/**
45
 * The ModuleManager manages all modules in the framework. Please do
46
 * not create an instance of the module manager. Use the framework to get 
47
 * the ModuleManager instance.
48
 * 
49
 * @author Matthias Zobrist <[email protected]>
50
 * @copyright Copyright (c) 2015 zepi
51
 */
52
class ModuleManager
53
{
54
    /**
55
     * @access protected
56
     * @var Framework
57
     */
58
    protected $framework;
59
    
60
    /**
61
     * @access protected
62
     * @var \Zepi\Turbo\Backend\ObjectBackendAbstract
63
     */
64
    protected $moduleObjectBackend;
65
    
66
    /**
67
     * @access protected
68
     * @var array
69
     */
70
    protected $moduleDirectories = array();
71
    
72
    /**
73
     * @access protected
74
     * @var array
75
     */
76
    protected $activatedModules = array();
77
    
78
    /**
79
     * @access protected
80
     * @var array
81
     */
82
    protected $modules = array();
83
84
    /**
85
     * Constructs the object
86
     * 
87
     * @access public
88
     * @param \Zepi\Turbo\Framework $framework
89
     * @param \Zepi\Turbo\Backend\ObjectBackendAbstract $moduleObjectBackend
90
     */
91 32
    public function __construct(Framework $framework, ObjectBackendAbstract $moduleObjectBackend)
92
    {
93 32
        $this->framework = $framework;
94 32
        $this->moduleObjectBackend = $moduleObjectBackend;
95 32
    }
96
    
97
    /**
98
     * Initializes the module system. Loads the activated modules from the 
99
     * object backend and loads all modules.
100
     * 
101
     * @access public
102
     */
103 32
    public function initializeModuleSystem()
104
    {
105 32
        $activatedModules = $this->moduleObjectBackend->loadObject();
106 32
        if (!is_array($activatedModules)) {
107 32
            $activatedModules = array();
108
        }
109
        
110 32
        $this->activatedModules = $activatedModules;
111
        
112 32
        foreach ($this->activatedModules as $activatedModule) {
113 1
            $this->initializeModule($activatedModule['path']);
114
        }
115 32
    }
116
    
117
    /**
118
     * Adds a directory as module directory
119
     * 
120
     * @access public
121
     * @param string $directory
122
     * @param string $excludePattern
123
     * @return boolean
124
     */
125 18
    public function registerModuleDirectory($directory, $excludePattern = '/\/tests\//')
126
    {
127 18
        if (!isset($this->moduleDirectories[$directory])) {
128 18
            $this->moduleDirectories[$directory] = $excludePattern;
129
            
130 18
            return true;
131
        }
132
        
133 1
        return false;
134
    }
135
    
136
    /**
137
     * Returns all activated modules.
138
     * 
139
     * @access public
140
     * @return array
141
     */
142 3
    public function getModules()
143
    {
144 3
        return $this->modules;
145
    }
146
    
147
    /**
148
     * Returns the module for the given module namespace or false, 
149
     * if the module wasn't initialized.
150
     * 
151
     * @access public
152
     * @param string $namespace
153
     * @return ModuleAbstract|boolean
154
     */
155 13
    public function getModule($namespace)
156
    {
157 13
        if (!isset($this->modules[$namespace])) {
158 13
            return false;
159
        }
160
        
161 3
        return $this->modules[$namespace];
162
    }
163
    
164
    /**
165
     * Searches and activates the module with the given 
166
     * namespace. The register method of the module will 
167
     * be executed.
168
     * 
169
     * @access public
170
     * @param string $namespace
171
     * @param boolean $activateDependencies
172
     * @return boolean
173
     * 
174
     * @throws Zepi\Turbo\Exception Can not find the module "$namespace".
175
     */
176 15
    public function activateModule($namespace, $activateDependencies = false)
177
    {
178 15
        $namespace = Framework::prepareNamespace($namespace);
179
180
        // If the module already is activated we won't activate it again
181 15
        if (isset($this->activatedModules[$namespace])) {
182 1
            return true;
183
        }
184
        
185
        // Search the path for the module and initialize it
186 15
        $path = $this->searchModulePath($namespace);
187 14
        if ($path === false) {
188 2
            throw new Exception('Can not find the module "' . $namespace . '".');
189
        }
190
        
191
        // Get the version
192 12
        $moduleProperties = $this->parseModuleJson($path);
193 12
        $version = $moduleProperties->module->version;
194
        
195 12
        $module = $this->initializeModule($path, $activateDependencies);
196 11
        $module->activate($version, 0);
197
        
198
        // Save the path in the activated modules array
199 11
        $this->activatedModules[$namespace] = array('version' => $version, 'path' => $path);
200 11
        $this->saveActivatedModules();
201
        
202 11
        return true;
203
    }
204
    
205
    /**
206
     * Deactivates an activated module. The deregister method
207
     * of the module will be executed.
208
     * 
209
     * @access public
210
     * @param string $namespace
211
     * @return boolean
212
     */
213 2
    public function deactivateModule($namespace)
214
    {
215 2
        $namespace = Framework::prepareNamespace($namespace);
216
217
        // If the module isn't activated we have nothing to deactivate
218 2
        if (!isset($this->activatedModules[$namespace])) {
219 1
            return false;
220
        }
221
        
222
        // Load the module to deactivate it
223 1
        $namespace = Framework::prepareNamespace($namespace);
224 1
        $module = $this->getModule($namespace);
225
226
        // If the module isn't initialized it isn't active
227 1
        if ($module === false) {
228
            return false;
229
        }
230
        
231
        // Deactivate the module
232 1
        $module->deactivate();
233
        
234
        // Remove the module and save the module cache
235 1
        unset($this->activatedModules[$namespace]);
236 1
        unset($this->modules[$namespace]);
237 1
        $this->saveActivatedModules();
238
        
239 1
        return true;
240
    }
241
    
242
    /**
243
     * Searches the module for the given class name.
244
     * 
245
     * @access public
246
     * @param string $className
247
     * @return boolean|Module
248
     */
249 7
    public function getModuleByClassName($className)
250
    {
251 7
        $longest = 0;
252 7
        $foundModule = false;
253
        
254 7
        $className = Framework::prepareClassName($className);
255
        
256 7
        foreach ($this->modules as $moduleNamespace => $module) {
257 6
            $moduleNamespace = Framework::prepareNamespace($moduleNamespace);
258 6
            $sameNamespace = strpos($className, $moduleNamespace);
259 6
            $length = strlen($moduleNamespace);
260
261 6
            if ($sameNamespace !== false && $length > $longest) {
262 6
                $longest = $length;
263 6
                $foundModule = $module;
264
            }
265
        }
266
        
267 7
        return $foundModule;
268
    }
269
    
270
    /**
271
     * Iterates trough the modules and activates each of the
272
     * modules.
273
     * 
274
     * @access public
275
     */
276
    public function reactivateModules()
277
    {
278
        foreach ($this->modules as $module) {
279
            $moduleProperties = $this->parseModuleJson($module->getDirectory());
280
            $version = $moduleProperties->module->version;
281
282
            $this->activateModule($moduleProperties->module->namespace, true);
283
            $module->activate($version, $version);
284
        }
285
    }
286
    
287
    /**
288
     * Returns an object with the properties for the given path.
289
     * 
290
     * @access public
291
     * @param string $path
292
     * @return \stdClass
293
     */
294 2
    public function getModuleProperties($path)
295
    {
296 2
        return $this->parseModuleJson($path);
297
    }
298
    
299
    /**
300
     * Returns an object with the properties of the module from
301
     * the Module.json file in the given path.
302
     * 
303
     * @access protected
304
     * @param string $path
305
     * @return \stdClass
306
     * 
307
     * @throws Zepi\Turbo\Exception Cannot find Module.json in the path "$path".
308
     */
309 17
    protected function parseModuleJson($path)
310
    {
311 17
        if (!file_exists($path . '/Module.json')) {
312 1
            throw new Exception('Cannot find Module.json in the path "' . $path . '".');
313
        }
314
315 16
        $moduleProperties = json_decode(file_get_contents($path . '/Module.json'));
316
        
317 16
        return $moduleProperties;
318
    }
319
    
320
    /**
321
     * Returns the namespace for the module in the given path.
322
     * 
323
     * @access protected
324
     * @param string $path
325
     * @return string
326
     * 
327
     * @throws Zepi\Turbo\Exception The namespace is not set in the module properties for the Module in "$path".
328
     */
329 15
    protected function getNamespaceFromModuleJson($path)
330
    {
331 15
        $moduleProperties = $this->parseModuleJson($path);
332
333 15
        if (!isset($moduleProperties->module->namespace)) {
334 1
            throw new Exception('The namespace is not set in the module properties for the module in "' . $path . '".');
335
        }
336
        
337 15
        return Framework::prepareNamespace($moduleProperties->module->namespace);
338
    }
339
    
340
    /**
341
     * Initializes the module. This creates an new Module object if the 
342
     * given module path is valid. The function returns the initialized 
343
     * module or false, if the module can't be initialized.
344
     * 
345
     * @access protected
346
     * @param string $path
347
     * @param boolean $activateDependencies
348
     * @return ModuleAbstract
349
     * 
350
     * @throws Zepi\Turbo\Exception The module "$path" is not valid
351
     */
352 13
    protected function initializeModule($path, $activateDependencies = false)
353
    {
354 13
        $moduleNamespace = $this->getNamespaceFromModuleJson($path);
355 13
        $module = $this->getModule($moduleNamespace);
356
        
357
        // If the module is already initialized, return it
358 13
        if ($module instanceof ModuleAbstract) {
359 1
            return $module;
360
        }
361
        
362
        // Try to find and load the module
363 13
        if (!file_exists($path . '/Module.php')) {
364 1
            throw new Exception('The module "' . $path . '" is not valid!');
365
        }
366
367
        // Look for dependencies and warn the user or activate the dependencies
368 12
        $this->handleModuleDependencies($moduleNamespace, $path, $activateDependencies);
369
        
370
        // Load the module
371 12
        require_once($path . '/Module.php');
372 12
        $moduleClassName = Framework::prepareClassName($moduleNamespace . 'Module');
373
        
374
        // Initialize the module
375 12
        $module = new $moduleClassName($this->framework, $moduleNamespace, $path);
376 12
        $this->modules[$moduleNamespace] = $module;
377
        
378 12
        $module->initialize();
379
        
380 12
        return $module;
381
    }
382
383
    /**
384
     * Loads the Module.json and checks it for dependencies. If the module has
385
     * dependencies the function will verify the modules and activate them
386
     * if the parameter $activateDependencies is set to true.
387
     * 
388
     * @access public
389
     * @param string $moduleNamespace
390
     * @param string $path
391
     * @param boolean $activateDependencies
392
     */
393 12
    protected function handleModuleDependencies($moduleNamespace, $path, $activateDependencies)
394
    {
395 12
        $moduleProperties = $this->parseModuleJson($path);
396
397
        // If the config file has no dependencies we have nothing to do...
398 12
        if (!isset($moduleProperties->dependencies) || $moduleProperties->dependencies === null) {
399 12
            return;
400
        }
401
        
402
        foreach ($moduleProperties->dependencies as $type => $dependencies) {
403
            switch ($type) {
404
                case 'required':
405
                    $this->handleRequiredDependencies($moduleNamespace, $dependencies, $activateDependencies);
406
                break;
407
            }
408
        }
409
    }
410
411
    /**
412
     * Handles all required dependencies.
413
     * 
414
     * @access public
415
     * @param string $moduleNamespace
416
     * @param array $dependencies
417
     * @param boolean $activateDependencies
418
     * 
419
     * @throws Zepi\Turbo\Exception Can not activate the module "$moduleNamespace". The module requires the module "$dependencyModuleNamespace" which isn't activated.
420
     */
421
    protected function handleRequiredDependencies($moduleNamespace, $dependencies, $activateDependencies)
422
    {
423
        foreach ($dependencies as $dependencyModuleNamespace => $version) {
424
            $dependencyModuleNamespace = Framework::prepareNamespace($dependencyModuleNamespace);
425
            
426
            $module = $this->getModule($dependencyModuleNamespace);
427
            if ($module === false) {
428
                if ($activateDependencies) {
429
                    $this->activateModule($dependencyModuleNamespace, $activateDependencies);
430
                } else {
431
                    throw new Exception('Can not activate the module "' . $moduleNamespace . '". The module requires the module "' . $dependencyModuleNamespace . '" which isn\'t activated.');
432
                }
433
            }
434
        }
435
    }
436
    
437
    /**
438
     * Iterates over the available module directories and searches the 
439
     * target namespace in the module directories. If the namespace is 
440
     * found the function return the path to the module.
441
     * 
442
     * @access protected
443
     * @param string $namespace
444
     * @return string
445
     */
446 15
    protected function searchModulePath($namespace)
447
    {
448 15
        $targetPath = false;
449
        
450
        // Iterate trough the module directories
451 15
        foreach ($this->moduleDirectories as $directory => $excludePattern) {
452 15
            $recursiveDirectoryIterator = new \RecursiveDirectoryIterator($directory, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
453 15
            $iterator = new \RecursiveIteratorIterator($recursiveDirectoryIterator);
454 15
            $regexIterator = new \RegexIterator($iterator, '/^.+\/Module\.json$/i');
455
456 15
            foreach ($regexIterator as $item) {
457
                // Ignore modules which are located inside a tests directory
458 15
                if ($excludePattern !== false && preg_match($excludePattern, $item->getPath())) {
459 1
                    continue;
460
                }
461
                
462 14
                $moduleNamespace = $this->getNamespaceFromModuleJson($item->getPath());
463
                
464 14
                if ($moduleNamespace === $namespace) {
465 12
                    $targetPath = $item->getPath();
466 15
                    break 2;
467
                }
468
            }
469
        }
470
        
471 14
        return $targetPath;
472
    }
473
    
474
    /**
475
     * Saves the activated modules in the object backend.
476
     * 
477
     * @access protected
478
     */
479 11
    protected function saveActivatedModules()
480
    {
481 11
        $this->moduleObjectBackend->saveObject($this->activatedModules);
482 11
    }
483
}
484