I am using cakePHP and got stuck in a situation. I want to pass a named parameter to my action that contains forward slashes. For example
http://www.test.com/claim/index/user:rLbu78h2RpVwLTy/bki3pK1NkkXhCCaYfQ/zXDIfZR4=
Basically "rLbu78h2RpVwLTy/bki3pK1NkkXhCCaYfQ/zXDIfZR4=" is one parameter but cake is only recognizing the first part of it until the fist slash.
When printing the named params array I get:
Array
(
[user] => rLbu78h2RpVwLTy
)
How can I escape the forward slash and allow cake to accept it as part of the named parameter?
Thank you
Don't use named-parameters
While you can use named parameters (/foo/this-is:a-named-parameter/), it's best to stick to normal url positional/path arguments and/or GET arguments. Over time it has proven not to be a great solution to pass around information using named parameters.
Use a trailing star route
If you modify the format of the url you're using to be e.g.:
/claim/index/<user token>
Then you can use a trailing star route to captuer everythiing that occurs after /index/ in a single variable:
Router::connect(
'/claim/index/**',
array('controller' => 'claims', 'action' => 'index')
);
In this way it doesn't matter what comes after /index/ you'll receive it in your index action:
// Request to /index/rLbu78h2RpVwLTy/bki3pK1NkkXhCCaYfQ/zXDIfZR4=
function index($user) {
// $user === 'rLbu78h2RpVwLTy/bki3pK1NkkXhCCaYfQ/zXDIfZR4='
}
Related
I am writing some simple redirects in CakePHP 2.4.2 and came across one that stumped me a bit.
How can I redirect to another view with the same passedArgs? It seems like it should be simple, but I must be overlooking something. I tried this:
$this->redirect(array_merge(array('action' => 'index',$this->params['named'])));
The debug output seems correct:
array(
'action' => 'index',
(int) 0 => array(
'this' => 'url',
'and' => 'stuff'
)
)
My desired outcome is that
view/this:url/and:stuff
redirects to
index/this:url/and:stuff
but now it just sends me to
index/
Not sure what I am missing here, perhaps I have deeper configuration issues - although it's not a terribly complicated app.
For passed params (numeric keys) use array_merge.
But since you use named params, which have a string based key, you can leverage PHP basics:
$this->redirect(array('action' => 'index') + $this->request->params['named']));
This adds all array values from the named params except for action, which is already in use here (and hence should not be overwritten anyway). So the priority is clear, as well.
Cake expects a flat array of parameters, so you need to use array_merge to add in extra arrays to it on the sides. Try this:
$this->redirect(array_merge(array('action' => 'index'), $this->params['named']));
... or using your original variable $passedArgs:
$this->redirect(array_merge(array('action' => 'index'), $this->passedArgs));
Maybe better solution will be used persist attribute in Router::connect()?
i have a controller called "Movies" so the link is "localhost/Movies" and have some actions in this controller like "localhost/Movies/view/[id]" , "localhost/Movies/view/category/[id]" if i want to make a path by the category name and sub category name like "localhost/Movies/English/new" .
how can i do something like this in cakephp 2. my project now like "localhost/English/new" but i want to put Movies in this path, to make it more fixable, if i want to make a new category just add a column in my database .
thanks
If i understand, maybe by creating a custom route for your action:
In app/config/routes.php:
Router::connect('/movies/:category/:subcategory', array('controller'=>'movies','action' => 'index'));
And you can retrieve the value in your controller with:
echo $this->params['category'];
echo $this->params['subcategory'];
You can also read about it in the cookbook http://book.cakephp.org/1.3/view/945/Routes-Configuration
The default routing in CakePHP is as follows:
mydomain/controller/action/param1/param2/param3/param4/...
If you want to add filter options to an action, just add optional parameters to the action.
function index($category = null, $subcategory = null) {
if(isset($subcategory)){
//will execute if you pass both arguments
}else if(isset($category)){
//will execute if you pass one argument
}else{
//will execute if you pass no arguments
}
}
EDIT
In this case, $category is param1, and $subcategory is param2. The overloaded function can receive either 1, 2, or no arguments. If this is in, for example, the ObjectsController, all these are valid URLs:
localhost/objects/index/ //$category==null, $subcategory==null
localhost/objects/index/foods/ //$category=='foods', $subcategory==null
localhost/objects/index/foods/green/ //$category=='foods', $subcategory=='green'
This allows you to control many options in the same action.
First part of this question will look trivial, but the point is in second part.
So, let's say that I have next few links on my app:
http://myapp.com/cars-audi
http://myapp.com/cars-opel
http://myapp.com/cars-fiat
http://myapp.com/cars-vw
In these cases, model car is used. So, in this case, I want to escape using slash in URL.
Then I will have more pages, and URLs, where roads will be involved, like:
http://myapp.com/roads/germany
http://myapp.com/roads/austria
http://myapp.com/roads/hungary
http://myapp.com/roads/poland
So, if it starts with cars-, in that case model cars should be used, and if it starts with roads/, model roads will be in the game.
Is it possible to do with some regular expressions in routes.php, or is it better to load (use) one same model in both cases, and to work with them like that?
Also, is it possible to help parsing URL using .htaccess file?
This is a simple case of routing a URL to a controller action, it doesn't involve models at all.
Router::connect('/:carlink',
array('controller' => 'cars', 'action' => 'view'),
array('carlink' => 'cars-\w+', 'pass' => array('carlink')));
This route says any URL that matches /:carlink should be routed to the given controller and action. In the last part you're clarifying what :carlink can be with the regular expression cars-\w+ ("cars-" followed by any word). You also pass that value to your called action.
class CarsController extends AppController {
public function view($car) {
if (!preg_match('/cars-(\w+)/', $car, $matches)) {
// action was accessed with invalid URL, bail out
$this->cakeError('error404');
}
// use $matches[1], which will be 'audi', for example
…
}
}
Your road URLs would be routed to the RoadsController as usual like this:
Router::connect('/roads/*', array('controller' => 'roads', 'action' => 'view'));
I need several routes in my application to allow for a dynamic string to proceed the prefix.
Here's my route:
Router::connect('/:location/traveler/:controller/*', array('action' => 'index', 'traveler' => true, 'prefix' => 'traveler'), array('pass' => array('location')));
For instance, if I went to /south/traveler/requests it would route successfully to RequestsController::traveler_index($location = 'south').
This is what I want, but I also need HtmlHelper::link() to properly reverse route a URL array into that route.
Here's my call to HtmlHelper::link():
$this->Html->link('List Requests', array('controller' => 'requests', 'action' => 'index', 'location' => 'south'));
The prefix routing is (or should be) implied since this is being called from a view within the traveler prefix.
The URL that call spits out is:
http://domain.com/traveler/requests/location:south
Have I not done something correctly? Is there any way I can avoid creating a custom route class to properly reverse route these URL arrays?
You need to inform the router that location should be a named parameter using Router::connectNamed. See the Named Parameters section of the CakePHP v1.3 Book:
URL: /contents/view/chapter:models/section:associations
When making custom routes, a common pitfall is that using named parameters will break your custom routes. In order to solve this you should inform the Router about which parameters are intended to be named parameters. Without this knowledge the Router is unable to determine whether named parameters are intended to actually be named parameters or routed parameters, and defaults to assuming you intended them to be routed parameters. To connect named parameters in the router use Router::connectNamed().
Router::connectNamed(array('chapter', 'section'));
Will ensure that your chapter and section parameters reverse route correctly.
I solved the problem.
Removing Router::connectNamed() from routes.php, I repaired my route which was misconfigured.
The reverse route to traveler_index() worked properly using the route I listed above, but any call to any other function, like traveler_edit() would fail.
Using the route below, I was able to get it to reverse route for any action on any controller in the traveler prefix with location as a variable.
Router::connect('/:location/traveler/:controller/:action/*', array('traveler' => true, 'prefix' => 'traveler'), array('pass' => array('location')));
Now, my call to HtmlHelper::link() correctly reverse-routes my URL array:
$this->Html->link('Edit Details', array('controller' => 'requests', 'action' => 'edit', 'location' => 'south', 1234));
...reverse routes to /south/traveler/requests/edit/1234.
Okay, this will require some setup:
I'm working on a method of using nice post title "slugs" in the URL's of my cakePHP powered blog.
For example: /blog/post-title-here instead of /blog/view_post/123.
Since I'm obviously not going to write a new method for every post, I'm trying to be slick and use CakePHP callbacks to emulate the behavior of PHP 5's __call() magic method. For those who do not know, CakePHP's dispatcher checks to see if a method exists and throws a cakePHP error before __call() can be invoked in the controller.
What I've done so far:
In the interest of full disclosure ('cause I have no Idea why I'm having a problem) I've got two routes:
Router::connect('/blog/:action/*', array('controller' => 'blog_posts'));
Router::connect('/blog/*', array('controller' => 'blog_posts'));
These set up an alias for the BlogPostsController so that my url doesn't look like /blog_posts/action
Then in the BlogPostsController:
public function beforeFilter() {
parent::beforeFilter();
if (!in_array($this->params['action'], $this->methods)) {
$this->setAction('single_post', $this->params['action']);
}
}
public function single_post($slug = NULL) {
$post = $this->BlogPost->get_post_by_slug($slug);
$this->set('post', $post);
//$this->render('single_post');
}
The beforeFilter catches actions that do not exist and passes them to my single_post method. single_post grabs the data from the model, and sets a variable $post for the view.
There's also an index method that displays the 10 most recent posts.
Here's the confounding part:
You'll notice that there is a $this->render method that is commented-out above.
When I do not call $this->render('single_post'), the view renders once, but the $post variable is not set.
When I do call $this->render('single_post'), The view renders with the $post variable set, and then renders again with it not set. So in effect I get two full layouts, one after the other, in the same document. One with the content, and one without.
I've tried using a method named single_post and a method named __single_post and both have the same problem. I would prefer the end result to be a method named __single_post so that it cannot be accessed directly with the url /blog/single_post.
Also
I've not yet coded error handling for when the post does not exist (so that when people type random things in the url they don't get the single_post view). I plan on doing that after I figure out this problem.
This doesn't explicitly answer your question, but I'd just forego the whole complexity by solving the problem using only routes:
// Whitelist other public actions in BlogPostsController first,
// so they're not caught by the catch-all slug rule.
// This whitelists BlogPostsController::other() and ::actions(), so
// the URLs /blog/other/foo and /blog/actions/bar still work.
Router::connect('/blog/:action/*',
array('controller' => 'blog_posts'),
array('action' => 'other|actions'));
// Connect all URLs not matching the above, like /blog/my-frist-post,
// to BlogPostsController::single_post($slug). Optionally use RegEx to
// filter slug format.
Router::connect('/blog/:slug',
array('controller' => 'blog_posts', 'action' => 'single_post'),
array('pass' => array('slug') /*, 'slug' => 'regex for slug' */));
Note that the above routes depend on a bug fix only recently, as of the time of this writing, incorporated into Cake (see http://cakephp.lighthouseapp.com/projects/42648/tickets/1197-routing-error-when-using-regex-on-action). See the edit history of this post for a more compatible solution.
As for the single_post method being accessible directly: I won't. Since the /blog/:slug route catches all URLs that start with /blog/, it'll catch /blog/single_post and invoke BlogPostsController::single_post('single_post'). You will then try to find a post with the slug "single_post", which probably won't exist. In that case, you can throw a 404 error:
function single_post($slug) {
$post = $this->BlogPost->get_post_by_slug($slug);
if (!$post) {
$this->cakeError('error404');
}
// business as usual here
}
Error handling: done.