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 $_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 $_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.
[Keywords: columns, Kohana, redirecting, sessions, sorting, URI, WebDB, Zend] [No comments] [Permanent link]
Sam Wilson's Journal
Comments: