Genéricos PHP hoje (bem, quase)

Se você perguntar aos desenvolvedores de PHP que tipo de oportunidade eles desejam ver no PHP, a maioria chamará genéricos.


Suporte genérico no nível do idioma seria a melhor solução. Mas, percebê-los é difícil . Esperamos que um dia o suporte nativo se torne parte do idioma, mas provavelmente levará vários anos para esperar.


Este artigo mostrará como, usando as ferramentas existentes, em alguns casos com modificações mínimas, podemos obter o poder dos genéricos no PHP agora.


De um tradutor: uso deliberadamente papel vegetal dos "genéricos" ingleses, porque Nunca ouvi na comunicação que alguém a chamasse de "programação generalizada".

Conteúdo:



O que são genéricos


Esta seção cobre uma breve introdução aos genéricos .


Lendo links:


  • RFC para adicionar genéricos PHP
  • Suporte genérico em Phan
  • Modelos e genéricos de salmo

Exemplo mais simples


Como atualmente não é possível definir genéricos no nível do idioma, teremos que usar outra grande oportunidade - defini-los nos blocos de encaixe.


Já estamos usando esta opção em muitos projetos. Veja este exemplo:


/** * @param string[] $names * @return User[] */ function createUsers(iterable $names): array { ... } 

No código acima, fazemos o que é possível no nível do idioma. Definimos o parâmetro $names como algo que poderia ser listado. Também indicamos que a função retornará uma matriz. O PHP lançará um TypeError se os tipos de parâmetro e o valor de retorno não corresponderem.


Docblock melhora a compreensão do código. $names devem ser cadeias de caracteres e a função deve retornar uma matriz de objetos User . O próprio PHP não faz essas verificações. Porém, IDEs como o PhpStorm entendem essa notação e alertam o desenvolvedor que o contrato adicional não foi respeitado. Além disso, ferramentas de análise estática, como Psalm, PHPStan e Phan, podem validar a correção dos dados transferidos para e da função.


Genéricos para determinar chaves e valores de tipos enumerados


Acima está o exemplo mais simples de um genérico. Métodos mais complexos incluem a capacidade de especificar o tipo de suas chaves, juntamente com o tipo de valores. Abaixo está uma maneira de descrever isso:


 /** * @return array<string, User> */ function getUsers(): array { ... } 

Diz aqui que a matriz retornada por getUsers possui chaves e valores de string do tipo User .


Analisadores estáticos, como Psalm, PHPStan e Phan, entendem essa anotação e a levam em consideração na verificação.


Considere o seguinte código:


 /** * @return array<string, User> */ function getUsers(): array { ... } function showAge(int $age): void { ... } foreach(getUsers() as $name => $user) { showAge($name); } 

Os analisadores estáticos showAge um aviso na chamada showAge com um erro como este: O Argument 1 of showAge expects int, string provided .


Infelizmente, no momento da redação, o PhpStorm não sabe como.


Genéricos mais sofisticados


Continuamos a nos aprofundar no tópico dos genéricos. Considere um objeto que é uma pilha :


 class Stack { public function push($item): void { ... } public function pop() { ... } } 

A pilha pode aceitar qualquer tipo de objeto. Mas e se quisermos restringir a pilha apenas aos objetos do tipo User ?


Salmo e Phan suportam as seguintes anotações:


 /** * @template T */ class Stack { /** * @param T $item */ public function push($item): void; /** * @return T */ public function pop(); } 

O docblock é usado para transmitir informações de tipo adicionais, por exemplo:


 /** @var Stack<User> $userStack */ $stack = new Stack(); Means that $userStack must only contain Users. 

Salmo, ao analisar o seguinte código:


 $userStack->push(new User()); $userStack->push("hello"); 

Irá reclamar da linha 2 com o erro Argument 1 of Stack::push expects User, string(hello) provided.


Atualmente, o PhpStorm não suporta esta anotação.


De fato, cobrimos apenas parte das informações sobre genéricos, mas no momento isso é suficiente.


Como implementar genéricos sem suporte ao idioma


Você deve concluir as seguintes etapas:


  • No nível da comunidade, defina padrões genéricos em blocos de encaixe (por exemplo, novo PSR ou volte ao PSR-5)
  • Adicione anotações do dockblock ao seu código
  • Use IDEs que entendam essas convenções para realizar análises estáticas em tempo real e encontrar inconsistências.
  • Use ferramentas de análise estática (como o Salmo) como uma das etapas do IC para detectar erros.
  • Defina um método para passar informações de tipo para bibliotecas de terceiros.

Padronização


No momento, a comunidade PHP adotou oficialmente este formato genérico (eles são suportados pela maioria das ferramentas e seu significado é claro para a maioria):


 /** * @return User[] */ function getUsers(): array { ... } 

No entanto, temos problemas com exemplos simples como este:


 /** * @return array<string, User> */ function getUsers(): array { ... } 

O Salmo entende e sabe qual o tipo da chave e os valores da matriz retornada.


No momento da redação deste artigo, o PhpStorm não entende isso. Usando esta entrada, sinto falta do poder da análise estática em tempo real oferecida pelo PhpStorm.


Considere o código abaixo. O PhpStorm não entende que $user é do tipo User e $name é do tipo string:


 foreach(getUsers() as $name => $user) { ... } 

Se eu escolhesse o Salmo como uma ferramenta de análise estática, poderia escrever o seguinte:


 /** * @return User[] * @psalm-return array<string, User> */ function getUsers(): array { ... } 

O salmo entende tudo isso.


PhpStorm sabe que a variável $user é do tipo User . Mas ele ainda não entende que a chave da matriz se refere a uma string. Phan e PHPStan não entendem as anotações específicas do salmo. O máximo que eles entendem neste código é o mesmo que no PhpStorm: o tipo de $user


Você poderia argumentar que o PhpStorm deveria apenas aceitar a array<keyType, valueType> acordos array<keyType, valueType> . Eu não concordo com você, porque Acredito que esse ditado de padrões é tarefa da língua e da comunidade, e as ferramentas devem apenas segui-los.


Suponho que o contrato descrito acima será recebido calorosamente pela maioria da comunidade PHP. Um interessado em genéricos. No entanto, as coisas ficam muito mais complicadas quando se trata de padrões. No momento, nem o PHPStan nem o PhpStorm suportam modelos. Ao contrário do Salmo e Phan. O objetivo deles é semelhante, mas se você se aprofundar, perceberá que as implementações são um pouco diferentes.


Cada uma das opções apresentadas é uma espécie de compromisso.


Simplificando, é necessário um acordo sobre o formato de registro genérico:


  • Eles melhoram a vida dos desenvolvedores. Os desenvolvedores podem adicionar genéricos ao seu código e se beneficiar dele.
  • Os desenvolvedores podem usar as ferramentas de que mais gostam e alternar entre elas (ferramentas) conforme necessário.
  • Os fabricantes de ferramentas podem criar essas mesmas ferramentas, entendendo os benefícios para a comunidade e não temendo que algo mude ou que sejam acusados ​​de uma "abordagem incorreta".

Suporte de ferramenta


O Salmo tem toda a funcionalidade necessária para verificar os genéricos. Phan também gosta disso.


Tenho certeza de que o PhpStorm apresentará genéricos assim que a comunidade criar um contrato de formato único.


Suporte de código de terceiros


A parte final do quebra-cabeça genérico é adicionar suporte para bibliotecas de terceiros.


Felizmente, assim que o padrão de definição genérica aparecer, a maioria das bibliotecas o implementará. No entanto, isso não acontecerá imediatamente. Algumas bibliotecas são usadas, mas não têm suporte ativo. Ao usar analisadores estáticos para validar tipos em genéricos, é importante que todas as funções que esses genéricos aceitem ou retornem sejam definidas.


O que acontece se o seu projeto depende de bibliotecas de terceiros que não têm suporte genérico?


Felizmente, esse problema já foi resolvido e as funções de stub são a solução. Salmos, Phan e PhpStorm suportam stubs.


Os stubs são arquivos comuns que contêm assinaturas de funções e métodos, mas não os implementam. Ao adicionar blocos de encaixe aos stubs, as ferramentas de análise estática obtêm as informações adicionais necessárias. Por exemplo, se você tiver uma classe de pilha sem dicas de tipo e genéricos como este.


 class Stack { public function push($item) { /* some implementation */ } public function pop() { /* some implementation */ } } 

Você pode criar um arquivo stub que possua métodos idênticos, mas com a adição de blocos de encaixe e sem a implementação de funções.


 /** * @template T */ class Stack { /** * @param T $item * @return void */ public function push($item); /** * @return T */ public function pop(); } 

Quando o analisador estático vê a classe de pilha, recebe informações de tipo do stub, não do código real.


A capacidade de simplesmente compartilhar o código de stubs (por exemplo, através do compositor) seria extremamente útil, porque permitiria compartilhar o trabalho realizado.


Passos adicionais


A comunidade precisa se afastar de acordos e estabelecer padrões.


Talvez a melhor opção seja um PSR genérico?


Ou talvez os criadores dos principais analisadores estáticos, PhpStorm, outros IDEs e qualquer pessoa envolvida no desenvolvimento de PHP (para controle) possam desenvolver um padrão que todos usariam.


Assim que o padrão aparecer, todos poderão ajudar com a adição de genéricos às bibliotecas e projetos existentes, criando solicitações de recebimento. E onde isso não for possível, os desenvolvedores podem escrever e compartilhar stubs.


Quando tudo estiver pronto, podemos usar ferramentas como o PhpStorm para verificar os genéricos em tempo real enquanto escrevemos o código. Podemos usar ferramentas de análise estática como parte do nosso IC como garantia de segurança.


Os genéricos também podem ser implementados em PHP (bem, quase).


Limitações


Existem várias limitações. PHP é uma linguagem dinâmica que permite fazer muitas coisas "mágicas", como essas . Se você usa muita mágica do PHP, pode acontecer que os analisadores estáticos não consigam extrair com precisão todos os tipos no sistema. Se algum tipo for desconhecido, as ferramentas não poderão usar os genéricos corretamente em todos os casos.


No entanto, a principal aplicação dessa análise é validar sua lógica de negócios. Se você escreve um código limpo, não deve usar muita mágica.


Por que você não adiciona genéricos à língua?


Essa seria a melhor opção. O PHP possui código-fonte aberto e ninguém incomoda você clonar os fontes e implementar genéricos!


E se eu não precisar de genéricos?


Apenas ignore todos os itens acima. Uma das principais vantagens do PHP é que ele é flexível na escolha do nível apropriado de complexidade da implementação, dependendo do que você cria. Com um código único, você não precisa pensar em coisas como digitar dicas. Mas em grandes projetos vale a pena usar essas oportunidades.


Obrigado a todos que leram para este lugar. Ficarei feliz em seus comentários no PM.

UPD : ghost404 nos comentários observou que o PHPStan da versão 0.12.x compreende anotações de salmos e suporta genéricos

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


All Articles