Zend Framework的路由提供了两个主要功能路由和创建路由。
Zend_Controller_Router的Route类和相应Route目录下的类定义常见的路由操作。
接口Zend_Controller_Router_Interface,类Zend_Controller_Router_Abstract和Zend_Controller_Router_Rewrite完成了基本的路由,创建路由,删除路由的功能。
└── Router ├── Abstract.php ├── Exception.php ├── Interface.php ├── Rewrite.php ├── Route │ ├── Abstract.php │ ├── Chain.php │ ├── Hostname.php │ ├── Interface.php │ ├── Module.php │ ├── Regex.php │ └── Static.php └── Route.php
Zend_Controller_Router路由功能的实现
Zend_Controller_Router_Interface
<?php interface Zend_Controller_Router_Interface{ /** * Processes a request and sets its controller and action. If * no route was possible, an exception is thrown. * * @param Zend_Controller_Request_Abstract * @throws Zend_Controller_Router_Exception * @return Zend_Controller_Request_Abstract|boolean */ public function route(Zend_Controller_Request_Abstract $dispatcher); /** * Generates a URL path that can be used in URL creation, redirection, etc. * * May be passed user params to override ones from URI, Request or even defaults. * If passed parameter has a value of null, it's URL variable will be reset to * default. * * If null is passed as a route name assemble will use the current Route or 'default' * if current is not yet set. * * Reset is used to signal that all parameters should be reset to it's defaults. * Ignoring all URL specified values. User specified params still get precedence. * * Encode tells to url encode resulting path parts. * * @param array $userParams Options passed by a user used to override parameters * @param mixed $name The name of a Route to use * @param bool $reset Whether to reset to the route defaults ignoring URL params * @param bool $encode Tells to encode URL parts on output * @throws Zend_Controller_Router_Exception * @return string Resulting URL path */ public function assemble($userParams, $name = null, $reset = false, $encode = true); /** * Retrieve Front Controller * * @return Zend_Controller_Front */ public function getFrontController(); /** * Set Front Controller * * @param Zend_Controller_Front $controller * @return Zend_Controller_Router_Interface */ public function setFrontController(Zend_Controller_Front $controller); /** * Add or modify a parameter with which to instantiate any helper objects * * @param string $name * @param mixed $param * @return Zend_Controller_Router_Interface */ public function setParam($name, $value); /** * Set an array of a parameters to pass to helper object constructors * * @param array $params * @return Zend_Controller_Router_Interface */ public function setParams(array $params); /** * Retrieve a single parameter from the controller parameter stack * * @param string $name * @return mixed */ public function getParam($name); /** * Retrieve the parameters to pass to helper object constructors * * @return array */ public function getParams(); /** * Clear the controller parameter stack * * By default, clears all parameters. If a parameter name is given, clears * only that parameter; if an array of parameter names is provided, clears * each. * * @param null|string|array single key or array of keys for params to clear * @return Zend_Controller_Router_Interface */ public function clearParams($name = null);}
<?php /** Zend_Controller_Router_Interface */require_once 'Zend/Controller/Router/Interface.php'; abstract class Zend_Controller_Router_Abstract implements Zend_Controller_Router_Interface{ /** * URI delimiter */ const URI_DELIMITER = '/'; /** * Front controller instance * @var Zend_Controller_Front */ protected $_frontController; /** * Array of invocation parameters to use when instantiating action * controllers * @var array */ protected $_invokeParams = array(); /** * Constructor * * @param array $params * @return void */ public function __construct(array $params = array()) { $this->setParams($params); } /** * Add or modify a parameter to use when instantiating an action controller * * @param string $name * @param mixed $value * @return Zend_Controller_Router */ public function setParam($name, $value) { $name = (string) $name; $this->_invokeParams[$name] = $value; return $this; } /** * Set parameters to pass to action controller constructors * * @param array $params * @return Zend_Controller_Router */ public function setParams(array $params) { $this->_invokeParams = array_merge($this->_invokeParams, $params); return $this; } /** * Retrieve a single parameter from the controller parameter stack * * @param string $name * @return mixed */ public function getParam($name) { if(isset($this->_invokeParams[$name])) { return $this->_invokeParams[$name]; } return null; } /** * Retrieve action controller instantiation parameters * * @return array */ public function getParams() { return $this->_invokeParams; } /** * Clear the controller parameter stack * * By default, clears all parameters. If a parameter name is given, clears * only that parameter; if an array of parameter names is provided, clears * each. * * @param null|string|array single key or array of keys for params to clear * @return Zend_Controller_Router */ public function clearParams($name = null) { if (null === $name) { $this->_invokeParams = array(); } elseif (is_string($name) && isset($this->_invokeParams[$name])) { unset($this->_invokeParams[$name]); } elseif (is_array($name)) { foreach ($name as $key) { if (is_string($key) && isset($this->_invokeParams[$key])) { unset($this->_invokeParams[$key]); } } } return $this; } /** * Retrieve Front Controller * * @return Zend_Controller_Front */ public function getFrontController() { // Used cache version if found if (null !== $this->_frontController) { return $this->_frontController; } require_once 'Zend/Controller/Front.php'; $this->_frontController = Zend_Controller_Front::getInstance(); return $this->_frontController; } /** * Set Front Controller * * @param Zend_Controller_Front $controller * @return Zend_Controller_Router_Interface */ public function setFrontController(Zend_Controller_Front $controller) { $this->_frontController = $controller; return $this; }}
<?php /** Zend_Controller_Router_Abstract */require_once 'Zend/Controller/Router/Abstract.php';/** Zend_Controller_Router_Route */require_once 'Zend/Controller/Router/Route.php'; class Zend_Controller_Router_Rewrite extends Zend_Controller_Router_Abstract{ /** * Whether or not to use default routes * * @var boolean */ protected $_useDefaultRoutes = true; /** * Array of routes to match against * * @var array */ protected $_routes = array(); /** * Currently matched route * * @var Zend_Controller_Router_Route_Interface */ protected $_currentRoute = null; /** * Global parameters given to all routes * * @var array */ protected $_globalParams = array(); /** * Separator to use with chain names * * @var string */ protected $_chainNameSeparator = '-'; /** * Determines if request parameters should be used as global parameters * inside this router. * * @var boolean */ protected $_useCurrentParamsAsGlobal = false; /** * Add default routes which are used to mimic basic router behaviour * * @return Zend_Controller_Router_Rewrite */ public function addDefaultRoutes() { if (!$this->hasRoute('default')) { $dispatcher = $this->getFrontController()->getDispatcher(); $request = $this->getFrontController()->getRequest(); require_once 'Zend/Controller/Router/Route/Module.php'; $compat = new Zend_Controller_Router_Route_Module(array(), $dispatcher, $request); $this->_routes = array('default' => $compat) + $this->_routes; } return $this; } /** * Add route to the route chain * * If route contains method setRequest(), it is initialized with a request object * * @param string $name Name of the route * @param Zend_Controller_Router_Route_Interface $route Instance of the route * @return Zend_Controller_Router_Rewrite */ public function addRoute($name, Zend_Controller_Router_Route_Interface $route) { if (method_exists($route, 'setRequest')) { $route->setRequest($this->getFrontController()->getRequest()); } $this->_routes[$name] = $route; return $this; } /** * Add routes to the route chain * * @param array $routes Array of routes with names as keys and routes as values * @return Zend_Controller_Router_Rewrite */ public function addRoutes($routes) { foreach ($routes as $name => $route) { $this->addRoute($name, $route); } return $this; } /** * Create routes out of Zend_Config configuration * * Example INI: * routes.archive.route = "archive/:year/*" * routes.archive.defaults.controller = archive * routes.archive.defaults.action = show * routes.archive.defaults.year = 2000 * routes.archive.reqs.year = "d+" * * routes.news.type = "Zend_Controller_Router_Route_Static" * routes.news.route = "news" * routes.news.defaults.controller = "news" * routes.news.defaults.action = "list" * * And finally after you have created a Zend_Config with above ini: * $router = new Zend_Controller_Router_Rewrite(); * $router->addConfig($config, 'routes'); * * @param Zend_Config $config Configuration object * @param string $section Name of the config section containing route's definitions * @throws Zend_Controller_Router_Exception * @return Zend_Controller_Router_Rewrite */ public function addConfig(Zend_Config $config, $section = null) { if ($section !== null) { if ($config->{$section} === null) { require_once 'Zend/Controller/Router/Exception.php'; throw new Zend_Controller_Router_Exception("No route configuration in section '{$section}'"); } $config = $config->{$section}; } foreach ($config as $name => $info) { $route = $this->_getRouteFromConfig($info); if ($route instanceof Zend_Controller_Router_Route_Chain) { if (!isset($info->chain)) { require_once 'Zend/Controller/Router/Exception.php'; throw new Zend_Controller_Router_Exception("No chain defined"); } if ($info->chain instanceof Zend_Config) { $childRouteNames = $info->chain; } else { $childRouteNames = explode(',', $info->chain); } foreach ($childRouteNames as $childRouteName) { $childRoute = $this->getRoute(trim($childRouteName)); $route->chain($childRoute); } $this->addRoute($name, $route); } elseif (isset($info->chains) && $info->chains instanceof Zend_Config) { $this->_addChainRoutesFromConfig($name, $route, $info->chains); } else { $this->addRoute($name, $route); } } return $this; } /** * Get a route frm a config instance * * @param Zend_Config $info * @return Zend_Controller_Router_Route_Interface */ protected function _getRouteFromConfig(Zend_Config $info) { $class = (isset($info->type)) ? $info->type : 'Zend_Controller_Router_Route'; if (!class_exists($class)) { require_once 'Zend/Loader.php'; Zend_Loader::loadClass($class); } $route = call_user_func(array($class, 'getInstance'), $info); if (isset($info->abstract) && $info->abstract && method_exists($route, 'isAbstract')) { $route->isAbstract(true); } return $route; } /** * Add chain routes from a config route * * @param string $name * @param Zend_Controller_Router_Route_Interface $route * @param Zend_Config $childRoutesInfo * @return void */ protected function _addChainRoutesFromConfig($name, Zend_Controller_Router_Route_Interface $route, Zend_Config $childRoutesInfo) { foreach ($childRoutesInfo as $childRouteName => $childRouteInfo) { if (is_string($childRouteInfo)) { $childRouteName = $childRouteInfo; $childRoute = $this->getRoute($childRouteName); } else { $childRoute = $this->_getRouteFromConfig($childRouteInfo); } if ($route instanceof Zend_Controller_Router_Route_Chain) { $chainRoute = clone $route; $chainRoute->chain($childRoute); } else { $chainRoute = $route->chain($childRoute); } $chainName = $name . $this->_chainNameSeparator . $childRouteName; if (isset($childRouteInfo->chains)) { $this->_addChainRoutesFromConfig($chainName, $chainRoute, $childRouteInfo->chains); } else { $this->addRoute($chainName, $chainRoute); } } } /** * Remove a route from the route chain * * @param string $name Name of the route * @throws Zend_Controller_Router_Exception * @return Zend_Controller_Router_Rewrite */ public function removeRoute($name) { if (!isset($this->_routes[$name])) { require_once 'Zend/Controller/Router/Exception.php'; throw new Zend_Controller_Router_Exception("Route $name is not defined"); } unset($this->_routes[$name]); return $this; } /** * Remove all standard default routes * * @param Zend_Controller_Router_Route_Interface Route * @return Zend_Controller_Router_Rewrite */ public function removeDefaultRoutes() { $this->_useDefaultRoutes = false; return $this; } /** * Check if named route exists * * @param string $name Name of the route * @return boolean */ public function hasRoute($name) { return isset($this->_routes[$name]); } /** * Retrieve a named route * * @param string $name Name of the route * @throws Zend_Controller_Router_Exception * @return Zend_Controller_Router_Route_Interface Route object */ public function getRoute($name) { if (!isset($this->_routes[$name])) { require_once 'Zend/Controller/Router/Exception.php'; throw new Zend_Controller_Router_Exception("Route $name is not defined"); } return $this->_routes[$name]; } /** * Retrieve a currently matched route * * @throws Zend_Controller_Router_Exception * @return Zend_Controller_Router_Route_Interface Route object */ public function getCurrentRoute() { if (!isset($this->_currentRoute)) { require_once 'Zend/Controller/Router/Exception.php'; throw new Zend_Controller_Router_Exception("Current route is not defined"); } return $this->getRoute($this->_currentRoute); } /** * Retrieve a name of currently matched route * * @throws Zend_Controller_Router_Exception * @return Zend_Controller_Router_Route_Interface Route object */ public function getCurrentRouteName() { if (!isset($this->_currentRoute)) { require_once 'Zend/Controller/Router/Exception.php'; throw new Zend_Controller_Router_Exception("Current route is not defined"); } return $this->_currentRoute; } /** * Retrieve an array of routes added to the route chain * * @return array All of the defined routes */ public function getRoutes() { return $this->_routes; } /** * Find a matching route to the current PATH_INFO and inject * returning values to the Request object. * * @throws Zend_Controller_Router_Exception * @return Zend_Controller_Request_Abstract Request object */ public function route(Zend_Controller_Request_Abstract $request) { if (!$request instanceof Zend_Controller_Request_Http) { require_once 'Zend/Controller/Router/Exception.php'; throw new Zend_Controller_Router_Exception('Zend_Controller_Router_Rewrite requires a Zend_Controller_Request_Http-based request object'); } if ($this->_useDefaultRoutes) { $this->addDefaultRoutes(); } // Find the matching route $routeMatched = false; foreach (array_reverse($this->_routes, true) as $name => $route) { // TODO: Should be an interface method. Hack for 1.0 BC if (method_exists($route, 'isAbstract') && $route->isAbstract()) { continue; } // TODO: Should be an interface method. Hack for 1.0 BC if (!method_exists($route, 'getVersion') || $route->getVersion() == 1) { $match = $request->getPathInfo(); } else { $match = $request; } if ($params = $route->match($match)) { $this->_setRequestParams($request, $params); $this->_currentRoute = $name; $routeMatched = true; break; } } if (!$routeMatched) { require_once 'Zend/Controller/Router/Exception.php'; throw new Zend_Controller_Router_Exception('No route matched the request', 404); } if($this->_useCurrentParamsAsGlobal) { $params = $request->getParams(); foreach($params as $param => $value) { $this->setGlobalParam($param, $value); } } return $request; } protected function _setRequestParams($request, $params) { foreach ($params as $param => $value) { $request->setParam($param, $value); if ($param === $request->getModuleKey()) { $request->setModuleName($value); } if ($param === $request->getControllerKey()) { $request->setControllerName($value); } if ($param === $request->getActionKey()) { $request->setActionName($value); } } } /** * Generates a URL path that can be used in URL creation, redirection, etc. * * @param array $userParams Options passed by a user used to override parameters * @param mixed $name The name of a Route to use * @param bool $reset Whether to reset to the route defaults ignoring URL params * @param bool $encode Tells to encode URL parts on output * @throws Zend_Controller_Router_Exception * @return string Resulting absolute URL path */ public function assemble($userParams, $name = null, $reset = false, $encode = true) { if (!is_array($userParams)) { require_once 'Zend/Controller/Router/Exception.php'; throw new Zend_Controller_Router_Exception('userParams must be an array'); } if ($name == null) { try { $name = $this->getCurrentRouteName(); } catch (Zend_Controller_Router_Exception $e) { $name = 'default'; } } // Use UNION (+) in order to preserve numeric keys $params = $userParams + $this->_globalParams; $route = $this->getRoute($name); $url = $route->assemble($params, $reset, $encode); if (!preg_match('|^[a-z]+://|', $url)) { $url = rtrim($this->getFrontController()->getBaseUrl(), self::URI_DELIMITER) . self::URI_DELIMITER . $url; } return $url; } /** * Set a global parameter * * @param string $name * @param mixed $value * @return Zend_Controller_Router_Rewrite */ public function setGlobalParam($name, $value) { $this->_globalParams[$name] = $value; return $this; } /** * Set the separator to use with chain names * * @param string $separator The separator to use * @return Zend_Controller_Router_Rewrite */ public function setChainNameSeparator($separator) { $this->_chainNameSeparator = $separator; return $this; } /** * Get the separator to use for chain names * * @return string */ public function getChainNameSeparator() { return $this->_chainNameSeparator; } /** * Determines/returns whether to use the request parameters as global parameters. * * @param boolean|null $use * Null/unset when you want to retrieve the current state. * True when request parameters should be global, false otherwise * @return boolean|Zend_Controller_Router_Rewrite * Returns a boolean if first param isn't set, returns an * instance of Zend_Controller_Router_Rewrite otherwise. * */ public function useRequestParametersAsGlobal($use = null) { if($use === null) { return $this->_useCurrentParamsAsGlobal; } $this->_useCurrentParamsAsGlobal = (bool) $use; return $this; }}
public function addRoute($name, Zend_Controller_Router_Route_Interface $route)
public function addRoutes($routes)
$router = $ctrl->getRouter(); // returns a rewrite router by default$router->addRoute('user', new Zend_Controller_Router_Route('user/:username'));
addRoute的第一个参数是路由名。第二个参数是路由自己。路由名最普通的用法是通过Zend_View_Url助手的方法:
"<?= $this->url(array('username' => 'martel'), 'user') ?>">Martel</a>
它将导致在 href: user/martel.
路由是一个简单的过程,这个过程通过所有提供的路由和匹配它的当前请求的URI定义来迭代。当一个正匹配被发现,变量值从路由实例返回并注入到Zend_Controller_Request对象以备将来在派遣器和用户创建的控制器中使用。如果是负匹配,在链中的下个路由被检查。
Note: 倒序匹配
用倒序来匹配路由确保最通用的路由被首先定义。
Note: 返回的值
从路由返回的值来自于URL参数或用于定义的缺省值。这些变量以后可通过Zend_Controller_Request::getParam() 或 Zend_Controller_Action::_getParam() 方法来访问。
有三个特殊的变量可用于你的路由-'module'、 'controller' 和 'action'。这些特殊的变量被Zend_Controller_Dispatcher用来找出控制器和动作然后派遣过去。
Note: 特殊变量
如果你选择通过 setControllerKey 和 setActionKey方法的方式来改变缺省值,这些特殊变量的名字可能会不同。
缺省路由
Zend_Controller_Router_Rewrite 和缺省路由一起预先配置,它将以controller/action的形式匹配URIs。另外,模块名可以被指定作为第一个路径参数,允许这种module/controller/action形式的URIs。最后,它也将缺省地匹配任何另外的追加到URI的参数-controller/action/var1/value1/var2/value2。
一些路由如何匹配的例子:
// Assuming the following:$ctrl->setControllerDirectory( array( 'default' => '/path/to/default/controllers', 'news' => '/path/to/news/controllers', 'blog' => '/path/to/blog/controllers' ));Module only:http://example/news module == newsInvalid module maps to controller name:http://example/foo controller == fooModule + controller:http://example/blog/archive module == blog controller == archiveModule + controller + action:http://example/blog/archive/list module == blog controller == archive action == listModule + controller + action + params:http://example/blog/archive/list/sort/alpha/date/desc module == blog controller == archive action == list sort == alpha date == desc
$compat = new Zend_Controller_Router_Route_Module(array(), $dispatcher, $request);$this->addRoute('default', $compat);
// Remove any default routes$router->removeDefaultRoutes();
为了增加路由的灵活性,方便自定义新的路由类型,Zend_Controller_Router定义了Zend_Controller_Router_Route_Interface接口和类Zend_Controller_Router_Route_Abstract,实现相应的类方法即可定义路由类型,为开发提供了便利。
Zend_Controller_Router的路由类型
Zend_Controller_Router默认提供了以下路由类型,分别为:
Zend_Controller_Router_Route
Zend_Controller_Router_Route_Static
Zend_Controller_Router_Route_Regex
Zend_Controller_Router_Route_Hostname
Zend_Controller_Router_Route_Module
Zend_Controller_Router_Route_Chain
Zend_Controller_Router_Route
Zend_Controller_Router_Route是标准的框架路由。它结合了灵活路由定义的易用性。每个路由包含了基本的URL映射(静态的和动态的部分(变量))并且可以被缺省地初始化,也可以根据不同的要求初始化。
让我们想象一下我们假设的应用程序将需要一些广域内容作者的信息页面。我们想能够把浏览器指向http://domain.com/author/martel去看一个叫"martel"的信息。有这样功能的路由看起来是这样的:
$route = new Zend_Controller_Router_Route( 'author/:username', array( 'controller' => 'profile', 'action' => 'userinfo' ));$router->addRoute('user', $route);
$values = array( 'username' => 'martel', 'controller' => 'profile', 'action' => 'userinfo');
public function userinfoAction(){ $request = $this->getRequest(); $username = $request->getParam('username'); $username = $this->_getParam('username');}
$route = new Zend_Controller_Router_Route( ':module/:controller/:action/*', array('module' => 'default'));$router->addRoute('default', $route);
$route = new Zend_Controller_Router_Route( 'archive/:year', array('year' => 2006));$router->addRoute('archive', $route);
$route = new Zend_Controller_Router_Route( 'archive/:year', array( 'year' => 2006, 'controller' => 'archive', 'action' => 'show' ));$router->addRoute('archive', $route);
$route = new Zend_Controller_Router_Route( 'archive/:year', array( 'year' => 2006, 'controller' => 'archive', 'action' => 'show' ), array('year' => 'd+'));$router->addRoute('archive', $route);
$route = new Zend_Controller_Router_Route( array( 'host' => 'blog.mysite.com', 'path' => 'archive' ), array( 'module' => 'blog', 'controller' => 'archive', 'action' => 'index' ));$router->addRoute('archive', $route);
$route = new Zend_Controller_Router_Route( array( 'host' => array( 'regex' => '([a-z]+).mysite.com', 'reverse' => '%s.mysite.com' 'params' => array( 1 => 'username' ) ), 'path' => '' ), array( 'module' => 'users', 'controller' => 'profile', 'action' => 'index' ));$router->addRoute('profile', $route);
$route = new Zend_Controller_Router_Route_Static( 'login', array('controller' => 'auth', 'action' => 'login'));$router->addRoute('login', $route);
$route = new Zend_Controller_Router_Route_Regex( 'archive/(d+)', array( 'controller' => 'archive', 'action' => 'show' ));$router->addRoute('archive', $route);
$values = array( 1 => '2006', 'controller' => 'archive', 'action' => 'show');
public function showAction(){ $request = $this->getRequest(); $year = $request->getParam(1); // $year = '2006';}
$route = new Zend_Controller_Router_Route_Regex( 'archive(?:/(d+))?', array( 1 => '2006', 'controller' => 'archive', 'action' => 'show' ));$router->addRoute('archive', $route);
$route = new Zend_Controller_Router_Route_Regex( 'archive/(d+)', array( 'controller' => 'archive', 'action' => 'show' ), array( 1 => 'year' ));$router->addRoute('archive', $route);
$values = array( 'year' => '2006', 'controller' => 'archive', 'action' => 'show');
$route = new Zend_Controller_Router_Route_Regex( 'archive/(d+)', array( ... ), array(1 => 'year'));// OR$route = new Zend_Controller_Router_Route_Regex( 'archive/(d+)', array( ... ), array('year' => 1));
$route = new Zend_Controller_Router_Route_Regex( 'archive/(d+)/page/(d+)', array( ... ), array('year' => 1));
$values = array( 'year' => '2006', 2 => 10, 'controller' => 'archive', 'action' => 'show');
$route = new Zend_Controller_Router_Route_Regex( 'archive/(d+)', array( ... ), array('year' => 1), 'archive/%s');
$route = new Zend_Controller_Router_Route_Regex( 'blog/archive/(d+)-(.+).html', array( 'controller' => 'blog', 'action' => 'view' ), array( 1 => 'id', 2 => 'description' ), 'blog/archive/%d-%s.html');$router->addRoute('blogArchive', $route);
[production]routes.archive.route = "archive/:year/*"routes.archive.defaults.controller = archiveroutes.archive.defaults.action = showroutes.archive.defaults.year = 2000routes.archive.reqs.year = "d+"routes.news.type = "Zend_Controller_Router_Route_Static"routes.news.route = "news"routes.news.defaults.controller = "news"routes.news.defaults.action = "list"routes.archive.type = "Zend_Controller_Router_Route_Regex"routes.archive.route = "archive/(d+)"routes.archive.defaults.controller = "archive"routes.archive.defaults.action = "show"routes.archive.map.1 = "year"; OR: routes.archive.map.year = 1
$config = new Zend_Config_Ini('/path/to/config.ini', 'production');$router = new Zend_Controller_Router_Rewrite();$router->addConfig($config, 'routes');
interface Zend_Controller_Router_Interface{ /** * @param Zend_Controller_Request_Abstract $request * @throws Zend_Controller_Router_Exception * @return Zend_Controller_Request_Abstract */ public function route(Zend_Controller_Request_Abstract $request);}
下一篇 总结网站优化中的几个处理方式