Completed
Push — master ( f827da...0459a0 )
by Vitaly
07:04
created

Core::e404()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 11
rs 9.4286
cc 2
eloc 4
nc 2
nop 1
1
<?php
2
/*
3
 * This file is part of the SamsonPHP\Core package.
4
 * (c) 2013 Vitaly Iegorov <[email protected]>
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
namespace samson\core;
10
11
use samsonframework\core\SystemInterface;
12
use samsonframework\resource\ResourceMap;
13
use samsonphp\config\Scheme;
14
use samsonphp\core\exception\ViewPathNotFound;
15
use samsonphp\event\Event;
16
17
/**
18
 * Core of SamsonPHP
19
 * 
20
 * @package SamsonPHP
21
 * @author 	Vitaly Iegorov <[email protected]>
1 ignored issue
show
Coding Style introduced by
Spaces must be used for alignment; tabs are not allowed
Loading history...
22
 * @version @version@
23
 */
24
class Core implements SystemInterface
25
{
26
    /* Rendering models */
27
    /** Standard algorithm for view rendering */
28
    const RENDER_STANDART = 1;
29
    /** View rendering algorithm from array of view variables */
30
    const RENDER_VARIABLE = 3;
31
32
    /** @var  ResourceMap Current web-application resource map */
33
    public $map;
34
35
    /** @var Module[] Collection of loaded modules */
36
    public $module_stack = array();
37
    /** @var string Path to current web-application */
38
    public $system_path = __SAMSON_CWD__;
39
    /** @var string View path loading mode */
40
    public $render_mode = self::RENDER_STANDART;
41
    /** @var Module Pointer to current active module */
42
    protected $active = null;
43
    /** @var bool Flag for outputting layout template, used for asynchronous requests */
44
    protected $async = false;
45
    /** @var string Path to main system template */
46
    protected $template_path = __SAMSON_DEFAULT_TEMPLATE;
47
48
    /**
49
     * Core constructor.
50
     *
51
     * @param ResourceMap|null $map system resources
52
     */
53
    public function __construct(ResourceMap $map = null)
54
    {
55
        if (!isset($map)) {
56
            // Get correct web-application path
57
            $this->system_path = __SAMSON_CWD__;
58
59
            // Get web-application resource map
60
            $this->map = ResourceMap::get($this->system_path, false, array('src/'));
61
        } else { // Use data from passed map
62
            $this->map = $map;
63
            $this->system_path = $map->entryPoint;
64
        }
65
66
        // Connect static collection with this dynamic field to avoid duplicates
67
        $this->module_stack = &Module::$instances;
68
69
        // Temporary add template worker
70
        $this->subscribe('core.rendered', array($this, 'generateTemplate'));
71
72
        // Fire core creation event
73
        Event::fire('core.created', array(&$this));
74
75
        // Signal core configure event
76
        Event::signal('core.configure', array($this->system_path . __SAMSON_CONFIG_PATH));
77
    }
78
79
    /**
80
     * Generic wrap for Event system subscription.
81
     * @see \samson\core\\samsonphp\event\Event::subscribe()
82
     *
83
     * @param string   $key     Event identifier
84
     * @param callable $handler Event handler
85
     * @param array    $params  Event parameters
86
     *
87
     * @return $this Chaining
88
     */
89
    public function subscribe($key, $handler, $params = array())
90
    {
91
        Event::subscribe($key, $handler, $params);
92
93
        return $this;
94
    }
95
96
    /**
97
     * Change current system working environment.
98
     *
99
     * @param string $environment Environment identifier
100
     *
101
*@return self Chaining
102
     */
103
    public function environment($environment = Scheme::BASE)
104
    {
105
        // Signal core environment change
106
        Event::signal('core.environment.change', array($environment, &$this));
107
108
        return $this;
109
    }
110
111
    /**
112
     * Generate special response header triggering caching mechanisms
113
     * @param int $cacheLife Amount of seconds for cache(default 3600 - 1 hour)
114
     * @param string $accessibility Cache-control accessibility value(default public)
115
     */
116
    public function cached($cacheLife = 3600, $accessibility = 'public')
117
    {
118
        static $cached;
119
        // Protect sending cached headers once
120
        if (!isset($cached) or $cached !== true) {
121
            header('Expires: ' . gmdate('D, d M Y H:i:s T', time() + $cacheLife));
122
            header('Cache-Control: ' . $accessibility . ', max-age=' . $cacheLife);
123
            header('Pragma: cache');
124
125
            $cached = true;
126
        }
127
    }
128
129
    /**
130
     * Set asynchronous mode.
131
     * This mode will not output template and will just path everything that
132
     * was outputted to client.
133
     *
134
     * @param bool $async True to switch to asynchronous output mode
135
     *
136
     * @return $this Chaining
137
     */
138
    public function async($async)
139
    {
140
        $this->async = $async;
141
142
        return $this;
143
    }
144
145
    /** @see iCore::path() */
146
    public function path($path = null)
147
    {
148
        // Если передан аргумент
149
        if (func_num_args()) {
150
            // Сформируем новый относительный путь к главному шаблону системы
151
            $this->template_path = $path . $this->template_path;
152
153
            // Сохраним относительный путь к Веб-приложению
154
            $this->system_path = $path;
155
156
            // Продолжил цепирование
157
            return $this;
158
        }
159
160
        // Вернем текущее значение
161
        return $this->system_path;
162
    }
163
164
    /**    @see iModule::active() */
165
    public function &active(iModule &$module = null)
166
    {
167
        // Сохраним старый текущий модуль
168
        $old = &$this->active;
169
170
        // Если передано значение модуля для установки как текущий - проверим и установим его
171
        if (isset($module)) {
172
            $this->active = &$module;
173
        }
174
175
        // Вернем значение текущего модуля
176
        return $old;
177
    }
178
179
    /**
180
     * Retrieve module instance by identifier.
181
     *
182
     * @param string|null $module Module identifier
183
     *
184
     * @return null|Module Found or active module
185
     */
186
    public function &module(&$module = null)
187
    {
188
        $return = null;
189
190
        // Ничего не передано - вернем текущуй модуль системы
191
        if (!isset($module) && isset($this->active)) {
192
            $return = &$this->active;
193
        } elseif (is_object($module)) {
194
            $return = &$module;
195
        } elseif (is_string($module) && isset($this->module_stack[$module])) {
196
            $return = &$this->module_stack[$module];
197
        }
198
199
        // Ничего не получилось вернем ошибку
200
        if ($return === null) {
201
            e('Не возможно получить модуль(##) системы', E_SAMSON_CORE_ERROR, array($module));
202
        }
203
204
        return $return;
205
    }
206
207
    /**
208
     * Unload module from core.
209
     *
210
     * @param string $moduleID Module identifier
211
     */
212
    public function unload($moduleID)
213
    {
214
        if (isset($this->module_stack[$moduleID])) {
215
            unset($this->module_stack[$moduleID]);
216
        }
217
    }
218
219
    /**
220
     * Insert generic html template tags and data
221
     *
222
     * @param string $templateHtml Generated HTML
223
     *
224
     * @deprecated Must be moved to a new HTML output object
225
     * @return mixed Changed HTML template
226
     */
227
    public function generateTemplate(&$templateHtml)
228
    {
229
        // Добавим путь к ресурсам для браузера
230
        $headHtml = "\n" . '<base href="' . url()->base() . '">';
231
        // Добавим отметку времени для JavaScript
232
        $headHtml .= "\n" . '<script type="text/javascript">var __SAMSONPHP_STARTED = new Date().getTime();</script>';
233
234
        // Добавим поддержку HTML для старых IE
235
        $headHtml .= "\n" . '<!--[if lt IE 9]>';
236
        $headHtml .= "\n" . '<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>';
237
        $headHtml .= "\n" . '<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>';
238
        $headHtml .= "\n" . '<![endif]-->';
239
240
        // Выполним вставку главного тега <base> от которого зависят все ссылки документа
241
        // также подставим МЕТА-теги для текущего модуля и сгенерированный минифицированный CSS
242
        $templateHtml = str_ireplace('<head>', '<head>' . $headHtml, $templateHtml);
243
244
        // Вставим указатель JavaScript ресурсы в конец HTML документа
245
        $templateHtml = str_ireplace('</html>', '</html>' . __SAMSON_COPYRIGHT, $templateHtml);
246
247
        return $templateHtml;
248
    }
249
250
    /**
251
     * Start SamsonPHP framework.
252
     *
253
     * @param string $default Default module identifier
254
     *
255
     * @throws ViewPathNotFound
256
     */
257
    public function start($default)
258
    {
259
        // TODO: Change ExternalModule::init() signature
260
        // Fire core started event
261
        Event::fire('core.started');
262
263
        // TODO: Does not see why it should be here
264
        // Set main template path
265
        $this->template($this->template_path);
266
267
        // Security layer
268
        $securityResult = true;
269
        // Fire core security event
270
        Event::fire('core.security', array(&$this, &$securityResult));
271
272
        /** @var mixed $result External route controller action result */
273
        $result = false;
274
275
        // If we have passed security application layer
276
        if ($securityResult) {
277
            // Fire core routing event - go to routing application layer
278
            Event::signal('core.routing', array(&$this, &$result, $default));
279
        }
280
281
        // If no one has passed back routing callback
282
        if (!isset($result) || $result === false) {
283
            // Fire core e404 - routing failed event
284
            $result = Event::signal('core.e404', array(url()->module, url()->method));
285
        }
286
287
        // Response
288
        $output = '';
289
290
        // If this is not asynchronous response and controller has been executed
291
        if (!$this->async && ($result !== false)) {
292
            // Store module data
293
            $data = $this->active->toView();
294
295
            // Render main template
296
            $output = $this->render($this->template_path, $data);
297
298
            // Fire after render event
299
            Event::fire('core.rendered', array(&$output, &$data, &$this->active));
300
        }
301
302
        // Output results to client
303
        echo $output;
304
305
        // Fire ended event
306
        Event::fire('core.ended', array(&$output));
307
    }
308
309
    /**    @see iCore::template() */
310
    public function template($template = null)
311
    {
312
        // Если передан аргумент
313
        if (func_num_args()) {
314
            $this->template_path = $this->active->path() . $template;
315
        }
316
317
        // Аргументы не переданы - вернем текущий путь к шаблону системы
318
        return $this->template_path;
319
    }
320
321
    /**
322
     * Render file to a buffer.
323
     *
324
     * @param string $view Path to file
325
     * @param array  $data Collection of variables to path to file
326
     *
327
     * @return string Rendered file contents
328
     * @throws ViewPathNotFound
329
     */
330
    public function render($view, $data = array())
331
    {
332
        // TODO: Make rendering as external system, to split up these 3 rendering options
333
334
        // Объявить ассоциативный массив переменных в данном контексте
335
        if (is_array($data)) {
336
            extract($data);
337
        }
338
339
        // Начать вывод в буффер
340
        ob_start();
341
342
        // Path to another template view, by default we are using default template folder path,
343
        // for meeting first condition
344
        $templateView = $view;
345
346
        if (locale() != SamsonLocale::DEF) {
347
            // Modify standard view path with another template
348
            $templateView = str_replace(__SAMSON_VIEW_PATH, __SAMSON_VIEW_PATH . locale() . '/', $templateView);
349
        }
350
351
        // Depending on core view rendering model
352
        switch ($this->render_mode) {
353
            // Standard algorithm for view rendering
354
            case self::RENDER_STANDART:
355
                // Trying to find another template path, by default it's an default template path
356
                if (file_exists($templateView)) {
357
                    include($templateView);
358
                } elseif (file_exists($view)) {
359
                    // If another template wasn't found - we will use default template path
360
                    include($view);
361
                } else { // Error no template view was found
362
                    throw(new ViewPathNotFound($view));
363
                }
364
                break;
365
366
            // View rendering algorithm from array of view variables
367
            case self::RENDER_VARIABLE:
368
                // Collection of views
369
                $views = &$GLOBALS['__compressor_files'];
370
                // Trying to find another template path, by default it's an default template path
371
                if (isset($views[$templateView])) {
372
                    eval(' ?>' . $views[$templateView] . '<?php ');
373
                } elseif (isset($views[$view])) {
374
                    // If another template wasn't found - we will use default template path
375
                    eval(' ?>' . $views[$view] . '<?php ');
376
                } else { // Error no template view was found
377
                    throw(new ViewPathNotFound($view));
378
                }
379
                break;
380
        }
381
382
        // Получим данные из буффера вывода
383
        $html = ob_get_contents();
384
385
        // Очистим буффер
386
        ob_end_clean();
387
388
        // Fire core render event
389
        Event::fire('core.render', array(&$html, &$data, &$this->active));
390
391
        ////elapsed('End rendering '.$__view);
392
        return $html;
393
    }
394
395
    //[PHPCOMPRESSOR(remove,start)]
396
397
    /**
398
     * Load system from composer.json
399
     * @param string $dependencyFilePath Path to dependencies file
400
     * @return $this Chaining
401
     */
402
    public function composer($dependencyFilePath = null)
403
    {
404
        $composerModules = array();
405
406
        Event::fire(
407
            'core.composer.create',
408
            array(
409
                &$composerModules,
410
                isset($dependencyFilePath) ? $dependencyFilePath : $this->system_path,
411
                array(
412
                    'vendorsList' => array('samsonphp/', 'samsonos/', 'samsoncms/'),
413
                    'ignoreKey' => 'samson_module_ignore',
414
                    'includeKey' => 'samson_module_include'
415
                )
416
            )
417
        );
418
419
        // Iterate requirements
420
        foreach ($composerModules as $requirement => $parameters) {
421
            // Load module
422
            $this->load(
423
                __SAMSON_CWD__ . __SAMSON_VENDOR_PATH . $requirement,
424
                array_merge(
425
                    is_array($parameters) ? $parameters : array($parameters),
426
                    array('module_id' => $requirement)
427
                )
428
            );
429
        }
430
431
        // Create local module and set it as active
432
        $this->active = new CompressableLocalModule('local', $this->system_path, $this->map, $this);
433
434
        // TODO: This should be changed to one single logic
435
        // Require all local module model files
436
        foreach ($this->map->models as $model) {
437
            // TODO: Why have to require once?
438
            require_once($model);
439
        }
440
441
        // Create all local modules
442
        foreach ($this->map->controllers as $controller) {
443
            // Require class into PHP
444
            require($controller);
445
446
            // Create module connector instance
447
            new CompressableLocalModule(basename($controller, '.php'), $this->system_path, $this->map, $this);
448
        }
449
450
        return $this;
451
    }
452
453
    /**
454
     * Load module from path to core.
455
     *
456
     * @param string $path       Path for module loading
457
     * @param array  $parameters Collection of loading parameters
458
     *
459
     * @return $this|bool
460
     */
461
    public function load($path, $parameters = array())
462
    {
463
        // Check path
464
        if (file_exists($path)) {
465
            /** @var ResourceMap $resourceMap Gather all resources from path */
466
            $resourceMap = ResourceMap::get($path);
467
            if (isset($resourceMap->module[0])) {
468
469
                /** @var string $controllerPath Path to module controller file */
470
                $controllerPath = $resourceMap->module[1];
471
472
                /** @var string $moduleClass Name of module controller class to load */
473
                $moduleClass = $resourceMap->module[0];
474
475
                // Require module controller class into PHP
476
                if (file_exists($controllerPath)) {
477
                    require_once($controllerPath);
478
                }
479
480
                // TODO: this should be done via composer autoload file field
481
                // Iterate all function-style controllers and require them
482
                foreach ($resourceMap->controllers as $controller) {
483
                    require_once($controller);
484
                }
485
486
                /** @var ExternalModule $connector Create module controller instance */
487
                $connector = new $moduleClass($path, $resourceMap, $this);
488
489
                // Set composer parameters
490
                $connector->composerParameters = $parameters;
491
492
                // Get module identifier
493
                $moduleID = $connector->id();
494
495
                // Fire core module load event
496
                Event::fire('core.module_loaded', array($moduleID, &$connector));
497
498
                // Signal core module configure event
499
                Event::signal('core.module.configure', array(&$connector, $moduleID));
500
501
                // TODO: Think how to decouple this
502
                // Call module preparation handler
503
                if (!$connector->prepare()) {
504
                    // Handle module failed preparing
505
                }
506
507
                // Trying to find parent class for connecting to it to use View/Controller inheritance
508
                $parentClass = get_parent_class($connector);
509
                if (!in_array($parentClass, array(AutoLoader::className('samson\core\ExternalModule'), AutoLoader::className('samson\core\CompressableExternalModule')))) {
510
                    // Переберем загруженные в систему модули
511
                    foreach ($this->module_stack as &$m) {
512
                        // Если в систему был загружен модуль с родительским классом
513
                        if (get_class($m) == $parentClass) {
514
                            $connector->parent = &$m;
515
                            //elapsed('Parent connection for '.$moduleClass.'('.$connector->uid.') with '.$parent_class.'('.$m->uid.')');
516
                        }
517
                    }
518
                }
519
            } elseif (is_array($parameters) && isset($parameters['samsonphp_package_compressable']) && ($parameters['samsonphp_package_compressable'] == 1)) {
520
                /** @var \samson\core\ExternalModule $connector Create module controller instance */
521
                $connector = new CompressableExternalModule($path, $resourceMap, $this);
522
523
                // Set composer parameters
524
                $connector->composerParameters = $parameters;
525
            } else { // Signal error
526
                return e('Cannot load module from: "##"', E_SAMSON_FATAL_ERROR, $path);
527
            }
528
529
        } else {
530
            return e('Cannot load module from[##]', E_SAMSON_FATAL_ERROR, $path);
531
        }
532
533
        // Chaining
534
        return $this;
535
    }
536
    //[PHPCOMPRESSOR(remove,end)]
537
538
    /** Магический метод для десериализации объекта */
539
    public function __wakeup()
540
    {
541
        $this->active = &$this->module_stack['local'];
542
    }
543
544
    /** Магический метод для сериализации объекта */
545
    public function __sleep()
546
    {
547
        return array('module_stack', 'render_mode');
548
    }
549
}
550