Standard.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  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_Controller
  17. * @subpackage Dispatcher
  18. * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id$
  21. */
  22. /** Zend_Loader */
  23. require_once 'Zend/Loader.php';
  24. /** Zend_Controller_Dispatcher_Abstract */
  25. require_once 'Zend/Controller/Dispatcher/Abstract.php';
  26. /**
  27. * @category Zend
  28. * @package Zend_Controller
  29. * @subpackage Dispatcher
  30. * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  31. * @license http://framework.zend.com/license/new-bsd New BSD License
  32. */
  33. class Zend_Controller_Dispatcher_Standard extends Zend_Controller_Dispatcher_Abstract
  34. {
  35. /**
  36. * Current dispatchable directory
  37. * @var string
  38. */
  39. protected $_curDirectory;
  40. /**
  41. * Current module (formatted)
  42. * @var string
  43. */
  44. protected $_curModule;
  45. /**
  46. * Controller directory(ies)
  47. * @var array
  48. */
  49. protected $_controllerDirectory = array();
  50. /**
  51. * Constructor: Set current module to default value
  52. *
  53. * @param array $params
  54. * @return void
  55. */
  56. public function __construct(array $params = array())
  57. {
  58. parent::__construct($params);
  59. $this->_curModule = $this->getDefaultModule();
  60. }
  61. /**
  62. * Add a single path to the controller directory stack
  63. *
  64. * @param string $path
  65. * @param string $module
  66. * @return Zend_Controller_Dispatcher_Standard
  67. */
  68. public function addControllerDirectory($path, $module = null)
  69. {
  70. if (null === $module) {
  71. $module = $this->_defaultModule;
  72. }
  73. $module = (string) $module;
  74. $path = rtrim((string) $path, '/\\');
  75. $this->_controllerDirectory[$module] = $path;
  76. return $this;
  77. }
  78. /**
  79. * Set controller directory
  80. *
  81. * @param array|string $directory
  82. * @return Zend_Controller_Dispatcher_Standard
  83. */
  84. public function setControllerDirectory($directory, $module = null)
  85. {
  86. $this->_controllerDirectory = array();
  87. if (is_string($directory)) {
  88. $this->addControllerDirectory($directory, $module);
  89. } elseif (is_array($directory)) {
  90. foreach ((array) $directory as $module => $path) {
  91. $this->addControllerDirectory($path, $module);
  92. }
  93. } else {
  94. require_once 'Zend/Controller/Exception.php';
  95. throw new Zend_Controller_Exception('Controller directory spec must be either a string or an array');
  96. }
  97. return $this;
  98. }
  99. /**
  100. * Return the currently set directories for Zend_Controller_Action class
  101. * lookup
  102. *
  103. * If a module is specified, returns just that directory.
  104. *
  105. * @param string $module Module name
  106. * @return array|string Returns array of all directories by default, single
  107. * module directory if module argument provided
  108. */
  109. public function getControllerDirectory($module = null)
  110. {
  111. if (null === $module) {
  112. return $this->_controllerDirectory;
  113. }
  114. $module = (string) $module;
  115. if (array_key_exists($module, $this->_controllerDirectory)) {
  116. return $this->_controllerDirectory[$module];
  117. }
  118. return null;
  119. }
  120. /**
  121. * Remove a controller directory by module name
  122. *
  123. * @param string $module
  124. * @return bool
  125. */
  126. public function removeControllerDirectory($module)
  127. {
  128. $module = (string) $module;
  129. if (array_key_exists($module, $this->_controllerDirectory)) {
  130. unset($this->_controllerDirectory[$module]);
  131. return true;
  132. }
  133. return false;
  134. }
  135. /**
  136. * Format the module name.
  137. *
  138. * @param string $unformatted
  139. * @return string
  140. */
  141. public function formatModuleName($unformatted)
  142. {
  143. if (($this->_defaultModule == $unformatted) && !$this->getParam('prefixDefaultModule')) {
  144. return $unformatted;
  145. }
  146. return ucfirst($this->_formatName($unformatted));
  147. }
  148. /**
  149. * Format action class name
  150. *
  151. * @param string $moduleName Name of the current module
  152. * @param string $className Name of the action class
  153. * @return string Formatted class name
  154. */
  155. public function formatClassName($moduleName, $className)
  156. {
  157. return $this->formatModuleName($moduleName) . '_' . $className;
  158. }
  159. /**
  160. * Convert a class name to a filename
  161. *
  162. * @param string $class
  163. * @return string
  164. */
  165. public function classToFilename($class)
  166. {
  167. return str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php';
  168. }
  169. /**
  170. * Returns TRUE if the Zend_Controller_Request_Abstract object can be
  171. * dispatched to a controller.
  172. *
  173. * Use this method wisely. By default, the dispatcher will fall back to the
  174. * default controller (either in the module specified or the global default)
  175. * if a given controller does not exist. This method returning false does
  176. * not necessarily indicate the dispatcher will not still dispatch the call.
  177. *
  178. * @param Zend_Controller_Request_Abstract $action
  179. * @return boolean
  180. */
  181. public function isDispatchable(Zend_Controller_Request_Abstract $request)
  182. {
  183. $className = $this->getControllerClass($request);
  184. if (!$className) {
  185. return false;
  186. }
  187. if (class_exists($className, false)) {
  188. return true;
  189. }
  190. $fileSpec = $this->classToFilename($className);
  191. $dispatchDir = $this->getDispatchDirectory();
  192. $test = $dispatchDir . DIRECTORY_SEPARATOR . $fileSpec;
  193. return Zend_Loader::isReadable($test);
  194. }
  195. /**
  196. * Dispatch to a controller/action
  197. *
  198. * By default, if a controller is not dispatchable, dispatch() will throw
  199. * an exception. If you wish to use the default controller instead, set the
  200. * param 'useDefaultControllerAlways' via {@link setParam()}.
  201. *
  202. * @param Zend_Controller_Request_Abstract $request
  203. * @param Zend_Controller_Response_Abstract $response
  204. * @return void
  205. * @throws Zend_Controller_Dispatcher_Exception
  206. */
  207. public function dispatch(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response)
  208. {
  209. $this->setResponse($response);
  210. /**
  211. * Get controller class
  212. */
  213. if (!$this->isDispatchable($request)) {
  214. $controller = $request->getControllerName();
  215. if (!$this->getParam('useDefaultControllerAlways') && !empty($controller)) {
  216. require_once 'Zend/Controller/Dispatcher/Exception.php';
  217. throw new Zend_Controller_Dispatcher_Exception('Invalid controller specified (' . $request->getControllerName() . ')');
  218. }
  219. $className = $this->getDefaultControllerClass($request);
  220. } else {
  221. $className = $this->getControllerClass($request);
  222. if (!$className) {
  223. $className = $this->getDefaultControllerClass($request);
  224. }
  225. }
  226. /**
  227. * Load the controller class file
  228. */
  229. $className = $this->loadClass($className);
  230. /**
  231. * Instantiate controller with request, response, and invocation
  232. * arguments; throw exception if it's not an action controller
  233. */
  234. $controller = new $className($request, $this->getResponse(), $this->getParams());
  235. if (!($controller instanceof Zend_Controller_Action_Interface) &&
  236. !($controller instanceof Zend_Controller_Action)) {
  237. require_once 'Zend/Controller/Dispatcher/Exception.php';
  238. throw new Zend_Controller_Dispatcher_Exception(
  239. 'Controller "' . $className . '" is not an instance of Zend_Controller_Action_Interface'
  240. );
  241. }
  242. /**
  243. * Retrieve the action name
  244. */
  245. $action = $this->getActionMethod($request);
  246. /**
  247. * Dispatch the method call
  248. */
  249. $request->setDispatched(true);
  250. // by default, buffer output
  251. $disableOb = $this->getParam('disableOutputBuffering');
  252. $obLevel = ob_get_level();
  253. if (empty($disableOb)) {
  254. ob_start();
  255. }
  256. try {
  257. $controller->dispatch($action);
  258. } catch (Exception $e) {
  259. // Clean output buffer on error
  260. $curObLevel = ob_get_level();
  261. if ($curObLevel > $obLevel) {
  262. do {
  263. ob_get_clean();
  264. $curObLevel = ob_get_level();
  265. } while ($curObLevel > $obLevel);
  266. }
  267. require_once 'Zend/Controller/Exception.php';
  268. throw new Zend_Controller_Exception($e->getMessage(), $e->getCode(), $e);
  269. }
  270. if (empty($disableOb)) {
  271. $content = ob_get_clean();
  272. $response->appendBody($content);
  273. }
  274. // Destroy the page controller instance and reflection objects
  275. $controller = null;
  276. }
  277. /**
  278. * Load a controller class
  279. *
  280. * Attempts to load the controller class file from
  281. * {@link getControllerDirectory()}. If the controller belongs to a
  282. * module, looks for the module prefix to the controller class.
  283. *
  284. * @param string $className
  285. * @return string Class name loaded
  286. * @throws Zend_Controller_Dispatcher_Exception if class not loaded
  287. */
  288. public function loadClass($className)
  289. {
  290. $finalClass = $className;
  291. if (($this->_defaultModule != $this->_curModule)
  292. || $this->getParam('prefixDefaultModule'))
  293. {
  294. $finalClass = $this->formatClassName($this->_curModule, $className);
  295. }
  296. if (class_exists($finalClass, false)) {
  297. return $finalClass;
  298. }
  299. $dispatchDir = $this->getDispatchDirectory();
  300. $loadFile = $dispatchDir . DIRECTORY_SEPARATOR . $this->classToFilename($className);
  301. if (Zend_Loader::isReadable($loadFile)) {
  302. include_once $loadFile;
  303. } else {
  304. require_once 'Zend/Controller/Dispatcher/Exception.php';
  305. throw new Zend_Controller_Dispatcher_Exception('Cannot load controller class "' . $className . '" from file "' . $loadFile . "'");
  306. }
  307. if (!class_exists($finalClass, false)) {
  308. require_once 'Zend/Controller/Dispatcher/Exception.php';
  309. throw new Zend_Controller_Dispatcher_Exception('Invalid controller class ("' . $finalClass . '")');
  310. }
  311. return $finalClass;
  312. }
  313. /**
  314. * Get controller class name
  315. *
  316. * Try request first; if not found, try pulling from request parameter;
  317. * if still not found, fallback to default
  318. *
  319. * @param Zend_Controller_Request_Abstract $request
  320. * @return string|false Returns class name on success
  321. */
  322. public function getControllerClass(Zend_Controller_Request_Abstract $request)
  323. {
  324. $controllerName = $request->getControllerName();
  325. if (empty($controllerName)) {
  326. if (!$this->getParam('useDefaultControllerAlways')) {
  327. return false;
  328. }
  329. $controllerName = $this->getDefaultControllerName();
  330. $request->setControllerName($controllerName);
  331. }
  332. $className = $this->formatControllerName($controllerName);
  333. $controllerDirs = $this->getControllerDirectory();
  334. $module = $request->getModuleName();
  335. if ($this->isValidModule($module)) {
  336. $this->_curModule = $module;
  337. $this->_curDirectory = $controllerDirs[$module];
  338. } elseif ($this->isValidModule($this->_defaultModule)) {
  339. $request->setModuleName($this->_defaultModule);
  340. $this->_curModule = $this->_defaultModule;
  341. $this->_curDirectory = $controllerDirs[$this->_defaultModule];
  342. } else {
  343. require_once 'Zend/Controller/Exception.php';
  344. throw new Zend_Controller_Exception('No default module defined for this application');
  345. }
  346. return $className;
  347. }
  348. /**
  349. * Determine if a given module is valid
  350. *
  351. * @param string $module
  352. * @return bool
  353. */
  354. public function isValidModule($module)
  355. {
  356. if (!is_string($module)) {
  357. return false;
  358. }
  359. $module = strtolower($module);
  360. $controllerDir = $this->getControllerDirectory();
  361. foreach (array_keys($controllerDir) as $moduleName) {
  362. if ($module == strtolower($moduleName)) {
  363. return true;
  364. }
  365. }
  366. return false;
  367. }
  368. /**
  369. * Retrieve default controller class
  370. *
  371. * Determines whether the default controller to use lies within the
  372. * requested module, or if the global default should be used.
  373. *
  374. * By default, will only use the module default unless that controller does
  375. * not exist; if this is the case, it falls back to the default controller
  376. * in the default module.
  377. *
  378. * @param Zend_Controller_Request_Abstract $request
  379. * @return string
  380. */
  381. public function getDefaultControllerClass(Zend_Controller_Request_Abstract $request)
  382. {
  383. $controller = $this->getDefaultControllerName();
  384. $default = $this->formatControllerName($controller);
  385. $request->setControllerName($controller)
  386. ->setActionName(null);
  387. $module = $request->getModuleName();
  388. $controllerDirs = $this->getControllerDirectory();
  389. $this->_curModule = $this->_defaultModule;
  390. $this->_curDirectory = $controllerDirs[$this->_defaultModule];
  391. if ($this->isValidModule($module)) {
  392. $found = false;
  393. if (class_exists($default, false)) {
  394. $found = true;
  395. } else {
  396. $moduleDir = $controllerDirs[$module];
  397. $fileSpec = $moduleDir . DIRECTORY_SEPARATOR . $this->classToFilename($default);
  398. if (Zend_Loader::isReadable($fileSpec)) {
  399. $found = true;
  400. $this->_curDirectory = $moduleDir;
  401. }
  402. }
  403. if ($found) {
  404. $request->setModuleName($module);
  405. $this->_curModule = $this->formatModuleName($module);
  406. }
  407. } else {
  408. $request->setModuleName($this->_defaultModule);
  409. }
  410. return $default;
  411. }
  412. /**
  413. * Return the value of the currently selected dispatch directory (as set by
  414. * {@link getController()})
  415. *
  416. * @return string
  417. */
  418. public function getDispatchDirectory()
  419. {
  420. return $this->_curDirectory;
  421. }
  422. /**
  423. * Determine the action name
  424. *
  425. * First attempt to retrieve from request; then from request params
  426. * using action key; default to default action
  427. *
  428. * Returns formatted action name
  429. *
  430. * @param Zend_Controller_Request_Abstract $request
  431. * @return string
  432. */
  433. public function getActionMethod(Zend_Controller_Request_Abstract $request)
  434. {
  435. $action = $request->getActionName();
  436. if (empty($action)) {
  437. $action = $this->getDefaultAction();
  438. $request->setActionName($action);
  439. }
  440. return $this->formatActionName($action);
  441. }
  442. }