File indexing completed on 2024-12-22 05:36:58

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Rest
0017  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0019  * @version    $Id$
0020  */
0021 
0022 /**
0023  * @see Zend_Controller_Router_Route_Interface
0024  */
0025 // require_once 'Zend/Controller/Router/Route/Interface.php';
0026 
0027 /**
0028  * @see Zend_Controller_Router_Route_Module
0029  */
0030 // require_once 'Zend/Controller/Router/Route/Module.php';
0031 
0032 /**
0033  * @see Zend_Controller_Dispatcher_Interface
0034  */
0035 // require_once 'Zend/Controller/Dispatcher/Interface.php';
0036 
0037 /**
0038  * @see Zend_Controller_Request_Abstract
0039  */
0040 // require_once 'Zend/Controller/Request/Abstract.php';
0041 
0042 /**
0043  * Rest Route
0044  *
0045  * Request-aware route for RESTful modular routing
0046  *
0047  * @category   Zend
0048  * @package    Zend_Rest
0049  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0050  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0051  */
0052 class Zend_Rest_Route extends Zend_Controller_Router_Route_Module
0053 {
0054     /**
0055      * Specific Modules to receive RESTful routes
0056      * @var array
0057      */
0058     protected $_restfulModules = null;
0059 
0060     /**
0061      * Specific Modules=>Controllers to receive RESTful routes
0062      * @var array
0063      */
0064     protected $_restfulControllers = null;
0065 
0066     /**
0067      * @var Zend_Controller_Front
0068      */
0069     protected $_front;
0070 
0071     /**
0072      * Constructor
0073      *
0074      * @param Zend_Controller_Front $front Front Controller object
0075      * @param array $defaults Defaults for map variables with keys as variable names
0076      * @param array $responders Modules or controllers to receive RESTful routes
0077      */
0078     public function __construct(Zend_Controller_Front $front,
0079         array $defaults = array(),
0080         array $responders = array()
0081     ) {
0082         $this->_defaults = $defaults;
0083 
0084         if ($responders) {
0085             $this->_parseResponders($responders);
0086         }
0087 
0088         $this->_front      = $front;
0089         $this->_dispatcher = $front->getDispatcher();
0090     }
0091 
0092     /**
0093      * Instantiates route based on passed Zend_Config structure
0094      */
0095     public static function getInstance(Zend_Config $config)
0096     {
0097         $frontController = Zend_Controller_Front::getInstance();
0098         $defaultsArray = array();
0099         $restfulConfigArray = array();
0100         foreach ($config as $key => $values) {
0101             if ($key == 'type') {
0102                 // do nothing
0103             } elseif ($key == 'defaults') {
0104                 $defaultsArray = $values->toArray();
0105             } else {
0106                 $restfulConfigArray[$key] = explode(',', $values);
0107             }
0108         }
0109         $instance = new self($frontController, $defaultsArray, $restfulConfigArray);
0110         return $instance;
0111     }
0112 
0113     /**
0114      * Matches a user submitted request. Assigns and returns an array of variables
0115      * on a successful match.
0116      *
0117      * If a request object is registered, it uses its setModuleName(),
0118      * setControllerName(), and setActionName() accessors to set those values.
0119      * Always returns the values as an array.
0120      *
0121      * @param Zend_Controller_Request_Http $request Request used to match against this routing ruleset
0122      * @return array An array of assigned values or a false on a mismatch
0123      */
0124     public function match($request, $partial = false)
0125     {
0126         if (!$request instanceof Zend_Controller_Request_Http) {
0127             $request = $this->_front->getRequest();
0128         }
0129         $this->_request = $request;
0130         $this->_setRequestKeys();
0131 
0132         $path   = $request->getPathInfo();
0133         $params = $request->getParams();
0134         $values = array();
0135         $path   = trim($path, self::URI_DELIMITER);
0136 
0137         if ($path != '') {
0138 
0139             $path = explode(self::URI_DELIMITER, $path);
0140             // Determine Module
0141             $moduleName = $this->_defaults[$this->_moduleKey];
0142             $dispatcher = $this->_front->getDispatcher();
0143             if ($dispatcher && $dispatcher->isValidModule($path[0])) {
0144                 $moduleName = $path[0];
0145                 if ($this->_checkRestfulModule($moduleName)) {
0146                     $values[$this->_moduleKey] = array_shift($path);
0147                     $this->_moduleValid = true;
0148                 }
0149             }
0150 
0151             // Determine Controller
0152             $controllerName = $this->_defaults[$this->_controllerKey];
0153             if (count($path) && !empty($path[0])) {
0154                 if ($this->_checkRestfulController($moduleName, $path[0])) {
0155                     $controllerName = $path[0];
0156                     $values[$this->_controllerKey] = array_shift($path);
0157                     $values[$this->_actionKey] = 'get';
0158                 } else {
0159                     // If Controller in URI is not found to be a RESTful
0160                     // Controller, return false to fall back to other routes
0161                     return false;
0162                 }
0163             } elseif ($this->_checkRestfulController($moduleName, $controllerName)) {
0164                 $values[$this->_controllerKey] = $controllerName;
0165                 $values[$this->_actionKey] = 'get';
0166             } else {
0167                 return false;
0168             }
0169 
0170             //Store path count for method mapping
0171             $pathElementCount = count($path);
0172 
0173             // Check for "special get" URI's
0174             $specialGetTarget = false;
0175             if ($pathElementCount && array_search($path[0], array('index', 'new')) > -1) {
0176                 $specialGetTarget = array_shift($path);
0177             } elseif ($pathElementCount && $path[$pathElementCount-1] == 'edit') {
0178                 $specialGetTarget = 'edit';
0179                 $params['id'] = urldecode($path[$pathElementCount-2]);
0180             } elseif ($pathElementCount == 1) {
0181                 $params['id'] = urldecode(array_shift($path));
0182             } elseif ($pathElementCount == 0 && !isset($params['id'])) {
0183                 $specialGetTarget = 'index';
0184             }
0185 
0186             // Digest URI params
0187             if ($numSegs = count($path)) {
0188                 for ($i = 0; $i < $numSegs; $i = $i + 2) {
0189                     $key = urldecode($path[$i]);
0190                     $val = isset($path[$i + 1]) ? $path[$i + 1] : null;
0191                     $params[$key] = urldecode($val);
0192                 }
0193             }
0194 
0195             // Determine Action
0196             $requestMethod = strtolower($request->getMethod());
0197             if ($requestMethod != 'get') {
0198                 if ($request->getParam('_method')) {
0199                     $values[$this->_actionKey] = strtolower($request->getParam('_method'));
0200                 } elseif ( $request->getHeader('X-HTTP-Method-Override') ) {
0201                     $values[$this->_actionKey] = strtolower($request->getHeader('X-HTTP-Method-Override'));
0202                 } else {
0203                     $values[$this->_actionKey] = $requestMethod;
0204                 }
0205 
0206                 // Map PUT and POST to actual create/update actions
0207                 // based on parameter count (posting to resource or collection)
0208                 switch( $values[$this->_actionKey] ){
0209                     case 'post':
0210                         if ($pathElementCount > 0) {
0211                             $values[$this->_actionKey] = 'put';
0212                         } else {
0213                             $values[$this->_actionKey] = 'post';
0214                         }
0215                         break;
0216                     case 'put':
0217                         $values[$this->_actionKey] = 'put';
0218                         break;
0219                 }
0220 
0221             } elseif ($specialGetTarget) {
0222                 $values[$this->_actionKey] = $specialGetTarget;
0223             }
0224 
0225         }
0226         $this->_values = $values + $params;
0227 
0228         $result = $this->_values + $this->_defaults;
0229 
0230         if ($partial && $result)
0231             $this->setMatchedPath($request->getPathInfo());
0232 
0233         return $result;
0234     }
0235 
0236     /**
0237      * Assembles user submitted parameters forming a URL path defined by this route
0238      *
0239      * @param array $data An array of variable and value pairs used as parameters
0240      * @param bool $reset Weither to reset the current params
0241      * @param bool $encode Weither to return urlencoded string
0242      * @return string Route path with user submitted parameters
0243      */
0244     public function assemble($data = array(), $reset = false, $encode = true)
0245     {
0246         if (!$this->_keysSet) {
0247             if (null === $this->_request) {
0248                 $this->_request = $this->_front->getRequest();
0249             }
0250             $this->_setRequestKeys();
0251         }
0252 
0253         $params = (!$reset) ? $this->_values : array();
0254 
0255         foreach ($data as $key => $value) {
0256             if ($value !== null) {
0257                 $params[$key] = $value;
0258             } elseif (isset($params[$key])) {
0259                 unset($params[$key]);
0260             }
0261         }
0262 
0263         $params += $this->_defaults;
0264 
0265         $url = '';
0266 
0267         if ($this->_moduleValid || array_key_exists($this->_moduleKey, $data)) {
0268             if ($params[$this->_moduleKey] != $this->_defaults[$this->_moduleKey]) {
0269                 $module = $params[$this->_moduleKey];
0270             }
0271         }
0272         unset($params[$this->_moduleKey]);
0273 
0274         $controller = $params[$this->_controllerKey];
0275         unset($params[$this->_controllerKey]);
0276 
0277         // set $action if value given is 'new' or 'edit'
0278         if (in_array($params[$this->_actionKey], array('new', 'edit'))) {
0279             $action = $params[$this->_actionKey];
0280         }
0281         unset($params[$this->_actionKey]);
0282 
0283         if (isset($params['index']) && $params['index']) {
0284             unset($params['index']);
0285             $url .= '/index';
0286             if (isset($params['id'])) {
0287                 $url .= '/'.$params['id'];
0288                 unset($params['id']);
0289             }
0290             foreach ($params as $key => $value) {
0291                 if ($encode) $value = urlencode($value);
0292                 $url .= '/' . $key . '/' . $value;
0293             }
0294         } elseif (! empty($action) && isset($params['id'])) {
0295             $url .= sprintf('/%s/%s', $params['id'], $action);
0296         } elseif (! empty($action)) {
0297             $url .= sprintf('/%s', $action);
0298         } elseif (isset($params['id'])) {
0299             $url .= '/' . $params['id'];
0300         }
0301 
0302         if (!empty($url) || $controller !== $this->_defaults[$this->_controllerKey]) {
0303             $url = '/' . $controller . $url;
0304         }
0305 
0306         if (isset($module)) {
0307             $url = '/' . $module . $url;
0308         }
0309 
0310         return ltrim($url, self::URI_DELIMITER);
0311     }
0312 
0313     /**
0314      * Tells Rewrite Router which version this Route is
0315      *
0316      * @return int Route "version"
0317      */
0318     public function getVersion()
0319     {
0320         return 2;
0321     }
0322 
0323     /**
0324      * Parses the responders array sent to constructor to know
0325      * which modules and/or controllers are RESTful
0326      *
0327      * @param array $responders
0328      */
0329     protected function _parseResponders($responders)
0330     {
0331         $modulesOnly = true;
0332         foreach ($responders as $responder) {
0333             if(is_array($responder)) {
0334                 $modulesOnly = false;
0335                 break;
0336             }
0337         }
0338         if ($modulesOnly) {
0339             $this->_restfulModules = $responders;
0340         } else {
0341             $this->_restfulControllers = $responders;
0342         }
0343     }
0344 
0345     /**
0346      * Determine if a specified module supports RESTful routing
0347      *
0348      * @param string $moduleName
0349      * @return bool
0350      */
0351     protected function _checkRestfulModule($moduleName)
0352     {
0353         if ($this->_allRestful()) {
0354             return true;
0355         }
0356         if ($this->_fullRestfulModule($moduleName)) {
0357             return true;
0358         }
0359         if ($this->_restfulControllers && array_key_exists($moduleName, $this->_restfulControllers)) {
0360             return true;
0361         }
0362         return false;
0363     }
0364 
0365     /**
0366      * Determine if a specified module + controller combination supports
0367      * RESTful routing
0368      *
0369      * @param string $moduleName
0370      * @param string $controllerName
0371      * @return bool
0372      */
0373     protected function _checkRestfulController($moduleName, $controllerName)
0374     {
0375         if ($this->_allRestful()) {
0376             return true;
0377         }
0378         if ($this->_fullRestfulModule($moduleName)) {
0379             return true;
0380         }
0381         if ($this->_checkRestfulModule($moduleName)
0382             && $this->_restfulControllers
0383             && (false !== array_search($controllerName, $this->_restfulControllers[$moduleName]))
0384         ) {
0385             return true;
0386         }
0387         return false;
0388     }
0389 
0390     /**
0391      * Determines if RESTful routing applies to the entire app
0392      *
0393      * @return bool
0394      */
0395     protected function _allRestful()
0396     {
0397         return (!$this->_restfulModules && !$this->_restfulControllers);
0398     }
0399 
0400     /**
0401      * Determines if RESTful routing applies to an entire module
0402      *
0403      * @param string $moduleName
0404      * @return bool
0405      */
0406     protected function _fullRestfulModule($moduleName)
0407     {
0408         return (
0409             $this->_restfulModules
0410             && (false !==array_search($moduleName, $this->_restfulModules))
0411         );
0412     }
0413 }