Test Failed
Pull Request — master (#33)
by
unknown
15:18 queued 05:24
created

Module::viewContext()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 38
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4

Importance

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