I wrote this class to make cookie management easier.
I know it makes use of serialize()
, but I have no intention of storing very much data in the cookie, and it seemed a cleaner solution than, say, the JSON functions (like, even if a class implements the JSONSerializable
interface, it doesn't retain the class name by default, etc.). serialize()
seemed more robust, although if there's a way to compress the output without it losing its integrity, please share. Probably the worst pitfall is that, if the cookie is messed with, unserialize()
will trigger a notice and who knows what else -- but I don't feel I need to cater to the user experience of users who mess with cookies. (Although if the cookie is maxed out at a number of bytes, like 4,096, and it truncates, that could be an issue....)
I am in the process of writing a class to put on top of this one for managing user sessions implementing Charles Miller's solution.
I decided against implementing exceptions because it was simple enough to just return constants.
<?php
/**
* Cookie.class.php, the Cookie class
*
* Contains a class for making cookies easier to manage via PHP
* @author Miller <wyattstorch42 at outlook dot com>
* @version 1.0
* @package Miller
* @subpackage Utils
*/
namespace Miller\Utils;
if (!function_exists('array_copy')) {
/**
* array_copy Properly and recursively clones an array
* @param array $source The original array
* @return array The clone
*/
function array_copy ($source) {
$return = array ();
$keys = array_keys($source);
$values = array_values($source);
for ($i = 0; $i < count($keys); ++$i) {
if (is_object($values[$i])) {
$return[$keys[$i]] = clone $values[$i];
}
else if (is_array($values[$i])) {
$return[$keys[$i]] = array_copy($values[$i]);
}
else {
$return[$keys[$i]] = $values[$i];
}
}
return $return;
}
}
/**
* A class for making cookies easier to manage via PHP
* @package Miller\Utils
*/
class Cookie {
/**
* Class constants
* SESSION, EXPIRE, DAY, WEEK, MONTH, and YEAR are used to define the life of the cookie
* ERROR_HEADERS_SENT, ERROR_SET_COOKIE, and ERROR_NONE are possible return values for the sent() method.
*/
const
SESSION = 0,
EXPIRE = -3600,
DAY = 86400,
WEEK = 604800,
MONTH = 2592000,
YEAR = 31536000,
ERROR_HEADERS_SENT = -1,
ERROR_SET_COOKIE = -2,
ERROR_NONE = 1;
protected
/**
* Holds the name of the cookie
* @var string
*/
$name,
/**
* Holds the expiration timestamp of the cookie
* @var integer
*/
$expiry,
/**
* Holds the path of the cookie
* @var string
*/
$path,
/**
* Holds the domain of the cookie
* @var string
*/
$domain,
/**
* A flag that sets the secure option for the cookie
* @var boolean
*/
$secure,
/**
* A flag that sets the httponly option for the cookie
* @var boolean
*/
$httponly,
/**
* An array that holds the current unserialized properties stored in the cookie
* @var array
*/
$properties,
/**
* An array that holds the properties that will be stored and serialzed in the cookie on send()
* @var string
*/
$internalProperties;
public function __construct ($name, $expiry = self::WEEK, $path = '', $domain = '', $secure = null, $httponly = false) {
/**
* Constructor
* @param string $name The name of the cookie
* @param null|integer|string $expiry The expiration timestamp of the cookie (if null, sets a session cookie; if string, converts using strtotime())
* @param string $path The path of the cookie
* @param string $domain The domain of the cookie
* @param null|boolean $secure The flag that sets the secure option for the cookie (if null, sets to true if $_SERVER['HTTPS'] is set, false otherwise)
* @param boolean $httponly The flag that sets the httponly option for the cookie
*/
$this->name = (string) $name;
if ($expiry === self::SESSION) {
$this->expiry = $expiry;
}
else if (is_numeric($expiry)) {
$this->expiry = time()+$expiry;
}
else {
$this->expiry = strtotime($expiry);
}
$this->path = (string) $path;
$this->domain = (string) $domain;
$this->secure = $secure === null ? isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] : (bool) $secure;
$this->httponly = (bool) $httponly;
$this->properties = array ();
$this->internalProperties = array ();
if (isset($_COOKIE[$this->name])) {
$properties = unserialize($_COOKIE[$this->name]);
$this->internalProperties = array_copy($properties);
$this->properties = array_copy($properties);
}
}
public function exists () {
/**
* Checks if the cookie was sent from the last browser request
* @return bool True if the cookie is set, false otherwise
*/
return (bool) $this->properties;
}
public function __isset ($property) {
/**
* Checks if the given property is set on the cookie
* @param string $property The property key
* @return bool True if the property is set, false otherwise
*/
return isset($this->properties[$property]);
}
public function __get ($property) {
/**
* Returns the value of the given property on the cookie
* @param string $property The property key
* @return bool The value of the property if it is set, null otherwise (use __isset() to differentiate a null value from an undefined value)
*/
return $this->__isset($property) ? $this->properties[$property] : null;
}
public function __set ($property, $value) {
/**
* Sets or overrides the given property (will not be accessible until the cookie is sent)
* @param string $property The property key
* @param mixed $value The value
* @return void
*/
$this->internalProperties[$property] = $value;
}
public function __unset ($property) {
/**
* Unsets the given property (will not be accessible until the cookie is sent)
* @param string $property The property key
* @return void
*/
if (isset($this->internalProperties[$property])) {
unset($this->internalProperties[$property]);
}
}
public function send () {
/**
* Serializes the properties and sends the cookie to the browser. On success, updates the values of the properties.
* @return integer ERROR_HEADERS_SENT if the headers have already been sent, ERROR_SET_COOKIE if the setcookie() function failed, or ERROR_NONE on success
*/
if (headers_sent()) {
return self::ERROR_HEADERS_SENT;
}
$value = $this->expiry === self::SESSION ? false : serialize($this->internalProperties);
if (setcookie($this->name, $value, $this->expiry, $this->path, $this->domain, $this->secure, $this->httponly)) {
$this->properties = array_copy($this->internalProperties);
return self::ERROR_NONE;
}
return self::ERROR_SET_COOKIE;
}
}
?>
Example usage:
<?php
namespace Miller\Utils;
error_reporting(E_ALL);
require 'Cookie.class.php';
ob_start();
$cookie = new Cookie('example_cookie', Cookie::SESSION);
var_dump($cookie->exists());
// true if cookie is stored in the browser already, false otherwise
var_dump(isset($cookie->example_prop));
// true if example_prop exists in cookie, false otherwise
var_dump($cookie->example_prop);
// the value of example_prop, or null if it is not set
$cookie->example_prop = 123;
var_dump($cookie->example_prop);
// will still be the previous value, because the cookie hasn't been sent
var_dump($cookie->example_prop_2);
// the value of example_prop_2, or null if it is not set
unset($cookie->example_prop_2);
var_dump($cookie->example_prop_2);
// will still be previous value, because cookie hasn't been sent
$cookie->current_time = new \DateTime('now', new \DateTimeZone('America/Phoenix'));
// will serialize objects using PHP native function and unserialize automatically
var_dump($cookie->send());
// -1, -2, or 0 (ERROR_HEADERS_SENT, ERROR_SET_COOKIE, or ERROR_NONE)
var_dump($cookie->example_prop);
// if send() was successful, this will now be 123
var_dump(isset($cookie->example_prop_2));
// if send() was successful, this will now be false
var_dump($cookie->current_time);
// if send() was successful, this will now be the recorded time
ob_end_flush();
?>
?>
in definition files (PHP recommends not using the closing tag!). If a file is calledsomething.class.php
, I'd expect it to contain only a class definition, not functions, too... Implement one of theTravesable
interfaces, so you can use the realisset
, not your pseudo-magic__isset
method \$\endgroup\$ – Elias Van Ootegem Sep 7 '13 at 16:51