Skip to content

Commit f0407aa

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

File tree

3 files changed

+142
-12
lines changed

3 files changed

+142
-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

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

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)