Skip to content

Commit 4943bcc

Browse files
rustagirGromNaN
andauthored
DOCSP-41306: schema version trait (#3051)
* DOCSP-41306: schema version trait * MW PR fixes 1 * add test wip * add tests * apply phpcbf formatting * JM tech review 1 * error * comment style * Fix test, expose 2 models, lock laravel version to avoid breaking change * JM tech review 2 * fixes * revert database v * JM tech review 3 * JM tech review 4 --------- Co-authored-by: rustagir <[email protected]> Co-authored-by: Jérôme Tamarelle <[email protected]>
1 parent 994e956 commit 4943bcc

File tree

4 files changed

+190
-1
lines changed

4 files changed

+190
-1
lines changed

docs/eloquent-models/model-class.txt

+100-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ to {+odm-short+} models:
3333
- :ref:`laravel-model-customize` explains several model class customizations.
3434
- :ref:`laravel-model-pruning` shows how to periodically remove models that
3535
you no longer need.
36+
- :ref:`laravel-schema-versioning` shows how to implement model schema
37+
versioning.
3638

3739
.. _laravel-model-define:
3840

@@ -67,7 +69,6 @@ This model is stored in the ``planets`` MongoDB collection.
6769
To learn how to specify the database name that your Laravel application uses,
6870
:ref:`laravel-quick-start-connect-to-mongodb`.
6971

70-
7172
.. _laravel-authenticatable-model:
7273

7374
Extend the Authenticatable Model
@@ -333,3 +334,101 @@ models that the prune action deletes:
333334
:emphasize-lines: 5,10,12
334335
:dedent:
335336

337+
.. _laravel-schema-versioning:
338+
339+
Create a Versioned Model Schema
340+
-------------------------------
341+
342+
You can implement a schema versioning pattern into your application by
343+
using the ``HasSchemaVersion`` trait on an Eloquent model. You might
344+
choose to implement a schema version to organize or standardize a
345+
collection that contains data with different schemas.
346+
347+
.. tip::
348+
349+
To learn more about schema versioning, see the :manual:`Model Data for
350+
Schema Versioning </tutorial/model-data-for-schema-versioning/>`
351+
tutorial in the {+server-docs-name+}.
352+
353+
To use this feature with models that use MongoDB as a database, add the
354+
``MongoDB\Laravel\Eloquent\HasSchemaVersion`` import to your model.
355+
Then, set the ``SCHEMA_VERSION`` constant to ``1`` to set the first
356+
schema version on your collection. If your collection evolves to contain
357+
multiple schemas, you can update the value of the ``SCHEMA_VERSION``
358+
constant in subsequent model classes.
359+
360+
When creating your model, you can define the ``migrateSchema()`` method
361+
to specify a migration to the current schema version upon retrieving a
362+
model. In this method, you can specify the changes to make to an older
363+
model to update it to match the current schema version.
364+
365+
When you save a model that does not have a schema version
366+
specified, the ``HasSchemaVersion`` trait assumes that it follows the
367+
latest schema version. When you retrieve a model that does not contain
368+
the ``schema_version`` field, the trait assumes that its schema version
369+
is ``0`` and performs the migration.
370+
371+
Schema Versioning Example
372+
~~~~~~~~~~~~~~~~~~~~~~~~~
373+
374+
In this sample situation, you are working with a collection that was
375+
first modeled by the following class:
376+
377+
.. literalinclude:: /includes/eloquent-models/PlanetSchemaVersion1.php
378+
:language: php
379+
:dedent:
380+
381+
Now, you want to implement a new schema version on the collection.
382+
You can define the new model class with the following behavior:
383+
384+
- Implements the ``HasSchemaVersion`` trait and sets the current
385+
``SCHEMA_VERSION`` to ``2``
386+
387+
- Defines the ``migrateSchema()`` method to migrate models in which the
388+
schema version is less than ``2`` to have a ``galaxy`` field that has a value
389+
of ``'Milky Way'``
390+
391+
.. literalinclude:: /includes/eloquent-models/PlanetSchemaVersion2.php
392+
:language: php
393+
:emphasize-lines: 10,12,20
394+
:dedent:
395+
396+
In the ``"WASP-39 b"`` document in the following code, the
397+
``schema_version`` field value is less than ``2``. When you retrieve the
398+
document, {+odm-short+} adds the ``galaxy`` field and updates the schema
399+
version to the current version, ``2``.
400+
401+
The ``"Saturn"`` document does not contain the ``schema_version`` field,
402+
so {+odm-short+} assigns it the current schema version upon saving.
403+
404+
Finally, the code retrieves the models from the collection to
405+
demonstrate the changes:
406+
407+
.. io-code-block::
408+
:copyable: true
409+
410+
.. input:: /includes/eloquent-models/SchemaVersionTest.php
411+
:language: php
412+
:dedent:
413+
:start-after: begin-schema-version
414+
:end-before: end-schema-version
415+
416+
.. output::
417+
:language: none
418+
:visible: false
419+
420+
[
421+
{
422+
"_id": ...,
423+
"name": "WASP-39 b",
424+
"type": "gas",
425+
"galaxy": "Milky Way",
426+
"schema_version": 2,
427+
},
428+
{
429+
"_id": ...,
430+
"name": "Saturn",
431+
"type": "gas",
432+
"schema_version": 2,
433+
}
434+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace App\Models;
4+
5+
use MongoDB\Laravel\Eloquent\Model;
6+
7+
class Planet extends Model
8+
{
9+
protected $fillable = ['name', 'type'];
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace App\Models;
4+
5+
use MongoDB\Laravel\Eloquent\HasSchemaVersion;
6+
use MongoDB\Laravel\Eloquent\Model;
7+
8+
class Planet extends Model
9+
{
10+
use HasSchemaVersion;
11+
12+
public const SCHEMA_VERSION = 2;
13+
14+
protected $fillable = ['name', 'type', 'galaxy'];
15+
16+
/**
17+
* Migrate documents with a lower schema version to the most current
18+
* schema when inserting new data or retrieving from the database.
19+
*/
20+
public function migrateSchema(int $fromVersion): void
21+
{
22+
if ($fromVersion < 2) {
23+
$this->galaxy = 'Milky Way';
24+
}
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Tests;
6+
7+
use App\Models\Planet;
8+
use MongoDB\Laravel\Tests\TestCase;
9+
10+
class SchemaVersionTest extends TestCase
11+
{
12+
/**
13+
* @runInSeparateProcess
14+
* @preserveGlobalState disabled
15+
*/
16+
public function testSchemaVersion(): void
17+
{
18+
require_once __DIR__ . '/PlanetSchemaVersion2.php';
19+
20+
Planet::truncate();
21+
22+
// begin-schema-version
23+
// Simulates a document in the collection with schema version 1
24+
Planet::insert([
25+
[
26+
'name' => 'WASP-39 b',
27+
'type' => 'gas',
28+
'schema_version' => 1,
29+
],
30+
]);
31+
32+
// Saves a document with no specified schema version
33+
$saturn = Planet::create([
34+
'name' => 'Saturn',
35+
'type' => 'gas',
36+
]);
37+
38+
// Retrieves both models from the collection
39+
$planets = Planet::where('type', 'gas')
40+
->get();
41+
// end-schema-version
42+
43+
$this->assertCount(2, $planets);
44+
45+
$p1 = Planet::where('name', 'Saturn')->first();
46+
47+
$this->assertEquals(2, $p1->schema_version);
48+
49+
$p2 = Planet::where('name', 'WASP-39 b')->first();
50+
51+
$this->assertEquals(2, $p2->schema_version);
52+
$this->assertEquals('Milky Way', $p2->galaxy);
53+
}
54+
}

0 commit comments

Comments
 (0)