Array helpers

A page's PHP file mostly works on arrays: a row from the database, a list of rows, a decoded JSON answer, nested settings. The array helpers do the everyday work on them - read a value deep inside, take one field of every row, keep the rows that pass, group, sort and sum - so the PHP stays a few lines and the template gets data ready to show. They are plain functions with a padArr name, loaded on every request like the rest of pad/lib/, and they work on objects too: an ArrayAccess object through its offsets, any other object through its public properties.

Dot paths

A dot path names a value inside nested arrays: 'customer.address.city', with an integer key as a segment - 'lines.0.sku'. A * segment reads every item of that level and answers a list: 'lines.*.sku' is every product. padArrGet answers the default when the path is not there - a Closure as the default is called only then - and padArrSet makes the levels on the way.

PHP
fragments/arrays_1.php
HTML
fragments/arrays_1.pad
Result
<?php

$order
= [
'number' => 1042,
'customer' => [ 'name' => 'Ann',
'address' => [ 'city' => 'Leiden' ] ],
'lines' => [ [ 'sku' => 'TEA', 'qty' => 2 ],
[
'sku' => 'MUG', 'qty' => 1 ] ]
];

$city = padArrGet ( $order, 'customer.address.city' );
$zip = padArrGet ( $order, 'customer.address.zip',
'not known' );
$skus = padArrGet ( $order, 'lines.*.sku' );

$products = implode ( ', ', $skus );

padArrSet ( $order, 'customer.address.zip', '2311 AB' );
padArrForget ( $order, 'lines.1' );

$hasZip = padArrHas ( $order, 'customer.address.zip' )
?
'yes' : 'no';
$zipNow = padArrGet ( $order, 'customer.address.zip' );
$lineLeft = padArrGet ( $order, 'lines.0.sku' );

?>
<p>City: {$city}</p>
<p>Zip code: {$zip}</p>
<p>Products: {$products}</p>
<p>After the set: zip there? {$hasZip} - {$zipNow}</p>
<p>After the forget: {$lineLeft} is the only line</p>

City: Leiden

Zip code: not known

Products: TEA, MUG

After the set: zip there? yes - 2311 AB

After the forget: TEA is the only line

padArrHas is true when every path given exists, also when its value is NULL - where isset() says no. padArrSet and padArrForget change the array they are given and answer it as well.

Picking keys

padArrOnly keeps the top-level keys given, in the order they are given; padArrExcept drops them, and takes dot paths. Keys are an array or a comma-separated string.

PHP
fragments/arrays_2.php
HTML
fragments/arrays_2.pad
Result
<?php

$user
= [
'id' => 7,
'name' => 'Ann',
'email' => 'ann@example.com',
'password'=> '*REDACTED*',
'login' => [ 'ip' => '10.0.0.1',
'browser' => 'Firefox' ]
];

$contact = padArrOnly ( $user, 'email, name' );
$public = padArrExcept ( $user, 'password, login.ip' );

$shown = json_encode ( $public );

?>
<p>{contact~}
{key@contact}: {$contact}
{~notLast@contact}, {/notLast@contact}
{~/contact}</p>
<p>{$shown}</p>

email: ann@example.com, name: Ann

{"id":7,"name":"Ann","email":"ann@example.com","login":{"browser":"Firefox"}}

Working with rows

padArrPluck takes one field of every row, keyed by another field when one is given - the id and name pairs of a select box. padArrKeyBy keys the rows themselves, to find one without a loop.

PHP
fragments/arrays_3.php
HTML
fragments/arrays_3.pad
Result
<?php

$customers
= [
[
'id' => 3, 'name' => 'Cleo', 'country' => 'NL' ],
[
'id' => 1, 'name' => 'Ann', 'country' => 'BE' ],
[
'id' => 2, 'name' => 'Bob', 'country' => 'NL' ]
];

$names = implode ( ', ', padArrPluck ( $customers, 'name' ) );

