Skip to content

Standalone Mode

Ray Fung edited this page Feb 26, 2026 · 1 revision

Standalone Mode (Lite)

Razy's Standalone Mode is the simplest way to build an application — a single-module, zero-config setup that requires no package manager, no distributor configuration, and no domain restrictions.

Overview

  • Default mode: Standalone activates automatically when a standalone/ folder exists and multisite is not enabled
  • Ultra-flat structure: No module.php, no package.php, no version directories — just controllers and views
  • Full framework access: DI container, template engine, routing, and all Razy utilities are available
  • Any domain: No domain restrictions — any incoming connection loads the standalone module

Quick Start

Option 1: CLI Scaffold

php Razy.phar standalone

This creates:

standalone/
  controller/
    app.php           # Main controller (route registration)
    app.index.php     # Index route handler (/)
  view/
    index.tpl         # Index page template

Option 2: Manual Setup

Create the standalone/ directory at your project root with at minimum:

standalone/
  controller/
    app.php           # Main controller

Folder Structure

my-project/
  index.php           # Entry point (or router.php)
  config.inc.php      # Framework config (timezone, debug, phar_location)
  Razy.phar           # Framework archive
  standalone/         # Your application lives here
    controller/
      app.php         # Main controller class
      app.index.php   # Handler for '/' route
      app.about.php   # Handler for 'about' route
      app.api.php     # Handler for 'api' route
    view/
      index.tpl       # Templates
      about.tpl
    library/          # Optional: custom classes (autoloaded)

How It Works

Detection

In main.php, before the normal multisite host-matching flow:

  1. Check if standalone/ directory exists at SYSTEM_ROOT
  2. Check if config.inc.php does NOT have 'multiple_site' => true
  3. Check if RAZY_MULTIPLE_SITE environment variable is NOT 'true'
  4. If all conditions are met → standalone mode activates
STANDALONE_MODE = is_dir('standalone/') && !$razyConfig['multiple_site'] && getenv('RAZY_MULTIPLE_SITE') !== 'true'

Boot Flow

index.php → Razy.phar → main.php → bootstrap.inc.php
  ↓
  Standalone detection (standalone/ exists, multisite not enabled)
  ↓
  Application::standalone($standalonePath)
    → Creates Standalone instance (no dist.php, no Domain)
    → DI Container injected from Application
  ↓
  Application::queryStandalone(URL_QUERY)
    → Standalone::initialize() creates one Module directly
    → Module loads controller/app.php
    → Standalone::matchRoute() dispatches to matched handler

What's Bypassed

Normal Mode Standalone Mode
sites.inc.php required Not needed (but can coexist)
Domain matching (host()) Skipped — any domain works
dist.php in sites/ Not needed
module.php per module Not needed — synthesized internally
package.php per version Not needed
vendor/package/ nesting Flat — standalone/controller/app.php
Version directories (default/) None — code lives at standalone/ root

Controller

The main controller at standalone/controller/app.php is identical to any Razy controller:

<?php
use Razy\Agent;
use Razy\Controller;

return new class extends Controller {
    public function __onInit(Agent $agent): bool
    {
        $agent->addLazyRoute([
            '/' => 'index',
            'about' => 'about',
            'api/users' => 'api/users',
        ]);

        return true;
    }
};

Route Handlers

Route handlers follow the standard naming convention: app.{route}.php

<?php
// standalone/controller/app.index.php
use Razy\Controller;

return function (): void {
    /** @var Controller $this */
    header('Content-Type: text/html; charset=UTF-8');
    
    $template = $this->loadTemplate('index');
    echo $template->output();
};

Templates

Templates in standalone/view/ use the standard Razy template engine:

{# standalone/view/index.tpl #}
<!DOCTYPE html>
<html>
<head><title>{$title}</title></head>
<body>
    <h1>{$heading}</h1>
    <!-- BEGIN: content -->
    <p>{$text}</p>
    <!-- END: content -->
</body>
</html>

DI Container

The standalone module has access to the Application-level DI container. Inject services in your controller:

return new class extends Controller {
    public function __onInit(Agent $agent): bool
    {
        // Access the DI container
        $container = $this->module->getContainer();
        
        // Resolve services
        $pluginManager = $container->make(\Razy\PluginManager::class);
        
        $agent->addLazyRoute(['/' => 'index']);
        return true;
    }
};

Switching to Multisite

To convert a standalone app to multisite:

  1. Set 'multiple_site' => true in config.inc.php
  2. Create sites.inc.php at the project root (use php Razy.phar set)
  3. Move your standalone code into a proper module under sites/<dist>/
  4. Standalone mode deactivates because multisite is enabled

Both can coexist during development — toggle between modes by changing the multiple_site config value.

CLI Support

# Scaffold a new standalone app
php Razy.phar standalone

# Scaffold with force-overwrite
php Razy.phar standalone --force

# Scaffold into a specific directory
php Razy.phar standalone -f /var/www/myapp

Limitations

  • Single module only: Standalone mode loads exactly one module
  • No inter-module API: No $this->api() calls (only one module exists)
  • No package prerequisites: No compose or dependency resolution
  • No shared modules: Global modules from shared/module/ are not scanned
  • No data mapping: Cross-site data mapping is not available

For multi-module applications or cross-module communication, use the standard multisite mode with sites.inc.php.

Clone this wiki locally