PageRenderTime 65ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/flight/Engine.php

https://gitlab.com/Laolballs/BcryptGenerator
PHP | 507 lines | 245 code | 68 blank | 194 comment | 26 complexity | 401d970aabb21b6734b4de093a7d6f3d MD5 | raw file
  1. <?php
  2. /**
  3. * Flight: An extensible micro-framework.
  4. *
  5. * @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
  6. * @license MIT, http://flightphp.com/license
  7. */
  8. namespace flight;
  9. use flight\core\Loader;
  10. use flight\core\Dispatcher;
  11. /**
  12. * The Engine class contains the core functionality of the framework.
  13. * It is responsible for loading an HTTP request, running the assigned services,
  14. * and generating an HTTP response.
  15. */
  16. class Engine {
  17. /**
  18. * Stored variables.
  19. *
  20. * @var array
  21. */
  22. protected $vars;
  23. /**
  24. * Class loader.
  25. *
  26. * @var object
  27. */
  28. protected $loader;
  29. /**
  30. * Event dispatcher.
  31. *
  32. * @var object
  33. */
  34. protected $dispatcher;
  35. /**
  36. * Constructor.
  37. */
  38. public function __construct() {
  39. $this->vars = array();
  40. $this->loader = new Loader();
  41. $this->dispatcher = new Dispatcher();
  42. $this->init();
  43. }
  44. /**
  45. * Handles calls to class methods.
  46. *
  47. * @param string $name Method name
  48. * @param array $params Method parameters
  49. * @return mixed Callback results
  50. */
  51. public function __call($name, $params) {
  52. $callback = $this->dispatcher->get($name);
  53. if (is_callable($callback)) {
  54. return $this->dispatcher->run($name, $params);
  55. }
  56. $shared = (!empty($params)) ? (bool)$params[0] : true;
  57. return $this->loader->load($name, $shared);
  58. }
  59. /*** Core Methods ***/
  60. /**
  61. * Initializes the framework.
  62. */
  63. public function init() {
  64. static $initialized = false;
  65. $self = $this;
  66. if ($initialized) {
  67. $this->vars = array();
  68. $this->loader->reset();
  69. $this->dispatcher->reset();
  70. }
  71. // Register default components
  72. $this->loader->register('request', '\flight\net\Request');
  73. $this->loader->register('response', '\flight\net\Response');
  74. $this->loader->register('router', '\flight\net\Router');
  75. $this->loader->register('view', '\flight\template\View', array(), function($view) use ($self) {
  76. $view->path = $self->get('flight.views.path');
  77. });
  78. // Register framework methods
  79. $methods = array(
  80. 'start','stop','route','halt','error','notFound',
  81. 'render','redirect','etag','lastModified','json','jsonp'
  82. );
  83. foreach ($methods as $name) {
  84. $this->dispatcher->set($name, array($this, '_'.$name));
  85. }
  86. // Default configuration settings
  87. $this->set('flight.base_url', null);
  88. $this->set('flight.handle_errors', true);
  89. $this->set('flight.log_errors', false);
  90. $this->set('flight.views.path', './views');
  91. $initialized = true;
  92. }
  93. /**
  94. * Enables/disables custom error handling.
  95. *
  96. * @param bool $enabled True or false
  97. */
  98. public function handleErrors($enabled)
  99. {
  100. if ($enabled) {
  101. set_error_handler(array($this, 'handleError'));
  102. set_exception_handler(array($this, 'handleException'));
  103. }
  104. else {
  105. restore_error_handler();
  106. restore_exception_handler();
  107. }
  108. }
  109. /**
  110. * Custom error handler. Converts errors into exceptions.
  111. *
  112. * @param int $errno Error number
  113. * @param int $errstr Error string
  114. * @param int $errfile Error file name
  115. * @param int $errline Error file line number
  116. * @throws \ErrorException
  117. */
  118. public function handleError($errno, $errstr, $errfile, $errline) {
  119. if ($errno & error_reporting()) {
  120. throw new \ErrorException($errstr, $errno, 0, $errfile, $errline);
  121. }
  122. }
  123. /**
  124. * Custom exception handler. Logs exceptions.
  125. *
  126. * @param \Exception $e Thrown exception
  127. */
  128. public function handleException(\Exception $e) {
  129. if ($this->get('flight.log_errors')) {
  130. error_log($e->getMessage());
  131. }
  132. $this->error($e);
  133. }
  134. /**
  135. * Maps a callback to a framework method.
  136. *
  137. * @param string $name Method name
  138. * @param callback $callback Callback function
  139. * @throws \Exception If trying to map over a framework method
  140. */
  141. public function map($name, $callback) {
  142. if (method_exists($this, $name)) {
  143. throw new \Exception('Cannot override an existing framework method.');
  144. }
  145. $this->dispatcher->set($name, $callback);
  146. }
  147. /**
  148. * Registers a class to a framework method.
  149. *
  150. * @param string $name Method name
  151. * @param string $class Class name
  152. * @param array $params Class initialization parameters
  153. * @param callback $callback Function to call after object instantiation
  154. * @throws \Exception If trying to map over a framework method
  155. */
  156. public function register($name, $class, array $params = array(), $callback = null) {
  157. if (method_exists($this, $name)) {
  158. throw new \Exception('Cannot override an existing framework method.');
  159. }
  160. $this->loader->register($name, $class, $params, $callback);
  161. }
  162. /**
  163. * Adds a pre-filter to a method.
  164. *
  165. * @param string $name Method name
  166. * @param callback $callback Callback function
  167. */
  168. public function before($name, $callback) {
  169. $this->dispatcher->hook($name, 'before', $callback);
  170. }
  171. /**
  172. * Adds a post-filter to a method.
  173. *
  174. * @param string $name Method name
  175. * @param callback $callback Callback function
  176. */
  177. public function after($name, $callback) {
  178. $this->dispatcher->hook($name, 'after', $callback);
  179. }
  180. /**
  181. * Gets a variable.
  182. *
  183. * @param string $key Key
  184. * @return mixed
  185. */
  186. public function get($key) {
  187. return isset($this->vars[$key]) ? $this->vars[$key] : null;
  188. }
  189. /**
  190. * Sets a variable.
  191. *
  192. * @param mixed $key Key
  193. * @param string $value Value
  194. */
  195. public function set($key, $value = null) {
  196. if (is_array($key) || is_object($key)) {
  197. foreach ($key as $k => $v) {
  198. $this->vars[$k] = $v;
  199. }
  200. }
  201. else {
  202. $this->vars[$key] = $value;
  203. }
  204. }
  205. /**
  206. * Checks if a variable has been set.
  207. *
  208. * @param string $key Key
  209. * @return bool Variable status
  210. */
  211. public function has($key) {
  212. return isset($this->vars[$key]);
  213. }
  214. /**
  215. * Unsets a variable. If no key is passed in, clear all variables.
  216. *
  217. * @param string $key Key
  218. */
  219. public function clear($key = null) {
  220. if (is_null($key)) {
  221. $this->vars = array();
  222. }
  223. else {
  224. unset($this->vars[$key]);
  225. }
  226. }
  227. /**
  228. * Adds a path for class autoloading.
  229. *
  230. * @param string $dir Directory path
  231. */
  232. public function path($dir) {
  233. $this->loader->addDirectory($dir);
  234. }
  235. /*** Extensible Methods ***/
  236. /**
  237. * Starts the framework.
  238. */
  239. public function _start() {
  240. $dispatched = false;
  241. $self = $this;
  242. $request = $this->request();
  243. $response = $this->response();
  244. $router = $this->router();
  245. // Flush any existing output
  246. if (ob_get_length() > 0) {
  247. $response->write(ob_get_clean());
  248. }
  249. // Enable output buffering
  250. ob_start();
  251. // Enable error handling
  252. $this->handleErrors($this->get('flight.handle_errors'));
  253. // Disable caching for AJAX requests
  254. if ($request->ajax) {
  255. $response->cache(false);
  256. }
  257. // Allow post-filters to run
  258. $this->after('start', function() use ($self) {
  259. $self->stop();
  260. });
  261. // Route the request
  262. while ($route = $router->route($request)) {
  263. $params = array_values($route->params);
  264. $continue = $this->dispatcher->execute(
  265. $route->callback,
  266. $params
  267. );
  268. $dispatched = true;
  269. if (!$continue) break;
  270. $router->next();
  271. }
  272. if (!$dispatched) {
  273. $this->notFound();
  274. }
  275. }
  276. /**
  277. * Stops the framework and outputs the current response.
  278. *
  279. * @param int $code HTTP status code
  280. */
  281. public function _stop($code = 200) {
  282. $this->response()
  283. ->status($code)
  284. ->write(ob_get_clean())
  285. ->send();
  286. }
  287. /**
  288. * Stops processing and returns a given response.
  289. *
  290. * @param int $code HTTP status code
  291. * @param string $message Response message
  292. */
  293. public function _halt($code = 200, $message = '') {
  294. $this->response(false)
  295. ->status($code)
  296. ->write($message)
  297. ->send();
  298. }
  299. /**
  300. * Sends an HTTP 500 response for any errors.
  301. *
  302. * @param \Exception Thrown exception
  303. */
  304. public function _error(\Exception $e) {
  305. $msg = sprintf('<h1>500 Internal Server Error</h1>'.
  306. '<h3>%s (%s)</h3>'.
  307. '<pre>%s</pre>',
  308. $e->getMessage(),
  309. $e->getCode(),
  310. $e->getTraceAsString()
  311. );
  312. try {
  313. $this->response(false)
  314. ->status(500)
  315. ->write($msg)
  316. ->send();
  317. }
  318. catch (\Exception $ex) {
  319. exit($msg);
  320. }
  321. }
  322. /**
  323. * Sends an HTTP 404 response when a URL is not found.
  324. */
  325. public function _notFound() {
  326. $this->response(false)
  327. ->status(404)
  328. ->write(
  329. '<h1>404 Not Found</h1>'.
  330. '<h3>The page you have requested could not be found.</h3>'.
  331. str_repeat(' ', 512)
  332. )
  333. ->send();
  334. }
  335. /**
  336. * Routes a URL to a callback function.
  337. *
  338. * @param string $pattern URL pattern to match
  339. * @param callback $callback Callback function
  340. * @param boolean $pass_route Pass the matching route object to the callback
  341. */
  342. public function _route($pattern, $callback, $pass_route = false) {
  343. $this->router()->map($pattern, $callback, $pass_route);
  344. }
  345. /**
  346. * Redirects the current request to another URL.
  347. *
  348. * @param string $url URL
  349. * @param int $code HTTP status code
  350. */
  351. public function _redirect($url, $code = 303) {
  352. $base = $this->get('flight.base_url');
  353. if ($base === null) {
  354. $base = $this->request()->base;
  355. }
  356. // Append base url to redirect url
  357. if ($base != '/' && strpos($url, '://') === false) {
  358. $url = preg_replace('#/+#', '/', $base.'/'.$url);
  359. }
  360. $this->response(false)
  361. ->status($code)
  362. ->header('Location', $url)
  363. ->write($url)
  364. ->send();
  365. }
  366. /**
  367. * Renders a template.
  368. *
  369. * @param string $file Template file
  370. * @param array $data Template data
  371. * @param string $key View variable name
  372. */
  373. public function _render($file, $data = null, $key = null) {
  374. if ($key !== null) {
  375. $this->view()->set($key, $this->view()->fetch($file, $data));
  376. }
  377. else {
  378. $this->view()->render($file, $data);
  379. }
  380. }
  381. /**
  382. * Sends a JSON response.
  383. *
  384. * @param mixed $data JSON data
  385. * @param int $code HTTP status code
  386. * @param bool $encode Whether to perform JSON encoding
  387. */
  388. public function _json($data, $code = 200, $encode = true) {
  389. $json = ($encode) ? json_encode($data) : $data;
  390. $this->response(false)
  391. ->status($code)
  392. ->header('Content-Type', 'application/json')
  393. ->write($json)
  394. ->send();
  395. }
  396. /**
  397. * Sends a JSONP response.
  398. *
  399. * @param mixed $data JSON data
  400. * @param string $param Query parameter that specifies the callback name.
  401. * @param int $code HTTP status code
  402. * @param bool $encode Whether to perform JSON encoding
  403. */
  404. public function _jsonp($data, $param = 'jsonp', $code = 200, $encode = true) {
  405. $json = ($encode) ? json_encode($data) : $data;
  406. $callback = $this->request()->query[$param];
  407. $this->response(false)
  408. ->status($code)
  409. ->header('Content-Type', 'application/javascript')
  410. ->write($callback.'('.$json.');')
  411. ->send();
  412. }
  413. /**
  414. * Handles ETag HTTP caching.
  415. *
  416. * @param string $id ETag identifier
  417. * @param string $type ETag type
  418. */
  419. public function _etag($id, $type = 'strong') {
  420. $id = (($type === 'weak') ? 'W/' : '').$id;
  421. $this->response()->header('ETag', $id);
  422. if (isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
  423. $_SERVER['HTTP_IF_NONE_MATCH'] === $id) {
  424. $this->halt(304);
  425. }
  426. }
  427. /**
  428. * Handles last modified HTTP caching.
  429. *
  430. * @param int $time Unix timestamp
  431. */
  432. public function _lastModified($time) {
  433. $this->response()->header('Last-Modified', date(DATE_RFC1123, $time));
  434. if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
  435. strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $time) {
  436. $this->halt(304);
  437. }
  438. }
  439. }