megaads/laravel-query-cache

该包最新版本(2.5.3)没有提供许可证信息。

Laravel模型缓存。

2.5.3 2020-12-03 06:32 UTC

README

CI codecov StyleCI Latest Stable Version Total Downloads Monthly Downloads License

Laravel Eloquent Query Cache重新引入了Laravel很久以前移除的remember()功能。它直接在Eloquent级别添加缓存功能,利用数据库查询中的缓存。

🤝 支持

Renoki Co.在GitHub上的目标是向世界带来许多开源项目和有用的项目。每天开发和维护项目是一项艰巨的工作,但我们热爱它。

如果你在日常工作中使用应用程序,在演示中,爱好项目或甚至是学校项目中,请为我们的工作说一些好话或赞助我们的工作。善意的言语会触动我们的心弦和氛围,而赞助将使开源项目保持活力。

ko-fi

🚀 安装

进入你的控制台,通过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变量时,该包会为你的模型附加一个观察者,任何createdupdateddeletedforceDeletedrestored事件都会触发缓存失效。

为了使自动刷新工作,你需要至少有一个基本标签。开箱即用,模型已经设置了基本标签。在某些情况下,如果你用空数组覆盖了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 联系,而不是使用问题跟踪器。

🎉 致谢