diff --git a/framework/base/Application.php b/framework/base/Application.php index 55dfeed..2e92aab 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -10,6 +10,7 @@ namespace yii\base; use yii\base\InvalidCallException; +use yii\util\StringHelper; /** * Application is the base class for all application classes. @@ -206,13 +207,13 @@ class Application extends Module * @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 BadRequestException if the route cannot be resolved into a controller + * @throws InvalidRequestException if the route cannot be resolved into a controller */ public function runController($route, $params = array()) { $result = $this->createController($route); if ($result === false) { - throw new BadRequestException(\Yii::t('yii', 'Unable to resolve the request.')); + throw new InvalidRequestException(\Yii::t('yii', 'Unable to resolve the request.')); } /** @var $controller Controller */ list($controller, $action) = $result; @@ -441,4 +442,126 @@ class Application extends Module ), )); } + + /** + * Performs a controller action specified by a route. + * This method parses the specified route and creates the corresponding controller and action + * instances under the context of the specified module. It then runs the created action + * with the given parameters. + * @param string $route the route that specifies the action. + * @param array $params the parameters to be passed to the action + * @param Module $module the module which serves as the context of the route + * @return integer the action + * @throws InvalidConfigException if the module's defaultRoute is empty or the controller's defaultAction is empty + * @throws InvalidRequestException if the requested route cannot be resolved into an action successfully + */ + public function runAction($route, $params = array(), $module = null) + { + if ($module === null) { + $module = $this; + } + $route = trim($route, '/'); + if ($route === '') { + $route = trim($module->defaultRoute, '/'); + if ($route == '') { + throw new InvalidConfigException(get_class($module) . '::defaultRoute cannot be empty.'); + } + } + if (($pos = strpos($route, '/')) !== false) { + $id = substr($route, 0, $pos); + $route = substr($route, $pos + 1); + } else { + $id = $route; + $route = ''; + } + + $childModule = $module->getModule($id); + if ($childModule !== null) { + return $this->runAction($route, $params, $childModule); + } + + /** @var $controller Controller */ + if (isset($module->controllerMap[$id])) { + $controller = \Yii::createObject($module->controllerMap[$id], $id, $module); + } else { + $controller = $this->createController($id, $module); + if ($controller === null) { + throw new InvalidRequestException("Unable to resolve the request: $route"); + } + } + + if (isset($controller)) { + $action = $this->createAction($route, $controller); + if ($action !== null) { + return $action->runWithParams($params); + } + } + + throw new InvalidRequestException("Unable to resolve the request: $route"); + } + + + /** + * Creates a controller instance based on the controller ID. + * + * The controller is created within the given module. The method first attempts to + * create the controller based on the [[controllerMap]] of the module. If not available, + * it will look for the controller class under the [[controllerPath]] and create an + * instance of it. + * + * @param string $id the controller ID + * @param Module $module the module that owns the controller + * @return Controller the newly created controller instance + */ + public function createController($id, $module) + { + if (isset($module->controllerMap[$id])) { + return \Yii::createObject($module->controllerMap[$id], $id, $module); + } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) { + $className = StringHelper::id2camel($id) . 'Controller'; + $classFile = $module->controllerPath . DIRECTORY_SEPARATOR . $className . '.php'; + if (is_file($classFile)) { + $className = $module->controllerNamespace . '\\' . $className; + if (!class_exists($className, false)) { + require($classFile); + } + if (class_exists($className, false) && is_subclass_of($className, '\yii\base\Controller')) { + return new $className($id, $module); + } + } + } + return null; + } + + /** + * Creates an action based on the given action ID. + * The action is created within the given controller. The method first attempts to + * create the action based on [[Controller::actions()]]. If not available, + * it will look for the inline action method within the controller. + * @param string $id the action ID + * @param Controller $controller the controller that owns the action + * @return Action the newly created action instance + * @throws InvalidConfigException if [[Controller::defaultAction]] is empty. + */ + public function createAction($id, $controller) + { + if ($id === '') { + $id = $controller->defaultAction; + if ($id == '') { + throw new InvalidConfigException(get_class($controller) . '::defaultAction cannot be empty.'); + } + } + if (isset($controller->actionMap[$id])) { + return \Yii::createObject($controller->actionMap[$id], $id, $controller); + } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) { + $methodName = 'action' . StringHelper::id2camel($id); + if (method_exists($controller, $methodName)) { + $method = new \ReflectionMethod($controller, $methodName); + if ($method->getName() === $methodName) { + return new InlineAction($id, $controller); + } + } + } + return null; + } } diff --git a/framework/base/Controller.php b/framework/base/Controller.php index 61dec24..0df287a 100644 --- a/framework/base/Controller.php +++ b/framework/base/Controller.php @@ -28,7 +28,7 @@ namespace yii\base; class Controller extends Component { /** - * @var string ID of this controller + * @var string the ID of this controller */ public $id; /** @@ -36,12 +36,39 @@ class Controller extends Component */ public $module; /** - * @var string the name of the default action. Defaults to 'index'. + * @var string the ID of the action that is used when the action ID is not specified + * in the request. Defaults to 'index'. */ public $defaultAction = 'index'; /** - * @var array mapping from action ID to action configuration. - * Array keys are action IDs, and array values are the corresponding + * @var string|boolean the name of the layout to be applied to this controller's views. + * This property mainly affects the behavior of [[render()]]. + * Defaults to null, meaning the actual layout value should inherit that from [[module]]'s layout value. + * If false, no layout will be applied. + */ + public $layout; + /** + * @var Action the action that is currently being executed. This property will be set + * by [[run()]] when it is called by [[Application]] to run an action. + */ + public $action; + + /** + * @param string $id the ID of this controller + * @param Module $module the module that this controller belongs to. + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct($id, $module, $config = array()) + { + $this->id = $id; + $this->module = $module; + parent::__construct($config); + } + + /** + * Declares external actions for the controller. + * This method is meant to be overwritten to declare external actions for the controller. + * It should return an array, with array keys being action IDs, and array values the corresponding * action class names or action configuration arrays. For example, * * ~~~ @@ -55,36 +82,12 @@ class Controller extends Component * ); * ~~~ * - * [[\Yii::createObject()]] will be invoked to create the requested action + * [[\Yii::createObject()]] will be used later to create the requested action * using the configuration provided here. - * - * Note, in order to inherit actions defined in the parent class, a child class needs to - * merge the parent actions with child actions using functions like `array_merge()`. - * @see createAction - */ - public $actions = array(); - /** - * @var Action the action that is currently being executed */ - public $action; - /** - * @var string|boolean the name of the layout to be applied to this controller's views. - * This property mainly affects the behavior of [[render()]]. - * Defaults to null, meaning the layout specified by the [[module]] should be used. - * If false, no layout will be applied. - */ - public $layout; - - /** - * @param string $id ID of this controller - * @param Module $module the module that this controller belongs to. - * @param array $config name-value pairs that will be used to initialize the object properties - */ - public function __construct($id, $module, $config = array()) + public function actions() { - $this->id = $id; - $this->module = $module; - parent::__construct($config); + return array(); } /** @@ -139,8 +142,8 @@ class Controller extends Component if ($actionID === '') { $actionID = $this->defaultAction; } - if (isset($this->actions[$actionID])) { - return \Yii::createObject($this->actions[$actionID], $actionID, $this); + if (isset($this->actionMap[$actionID])) { + return \Yii::createObject($this->actionMap[$actionID], $actionID, $this); } elseif (method_exists($this, 'action' . $actionID)) { return new InlineAction($actionID, $this); } else { @@ -188,11 +191,11 @@ class Controller extends Component * This method is invoked when the controller cannot find the requested action. * The default implementation simply throws an exception. * @param string $actionID the missing action name - * @throws BadRequestException whenever this method is invoked + * @throws InvalidRequestException whenever this method is invoked */ public function missingAction($actionID) { - throw new BadRequestException(\Yii::t('yii', 'The system is unable to find the requested action "{action}".', + throw new InvalidRequestException(\Yii::t('yii', 'The system is unable to find the requested action "{action}".', array('{action}' => $actionID == '' ? $this->defaultAction : $actionID))); } diff --git a/framework/base/BadRequestException.php b/framework/base/InvalidRequestException.php similarity index 58% rename from framework/base/BadRequestException.php rename to framework/base/InvalidRequestException.php index cb97419..2e2a04a 100644 --- a/framework/base/BadRequestException.php +++ b/framework/base/InvalidRequestException.php @@ -1,6 +1,6 @@ * @since 2.0 */ -class BadRequestException extends \Exception +class InvalidRequestException extends \Exception { } diff --git a/framework/base/Module.php b/framework/base/Module.php index f08d033..6337a9b 100644 --- a/framework/base/Module.php +++ b/framework/base/Module.php @@ -70,7 +70,11 @@ abstract class Module extends Component * ) * ~~~ */ - public $controllers = array(); + public $controllerMap = array(); + /** + * @var string the namespace that controller classes are in. Default is to use global namespace. + */ + public $controllerNamespace; /** * @return string the default route of this module. Defaults to 'default'. * The route may consist of child module ID, controller ID, and/or action ID. @@ -529,69 +533,4 @@ abstract class Module extends Component $this->getComponent($id); } } - - /** - * 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 - * - an ID in [[controllers]], create a controller instance using the corresponding configuration, - * and return the controller with the rest part of the route; - * - an ID in [[modules]], call the [[createController()]] method of the corresponding module. - * - a controller class under [[controllerPath]], create the controller instance, and return it - * with the rest part of the route; - * - * @param string $route the route which may consist module ID, controller ID and/or action ID (e.g. `post/create`) - * @return array|boolean the array of controller instance and action ID. False if the route cannot be resolved. - */ - public function createController($route) - { - if (($route = trim($route, '/')) === '') { - $route = $this->defaultRoute; - } - - if (($pos = strpos($route, '/')) !== false) { - $id = substr($route, 0, $pos); - $route = (string)substr($route, $pos + 1); - } else { - $id = $route; - $route = ''; - } - - // 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 false; - } - - if (isset($this->controllers[$id])) { - return array( - \Yii::createObject($this->controllers[$id], $id, $this), - $route, - ); - } - - if (($module = $this->getModule($id)) !== null) { - $result = $module->createController($route); - if ($result !== false) { - return $result; - } - } - - $className = ucfirst($id) . 'Controller'; - $classFile = $this->getControllerPath() . DIRECTORY_SEPARATOR . $className . '.php'; - if (is_file($classFile)) { - if (!class_exists($className, false)) { - require($classFile); - } - if (class_exists($className, false) && is_subclass_of($className, '\yii\base\Controller')) { - return array( - new $className($id, $this), - $route, - ); - } - } - - return false; - } } diff --git a/framework/console/Application.php b/framework/console/Application.php index 9f23d15..23d80e0 100644 --- a/framework/console/Application.php +++ b/framework/console/Application.php @@ -70,14 +70,14 @@ class Application extends \yii\base\Application parent::init(); if ($this->enableCoreCommands) { foreach ($this->coreCommands() as $id => $command) { - if (!isset($this->controllers[$id])) { - $this->controllers[$id] = $command; + if (!isset($this->controllerMap[$id])) { + $this->controllerMap[$id] = $command; } } } // ensure we have the 'help' command so that we can list the available commands - if (!isset($this->controllers['help'])) { - $this->controllers['help'] = 'yii\console\controllers\HelpController'; + if (!isset($this->controllerMap['help'])) { + $this->controllerMap['help'] = 'yii\console\controllers\HelpController'; } } diff --git a/framework/console/controllers/HelpController.php b/framework/console/controllers/HelpController.php index 30d7a01..f4d1eb8 100644 --- a/framework/console/controllers/HelpController.php +++ b/framework/console/controllers/HelpController.php @@ -87,7 +87,7 @@ class HelpController extends Controller */ public function getActions($controller) { - $actions = array_keys($controller->actions); + $actions = array_keys($controller->actionMap); $class = new \ReflectionClass($controller); foreach ($class->getMethods() as $method) { /** @var $method \ReflectionMethod */ @@ -114,7 +114,7 @@ class HelpController extends Controller } $commands = array(); - foreach (array_keys($module->controllers) as $id) { + foreach (array_keys($module->controllerMap) as $id) { $commands[] = $prefix . $id; } diff --git a/framework/util/StringHelper.php b/framework/util/StringHelper.php index 1c2c736..a2be4b6 100644 --- a/framework/util/StringHelper.php +++ b/framework/util/StringHelper.php @@ -83,4 +83,17 @@ class StringHelper return trim(strtolower(str_replace('_', $separator, preg_replace('/(?