Skip to content

PHPORM-206 Add model schema version feature #3021

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
}
}
Loading