8
\$\begingroup\$

I've written a fairly small library that abstracts cookie functionality in a way that mimics working with a database model. The repository is on bitbucket and the code is small enough to embed here:

class SimpleCookie {

    /**
     * Cookie value char limit
     * @var int
     */
    const valueLimit = 2000;

    /**
     * Cookie data
     * @var array
     */
    protected $cookieData = array();

    /**
     * Cookie name
     * @var string
     */
    protected $cookieName = null;

    /**
     * If cookie exists, it get's loaded on construct
     *
     * @param string $cookieName
     */
    public function __construct($cookieName) {
        $this->load($cookieName);
    }

    /**
     * @param string $key
     * @return mixed
     */
    public function __get($key) {
        return
            isset($this->cookieData[$key])
            ? $this->cookieData[$key]
            : null;
    }

    /**
     * @param string $key
     * @param mixed $value
     */
    public function __set($key, $value) {
        $this->cookieData[$key] = $value;
    }

    /**
     * @param string $key
     * @return bool
     */
    public function __isset($key) {
        return isset($this->cookieData[$key]);
    }

    /**
     * @param string $key
     */
    public function __unset($key) {
        if(isset($this->cookieData[$key])) {
            unset($this->cookieData[$key]);
        }
    }

    /**
     * Load a cookie
     *
     * @param string $cookieName
     * @return SimpleCookie|false
     */
    public function load($cookieName) {
        $this->setName($cookieName);

        if(!$data = $this->getCookie($this->cookieName)) {
            return false;
        }

        if(is_array($data)) {
            $data = implode("", $data);
        }

        return $this->setData( $this->getCookieArray( $data ) );
    }

    /**
     * Save a cookie
     *
     * @param int $expires
     * @param string $path
     * @param string $domain
     * @param bool $secure
     * @param bool $httponly
     * @return bool
     */
    public function save($expires = 0, $path = "/", $domain = null, $secure = false, $httponly = false) {
        $value = $this->getCookieString($this->cookieData);
        $name  = $this->cookieName;

        if(strlen($value) > SimpleCookie::valueLimit) {
            $value  = str_split($value, SimpleCookie::valueLimit);
            $result = true;
            foreach($value as $k => $v) {
                $result = $result && $this->setCookie("{$name}[$k]", $v, $expires, $path, $domain, $secure, $httponly);
            }

            return $result;
        }

        return $this->setCookie($name, $value, $expires, $path, $domain, $secure, $httponly);
    }

    /**
     * Set cookie
     *
     * @since version 0.2
     * @param int $expires
     * @param string $value
     * @param string $path
     * @param string $domain
     * @param bool $secure
     * @param bool $httponly
     * @return bool
     */
    protected function setCookie($name, $value, $expires = 0, $path = "/", $domain = null, $secure = false, $httponly = false) {
        $expires  = is_int($expires) || ctype_digit($expires) ? (int) $expires : 0;
        $path     = is_string($path) ? $path : "/";
        $domain   = is_string($domain) || is_null($domain) ? $domain : null;
        $secure   = $secure === true;
        $httponly = $httponly === true;

        return setcookie($name, $value, $expires, $path, $domain, $secure, $httponly);
    }

    /**
     * Get cookie
     *
     * @since version 0.2
     * @param string $name
     * @return mixed|false
     */
    protected function getCookie($name) {
        return isset($_COOKIE[$name]) && !empty($_COOKIE[$name]) ? $_COOKIE[$name] : false;
    }

    /**
     * Delete a cookie
     *
     * @param string $path
     * @param string $domain
     * @param bool $secure
     * @param bool $httponly
     * @return bool
     */
    public function delete($path = "/", $domain = null, $secure = false, $httponly = false) {
        $this->setData(array());
        return $this->save(time() - 3600, $path, $domain, $secure, $httponly);
    }

    /**
     * Get cookie value from array
     *
     * Called on save
     *
     * @param array $data
     * @return string
     */
    protected function getCookieString(array $data) {
        return http_build_query($data);
    }

    /**
     * Get cookie array from cookie value
     *
     * Called on load
     *
     * @param string $string
     * @throws InvalidArgumentException
     * @return array
     */
    protected function getCookieArray($string) {
        if(!is_string($string)) {
            throw new InvalidArgumentException("Cookie string must be a string :)");
        }

        parse_str($string, $data);

        return $data;
    }

    /**
     * Set cookie data
     *
     * This function updates internal data array,
     * it does not save the cookie.
     *
     * @param array $data
     * @return SimpleCookie
     */
    public function setData(array $data) {
        $this->cookieData = $data;

        return $this;
    }

    /**
     * Get data array
     *
     * @return array
     */
    public function getData() {
        return $this->cookieData;
    }

    /**
     * Set cookie name
     *
     * @param string $cookieName
     * @throws InvalidArgumentException
     * @return SimpleCookie
     */
    protected function setName($cookieName) {
        if(!is_string($cookieName) || empty($cookieName)) {
            throw new InvalidArgumentException("Cookie name must be a non empty string");
        }

        $this->cookieName = $cookieName;

        return $this;
    }
}

I would really love to hear your critiques on:

  • Code quality
  • Code clarity
  • Comments sufficiency

I'm more interested in what I'm doing wrong than right. Any opinions on the actual usefulness of the library are welcome, although not central in my question.

\$\endgroup\$
4
\$\begingroup\$

Code quality

Well, I did not try your code, but at a first glance it seems to be very robust code. But for further development I would recomend you to set up a test case for your class. See http://www.phpunit.de.

Code clarity

The only part where I got stuck a bit was the save method, but after a second read I got the point. In my opinion any programmer with sufficient oo skills can get your code after one or two reads.

Comments sufficiency

Sometimes you comment, when there is no need for it. Consider the save method: through the methods name it should be clear, that a cookie gets saved. So no need for this comment. Same goes for the constructor, the load, setCookie, getCookie, setData and setName methods. As your code is very readable, I don't see any need for this degree of commenting. I would recomend you just to use the phpdoc comments.

| improve this answer | |
\$\endgroup\$
  • 1
    \$\begingroup\$ Bah, there is no such thing as overcommenting if you stick to -why- as opposed to what and how. \$\endgroup\$ – Kzqai Nov 13 '11 at 17:57
  • 2
    \$\begingroup\$ Well... Sometimes you do not need to explain why because it should be obvious by reading the code. But I think that's a matter of personal preference. \$\endgroup\$ – mAu Nov 13 '11 at 18:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.