Skip to content

Getting Started Tutorial

Ray Fung edited this page Feb 26, 2026 · 3 revisions

Getting Started Tutorial

Build a working Razy application from scratch in under 10 minutes. By the end, you'll have a multi-page site with routing, templates, and a JSON API — all running locally.

Prerequisites

  • PHP 8.2+ with ext-zip, ext-curl, ext-json

  • Composer installed globally

  • A terminal (PowerShell, Bash, etc.)

Step 1: Install Razy

# Create a project directory

mkdir my-razy-app

cd my-razy-app



# Install via Composer

composer require rayfunghk/razy

Step 2: Build the Environment

# Build the Razy working environment

php Razy.phar build

This creates the directory structure:


my-razy-app/

→?→ Razy.phar              # Framework binary

→?→ config.inc.php         # Global configuration

→?→ sites.inc.php          # Domain →distributor mapping

→?→ index.php              # Web entry point

→?→ shared/

→  →?→ module/            # Shared modules (all distributors)

→?→ sites/                 # Per-distributor sites

→?→ data/                  # Global data storage

Step 3: Create a Distributor

A distributor is a self-contained site/app within Razy. Each distributor has its own modules, routes, and configuration.

# Create a distributor called "mysite"

php Razy.phar init dist mysite

This creates sites/mysite/ with a dist.php configuration file.

Step 4: Create Your First Module

Modules are the building blocks of a Razy application. Each module has a controller, routes, templates, and optionally an API.

Create the following directory structure inside sites/mysite/:


sites/mysite/

→?→ app/

    →?→ hello/

        →?→ module.php

        →?→ default/

            →?→ package.php

            →?→ controller/

            →  →?→ hello.php

            →  →?→ hello.home.php

            →  →?→ hello.about.php

            →  →?→ hello.greet.php

            →?→ view/

                →?→ home.tpl

                →?→ about.tpl

Now create each file:

module.php — Module Identity

This file declares who the module is. The module_code must match the directory path (app/hello).

<?php

return [

    'module_code' => 'app/hello',

    'name'        => 'Hello Module',

    'author'      => 'Your Name',

    'description' => 'My first Razy module',

    'version'     => '1.0.0',

];

package.php — Package Configuration

This file defines what the module does at runtime. It sits inside the version folder (default/).

<?php

return [

    'module_code' => 'app/hello',

    'author'      => 'Your Name',

    'description' => 'My first Razy module',

    'version'     => '1.0.0',

];

hello.php — Main Controller

The main controller is an anonymous class extending Controller. The __onInit method is where you register routes, APIs, and event listeners.

<?php

namespace Razy\Module\hello;



use Razy\Agent;

use Razy\Controller;



return new class extends Controller {

    /**

     * Called when the module initializes.

     * Register all routes here.

     */

    public function __onInit(Agent $agent): bool

    {

        // Lazy routes: map URL paths to handler files

        // '/' → hello.home.php, 'about' → hello.about.php

        $agent->addLazyRoute([

            '/'     => 'home',

            'about' => 'about',

        ]);



        // Regex route: capture a name from the URL

        // /hello/greet/Alice → hello.greet.php with $name = 'Alice'

        $agent->addRoute('/hello/greet/(:a)', 'greet');



        return true;

    }

};

What's happening here:

  • addLazyRoute() maps simple path segments to handler files. The paths are relative to the module's alias (hello), so '/' becomes /hello/ and 'about' becomes /hello/about.

  • addRoute() uses a regex pattern with the full path from the site root. (:a) captures any non-slash characters and passes them as a function argument.

hello.home.php — Home Page Handler

Route handler files return a Closure. Inside the closure, $this refers to the controller, giving you access to templates, APIs, and other module features.

<?php

use Razy\Controller;



return function (): void {

    /** @var Controller $this */

    header('Content-Type: text/html; charset=UTF-8');



    // Load the 'home' template from view/home.tpl

    $source = $this->loadTemplate('home');



    // Assign variables to the template

    $source->assign([

        'title'   => 'Welcome to Razy',

        'message' => 'Your first module is working!',

    ]);



    echo $source->output();

};

hello.about.php — About Page Handler

<?php

use Razy\Controller;



return function (): void {

    /** @var Controller $this */

    header('Content-Type: text/html; charset=UTF-8');



    $source = $this->loadTemplate('about');

    $source->assign([

        'title' => 'About',

    ]);



    echo $source->output();

};

hello.greet.php — JSON API with URL Parameter

This handler demonstrates URL parameter capture. The (:a) token from addRoute() is passed as the $name argument.

<?php

return function (string $name): void {

    header('Content-Type: application/json');

    echo json_encode([

        'greeting' => "Hello, {$name}!",

        'time'     => date('Y-m-d H:i:s'),

    ], JSON_PRETTY_PRINT);

};

home.tpl — Home Page Template

