In which I try to implement column sorting in WebDB, but end up building a general system for persisting query string variables between HTTP requests. Sort of.
In the old, Zend-based, WebDB, column sort order was passed as a $_GET
parameter and then stored in per-table variables in $_SESSION
. In the index controller, both were checked when a table was loaded:
/* application/controllers/IndexController.php */
$orderby = ($this->_hasParam('orderby')) ? $this->_getParam('orderby') : $_SESSION['ordering'][$this->tableName]['orderby'];
$orderdir = ($this->_hasParam('orderdir')) ? $this->_getParam('orderdir') : $_SESSION['ordering'][$this->tableName]['orderdir'];
The advantage, of course, being that a user could sort by a column, go off somewhere else (into a record to edit it, say), and come back to the table view and have the previous sort order be remembered. Good. But annoying, too, because a table could be sorted without a sort directive being present in the URI. Not good, because the URI doesn’t reflect exactly the resource that one is looking at; one can’t bookmark that page, for instance.
We need the best of both:
- remembering the users’ sort preferences between requests; and
- ensuring that the current URI is an accurate identifier for the current resource.
The obvious answer seems to be to store sort directives in the session, and redirect requests to a proper, complete, URI if the current one doesn’t already contain those directives. Sounds good.
However, before jumping in and coding that: the same ideas apply to the filter variables, which are currently passed as $_GET
parameters. So could we generalize this idea of **storing parameters in $_SESSION but only ever using them from <math>\_GET**? (Oh, and I should point out that the validation of these parameters happens after this, and we’re not worried about that right now.) Can this be done in a way so that the rest of the application doesn’t need to know anything about the session stuff, and can just use `</math>_GET['foo']and
href="?foo=bar"` in whatever way it wants?
A first draft:
// To be called from Controller_WeBDB::before()
if (count($_GET)>0)
{
$_SESSION['qs'] = $_GET;
}
elseif (isset($_SESSION['qs']) && count($_SESSION['qs'])>0)
{
$query = URL::query($_SESSION['qs']);
$_SESSION['qs'] = array();
$uri = URL::base(FALSE, TRUE).$this->request->uri.$query;
$this->request->redirect($uri);
}
Which sort of works, except that it’s not possible to specify only some of the parameters (i.e. either load them all from $_GET
or all from $_SESSION
, which isn’t what we want).
A final draft (a new method in Controller_WebDB
, called from Controller_WebDB::before()
):
/**
* Save and load query string (i.e. `$_GET`) variables from the `$_SESSION`.
* The idea is to carry query string variables between requests, even
* when those variables have been omitted in the URI.
*
* 1. If a request has query string parameters, they are saved to
* `$_SESSION['qs']`, merging with whatever is already there.
* 2. If there are parameters saved in `$_SESSION['qs']`, and if they're
* not already in the query string, add them and redirect the request to
* the resulting URI.
*
* @return void
*/
private function _query_string_session()
{
// Save the query string, adding to what's already saved.
if (count($_GET)>0)
{
$existing_saved = (isset($_SESSION['qs'])) ? $_SESSION['qs'] : array();
$_SESSION['qs'] = array_merge($existing_saved, $_GET);
}
// Load query string variables, unless they're already present.
if (isset($_SESSION['qs']) && count($_SESSION['qs'])>0)
{
$has_new = FALSE; // Whether there's anything in SESSION that's not in GET
foreach ($_SESSION['qs'] as $key=>$val)
{
if (!isset($_GET[$key]))
{
$_GET[$key] = $val;
$has_new = TRUE;
}
}
if ($has_new)
{
$query = URL::query($_SESSION['qs']);
$_SESSION['qs'] = array();
$uri = URL::base(FALSE, TRUE).$this->request->uri.$query;
$this->request->redirect($uri);
}
}
}
To clear a parameter, it obviously has to actually be set, and not just omitted from the URI. Is this going to be a problem?
The other glaring issue with the above code seems to be the insecurity in storing unvalidated content in $_SESSION
, but as this content is only ever being fed back into the URI, I don’t think there’s too much of a problem there. Nowhere else in WebDB will use $_SESSION['qs']
— everything will only use $_GET
, and be responsible for validation.
Well, I seem to have got somewhere with the issue of saving $_GET
parameters between requests, but nowhere yet with what I started out with: sorting the columns! However, this is now going to be a pretty straight-forward matter of adding links to column headers that toggle ?orderby=asc
etc. I’ll get to it next first thing next week.