Value helpers

A page's PHP spends much of its time asking whether a value is there, picking a default when it is not, and guarding the parts that may fail. These helpers say each of those in one call - the functions Laravel knows as blank(), filled(), value(), transform(), tap(), retry(), rescue() and once(). They are loaded on every request, like the rest of pad/lib/.

function what it does
padBlank ( $value ) TRUE for NULL, '' or only whitespace, an empty array, an empty Countable - FALSE for 0, '0', FALSE and every other value
padFilled ( $value ) the opposite of padBlank
padValue ( $value, ...$args ) a Closure called with the arguments, anything else answered as it is
padTransform ( $value, $callback, $default ) the callback's answer for a filled value, the default for a blank one
padTap ( $value, $callback ) hands the value to the callback and answers the value itself
padRetry ( $times, $callback, $sleepMilliseconds, $when ) calls the callback until it stops throwing, at most $times times
padRescue ( $callback, $rescue, $report ) the callback's answer, or the rescue value when it throws
padOnce ( $callback ) runs the callback once per request for the line that calls it

Blank and filled

PHP's empty() calls a quantity of '0' empty and a remark of a few spaces filled. padBlank is the test a page means: nothing there is NULL, text of nothing but whitespace (a non-breaking space too), an empty array and an empty Countable. A number and a boolean are always values.

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

// A form came back: which fields did the visitor fill in?
// PHP's empty() calls the zero quantity empty and the spaces
// filled - padBlank calls them the other way round.

$posted = [ 'name' => 'Ann',
'quantity' => '0',
'remarks' => ' ',
'coupon' => NULL,
'tags' => [] ];

$answers = [];

foreach (
$posted as $name => $value )
$answers [] = [
'name' => $name,
'value' => json_encode ( $value ),
'blank' => padBlank ( $value ) ? 'blank' : 'filled',
'empty' => empty ( $value ) ? 'empty' : 'not empty'
];

?>
<table border="1" cellpadding="4" cellspacing="0">
<tr><th>field</th><th>value</th>
<th>padBlank</th><th>empty()</th></tr>
{answers}
<tr><td>{$name}</td><td><code>{$value}</code></td>
<td>{$blank}</td><td>{$empty}</td></tr>
{/answers}
</table>
field value padBlank empty()
name "Ann" filled not empty
quantity "0" filled empty
remarks " " blank not empty
coupon null blank empty
tags [] blank empty

Defaults and callbacks

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

$user
= [ 'name' => 'ann smith',
'nickname' => '',
'city' => NULL ];

// The callback runs for a filled value only; a blank one gets
// the default.

$name = padTransform ( $user ['name'],
fn (
$n ) => ucwords ( $n ),
'Guest' );
$nickname = padTransform ( $user ['nickname'],
fn (
$n ) => "\"$n\"",
'no nickname' );

// A default given as a Closure is made only when it is needed.

$unknown = fn () => 'somewhere';
$city = padValue ( $user ['city'] ?? $unknown );

// padTap looks at a value in passing and hands it on unchanged.

$noted = '';
$note = function ( $sum ) use ( &$noted ) {
$noted = "noted $sum";
};

$total = padTap ( array_sum ( [ 12.5, 30, 7.5 ] ), $note );

?>
<p>{$name} - {$nickname} - {$city}</p>
<p>Total {$total} ({$noted})</p>

Ann Smith - no nickname - somewhere

Total 50 (noted 50)

Retry and rescue

padRetry calls the callback with the number of the attempt until it answers without throwing. Between two attempts it waits $sleepMilliseconds - one number, or a list with a wait per attempt whose last one repeats. $when, a callable given the exception, decides whether another attempt is worth it; when it says no, or after the last attempt, the exception is thrown on.

padRescue lets a part of a page fail alone: when the callback throws - an Exception or an Error - the answer is the rescue value, a Closure given the exception. With $report (on unless it is FALSE) the failure is written to PHP's error log, so a rescued fault is not lost.

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

// A service that fails twice before it answers, as a busy
// remote one might: three attempts, 10 milliseconds apart.

$attempts = 0;

$service = function ( $attempt ) use ( &$attempts ) {
$attempts = $attempt;
if (
$attempt < 3 )
throw new
RuntimeException ( 'timeout' );
return
'EUR 1 = USD 1.17';
};

$rate = padRetry ( 3, $service, 10 );

// A part of the page that may fail fails alone: the page shows
// a fallback instead.

$weather = function () {
throw new
RuntimeException ( 'the weather service is down' );
};

$forecast = padRescue (
$weather,
fn (
$e ) => 'No forecast today - ' . $e->getMessage (),
FALSE
);

?>
<p>{$rate}, on attempt {$attempts}</p>
<p>{$forecast}</p>

EUR 1 = USD 1.17, on attempt 3

No forecast today - the weather service is down

Once per request

padOnce runs its callback the first time its line is reached in a request, and answers that first result each later time - in a loop, or in a function several parts of the page call. A callback that throws gave no result, so the next call runs it again. The place is the file and line of the call: two padOnce calls on one line share their result.

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

// A function the page calls for every row reads its settings
// once: padOnce answers the first result each later time its
// line is reached.

$reads = 0;

$currency = function () use ( &$reads ) {
return
padOnce ( function () use ( &$reads ) {
$reads++;
return
'EUR';
} );
};

$orders = [];

foreach ( [
120, 75, 310 ] as $amount )
$orders [] = [ 'amount' => $amount,
'currency' => $currency () ];

?>
{orders}
<p>{$currency} {$amount}</p>
{/orders}
<p>The settings were read {$reads} time.</p>

EUR 120

EUR 75

EUR 310

The settings were read 1 time.

Mistakes

A callback that is no function - a misspelled name - and a number of attempts below 1 are reported as errors that name the function; the function then answers NULL.