Skip to the content.

Pair

Lightweight PHP framework for fast server-rendered web applications.

Website · Wiki · Boilerplate · Issues · Releases · Security

CI Total Downloads Latest Tagged Release Development Branch License Pair v4 PHP Requirement

Pair is a lightweight PHP framework for server-rendered web applications. It focuses on fast setup, clear MVC routing, practical ActiveRecord-style ORM features, API tooling, progressive enhancement and optional integrations without heavy tooling.

Pair is designed for small and medium web applications where you want a clear PHP/MySQL stack, server-rendered pages, useful defaults, low operational overhead and a framework that remains easy to inspect, extend and maintain.

Version status

Line Status Recommended use
Pair v4 Stable / production Current applications and new development
Pair v3 Maintenance Existing applications pinned to v3 releases

Pair v4 is the current stable line and is used in production across the maintainer’s applications. Pair v3 remains available as a maintenance line for existing applications that have not yet migrated.

Quick start

1. Install Pair v4

composer require viames/pair:^4.0

2. Bootstrap the application

<?php

use Pair\Core\Application;

require __DIR__ . '/vendor/autoload.php';

$app = Application::getInstance();
$app->run();

3. Start from the boilerplate

For a ready-to-use application structure, start from:

https://github.com/viames/pair_boilerplate

Why Pair

Well suited to AI-assisted development

Pair grew out of practical web application development and has a public repository history dating back to 2017. It was not designed around generated code; its advantage for AI-assisted development comes from the same qualities that help human maintainers understand it: a compact codebase, predictable component boundaries, limited hidden behavior and conventions that favor small, reviewable changes.

These properties do not make AI-generated changes automatically correct. They make proposed changes easier to constrain, inspect, test and review.

Core features

Routing and MVC

Default route format after the base path:

/<module>/<action>/<params...>

Example:

example.com/user/login

Typical legacy MVC module structure:

/modules/user/controller.php
/modules/user/model.php
/modules/user/viewLogin.php
/modules/user/layouts/login.php

In Pair v4, legacy Pair\Core\Controller and Pair\Core\View remain available as migration bridges, but new modules should prefer explicit controllers and responses.

Docs: Router

ActiveRecord ORM

Pair maps PHP classes to database tables and supports practical ORM features such as:

Docs: ActiveRecord

Pair v4 explicit controller path

Pair v4 prefers explicit responses over hidden controller/view bootstrapping.

<?php

use Pair\Web\Controller;
use Pair\Web\PageResponse;

final class UserController extends Controller {

	public function defaultAction(): PageResponse {

		$state = new class ('Hello Pair v4') {

			public function __construct(public string $message) {}

		};

		return $this->page('default', $state, 'User');

	}

}

Minimal layout example:

<main class="user-page">
	<h1><?= htmlspecialchars($state->message, ENT_QUOTES, 'UTF-8') ?></h1>
</main>

For reusable output contracts, Pair v4 prefers ReadModel objects built explicitly from persistence records.

API and OpenAPI tooling

Pair includes API helpers for CRUD-oriented resources and explicit response contracts. In Pair v4, OpenAPI generation for CRUD resources can use readModel contracts, so generated response schemas describe the public output model instead of leaking persistence classes.

Useful docs:

Log bar and debugging

Pair includes a built-in log bar for development and diagnostics:

Frontend helpers

PairUI

PairUI is a dependency-free helper for progressive enhancement in server-rendered applications.

Main directives:

Docs: PairUI.js

PWA helpers

Available assets:

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>

Form validation presets

Pair can share common form validation rules between PHP and JavaScript through FormValidationPreset, FormControl::preset() and PairValidation.js.

<script src="/assets/PairValidation.js" defer></script>
$form->emailAddress('email')->required();
$form->iban('ibanCode');
$form->webUrl('website');
$form->italianFiscalCode('fiscalCode');
$form->italianVatNumber('vatNumber');

Italy-specific presets use explicit Italian names or it.* preset identifiers, for example italianFiscalCode() and it.vat_number. International presets such as iban, email, url, bic, e164_phone, uuid, ip_address, mac_address, hex_color, ean13 and slug remain territory-neutral.

Important notes:

Passkey quick start

Backend:

class ApiController extends \Pair\Api\PasskeyController {}

This enables:

POST   /api/passkey/login/options
POST   /api/passkey/login/verify
POST   /api/passkey/register/options
POST   /api/passkey/register/verify
GET    /api/passkey/list
DELETE /api/passkey/revoke/{id}

Native iOS and Android apps use the cookie-free mobile contract exposed by Pair\Api\ApiController:

POST   /api/auth/passkey/options
POST   /api/auth/passkey/verify
GET    /api/auth/passkeys
POST   /api/auth/passkeys/options
POST   /api/auth/passkeys/verify
DELETE /api/auth/passkeys/{id}

PairMobileKit uses AuthenticationServices and PairMobileAndroid uses Android Credential Manager. Both issue the same Pair Bearer session as password login and keep passkey creation, listing, and revocation under the authenticated account. Only the singular /auth/passkey/options and /auth/passkey/verify login routes are public. Every plural /auth/passkeys* management route requires a Bearer token and returns 401 when it is missing; revocation is owner-scoped, hides foreign IDs as not found, and is idempotent for an already revoked owned credential.

Optional integrations

Pair includes optional support for services and runtime integrations such as:

In Pair v4 these integrations should be exposed through Runtime Extensions and manually registered adapters. This is separate from Installable Packages, the ZIP/manifest mechanism used for modules, templates, providers and custom package records.

Configuration reference: Configuration (.env)

Pair v4 tools

Generate Pair v4 skeletons:

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_amount

The generator writes explicit Pair v4 files and avoids overwriting user-edited files unless --force is provided.

Additional migration and design docs:

Upgrading

Pair v4 ships a composable upgrader for each supported major-version boundary. Start from a clean working tree or a verified backup, run every required step in dry-run mode, review the warnings, and then repeat it in write mode.

Use this sequence for the version currently installed by the application:

From a Pair application that has Pair installed as a dependency:

php vendor/viames/pair/scripts/upgrade-to-v2.php --dry-run
php vendor/viames/pair/scripts/upgrade-to-v2.php --write

php vendor/viames/pair/scripts/upgrade-to-v3.php --dry-run
php vendor/viames/pair/scripts/upgrade-to-v3.php --write

php vendor/viames/pair/scripts/upgrade-to-v4.php --dry-run
php vendor/viames/pair/scripts/upgrade-to-v4.php --write

From inside the Pair repository itself:

composer run upgrade-to-v2 -- --dry-run --path=/absolute/app/path
composer run upgrade-to-v2 -- --write --path=/absolute/app/path
composer run upgrade-to-v3 -- --dry-run --path=/absolute/app/path
composer run upgrade-to-v3 -- --write --path=/absolute/app/path
composer run upgrade-to-v4 -- --dry-run
composer run upgrade-to-v4 -- --write

The v1 upgrader retains config.php as a safety copy after generating .env. If the configuration contains PHP expressions that cannot be represented safely as scalar environment values, it leaves both files untouched and reports a blocking error.

The upgrade tools are conservative by design. They rewrite low-risk patterns automatically, return a non-zero exit code when a requested write fails, and report application-specific code that still requires manual migration. See UPGRADE_V4.md for the detailed sequence and validation checklist.

Requirements

Software Minimum Recommended Notes
PHP 8.4.1 8.5 Required by Composer
Apache 2.4 2.4+ mod_rewrite recommended
MySQL 8.0 8.0+ utf8mb4, utf8mb4_unicode_ci, InnoDB
Composer 2.x Latest stable Required for package installation

Required PHP extensions:

Recommended or optional extensions:

Example project

Start from the boilerplate project to bootstrap a new application quickly:

https://github.com/viames/pair_boilerplate

Documentation

Main documentation lives in the Wiki:

https://github.com/viames/pair/wiki

Useful pages:

Development

Install dependencies:

composer install

Run tests:

composer test

Run the v4 benchmark harness:

composer run benchmark-v4

The benchmark harness measures:

Support

Changelog

Version history is available in GitHub Releases:

https://github.com/viames/pair/releases

Security

If you discover a security issue, follow the private reporting guidance in SECURITY.md.

Contributing

Feedback, code contributions and documentation improvements are welcome via pull request.

License

MIT