How To: Fixing CakePHP Broken GET Query Strings

After getting familiar with CakePHP 2.x for a little while, writing an application, I had a need to perform an AJAX action  using the query string of an HTTP GET request. I’ve done it countless times using straight PHP, ASP.Net, even custom constructed requests from C# desktop applications.  How difficult could it be? After all, the idea behind these PHP frameworks is to take all the heavy lifting out of writing your code, right? I set out writing the AJAX links to construct my query string using CakePHP’s JsHelper.

I started out by writing a simple query string with a single key/value and then retrieving it with some AJAX. It worked perfectly! Then I added a few more key/value pairs to the string and that’s when things went down hill. Apparently, I stumbled on to bug in the way CakePHP handles and encodes URL Query strings. Funny thing is, this bug was discovered and fixed in a previous version of the framework, but some how found it’s way into the code base again in version 2.x. Research revealed a number of work-arounds and hacks, most included editing a core file or two. I, however, did not want to have to resort to messing with core files of the framework, because they would likely be overwritten again after a version update or upgrade, leaving me back where I started. Instead I decided to “repair” the parts of the query string that CakePHP broke.

So, How did CakePHP Break the Query String Anyway?

A typical URL Query String should look something like this after it’s sent to the browser.

http://www.domain.com/?Key1=firstvalue&Key2=secondvalue&Key3=thirdvalue

The query string itself is all the text that appears AFTER the ‘?‘. In this case the query string is

Key1=firstvalue&Key2=secondvalue&Key3=thirdvalue

The string consists of three parameters and their values, each seperated by the ‘&‘ character. Where URL Query Strings are concerned, the & character is used as a delimiter to show where one key/value pair ends, and the next begins.

  1. Key1 = firstvalue
  2. Key2 = secondvalue
  3. Key3 = thirdvalue
Now we should be able to pull each of the key / value pairs with some simple code. The problem with CakePHP, however is that it encodes “special” characters in the URL string before sending it off.  So the & character is encoded and sent as &. After this happens, our Query String now looks like
Key1=firstvalue&Key=secondvalue&Key3=thirdvalue
Notice how the second and third key/value pairs have now changed.  Remember, the & character is used as the delimiter between key/value pairs, so that everything after the & character is read as part of the key/value pair up to the next & character. The result is that the key names now appear to be amp;Key2and amp;Key3
  1. Key1= firstvalue
  2. amp;Key2 = secondvalue
  3. amp;Key3= thirdvalue
Now, when we try to pull the Key2 or Key3 values in our application, they don’t exist.

Wonderful, So How Can We Fix This?

To be honest, this is more of a work-around than a “fix”, but one that I think is feasible until CakePHP developers fix the underlying problem. What we’re doing here undoing the damage the CakePHP did by taking the ‘broken’ query string and rebuilding it again with the correct delimiters and key names.

if($this->request->is(‘ajax’)){
$temparray = $this->params[‘url’];

foreach($this->params[‘url’] as $key => $value){

if(strpos($key, ‘amp;’) !== false){
$newkey = str_replace(‘amp;’,”,$key);
unset($temparray[$key]);
$temparray[$newkey] = $value;
}

}
$this->layout = ‘ajax’;
if($this->Model->save($temparray)){
echo ‘success’;
}
else{
echo “<br><strong>OOPS! something went wrong, refresh the page to reset your list</strong>”;
}
$this->autoRender = false;
exit();

else{
$this->Session->setFlash(“Not an AJAX request!”);
$this->redirect(‘/Model/View‘);
}

Break It Down

To see what’s happening here, lets step through code.

  • if($this->request->is(‘ajax’)){
    The first line checks to see if the page contains an AJAX request. If it does, then the code continues into the IF block and continues.
  • $temparray = $this->params[‘url’];
    If the page does contain our AJAX request, this line creates a new variable called temparray and sets it’s value to an associative array created by using CakePHP ‘params’ attribute with the URL parameter.  Basically, we’re just creating a copy of the associative array of Query String parameters. We create a copy of the original string because CakePHP will not allow us to alter the params array directly.
  • foreach($this->params[‘url’] as $key => $value){
    Next we start a foreach loop to iterate through all the URL parameters to access each parameter of the associative array setting the paramater name as $key and it’s value as $value.
  • if(strpos($key, ‘amp;’) !== false){
    Next, as we loop through each item in our array, we’re checking the string of each parameter name ($key) to see if it contains the encoded & string &amp; that CakePHP created.
  • $newkey = str_replace(‘amp;’,”,$key);
    If there is a match, we remove the ONLY the amp; portion of the string. We leave the leading & alone as we still need that as part of the new query string. Note here that the ” passed in the st_replace function is two single quotations and not one double. We’re replacing the amp;  found in the string with an empty string. Once we have our new string minus the amp; portion, we assign it to the $newkey variable.
  • unset($temparray[$key]);
    Now that we have the new parameter name with delimeter, we need to reconstruct the array by first removing the old array key.
  • $temparray[$newkey] = $value;
    Then, we add our newly created key $newkey and assign to it the original paired value, $value.
  • }  }
    The next two lines contain our closing brackets for the conditional IF block and next for the foreach loop.
  • $this->layout = ‘ajax’; 
    This line is just telling CakePHP to use our AJAX layout. Moving on…
  •  if($this->Model->save($temparray)){
    Now we can use our “New” array with any of CakePHP’s functions, classes etc. In this example, I’m using a conditional If statement and passing the array as part of CakePHP’s save function to update a bit of data in my database.
  • echo ‘success’;
    If the data update is successful, send the “success” string back  so that our AJAX handler can update the page.
  • }
    The next line contains the bracket that closes our conditional If statement
  • else{
    After that we have the followup to our If condition. If the data saved, do something, if not, do something else.
  • echo “<br><strong>OOPS! something went wrong, refresh the page to reset your list</strong>”;
    As part of the if / else condition, if the data update failed, then we send the error message back  so that our AJAX handler can update the page.
  • $this->autoRender = false;
    This line tells CakePHP not to render the page after we finish working our magic. Because we’re using AJAX to update the page, we don’t need CakePHP to keep reloading and rendering the page. AJAX is taking care of that for us.
  • exit();
    Next, if we got this far, there should be nothing else for us to do and we can tell CakePHP to wrap it up and quit doing anything further.
  • else{
    This else condition is the alternative to our beginning  If statement – if($this->request->is(‘ajax’)). If the page received a request, but not from our AJAX handler, then we need to do something about it.
  • $this->Session->setFlash(“Not an AJAX request!”);
    If the page received a request, but not from our AJAX handler, the we’re telling CakePHP to set a notification message to show on the page and inform the viewer that the request was “Not an AJAX request!”
  • $this->redirect(‘/Model/View‘);
    Finally, continuing with the alternative else actions, we need to tell CakePHP to redirect us back to a page (Model/View). Otherwise, because the example code used here called a Controller function using AJAX without an associated view, CakePHP would choke and complain about missing View files. IF you are calling a Controller function from AJAX that DOES have an associated CakePHP view, then you will not need this line.
  • }
    And to wrap it all up is the closing bracket bring our final else block to a close.

This sample was used as part of an existing application with specific conditions and goals. Some of the details in this How To may not apply to the specifics of your situation and this article should be used only as a guide.

That’s it. This is just one way to overcome the URL query string bug in CakePHP. There are other ways as well. I hope this at least helps in some way. Good luck and happy coding.

One Reply to “How To: Fixing CakePHP Broken GET Query Strings”

  1. Nice Step to Know about Fixing CakePHP Broken GET Query Strings.That was very needful to me and my group.

Comments are closed.