Show
Ignore:
Timestamp:
02/04/07 00:36:48 (2 years ago)
Author:
david
Message:

finally: caching. one config file per action, definitions can be specific to one or more request-method, each definition can contain settings specific to one or more output types, groups (like in smarty, multiple sources like string, locale, request param etc), cache TTL ('2 days 4 hours'), caching can be controlled on a per layer level, slots can be included in the cache, action attribs, template vars and request attribs (yes, with namespace) can be restored, restrictable to certain views, closes #78. also did some minor fixes here and there, added slots to sample app.

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • branches/0.11/src/filter/AgaviExecutionFilter.class.php

    r1621 r1635  
    8686      $group = base64_encode($group); 
    8787    } 
    88     return include(AgaviConfig::get('core.cache_dir') . DIRECTORY_SEPARATOR . self::CACHE_SUBDIR . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $groups) . '.cefcache'); 
     88    return unserialize(file_get_contents(AgaviConfig::get('core.cache_dir') . DIRECTORY_SEPARATOR . self::CACHE_SUBDIR . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $groups) . '.cefcache')); 
    8989  } 
    9090 
     
    106106    } 
    107107    @mkdir(AgaviConfig::get('core.cache_dir') . DIRECTORY_SEPARATOR  . self::CACHE_SUBDIR . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR , array_slice($groups, 0, -1)), 0777, true); 
    108     return file_put_contents(AgaviConfig::get('core.cache_dir') . DIRECTORY_SEPARATOR . self::CACHE_SUBDIR . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $groups) . '.cefcache', '<' . '?' . 'php return ' . var_export($data, true) . ';'); 
     108    return file_put_contents(AgaviConfig::get('core.cache_dir') . DIRECTORY_SEPARATOR . self::CACHE_SUBDIR . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $groups) . '.cefcache', serialize($data)); 
    109109  } 
    110110 
     
    131131 
    132132  /** 
    133    * Builds an array of cache groups 
    134    * 
    135    * @param      array  The configuration 
    136    * @param      string The Action's Module name 
    137    * @param      string The Action's name 
    138    * 
    139    * @return     array An array of groups 
     133   * Builds an array of cache groups using the configuration and a container. 
     134   * 
     135   * @param      array                   The group array from the configuration. 
     136   * @param      AgaviExecutionContainer The execution container. 
     137   * 
     138   * @return     array An array of groups. 
    140139   * 
    141140   * @author     David Zuelke <dz@bitxtender.com> 
    142141   * @since      0.11.0 
    143142   */ 
    144   public function determineGroups(array $cfg, $moduleName, $actionName) 
    145   { 
    146     $groups = array(); 
    147  
    148     if(isset($cfg['groups'])) { 
    149       foreach($cfg['groups'] as $group) { 
    150         $group += array('name' => null, 'source' => null, 'namespace' => null); 
    151         $val = $this->getVariable($group['name'], $group['source'], $group['namespace']); 
    152         if($val === null) { 
    153           $val = "0"; 
    154         } 
    155         $groups[] = $val; 
    156       } 
    157     } 
    158  
    159     $groups[] = $moduleName . '_' . $actionName; 
    160  
    161     return $groups; 
     143  public function determineGroups(array $groups, $container) 
     144  { 
     145    $retval = array(); 
     146     
     147    foreach($groups as $group) { 
     148      $group += array('name' => null, 'source' => null, 'namespace' => null); 
     149      $val = $this->getVariable($group['name'], $group['source'], $group['namespace']); 
     150      if($val === null) { 
     151        $val = "0"; 
     152      } 
     153      $retval[] = $val; 
     154    } 
     155     
     156    $retval[] = $container->getModuleName() . '_' . $container->getActionName(); 
     157     
     158    return $retval; 
    162159  } 
    163160   
     
    171168        $val = $this->context->getTranslationManager()->getCurrentLocaleIdentifier(); 
    172169        break; 
    173       case 'requestParameter': 
    174         $val = $this->context->getRequest()->getParameter($name); 
    175         break; 
    176       case 'requestAttribute': 
     170      case 'request_parameter': 
     171        $val = $this->context->getRequest()->getRequestData()->getParameter($name); 
     172        break; 
     173      case 'request_attribute': 
    177174        $val = $this->context->getRequest()->getAttribute($name, $namespace); 
    178175        break; 
    179       case 'userParameter': 
     176      case 'user_parameter': 
    180177        $val = $this->context->getUser()->getParameter($name); 
    181178        break; 
    182       case 'userAttribute': 
     179      case 'user_attribute': 
    183180        $val = $this->context->getUser()->getAttribute($name, $namespace); 
    184181        break; 
    185       case 'userCredential': 
     182      case 'user_credential': 
    186183        $val = $this->context->getUser()->hasCredential($name); 
    187184        break; 
     
    209206  public function execute(AgaviFilterChain $filterChain, AgaviExecutionContainer $container) 
    210207  { 
    211     $response = $container->getResponse(); 
    212      
    213208    $lm = $this->context->getLoggerManager(); 
    214209    // get the context, controller and validator manager 
    215210    $controller = $this->context->getController(); 
    216  
     211     
    217212    // get the current action information 
    218213    $actionName = $container->getActionName(); 
    219214    $moduleName = $container->getModuleName(); 
    220  
     215     
    221216    $request = $this->context->getRequest(); 
    222  
     217     
    223218    $isCacheable = false; 
    224     if($this->getParameter('enable_caching', false) && is_readable($cachingDotXml = AgaviConfig::get('core.module_dir') . '/' . $moduleName . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'caching.xml')) { 
    225       // $lm->log(new AgaviLoggerMessage('Caching enabled, configuration file found, loading...')); 
    226       $defaultConfig = array( 
    227         'enabled' => true, 
    228         // lifetime. null = forever 
    229         'lifetime' => null, 
    230         // request methods to cache. null = any 
    231         'methods' => null, 
    232         // group definitions 
    233         'groups' => array( 
    234         ), 
    235         // views to cache. null = any 
    236         'views' => null, 
    237         // a list of names of action attributes to cache and restore so they are available in view initialization 
    238         'actionAttributes' => array( 
    239         ), 
    240         // a list of request attributes to cache and restore so they are available for subsequent stuff 
    241         'requestAttributes' => array( 
    242         ), 
    243         'decorator' => array( 
    244           'include' => true, 
    245           'slots' => array( 
    246           ), 
    247           'variables' => array( 
    248           ), 
    249         ) 
    250       ); 
    251       // // $lm->log(new AgaviLoggerMessage(print_r(include(AgaviConfigCache::checkConfig($cachingDotXml)), true))); 
    252       //      $config = array_merge($config, include(AgaviConfigCache::checkConfig($cachingDotXml))); 
    253       $config['SearchEngineSpam'] = array_merge($defaultConfig, array( 
    254         'enabled' => false, 
    255         'lifetime' => '10 seconds', 
    256         // group definitions 
    257         'groups' => array( 
    258           array( 
    259             'name' => 'index', 
    260           ), 
    261           array( 
    262             'source' => 'requestParameter', 
    263             'name' => 'name', 
    264           ), 
    265           array( 
    266             'source' => 'locale', 
    267           ), 
    268         ), 
    269         'actionAttributes' => array( 
    270           'testing' 
    271         ), 
    272         // other variables to cache and restore so they are available for subsequent stuff 
    273         'requestAttributes' => array( 
    274           // array( 
    275           //  'namespace' => 'foo.bar', 
    276           //  'name' => 'testing', 
    277           // ), 
    278         ), 
    279         'decorator' => array( 
    280           'include' => true, 
    281           'slots' => array( 
    282           ), 
    283           'variables' => array( 
    284           ), 
    285         ), 
    286       )); 
    287       if(isset($config[$actionName]) && $config[$actionName]['enabled'] && (!is_array($config[$actionName]['methods']) || in_array($method = $request->getMethod(), $config[$actionName]['methods']))) { 
    288         // $lm->log(new AgaviLoggerMessage('Current action and request method are configured for caching, proceeding...')); 
    289         $config = $config[$actionName]; 
    290         $groups = $this->determineGroups($config, $moduleName, $actionName); 
    291         // $lm->log(new AgaviLoggerMessage('Fetched groups "' . implode('", "', $groups) . '"')); 
    292         $isCacheable = true; 
    293       } 
    294     } 
    295  
     219    if($this->getParameter('enable_caching', true) && is_readable($cachingDotXml = AgaviConfig::get('core.module_dir') . '/' . $moduleName . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . $actionName . '.xml')) { 
     220      // $lm->log('Caching enabled, configuration file found, loading...'); 
     221      // no _once please! 
     222      include(AgaviConfigCache::checkConfig($cachingDotXml)); 
     223    } 
     224     
    296225    $isActionCached = false; 
    297  
     226     
     227     
    298228    if($isCacheable) { 
     229      $groups = $this->determineGroups($config["groups"], $container); 
    299230      $isActionCached = $this->checkCache(array_merge($groups, array(self::ACTION_CACHE_ID)), $config['lifetime']); 
    300231    } else { 
    301       // $lm->log(new AgaviLoggerMessage('Action is not cacheable!')); 
    302     } 
    303  
     232      // $lm->log('Action is not cacheable!'); 
     233    } 
     234     
    304235    if($isActionCached) { 
    305       // $lm->log(new AgaviLoggerMessage('Action is cached, loading...')); 
    306       $viewModule = ''; 
    307       $viewName = ''; 
     236      // $lm->log('Action is cached, loading...'); 
    308237      // cache/dir/4-8-15-16-23-42 contains the action cache 
    309238      $actionCache = $this->readCache(array_merge($groups, array(self::ACTION_CACHE_ID))); 
    310       $viewModule = $actionCache['viewModule']; 
    311       $viewName = $actionCache['viewName']; 
    312       $actionAttributes = $actionCache['actionAttributes']; 
    313239    } else { 
    314       // $lm->log(new AgaviLoggerMessage('Action not cached, executing...')); 
     240      // $lm->log('Action not cached, executing...'); 
    315241      // execute the Action and get the View to execute 
    316       list($viewModule, $viewName) = $this->runAction($container); 
    317        
    318       // check if the returned view is even cacheable 
    319       if($isCacheable && is_array($config['views']) && !(in_array($viewName, $config['views'], true) || in_array(array($viewModule, $viewName), $config['views']))) { 
     242      list($actionCache['view_module'], $actionCache['view_name']) = $this->runAction($container); 
     243       
     244      // check if the returned view is cacheable 
     245      if($isCacheable && is_array($config['views']) && !in_array(array('module' => $actionCache['view_module'], 'name' => $actionCache['view_name']), $config['views'], true)) { 
    320246        $isCacheable = false; 
    321         // $lm->log(new AgaviLoggerMessage('Returned View is not cleared for caching, setting cacheable status to false.')); 
     247        // $lm->log('Returned View is not cleared for caching, setting cacheable status to false.'); 
    322248      } else { 
    323         // $lm->log(new AgaviLoggerMessage('Returned View is cleared for caching, proceeding...')); 
     249        // $lm->log('Returned View is cleared for caching, proceeding...'); 
    324250      } 
    325251       
     
    327253    } 
    328254     
    329     $response->clear(); 
    330  
    331     if($viewName !== AgaviView::NONE) { 
    332  
    333       $container->setViewModuleName($viewModule); 
    334       $container->setViewName($viewName); 
    335  
     255    if($actionCache['view_name'] !== AgaviView::NONE) { 
     256       
     257      $container->setViewModuleName($actionCache['view_module']); 
     258      $container->setViewName($actionCache['view_name']); 
     259       
    336260      // get the view instance 
    337       $viewInstance = $controller->createViewInstance($viewModule, $viewName); 
    338  
     261      $viewInstance = $controller->createViewInstance($actionCache['view_module'], $actionCache['view_name']); 
     262       
    339263      // initialize the view 
    340264      $viewInstance->initialize($container); 
    341  
     265       
     266      $outputType = $container->getOutputType()->getName(); 
     267       
    342268      $isViewCached = false; 
    343  
     269       
    344270      if($isCacheable) { 
    345         $outputType = $container->getOutputType()->getName(); 
    346  
    347         if($isActionCached) { 
    348           $isViewCached = $this->checkCache(array_merge($groups, array($outputType))); 
    349         } 
    350       } 
    351  
     271        if(isset($config['output_types'][$otConfig = $outputType]) || isset($config['output_types'][$otConfig = '*'])) { 
     272          $otConfig = $config['output_types'][$otConfig]; 
     273           
     274          if($isActionCached) { 
     275            $isViewCached = $this->checkCache(array_merge($groups, array($outputType))); 
     276          } 
     277        } else { 
     278          $isCacheable = false; 
     279        } 
     280      } 
     281       
    352282      if($isViewCached) { 
    353         // $lm->log(new AgaviLoggerMessage('View is cached, loading...')); 
     283        // $lm->log('View is cached, loading...'); 
    354284        $viewCache = $this->readCache(array_merge($groups, array($outputType))); 
    355         $response->import($viewCache['response']); 
    356         foreach($viewCache['requestAttributes'] as $requestAttribute) { 
    357           $request->setAttribute($requestAttribute['name'], $requestAttribute['value'], $requestAttribute['namespace']); 
    358         } 
    359285      } else { 
    360         // $lm->log(new AgaviLoggerMessage('View is not cached, executing...')); 
     286        $viewCache = array(); 
     287         
     288        // create a new response instance for this action 
     289        $rfi = $this->context->getFactoryInfo('response'); 
     290        $response = new $rfi['class']; 
     291        $response->initialize($this->context, $rfi['parameters']); 
     292        $container->setResponse($response); 
     293         
     294        // $lm->log('View is not cached, executing...'); 
    361295        // view initialization completed successfully 
    362         $executeMethod = 'execute' . $container->getOutputType()->getName(); 
     296        $executeMethod = 'execute' . $outputType; 
    363297        if(!method_exists($viewInstance, $executeMethod)) { 
    364298          $executeMethod = 'execute'; 
    365299        } 
    366300        $key = $request->toggleLock(); 
    367         $next = $viewInstance->$executeMethod($container->getRequestData()); 
     301        $viewCache['next'] = $viewInstance->$executeMethod($container->getRequestData()); 
    368302        $request->toggleLock($key); 
    369          
    370         if($next instanceof AgaviExecutionContainer) { 
    371           $container->setNext($next); 
     303      } 
     304       
     305      if($viewCache['next'] instanceof AgaviExecutionContainer) { 
     306        // $lm->log('Forwarding request, skipping rendering...'); 
     307        $container->setNext($viewCache['next']); 
     308      } else { 
     309        if($isViewCached) { 
     310          $layers = $viewCache['layers']; 
     311          $response = $viewCache['response']; 
     312          $container->setResponse($response); 
     313         
     314          foreach($viewCache['template_variables'] as $name => $value) { 
     315            $viewInstance->setAttribute($name, $value); 
     316          } 
     317         
     318          foreach($viewCache['request_attributes'] as $requestAttribute) { 
     319            $request->setAttribute($requestAttribute['name'], $requestAttribute['value'], $requestAttribute['namespace']); 
     320          } 
     321         
     322          $output = array(); 
     323          $nextOutput = $response->getContent(); 
    372324        } else { 
    373           $attributes =& $viewInstance->getAttributes(); 
    374  
     325          $layers = $viewInstance->getLayers(); 
     326         
     327          if($isCacheable) { 
     328            $viewCache['template_variables'] = array(); 
     329            foreach($otConfig['template_variables'] as $varName) { 
     330              $viewCache['template_variables'][$varName] = $viewInstance->getAttribute($varName); 
     331            } 
     332           
     333            $viewCache['response'] = clone $response; 
     334           
     335            $viewCache['layers'] = array(); 
     336           
     337            $viewCache['slots'] = array(); 
     338           
     339            $lastCacheableLayer = -1; 
     340            if(is_array($otConfig['layers'])) { 
     341              if(count($otConfig['layers'])) { 
     342                for($i = count($layers)-1; $i >= 0; $i--) { 
     343                  $layer = $layers[$i]; 
     344                  $layerName = $layer->getName(); 
     345                  $cacheSlots[$layerName] = array(); 
     346                  if(isset($otConfig['layers'][$layerName])) { 
     347                    if(is_array($otConfig['layers'][$layerName])) { 
     348                      $lastCacheableLayer = $i - 1; 
     349                    } else { 
     350                      $lastCacheableLayer = $i; 
     351                    } 
     352                  } 
     353                } 
     354              } 
     355            } else { 
     356              $lastLayer = end($layers); 
     357              if($lastLayer !== false) { 
     358                $lastCacheableLayer = $lastLayer->getName(); 
     359              } 
     360            } 
     361           
     362            for($i = $lastCacheableLayer + 1; $i < count($layers); $i++) { 
     363              // $lm->log('Adding non-cacheable layer "' . $layers[$i]->getName() . '" to list'); 
     364              $viewCache['layers'][] = clone $layers[$i]; 
     365            } 
     366          } 
     367         
    375368          $output = array(); 
    376369          $nextOutput = null; 
    377           foreach($viewInstance->getLayers() as $layer) { 
    378             foreach($layer->getSlots() as $slotName => $slotContainer) { 
     370        } 
     371         
     372        $attributes =& $viewInstance->getAttributes(); 
     373         
     374        // $lm->log('Starting rendering...'); 
     375        for($i = 0; $i < count($layers); $i++) { 
     376          $layer = $layers[$i]; 
     377          $layerName = $layer->getName(); 
     378          // $lm->log('Running layer "' . $layerName . '"...'); 
     379          foreach($layer->getSlots() as $slotName => $slotContainer) { 
     380            if($isViewCached && isset($viewCache['slots'][$layerName][$slotName])) { 
     381              // $lm->log('Loading cached slot "' . $slotName . '"...'); 
     382              $slotResponse = $viewCache['slots'][$layerName][$slotName]; 
     383            } else { 
     384              // $lm->log('Running slot "' . $slotName . '"...'); 
    379385              $slotResponse = $slotContainer->execute(); 
    380               // set the presentation data as a template attribute 
    381               if(($output[$slotName] = $slotResponse->getContent()) !== null) { 
    382                 // the slot really output something 
    383                 // let our response grab the stuff it needs from the slot's response 
    384                 $response->merge($slotResponse); 
     386              if($isCacheable && !$isViewCached && in_array($slotName, $otConfig['layers'][$layerName])) { 
     387                // $lm->log('Adding response of slot "' . $slotName . '" to cache...'); 
     388                $viewCache['slots'][$layerName][$slotName] = $slotResponse; 
    385389              } 
    386390            } 
    387             $moreAssigns = array( 
    388               'container' => $container, 
    389               'inner' => $nextOutput, 
    390               'view' => $viewInstance, 
    391               'request_data' => $container->getRequestData() 
    392             ); 
    393             $nextOutput = $layer->getRenderer()->render($layer, $attributes, $output, $moreAssigns); 
    394             $output = array(); 
    395             $output[$layer->getName()] = $nextOutput; 
    396           } 
     391            // set the presentation data as a template attribute 
     392            if(($output[$slotName] = $slotResponse->getContent()) !== null) { 
     393              // $lm->log('Merging in response from slot "' . $slotName . '"...'); 
     394              // the slot really output something 
     395              // let our response grab the stuff it needs from the slot's response 
     396              $response->merge($slotResponse); 
     397            } 
     398          } 
     399          $moreAssigns = array( 
     400            'container' => $container, 
     401            'inner' => $nextOutput, 
     402            'request_data' => $container->getRequestData(), 
     403            'view' => $viewInstance, 
     404          ); 
     405          $nextOutput = $layer->getRenderer()->render($layer, $attributes, $output, $moreAssigns); 
     406           
    397407          $response->setContent($nextOutput); 
    398         } 
    399       } 
    400  
     408           
     409          if($isCacheable && !$isViewCached && $i === $lastCacheableLayer) { 
     410            $viewCache['response'] = clone $response; 
     411          } 
     412           
     413          $output = array(); 
     414          $output[$layer->getName()] = $nextOutput; 
     415        } 
     416      } 
     417       
    401418      if($isCacheable) { 
    402419        if(!$isActionCached) { 
    403           $actionCache = array(); 
    404  
    405           $actionCache['viewModule'] = $viewModule; 
    406           $actionCache['viewName'] = $viewName; 
    407  
    408           $actionCache['actionAttributes'] = array(); 
    409           foreach($config['actionAttributes'] as $attributeName) { 
    410             $actionCache['actionAttributes'][$attributeName] = $actionAttributes[$attributeName]; 
    411           } 
    412            
    413           // $lm->log(new AgaviLoggerMessage('Writing Action cache...')); 
     420          $actionCache['action_attributes'] = array(); 
     421          foreach($config['action_attributes'] as $attributeName) { 
     422            $actionCache['action_attributes'][$attributeName] = $actionAttributes[$attributeName]; 
     423          } 
     424           
     425          // $lm->log('Writing Action cache...'); 
    414426           
    415427          $this->writeCache(array_merge($groups, array(self::ACTION_CACHE_ID)), $actionCache); 
    416428        } 
    417429        if(!$isViewCached) { 
    418           $viewCache = array(); 
    419            
    420           $viewCache['response'] = $response->export(); 
    421            
    422           $viewCache['requestAttributes'] = array(); 
    423           foreach($config['requestAttributes'] as $requestAttribute) { 
    424             $viewCache['requestAttributes'][] = $requestAttribute + array('value' => $request->getAttribute($requestAttribute['name'], $requestAttribute['namespace'])); 
     430          $viewCache['request_attributes'] = array(); 
     431          foreach($otConfig['request_attributes'] as $requestAttribute) { 
     432            $viewCache['request_attributes'][] = $requestAttribute + array('value' => $request->getAttribute($requestAttribute['name'], $requestAttribute['namespace'])); 
    425433          } 
    426434           
    427435          $this->writeCache(array_merge($groups, array($outputType)), $viewCache); 
    428436           
    429           // $lm->log(new AgaviLoggerMessage('Writing View cache...')); 
    430         } 
    431       } 
    432     } 
    433     // $lm->log(new AgaviLoggerMessage(print_r($request->getAttributeNamespace('foo.bar'), true))); 
     437          // $lm->log('Writing View cache...'); 
     438        } 
     439      } 
     440    } 
    434441  } 
    435442