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 2 times, most recently from 38d3044 to a3ce3eb Compare September 27, 2023 09:53
@dg dg force-pushed the master branch 2 times, most recently from 38cc5db to 0a8a17e Compare October 17, 2023 08:26
@dg dg force-pushed the master branch 5 times, most recently from 4b7b01e to 494d200 Compare November 1, 2023 20:33
@dg dg force-pushed the master branch 5 times, most recently from b843c77 to 77e9645 Compare November 26, 2023 23:02
@dg dg force-pushed the master branch 7 times, most recently from 7ae8df7 to 0d1508c Compare December 5, 2023 14:51
@dg dg force-pushed the master branch 3 times, most recently from 5b5c893 to 81e3e6d Compare January 17, 2024 16:51
@dg dg force-pushed the master branch 2 times, most recently from b080db7 to d668e02 Compare May 3, 2024 11:20
@dg dg force-pushed the master branch 4 times, most recently from 1e73622 to d6a9961 Compare May 16, 2024 20:06
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.

None yet

4 participants