How to update specific cached route? - caching

I am running a Symfony2 app and I have a question about caching.
There is a comment on an answer here in SO that says:
you could create a command that only updates this one cached route. or
maybe consider using a kernel event listener that newly registers the
route on every request if you can afford the performance impact.
How could I update only this one cached route?

Where are cached routes stored?
The cache classes for url matching/generation can be found in app/cache/environment and are called appEnvironmentUrlGenerator.php and appEnvironmentUrlGenerator.php with "environment" being one of dev,prod, .. etc.
API reference:
http://api.symfony.com/2.3/Symfony/Component/Routing/Matcher/UrlMatcher.html
http://api.symfony.com/2.3/Symfony/Component/Routing/Generator/UrlGenerator.html
How does it work?
The router service receives a url-matcher and a url-generator when being constructed. Those are then being used inside the match() and generate() methods of the router.
https://github.com/symfony/symfony/blob/2.3/src/Symfony/Component/Routing/Router.php
For warming up the cache the RoutingCacheWarmer uses the router's warmUp() method (if it implements WarmableInterface).

Everything written by nifr is true, but doesn't really help you.
Symfony's built in router designed to support 'static' routes, so on the first cache warmup the routes will be generated and cached. If you check the mentioned cache files you will see that the routes saved in a static private variable.
There is no simple way to update a route (or change routes).
Using Symfony2 CMF
This a bit complex solution for your simple problem: http://symfony.com/doc/master/cmf/index.html
Clearing (invalidating) the cache
If you check the CacheClearCommand you will see that the implementation is quite complex. Some suggest to delete the whole cache dir, which I don't recommend, it is quite heavy and makes your site hang on until the cache is regenerated (even you can get exceptions of missing files / and logged out users if the sessions saved under the cache folder)
Calling the cache_warmer warmup has no effect if the cache already delegated.
If you just remove the two related cache file and then call the warmUp on the router would be a better solution, but also not nice one..
$cacheDir = $this->container->getParameter("kernel.cache_dir");
// Delete routing cache files
$filesystem = $this->container->get('filesystem');
$finder = new Finder();
/** #var File $file */
foreach ($finder->files()->depth('== 0')->in($cacheDir) as $file) {
if (preg_match('/UrlGenerator|UrlMatcher/', $file->getFilename()) == 1) {
$filesystem->remove($file->getRealpath());
}
}
$router = $this->container->get('router');
$router->warmUp($cacheDir);
Override the default Router class
As of 2.8 the Router class is defined with a parameter router.class
See here: https://github.com/symfony/framework-bundle/blob/v2.8.2/Resources/config/routing.xml#L63
Add something like this to your config.yml
parameters:
router.class: "My\Fancy\Router"
You can implement your own Router class, extending the original Router, and also you will need to extend the UrlMatcher and UrlGenerator classes to call the parent implementation and add your own routes to match against / generate with. This way you don't need to refresh the cache, because you can add your routes dynamically.
Note: I'm not sure if you can rely on this on long term, if you check master, the definition has changed, the parameter is not there anymore:
https://github.com/symfony/framework-bundle/blob/master/Resources/config/routing.xml#L54

Related

Stop EPiServer clearing output cache on publish

