diff --git a/en/02_Developer_Guides/02_Controllers/02_Routing.md b/en/02_Developer_Guides/02_Controllers/02_Routing.md index 8016e3870..1783a9ffd 100644 --- a/en/02_Developer_Guides/02_Controllers/02_Routing.md +++ b/en/02_Developer_Guides/02_Controllers/02_Routing.md @@ -303,6 +303,53 @@ class BreadAPIController extends Controller In Silverstripe CMS versions prior to 4.6, an empty key (`''`) must be used in place of the `'/'` key. When specifying an HTTP method, the empty string must be separated from the method (e.g. `'GET '`). The empty key and slash key are also equivalent in Director rules. [/alert] +## Nested RequestHandlers + +Nested [`RequestHandler`](api:SilverStripe\Control\RequestHandler) routing is used extensively in the CMS and is used to create URL endpoints without yml configuration. Nesting is done by returning a [`RequestHandler`](api:SilverStripe\Control\RequestHandler) from an action method on another [`RequestHandler`](api:SilverStripe\Control\RequestHandler), usually a [`Controller`](api:SilverStripe\Control\Controller). + +`RequestHandler` is the base class for other classes that can handle HTTP requests such as [`Controller`](api:SilverStripe\Control\Controller), [`FormRequestHandler`](api:SilverStripe\Forms\FormRequestHandler) (used by [`Form`](api:SilverStripe\Forms\Form)) and [`FormField`](api:SilverStripe\Forms\FormField). + +### How it works + +[`Director::handleRequest()`](api:SilverStripe\Control\Director::handleRequest()) begins the url parsing process by parsing the start of the URL and workng out which [`RequestHandler`](api:SilverStripe\Control\RequestHandler) subclass to use, looking in: + +- Routes set in yml config under Director `rules` +- For `/admin` routes specifically, the `$url_segment` config for subclasses of `LeftAndMain` + +When a [`RequestHandler`](api:SilverStripe\Control\RequestHandler) matching the first portion of the URL is found, the `handleRequest()` method on the matched [`RequestHandler`](api:SilverStripe\Control\RequestHandler) subclass is called. This passes control to the matched [`RequestHandler`](api:SilverStripe\Control\RequestHandler) and the URL is effectively truncated from the left, removing the portion that lead to ths point. + +From there regular request handling occurs and URL will be check to see if it matches `$allowed_actions` on the [`RequestHandler`](api:SilverStripe\Control\RequestHandler), possibly routed via `$url_handlers`. If an `$allowed_action` i.e. method on the [`RequestHandler`](api:SilverStripe\Control\RequestHandler) is matched, if that method returns a [`RequestHandler`](api:SilverStripe\Control\RequestHandler), then control will now be passed to this nested [`RequestHandler`](api:SilverStripe\Control\RequestHandler) and the URL is again effectively truncated from the left. + +### Example of a nested RequestHandler being returned in an action method + +- There is yml routing directing the path `one` to `RequestHandlerOne` +- There is a class `RequestHandlerOne` with `$allowed_actions = ['two']`, and a `two()` method returning `RequestHandlerTwo::create();` +- There is a class `RequestHandlerTwo` with `$allowed_actions = ['hello']`, and a `hello()` method returning `HTTPResponse::create()->setBody('hello');` +- Navigating to the URL `/one/two/hello` will return a response with a body of "hello" + +### Form, HasRequestHandler, FormRequestHandler, and FormField + +[`Form`](api:SilverStripe\Forms\Form) does not extend [`RequestHandler`](api:SilverStripe\Control\RequestHandler), instead it implements the [`HasRequestHandler`](api:SilverStripe\Control\HasRequestHandler) interface that requires a method `getRequestHandler()`. [`Form::getRequestHandler()`](api:SilverStripe\Forms\Form::getRequestHandler()) returns a [`FormRequestHandler`](api:SilverStripe\Forms\FormRequestHandler) which is a subclass of [`RequestHandler`](api:SilverStripe\Control\RequestHandler). + +Subclasses of [`RequestHandler`](api:SilverStripe\Control\RequestHandler) and implementors of [`HasRequestHandler`](api:SilverStripe\Control\HasRequestHandler) are treated the same because they will both end up calling `handleRequest()` on the subclass of the appropriate [`RequestHandler`](api:SilverStripe\Control\RequestHandler) subclass. + +[`FormRequestHandler`](api:SilverStripe\Forms\FormRequestHandler) includes a `$url_handler` entry `'field/$FieldName!' => 'handleField'` which allows it to handle requests to form fields on the form. [`FormRequestHandler::handleField()`](api:SilverStripe\Forms\FormRequestHandler::handleField()) will find the form field matching `$FieldName` and return it. Control is then passed to the returned form field. + +[`FormField`](api:SilverStripe\Forms\FormField) extends [`RequestHandler`](api:SilverStripe\Control\RequestHandler), which means that form fields are able to handle HTTP requests and have the `$allowed_actions` config. This allows form fields have define their own AJAX endpoints. + +### Example of an AJAX `FormField` that uses nested [`RequestHandler`](api:SilverStripe\Control\RequestHandler) routing + + The "Viewer groups" dropdown AJAX request when viewing file details in asset admin has an endpoint of `/admin/assets/fileEditForm/{FileID}/field/ViewerGroups/tree?format=json` + +- `assets` matches the `$url_segment = 'assets` of `AssetAdmin extends LeftAndMain` +- `fileEditForm/{FileID}` matches the `'fileEditForm/$ID' => 'fileEditForm'` rule from `AssetAdmin` `$url_handlers` +- `AssetAdmin::fileEditForm()` will return a `Form` scaffolded for the `File` matching the `FileID` +- `Form::getRequestHandler()` will be called on the `Form` which returns a `LeftAndMainFormRequestHandler extends FormRequestHandler` +- `field/ViewerGroups` will matches the `'field/$FieldName!' => 'handleField'` rule from `FormRequestHandler` `$url_handlers` +- `FormRequestHandler::handleField()` will find the `ViewerGroups` field from the `Form`, the field is a `TreeMultiselectField extends TreeDropdownField` +- `tree` will match the `$allowed_action` rule `tree` on `TreeDropdownField` +- `TreeDropdownField::tree()` will return an `HTTPResponse` with its body containing JSON + ## Related lessons - [Creating filtered views](https://www.silverstripe.org/learn/lessons/v4/creating-filtered-views-1)