Completed
Push — master ( 7c5d16...1e7423 )
by Vitaly
03:53
created

Module   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 491
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 4

Importance

Changes 23
Bugs 5 Features 10
Metric Value
wmc 55
c 23
b 5
f 10
lcom 3
cbo 4
dl 0
loc 491
rs 6.8

26 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 40 2
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 13 2
A view() 0 19 2
A findView() 0 16 2
B viewContext() 0 38 4
B render() 0 27 4
B output() 0 53 5
A __destruct() 0 7 1
A __call() 0 15 4
A __sleep() 0 4 1
A __wakeup() 0 11 1
A offsetSet() 0 4 1
A offsetGet() 0 4 1
A __get() 0 13 2
B __set() 0 19 6
A offsetUnset() 0 4 1
A offsetExists() 0 4 1
A sortStrings() 0 4 1
A cache_refresh() 0 19 4
A _setObject() 0 14 2
A _setArray() 0 8 1

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
2
namespace samson\core;
3
4
// TODO: Разобраться почему с вызовом m()->render() во вьюхе, и почему не передаются параметры
5
use samsonframework\core\ResourcesInterface;
6
use samsonframework\core\SystemInterface;
7
use samsonphp\core\exception\ControllerActionNotFound;
8
use samsonphp\core\exception\ViewPathNotFound;
9
10
/**
11
 * Модуль системы
12
 *
13
 * @author Vitaly Iegorov <[email protected]>
14
 * @version 1.0
15
 */
16
class Module implements iModule, \ArrayAccess
0 ignored issues
show
Deprecated Code introduced by
The interface samson\core\iModule has been deprecated with message: Use samsonframework\core\ViewInterface

This class, trait or interface 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 type will be removed from the class and what other constant to use instead.