$sorted = padArrSortBy ( $customers, 'name' );
$choices = padArrPluck ( $sorted, 'name', 'id' );

$byId = padArrKeyBy ( $customers, 'id' );
$two = $byId [2] ['name'];

?>
<p>{$names}</p>
<select name="customer">
{choices~}
<option value="{key@choices}">{$choices}</option>
{~/choices}
</select>
<p>Customer 2 is {$two}</p>

Cleo, Ann, Bob

Customer 2 is Bob

padArrWhere keeps the rows that pass, with their keys: a field equal to a value, a field compared with an operator, or a callback. The operators are = == === != <> !== < > <= >= (and PAD's own eq ne lt gt le ge), in and not in with a list, and like with SQL's % and _, ignoring case. Equality is PHP's loose ==, so the string '5' a database answers equals a 5 written in PHP. padArrSortBy sorts by a field, stable, ascending or with TRUE or 'desc' descending; padArrFirst and padArrLast answer the first and last row a callback passes.

PHP
fragments/arrays_4.php
HTML
fragments/arrays_4.pad
Result
<?php

$orders
= [
[
'number' => 101, 'customer' => 'Ann',
'status' => 'paid', 'total' => '120.00' ],
[
'number' => 102, 'customer' => 'Bob',
'status' => 'open', 'total' => '35.50' ],
[
'number' => 103, 'customer' => 'Cleo',
'status' => 'paid', 'total' => '89.90' ],
[
'number' => 104, 'customer' => 'Carl',
'status' => 'open', 'total' => '15.00' ]
];

$large = padArrWhere ( $orders, 'total', '>=', 50 );
$large = padArrSortBy ( $large, 'total', 'desc' );
$withC = padArrWhere ( $orders, 'customer', 'like', 'c%' );
$unpaid = padArrWhere ( $orders, 'status', 'open' );

$isOpen = fn ( $order ) => $order ['status'] == 'open';
$isPaid = fn ( $order ) => $order ['status'] == 'paid';

$first = padArrFirst ( $orders, $isOpen ) ['number'];
$last = padArrLast ( $orders, $isPaid ) ['number'];

?>
<p>50 or more, largest first:
{large~}
{$number} ({$total}){notLast@large}, {/notLast@large}
{~/large}</p>
<p>A customer starting with C: {withC~}
{$customer}{notLast@withC}, {/notLast@withC}
{~/withC}</p>
<p>Not paid yet: {unpaid~}
{$number}{notLast@unpaid}, {/notLast@unpaid}
{~/unpaid}</p>
<p>First open order: {$first}, last paid order: {$last}</p>

50 or more, largest first: 101 (120.00), 103 (89.90)

A customer starting with C: Cleo, Carl

Not paid yet: 102, 104

First open order: 102, last paid order: 103

Groups and numbers

padArrGroupBy answers the rows in groups - the groups in the order their first row came, each group a list. padArrSum, padArrAvg, padArrMin and padArrMax work on the numbers among the values, or among a field of every row: a NULL or a text that is no number is left out rather than counted as 0. The sum of no numbers is 0, the others answer NULL.

PHP
fragments/arrays_5.php
HTML
fragments/arrays_5.pad
Result
<?php

$sales
= [
[
'region' => 'North', 'amount' => '120' ],
[
'region' => 'South', 'amount' => '80' ],
[
'region' => 'North', 'amount' => '200' ],
[
'region' => 'East', 'amount' => NULL ],
[
'region' => 'South', 'amount' => '40' ]
];

$groups = padArrGroupBy ( $sales, 'region' );
$regions = [];

foreach (
$groups as $region => $rows )
$regions [] = [
'region' => $region,
'sales' => count ( $rows ),
'total' => padArrSum ( $rows, 'amount' ),
'best' => padArrMax ( $rows, 'amount' ) ?? '-'
];

$sum = padArrSum ( $sales, 'amount' );
$avg = padArrAvg ( $sales, 'amount' );
$min = padArrMin ( $sales, 'amount' );

?>
{regions}
<p>{$region}: {$sales} sales, {$total} in all, best {$best}</p>
{/regions}
<p>All regions: {$sum}, on average {$avg}, the smallest {$min}</p>

North: 2 sales, 320 in all, best 200

South: 2 sales, 120 in all, best 80

East: 1 sales, 0 in all, best -

All regions: 440, on average 110, the smallest 40

Reshaping

padArrDot makes a nested array one level with dot path keys, and padArrUndot makes it nested again. padArrFlatten answers the values of nested arrays as one list, to a depth when one is given; padArrWrap makes any value an array - NULL none, an array itself, anything else one item.

PHP
fragments/arrays_6.php
HTML
fragments/arrays_6.pad
Result
<?php

$settings
= [
'mail' => [
'from' => 'shop@example.com',
'smtp' => [ 'host' => 'localhost', 'port' => 25 ]
],
'theme' => 'dark'
];

$flat = padArrDot ( $settings );

$changes = padArrUndot ( [ 'mail.smtp.port' => 587,
'theme' => 'light' ] );
$port = $changes ['mail'] ['smtp'] ['port'];

$nested = [ 'php', [ 'pad', [ 'templates', 'helpers' ] ] ];
$tags = implode ( ', ', padArrFlatten ( $nested ) );

$wrapped = count ( padArrWrap ( 'one recipient' ) ) . ' / '
. count ( padArrWrap ( NULL ) );

?>
{flat}
<p>{key@flat} = {$flat}</p>
{/flat}
<p>Undot: the port is {$port}</p>
<p>Flatten: {$tags}</p>
<p>Wrap: {$wrapped}</p>

mail.from = shop@example.com

mail.smtp.host = localhost

mail.smtp.port = 25

theme = dark

Undot: the port is 587

Flatten: php, pad, templates, helpers

Wrap: 1 / 0

The functions

function what it answers
padArrGet ( $target, $key, $default = NULL ) the value at a dot path, or the default; a NULL key is the target itself
padArrSet ( &$array, $key, $value ) sets the value at a dot path, making the levels on the way; the array
padArrHas ( $array, $keys ) whether every dot path given exists - a NULL value exists
padArrForget ( &$array, $keys ) removes one or several dot paths; the array
padArrOnly ( $array, $keys ) the top-level keys given, in the order given
padArrExcept ( $array, $keys ) the array without the keys given - dot paths too
padArrPluck ( $rows, $value, $key = NULL ) one field of every row, keyed by another field when given
padArrWhere ( $rows, $key, $operator, $value ) the rows that pass, keys kept
padArrFirst ( $array, $callback = NULL, $default = NULL ) the first value, or the first a callback passes, or the default
padArrLast ( $array, $callback = NULL, $default = NULL ) the same from the end
padArrGroupBy ( $rows, $key ) the rows in groups by a field, each group a list
padArrKeyBy ( $rows, $key ) the rows keyed by a field, a later row winning
padArrSortBy ( $rows, $key, $descending = FALSE ) the rows sorted by a field, stable; string keys kept, a list numbered again
padArrFlatten ( $array, $depth = INF ) the values, nested arrays opened up
padArrDot ( $array, $prepend = '' ) a nested array as one level with dot path keys
padArrUndot ( $array ) dot path keys made nested arrays again
padArrWrap ( $value ) the value as an array
padArrSum / Avg / Min / Max ( $rows, $key = NULL ) the sum, average, smallest, largest of the numbers

A field to work on - $key, $value - is a dot path or a callback (a Closure, given the row and its key); a string is always a dot path. Where a list is expected, NULL, an empty string or any other plain value is an empty list, so the answer of a query that found nothing goes through without a check. A wrong argument - an unknown operator, a negative depth, a key that is no key - is reported as a PAD error naming the function.