Passed
Push — master ( 421e0c...c27c49 )
by Vitaly
08:04
created

Module   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 487
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 5

Test Coverage

Coverage 95.68%

Importance

Changes 33
Bugs 11 Features 13
Metric Value
wmc 50
c 33
b 11
f 13
lcom 3
cbo 5
dl 0
loc 487
rs 8.6206
ccs 155
cts 162
cp 0.9568

25 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 6 1
A view() 0 19 2
A findView() 0 16 2
B viewContext() 0 38 4
B render() 0 37 4
B output() 0 46 2
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 19 6
A offsetUnset() 0 4 1
A offsetExists() 0 4 1
A sortStrings() 0 4 1
B cache_refresh() 0 23 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
use samsonphp\core\exception\ViewVariableNotFound;
10
11
/**
12
 * Модуль системы
13
 *
14
 * @author Vitaly Iegorov <[email protected]>
15
 * @version 1.0
16
 */
17
class Module implements iModule, \ArrayAccess
18
{
19
    /** Static module instances collection */
20
    public static $instances = array();
21
22
    /** Uniquer identifier to check pointers */
23
    public $uid;
24
25
    /** @var ResourcesInterface Pointer to module resource map */
26
    public $resourceMap;
27
28
    /** @var array TODO: WTF? */
29
    public $composerParameters = array();
30
31
    /** Module views collection */
32
    protected $views = array();
33
34
    /** Module location */
35
    protected $path = '';
36
37
    /** Unique module identifier */
38
    protected $id = '';
39
40
    /** Path to view for rendering */
41
    protected $view_path = self::VD_POINTER_DEF;
42
43
    /** Pointer to view data entry */
44
    protected $data = array(self::VD_POINTER_DEF => array(self::VD_HTML => ''));
45
46
    /** Collection of data for view rendering, filled with default pointer */
47
    protected $view_data = array(self::VD_POINTER_DEF => array(self::VD_HTML => ''));
48
49
    /** Name of current view context entry */
50
    protected $view_context = self::VD_POINTER_DEF;
51
52
    /** Unique module cache path in local web-application */
53
    protected $cache_path;
54
55
    /** @var SystemInterface Instance for interaction with framework */
56
    protected $system;
57
58
    /**
59
     * Constructor
60
     *
61
     * @param string $id Module unique identifier
62
     * @param string $path Module location
63
     * @param ResourcesInterface $resourceMap Pointer to module resource map
64
     * @param SystemInterface $system
65
     */
66 7
    public function __construct($id, $path, ResourcesInterface $resourceMap, SystemInterface $system)
67
    {
68
        // Inject generic module dependencies
69 7
        $this->system = $system;
70
71
        // Store pointer to module resource map
72 7
        $this->resourceMap = &$resourceMap;
73
        // Save views list
74 7
        $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...
75
76
        // Set default view context name
77 7
        $this->view_context = self::VD_POINTER_DEF;
78
79
        // Set up default view data pointer
80 7
        $this->data = &$this->view_data[$this->view_context];
81
82
        // Set module identifier
83 7
        $this->id = $id;
84
85
        // Set path to module
86 7
        $this->path(realpath($path));
87
88
        // Generate unique module identifier
89 7
        $this->uid = rand(0, 9999999) . '_' . microtime(true);
90
91
        // Add to module identifier to view data stack
92 7
        $this->data['id'] = $this->id;
93
94
        // Generate unique module cache path in local web-application
95 7
        $this->cache_path = __SAMSON_CWD__ . __SAMSON_CACHE_PATH . $this->id . '/';
96
97
        // Save ONLY ONE copy of this instance in static instances collection,
98
        // avoiding rewriting by cloned modules
99 7
        !isset(self::$instances[$this->id]) ? self::$instances[$this->id] = &$this : '';
100
101
        // Make view path relative to module - remove module path from view path
102 7
        $this->views = str_replace($this->path, '', $this->views);
103
104
        //elapsed('Registering module: '.$this->id.'('.$path.')' );
105 7
    }
106
107
    /** @see iModule::path() */
108 7
    public function path($value = null)
109
    {
110
        // Если передан параметр - установим его
111 7
        if (func_num_args()) {
112 7
            $this->path = isset($value{0}) ? rtrim(normalizepath($value), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : '';
113
114 7
            return $this;
115
        } // Вернем относительный путь к файлам модуля
116 1
        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...
117
    }
118
119
    /**    @see iModule::title() */
120
    public function title($title = null)
121
    {
122
        return $this->set('title', $title);
123
    }
124
125
    /** @see iModule::set() */
126 1
    public function set($field, $value = null)
127
    {
128 1
        $this->__set($field, $value);
129
130 1
        return $this;
131
    }
132
133
    /**    @see iModule::id() */
134 1
    public function id()
135
    {
136 1
        return $this->id;
137
    }
138
139
    /** @see iModuleViewable::toView() */
140
    public function toView($prefix = null, array $restricted = array())
141
    {
142
        // Get all module data variables
143
        $view_data = array_merge($this->data, get_object_vars($this));
144
145
        // Remove plain HTML from view data
146
        unset($view_data[self::VD_HTML]);
147
148
        return $view_data;
149
    }
150
151
    /** @see iModule::html() */
152 1
    public function html($value)
153
    {
154 1
        $this->data[self::VD_HTML] = $value;
155
156 1
        return $this;
157
    }
158
159 3
    public function view($viewPath)
160
    {
161
        // Find full path to view file
162 3
        $viewPath = $this->findView($viewPath);
163
164
        // We could not find view
165 3
        if ($viewPath !== false) {
166
            // Switch view context to founded module view
167 3
            $this->viewContext($viewPath);
168
169
            // Set current view path
170 3
            $this->view_path = $viewPath;
171 3
        } else {
172 1
            throw(new ViewPathNotFound($viewPath));
173
        }
174
175
        // Продолжим цепирование
176 3
        return $this;
177
    }
178
179
    /**
180
     * Find view file by its part in module view resources and return full path to it.
181
     *
182
     * @param string $viewPath Part of path to module view file
183
     * @return string Full path to view file
184
     */
185 3
    public function findView($viewPath)
186
    {
187
        // Remove file extension for correct array searching
188 3
        $viewPath = str_replace(array('.php', '.vphp'), '', $viewPath);
189
190
        // Try to find passed view_path in  resources views collection
191 3
        if (sizeof($view = preg_grep('/' . addcslashes($viewPath, '/\\') . '(\.php|\.vphp)/ui', $this->views))) {
192
            // Sort view paths to get the shortest path
193 3
            usort($view, array($this, 'sortStrings'));
194
195
            // Set current full view path as last found view
196 3
            return end($view);
197
        }
198
199 1
        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...
200
    }
201
202
    /**
203
     * Perform module view context switching
204
     * @param string $view_path New view context name
205
     */
206 4
    protected function viewContext($view_path)
207
    {
208
        // Pointer to NEW view data context
209 4
        $new = &$this->view_data[$view_path];
210
211
        // Pointer to OLD view data context
212 4
        $old = &$this->view_data[$this->view_context];
213
214
        // If we are trying to switch to NEW view context
215 4
        if ($this->view_context !== $view_path) {
216
            //elapsed( $this->id.' - Switching view context from '.$this->view_context.' to '.$view_path );
217
218
            // Create new entry in view data collection if it does not exists
219 3
            if (!isset($this->view_data[$view_path])) {
220
                // Create new view data record
221 3
                $new = array();
222
223
                // If current view data context has view data
224 3
                if (isset($old)) {
225
                    //elapsed($old);
226
                    //elapsed( $this->id.' - Copying previous view context view data '.$this->view_context.' to new view context '.$view_path.'('.sizeof($old).')');
227
228
                    // Copy default view context view data to new view context
229 3
                    $new = array_merge($new, $old);
230 3
                }
231
232
                // Clear plain HTML for new view context
233 3
                $new[self::VD_HTML] = '';
234 3
            }
235
236
            // Change view data pointer to appropriate view data entry
237 3
            $this->data = &$new;
238
239
            // Save current context name
240 3
            $this->view_context = $view_path;
241 3
        }
242
        //else elapsed( $this->id.' - NO need to switch view context from '.$this->view_context.' to '.$view_path );
243 4
    }
244
245
    /**    @see iModule::render() */
246 1
    public function render($controller = null)
247
    {
248
        // Switch current system active module
249 1
        $old = &$this->system->active($this);
250
251
        // If specific controller action should be run
252 1
        if (isset($controller)) {
253
            /**
254
             * TODO: This would be removed in next major version, here should be
255
             * passed real controller method for execution.
256
             */
257 1
            $controller = method_exists($this, iModule::OBJ_PREFIX . $controller)
258 1
                ? iModule::OBJ_PREFIX . $controller
259 1
                : $controller;
260
261
            // Define if this is a procedural controller or OOP
262 1
            $callback = array($this, $controller);
263
264
            // If this controller action is present
265 1
            if (method_exists($this, $controller)) {
266
                // Get passed arguments
267 1
                $parameters = func_get_args();
268
                // Remove first as its a controller action name
269 1
                array_shift($parameters);
270
                // Perform controller action with passed parameters
271 1
                call_user_func_array($callback, $parameters);
272 1
            } else {
273 1
                throw(new ControllerActionNotFound($this->id . '#' . $controller));
274
            }
275 1
        }
276
277
        // Output view
278 1
        echo $this->output();
279
280
        // Restore previous active module
281 1
        $this->system->active($old);
282 1
    }
283
284
    /**    @see iModule::output() */
285 3
    public function output()
286
    {
287
        // If view path not specified - use current correct view path
288 3
        $viewPath = $this->view_path;
289
290
        //elapsed('['.$this->id.'] Rendering view context: ['.$viewPath.'] with ['.$renderer->id.']');
291
292
        // Switch view context to new module view
293 3
        $this->viewContext($viewPath);
294
295
        //elapsed($this->id.' - Outputing '.$view_path.'-'.sizeof($this->data));
296
        //elapsed(array_keys($this->view_data));
297
298
        // Get current view context plain HTML
299 3
        $out = $this->data[self::VD_HTML];
300
301
        // If view path specified
302 3
        if (isset($viewPath{0})) {
303
            // Временно изменим текущий модуль системы
304 2
            $old = $this->system->active($this);
305
306
            // Прорисуем представление модуля
307 2
            $out .= $this->system->render($this->path . $viewPath, $this->data);
308
309
            // Вернем на место текущий модуль системы
310 2
            $this->system->active($old);
311 2
        }
312
313
        // Clear currently outputted view context from VCS
314 3
        unset($this->view_data[$viewPath]);
315
316
        // Get last element from VCS
317 3
        end($this->view_data);
318
319
        // Get last element from VCS name
320 3
        $this->view_context = key($this->view_data);
321
322
        // Set internal view data pointer to last VCS entry
323 3
        $this->data = &$this->view_data[$this->view_context];
324
325
        // Return view path to previous state
326 3
        $this->view_path = $this->view_context;
327
328
        // Вернем результат прорисовки
329 3
        return $out;
330
    }
331
332
    /** Magic method for calling un existing object methods */
333 1
    public function __call($method, $arguments)
334
    {
335
        //elapsed($this->id.' - __Call '.$method);
336
337
        // If value is passed - set it
338 1
        if (sizeof($arguments)) {
339
            // If first argument is object or array - pass method name as second parameter
340 1
            if (is_object($arguments[0]) || is_array($arguments[0])) {
341 1
                $this->__set($arguments[0], $method);
342 1
            } else { // Standard logic
343 1
                $this->__set($method, $arguments[0]);
344
            }
345 1
        }
346
347
        // Chaining
348 1
        return $this;
349
    }
350
351
    // Магический метод для получения переменных представления модуля
352
353
    /** Обработчик сериализации объекта */
354 1
    public function __sleep()
355
    {
356 1
        return array('id', 'path', 'data', 'views');
357
    }
358
359
    /** Обработчик десериализации объекта */
360 1
    public function __wakeup()
361
    {
362
        // Fill global instances
363 1
        self::$instances[$this->id] = &$this;
364
365
        // Set up default view data pointer
366 1
        $this->view_data[self::VD_POINTER_DEF] = $this->data;
367
368
        // Set reference to view context entry
369 1
        $this->data = &$this->view_data[self::VD_POINTER_DEF];
370 1
    }
371
372
    // TODO: Переделать обработчик в одинаковый вид для объектов и простых
373
374
    /** Группа методов для доступа к аттрибутам в виде массива */
375 1
    public function offsetSet($offset, $value)
376
    {
377 1
        $this->__set($offset, $value);
378 1
    }
379
380 2
    public function offsetGet($offset)
381
    {
382 2
        return $this->__get($offset);
383
    }
384
385
    // Магический метод для установки переменных представления модуля
386
387 3
    public function __get($field)
388
    {
389
        // Установим пустышку как значение переменной
390 3
        $result = null;
391
392
        // Если указанная переменная представления существует - получим её значение
393 3
        if (isset($this->data[$field])) {
394 2
            $result = &$this->data[$field];
395 2
        } else {
396 1
            throw(new ViewVariableNotFound($field));
397
        }
398
399
        // Иначе вернем пустышку
400 2
        return $result;
401
    }
402
403 3
    public function __set($field, $value = null)
404
    {
405
        // This is object
406 3
        if (is_object($field)) {
407 1
            $implements = class_implements($field);
408
            // If iModuleViewable implements is passed
409
            if (
410
                // TODO: Remove old interface support in future
411 1
                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...
412 1
                || in_array(AutoLoader::className('IViewSettable', 'samson\core'), $implements)
413 1
                || in_array('samsonframework\core\RenderInterface', $implements)
414 1
            ) {
415 1
                $this->_setObject($field, $value);
416 1
            }
417 1
        } // If array is passed
418 3
        else if (is_array($field)) $this->_setArray($field, $value);
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...
419
        // Set view variable
420 3
        else $this->data[$field] = $value;
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...
421 3
    }
422
423 1
    public function offsetUnset($offset)
424
    {
425 1
        unset($this->data[$offset]);
426 1
    }
427
428 2
    public function offsetExists($offset)
429
    {
430 2
        return isset($this->data[$offset]);
431
    }
432
433
    /** Sort array by string length */
434 1
    protected function sortStrings($a, $b)
435
    {
436 1
        return strlen($b) - strlen($a);
437
    }
438
439
    /**
440
     * Create unique module cache folder structure in local web-application.
441
     *
442
     * @param string $file Path to file relative to module cache location
443
     * @param boolean $clear Flag to perform generic cache folder clearence
444
     * @return boolean TRUE if cache file has to be regenerated
445
     */
446 1
    protected function cache_refresh(&$file, $clear = true)
0 ignored issues
show
Coding Style introduced by
Method name "Module::cache_refresh" is not in camel caps format
Loading history...
447
    {
448
        // If module cache folder does not exists - create it
449 1
        if (!file_exists($this->cache_path)) {
450 1
            mkdir($this->cache_path, 0777, true);
451 1
        }
452
453
        // Build full path to cached file
454 1
        $file = $this->cache_path . $file;
455
456
        // If cached file does not exsits
457 1
        if (!file_exists($file)) {
458
            // If clearence flag set to true - clear all files in module cache directory with same extension
459 1
            if ($clear) {
460 1
                File::clear($this->cache_path, pathinfo($file, PATHINFO_EXTENSION));
461 1
            }
462
463
            // Signal for cache file regeneration
464 1
            return true;
465
        }
466
467
        return false;
468
    }
469
470
    /**
471
     *
472
     * @param unknown $object
473
     * @param string $viewprefix
474
     */
475 1
    private function _setObject($object, $viewprefix = null)
476
    {
477
        // Generate viewprefix as only lowercase classname without NS if it is not specified
478 1
        $class_name = is_string($viewprefix) ? $viewprefix : '' . mb_strtolower(classname(get_class($object)), 'UTF-8');
479
480
        // Save object to view data
481 1
        $this->data[$class_name] = $object;
482
483
        // Add separator
484 1
        $class_name .= '_';
485
486
        // Generate objects view array data and merge it with view data
487 1
        $this->data = array_merge($this->data, $object->toView($class_name));
488 1
    }
489
490
    /**
491
     *
492
     * @param unknown $array
493
     * @param string $viewprefix
494
     */
495 1
    private function _setArray($array, $viewprefix = null)
496
    {
497
        // Save array to view data
498 1
        $this->data[$viewprefix] = $array;
499
500
        // Add array values to view data
501 1
        $this->data = array_merge($this->data, $array);
502 1
    }
503
}
504