Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP equivalent to Python's yield operator

In Python (and others), you can incrementally process large volumes of data by using the 'yield' operator in a function. What would be the similar way to do so in PHP?

For example, lets say in Python, if I wanted to read a potentially very large file, I could work on each line one at a time like so (this example is contrived, as it is basically the same thing as 'for line in file_obj'):

def file_lines(fname):
    f = open(fname)
    for line in f:
        yield line
    f.close()

for line in file_lines('somefile'):
    #process the line

What I'm doing right now (in PHP) is I'm using a private instance variable to keep track of state, and acting accordingly each time the function is called, but it seems like there must be a better way.

like image 683
nilamo Avatar asked Jul 15 '09 19:07

nilamo


2 Answers

There is a rfc at https://wiki.php.net/rfc/generators adressing just that, which might be included in PHP 5.5.

In the mean time, check out this proof-of-concept of a poor mans "generator function" implemented in userland.

namespace Functional;

error_reporting(E_ALL|E_STRICT);

const BEFORE = 1;
const NEXT = 2;
const AFTER = 3;
const FORWARD = 4;
const YIELD = 5;

class Generator implements \Iterator {
    private $funcs;
    private $args;
    private $key;
    private $result;

    public function __construct(array $funcs, array $args) {
        $this->funcs = $funcs;
        $this->args = $args;
    }

    public function rewind() {
        $this->key = -1;
        $this->result = call_user_func_array($this->funcs[BEFORE], 
                                             $this->args);
        $this->next();
    }

    public function valid() {
        return $this->result[YIELD] !== false;
    }

    public function current() {
        return $this->result[YIELD];
    }

    public function key() {
        return $this->key;
    }

    public function next() {
        $this->result = call_user_func($this->funcs[NEXT], 
                                       $this->result[FORWARD]);
        if ($this->result[YIELD] === false) {
            call_user_func($this->funcs[AFTER], $this->result[FORWARD]);
        }
        ++$this->key;
    }
}

function generator($funcs, $args) {
    return new Generator($funcs, $args);
}

/**
 * A generator function that lazily yields each line in a file.
 */
function get_lines_from_file($file_name) {
    $funcs = array(
        BEFORE => function($file_name) { return array(FORWARD => fopen($file_name, 'r'));   },
        NEXT   => function($fh)        { return array(FORWARD => $fh, YIELD => fgets($fh)); },
        AFTER  => function($fh)        { fclose($fh);                                       },
    );
    return generator($funcs, array($file_name));
}

// Output content of this file with padded linenumbers.
foreach (get_lines_from_file(__FILE__) as $k => $v) {
    echo str_pad($k, 8), $v;
}
echo "\n";
like image 145
Rune Kaagaard Avatar answered Sep 28 '22 17:09

Rune Kaagaard


PHP has a direct equivalent called generators.

Old (pre php 5.5 answer):

Unfortunately, there isn't a language equivalent. The easiest way is to either to what you're already doing, or to create a object that uses instance variables to maintain state.

There is however a good option if you want to use the function in conjunction with the foreach-statement: SPL Iterators. They can be used to achieve something quite similar to python generators.

like image 22
Emil H Avatar answered Sep 28 '22 15:09

Emil H