apk / fitter
Functional types and iterators inspired by Rust.
Requires
- php: >=7
Requires (Dev)
- phpunit/phpunit: ^6
This package is auto-updated.
Last update: 2024-05-19 09:44:03 UTC
README
A collection of classes and traits to easily develop in a functional way, inspired by Rust.
Installation
Composer
Add this to your composer.json:
{
"require": {
"apk/fitter": "^0.10"
}
}
Examples
Process a CSV file
Read a CSV file, assign meaningful names to columns, filter out invalid email addresses, using the standard PHP SplFileObject to show integration with other iterators:
$csvIterator = new \SplFileObject('emails.csv', 'r');
$csvIterator->setFlags(\SplFileObject::READ_CSV);
$csvIterator->setCsvControl(',', '"');
$processIterator = new Iterator($csvIterator);
$result = $processIterator
->map(function($el) {
return [
'email' => $el[0],
'name' => $el[1],
'surname' => $el[2],
'status' => $el[3]
];
})->filter(function($el) {
return (bool)filter_var($el['email'], FILTER_VALIDATE_EMAIL);
})->toArray()
;
This will use the first row in the file as column names (the "true" passed as second parameter), comma as a separator (third parameter) and double-quotes as enclosure (fourth parameter).
Process a PDO resultset
Get a list of users, count how many of them have each status.
This is just an example, a much better way to do it is using GROUP BY in SQL.
$pdo = new \PDO('sqlite:///tmp/db.sqlite');
$stmt = $pdo->query('select * from users');
$processIterator = new Iterator($stmt);
$result = $processIterator
->mapKey(function($el) {
return [ $el['status'], $el ];
})->reduce(function($el, $prev) {
if (is_null($prev)) { $prev = 0; }
return $prev + 1;
})->toArray();
You can stream a PDO resultset into an ArrayIterator after filtering them:
$pdo = new \PDO('sqlite:///tmp/db.sqlite');
$stmt = $pdo->query('select * from users');
$stmt->setFetchMode(\PDO::FETCH_ASSOC);
Iterator::from($stmt)
->filter(function($el) {
return $el['status'] == ENABLED;
})
->collect()
;
Naturally, all the processors can be used between the from() and the collect(). In this case, only enabled users will be written.
Using manual composition
If you don't like the "fluent" interface and prefer manual composition, you can do it, naturally. This (first exampple):
$csvIterator = new \SplFileObject('emails.csv', 'r');
$csvIterator->setFlags(\SplFileObject::READ_CSV);
$csvIterator->setCsvControl(',', '"');
$processIterator = new Iterator($csvIterator);
$result = $processIterator
->map(function($el) {
return [
'email' => $el[0],
'name' => $el[1],
'surname' => $el[2],
'status' => $el[3]
];
})->filter(function($el) {
return (bool)filter_var($el['email'], FILTER_VALIDATE_EMAIL);
})->toArray()
can be re-written like this:
$csvIterator = new \SplFileObject('emails.csv', 'r');
$csvIterator->setFlags(\SplFileObject::READ_CSV);
$csvIterator->setCsvControl(',', '"');
$result = new Filter(
new Map(
new Iterator($csvIterator),
function($el) {
return [
'email' => $el[0],
'name' => $el[1],
'surname' => $el[2],
'status' => $el[3]
];
}
),
function($el) {
return (bool)filter_var($el['email'], FILTER_VALIDATE_EMAIL);
}
)->toArray();
General description
Iterator methods can be divided in "adaptors" and "consumers". Iterators can be created by Generators and produce output by means of Consumers.
Adaptors are:
chain Returns a ChainIterator, which will return all the elements of the first iterator passed, one by one, then all from the second, etc.
filter Returns a Filter Iterator, which in turn returns only values from the original iterator that satisfy a certain condition defined by the given callback.
filterMap Returns a FilterMap Iterator, which in turn walks through the original values applying the given callback to each of them. If the function returns an Option::Some value, the calculated value will pass through. if it returns None, the value will not be in the result.
map Returns a Map Iterator, which in turn walks through the original values applying the given callback to each of them
mapKey Returns a MapKey Iterator, which will map every value in the original iterator to a tuple [key, value], suitable to be porocessed by reduce
skip Returns a Skip Iterator, which will skip n values from the original iterator when processing it.
take Returns a Take Iterator, which will take only n values from the original iterator when processing it.
zip Returns a Zip Iterator, which allows to iterate over a list of iterators, returning an array of each iterator's elements.
Consumers are:
min Takes a sort-compare function and returns the minimum value in the iterator
max Takes a sort-compare function and returns the maximum value in the iterator
avg Takes a function to convert iterator values to numbers and returns the average
find Takes a function to filter the values in the iterator and returns the first to match it
fold Recursively apply the given function to each value of the iterator and to the previous result and returns a single value
reduce Recursively apply the function to an indexed iterator to reduce it grouping by index
run Special consumer that will just go through all elements in the iterator. Useful with consumer objects and walk()
toArray Generates a PHP array from the iterator by going through all the processors in turn. WARNING: mapKey() generates overlapping keys. Transforming a mapKey() result into an array will keep only the latest value for each key.
collect Generates an ArrayIterator or fills the provided ArrayAccess-able object) from the iterator by going through all the processors in turn. WARNING: mapKey() generates overlapping keys. Transforming a mapKey() result into an ArrayIterator or an ArrayObject will keep only the latest value for each key.
toCollector Streams the iterator to a collector.
Generators are:
Range Generates a range of numbers.
StringWords Reads and processes a string word by word.
Consumers are:
- ArrayIterator Not just a collector, but also a wrapper around the PHP's \ArrayIterator, providing all this library's functions.
Adaptors
Adaptors wrap (or decorate) the iterator to return a new iterator that allows filtering/changing the data during processing.
chain
TODO: Chain description
filter
TODO: Filter description
filterMap
TODO: FilterMap description
map
TODO: Walk description
mapKey
TODO: map description
skip
TODO: skip description
take
TODO: take description
zip
TODO: zip description
Consumers
Consumers, as the name imply, "consume" the iterator and return an aggregated value.
min
TODO: Min description
max
TODO: Max description
avg
TODO: Avg description
find
TODO: Find description
fold
TODO: Fold description
reduce
TODO: Reduce description
run
TODO: run description
toArray
TODO: toArray description
collect
TODO: collect description
toCollector
TODO: toCollector description
Generators
Generators, as the name imply, generate an iterator that will iterate over a set of data.
Range
TODO: Range description
StringWords
TODO: StringWords description
Consumers
Consumers are special iterators consumers that can write an iterator processing result to some kind of output (files, streams, databases, etc.)
ArrayIterator
TODO: ArrayObject description
How to help
This is (still) a work-in-progress. If you wish to help, you can do it in several ways:
Write documentation. I am too lazy to do it. :D It shouldn't be hard, just look at the unit tests and translate them to english.
Write more tests. There are probably a million edge cases I didn't consider.
Suggest useful processors, consumers and writers. I cannot guarantee they will be implemented, but I will consider them.
Help with debugging: test it, use it, see where it breaks, possibly fix it and send a Pull Request.
Write more components and send a Pull Request, but, please, get in touch before. ;) It could be better to write a library using these components.
The code style is mostly PSR-1 and PSR-2, with some exceptions ("MUST", "SHOULD" as per PSR definition):
- MUST use tabs instead of spaces
- MUST use braces for loops and conditionals (even if one-liner)
- "static" SHOULD come before visibility declarations (public, protected, private)
- probably something else that doesn't come to my mind right now. :D
I can provide PHPStorm's configuration for automatic reformatting if you need it.
TODO
- [ ] toJson, possibly "streamed"
- [ ] Improve documentation
Copyright
(c) Copyright 2015-2017 Alessandro Pellizzari alex@amiran.it
Distributed under the BSD license.
For the full copyright and license informations, see the LICENSE file distributed with this source code.