PHP code example of dakujem / toru

1. Go to this page and download the library: Download dakujem/toru library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

dakujem / toru example snippets


_dash($collection)
    ->filter(predicate: $filterFunction)
    ->map(values: $mapperFunction)
    ->chain($moreElements)
    ->valuesOnly();

// no extra memory wasted on creating a filtered array
$filtered = Itera::filter(input: $hugeDataSet, predicate: $filterFunction);
foreach($filtered as $key => $value){ /* ... */ }

$addresses = [
    '[email protected]' => 'John Doe',
    '[email protected]' => 'Jane Doe',
];
$recipients = Itera::map(
    $addresses, 
    fn($recipientName, $recipientAddress) => new Recipient($recipientAddress, $recipientName),
);
Mailer::send($mail, $recipients);

use Dakujem\Toru\Itera;

// No memory wasted on creating a compound array. Especially true when the arrays are huge.
$all = Itera::chain($collection1, $collection2, [12,3,5], $otherCollection);

foreach ($all as $key => $element) {
    // do not repeat yourself
}

use Dakujem\Toru\Dash;

$mailingList = Dash::collect($mostActiveUsers)
    ->filter(
        predicate: fn(User $user): bool => $user->hasGivenMailingConsent()
    )
    ->adjust(
        values: fn(User $user) => $user->fullName(),
        keys: fn(User $user) => $user->emailAddress(),
    )
    ->limit(100);

foreach ($mailingList as $emailAddress => $recipientName) {
    $mail->addRecipient($emailAddress, $recipientName);
}

$files = _dash(
        new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir))
    )                                                                   // recursively iterate over a dir
    ->filter(fn(\SplFileInfo $fileInfo) => !$fileInfo->isDir())         // reject directories
    ->reindex(fn(\SplFileInfo $fileInfo) => $fileInfo->getPathname());  // index by full file path

use Dakujem\Toru\Itera;

$filtered = Itera::filter(input: $collection, predicate: $filterFunction);
$mapped = Itera::apply(input: $filtered, values: $mapperFunction);
$merged = Itera::chain($mapped, $moreElements);
$processed = Itera::valuesOnly(input: $merged);

use Dakujem\Toru\Dash;

$processed = Dash::collect($collection)
    ->filter(predicate: $filterFunction)
    ->apply(values: $mapperFunction)
    ->chain($moreElements)
    ->valuesOnly();

use Dakujem\Toru\Pipeline;
use Dakujem\Toru\IteraFn;

$processed = Pipeline::through(
    $collection,
    IteraFn::filter(predicate: $filterFunction),
    IteraFn::apply(values: $mapperFunction),
    IteraFn::chain($moreElements),
    IteraFn::valuesOnly(),
);

foreach ($processed as $value) {
    // The filtered and mapped values from $collection will appear here,
    // followed by the elements present in $moreElements.
}

use Dakujem\Toru\Dash;
use Dakujem\Toru\Itera;
use Dakujem\Toru\IteraFn;

Itera::chain(iterable ...$input): iterable

// `append` is only present in `Dash` and `IteraFn` classes as an alias to `chain`
Dash::append(iterable ...$more): Dash
IteraFn::append(iterable ...$more): callable

use Dakujem\Toru\Itera;

Itera::adjust(iterable $input, ?callable $values = null, ?callable $keys = null): iterable
Itera::apply(iterable $input, callable $values): iterable
Itera::map(iterable $input, callable $values): iterable
Itera::reindex(iterable $input, callable $keys): iterable
Itera::unfold(iterable $input, callable $mapper): iterable

fn(mixed $value, mixed $key): mixed

use Dakujem\Toru\Itera;

Itera::unfold(
    [1,2,3,4],
    fn($value, $key) => ['double value for key ' . $key => $value * 2],
)

// or "produce new index and key based on the original value"
Itera::unfold(
    ['one:1', 'two:2', 'three:3'],
    function(string $value) {
        [$name, $index] = split(':', $value); // there could be a fairly complex regex here
        return [$index => $name];
    },
)

use Dakujem\Toru\Itera;

Itera::reduce(iterable $input, callable $reducer, mixed $initial): mixed

fn(mixed $carry, mixed $value, mixed $key): iterable|mixed

use Dakujem\Toru\Dash;

// The value is returned directly, because it is not iterable:
Dash::collect([1,2,3])->reduce(fn() => 42); // 42

// The value `[42]` is iterable, thus a new `Dash` instance is returned:
Dash::collect([1,2,3])->reduce(fn() => [42])->count(); // 1

use Dakujem\Toru\Itera;

Itera::filter(iterable $input, callable $predicate): iterable

fn(mixed $value, mixed $key): bool

> new CallbackFilterIterator(Itera::toIterator($input), $predicate)
> 

use Dakujem\Toru\Itera;

Itera::search(iterable $input, callable $predicate, mixed $default = null): mixed
Itera::searchOrFail(iterable $input, callable $predicate): mixed
Itera::firstValue(iterable $input): mixed
Itera::firstKey(iterable $input): mixed
Itera::firstValueOrDefault(iterable $input, mixed $default = null): mixed
Itera::firstKeyOrDefault(iterable $input, mixed $default = null): mixed

fn(mixed $value, mixed $key): bool

use Dakujem\Toru\Itera;

Itera::limit(iterable $input, int $limit): iterable
Itera::omit(iterable $input, int $omit): iterable
Itera::slice(iterable $input, int $offset, int $limit): iterable

use Dakujem\Toru\Itera;

Itera::valuesOnly(iterable $input): iterable
Itera::keysOnly(iterable $input): iterable
Itera::flip(iterable $input): iterable

use Dakujem\Toru\Itera;

Itera::valuesOnly(['a' => 'Adam', 'b' => 'Betty']); // ['Adam', 'Betty']
Itera::keysOnly(['a' => 'Adam', 'b' => 'Betty']);   // ['a', 'b']
Itera::flip(['a' => 'Adam', 'b' => 'Betty']);       // ['Adam' => 'a', 'Betty' => 'b']

use Dakujem\Toru\Itera;

Itera::toArray(iterable $input): array
Itera::toArrayMerge(iterable $input): array
Itera::toArrayValues(iterable $input): array
Itera::toIterator(iterable $input): \Iterator
Itera::ensureTraversable(iterable $input): \Traversable

> Itera::toArray(Itera::chain([1,2], [3,4])); // --> [3,4] ❗
> 

use Dakujem\Toru\Itera;

Itera::tap(iterable $input, callable $effect): iterable
Itera::each(iterable $input, callable $effect): iterable  // alias for `tap`

fn(mixed $value, mixed $key): void

use Dakujem\Toru\Itera;

Itera::repeat(mixed $input): iterable
Itera::loop(iterable $input): iterable
Itera::replicate(iterable $input, int $times): iterable

use Dakujem\Toru\Itera;

Itera::produce(callable $producer): iterable

use Dakujem\Toru\Itera;

Itera::limit(Itera::produce(fn() => rand()), 1000); // a sequence of 1000 pseudorandom numbers
Itera::produce(fn() => 42); // an infinite sequence of the answer to life, the universe, and everything
Itera::produce(fn(int $i) => $i); // an infinite sequence of integers 0, 1, 2, 3, ...

use Dakujem\Toru\Itera;

Itera::unfold(
    Itera::produce(fn(int $i) => [ calculateKey($i) => calculateValue($i) ])
);

use Dakujem\Toru\Dash;
use Dakujem\Toru\Itera;

// Create a generator from an input collection.
$collection = Itera::apply(input: Itera::filter(input: $input, filter: $filter), values: $mapper);
// or using Dash
$collection = Dash::collect($input)->filter(filter: $filter)->apply(values: $mapper);


// No generator code has been executed so far.
// The evaluation of $filter and $mapper callables begins with the first iteration below.
foreach($collection as $key => $value) {
    // Only at this point the mapper and filter functions are executed,
    // once per element of the input collection.
    // The generator execution is then _paused_ until the next iteration.
}

$mapper = fn($value, $key) => /* ... */;
$predicate = fn($value, $key): bool => /* ... */;

$collection = array_filter(array_map($mapper, $array, array_keys($array)), $predicate, ARRAY_FILTER_USE_BOTH);

use Dakujem\Toru\Dash;

$mapper = fn($value, $key) => /* ... */;
$predicate = fn($value, $key): bool => /* ... */;

$collection = Dash::collect($array)->map($mapper)->filter($predicate);

$myActualReducer = fn($carry, $value, $key) => /**/;

// Transform the array into a form that ys($array), $array);

// Apply array_reduce
$result = array_reduce($transformedArray, function($carry, array $valueAndKey) use ($myActualReducer){ 
    [$value, $key] = $valueAndKey;
    return $myActualReducer($carry, $value, $key);
});

use Dakujem\Toru\Itera;

$myActualReducer = fn($carry, $value, $key) => /**/;

$result = Itera::reduce($array, $myActualReducer);

use Dakujem\Toru\Dash;

Dash::collect(['zero', 'one', 'two', 'three',])
    ->alter(function (iterable $collection): iterable {
        foreach ($collection as $k => $v) {
            yield $k * 2 => $v . ' suffix';
        }
    })
    ->alter(function (iterable $collection): iterable {
        foreach ($collection as $k => $v) {
            yield $k + 1 => 'prefix ' . $v;
        }
    })
    ->filter( /* ... */ )
    ->map( /* ... */ );

use Dakujem\Toru\Dash;
use Dakujem\Toru\Itera;

$keySum = Dash::collect($input)
    ->filter( /* ... */ )
    ->aggregate(function (iterable $collection): int {
        $keySum = 0;
        foreach ($collection as $k => $v) {
            $keySum += $k;
        }
        return $keySum;
    }); // no more fluent calls, integer is returned

$median = Dash::collect($input)
    ->filter( /* ... */ )
    ->aggregate(function (iterable $collection): int {
        return MyCalculus::computeMedian(Itera::toArray($collection));
    }); // the median value is returned

use Dakujem\Toru\Dash;
use Dakujem\Toru\Itera;

class MyItera extends Itera
{
    /**
     * A shorthand for mapping arrays to use instead of `array_map`
     * with keys provided for the mapper.
     */
    public static function mapToArray(iterable $input, callable $mapper): iterable
    {
        return static::toArray(
            static::apply(input: $input, values: $mapper),
        );
    }

    public static function appendBar(iterable $input): iterable
    {
        return static::chain($input, ['bar' => 'bar']);
    }
}

class MyDash extends Dash
{
    public function appendFoo(): self
    {
        return new static(
            Itera::chain($this->collection, ['foo' => 'foo'])
        );
    }

    public function appendBar(): self
    {
        return new static(
            MyItera::appendBar($this->collection)
        );
    }

    public function aggregateZero(): int
    {
        return 0; // ¯\_(ツ)_/¯ 
    }
}

// 0 (zero)
MyDash::collect([1, 2, 3])->appendFoo()->appendBar()->aggregateZero();

// [1, 2, 3, 'foo' => 'foo', 'bar' => 'bar']
MyDash::collect([1, 2, 3])->appendFoo()->appendBar()->toArray();

// [1, 2, 3, 'foo' => 'foo', 'bar' => 'bar'] (generator)
MyItera::appendBar([1, 2, 3]);

use Dakujem\Toru\Dash;

if (!function_exists('_dash')) {
    function _dash(iterable $input): Dash {
        return Dash::collect($input);
    }
}

