PHP
Generadores
Buscar..
¿Por qué usar un generador?
Los generadores son útiles cuando necesitas generar una colección grande para iterar más tarde. Son una alternativa más simple a la creación de una clase que implementa un iterador , que a menudo es una exageración.
Por ejemplo, considere la siguiente función.
function randomNumbers(int $length)
{
$array = [];
for ($i = 0; $i < $length; $i++) {
$array[] = mt_rand(1, 10);
}
return $array;
}
Todo lo que hace esta función es generar una matriz que está llena de números aleatorios. Para usarlo, podríamos hacer randomNumbers(10)
, lo que nos dará una matriz de 10 números aleatorios. ¿Y si queremos generar un millón de números aleatorios? randomNumbers(1000000)
lo hará por nosotros, pero a un costo de memoria. Un millón de enteros almacenados en una matriz utiliza aproximadamente 33 megabytes de memoria.
$startMemory = memory_get_usage();
$randomNumbers = randomNumbers(1000000);
echo memory_get_usage() - $startMemory, ' bytes';
Esto se debe a que un millón de números aleatorios completos se generan y se devuelven a la vez, en lugar de uno a la vez. Los generadores son una manera fácil de resolver este problema.
Reescribiendo números aleatorios () usando un generador
Nuestra función randomNumbers()
puede reescribirse para usar un generador.
<?php
function randomNumbers(int $length)
{
for ($i = 0; $i < $length; $i++) {
// yield tells the PHP interpreter that this value
// should be the one used in the current iteration.
yield mt_rand(1, 10);
}
}
foreach (randomNumbers(10) as $number) {
echo "$number\n";
}
Usando un generador, no tenemos que construir una lista completa de números aleatorios para regresar de la función, lo que lleva a que se use mucho menos memoria.
Leyendo un archivo grande con un generador.
Un caso de uso común para los generadores es leer un archivo del disco e iterar sobre su contenido. A continuación se muestra una clase que le permite iterar sobre un archivo CSV. El uso de memoria para este script es muy predecible y no fluctuará dependiendo del tamaño del archivo CSV.
<?php
class CsvReader
{
protected $file;
public function __construct($filePath) {
$this->file = fopen($filePath, 'r');
}
public function rows()
{
while (!feof($this->file)) {
$row = fgetcsv($this->file, 4096);
yield $row;
}
return;
}
}
$csv = new CsvReader('/path/to/huge/csv/file.csv');
foreach ($csv->rows() as $row) {
// Do something with the CSV row.
}
La palabra clave de rendimiento
Una declaración de yield
es similar a una declaración de retorno, excepto que en lugar de detener la ejecución de la función y devolverla, el rendimiento devuelve un objeto Generador y detiene la ejecución de la función del generador.
Aquí hay un ejemplo de la función de rango, escrita como un generador:
function gen_one_to_three() {
for ($i = 1; $i <= 3; $i++) {
// Note that $i is preserved between yields.
yield $i;
}
}
Puede ver que esta función devuelve un objeto Generador al inspeccionar la salida de var_dump
:
var_dump(gen_one_to_three())
# Outputs:
class Generator (0) {
}
Valores de rendimiento
El objeto Generador se puede iterar sobre una matriz.
foreach (gen_one_to_three() as $value) {
echo "$value\n";
}
El ejemplo anterior dará como resultado:
1
2
3
Rendimiento de valores con claves
Además de generar valores, también puede generar pares clave / valor.
function gen_one_to_three() {
$keys = ["first", "second", "third"];
for ($i = 1; $i <= 3; $i++) {
// Note that $i is preserved between yields.
yield $keys[$i - 1] => $i;
}
}
foreach (gen_one_to_three() as $key => $value) {
echo "$key: $value\n";
}
El ejemplo anterior dará como resultado:
first: 1
second: 2
third: 3
Uso de la función send () para pasar valores a un generador
Los generadores están codificados rápidamente y, en muchos casos, son una alternativa delgada a las implementaciones de iteradores pesados. Con la implementación rápida viene una pequeña falta de control cuando un generador debe dejar de generar o si debe generar algo más. Sin embargo, esto se puede lograr con el uso de la función send()
, lo que permite a la función de solicitud enviar parámetros al generador después de cada ciclo.
//Imagining accessing a large amount of data from a server, here is the generator for this:
function generateDataFromServerDemo()
{
$indexCurrentRun = 0; //In this example in place of data from the server, I just send feedback everytime a loop ran through.
$timeout = false;
while (!$timeout)
{
$timeout = yield $indexCurrentRun; // Values are passed to caller. The next time the generator is called, it will start at this statement. If send() is used, $timeout will take this value.
$indexCurrentRun++;
}
yield 'X of bytes are missing. </br>';
}
// Start using the generator
$generatorDataFromServer = generateDataFromServerDemo ();
foreach($generatorDataFromServer as $numberOfRuns)
{
if ($numberOfRuns < 10)
{
echo $numberOfRuns . "</br>";
}
else
{
$generatorDataFromServer->send(true); //sending data to the generator
echo $generatorDataFromServer->current(); //accessing the latest element (hinting how many bytes are still missing.
}
}
Resultando en esta salida: