Development

/branches/1.3/lib/controller/sfController.class.php

You must first sign up to be able to contribute.

root/branches/1.3/lib/controller/sfController.class.php

Revision 30912, 17.1 kB (checked in by fabien, 4 years ago)

[1.3, 1.4] fixed view class overriding (closes #5097, patch from caefer)

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