URL path mapped directly to docroot file path
Invoke the script at that file location
Parameters ...
via query string:
# GET http://example.com/foo/bar.php?id=1 /var/www/html/foo/bar.php $_GET['id'] = '1';
via path info:
# GET http://example.com/foo/bar.php/1 /var/www/html/foo/bar.php $_SERVER['PATH_INFO'] = '/1';
All HTTP verbs mapped to same script
Web server itself as degenerate form of Front Controller
Modern applications do not expose scripts in document root
Instead, functionality encapsulated in classes
Formal Front Controller script (index.php or front.php)
Router reads the URL and maps to class/method/params
# GET http://example.com/foo/bar/1class Foo{ public function bar(int $id) : Response { }}
Dispatcher invokes an instance method with params
Need a routing file: regular expressions
# genericget /:controller/:action/:id# ~^/([^/]+)/([^/]+)/([^/]+)$~# specificget /foo/bar/:id# ~^/foo/bar/([^/]+)$~# constraintsget /photo/archive/{:year}/{:month}, constraints: { year: /\d+/, month: /\d+/ }# ~^/photo/archive/(\d+)/(\d+)$~
$r->addRoute('GET', '/users', ['Users', 'index']);// {id} must be a number (\d+)$r->addRoute('GET', '/user/{id:\d+}', ['Users', 'show']);// The /{title} suffix is optional$r->addRoute('GET', '/articles/{id:\d+}[/{title}]', ['Articles', 'read']);
Others: https://packagist.org/?query=router
$app->get( '/hello/{name}', function (Request $request, Response $response, array $args) { $name = $args['name']; $response->getBody()->write("Hello, $name"); return $response; });
$r->addGroup('/admin', function (RouteCollector $r) { # GET /admin/foo $r->addRoute('GET', '/foo', ['Admin', 'foo']); # GET /admin/bar $r->addRoute('GET', '/bar', ['Admin', 'bar']); # GET /admin/baz $r->addRoute('GET', '/baz', ['Admin', 'baz']);});
resources photos
... creates:
get /photos, to: photos#indexget /photos/new, to: photos#newpost /photos, to: photos#createget /photos/:id, to: photos#showget /photos/:id/edit, to: photos#editpatch /photos/:id, to: photos#updateput /photos/:id, to: photos#updatedelete /photos/:id, to: photos#destroy
Laravel
Route::resource('photos', 'PhotoController');
FastRoute and others
$r->addRoute('GET', '/photos', ['Photos', 'index']);$r->addRoute('GET', '/photos/new', ['Photos', 'new']);$r->addRoute('POST', '/photos', ['Photos', 'create']);$r->addRoute('GET', '/photos/{id:\d+}', ['Photos', 'show']);$r->addRoute('GET', '/photos/{id:\d+}/edit', ['Photos', 'edit']);$r->addRoute('PATCH', '/photos/{id:\d+}', ['Photos', 'update']);$r->addRoute('PUT', '/photos/{id:\d+}', ['Photos', 'update']);$r->addRoute('DELETE', '/photos/{id:\d+}', ['Photos', 'destroy']);
class PhotoController extends AbstractController{ /** * @Route("/photo", name="photo_list") */ public function list() { } /** * @Route("/photo/{slug}", name="photo_show") */ public function show($slug) { }}
$route = $router->match($urlPath);$class = $route->getController();$method = $route->getAction();$params = $route->getParams();$object = new $class();$response = $object->$method(...$params); // or $request
Volume
resource photos
are framework-specificNon-DRY
AutoRoute automatically maps HTTP requests by verb and path to PHP classes in a specified namespace.
It reflects on a specified action method within that class to determine the dynamic URL parameters.
Merely adding a class to your source code, in the specified namespace and with the specified action method name, automatically makes it available as a route.
GET /photos
namespace App\Http\Photos;class GetPhotos{ public function __invoke() { }}
GET /photo/{id}
namespace App\Http\Photo;class GetPhoto{ public function __invoke(int $id) { }}
Under the App\Http\
PSR-4 namespace ...
Photos/ GetPhotos.php GET /photos (browse/index) Photo/ DeletePhoto.php DELETE /photo/1 (delete) GetPhoto.php GET /photo/1 (read) PatchPhoto.php PATCH /photo/1 (update) PostPhoto.php POST /photo (create) Add/ GetPhotoAdd.php GET /photo/add (form for creating) Edit/ GetPhotoEdit.php GET /photo/1/edit (form for updating)
Given POST /photo
and this class:
namespace App\Http\Photo;class PostPhoto{ public function __invoke() { }}
Given GET /photo/1
and this class:
namespace App\Http\Photo;class GetPhoto{ public function __invoke(int $id) { }}
Given PATCH /photo/1
and this class:
namespace App\Http\Photo;class PatchPhoto{ public function __invoke(int $id) { }}
(aka "single nested resource")
Given GET /photo/1/edit
and this class:
namespace App\Http\Photo\Edit;class GetPhotoEdit{ public function __invoke(int $id) { }}
Given GET /photos/archive/1979/11
and this class:
namespace App\Http\Photos\Archive;class GetPhotosArchive{ public function __invoke(int $year, int $month) { }}
Given GET /photos/by-tag/foo/bar/baz
and this class:
namespace App\Http\Photos\ByTag;class GetPhotosByTag{ public function __invoke(string ...$tags) { }}
'foo,bar,baz'
=> ['foo', 'bar', 'baz']
'1' | 'y' | 'yes' | 't' | 'true'
=> true
$ composer require pmjones/auto-route ^1.0
use AutoRoute\AutoRoute;$autoRoute = new AutoRoute( 'App\Http', dirname(__DIR__) . '/src/App/Http/');$router = $autoRoute->newRouter();
try { $route = $router->route($request->method, $request->url[PHP_URL_PATH]);} catch (\AutoRoute\InvalidNamespace $e) { // 400 Bad Request} catch (\AutoRoute\InvalidArgument $e) { // 400 Bad Request} catch (\AutoRoute\NotFound $e) { // 404 Not Found} catch (\AutoRoute\MethodNotAllowed $e) { // 405 Method Not Allowed}
// presuming a DI-based Factory that can create new action class instances:$action = Factory::new($route->class);// call the action instance with the method and params,// presumably getting back an HTTP Response$response = call_user_func($action, $route->method, ...$route->params);
$autoRoute->setSuffix('Action'); // class GetPhotoEditAction$autoRoute->setMethod('exec'); // public function exec(...)$autoRoute->setBaseUrl('/api'); // /api/photo/1/edit$autoRoute->setWordSeparator('_'); // foo_bar$router = $autoRoute()->newRouter();
$ php bin/autoroute-dump.php App\\Http ./src/Http
POST /photo App\Http\Photo\PostPhotoGET /photo/add App\Http\Photo\Add\GetPhotoAddDELETE /photo/{int:id} App\Http\Photo\DeletePhotoGET /photo/{int:id} App\Http\Photo\GetPhotoPATCH /photo/{int:id} App\Http\Photo\PatchPhotoGET /photo/{int:id}/edit App\Http\Photo\Edit\GetPhotoEditGET /photos/archive[/{int:year}][/{int:month}][/{int:day}] App\Http\Photos\Archive\GetPhotosArchiveGET /photos[/{int:page}] App\Http\Photos\GetPhotos
Generator instance:
$generator = $autoRoute->newGenerator();
Usage:
use App\Http\Photo\Edit\GetPhotoEdit;$href = $generator->generate(GetPhotoEdit::CLASS, 1);// => /photo/1/edit
Invokable (single-action) controllers
Must follow naming convention
URL path matching only
Request
object for headers, hostname, etc.No "fine-grained" validation/constraints
int
not \d{4}
Faster than FastRoute!
https://github.com/pmjones/AutoRoute-benchmark#autoroute-benchmarks
(Not that it matters.)
Keyboard shortcuts
↑, ←, Pg Up, k | Go to previous slide |
↓, →, Pg Dn, Space, j | Go to next slide |
Home | Go to first slide |
End | Go to last slide |
Number + Return | Go to specific slide |
b / m / f | Toggle blackout / mirrored / fullscreen mode |
c | Clone slideshow |
p | Toggle presenter mode |
t | Restart the presentation timer |
?, h | Toggle this help |
Esc | Back to slideshow |