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