root/branches/0.11/src/controller/AgaviController.class.php

Revision 2534, 16.6 KB (checked in by david, 5 months ago)

AgaviController::dispatch() now accepts module and action names in the request data argument, partially already implemented in [2528] for #777, but now all is fine and consistent. Still doesn't allow overwriting of module and action parameters with routing enabled, mind you. Closes #776 and #777

  • Property keywords set to Id
  • Property svn:keywords set to Id
Line 
1<?php
2
3// +---------------------------------------------------------------------------+
4// | This file is part of the Agavi package.                                   |
5// | Copyright (c) 2005-2008 the Agavi Project.                                |
6// | Based on the Mojavi3 MVC Framework, Copyright (c) 2003-2005 Sean Kerr.    |
7// |                                                                           |
8// | For the full copyright and license information, please view the LICENSE   |
9// | file that was distributed with this source code. You can also view the    |
10// | LICENSE file online at http://www.agavi.org/LICENSE.txt                   |
11// |   vi: set noexpandtab:                                                    |
12// |   Local Variables:                                                        |
13// |   indent-tabs-mode: t                                                     |
14// |   End:                                                                    |
15// +---------------------------------------------------------------------------+
16
17/**
18 * AgaviController directs application flow.
19 *
20 * @package    agavi
21 * @subpackage controller
22 *
23 * @author     Sean Kerr <skerr@mojavi.org>
24 * @author     David Zülke <dz@bitxtender.com>
25 * @copyright  Authors
26 * @copyright  The Agavi Project
27 *
28 * @since      0.9.0
29 *
30 * @version    $Id$
31 */
32class AgaviController extends AgaviParameterHolder
33{
34  /**
35   * @var        int The number of execution containers run so far.
36   */
37  protected $numExecutions = 0;
38 
39  /**
40   * @var        AgaviContext An AgaviContext instance.
41   */
42  protected $context = null;
43 
44  /**
45   * @var        AgaviResponse The global response.
46   */
47  protected $response = null;
48 
49  /**
50   * @var        array An array of filter instances for reuse.
51   */
52  protected $filters = array(
53    'global' => array(),
54    'action' => array(
55      '*' => null
56    ),
57    'dispatch' => null,
58    'execution' => null,
59    'security' => null
60  );
61 
62  /**
63   * @var        string The default Output Type.
64   */
65  protected $defaultOutputType = null;
66 
67  /**
68   * @var        array An array of registered Output Types.
69   */
70  protected $outputTypes = array();
71 
72  /**
73   * @var        array Ref to the request data object from the request.
74   */
75  private $requestData = null;
76 
77  /**
78   * Indicates whether or not a module has a specific action.
79   *
80   * @param      string A module name.
81   * @param      string An action name.
82   *
83   * @return     string The actual name of the action (might be modified).
84   *
85   * @throws     AgaviControllerException if the action could not be found.
86   * @author     David Zülke <dz@bitxtender.com>
87   * @author     Sean Kerr <skerr@mojavi.org>
88   * @since      0.11.0
89   */
90  public function resolveAction($moduleName, $actionName)
91  {
92    $actionName = str_replace('.', '/', $actionName);
93    $file = AgaviConfig::get('core.module_dir') . '/' . $moduleName . '/actions/' . $actionName . 'Action.class.php';
94    if(is_readable($file) && substr($actionName, 0, 1) !== '/') {
95      return $actionName;
96    }
97    throw new AgaviControllerException(sprintf('Action "%s" in Module "%s" could not be found.', $actionName, $moduleName));
98  }
99 
100  /**
101   * Increment the execution counter.
102   * Will throw an exception if the maximum amount of runs is exceeded.
103   *
104   * @throws     AgaviControllerException If too many execution runs were made.
105   *
106   * @author     David Zülke <dz@bitxtender.com>
107   * @since      0.11.0
108   */
109  public function countExecution()
110  {
111    $maxExecutions = $this->getParameter('max_executions');
112   
113    if(++$this->numExecutions > $maxExecutions && $maxExecutions > 0) {
114      throw new AgaviControllerException('Too many execution runs have been detected for this Context.');
115    }
116  }
117 
118  /**
119   * Create and initialize new execution container instance.
120   *
121   * @param      string                 The name of the module.
122   * @param      string                 The name of the action.
123   * @param      AgaviRequestDataHolder A RequestDataHolder with additional
124   *                                    request arguments.
125   * @param      string                 Optional name of an initial output type
126   *                                    to set.
127   *
128   * @return     AgaviExecutionContainer A new execution container instance,
129   *                                     fully initialized.
130   *
131   * @author     David Zülke <dz@bitxtender.com>
132   * @since      0.11.0
133   */
134  public function createExecutionContainer($moduleName = null, $actionName = null, AgaviRequestDataHolder $arguments = null, $outputType = null)
135  {
136    // create a new execution container
137    $ecfi = $this->context->getFactoryInfo('execution_container');
138    $container = new $ecfi['class']();
139    $container->initialize($this->context, $ecfi['parameters']);
140    $container->setModuleName($moduleName);
141    $container->setActionName($actionName);
142    $container->setRequestData($this->requestData);
143    if($arguments !== null) {
144      $container->setArguments($arguments);
145    }
146    $container->setOutputType($this->context->getController()->getOutputType($outputType));
147    return $container;
148  }
149 
150  /**
151   * Dispatch a request
152   *
153   * @param      AgaviRequestDataHolder A RequestDataHolder with additional
154   *                                    request arguments.
155   *
156   * @author     David Zülke <dz@bitxtender.com>
157   * @since      0.9.0
158   */
159  public function dispatch(AgaviRequestDataHolder $arguments = null)
160  {
161    $container = null;
162   
163    try {
164     
165      $rq = $this->context->getRequest();
166      $rd = $rq->getRequestData();
167     
168      // match routes and assign returned initial execution container
169      $container = $this->context->getRouting()->execute();
170     
171      // merge in any arguments given. they need to have precedence over what the routing found
172      if($arguments !== null) {
173        $rd->merge($arguments);
174      }
175     
176      // next, we have to see if the routing did anything useful, i.e. whether or not it was enabled.
177      $moduleName = $container->getModuleName();
178      $actionName = $container->getActionName();
179      if(!$moduleName) {
180        // no module has been specified; that means the routing did not run, as it would otherwise have the 404 action's module name
181       
182        // lets see if our request data has values for module and action
183        $ma = $rq->getParameter('module_accessor');
184        $aa = $rq->getParameter('action_accessor');
185        if($rd->hasParameter($ma) && $rd->hasParameter($aa)) {
186          // yup. grab those
187          $moduleName = $rd->getParameter($ma);
188          $actionName = $rd->getParameter($aa);
189        } else {
190          // nope. then its time for the default action
191          $moduleName = AgaviConfig::get('actions.default_module');
192          $actionName = AgaviConfig::get('actions.default_action');
193        }
194       
195        // so by now we hopefully have something reasonable for module and action names - let's set them on the container
196        $container->setModuleName($moduleName);
197        $container->setActionName($actionName);
198      }
199     
200      // create a new filter chain
201      $fcfi = $this->context->getFactoryInfo('filter_chain');
202      $filterChain = new $fcfi['class']();
203      $filterChain->initialize($this->context, $fcfi['parameters']);
204     
205      $this->loadFilters($filterChain, 'global');
206     
207      // register the dispatch filter
208      $filterChain->register($this->filters['dispatch']);
209     
210      // go, go, go!
211      $filterChain->execute($container);
212     
213      $response = $container->getResponse();
214      $response->merge($this->response);
215     
216      if($this->getParameter('send_response')) {
217        $response->send();
218      }
219     
220      return $response;
221     
222    } catch(Exception $e) {
223      AgaviException::printStackTrace($e, $this->context, $container);
224    }
225  }
226 
227  /**
228   * Get the global response instance.
229   *
230   * @return     AgaviResponse The global response.
231   *
232   * @author     David Zülke <dz@bitxtender.com>
233   * @since      0.11.0
234   */
235  public function getGlobalResponse()
236  {
237    return $this->response;
238  }
239 
240  /**
241   * Retrieve an Action implementation instance.
242   *
243   * @param      string A module name.
244   * @param      string An action name.
245   *
246   * @return     AgaviAction An Action implementation instance, if the action
247   *                         exists, otherwise null.
248   *
249   * @author     Sean Kerr <skerr@mojavi.org>
250   * @author     Mike Vincent <mike@agavi.org>
251   * @author     David Zülke <dz@bitxtender.com>
252   * @since      0.9.0
253   */
254  public function createActionInstance($moduleName, $actionName)
255  {
256    static $loaded = array();
257   
258    $file = AgaviConfig::get('core.module_dir') . '/' . $moduleName . '/actions/' . $actionName . 'Action.class.php';
259
260    if(!isset($loaded[$file]) && file_exists($file)) {
261      require($file);
262      $loaded[$file] = true;
263    }
264
265    $longActionName = $actionName;
266
267    // Nested action check?
268    $position = strrpos($actionName, '/');
269    if($position > -1) {
270      $longActionName = str_replace('/', '_', $actionName);
271      $actionName = substr($actionName, $position + 1);
272    }
273
274    if(class_exists($moduleName . '_' . $longActionName . 'Action', false)) {
275      $class = $moduleName . '_' . $longActionName . 'Action';
276    } elseif(class_exists($moduleName . '_' . $actionName . 'Action', false)) {
277      $class = $moduleName . '_' . $actionName . 'Action';
278    } elseif(class_exists($longActionName . 'Action', false)) {
279      $class = $longActionName . 'Action';
280    } elseif(class_exists($actionName . 'Action', false)) {
281      $class = $actionName . 'Action';
282    } else {
283      throw new AgaviException('Could not find Action "' . $longActionName . '" for module "' . $moduleName . '"');
284    }
285
286    return new $class();
287  }
288
289  /**
290   * Retrieve the current application context.
291   *
292   * @return     AgaviContext An AgaviContext instance.
293   *
294   * @author     Sean Kerr <skerr@mojavi.org>
295   * @since      0.9.0
296   */
297  public final function getContext()
298  {
299    return $this->context;
300  }
301
302  /**
303   * Retrieve a View implementation instance.
304   *
305   * @param      string A module name.
306   * @param      string A view name.
307   *
308   * @return     AgaviView A View implementation instance, if the model exists,
309   *                       otherwise null.
310   *
311   * @author     Sean Kerr <skerr@mojavi.org>
312   * @author     Mike Vincent <mike@agavi.org>
313   * @author     David Zülke <dz@bitxtender.com>
314   * @since      0.9.0
315   */
316  public function createViewInstance($moduleName, $viewName)
317  {
318    static $loaded;
319   
320    $viewName = str_replace('.', '/', $viewName);
321    $file = AgaviConfig::get('core.module_dir') . '/' . $moduleName . '/views/' . $viewName . 'View.class.php';
322
323    if(!isset($loaded[$file]) && file_exists($file)) {
324      require($file);
325      $loaded[$file] = true;
326    }
327
328    $longViewName = $viewName;
329
330    $position = strrpos($viewName, '/');
331    if($position > -1) {
332      $longViewName = str_replace('/', '_', $viewName);
333      $viewName = substr($viewName, $position + 1);
334    }
335
336    if(class_exists($moduleName . '_' . $longViewName . 'View', false)) {
337      $class = $moduleName . '_' . $longViewName . 'View';
338    } elseif(class_exists($moduleName . '_' . $viewName . 'View', false)) {
339      $class = $moduleName . '_' . $viewName . 'View';
340    } elseif(class_exists($longViewName . 'View', false)) {
341      $class = $longViewName . 'View';
342    } elseif(class_exists($viewName . 'View', false)) {
343      $class = $viewName . 'View';
344    } else {
345      throw new AgaviException('Could not find View "' . $longViewName . '" for module "' . $moduleName . '"');
346    }
347
348    return new $class();
349  }
350
351  /**
352   * Constructor.
353   *
354   * @author     David Zülke <dz@bitxtender.com>
355   * @since      0.11.0
356   */
357  public function __construct()
358  {
359    parent::__construct();
360    $this->setParameters(array(
361      'max_executions' => 20,
362      'send_response' => true,
363    ));
364  }
365 
366  /**
367   * Initialize this controller.
368   *
369   * @param      AgaviContext An AgaviContext instance.
370   * @param      array        An array of initialization parameters.
371   *
372   * @author     David Zülke <dz@bitxtender.com>
373   * @since      0.9.0
374   */
375  public function initialize(AgaviContext $context, array $parameters = array())
376  {
377    $this->context = $context;
378   
379    $this->setParameters($parameters);
380   
381    $rfi = $context->getFactoryInfo('response');
382    $this->response = new $rfi["class"](); 
383    $this->response->initialize($context, $rfi["parameters"]);
384   
385    $cfg = AgaviConfig::get('core.config_dir') . '/output_types.xml';
386    require(AgaviConfigCache::checkConfig($cfg, $this->context->getName()));
387   
388    if(AgaviConfig::get('core.use_security', false)) {
389      $sffi = $this->context->getFactoryInfo('security_filter');
390      $this->filters['security'] = new $sffi['class']();
391      $this->filters['security']->initialize($this->context, $sffi['parameters']);
392    }
393   
394    $dffi = $this->context->getFactoryInfo('dispatch_filter');
395    $this->filters['dispatch'] = new $dffi['class']();
396    $this->filters['dispatch']->initialize($this->context, $dffi['parameters']);
397   
398    $effi = $this->context->getFactoryInfo('execution_filter');
399    $this->filters['execution'] = new $effi['class']();
400    $this->filters['execution']->initialize($this->context, $effi['parameters']);
401  }
402 
403  /**
404   * Get a filter.
405   *
406   * @param      string The name of the filter list section.
407   *
408   * @return     AgaviFilter A filter instance, or null.
409   *
410   * @author     David Zülke <dz@bitxtender.com>
411   * @since      0.11.0
412   */
413  public function getFilter($which)
414  {
415    return (isset($this->filters[$which]) ? $this->filters[$which] : null);
416  }
417 
418  /**
419   * Load filters.
420   *
421   * @param      AgaviFilterChain A FilterChain instance.
422   * @param      string           "global" or "action".
423   * @param      string           A module name, or "*" for the generic config.
424   *
425   * @author     David Zülke <dz@bitxtender.com>
426   * @since      0.11.0
427   */
428  public function loadFilters(AgaviFilterChain $filterChain, $which = 'global', $module = null)
429  {
430    if($module === null) {
431      $module = '*';
432    }
433   
434    if(($which != 'global' && !isset($this->filters[$which][$module])) || $which == 'global' && $this->filters[$which] == null) {
435      if($which == 'global') {
436        $this->filters[$which] = array();
437        $filters =& $this->filters[$which];
438      } else {
439        $this->filters[$which][$module] = array();
440        $filters =& $this->filters[$which][$module];
441      }
442      $config = ($module == '*' ? AgaviConfig::get('core.config_dir') : AgaviConfig::get('core.module_dir') . '/' . $module . '/config') . '/' . $which . '_filters.xml';
443      if(is_readable($config)) {
444        require(AgaviConfigCache::checkConfig($config, $this->context->getName()));
445      }
446    } else {
447      if($which == 'global') {
448        $filters =& $this->filters[$which];
449      } else {
450        $filters =& $this->filters[$which][$module];
451      }
452    }
453   
454    foreach($filters as $filter) {
455      $filterChain->register($filter);
456    }
457  }
458
459  /**
460   * Indicates whether or not a module has a specific model.
461   *
462   * @param      string A module name.
463   * @param      string A model name.
464   *
465   * @return     bool true, if the model exists, otherwise false.
466   *
467   * @author     Sean Kerr <skerr@mojavi.org>
468   * @since      0.9.0
469   */
470  public function modelExists($moduleName, $modelName)
471  {
472    $file = AgaviConfig::get('core.module_dir') . '/' . $moduleName . '/models/' . $modelName . 'Model.class.php';
473
474    return is_readable($file);
475  }
476
477  /**
478   * Indicates whether or not a module exists.
479   *
480   * @param      string A module name.
481   *
482   * @return     bool true, if the module exists, otherwise false.
483   *
484   * @author     Sean Kerr <skerr@mojavi.org>
485   * @since      0.9.0
486   */
487  public function moduleExists($moduleName)
488  {
489    $file = AgaviConfig::get('core.module_dir') . '/' . $moduleName . '/config/module.xml';
490
491    return is_readable($file);
492  }
493
494  /**
495   * Do any necessary startup work after initialization.
496   *
497   * This method is not called directly after initialize().
498   *
499   * @author     David Zülke <dz@bitxtender.com>
500   * @since      0.11.0
501   */
502  public function startup()
503  {
504    // grab a pointer to the request data
505    $this->requestData = $this->context->getRequest()->getRequestData();
506  }
507
508  /**
509   * Execute the shutdown procedure for this controller.
510   *
511   * @author     Sean Kerr <skerr@mojavi.org>
512   * @since      0.9.0
513   */
514  public function shutdown()
515  {
516  }
517
518  /**
519   * Indicates whether or not a module has a specific view.
520   *
521   * @param      string A module name.
522   * @param      string A view name.
523   *
524   * @return     bool true, if the view exists, otherwise false.
525   *
526   * @author     Sean Kerr <skerr@mojavi.org>
527   * @since      0.9.0
528   */
529  public function viewExists($moduleName, $viewName)
530  {
531    $viewName = str_replace('.', '/', $viewName);
532    $file = AgaviConfig::get('core.module_dir') . '/' . $moduleName . '/views/' . $viewName . 'View.class.php';
533    return is_readable($file);
534  }
535 
536  /**
537   * Retrieve an Output Type object
538   *
539   * @param      string The optional output type name.
540   *
541   * @return     AgaviOutputType An Output Type object.
542   *
543   * @author     David Zülke <dz@bitxtender.com>
544   * @since      0.11.0
545   */
546  public function getOutputType($name = null)
547  {
548    if($name === null) {
549      $name = $this->defaultOutputType;
550    }
551    if(isset($this->outputTypes[$name])) {
552      return $this->outputTypes[$name];
553    } else {
554      throw new AgaviException('Output Type "' . $name . '" has not been configured.');
555    }
556  }
557}
558
559?>
Note: See TracBrowser for help on using the browser.