apk/fitter

Functional types and iterators inspired by Rust.

0.10.2 2017-07-04 22:00 UTC

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.