-
Notifications
You must be signed in to change notification settings - Fork 0
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.
- PHP 8.2+ with
ext-zip,ext-curl,ext-json - Composer installed globally
- A terminal (PowerShell, Bash, etc.)
# Create a project directory
mkdir my-razy-app
cd my-razy-app
# Install via Composer
composer require rayfunghk/razy# Build the Razy working environment
php Razy.phar buildThis 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
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 mysiteThis creates sites/mysite/ with a dist.php configuration file.
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:
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',
];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',
];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.
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();
};<?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();
};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);
};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><!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>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 |
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.
You have two options to test:
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# Generate rewrite rules
php Razy.phar rewrite mysite
# Start the PHP development server
php -S localhost:8080 router.phpThen 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
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
└──────────────────┘
| 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) |
📍 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 |