8000 [HttpFoundation][Lock] Makes MongoDB adapters usable with `ext-mongodb` directly by GromNaN · Pull Request #52336 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[HttpFoundation][Lock] Makes MongoDB adapters usable with ext-mongodb directly #52336

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ jobs:
echo COMPOSER_ROOT_VERSION=$COMPOSER_ROOT_VERSION >> $GITHUB_ENV

echo "::group::composer update"
composer require --dev --no-update mongodb/mongodb
composer update --no-progress --ansi
echo "::endgroup::"

Expand Down
5 changes: 0 additions & 5 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,6 @@ jobs:
exit 0
fi

(cd src/Symfony/Component/HttpFoundation; cp composer.json composer.bak; composer require --dev --no-update mongodb/mongodb)
(cd src/Symfony/Component/Lock; cp composer.json composer.bak; composer require --dev --no-update mongodb/mongodb)

# matrix.mode = high-deps
echo "$COMPONENTS" | xargs -n1 | parallel -j +3 "_run_tests {} 'cd {} && $COMPOSER_UP && $PHPUNIT$LEGACY'" || X=1

Expand All @@ -211,8 +208,6 @@ jobs:
git fetch --depth=2 origin $SYMFONY_VERSION
git checkout -m FETCH_HEAD
PATCHED_COMPONENTS=$(echo "$PATCHED_COMPONENTS" | xargs dirname | xargs -n1 -I{} bash -c "[ -e '{}/phpunit.xml.dist' ] && echo '{}'" | sort || true)
(cd src/Symfony/Component/HttpFoundation; composer require --dev --no-update mongodb/mongodb)
(cd src/Symfony/Component/Lock; composer require --dev --no-update mongodb/mongodb)
if [[ $PATCHED_COMPONENTS ]]; then
echo "::group::install phpunit"
./phpunit install
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/HttpFoundation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ CHANGELOG
* Add `UriSigner` from the HttpKernel component
* Add `partitioned` flag to `Cookie` (CHIPS Cookie)
* Add argument `bool $flush = true` to `Response::send()`
* Make `MongoDbSessionHandler` instantiable with the mongodb extension directly

6.3
---
Expand Down
6D40 443F
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,22 @@
use MongoDB\BSON\Binary;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Client;
use MongoDB\Collection;
use MongoDB\Driver\BulkWrite; 8000
use MongoDB\Driver\Manager;
use MongoDB\Driver\Query;

/**
* Session handler using the mongodb/mongodb package and MongoDB driver extension.
* Session handler using the MongoDB driver extension.
*
* @author Markus Bachmann <markus.bachmann@bachi.biz>
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*
* @see https://packagist.org/packages/mongodb/mongodb
* @see https://php.net/mongodb
*/
class MongoDbSessionHandler extends AbstractSessionHandler
{
private Client $mongo;
private Collection $collection;
private Manager $manager;
private string $namespace;
private array $options;
private int|\Closure|null $ttl;

Expand Down Expand Up @@ -62,13 +64,18 @@ class MongoDbSessionHandler extends AbstractSessionHandler
*
* @throws \InvalidArgumentException When "database" or "collection" not provided
*/
public function __construct(Client $mongo, array $options)
public function __construct(Client|Manager $mongo, array $options)
{
if (!isset($options['database']) || !isset($options['collection'])) {
throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler.');
}

$this->mongo = $mongo;
if ($mongo instanceof Client) {
$mongo = $mongo->getManager();
}

$this->manager = $mongo;
$this->namespace = $options['database'].'.'.$options['collection'];

$this->options = array_merge([
'id_field' => '_id',
Expand All @@ -86,77 +93,94 @@ public function close(): bool

protected function doDestroy(#[\SensitiveParameter] string $sessionId): bool
{
$this->getCollection()->deleteOne([
$this->options['id_field'] => $sessionId,
]);
$write = new BulkWrite();
$write->delete(
[$this->options['id_field'] => $sessionId],
['limit' => 1]
);

$this->manager->executeBulkWrite($this->namespace, $write);

return true;
}

public function gc(int $maxlifetime): int|false
{
return $this->getCollection()->deleteMany([
$this->options['expiry_field'] => ['$lt' => new UTCDateTime()],
])->getDeletedCount();
$write = new BulkWrite();
$write->delete(
[$this->options['expiry_field'] => ['$lt' => $this->getUTCDateTime()]],
);
$result = $this->manager->executeBulkWrite($this->namespace, $write);

return $result->getDeletedCount() ?? false;
}

protected function doWrite(#[\SensitiveParameter] string $sessionId, string $data): bool
{
$ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime');
$expiry = new UTCDateTime((time() + (int) $ttl) * 1000);
$expiry = $this->getUTCDateTime($ttl);

$fields = [
$this->options['time_field'] => new UTCDateTime(),
$this->options['time_field'] => $this->getUTCDateTime(),
$this->options['expiry_field'] => $expiry,
$this->options['data_field'] => new Binary($data, Binary::TYPE_OLD_BINARY),
$this->options['data_field'] => new Binary($data, Binary::TYPE_GENERIC),
];

$this->getCollection()->updateOne(
$write = new BulkWrite();
$write->update(
[$this->options['id_field'] => $sessionId],
['$set' => $fields],
['upsert' => true]
);

$this->manager->executeBulkWrite($this->namespace, $write);

return true;
}

public function updateTimestamp(#[\SensitiveParameter] string $sessionId, string $data): bool
{
$ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime');
$expiry = new UTCDateTime((time() + (int) $ttl) * 1000);
$expiry = $this->getUTCDateTime($ttl);

$this->getCollection()->updateOne(
$write = new BulkWrite();
$write->update(
[$this->options['id_field'] => $sessionId],
['$set' => [
$this->options['time_field'] => new UTCDateTime(),
$this->options['time_field'] => $this->getUTCDateTime(),
$this->options['expiry_field'] => $expiry,
]]
]],
['multi' => false],
);

$this->manager->executeBulkWrite($this->namespace, $write);

return true;
}

protected function doRead(#[\SensitiveParameter] string $sessionId): string
{
$dbData = $this->getCollection()->findOne([
$cursor = $this->manager->executeQuery($this->namespace, new Query([
$this->options['id_field'] => $sessionId,
$this->options['expiry_field'] => ['$gte' => new UTCDateTime()],
]);

if (null === $dbData) {
return '';
$this->options['expiry_field'] => ['$gte' => $this->getUTCDateTime()],
], [
'projection' => [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does anything enforce that $this->options['id_field'] is actually _id or even a field with a unique index?

I assume this will only ever return one or no documents, but I noticed you use limit: 1 for the delete operation in doDestroy(), but omit the limit here. I don't disagree with an explicit limit on the delete operation, since the default is to remove all matching documents.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this options were copied from PdoSessionHandler when this provider was created. This provider has a configureSchema or createTable methods to init the table. We can add a similar method for MongoDB. But since the collection already have an unique index on _id, I think it's fine to let people that would like to change the id field, initialize the collection themselve.

'_id' => false,
$this->options['data_field'] => true,
],
'limit' => 1,
]));

foreach ($cursor as $document) {
return (string) $document->{$this->options['data_field']} ?? '';
}

return $dbData[$this->options['data_field']]->getData();
}

private function getCollection(): Collection
{
return $this->collection ??= $this->mongo->selectCollection($this->options['database'], $this->options['collection']);
// Not found
return '';
}

protected function getMongo(): Client
private function getUTCDateTime(int $additionalSeconds = 0): UTCDateTime
{
return $this->mongo;
return new UTCDateTime((time() + $additionalSeconds) * 1000);
}
}
Loading
0