Javascript/PHP Cookies

Important references

By way of background, please see a very informative article on the subject of Javascript cookies available at Quirksmode and then come back here because although Quirksmode makes Javascript cookies fairly functional while not providing the necessary encoding, this article makes them powerful by not only providing URL encoding but also, the ability to pack more information into a single cookie.

Another useful reference is of course, the W3CSchools website where one may find information, from the horse's mouth so to speak regarding the use of document.cookie. One should make note though that although the example shown on the W3C's website does try to deal with cookie value encoding issues, it is done there in such a way that makes it no more guarenteed to be interoperable with PHP's use of cookies than it would have been had they done nothing.

The need for cookies...

The need for cookies

Even though the use of cookies has gathered a bit negative press over time, they are, among other things, an integral part of developping web applications due to their being able to provide state information in the stateless process which is HTTP.

Without cookies DHTML applications are limited to page specific functionality as opposed to site wide functionality. A simple example is the font size selection system used here on Cass-Hacks.

Turn off Javascript and refuse to accept cookies and you'll find that selecting anything other than the default font size, while it does work, is only effective on the given page for which a non-default font size was selected. Loading a new page or even simply reloading the current page and you have to select the same font size again. That is not what I would consider 'accessible' but more like, painful.

And, although cookies are commonly used to maintain some sort of state, they seem to rarely be used by both server side and client side processes but instead, some cookies on a given site will be set and used client side, some server side but almost never, both. Even more so, many do not realize that the cookies they set on the client side are not only sent to the server on each request but can and will interfere with any cookies of the same name that the server may set.

More often than not, a 'state' cookie set on the client side is checked each time a page is loaded and the page updated accordingly. Were that done in the case of font size, the font size would initially be displayed using the default, then Javascript would execute and change the font thereby causing an annoying shift on each page load. Contrast that with using Javascript to only effect the initial change and thereafter, the server checking the same "state" cookie and serving the page accordingly on each new page request.

Cookie limitations...

Cookie limitations

Additonally, the IETF in their RFC-2109 document on page 14 section 6.3 Implementation Limits suggest minimum UA support that while being only suggestive in nature, would seem useful to follow in any event as the maximums that one can expect.

In this regard, the important parameter for this discussion is, at least 20 cookies per unique host or domain name (which to be safe, should be read as being the maximum that one can safely expect). And, although the over use of cookies is not a good idea, using numerous name/value pairs does afford one a finer level of granularity in maintaining not only site wide state information but also, finer granular control within individual pages.

However, although one may want to be able to "stack" cookies and encode numerous name/value pairs into a single cookie, that is not always the case and since assuming cookie stacking would require equivalent support on the server side even though it may not be needed, the solution should also support Javascript cookies being generated and used in their more usual fashion.

Domain and path are important!...

Domain and path are important!

Also, one of the problems that plague many implementations using cookies, whether Javascript or PHP, is the use of the "Domain" element of the cookie. What often happens is that a cookie will be set without any consideration for the value assigned to the "Domain" component of the cookie and in fact often isn't set at all leaving it to assume the default value, the site's domain without any host name/sub-domain. Then, when a user accesses a site via a host name including "www.", has a cookie set, but then accesses the site, for whatever reason, without the "www.", the cookie previously set will not be used and instead a new cookie will be set.

Cookies are only valid for the path and domain values for which they were originally set, left unset and assuming default values or, set using values allowing them to be applied to any path and/or host name/sub-domain on a given site.

This last point is often seen as the cause of users being logged in to a given account and then all of a sudden finding themselves having to log in again. And, while all links internal to a site may have been very carefully set so that the URL used for access is consistent, one has no control over links into the site from other sites and once a given link is posted by someone somewhere, trying to get them to change it becomes an impossible task especially considering that links can have a "bad" habit of propagating all over the net in a short amount of time, especially for a popular site.

One can somewhat mitigate the problem of "To www or not to www" by using mod_rewrite rules in the server to redirect requests made to any host-name/sub-domain to the domain name itself but often that is not practical. And as well, even were one to do that dealing with improperly/default set path values likely would become very complicated.

PHP and Javascript cookies...

PHP and Javascript cookies

Finally, and one of the most important reasons for this and the previous project, URL Encode/Decode is that while a cookie should be a cookie should be a cookie, Javascript and PHP cookies are not guarenteed to be 100% interoperable because Javascript does not URL encode cookie data, while PHP does.

