PHP no le permite definir matrices escritas. Cualquier matriz puede contener cualquier valor, lo que dificulta la aplicación de la coherencia en la base del código. A continuación, se muestran algunas soluciones para crear colecciones de objetos con tipo utilizando funciones PHP existentes.
Identificación del problema
Las matrices PHP son una estructura de datos muy flexible. Puede agregar cualquier cosa que desee a una matriz, desde valores escalares hasta objetos complejos:
$arr = [ "foobar", 123, new DateTimeImmutable() ];
En la práctica, es raro que realmente desee una matriz con un rango de valores tan variado. Es más probable que sus matrices contengan varias instancias del mismo tipo de valor.
$times = [ new DateTimeImmutable(), new DateTimeImmutable(), new DateTimeImmutable() ];
Luego, podría crear un método que actúe sobre todos los valores dentro de su matriz:
final class Stopwatch { protected array $laps = []; public function recordLaps(array $times) : void { foreach ($times as $time) { $this -> laps[] = $time -> getTimestamp(); } } }
Este código se repite DateTimeInterface
instancias en $times
. La representación de la marca de tiempo de Unix del tiempo (segundos medidos como un número entero) se almacena en $laps
.
El problema con este código es que crea un archivo contratación ese $times
se compone enteramente de DateTimeInterface
instancias. No hay nada que garantice que este sea el caso, por lo que una persona que llama aún podría pasar una matriz de valores mixtos. Si alguno de los valores no se ha implementado DateTimeInterface
, la llamada a getTimestamp()
sería ilegal y se produciría un error en tiempo de ejecución.
$stopwatch = new Stopwatch(); // OK $stopwatch -> recordLaps([ new DateTimeImmutable(), new DateTimeImmutable() ]); // Crash! $stopwatch -> recordLaps([ new DateTimeImmutable(), 123 // can't call `getTimestamp()` on an integer! ]);
Agregar consistencia de tipo con argumentos variables
Idealmente, el problema debería resolverse especificando que el $times
la matriz solo puede contener DateTimeInterface
instancias. Dado que PHP no tiene soporte para matrices escritas, necesitamos buscar características de lenguaje alternativas en su lugar.
La primera opción es usar argumentos variados y descomprimir el archivo. $times
matriz antes de que se pase a recordLaps()
. Los argumentos variables permiten que una función acepte un número desconocido de argumentos que luego se ponen a disposición como una única matriz. Es importante destacar que para nuestro caso de uso, puede escribir argumentos variados como de costumbre. Por tanto, cada argumento que se pase debe ser del tipo especificado.
Los argumentos variádicos se utilizan comúnmente para funciones matemáticas. Aquí hay un ejemplo simple que suma cada argumento proporcionado:
function sumAll(int ...$numbers) { return array_sum($numbers); } echo sumAll(1, 2, 3, 4, 5); // emits 15
sumAll()
no se pasa una matriz. En cambio, recibe más argumentos que PHP combina en el archivo. $numbers
Vector. los int
typehint significa que cada valor debe ser un número entero. Esto sirve como garantía de que $numbers
constará únicamente de números enteros. Ahora podemos aplicarlo al ejemplo del cronómetro:
final class Stopwatch { protected array $laps = []; public function recordLaps(DateTimeInterface ...$times) : void { foreach ($times as $time) { $this -> laps[] = $time -> getTimestamp(); } } } $stopwatch = new Stopwatch(); $stopwatch -> recordLaps( new DateTimeImmutable(), new DateTimeImmutable() );
Ya no es posible pasar tipos no admitidos a recordLaps()
. Los intentos de hacerlo surgirán mucho antes, antes getTimestamp()
se intenta la llamada.
Si ya tiene un conjunto de horas para cambiar recordLaps()
, tendrás que descomprimirlo con el operador de splat (...
) cuando llamas al método. El intento de pasarlo directamente fracasará: se trataría como uno de los tiempos variados, que son necesarios para ser un int
y no un array
.
$times = [ new DateTimeImmutable(), new DateTimeImmutable() ]; $stopwatch -> recordLaps(...$times);
Limitaciones de los argumentos variádicos
Los argumentos variables pueden ser de gran ayuda cuando necesita pasar una matriz de elementos a una función. Sin embargo, existen algunas limitaciones sobre cómo se pueden utilizar.
La limitación más significativa es que solo puede usar un conjunto de argumentos variados por función. Esto significa que cada función solo puede aceptar una matriz "escrita". Además, el argumento variadic debe definirse en último lugar, después de cualquier argumento regular.
function variadic(string $something, DateTimeInterface ...$times);
Por naturaleza, los argumentos variádicos solo se pueden usar con funciones. Esto significa que no pueden ayudarte cuando los necesitas. tienda una matriz como una propiedad o devolverla de una función. Podemos ver esto en el código del cronómetro: el Stopwatch
la clase tiene un archivo laps
matriz que está destinada a almacenar solo marcas de tiempo enteras. Por el momento no es posible imponer que este sea el caso.
Clases de colección
En estas circunstancias, es necesario seleccionar un enfoque diferente. Una forma de crear algo como una "matriz escrita" en el área de usuario de PHP es escribir una clase de colección dedicada:
final class User { protected string $Email; public function getEmail() : string { return $this -> Email; } } final class UserCollection implements IteratorAggregate { private array $Users; public function __construct(User ...$Users) { $this -> Users = $Users; } public function getIterator() : ArrayIterator { return new ArrayIterator($this -> Users); } }
los UserCollection
La clase ahora se puede usar en cualquier lugar donde normalmente esperaría una matriz de User
instancias. UserCollection
utiliza argumentos variados para aceptar un rango de User
instancias en su constructor. Aunque el $Users
la propiedad debe escribirse como genérica array
, se garantiza que consta completamente de instancias de usuario, ya que está escrito solo en el constructor.
Puede parecer tentador proporcionar un archivo get() : array
método que expone todos los elementos de la colección. Esto debe evitarse, ya que nos devuelve a la vaga array
problema del tipo de punta. En cambio, la colección se hace iterable para que los consumidores puedan usarla en un archivo foreach
lazo. De esta manera, pudimos crear una "matriz" indicativa de un tipo que nuestro código puede asumir con seguridad que solo contiene usuarios.
function sendMailToUsers(UserCollection $Users) : void { foreach ($Users as $User) { mail($user -> getEmail(), "Test Email", "Hello World!"); } } $users = new UserCollection(new User(), new User()); sendMailToUsers($users);
Haga que las colecciones se parezcan más a matrices
Las clases de Colección resuelven el problema de las pistas tipográficas, pero significan que pierde algunos de los archivos servicial funcionalidad de matriz. Funciones PHP integradas como count()
es isset()
no funcionará con su clase de colección personalizada.
Se puede agregar soporte para estas funciones implementando interfaces integradas adicionales. Si implementa Countable
, tu clase se podrá utilizar con count()
:
final class UserCollection implements Countable, IteratorAggregate { private array $Users; public function __construct(User ...$Users) { $this -> Users = $Users; } public function count() : int { return count($this -> Users); } public function getIterator() : ArrayIterator { return new ArrayIterator($this -> Users); } } $users = new UserCollection(new User(), new User()); echo count($users); // 2
Implementación ArrayAccess
le permite acceder a los elementos de su colección utilizando la sintaxis de matriz. También permite isset()
es unset()
funciones. Tienes que implementar cuatro métodos para que PHP pueda interactuar con tus elementos.
final class UserCollection implements ArrayAccess, IteratorAggregate { private array $Users; public function __construct(User ...$Users) { $this -> Users = $Users; } public function offsetExists(mixed $offset) : bool { return isset($this -> Users[$offset]); } public function offsetGet(mixed $offset) : User { return $this -> Users[$offset]; } public function offsetSet(mixed $offset, mixed $value) : void { if ($value instanceof User) { $this -> Users[$offset] = $value; } else throw new TypeError("Not a user!"); } public function offsetUnset(mixed $offset) : void { unset($this -> Users[$offset]); } public function getIterator() : ArrayIterator { return new ArrayIterator($this -> Users); } } $users = new UserCollection( new User("[email protected]"), new User("[email protected]") ); echo $users[1] -> getEmail(); // [email protected] var_dump(isset($users[2])); // false
Ahora tienes una clase que solo puede contener User
instancias y que también se ve y se siente como una matriz. Un punto a tener en cuenta ArrayAccess
y el offsetSet
implementación - cómo $value
debe ser mixed
, esto podría permitirle agregar valores incompatibles a su colección. Verificamos explícitamente el tipo pasado $value
para evitar esto.
Conclusión
Las versiones recientes de PHP han evolucionado el lenguaje hacia una escritura más fuerte y una mayor consistencia. Sin embargo, esto todavía no se extiende a los elementos de la matriz. Una especie de insinuación en contra array
a menudo es demasiado relajado, pero puede sortear las limitaciones creando sus propias clases de colección.
Cuando se combina con argumentos variados, el modelo de colección es una forma viable de aplicar tipos de valores agregados en el código. Puede escribir una pista sobre sus colecciones y desplazarse por ellas sabiendo que solo habrá un tipo de valor.
Descubre más contenido