Magento路由分发过程解析(四):请求重写(转)
Magento请求重写的目的是在路由对象迭代之前,更改请求对象的请求信息。本质上来说,Magento请求重写允许你更改路由对象需求的路径信息,这就意味着,你可以使用重写系统将一个请求从原本的地址(404页面)重新定位到另外一个地址。
请求重写的类型
Magento请求重写系统包含两个部分。第一部分是基于数据库的路由重写规则,通过core/url_rewrite模型类调用。第二部分通过添加在config.xml配置文件中的一系列重写规则完成的。
当然,这两种不同的请求重写内容,拥有一致的目的,只是实现的方式不同而已。
基于模型(数据库)的重写
基于模型的请求重写过程的第一步就是要确定该请求是否需要被重写。这一步通过实例化core/url_rewrite模型,并使用请求对象的路径信息,作为模型中的request_path属性,进行读取。下面主要来分析请求重写模型类的rewrite()方法。略过糟粕,直击重点。
01
02
03
04
05
06
07
08
09
10
11
12
13
|
$requestCases = array (); $pathInfo = $request ->getPathInfo(); $origSlash = ( substr ( $pathInfo , -1) == '/' ) ? '/' : '' ; $requestPath = trim( $pathInfo , '/' ); $altSlash = $origSlash ? '' : '/' ; // If there were final slash - add nothing to less priority paths. And vice versa. $queryString = $this ->_getQueryString(); // Query params in request, matching "path + query" has more priority if ( $queryString ) { $requestCases [] = $requestPath . $origSlash . '?' . $queryString ; $requestCases [] = $requestPath . $altSlash . '?' . $queryString ; } $requestCases [] = $requestPath . $origSlash ; $requestCases [] = $requestPath . $altSlash ; |
这一部分实际上是获取对象中的路径信息,然后根据url地址最后是否包含’/'以及?参数,将该请求的路径信息转换为两种或四种url变体格式,以适应用户在输入url的时候的各种可能性。$requestCases数组的最终值可能如下,
sony-vaio-vgn-txn27n-b-11-1-notebook-pc.html/
sony-vaio-vgn-txn27n-b-11-1-notebook-pc.html
sony-vaio-vgn-txn27n-b-11-1-notebook-pc.html/?thisparams=1
sony-vaio-vgn-txn27n-b-11-1-notebook-pc.html?thisparams=1
PS:上面这段代码实际上算是Magento比较容易理解的。但是从编程的角度来说,上面判断并保存四种url的代码可谓经典,特别是判断结尾是否包含‘/’的那段。
在基于数据库的重写规则来看,上述的四种url地址实际上的指向是一致的。也就是说用户可能会输入它们中的任何一个到浏览器中进行访问,而店铺管理员也可能设置它们中的任意一个。所以这里需要对请求路径进行一些处理,然后在继续寻找可能的匹配。当获取到$requestCases数组之后,系统尝试使用该值读取数据库。下面一段我们先跳出rewrite()方法,看下loadByRequestPath()方法。
01
02
03
04
05
06
07
08
09
|
public function loadByRequestPath( $path ) { $this ->setId(null); $this ->_getResource()->loadByRequestPath( $this , $path ); $this ->_afterLoad(); $this ->setOrigData(); $this ->_hasDataChanges = false; return $this ; } |
可以看到,该方法内部调用了资源模型,并通过资源模型内部的同名方法在数据库中进行匹配。那我们下面着重来看重写规则的资源模型类下的loadByRequestPath()方法。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
public function loadByRequestPath(Mage_Core_Model_Url_Rewrite $object , $path ) { if (! is_array ( $path )) { $path = array ( $path ); } $pathBind = array (); foreach ( $path as $key => $url ) { $pathBind [ 'path' . $key ] = $url ; } // Form select $read = $this ->_getReadAdapter(); $select = $read ->select() ->from( $this ->getMainTable()) ->where( $this ->getMainTable() . '.request_path IN (:' . implode( ', :' , array_flip ( $pathBind )) . ')' ) ->where( 'store_id IN(?)' , array (0, (int) $object ->getStoreId())); $items = $read ->fetchAll( $select , $pathBind ); |
该方法是请求重写模型的模型资源类,很简单,大概行程如下的查询语句,
01
02
03
04
05
06
|
SELECT `core_url_rewrite`.* FROM `core_url_rewrite` WHERE (request_path IN (:path0, :path1)) AND (store_id IN (0, 1)) |
上面的$pathBind数组通过请求重写模型传递来的$path参数构建,包含了url的可能的两种或四种变体。该数组根据select语句需求的格式,重新构建了,包含键(path0)及值(请求路径变体)的$pathBind数组。然后在select语句中,通过implode()及array_flip()函数构建查询语句需求的”:path0,:path1″格式。
另外,注意到上述select语句中对于store_id的判断,0肯定是管理员界面,这里按时请求重写也能够应用于管理员界面。1,肯定就是当前的店铺ID了,因为Magento允许为不同商铺的不同地址设置重写规则。
读取正确的重写信息
多个店铺ID以及多个重写路径变体导致了一个问题。查询语句获取到的$items可能包含最多四行记录。也就是说,获取该结果之后,我们还需要确定哪一个$item是正确的。
Mangento通过一系列复杂的逻辑,实现了一个优先级系统,并通过该算法最终决定哪个一$item是最匹配的。该部分代码如下,
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
// Go through all found records and choose one with lowest penalty - earlier path in array, concrete store $mapPenalty = array_flip ( array_values ( $path )); // we got mapping array(path => index), lower index - better $currentPenalty = null; $foundItem = null; foreach ( $items as $item ) { $penalty = $mapPenalty [ $item [ 'request_path' ]] << 1 + ( $item [ 'store_id' ] ? 0 : 1); if (! $foundItem || $currentPenalty > $penalty ) { $foundItem = $item ; $currentPenalty = $penalty ; if (! $currentPenalty ) { break ; // Found best matching item with zero penalty, no reason to continue } } } |
PS:这部分代码涉及到位运算以及二进制的运算。超出了我的只是范围,略过,但是如果进行匹配已经如何匹配就是发生在这里。
当然,我们还是需要了解该算法是如何实现优先级匹配的。我们回到最初的问题,原始请求路径的格式可能如下所示,
/electronice/cell-phones.html
/electronice/foo/
然后在请求重新模型中,会考虑到url的变体,于是为上面的url分别生成以下变体,
/electronice/cell-phones.html/
/electronice/foo
另外,除了url变体之外,我们还有不同的店铺id,一个真实的店铺id,一个控制台id(0)。这样的话,两两组合,最终可能会有四种返回。即,当读取一个url重写时,Magento可能会按以下顺序返回四个结果,
- A URL in its Natural state, with the Admin Store ID
- A URL in its Natural state, with the Non-Admin Store ID
- A URL in its adulterated state, with the Admin Store ID set
- A URL in its adulterated state, with the Non-Admin Store ID set
好了,基本分析结束,稍等下回到请求重写的rewrite()的方法中去。
应用重写信息
回到rewrite()方法中,我们略过那段对于没有获取到正确重新信息的代码,直接进入下面一段。
01
|
$request ->setAlias(self::REWRITE_REQUEST_PATH_ALIAS, $this ->getRequestPath()); |
一切征程获取到了正确的重写信息之后,接着使用core/url_rewrite模型的request_path属性给请求对象设置一个别名(alias)。
请求对象可以包含无数个别名,成键值对。这些别名告诉请求对象,尽管它拥有一个路径信息,但该路径信息并非是直接从HTTP请求中获取的。
Ghosts of Apache
接着,分析如下代码,
01
02
03
04
05
06
07
08
09
10
11
12
|
$external = substr ( $this ->getTargetPath(), 0, 6); $isPermanentRedirectOption = $this ->hasOption( 'RP' ); if ( $external === 'http:/' || $external === 'https:' ) { if ( $isPermanentRedirectOption ) { header( 'HTTP/1.1 301 Moved Permanently' ); } header( "Location: " . $this ->getTargetPath()); exit ; } else { $targetUrl = $request ->getBaseUrl(). '/' . $this ->getTargetPath(); } //var_dump($this->getTargetPath());exit(); |
如果说,我们获取到的目标路径是一个完整的url地址,包含http://或者https://,Magento系统会立刻进行http转向,而不是通过内部的请求重写。
转载:http://www.ruiwant.com/magento-dispatch-rewrites-advanced.html