Welcome Guest, Not a member yet? Register   Sign In
Partial Caching, the way it should be
#1

[eluser]Yorick Peterse[/eluser]
After almost 6 hours of thinking about a way to get partial caching, the advanced version, working in CodeIgniter I came up with an idea. The problem was that CI's native cache system was too limited, it caches either everything or nothing at all. This is fine for normal websites, but as soon as you need dynamic content inside cached files, such as usernames, this isn't going to work.

Imagine logging in with the username "Rubber Duck", now everybody else will see a message saying "Welcome Rubber Duck" because everything is cached, even the things that shouldn't.

Partial caching is something that fixes this problem. MP_Cache is a library that does this in a very nice way, the only prolbem is that it's really, really buggy. The second problem is that it doesn't enable the developer to include dynamic content inside cache files. For example, the following piece of code will not work with cache files :

Code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
&lt;html &gt;
&lt;head&gt;
    &lt;meta http-equiv="Content-type" content="text/html; charset=utf-8" /&gt;
    &lt;title&gt;&lt;/title>
&lt;/head&gt;
&lt;body&gt;
<p>Welcone &lt;?php echo $username; ?&gt;</p>
&lt;/body&gt;
&lt;/html&gt;

If somebody logged in with the name "Rubber Duck", everybody will now see "Welcome Rubber Duck", rather than their own username. This can be fixed by splitting the file up in seperate blocks. In this case we could split this file into three parts: header,body and footer. In this case we would only cache the header and the footer.

Again, this wasn't exactly what I wanted since I'm not going to split an relatively easy to understand template file into 3 hard to understand PHP files.

So how do we fix the problem ?

I couldn't come up with any ideas until about 30 minutes ago, and I just smacked myself in the face for thinking so complicated, the solution has been there ever since people came up with functions to replace content.

Let's take a look at the file shown earlier, using a regular cache library or method isn't going to work, but what if we replace this

Code:
&lt;?php echo $username; ?&gt;

with this

Code:
{USERNAME}

It may look simple, but by doing this you've already solved 50% of the problem, the other 50% can be solved by doing the following inside one of your PHP files :

Code:
// The username (or any dynamic variable)
$username = $this->session->userdata('username');

// Get the file show earlier
$file = $this->load->view('file_i_showed_earlier','',true);

// Replace {USENAME} with the dynamic username, probably faster to use preg_replace()
$results = str_replace('{USERNAME}',$username,$file);

// Output the results
echo $results;

This will result in a cached page with dynamic content. In this example I used pseudo code wrapper in curly brackets, but you could use anything as long as it isn't real PHP code (since this doesn't work).

I am aware that there's already a library out there that does it (Sparks), but it hasn't been updated for the past 2 years. Besides that it uses templates and some other stupid stuff that shouldn't be required for a cache system.

If you have any ideas on how to do this in a better way, feel free to post a comment Smile
#2

[eluser]JoostV[/eluser]
You could also use Zend Framework's zend_cache, which is very flexible and good at partial caching.
#3

[eluser]ChrisMiller[/eluser]
Or you could also modify the source code like I did to automatically replaces USERNAME etc when a file is loaded. We also use this hook to add in our advertisments based on what we set like {AD=125x125} etc... Its really not hard to write at all. I am sure CI has a way to do it through a hook just it was faster for us to add in an include before output and run it through our function then look it all up.
#4

[eluser]Phil Sturgeon[/eluser]
Sorry Yorick, I don't want to look like im shooting you down again but...

That is a fairly long way round, and seems to do what the Parser library does. It also does not address one of the main points of caching, stopping your data being called multiple times. Your code will still require the data to be fetched and handled so in the end all you are doing is storing compiled PHP/HTML which is only a fraction of the work being done.

One good solution would be to use the Cache library in PyroCMS that is based on MP_Cache (but much less buggy) and add a new method in. We already have library() and model() so why not add view() which checks a page is cached before outputting content or returning FALSE and set_view() which accepts the page to load and the data for it.

Combine that with a method that will allow a sort of salt. This would just be something user specific like the users id or similar. Basically that user id would be passed into the cache file names and hashed.

Syntax would be:

Code:
// Set this somewhere high up, MY_Controller perhaps?
$this->cache->salt($user->id);

// .... now we are in the controller

// Checks for cached view, will either return FALSE our directly output the view.
// Pass TRUE to the third parameter to return the cached view as a string instead
if($this->cache->view('something') === FALSE)
{
  // Cache doesn't exist so lets fetch our data.
  $data = $this->random_model->get();
  $this->cache->set_view('something', $data);
}

That should do everything you are looking for with minimal extra code in your controller.
#5

[eluser]Yorick Peterse[/eluser]
[quote author="Phil Sturgeon" date="1246887130"]Sorry Yorick, I don't want to look like im shooting you down again but...

That is a fairly long way round, and seems to do what the Parser library does. It also does not address one of the main points of caching, stopping your data being called multiple times. Your code will still require the data to be fetched and handled so in the end all you are doing is storing compiled PHP/HTML which is only a fraction of the work being done.

One good solution would be to use the Cache library in PyroCMS that is based on MP_Cache (but much less buggy) and add a new method in. We already have library() and model() so why not add view() which checks a page is cached before outputting content or returning FALSE and set_view() which accepts the page to load and the data for it.

Combine that with a method that will allow a sort of salt. This would just be something user specific like the users id or similar. Basically that user id would be passed into the cache file names and hashed.

Syntax would be:

Code:
// Set this somewhere high up, MY_Controller perhaps?
$this->cache->salt($user->id);

// .... now we are in the controller

// Checks for cached view, will either return FALSE our directly output the view.
// Pass TRUE to the third parameter to return the cached view as a string instead
if($this->cache->view('something') === FALSE)
{
  // Cache doesn't exist so lets fetch our data.
  $data = $this->random_model->get();
  $this->cache->set_view('something', $data);
}

That should do everything you are looking for with minimal extra code in your controller.[/quote]

I know it isn't the best way, considering it will go through the PHP parser anyway. The thing is though that this is the only way to do this without using files that are split into seperate files (such as template files). Right now I split the file up into two blocks anyway, it doesn't make much of a difference but it's still a few miliseconds faster.

And about your cache library, I took a look at it before I actually downloaded MP_Cache, the problem is that your library doesn't do anything at all when using MP_Cache's syntax, whereas MP_Cache 2.0b5 works fine (though it's 10 ms slower than using no cache at all).

Quote:Combine that with a method that will allow a sort of salt. This would just be something user specific like the users id or similar. Basically that user id would be passed into the cache file names and hashed.

Syntax would be:

Code:
// Set this somewhere high up, MY_Controller perhaps?
$this->cache->salt($user->id);

// .... now we are in the controller

// Checks for cached view, will either return FALSE our directly output the view.
// Pass TRUE to the third parameter to return the cached view as a string instead
if($this->cache->view('something') === FALSE)
{
  // Cache doesn't exist so lets fetch our data.
  $data = $this->random_model->get();
  $this->cache->set_view('something', $data);
}

Wouldn't that make you end up having a billion cache files for every user, which in turn doesn't decreases the server load, it only shifts the load from the database to the I/O.

The thing you could also do is to define the cache regions inside the template, but this would prevent the system from caching the file at all if the designers forgets to add it. Doing so would make you end up having something like the following :

Code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
&lt;html &gt;
&lt;head&gt;
    &lt;meta http-equiv="Content-type" content="text/html; charset=utf-8" /&gt;
    &lt;title&gt;&lt;/title>
&lt;/head&gt;
&lt;body&gt;

// Check if we want to cache at all
&lt;?php if(cache_enabled() == true) : ?&gt;

// Cache the block below
&lt;?php start_cache(); ?&gt;

<p>Welcone &lt;?php echo $username; ?&gt;</p>

// Stop the cache block
&lt;?php end_cache(); ?&gt;

// End the if statement
&lt;?php endif; ?&gt;
&lt;/body&gt;
&lt;/html&gt;
#6

[eluser]Phil Sturgeon[/eluser]
I forked the MP_Cache library months ago, and it seems the only modifications to the library are adding more helper function's and alternative syntax. I don't need 5 ways of doing the same thing, the 1 way is fine with me.

You're method has a simple elegance to it but as always here is my feedback.

The if(cache_enabled()) check would be better off in the start/end functions themselves as otherwise with cache turned off it would do nothing :-)

