Hi guys,

sorry I didn't get to write this mail any sooner...

Agavi finally has caching!


It works like this:
- you create a Foo.xml in module/Blah/cache/ to make FooAction cacheable
- you put your settings and rules in there
- you lean back and enjoy the speedup


Now let's examine the options in detail.

First, these caching configs have the usual <configurations> and  
<configuration> stuff at the top level. You should maybe enable  
caching for "production" environment only since the caches the system  
writes get thrown away in debug mode anyway.

Now the top element is <cachings>, which is, as most plural tags that  
do not require an attribute, optional. In there we're getting to the  
more exciting things: the <caching> element. Let's have a look:

<caching method="read" lifetime="2 hours" enabled="false">

The "method" attribute is optional. It may contain a space-separated  
list of request method names that caching definition is valid for.  
You could set up caching for "read" for a complex form, for example,  
to speed things up a bit.

The "lifetime" attribute is optional, too. If omitted (omitted means  
omitted, not lifetime="" !), the cache will be stored forever. You  
can use any relative format allowed by the GNU date input formats  
(http://www.gnu.org/software/tar/manual/html_node/ 
tar_115.html#SEC115, http://php.net/manual/en/ 
function.strtotime.php). Examples:
"1 day 6 hours"
"3 minutes 14 seconds"
Note that you can NOT use "thursday 02:00" and other formats, because  
this would give the same day, and not the thursday of next week, on  
thursdays after 02:00 o'clock. This is a strtotime limitation, but we  
might add a fix for that in the future.

Last but not least the "enabled" attribute. Also optional, and  
defaults to true.


Inside a <caching> block, you may define the following elements:
- <groups> (can be omitted) with <group> elements inside.
- <views> (can be omitted) with <view> elements containg names of  
views to cache.
- <action_attributes> (can be omitted) with <action_attribute>  
elements containing the names of action attributes to restore when  
serving a page from cache, these will be available in the view's  
initialize() method.
- <output_types> (can be omitted) with <output_type> elements that  
define the layers, slots, request attributes and template variables  
to cache.

Important: even when served from a cache, the action and view will  
still be initialize()d! This is because a view's initialize() method  
could change the container's output type. If you need an action  
attribute for that, specify it using <action_attribute>.

WARNING: Be careful what you include in the cache. <action_attribute>  
as well as <request_attribute> and <template_variable> (more on these  
in a minute) can be used to include such items in a cache and restore  
them when the cache is hit. You might need this in case of request  
attributes to pass information to a global filter, for instance. But  
always avoid to cache objects, especially models, propel rows etc.  
The data is serialized, and in case of models, that would mean that  
the entire context, and thus ALL objects in the framework are  
included in the serialization. If you absolutely have to cache  
objects, implement __sleep() and __wakeup() methods that remember the  
context name and exclude the context itself from serialization.

Let's talk about the <view> elements first. If you don't specify any  
<view>, all views will be allowed to be cached (<views></views>,  
however, means NO views will be cached,, careful!). You can either  
give the name of an action's view as you would when returning it from  
the action, or the full name of a view including it's module:
<view>Success</view>
<view module="Admin">AddProductInput</view>

WARNING: You usually don't want to cache Error views, only Success.  
Caching Error views would mean that attackers can quickly fill the  
hard drive of your server by requesting random, invalid pages. You  
know the drill.

But now to the most important element: the <group>. Groups work like  
in Smarty, so I recommend you read http://smarty.php.net/manual/en/ 
caching.groups.php to understand the fundamental concept.

Groups may also have a source. You are not limited to a fixed string  
as a group value. You can use
- request parameters (very useful and often needed)
- request attributes (also from namespaces)
- constants
- the current locale
- user parameters
- user attributes (also from namespaces)
- user authenticated status
- user credential (or, rather, if the user has the credential or not)

I think it's best to give an example here. Let's say we want to cache  
the page for viewing a product (Default module, ViewProductAction).  
Each product has an ID, passed in via the request parameter "id". You  
have i18n in your app, so we need separate caches for each locale.  
And finally, authenticated users with the credential "reseller" see a  
special price, so we need different caches for these users:
<groups>
   <group source="request_parameter">id</group>
   <group source="locale" />
   <group source="user_credential">reseller</group>
</groups>

For product ID "3", locale "de" and non-resellers, this would put the  
cache into:

app/
        cache/
                content/
                        3/
                                de/
                                        0/
                                                Default_ViewProduct/

In reality, these directory names are base64-encoded. The last folder  
will contain a file "4-8-15-16-23-42.cefcache" containing the action  
information, and one file for each cached output type (e.g.  
"html.cefcache").

Now, it is difficult to clear such a cache (more on that later). You  
might want to cache categories. These have IDs, too. You'd get  
collisions. Not good. Besides, you cannot easily clear the cache for  
all products. So we should add another group at the beginning of the  
list:
<group>products</group>

Again, remember that the wrapping <groups> element is optional.

That was easy, wasn't it?

But that doesn't cache anything yet. We need <output_type> elements,  
too (yep, the <output_types> container is optional).

An <output_type> may have an optional "name" attribute to restrict  
the rules inside to that output type (like the "methods" attribute on  
<caching>, it can have a space-separated list of output type names).

Inside, the following elements may occur:
- <layers> (can be omitted) with a list of <layer> children defining  
which layers to cache
- <request_attributes> (can be omitted) with a list of  
<request_attribute> elements containing names of request attributes  
to store in the cache
- <template_variables> (can be omitted) with a list of  
<template_variable> elements holding names of template variables that  
are stored in the cache (and then available to all layers that aren't  
cached and thus rendered even on a cache hit

I'll explain <request_attribute> first:

<request_attribute  
namespace="org.agavi.filter.FormPopulationFilter">populate</ 
request_attribute>

Will store the attribute "populate" from the namespace  
"org.agavi.filter.FormPopulationFilter" and restore it when the cache  
is read. In our example, this attribute would contain a  
ParameterHolder object with fields to populate. Obviously, you should  
only use that for the "read" request method to fill initial values  
into your form. A bit of a stupid example, since you'll often have  
default values (like "[EMAIL PROTECTED]" or a checkbox pre-selected)  
in the template itself, but who knows. You get the idea.

Next, layers. For this example, we assume that out current view has  
two layers - "content" and "decorator".

To cache everything, you simply don't define any layers.

To cache only the "content" layer, you'd do
<layer name="content" />
Then this layer and all layers inside will be cached. The "decorator"  
layer would still be rendered on each request.

That puts you into trouble - you have the "_title" template variable  
you want to output in the decorator, in the html <title> element. But  
since the view is not executed anymore when a cache hit occurs, we  
have to tell the caching engine to store this template variable along  
the content and then restore it before the decorator is rendered.  
Easy task:
<template_variable>_title</template_variable>

Now back to the layers. Instead of
<layer name="content" />
you could also have done
<layer name="decorator" include="false" />

That would exclude the layer from the cache, and include all layers  
inside. Surprise surprise, that is actually the better way of doing  
it! Simple reason: your view _might_ insert another intermediate  
layer (let's call it "wrapper") between "content" and "decorator".  
That layer should be cached, too (at least we assume that here), but  
setting the "content" layer as the cacheable layer wouldn't work for  
that - only that layer would be cached. If we define "decorator" to  
be the last layer before the cache kicks in, though, we'll achieve  
that goal and have any layer inside "decorator" in the cache.

Now let's assume we have that setup, but we do NOT want the "wrapper"  
layer in the cache. Then we could do:
<layer name="decorator" include="false" />
<layer name="wrapper" include="false" />

But now you might ask "hey isn't that stupid, why do I have to  
declare them both as non-cacheble?", and you're right to insist:
<layer name="wrapper" include="false" />
does exactly the same thing.

BUT

What about slots?

Here's the nice thing. Obviously, slots set on a layer are included  
in the layer's cache, since the whole layer output is cached, so the  
slot output will already be included. But if you have include="false"  
on a layer, then the slots in it will NOT be included in the cache,  
since the layer is rendered each time.

But of course, we can include slots in a cache, even if their layer  
is not cacheable:
<layer name="decorator">
   <slot>menu</slot>
</layer>

This will include the "slot" layout in the cache

Note: I omitted the "include" attribute, since declaring slots inside  
a <layer> automatically sets include to false (unless you provide it  
and set it to "true", of course).

Note2: I didn't specify <slots>, but you can, if you like.

Note3: The order of layers in the configuration does not matter,  
unlike in the layout configuration in output_types.xml.

And now the above example with duplicate layers makes sense again:
<layer name="decorator">
   <slot>menu</slot>
</layer>
<layer name="wrapper" include="false" />

Of course, every layer can specify slots it wants to cache. Also, if  
you do not want to cache a slot inside the calling cache, you can of  
course set up a caching xml config for the slot action itself.

Tip: Agavi stores cookies with a lifetime, not with an expiry time.  
Thus, you can cache actions/views that set a cookie on their  
response, it's not a problem and works just fine! The same goes for  
all other response headers you set. They are all included in the  
cache and restored afterwards. You can even set a stream as the  
resource content, e.g. $this->getResponse()->setContent(fopen('/path/ 
to/image.png', 'rb')); - that file will be re-opened when the cache  
is read and, like all streams set in the response, output using  
fpassthru() for maximum performance.

I hope that helps a bit. I'm sure there will be many questions from  
now, and of course, caching will be covered extensively in the  
documentation, on which we will focus from now on. Just shoot a mail  
to the users list if find something confusing, or drop by on the IRC  
channel.

Cheers,


David


P.S: yup, RC2 is really coming tonight! It's only a matter of hours now.

_______________________________________________
Agavi Dev Mailing List
[email protected]
http://lists.agavi.org/mailman/listinfo/dev

Reply via email to