8000 feature #26096 [HttpFoundation] Added a migrating session handler (ro… · symfony/symfony@0f4c0e9 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0f4c0e9

Browse files
committed
feature #26096 [HttpFoundation] Added a migrating session handler (rossmotley)
This PR was squashed before being merged into the 4.1-dev branch (closes #26096). Discussion ---------- [HttpFoundation] Added a migrating session handler | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | | License | MIT | Doc PR | symfony/symfony-docs#9496 - [x] gather feedback for my changes - [x] submit changes to the documentation - [x] update the changelog 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 an additional handler to enable a migration workflow like: * Switch to migrating handler, with your new handler as the 'write only' one. The old handler behaves as usual and sessions get written to the new 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 instead, so the sessions will now be read from the new handler. This step allows easier rollbacks. * After verifying everything, switch from the migrating handler to the new handler Commits ------- 3acd548 [HttpFoundation] Added a migrating session handler
2 parents 47e2bd3 + 3acd548 commit 0f4c0e9

File tree

3 files changed

+239
-0
lines changed

3 files changed

+239
-0
lines changed

src/Symfony/Component/HttpFoundation/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ CHANGELOG
1515
* added `CannotWriteFileException`, `ExtensionFileException`, `FormSizeFileException`,
1616
`IniSizeFileException`, `NoFileException`, `NoTmpDirFileException`, `PartialFileException` to
1717
handle failed `UploadedFile`.
18+
* added `MigratingSessionHandler` for migrating between two session handlers without losing sessions
1819

1920
4.0.0
2021
-----
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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 <ross.motley@amara.com>
21+
* @author Oliver Radwell <oliver.radwell@amara.com>
22+
*/
23+
class MigratingSessionHandler implements \SessionHandlerInterface
24+
{
25+
private $currentHandler;
26+
private $writeOnlyHandler;
27+
28+
public function __construct(\SessionHandlerInterface $currentHandler, \SessionHandlerInterface $writeOnlyHandler)
29+
{
30+
$this->currentHandler = $currentHandler;
31+
$this->writeOnlyHandler = $writeOnlyHandler;
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function close()
38+
{
39+
$result = $this->currentHandler->close();
40+
$this->writeOnlyHandler->close();
41+
42+
return $result;
43+
}
44+
45+
/**
46+
* {@inheritdoc}
47+
*/
48+
public function destroy($sessionId)
49+
{
50+
$result = $this->currentHandler->destroy($sessionId);
51+
$this->writeOnlyHandler->destroy($sessionId);
52+
53+
return $result;
54+
}
55+
56+
/**
57+
* {@inheritdoc}
58+
*/
59+
public function gc($maxlifetime)
60+
{
61+
$result = $this->currentHandler->gc($maxlifetime);
62+
$this->writeOnlyHandler->gc($maxlifetime);
63+
64+
return $result;
65+
}
66+
67+
/**
68+
* {@inheritdoc}
69+
*/
70+
public function open($savePath, $sessionId)
71+
{
72+
$result = $this->currentHandler->open($savePath, $sessionId);
73+
$this->writeOnlyHandler->open($savePath, $sessionId);
74+
75+
return $result;
76+
}
77+
78+
/**
79+
* {@inheritdoc}
80+
*/
81+
public function read($sessionId)
82+
{
83+
// No reading from new handler until switch-over
84+
return $this->currentHandler->read($sessionId);
85+
}
86+
87+
/**
88+
* {@inheritdoc}
89+
*/
90+
public function write($sessionId, $sessionData)
91+
{
92+
$result = $this->currentHandler->write($sessionId, $sessionData);
93+
$this->writeOnlyHandler->write($sessionId, $sessionData);
94+
95+
return $result;
96+
}
97+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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+
protected function setUp()
24+
{
25+
$this->currentHandler = $this->createMock(\SessionHandlerInterface::class);
26+
$this->writeOnlyHandler = $this->createMock(\SessionHandlerInterface::class);
27+
28+
$this->dualHandler = new MigratingSessionHandler($this->currentHandler, $this->writeOnlyHandler);
29+
}
30+
31+
public function testCloses()
32+
{
33+
$this->currentHandler->expects($this->once())
34+
->method('close')
35+
->will($this->returnValue(true));
36+
37+
$this->writeOnlyHandler->expects($this->once())
38+
->method('close')
39+
->will($this->returnValue(false));
40+
41+
$result = $this->dualHandler->close();
42+
43+
$this->assertTrue($result);
44+
}
45+
46+
public function testDestroys()
47+
{
48+
$sessionId = 'xyz';
49+
50+
$this->currentHandler->expects($this->once())
51+
->method('destroy')
52+
->with($sessionId)
53+
->will($this->returnValue(true));
54+
55+
$this->writeOnlyHandler->expects($this->once())
56+
->method('destroy')
57+
->with($sessionId)
58+
->will($this->returnValue(false));
59+
60+
$result = $this->dualHandler->destroy($sessionId);
61+
62+
$this->assertTrue($result);
63+
}
64+
65+
public function testGc()
66+
{
67+
$maxlifetime = 357;
68+
69+
$this->currentHandler->expects($this->once())
70+
->method('gc')
71+
->with($maxlifetime)
72+
->will($this->returnValue(true));
73+
74+
$this->writeOnlyHandler->expects($this->once())
75+
->method('gc')
76+
->with($maxlifetime)
77+
->will($this->returnValue(false));
78+
79+
$result = $this->dualHandler->gc($maxlifetime);
80+
$this->assertTrue($result);
81+
}
82+
83+
public function testOpens()
84+
{
85+
$savePath = '/path/to/save/location';
86+
$sessionId = 'xyz';
87+
88+
$this->currentHandler->expects($this->once())
89+
->method('open')
90+
->with($savePath, $sessionId)
91+
->will($this->returnValue(true));
92+
93+
$this->writeOnlyHandler->expects($this->once())
94+
->method('open')
95+
->with($savePath, $sessionId)
96+
->will($this->returnValue(false));
97+
98+
$result = $this->dualHandler->open($savePath, $sessionId);
99+
100+
$this->assertTrue($result);
101+
}
102+
103+
public function testReads()
104+
{
105+
$sessionId = 'xyz';
106+
$readValue = 'something';
107+
108+
$this->currentHandler->expects($this->once())
109+
->method('read')
110+
->with($sessionId)
111+
->will($this->returnValue($readValue));
112+
113+
$this->writeOnlyHandler->expects($this->never())
114+
->method('read')
115+
->with($this->any());
116+
117+
$result = $this->dualHandler->read($sessionId);
118+
119+
$this->assertEquals($readValue, $result);
120+
}
121+
122+
public function testWrites()
123+
{
124+
$sessionId = 'xyz';
125+
$data = 'my-serialized-data';
126+
127+
$this->currentHandler->expects($this->once())
128+
->method('write')
129+
->with($sessionId, $data)
130+
->will($this->returnValue(true));
131+
CE7 132+
$this->writeOnlyHandler->expects($this->once())
133+
->method('write')
134+
->with($sessionId, $data)
135+
->will($this->returnValue(false));
136+
137+
$result = $this->dualHandler->write($sessionId, $data);
138+
139+
$this->assertTrue($result);
140+
}
141+
}

0 commit comments

Comments
 (0)
0