Standard.php 15 KB

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