Completed
Push — master ( 1e7423...19ef53 )
by Vitaly
02:23
created

Module::offsetUnset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
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
            // Define if this is a procedural controller or OOP
253
            $callback = array($this, iModule::OBJ_PREFIX . $controller);
254
255
            // If this controller action is present
256
            if (is_callable($callback)) {
257
                // Get passed arguments
258
                $parameters = func_get_args();
259
                // Remove first as its a controller action name
260
                array_shift($parameters);
261
                // Perform controller action with passed parameters
262
                call_user_func($callback, $parameters);
263
            } else {
264
                throw(new ControllerActionNotFound($this->id . '#' . $controller));
265
            }
266
        }
267
268
        // Output view
269
        echo $this->output();
270
271
        // Restore previous active module
272
        $this->system->active($old);
273
    }
274
275
    /**    @see iModule::output() */
276
    public function output($viewPath = null)
277
    {
278
        // If view path not specified - use current correct view path
279
        if (!isset($viewPath)) {
280
            $viewPath = $this->view_path;
281
        } elseif (isset($viewPath{0})) { // Direct rendering of specific view, not default view data entry
282
            elapsed('Outputting to a view is deprecated, split your rendering chain into ->view(..)->output()');
283
            $viewPath = $this->findView($viewPath);
284
        }
285
286
        //elapsed('['.$this->id.'] Rendering view context: ['.$viewPath.'] with ['.$renderer->id.']');
287
288
        // Switch view context to new module view
289
        $this->viewContext($viewPath);
290
291
        //elapsed($this->id.' - Outputing '.$view_path.'-'.sizeof($this->data));
292
        //elapsed(array_keys($this->view_data));
293
294
        // Get current view context plain HTML
295
        $out = $this->data[self::VD_HTML];
296
297
        // If view path specified
298
        if (isset($viewPath{0})) {
299
            // Временно изменим текущий модуль системы
300
            $old = $this->system->active($this);
301
302
            // Прорисуем представление модуля
303
            $out .= $this->system->render($this->path . $viewPath, $this->data);
304
305
            // Вернем на место текущий модуль системы
306
            $this->system->active($old);
307
        } elseif (!isset($out{0})) { // No plain HTML view data is set also
308
            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...
309
        }
310
311
        // Clear currently outputted view context from VCS
312
        unset($this->view_data[$viewPath]);
313
314
        // Get last element from VCS
315
        end($this->view_data);
316
317
        // Get last element from VCS name
318
        $this->view_context = key($this->view_data);
319
320
        // Set internal view data pointer to last VCS entry
321
        $this->data = &$this->view_data[$this->view_context];
322
323
        // Return view path to previous state
324
        $this->view_path = $this->view_context;
325
326
        // Вернем результат прорисовки
327
        return $out;
328
    }
329
330
    /** Обработчик уничтожения объекта */
331
    public function __destruct()
332
    {
333
        //trace('Уничтожение модуля:'.$this->id );
334
335
        // Очистим коллекцию загруженых модулей
336
        unset(Module::$instances[$this->id]);
337
    }
338
339
    /** Magic method for calling unexisting object methods */
340
    public function __call($method, $arguments)
341
    {
342
        //elapsed($this->id.' - __Call '.$method);
343
344
        // If value is passed - set it
345
        if (sizeof($arguments)) {
346
            // If first argument is object or array - pass method name as second parameter
347
            if (is_object($arguments[0]) || is_array($arguments[0])) $this->__set($arguments[0], $method);
348
            // Standard logic
349
            else  $this->__set($method, $arguments[0]);
350
        }
351
352
        // Chaining
353
        return $this;
354
    }
355
356
    // Магический метод для получения переменных представления модуля
357
358
    /** Обработчик сериализации объекта */
359
    public function __sleep()
360
    {
361
        return array('id', 'path', 'data', 'views');
362
    }
363
364
    /** Обработчик десериализации объекта */
365
    public function __wakeup()
366
    {
367
        // Fill global instances
368
        self::$instances[$this->id] = &$this;
369
370
        // Set up default view data pointer
371
        $this->view_data[self::VD_POINTER_DEF] = $this->data;
372
373
        // Set reference to view context entry
374
        $this->data = &$this->view_data[self::VD_POINTER_DEF];
375
    }
