Aura.Router Documentation

repository·3.x·Indexed 19 days ago

https://github.com/auraphp/aura.router

A powerful and flexible web routing library designed specifically for PSR-7 requests. It provides features for defining routes with placeholder tokens, optional attributes, and wildcard paths, as well as tools for grouping routes via Map::attach(). The library supports custom matching rules through RuleInterface, custom Map and Route class extensions, and automated route building and caching via setMapBuilder().

Tokens
15.1K
Snippets
48
Records
55
Agent score
67%

What's inside Aura.Router

  1. Define Optional Placeholder Tokens

    3.x

    You can make path segments optional using the notation {/attribute1,attribute2,...}.

    Rules for optional attributes:

    • The leading slash separator must be inside the placeholder token (e.g., {/year,month}).
    • Attributes are sequentially optional: you cannot match a later attribute without matching all preceding ones (e.g., you cannot have month without year).
    • You can only have one set of optional attributes per route.
    • Optional attributes should always be placed at the end of the route path.
    <?php
    // Matches /archive, /archive/1979, /archive/1979/11, etc.
    $map->get('archive', '/archive{/year,month,day}')
        ->tokens([
            'year' => '\d{4}',
            'month' => '\d{2}',
            'day' => '\d{2}',
        ]);
    ?>
  2. Use optional attributes in generated paths

    3.x

    When a route is defined with optional segments (e.g., /archive{/year,month,day}), the Generator::generate() method will fill in these segments if the corresponding keys are present in the attributes array.

    Important: Optional attributes are processed sequentially. If an attribute is missing, the generator will not attempt to fill in any subsequent optional attributes in that sequence.

    <?php
    // Route with sequential optional segments
    $map->route('archive', '/archive{/year,month,day}')
        ->tokens([
            'year'  => '\d{4}',
            'month' => '\d{2}',
            'day'   => '\d{2}'
        ]);
    
    // Generating a link with only year and month
    $link = $generator->generate('archive', [
        'year' => '1979',
        'month' => '11',
    ]); // Result: "/archive/1979/11"
    ?>
  3. Handle routing failures (404 and 405)

    3.x

    If $matcher->match($request) returns null (or an empty result), no route matched the request. You can use $matcher->getFailedRoute() to inspect why the match failed and return appropriate HTTP status codes.

    Common failure rules:

    • Aura\Router\Rule\Allows: The path matched, but the HTTP method was incorrect (e.g., POST instead of GET). This typically results in a 405 Method Not Allowed response. You can use $failedRoute->allows to populate the Allow header.
    • Aura\Router\Rule\Accepts: The request did not match the required content negotiation (e.g., Accept header). This typically results in a 406 Not Acceptable response.
    • Default: If no specific rule is identified, treat it as a 404 Not Found.
    <?php
    $route = $matcher->match($request);
    if (! $route) {
        $failedRoute = $matcher->getFailedRoute();
    
        switch ($failedRoute->failedRule) {
            case 'Aura\Router\Rule\Allows':
                // Handle 405 METHOD NOT ALLOWED
                break;
            case 'Aura\Router\Rule\Accepts':
                // Handle 406 NOT ACCEPTABLE
                break;
            default:
                // Handle 404 NOT FOUND
                break;
        }
    }
  4. Use wildcard attributes for trailing path segments

    3.x

    If a route is configured with a wildcard() definition, you can provide an array of values under that wildcard key in the generate() method. The generator will append these values as trailing arbitrary segments in the path.

    <?php
    // Route with a wildcard named 'other'
    $map->route('wild_post', '/post/{id}')
        ->wildcard('other');
    
    // Providing an array to the 'other' key
    $link = $generator->generate('wild_post', [
        'id' => '88',
        'other' => [
            'foo',
            'bar',
            'baz',
        ]
    ]); // Result: "/post/88/foo/bar/baz"
    ?>
  5. Create catchall routes using optional placeholders

    3.x

    You can create generic catchall routes by using optional placeholder tokens in your route pattern. This allows a single route to match various path depths and structures.

    Important: Because routes are matched in the order they are added, always define catchall routes as the last route in your Map to ensure more specific routes have the opportunity to match first.

    <?php
    $map->get('catchall', '{/controller,action,id}')
        ->defaults([
            'controller' => 'index',
            'action' => 'browse',
            'id' => null,
        ]);
    ?>
  6. Write a custom matching rule

    3.x

    To implement custom matching logic (such as checking authentication state or specific headers), create a class that implements Aura\Router\Rule\RuleInterface.

    The __invoke method must accept a Psr\Http\Message\ServerRequestInterface $request and an Aura\Router\Route $route.

    • Return true if the rule matches.
    • Return false if the rule fails.
    • You can use the $route->attributes() method within your rule to capture data (like header values) and inject them into the route's attributes for later use.
    <?php
    use Aura\Router\Route;
    use Aura\Router\Rule\RuleInterface;
    use Psr\Http\Message\ServerRequestInterface;
    
    class ApiVersionRule implements RuleInterface
    {
        public function __invoke(ServerRequestInterface $request, Route $route)
        {
            $versions = $request->getHeader('X-Api-Version');
            if (count($versions) !== 1) {
                return false;
            }
    
            $route->attributes(['apiVersion' => $versions[0]]);
            return true;
        }
    }
    ?>
  7. Generate paths from routes using the Generator

    3.x

    To create links in your application, retrieve the Generator from your RouterContainer and use its generate() method. You provide the route name and an associative array of attributes to fill the named placeholder tokens defined in the route.

    Key behaviors:

    • Encoding: generate() automatically URL-encodes placeholder token values. Use generateRaw() if you want to skip encoding.
    • Missing Tokens: If a route has named placeholder tokens that are not provided in the attributes array, the tokens will remain unreplaced in the resulting path.
    • Extra Attributes: If attributes are provided that do not correspond to any tokens in the route, they are ignored and not added to the path.
    <?php
    $generator = $routerContainer->getGenerator();
    
    // Generate a path for the 'blog.read' route with id 42
    $path = $generator->generate('blog.read', ['id' => 42]);
    
    // Use htmlspecialchars when outputting to HTML
    $href = htmlspecialchars($path, ENT_QUOTES, 'UTF-8');
    echo "<a href=\"{$href}\">Blog link</a>";
    ?>
  8. Extend the Route class to add custom parameters

    3.x

    If you need to add custom properties or methods to individual routes (e.g., a model() method to specify a data model), you can extend the Aura\Router\Route class.

    To implement this:

    1. Create a class that extends Aura\Router\Route.
    2. Register a factory callable using $routerContainer->setRouteFactory() that returns an instance of your custom class.
    3. When you call methods on the Map object that are not part of the standard API, the Map proxies those calls to the underlying Route object. This allows you to set default values for all routes created by that map.

    Example: If you add a model() method to your Route class, you can call $map->model('DefaultClass') to set a default for all subsequent routes, or $map->get(...)->model('SpecificClass') for a single route.

    <?php
    use Aura\Router\Route;
    
    class ModelRoute extends Route
    {
        protected $model;
    
        public function model($model)
        {
            $this->model = $model;
            return $this;
        }
    }
    
    // Register the factory
    $routerContainer->setRouteFactory(function () {
        return new ModelRoute();
    });
    
    // Usage
    $map = $routerContainer->getMap();
    
    // Setting a default on the map proxies to the route
    $map->model('DefaultModelClass');
    $route = $map->get('foo', '/path/to/foo');
    echo get_class($route); // "ModelRoute"
    echo $route->model; // "DefaultModelClass"
    ?>
  9. Register custom rules with the RouterContainer

    3.x

    Once a custom rule is written, you must add it to the RuleIterator provided by the RouterContainer. You have two primary ways to do this:

    1. Append or Prepend: Use $routerContainer->getRuleIterator()->append() or prepend() to add your rule to the existing set.

      • prepend(): The rule runs first.
      • append(): The rule runs last.
      • You can wrap rules in a callable (e.g., using Aura.Di Lazy instances) for lazy loading.
    2. Set the entire set: Use $routerContainer->getRuleIterator()->set() to define the complete list of rules. This allows you to control the exact execution order by placing your rule anywhere in the array. Note that if you use set(), you must include the default rules if you still want them to function.

    <?php
    // Option 1: Append/Prepend
    $ruleIterator = $routerContainer->getRuleIterator();
    $ruleIterator->append(new ApiVersionRule());
    
    // Option 2: Set the entire list for precise ordering
    use Aura
    outer\Rule;
    $routerContainer->getRuleIterator()->set([
        new Rule\Secure(),
        new Rule\Host(),
        new ApiVersionRule(), // custom rule in the middle
        new Rule\Path(),
        new Rule\Allows(),
        new Rule\Accepts(),
        new Rule\Special(),
    ]);
    ?>
  10. Extend the Map class to add convenience methods

    3.x

    You can extend the Aura\Router\Map class to add application-specific convenience methods (e.g., a resource() method that attaches multiple related routes at once).

    To implement this:

    1. Create a class that extends Aura\Router\Map.
    2. Register a factory callable using $routerContainer->setMapFactory() that returns an instance of your custom class.
    3. Use $routerContainer->getMap() to retrieve your extended map instance.

    Note: When using a custom Map, the RouterContainer will return your extended class instead of the standard Map class.

    <?php
    use Aura
    outer\
    use Aura\Router\Map;
    
    class MyResourceMap extends Map
    {
        public function resource($namePrefix, $pathPrefix)
        {
            return $this->attach($namePrefix, $pathPrefix, function ($map) {
                $map->get('browse', '');
                $map->get('read', '/{id}');
                $map->patch('edit', '/{id}');
                $map->post('add', '');
                $map->delete('delete', '/{id}');
            });
        }
    }
    
    // Register the factory
    $routerContainer->setMapFactory(function () {
        return new MyResourceMap(new Aura\Router\Route());
    });
    
    // Usage
    $map = $routerContainer->getMap();
    echo get_class($map); // "MyResourceMap"
    ?>
  11. Automate route building and caching with setMapBuilder()

    3.x

    You can automate the construction of your route map or implement caching (e.g., for production) by using $routerContainer->setMapBuilder().

    How it works:

    • Pass a callable to setMapBuilder(). This callable receives a Map instance as its only argument.
    • The logic inside the builder is executed automatically when $routerContainer->getMap() is called.
    • You can use Map::setRoutes() and Map::getRoutes() to manage the array of mapped route objects for serialization/caching.

    Important Limitation: If your routes use PHP Closures as handlers, you cannot serialize the routes for caching because closures cannot be serialized. Use non-closure callables (like class/method strings) instead.

    <?php
    $routerContainer->setMapBuilder(function ($map) {
        $cache = '/path/to/routes.cache';
    
        if (file_exists($cache)) {
            // Restore from cache
            $routes = unserialize(file_get_contents($cache));
            $map->setRoutes($routes);
        } else {
            // Build routes
            $map->get('home', '/');
            $map->get('about', '/about');
    
            // Save to cache
            $routes = $map->getRoutes();
            file_put_contents($cache, serialize($routes));
        }
    });
    
    // The builder runs automatically when this is called
    $map = $routerContainer->getMap();
  12. Dispatch a matched route

    3.x

    Once a route is matched, you can access two key properties on the returned $route object:

    • $route->attributes: An array of captured attribute values (e.g., from {id} placeholders).
    • $route->handler: The handler assigned to the route during mapping.

    Common Dispatch Patterns

    1. Transfer attributes to the request:

    foreach ($route->attributes as $key => $val) {
        $request = $request->withAttribute($key, $val);
    }

    2. Dispatch a closure or callable:

    $callable = $route->handler;
    $response = $callable($request);

    3. Dispatch a class-based handler:

    $actionClass = $route->handler;
    $action = new $actionClass();
    $response = $action($request);