Análise estática do código PHP usando PHPStan, Phan e Salmo como exemplo



O Badoo existe há mais de 12 anos. Temos muito código PHP (milhões de linhas) e provavelmente até as linhas escritas há 12 anos foram preservadas. Temos código gravado nos dias do PHP 4 e PHP 5. Carregamos o código duas vezes por dia e cada layout contém cerca de 10 a 20 tarefas. Além disso, os programadores podem publicar correções urgentes - pequenas alterações. E no dia de tais correções, ganhamos algumas dúzias. Em geral, nosso código está mudando muito ativamente.

Estamos constantemente à procura de oportunidades para acelerar o desenvolvimento e melhorar a qualidade do código. Então, um dia, decidimos implementar a análise de código estático. O que veio disso, leia abaixo do corte.

Tipos estritos: por que ainda não o estamos usando


Uma vez, iniciou-se uma discussão em nosso bate-papo corporativo sobre PHP. Um dos novos funcionários contou como, no local de trabalho anterior, eles introduziram as dicas obrigatórias do tipo strict_types + escalar para todo o código - e isso reduziu significativamente o número de bugs na produção.

A maioria dos veteranos do bate-papo era contra essa inovação. O principal motivo foi que o PHP não possui um compilador que verifique todos os tipos no código no estágio de compilação, e se você não tiver 100% de cobertura do código com testes, sempre haverá o risco de que erros apareçam na produção, o que não temos quer permitir.

Obviamente, strict_types encontrará uma certa porcentagem de erros causados ​​pela incompatibilidade de tipos e como o PHP "silenciosamente" converte tipos. Mas muitos programadores experientes em PHP já sabem como o sistema de tipos em PHP funciona, por quais regras a conversão de tipos acontece e, na maioria dos casos, eles escrevem código de trabalho correto.

Mas a ideia de ter um certo sistema mostrando onde no código há uma incompatibilidade de tipos, gostamos. Pensamos em alternativas para strict_types.

No começo, queríamos até corrigir o PHP. Queríamos que, se a função usasse algum tipo de escalar (digamos int) e outro tipo escalar (como float), então TypeError (que é uma exceção em si) não seria lançado, mas ocorreria uma conversão de tipo, bem como registrar esse evento em error.log. Isso nos permitiria encontrar todos os lugares onde nossas suposições sobre tipos estão incorretas. Mas esse patch parecia arriscado para nós, e até poderia haver problemas com dependências externas que não estavam prontas para esse comportamento.

Abandonamos a idéia de corrigir o PHP, mas com o tempo tudo coincidiu com os primeiros lançamentos do analisador estático Phan, os primeiros commits feitos pelo próprio Rasmus Lerdorf. Então, tivemos a ideia de tentar analisadores de código estático.

O que é análise de código estático?


Os analisadores de código estático apenas leem o código e tentam encontrar erros nele. Eles podem executar verificações muito simples e óbvias (por exemplo, a existência de classes, métodos e funções e outras mais complicadas (por exemplo, procurar incompatibilidades de tipos, condições de corrida ou vulnerabilidades no código) .A chave é que os analisadores não executam código - eles analise o texto do programa e verifique erros típicos (e não muito).

O exemplo mais óbvio de um analisador de código PHP estático são as inspeções no PHPStorm: quando você escreve um código, destaca chamadas incorretas para funções, métodos, incompatibilidades de tipo de parâmetro, etc. No entanto, o PHPStorm não executa seu código PHP - apenas o analisa.

Observo que neste artigo estamos falando de analisadores que procuram erros no código. Há outra classe de analisadores - eles verificam o estilo de escrita do código, a complexidade ciclomática, os tamanhos dos métodos, os comprimentos das linhas etc. Não consideramos esses analisadores aqui.

Embora nem tudo o que os analisadores que estamos considerando encontre seja precisamente um erro. Por engano, quero dizer o código que Fatal criará na produção. Muitas vezes, o que os analisadores descobrem é provavelmente uma imprecisão. Por exemplo, um tipo de parâmetro incorreto pode ser especificado no PHPDoc. Essa imprecisão não afeta a operação do código, mas subsequentemente o código evoluirá - outro programador pode cometer um erro.

Analisadores de código PHP existentes


Existem três analisadores de código PHP populares:

  1. PHPStan .
  2. Salmo .
  3. Phan .

E há Exakat , que não tentamos.

No lado do usuário, todos os três analisadores são os mesmos: você os instala (provavelmente através do Composer), os configura e depois pode iniciar a análise de todo o projeto ou grupo de arquivos. Como regra, o analisador pode exibir belamente os resultados no console. Você também pode produzir os resultados no formato JSON e usá-los no IC.

Todos os três projetos estão agora se desenvolvendo ativamente. Seus mantenedores são muito ativos na resposta a problemas no GitHub. Frequentemente, no primeiro dia após a criação de um ticket, eles reagem a ele pelo menos (comente ou coloque uma tag como bug / aprimoramento). Muitos bugs que encontramos foram corrigidos em alguns dias. Mas gosto especialmente do fato de que os mantenedores do projeto se comunicam ativamente entre si, relatam bugs e enviam solicitações pull.

Implementamos e usamos todos os três analisadores. Cada um tem suas próprias nuances, seus próprios insetos. Porém, o uso de três analisadores ao mesmo tempo facilita a compreensão de onde está o problema real e onde está o falso positivo.

O que os analisadores podem fazer


Os analisadores têm muitos recursos em comum. Primeiro, veremos o que todos eles podem fazer e depois passaremos aos recursos de cada um deles.

Verificações padrão


Obviamente, os analisadores realizam todas as verificações de código padrão pelo fato de:

  • O código não contém erros de sintaxe;
  • existem todas as classes, métodos, funções, constantes;
  • existem variáveis;
  • no PHPDoc, as dicas são verdadeiras.

Além disso, os analisadores verificam o código em busca de argumentos e variáveis ​​não utilizados. Muitos desses erros levam a fatais reais no código.

À primeira vista, pode parecer que bons programadores não cometem tais erros, mas às vezes temos pressa, às vezes copia e cola, às vezes simplesmente desatentos. E nesses casos, essas verificações economizam muito.

Verificações de tipo de dados


Obviamente, os analisadores estáticos também realizam verificações padrão em relação aos tipos de dados. Se estiver escrito no código que a função aceita, digamos, int, o analisador verificará se há lugares onde um objeto é passado para essa função. Para a maioria dos analisadores, você pode configurar a gravidade do teste e simular strict_types: verifique se nenhuma string ou Boolean é passada para esta função.

Além das verificações padrão, os analisadores ainda têm muito o que fazer.

Tipos de união

Todos os analisadores suportam o conceito de tipos de união. Suponha que você tenha uma função como:

/** * @var string|int|bool $yes_or_no */ function isYes($yes_or_no) :bool {    if (\is_bool($yes_or_no)) {         return $yes_or_no;     } elseif (is_numeric($yes_or_no)) {         return $yes_or_no > 0;     } else {         return strtoupper($yes_or_no) == 'YES';     } } 

Seu conteúdo não é muito importante - o tipo do parâmetro de entrada string|int|bool é importante. Ou seja, a variável $yes_or_no é uma sequência de caracteres, um número inteiro ou um Boolean .

Usando PHP, este tipo de parâmetro de função não pode ser descrito. Mas no PHPDoc, isso é possível, e muitos editores (como o PHPStorm) entendem.

Nos analisadores estáticos, esse tipo é chamado de tipo de união e eles são muito bons em verificar esses tipos de dados. Por exemplo, se escrevemos a função acima dessa maneira (sem verificar o valor Boolean ):

 /** * @var string|int|bool $yes_or_no */ function isYes($yes_or_no) :bool {    if (is_numeric($yes_or_no)) {        return $yes_or_no > 0;    } else {        return strtoupper($yes_or_no) == 'YES';    } } 

os analisadores veriam que uma string ou um booleano poderia chegar ao strtoupper e retornar um erro - você não pode passar um booleano para o strtoupper.

Esse tipo de verificação ajuda os programadores a lidar corretamente com erros ou situações em que uma função não pode retornar dados. Geralmente escrevemos funções que podem retornar alguns dados ou null :

 // load()  null   \User $User = UserLoader::load($user_id); $User->getName(); 

No caso de tal código, o analisador informará que a variável $User aqui pode ser null e esse código pode levar a fatal.

Digite false

Na própria linguagem PHP, existem algumas funções que podem retornar algum valor ou falso. Se escrevêssemos tal função, como documentaríamos seu tipo?

          /** @return resource|bool */ function fopen(...) {       … } 

Formalmente, tudo parece ser verdade aqui: fopen retorna resource ou false (que é do tipo Boolean ). Mas quando dizemos que uma função retorna algum tipo de tipo de dado, significa que pode retornar qualquer valor de um conjunto pertencente a esse tipo de dado. No nosso exemplo, para o analisador, isso significa que fopen() pode retornar true . E, por exemplo, no caso desse código:

 $fp = fopen('some.file','r'); if($fp === false) {     return false; } fwrite($fp, "some string"); 

os analisadores reclamariam que fwrite aceita o primeiro recurso de parâmetro e passamos bool (porque o analisador vê que uma opção verdadeira é possível). Por esse motivo, todos os analisadores entendem um tipo de dados "artificial" como false e, em nosso exemplo, podemos escrever @return false|resource . O PHPStorm também entende essa descrição do tipo.

Formas de matriz

Muitas vezes, matrizes em PHP são usadas como um tipo de record - uma estrutura com uma lista clara de campos, onde cada campo tem seu próprio tipo. Obviamente, muitos programadores já usam classes para isso. Mas temos muito código legado no Badoo, e os arrays são usados ​​ativamente lá. E também acontece que os programadores são preguiçosos demais para criar uma classe separada para alguma estrutura única e, nesses locais, as matrizes também são frequentemente usadas.

O problema com essas matrizes é que não há uma descrição clara dessa estrutura (uma lista de campos e seus tipos) no código. Os programadores podem cometer erros ao trabalhar com essa estrutura: esqueça os campos obrigatórios ou adicione as teclas "esquerda", confundindo ainda mais o código.

Os analisadores permitem inserir uma descrição dessas estruturas:

 /** @param array{scheme:string,host:string,path:string} $parsed_url */ function showUrl(array $parsed_url) { … } 

Neste exemplo, descrevemos uma matriz com três campos de sequência: scheme, host e path . Se dentro da função nos voltarmos para outro campo, o analisador mostrará um erro.

Se você não descrever os tipos, os analisadores tentarão "adivinhar" a estrutura da matriz, mas, como mostra a prática, eles realmente não terão sucesso com nosso código. :)

Essa abordagem tem uma desvantagem. Suponha que você tenha uma estrutura usada ativamente no código. Você não pode declarar um pseudótipo em um local e usá-lo em qualquer lugar. Você precisará registrar o PHPDoc com a descrição da matriz em qualquer parte do código, o que é muito inconveniente, especialmente se houver muitos campos na matriz. Também será problemático editar esse tipo posteriormente (adicionar e remover campos).

Descrição dos tipos de chave de matriz

No PHP, chaves de matriz podem ser números inteiros e seqüências de caracteres. Às vezes, tipos podem ser importantes para análise estática (e também para programadores). Analisadores estáticos permitem que você descreva as chaves do array no PHPDoc:

 /** @var array<int, \User> $users */ $users = UserLoaders::loadUsers($user_ids); 

Neste exemplo, usando PHPDoc, adicionamos uma dica de que na matriz $users as chaves são inteiras e os valores são objetos da classe \User . Poderíamos descrever o tipo como \ Usuário []. Isso informa ao analisador que existem objetos na classe \User na matriz, mas não nos diz nada sobre o tipo de chaves.

O PHPStorm suporta esse formato para descrever matrizes a partir da versão 2018.3.

Seu espaço para nome no PHPDoc

O PHPStorm (e outros editores) e os analisadores estáticos podem entender o PHPDoc de maneira diferente. Por exemplo, os analisadores suportam este formato:

 /** @param array{scheme:string,host:string,path:string} $parsed_url */ function showUrl($parsed_url) { … } 

Mas o PHPStorm não o entende. Mas podemos escrever assim:

 /** * @param array $parsed_url * @phan-param array{scheme:string,host:string,path:string} $parsed_url * @psalm-param array{scheme:string,host:string,path:string} $parsed_url */ function showUrl($parsed_url) { … } 

Nesse caso, os analisadores e o PHPStorm serão satisfeitos. O PHPStorm usará o @param e os analisadores usarão suas próprias tags PHPDoc.

Recursos do PHP


Esse tipo de teste é melhor ilustrado pelo exemplo.

Todos sabemos o que a função explodir () pode retornar? Se você olhar para a documentação, parece que ela retorna uma matriz. Mas se você examiná-lo com mais cuidado, veremos que ele também pode retornar falso. De fato, ele pode retornar nulo e um erro se você passar os tipos errados, mas passar o valor errado com o tipo de dados errado já é um erro, portanto essa opção não é interessante para nós agora.

Formalmente, do ponto de vista do analisador, se uma função pode retornar falso ou uma matriz, provavelmente o código deve procurar falso. Mas a função explode () retornará false somente se o delimitador (primeiro parâmetro) for igual a uma string vazia. Geralmente, ele é explicitamente escrito no código, e os analisadores podem verificar se não está vazio, o que significa que, nesse local, a função explode () retorna com precisão uma matriz e não é necessária uma verificação falsa.

O PHP tem alguns recursos. Os analisadores gradualmente adicionam verificações apropriadas ou as aprimoram, e nós, programadores, não precisamos mais lembrar de todos esses recursos.

Passamos à descrição de analisadores específicos.

PHPStan


Desenvolvimento de um certo Ondřej Mirtes da República Tcheca. Desenvolvido ativamente desde o final de 2016.

Para começar a usar o PHPStan, você precisa:

  1. Instale (a maneira mais fácil de fazer isso é através do Composer).
  2. (opcional) Configure.
  3. No caso mais simples, basta executar:

vendor/bin/phpstan analyse ./src

(em vez de src pode haver uma lista de arquivos específicos que você deseja verificar).

O PHPStan lerá o código PHP dos arquivos transferidos. Se ele encontrar aulas desconhecidas, ele tentará carregá-las com carregamento automático e através da reflexão para entender sua interface. Você também pode transferir o caminho para o arquivo Bootstrap através do qual você configura o carregamento automático e anexar alguns arquivos adicionais para simplificar a análise do PHPStan.

Principais recursos:

  1. É possível analisar não toda a base de código, mas apenas uma parte - as classes desconhecidas PHPStan tentarão carregar o carregamento automático.
  2. Se, por algum motivo, algumas de suas aulas não estiverem no carregamento automático, o PHPStan não poderá encontrá-las e cometerá um erro.
  3. Se você estiver usando ativamente métodos mágicos por meio de __call / __get / __set , poderá escrever um plugin para o PHPStan. Plugins para Symfony, Doutrina, Laravel, Zombaria, etc. já existem.
  4. De fato, o PHPStan realiza o carregamento automático não apenas para classes desconhecidas, mas em geral para todos. Temos muitos códigos antigos escritos antes do aparecimento de classes anônimas, quando criamos uma classe em um arquivo e instanciamos instantaneamente e, possivelmente, até chamamos alguns métodos. O carregamento automático ( include ) desses arquivos leva a erros, porque o código não é executado em um ambiente normal.
  5. Configura no formato neon (nunca ouvi dizer que esse formato foi usado em outro lugar).
  6. Não há suporte para suas tags PHPDoc como @phpstan-var, @phpstan-return , etc.

Outra característica é que os erros têm texto, mas não há tipo. Ou seja, o texto do erro é retornado a você, por exemplo:

  • Method \SomeClass::getAge() should return int but returns int|null
  • Method \SomeOtherClass::getName() should return string but returns string|null

Neste exemplo, ambos os erros são basicamente a mesma coisa: o método deve retornar um tipo, mas, na realidade, ele retorna o outro. Mas os textos dos erros são diferentes, embora semelhantes. Portanto, se você deseja filtrar quaisquer erros no PHPStan, faça-o apenas através de expressões regulares.

Para comparação, em outros analisadores, os erros têm um tipo. Por exemplo, no Phan, esse erro é do tipo PhanPossiblyNullTypeReturn e você pode especificar na configuração que não precisa verificar esses erros. Além disso, tendo o tipo de erro, é possível, por exemplo, coletar facilmente estatísticas sobre erros.

