Browse Source

...

tags/2.0.0-beta
Qiang Xue 13 years ago
parent
commit
be8dd940db
  1. 13
      framework/base/Action.php
  2. 43
      framework/base/Application.php
  3. 12
      framework/base/Controller.php
  4. 14
      framework/base/InlineAction.php
  5. 55
      framework/base/Module.php
  6. 35
      framework/console/Application.php
  7. 127
      framework/console/Controller.php
  8. 77
      framework/util/ReflectionHelper.php

13
framework/base/Action.php

@ -9,6 +9,8 @@
namespace yii\base; namespace yii\base;
use yii\util\ReflectionHelper;
/** /**
* Action is the base class for all controller action classes. * Action is the base class for all controller action classes.
* *
@ -52,13 +54,12 @@ class Action extends Component
*/ */
public function runWithParams($params) public function runWithParams($params)
{ {
$method = new \ReflectionMethod($this, 'run'); try {
$params = \yii\util\ReflectionHelper::bindParams($method, $params); $params = ReflectionHelper::extractMethodParams($this, 'run', $params);
if ($params === false) { return (int)call_user_func_array(array($this, 'run'), $params);
$this->controller->invalidActionParams($this); } catch (Exception $e) {
$this->controller->invalidActionParams($this, $e);
return 1; return 1;
} else {
return (int)$method->invokeArgs($this, $params);
} }
} }
} }

43
framework/base/Application.php

@ -73,7 +73,7 @@ use yii\base\Exception;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
abstract class Application extends Module class Application extends Module
{ {
/** /**
* @var string the application name. Defaults to 'My Application'. * @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. * Terminates the application.
* This method replaces PHP's exit() function by calling [[afterRequest()]] before exiting. * 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). * @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. * Raises the [[beforeRequest]] event right BEFORE the application processes the request.
*/ */
public function beforeRequest() public function beforeRequest()
@ -174,16 +180,25 @@ abstract class Application extends Module
/** /**
* Processes the request. * Processes the request.
* The request is represented in terms of a controller route and action parameters. * Child classes should override this method with actual request processing logic.
* @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.
* @return integer the exit status of the controller action (0 means normal, non-zero values mean abnormal) * @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 * @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); $result = $this->createController($route);
if ($result === null) { if ($result === false) {
throw new Exception(\Yii::t('yii', 'Unable to resolve the request.')); throw new Exception(\Yii::t('yii', 'Unable to resolve the request.'));
} }
list($controller, $action) = $result; list($controller, $action) = $result;

12
framework/base/Controller.php

@ -114,7 +114,10 @@ class Controller extends Component implements Initable
$this->action = $action; $this->action = $action;
if ($this->authorize($action) && $this->beforeAction($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); $this->afterAction($action);
} else { } else {
$status = 1; $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. * This method is invoked when the request parameters do not satisfy the requirement of the specified action.
* The default implementation will throw an exception. * The default implementation will throw an exception.
* @param Action $action the action being executed * @param Action $action the action being executed
* @param Exception $exception the exception about the invalid parameters
* @throws Exception whenever this method is invoked * @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) { if ($route[0] !== '/' && !$this->module instanceof Application) {
$route = '/' . $this->module->getUniqueId() . '/' . $route; $route = '/' . $this->module->getUniqueId() . '/' . $route;
} }
$status = \Yii::$application->processRequest($route, $params); $status = \Yii::$application->runController($route, $params);
} }
if ($exit) { if ($exit) {
\Yii::$application->end($status); \Yii::$application->end($status);

14
framework/base/InlineAction.php

@ -9,6 +9,8 @@
namespace yii\base; namespace yii\base;
use yii\util\ReflectionHelper;
/** /**
* InlineAction represents an action that is defined as a controller method. * InlineAction represents an action that is defined as a controller method.
* *
@ -28,13 +30,13 @@ class InlineAction extends Action
*/ */
public function runWithParams($params) public function runWithParams($params)
{ {
$method = new \ReflectionMethod($this->controller, 'action' . $this->id); try {
$params = \yii\util\ReflectionHelper::bindParams($method, $params); $method = 'action' . $this->id;
if ($params === false) { $params = ReflectionHelper::extractMethodParams($this->controller, $method, $params);
$this->controller->invalidActionParams($this); return (int)call_user_func_array(array($this->controller, $method), $params);
} catch (Exception $e) {
$this->controller->invalidActionParams($this, $e);
return 1; return 1;
} else {
return (int)$method->invokeArgs($this, $params);
} }
} }
} }

55
framework/base/Module.php

@ -481,8 +481,8 @@ abstract class Module extends Component implements Initable
} }
/** /**
* Parses a given route into controller and action ID. * Creates a controller instance based on the given route.
* The parsing process follows the following algorithm: * This method tries to parse the given route (e.g. `post/create`) using the following algorithm:
* *
* 1. Get the first segment in route * 1. Get the first segment in route
* 2. If the segment matches * 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; * and return the controller with the rest part of the route;
* - a controller class under [[controllerPath]], create the controller instance, and return it * - a controller class under [[controllerPath]], create the controller instance, and return it
* with the rest part of the route; * 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`) * @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, '/')) === '') { if (($route = trim($route, '/')) === '') {
$route = $this->defaultRoute; $route = $this->defaultRoute;
@ -509,40 +509,16 @@ abstract class Module extends Component implements Initable
$route = ''; $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 // 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)) { if (!preg_match('/^[a-z][a-zA-Z0-9_]*$/', $id)) {
return null; return false;
} }
if (isset($this->controllers[$id])) { 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'; $className = ucfirst($id) . 'Controller';
@ -552,10 +528,17 @@ abstract class Module extends Component implements Initable
require($classFile); require($classFile);
} }
if (class_exists($className, false) && is_subclass_of($className, '\yii\base\Controller')) { 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;
} }
} }

35
framework/console/Application.php

@ -10,6 +10,7 @@
namespace yii\console; namespace yii\console;
use yii\base\Exception; use yii\base\Exception;
use yii\util\ReflectionHelper;
/** /**
* Application represents a console application. * Application represents a console application.
@ -71,19 +72,39 @@ class Application extends \yii\base\Application
} }
/** /**
* Runs the application. * Processes the request.
* This is the main entrance of an application. * The request is represented in terms of a controller route and action parameters.
* @return integer the exit status (0 means normal, non-zero values mean abnormal) * @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'])) { if (!isset($_SERVER['argv'])) {
die('This script must be run from the command line.'); die('This script must be run from the command line.');
} }
list($route, $params) = $this->resolveRequest($_SERVER['argv']); list($route, $params) = $this->resolveRequest($_SERVER['argv']);
$this->beforeRequest(); return $this->runController($route, $params);
$status = $this->processRequest($route, $params); }
$this->afterRequest();
/**
* 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; return $status;
} }

127
framework/console/Controller.php

@ -1,6 +1,6 @@
<?php <?php
/** /**
* Command class file. * Controller class file.
* *
* @link http://www.yiiframework.com/ * @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2012 Yii Software LLC * @copyright Copyright &copy; 2008-2012 Yii Software LLC
@ -9,6 +9,10 @@
namespace yii\console; namespace yii\console;
use yii\base\InlineAction;
use yii\base\Exception;
use yii\util\ReflectionHelper;
/** /**
* Command represents an executable console command. * Command represents an executable console command.
* *
@ -47,105 +51,6 @@ namespace yii\console;
class Controller extends \yii\base\Controller 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. * Provides the command description.
* This method may be overridden to return the actual 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'. * @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. * 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 * @param string $message to echo out before waiting for user input

77
framework/util/ReflectionHelper.php

@ -9,6 +9,8 @@
namespace yii\util; namespace yii\util;
use yii\base\Exception;
/** /**
* ReflectionHelper * ReflectionHelper
* *
@ -19,46 +21,83 @@ class ReflectionHelper
{ {
/** /**
* Prepares parameters so that they can be bound to the specified method. * Prepares parameters so that they can be bound to the specified method.
* This method mainly helps method parameter binding. It converts `$params` * This method converts the input parameters into an array that can later be
* into an array which can be passed to `call_user_func_array()` when calling * passed to `call_user_func_array()` when calling the specified method.
* the specified method. The conversion is based on the matching of method parameter names * The conversion is based on the matching of method parameter names
* and the input array keys. For example, * and the input array keys. For example,
* *
* ~~~ * ~~~
* class Foo { * class Foo {
* function bar($a, $b) { ... } * function bar($a, $b) { ... }
* } * }
* * $object = new Foo;
* $method = new \ReflectionMethod('Foo', 'bar');
* $params = array('b' => 2, 'c' => 3, 'a' => 1); * $params = array('b' => 2, 'c' => 3, 'a' => 1);
* var_export(ReflectionHelper::bindMethodParams($method, $params)); * var_export(ReflectionHelper::extractMethodParams($object, 'bar', $params));
* // would output: array('a' => 1, 'b' => 2) * // 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 * @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()`. * @return array parameters that are needed by the method only and
* False is returned if the input parameters do not follow the method declaration. * 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(); $ps = array();
foreach ($method->getParameters() as $param) { foreach ($m->getParameters() as $param) {
$name = $param->getName(); $name = $param->getName();
if (array_key_exists($name, $params)) { 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]; $ps[$name] = $params[$name];
} else {
return false;
}
} elseif ($param->isDefaultValueAvailable()) { } elseif ($param->isDefaultValueAvailable()) {
$ps[$name] = $param->getDefaultValue(); $ps[$name] = $param->getDefaultValue();
} else { } else {
return false; throw new Exception(\Yii::t('yii', 'Missing required parameter "{name}".', array('{name' => $name)));
} }
} }
return $ps; 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;
}
} }

Loading…
Cancel
Save