Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 94d4fb2

Browse files
authored
PHPORM-310 Create dedicated session handler (#3348)
1 parent 251d6e2 commit 94d4fb2

File tree

5 files changed

+149
-13
lines changed

5 files changed

+149
-13
lines changed

‎phpstan.neon.dist

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ parameters:
1111

1212
editorUrl: 'phpstorm://open?file=%%file%%&line=%%line%%'
1313

14+
universalObjectCratesClasses:
15+
- MongoDB\BSON\Document
16+
1417
ignoreErrors:
1518
- '#Unsafe usage of new static#'
1619
- '#Call to an undefined method [a-zA-Z0-9\\_\<\>\(\)]+::[a-zA-Z]+\(\)#'

‎src/MongoDBServiceProvider.php

Lines changed: 5 additions & 7 deletions
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

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

‎tests/Query/BuilderTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -868,7 +868,7 @@ function (Builder $builder) {
868868
[],
869869
],
870870
],
871-
fn (Builder $builder) => $builder->whereDate('created_at', '=', new DateTimeImmutable('2018年09月30日 15:00:00 +02:00')),
871+
fn (Builder $builder) => $builder->whereDate('created_at', '=', new DateTimeImmutable('2018年09月30日 15:00:00 +00:00')),
872872
];
873873

874874
yield 'where date !=' => [

‎tests/SessionTest.php

Lines changed: 25 additions & 5 deletions
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)

AltStyle によって変換されたページ (->オリジナル) /