This isn't a question I've seen around, usually it's 'EPiServer isn't clearing the output cache'. I'm trying to achieve the opposite. Each time a page is published the entire cache is dropped and as the client publishes several times a day, this is frustrating.
I'm using the [ContentOutputCache] attribute and tried to implement a httpCacheVaryByCustom rule with an accompanying scheduled task in EPiServer to invalidate the cache when we decide to i.e. bundle updates together and invalidate at a predetermined time.
I've tested this rule and it works using:
public override string GetVaryByCustomString(HttpContext context, string custom)
I was under the impression that by using this type of caching rule it would stop EPiServer dumping my cache whenever something is published / media uploaded.
It doesn't though is there a way to stop this from happening?
I've had success by using the standard [OutputCache] with the same custom string rule the only problem with this is that editors will always see a cached version of the page they are editing.
The application settings I have in my web.config for EPiServer are:
<applicationSettings globalErrorHandling="Off" operationCompatibility="DynamicProperties" uiSafeHtmlTags="b,i,u,br,em,strong,p,a,img,ol,ul,li" disableVersionDeletion="false"
httpCacheability="Public" uiEditorCssPaths="~/assets/css/styles.css, ~/assets/css/editor.css" urlRebaseKind="ToRootRelative"
pageUseBrowserLanguagePreferences="false" uiShowGlobalizationUserInterface="false" subscriptionHandler="EPiServer.Personalization.SubscriptionMail,EPiServer"
uiMaxVersions="20" pageValidateTemplate="false" utilUrl="~/util/"
uiUrl="~/EPiServer/CMS/" httpCacheExpiration="01:00:00" httpCacheVaryByCustom="invalidateSiteCache" />
A custom GetVaryByCustomString function will determine when the cache is invalidated, but any request for content that is using the ContentOutputCache is checked against a master cache key Episerver.DataFactoryCache.Version. This version number is incremented any time content is published, updated etc, and the cache is invalidated if the version number is changed.
To understand what you need to do, I recommend using a decompiler (e.g. DotPeek) and looking at the ContentOutputCacheAttribute and OutputCacheHandler classes in the Episerver dll.
You will need to:
Derive a new handler from EPiServer.Web.OutputCacheHandler
Create an alternative method to ValidateOutputCache(...) that still calls OutputCacheHandler.UseOutputCache(...) but ignores the cache version number
Derive a new attribute from ContentOutputCacheAttribute
Override the method OnResultExecuting(ResultExecutingContext filterContext) using the same logic as the current method (this is where a decompiler is useful), but that adds a callback to your new validate method instead of the current one. Unfortunately we can't inject the new handler because the validate method is passed statically.
e.g.
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
// Rest of method
filterContext.HttpContext.Response.Cache.AddValidationCallback(new HttpCacheValidateHandler(CustomOutputCacheHandler.CustomValidateOutputCache), (object) tuple);
}
Use the new attribute in place of [ContentOutputCache]

How do I get Magento to serve up the cached version of the page when I have unique URL parameters?

It's a simple question with no answer in search(google/bing/stackoverflow). The answer of course could be complicated.
I've read a couple articles on FPC within Magento, and have yet to really nail down where I need to add or create code so that when certain URL parameters are sent it serves up cached version of the page and not try and re-cache with the URL parameters.
http://www.kingletas.com/2012/09/how-does-magento-full-page-cache-works.html
So for example, when you go to http://www.example.com/shoes it loads the correct cached version. however, with google analytics and any other type of 3rd party reporting, especially with unique identifiers, it will reload the page as if it wasn't cached. So http://www.example.com/shoes?utm_key=A1537BD94EF07 would create a new cached version of that page and so on.
I would like to be able to exclude certain URL parameters and not all. Mainly any parameter I am using for tracking of customers.
As far as code, I have not come up with anything, due to the fact of the complexity of FPC and not having a dev site currently setup to test on.
Any leads as to where I can add this exception would be helpful, thanks!
EDIT: I would like to add that I am working with the Enterprise Edition. And using the Redis for cache.
I developed my own extension on the fix.
In short the get parameters are used in the cache ID. So in order to bypass this, I created an extension that changed the following:
/app/code/core/Enterprise/PageCache/Model/Processor/Category.php
Two functions where changed
protected function _getQueryParams()
AND
public function getPageIdWithoutApp(Enterprise_PageCache_Model_Processor $processor)
/app/code/core/Enterprise/PageCache/Model/Processor/Default.php
One function was changed
public function getPageIdWithoutApp(Enterprise_PageCache_Model_Processor $processor)
Once changed, it no longer created the cache ID with my specified tracking parameters.
example:
public function getPageIdWithoutApp(Enterprise_PageCache_Model_Processor $processor)
{
$queryParams = $_GET;
ksort($queryParams);
/**
* unset known tracking codes
*/
unset($queryParams["trk_msg"]);
unset($queryParams["trk_contact"]);
unset($queryParams["utm_source"]);
unset($queryParams["utm_medium"]);
unset($queryParams["utm_term"]);
unset($queryParams["utm_campaign"]);
unset($queryParams["utm_content"]);
/** End Edit */
$queryParamsHash = md5(serialize($queryParams));
return $processor->getRequestId() . '_' . $queryParamsHash;
}

