# PHP: Função auxiliar com contexto de objeto?!

Se você é iniciante no PHP, essa introdução pode ter te deixado com uma super incógnita na cabeça... E tudo bem! Eu vou explicar direitinho. Mas antes de começar eu quero destacar o que eu não vou me aprofundar tanto assim, beleza?

O ponto forte deste artigo será sobre funções anônimas (closures). Se você não sabe nem o que é função, começa pelo meu [artigo introdutório](https://blog.kaiquegarcia.dev/php-para-iniciantes-funcoes-introducao). Se já tem conhecimentos mas ainda se sente meio inseguro sobre o que é uma closure, lê meu artigo específico sobre [Closures, Funções variáveis e Arrow Functions](https://blog.kaiquegarcia.dev/php-para-iniciantes-funcoes-closures-funcoes-variaveis-e-arrow-functions).

Há um outro pré-requisito que é **estruturas de try-catch**, que ainda não falei em nenhum artigo... Se só não souber isso, tenta ler assim mesmo. Ficando alguma dúvida, sinta-se a vontade para perguntar nos comentários ou mandando mensagem lá no [meu BlueSky](https://bsky.app/profile/kaiquegarcia.dev)!

Agora que já estabeleci os pré-requisitos para nos aventurarmos na leitura de hoje, vamos nos jogar de cabeça!

> Mas Kiko, o que é uma função auxiliar?

É um bom começo, não é mesmo? Quem programa com Orientação a Objeto há algum tempo pode ter a mesma sensação que eu tenho hoje, sobre parecer um tabu escrever qualquer função que não seja um método de uma classe. Porém isso não é tão errado assim, tem momentos que uma função deixa o código bem mais legível do que instanciar uma classe para chamar apenas um método, sabe?

Um exemplo bem legal sobre isso vem de um problema que investiguei no trabalho hoje sobre transações de banco de dados. Calma, você não precisa entender isso a fundo para sacar o que está acontecendo, apenas pega o resumo e você vai entender o problema geral.

Quando nós enviamos um comando para o banco de dados fora de uma transação, esse comando é persistido nos dados instantaneamente (leia-se, no momento que o banco processar o seu comando). Já dentro de uma transação, você pode efetuar vários comandos em sequência, mas eles só serão persistidos quando você fizer o **commit** dos comandos. Isso serve para, caso algo dê errado, você possa reverter todos os comandos de uma só vez.

Então você tem a seguinte situação:

1. Vou fazer vários comandos complexos, então **inicio uma transação com o banco de dados**;
    
2. Faço um comando;
    
3. Faço outro comando;
    
4. ... Faço vários comandos de banco de dados;
    
5. Se deu tudo certo, mando o sinal de **commit** e salto para o passo 7;
    
6. Se algo deu errado, reverto com o sinal de **rollback** e salto para o próximo passo;
    
7. Concluo transmitindo se deu certo ou não.
    

Em vários frameworks você terá uma classe que fornece esses comandos de banco de dados. Hoje eu vou me basear no [Laravel](https://laravel.com/), usando como referência a classe `DB`, que, por sinal, já fornece um método auxiliar sobre o que iremos ver mais abaixo.

Enfim, então você pode fazer os passos acima da seguinte forma:

```php
DB::beginTransaction(); // passo 1
$success = false;
try {
    // passo 2
    // passo 3
    // ... passo 4
    DB::commit(); // passo 5
    $success = true;
} catch (Throwable $throwable) {
    DB::rollback(); // passo 6
    // eu colocaria um log aqui, mas não convém ao caso
}
return $success; // passo 7
```

O que é desagradável nessa situação, é ver essa estratégia repetida diversas vezes ao longo do código, sendo que temos pelo menos quatro passos iguais em todos os casos, concorda? Então por que não criar uma função que dá a possibilidade de executar um `callabe`?

## Criando a função auxiliar `transaction()`

Se você encapsular isso tudo em uma função, teremos o seguinte cenário:

```php
function transaction(): bool
{
    DB::beginTransaction(); // passo 1
    $success = false;
    try {
        // passo 2
        // passo 3
        // ... passo 4
        DB::commit(); // passo 5
        $success = true;
    } catch (Throwable $throwable) {
        DB::rollback(); // passo 6
    }
    return $success; // passo 7
}
```

Porém, os passos 2, 3, 4 e 7 não são exatamente responsabilidades dela. Isso precisa ser algo que possa ser criado do lado de fora dela... É aí que entra as funções anônimas: **você dá para quem usar a função auxiliar o poder de escrever outra função para executar dentro**.

Isso nos leva a criar um argumento `callable $closure`, chamando-o dentro da estrutura de `try-catch`:

```php
function transaction(callable $closure): mixed
{
    DB::beginTransaction(); // passo 1
    $result = null;
    try {
        $result = $closure();
        DB::commit(); // passo 5
    } catch (Throwable $throwable) {
        DB::rollback(); // passo 6
    }
    return $result; // passo 7
}
```

Notou que mudei o `$sucess` para `$result`? Porque eu não quero forçar o desenvolvedor a só responder booleanos. Ele vai responder o que precisar e isso poderá ter a tipagem que precisar.

Se o PHP tivesse **Generics**, nós poderíamos forçar a criação de um tipo genérico, modificando a assinatura do argumento para algo como `callable<Response> $closure` e a assinatura da função para `: Response`. Assim poderíamos facilmente mapear a partir da criação das closures qual seria o tipo de resposta...

> QUÊ?!

Ok, eu só estava pensando em como a linguagem ficaria ainda mais divertida se tivesse **Generics**, mas foi total fuga do tema: nota zero na redação. Mas, voltando ao assunto... Com o retorno `mixed`, o desenvolvedor já tem total controle para fazer algo assim:

```php
transaction(function() {
    // passo 2
    // passo 3
    // ... passo 4
    return true; // sucesso
});
```

Você pode até ajudá-lo, dando opção para definir o valor padrão do retorno negativo:

```php
function transaction(callable $closure, $defaultResult = null): mixed
{
    DB::beginTransaction(); // passo 1
    $result = $defaultResult;
    try {
        $result = $closure();
        DB::commit(); // passo 5
    } catch (Throwable $throwable) {
        DB::rollback(); // passo 6
    }
    return $result; // passo 7
}
```

Deixando o uso dessa forma:

```php
transaction(
    function() {
        // passo 2
        // passo 3
        // ... passo 4
        return true; // sucesso
    },
    false // erro
);
```

De todo modo, isso ainda não está tão legal assim. A função faz o que promete: transaciona baseado no sucesso de tudo o que for executado na sua closure... Mas não te dá o controle de poder enviar sinal de **commit** quando quiser... Além de fornecer outros controles que vamos ver mais embaixo.

Para melhorar esse controle, você terá de ter uma classe de gerenciamento de transações, a qual irei chamar de **TransactionManager**. Ela terá os métodos públicos para inicializar a transação, comitar ou reverter:

```php
class TransactionManager
{
    public function beginTransaction(): void
    {
        DB::beginTransaction();
    }

    public function commit(): void
    {
        DB::commit();
    }

    public function rollback(): void
    {
        DB::rollback();
    }

    public function bind(callable $closure): callable
    {
        return $closure->bindTo($this); // vincula a instância à closure
    }
}
```

Notou que coloquei um método `bind`? Esse método é o que vai tornar a execução de tudo bem mais legível... Você vai ver mais abaixo! Agora nós precisamos ajustar a função auxiliar para não chamar mais nada diretamente, apenas o gerenciador de transações:

```php
function transaction(callable $closure, $defaultResult = null): mixed
{
    $transactionManager = new TransactionManager();
    $transactionManager->beginTransaction();
    $result = $defaultResult;
    try {
        $closure = $transactionManager->bind($closure);
        $result = $closure();
        $transactionManager->commit();
    } catch (Throwable $throwable) {
        $transactionManager->rollback();
    }
    return $result;
}
```

> Ué, Kiko? O código ficou bem mais extenso de se ler... Não era pra ser simples?

Funções auxiliares raramente são simples. Elas estão ali para centralizar o grosso do código de modo que o seu controle, do lado de fora, seja simples. Quer ver na prática?

Bem, agora você pode controlar o commit e rollback de dentro da sua função:

```php
transaction(
    function() {
        // passo 2
        $this->commit();
        // passo 3
        // ... passo 4
        return true; // sucesso
    },
    false // erro
);
```

Legal?! Percebeu que eu chamei `$this->commit()` dentro de uma closure que é chamada dentro de uma função? Em cenários comuns, isso resultaria em um belíssimo erro. O que nos possibilita chamar o `$this` aqui é aquele método `bind` que mencionei lá em cima.

Ele vincula a função anônima ao objeto `$transactionManager`, de modo que ela passa a ter praticamente o mesmo escopo dos outros métodos ali dentro. Eu acho isso iradíssimo! E esse é o ponto forte desse artigo, mas vamos além...

> Kiko, e se eu quiser que alguma coisa rode após todo o fluxo de sucesso?

O céu é o limite! Você pode adicionar uma estrutura no gerenciador de transações para empilhar várias funções anônimas e chamar todas sequencialmente após qualquer `commit`. Para não deixar isso amarrado ao gerenciador de transações, vamos criar uma pilha de callbacks, pode ser? Chamarei de **CallbackStackManager**:

```php
class CallbackStackManager
{
    private array $stack = [];

    public function addCallback(callable $callback): void
    {
        $this->stack[] = $callback;
    }

    private function popStack(): array
    {
        $stack = $this->stack;
        $this->stack = [];
        return $stack;
    }

    public function run(): void
    {
        foreach($this->popStack() as $callback) {
            $callback();
        }
    }
}
```

É um controle de pilha bem simples. Você vai adicionando suas funções anônimas com o método `addCallback`. Conforme o código for funcionando, em algum momento você irá rodar todos os callbacks chamando o método `run()`. Simples assim!

> E o que é aquele `popStack`, Kiko?

É um método de segurança para evitar **loops infinitos**. Passei por um problema onde uma função desenvolvida por outra pessoa gerou um loop infinito por voltar ao ponto onde o código chamava o `run()` antes de resetar a `stack`. Com o método `pop`, você já reseta a `stack` antes mesmo de começar.

> Ok, Kiko, e como unimos essa classe ao resto da *obra de arte*?

HEHEHE, primeiramente: **nem pense em instanciar o** `CallbackStackManager` dentro do `TransactionManager`. Se fizer isso, você estará gerando uma dependência interna... Essa classe precisa ser uma entrada no construtor do outro gerenciador, de modo a nos permitir **mockar** a dependência quando precisarmos escrever algum teste.

Na prática, já era pra termos escrito algum teste para validar nossa teoria... Mas aí iria ser mais um conhecimento de pré-requisito e eu quero tornar esse artigo mais acessível, ok? Se você não conhece o TDD (Test-Driven Development), recomendo a leitura.

> Tá, tá, e como faz a parada?

Pra já:

```php
class TransactionManager
{
    public function __construct(
        private CallbackStackManager $callbackStackManager
    ) { }

    public function runAfterCommit(callable $closure): void
    {
        $this->callbackStackManager->addCallback($closure);
    }

    public function beginTransaction(): void
    {
        DB::beginTransaction();
    }

    public function commit(): void
    {
        DB::commit();
        $this->callbackStackManager->run();
    }

    public function rollback(): void
    {
        DB::rollback();
    }

    public function bind(callable $closure): callable
    {
        return $closure->bindTo($this); // vincula a instância à closure
    }
}
```

**O que alterei**:

* incluí um construtor, recebendo uma instância da nova classe `CallbackStackManager`;
    
* incluí um método `runAfterCommit` para facilitar o uso na closure do desenvolvedor;
    
* incluí uma chamada ao `callbackStackManager->run()` dentro do método `commit`.
    

Lembrando que, em cada `run()`, o gerenciador apaga todas as funções anônimas da pilha.

Além dessas alterações, precisaremos modificar a função auxiliar:

```php
function transaction(callable $closure, $defaultResult = null): mixed
{
    $callbackStackManager = new CallbackStackManager();
    $transactionManager = new TransactionManager($callbackStackManager);
    $transactionManager->beginTransaction();
    $result = $defaultResult;
    try {
        $closure = $transactionManager->bind($closure);
        $result = $closure();
        $transactionManager->commit();
    } catch (Throwable $throwable) {
        $transactionManager->rollback();
    }
    return $result;
}
```

**O que modifiquei**:

* instanciei a nova classe `CallbackStackManager`;
    
* adicionei a nova instância no construtor da classe `TransactionManager`.
    

> E como seria o uso disso na prática, Kiko?

```php
transaction(
    function() {
        // passo 2
        // passo 3
        $this->runAfterCommit(function() {
            event(new Event('bonequinho_criado'));
        });
        // ... passo 4
        return true; // sucesso
    },
    false // erro
);
```

## Resultado final

```php
class CallbackStackManager
{
    private array $stack = [];

    public function addCallback(callable $callback): void
    {
        $this->stack[] = $callback;
    }

    private function popStack(): array
    {
        $stack = $this->stack;
        $this->stack = [];
        return $stack;
    }

    public function run(): void
    {
        foreach($this->popStack() as $callback) {
            $callback();
        }
    }
}


class TransactionManager
{
    public function __construct(
        private CallbackStackManager $callbackStackManager
    ) { }

    public function runAfterCommit(callable $closure): void
    {
        $this->callbackStackManager->addCallback($closure);
    }

    public function beginTransaction(): void
    {
        DB::beginTransaction();
    }

    public function commit(): void
    {
        DB::commit();
        $this->callbackStackManager->run();
    }

    public function rollback(): void
    {
        DB::rollback();
    }

    public function bind(callable $closure): callable
    {
        return $closure->bindTo($this); // vincula a instância à closure
    }
}

function transaction(callable $closure, $defaultResult = null): mixed
{
    $callbackStackManager = new CallbackStackManager();
    $transactionManager = new TransactionManager($callbackStackManager);
    $transactionManager->beginTransaction();
    $result = $defaultResult;
    try {
        $closure = $transactionManager->bind($closure);
        $result = $closure();
        $transactionManager->commit();
    } catch (Throwable $throwable) {
        $transactionManager->rollback();
    }
    return $result;
}
```

**OBS.: você não vai conseguir rodar esse código se só copiar e colar, pois falta importar a classe** `DB` do Laravel/Lumen. Além disso, não está nada organizado... O ideal é que cada classe esteja em um arquivo separado, assim como a função auxiliar.

Se ainda assim, quiser ver isso funcionando para brincar um pouco, eu criei um projeto no site [paiza.io](https://paiza.io/projects/Qrb2UOhRGHHH3hdEBDrMVg) para te fornecer o código pronto para brincadeira. Dá uma olhada: [https://paiza.io/projects/Qrb2UOhRGHHH3hdEBDrMVg](https://paiza.io/projects/Qrb2UOhRGHHH3hdEBDrMVg).

Como o objetivo era apenas mostrar um bom uso de funções auxiliares para simplificar a legibilidade do código, acredito que atingi o objetivo desse artigo. Só não sei se está tão claro assim pra quem nunca viu transações de banco de dados... Mas não era o foco, tá bom?

Curtiu?! Comenta e compartilha! E me conta o que mais gostou, o que mais odiou e se concorda que dá sim pra ter funções auxiliares legais nos projetos? Estou ansioso para saber sua opinião! Sei que esperava um artigo da série [PHP para Iniciantes](https://blog.kaiquegarcia.dev/series/php-para-iniciantes), mas hoje resolvi quebrar a rotina, hahahah.

**Inté!!**
