Content-Type Negotiation in CakePHP 4.4

During my workshop at Cakefest 2022 I covered the new content-type negotiation features shipped in CakePHP 4.4. I wanted to share that information here so it is more easily found in the future.

What is Content-Type negotiation anyways?

Content-type negotiation is a feature that allows APIs and their clients to ‘negotiate’ content types. Most commonly, clients use either the Accept header or a URL extension to declare what kind of content they would like. Servers can then reply with a 200 and a matching Content-Type header. If the server can’t respond with that content-type then the server should respond with a 40x status code.

Content-Type negotiation in 4.4

Historically, content-type negotiation was done by RequestHandlerComponent. While this works well for simple content types, adding new content types, and enabling content-types for a subset of your controllers wasn’t as simple as it should be. Additionally, there was no way for application developers to replace the fallback view classes should negotiation fail. With the new APIs, Controllers use a declarative interface to list the view classes they want to negotiate with. Each view declares which content-type it can generate, or uses ``TYPE_MATCH_ALL`` to become a fallback view. With these methods CakePHP can perform negotiation for client requests.

Show Plain Text
  1. // In a controller
  2. public function viewClasses(): array
  3. {
  4.     // Use a customized Json view or fallback to the default JsonView.
  5.     return [CustomJsonView::class, JsonView::class];
  6. }

By implementing Controller::viewClasses() your controller will opt-in to the new content-type negotiation system. When using Controller::viewClasses() you should take care to remove RequestHandler component from your controller.

In order for a view class to participate in content-type negotiation it needs to implement the contentType method. This method is used by CakePHP to match views with client preferences. For example, our custom JSON view would have the following method:

Show Plain Text
  1. // In src/View/CustomJsonView.php
  2. public static function contentType(): string
  3. {
  4.     return 'application/vnd.app+json';
  5. }

With the controller and view methods implemented, clients can now send Accept: application/vnd.app+json and receive a response rendered by your custom json view class. If you’re using a custom content-type like this and want to use your view with extension based routing, you’ll need to enable the extension in your route configuration:

Show Plain Text
  1. // in config/routes.php
  2. $routes->scope('/', function(RouteBuilder $builder) {
  3.     $builder->addExtensions(['appjson']);
  4. });

Next, we connect the extension to the content-type. In your controller’s beforeFilter() or beforeRender() methods add the following:

Show Plain Text
  1. // In a controller beforeFilter()
  2. // Bind the json extension type to our custom content type.
  3. $this->response->setTypeMap('appjson', ['application/vnd.app+json']);

With our new content-type linked to an extension, clients can would be able to use either the Accept header or .appjson in URLs and have the response generated by your custom view class.

These APIs are available as of CakePHP 4.4.0, so you can start using them today. As always, any feedback on these features or any part of CakePHP can be done on GitHub

Comments

There are no comments, be the first!

Have your say: