Better support for overloading start and destroy methods in Session

Move functionality from static start and destroy functions into instance
methods, allowing these to be overloaded. This works the same way as
calling Session::set() which then in turn calls inst_set()

Additionally use Injector to create the default Session instance to
allow the class to be swapped out.
This commit is contained in:
Sean Harvey 2014-06-19 10:38:26 +12:00
parent 11cc27f700
commit 0ee3a683a5
10 changed files with 101 additions and 86 deletions

View File

@ -439,7 +439,7 @@ class Controller extends RequestHandler implements TemplateGlobalProvider {
if(isset(self::$controller_stack[1])) { if(isset(self::$controller_stack[1])) {
$this->session = self::$controller_stack[1]->getSession(); $this->session = self::$controller_stack[1]->getSession();
} else { } else {
$this->session = new Session(null); $this->session = Injector::inst()->create('Session', array());
} }
} }
} }

View File

@ -135,12 +135,13 @@ class Director implements TemplateGlobalProvider {
$req->addHeader($header, $value); $req->addHeader($header, $value);
} }
// Initiate an empty session - doesn't initialize an actual PHP session until saved (see below)
$session = Injector::inst()->create('Session', isset($_SESSION) ? $_SESSION : array());
// Only resume a session if its not started already, and a session identifier exists // Only resume a session if its not started already, and a session identifier exists
if(!isset($_SESSION) && Session::request_contains_session_id()) { if(!isset($_SESSION) && Session::request_contains_session_id()) {
Session::start(); $session->inst_start();
} }
// Initiate an empty session - doesn't initialize an actual PHP session until saved (see belwo)
$session = new Session(isset($_SESSION) ? $_SESSION : null);
$output = Injector::inst()->get('RequestProcessor')->preRequest($req, $session, $model); $output = Injector::inst()->get('RequestProcessor')->preRequest($req, $session, $model);
@ -151,7 +152,7 @@ class Director implements TemplateGlobalProvider {
$result = Director::handleRequest($req, $session, $model); $result = Director::handleRequest($req, $session, $model);
// Save session data (and start/resume it if required) // Save session data. Note that inst_save() will start/resume the session if required.
$session->inst_save(); $session->inst_save();
// Return code for a redirection request // Return code for a redirection request
@ -229,7 +230,7 @@ class Director implements TemplateGlobalProvider {
if(!$httpMethod) $httpMethod = ($postVars || is_array($postVars)) ? "POST" : "GET"; if(!$httpMethod) $httpMethod = ($postVars || is_array($postVars)) ? "POST" : "GET";
if(!$session) $session = new Session(null); if(!$session) $session = Injector::inst()->create('Session', array());
// Back up the current values of the superglobals // Back up the current values of the superglobals
$existingRequestVars = isset($_REQUEST) ? $_REQUEST : array(); $existingRequestVars = isset($_REQUEST) ? $_REQUEST : array();

View File

@ -139,7 +139,7 @@ class Session {
/** /**
* Start PHP session, then create a new Session object with the given start data. * Start PHP session, then create a new Session object with the given start data.
* *
* @param $data Can be an array of data (such as $_SESSION) or another Session object to clone. * @param $data array|Session Can be an array of data (such as $_SESSION) or another Session object to clone.
*/ */
public function __construct($data) { public function __construct($data) {
if($data instanceof Session) $data = $data->inst_getAll(); if($data instanceof Session) $data = $data->inst_getAll();
@ -150,9 +150,8 @@ class Session {
if ($this->data['HTTP_USER_AGENT'] != $this->userAgent()) { if ($this->data['HTTP_USER_AGENT'] != $this->userAgent()) {
// Funny business detected! // Funny business detected!
$this->inst_clearAll(); $this->inst_clearAll();
$this->inst_destroy();
Session::destroy(); $this->inst_start();
Session::start();
} }
} }
} }
@ -347,11 +346,78 @@ class Session {
if(Controller::has_curr()) { if(Controller::has_curr()) {
return Controller::curr()->getSession(); return Controller::curr()->getSession();
} else { } else {
if(!self::$default_session) self::$default_session = new Session(isset($_SESSION) ? $_SESSION : array()); if(!self::$default_session) {
self::$default_session = Injector::inst()->create('Session', isset($_SESSION) ? $_SESSION : array());
}
return self::$default_session; return self::$default_session;
} }
} }
public function inst_start($sid = null) {
$path = Config::inst()->get('Session', 'cookie_path');
if(!$path) $path = Director::baseURL();
$domain = Config::inst()->get('Session', 'cookie_domain');
$secure = Director::is_https() && Config::inst()->get('Session', 'cookie_secure');
$session_path = Config::inst()->get('Session', 'session_store_path');
$timeout = Config::inst()->get('Session', 'timeout');
if(!session_id() && !headers_sent()) {
if($domain) {
session_set_cookie_params($timeout, $path, $domain, $secure, true);
} else {
session_set_cookie_params($timeout, $path, null, $secure, true);
}
// Allow storing the session in a non standard location
if($session_path) session_save_path($session_path);
// If we want a secure cookie for HTTPS, use a seperate session name. This lets us have a
// seperate (less secure) session for non-HTTPS requests
if($secure) session_name('SECSESSID');
// @ is to supress win32 warnings/notices when session wasn't cleaned up properly
// There's nothing we can do about this, because it's an operating system function!
if($sid) session_id($sid);
@session_start();
$this->data = isset($_SESSION) ? $_SESSION : array();
}
// Modify the timeout behaviour so it's the *inactive* time before the session expires.
// By default it's the total session lifetime
if($timeout && !headers_sent()) {
Cookie::set(session_name(), session_id(), $timeout/86400, $path, $domain ? $domain
: null, $secure, true);
}
}
public function inst_destroy($removeCookie = true) {
if(session_id()) {
if($removeCookie) {
$path = Config::inst()->get('Session', 'cookie_path');
if(!$path) $path = Director::baseURL();
$domain = Config::inst()->get('Session', 'cookie_domain');
$secure = Config::inst()->get('Session', 'cookie_secure');
if($domain) {
Cookie::set(session_name(), '', null, $path, $domain, $secure, true);
} else {
Cookie::set(session_name(), '', null, $path, null, $secure, true);
}
unset($_COOKIE[session_name()]);
}
session_destroy();
// Clean up the superglobal - session_destroy does not do it.
// http://nz1.php.net/manual/en/function.session-destroy.php
unset($_SESSION);
$this->data = array();
}
}
public function inst_set($name, $val) { public function inst_set($name, $val) {
// Quicker execution path for "."-free names // Quicker execution path for "."-free names
if(strpos($name,'.') === false) { if(strpos($name,'.') === false) {
@ -472,7 +538,11 @@ class Session {
public function inst_save() { public function inst_save() {
if($this->changedData) { if($this->changedData) {
$this->inst_finalize(); $this->inst_finalize();
if(!isset($_SESSION)) Session::start();
if(!isset($_SESSION)) {
$this->inst_start();
}
$this->recursivelyApply($this->changedData, $_SESSION); $this->recursivelyApply($this->changedData, $_SESSION);
} }
} }
@ -529,41 +599,7 @@ class Session {
* @param string $sid Start the session with a specific ID * @param string $sid Start the session with a specific ID
*/ */
public static function start($sid = null) { public static function start($sid = null) {
$path = Config::inst()->get('Session', 'cookie_path'); self::current_session()->inst_start($sid);
if(!$path) $path = Director::baseURL();
$domain = Config::inst()->get('Session', 'cookie_domain');
$secure = Director::is_https() && Config::inst()->get('Session', 'cookie_secure');
$session_path = Config::inst()->get('Session', 'session_store_path');
$timeout = Config::inst()->get('Session', 'timeout');
if(!session_id() && !headers_sent()) {
if($domain) {
session_set_cookie_params($timeout, $path, $domain,
$secure /* secure */, true /* httponly */);
} else {
session_set_cookie_params($timeout, $path, null,
$secure /* secure */, true /* httponly */);
}
// Allow storing the session in a non standard location
if($session_path) session_save_path($session_path);
// If we want a secure cookie for HTTPS, use a seperate session name. This lets us have a
// seperate (less secure) session for non-HTTPS requests
if($secure) session_name('SECSESSID');
// @ is to supress win32 warnings/notices when session wasn't cleaned up properly
// There's nothing we can do about this, because it's an operating system function!
if($sid) session_id($sid);
@session_start();
}
// Modify the timeout behaviour so it's the *inactive* time before the session expires.
// By default it's the total session lifetime
if($timeout && !headers_sent()) {
Cookie::set(session_name(), session_id(), $timeout/86400, $path, $domain ? $domain
: null, $secure, true);
}
} }
/** /**
@ -572,29 +608,7 @@ class Session {
* @param bool $removeCookie If set to TRUE, removes the user's cookie, FALSE does not remove * @param bool $removeCookie If set to TRUE, removes the user's cookie, FALSE does not remove
*/ */
public static function destroy($removeCookie = true) { public static function destroy($removeCookie = true) {
if(session_id()) { self::current_session()->inst_destroy($removeCookie);
if($removeCookie) {
$path = Config::inst()->get('Session', 'cookie_path');
if(!$path) $path = Director::baseURL();
$domain = Config::inst()->get('Session', 'cookie_domain');
$secure = Config::inst()->get('Session', 'cookie_secure');
if($domain) {
Cookie::set(session_name(), '', null, $path, $domain, $secure, true);
}
else {
Cookie::set(session_name(), '', null, $path, null, $secure, true);
}
unset($_COOKIE[session_name()]);
}
session_destroy();
// Clean up the superglobal - session_destroy does not do it.
// http://nz1.php.net/manual/en/function.session-destroy.php
unset($_SESSION);
}
} }
/** /**

View File

@ -201,7 +201,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
DataObject::reset(); DataObject::reset();
if(class_exists('SiteTree')) SiteTree::reset(); if(class_exists('SiteTree')) SiteTree::reset();
Hierarchy::reset(); Hierarchy::reset();
if(Controller::has_curr()) Controller::curr()->setSession(new Session(array())); if(Controller::has_curr()) Controller::curr()->setSession(Injector::inst()->create('Session', array()));
Security::$database_is_ready = null; Security::$database_is_ready = null;
$fixtureFile = static::get_fixture_file(); $fixtureFile = static::get_fixture_file();

View File

@ -23,7 +23,7 @@ class TestSession {
private $lastUrl; private $lastUrl;
public function __construct() { public function __construct() {
$this->session = new Session(array()); $this->session = Injector::inst()->create('Session', array());
$this->controller = new Controller(); $this->controller = new Controller();
$this->controller->setSession($this->session); $this->controller->setSession($this->session);
$this->controller->pushCurrent(); $this->controller->pushCurrent();

View File

@ -18,7 +18,7 @@ URLs. Here is an example from the subsites module:
* Return a session that has a user logged in as an administrator * Return a session that has a user logged in as an administrator
*/ */
public function adminLoggedInSession() { public function adminLoggedInSession() {
return new Session(array( return Injector::inst()->create('Session', array(
'loggedInAs' => $this->idFromFixture('Member', 'admin') 'loggedInAs' => $this->idFromFixture('Member', 'admin')
)); ));
} }

View File

@ -5,7 +5,7 @@ class FakeController extends Controller {
public function __construct() { public function __construct() {
parent::__construct(); parent::__construct();
$session = new Session(isset($_SESSION) ? $_SESSION : null); $session = Injector::inst()->create('Session', isset($_SESSION) ? $_SESSION : array());
$this->setSession($session); $this->setSession($session);
$this->pushCurrent(); $this->pushCurrent();

View File

@ -362,7 +362,7 @@ class RestfulServiceTest_MockRestfulService extends RestfulService {
public function request($subURL = '', $method = "GET", $data = null, $headers = null, $curlOptions = array()) { public function request($subURL = '', $method = "GET", $data = null, $headers = null, $curlOptions = array()) {
if(!$this->session) { if(!$this->session) {
$this->session = new Session(array()); $this->session = Injector::inst()->create('Session', array());
} }
$url = $this->baseURL . $subURL; // Url for the request $url = $this->baseURL . $subURL; // Url for the request

View File

@ -47,7 +47,7 @@ class SessionTest extends SapphireTest {
} }
public function testSettingExistingDoesntClear() { public function testSettingExistingDoesntClear() {
$s = new Session(array('something' => array('does' => 'exist'))); $s = Injector::inst()->create('Session', array('something' => array('does' => 'exist')));
$s->inst_set('something.does', 'exist'); $s->inst_set('something.does', 'exist');
$result = $s->inst_changedData(); $result = $s->inst_changedData();
@ -59,7 +59,7 @@ class SessionTest extends SapphireTest {
* Check that changedData isn't populated with junk when clearing non-existent entries. * Check that changedData isn't populated with junk when clearing non-existent entries.
*/ */
public function testClearElementThatDoesntExist() { public function testClearElementThatDoesntExist() {
$s = new Session(array('something' => array('does' => 'exist'))); $s = Injector::inst()->create('Session', array('something' => array('does' => 'exist')));
$s->inst_clear('something.doesnt.exist'); $s->inst_clear('something.doesnt.exist');
$result = $s->inst_changedData(); $result = $s->inst_changedData();
@ -77,7 +77,7 @@ class SessionTest extends SapphireTest {
* Check that changedData is populated with clearing data. * Check that changedData is populated with clearing data.
*/ */
public function testClearElementThatDoesExist() { public function testClearElementThatDoesExist() {
$s = new Session(array('something' => array('does' => 'exist'))); $s = Injector::inst()->create('Session', array('something' => array('does' => 'exist')));
$s->inst_clear('something.does'); $s->inst_clear('something.does');
$result = $s->inst_changedData(); $result = $s->inst_changedData();
@ -97,7 +97,7 @@ class SessionTest extends SapphireTest {
$_SERVER['HTTP_USER_AGENT'] = 'Test Agent'; $_SERVER['HTTP_USER_AGENT'] = 'Test Agent';
// Generate our session // Generate our session
$s = new Session(array()); $s = Injector::inst()->create('Session', array());
$s->inst_set('val', 123); $s->inst_set('val', 123);
$s->inst_finalize(); $s->inst_finalize();
@ -105,7 +105,7 @@ class SessionTest extends SapphireTest {
$_SERVER['HTTP_USER_AGENT'] = 'Fake Agent'; $_SERVER['HTTP_USER_AGENT'] = 'Fake Agent';
// Verify the new session reset our values // Verify the new session reset our values
$s2 = new Session($s); $s2 = Injector::inst()->create('Session', $s);
$this->assertNotEquals($s2->inst_get('val'), 123); $this->assertNotEquals($s2->inst_get('val'), 123);
} }
} }

View File

@ -537,7 +537,7 @@ class VersionedTest extends SapphireTest {
* Tests that reading mode persists between requests * Tests that reading mode persists between requests
*/ */
public function testReadingPersistent() { public function testReadingPersistent() {
$session = new Session(array()); $session = Injector::inst()->create('Session', array());
// Set to stage // Set to stage
Director::test('/?stage=Stage', null, $session); Director::test('/?stage=Stage', null, $session);
@ -568,7 +568,7 @@ class VersionedTest extends SapphireTest {
); );
// Test that session doesn't redundantly store the default stage if it doesn't need to // Test that session doesn't redundantly store the default stage if it doesn't need to
$session2 = new Session(array()); $session2 = Injector::inst()->create('Session', array());
Director::test('/', null, $session2); Director::test('/', null, $session2);
$this->assertEmpty($session2->inst_changedData()); $this->assertEmpty($session2->inst_changedData());
Director::test('/?stage=Live', null, $session2); Director::test('/?stage=Live', null, $session2);