Skip to content

Application

Viames Marino edited this page Mar 31, 2026 · 6 revisions

Pair framework: Application class

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/.env through the Env class.


Quick start

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();

What happens at startup

Application::getInstance() triggers the private constructor and performs these core tasks:

  1. Forces PHP error reporting to E_ALL and enables display_errors.
  2. Defines APPLICATION_PATH (project root, without trailing slash).
  3. If .env is missing and APPLICATION_PATH/installer/start.php exists, starts the installer and stops execution.
  4. Loads .env via Env::load().
  5. Defines runtime constants via defineConstants():
    • PAIR_FOLDER (Pair folder path relative to APPLICATION_PATH)
    • TEMP_PATH (temporary directory path)
    • BASE_TIMEZONE (UTC if UTC_DATE=true, otherwise PHP default)
    • URL_PATH (base URL path, or null on CLI)
    • BASE_HREF (absolute base URL with trailing slash, or null on CLI)
  6. Registers Pair error/exception handlers via Logger.
  7. Initializes routing via Router and parses the current route.
  8. If DB_UTF8=true, forces utf8mb4 for database communication.
  9. In web mode (not headless), starts an output buffer (gzip when supported/accepted) and restores queued modals/toasts from cookies.

Methods

Application::getInstance(): Application

Creates (or returns) the singleton Application instance.

$app = Application::getInstance();

Application::run(): void

Runs the current request.

  • Internally, run() calls initializeController() and then renderTemplate().
  • 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().


Application::setToastDriver(string $driver): void

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.

Application::setToastPosition(string $position): void

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.


Application::initializeController(): void

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');

Application::renderTemplate(): void

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();

Application::headless(bool $on = true): static

Enable/disable headless mode. When enabled, Pair avoids producing HTML output (useful for CLI scripts).

Application::getInstance()
    ->headless()
    ->run();

Application::getEnvironment(): string

Returns the current environment based on APP_ENV:

  • development
  • staging
  • production (default)
if (Application::getEnvironment() === 'development') {
    // dev-only behavior
}

Application::isCli(): bool

Returns true when Pair is running from CLI (php_sapi_name() === 'cli').

if (Application::isCli()) {
    // avoid redirects, sessions, output, ...
}

State variables

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)

Application::setState(string $name, mixed $value): void

Sets a state variable (in-memory only).

$app = Application::getInstance();
$app->setState('filters', ['year' => 2026]);

Application::getState(string $name): mixed

Returns a state variable or null when missing.

$filters = $app->getState('filters') ?? [];

Application::issetState(string $name): bool

Checks if a state variable exists (even if its value is null).

if ($app->issetState('filters')) {
    // ...
}

Application::unsetState(string $name): void

Deletes an in-memory state variable.

$app->unsetState('filters');

Persistent state (cookies)

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 from APP_NAME (see getCookiePrefix()). When reading, Pair unserializes with a whitelist of allowed classes for security. Reserved cookie names are defined in Application::RESERVED_COOKIE_NAMES.

Application::setPersistentState(string $stateName, mixed $value): void

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');

Application::getPersistentState(string $stateName): mixed

Retrieves a persistent state value from cookies, or null when missing.

$form = $app->getPersistentState('signup_form');

if ($form) {
    // ...
    $app->unsetPersistentState('signup_form');
}

Application::issetPersistentState(string $stateName): bool

Checks whether a persistent state exists (either already loaded or still in cookies).

if ($app->issetPersistentState('signup_form')) {
    // ...
}

Application::unsetPersistentState(string $stateName): void

Removes a persistent state cookie.

$app->unsetPersistentState('signup_form');

Application::unsetAllPersistentStates(): void

Removes all cookies belonging to the current Pair application (by cookie prefix).

$app->unsetAllPersistentStates();

Toast notifications and modal dialogs

Pair can queue toast notifications (IziToast) and a single modal (SweetAlert) that are rendered on the next page load.

Application::toast(string $title, string $message = '', ?string $type = null): IziToast

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');

Application::toastError(string $title, string $message = ''): IziToast

Convenience wrapper for an error toast.

$app->toastError('Invalid data', 'Please fix the highlighted fields.');

Application::makeToastNotificationsPersistent(): void

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');

Application::modal(string $title, string $text, ?string $icon = null): SweetAlert

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');

Application::persistentModal(string $title, string $text, ?string $icon = null): void

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');

Application::getAllNotificationsMessages(): array

Returns plain-text messages for the current modal and all queued toasts (useful for logging/audit).

$messages = Application::getInstance()->getAllNotificationsMessages();

Redirect helpers with notifications

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');

Assets: CSS, Manifest, JS

Pair does not force a SPA framework. Instead, you can incrementally add JS and CSS and print them from your layout.

Application::loadCss(string $href): void

Registers a stylesheet.

$app->loadCss('/assets/app.css');

Application::loadManifest(string $href): void

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)
  • integrity
  • crossorigin
  • charset
$app->loadScript('/assets/app.js', defer: true);

$app->loadScript(
    'https://cdn.jsdelivr.net/npm/alpinejs',
    defer: true,
    attribs: ['crossorigin' => 'anonymous']
);

Application::addScript(string $script): void

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
);

Application::styles(): string

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>

Application::scripts(): string

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.

Quick reference (high-use methods)

Boot/runtime:

  • getInstance(): Application
  • run(): void
  • initializeController(): void
  • renderTemplate(): void
  • headless(bool $on = true): static
  • isCli(): bool
  • getEnvironment(): string
  • setApiModules(array|string $modules): void

Navigation/session:

  • redirect(?string $url = null, bool $externalUrl = false): void
  • setUserClass(string $class): void
  • setCurrentUser(User $user): void
  • guestModule(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(...)

End-to-end pattern: POST/Redirect/GET with flash state

$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;
}

End-to-end pattern: headless CLI script

$app = \Pair\Core\Application::getInstance()->headless();

if (\Pair\Core\Application::isCli()) {
    // run maintenance task without HTML/session output
    (new CleanupService())->run();
}

Menu and page metadata

Application::pageTitle(string $title): void

Sets the page title (browser tab).

$app->pageTitle('Orders');

Application::pageHeading(string $heading): void

Sets the page heading text (typically shown inside the layout).

$app->pageHeading('Orders');

Application::menuUrl(string $url): void

Sets the currently selected menu item URL (useful for nav highlighting).

$app->menuUrl('orders');

Redirects

Application::redirect(?string $url = null, bool $externalUrl = false): void

Redirects the client.

  • If $url is null, Pair redirects to the current module.
  • If $externalUrl is true, 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);

Application::redirectToUserDefault(): void

Redirects the current user to their default landing page. If no user is logged in, it redirects to user/login.

$app->redirectToUserDefault();

API modules and guest modules

Application::setApiModules(array|string $modules): void

Defines which module names are treated as API endpoints. Default is ['api'].

$app->setApiModules(['api', 'webhooks']);

Application::guestModule(string $moduleName, array $allowedActions = []): void

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']);

ActiveRecord cache

The Application cache stores ActiveRecord instances to avoid re-loading the same object multiple times in the same request.

Application::getActiveRecordCache(string $class, int|string $id): ?ActiveRecord

Returns the cached object (or null).

$user = $app->getActiveRecordCache(\Pair\Models\User::class, 123);

Application::putActiveRecordCache(string $class, ActiveRecord $object): void

Stores an object into the cache (only for single-column primary keys).

$app->putActiveRecordCache(\Pair\Models\User::class, $user);

Widgets

Application::printWidget(string $name): void

Renders and prints a Widget by name.

$app->printWidget('LogBar');

Magic properties and template variables

Application::__set(string $name, mixed $value): void

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 variable

Application::__get(string $name): mixed

Reads 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 from Translator (useful for <html lang="...">)
$lang = $app->langCode;        // e.g. "en"
$title = $app->pageTitle;      // property
$value = $app->myVar;          // template variable

Clone this wiki locally