Commit 10061359 by Qiang Xue

Fixes #1467: Added support for organizing controllers in subdirectories

parent 80e9b800
...@@ -81,13 +81,16 @@ Routes ...@@ -81,13 +81,16 @@ Routes
------ ------
Each controller action has a corresponding internal route. In our example above `actionIndex` has `site/index` route Each controller action has a corresponding internal route. In our example above `actionIndex` has `site/index` route
and `actionTest` has `site/test` route. In this route `site` is referred to as controller ID while `test` is referred to and `actionTest` has `site/test` route. In this route `site` is referred to as controller ID while `test` is action ID.
as action ID.
By default you can access specific controller and action using the `http://example.com/?r=controller/action` URL. This By default you can access specific controller and action using the `http://example.com/?r=controller/action` URL. This
behavior is fully customizable. For details refer to [URL Management](url.md). behavior is fully customizable. For more details please refer to [URL Management](url.md).
If controller is located inside a module its action internal route will be `module/controller/action`. If a controller is located inside a module, the route of its actions will be in the format of `module/controller/action`.
A controller can be located under a subdirectory of the controller directory of an application or module. The route
will be prefixed with the corresponding directory names. For example, you may have a `UserController` under `controllers/admin`.
The route of its `actionIndex` would be `admin/user/index`, and `admin/user` would be the controller ID.
In case module, controller or action specified isn't found Yii will return "not found" page and HTTP status code 404. In case module, controller or action specified isn't found Yii will return "not found" page and HTTP status code 404.
......
...@@ -71,6 +71,7 @@ Yii Framework 2 Change Log ...@@ -71,6 +71,7 @@ Yii Framework 2 Change Log
- Enh #1293: Replaced Console::showProgress() with a better approach. See Console::startProgress() for details (cebe) - Enh #1293: Replaced Console::showProgress() with a better approach. See Console::startProgress() for details (cebe)
- Enh #1406: DB Schema support for Oracle Database (p0larbeer, qiangxue) - Enh #1406: DB Schema support for Oracle Database (p0larbeer, qiangxue)
- Enh #1437: Added ListView::viewParams (qiangxue) - Enh #1437: Added ListView::viewParams (qiangxue)
- Enh #1467: Added support for organizing controllers in subdirectories (qiangxue)
- Enh #1469: ActiveRecord::find() now works with default conditions (default scope) applied by createQuery (cebe) - Enh #1469: ActiveRecord::find() now works with default conditions (default scope) applied by createQuery (cebe)
- Enh #1476: Add yii\web\Session::handler property (nineinchnick) - Enh #1476: Add yii\web\Session::handler property (nineinchnick)
- Enh #1499: Added `ActionColumn::controller` property to support customizing the controller for handling GridView actions (qiangxue) - Enh #1499: Added `ActionColumn::controller` property to support customizing the controller for handling GridView actions (qiangxue)
......
...@@ -593,12 +593,21 @@ class Module extends Component ...@@ -593,12 +593,21 @@ class Module extends Component
} }
/** /**
* Creates a controller instance based on the controller ID. * Creates a controller instance based on the given route.
* *
* The controller is created within this module. The method first attempts to * The route should be relative to this module. The method implements the following algorithm
* create the controller based on the [[controllerMap]] of the module. If not available, * to resolve the given route:
* it will look for the controller class under the [[controllerPath]] and create an *
* instance of it. * 1. If the route is empty, use [[defaultRoute]];
* 2. If the first segment of the route is a valid module ID as declared in [[modules]],
* call the module's `createController()` with the rest part of the route;
* 3. If the first segment of the route is found in [[controllerMap]], create a controller
* based on the corresponding configuration found in [[controllerMap]];
* 4. The given route is in the format of `abc/def/xyz`. Try either `abc\DefController`
* or `abc\def\XyzController` class within the [[controllerNamespace|controller namespace]].
*
* If any of the above steps resolves into a controller, it is returned together with the rest
* part of the route which will be treated as the action ID. Otherwise, false will be returned.
* *
* @param string $route the route consisting of module, controller and action IDs. * @param string $route the route consisting of module, controller and action IDs.
* @return array|boolean If the controller is created successfully, it will be returned together * @return array|boolean If the controller is created successfully, it will be returned together
...@@ -610,6 +619,13 @@ class Module extends Component ...@@ -610,6 +619,13 @@ class Module extends Component
if ($route === '') { if ($route === '') {
$route = $this->defaultRoute; $route = $this->defaultRoute;
} }
// double slashes or leading/ending slashes may cause substr problem
$route = trim($route, '/');
if (strpos($route, '//') !== false) {
return false;
}
if (strpos($route, '/') !== false) { if (strpos($route, '/') !== false) {
list ($id, $route) = explode('/', $route, 2); list ($id, $route) = explode('/', $route, 2);
} else { } else {
...@@ -617,29 +633,73 @@ class Module extends Component ...@@ -617,29 +633,73 @@ class Module extends Component
$route = ''; $route = '';
} }
// module and controller map take precedence
$module = $this->getModule($id); $module = $this->getModule($id);
if ($module !== null) { if ($module !== null) {
return $module->createController($route); return $module->createController($route);
} }
if (isset($this->controllerMap[$id])) { if (isset($this->controllerMap[$id])) {
$controller = Yii::createObject($this->controllerMap[$id], $id, $this); $controller = Yii::createObject($this->controllerMap[$id], $id, $this);
} elseif (preg_match('/^[a-z0-9\\-_]+$/', $id) && strpos($id, '--') === false && trim($id, '-') === $id) { return [$controller, $route];
$className = str_replace(' ', '', ucwords(str_replace('-', ' ', $id))) . 'Controller'; }
$classFile = $this->controllerPath . DIRECTORY_SEPARATOR . $className . '.php';
if (!is_file($classFile)) { if (($pos = strrpos($route, '/')) !== false) {
return false; $id .= '/' . substr($route, 0, $pos);
} $route = substr($route, $pos + 1);
$className = ltrim($this->controllerNamespace . '\\' . $className, '\\'); }
Yii::$classMap[$className] = $classFile;
if (is_subclass_of($className, 'yii\base\Controller')) { $controller = $this->createControllerByID($id);
$controller = new $className($id, $this); if ($controller === null && $route !== '') {
} elseif (YII_DEBUG) { $controller = $this->createControllerByID($id . '/' . $route);
throw new InvalidConfigException("Controller class must extend from \\yii\\base\\Controller."); $route = '';
} }
return $controller === null ? false : [$controller, $route];
}
/**
* Creates a controller based on the given controller ID.
*
* The controller ID is relative to this module. The controller class
* should be located under [[controllerPath]] and namespaced under [[controllerNamespace]].
*
* Note that this method does not check [[modules]] or [[controllerMap]].
*
* @param string $id the controller ID
* @return Controller the newly created controller instance, or null if the controller ID is invalid.
* @throws InvalidConfigException if the controller class and its file name do not match.
* This exception is only thrown when in debug mode.
*/
public function createControllerByID($id)
{
if (!preg_match('%^[a-z0-9\\-_/]+$%', $id)) {
return null;
} }
return isset($controller) ? [$controller, $route] : false; $pos = strrpos($id, '/');
if ($pos === false) {
$prefix = '';
$className = $id;
} else {
$prefix = substr($id, 0, $pos + 1);
$className = substr($id, $pos + 1);
}
$className = str_replace(' ', '', ucwords(str_replace('-', ' ', $className))) . 'Controller';
$classFile = $this->controllerPath . '/' . $prefix . $className . '.php';
$className = $this->controllerNamespace . '\\' . str_replace('/', '\\', $prefix) . $className;
if (strpos($className, '-') !== false || !is_file($classFile)) {
return null;
}
Yii::$classMap[$className] = $classFile;
if (is_subclass_of($className, 'yii\base\Controller')) {
return new $className($id, $this);
} elseif (YII_DEBUG) {
throw new InvalidConfigException("Controller class must extend from \\yii\\base\\Controller.");
} else {
return null;
}
} }
/** /**
......
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