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
-
Instale
friendsofphp/php-cs-fixere configure para PSR-12 +declare_strict_types. Rode em um projeto existente e analise as diferenças reportadas. -
Instale
phpstan/phpstane rode no nível 5. Corrija todos os erros. Depois suba para o nível 7 e corrija novamente. -
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.
-
Refatore uma classe que usa
error_log()diretamente para receber umLoggerInterfacepor injeção de dependência. Use Monolog como implementação concreta, injetado pelo container. -
Desafio: crie um pipeline de CI simples com GitHub Actions (ou script Makefile) que rode
php-cs-fixer --dry-run,phpstan analyse, ephpunitem 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