tezmanian / utils-arr
A set of focused PHP array utilities: grouping, pagination, diffing, sorting, partitioning, flattening, cartesian products, and more — PHP 8.3+, no framework required.
Requires
- php: >=8.3
- tezmanian/utils-enum: ^1.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- jetbrains/phpstorm-attributes: ^1.0
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-10-14 14:25:08 UTC
README
A collection of focused, stateless array utility classes for PHP 8.3+. Each class does one thing and exposes a clean static API.
Installation
composer require tezmanian/utils-arr
Package: tezmanian/utils-arr · Namespace: Tezmanian\Utils\Arr
Components
| Class | Purpose |
|---|---|
ArrayDiff |
Recursively compares two arrays and returns all differences with dot-notation paths |
CartesianProduct |
Computes the Cartesian product of any number of sets lazily via a Generator |
DeepMerge |
Recursively merges override arrays into a base array |
Find |
Returns the first or last item matching a predicate |
Flatten |
Flattens a nested array into a single list |
GroupBy |
Groups items by a key derived from a callback |
KeyBy |
Builds an associative lookup map from a list |
MapWithKeys |
Maps over an array where the callback returns a [key => value] pair |
Paginator |
Paginates an array and returns an immutable Page with slice and metadata |
Partition |
Splits an array into two lists in a single pass |
Pluck |
Extracts values from an array of records using dot-notation keys |
SafeGet |
Safely reads typed values from deeply nested arrays using dot-notation paths |
Sliding |
Produces consecutive overlapping windows of a fixed size |
SortBy |
Sorts a copy of the array by a derived key |
Transpose |
Swaps rows and columns of a 2D array |
UniqueBy |
Removes duplicates where uniqueness is determined by a callback |
Wrap |
Ensures any value is an array |
Zip |
Combines multiple arrays element-by-element into a list of tuples |
ArrayDiff
Recursively compares two arrays and returns all differences with dot-notation paths.
use Tezmanian\Utils\Arr\ArrayDiff; $result = ArrayDiff::compare( ['user' => ['name' => 'Alice', 'age' => 30]], ['user' => ['name' => 'Bob', 'age' => 30, 'city' => 'Berlin']], ); $result->hasChanges(); // true foreach ($result->changes() as $change) { // $change->path → 'user.name' // $change->type → ChangeType::Changed // $change->old → 'Alice' // $change->new → 'Bob' } $result->paths(); // ['user.name', 'user.city']
Comparison is strict (1 !== "1"). Keys are reported as ChangeType::Added, ChangeType::Removed, or ChangeType::Changed.
CartesianProduct
Computes the Cartesian product of any number of sets lazily via a Generator.
use Tezmanian\Utils\Arr\CartesianProduct; foreach (CartesianProduct::create(['S', 'M', 'L'], ['red', 'blue']) as $tuple) { // ['S', 'red'], ['S', 'blue'], ['M', 'red'], ... }
No intermediate array is built — suitable for large input sets.
DeepMerge
Recursively merges override arrays into a base array. Multiple overrides are applied left to right.
use Tezmanian\Utils\Arr\DeepMerge; $result = DeepMerge::merge( ['db' => ['host' => 'localhost', 'port' => 3306]], ['db' => ['host' => 'prod.example.com']], ); // ['db' => ['host' => 'prod.example.com', 'port' => 3306]]
String keys are merged recursively when both sides are arrays. Integer keys are appended (like array_merge). Scalar values on the right side always win.
Find
Returns the first or last item matching a predicate.
use Tezmanian\Utils\Arr\Find; $first = Find::first($users, fn($u) => $u['active']); $last = Find::last($users, fn($u) => $u['active']); // Returns null when nothing matches
Flatten
Flattens a nested array into a single list. Accepts an optional depth limit.
use Tezmanian\Utils\Arr\Flatten; Flatten::flatten([1, [2, [3, 4]]]); // [1, 2, 3, 4] Flatten::flatten([1, [2, [3, 4]]], depth: 1); // [1, 2, [3, 4]]
GroupBy
Groups items by a key derived from a callback.
use Tezmanian\Utils\Arr\GroupBy; $grouped = GroupBy::group($orders, fn($o) => $o['status']); // ['pending' => [...], 'shipped' => [...]]
KeyBy
Builds an associative lookup map from a list. When two items produce the same key, the last wins.
use Tezmanian\Utils\Arr\KeyBy; $byId = KeyBy::index($users, fn($u) => $u['id']); // [42 => $user, 99 => $user, ...]
MapWithKeys
Maps over an array where the callback returns a [key => value] pair.
use Tezmanian\Utils\Arr\MapWithKeys; $result = MapWithKeys::map($products, fn($p) => [$p['sku'] => $p['price']]); // ['ABC-1' => 9.99, 'XYZ-2' => 4.49]
Paginator
Paginates an array and returns an immutable Page with slice and metadata.
use Tezmanian\Utils\Arr\Paginator; $page = Paginator::paginate($items, page: 2, perPage: 15); $page->items(); // items on this page $page->total(); // total item count $page->currentPage(); // 2 $page->lastPage(); // e.g. 7 $page->hasNext(); // true / false $page->hasPrev(); // true
Throws InvalidArgumentException when $page < 1 or $perPage < 1.
Partition
Splits an array into two lists in a single pass — items passing the predicate first, failing items second.
use Tezmanian\Utils\Arr\Partition; [$active, $inactive] = Partition::by($users, fn($u) => $u['active']);
Pluck
Extracts values from an array of records using dot-notation keys.
use Tezmanian\Utils\Arr\Pluck; // Extract a single column Pluck::values($users, 'name'); // ['Alice', 'Bob', ...] // Build key → value map Pluck::keyedBy($users, valueKey: 'name', indexKey: 'id'); // [42 => 'Alice', 99 => 'Bob'] // Dot-notation for nested values Pluck::values($orders, 'address.city');
Items where the key is absent are silently skipped.
SafeGet
Safely reads typed values from deeply nested arrays using dot-notation paths. Returns null (or a default) when any segment is missing.
use Tezmanian\Utils\Arr\SafeGet; $data = ['chart' => ['result' => [['meta' => ['currency' => 'EUR']]]]]; SafeGet::string($data, 'chart.result.0.meta.currency'); // 'EUR' SafeGet::int($data, 'chart.result.0.meta.missing'); // null SafeGet::float($data, 'price', default: 0.0); SafeGet::bool($data, 'flags.active'); SafeGet::array($data, 'chart.result');
Integer-looking segments (e.g. "0") address list indices automatically.
Sliding
Produces consecutive overlapping windows of a fixed size.
use Tezmanian\Utils\Arr\Sliding; Sliding::window([1, 2, 3, 4, 5], size: 3); // [[1,2,3], [2,3,4], [3,4,5]] Sliding::window([1, 2, 3, 4, 5], size: 3, step: 2); // [[1,2,3], [3,4,5]]
SortBy
Sorts a copy of the array by a derived key. Never mutates the original. Result is always re-indexed.
use Tezmanian\Utils\Arr\SortBy; $sorted = SortBy::sort($products, fn($p) => $p['price']); $desc = SortBy::sort($products, fn($p) => $p['price'], descending: true);
Transpose
Swaps rows and columns of a 2D array. All rows must have the same column count.
use Tezmanian\Utils\Arr\Transpose; Transpose::matrix([[1, 2, 3], [4, 5, 6]]); // [[1, 4], [2, 5], [3, 6]]
UniqueBy
Removes duplicates where uniqueness is determined by a callback.
use Tezmanian\Utils\Arr\UniqueBy; // Keep first occurrence (default) UniqueBy::filter($items, fn($i) => $i['email']); // Keep last occurrence UniqueBy::filter($items, fn($i) => $i['email'], lastWins: true); // Preserve original keys UniqueBy::filter($items, fn($i) => $i['email'], preserveKeys: true);
Wrap
Ensures any value is an array. null becomes [], arrays pass through, everything else gets wrapped.
use Tezmanian\Utils\Arr\Wrap; Wrap::ensure(null); // [] Wrap::ensure('hello'); // ['hello'] Wrap::ensure([1, 2]); // [1, 2]
Zip
Combines multiple arrays element-by-element into a list of tuples.
use Tezmanian\Utils\Arr\Zip; // Pad shorter arrays with null Zip::zip([1, 2, 3], ['a', 'b']); // [[1, 'a'], [2, 'b'], [3, null]] // Stop at shortest input Zip::shortest([1, 2, 3], ['a', 'b']); // [[1, 'a'], [2, 'b']]
Requirements
- PHP 8.3+
tezmanian/utils-enum^1.0
Development
make build && make install make ci # audit, PHPStan, code style, tests
License
MIT — see LICENSE.
Author: René Halberstadt