Will magic routes for plugins ever exist again? Why just the index() method? Baffling. There are tens if not twenties of plugin junkies out there and we have no voice.

Apsontonk on 7/3/10

@Apsontonk what are you talking about? giving more Router control within your plugin ? or are you talking about /plugin/ pointing to /plugin/controller/index ?

I think Mark’s solution could be used to search for a routes file in your plugins and implement whatever routes you define there. You just need a little creativity. Though it being a default feature sure would be nice.

Angel S. Moreno on 7/3/10

Apsontonk: I doubt the old magic plugin routes will return. They were a source of pain and numerous problems. Of course you can still define routes in your application to provide compatible routes. As for not having a voice, CakePHP is an open source project. Your ‘voice’ in a project is entirely dependent on you and your willingness to get involved. Sorry that you don’t agree with the decisions made, but perhaps if you were more involved a different solution could have been reached.

mark story on 7/8/10

Hey. Thanks for this post.
Do you have a good idea, how I can use your SlugRoute class fot a two layer navigation?

/:mainslug
/:mainslug/:subslug

Bests
Mathias

Mathias on 7/9/10

Hi mark, i am trying to work with subdomains using a custum route class. I am having problems with the link building…

Could you take a look to the code?

#config/routes.php
App::import(‘Lib’, ‘SubdomainRoute’);
define(‘MYDOMAIN’, ‘domain.com.ar’);

