Generator - viames/pair GitHub Wiki
Pair v4 ships a small CLI entrypoint at bin/pair.
The generator is intentionally simple: it writes explicit files, avoids hidden discovery, and does not overwrite user-edited files unless --force is provided.
Creates a Pair v4 web module:
vendor/bin/pair make:module ordersGenerated files:
modules/orders/controller.phpmodules/orders/classes/OrdersDefaultPageState.phpmodules/orders/layouts/default.php
Optional flags:
-
--with-jsaddsmodules/orders/assets/orders.js -
--with-testadds a PHPUnit skeleton undertests/Unit/Modules/ -
--path=/absolute/app/pathtargets another application directory -
--forcereplaces existing generated files
Generated layouts stay mostly HTML and read only from the typed $state object.
Generated controller shape:
use Pair\Web\Controller;
use Pair\Web\PageResponse;
require_once __DIR__ . '/classes/OrdersDefaultPageState.php';
/**
* Generated Pair v4 controller for the orders module.
*/
final class OrdersController extends Controller {
/**
* Return the default module page.
*/
public function defaultAction(): PageResponse {
return $this->page('default', new OrdersDefaultPageState('Orders'), 'Orders');
}
}Generated page-state shape:
use Pair\Data\ArraySerializableData;
use Pair\Data\ReadModel;
/**
* Generated typed state for the Orders default layout.
*/
final readonly class OrdersDefaultPageState implements ReadModel {
use ArraySerializableData;
/**
* Build the page state.
*/
public function __construct(public string $title) {}
/**
* Export the page state as an array.
*
* @return array<string, mixed>
*/
public function toArray(): array {
return [
'title' => $this->title,
];
}
}Creates a minimal API module controller:
vendor/bin/pair make:api apiGenerated file:
modules/api/controller.php
The controller extends Pair\Api\CrudController and includes a healthAction() returning an explicit JSON response.
Creates a CRUD model and read model skeleton:
vendor/bin/pair make:crud order --table=orders --fields=id,customer_id,total_amountGenerated files:
models/Order.phpclasses/OrderReadModel.php
--fields is optional. When omitted, the generator starts with id only so the skeleton is safe and minimal.
The generated model:
- extends
Pair\Orm\ActiveRecord - uses
Pair\Api\ApiExposable - declares
readModel - declares explicit
getBinds()
The generated read model:
- implements
Pair\Data\ReadModel - implements
Pair\Data\MapsFromRecord - exports an explicit array shape
Generated read-model shape:
use Pair\Data\ArraySerializableData;
use Pair\Data\MapsFromRecord;
use Pair\Data\ReadModel;
use Pair\Orm\ActiveRecord;
/**
* Generated read model for Order API responses.
*/
final readonly class OrderReadModel implements ReadModel, MapsFromRecord {
use ArraySerializableData;
/**
* Build the read model.
*/
public function __construct(
public mixed $id,
public mixed $customerId,
public mixed $totalAmount
) {}
/**
* Build the read model from a persistence record.
*/
public static function fromRecord(ActiveRecord $record): static {
return new self(
$record->id,
$record->customerId,
$record->totalAmount
);
}
/**
* Export the read model as an array.
*
* @return array<string, mixed>
*/
public function toArray(): array {
return [
'id' => $this->id,
'customerId' => $this->customerId,
'totalAmount' => $this->totalAmount,
];
}
}Module and API names are restricted to lowercase letters and numbers because Pair's current module controller resolution expects simple module names.
Examples:
- valid:
orders,api,crm2 - invalid:
order-items,order_items
Use folder or route aliases outside the generator if the application needs more complex URLs.
If a target file already exists:
- identical content is reported as unchanged
- different content blocks the command
-
--forceintentionally replaces it
This protects manual edits and keeps the generator safe to re-run.
See also: Controller, Input, PageResponse, ReadModel, MapsFromRecord, API, CrudController, RequestData.