Dispatcher.php 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2013, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\console;
  9. use lithium\core\Libraries;
  10. use lithium\core\Environment;
  11. use UnexpectedValueException;
  12. /**
  13. * The `Dispatcher` is the outermost layer of the framework, responsible for both receiving the
  14. * initial console request and returning back a response at the end of the request's life cycle.
  15. *
  16. * The console dispatcher is responsible for accepting requests from scripts called from the command
  17. * line, and executing the appropriate `Command` class(es). The `run()` method accepts an instance
  18. * of `lithium\console\Request`, which encapsulates the console environment and any command-line
  19. * parameters passed to the script. `Dispatcher` then invokes `lithium\console\Router` to determine
  20. * the correct `Command` class to invoke, and which method should be called.
  21. */
  22. class Dispatcher extends \lithium\core\StaticObject {
  23. /**
  24. * Fully-namespaced router class reference.
  25. *
  26. * Class must implement a `parse()` method, which must return an array with (at a minimum)
  27. * 'command' and 'action' keys.
  28. *
  29. * @see lithium\console\Router::parse()
  30. * @var array
  31. */
  32. protected static $_classes = array(
  33. 'request' => 'lithium\console\Request',
  34. 'router' => 'lithium\console\Router'
  35. );
  36. /**
  37. * Contains pre-process format strings for changing Dispatcher's behavior based on 'rules'.
  38. *
  39. * Each key in the array represents a 'rule'; if a key that matches the rule is present (and
  40. * not empty) in a route, (i.e. the result of `lithium\console\Router::parse()`) then the rule's
  41. * value will be applied to the route before it is dispatched. When applying a rule, any array
  42. * elements array elements of the flag which are present in the route will be modified using a
  43. * `lithium\util\String::insert()`-formatted string.
  44. *
  45. * @see lithium\console\Dispatcher::config()
  46. * @see lithium\util\String::insert()
  47. * @var array
  48. */
  49. protected static $_rules = array(
  50. 'command' => array(array('lithium\util\Inflector', 'camelize')),
  51. 'action' => array(array('lithium\util\Inflector', 'camelize', array(false)))
  52. );
  53. /**
  54. * Used to set configuration parameters for the Dispatcher.
  55. *
  56. * @param array $config Optional configuration params.
  57. * @return array If no parameters are passed, returns an associative array with the
  58. * current configuration, otherwise returns null.
  59. */
  60. public static function config($config = array()) {
  61. if (!$config) {
  62. return array('rules' => static::$_rules);
  63. }
  64. foreach ($config as $key => $val) {
  65. if (isset(static::${'_' . $key})) {
  66. static::${'_' . $key} = $val + static::${'_' . $key};
  67. }
  68. }
  69. }
  70. /**
  71. * Dispatches a request based on a request object (an instance of `lithium\console\Request`).
  72. *
  73. * If `$request` is `null`, a new request object is instantiated based on the value of the
  74. * `'request'` key in the `$_classes` array.
  75. *
  76. * @param object $request An instance of a request object with console request information. If
  77. * `null`, an instance will be created.
  78. * @param array $options
  79. * @return object The command action result which is an instance of `lithium\console\Response`.
  80. */
  81. public static function run($request = null, $options = array()) {
  82. $defaults = array('request' => array());
  83. $options += $defaults;
  84. $classes = static::$_classes;
  85. $params = compact('request', 'options');
  86. return static::_filter(__FUNCTION__, $params, function($self, $params) use ($classes) {
  87. $request = $params['request'];
  88. $options = $params['options'];
  89. $router = $classes['router'];
  90. $request = $request ?: new $classes['request']($options['request']);
  91. $request->params = $router::parse($request);
  92. $params = $self::applyRules($request->params);
  93. Environment::set($request);
  94. try {
  95. $callable = $self::invokeMethod('_callable', array($request, $params, $options));
  96. return $self::invokeMethod('_call', array($callable, $request, $params));
  97. } catch (UnexpectedValueException $e) {
  98. return (object) array('status' => $e->getMessage() . "\n");
  99. }
  100. });
  101. }
  102. /**
  103. * Determines which command to use for current request.
  104. *
  105. * @param object $request An instance of a `Request` object.
  106. * @param array $params Request params that can be accessed inside the filter.
  107. * @param array $options
  108. * @return class lithium\console\Command Returns the instantiated command object.
  109. */
  110. protected static function _callable($request, $params, $options) {
  111. $params = compact('request', 'params', 'options');
  112. return static::_filter(__FUNCTION__, $params, function($self, $params) {
  113. $request = $params['request'];
  114. $params = $params['params'];
  115. $name = $params['command'];
  116. if (!$name) {
  117. $request->params['args'][0] = $name;
  118. $name = 'lithium\console\command\Help';
  119. }
  120. if (class_exists($class = Libraries::locate('command', $name))) {
  121. return new $class(compact('request'));
  122. }
  123. throw new UnexpectedValueException("Command `{$name}` not found.");
  124. });
  125. }
  126. /**
  127. * Attempts to apply a set of formatting rules from `$_rules` to a `$params` array.
  128. *
  129. * Each formatting rule is applied if the key of the rule in `$_rules` is present and not empty
  130. * in `$params`. Also performs sanity checking against `$params` to ensure that no value
  131. * matching a rule is present unless the rule check passes.
  132. *
  133. * @param array $params An array of route parameters to which rules will be applied.
  134. * @return array Returns the `$params` array with formatting rules applied to array values.
  135. */
  136. public static function applyRules($params) {
  137. $result = array();
  138. if (!$params) {
  139. return false;
  140. }
  141. foreach (static::$_rules as $name => $rules) {
  142. foreach ($rules as $rule) {
  143. if (!empty($params[$name]) && isset($rule[0])) {
  144. $options = array_merge(
  145. array($params[$name]), isset($rule[2]) ? (array) $rule[2] : array()
  146. );
  147. $result[$name] = call_user_func_array(array($rule[0], $rule[1]), $options);
  148. }
  149. }
  150. }
  151. return $result + array_diff_key($params, $result);
  152. }
  153. /**
  154. * Calls a given command with the appropriate action.
  155. *
  156. * This method is responsible for calling a `$callable` command and returning its result.
  157. *
  158. * @param string $callable The callable command.
  159. * @param string $request The associated `Request` object.
  160. * @param string $params Additional params that should be passed along.
  161. * @return mixed Returns the result of the called action, typically `true` or `false`.
  162. */
  163. protected static function _call($callable, $request, $params) {
  164. $params = compact('callable', 'request', 'params');
  165. return static::_filter(__FUNCTION__, $params, function($self, $params) {
  166. if (is_callable($callable = $params['callable'])) {
  167. $request = $params['request'];
  168. $params = $params['params'];
  169. if (!method_exists($callable, $params['action'])) {
  170. array_unshift($params['args'], $request->params['action']);
  171. $params['action'] = 'run';
  172. }
  173. $isHelp = (
  174. !empty($params['help']) || !empty($params['h'])
  175. || !method_exists($callable, $params['action'])
  176. );
  177. if ($isHelp) {
  178. $params['action'] = '_help';
  179. }
  180. return $callable($params['action'], $params['args']);
  181. }
  182. throw new UnexpectedValueException("Callable `{$callable}` is actually not callable.");
  183. });
  184. }
  185. }
  186. ?>