PageRenderTime 41ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/library/Zend/Controller/Dispatcher/Standard.php

https://bitbucket.org/dbaltas/zend-framework-1.x-on-git
PHP | 512 lines | 412 code | 19 blank | 81 comment | 16 complexity | d20a9ccce5466cdecdf3c324d594f784 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.0, MIT
  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-2012 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id: Standard.php 24861 2012-06-01 23:40:13Z adamlundrigan $
  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-2012 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. $finalClass = $className;
  188. if (($this->_defaultModule != $this->_curModule)
  189. || $this->getParam('prefixDefaultModule'))
  190. {
  191. $finalClass = $this->formatClassName($this->_curModule, $className);
  192. }
  193. if (class_exists($finalClass, false)) {
  194. return true;
  195. }
  196. $fileSpec = $this->classToFilename($className);
  197. $dispatchDir = $this->getDispatchDirectory();
  198. $test = $dispatchDir . DIRECTORY_SEPARATOR . $fileSpec;
  199. return Zend_Loader::isReadable($test);
  200. }
  201. /**
  202. * Dispatch to a controller/action
  203. *
  204. * By default, if a controller is not dispatchable, dispatch() will throw
  205. * an exception. If you wish to use the default controller instead, set the
  206. * param 'useDefaultControllerAlways' via {@link setParam()}.
  207. *
  208. * @param Zend_Controller_Request_Abstract $request
  209. * @param Zend_Controller_Response_Abstract $response
  210. * @return void
  211. * @throws Zend_Controller_Dispatcher_Exception
  212. */
  213. public function dispatch(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response)
  214. {
  215. $this->setResponse($response);
  216. /**
  217. * Get controller class
  218. */
  219. if (!$this->isDispatchable($request)) {
  220. $controller = $request->getControllerName();
  221. if (!$this->getParam('useDefaultControllerAlways') && !empty($controller)) {
  222. require_once 'Zend/Controller/Dispatcher/Exception.php';
  223. throw new Zend_Controller_Dispatcher_Exception('Invalid controller specified (' . $request->getControllerName() . ')');
  224. }
  225. $className = $this->getDefaultControllerClass($request);
  226. } else {
  227. $className = $this->getControllerClass($request);
  228. if (!$className) {
  229. $className = $this->getDefaultControllerClass($request);
  230. }
  231. }
  232. /**
  233. * If we're in a module or prefixDefaultModule is on, we must add the module name
  234. * prefix to the contents of $className, as getControllerClass does not do that automatically.
  235. * We must keep a separate variable because modules are not strictly PSR-0: We need the no-module-prefix
  236. * class name to do the class->file mapping, but the full class name to insantiate the controller
  237. */
  238. $moduleClassName = $className;
  239. if (($this->_defaultModule != $this->_curModule)
  240. || $this->getParam('prefixDefaultModule'))
  241. {
  242. $moduleClassName = $this->formatClassName($this->_curModule, $className);
  243. }
  244. /**
  245. * Load the controller class file
  246. */
  247. $className = $this->loadClass($className);
  248. /**
  249. * Instantiate controller with request, response, and invocation
  250. * arguments; throw exception if it's not an action controller
  251. */
  252. $controller = new $moduleClassName($request, $this->getResponse(), $this->getParams());
  253. if (!($controller instanceof Zend_Controller_Action_Interface) &&
  254. !($controller instanceof Zend_Controller_Action)) {
  255. require_once 'Zend/Controller/Dispatcher/Exception.php';
  256. throw new Zend_Controller_Dispatcher_Exception(
  257. 'Controller "' . $moduleClassName . '" is not an instance of Zend_Controller_Action_Interface'
  258. );
  259. }
  260. /**
  261. * Retrieve the action name
  262. */
  263. $action = $this->getActionMethod($request);
  264. /**
  265. * Dispatch the method call
  266. */
  267. $request->setDispatched(true);
  268. // by default, buffer output
  269. $disableOb = $this->getParam('disableOutputBuffering');
  270. $obLevel = ob_get_level();
  271. if (empty($disableOb)) {
  272. ob_start();
  273. }
  274. try {
  275. $controller->dispatch($action);
  276. } catch (Exception $e) {
  277. // Clean output buffer on error
  278. $curObLevel = ob_get_level();
  279. if ($curObLevel > $obLevel) {
  280. do {
  281. ob_get_clean();
  282. $curObLevel = ob_get_level();
  283. } while ($curObLevel > $obLevel);
  284. }
  285. throw $e;
  286. }
  287. if (empty($disableOb)) {
  288. $content = ob_get_clean();
  289. $response->appendBody($content);
  290. }
  291. // Destroy the page controller instance and reflection objects
  292. $controller = null;
  293. }
  294. /**
  295. * Load a controller class
  296. *
  297. * Attempts to load the controller class file from
  298. * {@link getControllerDirectory()}. If the controller belongs to a
  299. * module, looks for the module prefix to the controller class.
  300. *
  301. * @param string $className
  302. * @return string Class name loaded
  303. * @throws Zend_Controller_Dispatcher_Exception if class not loaded
  304. */
  305. public function loadClass($className)
  306. {
  307. $finalClass = $className;
  308. if (($this->_defaultModule != $this->_curModule)
  309. || $this->getParam('prefixDefaultModule'))
  310. {
  311. $finalClass = $this->formatClassName($this->_curModule, $className);
  312. }
  313. if (class_exists($finalClass, false)) {
  314. return $finalClass;
  315. }
  316. $dispatchDir = $this->getDispatchDirectory();
  317. $loadFile = $dispatchDir . DIRECTORY_SEPARATOR . $this->classToFilename($className);
  318. if (Zend_Loader::isReadable($loadFile)) {
  319. include_once $loadFile;
  320. } else {
  321. require_once 'Zend/Controller/Dispatcher/Exception.php';
  322. throw new Zend_Controller_Dispatcher_Exception('Cannot load controller class "' . $className . '" from file "' . $loadFile . "'");
  323. }
  324. if (!class_exists($finalClass, false)) {
  325. require_once 'Zend/Controller/Dispatcher/Exception.php';
  326. throw new Zend_Controller_Dispatcher_Exception('Invalid controller class ("' . $finalClass . '")');
  327. }
  328. return $finalClass;
  329. }
  330. /**
  331. * Get controller class name
  332. *
  333. * Try request first; if not found, try pulling from request parameter;
  334. * if still not found, fallback to default
  335. *
  336. * @param Zend_Controller_Request_Abstract $request
  337. * @return string|false Returns class name on success
  338. */
  339. public function getControllerClass(Zend_Controller_Request_Abstract $request)
  340. {
  341. $controllerName = $request->getControllerName();
  342. if (empty($controllerName)) {
  343. if (!$this->getParam('useDefaultControllerAlways')) {
  344. return false;
  345. }
  346. $controllerName = $this->getDefaultControllerName();
  347. $request->setControllerName($controllerName);
  348. }
  349. $className = $this->formatControllerName($controllerName);
  350. $controllerDirs = $this->getControllerDirectory();
  351. $module = $request->getModuleName();
  352. if ($this->isValidModule($module)) {
  353. $this->_curModule = $module;
  354. $this->_curDirectory = $controllerDirs[$module];
  355. } elseif ($this->isValidModule($this->_defaultModule)) {
  356. $request->setModuleName($this->_defaultModule);
  357. $this->_curModule = $this->_defaultModule;
  358. $this->_curDirectory = $controllerDirs[$this->_defaultModule];
  359. } else {
  360. require_once 'Zend/Controller/Exception.php';
  361. throw new Zend_Controller_Exception('No default module defined for this application');
  362. }
  363. return $className;
  364. }
  365. /**
  366. * Determine if a given module is valid
  367. *
  368. * @param string $module
  369. * @return bool
  370. */
  371. public function isValidModule($module)
  372. {
  373. if (!is_string($module)) {
  374. return false;
  375. }
  376. $module = strtolower($module);
  377. $controllerDir = $this->getControllerDirectory();
  378. foreach (array_keys($controllerDir) as $moduleName) {
  379. if ($module == strtolower($moduleName)) {
  380. return true;
  381. }
  382. }
  383. return false;
  384. }
  385. /**
  386. * Retrieve default controller class
  387. *
  388. * Determines whether the default controller to use lies within the
  389. * requested module, or if the global default should be used.
  390. *
  391. * By default, will only use the module default unless that controller does
  392. * not exist; if this is the case, it falls back to the default controller
  393. * in the default module.
  394. *
  395. * @param Zend_Controller_Request_Abstract $request
  396. * @return string
  397. */
  398. public function getDefaultControllerClass(Zend_Controller_Request_Abstract $request)
  399. {
  400. $controller = $this->getDefaultControllerName();
  401. $default = $this->formatControllerName($controller);
  402. $request->setControllerName($controller)
  403. ->setActionName(null);
  404. $module = $request->getModuleName();
  405. $controllerDirs = $this->getControllerDirectory();
  406. $this->_curModule = $this->_defaultModule;
  407. $this->_curDirectory = $controllerDirs[$this->_defaultModule];
  408. if ($this->isValidModule($module)) {
  409. $found = false;
  410. if (class_exists($default, false)) {
  411. $found = true;
  412. } else {
  413. $moduleDir = $controllerDirs[$module];
  414. $fileSpec = $moduleDir . DIRECTORY_SEPARATOR . $this->classToFilename($default);
  415. if (Zend_Loader::isReadable($fileSpec)) {
  416. $found = true;
  417. $this->_curDirectory = $moduleDir;
  418. }
  419. }
  420. if ($found) {
  421. $request->setModuleName($module);
  422. $this->_curModule = $this->formatModuleName($module);
  423. }
  424. } else {
  425. $request->setModuleName($this->_defaultModule);
  426. }
  427. return $default;
  428. }
  429. /**
  430. * Return the value of the currently selected dispatch directory (as set by
  431. * {@link getController()})
  432. *
  433. * @return string
  434. */
  435. public function getDispatchDirectory()
  436. {
  437. return $this->_curDirectory;
  438. }
  439. /**
  440. * Determine the action name
  441. *
  442. * First attempt to retrieve from request; then from request params
  443. * using action key; default to default action
  444. *
  445. * Returns formatted action name
  446. *
  447. * @param Zend_Controller_Request_Abstract $request
  448. * @return string
  449. */
  450. public function getActionMethod(Zend_Controller_Request_Abstract $request)
  451. {
  452. $action = $request->getActionName();
  453. if (empty($action)) {
  454. $action = $this->getDefaultAction();
  455. $request->setActionName($action);
  456. }
  457. return $this->formatActionName($action);
  458. }
  459. }