# Cache

[hyperf/cache](https://github.com/hyperf/cache) 提供了基於 `Aspect` 實現的切面快取，也提供了實現 `Psr\SimpleCache\CacheInterface` 的快取類。

## 安裝
```
composer require hyperf/cache
```

## 預設配置

|  配置  |                  預設值                  |         備註          |
|:------:|:----------------------------------------:|:---------------------:|
| driver |  Hyperf\Cache\Driver\RedisDriver  | 快取驅動，預設為 Redis |
| packer | Hyperf\Utils\Packer\PhpSerializer |        打包器         |
| prefix |                   c:                   |       快取字首        |

```php
<?php

return [
    'default' => [
        'driver' => Hyperf\Cache\Driver\RedisDriver::class,
        'packer' => Hyperf\Utils\Packer\PhpSerializerPacker::class,
        'prefix' => 'c:',
    ],
];
```

## 使用

### Simple Cache 方式

Simple Cache 也就是 [PSR-16](https://www.php-fig.org/psr/psr-16/) 規範，本元件適配了該規範，如果您希望使用實現 `Psr\SimpleCache\CacheInterface` 快取類，比如要重寫 `EasyWeChat` 的快取模組，可以直接從依賴注入容器中獲取 `Psr\SimpleCache\CacheInterface` 即可，如下所示：

```php

$cache = $container->get(\Psr\SimpleCache\CacheInterface::class);

```

### 註解方式

元件提供 `Hyperf\Cache\Annotation\Cacheable` 註解，作用於類方法，可以配置對應的快取字首、失效時間、監聽器和快取組。
例如，UserService 提供一個 user 方法，可以查詢對應 id 的使用者資訊。當加上 `Hyperf\Cache\Annotation\Cacheable` 註解後，會自動生成對應的 Redis 快取，key 值為 `user:id` ，超時時間為 `9000` 秒。首次查詢時，會從資料庫中查，後面查詢時，會從快取中查。

> 快取註解基於 [aop](zh-tw/aop.md) 和 [di](zh-tw/di.md)，所以只有在 `Container` 中獲取到的物件例項才有效，比如通過 `$container->get` 和 `make` 方法所獲得的物件，直接 `new` 出來的物件無法使用。

```php
<?php

namespace App\Services;

use App\Models\User;
use Hyperf\Cache\Annotation\Cacheable;

class UserService
{
    /**
     * @Cacheable(prefix="user", ttl=9000, listener="user-update")
     */
    public function user($id)
    {
        $user = User::query()->where('id',$id)->first();

        if($user){
            return $user->toArray();
        }

        return null;
    }
}
```

### 清理 `@Cacheable` 生成的快取

當然，如果我們資料庫中的資料改變了，如何刪除快取呢？這裡就需要用到後面的監聽器。下面新建一個 Service 提供一方法，來幫我們處理快取。

```php
<?php

declare(strict_types=1);

namespace App\Service;

use Hyperf\Di\Annotation\Inject;
use Hyperf\Cache\Listener\DeleteListenerEvent;
use Psr\EventDispatcher\EventDispatcherInterface;

class SystemService
{
    /**
     * @Inject
     * @var EventDispatcherInterface
     */
    protected $dispatcher;

    public function flushCache($userId)
    {
        $this->dispatcher->dispatch(new DeleteListenerEvent('user-update', [$userId]));

        return true;
    }
}
```

當我們自定義了 `Cacheable` 的 `value` 時，比如以下情況。

```php
<?php

declare(strict_types=1);

namespace App\Service\Cache;

use Hyperf\Cache\Annotation\Cacheable;

class DemoService
{
    /**
     * @Cacheable(prefix="cache", value="_#{id}", listener="user-update")
     */
    public function getCache(int $id)
    {
        return $id . '_' . uniqid();
    }
}
```

則需要對應修改 `DeleteListenerEvent` 建構函式中的 `$arguments` 變數，具體程式碼如下。

```php
<?php

declare(strict_types=1);

namespace App\Service;

use Hyperf\Di\Annotation\Inject;
use Hyperf\Cache\Listener\DeleteListenerEvent;
use Psr\EventDispatcher\EventDispatcherInterface;

class SystemService
{
    /**
     * @Inject
     * @var EventDispatcherInterface
     */
    protected $dispatcher;

    public function flushCache($userId)
    {
        $this->dispatcher->dispatch(new DeleteListenerEvent('user-update', ['id' => $userId]));

        return true;
    }
}
```

## 註解介紹

### Cacheable

例如以下配置，快取字首為 `user`, 超時時間為 `7200`, 刪除事件名為 `USER_CACHE`。生成對應快取 KEY 為 `c:user:1`。

```php
use App\Models\User;
use Hyperf\Cache\Annotation\Cacheable;

/**
 * @Cacheable(prefix="user", ttl=7200, listener="USER_CACHE")
 */
public function user(int $id): array
{
    $user = User::query()->find($id);

    return [
        'user' => $user->toArray(),
        'uuid' => $this->unique(),
    ];
}
```

當設定 `value` 後，框架會根據設定的規則，進行快取 `KEY` 鍵命名。如下例項，當 `$user->id = 1` 時，快取 `KEY` 為 `c:userBook:_1`

```php
use App\Models\User;
use Hyperf\Cache\Annotation\Cacheable;

/**
 * @Cacheable(prefix="userBook", ttl=6666, value="_#{user.id}")
 */
public function userBook(User $user): array
{
    return [
        'book' => $user->book->toArray(),
        'uuid' => $this->unique(),
    ];
}
```

### CachePut

`CachePut` 不同於 `Cacheable`，它每次呼叫都會執行函式體，然後再對快取進行重寫。所以當我們想更新快取時，可以呼叫相關方法。

```php
use App\Models\User;
use Hyperf\Cache\Annotation\CachePut;

/**
 * @CachePut(prefix="user", ttl=3601)
 */
public function updateUser(int $id)
{
    $user = User::query()->find($id);
    $user->name = 'HyperfDoc';
    $user->save();

    return [
        'user' => $user->toArray(),
        'uuid' => $this->unique(),
    ];
}
```

### CacheEvict

CacheEvict 更容易理解了，當執行方法體後，會主動清理快取。

```php
use Hyperf\Cache\Annotation\CacheEvict;

/**
 * @CacheEvict(prefix="userBook", value="_#{id}")
 */
public function updateUserBook(int $id)
{
    return true;
}
```

## 快取驅動

### Redis 驅動

`Hyperf\Cache\Driver\RedisDriver` 會把快取資料存放到 `Redis` 中，需要使用者配置相應的 `Redis 配置`。此方式為預設方式。

### 協程記憶體驅動

> 本驅動乃 Beta 版本，請謹慎使用。

如果您需要將資料快取到 `Context` 中，可以嘗試此驅動。例如以下應用場景 `Demo::get` 會在多個地方呼叫多次，但是又不想每次都到 `Redis` 中進行查詢。

```php
<?php
use Hyperf\Cache\Annotation\Cacheable;

class Demo {
    
    public function get($userId, $id)
    {
        return $this->getArray($userId)[$id] ?? 0;
    }

    /**
     * @Cacheable(prefix="test", group="co")
     */
    public function getArray(int $userId): array
    {
        return $this->redis->hGetAll($userId);
    }
}
```

對應配置如下：

```php
<?php

return [
    'co' => [
        'driver' => Hyperf\Cache\Driver\CoroutineMemoryDriver::class,
        'packer' => Hyperf\Utils\Packer\PhpSerializerPacker::class,
    ],
];
```

