wizbii / mongo-bundle
Requires
- php: ^8.1
- ext-json: *
- ext-mongodb: *
- mongodb/mongodb: ^1.5
- psr/log: ^3.0
- symfony/config: ^6.4|^7
- symfony/console: ^6.4|^7
- symfony/dependency-injection: ^6.4|^7
- symfony/http-kernel: ^6.4|^7
- symfony/monolog-bundle: ^3.0
- symfony/stopwatch: ^6.4|^7
- symfony/yaml: ^6.4|^7
- wizbii/json-serializer: ^1.4
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.8
- phpstan/phpstan: ^1.7
- phpunit/phpunit: ^9.0
- symfony/framework-bundle: ^6.4|^7
README
Objectives
This package aims to help communicate with mongodb in an elegant manner for developers. It lifts the burden of having to manipulate arrays, depends on an heavy package like Doctrine ODM, ...
It is based on a MongoClient
class which provides most of basic mongodb operations but it's much more easy to use than the base mongodb implementation. This class implements a MongoClientInterface
that is also implemented by two other implementations :
- an InMemory implementation that is designed for unitary tests. Most query operators and updaters are supported. See below for a complete reference
- a LocalFile implementation that is designed for functional tests. Its is based on the same LocalEngine as the InMemory implementation and therefore, supports the same query operators and updaters
Installation
composer require wizbii/mongo-bundle
Usage in production mode
First, create an object that holds your model. Please note that this object must implement ArraySerializable
interface as explained in https://gitlab.com/wizbii-open-source/json-serializer-bundle:
<?php
namespace App\Model;
use Wizbii\JsonSerializerBundle\ArraySerializable;
class SimpleObject implements ArraySerializable
{
private string $id;
private string $value;
public function __construct(string $id, string $value)
{
$this->id = $id;
$this->value = $value;
}
public function serialize(): array
{
return [
'_id' => $this->id,
'value' => $this->value,
];
}
public static function deserialize(array $contentAsArray)
{
return new self($contentAsArray['_id'], $contentAsArray['value']);
}
}
Then, create a service that needs to manipulate this object from mongodb:
<?php
namespace App\Repository;
use Wizbii\OpenSource\MongoBundle\MongoClientBuilderInterface;
use Wizbii\OpenSource\MongoBundle\MongoClientInterface;
use App\Model\SimpleObject;
class SimpleObjectRepository
{
/** @phpstan-var MongoClientInterface<SimpleObject> */
private MongoClientInterface $mongoClient;
/** @phpstan-param MongoClientBuilderInterface<SimpleObject> $mongoClientBuilder */
public function __construct(MongoClientBuilderInterface $mongoClientBuilder)
{
$this->mongoClient = $mongoClientBuilder->buildFor('test_database', 'simple_object_collection', SimpleObject::class);
}
public function readSimpleObject(string $simpleObjectId): SimpleObject
{
/** @var SimpleObject */
$simpleObject = $this->mongoClient->get($simpleObjectId);
return $simpleObject;
}
/** @return SimpleObject[] */
public function findSimpleObjectNamedRemi(int $rows = 10, int $offset = 0): array
{
return $this->mongoClient->findBy(['name' => 'Rémi'], $rows, $offset);
}
public function createSimpleObject(): string
{
$this->mongoClient->put(new SimpleObject('id-abcd', 'Rémi'));
}
}
Of course, you can tune the mongo configuration using :
<?php
namespace App\Repository;
use Wizbii\OpenSource\MongoBundle\MongoClientBuilderInterface;
use Wizbii\OpenSource\MongoBundle\MongoClientInterface;
use App\Model\SimpleObject;
class SimpleObjectRepository
{
/** @phpstan-var MongoClientInterface<SimpleObject> */
private MongoClientInterface $mongoClient;
/** @phpstan-param MongoClientBuilderInterface<SimpleObject> $mongoClientBuilder */
public function __construct(MongoClientBuilderInterface $mongoClientBuilder)
{
$this->mongoClient = $mongoClientBuilder
->overrideConfigurationFor('test_database', 'simple_object_collection', SimpleObject::class)
->setReadPreference('primary')
->setRetryWrites(true)
//->...
->end()->build();
}
}
Usage in development mode
The code of your repository does not change: it is the same for production and development modes. But when you execute your unitary tests, the class that will be injected for the MongoClientBuilderInterface
constructor argument will be using the InMemory implementation.
This is an example of how to write unitary tests for such a Repository:
<?php
namespace App\Tests\Repository;
use App\Model\SimpleObject;
use App\Repository\SimpleObjectRepository;
use Tests\Wizbii\OpenSource\MongoBundle\MongoClientTestCase;
class SimpleObjectRepositoryTest extends MongoClientTestCase
{
public function test_it_can_create_and_retrieve_simple_objects()
{
$simpleObjectRepository = new SimpleObjectRepository($this->getMongoClientBuilder());
$simpleObjectRepository->createSimpleObject($this->getSimpleObject('remi', 'Rémi'));
$simpleObject = $simpleObjectRepository->readSimpleObject('remi');
$this->assertThat($simpleObject, $this->isInstanceOf(SimpleObject::class));
$this->assertThat($simpleObject->getValue(), $this->equalTo('Rémi'));
}
public function test_it_can_find_simple_objects_by_name()
{
$simpleObjectRepository = new SimpleObjectRepository($this->getMongoClientBuilder());
$simpleObjectRepository->create($this->getSimpleObject('remi-1', 'Rémi'));
$simpleObjectRepository->create($this->getSimpleObject('mark', 'Mark'));
$simpleObjectRepository->create($this->getSimpleObject('remi-2', 'Rémi'));
$simpleObjectRepository->create($this->getSimpleObject('remi-3', 'Rémi'));
$simpleObjects = $simpleObjectRepository->findSimpleObjectNamedRemi();
$this->assertThat($simpleObjects, $this->countOf(3));
$this->assertThat($simpleObjects[0]->getId(), $this->equalTo('remi-1'));
$this->assertThat($simpleObjects[1]->getId(), $this->equalTo('remi-2'));
$this->assertThat($simpleObjects[2]->getId(), $this->equalTo('remi-3'));
}
private function getSimpleObject(string $id, string $value): SimpleObject
{
return new SimpleObject($id, $value);
}
}
Supported Mongo Features
Query Operator | Real Implementation | Local Engine | Comments |
---|---|---|---|
$eq | OK | OK | |
$gt | OK | OK | |
$gte | OK | OK | |
$in | OK | OK | |
$lt | OK | OK | |
$lte | OK | OK | |
$ne | OK | OK | |
$nin | OK | OK | |
$and | OK | OK | |
$not | OK | OK | |
$nor | OK | OK | |
$or | OK | OK | |
$exists | OK | OK | |
$type | OK | OK | Some types are not supported. See TypeFilter class for details |
$expr | OK | KO | |
$jsonSchema | OK | KO | |
$mod | OK | KO | |
$regexp | OK | OK | |
$text | OK | KO | |
$where | OK | KO | |
$geoIntersects | OK | KO | |
$geoWithin | OK | KO | |
$near | OK | KO | |
$nearSphere | OK | KO | |
$all | OK | OK | |
$elemMatch | OK | OK | |
$size | OK | OK | |
$bitsAllClear | OK | KO | |
$bitsAllSet | OK | KO | |
$bitsAnyClear | OK | KO | |
$bitsAnySet | OK | KO | |
$comment | OK | OK |
Updaters | Real Implementation | Local Engine | Comments |
---|---|---|---|
$currentDate | OK | KO | |
$inc | OK | KO | |
$min | OK | KO | |
$max | OK | KO | |
$mul | OK | KO | |
$rename | OK | KO | |
$set | OK | OK | |
$setOnInsert | OK | KO | |
$unset | OK | KO | |
$addToSet | OK | KO | |
$pop | OK | KO | |
$pull | OK | KO | |
$push | OK | KO | |
$pushAll | OK | KO | |
$each | OK | KO | |
$position | OK | KO | |
$slice | OK | KO | |
$sort | OK | KO | |
$bit | OK | KO |
Contribute
- Fork the repository
- Make your changes
- Test them with
composer dev:checks
(it will run test, phpstan and cs:lint subcommands) - Create a Merge Request