Enfoques para crear matrices con tipo en PHP

Logotipo de PHP

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.

Índice de contenidos
  1. Identificación del problema
  2. Agregar consistencia de tipo con argumentos variables
  3. Limitaciones de los argumentos variádicos
  4. Clases de colección
  5. Haga que las colecciones se parezcan más a matrices
  6. Conclusión

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

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir Change privacy settings