Simple Factory com PHP

O Simple Factory é uma técnica que nos auxilia na criação de objetos, evitando a repetição de código e facilitando à adição de código novo. Diferente do padrão Factory e Abstract Factory, o Simple Factory não é considerado um Design Pattern, porém é uma técnica muito útil em diversas situações.

A maioria dos artigos que vemos por ai possuem exemplos muito abstratos, utilizando animais ou carros. Neste artigo pretendo trazer um exemplo mais próximo do mundo real.

Se você já trabalhou em alguma aplicação com área financeira já deve ter precisado implementar a geração de boletos. Apesar de haver uma certa padronização, cada banco implementa o boleto com suas particularidades, principalmente na geração do Nosso Número, que é o número de controle do boleto no banco.

Neste artigo, veremos desde um exemplo mais simples sem orientação a objetos até o exemplo utilizando o Simple Factory, para a geração do Nosso Número do boleto.

Código fonte disponível em: https://github.com/rafaelcouto/simple-factory-com-php.

O problema

Precisamos então implementar a geração de boletos em nossa aplicação e então começamos a ler a documentação de cada banco e percebemos que todos tem o Nosso Número, porém cada banco gera em um formato diferente e possui uma lógica específica para o cálculo do dígito verificador. Por exemplo, se formos gerar o primeiro boleto, o Nosso Número deve ser gerado da seguinte forma para cada banco:

  • Santander: 000000000001-9 (Carteira: 101)
  • Bradesco: 00000000001-1 (Carteira: 09)

Portanto, precisamos de um código que, a partir do código do banco, de um número base e da carteira de cobrança, formate e calcule o dígito verificador do Nosso Número. Para o artigo não ficar tão extenso, vamos focar os exemplos nos dois bancos descritos acima.

Preparando ambiente

Primeiramente, vamos montar um ambiente para executar os exemplos deste artigo. Para isso, vamos utilizar o Composer para gerar o autoload das nossas classes. O composer.json fica da seguinte forma então:

{
    "name": "rafaelcouto/simple-factory",
    "require": {
        "php": ">=8.0"
    },
    "autoload": {
        "psr-4": {
            "SimpleFactory\\": "src/"
        }
    }
}

Definimos um nome para nosso pacote, exigimos que tenha o PHP 8 ou superior e que será aplicado o autoload no padrão PSR-4 na nossa pasta src. Agora basta rodar composer update e estamos pronto para começar.

Como esse projeto é apenas para fins didáticos, todos os exemplos vão ser montados para serem rodados no terminal.

Exemplo sem orientação a objetos

Uma forma bem simples que poderíamos pensar seria utilizar um switch e para cada código de banco geraríamos o Nosso Número de uma forma diferente.

<?php declare(strict_types=1);

$codigoBanco = (int) readline('Informe o codigo do banco: ');
$numero = (int) readline('Informe o numero: ');
$carteira = (string) readline('Informe a carteira: ');
$nossoNumero = null;

function modulo11($n, $factor = 2, $base = 9, $x10 = 0, $resto10 = 0) : int
{
    // Lógica para cálculo do módulo 11
}

switch ($codigoBanco) {
    case 33:
        $nossoNumero = str_pad((string) $numero, 12, '0', STR_PAD_LEFT);
        $dv = modulo11((string) $numero);
        break;
    case 237:
        $nossoNumero = str_pad((string) $numero, 11, '0', STR_PAD_LEFT);
        $dv = modulo11($carteira . $nossoNumero, 2, 7, 0, 'P');
        break;
    default:
        throw new Exception('Banco não implementado');
}

echo "Nosso numero: {$nossoNumero}-{$dv}" . PHP_EOL;

  • Linha 1: a partir do PHP 7, é sempre interessante utilizar o strict_types para evitar bugs por conta de tipagem;
  • Linha 3 a 5: para quem não está familiarizado com PHP no terminal, a função readline() quando executada no terminal nos permite solicitar uma informação do usuário;
  • Linha 8 a 11: a maioria dos bancos utiliza a rotina de módulo 11 para cálculo do dígito verificador, como o foco do artigo não é o cálculo em si, não vamos entrar em detalhes. Se quiser saber mais, você pode consultar o pacote Laravel Boleto, que é um pacote bem completo para geração de boletos;
  • Linha 13 a 24: formatamos o número e calculamos o dígito verificador de acordo com a regra do banco informado.

Basta abrirmos o terminal na raiz do projeto e executar php examples/basic.php.

Imagem 1 – Resultado do exemplo sem orientação a objetos

Funciona? Sim! Porém esse código não é nada reutilizável, em uma aplicação grande, podemos ter que utilizar esse trecho de código em vários lugares (gravar o boleto, gerar remessa para o banco, impressão do boleto, etc.).

Outro ponto é a legibilidade do código, alguns bancos tem lógicas mais complexas para geração do Nosso Número, variando de acordo com a carteira, por exemplo.

Exemplo com orientação a objetos

Vamos pensar um pouco diferente agora. Todos os bancos tem que gerar o Nosso Número, porém cada um implementa de uma forma diferente, ou seja, podemos utilizar o conceito de herança, tendo uma classe base abstrata e cada banco pode ser representado por uma subclasse com a sua lógica para geração do Nosso Número.

Primeiramente, para não haver números mágicos em nosso código, vamos criar um Enum para representar o código do banco.

<?php declare(strict_types=1);

namespace SimpleFactory;

final class BancoEnum {
    public const SANTANDER = 33;
    public const BRADESCO = 237;
}

Pronto, agora ao invés de utilizar 33, podemos utilizar BancoEnum::SANTANDER, bem mais legível, não?

Como percebemos no primeiro exemplo, o módulo 11 é comum para ambos os bancos, portanto vamos criar uma classe utilitária com esse método.

<?php declare(strict_types=1);

namespace SimpleFactory;

class BoletoUtil {
    
    public static function modulo11($n, $factor = 2, $base = 9, $x10 = 0, $resto10 = 0) : int
    {
        // Lógica para cálculo do módulo 11
    }

}

Agora sim, vamos criar a classe abstrata do boleto, que será a classe base para cada banco que for implementado na nossa aplicação.

<?php declare(strict_types=1);

namespace SimpleFactory;

abstract class AbstractBoleto {

    protected int $codigoBanco;
    protected int $numero;
    protected string $carteira;

    public function __construct(array $data = []) {
        $this->setNumero($data['numero'] ?? 0);
        $this->setCarteira($data['carteira'] ?? '');
    }

    public function getNumero() : int {
        return $this->numero;
    }

    public function setNumero(int $numero) {
        $this->numero = $numero;
    }

    public function getCarteira() : string {
        return $this->carteira;
    }

    public function setCarteira(string $carteira) {
        $this->carteira = $carteira;
    }
    
    abstract public function getNossoNumero() : string;
    abstract public function getNossoNumeroDv() : string;
}
  • Linha 7 a 9: definimos então duas propriedades que são comuns do boleto, $numero e $carteira, e também o $codigoBanco que será útil mais adiante no artigo;
  • Linha 11 a 14: definimos também um construtor para simplificar a inicialização com a ajuda do Null Coalescing Operator (??);
  • Linha 32 e 33: declaramos os métodos getNossoNumero() e getNossoNumeroDv() que deverão ser implementados pelas subclasses de cada banco.

O próximo passo então é criarmos as subclasses de cada banco.

<?php declare(strict_types=1);

namespace SimpleFactory\Banco;

use SimpleFactory\AbstractBoleto;
use SimpleFactory\BancoEnum;
use SimpleFactory\BoletoUtil;

class Santander extends AbstractBoleto
{
    protected int $codigoBanco = BancoEnum::SANTANDER;

    public function getNossoNumero() : string
    {
        return str_pad((string) $this->getNumero(), 12, '0', STR_PAD_LEFT);
    }

    public function getNossoNumeroDv(): string
    {
        return (string) BoletoUtil::modulo11((string) $this->getNumero());
    }
        
}
<?php declare(strict_types=1);

namespace SimpleFactory\Banco;

use SimpleFactory\AbstractBoleto;
use SimpleFactory\BancoEnum;
use SimpleFactory\BoletoUtil;

class Bradesco extends AbstractBoleto
{
    protected int $codigoBanco = BancoEnum::BRADESCO;

    public function getNossoNumero() : string 
    {
        return str_pad((string) $this->getNumero(), 11, '0', STR_PAD_LEFT);
    }

    public function getNossoNumeroDv(): string
    {
        return (string) BoletoUtil::modulo11($this->getCarteira() . $this->getNossoNumero(), 2, 7, 0, 'P');
    }
}

Apenas colocamos a lógica para geração do Nosso Número que utilizamos no primeiro exemplo separados na classe de cada banco. Por fim, vamos montar o exemplo utilizando as classes criadas.

<?php declare(strict_types=1);

require_once __DIR__ . '/../vendor/autoload.php';

use SimpleFactory\BancoEnum;
use SimpleFactory\Banco\Santander;
use SimpleFactory\Banco\Bradesco;

$codigoBanco = (int) readline('Informe o codigo do banco: ');

$data = [
    'numero' => (int) readline('Informe o numero: '),
    'carteira' => (string) readline('Informe a carteira: ')
];

switch ($codigoBanco) {
    case BancoEnum::SANTANDER:
        $boleto = new Santander($data);
        break;
    case BancoEnum::BRADESCO:
        $boleto = new Bradesco($data);
        break;
    default:
        throw new Exception('Banco não implementado');
}

echo "Nosso numero: {$boleto->getNossoNumero()}-{$boleto->getNossoNumeroDv()}" . PHP_EOL;
  • Linha 3: como agora estamos trabalhando com classes no padrão PSR-4, precisamos incluir o autoload do Composer para que seja feito o carregamento delas conforme forem utilizadas.
Imagem 2 – Resultado do exemplo orientado a objetos

Mesmo resultado, porém nosso código já ficou um pouco mais organizado que o primeiro exemplo. Dessa forma, a lógica para gerar o Nosso Número fica isolada na classe de cada banco melhorando a legibilidade e a reutilização.

Entretanto, ainda precisamos do switch aqui, ou seja, em cada lugar que precisarmos gerar o Nosso Número, iremos precisar desse trecho de código, e sempre que implementarmos um banco novo, precisaremos ir nesses trechos e adicionar a nova classe.

Exemplo com Simple Factory

Enfim, agora que organizamos o nosso código de forma orientada a objetos, ficou fácil para chegar no Simple Factory, basta apenas isolarmos o switch do segundo exemplo em uma nova classe.

<?php declare(strict_types=1);

namespace SimpleFactory;

use SimpleFactory\AbstractBoleto;
use SimpleFactory\Banco\Santander;
use SimpleFactory\Banco\Bradesco;

final class BoletoFactory {

    public function create(int $codigoBanco, array $data = []) : AbstractBoleto {

        switch($codigoBanco) {
            case BancoEnum::SANTANDER:
                return new Santander($data);
            case BancoEnum::BRADESCO:
                return new Bradesco($data);
        }

        throw new \Exception('Banco não implementado');

    }

}

Reparem que o método create() retorna um AbstractBoleto, pois todas as nossas classes dos bancos herdam dessa classe, portanto é garantido que todas irão possuir os mesmos métodos.

<?php declare(strict_types=1);

require_once __DIR__ . '/../vendor/autoload.php';

use SimpleFactory\BoletoFactory;

$codigoBanco = (int) readline('Informe o codigo do banco: ');

$data = [
    'numero' => (int) readline('Informe o numero: '),
    'carteira' => (string) readline('Informe a carteira: ')
];

$boletoFactory = new BoletoFactory();
$boleto = $boletoFactory->create($codigoBanco, $data);

echo "Nosso numero: {$boleto->getNossoNumero()}-{$boleto->getNossoNumeroDv()}" . PHP_EOL;
Imagem 3 – Resultado do exemplo com Simple Factory

E continua dando o mesmo resultado, porém o código ficou muito melhor! Não precisamos saber os detalhes de como é gerado o Nosso Número, tudo que precisamos é passar o código do banco, os dados do boleto e a Simple Factory faz o trabalho de carregar o objeto correto para nós.

Caso você precise implementar um novo banco, basta criar a classe do banco herdando do AbstractBoleto e então adicionar o retorno da instância no switch do BoletoFactory e todo o restante do código continuará funcionando.

Neste artigo estamos focando apenas no Nosso Número, porém há outras propriedades do boleto, como carteiras, espécies, campo livre, etc. Cada banco pode implementar essas propriedades de um jeito diferente, e com o Simple Factory, podemos instanciar nosso boleto de qualquer lugar sem precisar se preocupar caso precisemos implementar um novo banco.

Exemplo com Simple Factory dinâmico

Podemos “fazer uma graça” e deixar o nosso Simple Factory mais dinâmico através de Reflection. Com a ReflectionClass é possível obter informações de uma classe em tempo de execução.

Primeiramente, vamos criar uma classe utilitária com um método para obter todas as ReflectionClasses a partir de um diretório.

<?php declare(strict_types=1);

namespace SimpleFactory;

class ReflectionUtil {
    
