Skip to the content.

Pair v4 Design

Problem Statement

Pair v3 delivered speed and convenience, but too much of that convenience was concentrated in ActiveRecord, controller/view bootstrapping, and implicit serialization. The result was a framework that stayed productive for small apps, yet accumulated structural coupling in exactly the places Pair v4 now needs to simplify.

The main Pair v3 problems that Pair v4 addresses are:

Guiding Principles

Core Decisions

1. ActiveRecord stays, but only as persistence

Pair v4 keeps ActiveRecord, Query, and the ORM query path because they are still practical, lightweight, and performant. The breaking change is conceptual: ORM objects are no longer the natural public contract for views or API responses.

2. Read contracts are explicit

Pair v4 introduces:

The normal path is now:

ActiveRecord -> explicit read model -> HTML or JSON

This keeps data mapping explicit, typed, and cheap at runtime.

3. Request input is immutable

Pair v4 introduces Pair\Http\Input as a small immutable request object. It provides explicit merged access to query/body data plus typed accessors without introducing validation containers, reflection metadata, or request mutation layers.

4. The controller path is response-oriented

Pair v4 introduces:

The new controller flow is explicit:

Application now understands response-returning actions directly. This creates a clean v4 path without requiring a heavy rewrite of the old runtime.

5. CRUD no longer serializes raw records implicitly

Pair\Api\CrudController now requires one of these explicit contracts:

The raw ActiveRecord::toArray() fallback is no longer the normal path. Legacy resource adapters are still supported as a migration bridge, but the preferred contract is now an explicit read model.

SpecGenerator follows the same rule for documentation: the OpenAPI response schema is generated from the configured readModel when present, so runtime behavior and published contract stay aligned.

6. Migration gets a deliberate bridge

Pair\Data\Payload exists as a minimal readonly adapter for code that still needs an explicit object before a richer typed read model is introduced. It is intentionally small and documented as a migration bridge, not as the ideal end-state for application code.

What Pair v4 Removes or De-emphasizes

What Pair v4 Introduces

Trade-offs

Why This Is Simpler

Pair v4 does not add more concepts than Pair v3 had in practice. It replaces implicit concepts with smaller explicit ones.

Why This Is Faster

The new v4 path adds explicit mapping cost where data leaves persistence, but avoids repeated magic lookups and fallback behavior. Benchmarks are defined in scripts/benchmark-v4.php so the common-path overhead stays visible and reviewable.