One may ask how often one would want to include values in a cookie requiring URL encoding but on the other hand, if you didn't know that URL encoding was required, things would likely seem to only work some of the time and the cause for them not working all the time would likely not be apparent.

PHP can however read non-URL encoded cookies set by Javascript fairly reliably but the other way around does not work. Javascript reading, correctly, cookie values that have been URL encoded by PHP, does not work right out of the box. And in any event, as it is always safest to URL encode anything that goes into headers whenever possible, even if a given Javascript cookie is not intended to be used server side, it WILL be sent along with any request within the set domain/path parameters so whether you intended it to be sent to the server or not, it's on its way regardless.

Requirements...

Requirements

From the above discussion, the requirements seem relatively simple.

  1. Provide URL encoding and decoding of values to be compatible with PHP
  2. Provide the ability to encode multiple name/value pairs into a "single" cookie
  3. Provide transparent support for non-stacked name/value pairs
  4. Include the setting of the "path" and "domain" elements to make cookies consistently applied
Solution...

Solution

The solution will be implemented to include the following points.

  • A single function will be used with context/function switching (Set, Read , Clear, Delete) signalled function parameters.
  • Internal to the primary function, context, determined by function parameters will indicate whether or not name/value pair stacking is required.
  • A corresponding URL function will be provided supporting the use of name/value stacked pairs on the server side as well.
  • The functions will take into account that Javascript accesses cookies slightly differently than PHP.
Walkthrough...

Walkthrough

The basic processing will take place as follows:

  1. Function opens with parameters; cookieName, func, name, value, days
    1. cookieName - The name by which the contents will be identified. This is what will be displayed in most browsers as the cookie name.
    2. func - The context of the function call;
      • kill - delete the cookie with the name given by the cookieName parameter.
      • clear - remove the name/value pair given by the name parameter for stacked cookies or deletes the cookie if not stacked.
      • set - create or update a name/value pair. Creates the cookie if necessary and then adds, the name/value pair.
      • read - checks for the existence of the name/value pair given by the name parameter and returns the associated value, after URLDecode on the client/Javascript side, if present, if not, return null.
    3. name - the name component of the name/value pair. If different than the cookieName, indicates a stacked cookie.
    4. value - the value component of the name/value pair. On the client/Javascript side, this value must be URL encoded.
    5. days - the number of days that the cookie should be valid for.
  2. Branch on func
    • kill
      1. Create an empty cookie of the same cookieName with a lifetime set to cause the browser to delete it.
      2. If cookieName is the same as name (a normal unstacked cookie) and the func is clear, perform this same operation.
    • Intermediate step - Javascript stores all cookies for a given domain/path under a single document.cookie with each actual cookie's name/value pairs separated by a ";". Split the document.cookie at the ";" and store the string beginning with the requested cookieName.
    • clear
      1. Construct and use a regular expression to search for the name/value pair to be removed.
      2. If found, remove the name/value pair using the match from the regex.
      3. Store the resulting string, if any, back into the cookie.
    • set
      • If a stacked cookie;
        1. Construct and use a regular expression to search for the required name of the name/value pair.
        2. If a match exists, replace the value.
        3. If a match does not exist, add the requested name/value pair to the new string using the '-' and '|' separators as needed.
        4. Store the resulting string back into the cookie.
      • If a normal, non-stacked cookie;
        1. Set a cookie as cookieName with the provided name/value pair.
    • read
      • If a stacked cookie;
        1. Construct and use a regex to search for a value assigned to the requested name.
        2. If a match is found, return the associated value.
        3. If the requested is not found, return "null"
      • If a normal, non-stacked cookie;
        1. If the requested name/value pair is present, i.e. the cookie exists, return the value.
Going Further...

Going Further

Some may find rolling all of the processing into a single function to be confusing or simply prefer separate access functions for each type of processing so one could do exactly that. I prefer a single interface for a given type of processing over multiple interfaces, but that is only a personal preference and the script would function just as well if divided into separate function calls

One will also notice that the cookie path as well as the domain are hardcoded and fixed. The function does however support the host name being automatically set no matter what site the script is used on but it assumes that one wishes the cookie(s) to be applicable for all host names/sub-domains. So, if one had an application where different cookies should only be available to different paths and/or host names/sub-domains, the script could easily be modified to add additional function call parameters to suit.

To the code...

To the code

Now, with all that out of the way, on to the implementation