Completed
Push — master ( 033fc6...4822d4 )
by Vitaly
04:13
created

Module::output()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 53
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
c 3
b 1
f 0
dl 0
loc 53
rs 8.7155
cc 5
eloc 20
nc 9
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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