PageRenderTime 63ms CodeModel.GetById 16ms app.highlight 39ms RepoModel.GetById 1ms app.codeStats 0ms

/system/core/load.php

https://github.com/gintsmurans/staticphp
PHP | 730 lines | 289 code | 76 blank | 365 comment | 21 complexity | c11da9897267acedb9175f4aeaff422b MD5 | raw file
  1<?php
  2
  3
  4namespace core;
  5
  6/**
  7 * LogLevel class.
  8 *
  9 * Defines multiple error level constants.
 10 */
 11class LogLevel
 12{
 13    const EMERGENCY = 'emergency';
 14    const ALERT = 'alert';
 15    const CRITICAL = 'critical';
 16    const ERROR = 'error';
 17    const WARNING = 'warning';
 18    const NOTICE = 'notice';
 19    const INFO = 'info';
 20    const DEBUG = 'debug';
 21}
 22
 23/**
 24 * Core class for loading resources, setting timers for profiling and error handling.
 25 */
 26class load
 27{
 28    /**
 29     * Global configuration array of mixed data.
 30     *
 31     * (default value: [])
 32     *
 33     * @var array
 34     * @access public
 35     * @static
 36     */
 37    public static $config = [];
 38
 39    /**
 40     * Array for started timers.
 41     *
 42     * (default value: [])
 43     *
 44     * @var array
 45     * @access protected
 46     * @static
 47     */
 48    protected static $started_timers = [];
 49
 50    /**
 51     * Array for finished timers.
 52     *
 53     * (default value: [])
 54     *
 55     * @var array
 56     * @access protected
 57     * @static
 58     */
 59    protected static $finished_timers = [];
 60
 61    /**
 62     * Array for log entries.
 63     *
 64     * (default value: [])
 65     *
 66     * @var array
 67     * @access protected
 68     * @static
 69     */
 70    protected static $logs = [];
 71
 72    /*
 73    |--------------------------------------------------------------------------
 74    | Configuration Methods
 75    |--------------------------------------------------------------------------
 76    */
 77
 78    /**
 79     * Get value from config by $key.
 80     *
 81     * Optionally set default value if there are no config value by $key found.
 82     *
 83     * @access public
 84     * @static
 85     * @param  string     $key
 86     * @param  mixed|null $default (default: null)
 87     * @return mixed      Returns mixed data
 88     */
 89    public static function &get($key, $default = null)
 90    {
 91        return (isset(self::$config[$name]) ? self::$config[$name] : $default);
 92    }
 93
 94    /**
 95     * Set configuration value.
 96     *
 97     * @access public
 98     * @static
 99     * @param  string $name
100     * @param  mixed  $value
101     * @return mixed  Returns new value
102     */
103    public static function set($name, $value)
104    {
105        return (self::$config[$name] = $value);
106    }
107
108    /**
109     * Merge configuration values.
110     *
111     * Merge configuration value by $name with $value. If $overwrite is set to true, same key values will be overwritten.
112     *
113     * @access public
114     * @static
115     * @param  string $name
116     * @param  mixed  $value
117     * @param  bool   $owerwrite (default: true)
118     * @return void
119     */
120    public static function merge($name, $value, $owerwrite = true)
121    {
122        if (!isset(self::$config[$name])) {
123            return (self::$config[$name] = $value);
124        }
125
126        switch (true) {
127            case is_array(self::$config[$name]):
128                if (empty($owerwrite)) {
129                    return (self::$config[$name] += $value);
130                } else {
131                    return (self::$config[$name] = array_merge((array) self::$config[$name], (array) $value));
132                }
133                break;
134
135            case is_object(self::$config[$name]):
136                if (empty($owerwrite)) {
137                    return (self::$config[$name] = (object) ((array) self::$config[$name] + (array) $value));
138                } else {
139                    return (self::$config[$name] = (object) array_merge((array) self::$config[$name], (array) $value));
140                }
141                break;
142
143            case is_int(self::$config[$name]):
144            case is_float(self::$config[$name]):
145                return (self::$config[$name] += $value);
146                break;
147
148            case is_string(self::$config[$name]):
149            default:
150                return (self::$config[$name] .= $value);
151                break;
152        }
153    }
154
155    /*
156    |--------------------------------------------------------------------------
157    | Filesystem Methods
158    |--------------------------------------------------------------------------
159    */
160
161    /**
162     * Generate UUID v4.
163     *
164     * @author http://php.net/manual/en/function.uniqid.php#94959
165     * @access public
166     * @static
167     * @return void
168     */
169    public static function uuid4()
170    {
171        return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
172            // 32 bits for "time_low"
173            mt_rand(0, 0xffff), mt_rand(0, 0xffff),
174
175            // 16 bits for "time_mid"
176            mt_rand(0, 0xffff),
177
178            // 16 bits for "time_hi_and_version",
179            // four most significant bits holds version number 4
180            mt_rand(0, 0x0fff) | 0x4000,
181
182            // 16 bits, 8 bits for "clk_seq_hi_res",
183            // 8 bits for "clk_seq_low",
184            // two most significant bits holds zero and one for variant DCE1.1
185            mt_rand(0, 0x3fff) | 0x8000,
186
187            // 48 bits for "node"
188            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
189        );
190    }
191
192    /**
193     * Generate sha1 hash from random v4 uuid.
194     *
195     * @see load::uuid4()
196     * @access public
197     * @static
198     * @return void
199     */
200    public static function randomHash()
201    {
202        return sha1(self::uuid4());
203    }
204
205    /**
206     * Generate hashed path.
207     *
208     * Generate hashed path to avoid reaching files per directory limit ({@link http://stackoverflow.com/a/466596}).
209     * By default it will create directories 2 levels deep and 2 symbols long, for example, for a filename /www/upload/files/image.jpg,
210     * it will generate filename /www/upload/files/ge/ma/image.jpg and optionally create all directories. It is also suggested
211     * for cases where image name is not important to set $randomize to true. This way generated filename becomes a sha1 hash
212     * and will provide better file distribution between directories.
213     *
214     * @see load::randomHash()
215     * @access public
216     * @static
217     * @param  string   $filename
218     * @param  bool     $randomize             (default: false)
219     * @param  bool     $create_directories    (default: false)
220     * @param  int      $levels_deep           (default: 2)
221     * @param  int      $directory_name_length (default: 2)
222     * @return string[] An array of string objects:
223     *                  <ul>
224     *                  <li>'hash_dir' Contains only hashed directory (e.g. ge/ma);</li>
225     *                  <li>'hash_file' hash_dir + filename (ge/ma/image.jpg);</li>
226     *                  <li>'filename' Filename without extension;</li>
227     *                  <li>'ext' File extension;</li>
228     *                  <li>'dir' Absolute path to file's containing directory, including hashed directories (/www/upload/files/ge/ma/);</li>
229     *                  <li>'file' Full path to a file.</li>
230     *                  </ul>
231     */
232    public static function hashedPath($filename, $randomize = false, $create_directories = false, $levels_deep = 2, $directory_name_length = 2)
233    {
234        // Explode path to get filename
235        $parts = explode(DIRECTORY_SEPARATOR, $filename);
236
237        // Predefine array elements
238        $data['hash_dir'] = '';
239        $data['hash_file'] = '';
240
241        // Get filename and extension
242        $data['filename'] = explode('.', array_pop($parts));
243        $data['ext'] = (count($data['filename']) > 1 ? array_pop($data['filename']) : '');
244        $data['filename'] = (empty($randomize) ? implode('.', $data['filename']) : self::randomHash());
245
246        if (strlen($data['filename']) < $levels_deep * $directory_name_length) {
247            throw new Exception('Filename length too small to satisfy how much sub-directories and how long each directory name should be made.');
248        }
249
250        // Put directory together
251        $dir = (empty($parts) ? '' : implode('/', $parts).'/');
252
253        // Create hashed directory
254        for ($i = 1; $i <= $levels_deep; ++$i) {
255            $data['hash_dir'] .= substr($data['filename'], -1 * $directory_name_length * $i, $directory_name_length).'/';
256        }
257
258        // Put other stuff together
259        $data['dir'] = str_replace($data['hash_dir'], '', $dir).$data['hash_dir'];
260        $data['file'] = $data['dir'].$data['filename'].(empty($data['ext']) ? '' : '.'.$data['ext']);
261        $data['hash_file'] = $data['hash_dir'].$data['filename'].(empty($data['ext']) ? '' : '.'.$data['ext']);
262
263        // Create directories
264        if (!empty($create_directories) && !is_dir($data['dir'])) {
265            mkdir($data['dir'], 0777, true);
266        }
267
268        return $data;
269    }
270
271    /**
272     * Delete file and directories created by load::hashedPath.
273     *
274     * @see load::hashedPath
275     * @access public
276     * @static
277     * @param  string $filename
278     * @return void
279     */
280    public static function deleteHashedFile($filename)
281    {
282        $path = self::hashedPath($filename);
283
284        // Trim off / from end
285        $path['hash_dir'] = rtrim($path['hash_dir'], '/');
286        $path['dir'] = rtrim($path['dir'], '/');
287
288        // Explode hash directories to get the count of them
289        $expl = explode('/', $path['hash_dir']);
290
291        // Unlink the file
292        if (is_file($path['file'])) {
293            unlink($path['file']);
294        }
295
296        // Remove directories
297        foreach ($expl as $null) {
298            if (!@rmdir($path['dir'])) {
299                break;
300            }
301
302            $path['dir'] = dirname($path['dir']);
303        }
304    }
305
306    /*
307    |--------------------------------------------------------------------------
308    | File Loading
309    |--------------------------------------------------------------------------
310    */
311
312    /**
313     * Load configuration files.
314     *
315     * Load configuration files from current application's config directory (APP_PATH/config) or
316     * from other application by providing name in $project parameter.
317     *
318     * @access public
319     * @static
320     * @param  string|array $files
321     * @param  string|null  $project (default: null)
322     * @return void
323     */
324    public static function config($files, $project = null)
325    {
326        $config = & self::$config;
327        foreach ((array) $files as $key => $name) {
328            $project1 = $project;
329            if (is_numeric($key) === false) {
330                $project1 = $name;
331                $name = $key;
332            }
333            require(empty($project1) ? APP_PATH : BASE_PATH.$project1.DS).'config'.DS.$name.'.php';
334        }
335    }
336
337    /**
338     * Load controller files.
339     *
340     * Load controller files from current application's controller directory (APP_PATH/controllers) or
341     * from other application by providing name in $project parameter.
342     *
343     * @access public
344     * @static
345     * @param  string|array $files
346     * @param  string|null  $project (default: null)
347     * @return void
348     */
349    public static function controller($files, $project = null)
350    {
351        foreach ((array) $files as $key => $name) {
352            $project1 = $project;
353            if (is_numeric($key) === false) {
354                $project1 = $name;
355                $name = $key;
356            }
357            require(empty($project1) ? APP_PATH : BASE_PATH.$project1.DS).'controllers'.DS.$name.'.php';
358        }
359    }
360
361    /**
362     * Load model files.
363     *
364     * Load model files from current application's model directory (APP_PATH/models) or
365     * from other application by providing name in $project parameter.
366     *
367     * @access public
368     * @static
369     * @param  string|array $files
370     * @param  string|null  $project (default: null)
371     * @return void
372     */
373    public static function model($files, $project = null)
374    {
375        foreach ((array) $files as $key => $name) {
376            $project1 = $project;
377            if (is_numeric($key) === false) {
378                $project1 = $name;
379                $name = $key;
380            }
381            require(empty($project1) ? APP_PATH : BASE_PATH.$project1.DS).'models'.DS.$name.'.php';
382        }
383    }
384
385    /**
386     * Load helper files.
387     *
388     * Load helper files from current application's helper directory (APP_PATH/helpers) or
389     * from other application by providing name in $project parameter.
390     *
391     * @access public
392     * @static
393     * @param  string|array $files
394     * @param  string|null  $project (default: null)
395     * @return void
396     */
397    public static function helper($files, $project = null)
398    {
399        foreach ((array) $files as $key => $name) {
400            $project1 = $project;
401            if (is_numeric($key) === false) {
402                $project1 = $name;
403                $name = $key;
404            }
405            require(empty($project1) ? APP_PATH : BASE_PATH.$project1.DS).'helpers'.DS.$name.'.php';
406        }
407    }
408
409    /**
410     * Render a view or multiple views.
411     *
412     * Render views from current application's view directory (APP_PATH/views).
413     * Setting $return to true, instead of outputing, rendered view's html will be returned.
414     *
415     * @access public
416     * @static
417     * @param  strign|array $files
418     * @param  array        &$data  (default: [])
419     * @param  bool         $return (default: false)
420     * @return void|string
421     */
422    public static function view($files, &$data = [], $return = false)
423    {
424        static $globals_added = false;
425
426        // Check for global views variables, can be set, for example, by controller's constructor
427        if (!empty(self::$config['view_data'])) {
428            $data = (array) $data + (array) self::$config['view_data'];
429        }
430
431        // Add default view data
432        if (empty($globals_added)) {
433            load::$config['view_engine']->addGlobal('base_url', router::$base_url);
434            load::$config['view_engine']->addGlobal('config', self::$config);
435            load::$config['view_engine']->addGlobal('namespace', router::$namespace);
436            load::$config['view_engine']->addGlobal('class', router::$class);
437            load::$config['view_engine']->addGlobal('method', router::$method);
438            load::$config['view_engine']->addGlobal('segments', router::$segments);
439            $globals_added = true;
440        }
441
442        // Load view data
443        $contents = '';
444        foreach ((array) $files as $key => $file) {
445            $contents .= load::$config['view_engine']->render($file, (array) $data);
446        }
447
448        // Output or return view data
449        if (empty($return)) {
450            echo $contents;
451
452            return true;
453        }
454
455        return $contents;
456    }
457
458    /*
459    |--------------------------------------------------------------------------
460    | Timer methods
461    |--------------------------------------------------------------------------
462    */
463
464    /**
465     * Start timer. Timers are started incrementally, i.e. if two timers are started, first second timer needs to be stopped, then first one.
466     *
467     * @access public
468     * @static
469     * @return void
470     */
471    public static function startTimer()
472    {
473        self::$started_timers[] = microtime(true);
474    }
475
476    /**
477     * Stop timer by providing name of the timer.
478     *
479     * @access public
480     * @static
481     * @param  string $name
482     * @return float  Returns time in microseconds it took timer to execute.
483     */
484    public static function stopTimer($name)
485    {
486        self::$finished_timers[$name] = round(microtime(true) - array_shift(self::$started_timers), 5);
487
488        return self::$finished_timers[$name];
489    }
490
491    /**
492     * Mark time with a name.
493     *
494     * @access public
495     * @static
496     * @param  string $name
497     * @return float  Returns time in microseconds it took to execute from startup to the time the method was called.
498     */
499    public static function markTime($name)
500    {
501        global $microtime;
502        self::$finished_timers['*'.$name] = round(microtime(true) - $microtime, 5);
503
504        return self::$finished_timers['*'.$name];
505    }
506
507    /**
508     * Generate debug output for all timers.
509     *
510     * @access protected
511     * @static
512     * @return void
513     */
514    protected static function executionTime()
515    {
516        global $microtime;
517
518        self::info('Total execution time: '.round(microtime(true) - $microtime, 5)." seconds;");
519        self::info('Memory used: '.round(memory_get_usage() / 1024 / 1024, 4)." MB;\n");
520
521        if (!empty(self::$finished_timers)) {
522            krsort(self::$finished_timers);
523            foreach (self::$finished_timers as $key => $value) {
524                self::info("[{$value}s] {$key}");
525            }
526        }
527    }
528
529    /*
530    |--------------------------------------------------------------------------
531    | Logger methods
532    |--------------------------------------------------------------------------
533    */
534
535    /**
536     * System is unusable.
537     *
538     * @param  string $message
539     * @param  array  $context
540     * @return void
541     */
542    public static function emergency($message, array $context = array())
543    {
544        self::log(LogLevel::EMERGENCY, $message, $context);
545    }
546
547    /**
548     * Action must be taken immediately.
549     *
550     * Example: Entire website down, database unavailable, etc. This should
551     * trigger the SMS alerts and wake you up.
552     *
553     * @param  string $message
554     * @param  array  $context
555     * @return void
556     */
557    public static function alert($message, array $context = array())
558    {
559        self::log(LogLevel::ALERT, $message, $context);
560    }
561
562    /**
563     * Critical conditions.
564     *
565     * Example: Application component unavailable, unexpected exception.
566     *
567     * @param  string $message
568     * @param  array  $context
569     * @return void
570     */
571    public static function critical($message, array $context = array())
572    {
573        self::log(LogLevel::CRITICAL, $message, $context);
574    }
575
576    /**
577     * Runtime errors that do not require immediate action but should typically
578     * be logged and monitored.
579     *
580     * @param  string $message
581     * @param  array  $context
582     * @return void
583     */
584    public static function error($message, array $context = array())
585    {
586        self::log(LogLevel::ERROR, $message, $context);
587    }
588
589    /**
590     * Exceptional occurrences that are not errors.
591     *
592     * Example: Use of deprecated APIs, poor use of an API, undesirable things
593     * that are not necessarily wrong.
594     *
595     * @param  string $message
596     * @param  array  $context
597     * @return void
598     */
599    public static function warning($message, array $context = array())
600    {
601        self::log(LogLevel::WARNING, $message, $context);
602    }
603
604    /**
605     * Normal but significant events.
606     *
607     * @param  string $message
608     * @param  array  $context
609     * @return void
610     */
611    public static function notice($message, array $context = array())
612    {
613        self::log(LogLevel::NOTICE, $message, $context);
614    }
615
616    /**
617     * Interesting events.
618     *
619     * Example: User logs in, SQL logs.
620     *
621     * @param  string $message
622     * @param  array  $context
623     * @return void
624     */
625    public static function info($message, array $context = array())
626    {
627        self::log(LogLevel::INFO, $message, $context);
628    }
629
630    /**
631     * Detailed debug information.
632     *
633     * @param  string $message
634     * @param  array  $context
635     * @return void
636     */
637    public static function debug($message, array $context = array())
638    {
639        self::log(LogLevel::DEBUG, $message, $context);
640    }
641
642    /**
643     * Logs with an arbitrary level.
644     *
645     * @param  mixed  $level
646     * @param  string $message
647     * @param  array  $context
648     * @return void
649     */
650    public static function log($level, $message, array $context = array())
651    {
652        self::$logs[] = ['level' => $level, 'message' => $message, 'context' => $context];
653    }
654
655    /*
656    |--------------------------------------------------------------------------
657    | Debug Output
658    |--------------------------------------------------------------------------
659    */
660
661    /**
662     * Generate debug output.
663     *
664     * @see load::emergency()
665     * @see load::alert()
666     * @see load::critical()
667     * @see load::error()
668     * @see load::warning()
669     * @see load::notice()
670     * @see load::info()
671     * @access public
672     * @static
673     * @return string Returns formatted html string of debug information, including timers, but also custom messages logged using logger interface.
674     */
675    public static function debugOutput()
676    {
677        // Log execution time
678        self::executionTime();
679
680        // Generate debug output
681        $output = '';
682        foreach (self::$logs as $item) {
683            $class = '';
684            switch ($item['level']) {
685                case LogLevel::EMERGENCY:
686                case LogLevel::ALERT:
687                case LogLevel::CRITICAL:
688                    $class = 'danger';
689                    break;
690
691                case LogLevel::ERROR:
692                case LogLevel::WARNING:
693                    $class = 'warning';
694                    break;
695
696                case LogLevel::NOTICE:
697                case LogLevel::INFO:
698                case LogLevel::DEBUG:
699                    $class = 'info';
700                    break;
701            }
702
703            $output .= '<span class="text-'.$class.'">'.strtoupper($item['level']).': </span>';
704            $output .= $item['message'];
705            $output .= (!empty($item['context']) ? " [".implode(',', $item['context'])."]\n" : "\n");
706        }
707
708        // Return it
709        return $output;
710    }
711}
712
713// Autoload function
714spl_autoload_register(function ($classname) {
715    $classname = str_replace('\\', DS, $classname);
716    $classname = ltrim($classname, DS);
717
718    if (is_file(APP_PATH.$classname.'.php')) {
719        require APP_PATH.$classname.'.php';
720    } elseif (is_file(SYS_PATH.$classname.'.php')) {
721        require SYS_PATH.$classname.'.php';
722    } else {
723        $classname = dirname($classname);
724        if (is_file(APP_PATH.$classname.'.php')) {
725            require APP_PATH.$classname.'.php';
726        } elseif (is_file(SYS_PATH.$classname.'.php')) {
727            require SYS_PATH.$classname.'.php';
728        }
729    }
730}, true, true);