-
Notifications
You must be signed in to change notification settings - Fork 0
Module System
Modules are the fundamental building blocks of a Razy application. Each module is a self-contained package with its own controller, views, plugins, API commands, and assets.
sites/{distributor}/{vendor}/{module}/
→?→ module.php # REQUIRED: Module metadata
→?→ {version}/ # e.g., "default", "1.0.0"
→?→ package.php # REQUIRED: Config & dependencies
→?→ controller/
→ →?→ {module_code}.php # REQUIRED: Main controller
→ →?→ {module_code}.main.php # Route handler
→ →?→ {subfolder}/ # Nested route handlers
→?→ view/ # Template files (.tpl)
→?→ plugins/ # Custom plugins
→?→ api/ # API command handlers
→?→ webassets/ # Client-side assets
→?→ src/ # Library source code
🚨 Danger: Important: Modules go directly under
sites/{dist}/{vendor}/{module}/— do NOT create amodules/subfolder.
Every module requires a module.php file at the module root:
<?php
return [
'module_code' => 'vendor/module_name',
'name' => 'My Module',
'author' => 'Ray Fung',
'description' => 'Module description',
'version' => '1.0.0',
];Each version folder contains a package.php defining dependencies, API name, and prerequisites:
<?php
return [
'api_name' => 'my_api', // Optional API alias
'require' => [
'vendor/other_module' => '*', // Version constraint
],
'prerequisite' => [
'league/commonmark' => '^2.0', // Composer package
],
];
requirevsprerequisite: Therequirekey declares dependencies on other Razy modules (resolved by the module loader at startup). Theprerequisitekey declares dependencies on Composer packages from Packagist or a private mirror (resolved byPrerequisiteResolver+PackageManagerwhen you runphp Razy.phar compose <distcode>). Packages are extracted intoautoload/{distributor_code}/with per-distributor isolation. See Packaging & Distribution for details.
Controllers use the anonymous class pattern, extending Razy\Controller:
<?php
namespace Razy\Module\my_module;
use Razy\Agent;
use Razy\Controller;
return new class extends Controller {
public function __onInit(Agent $agent): bool
{
// Register routes
$agent->addLazyRoute([
'/' => 'main',
'action' => 'action',
]);
// Register API commands
$agent->addAPICommand('getData', 'api/get_data.php');
// Listen to events
$agent->listen('vendor/other:event', function($data) {
return ['handled' => true];
});
return true;
}
public function __onReady(): void
{
// Safe to access APIs here
// DI Container access via resolve()
$service = $this->resolve(MyService::class);
}
};Route handler files return a closure that receives captured URL parameters:
<?php
// controller/my_module.main.php
return function (): void {
header('Content-Type: application/json');
echo json_encode([
'status' => 'ok',
'module' => $this->getModuleInfo()->getCode(),
]);
};| Hook | Return | Purpose |
| --- | --- | --- |
| __onInit(Agent) | bool | Register routes, APIs, events. Return false to unload module. |
| __onLoad(Agent) | bool | Preload cross-module data. Return false to remove from queue. |
| __onDispatch() | bool | Before verifying module requirements. Return false to remove from queue. |
| __onRequire() | bool | Final validation. Return false to prevent execution. |
| __onReady() | void | Post-await setup. All APIs are safe to access. |
| __onRouted() | void | URL query matched this module's route. |
| __onEntry() | void | Route handler about to execute. Pre-handler logic. |
| __onDispose() | void | Cleanup after route/script completed. |
| __onAPICall() | bool | Authorize incoming API call. Return false to deny. |
Modules can have multiple version folders. The distributor's dist.php selects which version to load:
'modules' => [
'*' => ['vendor/module' => 'default'], // Default tag
'dev' => ['vendor/module' => '0.1.0'], // Dev tag
'v2' => ['vendor/module' => '2.0.0'], // v2 tag
],
Shared modules live in shared/module/ and are accessible to all distributors:
// dist.php →enable shared modules
'autoload_shared' => true,
'modules' => [
'*' => [
'shared/api-service' => 'default',
],
],
Module lifecycle status is tracked via the ModuleStatus enum:
| Status | Value | Description |
| --- | --- | --- |
| Failed | -1 | Module failed to load |
| Disabled | 0 | Module is disabled |
| Unloaded | 1 | Module has not been loaded |
| Pending | 2 | Module is queued for loading |
| Initialing | 3 | Module is initializing |
| Processing | 4 | Module is processing |
| InQueue | 5 | Module is in the execution queue |
| Loaded | 6 | Module is fully loaded |
use Razy\Module\ModuleStatus;
$status = $module->getStatus();
if ($status === ModuleStatus::Loaded) {
// Module is ready
}
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│Unloaded →?→→→? Pending →?→→→? Initialing →?→→→? Processing →?→→→? InQueue →?→→→? Loaded │
└────────────────────┬──────────────┬────────────────┬────────────────────────────────────┘
│ │ │
│ │ │
┌────────────────────┴──────────────┴────────────────┴────────────────────────────────────┐
│Failed → →Failed → →Failed │
└─────────────────────────────────────────────────────────────────────────────────────────┘
Initialing —
__onInit()is called.
Processing — routes and event listeners are registered.
Loaded — module is ready to handle requests.
| Problem | Cause | Fix |
| --- | --- | --- |
| Module not found | module_code in module.php doesn't match folder path | Align module_code to vendor/module_name exactly |
| __onInit not called | Module status stuck at Pending | Ensure module.php returns a valid config array |
| Route never matches | Module loaded after routing phase | Check module status — must reach Loaded before requests |
| Shared module not visible | autoload_shared is false in dist.php | Set 'autoload_shared' => true or list the module explicitly |