1
votes

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.

1

1 Answers

3
votes

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.