request.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. <?php
  2. /**
  3. * Part of the Fuel framework.
  4. *
  5. * @package Fuel
  6. * @version 1.5
  7. * @author Fuel Development Team
  8. * @license MIT License
  9. * @copyright 2010 - 2013 Fuel Development Team
  10. * @link http://fuelphp.com
  11. */
  12. namespace Fuel\Core;
  13. /**
  14. * The Request class is used to create and manage new and existing requests. There
  15. * is a main request which comes in from the browser or command line, then new
  16. * requests can be created for HMVC requests.
  17. *
  18. * Example Usage:
  19. *
  20. * $request = Request::forge('foo/bar')->execute();
  21. * echo $request->response();
  22. *
  23. * @package Fuel
  24. * @subpackage Core
  25. */
  26. class Request
  27. {
  28. /**
  29. * Holds the main request instance
  30. *
  31. * @var Request
  32. */
  33. protected static $main = false;
  34. /**
  35. * Holds the global active request instance
  36. *
  37. * @var Request
  38. */
  39. protected static $active = false;
  40. /**
  41. * Generates a new request. The request is then set to be the active
  42. * request. If this is the first request, then save that as the main
  43. * request for the app.
  44. *
  45. * Usage:
  46. *
  47. * Request::forge('hello/world');
  48. *
  49. * @param string The URI of the request
  50. * @param mixed Internal: whether to use the routes; external: driver type or array with settings (driver key must be set)
  51. * @param string request method
  52. * @return Request The new request object
  53. */
  54. public static function forge($uri = null, $options = true, $method = null)
  55. {
  56. is_bool($options) and $options = array('route' => $options);
  57. is_string($options) and $options = array('driver' => $options);
  58. if ( ! empty($options['driver']))
  59. {
  60. $class = \Inflector::words_to_upper('Request_'.$options['driver']);
  61. return $class::forge($uri, $options, $method);
  62. }
  63. $request = new static($uri, isset($options['route']) ? $options['route'] : true, $method);
  64. if (static::$active)
  65. {
  66. $request->parent = static::$active;
  67. static::$active->children[] = $request;
  68. }
  69. // fire any request created events
  70. \Event::instance()->has_events('request_created') and \Event::instance()->trigger('request_created', '', 'none');
  71. return $request;
  72. }
  73. /**
  74. * Returns the main request instance (the one from the browser or CLI).
  75. * This is the first executed Request, not necessarily the root parent of the current request.
  76. *
  77. * Usage:
  78. *
  79. * Request::main();
  80. *
  81. * @return Request
  82. */
  83. public static function main()
  84. {
  85. return static::$main;
  86. }
  87. /**
  88. * Returns the active request currently being used.
  89. *
  90. * Usage:
  91. *
  92. * Request::active();
  93. *
  94. * @param Request|null|false overwrite current request before returning, false prevents overwrite
  95. * @return Request
  96. */
  97. public static function active($request = false)
  98. {
  99. if ($request !== false)
  100. {
  101. static::$active = $request;
  102. }
  103. return static::$active;
  104. }
  105. /**
  106. * Returns the current request is an HMVC request
  107. *
  108. * Usage:
  109. *
  110. * if (Request::is_hmvc())
  111. * {
  112. * // Do something special...
  113. * return;
  114. * }
  115. *
  116. * @return bool
  117. */
  118. public static function is_hmvc()
  119. {
  120. return ((\Fuel::$is_cli and static::main()) or static::active() !== static::main());
  121. }
  122. /**
  123. * Reset's the active request with the previous one. This is needed after
  124. * the active request is finished.
  125. *
  126. * Usage:
  127. *
  128. * Request::reset_request();
  129. *
  130. * @return void
  131. */
  132. public static function reset_request()
  133. {
  134. // Let's make the previous Request active since we are done executing this one.
  135. static::$active = static::$active->parent();
  136. }
  137. /**
  138. * Holds the response object of the request.
  139. *
  140. * @var Response
  141. */
  142. public $response = null;
  143. /**
  144. * The Request's URI object.
  145. *
  146. * @var Uri
  147. */
  148. public $uri = null;
  149. /**
  150. * The request's route object
  151. *
  152. * @var Route
  153. */
  154. public $route = null;
  155. /**
  156. * @var string $method request method
  157. */
  158. protected $method = null;
  159. /**
  160. * The current module
  161. *
  162. * @var string
  163. */
  164. public $module = '';
  165. /**
  166. * The current controller directory
  167. *
  168. * @var string
  169. */
  170. public $directory = '';
  171. /**
  172. * The request's controller
  173. *
  174. * @var string
  175. */
  176. public $controller = '';
  177. /**
  178. * The request's controller action
  179. *
  180. * @var string
  181. */
  182. public $action = '';
  183. /**
  184. * The request's method params
  185. *
  186. * @var array
  187. */
  188. public $method_params = array();
  189. /**
  190. * The request's named params
  191. *
  192. * @var array
  193. */
  194. public $named_params = array();
  195. /**
  196. * Controller instance once instantiated
  197. *
  198. * @var Controller
  199. */
  200. public $controller_instance;
  201. /**
  202. * Search paths for the current active request
  203. *
  204. * @var array
  205. */
  206. public $paths = array();
  207. /**
  208. * Request that created this one
  209. *
  210. * @var Request
  211. */
  212. protected $parent = null;
  213. /**
  214. * Requests created by this request
  215. *
  216. * @var array
  217. */
  218. protected $children = array();
  219. /**
  220. * Creates the new Request object by getting a new URI object, then parsing
  221. * the uri with the Route class.
  222. *
  223. * Usage:
  224. *
  225. * $request = new Request('foo/bar');
  226. *
  227. * @param string the uri string
  228. * @param bool whether or not to route the URI
  229. * @param string request method
  230. * @return void
  231. */
  232. public function __construct($uri, $route = true, $method = null)
  233. {
  234. $this->uri = new \Uri($uri);
  235. $this->method = $method ?: \Input::method();
  236. logger(\Fuel::L_INFO, 'Creating a new Request with URI = "'.$this->uri->get().'"', __METHOD__);
  237. // check if a module was requested
  238. if (count($this->uri->get_segments()) and $module_path = \Module::exists($this->uri->get_segment(1)))
  239. {
  240. // check if the module has routes
  241. if (is_file($module_path .= 'config/routes.php'))
  242. {
  243. $module = $this->uri->get_segment(1);
  244. // load and add the module routes
  245. $module_routes = \Fuel::load($module_path);
  246. $prepped_routes = array();
  247. foreach($module_routes as $name => $_route)
  248. {
  249. if ($name === '_root_')
  250. {
  251. $name = $module;
  252. }
  253. elseif (strpos($name, $module.'/') !== 0 and $name != $module and $name !== '_404_')
  254. {
  255. $name = $module.'/'.$name;
  256. }
  257. $prepped_routes[$name] = $_route;
  258. };
  259. // update the loaded list of routes
  260. \Router::add($prepped_routes, null, true);
  261. }
  262. }
  263. $this->route = \Router::process($this, $route);
  264. if ( ! $this->route)
  265. {
  266. return;
  267. }
  268. $this->module = $this->route->module;
  269. $this->controller = $this->route->controller;
  270. $this->action = $this->route->action;
  271. $this->method_params = $this->route->method_params;
  272. $this->named_params = $this->route->named_params;
  273. if ($this->route->module !== null)
  274. {
  275. $this->add_path(\Module::exists($this->module));
  276. }
  277. }
  278. /**
  279. * This executes the request and sets the output to be used later.
  280. *
  281. * Usage:
  282. *
  283. * $request = Request::forge('hello/world')->execute();
  284. *
  285. * @param array|null $method_params An array of parameters to pass to the method being executed
  286. * @return Request This request object
  287. */
  288. public function execute($method_params = null)
  289. {
  290. // fire any request started events
  291. \Event::instance()->has_events('request_started') and \Event::instance()->trigger('request_started', '', 'none');
  292. if (\Fuel::$profiling)
  293. {
  294. \Profiler::mark(__METHOD__.' Start');
  295. }
  296. logger(\Fuel::L_INFO, 'Called', __METHOD__);
  297. // Make the current request active
  298. static::$active = $this;
  299. // First request called is also the main request
  300. if ( ! static::$main)
  301. {
  302. logger(\Fuel::L_INFO, 'Setting main Request', __METHOD__);
  303. static::$main = $this;
  304. }
  305. if ( ! $this->route)
  306. {
  307. static::reset_request();
  308. throw new \HttpNotFoundException();
  309. }
  310. // save the current language so we can restore it after the call
  311. $current_language = \Config::get('language', 'en');
  312. try
  313. {
  314. if ($this->route->callable !== null)
  315. {
  316. $response = call_user_func_array($this->route->callable, array($this));
  317. if ( ! $response instanceof Response)
  318. {
  319. $response = new \Response($response);
  320. }
  321. }
  322. else
  323. {
  324. $method_prefix = $this->method.'_';
  325. $class = $this->controller;
  326. // Allow override of method params from execute
  327. if (is_array($method_params))
  328. {
  329. $this->method_params = array_merge($this->method_params, $method_params);
  330. }
  331. // If the class doesn't exist then 404
  332. if ( ! class_exists($class))
  333. {
  334. throw new \HttpNotFoundException();
  335. }
  336. // Load the controller using reflection
  337. $class = new \ReflectionClass($class);
  338. if ($class->isAbstract())
  339. {
  340. throw new \HttpNotFoundException();
  341. }
  342. // Create a new instance of the controller
  343. $this->controller_instance = $class->newInstance($this);
  344. $this->action = $this->action ?: ($class->hasProperty('default_action') ? $class->getProperty('default_action')->getValue($this->controller_instance) : 'index');
  345. $method = $method_prefix.$this->action;
  346. // Allow to do in controller routing if method router(action, params) exists
  347. if ($class->hasMethod('router'))
  348. {
  349. $method = 'router';
  350. $this->method_params = array($this->action, $this->method_params);
  351. }
  352. if ( ! $class->hasMethod($method))
  353. {
  354. $method = 'action_'.$this->action;
  355. }
  356. if ($class->hasMethod($method))
  357. {
  358. $action = $class->getMethod($method);
  359. if ( ! $action->isPublic())
  360. {
  361. throw new \HttpNotFoundException();
  362. }
  363. // fire any controller started events
  364. \Event::instance()->has_events('controller_started') and \Event::instance()->trigger('controller_started', '', 'none');
  365. $class->hasMethod('before') and $class->getMethod('before')->invoke($this->controller_instance);
  366. $response = $action->invokeArgs($this->controller_instance, $this->method_params);
  367. $class->hasMethod('after') and $response = $class->getMethod('after')->invoke($this->controller_instance, $response);
  368. // fire any controller finished events
  369. \Event::instance()->has_events('controller_finished') and \Event::instance()->trigger('controller_finished', '', 'none');
  370. }
  371. else
  372. {
  373. throw new \HttpNotFoundException();
  374. }
  375. }
  376. // restore the language setting
  377. \Config::set('language', $current_language);
  378. }
  379. catch (\Exception $e)
  380. {
  381. static::reset_request();
  382. // restore the language setting
  383. \Config::set('language', $current_language);
  384. throw $e;
  385. }
  386. // Get the controller's output
  387. if ($response instanceof Response)
  388. {
  389. $this->response = $response;
  390. }
  391. else
  392. {
  393. throw new \FuelException(get_class($this->controller_instance).'::'.$method.'() or the controller after() method must return a Response object.');
  394. }
  395. // fire any request finished events
  396. \Event::instance()->has_events('request_finished') and \Event::instance()->trigger('request_finished', '', 'none');
  397. if (\Fuel::$profiling)
  398. {
  399. \Profiler::mark(__METHOD__.' End');
  400. }
  401. static::reset_request();
  402. return $this;
  403. }
  404. /**
  405. * Sets the request method.
  406. *
  407. * @param string $method request method
  408. * @return object current instance
  409. */
  410. public function set_method($method)
  411. {
  412. $this->method = strtoupper($method);
  413. return $this;
  414. }
  415. /**
  416. * Returns the request method.
  417. *
  418. * @return string request method
  419. */
  420. public function get_method()
  421. {
  422. return $this->method;
  423. }
  424. /**
  425. * Gets this Request's Response object;
  426. *
  427. * Usage:
  428. *
  429. * $response = Request::forge('foo/bar')->execute()->response();
  430. *
  431. * @return Response This Request's Response object
  432. */
  433. public function response()
  434. {
  435. return $this->response;
  436. }
  437. /**
  438. * Returns the Request that created this one
  439. *
  440. * @return Request|null
  441. */
  442. public function parent()
  443. {
  444. return $this->parent;
  445. }
  446. /**
  447. * Returns an array of Requests created by this one
  448. *
  449. * @return array
  450. */
  451. public function children()
  452. {
  453. return $this->children;
  454. }
  455. /**
  456. * Add to paths which are used by Finder::search()
  457. *
  458. * @param string the new path
  459. * @param bool whether to add to the front or the back of the array
  460. * @return void
  461. */
  462. public function add_path($path, $prefix = false)
  463. {
  464. if ($prefix)
  465. {
  466. // prefix the path to the paths array
  467. array_unshift($this->paths, $path);
  468. }
  469. else
  470. {
  471. // add the new path
  472. $this->paths[] = $path;
  473. }
  474. }
  475. /**
  476. * Returns the array of currently loaded search paths.
  477. *
  478. * @return array the array of paths
  479. */
  480. public function get_paths()
  481. {
  482. return $this->paths;
  483. }
  484. /**
  485. * Gets a specific named parameter
  486. *
  487. * @param string $param Name of the parameter
  488. * @param mixed $default Default value
  489. * @return mixed
  490. */
  491. public function param($param, $default = null)
  492. {
  493. if ( ! isset($this->named_params[$param]))
  494. {
  495. return \Fuel::value($default);
  496. }
  497. return $this->named_params[$param];
  498. }
  499. /**
  500. * Gets all of the named parameters
  501. *
  502. * @return array
  503. */
  504. public function params()
  505. {
  506. return $this->named_params;
  507. }
  508. /**
  509. * PHP magic function returns the Output of the request.
  510. *
  511. * Usage:
  512. *
  513. * $request = Request::forge('hello/world')->execute();
  514. * echo $request;
  515. *
  516. * @return string the response
  517. */
  518. public function __toString()
  519. {
  520. return (string) $this->response;
  521. }
  522. }