Abordagens para a criação de matrizes tipadas em PHP

PHP não permite que você defina matrizes digitadas. Qualquer array pode conter qualquer valor, o que torna difícil aplicar consistência em sua base de código. Aqui estão algumas soluções alternativas para ajudá-lo a criar coleções digitadas de objetos usando recursos PHP existentes.
Identificando o problema
Arrays PHP são uma estrutura de dados muito flexível. Você pode adicionar o que quiser a uma matriz, variando de valores escalares a objetos complexos:
$ arr = & # 91; " foobar ", 123, new DateTimeImmutable & # 40; & # 41; & # 93 ;;
Na prática, é raro você realmente querer uma matriz com uma gama tão variada de valores. É mais provável que seus arrays contenham várias instâncias do mesmo tipo de valor.
$ times = & # 91; novo DateTimeImmutable & # 40; & # 41;, novo DateTimeImmutable & # 40; & # 41;, novo DateTimeImmutable & # 40; & # 41; & # 93 ;;
Você pode então criar um método que atue em todos os valores da sua matriz:
classe final Cronômetro & # 123; protectedarray $ laps = & # 91; & # 93 ;; publicfunction recordLaps & # 40; array $ times & # 41 ;: void & # 123; foreach & # 40; $ timesas $ time & # 41; & # 123; $ this- > laps & # 91; & # 93; = $ time- & gt ; getTimestamp & # 40; & # 41 ;; & # 125; & # 125; & # 125;
Este código itera sobre as instâncias DateTimeInterface em $ times. A representação do carimbo de data / hora Unix do tempo (segundos medidos como um inteiro) é então armazenada em $ voltas.
O problema com esse código é que ele pressupõe que $ times é composto totalmente por instâncias de DateTimeInterface. Não há nada que garanta que esse seja o caso, então um chamador ainda pode passar uma matriz de valores mistos. Se um dos valores não implementasse DateTimeInterface, a chamada para getTimestamp () seria ilegal e ocorreria um erro de tempo de execução.
$ stopwatch = new Stopwatch & # 40; & # 41 ;; // OK $ stopwatch- > recordLaps & # 40; & # 91; new DateTimeImmutable & # 40; & # 41;, new DateTimeImmutable & # 40; & # 41; & # 93; & # 41 ;; // Crash! $ Stopwatch- > recordLaps & # 40; & # 91; new DateTimeImmutable & # 40; & # 41;, 123 // não é possível chamar `getTimestamp ()` em um número inteiro! & # 93; & # 41 ;;
Adicionando consistência de tipo com argumentos variáveis
O ideal é que o problema seja resolvido especificando-se que a matriz $ times pode conter apenas ocorrências de DateTimeInterface. Como o PHP não tem suporte para matrizes digitadas, devemos procurar recursos de linguagem alternativa.
A primeira opção é usar argumentos variadic e descompactar o array $ times antes de ser passado para recordLaps (). Os argumentos variáveis permitem que uma função aceite um número desconhecido de argumentos que são então disponibilizados como um único array. É importante para nosso caso de uso, você pode digitar argumentos variadic de sugestão normalmente. Cada argumento transmitido deve ser do tipo fornecido.
Argumentos variáveis são comumente usados para funções matemáticas. Aqui está um exemplo simples que soma todos os argumentos fornecidos:
função sumAll & # 40; int ... $ numbers & # 41; & # 123; returnarray_sum & # 40; $ numbers & # 41 ;; & # 125; echo sumAll & # 40; 1,2,3,4,5 & # 41 ;; // emite 15
sumAll () não é passado um array. Em vez disso, ele recebe vários argumentos que o PHP combina no array $ numbers. A dica de tipo int significa que cada valor deve ser um número inteiro. Isso funciona como uma garantia de que $ números consistirão apenas em inteiros. Agora podemos aplicar isso ao exemplo do cronômetro:
classe final Cronômetro & # 123; protectedarray $ laps = & # 91; & # 93 ;; publicfunction recordLaps & # 40; DateTimeInterface ... $ times & # 41 ;: void & # 123; foreach & # 40; $ timesas $ time & # 41; & # 123; $ this- > voltas & # 91; & # 93; = $ time- > getTimestamp & # 40; & # 41 ;; & # 125; & # 125; & # 125; $ cronômetro = novo Cronômetro & # 40; & # 41 ;; $ stopwatch- > recordLaps & # 40; new DateTimeImmutable & # 40; & # 41;, new DateTimeImmutable & # 40; & # 41; & # 41 ;;
Não é mais possível passar tipos não suportados para recordLaps (). As tentativas de fazer isso serão apresentadas muito antes, antes que a chamada getTimestamp () seja feita.
Se você já tem um array de tempos para passar para recordLaps (), você precisará descompactá-lo com o operador splat (...) quando você chama o método. Tentar transmiti-lo diretamente falhará – ele seria tratado como um dos tempos variáveis, que devem ser um int e não uma matriz.
$ times = & # 91; novo DateTimeImmutable & # 40; & # 41;, novo DateTimeImmutable & # 40; & # 41; & # 93 ;; $ stopwatch- > recordLaps & # 40; ... $ times & # 41 ;;
Limitações de argumentos variados
Argumentos variáveis podem ser uma grande ajuda quando você precisa passar um array de itens para uma função. No entanto, existem algumas restrições sobre como eles podem ser usados.
A limitação mais significativa é que você só pode usar um conjunto de argumentos variáveis por função. Isso significa que cada função pode aceitar apenas um “ digitado ” variedade. Além disso, o argumento variável deve ser definido por último, depois de quaisquer argumentos regulares.
função variadic & # 40; string $ something, DateTimeInterface ... $ times & # 41 ;;
Por natureza, argumentos variadic só podem ser usados com funções. Isso significa que eles não podem ajudá-lo quando você precisa armazenar uma matriz como uma propriedade ou retorná-la de uma função. Podemos ver isso no código do cronômetro – a classe Stopwatch tem uma matriz de voltas que se destina a armazenar apenas carimbos de data / hora inteiros. No momento, não temos como garantir que este seja o caso.
Classes de coleção
Nessas circunstâncias, uma abordagem diferente deve ser selecionada. Uma maneira de criar algo próximo a uma “ matriz digitada ” em userland, o PHP é escrever uma classe de coleção dedicada:
final class User & # 123; string protegida $ Email; publicfunction getEmail & # 40; & # 41 ;: string & # 123; return $ this- > Email; & # 125; & # 125; classe final UserCollection implementa IteratorAggregate & # 123; privatearray $ Users; publicfunction __construct & # 40; User ... $ Users & # 41; & # 123; $ this- > Users = $ Users; & # 125; publicfunction getIterator & # 40; & # 41 ;: ArrayIterator & # 123; returnnew ArrayIterator & # 40; $ this- > Users & # 41 ;; & # 125; & # 125;
A classe UserCollection agora pode ser usada em qualquer lugar que você normalmente esperaria uma matriz de instâncias de User. UserCollection usa argumentos variadic para aceitar uma série de instâncias de User em seu construtor. Embora a propriedade $ Users tenha que ser sugerida pelo tipo como a matriz genérica, é garantido que ela consista inteiramente de instâncias de usuário, pois é gravada apenas no construtor.
Pode parecer tentador fornecer um método get (): array que expõe todos os itens da coleção. Isso deve ser evitado, pois nos traz de volta ao problema da dica de tipo de array vago. Em vez disso, a coleção é iterável para que os consumidores possam usá-la em um loop foreach. Dessa forma, conseguimos criar um “ array ” capaz de indicar o tipo; que nosso código pode assumir com segurança contém apenas usuários.
função sendMailToUsers & # 40; UserCollection $ Users & # 41 ;: void & # 123; foreach & # 40; $ Usersas $ User & # 41; & # 123; mail & # 40; $ user- > getEmail & # 40; & # 41;, " Email de teste & quot ;, " Hello World! " & # 41 ;; & # 125; & # 125; $ users = new UserCollection & # 40; new User & # 40; & # 41;, new User & # 40; & # 41; & # 41 ;; sendMailToUsers & # 40; $ users & # 41 ;;
Fazendo coleções mais parecidas com matrizes
Classes de coleção resolvem o problema de dicas de tipo, mas significam que você perde algumas das funcionalidades úteis dos arrays. Funções PHP integradas, como count () e isset (), não funcionarão com sua classe de coleção personalizada.
O suporte para essas funções pode ser adicionado implementando interfaces integradas adicionais. Se você implementar Countable, sua classe poderá ser usada com count ():
classe final UserCollection implementa Countable, IteratorAggregate & # 123; privatearray $ Users; publicfunction __construct & # 40; User ... $ Users & # 41; & # 123; $ this- > Users = $ Users; & # 125; publicfunctioncount & # 40; & # 41 ;: int & # 123; returncount & # 40; $ this- > Users & # 41 ;; & # 125; publicfunction getIterator & # 40; & # 41 ;: ArrayIterator & # 123; returnnew ArrayIterator & # 40; $ this- > Users & # 41 ;; & # 125; & # 125; $ users = new UserCollection & # 40; new User & # 40; & # 41;, new User & # 40; & # 41; & # 41 ;; echocount & # 40; $ users & # 41 ;; // 2
A implementação de ArrayAccess permite acessar itens em sua coleção usando a sintaxe de array. Ele também ativa as funções isset () e unset (). Você precisa implementar quatro métodos para que o PHP possa interagir com seus itens.
classe final UserCollection implementa ArrayAccess, IteratorAggregate & # 123; privatearray $ Users; publicfunction __construct & # 40; User ... $ Users & # 41; & # 123; $ this- > Users = $ Users; & # 125; publicfunction offsetExists & # 40; mixed $ offset & # 41 ;: bool & # 123; returnisset & # 40; $ this- > Users & # 91; $ offset & # 93; & # 41 ;; & # 125; publicfunction offsetGet & # 40; mixed $ offset & # 41 ;: User & # 123; return $ this- > Users & # 91; $ offset & # 93 ;; & # 125; publicfunction offsetSet & # 40; mixed $ offset, mixed $ value & # 41 ;: void & # 123; if & # 40; $ value instanceof User & # 41; & # 123; $ this- > Users & # 91; $ offset & # 93; = $ value; & # 125; elsethrownew \ TypeError & # 40; " Não é um usuário! " & # 41 ;; & # 125; publicfunction offsetUnset & # 40; mixed $ offset & # 41 ;: void & # 123; unset & # 40; $ this- > Users & # 91; $ offset & # 93; & # 41 ;; & # 125; publicfunction getIterator & # 40; & # 41 ;: ArrayIterator & # 123; returnnew ArrayIterator & # 40; $ this- > Users & # 41 ;; & # 125; & # 125; $ users = new UserCollection & # 40; new User & # 40; " example@example. com" & # 41;, new User & # 40; " hello@world. com" & # 41; & # 41 ;; echo $ users & # 91; 1 & # 93; - > getEmail & # 40; & # 41 ;; // hello@world. comvar_dump( isset & # 40; $ users & # 91; 2 & # 93; & # 41; & # 41 ;; // falso
Agora você tem uma classe que pode conter apenas instâncias de User e que também se parece com um array. Um ponto a ser observado sobre ArrayAccess é a implementação offsetSet – como $ value deve ser misturado, isso pode permitir que valores incompatíveis sejam adicionados à sua coleção. Verificamos explicitamente o tipo do valor $ passado para evitar isso.
Conclusão
Os lançamentos recentes do PHP evoluíram a linguagem para uma digitação mais forte e maior consistência. No entanto, isso ainda não se estende aos elementos da matriz. A sugestão de tipo contra array costuma ser muito relaxada, mas você pode contornar as limitações criando suas próprias classes de coleção.
Quando combinado com argumentos variáveis, o padrão de coleção é uma maneira viável de impor os tipos de valores agregados em seu código. Você pode digitar dicas para suas coleções e iterar sobre elas, sabendo que apenas um tipo de valor estará presente.
Nenhum comentário