PHP

PSR e Padrões de Código Já leu

6 min de leitura

PSR e Padrões de Código
PSR significa PHP Standards Recommendation — recomendações criadas pelo PHP-FIG (Framework Interoperability Group), um grupo formado pelos mantenedores dos principais frameworks e bibliotecas PHP. O objetivo é garantir i

PSR significa PHP Standards Recommendation — recomendações criadas pelo PHP-FIG (Framework Interoperability Group), um grupo formado pelos mantenedores dos principais frameworks e bibliotecas PHP. O objetivo é garantir interoperabilidade: que componentes de diferentes projetos funcionem juntos sem atrito.

Conhecer as PSRs mais importantes não é pedantismo acadêmico. É falar a língua da comunidade PHP profissional, entender como frameworks como Laravel e Symfony funcionam por dentro, e escrever código que qualquer desenvolvedor PHP experiente vai reconhecer imediatamente.


PSR-1 e PSR-12 — Estilo de código

PSR-1 define o básico: arquivos PHP usam apenas <?php ou <?=, declaram classes ou executam efeitos (não ambos no mesmo arquivo), e seguem StudlyCaps para nomes de classes.

PSR-12 é a extensão moderna que substitui a PSR-2. Os pontos principais:

<?php
declare(strict_types=1);          // declare logo após a abertura

namespace App\Domain\Pedido;      // linha em branco antes e depois

use App\Domain\Shared\Dinheiro;   // use statements agrupados
use DateTimeImmutable;

class Pedido                      // { na mesma linha da declaração
{                                 // exceto para classes, métodos e funções
    private const STATUS_PENDENTE = 'pendente';

    private array $itens = [];    // 4 espaços de indentação (não tabs)

    public function __construct(  // parâmetros longos: um por linha
        private readonly int $id,
        private string $status = self::STATUS_PENDENTE,
        private readonly DateTimeImmutable $criadoEm = new DateTimeImmutable(),
    ) {}

    public function adicionarItem(   // visibilidade sempre declarada
        int $produtoId,
        int $quantidade,
        Dinheiro $preco,
    ): void {
        if ($quantidade <= 0) {      // espaço antes do (
            throw new \InvalidArgumentException('Quantidade inválida.');
        }                            // } na própria linha

        $this->itens[] = [
            'produto_id' => $produtoId,
            'quantidade' => $quantidade,
            'preco'      => $preco,
        ];
    }

    public function total(): Dinheiro
    {
        return array_reduce(
            $this->itens,
            fn(Dinheiro $acc, array $item) => $acc->somar(
                $item['preco']->multiplicar($item['quantidade'])
            ),
            Dinheiro::de(0),
        );
    }
}

PHP CS Fixer — formatação automática:

composer require --dev friendsofphp/php-cs-fixer

# Cria o arquivo de configuração .php-cs-fixer.php
cat > .php-cs-fixer.php << 'EOF'
<?php
return (new PhpCsFixer\Config())
    ->setRules([
        '@PSR12'                         => true,
        'declare_strict_types'           => true,
        'array_syntax'                   => ['syntax' => 'short'],
        'ordered_imports'                => ['sort_algorithm' => 'alpha'],
        'no_unused_imports'              => true,
        'trailing_comma_in_multiline'    => true,
    ])
    ->setFinder(PhpCsFixer\Finder::create()->in('src')->in('tests'));
EOF

# Verificar sem alterar
vendor/bin/php-cs-fixer fix --dry-run --diff

# Corrigir automaticamente
vendor/bin/php-cs-fixer fix

PSR-3 — Interface de Logger

PSR-3 define uma interface padrão para logging. Qualquer biblioteca que aceite um LoggerInterface funciona com Monolog, ou com qualquer outro logger PSR-3 compatível:

<?php
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

class ProcessadorDePedidos
{
    // Recebe a interface, não a implementação concreta
    public function __construct(private readonly LoggerInterface $logger) {}

    public function processar(Pedido $pedido): void
    {
        $this->logger->info('Processando pedido', ['id' => $pedido->id]);

        try {
            // processamento...
            $this->logger->info('Pedido processado com sucesso', ['id' => $pedido->id]);
        } catch (\Exception $e) {
            $this->logger->error('Falha ao processar pedido', [
                'id'    => $pedido->id,
                'erro'  => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);
            throw $e;
        }
    }
}

// Níveis PSR-3 (do menos para o mais grave):
// debug, info, notice, warning, error, critical, alert, emergency

PSR-7 — HTTP Messages

PSR-7 define interfaces para representar requisições e respostas HTTP como objetos imutáveis. É a base de frameworks como Slim, Laminas e middlewares PSR-15:

<?php
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;

// Um handler de rota recebe uma Request e retorna uma Response
function listarUsuarios(ServerRequestInterface $request): ResponseInterface
{
    $queryParams = $request->getQueryParams();
    $pagina      = (int) ($queryParams['pagina'] ?? 1);

    $usuarios = buscarUsuarios($pagina);

    // Response é imutável — withHeader retorna uma nova instância
    $response = new \Nyholm\Psr7\Response();
    $response = $response
        ->withHeader('Content-Type', 'application/json')
        ->withStatus(200);

    $response->getBody()->write(json_encode($usuarios));

    return $response;
}

PSR-11 — Container de Injeção de Dependência

PSR-11 define a interface para containers de DI. Laravel, Symfony e PHP-DI todos implementam esta interface:

<?php
use Psr\Container\ContainerInterface;

class ServiceLocator
{
    public function __construct(private readonly ContainerInterface $container) {}

    public function obterServico(string $id): mixed
    {
        if (!$this->container->has($id)) {
            throw new \RuntimeException("Serviço não registrado: {$id}");
        }
        return $this->container->get($id);
    }
}

// Implementação simples de container
class Container implements ContainerInterface
{
    private array $bindings  = [];
    private array $instancias = [];

    public function bind(string $abstract, callable $factory): void
    {
        $this->bindings[$abstract] = $factory;
    }

    public function singleton(string $abstract, callable $factory): void
    {
        $this->bindings[$abstract] = function () use ($abstract, $factory) {
            if (!isset($this->instancias[$abstract])) {
                $this->instancias[$abstract] = $factory($this);
            }
            return $this->instancias[$abstract];
        };
    }

    public function get(string $id): mixed
    {
        if (!$this->has($id)) {
            throw new \RuntimeException("Não encontrado: {$id}");
        }
        return ($this->bindings[$id])($this);
    }

    public function has(string $id): bool
    {
        return isset($this->bindings[$id]);
    }
}

// Configurando o container:
$container = new Container();

$container->singleton(PDO::class, fn() => new PDO(
    'pgsql:host=localhost;dbname=loja',
    'user', 'pass',
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
));

$container->bind(
    UsuarioRepositorioInterface::class,
    fn(ContainerInterface $c) => new UsuarioRepositorioPDO($c->get(PDO::class))
);

$container->bind(
    ProcessadorDePedidos::class,
    fn(ContainerInterface $c) => new ProcessadorDePedidos($c->get(LoggerInterface::class))
);

PHPStan — análise estática de tipos

PHPStan analisa o código sem executá-lo, detectando erros de tipo, chamadas a métodos inexistentes, variáveis não definidas e muitos outros problemas:

composer require --dev phpstan/phpstan

# Configura em phpstan.neon
cat > phpstan.neon << 'EOF'
parameters:
    level: 8          # 0 a 9 — quanto maior, mais rigoroso
    paths:
        - src
    checkMissingIterableValueType: false
EOF

vendor/bin/phpstan analyse
<?php
// PHPStan detecta isso como erro no nível 5+:

function processarId(int $id): string
{
    $resultado = buscarNoBanco($id); // retorna string|null

    return strtoupper($resultado); // ERRO: argumento pode ser null
}

// Correto:
function processarId(int $id): string
{
    $resultado = buscarNoBanco($id);

    if ($resultado === null) {
        throw new \RuntimeException("ID {$id} não encontrado.");
    }

    return strtoupper($resultado);
}

Resumo das PSRs mais importantes

PSR Assunto Uso prático
PSR-1 Estilo básico Base de todo código PHP profissional
PSR-4 Autoloading Mapeamento namespace → diretório
PSR-3 Logger Interface comum para logging
PSR-7 HTTP Messages Requisições/respostas imutáveis
PSR-11 Container DI Interface comum para containers
PSR-12 Estilo estendido Formatação consistente do código

Exercício da semana

  1. Instale friendsofphp/php-cs-fixer e configure para PSR-12 + declare_strict_types. Rode em um projeto existente e analise as diferenças reportadas.

  2. Instale phpstan/phpstan e rode no nível 5. Corrija todos os erros. Depois suba para o nível 7 e corrija novamente.

  3. Implemente o container de DI da aula. Adicione suporte a autowiring básico: se a classe tem um construtor com type hints de classes registradas, resolva automaticamente.

  4. Refatore uma classe que usa error_log() diretamente para receber um LoggerInterface por injeção de dependência. Use Monolog como implementação concreta, injetado pelo container.

  5. Desafio: crie um pipeline de CI simples com GitHub Actions (ou script Makefile) que rode php-cs-fixer --dry-run, phpstan analyse, e phpunit em sequência — e falhe se qualquer um deles falhar.


Referências

  • PHP-FIG (todas as PSRs): https://www.php-fig.org/psr/
  • PHP CS Fixer: https://cs.symfony.com/
  • PHPStan: https://phpstan.org/
  • Packagist — PSR interfaces: https://packagist.org/packages/psr/log
Comentários

Mais em PHP

Composer e Namespaces
Composer e Namespaces

Antes do Composer, gerenciar dependências em PHP era um pesadelo manual: baix...

Artigo 49 — Performance e Otimização
Artigo 49 — Performance e Otimização

Performance não é premature optimization — é entender onde estão os gargalos...

Artigo 40 — Boas Práticas e Código Limpo em PHP
Artigo 40 — Boas Práticas e Código Limpo em PHP

Boas práticas não são regras arbitrárias inventadas por puristas. São soluçõe...