-
Notifications
You must be signed in to change notification settings - Fork 2
Application
The Application class is the core singleton of Pair. It is responsible for bootstrapping the runtime environment, routing the current request, and (when running on the web) rendering the response.
In addition, it provides a few pragmatic “app-level services” that are used everywhere in Pair applications:
- a small in-memory state (for the current request);
- a persistent state stored in browser cookies (useful across redirects);
- a toast + modal queue that survives redirects and is rendered on page load;
- a collector for CSS / JS assets to be printed by the layout template;
- a lightweight ActiveRecord object cache (per request / per process).
Pair loads environment variables from
APPLICATION_PATH/.envthrough theEnvclass.
A typical Pair app starts from public/index.php and looks like this:
use Pair\Core\Application;
require __DIR__ . '/../vendor/autoload.php';
$app = Application::getInstance();
$app->setToastDriver('sweetalert');
$app->run();For CLI scripts (no HTML output, no sessions), enable headless mode:
use Pair\Core\Application;
$app = Application::getInstance()->headless();
$app->run();Application::getInstance() triggers the private constructor and performs these core tasks:
- Forces PHP error reporting to
E_ALLand enablesdisplay_errors. - Defines
APPLICATION_PATH(project root, without trailing slash). - If
.envis missing andAPPLICATION_PATH/installer/start.phpexists, starts the installer and stops execution. - Loads
.envviaEnv::load(). - Defines runtime constants via
defineConstants():-
PAIR_FOLDER(Pair folder path relative toAPPLICATION_PATH) -
TEMP_PATH(temporary directory path) -
BASE_TIMEZONE(UTC ifUTC_DATE=true, otherwise PHP default) -
URL_PATH(base URL path, ornullon CLI) -
BASE_HREF(absolute base URL with trailing slash, ornullon CLI)
-
- Registers Pair error/exception handlers via
Logger. - Initializes routing via
Routerand parses the current route. - If
DB_UTF8=true, forcesutf8mb4for database communication. - In web mode (not headless), starts an output buffer (gzip when supported/accepted) and restores queued modals/toasts from cookies.
Creates (or returns) the singleton Application instance.
$app = Application::getInstance();Runs the current request.
- Internally,
run()callsinitializeController()and thenrenderTemplate(). - If the current module matches one of the configured API modules, Pair executes the API pipeline and exits without rendering a template.
- Otherwise Pair initializes the web session when needed, runs the MVC controller, renders the view, and finally renders the selected template unless the app is headless.
$app = Application::getInstance();
$app->run();See also: Application::setApiModules().
Configures the toast driver used by server-side helpers and exposed to PairUI.toast.
$app = Application::getInstance();
$app->setToastDriver('sweetalert');Supported aliases include izitoast, sweetalert, sweetalert2, and swal.
Configures the global default toast position for both PHP toasts and client-side PairUI.toast calls.
$app = Application::getInstance();
$app->setToastPosition('topRight');Accepted values include Pair aliases such as topRight, topLeft, bottomRight,
bottomLeft, topCenter, bottomCenter, center, plus SweetAlert2 aliases such as
top-end and bottom-start.
When this method is not called, Pair leaves the native default position of the active toast driver unchanged.
Runs the routing, session/API bootstrap, controller action, and view rendering, but does not render the outer template yet.
This is useful for advanced bootstraps, tests, or instrumentation that need to inject template variables after the controller has executed.
$app = Application::getInstance();
$app->initializeController();
$app->var('buildNumber', '2026.03.26');Renders the selected template around the already generated page content.
This method does nothing for API modules and headless executions.
$app = Application::getInstance();
$app->initializeController();
$app->renderTemplate();Enable/disable headless mode. When enabled, Pair avoids producing HTML output (useful for CLI scripts).
Application::getInstance()
->headless()
->run();Returns the current environment based on APP_ENV:
developmentstaging-
production(default)
if (Application::getEnvironment() === 'development') {
// dev-only behavior
}Returns true when Pair is running from CLI (php_sapi_name() === 'cli').
if (Application::isCli()) {
// avoid redirects, sessions, output, ...
}Application exposes two kinds of “state”:
-
state: stored only in memory for the current request (
$this->state) -
persistentState: stored in cookies to survive redirects (
$this->persistentState)
Sets a state variable (in-memory only).
$app = Application::getInstance();
$app->setState('filters', ['year' => 2026]);Returns a state variable or null when missing.
$filters = $app->getState('filters') ?? [];Checks if a state variable exists (even if its value is null).
if ($app->issetState('filters')) {
// ...
}Deletes an in-memory state variable.
$app->unsetState('filters');Persistent state is useful for “flash” values across redirects (messages, form errors, UI state, etc.).
Values are stored through
serialize()in a cookie whose name is derived fromAPP_NAME(seegetCookiePrefix()). When reading, Pair unserializes with a whitelist of allowed classes for security. Reserved cookie names are defined inApplication::RESERVED_COOKIE_NAMES.
Stores a value in a cookie for later retrieval.
- Cookie lifetime: 30 days
- May throw an exception if
setcookie()fails.
$app = Application::getInstance();
// store form data for the next request
$app->setPersistentState('signup_form', [
'email' => 'user@example.com',
'name' => 'John',
]);
$app->redirect('user/signup');Retrieves a persistent state value from cookies, or null when missing.
$form = $app->getPersistentState('signup_form');
if ($form) {
// ...
$app->unsetPersistentState('signup_form');
}Checks whether a persistent state exists (either already loaded or still in cookies).
if ($app->issetPersistentState('signup_form')) {
// ...
}Removes a persistent state cookie.
$app->unsetPersistentState('signup_form');Removes all cookies belonging to the current Pair application (by cookie prefix).
$app->unsetAllPersistentStates();Pair can queue toast notifications (IziToast) and a single modal (SweetAlert) that are rendered on the next page load.
Queues a toast notification for the current request.
$app = Application::getInstance();
$app->toast('Saved', 'The record has been updated.', 'success');
$app->toast('Heads up', 'This action cannot be undone.', 'warning');Convenience wrapper for an error toast.
$app->toastError('Invalid data', 'Please fix the highlighted fields.');Stores the current queued toasts so they can be restored on the next request (typical before redirect).
$app->toast('Saved', 'Ok', 'success');
$app->makeToastNotificationsPersistent();
$app->redirect('orders');Sets a modal to be displayed on the next page load and returns the SweetAlert object (so you can further customize it, if needed).
$app->modal('Welcome', 'Your account is ready.', 'success');Queues a modal to be shown on the next request by storing it into persistent state (Modal cookie).
$app->persistentModal('Session expired', 'Please login again.', 'warning');
$app->redirect('user/login');Returns plain-text messages for the current modal and all queued toasts (useful for logging/audit).
$messages = Application::getInstance()->getAllNotificationsMessages();These helpers queue notifications, persist them, and then redirect:
-
Application::toastRedirect(string $title, string $message = '', ?string $url = null): void(success) -
Application::toastErrorRedirect(string $title, string $message = '', ?string $url = null): void(error)
$app->toastRedirect('Saved', 'Everything OK', 'orders');
$app->toastErrorRedirect('Error', 'Something went wrong', 'orders');Pair does not force a SPA framework. Instead, you can incrementally add JS and CSS and print them from your layout.
Registers a stylesheet.
$app->loadCss('/assets/app.css');Registers a manifest.
$app->loadManifest('/manifest.webmanifest');Application::loadScript(string $src, bool $defer = false, bool $async = false, array $attribs = []): void
Registers an external script with optional attributes.
Supported attributes include:
-
type(only valid JS MIME types) integritycrossorigincharset
$app->loadScript('/assets/app.js', defer: true);
$app->loadScript(
'https://cdn.jsdelivr.net/npm/alpinejs',
defer: true,
attribs: ['crossorigin' => 'anonymous']
);Queues plain JavaScript source code to be printed at the end of the page.
$app->addScript("console.log('hello from Pair');");Application::loadPwaScripts(string $assetsPath = '/assets', bool $includePairUi = false, bool $includePairPush = false, bool $includePairPasskey = false): void
Registers the default Pair PWA asset bundle.
$app->loadPwaScripts(
assetsPath: '/assets',
includePairUi: true,
includePairPush: true,
includePairPasskey: true
);Returns the <link> tags for registered stylesheets and manifests.
Call it from your layout template, inside <head>:
// layout.php
$app = \Pair\Core\Application::getInstance();
?>
<!doctype html>
<html lang="<?= htmlspecialchars($app->langCode) ?>">
<head>
<meta charset="utf-8">
<?= $app->styles(); ?>
</head>Returns the <script> tags for registered scripts and queued inline script content.
Call it from your layout template before </body>:
// layout.php
?>
<?= $app->scripts(); ?>
</body>
</html>scripts() also injects the JS required to render queued modals and toasts on DOMContentLoaded.
Boot/runtime:
getInstance(): Applicationrun(): voidinitializeController(): voidrenderTemplate(): voidheadless(bool $on = true): staticisCli(): boolgetEnvironment(): stringsetApiModules(array|string $modules): void
Navigation/session:
redirect(?string $url = null, bool $externalUrl = false): voidsetUserClass(string $class): voidsetCurrentUser(User $user): voidguestModule(string $moduleName, array $allowedActions = []): void
UI notifications:
-
toast(...),toastError(...) -
toastRedirect(...),toastErrorRedirect(...) -
modal(...),persistentModal(...)
State and caching:
-
setState(...),getState(...),unsetState(...) -
setPersistentState(...),getPersistentState(...),unsetPersistentState(...) -
putActiveRecordCache(...),getActiveRecordCache(...)
Assets/layout helpers:
-
loadCss(...),loadScript(...),loadManifest(...),loadPwaScripts(...) -
styles(): string,scripts(): string -
pageTitle(...),pageHeading(...),menuUrl(...)
$app = \Pair\Core\Application::getInstance();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$payload = ['email' => trim($_POST['email'] ?? '')];
if (!filter_var($payload['email'], FILTER_VALIDATE_EMAIL)) {
$app->setPersistentState('signup.errors', ['Invalid email']);
$app->setPersistentState('signup.old', $payload);
$app->toastErrorRedirect('Validation error', 'Check your input', 'user/signup');
return;
}
$app->toastRedirect('Account created', 'Welcome', 'user/login');
return;
}$app = \Pair\Core\Application::getInstance()->headless();
if (\Pair\Core\Application::isCli()) {
// run maintenance task without HTML/session output
(new CleanupService())->run();
}Sets the page title (browser tab).
$app->pageTitle('Orders');Sets the page heading text (typically shown inside the layout).
$app->pageHeading('Orders');Sets the currently selected menu item URL (useful for nav highlighting).
$app->menuUrl('orders');Redirects the client.
- If
$urlisnull, Pair redirects to the current module. - If
$externalUrlistrue, Pair redirects to the provided URL as-is. - Otherwise Pair redirects internally using
BASE_HREF . $url. - If the current router page is
> 1, Pair appends/page-N.
Before redirecting, Pair also persists any queued modal/toasts for the next request.
// internal redirect
$app->redirect('orders');
// external redirect
$app->redirect('https://example.com', externalUrl: true);Redirects the current user to their default landing page. If no user is logged in, it redirects to user/login.
$app->redirectToUserDefault();Defines which module names are treated as API endpoints. Default is ['api'].
$app->setApiModules(['api', 'webhooks']);Registers guest-access rules for unauthenticated web requests.
Important: in the current code, the access check matches both the module and an explicit allow-list of actions. Pass the actions you want to expose; an empty array is not treated as a wildcard.
// allow only selected public actions
$app->guestModule('public', ['status', 'ping']);
// explicit oauth2 actions
$app->guestModule('oauth2', ['login', 'callback', 'refresh']);The Application cache stores ActiveRecord instances to avoid re-loading the same object multiple times in the same request.
Returns the cached object (or null).
$user = $app->getActiveRecordCache(\Pair\Models\User::class, 123);Stores an object into the cache (only for single-column primary keys).
$app->putActiveRecordCache(\Pair\Models\User::class, $user);Renders and prints a Widget by name.
$app->printWidget('LogBar');When $name matches an Application property, it sets it. Otherwise it stores the value into layout variables ($this->vars) to be consumed by templates.
$app->pageContent = '<p>Hello</p>'; // sets a real property
$app->myVar = 123; // assigns a template variableReads template variables first, then a whitelist of Application properties. Returns null if the requested key is unknown.
It also supports a special property:
-
langCode– current language code fromTranslator(useful for<html lang="...">)
$lang = $app->langCode; // e.g. "en"
$title = $app->pageTitle; // property
$value = $app->myVar; // template variable