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:
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:
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:
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:
class Stack { public function push($item): void; public function pop(); }
O docblock é usado para transmitir informações de tipo adicionais, por exemplo:
$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):
function getUsers(): array { ... }
No entanto, temos problemas com exemplos simples como este:
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:
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".
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) { } public function pop() { } }
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.
class Stack { public function push($item); 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