376
377
    // TODO: Переделать обработчик в одинаковый вид для объектов и простых
378
379
    /** Группа методов для доступа к аттрибутам в виде массива */
380
    public function offsetSet($offset, $value)
381
    {
382
        $this->__set($offset, $value);
383
    }
384
385
    public function offsetGet($offset)
386
    {
387
        return $this->__get($offset);
388
    }
389
390
    // Магический метод для установки переменных представления модуля
391
392
    public function __get($field)
393
    {
394
        // Установим пустышку как значение переменной
395
        $result = null;
396
397
        // Если указанная переменная представления существует - получим её значение
398
        if (isset($this->data[$field])) $result = &$this->data[$field];
399
        // Выведем ошибку
400
        else return e('Ошибка получения данных модуля(##) - Требуемые данные(##) не найдены', E_SAMSON_CORE_ERROR, array($this->id, $field));
401
402
        // Иначе вернем пустышку
403
        return $result;
404
    }
405
406
    public function __set($field, $value = null)
407
    {
408
        // This is object
409
        if (is_object($field)) {
410
            $implements = class_implements($field);
411
            // If iModuleViewable implements is passed
412
            if (
413
                // TODO: Remove old interface support in future
414
                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...
415
                || in_array(AutoLoader::className('IViewSettable', 'samson\core'), $implements)
416
                || in_array('samsonframework\core\RenderInterface', $implements)
417
            ) {
418
                $this->_setObject($field, $value);
419
            }
420
        } // If array is passed
421
        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...
422
        // Set view variable
423
        else $this->data[$field] = $value;
424
    }
425
426
    public function offsetUnset($offset)
427
    {
428
        $this->data[$offset] = '';
429
    }
430
431
    public function offsetExists($offset)
432
    {
433
        return isset($this->data[$offset]);
434
    }
435
436
    /** Sort array by string length */
437
    protected function sortStrings($a, $b)
438
    {
439
        return strlen($b) - strlen($a);
440
    }
441
442
    /**
443
     * Create unique module cache folder structure in local web-application
444
     * @param string $file Path to file relative to module cache location
445
     * @param boolean $clear Flag to perform generic cache folder clearence
446
     * @return boolean TRUE if cache file has to be regenerated
447
     */
448
    protected function cache_refresh(& $file, $clear = true)
449
    {
450
        // If module cache folder does not exists - create it
451
        if (!file_exists($this->cache_path)) mkdir($this->cache_path, 0777, TRUE);
452
453
        // Build full path to cached file
454
        $file = $this->cache_path . $file;
455
456
        // If cached file does not exsits
457
        if (file_exists($file)) return false;
458
        // Needed file does not exists
459
        else {
460
            // If clearence flag set to true - clear all files in module cache directory with same extension
461
            if ($clear) File::clear($this->cache_path, pathinfo($file, PATHINFO_EXTENSION));
462
463
            // Singal for cache file regeneration
464
            return true;
465
        }
466
    }
467
468
    /**
469
     *
470
     * @param unknown $object
471
     * @param string $viewprefix
472
     */
473
    private function _setObject($object, $viewprefix = null)
474
    {
475
        // Generate viewprefix as only lowercase classname without NS if it is not specified
476
        $class_name = is_string($viewprefix) ? $viewprefix : '' . mb_strtolower(classname(get_class($object)), 'UTF-8');
477
478
        // Save object to view data
479
        $this->data[$class_name] = $object;
480
481
        // Add separator
482
        $class_name .= '_';
483
484
        // Generate objects view array data and merge it with view data
485
        $this->data = array_merge($this->data, $object->toView($class_name));
486
    }
487
488
    /**
489
     *
490
     * @param unknown $array
491
     * @param string $viewprefix
492
     */
493
    private function _setArray($array, $viewprefix = null)
494
    {
495
        // Save array to view data
496
        $this->data[$viewprefix] = $array;
497
498
        // Add array values to view data
499
        $this->data = array_merge($this->data, $array);
500
    }
501
}
502