Transparent Caching of Eloquent Models
In a Laravel application I had to iterate a large set of items and, for each of them, access to a related other item which, in most cases, is selected within a smaller set. The reference example: a lot of food products, each having a unit measure such as "kilos", "liters", and a few others.
$products = Product::get();
foreach ($products as $product) {
echo $product->measure->name;
}
This triggers a new query for each access to the measure
attribute of each $product
, since each product has be to populated with his own measure: not quite affordable when iterating thousands of products...
Given that Eloquent is smart enough to optimize this kind of stuffs, and the above sample can be fixed as easily as
$products = Product::with('measure')->get();
foreach ($products as $product) {
echo $product->measure->name;
}
it may happen that, given lots of stratifications in the internal abtraction of the application, it is not possible to optimize the upper query. So, I've introduced a sort of "transparent caching" of the attributes.
In a common Trait class, I've defined the following function:
public function __get($name)
{
if (substr($name, -3) == '_ro') {
$name = substr($name, 0, strlen($name) - 3);
$key = $this->{$name}()->getForeignKey();
$id = $this->$key;
return Cache::remember($name . '_' . $id, 1, function() use ($name) {
return $this->{$name};
});
}
return parent::__get($name);
}
This overwrites the __get()
function of Model
, and intercepts accesses to attribute_ro
attributes (where "ro" stands for "read only"), interacting with the system wide Cache
system of Laravel to keep a copy of already loaded models.
So
$product->measure
fetches the value from the database, and
$product->measure_ro
tries before to fetch it from cache (and access $product->measure
if needed).
The function is able to retrieve itself the relevant foreign key for the required relationship, and use its value as unique key for the cached element. When the same value of the foreign key is asked, the value stored in cache is returned.
Crazy enough to work...