    /**
     * @return \ReflectionClass[]
     */
    public static function getReflectionClassesFromFolder(string $folder, string $namespace) : array
    {
        $reflectionClasses = [];

        // Buscando os arquivos da pasta Banco e ignorando os caminhos '.' e '..'
        $files = array_diff(scandir($folder), ['.', '..']);
        
        foreach ($files as $file) {
            
            // Retirando a extensão do nome do arquivo
            $fileNameWithoutExtension = pathinfo($file, PATHINFO_FILENAME);

            // Descobrindo o nome da classe
            $className = $namespace . '\\' . $fileNameWithoutExtension;

            // Armazenando objeto com informações da classe
            $reflectionClasses[] = new \ReflectionClass($className);

        }

        return $reflectionClasses;
    }

}

E agora vamos refatorar o Simple Factory criado no exemplo anterior e deixá-lo de forma dinâmica.

<?php declare(strict_types=1);

namespace SimpleFactory;

use SimpleFactory\AbstractBoleto;

final class BoletoFactoryImproved {

    public function create(int $codigoBanco, array $data = []) : AbstractBoleto {

        $reflectionClasses = ReflectionUtil::getReflectionClassesFromFolder(__DIR__ . '/Banco', __NAMESPACE__ . '\\Banco');

        foreach ($reflectionClasses as $reflectionClass) {
            
            $defaultProperties = $reflectionClass->getDefaultProperties();

            // Se a propriedade 'codigoBanco' for igual ao código que estamos buscando
            if ($defaultProperties['codigoBanco'] && $defaultProperties['codigoBanco'] === $codigoBanco) {

                // Retornando uma instancia dessa classe
                $className = $reflectionClass->getName();
                return new $className($data);

            }
        }

        throw new \Exception('Banco não implementado');
    }

}
  • Linha 11: como todas as classes que implementam o AbstractBoleto estarão na pasta src/Banco e consequentemente, por conta do PSR-4, fazem parte do namespace SimpleFactory\Banco, utilizamos então o método utilitário para passar pelos arquivos da pasta e obter a ReflectionClass referente a classe de cada arquivo;
  • Linha 15: um ReflectionClass possui o método getDefaultProperties() que nos retorna as propriedades da classe com seus respectivos valores padrão;
  • Linha 17 a 25: lembra que criamos a propriedade $codigoBanco lá no nosso AbstractBoleto? Basta compararmos se o código do banco da classe é igual o código que estamos procurando. Caso seja, retornamos uma instância dessa classe.

Em resumo, sempre que implementarmos um novo banco, não precisamos nem sequer mexer no nosso Simple Factory, basta apenas adicionar o arquivo da classe na pasta src/Banco, implementar os métodos e estaria tudo funcionando normalmente.

Enfim, vamos testar nossa Simple Factory dinâmica.

<?php declare(strict_types=1);

require_once __DIR__ . '/../vendor/autoload.php';

use SimpleFactory\BoletoFactoryImproved;

$codigoBanco = (int) readline('Informe o codigo do banco: ');

$data = [
    'numero' => (int) readline('Informe o numero: '),
    'carteira' => (string) readline('Informe a carteira: ')
];

$boletoFactoryImproved = new BoletoFactoryImproved();
$boleto = $boletoFactoryImproved->create($codigoBanco, $data);

echo "Nosso numero: {$boleto->getNossoNumero()}-{$boleto->getNossoNumeroDv()}" . PHP_EOL;
Imagem 4 – Resultado do exemplo com Simple Factory dinâmico

E pronto, tudo continua funcionando da mesma forma!

Conclusão

Neste artigo vimos que utilizar conceitos de orientação a objetos juntamente com a técnica de Simple Factory tornam nosso código muito mais legível e reutilizável. E isso é muito importante, pois temos que lembrar que o código deve sempre ser escrito pensando na facilidade de sua extensão e manutenção.

Vimos como aplicar essa técnica com PHP, porém ela pode ser aplicada em qualquer linguagem com recursos de orientação a objetos.

Acredito que a “parte mais difícil” do Simple Factory é identificar quando utilizá-lo. Basicamente, sempre que você ver um trecho de código com switch ou muitas condições encadeadas, que está se repetindo em muitos lugares, talvez seja uma situação para aplicar a técnica.

Espero que este artigo tenha sido útil de alguma forma. Até a próxima.

Código fonte disponível em: https://github.com/rafaelcouto/simple-factory-com-php.

Referências