Search by

patterns / guard

ecom

Guard pattern - validate inputs and preconditions early, returning a Result instead of throwing.

v1.0.0 2026-10-04 05:10 UTC

This package is auto-updated.

Last update: 2026-10-28 01:26:04 UTC


README

Validate inputs and preconditions early — returning a Result, never throwing.

Part of the Patterns collection: small, rock-solid, dependency-free building blocks. One pattern, one package.

  • Package: patterns/guard
  • Namespace: Patterns\Guard
  • Depends on: patterns/result
  • PHP: >=8.1

Install

composer require patterns/guard

Why

Validation scattered through constructors is noisy, repetitive, and inconsistent:

// Before — mixed with assignment, ad-hoc messages, throws
public static function create(array $data): self
{
    if (!isset($data['name'])) {
        throw new \Exception("Required:name");
    }
    if (!isset($data['price']) || $data['price'] <= 0) {
        throw new \Exception("Price must be positive");
    }
    // ...
}
// After — declarative, consistent messages, one Result
use Patterns\Guard\Guard;
use Patterns\Guard\Numbers;
use Patterns\Guard\Strings;
use Patterns\Result;

public static function create(array $data): Result
{
    $check = Guard::combine([
        Strings::nonEmpty($data['name'] ?? null, 'name'),
        Numbers::positive($data['price'] ?? null, 'price'),
    ]);

    if ($check->isFailure()) {
        return Result::fail($check->errorMessage());   // "name cannot be empty; price must be positive"
    }

    return Result::success(new self(/* ... */));
}

Every guard returns a Result — success carries the validated value, failure carries a consistent message. Guard::combine() merges messages with "; ".

API

All guards accept mixed and return a failed Result for the wrong type (they never throw a TypeError), so they're safe on raw request/DB data.

Common — Patterns\Guard\Guard

Method Fails when
Guard::defined($value, $name) value is null
Guard::assert($condition, $message) condition is false
Guard::combine(array $results) any result is a failure (messages joined with "; ")
Guard::isTrue($value, $name) not the boolean true

Strings — Patterns\Guard\Strings

nonEmpty($v, $name) · pattern($v, $regex, $name, $message?) · email($v, $name) · minLength($v, $min, $name) · maxLength($v, $max, $name) · length($v, $min, $max, $name) · letters($v, $name) · alphanumeric($v, $name)

Numbers — Patterns\Guard\Numbers

positive($v, $name) · nonNegative($v, $name) · range($v, $min, $max, $name) · min($v, $min, $name) · max($v, $max, $name) · integer($v, $name)

Arrays — Patterns\Guard\Arrays

nonEmpty($v, $name) · minLength($v, $min, $name) · maxLength($v, $max, $name) · includes($v, $item, $name) · every($v, $predicate, $name, $message)

Objects — Patterns\Guard\Objects

hasProperty($v, $property, $name) · nonEmpty($v, $name) (accepts objects and associative arrays)

Dates — Patterns\Guard\Dates

inPast($v, $name) · inFuture($v, $name) · before($v, $threshold, $name) · after($v, $threshold, $name)

Usage

use Patterns\Guard\Guard;
use Patterns\Guard\Numbers;
use Patterns\Guard\Strings;

$check = Guard::combine([
    Strings::email($email, 'email'),
    Strings::minLength($password, 8, 'password'),
    Numbers::range($age, 18, 120, 'age'),
]);

if ($check->isSuccess()) {
    // proceed with validated values
}

PHP notes (vs the TypeScript original)

  • Grouped static classes mirror Guard.String.nonEmpty → Strings::nonEmpty, Guard.Number.positive → Numbers::positive, etc.
  • Guards validate the type too (mixed in, failed Result out) instead of relying on declared types — so Strings::nonEmpty(123, 'name') fails with "name must be a string" rather than a fatal TypeError.
  • Guard.isTrue() is static (in the TS source it was accidentally an instance method, so Guard.isTrue(...) was unreachable).
  • Objects accepts arrays as well as objects.

Tests

composer install
composer test
# or
vendor/bin/phpunit

License

MIT.