sfController.class.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. <?php
  2. /*
  3. * This file is part of the symfony package.
  4. * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
  5. * (c) 2004-2006 Sean Kerr <sean@code-box.org>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. /**
  11. * sfController directs application flow.
  12. *
  13. * @package symfony
  14. * @subpackage controller
  15. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  16. * @author Sean Kerr <sean@code-box.org>
  17. * @version SVN: $Id: sfController.class.php 19442 2009-06-21 12:27:42Z fabien $
  18. */
  19. abstract class sfController
  20. {
  21. protected
  22. $context = null,
  23. $dispatcher = null,
  24. $controllerClasses = array(),
  25. $maxForwards = 5,
  26. $renderMode = sfView::RENDER_CLIENT;
  27. /**
  28. * Class constructor.
  29. *
  30. * @see initialize()
  31. */
  32. public function __construct($context)
  33. {
  34. $this->initialize($context);
  35. }
  36. /**
  37. * Initializes this controller.
  38. *
  39. * @param sfContext $context A sfContext implementation instance
  40. */
  41. public function initialize($context)
  42. {
  43. $this->context = $context;
  44. $this->dispatcher = $context->getEventDispatcher();
  45. if (sfConfig::get('sf_logging_enabled'))
  46. {
  47. $this->dispatcher->notify(new sfEvent($this, 'application.log', array('Initialization')));
  48. }
  49. // set max forwards
  50. $this->maxForwards = sfConfig::get('sf_max_forwards', $this->maxForwards);
  51. }
  52. /**
  53. * Indicates whether or not a module has a specific component.
  54. *
  55. * @param string $moduleName A module name
  56. * @param string $componentName An component name
  57. *
  58. * @return bool true, if the component exists, otherwise false
  59. */
  60. public function componentExists($moduleName, $componentName)
  61. {
  62. return $this->controllerExists($moduleName, $componentName, 'component', false);
  63. }
  64. /**
  65. * Indicates whether or not a module has a specific action.
  66. *
  67. * @param string $moduleName A module name
  68. * @param string $actionName An action name
  69. *
  70. * @return bool true, if the action exists, otherwise false
  71. */
  72. public function actionExists($moduleName, $actionName)
  73. {
  74. return $this->controllerExists($moduleName, $actionName, 'action', false);
  75. }
  76. /**
  77. * Looks for a controller and optionally throw exceptions if existence is required (i.e.
  78. * in the case of {@link getController()}).
  79. *
  80. * @param string $moduleName The name of the module
  81. * @param string $controllerName The name of the controller within the module
  82. * @param string $extension Either 'action' or 'component' depending on the type of controller to look for
  83. * @param boolean $throwExceptions Whether to throw exceptions if the controller doesn't exist
  84. *
  85. * @throws sfConfigurationException thrown if the module is not enabled
  86. * @throws sfControllerException thrown if the controller doesn't exist and the $throwExceptions parameter is set to true
  87. *
  88. * @return boolean true if the controller exists, false otherwise
  89. */
  90. protected function controllerExists($moduleName, $controllerName, $extension, $throwExceptions)
  91. {
  92. $dirs = $this->context->getConfiguration()->getControllerDirs($moduleName);
  93. foreach ($dirs as $dir => $checkEnabled)
  94. {
  95. // plugin module enabled?
  96. if ($checkEnabled && !in_array($moduleName, sfConfig::get('sf_enabled_modules')) && is_readable($dir))
  97. {
  98. throw new sfConfigurationException(sprintf('The module "%s" is not enabled.', $moduleName));
  99. }
  100. // one action per file or one file for all actions
  101. $classFile = strtolower($extension);
  102. $classSuffix = ucfirst(strtolower($extension));
  103. $file = $dir.'/'.$controllerName.$classSuffix.'.class.php';
  104. if (is_readable($file))
  105. {
  106. // action class exists
  107. require_once($file);
  108. $this->controllerClasses[$moduleName.'_'.$controllerName.'_'.$classSuffix] = $controllerName.$classSuffix;
  109. return true;
  110. }
  111. $module_file = $dir.'/'.$classFile.'s.class.php';
  112. if (is_readable($module_file))
  113. {
  114. // module class exists
  115. require_once($module_file);
  116. if (!class_exists($moduleName.$classSuffix.'s', false))
  117. {
  118. if ($throwExceptions)
  119. {
  120. throw new sfControllerException(sprintf('There is no "%s" class in your action file "%s".', $moduleName.$classSuffix.'s', $module_file));
  121. }
  122. return false;
  123. }
  124. // action is defined in this class?
  125. if (!in_array('execute'.ucfirst($controllerName), get_class_methods($moduleName.$classSuffix.'s')))
  126. {
  127. if ($throwExceptions)
  128. {
  129. throw new sfControllerException(sprintf('There is no "%s" method in your action class "%s".', 'execute'.ucfirst($controllerName), $moduleName.$classSuffix.'s'));
  130. }
  131. return false;
  132. }
  133. $this->controllerClasses[$moduleName.'_'.$controllerName.'_'.$classSuffix] = $moduleName.$classSuffix.'s';
  134. return true;
  135. }
  136. }
  137. // send an exception if debug
  138. if ($throwExceptions && sfConfig::get('sf_debug'))
  139. {
  140. $dirs = array_keys($dirs);
  141. // remove sf_root_dir from dirs
  142. foreach ($dirs as &$dir)
  143. {
  144. $dir = str_replace(sfConfig::get('sf_root_dir'), '%SF_ROOT_DIR%', $dir);
  145. }
  146. throw new sfControllerException(sprintf('Controller "%s/%s" does not exist in: %s.', $moduleName, $controllerName, implode(', ', $dirs)));
  147. }
  148. return false;
  149. }
  150. /**
  151. * Forwards the request to another action.
  152. *
  153. * @param string $moduleName A module name
  154. * @param string $actionName An action name
  155. *
  156. * @throws <b>sfConfigurationException</b> If an invalid configuration setting has been found
  157. * @throws <b>sfForwardException</b> If an error occurs while forwarding the request
  158. * @throws <b>sfInitializationException</b> If the action could not be initialized
  159. * @throws <b>sfSecurityException</b> If the action requires security but the user implementation is not of type sfSecurityUser
  160. */
  161. public function forward($moduleName, $actionName)
  162. {
  163. // replace unwanted characters
  164. $moduleName = preg_replace('/[^a-z0-9_]+/i', '', $moduleName);
  165. $actionName = preg_replace('/[^a-z0-9_]+/i', '', $actionName);
  166. if ($this->getActionStack()->getSize() >= $this->maxForwards)
  167. {
  168. // let's kill this party before it turns into cpu cycle hell
  169. throw new sfForwardException(sprintf('Too many forwards have been detected for this request (> %d).', $this->maxForwards));
  170. }
  171. // check for a module generator config file
  172. $this->context->getConfigCache()->import('modules/'.$moduleName.'/config/generator.yml', false, true);
  173. if (!$this->actionExists($moduleName, $actionName))
  174. {
  175. // the requested action doesn't exist
  176. if (sfConfig::get('sf_logging_enabled'))
  177. {
  178. $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Action "%s/%s" does not exist', $moduleName, $actionName))));
  179. }
  180. throw new sfError404Exception(sprintf('Action "%s/%s" does not exist.', $moduleName, $actionName));
  181. }
  182. // create an instance of the action
  183. $actionInstance = $this->getAction($moduleName, $actionName);
  184. // add a new action stack entry
  185. $this->getActionStack()->addEntry($moduleName, $actionName, $actionInstance);
  186. // include module configuration
  187. require($this->context->getConfigCache()->checkConfig('modules/'.$moduleName.'/config/module.yml'));
  188. // check if this module is internal
  189. if ($this->getActionStack()->getSize() == 1 && sfConfig::get('mod_'.strtolower($moduleName).'_is_internal') && !sfConfig::get('sf_test'))
  190. {
  191. throw new sfConfigurationException(sprintf('Action "%s" from module "%s" cannot be called directly.', $actionName, $moduleName));
  192. }
  193. // module enabled?
  194. if (sfConfig::get('mod_'.strtolower($moduleName).'_enabled'))
  195. {
  196. // check for a module config.php
  197. $moduleConfig = sfConfig::get('sf_app_module_dir').'/'.$moduleName.'/config/config.php';
  198. if (is_readable($moduleConfig))
  199. {
  200. require_once($moduleConfig);
  201. }
  202. // create a new filter chain
  203. $filterChain = new sfFilterChain();
  204. $filterChain->loadConfiguration($actionInstance);
  205. $this->context->getEventDispatcher()->notify(new sfEvent($this, 'controller.change_action', array('module' => $moduleName, 'action' => $actionName)));
  206. if ($moduleName == sfConfig::get('sf_error_404_module') && $actionName == sfConfig::get('sf_error_404_action'))
  207. {
  208. $this->context->getResponse()->setStatusCode(404);
  209. $this->context->getResponse()->setHttpHeader('Status', '404 Not Found');
  210. $this->dispatcher->notify(new sfEvent($this, 'controller.page_not_found', array('module' => $moduleName, 'action' => $actionName)));
  211. }
  212. // process the filter chain
  213. $filterChain->execute();
  214. }
  215. else
  216. {
  217. $moduleName = sfConfig::get('sf_module_disabled_module');
  218. $actionName = sfConfig::get('sf_module_disabled_action');
  219. if (!$this->actionExists($moduleName, $actionName))
  220. {
  221. // cannot find mod disabled module/action
  222. throw new sfConfigurationException(sprintf('Invalid configuration settings: [sf_module_disabled_module] "%s", [sf_module_disabled_action] "%s".', $moduleName, $actionName));
  223. }
  224. $this->forward($moduleName, $actionName);
  225. }
  226. }
  227. /**
  228. * Retrieves an sfAction implementation instance.
  229. *
  230. * @param string $moduleName A module name
  231. * @param string $actionname An action name
  232. *
  233. * @return sfAction An sfAction implementation instance, if the action exists, otherwise null
  234. */
  235. public function getAction($moduleName, $actionName)
  236. {
  237. return $this->getController($moduleName, $actionName, 'action');
  238. }
  239. /**
  240. * Retrieves a sfComponent implementation instance.
  241. *
  242. * @param string $moduleName A module name
  243. * @param string $componentName A component name
  244. *
  245. * @return sfComponent A sfComponent implementation instance, if the component exists, otherwise null
  246. */
  247. public function getComponent($moduleName, $componentName)
  248. {
  249. return $this->getController($moduleName, $componentName, 'component');
  250. }
  251. /**
  252. * Retrieves a controller implementation instance.
  253. *
  254. * @param string $moduleName A module name
  255. * @param string $controllerName A component name
  256. * @param string $extension Either 'action' or 'component' depending on the type of controller to look for
  257. *
  258. * @return object A controller implementation instance, if the controller exists, otherwise null
  259. *
  260. * @see getComponent(), getAction()
  261. */
  262. protected function getController($moduleName, $controllerName, $extension)
  263. {
  264. $classSuffix = ucfirst(strtolower($extension));
  265. if (!isset($this->controllerClasses[$moduleName.'_'.$controllerName.'_'.$classSuffix]))
  266. {
  267. $this->controllerExists($moduleName, $controllerName, $extension, true);
  268. }
  269. $class = $this->controllerClasses[$moduleName.'_'.$controllerName.'_'.$classSuffix];
  270. // fix for same name classes
  271. $moduleClass = $moduleName.'_'.$class;
  272. if (class_exists($moduleClass, false))
  273. {
  274. $class = $moduleClass;
  275. }
  276. return new $class($this->context, $moduleName, $controllerName);
  277. }
  278. /**
  279. * Retrieves the action stack.
  280. *
  281. * @return sfActionStack An sfActionStack instance, if the action stack is enabled, otherwise null
  282. */
  283. public function getActionStack()
  284. {
  285. return $this->context->getActionStack();
  286. }
  287. /**
  288. * Retrieves the presentation rendering mode.
  289. *
  290. * @return int One of the following:
  291. * - sfView::RENDER_CLIENT
  292. * - sfView::RENDER_VAR
  293. */
  294. public function getRenderMode()
  295. {
  296. return $this->renderMode;
  297. }
  298. /**
  299. * Retrieves a sfView implementation instance.
  300. *
  301. * @param string $moduleName A module name
  302. * @param string $actionName An action name
  303. * @param string $viewName A view name
  304. *
  305. * @return sfView A sfView implementation instance, if the view exists, otherwise null
  306. */
  307. public function getView($moduleName, $actionName, $viewName)
  308. {
  309. // user view exists?
  310. $file = sfConfig::get('sf_app_module_dir').'/'.$moduleName.'/view/'.$actionName.$viewName.'View.class.php';
  311. if (is_readable($file))
  312. {
  313. require_once($file);
  314. $class = $actionName.$viewName.'View';
  315. // fix for same name classes
  316. $moduleClass = $moduleName.'_'.$class;
  317. if (class_exists($moduleClass, false))
  318. {
  319. $class = $moduleClass;
  320. }
  321. }
  322. else
  323. {
  324. // view class (as configured in module.yml or defined in action)
  325. $class = sfConfig::get('mod_'.strtolower($moduleName).'_view_class', 'sfPHP').'View';
  326. }
  327. return new $class($this->context, $moduleName, $actionName, $viewName);
  328. }
  329. /**
  330. * [DEPRECATED] Sends and email.
  331. *
  332. * This methods calls a module/action with the sfMailView class.
  333. *
  334. * @param string $module A module name
  335. * @param string $action An action name
  336. *
  337. * @return string The generated mail content
  338. *
  339. * @see sfMailView, getPresentationFor(), sfController
  340. * @deprecated 1.1
  341. */
  342. public function sendEmail($module, $action)
  343. {
  344. if (sfConfig::get('sf_logging_enabled'))
  345. {
  346. $this->dispatcher->notify(new sfEvent($this, 'application.log', array('sendEmail method is deprecated', 'priority' => sfLogger::ERR)));
  347. }
  348. return $this->getPresentationFor($module, $action, 'sfMail');
  349. }
  350. /**
  351. * Returns the rendered view presentation of a given module/action.
  352. *
  353. * @param string $module A module name
  354. * @param string $action An action name
  355. * @param string $viewName A View class name
  356. *
  357. * @return string The generated content
  358. */
  359. public function getPresentationFor($module, $action, $viewName = null)
  360. {
  361. if (sfConfig::get('sf_logging_enabled'))
  362. {
  363. $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Get presentation for action "%s/%s" (view class: "%s")', $module, $action, $viewName))));
  364. }
  365. // get original render mode
  366. $renderMode = $this->getRenderMode();
  367. // set render mode to var
  368. $this->setRenderMode(sfView::RENDER_VAR);
  369. // grab the action stack
  370. $actionStack = $this->getActionStack();
  371. // grab this next forward's action stack index
  372. $index = $actionStack->getSize();
  373. // set viewName if needed
  374. if ($viewName)
  375. {
  376. $currentViewName = sfConfig::get('mod_'.strtolower($module).'_view_class');
  377. sfConfig::set('mod_'.strtolower($module).'_view_class', $viewName);
  378. }
  379. try
  380. {
  381. // forward to the mail action
  382. $this->forward($module, $action);
  383. }
  384. catch (Exception $e)
  385. {
  386. // put render mode back
  387. $this->setRenderMode($renderMode);
  388. // remove viewName
  389. if ($viewName)
  390. {
  391. sfConfig::set('mod_'.strtolower($module).'_view_class', $currentViewName);
  392. }
  393. throw $e;
  394. }
  395. // grab the action entry from this forward
  396. $actionEntry = $actionStack->getEntry($index);
  397. // get raw email content
  398. $presentation =& $actionEntry->getPresentation();
  399. // put render mode back
  400. $this->setRenderMode($renderMode);
  401. // remove the action entry
  402. $nb = $actionStack->getSize() - $index;
  403. while ($nb-- > 0)
  404. {
  405. $actionEntry = $actionStack->popEntry();
  406. if ($actionEntry->getModuleName() == sfConfig::get('sf_login_module') && $actionEntry->getActionName() == sfConfig::get('sf_login_action'))
  407. {
  408. throw new sfException('Your action is secured, but the user is not authenticated.');
  409. }
  410. else if ($actionEntry->getModuleName() == sfConfig::get('sf_secure_module') && $actionEntry->getActionName() == sfConfig::get('sf_secure_action'))
  411. {
  412. throw new sfException('Your action is secured, but the user does not have access.');
  413. }
  414. }
  415. // remove viewName
  416. if ($viewName)
  417. {
  418. sfConfig::set('mod_'.strtolower($module).'_view_class', $currentViewName);
  419. }
  420. return $presentation;
  421. }
  422. /**
  423. * Sets the presentation rendering mode.
  424. *
  425. * @param int $mode A rendering mode
  426. *
  427. * @throws sfRenderException If an invalid render mode has been set
  428. */
  429. public function setRenderMode($mode)
  430. {
  431. if ($mode == sfView::RENDER_CLIENT || $mode == sfView::RENDER_VAR || $mode == sfView::RENDER_NONE)
  432. {
  433. $this->renderMode = $mode;
  434. return;
  435. }
  436. // invalid rendering mode type
  437. throw new sfRenderException(sprintf('Invalid rendering mode: %s.', $mode));
  438. }
  439. /**
  440. * Indicates whether or not we were called using the CLI version of PHP.
  441. *
  442. * @return bool true, if using cli, otherwise false.
  443. */
  444. public function inCLI()
  445. {
  446. return 0 == strncasecmp(PHP_SAPI, 'cli', 3);
  447. }
  448. /**
  449. * Calls methods defined via sfEventDispatcher.
  450. *
  451. * @param string $method The method name
  452. * @param array $arguments The method arguments
  453. *
  454. * @return mixed The returned value of the called method
  455. */
  456. public function __call($method, $arguments)
  457. {
  458. $event = $this->dispatcher->notifyUntil(new sfEvent($this, 'controller.method_not_found', array('method' => $method, 'arguments' => $arguments)));
  459. if (!$event->isProcessed())
  460. {
  461. throw new sfException(sprintf('Call to undefined method %s::%s.', get_class($this), $method));
  462. }
  463. return $event->getReturnValue();
  464. }
  465. }