Skip to content

Commit

Permalink
DOC Nested RequestHandlers
Browse files Browse the repository at this point in the history
  • Loading branch information
emteknetnz committed Dec 4, 2023
1 parent 6314244 commit 01a26a9
Showing 1 changed file with 47 additions and 0 deletions.
47 changes: 47 additions & 0 deletions en/02_Developer_Guides/02_Controllers/02_Routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`'s

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)
Expand Down

0 comments on commit 01a26a9

Please sign in to comment.