Module   C
last analyzed

Complexity

Total Complexity 55

Size/Duplication

Total Lines 509
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 6

Test Coverage

Coverage 95.68%

Importance

Changes 0
Metric Value
dl 0
loc 509
ccs 155
cts 162
cp 0.9568
rs 6.8
c 0
b 0
f 0
wmc 55
lcom 3
cbo 6

25 Methods

Rating   Name   Duplication   Size   Complexity  
A path() 0 10 3
A title() 0 4 1
A set() 0 6 1
A id() 0 4 1
A toView() 0 10 1
A html() 0 6 1
B view() 0 23 4
A findView() 0 16 2
B viewContext() 0 38 4
B render() 0 37 4
B output() 0 53 4
A __call() 0 17 4
A __sleep() 0 4 1
A __wakeup() 0 11 1
A offsetSet() 0 4 1
A offsetGet() 0 4 1
A __get() 0 15 2
B __set() 0 20 6
A offsetUnset() 0 4 1
A offsetExists() 0 4 1
A sortStrings() 0 4 1
B cache_refresh() 0 28 5
A _setObject() 0 14 2
A _setArray() 0 8 1
B __construct() 0 41 2

How to fix   Complexity   

Complex Class

Complex classes like Module 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 Module, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
namespace samsonphp\core;
3
4
use samson\core\File;
5
use samsonframework\core\ResourcesInterface;
6
use samsonframework\core\SystemInterface;
7
use samsonframework\core\ViewInterface;
8
use samsonframework\resource\ResourceMap;
9
use samsonphp\core\deprecated\iModule;
10
use samsonphp\core\exception\ControllerActionNotFound;
11
use samsonphp\core\exception\ViewPathNotFound;
12
use samsonphp\core\exception\ViewVariableNotFound;
13
14
/**
15
 * Модуль системы
16
 *
17
 * @author Vitaly Iegorov <[email protected]>
18
 * @deprecated Will be merged with ExternalModule
19
 */
20
class Module implements \ArrayAccess, iModule
21
{
22
    /** Static module instances collection */
23
    public static $instances = array();
24
25
    /** Uniquer identifier to check pointers */
26
    public $uid;
27
28
    /** @var ResourcesInterface Pointer to module resource map */
29
    public $resourceMap;
30
31
    /** @var array TODO: WTF? */
32
    public $composerParameters = array();
33
34
    /** Module views collection */
35
    protected $views = array();
36
37
    /** Module location */
38
    protected $path = '';
39
40
    /** Unique module identifier */
41
    protected $id;
42
43
    /** Path to view for rendering */
44
    protected $view_path = self::VD_POINTER_DEF;
45
46
    /** Pointer to view data entry */
47
    protected $data = array(self::VD_POINTER_DEF => array(self::VD_HTML => ''));
48
49
    /** Collection of data for view rendering, filled with default pointer */
50
    protected $view_data = array(self::VD_POINTER_DEF => array(self::VD_HTML => ''));
51
52
    /** Name of current view context entry */
53
    protected $view_context = self::VD_POINTER_DEF;
54
55
    /** Unique module cache path in local web-application */
56
    protected $cache_path;
57
58
    /** @var SystemInterface Instance for interaction with framework */
59
    protected $system;
60
61
    /**
62
     * Constructor
63
     *
64
     * @param string $id Module unique identifier
65
     * @param string $path Module location
66 7
     * @param ResourcesInterface $resourceMap Pointer to module resource map
67
     * @param SystemInterface $system
68
     */
69 7
    public function __construct($id, $path, ResourcesInterface $resourceMap, SystemInterface $system)
70
    {
71
        // Inject generic module dependencies
72 7
        $this->system = $system;
73
74 7
        // Store pointer to module resource map
75
        // TODO: Should be changed or removed
76
        $this->resourceMap = $resourceMap = ResourceMap::get($path);
77 7
        // Save views list
78
        $this->views = $resourceMap->views;
79
80 7
        // Set default view context name
81
        $this->view_context = self::VD_POINTER_DEF;
82
83 7
        // Set up default view data pointer
84
        $this->data = &$this->view_data[$this->view_context];
85
86 7
        // Set module identifier
87
        $this->id = $id;
88
89 7
        // Set path to module
90
        $this->path(realpath($path));
91
92 7
        // Generate unique module identifier
93
        $this->uid = rand(0, 9999999) . '_' . microtime(true);
94
95 7
        // Add to module identifier to view data stack
96
        $this->data['id'] = $this->id;
97
98
        // Generate unique module cache path in local web-application
99 7
        $this->cache_path = __SAMSON_CWD__ . __SAMSON_CACHE_PATH . $this->id . '/';
100
101
        // Save ONLY ONE copy of this instance in static instances collection,
102 7
        // avoiding rewriting by cloned modules
103
        !isset(self::$instances[$this->id]) ? self::$instances[$this->id] = &$this : '';
104
105 7
        // Make view path relative to module - remove module path from view path
106
        $this->views = str_replace($this->path, '', $this->views);
107
108 7
        //elapsed('Registering module: '.$this->id.'('.$path.')' );
109
    }
110
111 7
    /** @see iModule::path() */
112 7
    public function path($value = null)
113
    {
114 7
        // Если передан параметр - установим его
115
        if (func_num_args()) {
116 1
            $this->path = isset($value{0}) ? rtrim(normalizepath($value), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : '';
117
118
            return $this;
119
        } // Вернем относительный путь к файлам модуля
120
        else return $this->path;
0 ignored issues
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
121
    }
122
123
    /**    @see iModule::title() */
124
    public function title($title = null)
125
    {
126 1
        return $this->set($title, 'title');
127
    }
128 1
129
    /** @see iModule::set() */
130 1
    public function set($value, $field = null)
131
    {
132
        $this->__set($value, $field);
133
134 1
        return $this;
135
    }
136 1
137
    /**    @see iModule::id() */
138
    public function id()
139
    {
140
        return $this->id;
141
    }
142
143
    /** @see iModuleViewable::toView() */
144
    public function toView($prefix = null, array $restricted = array())
145
    {
146
        // Get all module data variables
147
        $view_data = array_merge($this->data, get_object_vars($this));
148
149
        // Remove plain HTML from view data
150
        unset($view_data[self::VD_HTML]);
151
152 1
        return $view_data;
153
    }
154 1
155
    /** @see iModule::html() */
156 1
    public function html($value)
157
    {
158
        $this->data[self::VD_HTML] = $value;
159 3
160
        return $this;
161
    }
162 3
163
    public function view($viewPath)
164
    {
165 3
        if (is_a($viewPath, ViewInterface::class)) {
166
            $this->view_path = $viewPath;
167 3
        } elseif (is_string($viewPath)) {
168
            // Find full path to view file
169
            $foundViewPath = $this->findView($viewPath);
170 3
171 3
            // We could not find view
172 1
            if ($foundViewPath !== false) {
173
                // Switch view context to founded module view
174
                $this->viewContext($foundViewPath);
175
176 3
                // Set current view path
177
                $this->view_path = $foundViewPath;
178
            } else {
179
                throw(new ViewPathNotFound($viewPath));
180
            }
181
        }
182
183
        // Продолжим цепирование
184
        return $this;
185 3
    }
186
187
    /**
188 3
     * Find view file by its part in module view resources and return full path to it.
189
     *
190
     * @param string $viewPath Part of path to module view file
191 3
     * @return string Full path to view file
192
     */
193 3
    public function findView($viewPath)
194
    {
195
        // Remove file extension for correct array searching
196 3
        $viewPath = str_replace(array('.php', '.vphp'), '', $viewPath);
197
198
        // Try to find passed view_path in  resources views collection
199 1
        if (sizeof($view = preg_grep('/' . addcslashes($viewPath, '/\\') . '(\.php|\.vphp)/ui', $this->views))) {
200
            // Sort view paths to get the shortest path
201
            usort($view, array($this, 'sortStrings'));
202
203
            // Set current full view path as last found view
204
            return end($view);
205
        }
206 4
207
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by samsonphp\core\Module::findView of type string.

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...
208
    }
209 4
210
    /**
211
     * Perform module view context switching
212 4
     * @param string $view_path New view context name
213
     */
214
    protected function viewContext($view_path)
215 4
    {
216
        // Pointer to NEW view data context
217
        $new = &$this->view_data[$view_path];
218
219 3
        // Pointer to OLD view data context
220
        $old = &$this->view_data[$this->view_context];
221 3
222
        // If we are trying to switch to NEW view context
223
        if ($this->view_context !== $view_path) {
224 3
            //elapsed( $this->id.' - Switching view context from '.$this->view_context.' to '.$view_path );
225
226
            // Create new entry in view data collection if it does not exists
227
            if (!isset($this->view_data[$view_path])) {
228
                // Create new view data record
229 3
                $new = array();
230 3
231
                // If current view data context has view data
232
                if (isset($old)) {
233 3
                    //elapsed($old);
234 3
                    //elapsed( $this->id.' - Copying previous view context view data '.$this->view_context.' to new view context '.$view_path.'('.sizeof($old).')');
235
236
                    // Copy default view context view data to new view context
237 3
                    $new = array_merge($new, $old);
238
                }
239
240 3
                // Clear plain HTML for new view context
241 3
                $new[self::VD_HTML] = '';
242
            }
243 4
244
            // Change view data pointer to appropriate view data entry
245
            $this->data = &$new;
246 1
247
            // Save current context name
248
            $this->view_context = $view_path;
249 1
        }
250
        //else elapsed( $this->id.' - NO need to switch view context from '.$this->view_context.' to '.$view_path );
251
    }
252 1
253
    /**
254
     * @param null $controller
255
     *
256
     * @throws ControllerActionNotFound
257 1
     */
258 1
    public function render($controller = null)
259 1
    {
260
        // Switch current system active module
261
        $old = $this->system->active($this);
262 1
263
        // If specific controller action should be run
264
        if (isset($controller)) {
265 1
            /**
266
             * TODO: This would be removed in next major version, here should be
267 1
             * passed real controller method for execution.
268
             */
269 1
            $controller = method_exists($this, iModule::OBJ_PREFIX . $controller)
270
                ? iModule::OBJ_PREFIX . $controller
271 1
                : $controller;
272 1
273 1
            // Define if this is a procedural controller or OOP
274
            $callback = array($this, $controller);
275 1
276
            // If this controller action is present
277
            if (method_exists($this, $controller)) {
278 1
                // Get passed arguments
279
                $parameters = func_get_args();
280
                // Remove first as its a controller action name
281 1
                array_shift($parameters);
282 1
                // Perform controller action with passed parameters
283
                call_user_func_array($callback, $parameters);
284
            } else {
285 3
                throw new ControllerActionNotFound($this->id . '#' . $controller);
286
            }
287
        }
288 3
289
        // Output view
290
        echo $this->output();
291
292
        // Restore previous active module
293 3
        $this->system->active($old);
294
    }
295
296
    /**    @see iModule::output() */
297
    public function output()
298
    {
299 3
        // If view path not specified - use current correct view path
300
        $viewPath = $this->view_path;
301
302 3
        if (is_string($viewPath)) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
303
304 2
            //elapsed('['.$this->id.'] Rendering view context: ['.$viewPath.'] with ['.$renderer->id.']');
305
306
            // Switch view context to new module view
307 2
            $this->viewContext($viewPath);
308
309
            //elapsed($this->id.' - Outputing '.$view_path.'-'.sizeof($this->data));
310 2
            //elapsed(array_keys($this->view_data));
311 2
312
            // Get current view context plain HTML
313
            $out = $this->data[self::VD_HTML];
314 3
315
            // If view path specified
316
            if (isset($viewPath{0})) {
317 3
                // Временно изменим текущий модуль системы
318
                $old = $this->system->active($this);
319
320 3
                // Прорисуем представление модуля
321
                $out .= $this->system->render($this->path . $viewPath, $this->data);
322
323 3
                // Вернем на место текущий модуль системы
324
                $this->system->active($old);
325
            }
326 3
327
            // Clear currently outputted view context from VCS
328
            unset($this->view_data[$viewPath]);
329 3
330
            // Get last element from VCS
331
            end($this->view_data);
332
333 1
            // Get last element from VCS name
334
            $this->view_context = key($this->view_data);
335
336
            // Set internal view data pointer to last VCS entry
337
            $this->data = &$this->view_data[$this->view_context];
338 1
339
            // Return view path to previous state
340 1
            $this->view_path = $this->view_context;
341 1
342 1
            // Вернем результат прорисовки
343 1
            return $out;
344
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
345 1
        } elseif (is_a($viewPath, ViewInterface::class)) {
346
            /** @var ViewInterface $viewPath */
347
            return $viewPath->output();
348 1
        }
349
    }
350
351
    /** Magic method for calling un existing object methods */
352
    public function __call($method, $arguments)
353
    {
354 1
        //elapsed($this->id.' - __Call '.$method);
355
356 1
        // If value is passed - set it
357
        if (count($arguments)) {
358
            // If first argument is object or array - pass method name as second parameter
359
            if (is_object($arguments[0]) || is_array($arguments[0])) {
360 1
                $this->__set($arguments[0], $method);
361
            } else { // Standard logic
362
                $this->__set($arguments[0], $method);
363 1
            }
364
        }
365
366 1
        // Chaining
367
        return $this;
368
    }
369 1
370 1
    // Магический метод для получения переменных представления модуля
371
372
    /** Обработчик сериализации объекта */
373
    public function __sleep()
374
    {
375 1
        return array('id', 'path', 'data', 'views');
376
    }
377 1
378 1
    /** Обработчик десериализации объекта */
379
    public function __wakeup()
380 2
    {
381
        // Fill global instances
382 2
        self::$instances[$this->id] = &$this;
383
384
        // Set up default view data pointer
385
        $this->view_data[self::VD_POINTER_DEF] = $this->data;
386
387 3
        // Set reference to view context entry
388
        $this->data = &$this->view_data[self::VD_POINTER_DEF];
389
    }
390 3
391
    // TODO: Переделать обработчик в одинаковый вид для объектов и простых
392
393 3
    /** Группа методов для доступа к аттрибутам в виде массива */
394 2
    public function offsetSet($value, $offset)
395 2
    {
396 1
        $this->__set($offset, $value);
397
    }
398
399
    public function offsetGet($offset)
400 2
    {
401
        return $this->__get($offset);
402
    }
403 3
404
    // Магический метод для установки переменных представления модуля
405
406 3
    public function __get($field)
407 1
    {
408
        // Установим пустышку как значение переменной
409
        $result = null;
410
411 1
        // Если указанная переменная представления существует - получим её значение
412 1
        if (isset($this->data[$field])) {
413 1
            $result = &$this->data[$field];
414 1
        } else {
415 1
            throw(new ViewVariableNotFound($field));
416 1
        }
417 1
418 3
        // Иначе вернем пустышку
419
        return $result;
420 3
    }
421 3
422
    public function __set($value, $field = null)
423 1
    {
424
        if (is_object($field) || is_array($field)) {
425 1
            $tempValue = $field;
426 1
            $field = $value;
427
            $value = $tempValue;
428 2
429
            // TODO: Will be added in next major version
430 2
            //throw new \Exception('ViewInterface::set($value, $name) has changed, first arg is variable second is name or prefix');
431
        }
432
433
		// This is object
434 1
		if (is_object($value) && is_a($value, 'samsonframework\core\RenderInterface')) {
435
			$this->_setObject($value, $field);
436 1
		} elseif (is_array($value)) { // If array is passed
437
			$this->_setArray($value, $field);
438
		} else { // Set view variable
439
            $this->data[$field] = $value;
440
		}
441
    }
442
443
    public function offsetUnset($offset)
444
    {
445
        unset($this->data[$offset]);
446 1
    }
447
448
    public function offsetExists($offset)
449 1
    {
450 1
        return isset($this->data[$offset]);
451 1
    }
452
453
    /** Sort array by string length */
454 1
    protected function sortStrings($a, $b)
455
    {
456
        return strlen($b) - strlen($a);
457 1
    }
458
459 1
    /**
460 1
     * Create unique module cache folder structure in local web-application.
461 1
     *
462
     * @param string $file Path to file relative to module cache location
463
     * @param boolean $clear Flag to perform generic cache folder clearence
464 1
     * @return boolean TRUE if cache file has to be regenerated
465
     */
466
    protected function cache_refresh(&$file, $clear = true, $folder = null)
0 ignored issues
show
Coding Style introduced by
Method name "Module::cache_refresh" is not in camel caps format
Loading history...
467
    {
468
        // Add slash to end
469
        $folder = isset($folder) ? rtrim($folder, '/') . '/' : '';
470
471
        $path = $this->cache_path . $folder;
472
473
        // If module cache folder does not exists - create it
474
        if (!file_exists($path)) {
475 1
            mkdir($path, 0777, true);
476
        }
477
478 1
        // Build full path to cached file
479
        $file = $path . $file;
480
481 1
        // If cached file does not exsits
482
        if (!file_exists($file)) {
483
            // If clearence flag set to true - clear all files in module cache directory with same extension
484 1
            if ($clear) {
485
                File::clear($path, pathinfo($file, PATHINFO_EXTENSION));
486
            }
487 1
488 1
            // Signal for cache file regeneration
489
            return true;
490
        }
491
492
        return false;
493
    }
494
495 1
    /**
496
     *
497
     * @param unknown $object
498 1
     * @param string $viewprefix
499
     */
500
    private function _setObject($object, $viewprefix = null)
501 1
    {
502 1
        // Generate viewprefix as only lowercase classname without NS if it is not specified
503
        $class_name = is_string($viewprefix) ? $viewprefix : '' . mb_strtolower(ns_classname(get_class($object)), 'UTF-8');
0 ignored issues
show
Deprecated Code introduced by
The function ns_classname() has been deprecated with message: use \samson\core\AutoLoader::value() and pass full class name to it without splitting into class name and namespace

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
504
505
        // Save object to view data
506
        $this->data[$class_name] = $object;
507
508
        // Add separator
509
        $class_name .= '_';
510
511
        // Generate objects view array data and merge it with view data
512
        $this->data = array_merge($this->data, $object->toView($class_name));
513
    }
514
515
    /**
516
     *
517
     * @param unknown $array
518
     * @param string $viewprefix
519
     */
520
    private function _setArray($array, $viewprefix = null)
521
    {
522
        // Save array to view data
523
        $this->data[$viewprefix] = $array;
524
525
        // Add array values to view data
526
        $this->data = array_merge($this->data, $array);
527
    }
528
}
529