Compose small single-purpose validators into a single check that either passes or returns a list of every problem. Compare with imperative validation code that short-circuits on the first failure.
function validate(array $data): array {
$errors = [];
if (empty($data['name'])) { $errors[] = 'name is required'; }
if (empty($data['email']) || !str_contains($data['email'], '@')) { $errors[] = 'email invalid'; }
if (!is_int($data['age']) || $data['age'] < 0){ $errors[] = 'age invalid'; }
return $errors;
}Works. But each rule mixes “predicate” and “error message” together and you can’t reuse them.
Each rule is a pair: a predicate that says whether the field is VALID, and the message when it’s not:
use PinkCrab\FunctionConstructors\GeneralFunctions as F;
use PinkCrab\FunctionConstructors\Arrays as A;
use PinkCrab\FunctionConstructors\Comparisons as C;
use PinkCrab\FunctionConstructors\Strings as Str;
$rules = [
['name is required', fn($d) => C\notEmpty($d['name'] ?? '')],
['email invalid', fn($d) => Str\contains('@')($d['email'] ?? '')],
['age invalid', fn($d) => is_int($d['age'] ?? null) && $d['age'] >= 0],
];$validate = fn(array $data): array => array_values(array_map(
fn($rule) => $rule[0], // the message
array_filter($rules, fn($rule) => ! $rule[1]($data)) // rules that failed
));print_r($validate([
'name' => '',
'email' => 'nope',
'age' => 30,
]));
// ['name is required', 'email invalid']Every failing rule is collected. A well-formed input returns [] — truthy-empty, so $errors ? fail() : proceed() reads naturally.
['password too short', fn($d) => strlen($d['password']) >= 12].Comparisons\groupAnd / groupOr.If several rules share a shape (“field X is a non-empty string”), compose that predicate once:
$isPresentString = F\compose(
fn($d) => $d['name'] ?? null, // or parameterise
fn($v) => is_string($v) && $v !== ''
);Or build a rule factory:
$mustBeString = fn(string $field) => fn(array $d) =>
isset($d[$field]) && is_string($d[$field]) && $d[$field] !== '';
$rules = [
['name is required', $mustBeString('name')],
['title is required', $mustBeString('title')],
// ...
];