Skip to content

Commit 959f902

Browse files
authored
Merge pull request #8 from xp-forge/refactor/document-structure
Refactor document substructure
2 parents 48b3207 + b90e86e commit 959f902

5 files changed

Lines changed: 107 additions & 20 deletions

File tree

src/main/php/web/session/InMongoDB.class.php

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,13 @@ public function gc() {
7373
*/
7474
public function create() {
7575
$now= time();
76-
$this->collection->insert($values= new Document(['_created' => new Date($now)]));
76+
$this->collection->insert($doc= new Document(['_created' => new Date($now), 'values' => (object)[]]));
7777

7878
// Clean up expired sessions while we're here.
7979
$this->gc();
8080

81-
return new Session($this, $this->collection, $values, $now + $this->duration, true);
81+
$doc['values']= [];
82+
return new Session($this, $this->collection, $doc, $now + $this->duration, true);
8283
}
8384

8485
/**
@@ -89,12 +90,24 @@ public function create() {
8990
*/
9091
public function open($id) {
9192
$oid= new ObjectId($id);
92-
if ($values= $this->collection->find($oid)->first()) {
93+
if ($doc= $this->collection->find($oid)->first()) {
9394

9495
// Check for expired sessions not already taken care by TTL...
95-
$created= $values['_created'] instanceof Date ? $values['_created']->getTime() : $values['_created'];
96+
$created= $doc['_created'] instanceof Date ? $doc['_created']->getTime() : $doc['_created'];
9697
$timeout= $created + $this->duration;
97-
if ($timeout > time()) return new Session($this, $this->collection, $values, $timeout, false);
98+
if ($timeout > time()) {
99+
100+
// Migrate old session layout
101+
if (!isset($doc['values'])) {
102+
$values= [];
103+
foreach ($doc->properties() as $key => $value) {
104+
'_' === $key[0] || $values[strtr($key, Session::ENCODE)]= $value;
105+
}
106+
$doc['values']= $values;
107+
}
108+
109+
return new Session($this, $this->collection, $doc, $timeout, false);
110+
}
98111

99112
// ...and delete them
100113
$this->collection->delete($oid);

src/main/php/web/session/mongo/Session.class.php

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use web\session\SessionInvalid;
66

77
class Session implements ISession {
8+
const ENCODE= ['%' => '%25', '.' => '%2e'];
89
private $sessions, $collection, $document, $timeout, $new;
910

1011
/**
@@ -49,8 +50,8 @@ public function keys() {
4950
}
5051

5152
$r= [];
52-
foreach ($this->document->properties() as $key => $_) {
53-
'_' === $key[0] || $r[]= $key;
53+
foreach ($this->document['values'] as $key => $_) {
54+
$r[]= rawurldecode($key);
5455
}
5556
return $r;
5657
}
@@ -59,14 +60,16 @@ public function keys() {
5960
* Update document in MongoDB with the given operations. Used by
6061
* `register()` and `remove()`.
6162
*
62-
* @param [:var] $operations
63+
* @param string $operation
64+
* @param string $name
65+
* @param var $value
6366
* @return com.mongodb.Document
6467
* @throws web.session.SessionInvalid
6568
*/
66-
private function update($operations) {
69+
private function update($operation, $name, $value) {
6770
$arguments= [
6871
'query' => ['_id' => $this->document->id()],
69-
'update' => $operations,
72+
'update' => [$operation => ['values.'.strtr($name, self::ENCODE) => $value]],
7073
'new' => true,
7174
'upsert' => false,
7275
];
@@ -94,7 +97,7 @@ public function register($name, $value) {
9497
throw new SessionInvalid($this->id());
9598
}
9699

97-
$this->document= $this->update(['$set' => [$name => $value]]);
100+
$this->document= $this->update('$set', $name, $value);
98101
}
99102

100103
/**
@@ -110,7 +113,7 @@ public function value($name, $default= null) {
110113
throw new SessionInvalid($this->id());
111114
}
112115

113-
return $this->document[$name] ?? $default;
116+
return $this->document['values'][strtr($name, self::ENCODE)] ?? $default;
114117
}
115118

116119
/**
@@ -125,7 +128,7 @@ public function remove($name) {
125128
throw new SessionInvalid($this->id());
126129
}
127130

128-
$this->document= $this->update(['$unset' => [$name => '']]);
131+
$this->document= $this->update('$unset', $name, '');
129132
}
130133

131134
/**

src/test/php/web/session/mongo/unittest/CollectionV1.class.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,22 @@ public function run($name, array $params= [], $method= 'write', Session $session
5959
switch (key($params['update'])) {
6060
case '$set':
6161
foreach ($params['update']['$set'] as $name => $value) {
62-
$result[$name]= $value;
62+
$ptr= &$result;
63+
foreach (explode('.', $name) as $segment) {
64+
$ptr= &$ptr[$segment];
65+
}
66+
$ptr= $value;
6367
}
6468
break;
6569

6670
case '$unset':
6771
foreach ($params['update']['$unset'] as $name => $_) {
68-
unset($result[$name]);
72+
$ptr= &$result;
73+
$segments= explode('.', $name);
74+
for ($i= 0; $i < sizeof($segments) - 1; $i++) {
75+
$ptr= &$ptr[$segments[$i]];
76+
}
77+
unset($ptr[$segments[$i]]);
6978
}
7079
break;
7180
}

src/test/php/web/session/mongo/unittest/CollectionV2.class.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,22 @@ public function run($name, array $params= [], $method= 'write', Options... $opti
5959
switch (key($params['update'])) {
6060
case '$set':
6161
foreach ($params['update']['$set'] as $name => $value) {
62-
$result[$name]= $value;
62+
$ptr= &$result;
63+
foreach (explode('.', $name) as $segment) {
64+
$ptr= &$ptr[$segment];
65+
}
66+
$ptr= $value;
6367
}
6468
break;
6569

6670
case '$unset':
6771
foreach ($params['update']['$unset'] as $name => $_) {
68-
unset($result[$name]);
72+
$ptr= &$result;
73+
$segments= explode('.', $name);
74+
for ($i= 0; $i < sizeof($segments) - 1; $i++) {
75+
$ptr= &$ptr[$segments[$i]];
76+
}
77+
unset($ptr[$segments[$i]]);
6978
}
7079
break;
7180
}

src/test/php/web/session/mongo/unittest/MongoTest.class.php

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
use com\mongodb\{Collection, Document, ObjectId, Options};
44
use lang\IllegalStateException;
5-
use test\{Assert, Expect, Test};
5+
use test\{Assert, Expect, Test, Values};
66
use util\{Date, Dates};
77
use web\session\{ISession, InMongoDB, SessionInvalid};
88

@@ -34,6 +34,7 @@ public function open_session() {
3434
$collection= $this->collection([new Document([
3535
'_id' => $id,
3636
'_created' => Date::now(),
37+
'values' => [],
3738
])]);
3839

3940
$sessions= new InMongoDB($collection);
@@ -49,6 +50,7 @@ public function open_expired_session() {
4950
$collection= $this->collection([new Document([
5051
'_id' => $id,
5152
'_created' => Dates::subtract(Date::now(), 3601),
53+
'values' => [],
5254
])]);
5355

5456
$sessions= (new InMongoDB($collection))->lasting(3600);
@@ -71,6 +73,7 @@ public function open_old_session() {
7173

7274
Assert::instance(ISession::class, $session);
7375
Assert::true($collection->present($session->id()));
76+
Assert::equals([], $session->keys());
7477
}
7578

7679
#[Test]
@@ -79,7 +82,7 @@ public function value() {
7982
$collection= $this->collection([new Document([
8083
'_id' => $id,
8184
'_created' => Date::now(),
82-
'user' => 'test',
85+
'values' => ['user' => 'test'],
8386
])]);
8487

8588
$sessions= new InMongoDB($collection);
@@ -88,12 +91,44 @@ public function value() {
8891
Assert::equals('test', $session->value('user'));
8992
}
9093

94+
#[Test]
95+
public function values_migrated_from_old_session_layout() {
96+
$id= ObjectId::create();
97+
$collection= $this->collection([new Document([
98+
'_id' => $id,
99+
'_created' => Date::now(),
100+
'user' => 'test',
101+
'%host' => 'example.com',
102+
])]);
103+
104+
$sessions= new InMongoDB($collection);
105+
$session= $sessions->open($id->string());
106+
107+
Assert::equals(['test', 'example.com'], [$session->value('user'), $session->value('%host')]);
108+
}
109+
110+
#[Test]
111+
public function keys() {
112+
$id= ObjectId::create();
113+
$collection= $this->collection([new Document([
114+
'_id' => $id,
115+
'_created' => Date::now(),
116+
'values' => ['user' => 'test'],
117+
])]);
118+
119+
$sessions= new InMongoDB($collection);
120+
$session= $sessions->open($id->string());
121+
122+
Assert::equals(['user'], $session->keys());
123+
}
124+
91125
#[Test]
92126
public function register() {
93127
$id= ObjectId::create();
94128
$collection= $this->collection([new Document([
95129
'_id' => $id,
96130
'_created' => Date::now(),
131+
'values' => [],
97132
])]);
98133

99134
$sessions= new InMongoDB($collection);
@@ -109,7 +144,7 @@ public function remove() {
109144
$collection= $this->collection([new Document([
110145
'_id' => $id,
111146
'_created' => Date::now(),
112-
'user' => 'test',
147+
'values' => ['user' => 'test'],
113148
])]);
114149

115150
$sessions= new InMongoDB($collection);
@@ -125,6 +160,7 @@ public function invalid_session() {
125160
$collection= $this->collection([new Document([
126161
'_id' => $id,
127162
'_created' => Date::now(),
163+
'values' => [],
128164
])]);
129165

130166
$sessions= new InMongoDB($collection);
@@ -147,6 +183,7 @@ public function gc_returns_number_of_expired_sessions() {
147183
$collection= $this->collection([new Document([
148184
'_id' => $id,
149185
'_created' => Dates::subtract(Date::now(), $duration + 1),
186+
'values' => [],
150187
])]);
151188

152189
$sessions= (new InMongoDB($collection))->lasting($duration);
@@ -172,9 +209,25 @@ public function gc_is_a_noop_when_ttl_indexes_are_used() {
172209
$collection= $this->collection([new Document([
173210
'_id' => $id,
174211
'_created' => Dates::subtract(Date::now(), $duration + 1),
212+
'values' => [],
175213
])]);
176214

177215
$sessions= (new InMongoDB($collection, InMongoDB::USING_TTL));
178216
Assert::equals(0, $sessions->gc());
179217
}
218+
219+
#[Test, Values([['user.name', 'user%2ename'], ['%user', '%25user'], ['%5fuser', '%255fuser']])]
220+
public function special_characters_are_escaped($key, $stored) {
221+
$id= ObjectId::create();
222+
$collection= $this->collection([new Document([
223+
'_id' => $id,
224+
'_created' => Date::now(),
225+
'values' => [$stored => 'test'],
226+
])]);
227+
228+
$sessions= new InMongoDB($collection);
229+
$session= $sessions->open($id->string());
230+
231+
Assert::equals('test', $session->value($key));
232+
}
180233
}

0 commit comments

Comments
 (0)