b2pweb / bdf-prime-mongodb
Bdf prime MongoDB component
Requires
- php: ~7.4 | ~8.0.0 | ~8.1.0 | ~8.2.0
- ext-mongodb: *
- b2pweb/bdf-prime: ~2.0
- b2pweb/bdf-serializer: ~1.2
- symfony/polyfill-php80: ~1.25
Requires (Dev)
- phpunit/phpunit: ~9.0
- psr/container: ~1.0|~2.0
- squizlabs/php_codesniffer: ~3.0
- vimeo/psalm: ~4.23@stable
README
MongoDB driver for Prime
Installation
Install with composer :
composer require b2pweb/bdf-prime-mongodb
Create connection :
<?php use Bdf\Prime\ConnectionManager; use Bdf\Prime\MongoDB\Collection\MongoCollectionLocator; use Bdf\Prime\MongoDB\Mongo; // declare your connexion manager $connections = new ConnectionManager(); // Declare connection without username and password $connections->declareConnection('mongo', 'mongodb://127.0.0.1/my_collection?noAuth=true'); // With credentials $connections->declareConnection('mongo', 'mongodb://user:password@127.0.0.1/my_database'); // Get the connection locator $locator = new MongoCollectionLocator($connections); Mongo::configure($locator); // Configure active record system
Usage
Declare a document
Declare the base document class by extending Bdf\Prime\MongoDB\Document\MongoDocument
.
The _id
field is declared by this class.
You can use typed property for generate an automatic type mapping. Untyped fields will not be converted when retrieving from mongo.
Note: it's advisable to declare all fields as nullable in case of missing field
<?php use Bdf\Prime\MongoDB\Document\MongoDocument; use \MongoDB\BSON\Binary; class MyDocument extends MongoDocument { public ?string $name; public ?DateTimeInterface $creationDate; public ?Binary $data; }
Declare a Mapper
For a basic usage, simply declare a mapper by extending Bdf\Prime\MongoDB\Document\DocumentMapper
, and implementing connection()
and collection()
methods :
<?php use Bdf\Prime\MongoDB\Document\DocumentMapper; class MyDocumentMapper extends DocumentMapper { /** * {@inheritdoc} */ public function connection(): string { // The declared connection name return 'mongo'; } /** * {@inheritdoc} */ public function collection(): string { return 'my_collection'; // The storage collection name } }
Mapping and fields will be automatically resolved from the document class.
Querying MongoDB
The query system use Prime interfaces, so usage is almost the same :
<?php // Get the query /** @var \Bdf\Prime\MongoDB\Query\MongoQuery $query */ $query = MyDocument::query(); $query ->where('name', 'John') // Simple where works as expected ->where('value.attr', ':like', 'P%') // "like" operator is converted to a regex ->where('value.foo', '$type', 'javascript') // Use mongodb operator ; // Get all documents which match with filters $query->all(); // First returns the first matching document or null $query->first();
Testing
Use Bdf\Prime\MongoDB\Test\MongoTester
for create testing data.
<?php use PHPUnit\Framework\TestCase; use Bdf\Prime\MongoDB\Test\MongoTester; class MyTest extends TestCase { private MongoTester $tester; protected function setUp() : void { $this->tester = new MongoTester(); $this->tester // Declare given collections. Collections are automatically declared when `push()` on new collection ->declare(FooDocument::class, BarDocument::class) // Push to mongo given documents with a key for retrieve the value on test ->push([ 'doc1' => new MyDocument(), 'doc2' => new MyDocument(), ]) ; } protected function tearDown() : void { $this->tester->destroy(); // Drop all declared collections } public function my_test() { $doc1 = $this->tester->get('doc1'); // get document declared on setUp method $this->tester->push($newDoc = new FooDocument()); // Push a single document without a key (cannot be retrieved with `get()`) // ... $this->assertNotEquals($doc1, $this->tester->refresh($doc1)); // Retrieve the DB version of the given document $this->assertNull($newDoc, $this->tester->refresh($newDoc)); // refresh can be used to check if the document exists on DB } public function with_array_access_test() { // Array access syntax can also be used instead of "classic" method calls $doc1 = $this->tester['doc1']; // get document declared on setUp method $this->tester[] = $newDoc = new FooDocument(); // Push a single document without a key (cannot be retrieved with `get()`) $this->tester['doc3'] = new MyDocument(); // Push a single document with a key // ... $this->assertNotEquals($doc1, $this->tester[$doc1]); // Retrieve the DB version of the given document $this->assertTrue(isset($this->tester[$newDoc])); // Check if the document exists on DB unset($this->tester['doc3']); // Deleted a declared document unset($this->tester[$newDoc]); // Can also be used to delete a document without key $this->assertFalse(isset($this->tester[$newDoc])); // Document is now deleted } }
Case-insensitive search and index
To enable case-insensitive search by default, you can add default collation on table options. See Case Insensitive Indexes
<?php use Bdf\Prime\MongoDB\Document\DocumentMapper; class MyDocumentMapper extends DocumentMapper { /** * {@inheritdoc} */ public function connection(): string { // The declared connection name return 'mongo'; } /** * {@inheritdoc} */ public function collection(): string { return 'my_collection'; // The storage collection name } /** * {@inheritdoc} */ protected function buildDefinition(\Bdf\Prime\MongoDB\Schema\CollectionDefinitionBuilder $builder) : void { $builder->collation(['locale' => 'en', 'strength' => 2]); } }
Multiple document classes
Mongo is schemaless, so a collection can store documents with different formats.
You can select a document class corresponding to DB fields by using a custom Bdf\Prime\MongoDB\Document\Selector\DocumentSelectorInterface
,
declared using DocumentMapper::createDocumentSelector()
:
<?php // Declare document classes. Note: all documents classes must inherit from a base class class BaseDocument extends MongoDocument { public ?string $_type = null; // _type is the default field used by DiscriminatorFieldDocumentSelector } class FooDocument extends BaseDocument { public ?string $_type = 'foo'; public ?string $foo = null; } class BarDocument extends BaseDocument { public ?string $_type = 'bar'; public ?string $bar = null; } // Declare a single mapper class MyDocumentMapper extends DocumentMapper { /** * {@inheritdoc} */ public function connection(): string { return 'mongo'; } /** * {@inheritdoc} */ public function collection(): string { return 'my_collection'; } /** * {@inheritdoc} */ protected function createDocumentSelector(string $documentBaseClass): DocumentSelectorInterface { // Define document class mapping return new DiscriminatorFieldDocumentSelector($documentBaseClass, [ 'foo' => FooDocument::class, 'bar' => BarDocument::class, ]); // If you can't introduce a field for perform discrimination, you can check fields existence : return new DiscriminatorFieldDocumentSelector($documentBaseClass, [ FooDocument::class => ['foo'], BarDocument::class => ['bar'], ]); } } // Get the base collection : it handles all document types $collection = BaseDocument::collection(); $collection->add(new BaseDocument(...)); $collection->add(new FooDocument(...)); $collection->add(new BarDocument(...)); $collection->all(); // Return all documents from all types // Handle only "FooDocument" document class $fooCollection = FooDocument::collection(); $fooCollection->all(); // Return only document of type "foo"