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

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
}
}

Related

How can i Get the paramter of URL contains slash

I have this URL :
dev.local.co/fr/admin/quoteManag/addquote/numberModel/123456/5
when I want to get the parameter numberModel.
On var_dump I get just "123456", not "123456/5"
You can use urlencode
$parameter = urlencode('123456/5'); // 123456%2F5
echo urldecode($_GET['numberModel']); // 123456/5
Or in your router, create a regex that will accept the slash as part of the parameter.
$route = new Zend_Controller_Router_Route_Regex("numberModel/([0-9\/]*)", array("module" => "MODULE", "controller" => "CONTROLLER", "action" => "ACTION"), array(1 => "numberModel"));
Not 100% but if this was me, I would get the url, explode it using /
Get the last 2 from the array, implode them.
But it depends on how you get the url, do you have a rewrite rule?
If so then I would look in that and you need to just sharpen it up slightly.

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

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);

Can PHP Array Keys have an Alias?

This is just a curious question, the reasoning behind it is purely to be slightly more lazy on my part. Here is what I mean..
Say I have a website, where htaccess makes nice urls, and sends that data to the $_GET['p'] array key as the current 'page'. In the index file, I setup the page, and the first thing I do is setup some page settings in a config file, $_PAGE array. Now, say I have multiple pages I want to have the same settings, (and down in the page, other things may slightly change that do not correspond to the settings. So currently, I have something that looks like the following 2 php files.
// index.php
include('page.array.php');
echo '<title>'.$_PAGE[$_GET['p']]['title'].'</title>';
// page.array.php
$_PAGE = array(
'some/page/' => array(
'title' => 'This is an example'
)
)
$_PAGE['some/aliased/page/'] = $_PAGE['some/page/'];
Notice that at the end ofthe page array, in order to 'alias' a page I must add this to the end after the array has been created.
Is there any method in php that maybe I am just unaware of, that could make me a tad bit lazier (and at the same time add to cleaner code), and make it so I can simply alias the key? I notice the following doesn't work, and I suppose my question is, is there any way to create the alias within the same array during the creation of the array?
This example deosn't work:
// page.array.php
$_PAGE = array(
'some/page/' => array(
'title' => 'This is an example'
),
'some/aliased/page/' => $_PAGE['some/page/']
)
Maybe a way to refer to "this" array, from within itself?
If this is not possible, I don't have an issue with the "Not Possible" answer. Though if you have a better method of solving this, other then the way I have described above, in the sake of being lazier, I would be interested in reading it :)
I don't believe you can have array values that mirror other values in the array like this. The first thing that comes to mind though would be for you to construct your $_PAGE array from within a switch statement, using fall-through values as aliases:
// Define path for testing, and empty page array
$path = "some/aliased/page";
$page = Array();
// Time to evaluate our path
switch ($path) {
// If it's either of these two cases
case "some/page":
case "some/aliased/page":
// Assign this array to $page
$page = Array("Title" => "Two Paths, One Page.");
break;
// If it's this case
case "some/other/path":
// Assign this array to $page
$page = Array("Title" => "Something else.");
break;
// If the path isn't found, default data
default:
$page = Array("Title" => "Page not found");
}
// Output the result
var_dump($page);
Execute it: http://sandbox.onlinephpfunctions...ebd3dee1f37c5612c25
It's possible:
$_PAGE = array('some/page/' => array('title' => 'This is an example'));
$_PAGE['some/aliased/page/'] = &$_PAGE['some/page/'];
$_PAGE['some/page/'] = 7;
var_dump($_PAGE);
Use the & to get a reference to a (non-object) variable instead of its value.

codeigniter array

How can I grab this data and increment it in Codeigniter?
$_SESSION['cart'][$_GET[id]]++;
because CI destroys the $_GET array, you can do this
$_SESSION['cart'][$this->uri->segment(3)]++;
where 3 is the URL segment of the ID. But I would look in to the shopping cart class as recommended by Malachi.
from the docs ~
$data = array(
'rowid' => 'b99ccdf16028f015540f341130b6d8ec',
'qty' => 3
);
$this->cart->update($data);
It's frowned upon, but if you really want to use the $_GET var you can always do the following:
parse_str($_SERVER['QUERY_STRING'],$_GET);
I would stick with using URI segments as shown by Ross or have the 'id' supplied as a parameter in the controller function.
maybe like this...
$cart = $this->session->userdata('cart');
$cart[$this->uri->segment(3)];
$this->input->get() is no longer messed with, so GET away.
You can do in this way.
By passing variable in your controller function, Your controller function will look like this
function my_function($id='')
{
//Your code goes here
$my_cart = $this->session->userdata('cart');
$my_data = $my_cart[$id];
}

How to match numbers in an array in PHP

I am working on the routing or uri's in my PHP app. Currently I have an array with a regex => url map like this...
<?php
$uri_routes = array(
//users/account like http://mysite.com/users/324 (any digit)
'users/friends/page-(\d+)' => 'modules/users/friends/page-$1',
'users/friends/' => 'modules/users/friends/',
'users/online' => 'modules/users/online/' ,
'users/online/page-(\d+)' => 'modules/users/online/page-$1',
'users/create' => 'modules/users/create',
'users/settings' => 'modules/users/settings',
'users/logout(\d+)' => 'modules/users/logout',
'users/login' => 'modules/users/login',
'users/home' => 'modules/users/home',
//forums
'forums/' => 'modules/forums/index',
'forums/viewthread/(\d+)' => 'modules/forums/viewthread/$1',
'forums/viewforum/(\d+)' => 'modules/forums/viewforum/$1',
'forums/viewthread/(\d+)/page-(\d+)' => 'modules/forums/viewthread/$1/page-$2',
'forums/viewforum/(\d+)/page-(\d+)' => 'modules/forums/viewforum/$1/page-$2'
//blog routes coming soon
//mail message routes coming soon
//various other routes coming soon
);
?>
I can then cycle through my $uri_routes map array and match a uri with preg_match() like this...
<?php
//get url from URL
$uri = isset($_GET['uri']) ? $_GET['uri'] : null;
//runs our function and returns an array
// $uri['module'] this will be the class/module/section
// $uri['method'] this will be the page in that section or method in that class
// $uri['urifragments'] this will either page a user ID, or an item ID or a page number for paging
$uri = get_route($_GET['uri'],$uri_routes);
function get_route($uri,$uri_routes)
{
foreach($uri_routes as $rUri => $rRoute)
{
if(preg_match("#^{$rUri}$#Ui",$uri))
{
$uri = preg_replace("#^{$rUri}$#Ui",$rRoute,$uri);
break;
}
}
$uri = explode('/',$uri);
$return['module'] = $uri['1'];
$return['method'] = $uri['2'];
$return['urifragments'] = $uri['3'];
$return['urifragments2'] = $uri['4'];
return $return;
}
I am open to an suggestion to improve this in any way. Right now I am stuck as there is 4 possible array key/values returned. If array key 3 or key 4 contains the word "page-" followed by a number, I would like to assign it to a $page variable. But if key 3 or key 4 contains just a number with no "page-" word, then I can assume it is a user ID, blog ID, forum ID, etc and assign it to an $id variable.
If you know a good approach to this, please help.
UPDATE
to simplify things, in addition to having "page-" in front of page numbers, I could have "id-" in front of id numbers
Instead of using $1 and $2 to match our routes try using named captures.
5.2.2 Named subpatterns now accept the syntax (?) and (?'name') as
well as (?P). Previous versions
accepted only (?P).
Source : preg_match
Also when you are doing a preg_replace you use \[0-99] where \0 is the whole string and \1 through \99 are the matches.
But if you are going to be using named captures you can assign an array to the $replacement parameter with the name capture (e.g. if you capture ?P<page> then you would pass an array('page'=>"new value of page")).
Hope that helps.

Categories