Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pattern-based routing implemented as web.Routes #111

Open
wants to merge 28 commits into
base: master
Choose a base branch
from

Conversation

thekid
Copy link
Member

@thekid thekid commented Apr 6, 2024

This pull request refactors the web.Routes class to use regular expressions for routing. The new implementation comes with decreased complexity and increased performance (by not delegating to a variety of method calls) while sacrificing a completely flexible way of routing requests by using matcher functions, while adding the possibility to route patterns.

Route patterns

...are essentially regular expressions, with the small difference that . matches its literal form and that the ^ and $ metacharacters cannot be used due to how the patterns are constructed. If you want to match a single character use \C instead.

Here are a couple of examples:

Paths

  • /(favicon.ico|robots.txt) matches the files favicon.ico and robots.txt (but not favicon_ico, which a regular expression would match)
  • /(?i)test.html using the internal-option syntax matches the file test.html in any case
  • /users/[a-z]+ matches /users/test but not /users/0815.

Restricting to methods

  • GET|POST /users matches GET and POST requests to /users
  • HEAD / and HEAD both match HEAD requests to the root path.

Route patterns are matched against /[PATH] or [METHOD] /[PATH] if they do not start with forward slash.

Placeholders

Enclosed in curly braces:

  • /users/{id} will match any path segment and pass it as request value "id"
  • /users/{id:[a-z]+} will match lowercase letters and pass them as request value "id"

Nested routes

This pull request enables nested routes by using maps:

use web\rest\{RestApi, ResourcesIn};

// This is essentially the same as supplying two keys, one with */api/v1* and one with */api/v2*.
return [
  '/api' => [
    '/v1' => new RestApi(new ResourcesIn('com.example.api.legacy')),
    '/v2' => new RestApi(new ResourcesIn('com.example.api.impl')),
  ],
];

// A simple REST CRUD API rolled "by hand" could be implemented as follows:
return [
  '/customers' => [
    '/{id:[0-9a-f]+}' => [
      'GET'    => function($req, $res) { return $this->customers->by($req->value('id')); },
      'PUT'    => function($req, $res) { /* Update */ },
      'DELETE' => function($req, $res) { /* Delete */},
    ],
    'GET'  => function($req, $res) { /* Gets list of customers */ },
    'POST' => function($req, $res) { /* Creates new customer */ },
  ]
];

Compacting

With route patterns, the following typical routing definition can be rewritten in a more compact way:

// Each static resource needed to be listed individually
$static= new AssetsFrom($this->environment->path('src/main/webapp'));
return [
  '/static'      => $static,
  '/favicon.ico' => $static,
  '/robots.txt'  => $static,
  /* ... */
];

// Group static resources in one pattern
return [
  '/(static|favicon.ico|robots.txt)' => new AssetsFrom($this->environment->path('src/main/webapp')),
  /* ... */
];

Fluent API

Instead of returning an array, a fluent style can be used:

use web\Routes;
use web\frontend\{AssetsFrom, Frontend, Handlebars, HandlersIn};

return (new Routes())
  ->route('/(static|favicon.ico|robots.txt)', new AssetsFrom($this->environment->path('src/main/webapp')))
  ->default(new Frontend(
    new HandlersIn('com.example.web'),
    new Handlebars($this->environment->path('src/main/handlebars'))
  ))
;

Routing deprecation

This pull request also deprecates the web.Routing implementation and the web.routing package, both of which will be removed in the upcoming major release.

This affects the following types:

+- web
   |- class Route
   |- class Routing
   |
   +- routing
   |  |- interface RouteMatch
   |  |- class CannotRoute
   |  |- class NoRoute
   |  |- class Path
   |  `- class Target
   |
   `- unittest
      |- class RoutingTest
      |
      `- routing
         |- class PathTest
         `- class TargetTest

See also

@thekid thekid mentioned this pull request May 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant