Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 68bbd0e

Browse files
committed
[HttpFoundation] Added a migrating session handler
When migrating to a new session handler on a live system, it's useful to be able to do it with no loss of session data. This migrating handler allows the sessions to be written to two handlers to enable a migration workflow like: * Switch to migrating handler, with your new handler as the 'write only' one. * After verifying the data in the new handler (and after the session gc period), switch the migrating handler to use your old handler as the 'write only' one (this step allows easier rollbacks). * After verifying, switch to the new handler
1 parent 4a602ca commit 68bbd0e

File tree

3 files changed

+250
-0
lines changed

3 files changed

+250
-0
lines changed

src/Symfony/Component/HttpFoundation/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ CHANGELOG
1212
`*` and `*/*` default values (if they are present in the Accept HTTP header)
1313
when looking for items.
1414
* deprecated `Request::getSession()` when no session has been set. Use `Request::hasSession()` instead.
15+
* added `MigratingSessionHandler` for migrating between between two session handlers without losing sessions
1516

1617
4.0.0
1718
-----
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
13+
14+
/**
15+
* Migrating session handler for migrating from one handler to another. It reads
16+
* from the current handler and writes both the current and new ones.
17+
*
18+
* It ignores errors from the new handler.
19+
*
20+
* @author Ross Motley <[email protected]>
21+
* @author Oliver Radwell <[email protected]>
22+
*/
23+
class MigratingSessionHandler implements \SessionHandlerInterface
24+
{
25+
private $currentHandler;
26+
private $writeOnlyHandler;
27+
28+
/**
29+
* @param \SessionHandlerInterface $currentHandler
30+
* @param \SessionHandlerInterface $newHandler
31+
*/
32+
public function __construct(\SessionHandlerInterface $currentHandler, \SessionHandlerInterface $newHandler)
33+
{
34+
$this->currentHandler = $currentHandler;
35+
$this->writeOnlyHandler = $newHandler;
36+
}
37+
38+
/**
39+
* {@inheritdoc}
40+
*/
41+
public function close()
42+
{
43+
$result = $this->currentHandler->close();
44+
$this->writeOnlyHandler->close();
45+
46+
return $result;
47+
}
48+
49+
/**
50+
* {@inheritdoc}
51+
*/
52+
public function destroy($session_id)
53+
{
54+
$result = $this->currentHandler->destroy($session_id);
55+
$this->writeOnlyHandler->destroy($session_id);
56+
57+
return $result;
58+
}
59+
60+
/**
61+
* {@inheritdoc}
62+
*/
63+
public function gc($maxlifetime)
64+
{
65+
$result = $this->currentHandler->gc($maxlifetime);
66+
$this->writeOnlyHandler->gc($maxlifetime);
67+
68+
return $result;
69+
}
70+
71+
/**
72+
* {@inheritdoc}
73+
*/
74+
public function open($save_path, $session_id)
75+
{
76+
$result = $this->currentHandler->open($save_path, $session_id);
77+
$this->writeOnlyHandler->open($save_path, $session_id);
78+
79+
return $result;
80+
}
81+
82+
/**
83+
* {@inheritdoc}
84+
*/
85+
public function read($session_id)
86+
{
87+
// No reading from new handler until switch-over
88+
$result = $this->currentHandler->read($session_id);
89+
90+
return $result;
91+
}
92+
93+
/**
94+
* {@inheritdoc}
95+
*/
96+
public function write($session_id, $session_data)
97+
{
98+
$result = $this->currentHandler->write($session_id, $session_data);
99+
$this->writeOnlyHandler->write($session_id, $session_data);
100+
101+
return $result;
102+
}
103+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpFoundation\Session\Storage\Handler\MigratingSessionHandler;
16+
17+
class MigratingSessionHandlerTest extends TestCase
18+
{
19+
private $dualHandler;
20+
private $currentHandler;
21+
private $writeOnlyHandler;
22+
23+
public function setUp()
24+
{
25+
$this->currentHandler = $this->getMockBuilder(\SessionHandlerInterface::class)
26+
->disableOriginalConstructor()
27+
->getMock();
28+
29+
$this->writeOnlyHandler = $this->getMockBuilder(\SessionHandlerInterface::class)
30+
->disableOriginalConstructor()
31+
->getMock();
32+
33+
$this->dualHandler = new MigratingSessionHandler($this->currentHandler, $this->writeOnlyHandler);
34+
}
35+
36+
public function testCloses()
37+
{
38+
$this->currentHandler->expects($this->once())
39+
->method('close')
40+
->will($this->returnValue(true));
41+
42+
$this->writeOnlyHandler->expects($this->once())
43+
->method('close')
44+
->will($this->returnValue(false));
45+
46+
$result = $this->dualHandler->close();
47+
48+
$this->assertTrue($result);
49+
}
50+
51+
public function testDestroys()
52+
{
53+
$sessionId = 'xyz';
54+
55+
$this->currentHandler->expects($this->once())
56+
->method('destroy')
57+
->with($sessionId)
58+
->will($this->returnValue(true));
59+
60+
$this->writeOnlyHandler->expects($this->once())
61+
->method('destroy')
62+
->with($sessionId)
63+
->will($this->returnValue(false));
64+
65+
$result = $this->dualHandler->destroy($sessionId);
66+
67+
$this->assertTrue($result);
68+
}
69+
70+
public function testGc()
71+
{
72+
$maxlifetime = 357;
73+
74+
$this->currentHandler->expects($this->once())
75+
->method('gc')
76+
->with($maxlifetime)
77+
->will($this->returnValue(true));
78+
79+
$this->writeOnlyHandler->expects($this->once())
80+
->method('gc')
81+
->with($maxlifetime)
82+
->will($this->returnValue(false));
83+
84+
$result = $this->dualHandler->gc($maxlifetime);
85+
$this->assertTrue($result);
86+
}
87+
88+
public function testOpens()
89+
{
90+
$savePath = '/path/to/save/location';
91+
$sessionId = 'xyz';
92+
93+
$this->currentHandler->expects($this->once())
94+
->method('open')
95+
->with($savePath, $sessionId)
96+
->will($this->returnValue(true));
97+
98+
$this->writeOnlyHandler->expects($this->once())
99+
->method('open')
100+
->with($savePath, $sessionId)
101+
->will($this->returnValue(false));
102+
103+
$result = $this->dualHandler->open($savePath, $sessionId);
104+
105+
$this->assertTrue($result);
106+
}
107+
108+
public function testReads()
109+
{
110+
$sessionId = 'xyz';
111+
$readValue = 'something';
112+
113+
$this->currentHandler->expects($this->once())
114+
->method('read')
115+
->with($sessionId)
116+
->will($this->returnValue($readValue));
117+
118+
$this->writeOnlyHandler->expects($this->never())
119+
->method('read')
120+
->with($this->any());
121+
122+
$result = $this->dualHandler->read($sessionId);
123+
124+
$this->assertEquals($readValue, $result);
125+
}
126+
127+
public function testWrites()
128+
{
129+
$sessionId = 'xyz';
130+
$data = array('zfa' => 'hwq');
131+
132+
$this->currentHandler->expects($this->once())
133+
->method('write')
134+
->with($sessionId, $data)
135+
->will($this->returnValue(true));
136+
137+
$this->writeOnlyHandler->expects($this->once())
138+
->method('write')
139+
->with($sessionId, $data)
140+
->will($this->returnValue(false));
141+
142+
$result = $this->dualHandler->write($sessionId, $data);
143+
144+
$this->assertTrue($result);
145+
}
146+
}

0 commit comments

Comments
 (0)