Loading history...
17
{
18
    /** Static module instances collection */
19
    public static $instances = array();
20
21
    /** Uniquer identifier to check pointers */
22
    public $uid;
23
24
    /** @var ResourcesInterface Pointer to module resource map */
25
    public $resourceMap;
26
27
    /** @var array TODO: WTF? */
28
    public $composerParameters = array();
29
30
    /** Module views collection */
31
    protected $views = array();
32
33
    /** Module location */
34
    protected $path = '';
35
36
    /** Unique module identifier */
37
    protected $id = '';
38
39
    /** Path to view for rendering */
40
    protected $view_path = self::VD_POINTER_DEF;
41
42
    /** Pointer to view data entry */
43
    protected $data = array(self::VD_POINTER_DEF => array(self::VD_HTML => ''));
44
45
    /** Collection of data for view rendering, filled with default pointer */
46
    protected $view_data = array(self::VD_POINTER_DEF => array(self::VD_HTML => ''));
47
48
    /** Name of current view context entry */
49
    protected $view_context = self::VD_POINTER_DEF;
50
51
    /** Unique module cache path in local web-application */
52
    protected $cache_path;
53
54
    /** @var SystemInterface Instance for interaction with framework */
55
    protected $system;
56
57
    /**
58
     * Constructor
59
     *
60
     * @param string $id Module unique identifier
61
     * @param string $path Module location
62
     * @param ResourcesInterface $resourceMap Pointer to module resource map
63
     * @param SystemInterface $system
64
     */
65
    public function __construct($id, $path, ResourcesInterface $resourceMap, SystemInterface $system)
66
    {
67
        // Inject generic module dependencies
68
        $this->system = $system;
69
70
        // Store pointer to module resource map
71
        $this->resourceMap = &$resourceMap;
72
        // Save views list
73
        $this->views = $resourceMap->views;
0 ignored issues
show
Bug introduced by
Accessing views on the interface samsonframework\core\ResourcesInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
74
75
        // Set default view context name
76
        $this->view_context = self::VD_POINTER_DEF;
77
78
        // Set up default view data pointer
79
        $this->data = &$this->view_data[$this->view_context];
80
81
        // Set module identifier
82
        $this->id = $id;
83
84
        // Set path to module
85
        $this->path(realpath($path));
86
87
        // Generate unique module identifier
88
        $this->uid = rand(0, 9999999) . '_' . microtime(true);
89
90
        // Add to module identifier to view data stack
91
        $this->data['id'] = $this->id;
92
93
        // Generate unique module cache path in local web-application
94
        $this->cache_path = __SAMSON_CWD__ . __SAMSON_CACHE_PATH . $this->id . '/';
95
96
        // Save ONLY ONE copy of this instance in static instances collection,
97
        // avoiding rewriting by cloned modules
98
        !isset(self::$instances[$this->id]) ? self::$instances[$this->id] = &$this : '';
99
100
        // Make view path relative to module - remove module path from view path
101
        $this->views = str_replace($this->path, '', $this->views);
102
103
        //elapsed('Registering module: '.$this->id.'('.$path.')' );
104
    }
105
106
    /** @see iModule::path() */
107
    public function path($value = null)
108
    {
109
        // Если передан параметр - установим его
110
        if (func_num_args()) {
111
            $this->path = isset($value{0}) ? rtrim(normalizepath($value), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : '';
112
113
            return $this;
114
        } // Вернем относительный путь к файлам модуля
115
        else return $this->path;
116
    }
117
118
    /**    @see iModule::title() */
119
    public function title($title = null)
120
    {
121
        return $this->set('title', $title);
122
    }
123
124
    /** @see iModule::set() */
125
    public function set($field, $value = null)
126
    {
127
        $this->__set($field, $value);
128
129
        return $this;
130
    }
131
132
    /**    @see iModule::id() */
133
    public function id()
134
    {
135
        return $this->id;
136
    }
137
138
    /** @see iModuleViewable::toView() */
139
    public function toView($prefix = null, array $restricted = array())
140
    {
141
        // Get all module data variables
142
        $view_data = array_merge($this->data, get_object_vars($this));
143
144
        // Remove plain HTML from view data
145
        unset($view_data[self::VD_HTML]);
146
147
        return $view_data;
148
    }
149
150
    /** @see iModule::html() */
151
    public function html($value = null)
152
    {
153
        //elapsed($this->id.' - Setting HTML for '.array_search(  $this->data, $this->view_data ).'('.strlen($value).')');
154
155
        // Если передан параметр то установим его
156
        if (func_num_args()) {
157
            $this->data[self::VD_HTML] = $value;
158
        } else {
159
            return $this->data['html'];
160
        }
161
162
        return $this;
163
    }
164
165
    public function view($viewPath)
166
    {
167
        // Find full path to view file
168
        $viewPath = $this->findView($viewPath);
169
170
        // We could not find view
171
        if ($viewPath !== false) {
172
            // Switch view context to founded module view
173
            $this->viewContext($viewPath);
174
175
            // Set current view path
176
            $this->view_path = $viewPath;
177
        } else {
178
            throw(new ViewPathNotFound($viewPath));
179
        }
180
181
        // Продолжим цепирование
182
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (samson\core\Module) is incompatible with the return type declared by the interface samsonframework\core\ViewInterface::view of type samsonframework\core\IViewable.

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...
183
    }
184
185
    /**
186
     * Find view file by its part in module view resources and return full path to it.
187
     *
188
     * @param string $viewPath Part of path to module view file
189
     * @return string Full path to view file
190
     */
191
    public function findView($viewPath)
192
    {
193
        // Remove file extension for correct array searching
194
        $viewPath = str_replace(array('.php', '.vphp'), '', $viewPath);
195
196
        // Try to find passed view_path in  resources views collection
197
        if (sizeof($view = preg_grep('/' . addcslashes($viewPath, '/\\') . '(\.php|\.vphp)/ui', $this->views))) {
198
            // Sort view paths to get the shortest path
199
            usort($view, array($this, 'sortStrings'));
200
201
            // Set current full view path as last found view
202
            return end($view);
203
        }
204
205
        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 samson\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...
206
    }
207
208
    /**
209
     * Perform module view context switching
210
     * @param string $view_path New view context name
211
     */
212
    protected function viewContext($view_path)
213
    {
214
        // Pointer to NEW view data context
215
        $new = &$this->view_data[$view_path];
216
217
        // Pointer to OLD view data context
218
        $old = &$this->view_data[$this->view_context];
219
220
        // If we are trying to switch to NEW view context
221
        if ($this->view_context !== $view_path) {
222
            //elapsed( $this->id.' - Switching view context from '.$this->view_context.' to '.$view_path );
223
224
            // Create new entry in view data collection if it does not exists
225
            if (!isset($this->view_data[$view_path])) {
226
                // Create new view data record
227
                $new = array();
228
229
                // If current view data context has view data
230
                if (isset($old)) {
231
                    //elapsed($old);
232
                    //elapsed( $this->id.' - Copying previous view context view data '.$this->view_context.' to new view context '.$view_path.'('.sizeof($old).')');
233
234
                    // Copy default view context view data to new view context
235
                    $new = array_merge($new, $old);
236
                }
237
238
                // Clear plain HTML for new view context
239
                $new[self::VD_HTML] = '';
240
            }
241
242
            // Change view data pointer to appropriate view data entry
243
            $this->data = &$new;
244
245
            // Save current context name
246
            $this->view_context = $view_path;
247
        }
248
        //else elapsed( $this->id.' - NO need to switch view context from '.$this->view_context.' to '.$view_path );
249
    }
250
251
    /**    @see iModule::render() */
252
    public function render($controller = null)
253
    {
254
        // Switch current system active module
255
        $old = &$this->system->active($this);
256
257
        // If specific controller action should be run
258
        if (isset($controller)) {
259
            // Define if this is a procedural controller or OOP
260
            $callback = function_exists($controller)
261
                ? $controller
262
                : array($this, iModule::OBJ_PREFIX . $controller);
263
264
            // If this controller action is present
265
            if (is_callable($callback)) {
266
                // Perform controller action
267
                call_user_func_array($callback, url()->parameters);
268
            } else {
269
                throw(new ControllerActionNotFound($this->id . '#' . $controller));
270
            }
271
        }
272
273
        // Output view
274
        echo $this->output();
275
276
        // Restore previous active module
277
        $this->system->active($old);
278
    }
279
280
    /**    @see iModule::output() */
281
    public function output($viewPath = null)
282
    {
283
        // If view path not specified - use current correct view path
284
        if (!isset($viewPath)) {
285
            $viewPath = $this->view_path;
286
        } elseif (isset($viewPath{0})) { // Direct rendering of specific view, not default view data entry
287
            elapsed('Outputting to a view is deprecated, split your rendering chain into ->view(..)->output()');
288
            $viewPath = $this->findView($viewPath);
289
        }
290
291
        //elapsed('['.$this->id.'] Rendering view context: ['.$viewPath.'] with ['.$renderer->id.']');
292
293
        // Switch view context to new module view
294
        $this->viewContext($viewPath);
295
296
        //elapsed($this->id.' - Outputing '.$view_path.'-'.sizeof($this->data));
297
        //elapsed(array_keys($this->view_data));
298
299
        // Get current view context plain HTML
300
        $out = $this->data[self::VD_HTML];
301
302
        // If view path specified
303
        if (isset($viewPath{0})) {
304
            // Временно изменим текущий модуль системы
305
            $old = $this->system->active($this);
306
307
            // Прорисуем представление модуля
308
            $out .= $this->system->render($this->path . $viewPath, $this->data);
309
310
            // Вернем на место текущий модуль системы
311
            $this->system->active($old);
312
        } elseif (!isset($out{0})) { // No plain HTML view data is set also
313
            return e('Cannot render view for module ## - No view path or data has been set', E_SAMSON_CORE_ERROR, $this->id);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return e('Cannot render ...CORE_ERROR, $this->id); (boolean) is incompatible with the return type declared by the interface samsonframework\core\ViewInterface::output 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...
314
        }
315
316
        // Clear currently outputted view context from VCS
317
        unset($this->view_data[$viewPath]);
318
319
        // Get last element from VCS
320
        end($this->view_data);
321
322
        // Get last element from VCS name
323
        $this->view_context = key($this->view_data);
324
325
        // Set internal view data pointer to last VCS entry
326
        $this->data = &$this->view_data[$this->view_context];
327
328
        // Return view path to previous state
329
        $this->view_path = $this->view_context;
330
331
        // Вернем результат прорисовки
332
        return $out;
333
    }
334
335
    /** Обработчик уничтожения объекта */
336
    public function __destruct()
337
    {
338
        //trace('Уничтожение модуля:'.$this->id );
339
340
        // Очистим коллекцию загруженых модулей
341
        unset(Module::$instances[$this->id]);
342
    }
343
344
    /** Magic method for calling unexisting object methods */
345
    public function __call($method, $arguments)
346
    {
347
        //elapsed($this->id.' - __Call '.$method);
348
349
        // If value is passed - set it
350
        if (sizeof($arguments)) {
351
            // If first argument is object or array - pass method name as second parameter
352
            if (is_object($arguments[0]) || is_array($arguments[0])) $this->__set($arguments[0], $method);
353
            // Standard logic
354
            else  $this->__set($method, $arguments[0]);
355
        }
356
357
        // Chaining
358
        return $this;
359
    }
360
361
    // Магический метод для получения переменных представления модуля
362
363
    /** Обработчик сериализации объекта */
364
    public function __sleep()
365
    {
366
        return array('id', 'path', 'data', 'views');
367
    }
368
369
    /** Обработчик десериализации объекта */
370
    public function __wakeup()
371
    {
372
        // Fill global instances
373
        self::$instances[$this->id] = &$this;
374
375
        // Set up default view data pointer
376
        $this->view_data[self::VD_POINTER_DEF] = $this->data;
377
378
        // Set reference to view context entry
379
        $this->data = &$this->view_data[self::VD_POINTER_DEF];
380
    }
381
382
    // TODO: Переделать обработчик в одинаковый вид для объектов и простых
383
384
    /** Группа методов для доступа к аттрибутам в виде массива */
385
    public function offsetSet($offset, $value)
386
    {
387
        $this->__set($offset, $value);
388
    }
389
390
    public function offsetGet($offset)
391
    {
392
        return $this->__get($offset);
393
    }
394
395
    // Магический метод для установки переменных представления модуля
396
397
    public function __get($field)
398
    {
399
        // Установим пустышку как значение переменной
400
        $result = null;
401
402
        // Если указанная переменная представления существует - получим её значение
403
        if (isset($this->data[$field])) $result = &$this->data[$field];
404
        // Выведем ошибку
405
        else return e('Ошибка получения данных модуля(##) - Требуемые данные(##) не найдены', E_SAMSON_CORE_ERROR, array($this->id, $field));
406
407
        // Иначе вернем пустышку
408
        return $result;
409
    }
410
411
    public function __set($field, $value = null)
412
    {
413
        // This is object
414
        if (is_object($field)) {
415
            $implements = class_implements($field);
416
            // If iModuleViewable implements is passed
417
            if (
418
                // TODO: Remove old interface support in future
419
                in_array(ns_classname('iModuleViewable', 'samson\core'), $implements)
0 ignored issues
show
Deprecated Code introduced by
The function ns_classname() has been deprecated with message: use \samson\core\AutoLoader::className() 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...
420
                || in_array(AutoLoader::className('IViewSettable', 'samson\core'), $implements)
421
                || in_array('samsonframework\core\RenderInterface', $implements)
422
            ) {
423
                $this->_setObject($field, $value);
424
            }
425
        } // If array is passed
426
        else if (is_array($field)) $this->_setArray($field, $value);
0 ignored issues
show
Documentation introduced by
$field is of type array, but the function expects a object<samson\core\unknown>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
427
        // Set view variable
428
        else $this->data[$field] = $value;
429
    }
430
431
    public function offsetUnset($offset)
432
    {
433
        $this->data[$offset] = '';
434
    }
435
436
    public function offsetExists($offset)
437
    {
438
        return isset($this->data[$offset]);
439
    }
440
441
    /** Sort array by string length */
442
    protected function sortStrings($a, $b)
443
    {
444
        return strlen($b) - strlen($a);
445
    }
446
447
    /**
448
     * Create unique module cache folder structure in local web-application
449
     * @param string $file Path to file relative to module cache location
450
     * @param boolean $clear Flag to perform generic cache folder clearence
451
     * @return boolean TRUE if cache file has to be regenerated
452
     */
453
    protected function cache_refresh(& $file, $clear = true)
454
    {
455
        // If module cache folder does not exists - create it
456
        if (!file_exists($this->cache_path)) mkdir($this->cache_path, 0777, TRUE);
457
458
        // Build full path to cached file
459
        $file = $this->cache_path . $file;
460
461
        // If cached file does not exsits
462
        if (file_exists($file)) return false;
463
        // Needed file does not exists
464
        else {
465
            // If clearence flag set to true - clear all files in module cache directory with same extension
466
            if ($clear) File::clear($this->cache_path, pathinfo($file, PATHINFO_EXTENSION));
467
468
            // Singal for cache file regeneration
469
            return true;
470
        }
471
    }
472
473
    /**
474
     *
475
     * @param unknown $object
476
     * @param string $viewprefix
477
     */
478
    private function _setObject($object, $viewprefix = null)
479
    {
480
        // Generate viewprefix as only lowercase classname without NS if it is not specified
481
        $class_name = is_string($viewprefix) ? $viewprefix : '' . mb_strtolower(classname(get_class($object)), 'UTF-8');
482
483
        // Save object to view data
484
        $this->data[$class_name] = $object;
485
486
        // Add separator
487
        $class_name .= '_';
488
489
        // Generate objects view array data and merge it with view data
490
        $this->data = array_merge($this->data, $object->toView($class_name));
491
    }
492
493
    /**
494
     *
495
     * @param unknown $array
496
     * @param string $viewprefix
497
     */
498
    private function _setArray($array, $viewprefix = null)
499
    {
500
        // Save array to view data
501
        $this->data[$viewprefix] = $array;
502
503
        // Add array values to view data
504
        $this->data = array_merge($this->data, $array);
505
    }
506
}
507