How to reconstruct a url using cakephp while removing selected query parameters? - php

I am using CakePHP 2.4
I have a url for e.g. /sent?note=123&test=abc
I want to remove the note parameter while giving me the rest of the url back. i.e.
/sent?test=abc
I have a piece of code that works but only for query parameters. I would like to find out how to improve my code so that it works with:
named parameters
passed parameters
hashtag
E.g.
/sent/name1:value1?note=123&test=abc#top
This is the code I have written so far. https://github.com/simkimsia/UtilityComponents/blob/master/Controller/Component/RequestExtrasHandlerComponent.php#L79
UPDATE PART III:
Let me illustrate with more examples to demonstrate what I mean by a more generic answer.
The more generic answer should assume no prior knowledge about the url patterns.
Assuming given this url
/sent/name1:value1?note=123&test=abc
I want to get rid of only the query parameter note and get back
/sent/name1:value1?test=abc
The more generic solution should work to give me back this url.
Another example. This time to get rid of named parameters.
Assuming given this url again
/sent/name1:value1?note=123&test=abc
I want to get rid of name1 and get back:
/sent?note=123&test=abc
Once again, the more generic solution should be able to accomplish this as well.
UPDATE PART II:
I am looking for a more generic answer. Assuming the web app does not know the url is called sent. You also do not know if the query parameters contain the word note or test. How do I still accomplish the above?
I want to be able to use the same code for any actions. That way, I can package it into a Component to be reused easily.
UPDATE PART I:
I understand that hashtag will not be passed to PHP. So please ignore that.
Clues on how to get the values from the hashtag:
https://stackoverflow.com/a/7817134/80353
https://stackoverflow.com/a/940996/80353

What about using mod_rewrite ?
You can handle your URLS in an other way :
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule ^/sent/name:(.*)?note=(.*)&test=([az-AZ])(#(.*))$ /sent/name:$1/note:$2/test:$3$4
</IfModule>
I'm not sure about the regex, but this may pass variables to cakePHP in a clean way (but I haven't tested it, though)
[EDIT]
But if you want to work without knowing urls patterns, then you can use the $this->request array : with an URL like
action/id:10/test:sample?sothertest=othersample&lorem=ipsum
I can get all the arguments using this in my controller :
// In your controller's method
$arguments= array_merge_recursive($this->request->named,$this->request->query);
Then, $arguments will be an array containing both named and passed params :
array(
'id' => '10',
'test' => 'sample',
'sothertest' => 'othersample',
'lorem' => 'ipsum'
)
Is it better ?
[EDIT 2]
If you know which parameter you have to get rid of, and directly redirect to the new URL, this should work:
action/id:10/test:sample?knownParam=value&lorem=ipsum
or with
action/id:10/knownParam:value?othertest=othersample&lorem=ipsum
In your controller/appController action:
// Name of the known param
$knownParam = 'knownParam';
// Arguments
$arguments = array_merge_recursive($this->request->named, $this->request->query);
if (key_exists($knownParam, $arguments)) {
// Unset in named params:
unset($arguments[$knownParam]);
// Creating url:
$url = array(
'admin' => $this->request->params['prefix'],
'plugin' => $this->request->params['plugin'],
'controller' => $this->request->params['controller'],
'action' => $this->request->params['action']
);
// Adding args
foreach ($arguments as $k => $v) {
$url[$k] = $v;
}
// Redirect
$this->redirect($url);
}
This will redirect both urls to
action/id:10/param1:value1/param2:value2
without the "know param"...

