laminas-api-tools / api-tools-content-negotiation
Laminas Module providing content-negotiation features
Fund package maintenance!
Community Bridge
Installs: 123 951
Dependents: 10
Suggesters: 0
Security: 0
Stars: 2
Watchers: 11
Forks: 2
Open Issues: 14
Requires
- php: ^5.6 || ^7.0
- laminas-api-tools/api-tools-api-problem: ^1.2.1
- laminas/laminas-eventmanager: ^2.6.3 || ^3.0.1
- laminas/laminas-filter: ^2.7.1
- laminas/laminas-http: ^2.5.4
- laminas/laminas-json: ^2.6.1 || ^3.0
- laminas/laminas-mvc: ^2.7.15 || ^3.0.2
- laminas/laminas-servicemanager: ^2.7.6 || ^3.1
- laminas/laminas-stdlib: ^2.7.7 || ^3.0.1
- laminas/laminas-validator: ^2.8.1
- laminas/laminas-view: ^2.8.1
- laminas/laminas-zendframework-bridge: ^1.0
Requires (Dev)
- laminas-api-tools/api-tools-hal: ^1.4
- laminas/laminas-coding-standard: ~1.0.0
- laminas/laminas-console: ^2.0
- phpunit/phpunit: ^5.7.27 || ^6.5.8 || ^7.1.5
Suggests
- laminas/laminas-console: ^2.0, if you intend to use the console request of RequestFactory
Replaces
README
Introduction
api-tools-content-negotiation
is a module for automating content negotiation tasks within a Laminas
Framework application.
The following features are provided
- Mapping
Accept
header media types to specific view model types, and automatically casting controller results to those view model types. - Defining
Accept
header media type whitelists; requests withAccept
media types that fall outside the whitelist will be immediately rejected with a406 Not Acceptable
response. - Defining
Content-Type
header media type whitelists; requests sending content bodies withContent-Type
media types that fall outside the whitelist will be immediately rejected with a415 Unsupported Media Type
response.
Requirements
Please see the composer.json file.
Installation
Run the following composer
command:
$ composer require laminas-api-tools/api-tools-content-negotiation
Alternately, manually add the following to your composer.json
, in the require
section:
"require": { "laminas-api-tools/api-tools-content-negotiation": "^1.2" }
And then run composer update
to ensure the module is installed.
Finally, add the module name to your project's config/application.config.php
under the modules
key:
return [ /* ... */ 'modules' => [ /* ... */ 'Laminas\ApiTools\ContentNegotiation', ], /* ... */ ];
laminas-component-installer
If you use laminas-component-installer, that plugin will install api-tools-content-negotiation as a module for you.
Configuration
User Configuration
The top-level configuration key for user configuration of this module is api-tools-content-negotiation
.
Key: controllers
The controllers
key is utilized for mapping a content negotiation strategy to a particular
controller service name (from the top-level controllers
section). The value portion
of the controller array can either be a named selector (see selectors
below), or a
selector definition.
A selector definition consists of an array with the key of the array being the name of a view model, and the value of it being an indexed array of media types that, when matched, will select that view model.
Example:
'controllers' => [ // Named selector: 'Application\Controller\HelloWorld1' => 'Json', // Selector definition: 'Application\Controller\HelloWorld2' => [ 'Laminas\ApiTools\ContentNegotiation\JsonModel' => [ 'application/json', 'application/*+json', ], ], ],
Key: selectors
The selectors
key is utilized to create named selector definitions for reuse between many different
controllers. The key part of the selectors array will be a name used to correlate the selector
definition (which uses the format described in the controllers key).
Example:
'selectors' => [ 'Json' => [ 'Laminas\ApiTools\ContentNegotiation\JsonModel' => [ 'application/json', 'application/*+json', ], ], ],
A selector can contain multiple view models, each associated with different media types, allowing you to provide multiple representations. As an example, the following selector would allow a given controller to return either JSON or HTML output:
'selectors' => [ 'HTML-Json' => [ 'Laminas\ApiTools\ContentNegotiation\JsonModel' => [ 'application/json', 'application/*+json', ], 'Laminas\ApiTools\ContentNegotiation\ViewModel' => [ 'text/html', ], ], ],
Key: accept_whitelist
The accept_whitelist
key is utilized to instruct the content negotiation module which media types
are acceptable for a given controller service name. When a controller service name is configured
in this key, along with an indexed array of matching media types, only media types that match
the Accept
header of a given request will be allowed to be dispatched. Unmatched media types
will receive a 406 Cannot honor Accept type specified
response.
The value of each controller service name key can either be a string or an array of strings.
Example:
'accept_whitelist' => [ 'Application\\Controller\\HelloApiController' => [ 'application/vnd.application-hello+json', 'application/hal+json', 'application/json', ], ],
Key: content_type_whitelist
The content_type_whitelist
key is utilized to instruct the content negotiation module which media
types are valid for the Content-Type
of a request. When a controller service name is
configured in this key, along with an indexed array of matching media types, only media types
that match the Content-Type
header of a given request will be allowed to be dispatched. Unmatched
media types will receive a 415 Invalid content-type specified
response.
The value of each controller service name key can either be a string or an array of strings.
Example:
'content_type_whitelist' => [ 'Application\\Controller\\HelloWorldController' => [ 'application/vnd.application-hello-world+json', 'application/json', ], ],
Key: x_http_method_override_enabled
- Since 1.3.0
This boolean flag determines whether or not the HttpMethodOverrideListener
will be enabled by default.
Key: http_override_methods
- Since 1.3.0
The http_override_methods
key is utilized to provide the
HttpMethodOverrideListener
with a map of allowed override methods for a given
HTTP method, as specified via the X-HTTP-Method-Override
header. Essentially,
the values are:
'Incoming HTTP request method' => $arrayOfAllowedOverrideMethods,
As an example, if you want to allow the X-HTTP-Method-Override
header to allow
overriding HTTP GET
requests with an alternate method, you might define this
as follows:
'x_http_method_override_enabled' => true, 'http_override_methods' => [ 'GET' => [ 'HEAD', 'POST', 'PUT', 'DELETE', 'PATCH', ], ];
Then, to make a request, you could do the following:
GET /foo HTTP/1.1 Host: example.com X-HTTP-Method-Override: PATCH some=content&more=content
The above would then be interpreted as a PATCH
request. If the same request
were made via HTTP POST
, or if a GET
request were made with an override
value of OPTIONS
, the listener would raise a problem, as, in the former case,
no maps are defined for POST
, and, in the latter, OPTIONS
is not in the map
for GET
.
System Configuration
The following configuration is provided in config/module.config.php
to enable the module to
function:
'filters' => [ 'aliases' => [ 'Laminas\Filter\File\RenameUpload' => 'filerenameupload', ], 'factories' => [ 'filerenameupload' => Factory\RenameUploadFilterFactory::class, ], ], 'validators' => [ 'aliases' => [ 'Laminas\Validator\File\UploadFile' => 'fileuploadfile', ], 'factories' => [ 'fileuploadfile' => Factory\UploadFileValidatorFactory::class, ], ], 'service_manager' => [ 'factories' => [ ContentTypeListener::class => InvokableFactory::class, 'Request' => Factory\RequestFactory::class, AcceptListener::class => Factory\AcceptListenerFactory::class, AcceptFilterListener::class => Factory\AcceptFilterListenerFactory::class, ContentTypeFilterListener::class => Factory\ContentTypeFilterListenerFactory::class, ContentNegotiationOptions::class => Factory\ContentNegotiationOptionsFactory::class, HttpMethodOverrideListener::class => Factory\HttpMethodOverrideListenerFactory::class, ], ], 'controller_plugins' => [ 'aliases' => [ 'routeParam' => ControllerPlugin\RouteParam::class, 'queryParam' => ControllerPlugin\QueryParam::class, 'bodyParam' => ControllerPlugin\BodyParam::class, 'routeParams' => ControllerPlugin\RouteParams::class, 'queryParams' => ControllerPlugin\QueryParams::class, 'bodyParams' => ControllerPlugin\BodyParams::class, ], 'factories' => [ ControllerPlugin\RouteParam::class => InvokableFactory::class, ControllerPlugin\QueryParam::class => InvokableFactory::class, ControllerPlugin\BodyParam::class => InvokableFactory::class, ControllerPlugin\RouteParams::class => InvokableFactory::class, ControllerPlugin\QueryParams::class => InvokableFactory::class, ControllerPlugin\BodyParams::class => InvokableFactory::class, ], ],
Laminas Events
Listeners
Laminas\ApiTools\ContentNegotiation\AcceptListener
This listener is attached to the MvcEvent::EVENT_DISPATCH
event with priority -10
. It is
responsible for performing the actual selection and casting of a controller's view model based on
the content negotiation configuration.
Laminas\ApiTools\ContentNegotiation\ContentTypeListener
This listener is attached to the MvcEvent::EVENT_ROUTE
event with a priority of -625
. It is
responsible for examining the Content-Type
header in order to determine how the content body
should be deserialized. Values are then persisted inside of a ParameterDataContainer
which is
stored in the LaminasContentNegotiationParameterData
key of the MvcEvent
object.
Laminas\ApiTools\ContentNegotiation\AcceptFilterListener
This listener is attached to the MvcEvent::EVENT_ROUTE
event with a priority of -625
. It is
responsible for ensuring the controller selected by routing is configured to respond to the specific
media type in the current request's Accept
header. If it cannot, it will short-circuit the MVC
dispatch process by returning a 406 Cannot honor Accept type specified
response.
Laminas\ApiTools\ContentNegotiation\ContentTypeFilterListener
This listener is attached to the MvcEvent::EVENT_ROUTE
event with a priority of -625
. It is
responsible for ensuring the route matched controller can accept content in the request body
specified by the media type in the current request's Content-Type
header. If it cannot, it will
short-circuit the MVC dispatch process by returning a 415 Invalid content-type specified
response.
Laminas\ApiTools\ContentNegotiation\HttpMethodOverrideListener
- Since 1.3.0
This listener is attached to the MvcEvent::EVENT_ROUTE
event with a priority
of -40
, but only if the x_http_method_override_enabled
configuration flag
was toggle on. It is responsible for checking if an X-HTTP-Method-Override
header is present, and, if so, if it contains a value in the set allowed for the
current HTTP request method invoked. If so, it resets the HTTP request method to
the header value.
Laminas Services
Controller Plugins
Laminas\ApiTools\ContentNegotiation\ControllerPlugin\RouteParam (a.k.a "routeParam")
A controller plugin (Laminas\Mvc\Controller\AbstractActionController
callable) that will return a
single parameter with a particular name from the route match.
use Laminas\Mvc\Controller\AbstractActionController; class IndexController extends AbstractActionController { public function indexAction() { return $this->routeParam('id', 'someDefaultValue'); } }
Laminas\ApiTools\ContentNegotiation\ControllerPlugin\QueryParam (a.k.a "queryParam")
A controller plugin (Laminas\Mvc\Controller\AbstractActionController
callable) that will return a
single parameter from the current request query string.
use Laminas\Mvc\Controller\AbstractActionController; class IndexController extends AbstractActionController { public function indexAction() { return $this->queryParam('foo', 'someDefaultValue'); } }
Laminas\ApiTools\ContentNegotiation\ControllerPlugin\BodyParam (a.k.a "bodyParam")
A controller plugin (Laminas\Mvc\Controller\AbstractActionController
callable) that will return a
single parameter from the content-negotiated content body.
use Laminas\Mvc\Controller\AbstractActionController; class IndexController extends AbstractActionController { public function indexAction() { return $this->bodyParam('foo', 'someDefaultValue'); } }
Laminas\ApiTools\ContentNegotiation\ControllerPlugin\RouteParams (a.k.a "routeParams")
A controller plugin (Laminas\Mvc\Controller\AbstractActionController
callable) that will return a
all the route parameters.
use Laminas\Mvc\Controller\AbstractActionController; class IndexController extends AbstractActionController { public function indexAction() { return $this->routeParams() } }
Laminas\ApiTools\ContentNegotiation\ControllerPlugin\QueryParams (a.k.a "queryParams")
A controller plugin (Laminas\Mvc\Controller\AbstractActionController
callable) that will return a
all the query parameters.
use Laminas\Mvc\Controller\AbstractActionController; class IndexController extends AbstractActionController { public function indexAction() { return $this->queryParams() } }
Laminas\ApiTools\ContentNegotiation\ControllerPlugin\BodyParams (a.k.a "bodyParams")
A controller plugin (Laminas\Mvc\Controller\AbstractActionController
callable) that will return a
all the content-negotiated body parameters.
use Laminas\Mvc\Controller\AbstractActionController; class IndexController extends AbstractActionController { public function indexAction() { return $this->bodyParams() } }