PHPackages                             pinkcrab/function-constructors - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. pinkcrab/function-constructors

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

pinkcrab/function-constructors
==============================

A collection of functions to make working with the standard php library a little easier for function composition. Allows the creation of partially applied library functions, to work as close to pure fucntions as possible.

1.0.0(2mo ago)466.4k↓59.4%1[1 issues](https://github.com/gin0115/pinkcrab_function_constructors/issues)[1 PRs](https://github.com/gin0115/pinkcrab_function_constructors/pulls)7GPL-2.0-or-laterPHPPHP &gt;=7.1.0CI passing

Since Dec 3Pushed 2mo ago1 watchersCompare

[ Source](https://github.com/gin0115/pinkcrab_function_constructors)[ Packagist](https://packagist.org/packages/pinkcrab/function-constructors)[ Docs](https://pinkcrab.co.uk)[ RSS](/packages/pinkcrab-function-constructors/feed)WikiDiscussions master Synced yesterday

READMEChangelog (10)Dependencies (8)Versions (49)Used By (7)

The PinkCrab FunctionConstructors library.
==========================================

[](#the-pinkcrab-functionconstructors-library)

[![Latest Stable Version](https://camo.githubusercontent.com/5414ef234ab18e8aee2094e7a59f5e4fd371165d92b01e47ece7e12feb12b681/68747470733a2f2f706f7365722e707567782e6f72672f70696e6b637261622f66756e6374696f6e2d636f6e7374727563746f72732f76)](https://packagist.org/packages/pinkcrab/function-constructors)[![Total Downloads](https://camo.githubusercontent.com/87d22bf54b80f9d9a39845af350c460f8a69a930d01ec0cec0285a5c72b3219e/68747470733a2f2f706f7365722e707567782e6f72672f70696e6b637261622f66756e6374696f6e2d636f6e7374727563746f72732f646f776e6c6f616473)](https://packagist.org/packages/pinkcrab/function-constructors)[![License](https://camo.githubusercontent.com/99b88cbeedb9facf436afe249917c651cafb82bbd06ae17a067f7b1cd7765a52/68747470733a2f2f706f7365722e707567782e6f72672f70696e6b637261622f66756e6374696f6e2d636f6e7374727563746f72732f6c6963656e7365)](https://packagist.org/packages/pinkcrab/function-constructors)[![PHP Version Require](https://camo.githubusercontent.com/f4865968c3ac176d964dedb07af1c08f46758bbfcc595dc056782af3b8a65254/68747470733a2f2f706f7365722e707567782e6f72672f70696e6b637261622f66756e6374696f6e2d636f6e7374727563746f72732f726571756972652f706870)](https://packagist.org/packages/pinkcrab/function-constructors)[![GitHub contributors](https://camo.githubusercontent.com/6d95667f3f188d002cdbb9bc0be67f0ca850145658aaac74499f16f13b47e6cb/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f636f6e7472696275746f72732f67696e303131352f70696e6b637261625f66756e6374696f6e5f636f6e7374727563746f72733f6c6162656c3d436f6e7472696275746f7273)](https://camo.githubusercontent.com/6d95667f3f188d002cdbb9bc0be67f0ca850145658aaac74499f16f13b47e6cb/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f636f6e7472696275746f72732f67696e303131352f70696e6b637261625f66756e6374696f6e5f636f6e7374727563746f72733f6c6162656c3d436f6e7472696275746f7273)[![GitHub issues](https://camo.githubusercontent.com/4000b028f099fc16c7b7de36f3e6418dbb6fbc28cb4ec64200f9a5abea208090/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732d7261772f67696e303131352f70696e6b637261625f66756e6374696f6e5f636f6e7374727563746f7273)](https://camo.githubusercontent.com/4000b028f099fc16c7b7de36f3e6418dbb6fbc28cb4ec64200f9a5abea208090/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732d7261772f67696e303131352f70696e6b637261625f66756e6374696f6e5f636f6e7374727563746f7273)

[![PHPUnit](https://github.com/gin0115/pinkcrab_function_constructors/actions/workflows/php.yml/badge.svg?branch=master)](https://github.com/gin0115/pinkcrab_function_constructors/actions/workflows/php.yml)

[![codecov](https://camo.githubusercontent.com/43bc7dabfe0e89af18f724f3ca6b750c22dc10da753ece7b278a818a84bd9041/68747470733a2f2f636f6465636f762e696f2f67682f67696e303131352f70696e6b637261625f66756e6374696f6e5f636f6e7374727563746f72732f6272616e63682f6d61737465722f67726170682f62616467652e7376673f746f6b656e3d58344c53353936315431)](https://codecov.io/gh/gin0115/pinkcrab_function_constructors)[![Scrutinizer Code Quality](https://camo.githubusercontent.com/0b43fe26c4e2d58934d19312acecba2ed99f1f1fc91dcb68f637bf49c6d6e2a6/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f67696e303131352f70696e6b637261625f66756e6374696f6e5f636f6e7374727563746f72732f6261646765732f7175616c6974792d73636f72652e706e673f623d6d6173746572)](https://scrutinizer-ci.com/g/gin0115/pinkcrab_function_constructors/?branch=master)

---

This library provides a small selection of functions for making functional programming a little cleaner and easier in php.

Setup
-----

[](#setup)

Can be included into your project using either composer or added manually to your codebase.

### Via Composer

[](#via-composer)

`$ composer require pinkcrab/function-constructors`

### Via Manual Loader

[](#via-manual-loader)

If you wish to use this library within WordPress or other PHP codebase where you do not or cannot use composer, you can use the **FunctionsLoader** class. Just clone the repo into your codebase and do the following.

```
require_once 'path/to/cloned/repo/FunctionsLoader.php';
FunctionsLoader::include();
```

All of our functions are namespaced as **PinkCrab\\FunctionConstructors\\{lib}**. So the easiest way to use them is to use with an alias. Throughout all the docs on the wiki we use the following aliases.

```
use PinkCrab\FunctionConstructors\Arrays as Arr;
use PinkCrab\FunctionConstructors\Numbers as Num;
use PinkCrab\FunctionConstructors\Strings as Str;
use PinkCrab\FunctionConstructors\Comparisons as C;
use PinkCrab\FunctionConstructors\GeneralFunctions as F;
use PinkCrab\FunctionConstructors\Objects as Obj;

// Allowing for
Arr\Map('esc_html') or Str\append('foo') or F\pipe($var, 'strtoupper', Str\append('foo'))
```

Usage
-----

[](#usage)

At its core, the Function Constructors library is designed to make using PHP easier to use in a functional manor. With the use of functions `compose()` and `pipe()` its possible to construct complex functions, from simpler ones.

### Function Composition and Piping

[](#function-composition-and-piping)

#### pipe()

[](#pipe)

> PLEASE NOTE THIS HAS CHANGED IN VERSION 2.0.0, using `compose()` is now the preferred method.

Using `pipe(mixed $value, callable ...$callables)` and [ `pipeR()` \*](#pipe "Same as pipe(), but callables in reverse order"), allows you to pass a value through a chain of callables. The result of the 1st function, is passed as the input the 2nd and so on, until the end when the final result is returned.

> The rest of this library makes it easier to use standard php functions as callables, by defining some of the parameters up front.

```
$data = [0,3,4,5,6,8,4,6,8,1,3,4];

// Remove all odd numbers, sort in an acceding order and double the value.
$newData = F\pipe(
    $data,
    Arr\filter(Num\isMultipleOf(2)), // Remove odd numbers
    Arr\natsort(),                 // Sort the remaining values
    Arr\map(Num\multiply(2))       // Double the values.
);

// Result
$newData = [
 2 => 8,
 6 => 8,
 11 => 8,
 4 => 12,
 7 => 12,
 5 => 16,
 8 => 16,
];
```

#### compose()

[](#compose)

Piping is ideal when you are working with a single value, but when it comes to working with Arrays or writing callbacks, compose() is much more useful.

`compose(callable ...$callables)` , `composeR(callable ...$callables)` , `composeSafe(callable ...$callables)` and `composeTypeSafe(callable $validator, callable ...$callables)` all allow you to create custom Closures.

```
$data = [
    ['details'=>['description' => '    This is some description ']],
    ['details'=>['description' => '        This is some other description    ']],
];

$callback = F\compose(
   F\pluckProperty('details','description'), // Plucks the description
   'trim',                                   // Remove all whitespace
   Str\slice(0, 20),                         // Remove all but first 20 chars
   'ucfirst',                                // Uppercase each word
   Str\append('...')                        // End the string with ...
);

$results = array_map($callback, $data);

$results = [
    'This Is Some Descrip...',
    'This Is Some Other D...'
]
```

> You can use `composeTypeSafe()` if you want to pass the return of each callable through a validator before being passed to the next. If the validator fails, the rest of the chain will be skipped and null will be returned.

---

### Working with Records

[](#working-with-records)

It is possible to work with the properties of *Records* (arrays and objects). Indexes or Properties can be checked, fetched and set using some of the `GeneralFunctions` .

#### Reading Properties

[](#reading-properties)

You can check if a property exists, get its value or compare it an defined value.

```
$data = [
    ['id' => 1, 'name' => 'James', 'timezone' => '+1', 'colour' => 'red'],
    ['id' => 2, 'name' => 'Sam', 'timezone' => '+1', 'colour' => 'red', 'special' => true],
    ['id' => 3, 'name' => 'Sarah', 'timezone' => '+2', 'colour' => 'green'],
    ['id' => 4, 'name' => 'Donna', 'timezone' => '+2', 'colour' => 'blue', 'special' => true],
];

// Filter all users with +2 timezone.
$zonePlus2 = array_filter($data, F\propertyEquals('timezone','+2'));
$results = [['id' => 3, ....],['id' => 4, ...]];

// Filter all user who have the special index.
$special = array_filter($data, F\hasProperty('special'));
$results = [['id' => 2, ....],['id' => 4, ...]];

// Get a list of all colours.
$colours = array_map(F\getProperty('colour'), $data);
$results = ['red', 'red', 'green', 'blue'];
```

> `pluckProperty()` can also be used if you need to traverse nested properties/indexes of either **arrays** or **objects** *also handles `ArrayAccess` objects, set with array syntax* [see example on `compose()` ](#compose)

#### Writing Properties

[](#writing-properties)

Its also possible to write properties of objects and set values to indexes in arrays using the `setProperty()` function. More complex structures can also be created using the [Record Encoder](../../../../pinkcrab_function_constructors/wiki/Record_Encoder)

```
// Set object property.
$object = new class(){ public $key = 'default'};

// Create a custom setter function.
$setKeyOfObject = F\setProperty($object, 'key');
$object = $setKeyOfObject('new value');
// {"key":"new value"}

// Can be used with arrays too
$array = ['key' => 'default'];

// Create a custom setter function.
$setKeyOfSArray = F\setProperty($array, 'key');
$array = $setKeyOfSArray('new value');
// [key => "new value"]
```

---

### String Functions

[](#string-functions)

Much of the string functions found in this library act as wrappers for common standard (PHP) library functions, but curried to allow them to be easier composed with.

#### String Manipulation

[](#string-manipulation)

There is a collection of functions with make for the concatenation of strings.

```
$appendFoo = Str\append('foo');
$result = $appendFoo('BAR');

$prependFoo = Str\prepend('foo');
$result = $prependFoo('BAR');

$replaceFooWithBar = Str\replaceWith('foo', 'bar');
$result = $replaceFooWithBar("its all a bit foo foo");
// "its all a bit bar bar"

$wrapStringWithBar = Str\wrap('bar-start-', '-bar-end');
$result = $wrapStringWithBar('foo');
// bar-start-foo-bar-end
```

#### String Contents

[](#string-contents)

There is a collection of functions that be used to check the contents of a string.

```
// Check if a string contains
$containsFoo = Str\contains('foo');
$containsFoo('foo');   // true
$containsFoo('fobar'); // false

// Check if string start with (ends with also included)
$startsBar = Str\startsWith('bar');
$startsBar('bar-foo'); // true
$startsBar('foo-bar'); // false

// Check if a blank string
Str\isBlank('');   // true
Str\isBlank(' ');  // false

// Unlike using empty(), this checks if the value is a string also.
Str\isBlank(0);    // false
Str\isBlank(null); // false

// Contains a regex pattern
$containsNumber = Str\containsPattern('~[0-9]+~');
$containsNumber('apple');   // false
$containsNumber('A12DFR3'); // true
```

> `Str\isBlank()` can be used when composing a function, thanks to the Functions::isBlank constant.

```
$data = [0 => '', 1 => 'fff', 2 => '    '];
$notBlanks = array_filter(PinkCrab\FunctionConstructors\Functions::IS_BLANK, $data);
// [0 => '']
```

#### Sub Strings

[](#sub-strings)

There is a series of functions that can be used to work with substrings.

```
// Split the string into sub string
$inFours = Str\split(4);
$split = $inFours('AAAABBBBCCCCDDDD');
// ['AAAA','BBBB','CCCC','DDDD']

// Chunk the string
$in5s = Str\chunk(5, '-');
$result = $in5s('aaaaabbbbbccccc');
// 'aaaaa-bbbbb-ccccc-'

// Count all characters in a given string.
$charCount = Str\countChars();
$results = $charCount('Hello World');
// [32 => 1, 72 => 1, 87 => 1, 100 => 1, 101 => 1, 108 => 3, 111 => 2, 114 => 1]
// If the keys are mapped using chr(), you will get
$results = (Arr\mapKey('chr')($results));
// ['H' => 1,'e' => 1,'l' => 3,'o' => 2,' ' => 1,'W' => 1,'r' => 1,'d' => 1,]

// Count occurrences of a substring.
$countFoo = Str\countSubString('foo');
$results = $countFoo('foo is foo and bar is not foo');
// 3

// Find the first position of foo in string.
$firstFoo = Str\firstPosition('foo');
$result = $firstFoo('abcdefoog');
// 5
```

> See more of the Strings functions [on the wiki](../../../../pinkcrab_function_constructors/wiki/Strings)

---

### Number Functions

[](#number-functions)

Much of the number functions found in this library act as wrappers for common standard (PHP) library functions, but curried to allow them to be easier composed with.

#### Basic Arithmetic

[](#basic-arithmetic)

You can do some basic arithmetic using composable functions. This allows for the creation of a base value, then work using the passed value.

> All these functions allow the use of `INT` or `FLOAT` only, all numerical strings must be cast before being used. Will throw `TypeError` otherwise.

```
// Add
$addTo5 = Num\sum(5);

$addTo5(15.5); // 20.5
$addTo5(-2); // 3

// Subtract
$subtractFrom10 = Num\subtract(10);
$subtractFrom10(3)  // 7
$subtractFrom10(20) // -10

// Multiply
$multiplyBy10 = Num\multiply(10)
$multiplyBy10(5);   // 50
$multiplyBy10(2.5); // 25.0

// Divide By
$divideBy3 = Num\divideBy(3);
$divideBy3(12); // 4 = 12/3
$divideBy3(10); // 3.333333333333

// Divide Into
$divideInto12 = Num\divideInto(12);
$divideInto12(4); // 3 = 12/4
```

#### Multiple and Modulus

[](#multiple-and-modulus)

It is possible to do basic modulus operations and working out if a number has a whole factor of another.

```
// Factor of
$isMultipleOf2 = Num\isMultipleOf(2);
$isMultipleOf2(12); // true
$isMultipleOf2(13); // false

// Getting the remainder
$remainderBy2 = Num\remainderBy(2);
$remainderBy2(10); // 0 = (5 * 2) - 10
$remainderBy2(9);  // 1 = (4 * 2) - 9
```

### Array Functions

[](#array-functions)

As you can imagine there are a large number of functions relating to arrays and working with them.

#### Map

[](#map)

This library contains a large number of variations of `array_map` , these can all be pre composed, using the other functions to be extremely powerful and easy to follow.

```
// Create a mapper which doubles the value.
$doubleIt = Arr\map( Num\multiply(2) );
$doubleIt([1,2,3,4]); // [2,4,6,8]

// Create mapper to normalise array keys
$normaliseKeys = Arr\mapKey(F\compose(
    'strval',
    'trim',
    Str\replace(' ', '-')
    Str\prepend('__')
));

$normaliseKeys(1 => 'a', ' 2 ' => 'b', 'some key' => 'c');
// ['__1'=> 'a', '__2' => 'b', '__some-key' => 'c']

// Map and array with the value and key.
$mapWithKey = Arr\mapWithKey( function($key, $value) {
    return $key . $value;
});
$mapWithKey('a' => 'pple', 'b' => 'anana');
// ['apple', 'banana']
```

> There is `flatMap()` and `mapWith()` also included, please see the wiki.

#### Filter and Take

[](#filter-and-take)

There is a large number of composible functions based around `array_filter()` . Combined with a basic set of `take*()` functions, you can compose functions to work with lists/collections much easier.

```
// Filter out ony factors of 3
$factorsOf3s = Arr\filter( Num\factorOf(3) );
$factorsOf3s([1,3,5,6,8,7,9,11]); // [3,6,9]

// Filer first and last of an array/
$games = [
    ['id'=>1, 'result'=>'loss'],
    ['id'=>2, 'result'=>'loss'],
    ['id'=>3, 'result'=>'win'],
    ['id'=>4, 'result'=>'win'],
    ['id'=>5, 'result'=>'loss'],
];

$firstWin = Arr\filterFirst( F\propertyEquals('result','win') );
$result = $firstWin($games); // ['id'=>3, 'result'=>'win']

$lastLoss = Arr\filterLast( F\propertyEquals('result','loss') );
$result = $lastLoss($games); // ['id'=>5, 'result'=>'loss']

// Count result of filter.
$totalWins = Arr\filterCount( F\propertyEquals('result','win') );
$result = $totalWins($games); // 2
```

> Filter is great if you want to just process every result in the collection, the `take()` family of functions allow for controlling how much of an array is filtered

```
// Take the first or last items from an array
$first5 = Arr\take(5);
$last3 = Arr\takeLast(5);

$nums = [1,3,5,6,8,4,1,3,5,7,9,3,4];
$first5($nums); // [1,3,5,6,8]
$last3($nums);  // [9,3,4]

// Using takeWhile and takeUntil to get the same result.
$games = [
    ['id'=>1, 'result'=>'loss'],
    ['id'=>2, 'result'=>'loss'],
    ['id'=>3, 'result'=>'win'],
    ['id'=>4, 'result'=>'win'],
    ['id'=>5, 'result'=>'loss'],
];

// All games while the result is a loss, then stop
$initialLoosingStreak = Arr\takeWhile(F\propertyEquals('result','loss'));
// All games until the first win, then stop
$untilFirstWin = Arr\takeUntil(F\propertyEquals('result', 'win'));

$result = $initialLoosingStreak($game);
$result = $untilFirstWin($game);
// [['id' => 1, 'result' => 'loss'], ['id' => 2, 'result' => 'loss']]
```

#### Fold and Scan

[](#fold-and-scan)

Folding or reducing an a list is a pretty common operation and unlike the native `array_reduce` you have a little more flexibility.

```
$payments = [
    'gfg1dg3d' => ['type' => 'card', 'amount' => 12.53],
    'eg43ytfh' => ['type' => 'cash', 'amount' => 21.95],
    '5g7tgxfb' => ['type' => 'card', 'amount' => 1.99],
    'oitu87uo' => ['type' => 'cash', 'amount' => 4.50],
    'ew1e5435' => ['type' => 'cash', 'amount' => 21.50],
];

// Get total for all cash payment.
$allCash = Arr\fold(function($total, $payment){
    if($payment['type'] === 'cash'){
        $total += $payment['amount'];
    }
    return $total;
},0.00);

$result = $allCash($payments); // 47.95

// Log all card payment in some class, with access to array keys.
$logCardPayments = Arr\foldKeys(function($log, $key, $payment){
    if($payment['type'] === 'card'){
        $log->addPayment(payment_key: $key, amount: $payment['amount']);
    }
    return $log;
}, new CardPaymentLog('some setup') );

$cardPaymentLog = $logCardPayments($payments);
var_dump($cardPayments->getPayments());
// [{'key': 'gfg1dg3d', 'amount': 12.53}, {'key': '5g7tgxfb', 'amount': 1.99}]

// Generate a running total of all payments.
$runningTotal = Arr\scan(function($runningTotal, $payment){
    $runningTotal += $payment['amount'];
    return $runningTotal;

}, 0.00);

$result = $runningTotal($payments);
// [0.0, 12.53, 34.48, 36.47, 40.97, 62.47]
```

> You also have access to `foldR()` and `scanR()` which will iterate through the array backwards.

#### Grouping and Partitioning

[](#grouping-and-partitioning)

Function Constructor has a number of functions which make it easy to group and partition arrays

```
$data = [
    ['id'=>1, 'name'=>'John', 'age'=>20, 'someMetric' => 'A12'],
    ['id'=>2, 'name'=>'Jane', 'age'=>21, 'someMetric' => 'B10'],
    ['id'=>3, 'name'=>'Joe', 'age'=>20, 'someMetric' => 'C15'],
    ['id'=>4, 'name'=>'Jack', 'age'=>18, 'someMetric' => 'B10'],
    ['id'=>5, 'name'=>'Jill', 'age'=>22, 'someMetric' => 'A12'],
];

// Group by the return value of the function.
$groupedByMetric = Arr\groupBy(function($item){
    return $item['someMetric'];
});

$results = $groupedByMetric($data);
["A12" =>  [
    ["id" => 1,"name" => "John", ...],
    ["id" => 5,"name" => "Jill", ...]
],
"B10" =>  [
    ["id" => 2,"name" => "Jane", ...],
    ["id" => 4,"name" => "Jack", ...]
],
"C15" =>  [
    ["id" => 3,"name" => "Joe", ...]
]];

// Partition using a predicate function.
$over21 = Arr\partition(function($item){
    return $item['age'] >= 21;
});

$results = $over21($data);
[0 => [ // false values
    ["name" => "John", "age" => 20, ...],
    ["name" => "Joe", "age" => 20, ...],
    ["name" => "Jack", "age" => 18, ...]
],
1 => [ // true values
    ["name" => "Jane", "age" => 21, ...],
    ["name" => "Jill", "age" => 22, ...]
]];
```

> It is possible to chunk and split arrays, see the wiki for more.

#### Sorting

[](#sorting)

The native PHP `sort` functions are tricky with a functional approach, as they sort via reference, rather than by a return value. The Function Constructor library covers all native sorting as partially applied functions.

```
// Sorting simple arrays
$dataWords = ['Zoo', 'cat', 'Dog', 'ant', 'bat', 'Cow'];

$sortWords = Arr\sort(SORT_STRING);
$result = $sortWords($dataWords);
// ['ant', 'bat', 'cat', 'Cow', 'Dog', 'Zoo'];

// Sorting associative arrays
$dataBooks = [
    'ehjf89' => ['id'=>'ehjf89', 'title'=>'Some title', 'author'=> 'Adam James'],
    'retg23' => ['id'=>'retg23', 'title'=>'A Title', 'author'=> 'Jane Jones'],
    'fvbi43' => ['id'=>'fvbi43', 'title'=>'Some title words', 'author'=> 'Sam Smith'],
    'mgged3' => ['id'=>'mgged3', 'title'=>'Book', 'author'=> 'Will Adams'],
];

// Sort by key
$sortBookByKey = Arr\ksort(SORT_STRING | SORT_FLAG_CASE);
$result = $sortBookByKey($dataBooks);
[
    'ehJF89' => ['id' => 'ehjf89', 'title' => 'Some title', 'author' => 'Adam James'],
    'fvbI43' => ['id' => 'fvbi43', 'title' => 'Some title words', 'author' => 'Sam Smith'],
    'MggEd3' => ['id' => 'mgged3', 'title' => 'Book', 'author' => 'Will Adams'],
    'Retg23' => ['id' => 'retg23', 'title' => 'A Title', 'author' => 'Jane Jones'],
]

// Sort by author
$sortBookByAuthor = Arr\uasort(function ($a, $b) {
    return strcmp($a['author'], $b['author']);
});
$sortBookByAuthor($dataBooks);
[
    'ehJF89' => ['id' => 'ehjf89', 'title' => 'Some title', 'author' => 'Adam James'],
    'Retg23' => ['id' => 'retg23', 'title' => 'A Title', 'author' => 'Jane Jones'],
    'fvbI43' => ['id' => 'fvbi43', 'title' => 'Some title words', 'author' => 'Sam Smith'],
    'MggEd3' => ['id' => 'mgged3', 'title' => 'Book', 'author' => 'Will Adams'],
]
```

---

### Iterable support (v1.0.0+)

[](#iterable-support-v100)

From `1.0.0` onwards, every data-transforming function in the `Arrays\` namespace accepts any `iterable` — arrays, `Generator`s, `Iterator`s, and any other `Traversable`. Two output contracts apply:

- **Array in → array out.** Existing call sites keep returning arrays with identical shape. No breakage.
- **Generator (or other Traversable) in → Generator out** for the *lazy* functions; *materialised* result (array / int / bool / string / object) for *terminal* functions.

This lets you feed a `compose` / `pipe` chain a lazy source and keep it lazy end-to-end, until a terminal step collapses it:

```
$pipeline = Func\compose(
    Arr\filter(fn($x) => $x > 0),
    Arr\map(fn($x) => $x * 10),
    Arr\take(5)
);

// Array → array, unchanged:
$pipeline([1, -2, 3, -4, 5, 6, 7]);        // [10, 30, 50, 60, 70]

// Generator → Generator, lazy; source is only pulled far enough to yield 5 values:
$bigStream = (function () { $i = 1; while (true) { yield $i++; } })();
foreach ($pipeline($bigStream) as $v) { echo "$v\n"; } // 10, 20, 30, 40, 50
```

#### Which functions are lazy?

[](#which-functions-are-lazy)

CategoryFunctions**Lazy** (yield Generator for iterable input)`append`, `prepend`, `tail`, `head` (early-exit), `map`, `mapKey`, `mapWith`, `mapWithKey`, `filter`, `filterKey`, `filterAnd`, `filterOr`, `filterMap`, `take`, `takeUntil`, `takeWhile`, `zip`, `chunk`, `column`, `flatMap`, `flattenByN`, `scan`, `scanR`**Short-circuit terminal** (don't fully consume when they can bail early)`filterFirst`, `filterAll`, `filterAny`, `head`**Full-consume terminal** (materialise to produce a result)`last`, `takeLast`, `filterLast`, `filterCount`, `partition`, `groupBy`, `toString`, `toObject`, `toJson`, `sumWhere`, `pick`, `replace`, `replaceRecursive`, `fold`, `foldR`, `foldKeys`, all `sort` variants#### Caveats

[](#caveats)

1. **Generators are single-shot.** Once exhausted they cannot be rewound. Iterating a pipeline result twice yields empty on the second pass — this is a PHP Generator fact of life, not a library choice.
2. **Infinite Generators work with early-exit pipelines** (`head`, `take`, `filterFirst`, `filterAll` on first false, `filterAny` on first true). They hang forever with full-consume terminals (`sort`, `fold`, `groupBy`, etc.) — that's your call, not ours.
3. **Key collisions** when materialising a Generator to an array (inside terminal fns) follow PHP's native behaviour: duplicate keys overwrite earlier values.
4. **`tail` on an empty Generator** yields an empty Generator rather than returning `null` (the array-path behaviour for empty input). The array path is unchanged.

---

### Contributions

[](#contributions)

If you would like to contribute to this project, please feel to fork the project on github and submit a pull request.

---

> For more details, please read the [wiki](https://github.com/gin0115/pinkcrab_function_constructors/wiki)

Changes
-------

[](#changes)

- 1.0.0

    - **New Functions**
    - `Strings\digit()` — replaces `Strings\decimalNumber()`.
    - `Strings\similar()` — replaces `Strings\similarAsBase()` / `Strings\similarAsComparison()`.
    - `Strings\compare()`.
    - `Strings\countChars()` now has mode constants (`CHAR_COUNT_ARRAY`, `CHAR_COUNT_ARRAY_UNIQUE`, `CHAR_COUNT_ARRAY_UNUSED`).
    - `Arrays\last()` — returns the last element of an array.
    - `Arrays\append()` — replaces `Arrays\pushTail()`.
    - `Arrays\prepend()` — replaces `Arrays\pushHead()`.
    - `Arrays\pick()` — picks a subset of keys from an array.
    - `GeneralFunctions\sideEffect()` — runs a callable for its side effect and returns the input unchanged; handy for debug/log taps in compose/pipe chains.
    - **Breaking Changes — all deprecated functions removed**
    - Typo-alias functions removed:
        - `Strings\decimialNumber()` → use `Strings\digit()`
        - `Strings\similarAsComparisson()` → use `Strings\similar()`
        - `Strings\firstPosistion()` → use `Strings\firstPosition()`
        - `Strings\lastPosistion()` → use `Strings\lastPosition()`
    - Previously-deprecated functions removed:
        - `Strings\decimalNumber()` → use `Strings\digit()`
        - `Strings\similarAsBase()` → use `Strings\similar()`
        - `Strings\similarAsComparison()` → use `Strings\similar()`
        - `Arrays\pushHead()` → use `Arrays\prepend()`
        - `Arrays\pushTail()` → use `Arrays\append()`
        - `GeneralFunctions\toArray()` → use `Objects\toArray()`
    - `Arrays\tail()` now works as expected, returning the array without the first element.
    - `Strings\allowTags()` now accepts an array of allowed tags even for pre PHP 7.4.
    - **Iterable support**
    - Every data-transforming `Arrays\*` function now accepts any `iterable` — arrays, `Generator`s, `Iterator`s, any `Traversable` — not just `array`. See the "Iterable support" section above for the full contract, the lazy/terminal classification, and the gotchas (single-shot Generators, infinite streams, key collisions, `tail` on empty).
    - Lazy functions (`map`, `filter`, `take`, `chunk`, `zip`, `flatMap`, `scan`, …) return a `Generator` for `Traversable` input and an `array` for `array` input — pipelines stay lazy end-to-end when the source is lazy, and fully backward-compatible when the source is an array.
    - **Other Changes**
    - Made `Arrays\filterFirst()` and `Arrays\filterLast()` more efficient.
    - Bumped dev deps: phpstan `^1.0 || ^2.0`, phpunit `^7.5 || ^8.5 || ^9.6`, phpunit-polyfills `^1.0 || ^2.0 || ^4.0`.
    - CI matrix expanded to PHP 7.1 through 8.5; Codecov upload restricted to the PHP 8.5 job.
- 0.2.0 -

    - **New Functions**
    - `Numbers\isMultipleOf()`
    - `Numbers\isFactorOf()`
    - `Strings\isBlank()`
    - `Strings\splitByLength()`
    - `GeneralFunctions\ifThen()`
    - `GeneralFunctions\ifElse()`
    - `GeneralFunctions\composeR()`
    - `Arrays\fold()`
    - `Arrays\foldR()`
    - `Arrays\foldKey()`
    - `Arrays\scan()`
    - `Arrays\scanR()`
    - `Arrays\take()`
    - `Arrays\takeLast()`
    - `Arrays\takeUntil()`
    - `Arrays\takeWhile()`
    - `Arrays\filterAny()`
    - `Arrays\filterAll()`
    - `Arrays\mapWithKey()`
    - `Objects\isInstanceOf()`
    - `Objects\implementsInterface()`
    - `Objects\toArray()`
    - `Objects\usesTrait()`
    - `Objects\createWith()`
    - **Breaking Changes**
    - `GeneralFunctions\pipe()` &amp; `GeneralFunctions\pipeR()` have now changed and are no longer alias for `compose()`
    - `GeneralFunctions\setProperty()` now takes the property argument when creating the Closure.
    - `Strings\tagWrap()` has been removed
    - `Strings\asUrl()` has been removed
    - `Strings\vSprintf()` has has its arguments reversed.
    - `Strings\split()` is now a wrapper for explode() and the existing `Strings\split()` has been renamed to `Strings\splitByLength()`
    - **Other Changes**
    - Constants added using the `Functions` class-name, `Functions::isBlank` can be used as a string for a callable.
    - `GeneralFunctions\toArray()` has been moved to `Objects\toArray()`, `Objects\toArray()` is now an alias for `GeneralFunctions\toArray()`
- 0.1.2 - Added `Arrays\zip()`
- 0.1.3 - Added` Arrays\filterKey()`

###  Health Score

51

—

FairBetter than 95% of packages

Maintenance81

Actively maintained with recent releases

Popularity34

Limited adoption so far

Community16

Small or concentrated contributor base

Maturity62

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~191 days

Recently: every ~347 days

Total

11

Last Release

72d ago

Major Versions

0.2.0 → 1.0.02026-04-21

### Community

Maintainers

![](https://www.gravatar.com/avatar/d82b9e8ef7816d3d0b9812ad233f61f6a313f529e0ac85721781b46ad292e1ea?d=identicon)[glynnquelch](/maintainers/glynnquelch)

---

Top Contributors

[![gin0115](https://avatars.githubusercontent.com/u/28779094?v=4)](https://github.com/gin0115 "gin0115 (476 commits)")

---

Tags

functionalfpcurryingpartial applicationpiping

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/pinkcrab-function-constructors/health.svg)

```
[![Health](https://phpackages.com/badges/pinkcrab-function-constructors/health.svg)](https://phpackages.com/packages/pinkcrab-function-constructors)
```

###  Alternatives

[lstrojny/functional-php

Functional primitives for PHP

2.0k7.5M51](/packages/lstrojny-functional-php)[nikic/iter

Iteration primitives using generators

1.1k6.4M55](/packages/nikic-iter)[ihor/nspl

Non-standard PHP library (NSPL) - functional primitives toolbox and more

375369.6k](/packages/ihor-nspl)[qaribou/immutable.php

Immutable, highly-performant collections, well-suited for functional programming and memory-intensive applications.

345148.0k](/packages/qaribou-immutablephp)[lambdish/phunctional

λ PHP functional library

3642.1M24](/packages/lambdish-phunctional)[crell/fp

Functional utilities for PHP 8 and later

92522.5k13](/packages/crell-fp)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
