Commit be8dd940 by Qiang Xue

...

parent 5a72523a
......@@ -9,6 +9,8 @@
namespace yii\base;
use yii\util\ReflectionHelper;
/**
* Action is the base class for all controller action classes.
*
......@@ -52,13 +54,12 @@ class Action extends Component
*/
public function runWithParams($params)
{
$method = new \ReflectionMethod($this, 'run');
$params = \yii\util\ReflectionHelper::bindParams($method, $params);
if ($params === false) {
$this->controller->invalidActionParams($this);
try {
$params = ReflectionHelper::extractMethodParams($this, 'run', $params);
return (int)call_user_func_array(array($this, 'run'), $params);
} catch (Exception $e) {
$this->controller->invalidActionParams($this, $e);
return 1;
} else {
return (int)$method->invokeArgs($this, $params);
}
}
}
......@@ -73,7 +73,7 @@ use yii\base\Exception;
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
abstract class Application extends Module
class Application extends Module
{
/**
* @var string the application name. Defaults to 'My Application'.
......@@ -132,13 +132,6 @@ abstract class Application extends Module
}
/**
* Runs the application.
* This is the main entrance of an application. Derived classes must implement this method.
* @return integer the exit status (0 means normal, non-zero values mean abnormal)
*/
abstract public function run();
/**
* Terminates the application.
* This method replaces PHP's exit() function by calling [[afterRequest()]] before exiting.
* @param integer $status exit status (value 0 means normal exit while other values mean abnormal exit).
......@@ -157,6 +150,19 @@ abstract class Application extends Module
}
/**
* Runs the application.
* This is the main entrance of an application.
* @return integer the exit status (0 means normal, non-zero values mean abnormal)
*/
public function run()
{
$this->beforeRequest();
$status = $this->processRequest();
$this->afterRequest();
return $status;
}
/**
* Raises the [[beforeRequest]] event right BEFORE the application processes the request.
*/
public function beforeRequest()
......@@ -174,16 +180,25 @@ abstract class Application extends Module
/**
* Processes the request.
* The request is represented in terms of a controller route and action parameters.
* @param string $route the route of the request. It may contain module ID, controller ID, and/or action ID.
* @param array $params parameters to be bound to the controller action.
* Child classes should override this method with actual request processing logic.
* @return integer the exit status of the controller action (0 means normal, non-zero values mean abnormal)
*/
public function processRequest()
{
return 0;
}
/**
* Runs a controller with the given route and parameters.
* @param string $route the route (e.g. `post/create`)
* @param array $params the parameters to be passed to the controller action
* @return integer the exit status (0 means normal, non-zero values mean abnormal)
* @throws Exception if the route cannot be resolved into a controller
*/
public function processRequest($route, $params = array())
public function runController($route, $params = array())
{
$result = $this->parseRoute($route);
if ($result === null) {
$result = $this->createController($route);
if ($result === false) {
throw new Exception(\Yii::t('yii', 'Unable to resolve the request.'));
}
list($controller, $action) = $result;
......
......@@ -114,7 +114,10 @@ class Controller extends Component implements Initable
$this->action = $action;
if ($this->authorize($action) && $this->beforeAction($action)) {
$status = $action->runWithParams($params !== null ?: $this->getActionParams());
if ($params === null) {
$params = $this->getActionParams();
}
$status = $action->runWithParams($params);
$this->afterAction($action);
} else {
$status = 1;
......@@ -163,11 +166,12 @@ class Controller extends Component implements Initable
* This method is invoked when the request parameters do not satisfy the requirement of the specified action.
* The default implementation will throw an exception.
* @param Action $action the action being executed
* @param Exception $exception the exception about the invalid parameters
* @throws Exception whenever this method is invoked
*/
public function invalidActionParams($action)
public function invalidActionParams($action, $exception)
{
throw new Exception(\Yii::t('yii', 'Your request is invalid.'));
throw $exception;
}
/**
......@@ -219,7 +223,7 @@ class Controller extends Component implements Initable
if ($route[0] !== '/' && !$this->module instanceof Application) {
$route = '/' . $this->module->getUniqueId() . '/' . $route;
}
$status = \Yii::$application->processRequest($route, $params);
$status = \Yii::$application->runController($route, $params);
}
if ($exit) {
\Yii::$application->end($status);
......
......@@ -9,6 +9,8 @@
namespace yii\base;
use yii\util\ReflectionHelper;
/**
* InlineAction represents an action that is defined as a controller method.
*
......@@ -28,13 +30,13 @@ class InlineAction extends Action
*/
public function runWithParams($params)
{
$method = new \ReflectionMethod($this->controller, 'action' . $this->id);
$params = \yii\util\ReflectionHelper::bindParams($method, $params);
if ($params === false) {
$this->controller->invalidActionParams($this);
try {
$method = 'action' . $this->id;
$params = ReflectionHelper::extractMethodParams($this->controller, $method, $params);
return (int)call_user_func_array(array($this->controller, $method), $params);
} catch (Exception $e) {
$this->controller->invalidActionParams($this, $e);
return 1;
} else {
return (int)$method->invokeArgs($this, $params);
}
}
}
......@@ -481,8 +481,8 @@ abstract class Module extends Component implements Initable
}
/**
* Parses a given route into controller and action ID.
* The parsing process follows the following algorithm:
* Creates a controller instance based on the given route.
* This method tries to parse the given route (e.g. `post/create`) using the following algorithm:
*
* 1. Get the first segment in route
* 2. If the segment matches
......@@ -490,12 +490,12 @@ abstract class Module extends Component implements Initable
* and return the controller with the rest part of the route;
* - a controller class under [[controllerPath]], create the controller instance, and return it
* with the rest part of the route;
* - an ID in [[modules]], let the corresponding module to parse the rest part of the route.
* - an ID in [[modules]], call the [[createController()]] method of the corresponding module.
*
* @param string $route the route which may consist module ID, controller ID and/or action ID (e.g. `post/create`)
* @return array|null the controller instance and action ID. Null if the route cannot be resolved.
* @return array|boolean the array of controller instance and action ID. False if the route cannot be resolved.
*/
public function parseRoute($route)
public function createController($route)
{
if (($route = trim($route, '/')) === '') {
$route = $this->defaultRoute;
......@@ -509,40 +509,16 @@ abstract class Module extends Component implements Initable
$route = '';
}
$controller = $this->createController($id);
if ($controller !== null) {
return array($controller, $route);
}
if (($module = $this->getModule($id)) !== null) {
return $module->parseRoute($route);
}
return null;
}
/**
* Creates a controller instance according to the specified controller ID.
* For security reasons, the controller ID must start with a lower-case letter
* and consist of word characters only.
*
* This method will first match the controller ID in [[controllers]]. If found,
* it will create an instance of controller using the corresponding configuration.
* If not found, it will look for a controller class named `XyzController` under
* the [[controllerPath]] directory, where `xyz` is the controller ID with the first
* letter in upper-case.
* @param string $id the controller ID
* @return Controller|null the created controller instance. Null if the controller ID is invalid.
*/
public function createController($id)
{
// Controller IDs must start with a lower-case letter and consist of word characters only
if (!preg_match('/^[a-z][a-zA-Z0-9_]*$/', $id)) {
return null;
return false;
}
if (isset($this->controllers[$id])) {
return \Yii::createObject($this->controllers[$id], $id, $this);
return array(
\Yii::createObject($this->controllers[$id], $id, $this),
$route,
);
}
$className = ucfirst($id) . 'Controller';
......@@ -552,10 +528,17 @@ abstract class Module extends Component implements Initable
require($classFile);
}
if (class_exists($className, false) && is_subclass_of($className, '\yii\base\Controller')) {
return $className::newInstance(array(), $id, $this);
return array(
$className::newInstance(array(), $id, $this),
$route,
);
}
}
return null;
if (($module = $this->getModule($id)) !== null) {
return $module->createController($route);
}
return false;
}
}
......@@ -10,6 +10,7 @@
namespace yii\console;
use yii\base\Exception;
use yii\util\ReflectionHelper;
/**
* Application represents a console application.
......@@ -71,19 +72,39 @@ class Application extends \yii\base\Application
}
/**
* Runs the application.
* This is the main entrance of an application.
* @return integer the exit status (0 means normal, non-zero values mean abnormal)
* Processes the request.
* The request is represented in terms of a controller route and action parameters.
* @return integer the exit status of the controller action (0 means normal, non-zero values mean abnormal)
* @throws Exception if the route cannot be resolved into a controller
*/
public function run()
public function processRequest()
{
if (!isset($_SERVER['argv'])) {
die('This script must be run from the command line.');
}
list($route, $params) = $this->resolveRequest($_SERVER['argv']);
$this->beforeRequest();
$status = $this->processRequest($route, $params);
$this->afterRequest();
return $this->runController($route, $params);
}
/**
* Runs a controller with the given route and parameters.
* @param string $route the route (e.g. `post/create`)
* @param array $params the parameters to be passed to the controller action
* @return integer the exit status (0 means normal, non-zero values mean abnormal)
* @throws Exception if the route cannot be resolved into a controller
*/
public function runController($route, $params = array())
{
$result = $this->createController($route);
if ($result === false) {
throw new Exception(\Yii::t('yii', 'Unable to resolve the request.'));
}
list($controller, $action) = $result;
$priorController = $this->controller;
$this->controller = $controller;
$params = ReflectionHelper::initObjectWithParams($controller, $params);
$status = $controller->run($action, $params);
$this->controller = $priorController;
return $status;
}
......
<?php
/**
* Command class file.
* Controller class file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2012 Yii Software LLC
......@@ -9,6 +9,10 @@
namespace yii\console;
use yii\base\InlineAction;
use yii\base\Exception;
use yii\util\ReflectionHelper;
/**
* Command represents an executable console command.
*
......@@ -47,105 +51,6 @@ namespace yii\console;
class Controller extends \yii\base\Controller
{
/**
* Executes the command.
* The default implementation will parse the input parameters and
* dispatch the command request to an appropriate action with the corresponding
* option values
* @param array $args command line parameters for this command.
*/
public function run($args)
{
list($action, $options, $args) = $this->resolveRequest($args);
$methodName = 'action' . $action;
if (!preg_match('/^\w+$/', $action) || !method_exists($this, $methodName))
{
$this->usageError("Unknown action: " . $action);
}
$method = new \ReflectionMethod($this, $methodName);
$params = array();
// named and unnamed options
foreach ($method->getParameters() as $param) {
$name = $param->getName();
if (isset($options[$name])) {
if ($param->isArray())
$params[] = is_array($options[$name]) ? $options[$name] : array($options[$name]);
else if (!is_array($options[$name]))
$params[] = $options[$name];
else
$this->usageError("Option --$name requires a scalar. Array is given.");
} else if ($name === 'args')
$params[] = $args;
else if ($param->isDefaultValueAvailable())
$params[] = $param->getDefaultValue();
else
$this->usageError("Missing required option --$name.");
unset($options[$name]);
}
// try global options
if (!empty($options)) {
$class = new \ReflectionClass(get_class($this));
foreach ($options as $name => $value) {
if ($class->hasProperty($name)) {
$property = $class->getProperty($name);
if ($property->isPublic() && !$property->isStatic()) {
$this->$name = $value;
unset($options[$name]);
}
}
}
}
if (!empty($options))
$this->usageError("Unknown options: " . implode(', ', array_keys($options)));
if ($this->beforeAction($action, $params)) {
$method->invokeArgs($this, $params);
$this->afterAction($action, $params);
}
}
/**
* Parses the command line arguments and determines which action to perform.
* @param array $args command line arguments
* @return array the action name, named options (name=>value), and unnamed options
*/
protected function resolveRequest($args)
{
$options = array(); // named parameters
$params = array(); // unnamed parameters
foreach ($args as $arg) {
if (preg_match('/^--(\w+)(=(.*))?$/', $arg, $matches)) // an option
{
$name = $matches[1];
$value = isset($matches[3]) ? $matches[3] : true;
if (isset($options[$name])) {
if (!is_array($options[$name]))
$options[$name] = array($options[$name]);
$options[$name][] = $value;
} else
$options[$name] = $value;
} else if (isset($action))
$params[] = $arg;
else
$action = $arg;
}
if (!isset($action))
$action = $this->defaultAction;
return array($action, $options, $params);
}
/**
* @return string the command name.
*/
public function getName()
{
return $this->_name;
}
/**
* Provides the command description.
* This method may be overridden to return the actual command description.
* @return string the command description. Defaults to 'Usage: php entry-script.php command-name'.
......@@ -209,28 +114,6 @@ class Controller extends \yii\base\Controller
}
/**
* Renders a view file.
* @param string $_viewFile_ view file path
* @param array $_data_ optional data to be extracted as local view variables
* @param boolean $_return_ whether to return the rendering result instead of displaying it
* @return mixed the rendering result if required. Null otherwise.
*/
public function renderFile($_viewFile_, $_data_ = null, $_return_ = false)
{
if (is_array($_data_))
extract($_data_, EXTR_PREFIX_SAME, 'data');
else
$data = $_data_;
if ($_return_) {
ob_start();
ob_implicit_flush(false);
require($_viewFile_);
return ob_get_clean();
} else
require($_viewFile_);
}
/**
* Reads input via the readline PHP extension if that's available, or fgets() if readline is not installed.
*
* @param string $message to echo out before waiting for user input
......
......@@ -9,6 +9,8 @@
namespace yii\util;
use yii\base\Exception;
/**
* ReflectionHelper
*
......@@ -19,46 +21,83 @@ class ReflectionHelper
{
/**
* Prepares parameters so that they can be bound to the specified method.
* This method mainly helps method parameter binding. It converts `$params`
* into an array which can be passed to `call_user_func_array()` when calling
* the specified method. The conversion is based on the matching of method parameter names
* This method converts the input parameters into an array that can later be
* passed to `call_user_func_array()` when calling the specified method.
* The conversion is based on the matching of method parameter names
* and the input array keys. For example,
*
* ~~~
* class Foo {
* function bar($a, $b) { ... }
* }
*
* $method = new \ReflectionMethod('Foo', 'bar');
* $object = new Foo;
* $params = array('b' => 2, 'c' => 3, 'a' => 1);
* var_export(ReflectionHelper::bindMethodParams($method, $params));
* // would output: array('a' => 1, 'b' => 2)
* var_export(ReflectionHelper::extractMethodParams($object, 'bar', $params));
* // output: array('a' => 1, 'b' => 2);
* ~~~
*
* @param \ReflectionMethod $method the method reflection
* @param object|string $object the object or class name that owns the specified method
* @param string $method the method name
* @param array $params the parameters in terms of name-value pairs
* @return array|boolean the parameters that can be passed to the method via `call_user_func_array()`.
* False is returned if the input parameters do not follow the method declaration.
* @return array parameters that are needed by the method only and
* can be passed to the method via `call_user_func_array()`.
* @throws Exception if any required method parameter is not found in the given parameters
*/
public static function bindParams($method, $params)
public static function extractMethodParams($object, $method, $params)
{
$m = new \ReflectionMethod($object, $method);
$ps = array();
foreach ($method->getParameters() as $param) {
foreach ($m->getParameters() as $param) {
$name = $param->getName();
if (array_key_exists($name, $params)) {
if ($param->isArray()) {
$ps[$name] = is_array($params[$name]) ? $params[$name] : array($params[$name]);
} elseif (!is_array($params[$name])) {
$ps[$name] = $params[$name];
} else {
return false;
}
$ps[$name] = $params[$name];
} elseif ($param->isDefaultValueAvailable()) {
$ps[$name] = $param->getDefaultValue();
} else {
return false;
throw new Exception(\Yii::t('yii', 'Missing required parameter "{name}".', array('{name' => $name)));
}
}
return $ps;
}
/**
* Initializes an object with the given parameters.
* Only the public non-static properties of the object will be initialized, and their names must
* match the given parameter names. For example,
*
* ~~~
* class Foo {
* public $a;
* protected $b;
* }
* $object = new Foo;
* $params = array('b' => 2, 'c' => 3, 'a' => 1);
* $remaining = ReflectionHelper::bindObjectParams($object, $params);
* var_export($object); // output: $object->a = 1; $object->b = null;
* var_export($remaining); // output: array('b' => 2, 'c' => 3);
* ~~~
*
* @param object $object the object whose properties are to be initialized
* @param array $params the input parameters to be used to initialize the object
* @return array the remaining unused input parameters
*/
public static function initObjectWithParams($object, $params)
{
if (empty($params)) {
return array();
}
$class = new \ReflectionClass(get_class($object));
foreach ($params as $name => $value) {
if ($class->hasProperty($name)) {
$property = $class->getProperty($name);
if ($property->isPublic() && !$property->isStatic()) {
$object->$name = $value;
unset($params[$name]);
}
}
}
return $params;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment