The Invision Community software provides two separate, but related, methods of caching data: the data store, and caching abstraction layers. It is essential that both types of caching be considered volatile, and that the data can be regenerated on the fly as needed. In other words, while using a caching layer to speed up the application is a good idea in many cases, your code must handle situations where the data is unavailable in the cache gracefully.
The data store
The primary method of caching data throughout the Invision Community software is the data store. The data store is accessed through \IPS\Data\Store::i()->$key where $key is any string key that you desire to use (although you should take care to prevent key collisions by choosing a sufficiently unique key name). To set data in the data store you need only set the variable like so
\IPS\Data\Store::i()->my_key_name = "Some data";
That's it - the data will be saved in the storage location configured by the administrator (which may be on disk, or in the database).
To later retrieve the value that was set, you just call the data store like so
echo \IPS\Data\Store::i()->my_key_name;
You can also check if the key has been set, and unset it if desired.
if( isset( \IPS\Data\Store::i()->my_key_name ) ) { unset( \IPS\Data\Store::i()->my_key_name ); }
The data store library will take care of fetching, saving and deleting the values automatically when used in this manner.
The data store can be cleared at any time, so it is important that you ensure your values exist before relying on them. For instance, instead of simply echoing the value as shown above, you should do something similar to the following instead
if( !isset( \IPS\Data\Store::i()->my_key_name ) ) { \IPS\Data\Store::i()->my_key_name = 'my value'; } echo \IPS\Data\Store::i()->my_key_name;
Be aware that running the support tool, upgrading, and other features of the software will clear data store entries entirely.
The caching layer
In addition to the data store feature built into Invision Community software, true caching to a caching layer is also possible. By default, the following caching layers are supported:
- Redis
It is possible for third parties to add support for other caching layers as well, however userland code does not need to know what caching layer is in use (if any).
Firstly, it is worth noting that any values stored in the data store are automatically cached to the configured alternate caching layer if present. This is handled transparently, and you should still simply call to \IPS\Data\Store::i()->xxxxx regardless.
If your application would benefit from storing additional data in a caching engine that does not need to be stored in the data store, you may access the caching layer directly as well. For example, the Invision Community software has the capability to cache entire pages for guests for a configured amount of time, and this is not stored in the data store but is stored in the alternate caching layer if present (in effect, this means that your site can serve entire pages to guests through Memcache or a similar engine without ever even needing to establish a database connection).
The caching layer will automatically fallback to a special None class if no caching layer is configured, so you do not need to worry about whether it is set up or not. You simply call to it as you would the data store, like so
if( isset( \IPS\Data\Cache::i()->my_cache_key ) ) { echo \IPS\Data\Cache::i()->my_cache_key; } else { $variable = 'some data'; \IPS\Data\Cache::i()->my_cache_key = $variable; echo $variable; }
It is important to remember that caching systems typically automatically delete older or least frequently accessed data as they run out of storage room, so (like the data store) all data stored in a caching layer must be considered volatile and cannot be relied on to be present.
Other caching notes
Beyond these explicit caching classes that you can make use of, there are a couple of other notes regarding caching that are worth mentioning.
Inline caching
It is not uncommon when a page request is executed that a method may be called multiple times, and if that method performs resource-intensive work, this can slow your application down. It is good practice, when possible, to cache data that has been fetched or computed once and to return that data. In most cases, it is as simple as the following (assume this is pseudo-code inside a class):
/** * @brief Property to temporarily store computed data */ protected $temporaryCache = NULL; /** * This method does something computationaly expensive * * @return string */ public function resourceIntensiveMethod() { if( $this->temporaryCache !== NULL ) { return $this->temporaryCache; } // Do some work $variable = '....'; $this->temporaryCache = $variable; return $this->temporaryCache; }
The premise of this pseudo-code is that when our slow method is called, we check first to see if we've already done the work, and if so we return it. If not, we perform the work we need to do, but then store the result in a variable so that next time we can return it quicker. The values will not persist to another process or page load, but if this method took 2 seconds to execute and was called 5 times, the page would take at least 10 seconds to load. By caching the data after the first call, we can drop the page loading time down to the 2 seconds this method takes to execute the first time.
Keep in mind that there is a trade off with all caching. For example, PHP has a memory_limit and storing too much data in class properties in this manner could cause your application to more easily reach the configured memory limit, resulting in a PHP error. You will need to decide while working on your application what data is worth caching and what data isn't. Tools like XDebug can help you identify bottlenecks in your code to focus on.
HTML/Resource caching
Another way to take advantage of caching is by instructing browsers to cache the response that is sent to them. Before proceeding, it is worth mentioning that browser-based caching cannot be strictly relied upon. Some user agents may ignore caching headers sent to them, proxy servers can be set up which may ignore caching headers (or may cache resources unexpectedly), and so on. Additionally, you generally do not want browsers to cache most dynamically generated pages because they will be different on each page load (on one page load you may be a guest, and then you may log in and be viewing the page as a logged in user on the next visit for example). To that end, you should be careful and not rely on browser-based caching. That said, if you are sending a static resource to the browser you may wish to instruct the browser to cache the file to reduce overall strain on the server for later requests.
To send caching headers with a response you must send the appropriate HTTP response headers in the 4th parameter passed to \IPS\Output::i()->sendOutput(). A utility method is available to help you reliably set the headers.
\IPS\Output::i()->sendOutput( "The output to cache, e.g. a file", 200, 'text/html', \IPS\Output::getCacheHeaders( time(), 360 ) );
The first parameter to getCacheHeaders() is the time the file was last modified, and the second is how long to cache the response for. This is used, for example, when an attachment is downloaded so that if the user attempts to redownload the attachment within a short period of time their browser may retrieve the already downloaded file from the browser cache without having to request it again from the server.
Caches inside ActiveRecord classes
If you have any data which need to be rebuilt after an item was created, editor or deleted, you can use the $cache property inside the AR class which will prune the necessary caches automatically!
Report Document