
Compartilhar:
Sou ator formado, com uma dissertação sobre stand-up comedy, e comecei a me dedicar ao desenvolvimento em PHP por meio dos encontros da comunidade. Você pode me encontrar dando palestras e escrevendo sobre tecnologia, ou ouvindo e comprando discos curiosos da minha coleção de vinil.
Mãos à obra! Como limpar seu aplicativo PHP com o PHPStan
Durante o tempo em que trabalhei como desenvolvedor PHP, a maneira como escrevemos e lançamos código mudou drasticamente. No início do Symfony e Zend Framework Applications, o PHP-FIG ainda não existia e os padrões de codificação ficavam a critério de quem estivesse escrevendo o código. Ao longo dos anos, assistimos à ampla adoção dos padrões PSR, as ferramentas de análise estática realmente confiáveis têm sido um tanto irregulares. Isso é, até agora, com o lançamento da versão 1.0 do PHPStan. Vamos comemorar essa ocasião conhecendo alguns de seus recursos!
Linguagens compiladas: seu eliminador preventivo de bugs
Uma das grandes vantagens de usar uma linguagem compilada como Java ou C# é que a compilação falhará completamente se o seu código não for tipicamente seguro, o que impõe padrões (embora seja fácil para mim dizer isso, já que não são 2 da manhã e não estou tomando meu décimo café da noite). Com PHP ser uma linguagem interpretada, não temos esse mesmo luxo.
Interpretado como compilado: CI + ferramentas
Graças à enorme variedade de ferramentas de DevOps disponíveis para nós no desenvolvimento web moderno e na análise estática, a questão é que que temos as mesmas ferramentas disponíveis, mas por meios diferentes. Como esse é o caso, não posso defender o quanto recomendo que você tenha algo semelhante ao ambiente que vou apresentar. Então, por que você iria querer essas ferramentas? Vamos ver um exemplo.
O cenário
É comum tentar escolher um tema que seja divertido ou que se aplique ao que você está escrevendo quando se trata de ferramentas como essa. Mas, neste artigo, vou apresentar a vocês um cenário com o qual já me deparei pessoalmente inúmeras vezes em ambientes de agências:
"Socorro! Outra pessoa desenvolveu meu aplicativo em PHP e preciso de alguém para salvá-lo e assumir a manutenção, pois os recursos X/Y/Z precisam ser implementados, mas os recursos A/B/C nem sequer funcionam direito!"
Assumir o código-fonte ou o projeto de outra pessoa é sempre uma verdadeira loteria. Se você está assumindo o projeto porque ele precisa de novos recursos e já é uma bagunça repleta de dívida técnica, sabe que precisa resolver isso antes de mexer em qualquer outra coisa. Pior ainda, muitos desses projetos (pela minha experiência) costumam vir sem absolutamente nenhum teste para auto-documentar o código. Considere um exemplo clássico, que já vi inúmeras vezes:
$someData = \MyNamespace\MyORM\MyRepository::findAllBySomething(SOMETHING);
foreach ($someData as $myEntity) {
$myEntity->doTheThing();
}
Você não escreveu essa classe de entidade nem o método do repositório. Eles não têm indicações de tipo porque foram escritos originalmente em PHP 5.3, ou porque o desenvolvedor não utilizou nenhuma. Não há problema se o seu ORM retornar um array com as mesmas entidades, mas basta um bug, um resultado nulo no valor de retorno de findAllBySomething() e doTheThing() causará um erro fatal.
É hora de definir o PHPStan nele.

Conheça sua estratégia
Embora seja fácil dizer “use o PHPStan”, se você tiver uma aplicação legado ou com muita dívida técnica, é melhor ter uma estratégia em vez de simplesmente tentar coisas aleatoriamente para ver o que acontece. Em primeiro lugar, é importante se familiarizar com os níveis de regras.
Níveis de regras
O PHPStan está estruturado para ser executado com determinados níveis de regras, numerados de 0 a 9:
verificações básicas, classes desconhecidas, funções desconhecidas, métodos desconhecidos chamados em
$this, número incorreto de argumentos passados para esses métodos e funções, variáveis sempre indefinidasvariáveis possivelmente indefinidas, métodos mágicos desconhecidos e propriedades em classes com
__calle__getmétodos desconhecidos verificados em todas as expressões (não apenas
$this), validando os PHPDocstipos de retorno, tipos atribuídos a propriedades
verificação básica de código morto — sempre falso
instanceofe outras verificações de tipo, ramos mortoselse, código inacessível após o retorno; etc.verificação dos tipos de argumentos passados para métodos e funções
relatar dicas de tipo ausentes
alertar sobre tipos de união parcialmente incorretos — se você chamar um método que só existe em alguns tipos dentro de um tipo de união, o nível 7 começa a alertar sobre isso; outras situações possivelmente incorretas
relatório sobre a chamada de métodos e o acesso a propriedades em tipos nulosáveis
seja rigoroso quanto ao
mixedtipo — a única operação permitida que você pode realizar com ele é passá-lo para outromixed
É por isso que sua estratégia é importante. Se você tem um projeto legado escrito por outra pessoa e executa o executador de tarefas do PHPStan no nível 9, pode ficar surpreso com os resultados gerados. Tudo está errado! Para refatorar, sugiro o seguinte:
Estabeleça metas para cada nível identificado e comece aos poucos.
O investimento de longo prazo acabará valendo a pena (falaremos sobre os pipelines em breve), mas defina o limite máximo que você está disposto a aceitar ao classificar “resolução da dívida técnica” de acordo com sua própria “definição de concluído”
Uma boa meta de fato para um projeto legado é conseguir que o Nível 6 de Regras seja aprovado. É nesse ponto que sua base de código provavelmente poderá passar de um estado de “perigo” para “correto”. Isso tornaria o Nível 6 de Reglas sua linha de base).
Isso é superimportante: certifique-se de reservar tempo (sprints, tickets do Jira detalhados para os masoquistas) para corrigir o que o PHPStan está sinalizando em cada nível de regra. Corrigir a dívida técnica não é fácil em muitos casos, e você não tem ideia de que tipo de falhas na lógica do domínio de negócios podem existir em sua aplicação.
Ao definir as metas incrementais para os Níveis de Regra, certifique-se de configurar seu pipeline antes de confirmar as alterações, para não introduzir novos indícios de código problemático durante a refatoração. A configuração do seu pipeline exigirá que você estabeleça sua linha de base, assunto que abordaremos mais adiante.
Pipeline
No mundo do DevOps, há uma quantidade um tanto avassaladora de opções de ferramentas disponíveis para resolver seus problemas. Nesse caso, estou propondo apenas uma abordagem, mas ela é menos complexa do que outras opções disponíveis. Depois de definir sua estratégia, é hora de configurar seu pipeline para que não seja feito nenhum commit de código novo que não tenha passado primeiro pelo PHPStan.
Barreiras de defesa: locais x do lado do servidor
Gosto de implementar ferramentas para eliminar qualquer possibilidade de pontos únicos de falha e, devido a esse ceticismo, recomendo fortemente que você execute sua análise estática tanto nas máquinas locais dos desenvolvedores quanto verificações de CI do lado do servidor em seu repositório.
Local
Composer + PHPStan
Primeiro, você vai precisar instalar o PHPStan no seu projeto. Vamos usar o Composer para isso, partindo do princípio de que, esperamos, seu código legado já utilize gerenciamento de pacotes. Caso contrário, você pode instalar o composer e usar composer init para criar um novo projeto.
Para instalar o PHPStan, execute o seguinte:
Estamos adicionando --dev já que não precisamos disso para produção (em teoria!).
Configuração: definição da linha de base
Esse é um recurso bem legal do PHPStan. Sua linha de base define o “ponto zero” do seu aplicativo, de modo que quaisquer erros existentes no nível de regra de sua escolha sejam ignorados até que você decida corrigi-los, mas, ao mesmo tempo, pode impor um nível de regra para quaisquer novas alterações confirmadas. Uma abordagem sensata, conforme descrito na estratégia, seria definir uma linha de base no Nível de Regra 6:
Todo o código novo enviado para o projeto precisaria estar no Nível de Regra 6
Você pode, então, definir as metas de dívida técnica para os níveis inferiores, conforme identificado em suas metas estratégicas.
Para criar sua linha de base, execute o seguinte:
Agora você terá sua configuração de referência definida no arquivo especificado (phpstan.neon), que armazena uma visão geral detalhada dos erros por arquivo.
Agora, você vai querer que o PHPStan impeça commits no seu repositório antes que possam ser enviados para o seu código-fonte. Para isso, usamos os hooks do Git.
Git hooks
De alguma forma, levei anos para perceber que o Git, na verdade, instala hooks por padrão em um novo repositório Git em git init. Você pode ler mais sobre os hooks do Git aqui. Vamos editar o pre-commit hook. Contanto que você não tenha mexido em nenhum hook antes no seu projeto, é possível habilitar o hook pre-commit renomeando-o — execute este comando na raiz do seu projeto:
Agora abra o arquivo, apague o conteúdo e copie o seguinte:
Agora que você ativou pre-commit, o PHPStan será executado antes de cada commit e fará a análise em relação à linha de base para verificar se há novos arquivos que foram alterados no commit do Git. Chega de código de má qualidade enviado para o repositório!
Talvez você queira ajustar o acionador da linha de comando ao passar para níveis superiores; assim, quando for necessário alterá-lo (ou se você quiser habilitar outros recursos do PHPStan), altere os analysisResult=$(vendor/bin/phpstan analyse $gitDiffFiles) argumentos da linha de comando.
Do lado do servidor
Quanto mais proteção você puder oferecer ao seu código, melhor. Executar o PHPStan no lado do servidor após um push do seu código, como parte da sua Integração Contínua, é um imprescindível. Neste exemplo, vamos usar o GitHub Actions, mas lembre-se de que você pode configurar isso com o mesmo nível de funcionalidade no CircleCI, Bitbucket Pipelines, GitLab CI/CDou Jenkins. Aqui está um exemplo de fluxo de trabalho de ações configurado no GitHub, compilando seu código com um Ubuntu :
---
name: build
on: [ push, pull_request ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
name: Build example
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.0
extensions: json, mbstring
coverage: pcov
env:
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run PHPStan
run: vendor/bin/phpstan analyse .O comando na seção “Executar o PHPStan” pode ser configurado de acordo com suas necessidades, da mesma forma que você configura o comando ao executar o PHPStan localmente. Eu criei este fluxo de trabalho para executar o PHPStan em um nível padrão em todos os arquivos do projeto (este fluxo de trabalho ainda não foi acionado composer ainda, portanto não terá a etapa desnecessária e ineficiente de executá-lo na sua vendor pasta). Portanto, recomendo ter uma configuração que defina o Nível de Regra de todo o seu projeto.
Seu projeto legado agora conta com uma estratégia para aprimorar seu código e pipelines para impedir que novos bugs apareçam nos commits, ao mesmo tempo em que realiza análises em relação à linha de base de todo o código existente. É esse tipo de configuração que pode lhe dar muito mais confiança ao fazer commits no projeto, ao mesmo tempo em que fornece insights sobre as áreas prováveis que precisam de refatoração para eliminar a dívida técnica.
Por último, mas não menos importante: análise estática x testes
Quero deixar isso bem claro, especialmente para quem está lá atrás: o PHPStan e qualquer outra ferramenta de análise estática não substituem seus testes! A maneira como eu definiria o uso dessas ferramentas é que um conjunto de testes e o PHPStan se complementam na avaliação da qualidade do seu código.
É um equívoco acreditar que você tem pouca ou nenhuma necessidade de um conjunto de testes. O mais importante aqui é que a análise estática não consegue testar sua lógica de domínio. Embora possa parecer uma afirmação óbvia, vale a pena observar que isso pode causar confusão, já que o PHPStan pode eliminar a necessidade de certos testes. Um exemplo disso seria um instanceOf teste que verifique se uma classe que está sendo criada é o resultado final de um processo. O PHPStan pode eliminar essa exigência, pois fornece a análise necessária para eliminar esse possível bug, mas não conhece antecipadamente a sua lógica de domínio — é isso que você precisa precisa testar.
E lembre-se: existem alternativas!
Você experimentou? Não gostou muito? Cada um tem sua preferência, e embora eu vá elogiar muito o Ondřej pelo trabalho dele no PHPStan, vale a pena notar que existem várias outras ferramentas que fazem o mesmo trabalho ou podem ser usadas em conjunto com o PHPStan:
Obrigado
Um agradecimento especial a Ondřej Mirtes, tanto pelos conselhos quanto por todo o seu empenho no lançamento desta ferramenta incrível.
Compartilhar:
Sou ator formado, com uma dissertação sobre stand-up comedy, e comecei a me dedicar ao desenvolvimento em PHP por meio dos encontros da comunidade. Você pode me encontrar dando palestras e escrevendo sobre tecnologia, ou ouvindo e comprando discos curiosos da minha coleção de vinil.