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 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 | Installation

Clone this wiki locally