start_cache('something', $user_id) could set the cache area 'something' with the user id going in as a salt again. Then it can hash the name, check it in the cache file and if it doesnt exist it can catch the output buffer content and store it in the file.

One thing is working out how to skip to the end_cache if not. This actually would be a perfect opportunity to use goto... :-$ As we are not n00bs and wont be using goto, how about just:

Code:
// Cache the block below
&lt;?php if(!cache_block()) { ?&gt;

<p>Welcone &lt;?php echo $username; ?&gt;</p>

// Stop the cache block
&lt;?php } end_cache(); ?&gt;

That function can return false if its not there, therefore running the code and capturing the output buffer.

Remember though that this method still does not stop data being pulled out of the database multiple times. I guess that doesn't matter much if you cache the data AND the HTML.
#7

[eluser]Yorick Peterse[/eluser]
[quote author="Phil Sturgeon" date="1246903201"]I forked the MP_Cache library months ago, and it seems the only modifications to the library are adding more helper function's and alternative syntax. I don't need 5 ways of doing the same thing, the 1 way is fine with me.

You're method has a simple elegance to it but as always here is my feedback.

The if(cache_enabled()) check would be better off in the start/end functions themselves as otherwise with cache turned off it would do nothing :-)

start_cache('something', $user_id) could set the cache area 'something' with the user id going in as a salt again. Then it can hash the name, check it in the cache file and if it doesnt exist it can catch the output buffer content and store it in the file.

One thing is working out how to skip to the end_cache if not. This actually would be a perfect opportunity to use goto... :-$ As we are not n00bs and wont be using goto, how about just:

Code:
// Cache the block below
&lt;?php if(!cache_block()) { ?&gt;

<p>Welcone &lt;?php echo $username; ?&gt;</p>

// Stop the cache block
&lt;?php } end_cache(); ?&gt;

That function can return false if its not there, therefore running the code and capturing the output buffer.

Remember though that this method still does not stop data being pulled out of the database multiple times. I guess that doesn't matter much if you cache the data AND the HTML.[/quote]

Database caching could be handled by CodeIgniter's cache sytem, this idea is only ment to handle file caching. The main problem with this approach is to tell CodeIgniter to only cache the content between those blocks, such as the following :

Code:
&lt;?php start_cache() { ?&gt;

<p>Cache this stuff</p>

&lt;?php } end_cache(); ?&gt;

Code:
&lt;?php echo $cache_content; ?&gt; // Would output <p>Cache this stuff</p>
#8

[eluser]Phil Sturgeon[/eluser]
Well ok, if you aren't worried about a 'cache the lot at the same time' approach then this could work.

As I said just grab the output buffer contents and put them in a cache file. Each cache block would need a unique name and that can be checked against existing cache files.

You wouldnt even need echo $cache_content. You could simply send it off to the ouptut class in the end_cache() function.

This means it will only run the code if it has no cache file, and if it does have a cache file it will skip to end_cache() and output the text store.

I really cant see this speeding things up unless you have A LOT! of HTML. The data is the part that needs to be cached the most. In fact this may actually be making it slower lol.
#9

[eluser]Yorick Peterse[/eluser]
I don't see why this would cache something over and over...
#10

[eluser]Phil Sturgeon[/eluser]
It doesnt "cache it over and over" im just trying to work out if this really adds any benefit. If the HTML was being cached along with the data then it would help, but if both are being cached separately (or the data not at all) then this method will be slower or about the same... right?

I don't mean to be argumentative, i'd live to be proved wrong if you get this going and have some numbers for us.




Theme © iAndrew 2016 - Forum software by © MyBB