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

Revision 2772, 20.2 KB (checked in by david, 3 months ago)

Fixed #839: module.xml must not be per-context

  • 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// |                                                                           |
7// | For the full copyright and license information, please view the LICENSE   |
8// | file that was distributed with this source code. You can also view the    |
9// | LICENSE file online at http://www.agavi.org/LICENSE.txt                   |
10// |   vi: set noexpandtab:                                                    |
11// |   Local Variables:                                                        |
12// |   indent-tabs-mode: t                                                     |
13// |   End:                                                                    |
14// +---------------------------------------------------------------------------+
15
16/**
17 * A container used for each action execution that holds neecessary information,
18 * such as the output type, the response etc.
19 *
20 * @package    agavi
21 * @subpackage controller
22 *
23 * @author     David Zülke <dz@bitxtender.com>
24 * @copyright  Authors
25 * @copyright  The Agavi Project
26 *
27 * @since      0.11.0
28 *
29 * @version    $Id$
30 */
31class AgaviExecutionContainer extends AgaviAttributeHolder
32{
33  /**
34   * @var        AgaviContext The context instance.
35   */
36  protected $context = null;
37
38  /**
39   * @var        AgaviValidationManager The validation manager instance.
40   */
41  protected $validationManager = null;
42
43  /**
44   * @var        AgaviRequestDataHolder A request data holder with request info.
45   */
46  private $requestData = null;
47
48  /**
49   * @var        AgaviRequestDataHolder A pointer to the global request data.
50   */
51  private $globalRequestData = null;
52
53  /**
54   * @var        AgaviRequestDataHolder A request data holder with arguments.
55   */
56  protected $arguments = null;
57
58  /**
59   * @var        AgaviResponse A response instance holding the Action's output.
60   */
61  protected $response = null;
62
63  /**
64   * @var        AgaviOutputType The output type for this container.
65   */
66  protected $outputType = null;
67
68  /**
69   * @var        float The microtime at which this container was initialized.
70   */
71  protected $microtime = null;
72
73  /**
74   * @var        AgaviAction The Action instance that belongs to this container.
75   */
76  protected $actionInstance = null;
77
78  /**
79   * @var        AgaviView The View instance that belongs to this container.
80   */
81  protected $viewInstance = null;
82
83  /**
84   * @var        string The name of the Action's Module.
85   */
86  protected $moduleName = null;
87
88  /**
89   * @var        string The name of the Action.
90   */
91  protected $actionName = null;
92
93  /**
94   * @var        string Name of the module of the View returned by the Action.
95   */
96  protected $viewModuleName = null;
97
98  /**
99   * @var        string The name of the View returned by the Action.
100   */
101  protected $viewName = null;
102
103  /**
104   * @var        AgaviExecutionContainer The next container to execute.
105   */
106  protected $next = null;
107
108  /**
109   * Pre-serialization callback.
110   *
111   * Will set the name of the context instead of the instance, and the name of
112   * the output type instead of the instance. Both will be restored by __wakeup
113   *
114   * @author     David Zülke <dz@bitxtender.com>
115   * @since      0.11.0
116   */
117  public function __sleep()
118  {
119    $this->contextName = $this->context->getName();
120    $this->outputTypeName = $this->outputType->getName();
121    $arr = get_object_vars($this);
122    unset($arr['context'], $arr['outputType'], $arr['requestData'], $arr['globalRequestData']);
123    return array_keys($arr);
124  }
125
126  /**
127   * Post-unserialization callback.
128   *
129   * Will restore the context and output type instances based on their names set
130   * by __sleep.
131   *
132   * @author     David Zülke <dz@bitxtender.com>
133   * @since      0.11.0
134   */
135  public function __wakeup()
136  {
137    $this->context = AgaviContext::getInstance($this->contextName);
138    $this->outputType = $this->context->getController()->getOutputType($this->outputTypeName);
139    try {
140      $this->globalRequestData = $this->context->getRequest()->getRequestData();
141    } catch(AgaviException $e) {
142      $this->globalRequestData = new AgaviRequestDataHolder();
143    }
144    unset($this->contextName, $this->outputTypeName);
145  }
146
147  /**
148   * Initialize the container. This will create a response instance.
149   *
150   * @param      AgaviContext The current Context instance.
151   * @param      array        An array of initialization parameters.
152   *
153   * @author     David Zülke <dz@bitxtender.com>
154   * @since      0.11.0
155   */
156  public function initialize(AgaviContext $context, array $parameters = array())
157  {
158    $this->microtime = microtime(true);
159
160    $this->context = $context;
161
162    $this->parameters = $parameters;
163
164    $rfi = $this->context->getFactoryInfo('response');
165    $this->response = new $rfi['class'];
166    $this->response->initialize($this->context, $rfi['parameters']);
167  }
168
169  /**
170   * Creates a new container instance with the same output type as this one.
171   *
172   * @param      string                 The name of the module.
173   * @param      string                 The name of the action.
174   * @param      AgaviRequestDataHolder A RequestDataHolder with additional
175   *                                    request arguments.
176   * @param      string                 Optional name of an initial output type
177   *                                    to set.
178   *
179   * @return     AgaviExecutionContainer A new execution container instance,
180   *                                     fully initialized.
181   *
182   * @author     David Zülke <dz@bitxtender.com>
183   * @since      0.11.0
184   */
185  public function createExecutionContainer($moduleName = null, $actionName = null, AgaviRequestDataHolder $arguments = null, $outputType = null)
186  {
187    if($outputType === null) {
188      $outputType = $this->getOutputType()->getName();
189    }
190   
191    $container = $this->context->getController()->createExecutionContainer($moduleName, $actionName, $arguments, $outputType);
192   
193    // copy over parameters (could be is_slot, is_forward etc)
194    $container->setParameters($this->getParameters());
195   
196    return $container;
197  }
198
199  /**
200   * Start execution.
201   *
202   * This will create an instance of the action and merge in request parameters.
203   *
204   * This method returns a response. It is not necessarily the same response as
205   * the one of this container, but instead the one that contains the actual
206   * content that should be used for output etc, since the container's own
207   * response might be empty or invalid due to a "next" container that has been
208   * set and executed.
209   *
210   * @return     AgaviResponse The "real" response.
211   *
212   * @author     David Zülke <dz@bitxtender.com>
213   * @since      0.11.0
214   */
215  public function execute()
216  {
217    $controller = $this->context->getController();
218
219    $request = $this->context->getRequest();
220
221    $controller->countExecution();
222
223    $moduleName = $this->getModuleName();
224    $actionName = $this->getActionName();
225
226    if(!AgaviConfig::get('core.available', false)) {
227      // application is unavailable
228      $request->setAttributes(array(
229        'requested_module' => $moduleName,
230        'requested_action' => $actionName
231      ), 'org.agavi.controller.forwards.unavailable');
232      $moduleName = AgaviConfig::get('actions.unavailable_module');
233      $actionName = AgaviConfig::get('actions.unavailable_action');
234
235      try {
236        $actionName = $controller->resolveAction($moduleName, $actionName);
237      } catch(AgaviControllerException $e) {
238        $error = 'Invalid configuration settings: actions.unavailable_module "%s", actions.unavailable_action "%s"';
239        $error = sprintf($error, $moduleName, $actionName);
240        throw new AgaviConfigurationException($error);
241      }
242
243    } else {
244      try {
245        $actionName = $controller->resolveAction($moduleName, $actionName);
246      } catch(AgaviControllerException $e) {
247        // track the requested module so we have access to the data
248        // in the error 404 page
249        $request->setAttributes(array(
250          'requested_module' => $moduleName,
251          'requested_action' => $actionName,
252          'exception' => $e,
253        ), 'org.agavi.controller.forwards.error_404');
254
255        // switch to error 404 action
256        $moduleName = AgaviConfig::get('actions.error_404_module');
257        $actionName = AgaviConfig::get('actions.error_404_action');
258
259        try {
260          $actionName = $controller->resolveAction($moduleName, $actionName);
261        } catch(AgaviControllerException $e) {
262          // cannot find unavailable module/action
263          $error = 'Invalid configuration settings: actions.error_404_module "%s", actions.error_404_action "%s"';
264          $error = sprintf($error, $moduleName, $actionName);
265
266          throw new AgaviConfigurationException($error);
267        }
268      }
269    }
270
271    $this->setModuleName($moduleName);
272    $this->setActionName($actionName);
273
274    // include the module configuration
275    // loaded only once due to the way load() (former import()) works
276    if(is_readable(AgaviConfig::get('core.module_dir') . '/' . $moduleName . '/config/module.xml')) {
277      AgaviConfigCache::load(AgaviConfig::get('core.module_dir') . '/' . $moduleName . '/config/module.xml');
278    } else {
279      AgaviConfig::set('modules.' . strtolower($moduleName) . '.enabled', true);
280    }
281
282    // save autoloads so we can restore them later
283    $oldAutoloads = Agavi::$autoloads;
284
285    static $moduleAutoloads = array();
286    if(!isset($moduleAutoloads[$moduleName])) {
287      $moduleAutoloads[$moduleName] = array();
288      $moduleAutoload = AgaviConfig::get('core.module_dir') . '/' . $moduleName . '/config/autoload.xml';
289      if(is_readable($moduleAutoload)) {
290        include(AgaviConfigCache::checkConfig($moduleAutoload));
291        $moduleAutoloads[$moduleName] = Agavi::$autoloads;
292      }
293    } else {
294      Agavi::$autoloads = array_merge($moduleAutoloads[$moduleName], Agavi::$autoloads);
295    }
296
297    if(AgaviConfig::get('modules.' . strtolower($moduleName) . '.enabled')) {
298      // check for a module config.php
299      $moduleConfig = AgaviConfig::get('core.module_dir') . '/' . $moduleName . '/config.php';
300      if(is_readable($moduleConfig)) {
301        require_once($moduleConfig);
302      }
303
304      $this->actionInstance = $controller->createActionInstance($this->moduleName, $this->actionName);
305
306      // initialize the action
307      $this->actionInstance->initialize($this);
308
309      if($this->actionInstance->isSimple()) {
310        if($this->arguments !== null) {
311          // clone it so mutating it has no effect on the "outside world"
312          $this->requestData = clone $this->arguments;
313        } else {
314          $rdhc = $request->getParameter('request_data_holder_class');
315          $this->requestData = new $rdhc();
316        }
317        // run the execution filter, without a proper chain
318        $controller->getFilter('execution')->execute(new AgaviFilterChain(), $this);
319      } else {
320        // mmmh I smell awesomeness... clone the RD JIT, yay, that's the spirit
321        $this->requestData = clone $this->globalRequestData;
322
323        if($this->arguments !== null) {
324          $this->requestData->merge($this->arguments);
325        }
326
327        // create a new filter chain
328        $fcfi = $this->context->getFactoryInfo('filter_chain');
329        $filterChain = new $fcfi['class']();
330        $filterChain->initialize($this->context, $fcfi['parameters']);
331
332        if(AgaviConfig::get('core.available', false)) {
333          // the application is available so we'll register
334          // global and module filters, otherwise skip them
335
336          // does this action require security?
337          if(AgaviConfig::get('core.use_security', false) && $this->actionInstance->isSecure()) {
338            // register security filter
339            $filterChain->register($controller->getFilter('security'));
340          }
341
342          // load filters
343          $controller->loadFilters($filterChain, 'action');
344          $controller->loadFilters($filterChain, 'action', $moduleName);
345        }
346
347        // register the execution filter
348        $filterChain->register($controller->getFilter('execution'));
349
350        // process the filter chain
351        $filterChain->execute($this);
352      }
353
354      // restore autoloads
355      Agavi::$autoloads = $oldAutoloads;
356
357    } else {
358
359      $request->setAttributes(array(
360        'requested_module' => $moduleName,
361        'requested_action' => $actionName
362      ), 'org.agavi.controller.forwards.module_disabled');
363      $moduleName = AgaviConfig::get('actions.module_disabled_module');
364      $actionName = AgaviConfig::get('actions.module_disabled_action');
365
366      try {
367        $actionName = $controller->resolveAction($moduleName, $actionName);
368      } catch(AgaviControllerException $e) {
369        // cannot find mod disabled module/action
370        $error = 'Invalid configuration settings: actions.module_disabled_module "%s", actions.module_disabled_action "%s"';
371        $error = sprintf($error, $moduleName, $actionName);
372        throw new AgaviConfigurationException($error);
373      }
374
375      $this->setNext($this->createExecutionContainer($moduleName, $actionName));
376    }
377
378    if($this->next !== null) {
379      return $this->next->execute();
380    } else {
381      return $this->getResponse();
382    }
383  }
384
385  /**
386   * Get the Context.
387   *
388   * @return     AgaviContext The Context.
389   *
390   * @author     David Zülke <dz@bitxtender.com>
391   * @since      0.11.0
392   */
393  public final function getContext()
394  {
395    return $this->context;
396  }
397
398  /**
399   * Retrieve the ValidationManager
400   *
401   * @return     AgaviValidationManager The container's ValidationManager
402   *                                    implementation instance.
403   *
404   * @author     David Zülke <dz@bitxtender.com>
405   * @since      0.11.0
406   */
407  public function getValidationManager()
408  {
409    if($this->validationManager === null) {
410      $vmfi = $this->context->getFactoryInfo('validation_manager');
411      $this->validationManager = new $vmfi['class']();
412      $this->validationManager->initialize($this->context, $vmfi['parameters']);
413    }
414    return $this->validationManager;
415  }
416
417  /**
418   * Retrieve this container's request data holder instance.
419   *
420   * @return     AgaviRequestDataHolder The request data holder.
421   *
422   * @author     David Zülke <dz@bitxtender.com>
423   * @since      0.11.0
424   */
425  public final function getRequestData()
426  {
427    return $this->requestData;
428  }
429
430  /**
431   * Set this container's global request data holder reference.
432   *
433   * @param      AgaviRequestDataHolder The request data holder.
434   *
435   * @author     David Zülke <dz@bitxtender.com>
436   * @since      0.11.0
437   */
438  public final function setRequestData(AgaviRequestDataHolder $rd)
439  {
440    $this->globalRequestData = $rd;
441  }
442
443  /**
444   * Get this container's request data holder instance for additional arguments.
445   *
446   * @return     AgaviRequestDataHolder The additional arguments.
447   *
448   * @author     David Zülke <dz@bitxtender.com>
449   * @since      0.11.0
450   */
451  public function getArguments()
452  {
453    return $this->arguments;
454  }
455
456  /**
457   * Set this container's request data holder instance for additional arguments.
458   *
459   * @return     AgaviRequestDataHolder The request data holder.
460   *
461   * @author     David Zülke <dz@bitxtender.com>
462   * @since      0.11.0
463   */
464  public function setArguments(AgaviRequestDataHolder $arguments)
465  {
466    $this->arguments = $arguments;
467  }
468
469  /**
470   * Retrieve this container's response instance.
471   *
472   * @return     AgaviResponse The Response instance for this action.
473   *
474   * @author     David Zülke <dz@bitxtender.com>
475   * @since      0.11.0
476   */
477  public function getResponse()
478  {
479    return $this->response;
480  }
481
482  /**
483   * Set a new response.
484   *
485   * @param      AgaviResponse A new Response instance.
486   *
487   * @author     David Zülke <dz@bitxtender.com>
488   * @since      0.11.0
489   */
490  public function setResponse(AgaviResponse $response)
491  {
492    $this->response = $response;
493    // do not set the output type on the response here!
494  }
495
496  /**
497   * Retrieve the output type of this container.
498   *
499   * @return     AgaviOutputType The output type object.
500   *
501   * @author     David Zülke <dz@bitxtender.com>
502   * @since      0.11.0
503   */
504  public function getOutputType()
505  {
506    return $this->outputType;
507  }
508
509  /**
510   * Set a different output type for this container.
511   *
512   * @param      AgaviOutputType An output type object.
513   *
514   * @author     David Zülke <dz@bitxtender.com>
515   * @since      0.11.0
516   */
517  public function setOutputType(AgaviOutputType $outputType)
518  {
519    $this->outputType = $outputType;
520    if($this->response) {
521      $this->response->setOutputType($outputType);
522    }
523  }
524
525  /**
526   * Retrieve this container's microtime.
527   *
528   * @return     string A string representing the microtime this container was
529   *                    initialized.
530   *
531   * @author     David Zülke <dz@bitxtender.com>
532   * @since      0.11.0
533   */
534  public function getMicrotime()
535  {
536    return $this->microtime;
537  }
538
539  /**
540   * Retrieve this container's action instance.
541   *
542   * @return     AgaviAction An action implementation instance.
543   *
544   * @author     David Zülke <dz@bitxtender.com>
545   * @since      0.11.0
546   */
547  public function getActionInstance()
548  {
549    return $this->actionInstance;
550  }
551
552  /**
553   * Retrieve this container's view instance.
554   *
555   * @return     AgaviView A view implementation instance.
556   *
557   * @author     Ross Lawley <ross.lawley@gmail.com>
558   * @since      0.11.0
559   */
560  public function getViewInstance()
561  {
562    return $this->viewInstance;
563  }
564
565  /**
566   * Set this container's view instance.
567   *
568   * @param      AgaviView A view implementation instance.
569   *
570   * @author     Ross Lawley <ross.lawley@gmail.com>
571   * @since      0.11.0
572   */
573  public function setViewInstance($viewInstance)
574  {
575    return $this->viewInstance = $viewInstance;
576  }
577
578  /**
579   * Retrieve this container's module name.
580   *
581   * @return     string A module name.
582   *
583   * @author     David Zülke <dz@bitxtender.com>
584   * @since      0.11.0
585   */
586  public function getModuleName()
587  {
588    return $this->moduleName;
589  }
590
591  /**
592   * Retrieve this container's action name.
593   *
594   * @return     string An action name.
595   *
596   * @author     David Zülke <dz@bitxtender.com>
597   * @since      0.11.0
598   */
599  public function getActionName()
600  {
601    return $this->actionName;
602  }
603
604  /**
605   * Retrieve this container's view module name. This is the name of the module of
606   * the View returned by the Action.
607   *
608   * @return     string A view module name.
609   *
610   * @author     David Zülke <dz@bitxtender.com>
611   * @since      0.11.0
612   */
613  public function getViewModuleName()
614  {
615    return $this->viewModuleName;
616  }
617
618  /**
619   * Retrieve this container's view name.
620   *
621   * @return     string A view name.
622   *
623   * @author     David Zülke <dz@bitxtender.com>
624   * @since      0.11.0
625   */
626  public function getViewName()
627  {
628    return $this->viewName;
629  }
630
631  /**
632   * Set the module name for this container.
633   *
634   * @param      string A module name.
635   *
636   * @author     David Zülke <dz@bitxtender.com>
637   * @since      0.11.0
638   */
639  public function setModuleName($moduleName)
640  {
641    $this->moduleName = preg_replace('/[^a-z0-9\-_]+/i', '', $moduleName);
642  }
643
644  /**
645   * Set the action name for this container.
646   *
647   * @param      string An action name.
648   *
649   * @author     David Zülke <dz@bitxtender.com>
650   * @since      0.11.0
651   */
652  public function setActionName($actionName)
653  {
654    $this->actionName = preg_replace(array('/\./', '/[^a-z0-9\-_\/]+/i'), array('/', ''), $actionName);
655  }
656
657  /**
658   * Set the view module name for this container.
659   *
660   * @param      string A view module name.
661   *
662   * @author     David Zülke <dz@bitxtender.com>
663   * @since      0.11.0
664   */
665  public function setViewModuleName($viewModuleName)
666  {
667    $this->viewModuleName = $viewModuleName;
668  }
669
670  /**
671   * Set the module name for this container.
672   *
673   * @param      string A view name.
674   *
675   * @author     David Zülke <dz@bitxtender.com>
676   * @since      0.11.0
677   */
678  public function setViewName($viewName)
679  {
680    $this->viewName = $viewName;
681  }
682
683   /**
684   * Check if a "next" container has been set.
685   *
686   * @return     bool True, if a container for eventual execution has been set.
687   *
688   * @author     David Zülke <dz@bitxtender.com>
689   * @since      0.11.0
690   */
691  public function hasNext()
692  {
693    return $this->next !== null;
694  }
695
696  /**
697   * Get the "next" container.
698   *
699   * @return     AgaviExecutionContainer The "next" container, of null if unset.
700   *
701   * @author     David Zülke <dz@bitxtender.com>
702   * @since      0.11.0
703   */
704  public function getNext()
705  {
706    return $this->next;
707  }
708
709  /**
710   * Set the container that should be executed once this one finished running.
711   *
712   * @param      AgaviExecutionContainer An execution container instance.
713   *
714   * @author     David Zülke <dz@bitxtender.com>
715   * @since      0.11.0
716   */
717  public function setNext(AgaviExecutionContainer $container)
718  {
719    $this->next = $container;
720  }
721
722  /**
723   * Remove a possibly set "next" container.
724   *
725   * @return     AgaviExecutionContainer The removed "next" container, or null
726   *                                     if none had been set.
727   *
728   * @author     David Zülke <dz@bitxtender.com>
729   * @since      0.11.0
730   */
731  public function clearNext()
732  {
733    $retval = $this->next;
734    $this->next = null;
735    return $retval;
736  }
737}
738
739?>
Note: See TracBrowser for help on using the browser.