$ops=array(‘routeClass’ => ‘SubdomainRoute’);
Router::connect(’/:subdomain/’, array(‘controller’=>‘pages’,‘action’ => ‘display’,‘home’),$ops);
Router::connect(’/:subdomain/:controller’, array(‘action’ => ‘index’),$ops);
Router::connect(’/:subdomain/:controller/:action/*’,array(),$ops);

  1. libs/subdomain_route.php
    class SubdomainRoute extends CakeRoute { function parse($url) { if(defined(‘MYDOMAIN’)){ $subdomain = substr( env(“HTTP_HOST”), 0, strpos(env(“HTTP_HOST”), “.”.MYDOMAIN) ); $url=’/’.$subdomain.$url; }

return parent::parse($url); }

function _writeUrl($params) { $params[‘subdomain’]=isset($params[‘subdomain’])? strtolower($params[‘subdomain’]):‘www’;

$out=parent::_writeUrl($params);

if(!defined(‘MYDOMAIN’)) return $out;

$out=substr($out,strpos($out, ‘/’)+1); $out=substr($out,strpos($out, ‘/’));

return ‘http://’.$params[‘subdomain’].’.’.MYDOMAIN.$out; }
}

when i access to some url like;
http://sub.domain.com/
or
http://sub.domain.com/pages/display/test

everything its okey

but when i build a link using;
echo $this->Html->link(‘test’,array(‘controller’=>‘pages’,‘action’=>‘display’,‘home’,‘subdomain’=>‘www’));

it outputs this url; /app_path/http:/www.domain.com

any ideas?

Eugenio on 8/11/10

@Erik

I passed the id by assigning $params[‘post_id’] in my route class, then adding the optional ‘pass’ paramater in Router::Connect().

Some code will hopefully make more sense – here are the important bits:

$post = ClassRegistry::init(‘Post’)->find(‘first’, array( ‘fields’ => array(‘id’), ‘conditions’ => array(‘Post.slug’ => $params[‘slug’]), ‘recursive’ => -1
));
if ($post) { $params[‘post_id’] = $post[‘Post’][‘id’]; return $params;
}

———

Router::connect(’/:slug/*’, array(‘controller’ => ‘posts’, ‘action’ => ‘view’), array(‘routeClass’ => ‘SlugRoute’, ‘pass’ => array(‘post_id’))
);

Not sure if this is the best way, but it works for me :)

Mark P on 8/12/10

Erik, MarkP

Why not set the ‘pass’ paramater inside parse() directly?

$fpost = $Post->find(‘first’, array( ‘conditions’ => array(‘Post.slug’ => $params[‘slug’]), ‘recursive’ => -1
));

if ($count) { $params[‘pass’][] = $count[‘Post’][‘id’]; return $params;
}

Gabriel on 8/18/10

I have a question about dynamic routing. I’m fairly new to CakePHP, so setting up routing is a little confusing to me.

Background:
I have two tables, ‘webpages’ and ‘webpage_categs’. Here are the pertinent fields of each table:

[webpages]-> (‘id’, ‘url’, ‘webpage_categ_id’, ‘page_html);
[webpage_categs] -> (‘id’, ‘url’, ‘name’)

As you may have guessed, these two tables are for an admin back-end which allows users to add/edit/delete their own static html pages. Each html page is linked to a category with the field ‘webpage_categ_id’.

My main controller is named ‘webpages_controller’, and thus by default you could view a web page by going to ‘/webpages/view/6’ (where 6 is the ‘id’ of the webpage).

Ok, onto my question… Instead of having all of my url’s appear like this: ‘/webpages/view/6’ I’d like them to appear like this: ‘/category/page’ where category=webpage_categs.url and page=webpages.url. So my question is, can I do this dynamically so I don’t have to define a route for each page?

If anyone could help me out or provide suggestions, I’d be very grateful!

Thanks in advance – Ryan

Ryan on 8/25/10

If you need multi-level slugs try this:

1. Change your route:
from
Router::connect(’/:slug’
to
Router::connect(’/*’

2. In your route class you now don’t use
$params[‘slug’]
but
$params[’args‘]
This stores the fully entered “query string”

3. The slugs in your database can now be entered with unlimited number of levels:
having a slug (with slashes) like
“first/second/third”
will allow the url
http://localhost/first/second/third

P.S: I also added a check to make sure that (the first part of) the slug is not the same as an existing controller by checking if its present in the controller list. You can get it by reading App::objects(‘controller’);

Mark Story thanks for this great article!

whootland on 10/5/10

ATTENTION: In step 2 of my previous post use underscores before and after ‘args’, this got stripped of by the blogging software.

$params[’ _ args _ ‘]

Just check out the $params array, you will find them.

whootland on 10/5/10

Thanks for the article! There’s definitely some non-documented functionality that opens up a lot of doors with these new classes. Most importantly, returning a modified set of $params allowing you to change the route depending on the URL.

For example, I’m using a single router connection routing /:slug/* using a custom route class that is set up to allow me the following possibilities:

/manufacturer [renders ManufacturersController::view]

/manufacturer/category1/category2/infinite/number/of/categories [renders ProductCategoriesController::view]

/manufacturer/category1/category2/infinite/number/of/categories/product_name [renders ProductsController::view]

/manufacturer/category1/category2/infinite/number/of/categories/product_name/specs [renders ProductsController::view and passes ‘specs’ as the 2nd argument to my view method]

Real world examples would be
/apple [renders manufacturers/view]
/apple/imac [renders product_categories/view]
/apple/imac/intel [renders product_categories/view]
/apple/imac/intel/3rd-gen [renders product_categories/view]
/apple/imac/intel/3rd-gen/27-inch [renders products/view]
/apple/imac/intel/3rd-gen/27-inch/specs [renders products/view passing ‘specs’ as the 2nd argument]

Thanks, Mark!

Jeff Jassky on 10/26/10

hi mark
i need to change and overwrite the __connectDefaultRoutes() method in Router Class,
can you help me

saleh on 10/27/10

Jeff: Actually it is documented . With the examples you gave, you could easily handle those rules in a custom route class, you just need to implement the logic to handle nesting. $params is just an array, so you can return whatever you like from a routes parse() method.

mark story on 10/29/10

This is really cool, thanks for the Update!

Was very useful information, thanks alot…

Neil on 12/9/10

Saleh: as far as I know there is no other way to do it than duplicating CakePHP code and adapt it to your situation. You can see how it is implemented in the I18nRoute : https://github.com/CakeDC/i18n/blob/master/libs/i18n_route.php#L116

Pierre Martin on 12/9/10

thanks a lot for the article!

I wonder how you will keep that cache file always up to date. I mean when you create new posts the new slugs won’t be on that cached index. I’m trying to work this out by recreating a new cached index every time a new register is created on an afterSave call back.

huoxito on 12/13/10

huoxito: take a look at Jemery Harris sluggabler route, there is an invalidateCache method that does exactly this. http://codaset.com/jeremyharris/slugger/source/master/blob/libs/routes/sluggable_route.php

Pierre Martin on 12/13/10

Thanks for your post, it was very helpfull!
But I’m having a problem, this is what I did: – created the SlugRoute – created the routes on routes.php Router::connect(’/:slug’, array(‘controller’ => ‘users’, ‘action’ => ‘login’), array(‘routeClass’ => ‘SlugRoute’)); Router::connect(’/:slug/:controller/:action/*’, array(), array(‘routeClass’ => ‘SlugRoute’));

- created a view with a form created by $form->create

PROBLEM: when I submit the form, the url magically created came without the slug, how can I manage this?

Adriano Bacha on 2/23/11

Thank you very much for this tutorial, I’m trying to get this working but it’s currently not. I’ve created a fresh project with a table named “Posts”. In the table I have the following fields: id (int, auto-inc, primary), slug (varchar(20)), title (varchar(100)), body(text), created(datetime), modified(datetime).

I baked out an MVC using this table and it displays fine. Then I created a dummy post with the slug = “about”. Then I created a folder called “routes” in my lib folder, copied and pasted the SlugRoute class on this page, added the couple lines to the routes.php file. Everything should be good (and mod_rewrite is enabled in Apache).

When I enter “localhost/about” into my browser, I get redirected to “localhost/posts” and the error appears: “Invalid post.”

What am I doing wrong to implement this code, or is it not doing what I think it’s doing?

Thanks so much for your help!!!

Sean on 3/14/11

Sean: If there is a redirect, like 301 redirect being issued then the error might be in your controller. Perhaps its expecting an id and getting a slug or the inverse.

mark story on 4/20/11