|
1 <?php |
|
2 /** |
|
3 * Zend Framework |
|
4 * |
|
5 * LICENSE |
|
6 * |
|
7 * This source file is subject to the new BSD license that is bundled |
|
8 * with this package in the file LICENSE.txt. |
|
9 * It is also available through the world-wide-web at this URL: |
|
10 * http://framework.zend.com/license/new-bsd |
|
11 * If you did not receive a copy of the license and are unable to |
|
12 * obtain it through the world-wide-web, please send an email |
|
13 * to license@zend.com so we can send you a copy immediately. |
|
14 * |
|
15 * @category Zend |
|
16 * @package Zend_Rest |
|
17 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
18 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
19 * @version $Id: Route.php 23421 2010-11-21 10:03:53Z wilmoore $ |
|
20 */ |
|
21 |
|
22 /** |
|
23 * @see Zend_Controller_Router_Route_Interface |
|
24 */ |
|
25 require_once 'Zend/Controller/Router/Route/Interface.php'; |
|
26 |
|
27 /** |
|
28 * @see Zend_Controller_Router_Route_Module |
|
29 */ |
|
30 require_once 'Zend/Controller/Router/Route/Module.php'; |
|
31 |
|
32 /** |
|
33 * @see Zend_Controller_Dispatcher_Interface |
|
34 */ |
|
35 require_once 'Zend/Controller/Dispatcher/Interface.php'; |
|
36 |
|
37 /** |
|
38 * @see Zend_Controller_Request_Abstract |
|
39 */ |
|
40 require_once 'Zend/Controller/Request/Abstract.php'; |
|
41 |
|
42 /** |
|
43 * Rest Route |
|
44 * |
|
45 * Request-aware route for RESTful modular routing |
|
46 * |
|
47 * @category Zend |
|
48 * @package Zend_Rest |
|
49 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
50 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
51 */ |
|
52 class Zend_Rest_Route extends Zend_Controller_Router_Route_Module |
|
53 { |
|
54 /** |
|
55 * Specific Modules to receive RESTful routes |
|
56 * @var array |
|
57 */ |
|
58 protected $_restfulModules = null; |
|
59 |
|
60 /** |
|
61 * Specific Modules=>Controllers to receive RESTful routes |
|
62 * @var array |
|
63 */ |
|
64 protected $_restfulControllers = null; |
|
65 |
|
66 /** |
|
67 * @var Zend_Controller_Front |
|
68 */ |
|
69 protected $_front; |
|
70 |
|
71 /** |
|
72 * Constructor |
|
73 * |
|
74 * @param Zend_Controller_Front $front Front Controller object |
|
75 * @param array $defaults Defaults for map variables with keys as variable names |
|
76 * @param array $responders Modules or controllers to receive RESTful routes |
|
77 */ |
|
78 public function __construct(Zend_Controller_Front $front, |
|
79 array $defaults = array(), |
|
80 array $responders = array() |
|
81 ) { |
|
82 $this->_defaults = $defaults; |
|
83 |
|
84 if ($responders) { |
|
85 $this->_parseResponders($responders); |
|
86 } |
|
87 |
|
88 $this->_front = $front; |
|
89 $this->_dispatcher = $front->getDispatcher(); |
|
90 } |
|
91 |
|
92 /** |
|
93 * Instantiates route based on passed Zend_Config structure |
|
94 */ |
|
95 public static function getInstance(Zend_Config $config) |
|
96 { |
|
97 $frontController = Zend_Controller_Front::getInstance(); |
|
98 $defaultsArray = array(); |
|
99 $restfulConfigArray = array(); |
|
100 foreach ($config as $key => $values) { |
|
101 if ($key == 'type') { |
|
102 // do nothing |
|
103 } elseif ($key == 'defaults') { |
|
104 $defaultsArray = $values->toArray(); |
|
105 } else { |
|
106 $restfulConfigArray[$key] = explode(',', $values); |
|
107 } |
|
108 } |
|
109 $instance = new self($frontController, $defaultsArray, $restfulConfigArray); |
|
110 return $instance; |
|
111 } |
|
112 |
|
113 /** |
|
114 * Matches a user submitted request. Assigns and returns an array of variables |
|
115 * on a successful match. |
|
116 * |
|
117 * If a request object is registered, it uses its setModuleName(), |
|
118 * setControllerName(), and setActionName() accessors to set those values. |
|
119 * Always returns the values as an array. |
|
120 * |
|
121 * @param Zend_Controller_Request_Http $request Request used to match against this routing ruleset |
|
122 * @return array An array of assigned values or a false on a mismatch |
|
123 */ |
|
124 public function match($request, $partial = false) |
|
125 { |
|
126 if (!$request instanceof Zend_Controller_Request_Http) { |
|
127 $request = $this->_front->getRequest(); |
|
128 } |
|
129 $this->_request = $request; |
|
130 $this->_setRequestKeys(); |
|
131 |
|
132 $path = $request->getPathInfo(); |
|
133 $params = $request->getParams(); |
|
134 $values = array(); |
|
135 $path = trim($path, self::URI_DELIMITER); |
|
136 |
|
137 if ($path != '') { |
|
138 |
|
139 $path = explode(self::URI_DELIMITER, $path); |
|
140 // Determine Module |
|
141 $moduleName = $this->_defaults[$this->_moduleKey]; |
|
142 $dispatcher = $this->_front->getDispatcher(); |
|
143 if ($dispatcher && $dispatcher->isValidModule($path[0])) { |
|
144 $moduleName = $path[0]; |
|
145 if ($this->_checkRestfulModule($moduleName)) { |
|
146 $values[$this->_moduleKey] = array_shift($path); |
|
147 $this->_moduleValid = true; |
|
148 } |
|
149 } |
|
150 |
|
151 // Determine Controller |
|
152 $controllerName = $this->_defaults[$this->_controllerKey]; |
|
153 if (count($path) && !empty($path[0])) { |
|
154 if ($this->_checkRestfulController($moduleName, $path[0])) { |
|
155 $controllerName = $path[0]; |
|
156 $values[$this->_controllerKey] = array_shift($path); |
|
157 $values[$this->_actionKey] = 'get'; |
|
158 } else { |
|
159 // If Controller in URI is not found to be a RESTful |
|
160 // Controller, return false to fall back to other routes |
|
161 return false; |
|
162 } |
|
163 } elseif ($this->_checkRestfulController($moduleName, $controllerName)) { |
|
164 $values[$this->_controllerKey] = $controllerName; |
|
165 $values[$this->_actionKey] = 'get'; |
|
166 } else { |
|
167 return false; |
|
168 } |
|
169 |
|
170 //Store path count for method mapping |
|
171 $pathElementCount = count($path); |
|
172 |
|
173 // Check for "special get" URI's |
|
174 $specialGetTarget = false; |
|
175 if ($pathElementCount && array_search($path[0], array('index', 'new')) > -1) { |
|
176 $specialGetTarget = array_shift($path); |
|
177 } elseif ($pathElementCount && $path[$pathElementCount-1] == 'edit') { |
|
178 $specialGetTarget = 'edit'; |
|
179 $params['id'] = $path[$pathElementCount-2]; |
|
180 } elseif ($pathElementCount == 1) { |
|
181 $params['id'] = urldecode(array_shift($path)); |
|
182 } elseif ($pathElementCount == 0 && !isset($params['id'])) { |
|
183 $specialGetTarget = 'index'; |
|
184 } |
|
185 |
|
186 // Digest URI params |
|
187 if ($numSegs = count($path)) { |
|
188 for ($i = 0; $i < $numSegs; $i = $i + 2) { |
|
189 $key = urldecode($path[$i]); |
|
190 $val = isset($path[$i + 1]) ? urldecode($path[$i + 1]) : null; |
|
191 $params[$key] = $val; |
|
192 } |
|
193 } |
|
194 |
|
195 // Determine Action |
|
196 $requestMethod = strtolower($request->getMethod()); |
|
197 if ($requestMethod != 'get') { |
|
198 if ($request->getParam('_method')) { |
|
199 $values[$this->_actionKey] = strtolower($request->getParam('_method')); |
|
200 } elseif ( $request->getHeader('X-HTTP-Method-Override') ) { |
|
201 $values[$this->_actionKey] = strtolower($request->getHeader('X-HTTP-Method-Override')); |
|
202 } else { |
|
203 $values[$this->_actionKey] = $requestMethod; |
|
204 } |
|
205 |
|
206 // Map PUT and POST to actual create/update actions |
|
207 // based on parameter count (posting to resource or collection) |
|
208 switch( $values[$this->_actionKey] ){ |
|
209 case 'post': |
|
210 if ($pathElementCount > 0) { |
|
211 $values[$this->_actionKey] = 'put'; |
|
212 } else { |
|
213 $values[$this->_actionKey] = 'post'; |
|
214 } |
|
215 break; |
|
216 case 'put': |
|
217 $values[$this->_actionKey] = 'put'; |
|
218 break; |
|
219 } |
|
220 |
|
221 } elseif ($specialGetTarget) { |
|
222 $values[$this->_actionKey] = $specialGetTarget; |
|
223 } |
|
224 |
|
225 } |
|
226 $this->_values = $values + $params; |
|
227 |
|
228 $result = $this->_values + $this->_defaults; |
|
229 |
|
230 if ($partial && $result) |
|
231 $this->setMatchedPath($request->getPathInfo()); |
|
232 |
|
233 return $result; |
|
234 } |
|
235 |
|
236 /** |
|
237 * Assembles user submitted parameters forming a URL path defined by this route |
|
238 * |
|
239 * @param array $data An array of variable and value pairs used as parameters |
|
240 * @param bool $reset Weither to reset the current params |
|
241 * @param bool $encode Weither to return urlencoded string |
|
242 * @return string Route path with user submitted parameters |
|
243 */ |
|
244 public function assemble($data = array(), $reset = false, $encode = true) |
|
245 { |
|
246 if (!$this->_keysSet) { |
|
247 if (null === $this->_request) { |
|
248 $this->_request = $this->_front->getRequest(); |
|
249 } |
|
250 $this->_setRequestKeys(); |
|
251 } |
|
252 |
|
253 $params = (!$reset) ? $this->_values : array(); |
|
254 |
|
255 foreach ($data as $key => $value) { |
|
256 if ($value !== null) { |
|
257 $params[$key] = $value; |
|
258 } elseif (isset($params[$key])) { |
|
259 unset($params[$key]); |
|
260 } |
|
261 } |
|
262 |
|
263 $params += $this->_defaults; |
|
264 |
|
265 $url = ''; |
|
266 |
|
267 if ($this->_moduleValid || array_key_exists($this->_moduleKey, $data)) { |
|
268 if ($params[$this->_moduleKey] != $this->_defaults[$this->_moduleKey]) { |
|
269 $module = $params[$this->_moduleKey]; |
|
270 } |
|
271 } |
|
272 unset($params[$this->_moduleKey]); |
|
273 |
|
274 $controller = $params[$this->_controllerKey]; |
|
275 unset($params[$this->_controllerKey]); |
|
276 |
|
277 // set $action if value given is 'new' or 'edit' |
|
278 if (in_array($params[$this->_actionKey], array('new', 'edit'))) { |
|
279 $action = $params[$this->_actionKey]; |
|
280 } |
|
281 unset($params[$this->_actionKey]); |
|
282 |
|
283 if (isset($params['index']) && $params['index']) { |
|
284 unset($params['index']); |
|
285 $url .= '/index'; |
|
286 if (isset($params['id'])) { |
|
287 $url .= '/'.$params['id']; |
|
288 unset($params['id']); |
|
289 } |
|
290 foreach ($params as $key => $value) { |
|
291 if ($encode) $value = urlencode($value); |
|
292 $url .= '/' . $key . '/' . $value; |
|
293 } |
|
294 } elseif (! empty($action) && isset($params['id'])) { |
|
295 $url .= sprintf('/%s/%s', $params['id'], $action); |
|
296 } elseif (! empty($action)) { |
|
297 $url .= sprintf('/%s', $action); |
|
298 } elseif (isset($params['id'])) { |
|
299 $url .= '/' . $params['id']; |
|
300 } |
|
301 |
|
302 if (!empty($url) || $controller !== $this->_defaults[$this->_controllerKey]) { |
|
303 $url = '/' . $controller . $url; |
|
304 } |
|
305 |
|
306 if (isset($module)) { |
|
307 $url = '/' . $module . $url; |
|
308 } |
|
309 |
|
310 return ltrim($url, self::URI_DELIMITER); |
|
311 } |
|
312 |
|
313 /** |
|
314 * Tells Rewrite Router which version this Route is |
|
315 * |
|
316 * @return int Route "version" |
|
317 */ |
|
318 public function getVersion() |
|
319 { |
|
320 return 2; |
|
321 } |
|
322 |
|
323 /** |
|
324 * Parses the responders array sent to constructor to know |
|
325 * which modules and/or controllers are RESTful |
|
326 * |
|
327 * @param array $responders |
|
328 */ |
|
329 protected function _parseResponders($responders) |
|
330 { |
|
331 $modulesOnly = true; |
|
332 foreach ($responders as $responder) { |
|
333 if(is_array($responder)) { |
|
334 $modulesOnly = false; |
|
335 break; |
|
336 } |
|
337 } |
|
338 if ($modulesOnly) { |
|
339 $this->_restfulModules = $responders; |
|
340 } else { |
|
341 $this->_restfulControllers = $responders; |
|
342 } |
|
343 } |
|
344 |
|
345 /** |
|
346 * Determine if a specified module supports RESTful routing |
|
347 * |
|
348 * @param string $moduleName |
|
349 * @return bool |
|
350 */ |
|
351 protected function _checkRestfulModule($moduleName) |
|
352 { |
|
353 if ($this->_allRestful()) { |
|
354 return true; |
|
355 } |
|
356 if ($this->_fullRestfulModule($moduleName)) { |
|
357 return true; |
|
358 } |
|
359 if ($this->_restfulControllers && array_key_exists($moduleName, $this->_restfulControllers)) { |
|
360 return true; |
|
361 } |
|
362 return false; |
|
363 } |
|
364 |
|
365 /** |
|
366 * Determine if a specified module + controller combination supports |
|
367 * RESTful routing |
|
368 * |
|
369 * @param string $moduleName |
|
370 * @param string $controllerName |
|
371 * @return bool |
|
372 */ |
|
373 protected function _checkRestfulController($moduleName, $controllerName) |
|
374 { |
|
375 if ($this->_allRestful()) { |
|
376 return true; |
|
377 } |
|
378 if ($this->_fullRestfulModule($moduleName)) { |
|
379 return true; |
|
380 } |
|
381 if ($this->_checkRestfulModule($moduleName) |
|
382 && $this->_restfulControllers |
|
383 && (false !== array_search($controllerName, $this->_restfulControllers[$moduleName])) |
|
384 ) { |
|
385 return true; |
|
386 } |
|
387 return false; |
|
388 } |
|
389 |
|
390 /** |
|
391 * Determines if RESTful routing applies to the entire app |
|
392 * |
|
393 * @return bool |
|
394 */ |
|
395 protected function _allRestful() |
|
396 { |
|
397 return (!$this->_restfulModules && !$this->_restfulControllers); |
|
398 } |
|
399 |
|
400 /** |
|
401 * Determines if RESTful routing applies to an entire module |
|
402 * |
|
403 * @param string $moduleName |
|
404 * @return bool |
|
405 */ |
|
406 protected function _fullRestfulModule($moduleName) |
|
407 { |
|
408 return ( |
|
409 $this->_restfulModules |
|
410 && (false !==array_search($moduleName, $this->_restfulModules)) |
|
411 ); |
|
412 } |
|
413 } |