Razy templates use {$variable} syntax for variable substitution and <!-- START BLOCK: name --> / <!-- END BLOCK: name --> for repeatable blocks.

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <title>{$title}</title>

    <style>

        body { font-family: system-ui, sans-serif; max-width: 640px; margin: 40px auto; padding: 0 20px; color: #1e293b; }

        h1 { color: #2563eb; }

        a { color: #2563eb; text-decoration: none; }

        a:hover { text-decoration: underline; }

        .nav { display: flex; gap: 16px; margin-bottom: 32px; padding: 12px 0; border-bottom: 1px solid #e2e8f0; }

        .card { background: #f8fafc; border-radius: 8px; padding: 20px; margin: 16px 0; }

    </style>

</head>

<body>

    <nav class="nav">

        <a href="{$site_url}hello/">Home</a>

        <a href="{$site_url}hello/about">About</a>

        <a href="{$site_url}hello/greet/World">Greet API</a>

    </nav>



    <h1>{$title}</h1>

    <div class="card">

        <p>{$message}</p>

    </div>



    <h2>Try It</h2>

    <ul>

        <li><a href="{$site_url}hello/greet/Alice">/hello/greet/Alice</a> →JSON response</li>

        <li><a href="{$site_url}hello/greet/Razy">/hello/greet/Razy</a> →Another name</li>

    </ul>

</body>

</html>

about.tpl — About Page Template

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <title>{$title}</title>

    <style>

        body { font-family: system-ui, sans-serif; max-width: 640px; margin: 40px auto; padding: 0 20px; color: #1e293b; }

        h1 { color: #2563eb; }

        a { color: #2563eb; text-decoration: none; }

        a:hover { text-decoration: underline; }

        .nav { display: flex; gap: 16px; margin-bottom: 32px; padding: 12px 0; border-bottom: 1px solid #e2e8f0; }

    </style>

</head>

<body>

    <nav class="nav">

        <a href="{$site_url}hello/">Home</a>

        <a href="{$site_url}hello/about">About</a>

    </nav>



    <h1>{$title}</h1>

    <p>This is a simple Razy module demonstrating routing and templates.</p>

    <p>Built with the <strong>Razy Framework</strong> →a modular PHP framework for multi-site development.</p>

</body>

</html>

Step 5: Configure the Distributor

Edit sites/mysite/dist.php to load your module:

<?php

return [

    'dist'            => 'mysite',

    'global_module'   => false,

    'autoload_shared' => false,

    'greedy'          => false,

    'modules' => [

        '*' => [

            'app/hello' => 'default',    // Load 'default' version of app/hello

        ],

    ],

];

Key settings:

| Key | Value | Meaning |

| --- | --- | --- |

| dist | 'mysite' | Distributor identifier |

| global_module | false | Don't auto-load shared modules |

| autoload_shared | false | Don't auto-load from shared/module/ |

| greedy | false | Return 404 for unmatched routes (don't pass to next distributor) |

| modules | [...] | Module list under the '*' (default) tag |

| 'app/hello' => 'default' | — | Load app/hello using the default/ version folder |

Step 6: Configure Domain Mapping

Edit sites.inc.php in the project root to map localhost to your distributor:

<?php

return [

    'domains' => [

        'localhost' => [

            '/mysite' => 'mysite',

        ],

    ],

    'alias' => [],

];

This means: when someone visits http://localhost/mysite/..., Razy routes it to the mysite distributor.

Step 7: Run It

You have two options to test:

Option A: Interactive Shell (Recommended for Learning)

The runapp command lets you test routes without setting up a web server:

# Start the interactive shell

php Razy.phar runapp mysite



# Inside the shell, type a path to test:

> hello/

# →Outputs the home page HTML



> hello/about

# →Outputs the about page HTML



> hello/greet/Alice

# →{"greeting": "Hello, Alice!", "time": "2026-02-25 12:00:00"}



# Type 'exit' to quit

> exit

Option B: PHP Built-in Server

# Generate rewrite rules

php Razy.phar rewrite mysite



# Start the PHP development server

php -S localhost:8080 router.php

Then open your browser:

  • http://localhost:8080/mysite/hello/ → Home page

  • http://localhost:8080/mysite/hello/about → About page

  • http://localhost:8080/mysite/hello/greet/World → JSON API

What You've Built


Request: GET /mysite/hello/greet/Alice

         │

         │

┌────────┴────────┐

│  Application    │ →sites.inc.php: /mysite →mysite distributor

└────────┬────────┘

         │

┌────────┴────────┐

│  Distributor    │ →dist.php: loads app/hello module

│   (mysite)      │

└────────┬────────┘

         │

┌────────┴────────┐

│  app/hello      │ →module.php: identity

│  Controller     │ →hello.php: __onInit registers routes

└────────┬────────┘

         │

┌────────┴────────┐

│ Route Match     │ →addRoute('/hello/greet/(:a)', 'greet')

│ (:a) = "Alice"  │

└────────┬────────┘

         │

┌────────┴────────┐

│hello.greet.php  │ →function(string $name) →JSON

└─────────────────┘

Common Mistakes

| Problem | Cause | Fix |

| --- | --- | --- |

| "Module not found" | module_code doesn't match directory path | Ensure app/hello matches sites/mysite/app/hello/ |

| Route returns 404 | Missing leading / in addRoute() | Use /hello/greet/(:a) not hello/greet/(:a) |

| Empty page | Handler doesn't echo output | Use echo $source->output() or echo json_encode(...) |

| Template variables blank | Forgot to call assign() | Call $source->assign([...]) before output() |

| Wrong handler file loaded | File naming mismatch | Must be {controller_name}.{handler_name}.php (e.g., hello.home.php) |

Next Steps

— See the Learning Roadmap for a structured path: 30-minute intro — 1-day hands-on — 1-week mastery.

Now that you have a working module, explore these topics to build more complex applications:

| Topic | What You'll Learn |

| --- | --- |

| Module-System | Module lifecycle hooks, dependencies, versioning |

| Routing | Regex routes, HTTP method filtering, shadow routes |

| Template-Engine | Blocks, modifiers, function tags, iteration |

| Database | Query builder, Simple Syntax, migrations |

| Cross-Module-API | Module-to-module communication |

| Event-System | Inter-module events and listeners |

| CLI-Commands | All available CLI commands |

| Collection | Data manipulation with selectors and processors |


🏠 Home | Next → Installation

Clone this wiki locally