diff --git a/en/appendices/3-5-migration-guide.rst b/en/appendices/3-5-migration-guide.rst index 80f5cd0a34..3c77f1a5e9 100644 --- a/en/appendices/3-5-migration-guide.rst +++ b/en/appendices/3-5-migration-guide.rst @@ -64,6 +64,8 @@ The following is a list of methods that are deprecated and replaced with * ``tableLocator()`` ``Cake\ORM\Table`` * ``validator()`` +``Cake\Routing\RouteCollection`` + * ``extensions()`` ``Cake\TestSuite\TestFixture`` * ``schema()`` ``Cake\Utility\Security`` @@ -112,6 +114,22 @@ behavior that may affect your application: New Features ============ +Core +---- + +* ``Cake\Core\ObjectRegistry`` now implements the ``Countable`` and + ``IteratorAggregate`` interfaces. + +Console +------- + +* ``Cake\Console\ConsoleOptionParser::setHelpAlias()`` was added. This method + allows you to set the command name used when generating help output. Defaults + to ``cake``. + +Http +---- + * New Cookie & CookieCollection classes have been added. These classes allow you to work with cookies in an object-orientated way, and are available on ``Cake\Http\ServerRequest``, ``Cake\Http\Repsonse``, and @@ -123,37 +141,54 @@ New Features :ref:`encrypted-cookie-middleware` for more information. * New middleware has been added to make protecting against CSRF easier. See :ref:`csrf-middleware` for more information. +* ``Cake\Http\Client::addCookie()`` was added to make it easy to add cookies to + a client instance. + +Event +----- + * ``Cake\Event\EventManager::on()`` and ``off()`` methods are now chainable making it simpler to set multiple events at once. -* ``Cake\Validation\Validator::regex()`` was added for a more convenient way - to validate data against a regex pattern. -* ``Cake\Routing\Router::reverseToArray()`` was added. This method allow you to - convert a request object into an array that can be used to generate URL - strings. + +ORM +--- + +* ``Cake\Datasource\SchemaInterface`` was added. +* New abstract types were added for ``smallinteger`` and ``tinyinteger``. + Existing ``SMALLINT`` and ``TINYINT`` columns will now be reflected as these + new abstract types. ``TINYINT(1)`` columns will continue to be treated as + boolean columns in MySQL. * ``Cake\ORM\Query::contain()`` now allows you to call it without the wrapping array when containing a single association. ``contain('Comments', function () { ... });`` will now work. This makes ``contain()`` consistent with other eagerloading related methods like ``leftJoinWith()`` and ``matching()``. + +Routing +------- + +* ``Cake\Routing\Router::reverseToArray()`` was added. This method allow you to + convert a request object into an array that can be used to generate URL + strings. * ``Cake\Routing\RouteBuilder::resources()`` had the ``path`` option added. This option lets you make the resource path and controller name not match. -* New abstract types were added for ``smallinteger`` and ``tinyinteger``. - Existing ``SMALLINT`` and ``TINYINT`` columns will now be reflected as these - new abstract types. ``TINYINT(1)`` columns will continue to be treated as - boolean columns in MySQL. +* ``Cake\Routing\RouteBuilder`` now has methods to create routes for + specific HTTP methods. e.g ``get()`` and ``post()``. +* ``Cake\Routing\Route`` now has fluent methods for defining options. + +Validation +---------- +* ``Cake\Validation\Validator::regex()`` was added for a more convenient way + to validate data against a regex pattern. * ``Cake\Validation\Validator::addDefaultProvider()`` was added. This method lets you inject validation providers into all the validators created in your application. +* ``Cake\Validation\ValidatorAwareInterface`` was added to define the methods + implemented by ``Cake\Validation\ValidatorAwareTrait``. + +View +---- + * ``Cake\View\Helper\PaginatorHelper::limitControl()`` was added. This method lets you create a form with a select box for updating the limit value on a paginated result set. -* ``Cake\Core\ObjectRegistry`` now implements the ``Countable`` and - ``IteratorAggregate`` interfaces. -* ``Cake\Console\ConsoleOptionParser::setHelpAlias()`` was added. This method - allows you to set the command name used when generating help output. Defaults - to ``cake``. -* ``Cake\Datasource\SchemaInterface`` was added. -* ``Cake\Validation\ValidatorAwareInterface`` was added to define the methods - implemented by ``Cake\Validation\ValidatorAwareTrait``. -* ``Cake\Http\Client::addCookie()`` was added to make it easy to add cookies to - a client instance. diff --git a/en/development/routing.rst b/en/development/routing.rst index 5f98756f49..f44e819a12 100644 --- a/en/development/routing.rst +++ b/en/development/routing.rst @@ -47,13 +47,21 @@ viewing an article's content:: The above route will accept any URL looking like ``/articles/15`` and invoke the method ``view(15)`` in the ``ArticlesController``. This will not, though, prevent people from trying to access URLs looking like ``/articles/foobar``. If -you wish, you can restring some parameters to conform to a regular expression:: +you wish, you can restrict some parameters to conform to a regular expression:: + $routes->connect( + '/articles/:id', + ['controller' => 'Articles', 'action' => 'view'], + ) + ->setPatterns(['id' => '\d+']) + ->setPass(['id']); + + // Prior to 3.5 use the options array $routes->connect( '/articles/:id', ['controller' => 'Articles', 'action' => 'view'], ['id' => '\d+', 'pass' => ['id']] - ); + ) The previous example changed the star matcher by a new placeholder ``:id``. Using placeholders allows us to validate parts of the URL, in this case we used @@ -75,7 +83,6 @@ Routes can also be labelled with a unique name, this allows you to quickly reference them when building links instead of specifying each of the routing parameters:: - // In routes.php $routes->connect( '/login', @@ -123,6 +130,7 @@ some routes we'll use the ``scope()`` method:: use Cake\Routing\Route\DashedRoute; Router::scope('/', function ($routes) { + // Connect the generic fallback routes. $routes->fallbacks(DashedRoute::class); }); @@ -134,7 +142,7 @@ match elements in the URL. The basic format for a route definition is:: $routes->connect( - 'URL template', + '/url/template', ['default' => 'defaultValue'], ['option' => 'matchingRegex'] ); @@ -188,29 +196,65 @@ define default parameters. If you built a site that features products for different categories of customers, you might consider creating a route. This allows you to link to ``/government`` rather than ``/pages/display/5``. -Another common use for the Router is to define an "alias" for a -controller. Let's say that instead of accessing our regular URL at -``/users/some_action/5``, we'd like to be able to access it by -``/cooks/some_action/5``. The following route takes care of -that:: +A common use for routing is to create URL segments that don't match your +controller or model names. Let's say that instead of accessing our regular URL +at ``/users/some_action/5``, we'd like to be able to access it by +``/cooks/some_action/5``. The following route takes care of that:: $routes->connect( '/cooks/:action/*', ['controller' => 'Users'] ); This is telling the Router that any URL beginning with ``/cooks/`` should be -sent to the users controller. The action called will depend on the value of the -``:action`` parameter. By using :ref:`route-elements`, you can create variable -routes, that accept user input or variables. The above route also uses the -greedy star. The greedy star indicates to ``Router`` that this route -should accept any additional positional arguments given. These arguments will be -made available in the :ref:`passed-arguments` array. +sent to the ``UsersController``. The action called will depend on the value of +the ``:action`` parameter. By using :ref:`route-elements`, you can create +variable routes, that accept user input or variables. The above route also uses +the greedy star. The greedy star indicates that this route should accept any +additional positional arguments given. These arguments will be made available in +the :ref:`passed-arguments` array. When generating URLs, routes are used too. Using ``['controller' => 'Users', 'action' => 'some_action', 5]`` as a URL will output ``/cooks/some_action/5`` if the above route is the first match found. +The routes we've connected so far will match any HTTP verb. If you are building +a REST API you'll often want to map HTTP actions to different controller methods. +The ``RouteBuilder`` provides helper methods that make defining routes for +specific HTTP verbs simpler:: + + // Create a route that only responds to GET requests. + $routes->get( + '/cooks/:id', + ['controller' => 'Users', 'action' => 'view'], + 'users:view' + ); + + // Create a route that only responds to PUT requests + $routes->put( + '/cooks/:id', + ['controller' => 'Users', 'action' => 'update'], + 'users:update' + ); + +The above routes map the same URL to different controller actions based on the +HTTP verb used. GET requests will go to the 'view' action, while PUT requests +will go to the 'update' action. There are HTTP helper methods for: + +* GET +* POST +* PUT +* PATCH +* DELETE +* OPTIONS +* HEAD + +All of these methods return the route instance allowing you to leverage the +:ref:`fluent setters ` to further configure your route. + +.. versionadded:: 3.5.0 + The HTTP verb helper methods were added in 3.5.0 + .. _route-elements: Route Elements @@ -225,6 +269,12 @@ expression - this tells CakePHP how to know if the URL is correctly formed or not. If you choose to not provide a regular expression, any non ``/`` character will be treated as part of the parameter:: + $routes->connect( + '/:controller/:id', + ['action' => 'view'] + )->setPatterns(['id' => '[0-9]+']); + + // Prior to 3.5 use the options array $routes->connect( '/:controller/:id', ['action' => 'view'], @@ -246,11 +296,19 @@ rewritten like so:: use Cake\Routing\Route\DashedRoute; - $routes->connect( - '/:controller/:id', - ['action' => 'view'], - ['id' => '[0-9]+', 'routeClass' => DashedRoute::class] - ); + // Create a builder with a different route class. + $routes->scope('/', function ($routes) { + $routes->routeClass(DashedRoute::class); + $routes->connect('/:controller/:id', ['action' => 'view']) + ->setPatterns(['id' => '[0-9]+']); + + // Prior to 3.5 use options array + $routes->connect( + '/:controller/:id', + ['action' => 'view'], + ['id' => '[0-9]+'] + ); + }); The ``DashedRoute`` class will make sure that the ``:controller`` and ``:plugin`` parameters are correctly lowercased and dashed. @@ -278,23 +336,23 @@ following:: If you would like to provide a case insensitive URL, you can use regular expression inline modifiers:: + // Prior to 3.5 use the options array instead of setPatterns() $routes->connect( '/:userShortcut', ['controller' => 'Teachers', 'action' => 'profile', 1], - ['userShortcut' => '(?i:principal)'] - ); + )->setPatterns(['userShortcut' => '(?i:principal)']); One more example, and you'll be a routing pro:: + // Prior to 3.5 use the options array instead of setPatterns() $routes->connect( '/:controller/:year/:month/:day', - ['action' => 'index'], - [ - 'year' => '[12][0-9]{3}', - 'month' => '0[1-9]|1[012]', - 'day' => '0[1-9]|[12][0-9]|3[01]' - ] - ); + ['action' => 'index'] + )->setPatterns([ + 'year' => '[12][0-9]{3}', + 'month' => '0[1-9]|1[012]', + 'day' => '0[1-9]|[12][0-9]|3[01]' + ]); This is rather involved, but shows how powerful routes can be. The URL supplied has four route elements. The first is familiar to us: it's a default route @@ -338,6 +396,44 @@ CakePHP, and should not be used unless you want the special meaning * ``_name`` Name of route. If you have setup named routes, you can use this key to specify it. +.. _route-fluent-methods: + +Configuring Route Options +------------------------- + +There are a number of route options that can be set on each route. After +connecting a route you can use its fluent builder methods to further configure +the route. These methods replace many of the keys in the ``$options`` parameter +of ``connect()``:: + + $routes->connect( + '/:lang/articles/:slug', + ['controller' => 'Articles', 'action' => 'view'], + ) + // Allow GET and POST requests. + ->setMethods(['GET', 'POST']) + + // Only match on the blog subdomain. + ->setHost('blog.example.com') + + // Set the route elements that should be converted to passed arguments + ->setPass(['slug']) + + // Set the matching patterns for route elements + ->setPatterns([ + 'slug' => '[a-z0-9-_]+', + 'lang' => 'en|fr|es', + ]) + + // Also allow JSON file extensions + ->setExtenions(['json']) + + // Set lang to be a persistent parameter + ->setPersist(['lang']); + +.. versionadded:: 3.5.0 + Fluent builder methods were added in 3.5.0 + Passing Parameters to Action ---------------------------- @@ -356,16 +452,16 @@ functions:: Router::scope('/', function ($routes) { $routes->connect( '/blog/:id-:slug', // E.g. /blog/3-CakePHP_Rocks - ['controller' => 'Blogs', 'action' => 'view'], - [ - // Define the route elements in the route template - // to pass as function arguments. Order matters since this - // will simply map ":id" to $articleId in your action - 'pass' => ['id', 'slug'], - // Define a pattern that `id` must match. - 'id' => '[0-9]+' - ] - ); + ['controller' => 'Blogs', 'action' => 'view'] + ) + ->setPatterns([ + // Define the route elements in the route template + // to pass as function arguments. Order matters since this + // will simply map ":id" to $articleId in your action + 'pass' => ['id', 'slug'], + // Define a pattern that `id` must match. + 'id' => '[0-9]+', + ]); }); Now thanks to the reverse routing capabilities, you can pass in the URL array @@ -405,8 +501,15 @@ option can be used in reverse routing to identify the route you want to use:: ['_name' => 'login'] ); + // Name a verb specific route (3.5.0+) + $routes->post( + '/logout', + ``['controller' => 'Users', 'action' => 'logout'], + 'logout' + ); + // Generate a URL using a named route. - $url = Router::url(['_name' => 'login']); + $url = Router::url(['_name' => 'logout']); // Generate a URL using a named route, // with some query string args. @@ -427,7 +530,7 @@ you to define name prefixes in each scope:: Router::scope('/api', ['_namePrefix' => 'api:'], function ($routes) { // This route's name will be `api:ping` - $routes->connect('/ping', ['controller' => 'Pings'], ['_name' => 'ping']); + $routes->get('/ping', ['controller' => 'Pings'], 'ping'); }); // Generate a URL for the ping route Router::url(['_name' => 'api:ping']); @@ -448,7 +551,7 @@ you'd expect:: Router::plugin('Contacts', ['_namePrefix' => 'contacts:'], function ($routes) { $routes->scope('/api', ['_namePrefix' => 'api:'], function ($routes) { // This route's name will be `contacts:api:ping` - $routes->connect('/ping', ['controller' => 'Pings'], ['_name' => 'ping']); + $routes->get('/ping', ['controller' => 'Pings'], 'ping'); }); }); @@ -631,19 +734,36 @@ with the following router connection:: Matching Specific HTTP Methods ------------------------------ -Routes can match specific HTTP methods using the ``_method`` routing key:: +Routes can match specific HTTP methods using the HTTP verb helper methods:: Router::scope('/', function($routes) { // This route only matches on POST requests. - $routes->connect( + $routes->post( '/reviews/start', - ['controller' => 'Reviews', 'action' => 'start', '_method' => 'POST'] + ['controller' => 'Reviews', 'action' => 'start'] ); + + // Match multiple verbs + // Prior to 3.5 use $options['_method'] to set method + $routes->connect( + '/reviews/start', + [ + 'controller' => 'Reviews', + 'action' => 'start', + ] + )->setMethods(['POST', 'PUT']); }); You can match multiple HTTP methods by using an array. Because the ``_method`` parameter is a routing key, it participates in both URL parsing and URL -generation. +generation. To generate URLs for method specific routes you'll need to include +the ``_method`` key when generating the URL:: + + $url = Router::url([ + 'controller' => 'Reviews', + 'action' => 'start', + '_method' => 'POST', + ]); Matching Specific Hostnames --------------------------- @@ -653,18 +773,17 @@ the ``*.`` wildcard to match any subdomain:: Router::scope('/', function($routes) { // This route only matches on http://images.example.com + // Prior to 3.5 use the _host option $routes->connect( '/images/default-logo.png', - ['controller' => 'Images', 'action' => 'default'], - ['_host' => 'images.example.com'] - ); + ['controller' => 'Images', 'action' => 'default'] + )->setHost('images.example.com'); // This route only matches on http://*.example.com $routes->connect( '/images/old-log.png', - ['controller' => 'Images', 'action' => 'oldLogo'], - ['_host' => '*.example.com'] - ); + ['controller' => 'Images', 'action' => 'oldLogo'] + )->setHost('images.example.com'); }); The ``_host`` option is also used in URL generation. If your ``_host`` option @@ -675,15 +794,14 @@ parameter when generating URLs:: // If you have this route $routes->connect( '/images/old-log.png', - ['controller' => 'Images', 'action' => 'oldLogo'], - ['_host' => '*.example.com'] - ); + ['controller' => 'Images', 'action' => 'oldLogo'] + )->setHost('images.example.com'); // You need this to generate a url echo Router::url([ 'controller' => 'Images', 'action' => 'oldLogo', - '_host' => 'images.example.com' + '_host' => 'images.example.com', ]); .. versionadded:: 3.4.0 @@ -737,11 +855,8 @@ and then parse what remains. If you want to create a URL such as $routes->extensions(['json', 'xml', 'html']); $routes->connect( '/:title', - ['controller' => 'Pages', 'action' => 'view'], - [ - 'pass' => ['title'] - ] - ); + ['controller' => 'Pages', 'action' => 'view'] + )->setPass(['title']); }); Then to create links which map back to the routes simply use:: @@ -1111,6 +1226,7 @@ You can also use any of the special route elements when generating URLs: ``ftp``. Defaults to the current scheme. * ``_host`` Set the host to use for the link. Defaults to the current host. * ``_port`` Set the port if you need to create links on non-standard ports. +* ``_method`` Define the HTTP verb the URL is for. * ``_full`` If ``true`` the ``FULL_BASE_URL`` constant will be prepended to generated URLs. * ``_ssl`` Set to ``true`` to convert the generated URL to https or ``false`` @@ -1185,6 +1301,15 @@ option:: ['routeClass' => 'SlugRoute'] ); + // Or by setting the routeClass in your scope. + $routes->scope('/', function ($routes) { + $routes->routeClass('SlugRoute'); + $routes->connect( + '/:slug', + ['controller' => 'Articles', 'action' => 'view'] + ); + }); + This route would create an instance of ``SlugRoute`` and allow you to implement custom parameter handling. You can use plugin route classes using standard :term:`plugin syntax`.