Show
Ignore:
Timestamp:
01/01/07 19:42:27 (2 years ago)
Author:
david
Message:

work in progress: new execution flow, action stack is gone, decoration doesn't require special abilities of the renderer anymore, but still needs further abstraction. decorators in decorators (for slots) should work now, and slots are available in the main content template. also, caching for execution filter is in place, albeit not enabled/complete yet. refs #373, #377, #287 and #290

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • branches/david-execution_flow/src/controller/AgaviController.class.php

    r1236 r1448  
    3030{ 
    3131  /** 
     32   * @var        int The number of forward() calls done so far. 
     33   */ 
     34  protected $numForwards = 0; 
     35   
     36  /** 
    3237   * @var        int The maximum number of times this Controller will forward(). 
    3338   */ 
    3439  protected $maxForwards  = 20; 
    3540   
    36   /** 
    37    * @var        int The render mode, see AgaviView RENDER_* constants. 
    38    */ 
    39   protected $renderMode   = AgaviView::RENDER_CLIENT; 
    40  
    41   /** 
    42    * @var        AgaviActionStack An ActionStack instance. 
    43    */ 
    44   protected $actionStack = null; 
    45  
    4641  /** 
    4742   * @var        AgaviContext An AgaviContext instance. 
     
    5550    'global' => array(), 
    5651    'action' => array( 
    57       '*' => null 
    58     ), 
    59     'rendering' => array( 
    6052      '*' => null 
    6153    ), 
     
    7668   
    7769  /** 
    78    * @var        AgaviResponse The Response instance for this Controller. 
    79    */ 
    80   protected $response = null; 
    81    
    82   /** 
    83    * @var        AgaviResponse The Response to be used after redirects are set. 
    84    */ 
    85   protected $redirectResponse = null; 
    86  
    87   /** 
    88    * Retrieve the ActionStack. 
    89    * 
    90    * @return     AgaviActionStack the ActionStack instance 
    91    * 
    92    * @author     David Zuelke <dz@bitxtender.com> 
    93    * @since      0.11.0 
    94    */ 
    95   public function getActionStack() 
    96   { 
    97     return $this->actionStack; 
    98   } 
    99  
    100   /** 
    10170   * Indicates whether or not a module has a specific action. 
    10271   * 
     
    10473   * @param      string An action name. 
    10574   * 
    106    * @return     mixed  The actual name of the action (might be auto-resolved), 
    107    *                    or false if no Action related to that name was found. 
    108    * 
    109    * @author     Sean Kerr <skerr@mojavi.org> 
    110    * @author     David Zuelke <dz@bitxtender.com> 
    111    * @since      0.9.0 
    112    */ 
    113   public function actionExists($moduleName, $actionName = null) 
     75   * @return     mixed  The actual name of the action (might be auto-resolved). 
     76   * 
     77   * @throws     AgaviControllerException if the action could not be found. 
     78   * @author     Sean Kerr <skerr@mojavi.org> 
     79   * @author     David Zuelke <dz@bitxtender.com> 
     80   * @since      0.9.0 
     81   */ 
     82  public function resolveAction($moduleName, $actionName = null) 
    11483  { 
    11584    $actionName = str_replace('.', '/', $actionName); 
     
    12392      if(is_readable($file)) { 
    12493        return $actionName; 
    125       } else { 
    126         return false; 
    127       } 
    128     } 
     94      } 
     95    } 
     96    throw new AgaviControllerException(sprintf('Action "%s" in Module "%s" could not be found.', $actionName, $moduleName)); 
     97  } 
     98   
     99  public function incNumForwards() 
     100  { 
     101    if(++$this->numForwards > $this->maxForwards) { 
     102      throw new AgaviForwardException('Too many forwards have been detected for this request.'); 
     103    } 
     104  } 
     105   
     106  /** 
     107   * Create and initialize new execution container instance. 
     108   * 
     109   * @param      string The name of the module. 
     110   * @param      string The name of the action. 
     111   * @param      array  Optional additional parameters. 
     112   * 
     113   * @return     AgaviExecutionContainer A new execution container instance, 
     114   *                                     fully initialized. 
     115   * 
     116   * @author     David Zuelke <dz@bitxtender.com> 
     117   * @since      0.11.0 
     118   */ 
     119  public function createExecutionContainer($moduleName, $actionName, array $parameters = array()) 
     120  { 
     121    $container = new AgaviExecutionContainer(); 
     122    $container->initialize($this->context, $moduleName, $actionName, $parameters); 
     123    return $container; 
    129124  } 
    130125   
     
    146141      $request->setAttribute('matchedRoutes', $this->context->getRouting()->execute(), 'org.agavi.routing'); 
    147142     
    148       if($parameters != null) { 
    149         $request->setParametersByRef($parameters); 
    150       } 
     143      $request->setParameters($parameters); 
    151144     
    152145      // determine our module and action 
     
    171164      } 
    172165       
     166      $container = $this->createExecutionContainer($moduleName, $actionName); 
     167       
    173168      // create a new filter chain 
    174169      $fcfi = $this->context->getFactoryInfo('filter_chain'); 
    175170      $filterChain = new $fcfi['class'](); 
    176       $filterChain->initialize($this->response, $fcfi['parameters']); 
     171      $filterChain->initialize($this->context, $fcfi['parameters']); 
    177172       
    178173      $this->loadFilters($filterChain, 'global'); 
     
    182177     
    183178      // go, go, go! 
    184       $filterChain->execute(); 
    185        
    186       if($this->redirectResponse instanceof AgaviResponse) { 
    187         $this->redirectResponse->append($this->response->export()); 
    188         $this->redirectResponse->send(); 
     179      $filterChain->execute($container); 
     180       
     181      $container->getResponse()->send(); 
     182       
     183    } catch(Exception $e) { 
     184      if(isset($container) && $container instanceof AgaviExecutionContainer && $container->getResponse() instanceof AgaviResponse) { 
     185        AgaviException::printStackTrace($e, $this->context, $response); 
    189186      } else { 
    190         $this->response->send(); 
    191       } 
    192        
    193     } catch(Exception $e) { 
    194       AgaviException::printStackTrace($e, $this->context, $this->getResponse()); 
    195     } 
    196   } 
    197  
    198   /** 
    199    * Forward the request to another action. 
    200    * 
    201    * @param      string A module name. 
    202    * @param      string An action name. 
    203    * @param      array|AgaviParameterHolder Additional parameters which will be 
    204    *                                        passed to the action. 
    205    * 
    206    * @throws     <b>AgaviConfigurationException</b> If an invalid configuration  
    207    *                                                setting has been found. 
    208    * @throws     <b>AgaviForwardException</b> If an error occurs while  
    209    *                                          forwarding the request. 
    210    * @throws     <b>AgaviInitializationException</b> If the action could not be 
    211    *                                                 initialized. 
    212    * @throws     <b>AgaviSecurityException</b> If the action requires security  
    213    *                                           but the user implementation is  
    214    *                                           not of type SecurityUser. 
    215    * 
    216    * @author     Sean Kerr <skerr@mojavi.org> 
    217    * @since      0.9.0 
    218    */ 
    219   public function forward($moduleName, $actionName = 'Index', $additionalParams = array()) 
    220   { 
    221     $request = $this->context->getRequest(); 
    222  
    223     $actionName = str_replace('.', '/', $actionName); 
    224     $actionName = preg_replace('/[^a-z0-9\-_\/]+/i', '', $actionName); 
    225     $moduleName = preg_replace('/[^a-z0-9\-_]+/i', '', $moduleName); 
    226  
    227     if($this->actionStack->getSize() >= $this->maxForwards) { 
    228       throw new AgaviForwardException('Too many forwards have been detected for this request'); 
    229     } 
    230  
    231     if(!AgaviConfig::get('core.available', false)) { 
    232       // application is unavailable 
    233       $request->setAttributes(array( 
    234         'requested_module' => $moduleName, 
    235         'requested_action' => $actionName 
    236       ), 'org.agavi.controller.forwards.unavailable'); 
    237       $moduleName = AgaviConfig::get('actions.unavailable_module'); 
    238       $actionName = AgaviConfig::get('actions.unavailable_action'); 
    239  
    240       if(!$this->actionExists($moduleName, $actionName)) { 
    241         // cannot find unavailable module/action 
    242         $error = 'Invalid configuration settings: actions.unavailable_module "%s", actions.unavailable_action "%s"'; 
    243         $error = sprintf($error, $moduleName, $actionName); 
    244  
    245         throw new AgaviConfigurationException($error); 
    246       } 
    247  
    248     } elseif(!$this->actionExists($moduleName, $actionName)) { 
    249       // the requested action doesn't exist 
    250  
    251       // track the requested module so we have access to the data 
    252       // in the error 404 page 
    253       $request->setAttributes(array( 
    254         'requested_module' => $moduleName, 
    255         'requested_action' => $actionName 
    256       ), 'org.agavi.controller.forwards.error_404'); 
    257  
    258       // switch to error 404 action 
    259       $moduleName = AgaviConfig::get('actions.error_404_module'); 
    260       $actionName = AgaviConfig::get('actions.error_404_action'); 
    261  
    262       if(!$this->actionExists($moduleName, $actionName)) { 
    263         // cannot find unavailable module/action 
    264         $error = 'Invalid configuration settings: actions.error_404_module "%s", actions.error_404_action "%s"'; 
    265         $error = sprintf($error, $moduleName, $actionName); 
    266  
    267         throw new AgaviConfigurationException($error); 
    268       } 
    269     } 
    270      
    271     // get the "real" action name, i.e. allow auto-resolving of sub-action IndexActions 
    272     $actionName = $this->actionExists($moduleName, $actionName); 
    273  
    274     // create an instance of the action 
    275     $actionInstance = $this->getAction($moduleName, $actionName); 
    276  
    277     // add a new action stack entry 
    278     $actionEntry = $this->actionStack->addEntry($moduleName, $actionName, $actionInstance, new AgaviParameterHolder(array_merge($request->getParameters(), $additionalParams instanceof AgaviParameterHolder ? $additionalParams->getParameters() : (array) $additionalParams))); 
    279  
    280     // include the module configuration 
    281     // laoded only once due to the way import() works 
    282     if(is_readable(AgaviConfig::get('core.module_dir') . '/' . $moduleName . '/config/module.xml')) { 
    283       AgaviConfigCache::import(AgaviConfig::get('core.module_dir') . '/' . $moduleName . '/config/module.xml', $this->context->getName()); 
    284     } else { 
    285       AgaviConfig::set('modules.' . strtolower($moduleName) . '.enabled', true); 
    286     } 
    287  
    288     // save autoloads so we can restore them later 
    289     $oldAutoloads = Agavi::$autoloads; 
    290      
    291     static $moduleAutoloads = array(); 
    292     if(!isset($moduleAutoloads[$moduleName])) { 
    293       $moduleAutoloads[$moduleName] = array(); 
    294       $moduleAutoload = AgaviConfig::get('core.module_dir') . '/' . $moduleName . '/config/autoload.xml'; 
    295       if(is_readable($moduleAutoload)) { 
    296         include(AgaviConfigCache::checkConfig($moduleAutoload)); 
    297         $moduleAutoloads[$moduleName] = Agavi::$autoloads; 
    298       } 
    299     } else { 
    300       Agavi::$autoloads = array_merge($moduleAutoloads[$moduleName], Agavi::$autoloads); 
    301     } 
    302      
    303     if(AgaviConfig::get('modules.' . strtolower($moduleName) . '.enabled')) { 
    304       // check for a module config.php 
    305       $moduleConfig = AgaviConfig::get('core.module_dir') . '/' . $moduleName . '/config.php'; 
    306       if(is_readable($moduleConfig)) { 
    307         require_once($moduleConfig); 
    308       } 
    309  
    310       // initialize the action 
    311       $actionInstance->initialize($this->context); 
    312        
    313       // create a new response instance for this action 
    314       $rfi = $this->context->getFactoryInfo('response'); 
    315       $response = new $rfi['class']; 
    316       $response->initialize($this->context, $rfi['parameters']); 
    317  
    318       // create a new filter chain 
    319       $fcfi = $this->context->getFactoryInfo('filter_chain'); 
    320       $filterChain = new $fcfi['class'](); 
    321       $filterChain->initialize($response, $fcfi['parameters']); 
    322  
    323       if(AgaviConfig::get('core.available', false)) { 
    324         // the application is available so we'll register 
    325         // global and module filters, otherwise skip them 
    326  
    327         // does this action require security? 
    328         if(AgaviConfig::get('core.use_security', false) && $actionInstance->isSecure()) { 
    329           // register security filter 
    330           $filterChain->register($this->filters['security']); 
    331         } 
    332  
    333         // load filters 
    334         $this->loadFilters($filterChain, 'action'); 
    335         $this->loadFilters($filterChain, 'action', $moduleName); 
    336       } 
    337  
    338       // register the execution filter 
    339       $filterChain->register($this->filters['execution']); 
    340  
    341       // process the filter chain 
    342       $filterChain->execute(); 
    343        
    344       // clear the global request attribute namespace containing attributes for the View 
    345       $request->removeAttributeNamespace($request->getDefaultNamespace()); 
    346        
    347       if($this->renderMode == AgaviView::RENDER_CLIENT && !$actionEntry->hasNext()) { 
    348         // add the output for this action to the global one 
    349         $this->getResponse()->append($response->export()); 
    350       } 
    351        
    352       // restore autoloads 
    353       Agavi::$autoloads = $oldAutoloads; 
    354  
    355     } else { 
    356       // module is disabled 
    357       $request->setAttributes(array( 
    358         'requested_module' => $moduleName, 
    359         'requested_action' => $actionName 
    360       ), 'org.agavi.controller.forwards.disabled'); 
    361       $moduleName = AgaviConfig::get('actions.module_disabled_module'); 
    362       $actionName = AgaviConfig::get('actions.module_disabled_action'); 
    363  
    364       if(!$this->actionExists($moduleName, $actionName)) { 
    365         // cannot find mod disabled module/action 
    366         $error = 'Invalid configuration settings: actions.module_disabled_module "%s", actions.module_disabled_action "%s"'; 
    367         $error = sprintf($error, $moduleName, $actionName); 
    368         throw new AgaviConfigurationException($error); 
    369       } 
    370  
    371       $this->forward($moduleName, $actionName); 
    372     } 
    373      
    374     if($actionEntry->hasNext()) { 
    375       $next = $actionEntry->getNext(); 
    376       $request->setParameters($next['parameters'] instanceof AgaviParameterHolder ? $next['parameters']->getParameters() : $next['parameters']); 
    377       $this->forward($next['moduleName'], $next['actionName']); 
     187        AgaviException::printStackTrace($e, $this->context); 
     188      } 
    378189    } 
    379190  } 
     
    391202   */ 
    392203  abstract public function redirect($to); 
    393  
    394   /** 
    395    * Retrieve the currently executing Action's name. 
    396    * 
    397    * @return     string The currently executing action name, if one is set, 
    398    *                    otherwise null. 
    399    * 
    400    * @author     Sean Kerr <skerr@mojavi.org> 
    401    * @author     David Zuelke <dz@bitxtender.com> 
    402    * @since      0.11.0 
    403    */ 
    404   public function getActionName() 
    405   { 
    406     // get the last action stack entry 
    407     $actionEntry = $this->actionStack->getLastEntry(); 
    408  
    409     return $actionEntry->getActionName(); 
    410   } 
    411    
    412   /** 
    413    * Retrieve the currently executing Action's module directory. 
    414    * 
    415    * @return     string An absolute filesystem path to the directory of the 
    416    *                    currently executing module if set, otherwise null. 
    417    * 
    418    * @author     Sean Kerr <skerr@mojavi.org> 
    419    * @author     David Zuelke <dz@bitxtender.com> 
    420    * @since      0.11.0 
    421    */ 
    422   public function getModuleDirectory() 
    423   { 
    424     // get the last action stack entry 
    425     $actionEntry = $this->actionStack->getLastEntry(); 
    426  
    427     return AgaviConfig::get('core.module_dir') . '/' . $actionEntry->getModuleName(); 
    428   } 
    429  
    430   /** 
    431    * Retrieve the currently executing Action's module name. 
    432    * 
    433    * @return     string The currently executing module name, if one is set, 
    434    *                    otherwise null. 
    435    * 
    436    * @author     Sean Kerr <skerr@mojavi.org> 
    437    * @author     David Zuelke <dz@bitxtender.com> 
    438    * @since      0.11.0 
    439    */ 
    440   public function getModuleName() 
    441   { 
    442     // get the last action stack entry 
    443     $actionEntry = $this->actionStack->getLastEntry(); 
    444  
    445     return $actionEntry->getModuleName(); 
    446   } 
    447204 
    448205  /** 
     
    510267 
    511268  /** 
    512    * Retrieve the Response object. 
    513    * 
    514    * @return     AgaviResponse The current Response implementation instance. 
    515    * 
    516    * @author     David Zuelke <dz@bitxtender.com> 
    517    * @since      0.11.0 
    518    */ 
    519   protected final function getResponse() 
    520   { 
    521     return $this->response; 
    522   } 
    523  
    524   /** 
    525    * Retrieve the presentation rendering mode. 
    526    * 
    527    * @return     int One of the following: 
    528    *                 - AgaviView::RENDER_CLIENT 
    529    *                 - AgaviView::RENDER_VAR 
    530    * 
    531    * @author     Sean Kerr <skerr@mojavi.org> 
    532    * @since      0.9.0 
    533    */ 
    534   public function getRenderMode() 
    535   { 
    536     return $this->renderMode; 
    537   } 
    538  
    539   /** 
    540269   * Retrieve a View implementation instance. 
    541270   * 
     
    623352    $this->filters['execution'] = new $effi['class'](); 
    624353    $this->filters['execution']->initialize($this->context, $effi['parameters']); 
    625      
     354  } 
     355   
     356  public function getFilter($which) 
     357  { 
     358    return (isset($this->filters[$which]) ? $this->filters[$which] : null); 
    626359  } 
    627360   
     
    630363   * 
    631364   * @param      AgaviFilterChain A FilterChain instance. 
    632    * @param      string           "global", "action" or "rendering". 
     365   * @param      string           "global" or "action". 
    633366   * @param      string           A module name, or "*" for the generic config. 
    634367   * 
     
    703436 
    704437  /** 
    705    * Set the presentation rendering mode. 
    706    * 
    707    * @param      int A rendering mode. 
    708    * 
    709    * @throws     <b>AgaviRenderException</b> - If an invalid render mode has  
    710    *                                           been set. 
    711    * 
    712    * @author     Sean Kerr <skerr@mojavi.org> 
    713    * @since      0.9.0 
    714    */ 
    715   public function setRenderMode($mode) 
    716   { 
    717     if($mode == AgaviView::RENDER_CLIENT || $mode == AgaviView::RENDER_VAR || $mode == AgaviView::RENDER_NONE) { 
    718       $this->renderMode = $mode; 
    719       return; 
    720     } 
    721  
    722     // invalid rendering mode type 
    723     $error = 'Invalid rendering mode: %s'; 
    724     $error = sprintf($error, $mode); 
    725  
    726     throw new AgaviRenderException($error); 
    727   } 
    728  
    729   /** 
    730438   * Execute the shutdown procedure for this controller. 
    731439   * 
     
    760468   * @param      string The output type name. 
    761469   * 
    762    * @return     bool Whether or not the operation was successful. 
    763    * 
    764470   * @throws     <b>AgaviConfigurationException</b> If the given output type  
    765471   *                                                doesnt exist. 
     
    771477  { 
    772478    if(isset($this->outputTypes[$outputType])) { 
    773       if(!$this->getResponse()->isLocked()) { 
    774         $this->outputType = $outputType; 
    775         return true; 
    776       } 
    777       return false; 
     479      $this->outputType = $outputType; 
    778480    } else { 
    779481      throw new AgaviConfigurationException('Output Type "' . $outputType . '" has not been configured.');