diff --git a/en/_static/img/middleware-request.png b/en/_static/img/middleware-request.png new file mode 100644 index 0000000000..7301b09033 Binary files /dev/null and b/en/_static/img/middleware-request.png differ diff --git a/en/_static/img/middleware-setup.png b/en/_static/img/middleware-setup.png new file mode 100644 index 0000000000..5e65f5c499 Binary files /dev/null and b/en/_static/img/middleware-setup.png differ diff --git a/en/appendices/3-5-migration-guide.rst b/en/appendices/3-5-migration-guide.rst index 10f833dc12..8e4964a591 100644 --- a/en/appendices/3-5-migration-guide.rst +++ b/en/appendices/3-5-migration-guide.rst @@ -135,6 +135,15 @@ behavior that may affect your application: New Features ============ +Scoped Middleware +----------------- + +Middleware can now be conditionally applied to routes in specific URL +scopes. This allows you to build specific stacks of middleware for different +parts of your application without having to write URL checking code in your +middleware. See the :ref:`connecting-scoped-middleware` section for more +information. + New Console Runner ------------------ @@ -146,13 +155,13 @@ how they are named and how the shells get their dependencies. Adopting this new class requires replacing the contents of your ``bin/cake.php`` file with the `following file `_. -Cache ------ +Cache Engine Fallbacks +---------------------- -* Cache engines can now be configured with a ``fallback`` key that defines a - cache configuration to fall back to if the engine is misconfigured (or - unavailable). See :ref:`cache-configuration-fallback` for more information on - configuring fallbacks. +Cache engines can now be configured with a ``fallback`` key that defines a +cache configuration to fall back to if the engine is misconfigured (or +unavailable). See :ref:`cache-configuration-fallback` for more information on +configuring fallbacks. Core ---- diff --git a/en/controllers/middleware.rst b/en/controllers/middleware.rst index 877886214c..4314540e36 100644 --- a/en/controllers/middleware.rst +++ b/en/controllers/middleware.rst @@ -2,12 +2,34 @@ Middleware ########## Middleware objects give you the ability to 'wrap' your application in re-usable, -composable layers of Request handling, or response building logic. Middleware -are part of the new HTTP stack in CakePHP that leverages the PSR-7 request and -response interfaces. By leveraging the PSR-7 standard you can use any PSR-7 -compatible middleware available on `The Packagist `__. +composable layers of Request handling, or response building logic. Visually, +your application ends up at the center, and middleware is wrapped aroud the app +like an onion. Here we can see an application wrapped with Routes, Assets, +Exception Handling and CORS header middleware. -CakePHP provides several middleware out of the box: +.. image:: /_static/img/middleware-setup.png + +When a request is handled by your application it enters from the outermost +middleware. Each middleware can either delegate the request/response to the next +layer, or return a response. Returning a response prevents lower layers from +ever seeing the request. An example of that is the AssetMiddleware handling +a request for a plugin image during development. + +.. image:: /_static/img/middleware-request.png + +If no middleware take action to handle the request, a controller will be located +and have its action invoked, or an exception will be raised generating an error +page. + +Middleware are part of the new HTTP stack in CakePHP that leverages the PSR-7 +request and response interfaces. Because CakePHP is leveraging the PSR-7 +standard you can use any PSR-7 compatible middleware available on `The Packagist +`__. + +Middleware in CakePHP +===================== + +CakePHP provides several middleware to handle common tasks in web applications: * ``Cake\Error\Middleware\ErrorHandlerMiddleware`` traps exceptions from the wrapped middleware and renders an error page using the @@ -32,11 +54,14 @@ CakePHP provides several middleware out of the box: Using Middleware ================ -You attach middleware in your ``App\Application`` class' ``middleware`` method. -If you don't have an ``App\Application`` class, see the section on -:ref:`adding-http-stack` for more information. Your application's ``middleware`` -hook method will be called early in the request process, you can use the -``Middleware`` object to attach middleware:: +Middleware can be applied to your application globally, or to individual +routing scopes. + +To apply middleware to all requests, use the ``middleware`` method of your +``App\Application`` class. If you don't have an ``App\Application`` class, see +the section on :ref:`adding-http-stack` for more information. Your application's +``middleware`` hook method will be called at the beginning of the request +process, you can use the ``MiddlewareQueue`` object to attach middleware:: namespace App; @@ -45,11 +70,11 @@ hook method will be called early in the request process, you can use the class Application extends BaseApplication { - public function middleware($middlewareStack) + public function middleware($middlewareQueue) { // Bind the error handler into the middleware queue. - $middlewareStack->add(new ErrorHandlerMiddleware()); - return $middlewareStack; + $middlewareQueue->add(new ErrorHandlerMiddleware()); + return $middlewareQueue; } } @@ -59,19 +84,19 @@ a variety of operations:: $layer = new \App\Middleware\CustomMiddleware; // Added middleware will be last in line. - $middlewareStack->add($layer); + $middlewareQueue->add($layer); // Prepended middleware will be first in line. - $middlewareStack->prepend($layer); + $middlewareQueue->prepend($layer); // Insert in a specific slot. If the slot is out of // bounds, it will be added to the end. - $middlewareStack->insertAt(2, $layer); + $middlewareQueue->insertAt(2, $layer); // Insert before another middleware. // If the named class cannot be found, // an exception will be raised. - $middlewareStack->insertBefore( + $middlewareQueue->insertBefore( 'Cake\Error\Middleware\ErrorHandlerMiddleware', $layer ); @@ -79,7 +104,7 @@ a variety of operations:: // Insert after another middleware. // If the named class cannot be found, the // middleware will added to the end. - $middlewareStack->insertAfter( + $middlewareQueue->insertAfter( 'Cake\Error\Middleware\ErrorHandlerMiddleware', $layer ); @@ -100,8 +125,8 @@ scripts, that add middleware:: EventManager::instance()->on( 'Server.buildMiddleware', - function ($event, $middlewareStack) { - $middlewareStack->add(new ContactPluginMiddleware()); + function ($event, $middlewareQueueStack) { + $middlewareQueueStack->add(new ContactPluginMiddleware()); }); PSR-7 Requests and Responses @@ -253,14 +278,14 @@ application:: class Application { - public function middleware($middlewareStack) + public function middleware($middlewareQueueStack) { // Add your simple middleware onto the queue - $middlewareStack->add(new TrackingCookieMiddleware()); + $middlewareQueueStack->add(new TrackingCookieMiddleware()); // Add some more middleware onto the queue - return $middlewareStack; + return $middlewareQueueStack; } } @@ -293,7 +318,7 @@ your application's middleware stack:: ->noOpen() ->noSniff(); - $middleware->add($headers); + $middlewareQueue->add($headers); .. versionadded:: 3.5.0 The ``SecurityHeadersMiddleware`` was added in 3.5.0 @@ -316,7 +341,7 @@ Cookie data is encrypted with via OpenSSL using AES:: Configure::read('Security.cookieKey') ); - $middleware->add($cookies); + $middlewareQueue->add($cookies); .. note:: It is recommended that the encryption key you use for cookie data, is used @@ -337,13 +362,13 @@ CSRF protection can be applied to your entire application, or to specific scopes by applying the ``CsrfProtectionMiddleware`` to your middleware stack:: use Cake\Http\Middleware\CsrfProtectionMiddleware; - + $options = [ // ... ]; $csrf = new CsrfProtectionMiddleware($options); - $middleware->add($csrf); + $middlewareQueue->add($csrf); Options can be passed into the middleware's constructor. The available configuration options are: diff --git a/en/development/routing.rst b/en/development/routing.rst index fc9db66a78..47cdd054c1 100644 --- a/en/development/routing.rst +++ b/en/development/routing.rst @@ -878,9 +878,13 @@ to do automatic view switching based on content types. Connecting Scoped Middleware ---------------------------- -Middleware can be applied to your entire application, or to an individual -routing scope. Before middleware can be applied to a scope, it needs to be -registered:: +While Middleware can be applied to your entire application, applying middleware +to specific routing scopes offers more flexibility, as you can apply middleware +only where it is needed allowing your middleware to not concern itself with +how/where it is being applied. + +Before middleware can be applied to a scope, it needs to be +registered into the route collection:: // in config/routes.php use Cake\Http\Middleware\CsrfProtectionMiddleware; @@ -891,21 +895,61 @@ registered:: $routes->registerMiddleware('cookies', new EncryptedCookiesMiddleware()); }); -Once registered into the route builder, middleware can be applied to specific +Once registered, scoped middleware can be applied to specific scopes:: $routes->scope('/cms', function ($routes) { - // Enable registered middleware for this scope. + // Enable CSRF & cookies middleware $routes->applyMiddleware('csrf', 'cookies'); + $routes->get('/articles/:action/*', ['controller' => 'Articles']) + }); + +In situations where you have nested scopes, inner scopes will inherit the +middleware applied in the containing scope:: + + $routes->scope('/api', function ($routes) { + $routes->applyMiddleware('ratelimit', 'auth.api'); + $routes->scope('/v1', function ($routes) { + $routes->applyMiddleware('v1compat'); + // Define routes here. + }); + }); + +In the above example, the routes defined in ``/v1`` will have 'ratelimit', +'auth.api', and 'v1compat' middleware applied. If you re-open a scope, the +middleware applied to routes in each scope will be isolated:: + + $routes->scope('/blog', function ($routes) { + $routes->applyMiddleware('auth'); + // Connect the authenticated actions for the blog here. }); + $routes->scope('/blog', function ($routes) { + // Connect the public actions for the blog here. + }); + +In the above example, the two uses of the ``/blog`` scope do not share +middleware. However, both of these scopes will inherit middleware defied in +their enclosing scopes. + + +Grouping Middleware +------------------- + +To help keep your route code :abbr:`DRY (Do not Repeat Yourself)` middleware can +be combined into groups. Once combined groups can be applied like middleware +can:: + + $routes->registerMiddleware('cookie', new EncryptedCookieMiddleware()); + $routes->registerMiddleware('auth', new AuthenticationMiddleware()); + $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware()); + $routes->middlewareGroup('web', ['cookie', 'auth', 'csrf']); + + // Apply the group + $routes->applyMiddleware('web'); -In situations where you have nested scopes, all middleware applied in each scope -will be applied starting from the top-most scope and ending with the nested -scopes. By applying middleware in specific scopes you can omit complicated URL -matching logic out of your middleware layers and let them focus on their task. .. versionadded:: 3.5.0 - Scoped middleware support was added in 3.5.0 + Scoped middleware & middleware groups were added in 3.5.0 .. _resource-routes: