district5 / mondoc
Mondoc is a lightweight and easy to use model library for MongoDB
Requires
- php: >=8.1
- ext-mongodb: *
- district5/date: *
- district5/mondoc-builder: *
- mongodb/mongodb: ^1.8
Requires (Dev)
- phpunit/phpunit: 9.5.26
This package is auto-updated.
Last update: 2024-05-03 09:26:48 UTC
README
Composer...
Use composer to add this library as a dependency onto your project.
composer require district5/mondoc
Usage...
Setting up connections...
The MondoConnections
object is a singleton. Set this up somewhere in your code to initialise the connection, and
within your services you can define protected static function getConnectionId(): string
to return the correct
identifier for the relevant model.
<?php use District5\Mondoc\MondocConfig; use MongoDB\Client; $connection = new Client('< mongo connection string >'); $database = $connection->selectDatabase('< database name >'); $config = MondocConfig::getInstance(); $config->addDatabase( $database, 'default' // a connection identifier ('default' is the default value). ); // Add another one for something else... // $config->addDatabase( // $database, // 'authentication' // ); $config->addServiceMapping( MyModel::class, // You can also just use a string like '\MyNamespace\Model\MyModel' MyService::class // You can also just use a string like '\MyNamespace\Service\MyService' ); // Or you can use... // $config->setServiceMap( // [ // MyModel::class => MyService::class, // Also replaceable by strings // AnotherModel::class => AnotherService::class, // ] // );
The data model
<?php namespace MyNs\Model; use District5\Mondoc\Db\Model\MondocAbstractModel; use MyNs\Service\MyService; /** * Class MyModel * @package MyNs\Model */ class MyModel extends MondocAbstractModel { /** * @var string */ protected $name = null; /** * @return string */ public function getName(): ?string { return $this->name; } /** * @param string $val * @return $this */ public function setName(string $val) { $this->name = trim($val); $this->addDirty('name'); return $this; } }
Optional traits...
MondocVersionedModelTrait
- You can easily version data within a model by using the\District5\Mondoc\Db\Model\Traits\MondocVersionedModelTrait
trait. This trait introduces a_v
variable in the model, which you can choose to increment when you choose.- You can detect if a model has a version by calling
isVersionableModel()
on the model.
- You can detect if a model has a version by calling
MondocCreatedDateTrait
- Adds acd
property to a model to utilise as a created date.- This value is automatically assigned to the current UTC date upon initial save of a model,
or if an existing model is updated and the
cd
property has not been set. You can override this behaviour by assigning a value to thecd
property.
- This value is automatically assigned to the current UTC date upon initial save of a model,
or if an existing model is updated and the
MondocModifiedDateTrait
- Adds amd
property to a model to utilise as an updated date.- This value is automatically assigned the current UTC date, but you can override this
behaviour by assigning a value to the
md
property prior to saving.
- This value is automatically assigned the current UTC date, but you can override this
behaviour by assigning a value to the
Traits examples
<?php class MyModel extends \District5\Mondoc\Db\Model\MondocAbstractModel { use \District5\Mondoc\Db\Model\Traits\MondocVersionedModelTrait; use \District5\Mondoc\Db\Model\Traits\MondocCreatedDateTrait; use \District5\Mondoc\Db\Model\Traits\MondocModifiedDateTrait; // Rest of your model code... }
The service layer
<?php namespace Myns\Service; use MyNs\Model\MyModel; /** * Class MyService * @package MyNs\Service */ class MyService extends AbstractService { /** * @return string */ protected static function getCollectionName(): string { return 'users'; } }
The logic for querying the database etc., is always performed in the service layer.
Nesting objects
You can nest objects in each other. The main model must extend \District5\Mondoc\Db\Model\MondocAbstractModel
and have the sub
models defined in the $mondocNested
array.
Sub models must extend \District5\Mondoc\Db\Model\MondocAbstractSubModel
.
use District5\Mondoc\Db\Model\MondocAbstractModel; use District5\Mondoc\Db\Model\MondocAbstractSubModel; class FavouriteFood extends MondocAbstractSubModel { protected string|null $foodName = null; public function getFoodName() { return $this->foodName; } } class Car extends MondocAbstractSubModel { protected string|null $brand = null; protected string|null $colour = null; public function getBrand(): ?string { return $this->brand; } public function getColour(): ?string { return $this->colour; } } class Person extends MondocAbstractModel { /** * @var string|null */ protected string|null $name = null; /** * @var FavouriteFood */ protected FavouriteFood|\MongoDB\Model\BSONDocument|null $favouriteFood = null; // Having BSONDocument here is important as inflation will use the property /** * @var FavouriteFood[] */ protected array|\MongoDB\Model\BSONArray $allFoods = []; // Having BSONArray here is important as inflation will use the property /** * @var Car */ protected Car|\MongoDB\Model\BSONDocument|null $car = null; // Having BSONDocument here is important as inflation will use the property /** * @var string[] */ protected array $mondocNested = [ 'allFoods' => FavouriteFood::class . '[]', // Indicates an array of FavouriteFood objects 'favouriteFood' => FavouriteFood::class, 'car' => Car::class ]; public function getAllFoods(): array { return $this->allFoods; } public function getFavouriteFoodName(): ?string { return $this->favouriteFood->getFoodName(); } public function getCarBrand(): ?string { return $this->car->getBrand(); } public function getCarColour(): ?string { return $this->car->getColour(); } }
Finding documents..
<?php // count documents matching a filter \District5Tests\MondocTests\Example\MyService::countAll([], []); // count documents using a query builder $builder = \District5Tests\MondocTests\Example\MyService::getQueryBuilder(); \District5Tests\MondocTests\Example\MyService::countAllByQueryBuilder($builder); // get single model by id, accepts a string or ObjectId \District5Tests\MondocTests\Example\MyService::getById('the-mongo-id'); // get multiple models by ids. accepts string or ObjectIds \District5Tests\MondocTests\Example\MyService::getByIds(['an-id', 'another-id']); // get single model with options \District5Tests\MondocTests\Example\MyService::getOneByCriteria(['foo' => 'bar'], ['sort' => ['foo' => -1]]); // get multiple models with options \District5Tests\MondocTests\Example\MyService::getMultiByCriteria(['foo' => 'bar'], ['sort' => ['foo' => -1]]); // paginating results by page number $currentPage = 1; $perPage = 10; $sortByField = 'foo'; $sortDirection = -1; $pagination = \District5Tests\MondocTests\Example\MyService::getPaginationHelper($currentPage, $perPage, ['foo' => 'bar']) $results = \District5Tests\MondocTests\Example\MyService::getPage($pagination, $perPage, ['foo' => 'bar'], $sortByField, $sortDirection); // paginating results by ID number descending (first page) $currentId = null; $perPage = 10; $sortDirection = -1; $pagination = \District5Tests\MondocTests\Example\MyService::getPaginationHelperForObjectIdPagination($perPage, ['foo' => 'bar']) $results = \District5Tests\MondocTests\Example\MyService::getPageByByObjectIdPagination($pagination, $currentId, $perPage, $sortDirection, ['foo' => 'bar']); // paginating results by ID number descending $currentId = '5f7deca120c41f29827c0c60'; // or new ObjectId('5f7deca120c41f29827c0c60'); $perPage = 10; $sortDirection = -1; $pagination = \District5Tests\MondocTests\Example\MyService::getPaginationHelperForObjectIdPagination($perPage, ['foo' => 'bar']) $results = \District5Tests\MondocTests\Example\MyService::getPageByByObjectIdPagination($pagination, $currentId, $perPage, $sortDirection, ['foo' => 'bar']); // paginating results by ID number ascending $currentId = '5f7deca120c41f29827c0c60'; // or new ObjectId('5f7deca120c41f29827c0c60'); $perPage = 10; $sortDirection = 1; $pagination = \District5Tests\MondocTests\Example\MyService::getPaginationHelperForObjectIdPagination($perPage, ['foo' => 'bar']) $results = \District5Tests\MondocTests\Example\MyService::getPageByByObjectIdPagination($pagination, $currentId, $perPage, $sortDirection, ['foo' => 'bar']); // get the distinct values for 'age' with a filter and options \District5Tests\MondocTests\Example\MyService::getDistinctValuesForKey('age', ['foo' => 'bar'], ['sort' => ['age' => 1]]); // average age with filter \District5Tests\MondocTests\Example\MyService::aggregate()->getAverage('age', ['foo' => 'bar']); // 10% percentile, sorted asc with filter \District5Tests\MondocTests\Example\MyService::aggregate()->getPercentile('age', 0.1, 1, ['foo' => 'bar']); // get sum of a field with a given filter \District5Tests\MondocTests\Example\MyService::aggregate()->getSum('age', ['foo' => 'bar']); // get the min value of a field with a given filter \District5Tests\MondocTests\Example\MyService::aggregate()->getMin('age', ['foo' => 'bar']); // ...or with a string... // \District5Tests\MondocTests\Example\MyService::aggregate()->getMin('name', ['foo' => 'bar']); // get the max value of a field with a given filter \District5Tests\MondocTests\Example\MyService::aggregate()->getMax('age', ['foo' => 'bar']); // ...or with a string... // \District5Tests\MondocTests\Example\MyService::aggregate()->getMax('name', ['foo' => 'bar']);
Useful information...
To use a pre-determined ObjectId as the document _id
, you can call setPresetObjectId
against the model. For example:
<?php /** @noinspection SpellCheckingInspection */ $theId = new \MongoDB\BSON\ObjectId('61dfee5591efcf44e023d692'); $person = new Person(); $person->setPresetObjectId(new ObjectId()); $person->save(); echo $person->getObjectIdString(); // 61dfee5591efcf44e023d692
This will force the model to absorb this ObjectId and not generate a new one upon insertion.
Converting between types
MongoDB uses BSON types for data. This library holds a MondocTypes
helper, which can assist in the conversion of these
native types.
<?php use \District5\Mondoc\Helper\MondocTypes; // Dates $mongoDateTime = MondocTypes::phpDateToMongoDateTime(new \DateTime()); $phpDateTime = MondocTypes::dateToPHPDateTime($mongoDateTime); // BSON documents $bsonDocument = new \MongoDB\Model\BSONDocument(['foo' => 'bar']); $phpArrayFromDoc = MondocTypes::arrayToPhp($bsonDocument); // BSON arrays $bsonArray = new \MongoDB\Model\BSONArray(['foo', 'bar']); $phpArrayFromArray = MondocTypes::arrayToPhp($bsonArray); // ObjectIds /** @noinspection SpellCheckingInspection */ $anId = '61dfee5591efcf44e023d692'; $objectId = MondocTypes::toObjectId($anId); // You can also pass existing ObjectId's into the conversion and nothing happens. // MondocTypes::toObjectId(new \MongoDB\BSON\ObjectId()); // MondocTypes::toObjectId($objectId);
Query building
Query building is handled by the MondocBuilder
library https://github.com/district-5/php-mondoc-builder.
Testing
You can run PHPUnit against the library by running composer install
and then running phpunit
. Before doing so,
you'll need to copy the example.phpunit.xml
to phpunit.xml
and change the environment variables contained within.