构建一个更可测试的会话管理器

时间:2012-02-26 08:31:21

标签: php session phpunit global-variables testability

我正在研究一组组件(希望成为一个完整的框架),目前我正在开发一个组件来提供PHP会话的抽象。

我正在尝试使代码尽可能可测试,但根据定义,会话类将依赖于$ _SESSION超全局形式的全局状态。

我试图以这样的方式实现我的会话类:$ SESSION和session *函数只能在一个地方被调用,然后我可以在PHPUnit中覆盖它以进行测试,但是我不禁想知道是否有更好的方法。

如果您可以建议一个更好的方法来制作一个可测试的会话课程,那么我将非常感谢您提供的任何输入。

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

namespace gordian\reefknot\storage\session;

use gordian\reefknot\storage;

/**
 * Session management
 * 
 * Each instance of the Session object represents a "namespace" within the PHP
 * $_SESSION system.  This allows sessions to be easily managed and organized
 * within an application
 * 
 */
class Session implements storage\iface\Crud, iface\Session
{

    protected
        $name       = '',
        $storage    = NULL;


    /**
     * Add a new item to the session
     * 
     * @param mixed $data 
     * @param string $key
     * @return Session
     * @throws \InvalidArgumentException Thrown if no name is provided
     */
    public function createItem ($data, $key)
    {
        if (!empty ($key))
        {
            $key    = (string) $key;
            if (($this -> storage === NULL)
            || (!array_key_exists ($key, $this -> storage)))
            {
                $this -> storage [$key] = $data;
            }
        }
        else
        {
            throw new \Exception ('No valid key given');
        }
        return ($this);
    }

    /**
     * Delete the specified key
     * 
     * @param string $key 
     * @return Session
     */
    public function deleteItem ($key)
    {
        unset ($this -> storage [$key]);
        return ($this);
    }

    /**
     * Retrieve the data stored in the specified key
     * 
     * @param type $key 
     * @return mixed
     */
    public function readItem ($key)
    {
        return (array_key_exists ($key, $this -> storage)? 
            $this -> storage ['key']: 
            NULL);
    }

    /**
     * Update a previously stored data item to a new value
     * 
     * @param mixed $data 
     * @param string $key
     */
    public function updateItem ($data, $key)
    {
        if ($this -> storage === NULL)
        {
            throw new \RuntimeException ('Session contains no data');
        }

        if (array_key_exists ($key, $this -> storage))
        {
            $this -> storage [$key] = $data;
        }
        return ($this);
    }

    /**
     * Clear the session of all stored data
     * 
     * @return Session 
     */
    public function reset ()
    {
        $this -> storage = NULL;
        return ($this);
    }

    /**
     * Retrieve all data stored in the session
     * 
     * @return array 
     */
    public function getAll ()
    {
        return ($this -> storage);
    }

    /**
     * Return whether there is data stored in this session
     * 
     * @return bool 
     */
    public function hasData ()
    {
        return (!empty ($this -> storage));
    }

    /**
     * Initialize the back-end storage for the session
     * 
     * This method provides access for this class to the underlying PHP session
     * mechanism.  
     * 
     * @return bool Whether the newly initialized session contains data or not
     * @throws \RuntimeException Will be thrown if the session failed to start
     */
    protected function initStorage ()
    {
        // Check that storage hasn't already been initialized
        if ($this -> storage === NULL)
        {
            // Attempt to start the session if it hasn't already been started
            if ((session_id () === '')
            && ((headers_sent ()) 
            || ((!session_start ()))))
            {
                throw new \RuntimeException ('Unable to start session at this time');
            }
            // Alias our instance storage to the named $_SESSION variable
            $this -> storage    =& $_SESSION [$this -> name];
        }
        return ($this -> hasData ());
    }

    /**
     * Class constructor
     * 
     * @param string $sessName
     * @throws \InvalidArgumentException Thrown if no session name is provided
     */
    public function __construct ($sessName)
    {
        if (!empty ($sessName))
        {
            $this -> name   = $sessName;
            $this -> initStorage ();
        }
        else
        {
            throw new \InvalidArgumentException ('Session must have a name');
        }
    }
}

对于测试,当前的计划是使用仅设置内部数组的方法替换initStorage()。如果你能提出一个更好的方法,我会热衷于听到它。

2 个答案:

答案 0 :(得分:1)

如果我没有正确看待..

创建本机会话管理的抽象,以便您的会话存储帮助程序不需要实际执行任何session_ *调用或直接访问$ _SESSION。

有两个实现它,一个实际上做正确的事情,另一个是fakes session _ *()和$ _SESSION,并且在你的构造函数中你只需要调用SESSIONCLASS :: start()和SESSIONCLASS :: getVar(name) 。那么你可以完全测试“Session”。

答案 1 :(得分:1)

由于$_SESSION是常规数组,因此没有理由通过引用访问它。各种读/写方法应该直接对它进行操作。您可以在测试用例的setUp()方法中清除该数组。使用引用会使类过度复杂而无需支付。

进行单元测试时,您不应该考虑测试内置的PHP函数,如session_id()headers_sent()。创建一个类的部分模拟或一个覆盖这个方法的仅测试子类。