Skip to content

Recipes

Runnable, self-contained scripts for common problems. Every file lives in examples/ in the repository and can be run directly with php examples/<file>.php once dependencies are installed (composer install).

Looking for the business context behind these snippets — the risk, the legacy code, why it matters? See Case Studies for end-to-end walkthroughs: transactional email, SAP integration, and contract validation.

Checkout flow with coupons and payment authorization

Option for an optional coupon code, Result chaining a discount step into a payment authorization step.

examples/option-result.php

Customer registration with a full DTO

A complete DTO example: schema with trimmed(), regex(), bounded int, an optional field via Schema::option(), and structured error reporting through ValidationErrorBag.

examples/schema-dto.php

Repository lookup with Option

A repository returns Option instead of null; a service layer converts absence into a typed Result error only where it actually needs one, using okOr() and andThen().

php
final class CustomerRepository
{
    public function findById(int $id): Option
    {
        // ...
        return Option::none();
    }
}

$service = static function (CustomerRepository $repo, int $id): Result {
    return $repo->findById($id)
        ->okOr('customer_not_found')
        ->andThen(fn (array $customer): Result =>
            $customer['active'] ? Result::ok($customer['name']) : Result::err('customer_inactive')
        );
};

examples/recipe-repository-lookup.php

Wrapping a legacy, exception-throwing call

At a boundary with a third-party SDK or legacy code that only communicates failure via exceptions, wrap the call once instead of letting exceptions leak into Result-typed code:

php
function tryCatch(callable $fn): Result
{
    try {
        return Result::ok($fn());
    } catch (\Throwable $e) {
        return Result::err($e);
    }
}

examples/recipe-safe-external-call.php

Batch import with per-row error reporting

Validate an entire batch (e.g. a CSV import) in one pass with Schema::arrayOf() — errors report the row index in the path ($[1].email). For partial imports, validate row by row instead and keep both an imported and a rejected bucket.

examples/recipe-batch-import.php

More async recipes

For more before/after patterns (validating filters, standardizing JSON error responses, converting legacy null/false returns), see the Practical Recipes guide in the repository.

Using an AI assistant with Maybe

If you're using an AI coding assistant (Claude, Copilot, Cursor, etc.) to write code against this library, point it at llms.txt — a condensed, exact API reference meant for LLM consumption, so it doesn't have to guess method names or signatures.