CakePHP 2.2.4: Working with Multiple Slugs In Route - cakephp

I didn't see a question involving multiple slugs in a route.
I have a tables called "teachers", "courses", and "subjects". Each have a field called "shortcut".
How do I go about routing and pulling the correct Teacher.id, Course.id and Subject.id from a url like the following:
http://www.domain.com/bowlerae/grade-5/math
where "bowlerae" is the Teacher.shortcut, "grade-5" is the Course.shortcut and "math" is the Subject.shortcut.
I'm not sure how to route this initially, how to pull the ID of each 3 elements (would I do this in AppController or each individual controller I need it?) and how to build a link compatible with reverse routing AND pagination. Also, do the fields "shortcut" need to have unique names for each table such as "teacher_slug", "course_slug", "subject_slug"?
Right now, I'm loading each of the 3 models in my routes.php and querying to get the corresponding ID for each (if that portion of the URL exists) and then passing those values to the controller. But as you can imagine, it spends a lot of resources.
Additional question/concern (for AD7six and others)
If I want a URL like this
http://www.domain.com/bowlerae
to point to a teacher's homepage, or similar a URL like this
http://www.domain.com/bowlerae/grade-5
to point to a course's homepage, I would have a route like the following
Router::connect(
'/:teacher',
array('controller' => 'teachers', 'action' => 'view'),
array(
'teacher' => '[a-z0-9]{1}([a-z0-9\-]{2,}[a-z0-9]{1})?',
'pass' => array('teacher')
));
for teachers, and the following for courses
Router::connect(
'/:teacher/:course',
array('controller' => 'courses', 'action' => 'view'),
array(
'teacher' => '[a-z0-9]{1}([a-z0-9\-]{2,}[a-z0-9]{1})?',
'course' => '[a-z0-9]{1}([a-z0-9\-]{2,}[a-z0-9]{1})?',
'pass' => array('teacher', 'course')
));
My concern is that what if someone navigates to URLs like
http://www.domain.com/account where account is a valid controller but not a valid teacher
or
http://www.domain.com/bowlerae/students where students is a valid controller but not a valid course
How would I handle these instances? I tried using the redirect method like so...
TeachersController.php
public function view($tSlug = null) {
$this->Teacher->id = $this->Teacher->findByShortcut($tSlug);
if (!$this->Teacher->exists()) {
$this->Session->setFlash('This teacher does not exist');
$this->redirect(array('controller' => $tSlug));
}
else{
// do this
} // end else if teacher exists
}
I expected it to route the following URL
http://www.domain.com/account to the accounts controller but it routed it to
http://www.domain.com/account/index, correct accounts controller and correct index action but it appended the "/index" to the end of the URL.
With the way my routes are now, it is trying to access the courses controller, so I added the same redirect method to the courses controller like so...
CoursesController.php
public function view($tSlug = null, $cSlug = null) {
$this->Teacher->id = $this->Teacher->findByShortcut($tSlug);
$this->Course->id = $this->Course->findByShortcut($cSlug);
if (!$this->Teacher->exists()) {
$this->Session->setFlash('This teacher does not exist');
$this->redirect(array('controller' => $tSlug, 'action' => $cSlug));
}
elseif (!$this->Course->exists()) {
$this->Session->setFlash('This course does not exist');
$this->redirect(array('controller' => $tSlug, 'action' => $cSlug));
}
else{
// do this
} // end else if everything is fine
}
Unfortunately I get an error saying the redirect will never end.
Also, I HAVE created routes for URLs like that (if no teacher, course or subject are present) but perhaps they are incorrect.

It's a very bad idea to test all requests against a db to see if they match a route.
Put the logic in your controller
One simple way is to create a route ala:
Router::connect('/*', array('controller' => 'x', 'action' => 'y'));
Or slightly more specific:
Router::connect(
'/:teacher/:course/:subject',
array('controller' => 'x', 'action' => 'y'),
array('pass' => array('teacher', 'course', 'subject')
);
And make sure to declare all other routes you use first. Then simply create a controller action like so:
XController extends AppController {
public $uses = array('Teacher');
public function y($tSlug, $cSlug, $sSlug) {
$teacher = $this->Teacher->findBySlug($tSlug);
...
}
}
Restrict route elements
Using route validation you can restrict what matches a given route element, so for example if there are only 5 teachers, you can prevent any other url from matching by making sure it starts with their slug:
Router::connect(
'/:teacher/:course/:subject',
array('controller' => 'x', 'action' => 'y'),
array(
'teacher' => 'rita|sue|bob|larry|mo',
'pass' => array('teacher', 'course', 'subject')
);
If you'd like to read the slugs for teachers out of the db - cache the db query, don't read from the db in your routes file on all requests.
Put something fixed in your route
Using a route with no fixed string in it means any url at all with 3 path segments will match the route - it may be less error prone to put something fixed in the route so that you can be sure that the request is actually for a teacher, course, subject combination
i.e.
Router::connect('/anything/*', array('controller' => 'x', 'action' => 'y'));
This prevents the need for any "does this url match this route?" logic - as it does, or it doesn't.

Related

CakePHP 2.x - Custom Routes

I have two controllers: ArtistsController and RecordsController
I want to order routes logically depending on what the user is doing.
In this case once the user is editing an Artist (/artists/edit/some-artist) he's able to add some records for that artist.
So, I'd like the route to be something like:
"/artists/edit/some/artist/records/add"
And the same thing with the editing function of a record:
"/artists/edit/some-artist/records/edit/some-record"
I've been fighting with it for a while but I've never worked with Routes before on CakePHP and can't find a solution for this. Is this possible? Thanks
In Config/routes.php
Router::connect('/artists/edit/:some_artist', array('controller' => 'artists', 'action' => 'edit'), array('pass' => array('some_artist')));
Router::connect('/artists/edit/:some_artist/:records', array('controller' => 'artists', 'action' => 'edit'), array('pass' => array('some_artist','records')));
and you go on depending how what parameters you want to pass.
rule is simple: is some variable needs to be passed you put colon ":" before it, and add its name in array 'pass'.
I suggest read Routing: Route elements if you want specify type of passing element.
Additionally Artists Controller function should like this
public function edit($some_artist=null,$records = null) {
/**
[...]
*/
}

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.

Cakephp route and prefix

I have a problem with Routes in CakePHP. Let me explain.
I'm using authentication through the Auth component. I have a routing prefix called account.
When I want to edit a user, I'm calling the users controller which gives me a URL like:
/account/users/edit/5
What I want is to have a URL like:
/account/edit/5
So I changed my router like this:
Router::connect('/:prefix/edit/:id',
array('controller' => 'users', 'action' => 'edit'),
array('pass' => array('id'), 'id' => '[0-9]+')
);
which worked when I try to access /account/edit/5
My problem is located in my view. How can I access this route using the Html->link helper?
So far, I'm just doing it like this:
'/'.$this->Session->read('Auth.User.role').'/edit/'.$this->Session->read('Auth.User.id')
But it's not really clean in my opinion. I want to use the helper.
Thanks a lot for your help
Using a prefix "account" would mean needing an action like "account_edit" in your controller. That's probably not what you want. Also why put the "id" in url when it's already there in the session? Why not just use url "/account" for all users and get the id (and role if required) from session in the action?
Router::connect('/account',
array('controller' => 'users', 'action' => 'edit')
);
This would be the clean way to generate required url:
$this->Html->link('Account', array(
'controller' => 'users',
'action' => 'edit'
));
// Will return url "/account"
In general always use array form to specify url to benefit from reverse routing.
everything is just fine except router. it should be
Router::connect('/account/*',
array('controller' => 'users', 'action' => 'edit')
);
and creating anchor link in various way using Helper you can CHECK HERE

CakePHP 2.0 Router::connect issue without visible id in url

I want following SEO url like:
www.example.com/users/profile/webfacer
I do not want to use the unique user to fetch from database.
I try to use the Router method connect in my AppController. but I realised that it isn't possible (or not knowing it right now to use it in their also used in routes.php does not helped) like this:
//in AppController
Router::connect('/users/profile/:name',
array(
'controller' => 'users',
'action' => 'profile'
) ,
array(
'pass' => array('id', 'name'),
'id' => '[0-9]+'
)
);
How can I reproduce this link (below the example) with this html link helper to send the id but not show it in the url:
$this->Html->link('webfacer',array(
'controller'=>'users',
'action'=>'profile',
'id'=>1,
'name'=>'webfacer'
));
This would output www.example.com/users/profile/username:webfacer that mean my router doesn't appear to my route options.
Has anybody had the same issues and solved this?
Because you haven't put the :id argument in your route string, Cake won't know what to do when you pass it in the helper, that is why it's just appending it as a normal param in the URL. There is no way to pass a "hidden" id with the URL, you're best bet is to either expose it or at the other end of the app write something that fetches the ID based on the username you pass (make sure this column is indexed and url-safe).
I would just simplify your route to this:
//in AppController
Router::connect('/users/profile/:name',
array(
'controller' => 'users',
'action' => 'profile'
) ,
array('pass' => array('name'),
)
);
And don't bother passing ID to the helper. In your profile action you'd just have something like this:
public function profile($name) {
$user = $this->User->find('first', array('conditions' => array('name' => $name)));
}

CakePHP Routing - [home]/slug as URL

I'm trying to get to grips with Cake's routing, but I'm having no luck finding a solution to my particular problem.
I want to map www.example.com/slug to www.example.com/venues/view/slug, where slug is the URL friendly name of a record for a particular venue.
I also want to then map www.example.com/slug/events to www.example.com/events/index/venue:slug.
After reading the CakePHP documentation on Routing, a few times over, I'm none the wiser. I understand how I would create these routes for each individual venue, but I'm not sure how to get Cake to generate the routes on the fly.
You want to be careful mapping something to the first path after the domain name. This means that you would be breaking the controller/action/param/param paradigm. You can do this, but it may mean that you need to define every url for your site in your routes file rather than using Cake's routing magic.
An alternative may be to use /v/ for venues and /e/ for events to keep your url short but break up the url for the normal paradigm.
If you still want to do what you requested here, you could do something like the following. It limits the slug to letters, numbers, dashes, and underscores.
Router::connect(
'/:slug',
array(
'controller' => 'venues',
'action' => 'view'
),
array(
'slug' => '[a-zA-Z0-9_-]+'
)
);
Router::connect(
'/:slug/:events',
array(
'controller' => 'events',
'action' => 'index'
),
array(
'slug' => '[a-zA-Z0-9_-]+'
)
);
In your controller, you would then access the slug with the following (using the first route as an example).
function view(){
if(isset($this->params['slug'])){
//Do something with your code here.
}
}
First off, you're not connecting www.example.com/slug to www.example.com/venues/view/slug, you're connecting /slug to a controller action. Like this:
Router::connect('/:slug',
array('controller' => 'venues', 'action' => 'view'),
array('pass' => array('slug'));
To generate the appropriate link, you'd do the same in reverse:
$this->Html->link('Foo',
array('controller' => 'venues', 'action' => 'view', 'slug' => 'bar'))
That should result in the link /bar.
The problem with routing a /:slug URL is that it's a catch-all route. You need to carefully define all other routes you may want to use before this one.

Resources