Pair is a lightweight PHP framework for server-rendered web applications. It focuses on fast setup, clear MVC routing, practical ORM features, and optional integrations (S3, SES, Telegram, Stripe, Push, Passkey) without heavy tooling.
Pair v4 is currently in alpha and may include breaking changes while the next major version is under development.
Pair v3 is the current stable release line on the v3 branch and through the ^3.0 tags.
Pair v4 now has an explicit core path built around:
Pair\Http\Inputfor immutable request inputPair\Data\ReadModelfor reusable HTML/API read contractsPair\Web\Controller+Pair\Web\PageResponsefor server-rendered actionsPair\Http\JsonResponsefor explicit JSON endpointsPair\Api\ApiExposable::readModelfor CRUD output contracts
OpenAPI generation for CRUD resources now follows the explicit response contract too: when a resource defines readModel, the generated response schema is built from that read model instead of the persistence class.
Pair\Core\Controller and Pair\Core\View now remain only as legacy MVC bridges in Pair v4 and emit deprecation notices in development or staging environments.
The Pair v3 to v4 upgrader is conservative on purpose: it rewrites only the low-risk patterns automatically and reports legacy controller/view flows that still need a manual migration.
For classic MVC modules it now also generates readonly *PageState skeletons from legacy View::assign() usage, so migration work starts from concrete typed-state files instead of ad-hoc arrays or magic view variables.
composer require viames/pair<?php
use Pair\Core\Application;
require 'vendor/autoload.php';
$app = Application::getInstance();
$app->run();vendor/bin/pair make:module orders
vendor/bin/pair make:api api
vendor/bin/pair make:crud order --table=orders --fields=id,customer_id,total_amountThe generator writes explicit Pair v4 files and avoids overwriting user-edited files unless --force is provided.
- Small and fast for small/medium projects.
- MVC structure with SEO-friendly routing.
- ActiveRecord-style ORM with automatic type casting.
- Plugin-oriented architecture (modules/templates).
- Good defaults for timezone, logging, and framework utilities.
- Optional third-party integrations when needed.
Pair maps classes to DB tables and supports automatic casts (int, bool, DateTime, float, csv), relation helpers, and caching-oriented query helpers.
Docs: ActiveRecord
Default route format (after base path):
/<module>/<action>/<params...>
Example: example.com/user/login
- module:
/modules/user - controller legacy path:
/modules/user/controller.php(extendsPair/Core/Controller.php, legacy bridge in v4) - action:
loginAction()when present - auto-loaded by the legacy MVC bridge:
model.php,viewLogin.php(UserViewLogin), and/modules/user/layouts/login.php
Docs: Router
Pair v4 prefers explicit responses over hidden controller/view bootstrapping:
<?php
use Pair\Web\Controller;
use Pair\Web\PageResponse;
final class UserController extends Controller {
/**
* Render the default user page.
*/
public function defaultAction(): PageResponse {
$state = new class ('Hello Pair v4') {
/**
* Store the page message.
*/
public function __construct(public string $message) {}
};
return $this->page('default', $state, 'User');
}
}For reusable output contracts, Pair v4 prefers ReadModel objects built explicitly from persistence records.
Layout files in the v4 path should remain mostly HTML. Optional file-level preambles such as declare(strict_types=1) or /** @var UserPageState $state */ are only IDE/static-analysis hints and are not part of the runtime contract.
Minimal layout example:
<main class="user-page">
<h1><?= htmlspecialchars($state->message, ENT_QUOTES, 'UTF-8') ?></h1>
</main>Legacy Pair\Core\Controller and Pair\Core\View remain available only as a migration path and should not be used for new Pair v4 modules.
Built-in log bar for loaded objects, memory usage, timings, SQL traces, backtraces, and custom debug messages.
Dependency-free helper for progressive enhancement in server-rendered apps (assets/PairUI.js).
Main directives:
data-text,data-html,data-show,data-ifdata-class,data-attr,data-prop,data-styledata-model,data-on,data-each
Docs: PairUI.js
Available assets:
PairPWA.jsPairSW.jsPairRouter.jsPairSkeleton.jsPairDevice.jsPairPasskey.js
Minimal frontend setup:
<script src="/assets/PairUI.js" defer></script>
<script src="/assets/PairPWA.js" defer></script>
<script src="/assets/PairRouter.js" defer></script>
<script src="/assets/PairSkeleton.js" defer></script>
<script src="/assets/PairDevice.js" defer></script>
<script src="/assets/PairPasskey.js" defer></script>Important notes:
- Keep progressive enhancement.
- Service workers require HTTPS (except localhost).
- Use a single SW URL if you also enable Push.
Backend:
class ApiController extends \Pair\Api\PasskeyController {}This enables:
POST /api/passkey/login/optionsPOST /api/passkey/login/verifyPOST /api/passkey/register/options(requiressid)POST /api/passkey/register/verify(requiressid)GET /api/passkey/list(requiressid)DELETE /api/passkey/revoke/{id}(requiressid)
Pair includes optional support for services such as:
Configuration reference: Configuration (.env)
If you are upgrading a Pair v3 application to Pair v4:
composer run upgrade-to-v4 -- --dry-run
composer run upgrade-to-v4 -- --writeTo test unreleased Pair 4 development code from main:
composer require viames/pair dev-mainAdditional migration and design docs:
Main docs live in the Wiki. The release and branching workflow for the v3 stable / v4 dev transition is documented in RELEASING.md.
Useful pages:
- Generator
- Application
- Controller
- View
- ApiExposable
- CrudController
- Form
- Collection
- Push notifications
- index.php
- .htaccess
- classes
| Software | Recommended | Minimum | Configuration |
|---|---|---|---|
| Apache | 2.4+ | 2.4 | modules: mod_rewrite |
| MySQL | 8.0+ | 8.0 | character_set: utf8mb4 collation: utf8mb4_unicode_ci storage_engine: InnoDB |
| PHP | 8.4+ | 8.3 | Composer-required extensions: curl, intl, json, mbstring, PDO |
Runtime notes:
pdo_mysqlis required when using the default MySQL driver (Pair\\Orm\\Database).fileinfois strongly recommended for reliable MIME detection in uploads.opensslis required only for Passkey/WebAuthn features.pcreandReflectionare part of standard PHP 8+ builds.
Start from pair_boilerplate to bootstrap a new app quickly.
The repository includes a lightweight benchmark harness for the new v4 path:
composer run benchmark-v4It measures:
- minimal request bootstrap primitives
- simple server-rendered page rendering
- simple JSON endpoint payload preparation
- record-to-read-model mapping cost
- response serialization cost
- Issues: github.com/viames/pair/issues
- Wiki: github.com/viames/pair/wiki
- Source: github.com/viames/pair/tree/main/src
- Homepage: viames.github.io/pair
Version history is available in GitHub Releases: github.com/viames/pair/releases
If you discover a security issue, follow the private reporting guidance in SECURITY.md.
Feedback, code contributions, and documentation improvements are welcome via pull request.
MIT
