cakephpurl-routingcakephp-3.0cakephp-3.xcakephp-3.3

CakePHP 3 routing with parameters


I'm trying to create SEO friendly routing. I have a website with hotels and hotel rooms. I want to create a route progression that routes to different controllers/actions.

I want my urls to look like www.hotelwebsite.com/language/hotel-name/room-name

Here are the three routes I need:

If the url has a language parameter + 2 parameters:

$routes->connect('/:language/:hotelname/:room/', ['controller' => 'rooms', 'action' => 'viewRoom']); 

where

public function viewRoom($hotel_slug, $room_slug)

in which

:hotelname == $hotel_slug and :room == $room_slug

If the url has a language parameter + 1 parameter:

$routes->connect('/:language/:hotelname/', ['controller' => 'hotels', 'action' => 'viewHotel']);

where

public function viewHotel($hotel_slug)

in which

:hotelname == $hotel_slug

Otherwise use my standard route

$routes->connect('/:language/:controller/:action/*');

Is this even remotely possible?


Solution

  • Surely that's possible, the example routes are almost ready to go, you just need to define which elements should be passed as function arguments, and you most probably have to limit what :hotelname and/or :room matches, as otherwise the router will not be able to differentiate between:

    /:language/:hotelname/:room
    

    and:

    /:language/:controller/:action
    

    and the first route would always win.

    Passing as arguments can be configured via the pass option, like:

    $routes->connect(
        '/:language/:hotelname/:room',
        [
            'controller' => 'rooms',
            'action' => 'viewRoom'
        ],
        [
            'pass' => ['hotelname', 'room']
        ]
    );
    
    $routes->connect(
        '/:language/:hotelname',
        [
            'controller' => 'rooms',
            'action' => 'viewHotel'
        ],
        [
            'pass' => ['hotelname']
        ]
    );
    

    Restricting what an elment matches can be done via regular expressions like:

    $routes->connect(
        '/:language/:hotelname/:room',
        [
            'controller' => 'rooms',
            'action' => 'viewRoom'
        ],
        [
            'pass' => ['hotelname', 'room'],
            'hotelname' => '(?:name1|name2|name3)',
            'room' => '[0-9]+'
        ]
    );
    

    If you cannot restrict the elements that way because they are dynamic and/or there are too many, then you'll have to try a custom route class that for example matches against the database, check for example Mapping slugs from database in routing.

    See also