Join us at MongoDB.local London on 7 May to unlock new possibilities for your data. Use WEB50 to save 50%.
Register now >
Docs Menu
Docs Home
/ /

Queryable Encryption with Symfony and Doctrine ODM

In this guide, you can learn how to create a Symfony application that uses Doctrine MongoDB ODM to implement MongoDB's Queryable Encryption feature.

Queryable Encryption allows you to automatically encrypt and decrypt document fields. You can use Queryable Encryption to encrypt sensitive data in your application, store data fields as randomized encrypted data on the server, and query the encrypted fields. This tutorial shows you how to set up Queryable Encryption by using Doctrine MongoDB ODM, which provides an Object Document Mapper (ODM) for PHP applications.

Tip

To learn more about integrating Symfony and Doctrine with the MongoDB PHP Library, see the Symfony MongoDB Integration tutorial.

Before you begin this tutorial, complete the following prerequisite tasks:

This tutorial shows how to create a Queryable Encryption application that uses Symfony and Doctrine MongoDB ODM. The application encrypts and decrypts patient medical records and queries encrypted medical data.

Follow the steps in this section to install the project dependencies, configure your environment, and create the application structure.

1

Run the following commands in your terminal to create a new Symfony application and install the necessary dependencies:

symfony new qe-symfony-app
cd qe-symfony-app
composer require doctrine/mongodb-odm-bundle mongodb/mongodb

These commands create a qe-symfony-app project directory and install the following dependencies:

2

In your project directory, open the .env file and add or modify the following variables:

MONGODB_URI=<connection URI>
CRYPT_SHARED_LIB_PATH=<Automatic Encryption Shared Library path>

Replace the <connection URI> placeholder with the connection URI that connects to your cluster, and replace <Automatic Encryption Shared Library path> with the full path to your Automatic Encryption Shared Library. Ensure the path points to lib/mongo_crypt_v1.dylib inside your downloaded package.

3

In your project's src/ directory, create the following subdirectories and files to define the document structure and application logic:

  • src/Document/Patient.php

  • src/Document/PatientRecord.php

  • src/Document/Billing.php

  • src/Command/QueryableEncryptionCommand.php

Future steps in this tutorial instruct you to add code to each file.

4

This tutorial uses Patient, PatientRecord, and Billing classes to represent patient medical records.

Paste the following code into the src/Document/Patient.php file:

src/Document/Patient.php
<?php
namespace App\Document;
use Doctrine\ODM\MongoDB\Mapping\Attribute as ODM;
#[ODM\Document(collection: 'patients')]
class Patient
{
#[ODM\Id]
public string $id;
#[ODM\Field]
public string $patientName;
#[ODM\Field]
public int $patientId;
#[ODM\EmbedOne(targetDocument: PatientRecord::class)]
public PatientRecord $patientRecord;
}

Then, paste the following code into the src/Document/PatientRecord.php file:

src/Document/PatientRecord.php
<?php
namespace App\Document;
use Doctrine\ODM\MongoDB\Mapping\Attribute as ODM;
use Doctrine\ODM\MongoDB\Mapping\EncryptQuery;
#[ODM\EmbeddedDocument]
class PatientRecord
{
#[ODM\Field]
#[ODM\Encrypt(queryType: EncryptQuery::Equality)]
public string $ssn;
#[ODM\EmbedOne(targetDocument: Billing::class)]
#[ODM\Encrypt]
public Billing $billing;
#[ODM\Field]
public int $billAmount;
}

Finally, paste the following code into the src/Document/Billing.php file:

src/Document/Billing.php
<?php
namespace App\Document;
use Doctrine\ODM\MongoDB\Mapping\Attribute as ODM;
#[ODM\EmbeddedDocument]
class Billing
{
#[ODM\Field]
public string $type;
#[ODM\Field]
public string $number;
}

The document classes define the structure for documents in the patients collection. The PatientRecord class includes the #[ODM\Encrypt] attribute to specify the following encrypted fields:

  • patientRecord.ssn: Encrypted and configured for equality queries

  • patientRecord.billing: Encrypted, but not queryable

5

Paste the following code into the src/Command/QueryableEncryptionCommand.php file:

<?php
namespace App\Command;
use App\Document\Billing;
use App\Document\Patient;
use App\Document\PatientRecord;
use Doctrine\ODM\MongoDB\Configuration;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Mapping\Driver\AttributeDriver;
use MongoDB\BSON\Binary;
use MongoDB\Client;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(
name: 'app:queryable-encryption',
description: 'Demonstrates Queryable Encryption with '
. 'Doctrine MongoDB ODM',
)]
class QueryableEncryptionCommand extends Command
{
protected function execute(
InputInterface $input,
OutputInterface $output,
): int {
// Paste application variables below
// Paste encryption credentials below
// Paste connection and configuration below
// Paste collection setup below
// Paste insert operation below
// Paste encrypted query below
return Command::SUCCESS;
}
}

Future steps in this tutorial instruct you to add code under each corresponding comment.

After setting up your project files and dependencies, follow the steps in this section to configure your encryption credentials and connect to MongoDB.

1

In your src/Command/QueryableEncryptionCommand.php file, add the following code under the // Paste application variables below comment:

src/Command/QueryableEncryptionCommand.php
// Paste application variables below
$keyVaultNamespace = 'encryption.__keyVault';
$encryptedDatabase = 'medicalRecords';
$encryptedCollection = 'patients';

The code sets the following variables:

  • keyVaultNamespace - The namespace in MongoDB that stores your Data Encryption Keys (DEKs). This tutorial uses the __keyVault collection in the encryption database.

  • encryptedDatabase - The database that stores your encrypted data. This tutorial uses the medicalRecords database.

  • encryptedCollection - The collection that stores your encrypted data. This tutorial uses the patients collection.

2

Add the following code under the // Paste encryption credentials below comment:

src/Command/QueryableEncryptionCommand.php
// Paste encryption credentials below
$keyFile = __DIR__ . '/../../master-key.bin';
if (!file_exists($keyFile)) {
file_put_contents($keyFile, random_bytes(96));
}
$masterKeyBytes = file_get_contents($keyFile);
$kmsProvider = [
'type' => 'local',
'key' => new Binary(
$masterKeyBytes,
Binary::TYPE_GENERIC,
),
];

This code creates or loads a 96-byte local Customer Master Key file. The $kmsProvider array configures the local KMS provider used for encryption.

Warning

Local CMK Storage

This tutorial stores your Customer Master Key in a local file. Do not use this approach in production. Without a remote KMS, you risk unauthorized access to the encryption key or loss of the key needed to decrypt your data.

Tip

Key Management Systems

To learn more about KMS, see the Key Management Wikipedia entry.

3

Add the following code directly under the KMS provider configuration from the previous step:

src/Command/QueryableEncryptionCommand.php
$autoEncryptionOptions = [
'keyVaultNamespace' => $keyVaultNamespace,
];
$cryptSharedLibPath = $_ENV['CRYPT_SHARED_LIB_PATH'] ?? '';
if ($cryptSharedLibPath) {
$autoEncryptionOptions['extraOptions'] = [
'cryptSharedLibPath' => $cryptSharedLibPath,
];
}

This code sets the key vault namespace and configures the path to the Automatic Encryption Shared Library.

4

Add the following code under the // Paste connection and configuration below comment:

src/Command/QueryableEncryptionCommand.php
// Paste connection and configuration below
$cacheDir = __DIR__ . '/../../var/cache/doctrine';
$config = new Configuration();
$config->setAutoEncryption($autoEncryptionOptions);
$config->setKmsProvider($kmsProvider);
$config->setProxyDir($cacheDir . '/Proxies');
$config->setProxyNamespace('Proxies');
$config->setHydratorDir($cacheDir . '/Hydrators');
$config->setHydratorNamespace('Hydrators');
$config->setDefaultDB($encryptedDatabase);
$config->setMetadataDriverImpl(
new AttributeDriver([__DIR__ . '/../Document'])
);
$client = new Client(
uri: $_ENV['MONGODB_URI'],
uriOptions: [],
driverOptions: $config->getDriverOptions(),
);
$dm = DocumentManager::create($client, $config);

This code configures a Doctrine Configuration object with your encryption settings, creates a MongoDB client that passes encryption driver options, and initializes a DocumentManager for encrypted operations.

5

Add the following code under the // Paste collection setup below comment:

src/Command/QueryableEncryptionCommand.php
// Paste collection setup below
$schemaManager = $dm->getSchemaManager();
$schemaManager->dropDocumentCollection(Patient::class);
$schemaManager->createDocumentCollection(Patient::class);

This code drops any existing collection and creates a new patients collection with the encryption metadata required for Queryable Encryption. Each call to createDocumentCollection() generates new Data Encryption Keys for the encrypted fields.

After configuring your application and database connection, follow the steps in this section to insert and query encrypted documents.

1

In your src/Command/QueryableEncryptionCommand.php file, add the following code under the // Paste insert operation below comment:

src/Command/QueryableEncryptionCommand.php
// Paste insert operation below
$billing = new Billing();
$billing->type = 'Visa';
$billing->number = '4111111111111111';
$record = new PatientRecord();
$record->ssn = '987-65-4320';
$record->billing = $billing;
$record->billAmount = 1500;
$patient = new Patient();
$patient->patientName = 'Jon Doe';
$patient->patientId = 12345678;
$patient->patientRecord = $record;
$dm->persist($patient);
$dm->flush();
$dm->clear();
$output->writeln(
'Successfully inserted the patient document.'
);

Queryable Encryption automatically encrypts the patientRecord.ssn and patientRecord.billing fields before storing the document in MongoDB.

2

Add the following code under the // Paste encrypted query below comment:

src/Command/QueryableEncryptionCommand.php
// Paste encrypted query below
$found = $dm
->getRepository(Patient::class)
->findOneBy(['patientRecord.ssn' => '987-65-4320']);
if ($found instanceof Patient) {
$output->writeln('Found patient:');
$output->writeln(
' Name: ' . $found->patientName
);
$output->writeln(
' SSN: ' . $found->patientRecord->ssn
);
$output->writeln(
' Billing type: '
. $found->patientRecord->billing->type
);
$output->writeln(
' Billing number: '
. $found->patientRecord->billing->number
);
$output->writeln(
' Bill amount: '
. $found->patientRecord->billAmount
);
}
$output->writeln('Connection closed.');

This code performs an equality query on the encrypted patientRecord.ssn field.

3

To start your application, run the following command from your qe-symfony-app project directory:

php bin/console app:queryable-encryption

If successful, your command output resembles the following example:

Successfully inserted the patient document.
Found patient:
Name: Jon Doe
SSN: 987-65-4320
Billing type: Visa
Billing number: 4111111111111111
Bill amount: 1500
Connection closed.

In the database, the document is encrypted. MongoDB stores fields marked with the #[ODM\Encrypt] attribute as BSON binary data and includes a __safeContent__ field for metadata tags. The stored document resembles the following:

{
"_id": {
"$oid": "..."
},
"patientName": "Jon Doe",
"patientId": 12345678,
"patientRecord": {
"ssn": {
"$binary": {
...
}
},
"billing": {
"$binary": {
...
}
},
"billAmount": 1500
},
"__safeContent__": [
{
"$binary": {
...
}
}
]
}

Congratulations on completing the Doctrine ODM Queryable Encryption tutorial! You now have a sample Symfony application that uses Queryable Encryption to automatically encrypt and decrypt document fields. Your application encrypts sensitive data on the server side and queries the data on the client side.

To learn more about Queryable Encryption and Doctrine MongoDB ODM, visit the following resources:

Back

TLS

On this page