Completed
Push — master ( dce688...93c5f6 )
by Vitaly
02:26
created

Module::output()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 46
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 2 Features 2
Metric Value
c 6
b 2
f 2
dl 0
loc 46
rs 8.9412
cc 2
eloc 14
nc 2
nop 0
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)
152
    {
153
        $this->data[self::VD_HTML] = $value;
154
155
        return $this;
156
    }
157
158
    public function view($viewPath)
159
    {
160
        // Find full path to view file
161
        $viewPath = $this->findView($viewPath);
162
163
        // We could not find view
164
        if ($viewPath !== false) {
165
            // Switch view context to founded module view
166
            $this->viewContext($viewPath);
167
168
            // Set current view path
169
            $this->view_path = $viewPath;
170
        } else {
171
            throw(new ViewPathNotFound($viewPath));
172
        }
173
174
        // Продолжим цепирование
175
        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...
176
    }
177
178
    /**
179
     * Find view file by its part in module view resources and return full path to it.
180
     *
181
     * @param string $viewPath Part of path to module view file
182
     * @return string Full path to view file
183
     */
184
    public function findView($viewPath)
185
    {
186
        // Remove file extension for correct array searching
187
        $viewPath = str_replace(array('.php', '.vphp'), '', $viewPath);
188
189
        // Try to find passed view_path in  resources views collection
190
        if (sizeof($view = preg_grep('/' . addcslashes($viewPath, '/\\') . '(\.php|\.vphp)/ui', $this->views))) {
191
            // Sort view paths to get the shortest path
192
            usort($view, array($this, 'sortStrings'));
193
194
            // Set current full view path as last found view
195
            return end($view);
196
        }
197
198
        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...
199
    }
200
201
    /**
202
     * Perform module view context switching
203
     * @param string $view_path New view context name
204
     */
205
    protected function viewContext($view_path)
206
    {
207
        // Pointer to NEW view data context
208
        $new = &$this->view_data[$view_path];
209
210
        // Pointer to OLD view data context
211
        $old = &$this->view_data[$this->view_context];
212
213
        // If we are trying to switch to NEW view context
214
        if ($this->view_context !== $view_path) {
215
            //elapsed( $this->id.' - Switching view context from '.$this->view_context.' to '.$view_path );
216
217
            // Create new entry in view data collection if it does not exists
218
            if (!isset($this->view_data[$view_path])) {
219
                // Create new view data record
220
                $new = array();
221
222
                // If current view data context has view data
223
                if (isset($old)) {
224
                    //elapsed($old);
225
                    //elapsed( $this->id.' - Copying previous view context view data '.$this->view_context.' to new view context '.$view_path.'('.sizeof($old).')');
226
227
                    // Copy default view context view data to new view context
228
                    $new = array_merge($new, $old);
229
                }
230
231
                // Clear plain HTML for new view context
232
                $new[self::VD_HTML] = '';
233
            }
234
235
            // Change view data pointer to appropriate view data entry
236
            $this->data = &$new;
237
238
            // Save current context name
239
            $this->view_context = $view_path;
240
        }
241
        //else elapsed( $this->id.' - NO need to switch view context from '.$this->view_context.' to '.$view_path );
242
    }
243
244
    /**    @see iModule::render() */
245
    public function render($controller = null)
246
    {
247
        // Switch current system active module
248
        $old = &$this->system->active($this);
249
250
        // If specific controller action should be run
251
        if (isset($controller)) {
252
            /**
253
             * TODO: This would be removed in next major version, here should be
254
             * passed real controller method for execution.
255
             */
256
            $controller = method_exists($this, iModule::OBJ_PREFIX . $controller)
257
                ? iModule::OBJ_PREFIX . $controller
258
                : $controller;
259
260
            // Define if this is a procedural controller or OOP
261
            $callback = array($this, $controller);
262
263
            // If this controller action is present
264
            if (method_exists($this, $controller)) {
265
                // Get passed arguments
266
                $parameters = func_get_args();
267
                // Remove first as its a controller action name
268
                array_shift($parameters);
269
                // Perform controller action with passed parameters
270
                call_user_func_array($callback, $parameters);
271
            } else {
272
                throw(new ControllerActionNotFound($this->id . '#' . $controller));
273
            }
274
        }
275
276
        // Output view
277
        echo $this->output();
278
279
        // Restore previous active module
280
        $this->system->active($old);
281
    }
282
283
    /**    @see iModule::output() */
284
    public function output()
285
    {
286
        // If view path not specified - use current correct view path
287
        $viewPath = $this->view_path;
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 = $this->system->active($this);
304
305
            // Прорисуем представление модуля
306
            $out .= $this->system->render($this->path . $viewPath, $this->data);
307
308
            // Вернем на место текущий модуль системы
309
            $this->system->active($old);
310
        }
311
312
        // Clear currently outputted view context from VCS
313
        unset($this->view_data[$viewPath]);
314
315
        // Get last element from VCS
316
        end($this->view_data);
317
318
        // Get last element from VCS name
319
        $this->view_context = key($this->view_data);
320
321
        // Set internal view data pointer to last VCS entry
322
        $this->data = &$this->view_data[$this->view_context];
323
324
        // Return view path to previous state
325
        $this->view_path = $this->view_context;
326
327
        // Вернем результат прорисовки
328
        return $out;
329
    }
330
331
    /** Обработчик уничтожения объекта */
332
    public function __destruct()
333
    {
334
        unset(Module::$instances[$this->id]);
335
    }
336
337
    /** Magic method for calling unexisting object methods */
338
    public function __call($method, $arguments)
339
    {
340
        //elapsed($this->id.' - __Call '.$method);
341
342
        // If value is passed - set it
343
        if (sizeof($arguments)) {
344
            // If first argument is object or array - pass method name as second parameter
345
            if (is_object($arguments[0]) || is_array($arguments[0])) $this->__set($arguments[0], $method);
346
            // Standard logic
347
            else  $this->__set($method, $arguments[0]);
348
        }
349
350
        // Chaining
351
        return $this;
352
    }
353
354
    // Магический метод для получения переменных представления модуля
355
356
    /** Обработчик сериализации объекта */
357
    public function __sleep()
358
    {
359
        return array('id', 'path', 'data', 'views');
360
    }
361
362
    /** Обработчик десериализации объекта */
363
    public function __wakeup()
364
    {
365
        // Fill global instances
366
        self::$instances[$this->id] = &$this;
367
368
        // Set up default view data pointer
369
        $this->view_data[self::VD_POINTER_DEF] = $this->data;
370
371
        // Set reference to view context entry
372
        $this->data = &$this->view_data[self::VD_POINTER_DEF];
373
    }
374
375
    // TODO: Переделать обработчик в одинаковый вид для объектов и простых
376
377
    /** Группа методов для доступа к аттрибутам в виде массива */
378
    public function offsetSet($offset, $value)
379
    {
380
        $this->__set($offset, $value);
381
    }
382
383
    public function offsetGet($offset)
384
    {
385
        return $this->__get($offset);
386
    }
387
388
    // Магический метод для установки переменных представления модуля
389
390
    public function __get($field)
391
    {
392
        // Установим пустышку как значение переменной
393
        $result = null;
394
395
        // Если указанная переменная представления существует - получим её значение
396
        if (isset($this->data[$field])) $result = &$this->data[$field];
397
        // Выведем ошибку
398
        else return e('Ошибка получения данных модуля(##) - Требуемые данные(##) не найдены', E_SAMSON_CORE_ERROR, array($this->id, $field));
399
400
        // Иначе вернем пустышку
401
        return $result;
402
    }
403
404
    public function __set($field, $value = null)
405
    {
406
        // This is object
407
        if (is_object($field)) {
408
            $implements = class_implements($field);
409
            // If iModuleViewable implements is passed
410
            if (
411
                // TODO: Remove old interface support in future
412
                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...
413
                || in_array(AutoLoader::className('IViewSettable', 'samson\core'), $implements)
414
                || in_array('samsonframework\core\RenderInterface', $implements)
415
            ) {
416
                $this->_setObject($field, $value);
417
            }
418
        } // If array is passed
419
        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...
420
        // Set view variable
421
        else $this->data[$field] = $value;
422
    }
423
424
    public function offsetUnset($offset)
425
    {
426
        unset($this->data[$offset]);
427
    }
428
429
    public function offsetExists($offset)
430
    {
431
        return isset($this->data[$offset]);
432
    }
433
434
    /** Sort array by string length */
435
    protected function sortStrings($a, $b)
436
    {
437
        return strlen($b) - strlen($a);
438
    }
439
440
    /**
441
     * Create unique module cache folder structure in local web-application
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
    protected function cache_refresh(& $file, $clear = true)
447
    {
448
        // If module cache folder does not exists - create it
449
        if (!file_exists($this->cache_path)) mkdir($this->cache_path, 0777, TRUE);
450
451
        // Build full path to cached file
452
        $file = $this->cache_path . $file;
453
454
        // If cached file does not exsits
455
        if (file_exists($file)) return false;
456
        // Needed file does not exists
457
        else {
458
            // If clearence flag set to true - clear all files in module cache directory with same extension
459
            if ($clear) File::clear($this->cache_path, pathinfo($file, PATHINFO_EXTENSION));
460
461
            // Singal for cache file regeneration
462
            return true;
463
        }
464
    }
465
466
    /**
467
     *
468
     * @param unknown $object
469
     * @param string $viewprefix
470
     */
471
    private function _setObject($object, $viewprefix = null)
472
    {
473
        // Generate viewprefix as only lowercase classname without NS if it is not specified
474
        $class_name = is_string($viewprefix) ? $viewprefix : '' . mb_strtolower(classname(get_class($object)), 'UTF-8');
475
476
        // Save object to view data
477
        $this->data[$class_name] = $object;
478
479
        // Add separator
480
        $class_name .= '_';
481
482
        // Generate objects view array data and merge it with view data
483
        $this->data = array_merge($this->data, $object->toView($class_name));
484
    }
485
486
    /**
487
     *
488
     * @param unknown $array
489
     * @param string $viewprefix
490
     */
491
    private function _setArray($array, $viewprefix = null)
492
    {
493
        // Save array to view data
494
        $this->data[$viewprefix] = $array;
495
496
        // Add array values to view data
497
        $this->data = array_merge($this->data, $array);
498
    }
499
}
500