Skip to content

Commit

Permalink
Add document version feature (#3021)
Browse files Browse the repository at this point in the history
Co-authored-by: Jérôme Tamarelle <jerome@tamarelle.net>
  • Loading branch information
florianJacques and GromNaN authored Jul 9, 2024
1 parent 65f0a67 commit 179c6a6
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 2 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Changelog
All notable changes to this project will be documented in this file.

## [4.6.0] - upcoming
## [4.6.0] - 2024-07-09

* Add `DocumentTrait` to use any 3rd party model with MongoDB @GromNaN in [#2580](https://github.com/mongodb/laravel-mongodb/pull/2580)
* Add `DocumentModel` trait to use any 3rd party model with MongoDB @GromNaN in [#2580](https://github.com/mongodb/laravel-mongodb/pull/2580)
* Add `HasSchemaVersion` trait to help implementing the [schema versioning pattern](https://www.mongodb.com/docs/manual/tutorial/model-data-for-schema-versioning/) @florianJacques in [#3021](https://github.com/mongodb/laravel-mongodb/pull/3021)
* Add support for Closure for Embed pagination @GromNaN in [#3027](https://github.com/mongodb/laravel-mongodb/pull/3027)

## [4.5.0] - 2024-06-20
Expand Down
82 changes: 82 additions & 0 deletions src/Eloquent/HasSchemaVersion.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

declare(strict_types=1);

namespace MongoDB\Laravel\Eloquent;

use Error;
use LogicException;

use function sprintf;

/**
* Use this trait to implement schema versioning in your models. The document
* is updated automatically when its schema version retrieved from the database
* is lower than the current schema version of the model.
*
* class MyVersionedModel extends Model
* {
* use HasSchemaVersion;
*
* public const int SCHEMA_VERSION = 1;
*
* public function migrateSchema(int $fromVersion): void
* {
* // Your logic to update the document to the current schema version
* }
* }
*
* @see https://www.mongodb.com/docs/manual/tutorial/model-data-for-schema-versioning/
*
* Requires PHP 8.2+
*/
trait HasSchemaVersion
{
/**
* This method should be implemented in the model to migrate a document from
* an older schema version to the current schema version.
*/
public function migrateSchema(int $fromVersion): void
{
}

public static function bootHasSchemaVersion(): void
{
static::saving(function ($model) {
if ($model->getAttribute($model::getSchemaVersionKey()) === null) {
$model->setAttribute($model::getSchemaVersionKey(), $model->getModelSchemaVersion());
}
});

static::retrieved(function (self $model) {
$version = $model->getSchemaVersion();

if ($version < $model->getModelSchemaVersion()) {
$model->migrateSchema($version);
$model->setAttribute($model::getSchemaVersionKey(), $model->getModelSchemaVersion());
}
});
}

/**
* Get Current document version, fallback to 0 if not set
*/
public function getSchemaVersion(): int
{
return $this->{static::getSchemaVersionKey()} ?? 0;
}

protected static function getSchemaVersionKey(): string
{
return 'schema_version';
}

protected function getModelSchemaVersion(): int
{
try {
return $this::SCHEMA_VERSION;
} catch (Error) {
throw new LogicException(sprintf('Constant %s::SCHEMA_VERSION is required when using HasSchemaVersion', $this::class));
}
}
}
26 changes: 26 additions & 0 deletions tests/Models/SchemaVersion.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace MongoDB\Laravel\Tests\Models;

use MongoDB\Laravel\Eloquent\HasSchemaVersion;
use MongoDB\Laravel\Eloquent\Model as Eloquent;

class SchemaVersion extends Eloquent
{
use HasSchemaVersion;

public const SCHEMA_VERSION = 2;

protected $connection = 'mongodb';
protected $collection = 'documentVersion';
protected static $unguarded = true;

public function migrateSchema(int $fromVersion): void
{
if ($fromVersion < 2) {
$this->age = 35;
}
}
}
58 changes: 58 additions & 0 deletions tests/SchemaVersionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

namespace MongoDB\Laravel\Tests;

use Illuminate\Support\Facades\DB;
use LogicException;
use MongoDB\Laravel\Eloquent\HasSchemaVersion;
use MongoDB\Laravel\Eloquent\Model;
use MongoDB\Laravel\Tests\Models\SchemaVersion;

class SchemaVersionTest extends TestCase
{
public function tearDown(): void
{
SchemaVersion::truncate();
}

public function testWithBasicDocument()
{
$document = new SchemaVersion(['name' => 'Luc']);
$this->assertEmpty($document->getSchemaVersion());
$document->save();

// The current schema version of the model is stored by default
$this->assertEquals(2, $document->getSchemaVersion());

// Test automatic migration
SchemaVersion::insert([
['name' => 'Vador', 'schema_version' => 1],
]);
$document = SchemaVersion::where('name', 'Vador')->first();
$this->assertEquals(2, $document->getSchemaVersion());
$this->assertEquals(35, $document->age);

$document->save();

// The migrated version is saved
$data = DB::connection('mongodb')
->collection('documentVersion')
->where('name', 'Vador')
->get();

$this->assertEquals(2, $data[0]['schema_version']);
}

public function testIncompleteImplementation(): void
{
$this->expectException(LogicException::class);
$this->expectExceptionMessage('::SCHEMA_VERSION is required when using HasSchemaVersion');
$document = new class extends Model {
use HasSchemaVersion;
};

$document->save();
}
}

0 comments on commit 179c6a6

Please sign in to comment.