Let us say you have created the following routes:
Router::connect('/projects/:id/quotations/:quotation_id/*',
array(
'controller' => 'quotations',
'action' => 'get_all_by_project', "[method]" => "GET"),
array(
'pass' => array('id', 'quotation_id'),
'id' => '[0-9]+',
'quotation_id' => '[0-9]+'
),
array(
'named' => array(
'name1',
'name2',
'name3'
)
)
);
In this route:
Passed parameters will be the compulsory parameters id and quotation_id obeying the order as the first and second passed parameter
Named parameters will be the optional parameters name1, name2, and name3.
Query parameters will, of course, be optional as well and depend on what you actually have in the url.
you need the asterisk at the end so that the named parameters can pass through
Let us assume the following pretty url and the ugly url of the same action:
/projects/1/quotations/23/name2:value2/name3:value3/name1:value1?note=abc&test=123 (pretty)
/quotations/get_all_by_project/1/23/name2:value2/name3:value3/name1:value1?note=abc&test=123 (ugly)
First part of the answer:
Let us consider only the scenario of removing the query parameter note.
We should get back
/projects/1/quotations/23/name2:value2/name3:value3/name1:value1?test=123 (pretty)
/quotations/get_all_by_project/1/23/name2:value2/name3:value3/name1:value1?test=123 (ugly)
The following Component method will work. I have tested it on both the ugly and pretty urls.
public function removeQueryParameters($parameters, $here = '') {
if (empty($here)) {
$here = $this->controller->request->here;
}
$query = $this->controller->request->query;
$validQueryParameters = array();
foreach($query as $param=>$value) {
if (!in_array($param, $parameters)) {
$validQueryParameters[$param] = $value;
}
}
$queryString = $this->_reconstructQueryString($validQueryParameters);
return $here . $queryString;
}
protected function _reconstructQueryString($queryParameters = array()) {
$queryString = '';
foreach($queryParameters as $param => $value) {
$queryString .= $param . '=' . $value . '&';
}
if (strlen($queryString) > 0) {
$queryString = substr($queryString, 0, strlen($queryString) - 1);
$queryString = '?' . $queryString;
}
return $queryString;
}
This is how you call the Component method.
$newUrl = $this->RequestExtrasHandler->removeQueryParameters(array('note'));
RequestExtrasHandler is the name of Component I wrote that has the above method.
Second part of the answer:
Let us consider only the scenario of removing the named parameter name2.
We should get back
/projects/1/quotations/23/name3:value3/name1:value1?test=123 (pretty)
/quotations/get_all_by_project/1/23/name3:value3/name1:value1?test=123 (ugly)
The following Component method will work. I have tested it on both the ugly and pretty urls.
public function removeNamedParameters($parameters, $here = '') {
if (empty($here)) {
$here = $this->controller->request->here;
}
$query = $this->controller->request->query;
$named = $this->controller->request->params['named'];
$newHere = $here;
foreach($named as $param=>$value) {
if (in_array($param, $parameters)) {
$namedString = $param . ':' . $value;
$newHere = str_replace($namedString, "", $newHere);
}
}
$queryString = $this->_reconstructQueryString($query);
return $newHere . $queryString;
}
This is how you call the Component method.
$newUrl = $this->RequestExtrasHandler->removeNamedParameters(array('name2'));
RequestExtrasHandler is the name of Component I wrote that has the above method.
Third part of the answer:
After I realized that passed parameters are compulsory, I found that there is no real business need to remove passed parameters if at all.
Another problem is that unlike named parameters and query parameters, passed parameters tend not to have the keys present in the $this->controller->request->params['pass']
$this->controller->request->params['pass'] is usually in the form of a numerically indexed array.
Hence, there is huge challenge to take out the correct passed parameters.
Because of that, I will not create any method to remove passed parameters.
Check out the code here in details:
https://github.com/simkimsia/UtilityComponents/blob/d044da690c7b83c72a50ab97bfa1843c14355507/Controller/Component/RequestExtrasHandlerComponent.php#L89

maybe simple php functions can do what you want
$url = '/sent?note=123&test=abc'; //for example
$unwanted_string = substr($url, 0,strrpos($url,'&') + 1);
$unwanted_string = str_replace('/sent?', '', $unwanted_string);
$url = str_replace($unwanted_string, '', $url);

Related

CakePHP: How to use Find method + AJAX request with possibly empty search parameters

I'm working with CakePHP v2.3.x and on an edit page I need to dynamically update the page with search results...
I'm making an AJAX call from one of my Views/Tests/admin_edit.php view page to a specific action in my QuestionsController.php.
Here's the action (so far) that handles the request:
public function admin_search() {
if ($this->request->is('post')) {
$searchdata = $this->request->data;
$r = $this->Question->find('all', array('conditions' => array('Question.id' => $searchdata['id'])));
echo json_encode($r);
exit;
}
}
It currently only returns questions whose IDs match the one entered by the user, but the finished version will search several different fields. I know how to do this by adding additional key/value pairs to the conditions array. However, I don't know how to make those fields optional. What if the user enters the question name, but NOT the id, or visa versa? Is there a configuration so that CakePHP will ignore any empty field conditions?
Similarly, is there a way to set the operator so that, for example, I could match substrings or integer ranges? Update: I found this in the docs.
I would just remove any empty entries yourself first.
So let's say you have a $searchdata array with three optional fields, one of which is blank. First build your conditions array:
$searchdata = array("id" => 1, "name" => "", "type" => "foo");
$conditions = array('Question.id' => $searchdata['id'], 'Question.name' => $searchdata['name'], "Question.type" => $searchdata['type']);
(Or if you want to get fancy)
foreach($searchdata AS $key => $value) $conditions['Question.' . $key] = $value;
Now clean up $conditions, get rid of empty values:
$conditions = array_filter($conditions);
Tada:
$r = $this->Question->find('all', array('conditions' => $conditions));
See http://3v4l.org/JN6PA

CakePHP array() is empty, but query seems to be getting the correct results

I am trying to extract ONLY the PlanDetails where PlanDetail.company_id = Company.id AND PlanDetail.id' => $id.. ( you can see the conditions in my controller below)..
Controller:
function pd_list_by_company($id = null) {
$this->recursive = 2; // I am going to use containable to trim this.
return $this->PlanDetail->find('all',
array('conditions' =>
array('AND' =>
array('PlanDetail.company_id' => 'Company.id',
array('PlanDetail.id' => $id)))));
}
Test View:
$planDetailsByCompany = $this->requestAction('/planDetails/pd_list_by_company');
debug($planDetailsByCompany );
Output result of my debug??
Array()
If I remove the conditions and just have the find all, I get all PlanDetails as expected, so I know the data is being passed.. SQL debug dump even shows the query:
WHERE ((`PlanDetail`.`company_id` = 'Company.id') AND (`PlanDetail`.`id` IS NULL))
And yes, I did notice the $id is NULL, and I know the value needs to be there.. So maybe my question is why is the $id value not being passed to the controller even though I can see the PlanDetail.id value on a find('all') w/ out the conditions??
Thanks for any tips.
Since $id seems to be null, I would assume that you call the function without the parameter. And you don't get an error message, because as far as PHP is concerned the parameter is optional. In this case it's clearly required, so you should make it a required parameter in your function declaration:
function pd_list_by_company($id) {
Also you could simplify the return statement, you do not need the AND:
return $this->PlanDetail->find('all',
array('conditions' =>
array('PlanDetail.company_id' => 'Company.id','PlanDetail.id' => $id)
)
);
To answer the question why is the $id not being passed is because you're not passing it
To pass say $id of 2 you need to do the following in your requestAction
$this->requestAction('/planDetails/pd_list_by_company/2');
Seems to me that your code should just be
return $this->PlanDetail->find('array('PlanDetail.id' => $id));
Assuming you have the $this->PlanDetail->recursive flag set to > 0, your Model should already know about and return the associated data for any 'Company' table.....
I'm used to an old (1.3) version of CakePHP but the find() function is pretty basic and is designed to only return one row.
and yes, you definitely need to call the function with the id appended to the url, eg.
$planDetailsByCompany = $this->requestAction('/planDetails/pd_list_by_company/999');

CakePHP - building a search - how to include URL vars in query

I'm building a search page that will have many different search parameters that the user can click on, which will pass the variables in the URL.
$this->Restaurant->recursive = 1;
$this->paginate = array(
'conditions' => array(
'City.name' => $this->params['url']['city'],
'Cuisine.name' => $this->params['url']['cuisine'],
),
);
$data = $this->paginate('Restaurant');
$this->set('data', $data);
This works just fine IF there is ?city=newyork&cuisine=pizza in the URL - but if not, it errors out* If it were normal PHP, I'd build out the query as a string and append conditions only if the variables existed...etc. But with Cake I'm not sure where to begin or what would be the best way to manage this.
EDIT:
*If I do not have 'city' or 'cuisine' being passed as URL variables, I get this:
<b>Notice</b> (8)</a>: Undefined index: city [<b>APP/controllers
/restaurants_controller.php</b>, line <b>18</b>
...
City.name' => $this->params['url']['city'],
</span></code></span></pre></div><pre class="stack-trace">
RestaurantsController::search() - APP/controllers/restaurants_controller.php
, line 18
Dispatcher::_invoke() - CORE/cake/dispatcher.php, line 204
Dispatcher::dispatch() - CORE/cake/dispatcher.php, line 171
[main] - APP/webroot/index.php, line 83
It wouldn't be a problem if I just had "city" and "cuisine" - but if I plan on having 20 more search options to be passed in the same way, I'd like to be able to pass them or not at my discretion instead of being forced to have them ALL in the URL every time.
What do you mean errors out?
Usually adding $this->Paginator->options(array('url' => $this->passedArgs)); before your pagination call in the view will make cake add all the passed URL params to the url that it generates
EDIT
What you're looking for are named parameters : http://book.cakephp.org/view/947/Named-parameters
If you look at the last example, you'll see that the route maps to ContentsController->view(); with all the parameters you (optionally) chose to send available in the passedArgs array.
URL: /contents/view/chapter:models/section:associations
Mapping: ContentsController->view();
$this->passedArgs['chapter'] = 'models';
$this->passedArgs['section'] = 'associations';
$this->params['named']['chapter'] = 'models';
$this->params['named']['section'] = 'associations';
EDIT 2
Your problem here is that you have a variable set of search criteria. Your system should basically go through the search criteria and build your search query depending what filters have been chosen. There are several ways you can solve this. The easiest would be to loop through them.
$allowsFilters = array('city' => 'Model.city', 'name' => 'Model.name', 'price' => 'Model.price'); //list of allowed keys to search on.
$opts = array();
foreach ($this->params['named'] as $filter => $value) {
if (isset($allowsFilters[$filter])) {
$opts[$allowsFilters[$filter]] = $value;
}
}
$findOpts = array('conditions' => $opts)
$this->Model->find('all', $findOpts);
This way, you're whitelisting a set of filters that can be applied. And you loop through the params and build your conditions array. You'll have to modify this a bit if you need OR conditions or different MYSQL. But this will allow you to have a variable number of filters.
cake use MVC pattern, mean that url like http://localhost/controller/action/param1/param2
convert to index.php?controller=controller&action=action&param1=pararm1 ...
if you can't get the query string from the original url (http://localhost/controller.../?a=3&b=2)
you should add QSA param to your .htaccess, it should look something like that
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?url=$1 [**QSA**,L]
</IfModule>
and then you will get the query string to your action method.
if you don't want to use this way, you can get the params via the controller's action using
$arg_list = func_get_args();
in place of the query string
it will look something like that
Controller Pages{
public function search(){
$args = func_get_args();
foreach($args as $arg){
// build query string for the search, depending on the params you got
}
// do search
}
}

PHP Optional Parameters - specify parameter value by name?

I know it is possible to use optional arguments as follows:
function doSomething($do, $something = "something") {
}
doSomething("do");
doSomething("do", "nothing");
But suppose you have the following situation:
function doSomething($do, $something = "something", $or = "or", $nothing = "nothing") {
}
doSomething("do", $or=>"and", $nothing=>"something");
So in the above line it would default $something to "something", even though I am setting values for everything else. I know this is possible in .net - I use it all the time. But I need to do this in PHP if possible.
Can anyone tell me if this is possible? I am altering the Omnistar Affiliate program which I have integrated into Interspire Shopping Cart - so I want to keep a function working as normal for any places where I dont change the call to the function, but in one place (which I am extending) I want to specify additional parameters. I dont want to create another function unless I absolutely have to.
No, in PHP that is not possible as of writing. Use array arguments:
function doSomething($arguments = array()) {
// set defaults
$arguments = array_merge(array(
"argument" => "default value",
), $arguments);
var_dump($arguments);
}
Example usage:
doSomething(); // with all defaults, or:
doSomething(array("argument" => "other value"));
When changing an existing method:
//function doSomething($bar, $baz) {
function doSomething($bar, $baz, $arguments = array()) {
// $bar and $baz remain in place, old code works
}
Have a look at func_get_args: http://au2.php.net/manual/en/function.func-get-args.php
Named arguments are not currently available in PHP (5.3).
To get around this, you commonly see a function receiving an argument array() and then using extract() to use the supplied arguments in local variables or array_merge() to default them.
Your original example would look something like:
$args = array('do' => 'do', 'or' => 'not', 'nothing' => 'something');
doSomething($args);
PHP has no named parameters. You'll have to decide on one workaround.
Most commonly an array parameter is used. But another clever method is using URL parameters, if you only need literal values:
function with_options($any) {
parse_str($any); // or extract() for array params
}
with_options("param=123&and=and&or=or");
Combine this approach with default parameters as it suits your particular use case.

Is there an advantage to using parse_str for optional function parameters vs an array?

I happened to be making some changes to a WordPress blog and noticed that they use parse_str (http://php.net/parse_str) for parsing and setting their optional parameters to a function.
I'm wondering if there is an advantage to this over sending an array?
Examples:
With array:
$blahOptions = array(
'option_1' => true,
);
BlahArray($blahOptions);
function BlahArray($options = array()) {
$defaults = array(
'option_1' => false,
'option_2' => 'blah',
);
// this would probably be in a function to be used everywhere
foreach ($defaults as $defaultOption => $defaultValue) {
if (!isset($options[$defaultOption])) $options[$defaultOption] = $defaultValue;
}
}
With parse_str:
$blahOptions = 'option_1=1';
BlahString($blahOptions);
function BlahString($options = '') {
$defaults = array(
'option_1' => false,
'option_2' => 'blah',
);
parse_str($options, $defaults);
$options = $defaults;
}
No. That seems like a ridiculous way to pass functional parameter arguments. I could understand it if you needed to recreate $_GET or $_POST variables or something along those lines, but for parameters to a function? That's code smell right there.
They should be using an array, and then utilizing extract() instead. I've worked with Wordpress before, and my advice is to keep the code at arm's length. It is not an example of model programming.
No. There are more disadvantages than advantages.
When you’re using a single string, you just can pass string values. With an array you can use every PHP data type and every element’s value type is independently of each other.
With parse_str, you can potentially drop in the query string from the URL, and the function will work. If you use an array, and you want to use the query string, you'll have to enumerate everything into an array before calling the function.
I'm not totally convinced it's the best way to go, but I see how it can add a bit of flexibility.

Categories