How to cache data in symfony2

Is there any built in possibility (or an external bundle) to cache data in Symfony2?
I don't want to cache the page itself, but data inside the application, using a simple key -> value store on the file system for example.
There's no built in solution, but I recommend you giving APC, Redis or Memcache a try (they're all in-memory datastores).
You can use LiipDoctrineCacheBundle to integrate cache drivers from Doctrine common into your Symfony project.
i'm using winzouCacheBundle. it gives you a streamlined cache api on different backends (apc,file,memcache,array,xcache, zenddata).
For now, there is no unique solution for caching in Symfony2. Some parts of the framework use Doctrine Common.
There are discussions about a "standard" caching solution if Symfony2, but we will have to wait for some time...
I think the DoctrineCacheBundle is currently the way to go.
The DoctrineCacheBundle allows your Symfony application to use different caching systems through the Doctrine Cache library.
Docs # Symfony.com
Code # Github
If I understand well, you would like to store data (attached to the session) and reload them when the same session will call again a new controller, in order to avoid to execute the same procedure more times (for example to read a table from a database).
You can use the session system in your controllers:
<?php
namespace YourStuff\YourBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
class YourController extends Controller
{
$session = $this->get("session");
$variabile = 4;
$session->set("variableName",$variable); // setter
if ($session->has("variableName") // to check if the variable exists
{
$variableName = $session->get("variableName"); // getter
}
}
This is an example; the "variableName" could be accessed next time the same session will be called, if the lifetime of the session is not yet expired.
The "session" uses the __SESSION variable of PHP, so be sure to set correctly the session.cookie_lifetime and session.gc_maxlifetime, in order to give the desired lifetime.

What is the best practice for disabling a cache request from the Full Page Cache (FPC) in magento (enterprise)

I wish to remove the following setting:
<cms>enterprise_pagecache/processor_default</cms>
... from the config.xml of the core / Enterprise / PageCache / config.xml file so that the home page will not be cached, (because we have a complicated custom store switch in place).
Since this value is not stored in core_config_data I am unsure of the best way to override the out of the box value. The comments above the line in the core file do hint that it is not actually bad practice to edit this file, however, can I open this to the community to see what they think?
PS = This is a multi website setup with a custom store switcher.
Hole punching is what it sounds like you may need.
Add a etc/cache.xml file with a <config> root to your module. (see Enterprise/PageCache/etc/cache.xml). Choose a unique [placeholder] name.
The placeholders/[placeholder]/block node value must match the class-id of your custom dynamic block, e.g. mymodule/custom
The placeholders/[placeholder]/container node value is the class to generate the content dynamically and handle block level caching
The placeholders/[placeholder]/placeholder node value is a unique string to mark the dynamic parts in the cached page
placeholders/[placeholder]/cache_lifetime is ignored, specify a block cache lifetime in the container’s _saveCache() method if needed
Implement the container class and extends Enterprise_PageCache_Model_Container_Abstract. Use _renderBlock() to return the dynamic content.
Implement the _getCacheId() method in the container to enable the block level caching. Use cookie values instead of model ids (lower cost).
One last note: You DON’T have the full Magento App at your disposal when _renderBlock() is called. Be as conservative as possible.
SOURCE: http://tweetorials.tumblr.com/post/10160075026/ee-full-page-cache-hole-punching

How to integrate ezComponents with magento

in a 'native' Zend Framework application I would enable the use of ezComponents by adding the autoloader of ezComponents to Zends autoloader:
$autoLoader = Zend_Loader_Autoloader::getInstance();
require_once('../library/EZComponents/Base/src/base.php');
$autoLoader->pushAutoloader(array('ezcBase', 'autoload'), 'ezc');
Now, I'm wondering how I could do the same with Magento.
Is there a way to extend Varien_Autoload (magentos autoloader) to enable easy integration of ezComponents?
OR:
Is there a way to use Zends autoloader beside the one from Magento without interfering each other?
edit:
Well, I implemented a workaround, by adding the following to function autoload() in Varien_Autoload:
if(substr($class, 0, 3) == 'ezc'){
require_once('EZComponents/Base/src/base.php');
return ezcBase::autoload($class);
}
I consider this as a very bad hack though, because it will be overwritten, when upgrading Magento. Does anyone has a better idea?
My basic approach here would be to create a custom module with an observer for the
controller_front_init_before
event. In the event observer, you'd be able to setup your autoloader however you want. There's a Magento Wiki article on Setting up Event Observers. The controller_front_init_before event is one of the first non-generic events to fire in Magento. That's why we're using it.
The big problem we need to solve is this: Magento's autoloader is on the stack first, and if it doesn't find a file (which will be the case with the EZComponent classes), its include will raise a error that will halt execution.
So, what we need to do in our event observer above is to
Remove the Varien_Autoloader from the spl_autoload stack
Register our own autoloader (we'll use Zend_Autoloader, since it ships with Magento and you seem familiar with it)
Re-add the Varien_Autoloader to the stack
There'll be a little extra jiggery-pokery we'll need to do since loading of the classes in the Zend namespace is normally handled by the autoloader we'll be removing. See comments for more details
//we need to manually include Zend_Loader, or else our zend autoloader
//will try to use it, won't find it, and then try to use Zend_Loader to
//load Zend_Loader
require_once('lib/Zend/Loader.php');
//instantiate a zend autoloader first, since we
//won't be able to do it in an unautoloader universe
$autoLoader = Zend_Loader_Autoloader::getInstance();
//get a list of call the registered autoloader callbacks
//and pull out the Varien_Autoload. It's unlikely there
//are others, but famous last words and all that
$autoloader_callbacks = spl_autoload_functions();
$original_autoload=null;
foreach($autoloader_callbacks as $callback)
{
if(is_array($callback) && $callback[0] instanceof Varien_Autoload)
{
$original_autoload = $callback;
}
}
//remove the Varien_Autoloader from the stack
spl_autoload_unregister($original_autoload);
//register our autoloader, which gets on the stack first
require_once('library/EZComponents/Base/src/base.php');
$autoLoader->pushAutoloader(array('ezcBase', 'autoload'), 'ezc');
//lets just make sure we can instantiate an EZ class
#$test = new ezcBaseFile();
#var_dump(get_class($test));
//IMPORANT: add the Varien_Autoloader back to the stack
spl_autoload_register($original_autoload);
Put the above code in an observer method and you should be good to go.
The other approach you could take, one that would fit in more with Magento patterns, would be to create a custom module that implemented an EZComponent loader.
$o = Mypackage_Mymodule_Loader::getModel('ezcBaseFile');
You'd then implement autoloader style require code in your static getModel method, and use it whenever you wanted an ezcBaseFile class. You'd probably want methods for loading a class without instantiating an object in case you wanted to call a static method on an ezcBaseFile base class.
$o = Mypackage_Mymodule_Loader::getLoadclass('ezcBaseFile');
I took a quick look at the code for Varien's autoloader and it appears to use a call to spl_autoload_register, which is a stack for performing autoloads. While I don't think you'll have much success adding to the default Magento autoloader, this means that you should be able to push another autoloader on top of Magento's.
Hope that helps!
Thanks,
Joe
I've just integrated the Sailthru_Client class into Magento, thought this might help.
I have sailthru.php, the Sailthru Client API, which contains Sailthru_Client class.
I created magentoroot/lib/Sailthru folder then copy sailthru.php into it, then renamed to Client.php making it magentoroot/lib/Sailthru/Client.php. This pattern is auto-loaded by Varien_Autoload class.

Resources