Standard.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  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-2009 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-2009 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. throw $e;
  268. }
  269. if (empty($disableOb)) {
  270. $content = ob_get_clean();
  271. $response->appendBody($content);
  272. }
  273. // Destroy the page controller instance and reflection objects
  274. $controller = null;
  275. }
  276. /**
  277. * Load a controller class
  278. *
  279. * Attempts to load the controller class file from
  280. * {@link getControllerDirectory()}. If the controller belongs to a
  281. * module, looks for the module prefix to the controller class.
  282. *
  283. * @param string $className
  284. * @return string Class name loaded
  285. * @throws Zend_Controller_Dispatcher_Exception if class not loaded
  286. */
  287. public function loadClass($className)
  288. {
  289. $finalClass = $className;
  290. if (($this->_defaultModule != $this->_curModule)
  291. || $this->getParam('prefixDefaultModule'))
  292. {
  293. $finalClass = $this->formatClassName($this->_curModule, $className);
  294. }
  295. if (class_exists($finalClass, false)) {
  296. return $finalClass;
  297. }
  298. $dispatchDir = $this->getDispatchDirectory();
  299. $loadFile = $dispatchDir . DIRECTORY_SEPARATOR . $this->classToFilename($className);
  300. if (!include_once $loadFile) {
  301. require_once 'Zend/Controller/Dispatcher/Exception.php';
  302. throw new Zend_Controller_Dispatcher_Exception('Cannot load controller class "' . $className . '" from file "' . $loadFile . "'");
  303. }
  304. if (!class_exists($finalClass, false)) {
  305. require_once 'Zend/Controller/Dispatcher/Exception.php';
  306. throw new Zend_Controller_Dispatcher_Exception('Invalid controller class ("' . $finalClass . '")');
  307. }
  308. return $finalClass;
  309. }
  310. /**
  311. * Get controller class name
  312. *
  313. * Try request first; if not found, try pulling from request parameter;
  314. * if still not found, fallback to default
  315. *
  316. * @param Zend_Controller_Request_Abstract $request
  317. * @return string|false Returns class name on success
  318. */
  319. public function getControllerClass(Zend_Controller_Request_Abstract $request)
  320. {
  321. $controllerName = $request->getControllerName();
  322. if (empty($controllerName)) {
  323. if (!$this->getParam('useDefaultControllerAlways')) {
  324. return false;
  325. }
  326. $controllerName = $this->getDefaultControllerName();
  327. $request->setControllerName($controllerName);
  328. }
  329. $className = $this->formatControllerName($controllerName);
  330. $controllerDirs = $this->getControllerDirectory();
  331. $module = $request->getModuleName();
  332. if ($this->isValidModule($module)) {
  333. $this->_curModule = $module;
  334. $this->_curDirectory = $controllerDirs[$module];
  335. } elseif ($this->isValidModule($this->_defaultModule)) {
  336. $request->setModuleName($this->_defaultModule);
  337. $this->_curModule = $this->_defaultModule;
  338. $this->_curDirectory = $controllerDirs[$this->_defaultModule];
  339. } else {
  340. require_once 'Zend/Controller/Exception.php';
  341. throw new Zend_Controller_Exception('No default module defined for this application');
  342. }
  343. return $className;
  344. }
  345. /**
  346. * Determine if a given module is valid
  347. *
  348. * @param string $module
  349. * @return bool
  350. */
  351. public function isValidModule($module)
  352. {
  353. if (!is_string($module)) {
  354. return false;
  355. }
  356. $module = strtolower($module);
  357. $controllerDir = $this->getControllerDirectory();
  358. foreach (array_keys($controllerDir) as $moduleName) {
  359. if ($module == strtolower($moduleName)) {
  360. return true;
  361. }
  362. }
  363. return false;
  364. }
  365. /**
  366. * Retrieve default controller class
  367. *
  368. * Determines whether the default controller to use lies within the
  369. * requested module, or if the global default should be used.
  370. *
  371. * By default, will only use the module default unless that controller does
  372. * not exist; if this is the case, it falls back to the default controller
  373. * in the default module.
  374. *
  375. * @param Zend_Controller_Request_Abstract $request
  376. * @return string
  377. */
  378. public function getDefaultControllerClass(Zend_Controller_Request_Abstract $request)
  379. {
  380. $controller = $this->getDefaultControllerName();
  381. $default = $this->formatControllerName($controller);
  382. $request->setControllerName($controller)
  383. ->setActionName(null);
  384. $module = $request->getModuleName();
  385. $controllerDirs = $this->getControllerDirectory();
  386. $this->_curModule = $this->_defaultModule;
  387. $this->_curDirectory = $controllerDirs[$this->_defaultModule];
  388. if ($this->isValidModule($module)) {
  389. $found = false;
  390. if (class_exists($default, false)) {
  391. $found = true;
  392. } else {
  393. $moduleDir = $controllerDirs[$module];
  394. $fileSpec = $moduleDir . DIRECTORY_SEPARATOR . $this->classToFilename($default);
  395. if (Zend_Loader::isReadable($fileSpec)) {
  396. $found = true;
  397. $this->_curDirectory = $moduleDir;
  398. }
  399. }
  400. if ($found) {
  401. $request->setModuleName($module);
  402. $this->_curModule = $this->formatModuleName($module);
  403. }
  404. } else {
  405. $request->setModuleName($this->_defaultModule);
  406. }
  407. return $default;
  408. }
  409. /**
  410. * Return the value of the currently selected dispatch directory (as set by
  411. * {@link getController()})
  412. *
  413. * @return string
  414. */
  415. public function getDispatchDirectory()
  416. {
  417. return $this->_curDirectory;
  418. }
  419. /**
  420. * Determine the action name
  421. *
  422. * First attempt to retrieve from request; then from request params
  423. * using action key; default to default action
  424. *
  425. * Returns formatted action name
  426. *
  427. * @param Zend_Controller_Request_Abstract $request
  428. * @return string
  429. */
  430. public function getActionMethod(Zend_Controller_Request_Abstract $request)
  431. {
  432. $action = $request->getActionName();
  433. if (empty($action)) {
  434. $action = $this->getDefaultAction();
  435. $request->setActionName($action);
  436. }
  437. return $this->formatActionName($action);
  438. }
  439. }