Skip to content

Commit f5c7306

Browse files
committed
PHPORM-310 Create dedicated session handler
1 parent c3bab3c commit f5c7306

File tree

3 files changed

+147
-12
lines changed

3 files changed

+147
-12
lines changed

src/MongoDBServiceProvider.php

+5-7
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
use MongoDB\Laravel\Eloquent\Model;
2424
use MongoDB\Laravel\Queue\MongoConnector;
2525
use MongoDB\Laravel\Scout\ScoutEngine;
26+
use MongoDB\Laravel\Session\MongoDbSessionHandler;
2627
use RuntimeException;
27-
use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler;
2828

2929
use function assert;
3030
use function class_exists;
@@ -67,12 +67,10 @@ public function register()
6767
assert($connection instanceof Connection, new InvalidArgumentException(sprintf('The database connection "%s" used for the session does not use the "mongodb" driver.', $connectionName)));
6868

6969
return new MongoDbSessionHandler(
70-
$connection->getClient(),
71-
$app->config->get('session.options', []) + [
72-
'database' => $connection->getDatabaseName(),
73-
'collection' => $app->config->get('session.table') ?: 'sessions',
74-
'ttl' => $app->config->get('session.lifetime'),
75-
],
70+
$connection,
71+
$app->config->get('session.table', 'sessions'),
72+
$app->config->get('session.lifetime'),
73+
$app,
7674
);
7775
});
7876
});

src/Session/MongoDbSessionHandler.php

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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 MongoDB\Laravel\Session;
13+
14+
use Illuminate\Session\DatabaseSessionHandler;
15+
use MongoDB\BSON\Binary;
16+
use MongoDB\BSON\UTCDateTime;
17+
use MongoDB\Collection;
18+
use TypeError;
19+
20+
use function assert;
21+
use function get_debug_type;
22+
use function is_string;
23+
use function sprintf;
24+
use function tap;
25+
use function time;
26+
27+
/**
28+
* Session handler using the MongoDB driver extension.
29+
*/
30+
final class MongoDbSessionHandler extends DatabaseSessionHandler
31+
{
32+
private Collection $collection;
33+
34+
public function close(): bool
35+
{
36+
return true;
37+
}
38+
39+
public function gc($lifetime): int
40+
{
41+
$result = $this->getCollection()->deleteMany(['last_activity' => ['$lt' => $this->getUTCDateTime(-$lifetime)]]);
42+
43+
return $result->getDeletedCount() ?? 0;
44+
}
45+
46+
public function destroy($sessionId): bool
47+
{
48+
$this->getCollection()->deleteOne(['_id' => (string) $sessionId]);
49+
50+
return true;
51+
}
52+
53+
public function read($sessionId): string|false
54+
{
55+
$result = $this->getCollection()->findOne(
56+
['_id' => (string) $sessionId, 'expires_at' => ['$gte' => $this->getUTCDateTime()]],
57+
[
58+
'projection' => ['_id' => false, 'payload' => true],
59+
'typeMap' => ['root' => 'bson'],
60+
],
61+
);
62+
63+
return $result ? (string) $result->payload : false;
64+
}
65+
66+
public function write($sessionId, $data): bool
67+
{
68+
$payload = $this->getDefaultPayload($data);
69+
70+
$this->getCollection()->replaceOne(
71+
['_id' => (string) $sessionId],
72+
$payload,
73+
['upsert' => true],
74+
);
75+
76+
return true;
77+
}
78+
79+
/** Creates a TTL index that automatically deletes expired objects. */
80+
public function createTTLIndex(): void
81+
{
82+
$this->collection->createIndex(
83+
// UTCDateTime field that holds the expiration date
84+
['expires_at' => 1],
85+
// Delay to remove items after expiration
86+
['expireAfterSeconds' => 0],
87+
);
88+
}
89+
90+
protected function getDefaultPayload($data): array
91+
{
92+
$payload = [
93+
'payload' => new Binary($data),
94+
'last_activity' => $this->getUTCDateTime(),
95+
'expires_at' => $this->getUTCDateTime($this->minutes * 60),
96+
];
97+
98+
if (! $this->container) {
99+
return $payload;
100+
}
101+
102+
return tap($payload, function (&$payload) {
103+
$this->addUserInformation($payload)
104+
->addRequestInformation($payload);
105+
});
106+
}
107+
108+
private function getCollection(): Collection
109+
{
110+
return $this->collection ??= $this->connection->getCollection($this->table);
111+
}
112+
113+
private function getUTCDateTime(int $additionalSeconds = 0): UTCDateTime
114+
{
115+
return new UTCDateTime((time() + $additionalSeconds * 60) * 1000);
116+
}
117+
}

tests/SessionTest.php

+25-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
use Illuminate\Session\DatabaseSessionHandler;
66
use Illuminate\Session\SessionManager;
77
use Illuminate\Support\Facades\DB;
8-
use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler;
8+
use MongoDB\Laravel\Session\MongoDbSessionHandler;
9+
use PHPUnit\Framework\Attributes\TestWith;
10+
use SessionHandlerInterface;
911

1012
class SessionTest extends TestCase
1113
{
@@ -16,21 +18,31 @@ protected function tearDown(): void
1618
parent::tearDown();
1719
}
1820

19-
public function testDatabaseSessionHandlerCompatibility()
21+
/** @param class-string<SessionHandlerInterface> $class */
22+
#[TestWith([DatabaseSessionHandler::class])]
23+
#[TestWith([MongoDbSessionHandler::class])]
24+
public function testSessionHandlerFunctionality(string $class)
2025
{
21-
$sessionId = '123';
22-
23-
$handler = new DatabaseSessionHandler(
26+
$handler = new $class(
2427
$this->app['db']->connection('mongodb'),
2528
'sessions',
2629
10,
2730
);
2831

32+
$sessionId = '123';
33+
2934
$handler->write($sessionId, 'foo');
3035
$this->assertEquals('foo', $handler->read($sessionId));
3136

3237
$handler->write($sessionId, 'bar');
3338
$this->assertEquals('bar', $handler->read($sessionId));
39+
40+
$handler->destroy($sessionId);
41+
$this->assertEmpty($handler->read($sessionId));
42+
43+
$handler->write($sessionId, 'bar');
44+
$handler->gc(-1);
45+
$this->assertEmpty($handler->read($sessionId));
3446
}
3547

3648
public function testDatabaseSessionHandlerRegistration()
@@ -70,5 +82,13 @@ private function assertSessionCanStoreInMongoDB(SessionManager $session): void
7082

7183
self::assertIsObject($data);
7284
self::assertSame($session->getId(), $data->_id);
85+
86+
$session->remove('foo');
87+
$data = DB::connection('mongodb')
88+
->getCollection('sessions')
89+
->findOne(['_id' => $session->getId()]);
90+
91+
self::assertIsObject($data);
92+
self::assertSame($session->getId(), $data->_id);
7393
}
7494
}

0 commit comments

Comments
 (0)