# Cache System - Framework OfficeGest

> **Nível**: Básico a Avançado  
> **Pré-requisitos**: Conhecimento básico de PHP e conceitos de cache  
> **Tempo de leitura**: ~15 minutos

---

## 📋 Índice

1. [Introdução](#introdução)
2. [Recuperar e Guardar Itens](#recuperar-e-guardar-itens)
3. [Remover Itens](#remover-itens)
4. [Métodos Auxiliares](#métodos-auxiliares)
5. [Configuração](#configuração)
6. [Drivers de Cache](#drivers-de-cache)
7. [Exemplos Práticos do Projeto](#exemplos-práticos-do-projeto)
8. [Troubleshooting](#troubleshooting)

---

## Introdução

O sistema de cache do OfficeGest oferece uma API unificada e expressiva para diversos backends de armazenamento em cache. A configuração de cache encontra-se em `Modules/Common/Config/cache.php`, podendo ser ajustada via variáveis de ambiente `.env`.

O acesso é feito principalmente através da Facade `Og\Modules\Common\Facades\Cache`.

---

## Recuperar e Guardar Itens

A Facade `Cache` é a porta de entrada para todas as operações. Abaixo explicamos detalhadamente cada método fundamental.

### `Cache::get($key, $default = null)`

O método `get` é usado para recuperar itens do cache. Se o item não existir, retornará `null`.

Se desejar, pode passar um segundo argumento especificando um valor padrão a ser retornado caso o item não exista:

```php
use Og\Modules\Common\Facades\Cache;

// Recupera o valor
$value = Cache::get('key');

// Recupera com valor padrão (se 'key' não existir, retorna 'default')
$value = Cache::get('key', 'default');
```

Você pode até passar uma `Closure` (função anônima) como valor padrão. O resultado da Closure será retornado se o item não existir. Isto permite, por exemplo, ir à base de dados buscar o valor apenas se necessário:

```php
$value = Cache::get('settings', function () {
    return DB::table('settings')->get();
});
```

### `Cache::has($key)`

O método `has` pode ser usado para determinar se um item existe no cache. Este método também retornará `false` se o item existir mas o seu valor for `null`:

```php
if (Cache::has('user:1:permissions')) {
    // O cache existe
}
```

### `Cache::put($key, $value, $seconds)`

O método `put` armazena um item no cache por um determinado número de segundos:

```php
// Armazena 'meu_valor' na chave 'key' por 60 segundos
Cache::put('key', 'meu_valor', 60);

// Armazena um array (automaticamente serializado) por 10 minutos
Cache::put('user:profile', ['name' => 'Paulo', 'role' => 'admin'], 600);
```

### `Cache::add($key, $value, $seconds)`

O método `add` apenas adicionará o item ao cache se ele **ainda não existir**. Retorna `true` se o item for adicionado com sucesso, ou `false` se já existir. É útil para operações de "Lock" atómicas.

```php
// Só grava se 'lock_processamento' ainda não existir
Cache::add('lock_processamento', 1, 30);
```

### `Cache::remember($key, $seconds, $callback)`

Muitas vezes, você quer recuperar um item do cache, mas também armazenar um valor padrão se o item solicitado não existir. Por exemplo, você pode querer recuperar todas as permissões do cache ou, se não existirem, recuperá-las da base de dados e adicioná-las ao cache.

Pode fazer tudo isso de uma vez usando o método `remember`:

```php
// Tenta obter 'all_users'.
// Se falhar, executa a função, guarda o resultado por 600 segundos, e retorna o valor.
$users = Cache::remember('all_users', 600, function () {
    return DB::table('users')->get();
});
```

Se o item não existir no cache, a `Closure` passada para o método `remember` será executada e seu resultado será colocado no cache com o tempo de vida especificado.

---

## Remover Itens

### `Cache::forget($key)`

O método `forget` remove um item do cache através da sua chave:

```php
Cache::forget('key');
```

### `Cache::clearAll()`

Pode limpar **todo** o cache utilizando o método `clearAll`.
> **Cuidado:** Isto removerá todos os itens do cache da aplicação, o que pode causar um pico de carga momentâneo na base de dados enquanto os caches são reconstruídos.

```php
Cache::clearAll();
```

---

## Configuração `cache.php`

O arquivo de configuração está localizado em `Modules/Common/Config/cache.php`. Vamos analisar as partes mais importantes:

```php
return [
    // 1. Driver Padrão
    // Define qual driver será usado se nenhum for especificado.
    // Lida do .env CACHE_DRIVER ou usa 'redis' como fallback.
    'default' => envVar('CACHE_DRIVER', 'redis'),

    // 2. TTL Global
    // Tempo de vida padrão se não for especificado no código (usado em alguns helpers).
    'ttl' => envVar('CACHE_TTL', 60),

    // 3. Lojas (Stores) Disponíveis
    'stores' => [
        // Driver de Ficheiros (Útil para dev local sem Redis)
        'file' => [
            'driver' => 'file',
            'path' => rootPath($cacheConfiguration['CACHE_FILE_PATH'] ?? "_cache/{$server}cache"),
        ],
        
        // Driver Redis (Recomendado para Produção)
        'redis' => [
            'driver' => 'redis',
            'connection' => 'cache', // Nome da conexão no config do database/redis
            'prefix' => "{$serverCachePrefix}_cache", // Prefixo para evitar colisão entre apps no mesmo servidor
        ],
    ],
];
```

**Principais Variáveis de Ambiente (.env):**
- `CACHE_DRIVER`: Controla o backend (`redis`, `file`, `database`). Em produção deve ser sempre `redis`.
- `CACHE_PREFIX`: Prefixo único das chaves. Importante se tiver múltiplos ambientes (staging/prod) no mesmo servidor Redis.

---

## Drivers de Cache

O OfficeGest suporta nativamente 3 drivers, implementados através da `CacheDriverInterface`:

1.  **Redis (`RedisDriver`)**: O driver mais rápido e recomendado. Usa armazenamento em memória.
2.  **File (`FileDriver`)**: Armazena caches como ficheiros no disco. Bom para desenvolvimento local simples.
3.  **Database (`DatabaseDriver`)**: Armazena em uma tabela SQL. Útil se não tiver Redis disponível, mas mais lento que Redis.

---

## Exemplos Práticos do Projeto

Exemplos reais extraídos do nosso código (adaptados para clareza):

### Exemplo 1: Cachear Configurações Externas (WMS) (Uso de `remember`)

Em `Modules/Wmso/Actions/Common/LoadConfigs.php`, usamos o `remember` para evitar chamar uma API externa a cada request. Como as configurações mudam pouco, cacheamos por 5 segundos para aguentar picos de tráfego.

```php
public function __invoke(): array
{
    // A chave 'wms_configs' guardará o retorno da função por 5 segundos.
    return Cache::remember('wms_configs', 5, function () {
        // Esta lógica só corre se o cache expirou ou não existe
        return $this->connection
            ->setLoginType('super')
            ->makeHttpCall(url: "configs");
    });
}
```

### Exemplo 2: Estado de Processamento em Lote (Uso de `put` e `get`)

Em `Modules/Common/Queue/Drivers/RabbitMQ/RabbitMQBatchStateManager.php`, usamos `put` para gravar o estado de um trabalho e `get` para ler, pois precisamos de atualizar o valor manualmente passo-a-passo.

```php
// Inicializar estado
$initialState = ['status' => 'pending', 'total' => 100];
Cache::put("batch:123:state", $initialState, 86400); // 24 horas

// ... mais tarde no código ...

// Ler estado
$state = Cache::get("batch:123:state");
if ($state) {
    // Atualizar estado
    $state['status'] = 'processing';
    Cache::put("batch:123:state", $state, 86400);
}
```

### Exemplo 3: Listas para Dropdowns (Uso de `remember` com Query)

Em `StatusController.php`, cacheamos o resultado de uma query de base de dados que alimenta um Select2. Essas listas raramente mudam.

```php
$statusTypes = Cache::remember('wmso_select2_status_types', 3600, function () {
    return $this->database
        ->from('status_types')
        ->where('active', 1)
        ->order_by('name')
        ->get()
        ->result();
});
```

---

## Troubleshooting

**O cache não estó a ser salvo?**
Verifique se o seu objeto é serializável. O Cache serializa automaticamente os dados. Se tentar salvar um objeto que tem uma conexão de base de dados aberta ou um recurso de arquivo (stream) dentro dele, a serialização falhará.

**`Cache::get` retorna dados antigos?**
Possivelmente a chave não foi limpa (`Cache::forget`) quando o dado original foi alterado. Certifique-se de invalidar o cache sempre que atualizar o registo na base de dados.

**Redis vs File?**
Se estiver a usar `file` driver, certifique-se que a pasta `_cache/` tem permissões de escrita (`chmod -R 777 _cache` em dev). No Redis, verifique a conexão no `.env`.