Controller - viames/pair GitHub Wiki
Pair framework: Controller
Pair\Core\Controller is the legacy MVC controller base in Pair v4.
For new Pair v4 modules, prefer Pair\Web\Controller with explicit PageResponse, JsonResponse, or another ResponseInterface implementation.
The old controller remains available as a migration bridge for classic Pair MVC modules and emits a deprecation notice outside production.
When to use each controller base
Pair\Web\Controller (preferred)
Use it for new Pair v4 modules and for migrated modules that already return explicit responses.
Typical flow:
- read request input explicitly
- load or map data explicitly
- return
PageResponsefor HTML orJsonResponsefor JSON - keep layout state inside a typed object passed to the response
Example:
use Pair\Web\Controller;
use Pair\Web\PageResponse;
require_once __DIR__ . '/classes/UsersDefaultPageState.php';
final class UsersController extends Controller {
/**
* Render the default users page with explicit typed state.
*/
public function defaultAction(): PageResponse {
return $this->page('default', new UsersDefaultPageState('Users'), 'Users');
}
}
Minimal page-state class:
use Pair\Data\ArraySerializableData;
use Pair\Data\ReadModel;
/**
* Typed layout state for the users default page.
*/
final readonly class UsersDefaultPageState implements ReadModel {
use ArraySerializableData;
/**
* Build the page state.
*/
public function __construct(public string $title) {}
/**
* Export the page state for optional JSON reuse.
*
* @return array<string, mixed>
*/
public function toArray(): array {
return ['title' => $this->title];
}
}
Pair\Core\Controller (legacy bridge)
Keep using it only while the module still depends on the legacy MVC lifecycle, for example when it still uses:
_init()as the setup hooksetView()- implicit model/view loading
$this->modelas ambient state- classic
View::assign()variables
The Pair v3 to v4 upgrader keeps these controllers on Pair\Core\Controller until the module is ready for the explicit v4 path.
Legacy constructor lifecycle
final public function __construct()
Legacy execution flow:
- loads
Application,Router, andTranslatorsingletons - emits a non-production deprecation notice for the legacy controller class
- derives the controller name from the class name
- resolves the module path via reflection
- tells
Translatorwhich module is active - loads the default model from
model.phpif$modelwas not already set - runs
_init() - if the app is not headless and no view was selected yet, loads the action view or
default
Because the constructor is final, controller setup in the legacy MVC path still belongs in _init().
In the explicit v4 path, setup belongs in boot() on Pair\Web\Controller.
Legacy naming conventions
Given OrdersController:
- module folder:
modules/orders/ - default model file:
modules/orders/model.php - default model class:
OrdersModel - view file for action
edit:modules/orders/viewEdit.php - view class for action
edit:OrdersViewEdit
That convention is what powers the old auto-loaded MVC stack. Pair v4 keeps it only for migration compatibility.
Legacy MVC methods
_init(): void
Optional setup hook executed after the default model is ready and before the default view is autoloaded.
Use it only in classic legacy modules.
Migrated v4 web controllers should use boot() instead.
setView(string $viewName): void
Loads and stores a legacy Pair\Core\View instance for the controller.
This is part of the old MVC path and should not be used in new Pair v4 modules.
Use an explicit PageResponse return instead.
renderView(): void
Validates that the current view is a real Pair\Core\View subclass and then calls display().
This is the classic HTML rendering path used by legacy modules. It is not the preferred Pair v4 flow.
loadModel(string $modelName): void
Loads a non-default model from the current module folder.
This helper remains relevant only to the legacy controller lifecycle. In the explicit v4 path, prefer constructing or resolving the exact object needed by the action.
loadModelForActions(string $modelName, array $actions): void
Conditional shortcut around loadModel() for the legacy controller flow.
getObjectRequestedById(string $class): ?ActiveRecord
Loads an ActiveRecord from the first route parameter (Router::get(0)).
This helper is still useful during migration, but it also signals that the controller still depends on the legacy base class. Explicit v4 controllers usually read ids through request input or route state and then query explicitly.
Recommended Pair v4 migration shape
- replace
Pair\Core\ControllerwithPair\Web\Controller - replace
_init()withboot() - stop calling
setView() - move layout preparation into a typed page-state object
- return
PageResponseorJsonResponseexplicitly
Pair v4 helper examples
Pair\Web\Controller exposes a deliberately small helper surface.
use Pair\Http\JsonResponse;
use Pair\Http\ResponseInterface;
use Pair\Web\Controller;
use Pair\Web\PageResponse;
require_once __DIR__ . '/classes/OrdersDefaultPageState.php';
/**
* Orders controller using explicit Pair v4 responses.
*/
final class OrdersController extends Controller {
/**
* Prepare module dependencies without legacy model/view bootstrapping.
*/
protected function boot(): void {
// Put explicit module setup here, for example service construction.
}
/**
* Render the orders list with typed query-string input.
*/
public function defaultAction(): PageResponse {
$input = $this->input();
$state = new OrdersDefaultPageState(
status: $input->string('status', 'open'),
page: max(1, $input->int('page', 1))
);
return $this->page('default', $state, 'Orders');
}
/**
* Return JSON without entering the legacy view path.
*/
public function statusAction(): JsonResponse {
return $this->json([
'ok' => true,
'modulePath' => $this->modulePath(),
]);
}
/**
* Return any explicit response implementation when the action can vary.
*/
public function pingAction(): ResponseInterface {
return $this->json(['pong' => true]);
}
}
Use modulePath('layouts/default.php') when a module needs an explicit file inside its own folder.
Use page('default', $state, 'Title') for the standard layouts/default.php path.
See also: Input, PageResponse, JsonResponse, ResponseInterface, ReadModel, View, Upgrade-to-v4, ApiExposable, CrudController