skip to content

PHP: Saving a list of values in a cookie

As we well know, HTML was designed as a stateless protocol. Every time you open a new page, any values from the previous page are lost, unless transmitted via GET or POST variables, or using a COOKIE, which is what we're exploring in this article.

A simple ListBuilder class

Here we have a very simple class that lets us add items to a list, search within the list, and retrieve the entire list as an array. It's basically a wrapper for the built-in PHP Array:

<?PHP // Original PHP code by Chirp Internet: // Please acknowledge use of this code by including this header. class ListBuilder { private $list = []; public function contains($val) { return in_array($val, $this->list); } public function add($val) { if(!$this->contains($val)) { $this->list[] = $val; } } public function get() { return $this->list; } } ?>

The ListBuilder can be now used as follows:

<?PHP $list = new ListBuilder(); $list->add('red'); $list->add('green'); $list->add('blue'); var_dump($list->get()); var_dump($list->contains('green')); var_dump($list->contains('purple')); ?>

Which produces the following output:

array(3) { [0]=> string(3) "red" [1]=> string(5) "green" [2]=> string(4) "blue" } bool(true) bool(false)

So far, so good, but we really haven't achieved much, as any list we create will be dust in the wind as soon as we leave the page. To make it more useful we need to be able to save our list somehow so it can be retrieved when we open another page, or leave the website and come back.

The simplest approach is to use a browser cookie. Other options include using a SESSION, which is more secure if you are passing private information, but still essentially a COOKIE.

Saving and retrieving values using cookies

Using our ListBuilder class as a starting point, we can now add functionality for saving and retrieving the list values using a cookie:

<?PHP // Original PHP code by Chirp Internet: // Please acknowledge use of this code by including this header. class CookieList extends ListBuilder { public function save($expires = 0) // minutes { if($expires && is_int($expires)) { $expires = time() + 60 * $expires; } if(headers_sent()) { error_log(__METHOD__ . ": cookies must be sent *before* any output from your script"); return false; } return setcookie( __CLASS__, json_encode($this->get()), $expires, "/" ); } public function __construct() { if(isset($_COOKIE[__CLASS__]) && $_COOKIE[__CLASS__]) { // reload list items from cookie $arr = json_decode($_COOKIE[__CLASS__]); if(is_array($arr) && $arr) { foreach($arr as $val) { $this->add($val); } } } } } ?>

Along with the existing add, get and contains methods, our class now has a contructor and a new save method. Invoking save will send a cookie to the browser with the name CookieList (or possibly Namespace\CookieList) containing the list contents as a JSON-encoded string.

The cookie will expire at the end of the session unless a value representing the number of minutes to keep the cookie for is passed to the save method (see below).

The browser cookie expiry time is relative to the last time the list was saved (i.e. when the cookie was last set). If you want it to persist longer, your script will need to re-save the list at regular intervals.

A very common problem with PHP cookies is that they can only be sent to the browser before any HTML or other content has been output. Unless you're using Output Buffering, but that's another story.

Putting it into practice

In a real world environment you might be storing the contents of a shopping cart, or a list of viewed products. You could even create a list (array) of arrays for more complex data structures.

Here we're just using simple text strings as before:

Code to run on Page #1

<?PHP $list = new CookieList(); $list->add('alpha'); $list->add('beta'); $list->add('gamma'); var_dump($list->save(60)); // output: bool(true) $list->add('omega'); ?>

This will create a cookie with an expiry date one hour (60 minutes) in the future, after which the browser will 'forget'. The cookie contents will be %5B%22alpha%22%2C%22beta%22%2C%22gamma%22%5D.

Code to run on Page #2

While the cookie still exists in the browser, running the following:

<?PHP $list = new CookieList(); // the constructor will load any saved values from the cookie var_dump($list->get()); ?>

will output:

array(3) { [0]=> string(5) "alpha" [1]=> string(4) "beta" [2]=> string(5) "gamma" }

We've successfully created a list, stored it in a browser cookie, and retrieved it on another page.

Note that we don't see "omega" in the retrieved list as it was added only after the cookie had been set. But on any page loading the list, new items can be added, and the updated list saved. A remove method would also be a good addition to the base ListBuilder class.

Feel free to use this code as you wish, and let us know below if you have any questions or comment.

Saving to a SESSION

PHP sessions are a way of implementing cookies where the actual content is stored in a file on the server (according to session_save_path) while the browser cookie contains only a key that the server uses to retrieve the data.

The following class will save our list to a SESSION instead of a cookie:

<?PHP // Original PHP code by Chirp Internet: // Please acknowledge use of this code by including this header. class SessionList extends ListBuilder { public function save($expires = 0) // minutes { if(headers_sent()) { error_log(__METHOD__ . ": cookies must be sent *before* any output from your script"); return false; } session_name(__CLASS__); if($expires && is_int($expires)) { session_set_cookie_params(60 * $expires); } session_start(); $_SESSION['list'] = $this->get(); session_write_close(); return true; } public function __construct() { session_name(__CLASS__); session_start(); if(isset($_SESSION['list']) && is_array($_SESSION['list']) && $_SESSION['list']) { // reload list items from session foreach($_SESSION['list'] as $val) { $this->add($val); } } session_write_close(); } } ?>

This will create a cookie SessionList (or Namespace%5CSessionList) in the browser if you are using namespaces in your application. Otherwise implementation is identical to the previous example.

In this code the SESSION handling is fairly amateur, but it does work. A better system would preserve/restore any existing sessions, among other things. And the session functions could be made into a Trait so they can be bolted into any new class.

Check that session.gc_maxlifetime is not too short as this will delete idle sessions on the server (the default is 24 minutes).

None of the above classes have support for multiple lists, but you could extend them with different class names for different uses and then they won't clash.



Post your comment or question