megaads / laravel-query-cache
Laravel模型缓存。
Requires
- php: >=5.6.4
README
Laravel Eloquent Query Cache重新引入了Laravel很久以前移除的remember()功能。它直接在Eloquent级别添加缓存功能,利用数据库查询中的缓存。
🤝 支持
Renoki Co.在GitHub上的目标是向世界带来许多开源项目和有用的项目。每天开发和维护项目是一项艰巨的工作,但我们热爱它。
如果你在日常工作中使用应用程序,在演示中,爱好项目或甚至是学校项目中,请为我们的工作说一些好话或赞助我们的工作。善意的言语会触动我们的心弦和氛围,而赞助将使开源项目保持活力。
🚀 安装
进入你的控制台,通过Composer安装包
$ composer require rennokki/laravel-eloquent-query-cache
你想要缓存的所有模型都应该使用Rennokki\QueryCache\Traits\QueryCacheable特性。
use Rennokki\QueryCache\Traits\QueryCacheable; class Podcast extends Model { use QueryCacheable; ... }
🙌 使用
该包具有跟踪SQL使用情况并将其用作缓存存储中的键的功能,使得按查询缓存变得非常容易。
use Rennokki\QueryCache\Traits\QueryCacheable; class Article extends Model { use QueryCacheable; public $cacheFor = 3600; // cache time, in seconds ... } // SELECT * FROM articles ORDER BY created_at DESC LIMIT 1; $latestArticle = Article::latest()->first(); // SELECT * FROM articles WHERE published = 1; $publishedArticles = Article::wherePublished(true)->get();
在上面的例子中,这两个查询在缓存存储中有不同的键,因此我们处理什么查询都无关紧要。默认情况下,除非你为$cacheFor指定值,否则缓存是禁用的。只要$cacheFor存在且大于0,所有查询都将被缓存。
也可以通过不在查询中指定$cacheFor并在其中调用cacheFor()来为特定查询启用缓存
$postsCount = Post::cacheFor(60 * 60)->count(); // Using a DateTime instance like Carbon works perfectly fine! $postsCount = Post::cacheFor(now()->addDays(1))->count();
缓存标签与缓存失效
一些缓存存储接受标签。如果你计划对你的缓存查询进行标记,并在需要时仅失效一些查询,这将非常有用。
$shelfOneBooks = Book::whereShelf(1) ->cacheFor(60) ->cacheTags(['shelf:1']) ->get(); $shelfTwoBooks = Book::whereShelf(2) ->cacheFor(60) ->cacheTags(['shelf:2']) ->get(); // After flushing the cache for shelf:1, the query of$shelfTwoBooks will still hit the cache if re-called again. Book::flushQueryCache(['shelf:1']); // Flushing also works for both tags, invalidating them both, not just the one tagged with shelf:1 Book::flushQueryCache(['shelf:1', 'shelf:2']);
但是要小心 - 指定缓存标签不会改变键存储的行为。例如,以下两个查询虽然使用了相同的标签,但它们在缓存数据库中存储了不同的键。
$alice = Kid::whereName('Alice') ->cacheFor(60) ->cacheTags(['kids']) ->first(); $bob = Kid::whereName('Bob') ->cacheFor(60) ->cacheTags(['kids']) ->first();
全局缓存失效
要失效特定模型的全部缓存,请使用不带标签的flushQueryCache方法。
该包自动为每个来自模型的查询追加一个标签列表,称为每个查询的基本标签。默认为完整的模型类名。
如果你想更改基本标签,你可以在你的模型中这样做。
class Kid extends Model { use QueryCacheable; /** * Set the base cache tags that will be present * on all queries. * * @return array */ protected function getCacheBaseTags(): array { return [ 'custom_tag', ]; } } // Automatically works with `custom_tag` Kid::flushQueryCache();
完全自动失效
为了加快你的应用程序中失效的构建,你可以指定模型在创建、更新或删除任何记录时自动刷新缓存。
class Page extends Model { use QueryCacheable; /** * Invalidate the cache automatically * upon update in the database. * * @var bool */ protected static $flushCacheOnUpdate = true; }
当你设置$flushCacheOnUpdate变量时,该包会为你的模型附加一个观察者,任何created、updated、deleted、forceDeleted或restored事件都会触发缓存失效。
为了使自动刷新工作,你需要至少有一个基本标签。开箱即用,模型已经设置了基本标签。在某些情况下,如果你用空数组覆盖了
getCacheBaseTags(),它可能不起作用。
部分自动失效
在某些情况下,你可能不希望失效特定模型的整个缓存。也许你有两个单独运行的查询,只想为其中一个失效缓存。
要完成这个操作,请在您的模型中覆盖 getCacheTagsToInvalidateOnUpdate() 方法。
class Page extends Model { use QueryCacheable; /** * Invalidate the cache automatically * upon update in the database. * * @var bool */ protected static $flushCacheOnUpdate = true; /** * When invalidating automatically on update, you can specify * which tags to invalidate. * * @return array */ public function getCacheTagsToInvalidateOnUpdate(): array { return [ 'query1', ]; } } $query1 = Page::cacheFor(60) ->cacheTags(['query1']) ->get(); $query2 = Page::cacheFor(60) ->cacheTags(['query2']) ->get(); // The $query1 gets invalidated // but $query2 will still hit from cache if re-called. $page = Page::first(); $page->update([ 'name' => 'Reddit', ]);
请注意:将 $flushCacheOnUpdate 设置为 true 并不指定要使无效的单个标签将导致 完全自动无效化,因为默认要使无效的标签是基础标签,并且您至少需要一个要使无效的标签。
未指定要使无效的标签将回退到基础标签集合,从而导致完全自动无效化。
关系缓存
关系只是另一种查询。它们可以在数据库查询之前拦截和修改。以下示例需要 Order 模型(或与 orders 关系关联的模型)包含 QueryCacheable 特性。
$user = User::with(['orders' => function ($query) { return $query ->cacheFor(60 * 60) ->cacheTags(['my:orders']); }])->get(); // This comes from the cache if existed. $orders = $user->orders;
缓存键
该包会自动生成存储在缓存存储中所需的数据键。然而,如果缓存存储被其他应用程序和/或模型使用,并且您希望更好地管理键以避免冲突,则添加前缀可能是有用的。
$bob = Kid::whereName('Bob') ->cacheFor(60) ->cachePrefix('kids_') ->first();
如果没有指定前缀,将使用字符串 leqc。
缓存驱动程序
默认情况下,该特性使用默认的缓存驱动程序。如果您想 强制 使用特定的一个,可以通过调用 cacheDriver() 来实现。
$bob = Kid::whereName('Bob') ->cacheFor(60) ->cacheDriver('dynamodb') ->first();
禁用缓存
如果您启用了缓存(无论是通过模型变量还是通过 cacheFor 范围),您还可以选择在查询构建链中禁用它。
$uncachedBooks = Book::dontCache()->get(); $uncachedBooks = Book::doNotCache()->get(); // same thing
等效方法和变量
您可以使用此文档中提供的方法按查询查询,或者您可以在模型中为每个设置默认值;使用按查询查询的方法将覆盖默认值。虽然设置默认值不是强制性的(除了 $cacheFor 将在 所有 查询上启用缓存),但它可以避免在每个查询上使用链式方法很有用。
class Book extends Model { public $cacheFor = 3600; // equivalent of ->cacheFor(3600) public $cacheTags = ['books']; // equivalent of ->cacheTags(['books']) public $cachePrefix = 'books_' // equivalent of ->cachePrefix('books_'); public $cacheDriver = 'dynamodb'; // equivalent of ->cacheDriver('dynamodb'); }
高级
在您的Builder类中实现缓存方法
由于此包修改了模型中的 newBaseQueryBuilder(),因此具有多个修改此函数的特性将导致重叠。
这可能会发生在您正在为其他数据库驱动程序创建自己的Builder类或只是为了让您的应用程序查询构建器更灵活。
为了解决这个问题,您只需将 \Rennokki\QueryCache\Traits\QueryCacheModule 特性和 \Rennokki\QueryCache\Contracts\QueryCacheModuleInterface 接口添加到您的 Builder 类中。确保模型将不再使用原始的 QueryCacheable 特性。
use Rennokki\QueryCache\Traits\QueryCacheModule; use Illuminate\Database\Query\Builder as BaseBuilder; // the base laravel builder use Rennokki\QueryCache\Contracts\QueryCacheModuleInterface; // MyCustomBuilder.php class MyCustomBuilder implements QueryCacheModuleInterface { use QueryCacheModule; // the rest of the logic here. } // MyBuilderTrait.php trait MyBuilderTrait { protected function newBaseQueryBuilder() { return new MyCustomBuilder( // ); } } // app/CustomModel.php class CustomModel extends Model { use MyBuilderTrait; } CustomModel::cacheFor(30)->customGetMethod();
生成自己的键
这是默认键生成函数的示例
public function generatePlainCacheKey(string $method = 'get', $id = null, $appends = null): string { $name = $this->connection->getName(); // Count has no Sql, that's why it can't be used ->toSql() if ($method === 'count') { return $name.$method.$id.serialize($this->getBindings()).$appends; } return $name.$method.$id.$this->toSql().serialize($this->getBindings()).$appends; }
在某些情况下,例如实现自己的Builder用于MongoDB,您可能不想使用 toSql() 并使用您自己的生成每个-sql键的方法。您可以通过覆盖 MyCustomBuilder 类的 generatePlainCacheKey() 并使用您自己的来做到这一点。
然而,强烈建议使用函数提供的变量中的大多数,以避免缓存重叠问题。
class MyCustomBuilder implements QueryCacheModuleInterface { use QueryCacheModule; public function generatePlainCacheKey(string $method = 'get', $id = null, $appends = null): string { $name = $this->connection->getName(); // Using ->myCustomSqlString() instead of ->toSql() return $name.$method.$id.$this->myCustomSqlString().serialize($this->getBindings()).$appends; } }
为除get()之外的函数实现缓存
由于所有Laravel Eloquent函数都基于它,因此此包提供的构建器仅替换 get()。
class Builder { public function get($columns = ['*']) { if (! $this->shouldAvoidCache()) { return $this->getFromQueryCache('get', $columns); } return parent::get($columns); } }
如果您想缓存自定义Builder或自定义的 count() 方法不依赖于 get() 的自己的方法,可以使用此语法替换它。
class MyCustomBuilder { public function count() { if (! $this->shouldAvoidCache()) { return $this->getFromQueryCache('count'); } return parent::count(); } }
实际上,如果您使用 $this->shouldAvoidCache() 检查并使用 getFromQueryCache() 方法检索缓存的数据库,并将方法名作为字符串传递,并可选地传递一个默认为 ['*'] 的列数组,您还可以替换您的构建器中的任何Eloquent方法。
注意,getFromQueryCache() 方法接受一个方法名和一个 $columns 参数。如果您的函数没有实现 $columns,则不要传递它。
请注意,一些函数如 getQueryCacheCallback() 可能会带有一个 $id 参数。由于查询构建器默认使用 ->get() 接受只有列的参数,因此该包的默认行为不使用它。
然而,如果你的构建器替换了如 find() 这样的函数,则需要使用 $id,并且你还需要如此替换 getQueryCacheCallback()
class MyCustomBuilder { public function getQueryCacheCallback(string $method = 'get', $columns = ['*'], $id = null) { return function () use ($method, $columns, $id) { $this->avoidCache = true; // the function for find() caching // accepts different params if ($method === 'find') { return $this->find($id, $columns); } return $this->{$method}($columns); }; } public function find($id, $columns = ['*']) { // implementing the same logic if (! $this->shouldAvoidCache()) { return $this->getFromQueryCache('find', $columns, $id); } return parent::find($id, $columns); } }
🐛 测试
vendor/bin/phpunit
🤝 贡献
有关详细信息,请参阅 贡献指南。
🔒 安全性
如果您发现任何与安全性相关的问题,请通过电子邮件 alex@renoki.org 联系,而不是使用问题跟踪器。