DotApp Router
The Router is a key component of the DotApp Framework, managing the routing of HTTP requests within the application. It allows you to define how requests (e.g., GET, POST) are mapped to specific callback functions, controllers, or middleware. The Router is designed to handle both static and dynamic routes, support hooks (before and after), and provide flexibility in building web applications.
1.1 What is the Router?
The Router in the DotApp Framework is a class Dotsystems\App\Parts\Router that processes incoming HTTP requests and directs them to the appropriate handlers. It works in conjunction with the Request object, which contains information about the request (path, method, variables). Its primary role is to simplify route definition and ensure the correct code is executed for a given URL and HTTP method.
The Router is integrated directly into the framework's core, so you don’t need to install or configure it separately – simply use the Router facade: Router::get(...).
1.2 Key Features
The Router offers a wide range of features that make application development easier:
- HTTP Method Support: Define routes for
GET,POST,PUT,DELETE,PATCH,OPTIONS,HEAD,TRACE, and the universalANYmethod. - Dynamic Routes: Use variables (e.g.,
{id}) and regular expressions to capture parts of the URL. - Middleware: Support for
beforeandafterhooks to execute logic before and after the main handler. - Request Object: Passes request information to callbacks and controllers.
- Flexibility: Ability to use anonymous functions, controllers, or middleware via strings (e.g.,
"Module:Controller@method!"). - Chaining: Method chaining for cleaner code.
1.3 Basic Routing Principles
The Router compares the current URL (obtained from $request->getPath()) and HTTP method (from $request->getMethod()) with defined routes. If a match is found:
- Any
beforehooks are executed. - The main logic (callback, controller, or middleware) is performed.
- Any
afterhooks are executed.
Routes can be:
- Static: Exact URL match (e.g.,
/home). - Dynamic: Contain variables or wildcards (e.g.,
/user/{id}). - First Match Wins: The first matching route is used; subsequent matches are ignored.
The Router resolves requests using the resolve() method, which is typically called automatically within the DotApp lifecycle.
Example of a Basic Route:
Register routes in a module’s initialize($dotApp) method in app/modules/{Module}/module.init.php. Controllers live in app/modules/{Module}/Controllers/ and are called as 'Module:Controller@method!'.
// app/modules/HelloWorld/module.init.php → initialize($dotApp)
Router::get('/home', function ($request) {
return "Welcome to the homepage!";
}, Router::STATIC_ROUTE);
When accessing the URL http://example.com/home, the text "Welcome to the homepage!" is displayed.
2. Getting Started
This chapter guides you through the basics of working with the Router in the DotApp Framework – from initialization to defining your first route.
2.1 Router Initialization
The Router is a core service. Register application routes with the Router:: facade from each module’s initialize($dotApp). Controllers live in app/modules/{Module}/Controllers/.
Technical Details:
- The
Routeris an instance of theDotsystems\App\Parts\Routerclass. - During construction, it receives
$dotAppObj(an instance of the mainDotAppclass), giving it access to theRequestobject and other framework services.
2.2 Accessing the Router in DotApp
Register routes in module.init.php with the Router facade. The $request object is passed into callbacks and controller methods and carries the current path, method, and variables.
Example of Access:
// Check the current path
echo $request->getPath(); // E.g., "/home"
// Check the HTTP method
echo $request->getMethod(); // E.g., "get"
2.3 Defining Your First Route
The simplest way to start with the Router is to define a basic route using one of the HTTP methods (e.g., get()). A route can be linked to an anonymous function (callback), a controller, or middleware.
Example of a First Route with a Callback:
// app/modules/HelloWorld/module.init.php → initialize($dotApp)
Router::get('/home', function ($request) {
return "Welcome to the homepage!";
}, Router::STATIC_ROUTE);
Explanation:
/home: Static URL path.function($request): Callback that accepts theRequestobject and returns a response.- When calling
http://example.com/home, the text "Welcome to the homepage!" is displayed.
Example with a Controller:
Suppose you have a controller Home in app/modules/HelloWorld/Controllers/Home.php with a method index:
// app/modules/HelloWorld/Controllers/Home.php
namespace Dotsystems\App\Modules\HelloWorld\Controllers;
class Home extends \Dotsystems\App\Parts\Controller {
public static function index($request) {
return "This is the homepage from the controller!";
}
}
// app/modules/HelloWorld/module.init.php
Router::get('/home', 'HelloWorld:Home@index!', Router::STATIC_ROUTE);
Explanation:
'HelloWorld:Home@index!': ModuleHelloWorld, controllerHome, static methodindex. The trailing!disables DI on that method.- The
Routerautomatically loads and calls this method with theRequestobject.
Running the Routing:
The framework resolves routes after modules load. Application routes are declared in module.init.php.
// app/modules/HelloWorld/module.init.php
public function initialize($dotApp) {
Router::get('/home', function ($request) {
return "Welcome!";
}, Router::STATIC_ROUTE);
}
3. Defining Routes
This chapter explains how to define routes in the Router of the DotApp Framework. The Router supports various definition methods – from basic HTTP methods to dynamic routes with variables and working with controllers.
3.1 Basic HTTP Methods (GET, POST, etc.)
The Router provides methods for all standard HTTP methods: get(), post(), put(), delete(), patch(), options(), head(), and trace(). Each method defines a route for a specific HTTP request.
Example of a GET Route:
Router::get('/about', function ($request) {
return "This is the About Us page!";
});
Example of a POST Route:
Router::post('/submit', function ($request) {
return "The form has been submitted!";
});
Note: Each method takes the URL path as the first parameter and a callback (or controller reference) as the second parameter. The callback always receives the $request object.
3.2 The match() Method for Multiple Methods and URLs
The match() method allows defining a route for multiple HTTP methods at once or for an array of URLs. It’s a more flexible approach compared to standalone methods like get() or post().
Example with Multiple Methods:
Router::match(['get', 'post'], '/contact', function ($request) {
return "This is the contact page!";
});
This route works for both GET and POST requests to /contact.
Example with Multiple URLs:
Router::match(['get'], ['/home', '/index'], function ($request) {
return "Welcome to the homepage!";
});
The route captures requests to both /home and /index.
3.3 Static vs. Dynamic Routes
The Router distinguishes between static and dynamic routes:
- Static Routes: Exact URL match (e.g.,
/home). - Dynamic Routes: Contain variables or wildcards (e.g.,
/user/{id}).
Example of a Static Route:
Router::get('/profile', function ($request) {
return "This is a static profile!";
});
Example of a Dynamic Route:
Router::get('/user/{id}', function ($request) {
return "User profile with ID: " . $request->matchData()['id'];
});
For the URL /user/123, it displays "User profile with ID: 123".
3.4 Using Variables in Routes
Dynamic routes can include variables marked with curly braces (e.g., {id}). These variables are automatically extracted and available via $request->matchData().
Basic Usage:
Router::get('/article/{slug}', function ($request) {
return "Article: " . $request->matchData()['slug'];
});
For /article/how-to-cook, it displays "Article: how-to-cook".
Typed Variables:
The Router also supports variable typing:
{param:s}: String (no slashes).{param:i}: Integer.{param:l}: Letters only.{param:s?}: Optional typed parameter (the?belongs inside the braces).{param*}: Wildcard (captures everything).
Router::get('/user/{id:i}', function ($request) {
return "User ID: " . $request->matchData()['id'];
});
Works only for numbers, e.g., /user/123, but not /user/abc.
3.5 Working with Controllers and Middleware
In addition to anonymous functions, you can map routes to controllers or middleware using a string in the format "Module:Controller@method!" or "#Module:Middleware@method!".
Example with a Controller:
// app/modules/HelloWorld/Controllers/User.php
namespace Dotsystems\App\Modules\HelloWorld\Controllers;
class User extends \Dotsystems\App\Parts\Controller {
public static function show($request) {
return "Displaying the user!";
}
}
// Route definition
Router::get('/user', 'HelloWorld:User@show!');
Example with Middleware:
// app/modules/HelloWorld/Middleware/AuthMiddleware.php
namespace Dotsystems\App\Modules\HelloWorld\Middleware;
class AuthMiddleware extends \Dotsystems\App\Parts\ModuleMiddleware {
public static function check($request) {
// Return a Response to short-circuit. Returning nothing continues the route.
if (!\Dotsystems\App\Parts\Auth::isLogged()) {
return new \Dotsystems\App\Parts\Response(403, 'Forbidden');
}
}
}
// app/modules/HelloWorld/module.init.php → initialize($dotApp)
Router::get('/secure', 'HelloWorld:User@show!')
->before('#HelloWorld:AuthMiddleware@check!');
Note: Functions must be defined as public static and accept $request as a parameter. Middleware classes are almost always attached with ->before('#Module:Class@method!'), not used as the main route handler.
4. Working with the Request Object
The Request object is an integral part of the Router in the DotApp Framework. It carries information about the current HTTP request and is automatically passed to callbacks, controllers, and middleware. This chapter explains how it works and how to use it effectively.
4.1 What is Request?
The Request is an instance of the Dotsystems\App\Parts\Request class, serving as an interface for working with request data. It contains information about the path, HTTP method, variables from dynamic routes, and other request attributes. It is automatically created during the Router initialization and is accessible via $request.
Key Features:
- Retrieving the current URL path and method.
- Accessing variables from dynamic routes via
matchData(). - Passing data to callbacks and hooks.
4.2 Accessing Data from Request
The Request object provides methods to retrieve basic request information:
getPath(): Returns the current URL path (e.g.,/home).getMethod(): Returns the HTTP method (e.g.,get,post).matchData(): Returns an array of variables extracted from a dynamic route.hookData(): Returns data assigned to hooks (used with standalonebefore/after).
Example of Access:
Router::get('/user/{id}', function ($request) {
$path = $request->getPath(); // "/user/123"
$method = $request->getMethod(); // "get"
$id = $request->matchData()['id']; // "123"
return "Path: $path, Method: $method, ID: $id";
});
For a request to /user/123, it displays: "Path: /user/123, Method: get, ID: 123".
4.3 Using Request in Callbacks
The Request object is automatically passed as a parameter to all callbacks, controllers, and middleware defined in routes. It allows you to work with request data directly within the route’s logic.
Example with an Anonymous Function:
Router::get('/profile/{name}', function ($request) {
$name = $request->matchData()['name'];
return "Hello, $name!";
});
For /profile/Jano, it displays: "Hello, Jano!".
Example with a Controller:
// app/modules/HelloWorld/Controllers/Profile.php
namespace Dotsystems\App\Modules\HelloWorld\Controllers;
class Profile extends \Dotsystems\App\Parts\Controller {
public static function show($request) {
$name = $request->matchData()['name'] ?? '';
return "Profile for: $name";
}
}
// app/modules/HelloWorld/module.init.php → initialize($dotApp)
Router::get('/profile/{name}', 'HelloWorld:Profile@show!');
The result is the same as with the anonymous function.
Example with Middleware:
// app/modules/HelloWorld/Middleware/CheckMiddleware.php
namespace Dotsystems\App\Modules\HelloWorld\Middleware;
class CheckMiddleware extends \Dotsystems\App\Parts\ModuleMiddleware {
public static function verify($request) {
// Optional logging. Do not return a Response unless you want to stop the request.
$path = $request->getPath();
}
}
// app/modules/HelloWorld/module.init.php → initialize($dotApp)
Router::get('/check', 'HelloWorld:User@show!')
->before('#HelloWorld:CheckMiddleware@verify!');
For /check, the middleware runs first. If it does not return a Response, the User@show! handler runs.
Note: matchData() returns an empty array if the route contains no dynamic variables. Verify the existence of a key before use, e.g., isset($request->matchData()['id']), to avoid errors.
5. Middleware (Before and After Hooks)
Middleware in the Router of the DotApp Framework allows you to execute additional logic before or after the main route handler. These "hooks" are defined using the before() and after() methods and are ideal for tasks such as authentication, logging, or response modification.
5.1 What Are Hooks?
Hooks are functions that run automatically at specific stages of route processing:
before: Executes before the main route logic (e.g., callback or controller).after: Executes after the main logic, with access to the route’s result.
Hooks accept the $request object as a parameter and can be defined globally, for a specific route, or for a method with a route.
5.2 Defining before()
The before() method is used to add logic that executes before the main handler. It can be applied in three ways:
- Globally: For all routes.
- For a Specific Route: Only for a given path.
- For a Method and Route: Specifically for an HTTP method and path.
Global Before:
Router::before(function ($request) {
return "Before every route!";
});
Router::get('/test', function ($request) {
return "Test page";
});
The hook runs for all routes, e.g., for /test, "Before every route!" executes first.
Before for a Specific Route:
Router::get('/secure', function ($request) {
return "Secure page";
})->before(function ($request) {
return "Verifying access...";
});
The hook runs only for /secure.
Before with a Method and Route:
Router::before('get', '/login', function ($request) {
return "Checking login for GET";
});
Router::get('/login', function ($request) {
return "Login page";
});
5.3 Defining after()
The after() method runs after the main handler and has the same definition options as before(). It’s useful for modifying results or logging.
Global After:
Router::after(function ($request) {
return "After every route!";
});
Router::get('/test', function ($request) {
return "Test page";
});
The hook runs after every route, e.g., for /test, "Test page" executes first, followed by "After every route!".
After for a Specific Route:
Router::get('/profile', function ($request) {
return "Profile page";
})->after(function ($request) {
return "Profile has been displayed";
});
After with a Method and Route:
Router::after('post', '/submit', function ($request) {
return "Form has been processed";
});
Router::post('/submit', function ($request) {
return "Submission successful";
});
5.4 Using with Multiple Routes
You can assign hooks to multiple routes at once using an array of paths with the match() method or by calling before()/after() separately.
Example with Match:
Router::match(['get'], ['/home', '/index'], function ($request) {
return "Homepage";
})->before(function ($request) {
return "Before the homepage";
})->after(function ($request) {
return "After the homepage";
});
The hooks apply to both paths: /home and /index.
Example with an Array of Paths:
Router::before('get', ['/page1', '/page2'], function ($request) {
return "Before the pages";
});
Router::get('/page1', function ($request) {
return "Page 1";
});
Router::get('/page2', function ($request) {
return "Page 2";
});
Note: The output from hooks is appended to the route’s response. To modify the response, work directly with $request->response->body in the hook (more in advanced features).
6. Error and Exception Handling
The Router in the DotApp Framework allows developers to manage errors and exceptions that occur during request processing. This chapter explains how to handle standard errors like 404 and implement custom error-handling logic using callbacks and hooks.
6.1 Handling 404 Errors
If the Router finds no match, it fires dotapp.router.resolve.404. If no listener handles it, Router::errorHandle(404, $view) can render error_{$view}. Otherwise the framework sends an empty 404 and stops.
Preferred: event listener in module.listeners.php
use Dotsystems\App\Parts\Events;
use Dotsystems\App\Parts\Response;
Events::on('dotapp.router.resolve.404', function () {
return new Response(404, 'Page not found');
});
Alternative: named error view
Router::errorHandle(404, 'notfound');
That looks for a view named error_notfound. Register the listener or error view from a module.
6.2 Custom Error Handling
Developers can implement custom error-handling logic directly in callbacks or middleware using conditions and HTTP codes.
Example with a Condition in a Callback:
Router::get('/user/{id:i}', function ($request) {
$id = $request->matchData()['id'];
if ($id > 100) {
http_response_code(403);
return "Access forbidden for IDs greater than 100!";
}
return "User profile: $id";
});
For /user/150, it displays "Access forbidden for IDs greater than 100!" with code 403.
Example with Middleware:
Router::get('/user/{id:i}', function ($request) {
$id = $request->matchData()['id'];
return "User profile: $id";
})->before(function ($request) {
$id = $request->matchData()['id'];
if (!isset($id)) {
http_response_code(400);
return "ID is missing!";
}
});
For /user/, it displays "ID is missing!" with code 400.
Note: Using http_response_code() in callbacks or hooks allows setting custom error states. It’s up to the developer whether to terminate the script with exit or return an error message.
7. Advanced Features
The Router in the DotApp Framework offers advanced features that extend its capabilities. This chapter covers method chaining, dynamic URL matching, a detailed explanation of creating dynamic addresses, and defining API endpoints.
7.1 Method Chaining
The Router supports method chaining, allowing you to define routes, hooks, and other settings in a single command. This improves code readability and organization.
Example of Chaining:
Router::get('/profile/{id}', function ($request) {
$id = $request->matchData()['id'];
return "Profile ID: $id";
})->before(function ($request) {
return "Checking before displaying the profile";
})->after(function ($request) {
return "Profile displayed";
});
For /profile/123, before, the main logic, and after execute sequentially.
7.2 Dynamic Route Matching (matchUrl())
The matchUrl() method is used for manually matching a URL against a routing pattern. It returns an array of extracted variables if the pattern matches, or false if not. It’s useful for custom validations or route testing.
Example of Use:
Router::get('/test', function ($request) {
$pattern = '/user/{id:i}';
$url = '/user/123';
$match = Router::matchUrl($pattern, $url);
if ($match !== false) {
return "Match! ID: " . $match['id'];
}
return "No match";
});
For /test, it displays "Match! ID: 123".
7.3 Dynamic Addresses and Patterns
Dynamic addresses in the Router allow defining routes with variables and optional parts using special syntax. These patterns are recognized consistently across methods (e.g., get(), post(), match()), and variables are available via $request->matchData(). Below is a detailed explanation with an example and a list of the most common patterns.
Example of a Dynamic Address:
Router::get('/documentation/intro(?:/{language})?', function ($request) {
$language = $request->matchData()['language'] ?? 'default';
return "Introductory documentation, language: $language";
});
Explanation:
/documentation/intro(?:/{language})?: Defines a route where{language}is an optional part (marked with?:and?)./documentation/intro: Valid (language is "default")./documentation/intro/eng: Valid (language is "eng")./documentation/intro/: Invalid (theRouterexpects a value after the slash if present).
The language variable is extracted into $request->matchData() if provided, otherwise it’s null.
Most Commonly Used Patterns:
Here’s a list of 10 common dynamic address patterns used in web applications, with examples and explanations:
/{resource}/{id:i}- Basic CRUD RouteRouter::get('/users/{id:i}', function ($request) { return "User ID: " . $request->matchData()['id']; });Valid:
/users/123, Invalid:/users/abc/{category}/{slug:s}- Category and Article SlugRouter::get('/blog/{category}/{slug:s}', function ($request) { return "Category: " . $request->matchData()['category'] . ", Slug: " . $request->matchData()['slug']; });Valid:
/blog/tech/how-to-code/api/v{version}/{endpoint}- Versioned APIRouter::get('/api/v{version}/{endpoint}', function ($request) { return "API v" . $request->matchData()['version'] . ": " . $request->matchData()['endpoint']; });Valid:
/api/v1/users/{page}(?:/{subpage})?- Optional SubpageRouter::get('/docs/{page}(?:/{subpage})?', function ($request) { $subpage = $request->matchData()['subpage'] ?? 'main'; return "Page: " . $request->matchData()['page'] . ", Subpage: $subpage"; });Valid:
/docs/intro,/docs/intro/setup/{type}/{id:i}/{action}- Action on a ResourceRouter::get('/posts/{id:i}/{action}', function ($request) { return "ID: " . $request->matchData()['id'] . ", Action: " . $request->matchData()['action']; });Valid:
/posts/5/edit/{resource}/{filter:s?}- Optional FilterRouter::get('/products/{filter:s?}', function ($request) { $filter = $request->matchData()['filter'] ?? 'all'; return "Products, filter: $filter"; });Valid:
/products,/products/new/{path*}- Wildcard for Entire PathRouter::get('/files/{path*}', function ($request) { return "File path: " . $request->matchData()['path']; });Valid:
/files/images/photo.jpg/{lang:l}/{section}- Language and SectionRouter::get('/{lang:l}/{section}', function ($request) { return "Language: " . $request->matchData()['lang'] . ", Section: " . $request->matchData()['section']; });Valid:
/en/news, Invalid:/123/news/search(?:/{query})?- Optional Search QueryRouter::get('/search(?:/{query})?', function ($request) { $query = $request->matchData()['query'] ?? 'empty'; return "Search: $query"; });Valid:
/search,/search/php/{resource}/{id:i}(?:/{extra})?- Resource with an Optional ParameterRouter::get('/users/{id:i}(?:/{extra})?', function ($request) { $extra = $request->matchData()['extra'] ?? 'none'; return "ID: " . $request->matchData()['id'] . ", Extra: $extra"; });Valid:
/users/10,/users/10/details
Note: These patterns are flexible and combinable. Use {?:} for optional parts and types (:i, :s, :l) for precise constraints.
7.4 Defining API Endpoints with apiPoint
The apiPoint method in the Router provides a convenient way to define API endpoints with support for versioning, modules, and dynamic parameters. It offers flexibility in defining custom paths and methods, and when combined with the built-in abstract Controller class and its apiDispatch (main logic) and api (shorter alias) methods, it enables automatic dispatching of requests to specific controller methods with dependency injection (DI) support.
Definition
Router::apiPoint($version, $module, $controller, $custom = null);
Parameters:
$version: API version (e.g.,"1"for v1).$module: Module name (e.g.,"shop").$controller: Callback or string in the format"Module:Controller@method!"(e.g.,"HelloWorld:Posts@apiDispatch!","HelloWorld:Posts@api!", or a custom method).$custom(optional): Specific path (string) or array of paths. Supports regular expressions (e.g.,(?:/{id})?).
If $custom is not provided, the default dynamic path /api/v{version}/{module}/{resource}(?:/{id})? is used. If specified, only the paths from $custom are applied. First route wins! Static paths must be listed before dynamic ones to avoid being overridden by dynamic logic.
Built-in Controller and apiDispatch/api Methods:
The framework provides an abstract class Dotsystems\App\Parts\Controller with the apiDispatch method, which automatically dispatches requests to specific methods in the format (e.g., postUsers, getPosts) based on the HTTP method and the value of the dynamic resource parameter. Point apiPoint to Controller@apiDispatch (or Controller@api as a shorter alias). Automatic dispatching works when the path includes {resource}. You do not register a separate route for each resource method.
apiDispatch maps HTTP method + resource to a controller method named {method}{Resource}, for example GET …/posts → getPosts($request). Implement those methods as public static. If no method matches, error404($request) runs when it exists; otherwise the framework returns HTTP 404. Register the dispatcher with a trailing !:
Router::apiPoint("1", "shop", "HelloWorld:Posts@apiDispatch!");
Customizing Errors:
If the target method (e.g., postUsers) doesn’t exist, apiDispatch first checks if the controller defines an error404 method. If so, it calls it, allowing the user to define custom logic for 404 errors (e.g., JSON response, logging). If error404 isn’t present, it returns a default error message with HTTP code 404.
Using with Automatic Dispatching:
Automatic dispatching via apiDispatch (or api) works only if the path includes the dynamic {resource} parameter in the correct position (e.g., /api/v1/shop/{resource}). If $custom doesn’t maintain this format, the automation won’t work, and a custom method must be used.
Example without $custom (Automatic Dispatching):
Router::apiPoint("1", "shop", "HelloWorld:Posts@apiDispatch!");
Resulting Paths:
POST /api/v1/shop/users- TriggerspostUsersif it exists.GET /api/v1/shop/posts- TriggersgetPosts.GET /api/v1/shop/status- Triggerserror404if it exists, otherwise 404 with a default message./api/v1/shop/posts/- Not captured.
Example with $custom and Automatic Dispatching:
Router::apiPoint("1", "shop", "HelloWorld:Posts@apiDispatch!", ["{resource}(?:/{id})?/details"]);
Resulting Paths:
POST /api/v1/shop/users/details- TriggerspostUsers.GET /api/v1/shop/posts/details- TriggersgetPosts.GET /api/v1/shop/posts/abc123/details- TriggersgetPosts.PUT /api/v1/shop/status/details- Triggerserror404if it exists, otherwise 404 with a default message./api/v1/shop/users/- Not captured.
Example with Custom Routes and a Custom Method:
Router::apiPoint("1", "shop", "HelloWorld:Posts@customMethod!", ["users/details", "posts/summary"]);
Resulting Paths: Automatic dispatching doesn’t work here because {resource} is missing. The logic depends on the implementation of customMethod.
GET /api/v1/shop/users/details- TriggerscustomMethod.POST /api/v1/shop/posts/summary- TriggerscustomMethod.
Example Controller with DI and Custom Error:
namespace Dotsystems\App\Modules\Dotcmsfe\Controllers;
class Posts extends \Dotsystems\App\Parts\Controller {
public static function postUsers($request, \SomeService $service) {
return "Creating users: " . $service->process($request->getPath());
}
public static function getPosts($request) {
$id = $request->matchData()['id'] ?? null;
return "List of posts" . ($id ? " with ID: $id" : "");
}
public static function error404($request) {
http_response_code(404);
return json_encode([
'error' => 'Not Found',
'message' => "Resource '{$request->matchData()['resource']}' not found or method '{$request->getMethod()}' not supported",
'path' => $request->getPath()
]);
}
public static function customMethod($request) {
return "Custom method for path: " . $request->getPath();
}
}
Note: The built-in Controller simplifies API handling with apiDispatch (or api) when the path includes {resource}. For custom routes without {resource}, you can use custom methods, but automatic dispatching won’t work. The order of paths in $custom is critical – static paths must precede dynamic ones.
8. Practical Examples
This chapter provides practical examples of using the Router in the DotApp Framework. It demonstrates how to combine basic and advanced features to address common scenarios in web applications.
8.1 Simple GET Route
The most basic example of defining a static route with a simple response.
Example:
Router::get('/welcome', function ($request) {
return "Welcome to the application!";
});
For a request to /welcome, it displays: "Welcome to the application!".
Use Case: Ideal for static pages like homepages or "About Us".
8.2 Dynamic Route with Variables
An example of a dynamic route with variable extraction to display user data.
Example:
Router::get('/user/{id:i}/{name}', function ($request) {
$id = $request->matchData()['id'];
$name = $request->matchData()['name'];
return "User ID: $id, Name: $name";
});
For /user/123/Jano, it displays: "User ID: 123, Name: Jano".
Use Case: Suitable for profiles, product details, or other resources with identifiers.
8.3 Using Middleware
An example combining a route with before and after hooks for verification and logging.
Example:
Router::get('/dashboard', function ($request) {
return "Welcome to the dashboard!";
})->before(function ($request) {
$user = "guest"; // Simulated verification
if ($user === "guest") {
http_response_code(403);
return "Access denied!";
}
})->after(function ($request) {
return "Dashboard displayed at " . date('H:i:s');
});
For /dashboard, it displays "Access denied!" with code 403 (since the simulated verification fails). If verification succeeded, it would show "Welcome to the dashboard!" followed by the display time.
Use Case: Authentication, access logging, or response modification.
8.4 Combining with Controllers
An example of integrating a route with a controller to separate logic from routing.
Example:
// app/modules/HelloWorld/Controllers/Article.php
namespace Dotsystems\App\Modules\HelloWorld\Controllers;
class Article extends \Dotsystems\App\Parts\Controller {
public static function detail($request) {
$slug = $request->matchData()['slug'];
return "Article detail: $slug";
}
}
// Route definition
Router::get('/article/{slug:s}', 'HelloWorld:Article@detail!');
For /article/how-to-code, it displays: "Article detail: how-to-code".
Use Case: Larger applications where code organization into controllers is needed.
9. Tips and Tricks
This chapter offers practical tips and tricks for effectively using the Router in the DotApp Framework. These will help you optimize your code, debug issues, and follow best practices.
9.1 Optimizing Routing
The Router in the DotApp Framework operates on a "first match wins" principle – the first matching route in the order of definition is used, and others are ignored, regardless of whether they are static or dynamic. The order of definition is therefore critical for optimization.
- Define the most important routes first: Since the first match wins, place critical or frequently used routes at the top.
- Use specific patterns: E.g.,
{id:i}instead of{id}to prevent unintended matches on incorrect routes. - Group similar routes: Use
match()with an array of paths to reduce code duplication, but be mindful of order.
Example of Optimization:
Router::get('/user/{id:i}', function ($request) { // First dynamic route
return "Dynamic user ID: " . $request->matchData()['id'];
});
Router::get('/user/123', function ($request) { // Second static route
return "Static user 123";
});
For /user/123, the first route always wins ("Dynamic user ID: 123") because it was defined first, even though the second is static and more precise. To prioritize the static route, define it earlier.
Example with Reordered Priority:
Router::get('/user/123', function ($request) { // First static route
return "Static user 123";
});
Router::get('/user/{id:i}', function ($request) { // Second dynamic route
return "Dynamic user ID: " . $request->matchData()['id'];
});
Now, for /user/123, it displays "Static user 123" because it’s defined first.
9.2 Debugging Routes
When troubleshooting routing issues, use the tools available in the Router and PHP to identify which route is actually being triggered, especially with the "first match" rule.
- Check the path: Use
$request->getPath()to verify the URL theRouteris processing. - Dump variables: Print
$request->matchData()to see which values were extracted. - Test order: Add temporary outputs (e.g.,
echo) in callbacks to determine which route executed.
Example of Debugging:
Router::get('/page/{id}', function ($request) {
echo "Dynamic route triggered for ID: " . $request->matchData()['id'];
return "Dynamic page " . $request->matchData()['id'];
});
Router::get('/page/1', function ($request) {
echo "Static route triggered for /page/1";
return "Static page 1";
});
For /page/1, it displays "Dynamic route triggered for ID: 1" and "Dynamic page 1" because the dynamic route is defined first. Changing the order would prioritize the static route.
9.3 Best Practices for Route Structure
Following best practices helps maintain clarity and predictability in routing.
- Logical order: Define routes from most specific to most general to leverage the "first match wins" rule.
- Comments: Add comments above routes to clarify why they are in a specific order.
- Separate logic: Use controllers for complex routes instead of inline callbacks.
Example of Best Practices:
// Most specific static route
Router::get('/api/users/guest', function ($request) {
return "Guest user";
});
// Specific dynamic route
Router::get('/api/users/{id:i}', function ($request) {
return "User ID: " . $request->matchData()['id'];
});
// General route last
Router::get('/api/{resource}', function ($request) {
return "Resource: " . $request->matchData()['resource'];
});
For /api/users/guest, the first route triggers; for /api/users/5, the second; and for /api/products, the third, thanks to logical ordering.
10. Conclusion
This chapter concludes the documentation for the Router in the DotApp Framework. It summarizes its benefits and offers a look at its future development and community.
10.1 Why Use the Router in DotApp?
The Router in the DotApp Framework is a simple yet powerful tool for managing routing in web applications. Its key advantages include:
- Flexibility: Support for both static and dynamic routes with variables and optional parts.
- Simplicity: Intuitive interface for defining routes via HTTP methods like
get()andpost(). - Middleware: Ability to add
beforeandafterhooks for extended logic. - First Match Wins: Predictable behavior based on the order of route definition, giving developers full control.
- Integration: Seamless collaboration with controllers and the
Requestobject for request handling.
Whether you’re building a small application or a complex system, the Router provides the tools to map requests to logic quickly and efficiently.