Análise estática
LidoEsta aula apresenta a análise estática em PHP, focando nas ferramentas PHPStan e Psalm. Você aprenderá sobre os níveis de rigor, a declaração de tipos e a integração dessas ferramentas no fluxo de desenvolvimento, com exemplos práticos e exercícios.
A análise estática é uma prática essencial no desenvolvimento moderno de PHP. Diferente dos testes dinâmicos, que executam o código e verificam o comportamento em tempo de execução, a análise estática examina o código-fonte sem executá-lo, procurando por erros potenciais, inconsistências de tipos, uso incorreto de APIs, e outros problemas que podem passar despercebidos. Nesta aula, vamos explorar duas das ferramentas mais populares de análise estática para PHP: PHPStan e Psalm. Você aprenderá como elas funcionam, como configurá-las em diferentes níveis de rigor, como aproveitar ao máximo a declaração de tipos e como integrá-las ao seu fluxo de trabalho, seja em um projeto pequeno ou em uma grande base de código.
Dominar a análise estática não apenas melhora a qualidade do código, mas também aumenta a confiança do desenvolvedor ao refatorar e expandir o sistema. Ao final desta aula, você será capaz de configurar e utilizar essas ferramentas de forma eficaz, entendendo os trade-offs entre rigor e praticidade.
PHPStan e Psalm (visão geral)
PHPStan e Psalm são ferramentas de análise estática que atuam como um "linter" avançado e um verificador de tipos. Elas analisam o código-fonte e relatam problemas que vão desde erros de sintaxe até inconsistências de tipos que o PHP não detectaria em tempo de execução. Ambas são escritas em PHP e podem ser instaladas via Composer, o que as torna fáceis de integrar em qualquer projeto.
PHPStan, criado por Ondřej Mirtes, é uma das ferramentas mais adotadas. Ele se concentra em encontrar erros sem executar o código, utilizando um sistema de tipos que entende o PHPDoc e a tipagem nativa do PHP 7+. Por outro lado, Psalm, desenvolvido pela Vimeo, tem um foco similar, mas também oferece recursos adicionais, como a análise de fluxo de dados e a capacidade de sugerir melhorias de tipos. Ambas as ferramentas são altamente configuráveis, permitindo que você ajuste o nível de rigor conforme a necessidade do seu projeto.
Uma das principais diferenças está na forma como eles lidam com a inferência de tipos. PHPStan tende a ser mais conservador, exigindo que você declare tipos explicitamente em muitos casos, enquanto Psalm é mais agressivo em inferir tipos a partir do contexto. Isso pode influenciar a quantidade de falsos positivos e a curva de aprendizado. A escolha entre eles muitas vezes se resume a preferência pessoal ou requisitos específicos do projeto, mas ambos são excelentes e podem ser usados em conjunto.
Vamos ver um exemplo simples de como cada ferramenta reporta um problema comum: passar um argumento do tipo errado para uma função.
<?php
function greet(string $name): string {
return "Olá, $name!";
}
// Isso vai gerar um erro em ambas as ferramentas
echo greet(123);
PHPStan e Psalm detectarão que um inteiro está sendo passado para um parâmetro que espera uma string, mesmo que o PHP apenas converta o valor em tempo de execução. Isso é extremamente valioso para evitar bugs sutis.
Níveis
Uma das características mais poderosas do PHPStan e do Psalm é o sistema de níveis. Em vez de uma abordagem de tudo ou nada, eles permitem que você comece com um nível baixo, que detecta apenas os problemas mais óbvios, e aumente gradualmente o nível de rigor à medida que seu código se torna mais robusto. Isso facilita a adoção em projetos existentes, onde corrigir todos os problemas de uma vez seria inviável.
No PHPStan, os níveis vão de 0 a 10 (atualmente). O nível 0 é o mais básico e verifica apenas erros de sintaxe e chamadas a funções indefinidas. O nível 1 adiciona verificação de tipos básicos em chamadas de função e métodos. À medida que você sobe, a ferramenta se torna mais exigente, verificando tipos de retorno, propriedades, genéricos, etc. O nível máximo (10) é extremamente rigoroso e pode ser usado em projetos de missão crítica.
No Psalm, os níveis vão de 1 a 8, com o nível 1 sendo o mais rigoroso e o 8 o mais permissivo. A lógica é invertida em relação ao PHPStan. No Psalm, o nível 1 exige que todo o código seja totalmente tipado e sem qualquer erro, enquanto o nível 8 permite muitos relaxamentos. A maioria dos projetos usa níveis intermediários, como 5 ou 6 no Psalm, ou 6 ou 7 no PHPStan.
Para configurar o nível, você usa o parâmetro level no arquivo de configuração ou na linha de comando. Veja um exemplo de configuração para PHPStan:
vendor/bin/phpstan analyse src --level=6
E para Psalm:
vendor/bin/psalm --level=5
É importante escolher um nível que seja desafiador, mas não a ponto de gerar muitos falsos positivos que desmotivem a equipe. Uma boa prática é começar com um nível baixo, corrigir os problemas reportados e, gradualmente, aumentar o nível até atingir o desejado.
Tipos
A análise estática depende fortemente da declaração de tipos. O PHP moderno (7.0+) introduziu tipos escalares e de retorno, e o PHP 8.0 adicionou tipos union e mixed, entre outros. No entanto, para que as ferramentas de análise estática sejam realmente eficazes, é necessário também utilizar o PHPDoc para documentar tipos mais complexos, como arrays de objetos, genéricos e tipos de união.
Considere o seguinte exemplo, onde o PHPDoc ajuda a ferramenta a entender a estrutura do array:
<?php
/**
* @param array<string, int> $map
* @return array<string, int>
*/
function doubleValues(array $map): array {
$result = [];
foreach ($map as $key => $value) {
$result[$key] = $value * 2;
}
return $result;
}
Com essa anotação, o PHPStan e o Psalm saberão que a função espera um array com chaves string e valores inteiros, e que o retorno tem a mesma estrutura. Se você tentar passar um array de strings, a ferramenta emitirá um aviso.
Além disso, as ferramentas suportam a análise de classes, interfaces e traits, verificando se os tipos estão corretos em métodos e propriedades. Por exemplo:
<?php
class User {
private int $id;
private string $name;
public function __construct(int $id, string $name) {
$this->id = $id;
$this->name = $name;
}
public function getName(): string {
return $this->name;
}
}
Se você tentar atribuir um valor não inteiro à propriedade $id fora do construtor, a análise estática detectará. Isso ajuda a prevenir erros que só apareceriam em tempo de execução.
Outro recurso importante é o uso de generics, que permitem especificar tipos de forma mais precisa para coleções. Tanto o PHPStan quanto o Psalm suportam generics via PHPDoc, como no exemplo acima com array<string, int>. Isso é essencial para escrever código seguro e bem tipado.
Integração
Integrar a análise estática ao fluxo de desenvolvimento é crucial para colher seus benefícios. A maneira mais comum é executar as ferramentas como parte do processo de CI/CD, garantindo que nenhum código com problemas seja mesclado. Além disso, muitos editores de código, como VS Code e PHPStorm, oferecem integração com PHPStan e Psalm, mostrando erros em tempo real enquanto você digita.
Para integrar ao Composer, você pode adicionar scripts no composer.json para facilitar a execução:
{
"scripts": {
"phpstan": "phpstan analyse src --level=6",
"psalm": "psalm --level=5"
}
}
Assim, você pode rodar composer phpstan ou composer psalm no terminal.
Em um pipeline de CI (por exemplo, GitHub Actions), você pode adicionar um passo para rodar a análise estática. Veja um exemplo de arquivo de workflow:
name: PHP Quality
on: [push, pull_request]
jobs:
static-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
tools: composer
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Run PHPStan
run: vendor/bin/phpstan analyse src --level=6
Para desenvolvimento local, você pode usar extensões como PHPStan para VS Code, que exibe erros em arquivos abertos. Isso permite corrigir problemas antes mesmo de executar a análise completa.
Outra integração útil é com ferramentas de pré-commit, como o Husky (para JavaScript) ou simplesmente scripts de Git. Você pode adicionar um hook que roda a análise estática antes de cada commit, garantindo que o código commitado esteja limpo.
Por fim, vale a pena mencionar que ambas as ferramentas podem ser configuradas para ignorar certos arquivos ou diretórios, como código gerado ou bibliotecas de terceiros, através do parâmetro paths ou excludePaths.
Boas práticas
Ao adotar análise estática, é importante começar aos poucos e aumentar o nível gradualmente. Não tente corrigir todos os erros de uma vez em um projeto legado; em vez disso, priorize os arquivos mais críticos. Use as anotações PHPDoc generosas para ajudar as ferramentas a entender seu código, mas evite anotações desnecessárias que poluem o código. Lembre-se de que a análise estática é uma ferramenta complementar aos testes unitários e não deve substituí-los.
Além disso, mantenha as ferramentas atualizadas e revise as novas regras que podem ser adicionadas em versões mais recentes. Participe das comunidades e acompanhe as discussões sobre melhores práticas. Por fim, compartilhe o conhecimento com sua equipe para que todos possam se beneficiar e manter um padrão consistente.
Referências
- PHPStan - Site Oficial
- Psalm - Site Oficial
- PHP Manual - Tipos
- PHPStan - Níveis de regras
- Psalm - Níveis de erro
- PHPStan - Repositório GitHub
- Psalm - Repositório GitHub
Exercícios
- Instale o PHPStan em um projeto PHP (crie um diretório e execute
composer require --dev phpstan/phpstan). Crie um arquivoexample.phpcom o seguinte código:<?php function add(int $a, int $b) { return $a + $b; } echo add('1', 2);. Execute o PHPStan no nível 0 e depois no nível 5. Qual é a diferença nos relatórios?✓ Resposta: No nível 0, o PHPStan não detecta o erro de tipo, pois o nível 0 só verifica erros de sintaxe e funções indefinidas. No nível 5, ele reporta que o argumento '1' (string) não é compatível com o parâmetro int. A diferença é que níveis mais altos incluem verificações de tipos mais rigorosas. - Escreva uma função que receba um array de inteiros e retorne a soma dos quadrados. Utilize PHPDoc para documentar os tipos de entrada e saída. Em seguida, rode o Psalm e o PHPStan no nível 6 e veja se eles identificam algum problema em uma chamada que passa um array de strings.✓ Resposta: Exemplo de função:
Se você chamar/** * @param int[] $numbers * @return int */ function sumOfSquares(array $numbers): int { $sum = 0; foreach ($numbers as $n) { $sum += $n * $n; } return $sum; }sumOfSquares(['a', 'b']), tanto o PHPStan quanto o Psalm reportarão que o array contém strings, não inteiros, no nível 6. - Configure o PHPStan para ignorar um diretório chamado
vendore um arquivo específicolegacy.php. Crie um arquivo de configuraçãophpstan.neoncom as diretivas necessárias.✓ Resposta: Arquivophpstan.neon:
Ou usando a sintaxe de análise:parameters: paths: - src excludePaths: - vendor - legacy.phpanalyse: [src]eexcludePaths: [vendor, legacy.php]. - Explique a diferença entre os níveis do PHPStan e os níveis do Psalm. Qual abordagem é mais adequada para um projeto legado?✓ Resposta: No PHPStan, os níveis vão de 0 (mais permissivo) a 10 (mais rigoroso). No Psalm, os níveis vão de 1 (mais rigoroso) a 8 (mais permissivo). Para um projeto legado, é melhor começar com um nível baixo no PHPStan (ex: 0 ou 1) ou um nível alto no Psalm (ex: 7 ou 8) para evitar uma enxurrada de erros, e então aumentar gradualmente o rigor.
- Integre o PHPStan ao seu fluxo de CI (por exemplo, GitHub Actions). Escreva um arquivo de workflow YAML que execute o PHPStan em todo push ou pull request, usando o nível 6 e o diretório
src.✓ Resposta: Exemplo de.github/workflows/phpstan.yml:name: PHPStan on: [push, pull_request] jobs: phpstan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: '8.2' tools: composer - name: Install dependencies run: composer install --prefer-dist --no-progress - name: Run PHPStan run: vendor/bin/phpstan analyse src --level=6