Modern REST API v3 Module Example for MikoPBX (Pattern 3: Auto-Discovery)
This module demonstrates Pattern 3 (Auto-Discovery) - the recommended approach for building REST API endpoints in MikoPBX using PHP 8.3 attributes and automatic controller discovery.
MikoPBX supports 3 REST API patterns for module development:
- Manual route registration in
moduleRestAPICallback() - Simple, direct approach for basic endpoints
- Good for learning and simple use cases
- Namespace isolation with module prefix
- Manual registration but with better organization
- Prevents endpoint conflicts between modules
- Automatic controller discovery via
#[ApiResource]attributes - OpenAPI 3.1 schema auto-generation from DataStructure classes
- Processor + Actions architecture for clean code separation
- Recommended for all new development
- ✅ Auto-Discovery - Controllers automatically discovered from
Lib/RestAPI/directory - ✅ PHP 8 Attributes - Declarative routing using
#[ApiResource],#[ApiOperation], etc. - ✅ OpenAPI 3.1 - Automatic OpenAPI specification generation
- ✅ 7-Phase Pattern - Structured data processing (sanitization → validation → save)
- ✅ File Operations - Chunked file upload with Resumable.js and secure download
- ✅ Security Integration - JWT Bearer token authentication
- ✅ Multilingual - Translation support for API documentation
- ✅ Clean Architecture - Clear separation of concerns
ModuleExampleRestAPIv3/
├── Lib/RestAPI/ # REST API components
│ └── Tasks/ # Resource namespace
│ ├── Controller.php # HTTP interface with attributes
│ ├── Processor.php # Request router
│ ├── DataStructure.php # Schema & validation
│ └── Actions/ # Business logic
│ ├── GetListAction.php
│ ├── GetRecordAction.php
│ ├── SaveRecordAction.php
│ ├── DeleteRecordAction.php
│ ├── GetDefaultAction.php
│ ├── DownloadFileAction.php
│ └── UploadFileAction.php
├── Models/Tasks.php # Phalcon ORM model
├── Setup/PbxExtensionSetup.php # Module installer
├── Messages/ # Translations
│ ├── en.php
│ └── ru.php
└── App/ # Web UI for testing
├── Controllers/ModuleExampleRestAPIv3Controller.php
├── Views/ModuleExampleRestAPIv3/index.volt
└── public/assets/js/
Benefits:
- All resource components in one folder (
Lib/RestAPI/Tasks/) - Semantically clear: "Tasks" = resource namespace
- Easy to scale: add new resources as siblings
- Automatic route registration - no manual configuration needed
- OpenAPI documentation generated from code
┌─────────────┐
│ HTTP Request│
│ GET /tasks │
└──────┬──────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Controller.php (#[ApiResource]) │
│ - Attributes define routes, security, parameters │
│ - Validates JWT token (SecurityType::BEARER_TOKEN) │
│ - Extracts ID from URL (/tasks/{id}) │
│ - Sanitizes input data │
└──────┬──────────────────────────────────────────────────┘
│
▼ sendRequestToBackendWorker()
┌─────────────────────────────────────────────────────────┐
│ Redis Queue │
│ - Action: 'getRecord' │
│ - Data: ['id' => 'TASK-123', ...] │
│ - Processor: 'Processor::callBack' │
└──────┬──────────────────────────────────────────────────┘
│
▼ WorkerApiCommands
┌─────────────────────────────────────────────────────────┐
│ Processor.php │
│ - Routes action to appropriate Action class │
│ - switch($action) { case 'getRecord': ... } │
└──────┬──────────────────────────────────────────────────┘
│
▼ GetRecordAction::main($data)
┌─────────────────────────────────────────────────────────┐
│ Actions/GetRecordAction.php │
│ │
│ PHASE 1: Sanitization │
│ - Clean user input │
│ │
│ PHASE 2: Required Validation │
│ - Check mandatory fields │
│ │
│ PHASE 3: Determine Operation │
│ - Find record by ID │
│ │
│ PHASE 4: Apply Defaults (CREATE only) │
│ - Skip for GET/UPDATE/PATCH │
│ │
│ PHASE 5: Schema Validation │
│ - Validate against DataStructure │
│ │
│ PHASE 6: Execute Business Logic │
│ - Retrieve data from database/models │
│ │
│ PHASE 7: Format Response │
│ - Transform to API format │
└──────┬──────────────────────────────────────────────────┘
│
▼ PBXApiResult
┌─────────────────────────────────────────────────────────┐
│ Response │
│ { │
│ "result": true, │
│ "data": { │
│ "id": "TASK-123", │
│ "title": "Example Task", │
│ "status": "pending" │
│ } │
│ } │
└─────────────────────────────────────────────────────────┘
/pbxcore/api/v3/module-example-rest-api-v3/tasks
| Method | Endpoint | Action | Description |
|---|---|---|---|
| GET | /tasks |
getList | Get list of all tasks with pagination |
| GET | /tasks/{id} |
getRecord | Get specific task by ID |
| POST | /tasks |
create | Create new task |
| PUT | /tasks/{id} |
update | Full update (replaces all fields) |
| PATCH | /tasks/{id} |
patch | Partial update (only specified fields) |
| DELETE | /tasks/{id} |
delete | Delete task by ID |
| GET | /tasks:getDefault |
getDefault | Get default values for new task |
| GET | /tasks/{id}:download |
download | Download file attached to task |
| POST | /tasks/{id}:uploadFile |
uploadFile | Upload file to attach to task |
All endpoints require JWT Bearer token authentication:
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
https://your-mikopbx.com/pbxcore/api/v3/module-example-rest-api-v3/tasksSecurity Types:
SecurityType::LOCALHOST- Access from localhost without tokenSecurityType::BEARER_TOKEN- JWT token required for remote access
Chunked upload using Resumable.js:
# Files are uploaded via Core's upload API first
POST /pbxcore/api/v3/files:upload
# Then attached to task (resource-level custom method)
POST /pbxcore/api/v3/module-example-rest-api-v3/tasks/1:uploadFileFeatures:
- Chunked upload support for large files
- Progress tracking via EventBus
- Automatic file type validation
- Max file size: 10MB (configurable)
- Supported formats: mp3, wav, pdf, png, jpeg
# Download file from task
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-mikopbx.com/pbxcore/api/v3/module-example-rest-api-v3/tasks/1:download \
-o downloaded_file.pdf
# Download with custom filename
curl -H "Authorization: Bearer YOUR_TOKEN" \
"https://your-mikopbx.com/pbxcore/api/v3/module-example-rest-api-v3/tasks/1:download?filename=report.pdf" \
-o report.pdfSecurity Features:
- Directory whitelist prevents path traversal attacks
- File validation (existence, readability)
- Proper MIME type detection
- Range requests support for audio/video
- Create resource directory:
mkdir -p Lib/RestAPI/Projects/Actions- Create Controller.php:
#[ApiResource(
path: '/pbxcore/api/v3/module-example-rest-api-v3/projects',
tags: ['Module Example REST API v3 - Projects'],
processor: Processor::class
)]
#[ResourceSecurity('projects', requirements: [SecurityType::BEARER_TOKEN])]
class Controller extends BaseRestController { }- Create Processor.php:
public static function callBack(array $request): PBXApiResult
{
switch ($request['action'] ?? '') {
case 'getList': return GetListAction::main($request['data'] ?? []);
// ...
}
}- Create DataStructure.php:
public static function getParameterDefinitions(): array
{
return [
'request' => [ /* field definitions */ ],
'response' => [ /* response structure */ ]
];
}- Create Actions:
class GetListAction {
public static function main(array $data): PBXApiResult { }
}- Add translations:
// Messages/en.php
'rest_projects_GetList' => 'Get projects list',
'rest_tag_ModuleExampleRESTAPIV3Projects' => 'Projects',-
Access OpenAPI UI:
- Navigate to module page
- Click "View API Documentation" link
- Or directly:
/admin-cabinet/api-keys/openapi#/operations/getTasksList
-
Authorize:
- Click "Authorize" button
- Enter JWT Bearer token
- Click "Authorize"
-
Try it:
- Select operation (e.g., "Get tasks list")
- Click "Try it out"
- Click "Execute"
- View request/response
- Use
#[ApiParameterRef]to reference DataStructure definitions - Follow 7-phase pattern in SaveRecordAction
- Apply defaults ONLY on CREATE (never UPDATE/PATCH)
- Use
isset()checks for PATCH support - Add WHY comments in complex logic
- Include module namespace in path:
/module-{name}/{resource}
- Define sanitization rules inline (use DataStructure)
- Apply defaults on UPDATE/PATCH operations
- Skip required validation
- Create endpoints without security attributes
- Use generic paths like
/tasks(conflicts with Core)
- Check controller filename ends with
Controller.php - Verify
#[ApiResource]attribute present - Ensure
processorparameter points to valid Processor class - Check that controller is in
Lib/RestAPI/directory
- Check Processor passes
$request['data']not entire$request - Verify
handleCRUDRequest()adds ID to request data
- Add
#[ResourceSecurity]attribute to controller - Import
SecurityTypeandResourceSecurityin use block - Verify JWT token in Authorization header
- Add translation keys to Messages/en.php and Messages/ru.php
- Format:
rest_{resource}_{Operation}for summaries - Format:
rest_tag_{ModuleName}{Resource}for tags
- Check file size doesn't exceed 10MB limit
- Verify file type is in allowed list (mp3, wav, pdf, png, jpeg)
- Ensure Core's FilesAPI is properly loaded
- Check browser console for JavaScript errors
GPLv3 - see MikoPBX License