CakePHP 3 routing with parameters - cakephp

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?

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
API > \Cake\Routing\RouteBuilder::connect()
Cookbook > Routing > Route Elements
Cookbook > Routing > Passing Parameters to Action

Related

How to use route elements instead of query string arguments for pagination in CakePHP 4.x?

My website's pagination urls worked like this, but stopped working when i upgraded to 4.x - for example: 1st page: mywebsite.com/new - 2nd page: mywebsite.com/new/page/2, etc. I did that with the following code:
//routes.php
$routes->connect('/new/page/:page',
['controller' => 'Articles', 'action' => 'latest'], [
'pass' => [
'page'
],
'page' => '\d+'
]);
$routes->connect('/new', ['controller' => 'Articles', 'action' => 'latest']);
then in my view/pagination-element I have the following:
//view
$this->Paginator->options(['url' => ['controller' => 'Articles', 'action' => 'latest']]);
The URLs mywebsite.com/new/page/2, 3, etc. are still working when accessing them directly, but links created through the paginator in the view are mywebsite.com/new?page=2, 3, etc. when they should be mywebsite.com/new/page/2, 3, etc.
can someone point me in the right direction?
I'd suggest that you consider possibly changing your URL schema to use the query argument style. Using route/URI elements just means more work to get the paginator features working properly. Also all the arguments (SEO, readability, etc) made for using "pretty URLs" for pagination, that were so popular back in the days, turned out to be nothing but hot air.
That being said, you'd have to use a custom/extended paginator helper to change how URLs are being generated, as by default the helper will explicitly set the page parameter (and all other pagination related parameters) as query string arguments. You have control over all generated URLs if you override \Cake\View\Helper\PaginatorHelper::generateUrlParams().
A quick and dirty example:
// src/View/Helper/PaginatorHelper.php
/*
Load in `AppView::initialize()` via:
$this->loadHelper('Paginator', [
'className' => \App\View\Helper\PaginatorHelper::class
]);
*/
declare(strict_types=1);
namespace App\View\Helper;
class PaginatorHelper extends \Cake\View\Helper\PaginatorHelper
{
public function generateUrlParams(array $options = [], ?string $model = null, array $url = []): array
{
$params = parent::generateUrlParams($options, $model, $url);
if (isset($params['?']['page'])) {
$params[0] = $params['?']['page'];
unset($params['?']['page']);
} else {
$params[0] = 1;
}
return $params;
}
}
That would move to the page parameter value in the URL array from the query string config to a regular URL parameter, ie turn
['controller' => 'Articles', 'action' => 'latest', '?' => ['page' => 1, /* ... */]]
into
['controller' => 'Articles', 'action' => 'latest', 1, '?' => [/* ... */]]
But again, I'd strongly suggest considering to switch to the query string URL schema.
See also
Cookbook > Views > Helpers > Configuring Helpers > Aliasing Helpers
Cookbook > Views > Helpers > Creating helpers

CakePHP 3 use default routing in some cases

I have a CakePHP 3.5 website. It was necessary to set slugs without any url prefixes like /pages/slug, so I wrote the following rule:
$routes->connect(
'/:slug',
['controller' => 'Pages', 'action' => 'redirectPage']
)
->setMethods(['GET', 'POST'])
->setPass(['slug'])
->setPatterns([
'slug' => '[a-z0-9\-_]+'
]);
It works nice, but in some cases I want cakePHP to route as default (Controller/Action/Params). For example, I want /admin/login to call 'login' action in 'AdminController'.
I have two ideas that doesn't need exact routings, but I can't make any of them to work:
Filtering some strings by pattern: It would be nice if I could filter some strings, that if slug doesn't match pattern, it will simply skip the routing rule.
Create a '/admin/:action' routing rule, but then I cant use :action as an action variable. It causes errors.
$routes->connect(
'/admin/:action',
['controller' => 'Admin', 'action' => ':action']
)
Any ideas?
Thanks
You can use prefix for admin restricted area.
Example:
Router::prefix('admin', function ($routes) {
$routes->connect('/', ['controller' => 'Users', 'action' => 'login']);
$routes->fallbacks(DashedRoute::class);
});
$routes->connect('/:slug' , [
'controller' => 'Pages',
'action' => 'display',
'plugin' => NULL
], [
'slug' => '[A-Za-z0-9/-]+',
'pass' => ['slug']
]);
Now, for example, path /admin/dashboard/index will execute method in Admin "subnamespace" \App\Controller\Admin\DashboardController::index()
Its nicely described in docs: https://book.cakephp.org/3.0/en/development/routing.html#prefix-routing
Try this:
$routes->connect(
'/admin/:action',
['controller' => 'Admin'],
['action' => '(login|otherAllowedAction|someOtherAllowedAction)']
);
Also, your slug routes seems to not catch /admin/:action routes, b/c dash is not allowed there: [a-z0-9\-_]+

CakePHP3 routing: pass static variable to controller action

I trying to pass "language" param from CakePHP3 route, to the action, so I can set the language for those pages.
$routes->connect('/es/hola', ['controller' => 'StaticPages', 'action' => 'welcome']);
$routes->connect('/en/hello', ['controller' => 'StaticPages', 'action' => 'welcome']);
The only way I can make it work is using a dynamic parameter like this:
$routes->connect('/:lang/hola', ['controller' => 'StaticPages', 'action' => 'welcome'], ['pass' => ['lang']]);
But the problem is this route will be match:
/en/hola
/es/hello
...
/fr/hello
I think it should be another best way to do this in CakePHP3, but I can't find this.
Thanks!
If you don't want it to be dynamic, then you need to pass it in the defaults, ie alongside the controller and action:
$routes->connect(
'/es/hola',
[
'controller' => 'StaticPages',
'action' => 'welcome',
'lang' => 'es'
]
);
In the controller the parameter will be available via the request object:
$lang = $this->request->getParam('lang'); // param('lang') before CakePHP 3.4
If you want it to be passed as an argument to the controller action, you can still define it to be passed via the pass option.
See also
Cookbook > Routing > Connecting Routes
API > \Cake\Routing\RouteBuilder::connect()

want to remove action name from url CakePHP

i am working on a Cakephp 2.x.. i want to remove the action or controller name from url ... for example i am facing a problem is like that
i have a function name index on my Messages controller in which all the mobile numbers are displaying
the url is
www.myweb.com/Messages
now in my controller there is a second function whose name is messages in which i am getting the messages against the mobile number
so now my url becomes after clicking the number is
www.myweb.com/Messages/messages/823214
now i want to remove the action name messages because it looks weired...
want to have a url like this
www.myweb.com/Messages/823214
When connecting routes using Route elements you may want to have routed elements be passed arguments instead. By using the 3rd argument of Router::connect() you can define which route elements should also be made available as passed arguments:
// SomeController.php
public function messages($phoneNumber = null) {
// some code here...
}
// routes.php
Router::connect(
'/messages/:id', // E.g. /messages/number
array('controller' => 'messages', 'action' => 'messages'),
array(
// order matters since this will simply map ":id"
'id' => '[0-9]+'
)
);
and you can also refer link above given by me, hope it will work for you.
let me know if i can help you more.
REST Routing
The example in the question looks similar to REST routing, a built in feature which would map:
GET /recipes/123 RecipesController::view(123)
To enable rest routing just use Router::mapResources('controllername');
Individual route
If you want only to write a route for the one case in the question
it's necessary to use a star route:
Router::connect('/messages/*',
array(
'controller' => 'messages',
'action' => 'messages'
)
);
Usage:
echo Router::url(array(
'controller' => 'messages',
'action' => 'messages',
823214
));
// /messages/823214
This has drawbacks because it's not possible with this kind of route to validate what comes after /messages/. To avoid that requires using route parameters.
Router::connect('/messages/:id',
array(
'controller' => 'messages',
'action' => 'messages'
),
array(
'id' => '\d+',
)
);
Usage:
echo Router::url(array(
'controller' => 'messages',
'action' => 'messages',
'id' => 823214 // <- different usage
));
// /messages/823214
in config/routes.php
$routes->connect('/NAME-YOU-WANT/:id',
['controller' => 'CONTROLLER-NAME','action'=>'ACTIOn-NAME'])->setPass(['id'])->setPatterns(['id' => '[0-9]+']
);
You can use Cake-PHP's Routing Features. Check out this page.

Routing with named parameters

I have a URL that contains named parameters, which I want to map to a more user friendly URL.
Take, for example, the following URL:
/videos/index/sort:published/direction:desc
I want to map this to a more friendly URL, like:
/videos/recent
I have tried setting it up in the Router, but it doesn't work.
Code samples from the Router:
Router::connect(
'/videos/recent/*',
array('controller' => 'videos', 'action' => 'index'),
array('sort' => 'published', 'direction' => 'desc'
));
Which doesn't work. And the following also doesn't work:
Router::connect(
'/videos/recent/*',
array('controller' => 'videos', 'action' => 'index', 'sort' => 'published', 'direction' => 'desc'));
Any ideas?
Use get args
The easiest way to have routes work is to avoid named arguments all together. With pagination thats easy to achieve using appropriate config:
class FoosController extends AppController {
public $components = array(
'Paginator' => array(
'paramType' => 'querystring'
)
);
}
In this way when you load /videos/recent you should find it includes urls of the form:
/videos/recent?page=2
/videos/recent?page=3
Instead of (due to route mismatching)
/videos/index/sort:published/direction:desc/page:2
/videos/index/sort:published/direction:desc/page:3
But if you really want to use named args
You'll need to update your route definition - there's no page in the route config:
Router::connect(
'/videos/recent/*',
array(
'controller' => 'videos',
'action' => 'index',
'sort' => 'published',
'direction' => 'desc'
)
);
As such, if there is a page named parameter (which there will be for all urls generated by the paginator helper), the route won't match. You should be able to fix that by adding page to the route definition:
Router::connect(
'/videos/recent/*',
array(
'controller' => 'videos',
'action' => 'index',
'sort' => 'published',
'direction' => 'desc',
'page' => 1
)
);
Though even if it works, you may find it to be fragile.
lets see on [Router::connect documentation](Routes are a way of connecting request urls to objects in your application)
Routes are a way of connecting request urls to objects in your application
So, it's map urls to objects and not url to url.
You have 2 options:
use Router::redirect
Something like that:
Router::redirect( '/videos/recent/*', '/videos/index/sort:published/direction:desc');
but seems that's not that you want exactly
use Router::connect
use normal Router::connect which will connect url to some action which make appropriate scope. Something like that:
Router::connect(
'/videos/recent/*',
array(
'controller' => 'videos',
'action' => 'recent'
)
);
and in VideosController
public function recent() {
$this->request->named['sort'] = 'published';
$this->request->named['direction'] = 'desc';
$this->index();
}
it works and I saw such usage, but not sure, that is will satisfy you too.
as for me, I like normal named cakephp parameters. If such scope (published and desc) is your default state, just code default state in index action. For over cases i think it's normal to use ordinary named parameters.

Resources