PHPackages                             raoh/raoh - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Validation &amp; Sanitization](/categories/validation)
4. /
5. raoh/raoh

ActiveLibrary[Validation &amp; Sanitization](/categories/validation)

raoh/raoh
=========

Parse Don't Validate — composable decoder library for PHP 8.2+

v0.5.0(1mo ago)112[1 issues](https://github.com/kawasima/raoh-php/issues)Apache-2.0PHPPHP ^8.2

Since Apr 17Pushed 1mo agoCompare

[ Source](https://github.com/kawasima/raoh-php)[ Packagist](https://packagist.org/packages/raoh/raoh)[ Docs](https://github.com/kawasima/raoh-php)[ RSS](/packages/raoh-raoh/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (2)Versions (4)Used By (0)

raoh-php
========

[](#raoh-php)

[![License](https://camo.githubusercontent.com/114a8f0289e0cc41aae05346d6ad757b536c400ebae8f54eac34ad44f3296c6f/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f6b61776173696d612f72616f68)](LICENSE)[![PHP](https://camo.githubusercontent.com/ccaa43fc634d348cffccb1d8db7b55d9f17c5d46944bc99a15c3c982724b387d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e322532422d3737374242343f6c6f676f3d706870266c6f676f436f6c6f723d7768697465)](https://www.php.net/)

PHP port of [Raoh](https://github.com/kawasima/raoh) — a decoder library for turning untyped boundary input into typed domain values.

It is built around a parse-don't-validate approach:

- decode at the boundary
- keep invalid states out of the domain model
- return failures as values instead of throwing
- attach structured errors to precise paths

raoh-php is closer to a parser/decoder library than to a traditional validation library.

If you are coming from a validator-oriented library, the main difference in feel is this:

- you do not validate an already-constructed domain object
- you decode raw input into a domain object
- object construction happens only after decoding succeeds

Requirements
------------

[](#requirements)

- PHP 8.2+
- Composer

Install and run tests:

```
composer install
./vendor/bin/phpunit
```

Package Layout
--------------

[](#package-layout)

```
src/
├── Result.php              # abstract readonly class (Ok / Err parent)
├── Ok.php                  # final readonly class Ok
├── Err.php                 # final readonly class Err
├── Path.php                # JSON Pointer path (immutable cons-list)
├── Issue.php               # single error (path, code, message, meta)
├── Issues.php              # error collection (accumulation)
├── Decoder.php             # interface Decoder
├── DecoderTrait.php        # map / flatMap / pipe / asList defaults
├── CallableDecoder.php     # closure → Decoder adapter
├── Encoder.php             # interface Encoder
├── CallableEncoder.php     # closure → Encoder adapter
├── ErrorCodes.php          # enum ErrorCodes: string
├── Decoders.php            # utility: lazy / withDefault / recover / oneOf
├── StaticConstructor.php   # trait for first-class callable constructors
├── Presence.php            # tri-state presence base
├── Absent.php              # field not present
├── PresentNull.php         # field explicitly null
├── Present.php             # field present with a value
│
├── Builtin/
│   ├── StringDecoder.php
│   ├── IntDecoder.php
│   ├── FloatDecoder.php
│   └── BoolDecoder.php
│
├── Combinator/
│   └── Combiner.php        # variadic applicative combinator
│
└── Boundary/
    ├── Array_/
    │   ├── functions.php       # decoder use function imports
    │   ├── ArrayDecoders.php
    │   └── Encode/
    │       └── functions.php   # encoder use function imports
    └── Json/
        ├── functions.php       # decoder use function imports
        ├── JsonDecoders.php
        └── Encode/
            └── functions.php   # encoder use function imports

```

Core Model
----------

[](#core-model)

### `Result`

[](#result)

Decoding returns a value instead of throwing:

- `Ok` for success
- `Err` for failure

`Result` supports:

- `map(...)`
- `flatMap(...)`
- `fold(...)`
- `getOrThrow()`
- `orElseThrow(...)`
- `Result::map2(...)` — applicative combination of two results
- `Result::traverse(...)` — list traversal with full error accumulation

### `Issue`, `Issues`, and `Path`

[](#issue-issues-and-path)

Each error includes:

- `path`
- `code`
- `message`
- `meta`

Paths use JSON Pointer notation (RFC 6901), for example:

- `/email`
- `/address/city`
- `/items/0/name`

`Issues` can be merged, rebased, flattened, formatted, or converted to JSON-like data.

### `Decoder`

[](#decoder)

The core abstraction is:

```
interface Decoder {
    public function decode(mixed $in, ?Path $path = null): Result;
}
```

A decoder reads an input value and produces either:

- a typed value wrapped in `Ok`
- structured issues wrapped in `Err`

Two boundary implementations are included:

- `Raoh\Boundary\Array_` — PHP arrays and form data
- `Raoh\Boundary\Json` — raw JSON strings

### `Encoder`

[](#encoder)

The symmetric counterpart to `Decoder` is:

```
interface Encoder {
    public function encode(mixed $value): mixed;
    public function contramap(callable $f): Encoder;
    public function andThen(Encoder $next): Encoder;
}
```

An encoder converts a trusted domain object into an external representation (array, JSON string, etc.) and never fails.

- `contramap($f)` — pre-process the input before encoding (useful for unwrapping value objects)
- `andThen($next)` — post-process the output after encoding (useful for chaining transformations)

What It Feels Like
------------------

[](#what-it-feels-like)

The normal raoh-php workflow looks like this:

1. Start from raw input such as a JSON string or PHP array.
2. Define small decoders for domain primitives such as `Email`, `Age`, or `UserId`.
3. Combine them into object decoders.
4. If decoding succeeds, you get a fully-typed value.
5. If decoding fails, you get structured issues with paths.

That means the "happy path" looks like object construction, while the failure path looks like machine-readable diagnostics.

Quick Start
-----------

[](#quick-start)

### Decode an array into a domain object

[](#decode-an-array-into-a-domain-object)

```
