Repository navigation
Routing
- Introduction
- Installation
- Quick Start (Beginner)
- Basic Routing
- Route Parameters
- Named Routes
- Route Groups
- Middleware
- Controllers
- Attribute-Based Routing
- Route Model Binding
- Rate Limiting
- CORS Support
- AJAX & Traditional Routes
- Resource Routes
- Error Handling
- Route Caching
- Advanced Topics
- API Reference
The Machinjiri Routing System is a powerful, flexible routing library for PHP applications. It provides:
- Fluent API for defining routes
- Attribute-based routing for controllers
- Middleware pipeline for request filtering
- Rate limiting to protect your endpoints
- CORS support for cross-origin requests
- Route model binding for automatic model resolution
- Route caching for production performance
- Named routes and URL generation
- Resource routing for CRUD operations
- AJAX detection for API vs web requests
The Routing system is already bundled in the framework. no need to Install separatrly.
Create an web.php in the routes/ folder:
<?php
use Mlangeni\Machinjiri\Core\Routing\Router;
// Define routes...
Router::get('/', function () {
return 'Hello, World!';
});
// Dispatch the request
Router::dispatch();use Mlangeni\Machinjiri\Core\Routing\Router;
// Simple GET route
Router::get('/', function () {
return 'Welcome to my website!';
});
// Route with a view
Router::get('/about', function () {
return '<h1>About Us</h1><p>We build great things.</p>';
});Router::get('/users', function () {
return 'List of users';
});
Router::post('/users', function () {
return 'Create a new user';
});
Router::put('/users/{id}', function ($id) {
return "Update user {$id}";
});
Router::delete('/users/{id}', function ($id) {
return "Delete user {$id}";
});Router::get('/hello/{name}', function ($name) {
return "Hello, {$name}!";
});
// Visit /hello/John → Returns "Hello, John!"Router::dispatch();| Method | Description |
|---|---|
Router::get() |
GET requests |
Router::post() |
POST requests |
Router::put() |
PUT requests |
Router::delete() |
DELETE requests |
Router::any() |
All common methods |
Router::ajax() |
AJAX-only routes |
Router::traditional() |
Non-AJAX routes |
// Respond to multiple methods
Router::any('/api/data', function () {
return json_encode(['status' => 'ok']);
});
// AJAX only (returns 404 for regular browser requests)
Router::ajax('/api/notifications', function () {
return json_encode(['notifications' => []]);
});
// Traditional only (returns 404 for AJAX requests)
Router::traditional('/contact', function () {
return '<form>...</form>';
});Router::get('/users/{id}', function ($id) {
return "User profile: {$id}";
}, 'user.profile');Router::get('/posts/{slug}', function ($slug) {
return "Post: {$slug}";
});Router::get('/users/{userId}/posts/{postId}', function ($userId, $postId) {
return "User {$userId}, Post {$postId}";
});Use where() to restrict parameter values:
Router::get('/user/{id}', function ($id) {
return "User ID: {$id}";
})->where('id', '[0-9]+');
// Or multiple constraints
Router::get('/post/{year}/{month}', function ($year, $month) {
return "Posts from {$year}-{$month}";
})->where([
'year' => '[0-9]{4}',
'month' => '[0-9]{2}'
]);Router::get('/archive/{year}/{month?}', function ($year, $month = null) {
if ($month) {
return "Archive: {$year}-{$month}";
}
return "Archive: {$year}";
});Named routes allow you to generate URLs dynamically:
// Define a named route
Router::get('/users/{id}/profile', function ($id) {
return "Profile for user {$id}";
}, 'user.profile');
// Generate URL
$url = Router::route('user.profile', ['id' => 42]);
// Returns: /users/42/profile
// Generate absolute URL
$absoluteUrl = Router::absoluteRoute('user.profile', ['id' => 42]);
// Returns: http://example.com/users/42/profile
// Get base URL
$baseUrl = Router::baseUrl();Group routes to share attributes like prefixes and middleware:
Router::group(['prefix' => '/admin'], function () {
Router::get('/dashboard', function () {
return 'Admin Dashboard';
});
// URL: /admin/dashboard
Router::get('/users', function () {
return 'Manage Users';
});
// URL: /admin/users
});Router::group(['prefix' => '/api', 'middleware' => ['Auth', 'Throttle']], function () {
Router::get('/users', function () {
return json_encode(['users' => []]);
});
});Router::group(['prefix' => '/admin'], function () {
Router::group(['prefix' => '/users'], function () {
Router::get('/', function () {
return 'User list';
});
// URL: /admin/users
Router::get('/{id}', function ($id) {
return "User {$id}";
});
// URL: /admin/users/{id}
});
});Middleware filters requests before they reach your route handler.
Create a middleware class in App\Middleware:
<?php
namespace App\Middleware;
use Mlangeni\Machinjiri\Core\Http\HttpRequest;
use Mlangeni\Machinjiri\Core\Http\HttpResponse;
class Auth
{
public function handle(HttpRequest $request, HttpResponse $response, callable $next, array $params)
{
if (!$request->getSession('user_id')) {
$response->setStatusCode(401);
$response->setBody('Unauthorized');
$response->send();
return;
}
return $next($params);
}
}Router::get('/dashboard', function () {
return 'Dashboard';
}, null, ['middleware' => ['Auth']]);Router::group(['middleware' => ['Auth', 'LogRequests']], function () {
Router::get('/profile', function () {
return 'User Profile';
});
});Middleware executes in the order defined:
// LogRequests runs first, then Auth
Router::group(['middleware' => ['LogRequests', 'Auth']], function () {
// Routes here
});Router::get('/users', 'UserController@index');
Router::post('/users', 'UserController@store');
Router::get('/users/{id}', 'UserController@show');
Router::put('/users/{id}', 'UserController@update');
Router::delete('/users/{id}', 'UserController@destroy');Router::get('/users', 'App\\Controllers\\UserController@index');<?php
namespace App\Controllers;
use Mlangeni\Machinjiri\Core\Http\HttpRequest;
use Mlangeni\Machinjiri\Core\Http\HttpResponse;
class UserController
{
public function index(HttpRequest $request, HttpResponse $response)
{
return '<h1>All Users</h1>';
}
public function show(HttpRequest $request, HttpResponse $response, $id)
{
return "<h1>User: {$id}</h1>";
}
public function store(HttpRequest $request, HttpResponse $response)
{
$data = $request->getPostParams();
return json_encode(['created' => true, 'data' => $data]);
}
}Router::get('/users', [UserController::class, 'index']);
// With instance
$controller = new UserController();
Router::get('/users', [$controller, 'index']);Define routes directly on controller methods using PHP 8 attributes.
<?php
namespace App\Controllers;
use Mlangeni\Machinjiri\Core\Routing\Attributes\Get;
use Mlangeni\Machinjiri\Core\Routing\Attributes\Post;
use Mlangeni\Machinjiri\Core\Routing\Attributes\Put;
use Mlangeni\Machinjiri\Core\Routing\Attributes\Delete;
use Mlangeni\Machinjiri\Core\Routing\Attributes\Prefix;
#[Prefix('/api/users')]
class UserController
{
#[Get('', 'users.index')]
public function index()
{
return json_encode(['users' => []]);
}
#[Get('/{id}', 'users.show')]
public function show($id)
{
return json_encode(['user' => $id]);
}
#[Post('', 'users.store')]
public function store()
{
return json_encode(['created' => true]);
}
#[Put('/{id}', 'users.update')]
public function update($id)
{
return json_encode(['updated' => $id]);
}
#[Delete('/{id}', 'users.destroy')]
public function destroy($id)
{
return json_encode(['deleted' => $id]);
}
}// Register a single controller
Router::controller(UserController::class);
// Register multiple controllers
Router::controllers([
UserController::class,
PostController::class,
CommentController::class,
]);| Attribute | HTTP Method(s) |
|---|---|
#[Get] |
GET |
#[Post] |
POST |
#[Put] |
PUT |
#[Delete] |
DELETE |
#[Patch] |
PATCH |
#[Options] |
OPTIONS |
#[Head] |
HEAD |
#[Any] |
All methods |
#[Route] |
Custom methods |
#[Prefix('/api/v1')]
#[Middleware(['Auth', 'JsonResponse'])]
#[Cors(allowedOrigins: ['https://example.com'])]
#[RateLimit('api')]
class ApiController
{
#[Get('/status')]
public function status()
{
return json_encode(['status' => 'ok']);
}
#[Get('/data')]
#[Middleware(['Cache'])] // Merged with class middleware
#[Where('id', '[0-9]+')]
public function data()
{
return json_encode(['data' => []]);
}
}class SecureController
{
#[Get('/public')]
public function publicEndpoint()
{
return 'Public data';
}
#[Get('/private')]
#[Middleware(['Auth'])]
#[RateLimit('sensitive')]
public function privateEndpoint()
{
return 'Private data';
}
#[Get('/admin')]
#[Middleware(['Auth', 'Admin'])]
#[Ajax] // AJAX only
public function adminEndpoint()
{
return json_encode(['admin' => true]);
}
}class MultiRouteController
{
#[Get('/posts', 'posts.list')]
#[Get('/articles', 'articles.list')]
public function list()
{
return 'Content list';
}
#[Middleware(['Auth'])]
#[Middleware(['LogAccess'])]
public function secured()
{
return 'Secured content';
}
}#[Prefix('/api/v1')]
class ApiController {}#[Middleware(['Auth', 'Throttle'])]
#[Middleware('LogRequests')] // Single middleware
class Controller {}#[Cors(
allowedOrigins: ['https://app.example.com'],
allowedMethods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 3600
)]
class ApiController {}#[RateLimit('api')] // Uses 'api' limiter config
class ApiController {}class Controller
{
#[Get('/api/data')]
#[Ajax] // Only responds to AJAX requests
public function ajaxOnly() {}
#[Get('/page')]
#[Traditional] // Only responds to non-AJAX requests
public function traditionalOnly() {}
}class Controller
{
#[Get('/user/{id}')]
#[Where('id', '[0-9]+')]
public function show($id) {}
}class Controller
{
#[Get('/user/{user}')]
#[Bind('user', User::class)] // Auto-resolve User model
public function show(User $user) {}
}Automatically resolve route parameters to model instances.
Router::get('/users/{user}', function ($user) {
// $user is now a User instance
return "User: {$user->name}";
}, null, ['bindings' => ['user' => User::class]]);The User class must have a findOrFail() method:
class User
{
public static function findOrFail($id)
{
$user = static::find($id);
if (!$user) {
throw new Exception("User not found");
}
return $user;
}
}Router::get('/posts/{slug}', function ($post) {
return "Post: {$post->title}";
}, null, [
'bindings' => [
'slug' => function ($slug) {
return Post::where('slug', $slug)->firstOrFail();
}
]
]);Router::bind('user', User::class);
Router::bind('post', function ($id) {
return Post::findOrFail($id);
});class UserController
{
#[Get('/users/{user}')]
#[Bind('user', User::class)]
public function show($user)
{
return "User: {$user->name}";
}
}Protect your routes from abuse with rate limiting.
Define limiters in RoutingConfig:
use Mlangeni\Machinjiri\Core\Routing\RoutingConfig;
$config = new RoutingConfig(
rateLimiters: [
'api' => [
'max_requests' => 60,
'period' => 60, // seconds
],
'login' => [
'max_requests' => 5,
'period' => 300, // 5 attempts per 5 minutes
],
'sensitive' => [
'max_requests' => 10,
'period' => 3600, // 10 requests per hour
],
]
);Router::post('/login', 'AuthController@login', null, ['rate_limit' => 'login']);Router::group(['prefix' => '/api', 'rate_limit' => 'api'], function () {
Router::get('/users', 'UserController@index');
Router::get('/posts', 'PostController@index');
});class ApiController
{
#[Post('/login')]
#[RateLimit('login')]
public function login() {}
#[Get('/sensitive-data')]
#[RateLimit('sensitive')]
public function sensitiveData() {}
}When rate limited, the router returns a 429 response:
{
"error": "Rate Limit Exceeded",
"message": "Too many requests. Please try again later.",
"code": 429,
"retry_after": 60,
"timestamp": 1700000000
}With header: Retry-After: 60
Handle Cross-Origin Resource Sharing automatically.
Router::cors([
'allowed_origins' => ['https://app.example.com', 'https://admin.example.com'],
'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE'],
'allowed_headers' => ['Content-Type', 'Authorization'],
'max_age' => 86400,
], function () {
Router::get('/api/data', 'ApiController@data');
Router::post('/api/data', 'ApiController@store');
});Router::get('/api/public', 'ApiController@public', null, [
'cors' => [
'allowed_origins' => ['*'],
'allowed_methods' => ['GET'],
]
]);#[Cors(
allowedOrigins: ['https://app.example.com'],
allowedMethods: ['GET', 'POST'],
allowedHeaders: ['Content-Type']
)]
class ApiController
{
#[Get('/data')]
public function data() {}
#[Post('/data')]
#[Cors(allowedOrigins: ['*'])] // Override for this method
public function store() {}
}The router automatically handles OPTIONS preflight requests for routes with CORS configuration.
Default CORS configuration in RoutingConfig:
public array $corsDefaults = [
'allowed_origins' => ['*'],
'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
'allowed_headers' => ['*'],
'max_age' => 86400,
];Control whether routes respond to AJAX or traditional browser requests.
Router::ajax('/api/notifications', function () {
return json_encode(['notifications' => []]);
});Returns 404 for non-AJAX requests. AJAX detection uses the X-Requested-With: XMLHttpRequest header.
Router::traditional('/contact', function () {
return '<form>...</form>';
});Returns 404 for AJAX requests.
class Controller
{
#[Get('/api/data')]
#[Ajax]
public function ajaxOnly() {}
#[Get('/page')]
#[Traditional]
public function traditionalOnly() {}
#[Get('/mixed')]
public function mixed() {} // Responds to both
}The router also checks the Accept header for application/json:
// Browser with Accept: application/json
// Returns JSON error responsesQuickly generate CRUD routes for a resource.
Router::resource('users', 'UserController');This generates:
| Method | URI | Action | Route Name |
|---|---|---|---|
| GET | /users | index | users.index |
| GET | /users/create | create | users.create |
| POST | /users | store | users.store |
| GET | /users/{id} | show | users.show |
| GET | /users/{id}/edit | edit | users.edit |
| PUT | /users/{id} | update | users.update |
| DELETE | /users/{id} | destroy | users.destroy |
// Only index and show
Router::resource('posts', 'PostController', ['only' => ['index', 'show']]);
// All except create and edit
Router::resource('posts', 'PostController', ['except' => ['create', 'edit']]);When no route matches, the router returns a 404 response:
-
JSON (for AJAX/API requests):
{ "error": "Not Found", "message": "The requested resource was not found", "code": 404, "timestamp": 1700000000 } -
HTML (for browser requests): Renders an error page
Create error templates in resources/errors/:
resources/errors/
├── 404.php
├── 429.php
├── 500.php
└── 503.php
Example 404.php:
<!DOCTYPE html>
<html>
<head>
<title>404 - Not Found</title>
</head>
<body>
<h1>404 - Page Not Found</h1>
<p><?= htmlspecialchars($message ?? 'The page you requested does not exist.') ?></p>
<a href="/">Go Home</a>
</body>
</html>Define a fallback for unmatched routes:
Router::fallback(function () {
return 'Custom 404 page';
});The router automatically shows a maintenance page when the application is down:
// When container->isDownForMaintenance() returns true
// Returns 503 Service UnavailableImprove production performance by caching routes.
use Mlangeni\Machinjiri\Core\Routing\RouteCache;
use Mlangeni\Machinjiri\Core\Routing\Router;
$router = Router::getInstance();
$collection = $router->getCollection();
$cache = new RouteCache('/path/to/cache/routes.cache', $collection);
$cache->generate();Set the cache file path in RoutingConfig:
$config = new RoutingConfig(
cacheFile: '/var/www/app/storage/cache/routes.cache'
);The router automatically loads cached routes if the cache file exists:
// In Router::__construct()
$this->loadCachedRoutes();-
Closure-based routes cannot be cached (handlers become
null) - Only string and array handlers are fully cached
- Clear the cache after route changes
// Simply delete the cache file
unlink('/path/to/cache/routes.cache');HTML forms can only submit GET or POST. Use method spoofing for PUT/DELETE:
<form method="POST" action="/users/42">
<input type="hidden" name="_method" value="PUT">
<!-- form fields -->
</form>The router automatically detects _method and treats the request as PUT.
The router automatically detects the base path for applications in subdirectories:
https://example.com/myapp/users
Routes are defined as /users but matched correctly.
Override automatic detection:
// In Container
Container::setRoutingBase('/myapp');Add constraints after defining a route:
Router::get('/post/{id}', 'PostController@show')
->where('id', '[0-9]+')
->where('slug', '[a-z0-9-]+');The router supports multiple handler types:
// Closure
Router::get('/', function () { return 'Hello'; });
// Controller string
Router::get('/', 'HomeController@index');
// Fully qualified
Router::get('/', 'App\\Controllers\\HomeController@index');
// Array [class, method]
Router::get('/', [HomeController::class, 'index']);
// Array [instance, method]
Router::get('/', [$controller, 'index']);Handlers can return different types:
// String → HTML response
Router::get('/', function () {
return '<h1>Hello</h1>';
});
// Array/Object → JSON (for AJAX/API)
Router::get('/api/data', function () {
return ['users' => []];
});
// Using HttpResponse directly
Router::get('/custom', function ($request, $response) {
$response->setStatusCode(201)
->setJsonBody(['created' => true])
->send();
});Handlers receive request and response objects:
Router::get('/info', function (HttpRequest $request, HttpResponse $response) {
$method = $request->getMethod();
$uri = $request->getUri();
$isAjax = $request->isAjax();
$ip = $request->getIp();
return json_encode([
'method' => $method,
'uri' => $uri,
'ajax' => $isAjax,
'ip' => $ip,
]);
});Middleware forms a pipeline:
Request → Middleware1 → Middleware2 → Handler → Response
Each middleware can:
- Modify the request
- Short-circuit (return early)
- Pass to next middleware
- Modify the response
class LogRequests
{
public function handle($request, $response, $next, $params)
{
// Before handler
error_log("Request: " . $request->getMethod() . " " . $request->getUri());
// Call next middleware/handler
$result = $next($params);
// After handler
error_log("Response sent");
return $result;
}
}The router instantiates controllers directly. For DI, use the container:
class UserController
{
public function __construct()
{
$this->userService = Container::getInstance()->resolve(UserService::class);
}
}Or use a custom handler resolver:
class ContainerAwareResolver implements HandlerResolverInterface
{
public function execute($handler, $params, $request, $response)
{
if (is_string($handler) && str_contains($handler, '@')) {
[$class, $method] = explode('@', $handler);
$instance = Container::getInstance()->resolve($class);
return call_user_func_array([$instance, $method], [$request, $response, ...array_values($params)]);
}
// ... handle other types
}
}Router::group([
'prefix' => '/api/v1',
'middleware' => ['Auth', 'JsonResponse'],
'cors' => [
'allowed_origins' => ['https://app.example.com'],
'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE'],
],
'rate_limit' => 'api',
], function () {
Router::get('/users', 'UserController@index');
Router::post('/users', 'UserController@store');
});// Reset router between tests
Router::resetInstance();
// Define test routes
Router::get('/test', function () {
return 'test response';
});
// Simulate request (requires HttpRequest setup)| Method | Description |
|---|---|
Router::get($pattern, $handler, $name, $options) |
Define GET route |
Router::post($pattern, $handler, $name, $options) |
Define POST route |
Router::put($pattern, $handler, $name, $options) |
Define PUT route |
Router::delete($pattern, $handler, $name, $options) |
Define DELETE route |
Router::any($pattern, $handler, $name, $options) |
Define multi-method route |
Router::ajax($pattern, $handler, $name, $options) |
Define AJAX-only route |
Router::traditional($pattern, $handler, $name, $options) |
Define non-AJAX route |
Router::group($attributes, $callback) |
Create route group |
Router::middleware($middleware, $callback) |
Apply middleware group |
Router::cors($config, $callback) |
Apply CORS group |
Router::resource($name, $controller, $options) |
Generate resource routes |
Router::fallback($handler) |
Set fallback handler |
Router::bind($param, $resolver) |
Add global binding |
Router::controller($class) |
Register attribute controller |
Router::controllers($classes) |
Register multiple controllers |
Router::route($name, $params) |
Generate URL for named route |
Router::absoluteRoute($name, $params) |
Generate absolute URL |
Router::baseUrl() |
Get base URL |
Router::dispatch() |
Dispatch the current request |
Router::getInstance() |
Get router instance |
Router::resetInstance() |
Reset singleton |
| Method | Description |
|---|---|
addRoute($methods, $pattern, $handler, $name, $options) |
Add route |
addResource($name, $controller, $options) |
Add resource routes |
setFallback($handler) |
Set fallback |
addBinding($param, $resolver) |
Add binding |
where($param, $regex) |
Add constraint to last route |
instanceGroup($attributes, $callback) |
Create group |
instanceDispatch() |
Dispatch request |
| Option | Type | Description |
|---|---|---|
middleware |
array |
Middleware to apply |
cors |
array |
CORS configuration |
rate_limit |
string |
Rate limiter name |
ajax_only |
bool |
AJAX-only route |
no_ajax |
bool |
Non-AJAX route |
where |
array |
Parameter constraints |
bindings |
array |
Model bindings |
new RoutingConfig(
cacheFile: '/path/to/routes.cache',
errorsDir: '/path/to/errors',
controllersNamespace: 'App\\Controllers',
rateLimiters: [
'api' => ['max_requests' => 60, 'period' => 60],
],
viewsBasePath: '/path/to/views',
viewsCachePath: '/path/to/views/cache',
enableCsrf: true,
corsDefaults: [
'allowed_origins' => ['*'],
'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
'allowed_headers' => ['*'],
'max_age' => 86400,
]
);| Attribute | Target | Parameters |
|---|---|---|
#[Get] |
Method |
$pattern, $name, $options
|
#[Post] |
Method |
$pattern, $name, $options
|
#[Put] |
Method |
$pattern, $name, $options
|
#[Delete] |
Method |
$pattern, $name, $options
|
#[Patch] |
Method |
$pattern, $name, $options
|
#[Options] |
Method |
$pattern, $name, $options
|
#[Head] |
Method |
$pattern, $name, $options
|
#[Any] |
Method |
$pattern, $name, $options
|
#[Route] |
Method |
$methods, $pattern, $name, $options
|
#[Prefix] |
Class | $prefix |
#[Middleware] |
Class/Method | $middleware |
#[Cors] |
Class/Method |
$allowedOrigins, $allowedMethods, $allowedHeaders, $maxAge
|
#[RateLimit] |
Method | $limiter |
#[Ajax] |
Method | (none) |
#[Traditional] |
Method | (none) |
#[Where] |
Method |
$param, $regex
|
#[Bind] |
Method |
$param, $model
|
<?php
use Mlangeni\Machinjiri\Core\Routing\Router;
use Mlangeni\Machinjiri\Core\Routing\RoutingConfig;
// Configure
$config = new RoutingConfig(
rateLimiters: [
'api' => ['max_requests' => 100, 'period' => 60],
'auth' => ['max_requests' => 5, 'period' => 300],
]
);
// Public API
Router::group(['prefix' => '/api', 'rate_limit' => 'api'], function () {
Router::get('/status', function () {
return ['status' => 'ok', 'version' => '1.0.0'];
});
Router::get('/posts', 'PostController@index', 'api.posts.index');
Router::get('/posts/{id}', 'PostController@show', 'api.posts.show')
->where('id', '[0-9]+');
});
// Authenticated API
Router::group([
'prefix' => '/api',
'middleware' => ['Auth'],
'cors' => ['allowed_origins' => ['https://app.example.com']],
], function () {
Router::post('/posts', 'PostController@store', 'api.posts.store');
Router::put('/posts/{id}', 'PostController@update', 'api.posts.update');
Router::delete('/posts/{id}', 'PostController@destroy', 'api.posts.destroy');
Router::get('/me', 'UserController@me', 'api.me');
});
// Auth endpoints (strict rate limit)
Router::group(['prefix' => '/auth', 'rate_limit' => 'auth'], function () {
Router::post('/login', 'AuthController@login');
Router::post('/register', 'AuthController@register');
Router::post('/forgot-password', 'AuthController@forgotPassword');
});
// Web routes
Router::get('/', 'HomeController@index', 'home');
Router::get('/about', 'PageController@about', 'about');
Router::get('/contact', 'PageController@contact', 'contact')
->where('id', '[0-9]+');
// Fallback
Router::fallback(function () {
return 'Page not found';
});
// Dispatch
Router::dispatch();<?php
namespace App\Controllers;
use Mlangeni\Machinjiri\Core\Routing\Attributes\{
Get, Post, Put, Delete, Prefix, Middleware, Cors, RateLimit, Where, Bind, Ajax
};
#[Prefix('/api/v1')]
#[Middleware(['JsonResponse'])]
#[Cors(allowedOrigins: ['https://app.example.com'])]
class ProductController
{
#[Get('/products', 'api.products.index')]
public function index()
{
return ['products' => []];
}
#[Get('/products/{id}', 'api.products.show')]
#[Where('id', '[0-9]+')]
public function show($id)
{
return ['product' => $id];
}
#[Post('/products', 'api.products.store')]
#[Middleware(['Auth'])]
#[RateLimit('api')]
public function store()
{
return ['created' => true];
}
#[Put('/products/{id}', 'api.products.update')]
#[Middleware(['Auth'])]
#[Bind('id', Product::class)]
public function update($product)
{
return ['updated' => $product->id];
}
#[Delete('/products/{id}', 'api.products.destroy')]
#[Middleware(['Auth', 'Admin'])]
public function destroy($id)
{
return ['deleted' => $id];
}
#[Get('/products/search')]
#[Ajax]
public function search()
{
return ['results' => []];
}
}- Check the HTTP method matches exactly
- Verify the URI path (trailing slashes are normalized)
- Check parameter constraints
- Ensure base path detection is correct
- Verify middleware class exists in
App\Middleware - Check middleware implements
handle()method - Verify middleware name spelling in route definition
- Ensure the route has CORS configuration
- Check preflight handling for OPTIONS requests
- Verify allowed origins include the requesting origin
- Check the limiter name matches configuration
- Verify cache manager is available
- Check the cache store is working
- Ensure PHP 8+ is used
- Check attributes are properly imported
- Verify controller is registered with
Router::controller() - Check method is public and not static
Machinjiri Docs
- Home
- Learn
- Build
- Reference
-
Tutorials
- Routing
- Controllers and Middleware
- Views and Templates
- Database ORM
- Eloquent Database ORM
- Errors and Exceptions
- Forms and Validation
- Http
- Events and Logging
- Services and DI
- Security
- Authentication
- Caching
- File System
- Date and Time
- Debugging
- Queue and Jobs
- Task Scheduling
- Transport (Mail, SMS)
- LDAP
- Notifications
- Webhooks
- UUID Generations
- Terminal
- Helper Function