Como não usamos Laravel, Symfony, Doctrine e soluções similares, e raramente usamos métodos mágicos em nosso código, o principal recurso do PHPStan acabou não sendo reclamado por nós. ; (Além disso, devido ao fato de o PHPStan incluir todas as classes sendo verificadas, às vezes sua análise simplesmente não funciona em nossa base de código.

No entanto, o PHPStan continua sendo útil para nós:

  • Se você precisar verificar vários arquivos, o PHPStan é visivelmente mais rápido que o Phan e um pouco (20-50%) mais rápido que o Salmo.
  • Os relatórios do PHPStan facilitam a localização de false-positive em outros analisadores. Geralmente, se houver algum fatal explícito no código, ele será mostrado por todos os analisadores (ou pelo menos dois dos três).


Atualização:
O autor do PHPStan Ondřej Mirtes também leu nosso artigo e nos disse que o PhpStan, como o Salmo, tem um site com uma “caixa de areia”: https://phpstan.org/ . Isso é muito conveniente para relatórios de erros: você reproduz o erro e fornece um link no GitHub.

Phan


Desenvolvido por Etsy. Comete primeiro de Rasmus Lerdorf.

Dos três em questão, Phan é o único analisador estático real (no sentido de que não executa nenhum dos seus arquivos - ele analisa toda a sua base de códigos e analisa o que você diz). Mesmo para analisar vários arquivos em nossa base de código, ele precisa de aproximadamente 6 GB de RAM, e esse processo leva de quatro a cinco minutos. Porém, uma análise completa de toda a base de código leva de seis a sete minutos. Para comparação, o Salmo analisa em algumas dezenas de minutos. E com o PHPStan não conseguimos obter uma análise completa de toda a base de código porque inclui classes de inclusão.

A experiência de Phan é dupla. Por um lado, é o analisador mais estável e de alta qualidade, encontra muitos problemas e há menos problemas quando é necessário analisar toda a base de códigos. Por outro lado, possui duas características desagradáveis.

Sob o capô, Phan usa a extensão php-ast. Aparentemente, esse é um dos motivos pelos quais a análise de toda a base de código é relativamente rápida. Mas o php-ast mostra a representação interna da árvore AST, como aparece no próprio PHP. E no próprio PHP, a árvore AST não contém informações sobre comentários localizados dentro da função. Ou seja, se você escreveu algo como:

 /** * @param int $type */ function doSomething($type) {   /** @var \My\Object $obj **/   $obj = MyFactory::createObjectByType($type);   … } 

então, dentro da árvore AST, há informações sobre o PHPDoc externo para a função doSomething() , mas não há informações de ajuda do PHPDoc dentro da função. E, consequentemente, Phan também não sabe nada sobre ela. Esta é a causa mais comum de false-positive em Phan. Existem algumas recomendações sobre como inserir dicas de ferramentas (por meio de strings ou assert-s), mas, infelizmente, elas são muito diferentes do que nossos programadores estão acostumados. Parcialmente, resolvemos esse problema escrevendo um plugin para o Phan. Mas os plugins serão discutidos abaixo.

A segunda característica desagradável é que Phan não analisa bem as propriedades dos objetos. Aqui está um exemplo:

 class A { /** * @var string|null */ private $a; public function __construct(string $a = null) {      $this->a = $a; } public function doSomething() {      if ($this->a && strpos($this->a, 'a') === 0) {          var_dump("test1");      } } } 

Neste exemplo, Phan lhe dirá que em strpos você pode passar nulo. Você pode saber mais sobre esse problema aqui: https://github.com/phan/phan/issues/204 .

Sumário Apesar de algumas dificuldades, Phan é um desenvolvimento muito interessante e útil. Além desses dois tipos de false-positive , ele quase não comete erros, ou comete erros, mas em algum código realmente complexo. Também gostamos que a configuração esteja em um arquivo PHP - isso dá alguma flexibilidade. Phan também sabe como trabalhar como servidor de idiomas, mas não usamos esse recurso, pois o PHPStorm é suficiente para nós.

Plugins


O Phan possui uma API de desenvolvimento de plugins bem desenvolvida. Você pode adicionar suas próprias verificações, melhorar a inferência de tipo para o seu código. Essa API possui documentação , mas é especialmente interessante o fato de já existirem plug-ins em funcionamento que podem ser usados ​​como exemplos.

Conseguimos escrever dois plugins. O primeiro foi destinado a uma verificação única. Queríamos avaliar como nosso código está pronto para o PHP 7.3 (em particular, para descobrir se ele tem constantes que não case-insensitive ). Estávamos quase certos de que não existiam essas constantes, mas tudo poderia acontecer em 12 anos - deveria ser verificado. E nós escrevemos um plugin para o Phan que juraria se o terceiro parâmetro fosse usado em define() .

O plugin é muito simples
 <?php declare(strict_types=1); use Phan\AST\ContextNode; use Phan\CodeBase; use Phan\Language\Context; use Phan\Language\Element\Func; use Phan\PluginV2; use Phan\PluginV2\AnalyzeFunctionCallCapability; use ast\Node; class DefineThirdParamTrue extends PluginV2 implements AnalyzeFunctionCallCapability { public function getAnalyzeFunctionCallClosures(CodeBase $code_base) : array {   $define_callback = function (       CodeBase $code_base,                  Context $context,                  Func $function,                  array $args    ) {      if (\count($args) < 3) {         return;      }       $this->emitIssue(       $code_base,      $context,      'PhanDefineCaseInsensitiv',      'Define with 3 arguments',      []      );    };    return [          'define' => $define_callback,    ]; } } return new DefineThirdParamTrue(); 



No Phan, plugins diferentes podem ser pendurados em diferentes eventos. Em particular, plug-ins com a interface AnalyzeFunctionCallCapability acionados quando uma chamada de função é analisada. Neste plug-in, fizemos com que, quando chamamos a função define() , nossa função anônima seja chamada, que verifica que define() não define() mais que dois argumentos. Então, começamos o Phan, localizamos todos os lugares onde define() foi chamado com três argumentos e garantimos que não tivéssemos case-insensitive- .

Usando o plugin, também resolvemos parcialmente o problema de false-positive quando o Phan não vê dicas do PHPDoc dentro do código.

Geralmente usamos métodos de fábrica que recebem uma constante como entrada e criam um objeto a partir dela. Frequentemente, o código se parece com isso:

 /** @var \Objects\Controllers\My $Object */ $Object = \Objects\Factory::create(\Objects\Config::MY_CONTROLLER); 

O Phan não entende essas dicas do PHPDoc, mas nesse código a classe de objeto pode ser obtida a partir do nome da constante passada ao método create() . O Phan permite que você escreva um plug-in que é acionado quando analisa o valor de retorno de uma função. E com esse plug-in, você pode informar ao analisador qual tipo a função retorna nesta chamada.

Um exemplo deste plugin é mais complexo. Mas há um bom exemplo no código Phan em vendor/phan/phan/src/Phan/Plugin/Internal/DependentReturnTypeOverridePlugin.php.

No geral, estamos muito satisfeitos com o analisador Phan. Os false-positive listados acima aprendemos parcialmente (em casos simples, com código simples) a filtrar. Depois disso, Phan se tornou um analisador quase de referência. No entanto, a necessidade de analisar imediatamente toda a base de código (tempo e muita memória) ainda complica o processo de sua implementação.

Salmo


O salmo é um desenvolvimento do Vimeo. Honestamente, eu nem sabia que o Vimeo usa PHP até ver o Salmo.

Este analisador é o mais novo dos três. Quando li as notícias de que o Vimeo lançou o Salmo, fiquei perplexo: "Por que investir no Salmo se você já possui Phan e PHPStan?" Mas o Salmo tem seus próprios recursos úteis.

O Salmo seguiu os passos do PHPStan: você também pode fornecer uma lista de arquivos para análise, que serão analisados ​​e conectar classes que não são encontradas com o carregamento automático. Ao mesmo tempo, ele apenas conecta classes que não foram encontradas e os arquivos que solicitamos para análise não serão incluídos (isso é diferente do PHPStan). A configuração é armazenada em um arquivo XML (para nós, é mais provável que seja um sinal de menos, mas não muito crítico).

O Salmo tem um site em área restrita onde você pode escrever e analisar o código PHP. Isso é muito conveniente para relatórios de erros: você reproduz o erro no site e fornece o link no GitHub. E, a propósito, o site descreve todos os tipos possíveis de erros. Para comparação: no PHPS, os erros não têm tipos e, no Phan, existem, mas não existe uma lista única que possa ser encontrada.

Também gostamos que, ao emitir erros, o Salmo mostra imediatamente as linhas de código onde foram encontradas. Isso simplifica bastante a leitura dos relatórios.

Mas talvez o recurso mais interessante do Psalm sejam suas tags PHPDoc personalizadas, que permitem melhorar a análise (especialmente a definição de tipos). Listamos os mais interessantes deles.

@ psalm-ignore-nullable-return


Ocorre que formalmente um método pode retornar null , mas o código já está organizado de tal maneira que isso nunca acontece. Nesse caso, é muito conveniente adicionar uma dica do PHPDoc ao método / função - e o Salmo considerará que null não null retornado.

Existe uma dica semelhante para false: @psalm-ignore-falsable-return .

Tipos de fechamento


Se você já se interessou por programação funcional, pode ter notado que muitas vezes uma função pode retornar outra função ou assumir alguma função como parâmetro. No PHP, esse estilo pode ser muito confuso para seus colegas, e uma das razões é que o PHP não possui padrões para documentar essas funções. Por exemplo:

 function my_filter(array $ar, \Closure $func) { … } 

Como um programador pode entender qual interface a função possui no segundo parâmetro? Quais parâmetros devem ser tomados? O que ela deveria retornar?

O Salmo suporta sintaxe para descrever funções no PHPDoc:

 /** * @param array $ar * @psalm-param Closure(int):bool $func */ function my_filter(array $ar, \Closure $func) { … } 

Com essa descrição, já está claro que você precisa passar uma função anônima para my_filter , que aceitará um int e return bool. E, é claro, o Salmo verificará se você tem exatamente essa função passada no seu código.

Enums


Suponha que você tenha uma função que aceita um parâmetro de string e você só pode passar certas strings para lá:

 function isYes(string $yes_or_no) : bool {     $yes_or_no = strtolower($yes_or_no)     switch($yes_or_no)  {           case 'yes':                 return true;          case 'no':                 return false;           default:                throw new \InvalidArgumentException(…);     } } 

O Salmo permite que você descreva o parâmetro desta função assim:

 /** @psalm-param 'Yes'|'No' $yes_or_no **/ function isYes(string $yes_or_no) : bool { … } 

Nesse caso, o Salmo tentará entender quais valores específicos são passados ​​para essa função e lançará erros se houver valores diferentes de Yes e No

Leia mais sobre enum aqui .

Aliases de tipo


Anteriormente, na descrição das array shapes da array shapes mencionei que, embora os analisadores permitam descrever a estrutura das matrizes, não é muito conveniente usá-la, pois a descrição da matriz deve ser copiada em locais diferentes. A solução correta, é claro, é usar classes em vez de matrizes. Mas, no caso de muitos anos de legado, isso nem sempre é possível.

, , , :

  • ;
  • closure;
  • union- (, );
  • enum.

, , PHPDoc , , . Psalm . alias PHPDoc alias . , : PHP-. . , Psalm.

Generics aka templates


. , :

 function identity($x) { return $x; } 

? ? ?

, , , — mixed , .

mixed — . , . , identity() / , : , . -. , :

 $i = 5; // int $y = identity($i); 

(int) , , $y ( int ).

? Psalm PHPDoc-:

 /** * @template T * @psalm-param T $x * @psalm-return T */ function identity($x) { $return $x; } 

templates Psalm , / .

Psalm templates:

vendor/vimeo/psalm/src/Psalm/Stubs/CoreGenericFunctions.php ;
vendor/vimeo/psalm/src/Psalm/Stubs/CoreGenericClasses.php .

Phan, : https://github.com/phan/phan/wiki/Generic-Types .

, Psalm . , «» . , Psalm , , Phan PHPStan. .

PHPStorm


: , . , , .

. Phan, language server. PHPStorm, , .

, , PHPStorm ( ), . — Php Inspections (EA Extended). — , , . , . , scopes - scopes.

, deep-assoc-completion . .

Badoo


?

, .

, . , , git diff / , , () . , .

, : - git diff . . , . . , , , , .

, , :



false-positive . , , Phan , , . , - Phan , , .

QA


:



— , , , . :

  • 100% ( , );
  • , code review;
  • , .

strict types . , strict types , :

  • , strict types , ;
  • , (, , );
  • , PHP (, union types , PHP);
  • strict types , .

:


, . .

-, , , - , .

-, , — , , PHPDoc. — .

-, . , - , PHPDoc. :)

, , . , .

Source: https://habr.com/ru/post/pt426605/


All Articles