Usando eventos do Laravel para limpar cache do Cloudflare

Há uns anos, em um projeto freelancer, criei um CMS bastante simples utilizando Laravel. Nele, é possível criar posts. Recentemente, entretanto, o site apresentou lentidão ao carregar a página. Esse CMS roda sobre nginx e PHP-FPM e, ao que tudo indicava, o problema era a lentidão de resolver o DNS e receber o primeiro byte de resposta. Um detalhe importante é que esses posts são estáticos, ou seja, o mesmo post é exibido para todo mundo. Assim, para resolver esse problema, pensei em utilizar a função de Cache na borda (Edge Cache) do Cloudflare, que cacheia o HTML, já retornando o conteúdo diretamente ao usuário, enquanto o cache estiver válido (eu configurei um tempo de 4 horas).

Essa solução funcionou muito bem, diminuindo consideravelmente o tempo de resposta. Todavia, um problema surgiu: durante esse tempo em que o cache está ativo, qualquer mudança no post (alguma correção, ou trocar o thumbnail) não era refletido. Para solucionar isso, basta limpar o cache (como as mudanças são relativamente raras, e também precisam refletir na home, optei por limpar todo o cache). Para automatizar isso, utilizei a API do Cloudflare (v4), e os eventos do Eloquent.

Começando pela API do Cloudflare, ela é super simples de usar. O primeiro passo é criar um Token de autenticação, que é passado no header da requisição. Uma coisa interessante é que é possível delimitar qual o escopo dele. No meu caso, a única permissão necessária era a de limpar o cache, que pode ser visto na figura abaixo:

 

Tela da Cloudflare exibindo a única permissão que o token terá: Limpeza de cache.

Lendo a documentação, achei o endpoint certo, o de limpeza do cache. É uma requisição bastante simples, sendo um POST que envia um JSON no corpo da requisição. Nesse projeto, eu utilizo o Guzzle, e a requisição ficou assim:

$client = new Client();
$cloudflareToken = env('CLOUDFLARE_TOKEN');
$cloudflareZoneId = env('CLOUDFLARE_ZONE_ID');
$url = "https://api.cloudflare.com/client/v4/zones/$cloudflareZoneId/purge_cache";
$response = $client->request("POST", $url,
    [
        'json' => ["purge_everything" => true],
        'headers' => [
            'Authorization' => "Bearer $cloudflareToken"
        ]
    ]
);

 

Simples assim. Esse código retorna 200 em caso de sucesso e, em questão de um ou dois minutos, o cache será limpo e a nova versão será carregada, já com as atualizações. Eu coloquei essa função dentro um de método estático (CloudflareService::purgeCache()). Porém, como chamar esse método? Para isso, utilizei um evento do Eloquent. E o que seria isso?

Laravel possui uma série de eventos que serão executados sempre que uma entidade (model) for modificado. Há diversos eventos, como creating, created, saving, saved, deleting e deleted. A diferença dos métodos é que os métodos que estão no gerúndio (creating, saving, deleting), serão executados antes do efetivo salvamento no banco de dados. Já os métodos que estão no passado (created, saved, deleted), são executados após o salvamento no banco. Um parênteses aqui: temos os métodos saved, created updated. created é executado quando um novo registro é salvo, o updated quando um registro existente é atualizado, e o saved é executado nos dois casos. No meu cenário, o método que eu utilizei foi justamente o saved, pois assim eu limpo o cache tanto quando um novo registro foi criado, quando um registro existente foi alterado.

E o código para disparar um evento é super simples. Segue abaixo o código que eu utilizei:

class Post extends Model
{
    use SearchableTrait;
    use SoftDeletes;

    public static function boot() {
        parent::boot();

        static::saved(function($model) {
            CloudflareService::purgeCache();
        });

    }

 

Note que eu sobrescrevi o método boot, chamando o método da classe Model (parent::boot()). Dentro desse método, eu declaro todos os eventos. Note que eu recebo como parâmetro da função uma instância da classe, que é o próprio registro que foi salvo. Isso é bem útil em diversos cenários. Por exemplo, se eu quisesse disparar um e-mail após a inclusão no banco de dados, eu poderia chamar a classe responsável aqui, já que eu possuo todas as informações do model. 

Simples e prático. Esses eventos do Eloquent são uma funcionalidade bastante interessante, e que podem nos poupar um bom tempo.