_dash($collection)->filter( /* ... */ )->map( /* ... */ )->toArray();

use Dakujem\Toru\Itera;

Itera::toArray(
    Itera::chain([1, 2], [3, 4])
);

use Dakujem\Toru\Itera;

Itera::toArray(
    Itera::chain([
        0 => 1, 
        1 => 2,
    ], [
        0 => 3,
        1 => 4,
    ])
);

use Dakujem\Toru\Itera;

foreach(Itera::chain([1, 2], [3, 4]) as $key => $value){
    echo "$key: $value\n";
}

0:1
1:2
0:3
1:4

use Dakujem\Toru\Itera;

Itera::toArrayMerge(
    Itera::chain([
        0 => 1,
        1 => 2,
        'foo' => 'bar',
    ], [
        0 => 3,
        1 => 4,
    ])
);

[
     0 => 1,
     1 => 2,
     'foo' => 'bar',
     2 => 3,
     3 => 4,
]

$generatorFunction = function(): iterable {
    yield 1;
    yield 2;
    yield 'foo' => 'bar';
    yield 42;
};

$generatorObject = $generatorFunction();

foreach ($generatorObject as $key => $val) { /* ... */ }
// subsequent iteration will throw an exception
// Exception: Cannot rewind a generator that was already run
foreach ($generatorObject as $key => $val) { /* ... */ }

// Instead of iterating over the same generator object, the generator function
// is called multiple times. Each call creates a new generator object.
foreach ($generatorFunction() as $key => $val) { /* ... */ }
// A new generator object is created with every call to $generatorFunction().
foreach ($generatorFunction() as $key => $val) { /* ... */ } // ✅ no exception

use Dakujem\Toru\Dash;

// Not possible, the generator function is not iterable itself
$it = Dash::collect($generatorFunction);           // TypeError

// Not possible, the argument to `collect` must be iterable
$it = Dash::collect(fn() => $generatorFunction()); // TypeError

// The correct way is to wrap the generator returned by the call,
// but it has the same drawback as described above
$dash = Dash::collect($generatorFunction());       
foreach ($dash->filter($filterFn) as $val) { /* ... */ }
// Exception: Cannot rewind a generator that was already run
foreach ($dash->filter($otherFilterFn) as $val) { /* ... */ } // fails

use Dakujem\Toru\Dash;
use Dakujem\Toru\Regenerator;

$dash = new Regenerator(fn() => Dash::collect($generatorFunction()));
foreach ($dash->filter($filterFn) as $val) { /* ... */ }
foreach ($dash->filter($otherFilterFn) as $val) { /* ... */ } // works, hooray!

use Dakujem\Toru\Itera;

$filtered = Itera::filter($input, $predicate);
$mapped = Itera::apply($filtered, $mapper);

foreach($mapped as $k => $v) { /* ...*/ }

// Exception: Cannot rewind a generator that was already run
foreach($filtered as $k => $v) { /* ...*/ }

use Dakujem\Toru\Itera;

$filtered = fn() => Itera::filter($input, $predicate);
$mapped = fn() => Itera::apply($filtered(), $mapper);

foreach($mapped() as $k => $v) { /* ...*/ }

// this will work, the issue is mitigated by iterating over a new generator
foreach($filtered() as $k => $v) { /* ...*/ }

use Dakujem\Toru\Itera;
use Dakujem\Toru\Regenerator;

$filtered = new Regenerator(fn() => Itera::filter($input, $predicate));
$mapped = new Regenerator(fn() => Itera::apply($filtered(), $mapper));

foreach($mapped as $k => $v) { /* ...*/ }

// In this case, the regenerator handles function calls.
foreach($filtered as $k => $v) { /* ...*/ }

use Dakujem\Toru\IteraFn;
use Dakujem\Toru\Pipeline;

$alteredCollection = Pipeline::through(
    $collection,
    IteraFn::filter(predicate: $filterFunction),
    IteraFn::map(values: $mapper),
);
// Pipelines are not limited to producing iterable collections, they may produce any value types:
$average = Pipeline::through(
    $collection,
    IteraFn::filter(predicate: $filterFunction),
    IteraFn::reduce(reducer: fn($carry, $current) => $carry + $current, initial: 0),
    fn(int $sum) => $sum / $sizeOfCollection,
);

use Dakujem\Toru\Itera;
use Dakujem\Toru\IteraFn;
use Dakujem\Toru\Pipeline;
use Dakujem\Toru\Dash;

$sequence = Itera::produce(fn() => rand()); // infinite iterator

// A naive foreach may be the best solution in certain cases...
$count = 0;
$array = [];
foreach ($sequence as $i) {
    if (0 == $i % 2) {
        $array[$i] = 'the value is ' . $i;
        if ($count >= 1000) {
            break;
        }
    }
}

// While the standalone static methods may be handy,
// they are not suited for complex computations.
$interim = Itera::filter($sequence, fn($i) => 0 == $i % 2);
$interim = Itera::reindex($interim, fn($i) => $i);
$interim = Itera::apply($interim, fn($i) => 'the value is ' . $i);
$interim = Itera::limit($interim, 1000);
$array = Itera::toArray($interim);

// Without the interim variable(s), the reading order of the calls is reversed
// and the whole computation is not exactly legible.
$array = Itera::toArray(
    Itera::limit(
        Itera::apply(
            Itera::reindex(
                Itera::filter(
                    $sequence,
                    fn($i) => 0 == $i % 2,
                ),
                fn($i) => $i,
            ),
            fn($i) => 'the value is ' . $i,
        ),
        1000,
    )
);

// Complex pipelines may be composed using partially applied callables.
$array = Pipeline::through(
    $sequence,
    IteraFn::filter(fn($i) => 0 == $i % 2),
    IteraFn::reindex(fn($i) => $i),
    IteraFn::apply(fn($i) => 'the value is ' . $i),
    IteraFn::limit(1000),
    IteraFn::toArray(),
);

// Lodash-style fluent call chaining.
$array = Dash::collect($sequence)
    ->filter(fn($i) => 0 == $i % 2)
    ->reindex(fn($i) => $i)
    ->map(fn($i) => 'the value is ' . $i)
    ->limit(1000)
    ->toArray();

$images = [];
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
/** @var SplFileInfo $fileInfo */
foreach ($iterator as $fileInfo) {
    // Skip directories
    if ($fileInfo->isDir()) {
        continue;
    }
    // Get the full path of the file
    $filePath = $fileInfo->getPathname();
    // Reject non-image files (hacky)
    if (!@getimagesize($filePath)) {
        continue;
    }
    $images[$filePath] = $fileInfo;
}

$listImages = function(string $dir): Generator {
    $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
    /** @var SplFileInfo $fileInfo */
    foreach ($iterator as $fileInfo) {
        // Skip directories
        if ($fileInfo->isDir()) {
            continue;
        }
        // Get the full path of the file
        $filePath = $fileInfo->getPathname();
        // Reject non-image files (hacky)
        if (!@getimagesize($fileInfo->getPathname())) {
            continue;
        }
        yield $filePath => $fileInfo;
    }
};
$images = $listImages($dir);

$images = _dash(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir))) // recursively iterate over a dir
    ->filter(fn(SplFileInfo $fileInfo) => !$fileInfo->isDir())                       // reject directories
    ->filter(fn(SplFileInfo $fileInfo) => @getimagesize($fileInfo->getPathname()))   // accept only images (hacky)
    ->reindex(fn(SplFileInfo $fileInfo) => $fileInfo->getPathname());                // key by the full file path
json
{
  "autoload": {
    "files": [
      "bootstrap/dash.php"
    ]
  }
}