Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Nette\Utils\Future for lazy value evaluation #272

Open
wants to merge 25 commits into
base: master
Choose a base branch
from

Conversation

milo
Copy link
Member

@milo milo commented Nov 19, 2021

  • new feature
  • doc PR: will if accepted

The Future class is a helper for "building data structures" which I use about 4 years. It is handy for small tasks where ORM is too huge, or where two independent data sources are mixing up (database and database, HTTP API and database, local CSV and HTTP API...).

I used to call it "promise" but it is not a promise pattern. It is closer to "future evaluation strategy" but it is not the same. Maybe there exists the corrent name for this evaluation method but I didn't find it.

An example of usage:

# Fetch all books from database with theirs author & editor person ID.
$books = $db->query('
    SELECT
        title,
        author_id AS author,  -- this is an integer, ID of person
        editor_id AS editor   -- this is an integer, ID of person
    FROM
        book
')->fetchAll();

# Replace all authors & editors ID by corresponding entities.
$futurePersons = new Future(function (array $ids) use ($db) {
    return $db->query('SELECT id, first_name, last_name FROM person')->fetchAssoc('id');
});
$futurePersons->bindArraysKey('author', $books);
$futurePersons->bindArraysKey('editor', $books);
$futurePersons->resolve();

On resolve() call, the resolver (callback from constructor) is called with all required authors and editors ID. So there is no need to iterate over books, collects every ID, fetch them and iterate again to build the desired data structure.

The example with database is a little bit funny (even I'm using it in this way). Some ORM does this task probably better. But this Future class is pretty low level helper. If you compose data structures from heterogeneous sources, like files in filesystem, HTTP API or some other JSON sources, the resulting code is nice, short and clean.

Another use case is scalar to value object translation. For example, translate every e-mail string, to Email object:

$resolver = function (array $keys) {
    $result = [];
    foreach ($keys as $key) {
        $result[$key] = new Email($key);
    }
    return $result;
};

(new Future($resolver))
    ->bindArraysKey('from', $data)
    ->bindArraysKey('to', $data)
    ->resolve();

Or scalar to scalar translation (language translator).

Anyway, the workflow is following:

  1. write resolver
  2. bind variables for future evaluation
  3. resolve (translate)

and API looks like:

# Resolve 'key' and store it into $var
->bind('key', $var) 

# Get $var as a key, resolve it and store it back to $var
->bindVar($var) 

# Get an array of values as keys, and resolve them - like bindVar() for every array item
->bindArrayValues($array)

# The array keys are used as keys to be resolved and stored into array values - like bind('key', $var) for every key/value pair
->bindArrayKeys($array)

# Expects every item of $arrays is an array - do bind('key', $item) for every item
->bindArraysKey('key', $arrays)

 # dtto but expects every item is an object and resolved value is stored into propery
->bindObjectsProperty('key', $arrays)

These are common use cases I hit and examples can be found in attached test.

In general, there is a space for ->bindAssoc('a[]->foo->bar', $structure) but I rarely use it. It could be nice but if required, manual iteration through the $structure and ->bind...() calls work too.

One dark side - it is quite hard to goole future term so maybe different name should be used.

@MartinMystikJonas
Copy link

I like it. But it really should have better name. It basically just maps/binds values from data source into. Maybe something like LazyMapper? LazyBinder?

@milo
Copy link
Member Author

milo commented Nov 19, 2021

Yep, the hardest thing :)

@diegosardina
Copy link

Deferred? Delayed?

@dg
Copy link
Member

dg commented Nov 23, 2021

I think there's a missing $ids, or am I misunderstanding?

$futurePersons = new Future(function (array $ids) use ($db) {
    return $db->query('SELECT id, first_name, last_name FROM person')->fetchAssoc('id');
});

@dg dg force-pushed the master branch 12 times, most recently from 6733224 to 5de10a1 Compare June 18, 2024 21:22
@dg dg force-pushed the master branch 16 times, most recently from a846fab to 736c567 Compare August 7, 2024 16:18
@dg dg force-pushed the master branch 2 times, most recently from cd9170e to 2b48b24 Compare December 12, 2024 10:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants