If you need to sprinkle in some caching without building a bunch of Repositories. I've found this base Model and Service Provider very helpful. For updates: https://gist.github.com/danrichards/de22efa41b533d81fa1ec15f42cf64f5
// Model.php
<?php
namespace App;
use Cache;
use Closure;
use Illuminate\Database\Eloquent\Model as BaseModel;
/**
* Class Model
*/
abstract class Model extends BaseModel
{
/**
* If you want to specify the result of a Builder::get() as a property, you
* may do so by specifying a query property. All this does is magically
* call the a method or query scope on your call and hit it with get().
*
* @see \App\User::customers()
*
* e.g. User::find(1)->customers; // \Illuminate\Database\Eloquent\Collection
*
* Works with objects you can call get() on.
*
* e.g. instanceof:
*
* \Illuminate\Database\Query\Builder
*
* To add caching automatically, use the $cache_related property instead.
*
* @var array $query_props */
protected $query_props = [
// 'method_name', // Will not cache
// 'ex_2' => '' // Non-integer will not cache
// 'ex_3' => 30, // Cache for 30 minutes
// 'ex_4' => 0 // Cache forever
];
/**
* Same as query props but more explicit in that there is caching.
*
* e.g. instanceof:
*
* \Illuminate\Database\Query\Builder
* \Illuminate\Database\Query\HasMany
* \Illuminate\Database\Query\HasManyOne
* \Illuminate\Database\Query\HasManyThrough
*
* @var array
*/
protected $cache_related = [
// 'attr_name' // Cache forever
// 'ex_2' => 0 // Cache forever
// 'ex_3' => 30 // Cache for 30 minutes
];
/**
* Use Laravel's get attribute mutator to conveniently store cached data.
*
* Leverage for simple solutions for Models that do not have Repositories
* already, if you feel like you're doing something dirty, then that means
* you should create a new Repository for your model.
*
* @see https://laravel.com/docs/5.1/eloquent-mutators#accessors-and-mutators
* @var array $cache_attributes */
protected $cache_attributes = [
// 'attr_name' // Cache forever
// 'ex_2' => 0 // Cache forever
// 'ex_3' => 30 // Cache for 30 minutes
];
/**
* Simple attribute caching, for more robust caching, use a Repository
*
* @param string $related
* @param null $id
* @return string
*/
private function cacheAttributeKey($related, $id = null)
{
$id = $id ?: $this->getKey();
return implode('|', [get_class($this), $id, 'attribute', $related]);
}
/**
* Simple method caching, for more robust caching, use a Repository
*
* @param string $attribute
* @param null $id
* @return string
*/
private function cacheRelatedKey($attribute, $id = null)
{
$id = $id ?: $this->getKey();
return implode('|', [get_class($this), $id, 'related', $attribute]);
}
/**
* @param string $attribute
* @param int $minutes
* @param Closure $callback
* @return mixed
*/
private function cacheAttribute($attribute, $minutes, Closure $callback)
{
return Cache::remember($this->cacheAttributeKey($attribute), $minutes, $callback);
}
/**
* @param string $attribute
* @param int $minutes
* @param Closure $callback
* @return mixed
*/
private function cacheRelated($attribute, $minutes, Closure $callback)
{
return Cache::remember($this->cacheRelatedKey($attribute), $minutes, $callback);
}
/**
* @param string|array|null $attribute
*/
public function cacheAttributeBust($attribute = null, $parent_id)
{
$attribute = is_null($attribute)
? static::normalizeAssociative($this->cache_attributes, 0)
: $attribute;
$attribute = is_string($attribute)
? (array) $attribute
: $attribute;
foreach ($attribute as $a) {
Cache::forget($this->cacheAttributeKey($a));
}
}
/**
* @param string|array|null $related
*/
public function cacheRelatedBust($related = null)
{
$related = is_null($related)
? static::normalizeAssociative($this->cache_related, 0)
: $related;
$related = is_string($related)
? (array) $related
: $related;
foreach ($related as $r) {
Cache::forget($this->cacheRelatedKey($r));
}
}
/**
* @param string $parent_class The Model
* @param string|int $parent_id The Model's primary key value
* @param string $sub_key A classification / category
* @param string $key Cache key name
*/
public static function cacheBust($parent_class, $parent_id, $sub_key, $key)
{
Cache::forget(implode('|', func_get_args()));
}
/**
* @param string $name
* @return mixed
*/
public function __get($name)
{
$query_props_normalized = static::normalizeAssociative($this->query_props, 'no-cache');
$cache_attributes_normalized = static::normalizeAssociative($this->cache_attributes, 0);
$cache_related_normalized = static::normalizeAssociative($this->cache_related, 0);
/**
* Handle query props.
*/
if (in_array($name, array_keys($query_props_normalized))) {
return call_user_func([$this, $name])->get();
}
/**
* Handle any related models that are cached.
*/
if (in_array($name, array_keys($cache_related_normalized))) {
$minutes = (int) $cache_related_normalized[$name];
return $this->cacheRelated($name, $minutes, function() use($name) {
return call_user_func([$this, $name])->get();
});
}
/**
* Handle and get attribute mutators that are cached.
*/
if (in_array($name, array_keys($cache_attributes_normalized))) {
$minutes = (int) $cache_attributes_normalized[$name];
return $this->cacheAttribute($name, $minutes, function() use($name) {
return $this->getAttribute($name);
});
}
return parent::__get($name);
}
/**
* Build out an associative array with mixed values.
*
* e.g.
*
* [
* 'cats',
* 'dogs' => 1
* ]
*
* becomes
*
* [
* 'cats' => 0,
* 'dogs' => 1
* ]
*
* @todo Move this method somewhere in a Util class.
*
* @param array $arr
* @param int $default_value
* @return array
*/
public static function normalizeAssociative(array $arr, $default_value = 0)
{
$normalized = [];
foreach($arr as $key => $value) {
$new_key = $key;
$new_value = $value;
if (is_int($key)) {
$new_key = $value;
$new_value = $default_value;
}
$normalized[$new_key] = $new_value;
}
return $normalized;
}
}
// ModelCacheServiceProvider.php, don't forget to config/app.php
<?php
namespace App\Providers;
use App\Model;
// use App\User;
// use App\Models\Order;
// use App\Models\Listing;
use Illuminate\Support\ServiceProvider;
/**
* Class ModelCacheServiceProvider
*/
class ModelCacheServiceProvider extends ServiceProvider
{
/**
* Automatically clear caches for related data, cached on Parent models.
*
* @see Model::$cache_related
* @var array $uses_cache_related */
public static $uses_cache_related = [
// [
// 'model' => User::class,
// 'related' => Listing::class,
// 'foreign_key' => 'user_id',
// 'methods' => ['listings'],
// 'events' => ['saved', 'created', 'deleted']
// ]
];
/**
* Automatically clear caches for related data, cached on Parent models.
*
* @see Model::$cache_related
* @var array $uses_cache_attributes */
public static $uses_cache_attributes = [
// [
// 'model' => User::class,
// 'related' => Commission::class,
// 'foreign_key' => 'user_id',
// 'attributes' => ['total_owed'],
// 'events' => ['saved', 'created', 'deleted']
// ]
];
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
// For any usages of "uses cache related"
foreach (static::$uses_cache_related as $purge_event) {
// And for any of the specified model events
$events = $purge_event['events'];
foreach($events as $event) {
$model = $purge_event['model'];
$related = $purge_event['related'];
$foreign_key = $purge_event['foreign_key'];
$methods = $purge_event['methods'];
// Add a listener to the related model to purge the parent's key
call_user_func([$related, $event], function($r) use($model, $foreign_key, $methods) {
$parent_id = $r->{$foreign_key};
// To bust the related caches
foreach ($methods as $method) {
call_user_func_array([$model, 'cacheBust'], [$model, $parent_id, 'related', $method]);
}
});
}
}
// For any usages of "uses cache attributes"
foreach (static::$uses_cache_attributes as $purge_event) {
// And for any of the specified model events
$events = $purge_event['events'];
foreach($events as $event) {
$model = $purge_event['model'];
$related = $purge_event['related'];
$foreign_key = $purge_event['foreign_key'];
$attributes = $purge_event['attributes'];
// Add a listener to the related model to purge the parent's key
call_user_func([$related, $event], function($r) use($model, $foreign_key, $attributes) {
$parent_id = $r->{$foreign_key};
// To bust the related caches
foreach ($attributes as $attribute) {
call_user_func_array([$model, 'cacheBust'], [$model, $parent_id, 'attribute', $attribute]);
}
});
}
}
}
/**
* Register the application services.
*
* @return void
*/
public function register()
{
}
}