Repository: statamic/eloquent-driver (php)

Issue:
[Planetscale Compatibility] Add option to skip foreign key constraints 
I'm working on testing Statamic with PlanetScale and ran into this issue here:

```
 SQLSTATE[HY000]: General error: 1105 foreign key constraints are not allowed, see https://vitess.io/blog/2021-06-15-online-ddl-why-no-fk/ (SQL: alter table `form_submissions` add constraint `form_submissions_form_id_foreign` foreign key (`form_id`) references `forms` (`id`) on delete cascade)
 ```

I'm not quite sure what the best way to check for configure excluding [this line](https://github.com/statamic/eloquent-driver/blob/master/database/migrations/create_form_submissions_table.php.stub#L12). 

Thoughts?

Pull request:
Replaced foreign keys with simple indexes
Also added index to entries order, and re-aligned migrations with Laravel's stub.

Fixes #97 

Patch:
diff --git a/database/migrations/create_entries_table.php.stub b/database/migrations/create_entries_table.php.stub
index 52cb8a3..f216a4a 100644
--- a/database/migrations/create_entries_table.php.stub
+++ b/database/migrations/create_entries_table.php.stub
@@ -10,22 +10,17 @@ return new class extends Migration {
         Schema::create($this->prefix('entries'), function (Blueprint $table) {
             $table->id();
             $table->string('site')->index();
-            $table->unsignedBigInteger('origin_id')->nullable();
+            $table->unsignedBigInteger('origin_id')->nullable()->index();
             $table->boolean('published')->default(true);
             $table->string('status');
             $table->string('slug')->nullable();
             $table->string('uri')->nullable()->index();
             $table->string('date')->nullable();
-            $table->integer('order')->nullable();
+            $table->integer('order')->nullable()->index();
             $table->string('collection')->index();
             $table->string('blueprint', 30)->nullable()->index();
             $table->jsonb('data');
             $table->timestamps();
-
-            $table->foreign('origin_id')
-                ->references('id')
-                ->on($this->prefix('entries'))
-                ->onDelete('set null');
         });
     }
 
diff --git a/database/migrations/create_entries_table_with_string_ids.php.stub b/database/migrations/create_entries_table_with_string_ids.php.stub
index 27235f4..c738a43 100644
--- a/database/migrations/create_entries_table_with_string_ids.php.stub
+++ b/database/migrations/create_entries_table_with_string_ids.php.stub
@@ -10,23 +10,19 @@ return new class extends Migration {
         Schema::create($this->prefix('entries'), function (Blueprint $table) {
             $table->uuid('id');
             $table->string('site')->index();
-            $table->uuid('origin_id')->nullable();
+            $table->uuid('origin_id')->nullable()->index();
             $table->boolean('published')->default(true);
             $table->string('status');
             $table->string('slug')->nullable();
             $table->string('uri')->nullable()->index();
             $table->string('date')->nullable();
-            $table->integer('order')->nullable();
+            $table->integer('order')->nullable()->index();
             $table->string('collection')->index();
             $table->string('blueprint', 30)->nullable()->index();
             $table->jsonb('data');
             $table->timestamps();
 
             $table->primary('id');
-            $table->foreign('origin_id')
-                ->references('id')
-                ->on($this->prefix('entries'))
-                ->onDelete('set null');
         });
     }
 
diff --git a/database/migrations/create_form_submissions_table.php.stub b/database/migrations/create_form_submissions_table.php.stub
index 1487dcc..f0e763a 100644
--- a/database/migrations/create_form_submissions_table.php.stub
+++ b/database/migrations/create_form_submissions_table.php.stub
@@ -9,7 +9,7 @@ return new class extends Migration {
     {
         Schema::create($this->prefix('form_submissions'), function (Blueprint $table) {
             $table->id();
-            $table->foreignId('form_id')->constrained($this->prefix('forms'))->cascadeOnDelete();
+            $table->unsignedBigInteger('form_id')->index();
             $table->jsonb('data')->nullable();
             $table->timestamps(6);
 
diff --git a/database/migrations/updates/drop_foreign_keys_on_entries.php.stub b/database/migrations/updates/drop_foreign_keys_on_entries.php.stub
new file mode 100644
index 0000000..c3c5f0a
--- /dev/null
+++ b/database/migrations/updates/drop_foreign_keys_on_entries.php.stub
@@ -0,0 +1,26 @@
+<?php
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+use Statamic\Eloquent\Database\BaseMigration as Migration;
+
+return new class extends Migration {
+    public function up()
+    {
+        Schema::table($this->prefix('entries'), function (Blueprint $table) {
+            $table->dropForeign(['origin_id']);
+            $table->index('order');
+        });
+    }
+
+    public function down()
+    {
+        Schema::table($this->prefix('entries'), function (Blueprint $table) {
+            $table->foreign('origin_id')
+                ->references('id')
+                ->on($this->prefix('entries'))
+                ->onDelete('set null');
+            $table->dropIndex(['order']);
+        });
+    }
+};
diff --git a/database/migrations/updates/drop_foreign_keys_on_forms.php.stub b/database/migrations/updates/drop_foreign_keys_on_forms.php.stub
new file mode 100644
index 0000000..ae2591b
--- /dev/null
+++ b/database/migrations/updates/drop_foreign_keys_on_forms.php.stub
@@ -0,0 +1,24 @@
+<?php
+
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+use Statamic\Eloquent\Database\BaseMigration as Migration;
+
+return new class extends Migration {
+    public function up()
+    {
+        Schema::table($this->prefix('form_submissions'), function (Blueprint $table) {
+            $table->dropForeign(['origin_id']);
+        });
+    }
+
+    public function down()
+    {
+        Schema::table($this->prefix('form_submissions'), function (Blueprint $table) {
+            $table->foreign('form_id')
+                ->references('id')
+                ->on($this->prefix('forms'))
+                ->onDelete('cascade');
+        });
+    }
+};
diff --git a/src/Commands/ImportAssets.php b/src/Commands/ImportAssets.php
index 06becff..42c2362 100644
--- a/src/Commands/ImportAssets.php
+++ b/src/Commands/ImportAssets.php
@@ -3,6 +3,7 @@
 namespace Statamic\Eloquent\Commands;
 
 use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Facade;
 use Statamic\Assets\AssetRepository;
 use Statamic\Console\RunsInPlease;
 use Statamic\Contracts\Assets\Asset as AssetContract;
@@ -52,6 +53,9 @@ class ImportAssets extends Command
 
     private function useDefaultRepositories()
     {
+        Facade::clearResolvedInstance(AssetContainerRepositoryContract::class);
+        Facade::clearResolvedInstance(AssetRepositoryContract::class);
+
         Statamic::repository(AssetContainerRepositoryContract::class, AssetContainerRepository::class);
         Statamic::repository(AssetRepositoryContract::class, AssetRepository::class);
 
diff --git a/src/Commands/ImportBlueprints.php b/src/Commands/ImportBlueprints.php
index 22dd517..56102a1 100644
--- a/src/Commands/ImportBlueprints.php
+++ b/src/Commands/ImportBlueprints.php
@@ -4,6 +4,7 @@ namespace Statamic\Eloquent\Commands;
 
 use Illuminate\Console\Command;
 use Illuminate\Support\Carbon;
+use Illuminate\Support\Facades\Facade;
 use Statamic\Console\RunsInPlease;
 use Statamic\Facades\Blueprint;
 use Statamic\Facades\Fieldset;
@@ -47,6 +48,9 @@ class ImportBlueprints extends Command
 
     private function useDefaultRepositories()
     {
+        Facade::clearResolvedInstance(\Statamic\Fields\BlueprintRepository::class);
+        Facade::clearResolvedInstance(\Statamic\Fields\FieldsetRepository::class);
+
         app()->singleton(
             'Statamic\Fields\BlueprintRepository',
             'Statamic\Fields\BlueprintRepository'
diff --git a/src/Commands/ImportCollections.php b/src/Commands/ImportCollections.php
index 911ca5b..26e1292 100644
--- a/src/Commands/ImportCollections.php
+++ b/src/Commands/ImportCollections.php
@@ -59,6 +59,9 @@ class ImportCollections extends Command
         $originalTreeRepo = get_class(app()->make(CollectionTreeRepositoryContract::class));
         $originalCollection = get_class(app()->make(CollectionContract::class));
 
+        Facade::clearResolvedInstance(CollectionRepositoryContract::class);
+        Facade::clearResolvedInstance(CollectionTreeRepositoryContract::class);
+
         Statamic::repository(CollectionRepositoryContract::class, CollectionRepository::class);
         Statamic::repository(CollectionTreeRepositoryContract::class, CollectionTreeRepository::class);
         app()->bind(CollectionContract::class, StacheCollection::class);
@@ -68,7 +71,9 @@ class ImportCollections extends Command
         Statamic::repository(CollectionRepositoryContract::class, $originalRepo);
         Statamic::repository(CollectionTreeRepositoryContract::class, $originalTreeRepo);
         app()->bind(CollectionContract::class, $originalCollection);
+
         Facade::clearResolvedInstance(CollectionRepositoryContract::class);
+        Facade::clearResolvedInstance(CollectionTreeRepositoryContract::class);
     }
 
     private function importCollections()
diff --git a/src/Commands/ImportEntries.php b/src/Commands/ImportEntries.php
index 8334983..f54b933 100644
--- a/src/Commands/ImportEntries.php
+++ b/src/Commands/ImportEntries.php
@@ -3,6 +3,7 @@
 namespace Statamic\Eloquent\Commands;
 
 use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Facade;
 use Statamic\Console\RunsInPlease;
 use Statamic\Contracts\Entries\CollectionRepository as CollectionRepositoryContract;
 use Statamic\Contracts\Entries\Entry as EntryContract;
@@ -47,6 +48,9 @@ class ImportEntries extends Command
 
     private function useDefaultRepositories()
     {
+        Facade::clearResolvedInstance(EntryRepositoryContract::class);
+        Facade::clearResolvedInstance(CollectionRepositoryContract::class);
+
         Statamic::repository(EntryRepositoryContract::class, EntryRepository::class);
         Statamic::repository(CollectionRepositoryContract::class, CollectionRepository::class);
 
diff --git a/src/Commands/ImportForms.php b/src/Commands/ImportForms.php
index 8a5b1e0..1b977ab 100644
--- a/src/Commands/ImportForms.php
+++ b/src/Commands/ImportForms.php
@@ -4,6 +4,7 @@ namespace Statamic\Eloquent\Commands;
 
 use Illuminate\Console\Command;
 use Illuminate\Support\Carbon;
+use Illuminate\Support\Facades\Facade;
 use Statamic\Console\RunsInPlease;
 use Statamic\Contracts\Forms\Form as FormContract;
 use Statamic\Contracts\Forms\Submission as SubmissionContract;
@@ -47,6 +48,9 @@ class ImportForms extends Command
 
     private function useDefaultRepositories()
     {
+        Facade::clearResolvedInstance(FormContract::class);
+        Facade::clearResolvedInstance(SubmissionContract::class);
+
         app()->bind(FormContract::class, StacheForm::class);
         app()->bind(SubmissionContract::class, StacheSubmission::class);
     }
diff --git a/src/Commands/ImportGlobals.php b/src/Commands/ImportGlobals.php
index 097e99a..491e9e8 100644
--- a/src/Commands/ImportGlobals.php
+++ b/src/Commands/ImportGlobals.php
@@ -3,6 +3,7 @@
 namespace Statamic\Eloquent\Commands;
 
 use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Facade;
 use Statamic\Console\RunsInPlease;
 use Statamic\Contracts\Globals\GlobalRepository as GlobalRepositoryContract;
 use Statamic\Contracts\Globals\GlobalSet as GlobalSetContract;
@@ -45,6 +46,8 @@ class ImportGlobals extends Command
 
     private function useDefaultRepositories()
     {
+        Facade::clearResolvedInstance(GlobalRepositoryContract::class);
+
         Statamic::repository(GlobalRepositoryContract::class, GlobalRepository::class);
 
         app()->bind(GlobalSetContract::class, GlobalSet::class);
diff --git a/src/Commands/ImportNavs.php b/src/Commands/ImportNavs.php
index e1126fd..63c3709 100644
--- a/src/Commands/ImportNavs.php
+++ b/src/Commands/ImportNavs.php
@@ -3,6 +3,7 @@
 namespace Statamic\Eloquent\Commands;
 
 use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Facade;
 use Statamic\Console\RunsInPlease;
 use Statamic\Contracts\Structures\Nav as NavContract;
 use Statamic\Contracts\Structures\NavigationRepository as NavigationRepositoryContract;
@@ -50,6 +51,9 @@ class ImportNavs extends Command
 
     private function useDefaultRepositories()
     {
+        Facade::clearResolvedInstance(NavigationRepositoryContract::class);
+        Facade::clearResolvedInstance(NavTreeRepositoryContract::class);
+
         Statamic::repository(NavigationRepositoryContract::class, NavigationRepository::class);
         Statamic::repository(NavTreeRepositoryContract::class, NavTreeRepository::class);
 
diff --git a/src/Commands/ImportTaxonomies.php b/src/Commands/ImportTaxonomies.php
index c336a3e..cda2a4e 100644
--- a/src/Commands/ImportTaxonomies.php
+++ b/src/Commands/ImportTaxonomies.php
@@ -3,6 +3,7 @@
 namespace Statamic\Eloquent\Commands;
 
 use Illuminate\Console\Command;
+use Illuminate\Support\Facades\Facade;
 use Statamic\Console\RunsInPlease;
 use Statamic\Contracts\Taxonomies\Taxonomy as TaxonomyContract;
 use Statamic\Contracts\Taxonomies\TaxonomyRepository as TaxonomyRepositoryContract;
@@ -53,6 +54,9 @@ class ImportTaxonomies extends Command
 
     private function useDefaultRepositories()
     {
+        Facade::clearResolvedInstance(TaxonomyRepositoryContract::class);
+        Facade::clearResolvedInstance(TermRepositoryContract::class);
+
         Statamic::repository(TaxonomyRepositoryContract::class, TaxonomyRepository::class);
         Statamic::repository(TermRepositoryContract::class, TermRepository::class);
 
diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php
index 8159a00..2f50c96 100644
--- a/src/Entries/Entry.php
+++ b/src/Entries/Entry.php
@@ -14,12 +14,14 @@ class Entry extends FileEntry
 
     public static function fromModel(Model $model)
     {
+        $data = isset($model->data['__localized_fields']) ? collect($model->data)->only($model->data['__localized_fields']) : $model->data;
+
         $entry = (new static())
             ->origin($model->origin_id)
             ->locale($model->site)
             ->slug($model->slug)
             ->collection($model->collection)
-            ->data($model->data)
+            ->data($data)
             ->blueprint($model->blueprint ?? $model->data['blueprint'] ?? null)
             ->published($model->published)
             ->model($model);
@@ -47,16 +49,57 @@ class Entry extends FileEntry
         $class = app('statamic.eloquent.entries.model');
 
         $data = $source->data();
+        $date = $source->hasDate() ? $source->date() : null;
+
+        $origin = $source->origin();
+
+        if ($source->hasOrigin()) {
+            if ($blueprint = $source->blueprint()) {
+                $localizedBlueprintFields = $blueprint
+                    ->fields()
+                    ->localizable()
+                    ->all()
+                    ->map
+                    ->handle()
+                    ->all();
+
+                $originData = $origin->data();
+
+                // remove any fields in entry data that are marked as localized but value is present, and does not match origin
+                $localizedFields = [];
+                foreach ($localizedBlueprintFields as $blueprintField) {
+                    if ($data->has($blueprintField)) {
+                        if ($data->get($blueprintField) === $originData->get($blueprintField)) {
+                            $data->forget($blueprintField);
+                        } else {
+                            $localizedFields[] = $blueprintField;
+                        }
+                    }
+                }
+
+                $data = $originData->merge($data);
+
+                $data->put('__localized_fields', $localizedFields);
+
+                if (! in_array('date', $localizedFields)) {
+                    $date = $origin->hasDate() ? $origin->date() : null;
+                }
+            }
+        }
+
+        if ($parent = $source->parent()) {
+            $data->put('parent', (string) $parent->id);
+        }
 
         $attributes = [
-            'origin_id'  => $source->origin()?->id(),
+            'origin_id'  => $origin?->id(),
             'site'       => $source->locale(),
             'slug'       => $source->slug(),
             'uri'        => $source->uri(),
-            'date'       => $source->hasDate() ? $source->date() : null,
+            'date'       => $date,
             'collection' => $source->collectionHandle(),
             'blueprint'  => $source->blueprint ?? $source->blueprint()->handle(),
-            'data'       => $data->except(EntryQueryBuilder::COLUMNS),
+            'data'       => $data->except(EntryQueryBuilder::COLUMNS)->except(['parent']),
             'published'  => $source->published(),
             'status'     => $source->status(),
             'updated_at' => $source->lastModified(),
@@ -123,4 +166,12 @@ class Entry extends FileEntry
 
         return EntryFacade::find($this->model->origin_id);
     }
+
+    public function makeLocalization($site)
+    {
+        $this->localizations = null;
+
+        return parent::makeLocalization($site)
+            ->data($this->data());
+    }
 }
diff --git a/src/Entries/EntryRepository.php b/src/Entries/EntryRepository.php
index 8f2a14d..dfd9b10 100644
--- a/src/Entries/EntryRepository.php
+++ b/src/Entries/EntryRepository.php
@@ -58,6 +58,8 @@ class EntryRepository extends StacheRepository
 
         Blink::put("eloquent-entry-{$entry->id()}", $entry);
         Blink::put("eloquent-entry-{$entry->uri()}", $entry);
+
+        $entry->descendants()->each->save();
     }
 
     public function delete($entry)
diff --git a/src/Fields/BlueprintRepository.php b/src/Fields/BlueprintRepository.php
index 4800d83..3537805 100644
--- a/src/Fields/BlueprintRepository.php
+++ b/src/Fields/BlueprintRepository.php
@@ -114,6 +114,13 @@ class BlueprintRepository extends StacheRepository
         return [$namespace, $handle];
     }
 
+    public function getModel($blueprint)
+    {
+        return app('statamic.eloquent.blueprints.blueprint_model')::where('namespace', $blueprint->namespace() ?? null)
+            ->where('handle', $blueprint->handle())
+            ->first();
+    }
+
     public function updateModel($blueprint)
     {
         $model = app('statamic.eloquent.blueprints.blueprint_model')::firstOrNew([
@@ -121,15 +128,20 @@ class BlueprintRepository extends StacheRepository
             'namespace' => $blueprint->namespace() ?? null,
         ]);
 
-        $model->data = $this->addOrderToBlueprintSections($blueprint->contents());
+        $data = $this->addOrderToBlueprintSections($blueprint->contents());
+
+        if (! $blueprint->hidden()) {
+            unset($data['hide']);
+        }
+
+        $model->data = $data;
+
         $model->save();
     }
 
     public function deleteModel($blueprint)
     {
-        $model = app('statamic.eloquent.blueprints.blueprint_model')::where('namespace', $blueprint->namespace() ?? null)
-            ->where('handle', $blueprint->handle())
-            ->first();
+        $model = $this->getModel($blueprint);
 
         if ($model) {
             $model->delete();
diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php
index eb5c9ea..4709bfc 100644
--- a/src/ServiceProvider.php
+++ b/src/ServiceProvider.php
@@ -42,6 +42,7 @@ class ServiceProvider extends AddonServiceProvider
     protected $updateScripts = [
         \Statamic\Eloquent\Updates\AddOrderToEntriesTable::class,
         \Statamic\Eloquent\Updates\AddBlueprintToEntriesTable::class,
+        \Statamic\Eloquent\Updates\DropForeignKeysOnEntriesAndForms::class,
     ];
 
     protected $listen = [
diff --git a/src/Updates/DropForeignKeysOnEntriesAndForms.php b/src/Updates/DropForeignKeysOnEntriesAndForms.php
new file mode 100644
index 0000000..b4a3b48
--- /dev/null
+++ b/src/Updates/DropForeignKeysOnEntriesAndForms.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Statamic\Eloquent\Updates;
+
+use Statamic\UpdateScripts\UpdateScript;
+
+class DropForeignKeysOnEntriesAndForms extends UpdateScript
+{
+    public function shouldUpdate($newVersion, $oldVersion)
+    {
+        return $this->isUpdatingTo('2.3.0');
+    }
+
+    public function update()
+    {
+        $source = __DIR__.'/../../database/migrations/updates/drop_foreign_keys_on_entries.php.stub';
+        $dest = database_path('migrations/'.date('Y_m_d_His').'_drop_foreign_keys_on_entries.php');
+
+        $this->files->copy($source, $dest);
+
+        $source = __DIR__.'/../../database/migrations/updates/drop_foreign_keys_on_forms.php.stub';
+        $dest = database_path('migrations/'.date('Y_m_d_His').'_drop_foreign_keys_on_forms.php');
+
+        $this->files->copy($source, $dest);
+
+        $this->console()->info('Migrations created');
+        $this->console()->comment('Remember to run `php artisan migrate` to apply it to your database.');
+    }
+}


Test patch:
diff --git a/tests/Data/Fields/BlueprintTest.php b/tests/Data/Fields/BlueprintTest.php
new file mode 100644
index 0000000..44953f6
--- /dev/null
+++ b/tests/Data/Fields/BlueprintTest.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Tests\Data\Fields;
+
+use Illuminate\Foundation\Testing\RefreshDatabase;
+use Statamic\Facades\Blueprint;
+use Tests\TestCase;
+
+class BlueprintTest extends TestCase
+{
+    use RefreshDatabase;
+
+    public function setUp(): void
+    {
+        parent::setUp();
+
+        $this->app->singleton(
+            'Statamic\Fields\BlueprintRepository',
+            'Statamic\Eloquent\Fields\BlueprintRepository'
+        );
+
+        $this->app->singleton(
+            'Statamic\Fields\FieldsetRepository',
+            'Statamic\Eloquent\Fields\FieldsetRepository'
+        );
+
+        $this->app->bind('statamic.eloquent.blueprints.blueprint_model', function () {
+            return \Statamic\Eloquent\Fields\BlueprintModel::class;
+        });
+
+        $this->app->bind('statamic.eloquent.blueprints.fieldset_model', function () {
+            return \Statamic\Eloquent\Fields\FieldsetModel::class;
+        });
+    }
+
+    /** @test */
+    public function it_saves_and_removes_hidden_on_model()
+    {
+        $blueprint = Blueprint::make()
+            ->setHandle('test')
+            ->setHidden(true)
+            ->save();
+
+        $model = Blueprint::getModel($blueprint);
+
+        $this->assertTrue($model->data['hide']);
+
+        $blueprint->setHidden(false)->save();
+
+        $model = Blueprint::getModel($blueprint);
+
+        $this->assertFalse(isset($model->data['hide']));
+    }
+
+    /** @test */
+    public function it_deletes_the_model_when_the_blueprint_is_deleted()
+    {
+        $blueprint = Blueprint::make()
+            ->setHandle('test')
+            ->setHidden(true)
+            ->save();
+
+        $model = Blueprint::getModel($blueprint);
+
+        $this->assertNotNull($model);
+
+        $blueprint->delete();
+
+        $model = Blueprint::getModel($blueprint);
+
+        $this->assertNull($model);
+    }
+}
diff --git a/tests/Entries/EntryTest.php b/tests/Entries/EntryTest.php
index c6365a2..7fcff82 100644
--- a/tests/Entries/EntryTest.php
+++ b/tests/Entries/EntryTest.php
@@ -2,10 +2,12 @@
 
 namespace Tests\Entries;
 
+use Facades\Statamic\Fields\BlueprintRepository;
 use Illuminate\Foundation\Testing\RefreshDatabase;
 use Statamic\Eloquent\Collections\Collection;
 use Statamic\Eloquent\Entries\Entry;
 use Statamic\Eloquent\Entries\EntryModel;
+use Statamic\Facades;
 use Tests\TestCase;
 
 class EntryTest extends TestCase
@@ -56,4 +58,127 @@ class EntryTest extends TestCase
 
         $this->assertEquals(collect($model->toArray())->except(['updated_at'])->all(), collect($entry->toModel()->toArray())->except('updated_at')->all());
     }
+
+    /** @test */
+    public function it_propagates_entry_if_configured()
+    {
+        Facades\Site::setConfig([
+            'default' => 'en',
+            'sites' => [
+                'en' => ['name' => 'English', 'locale' => 'en_US', 'url' => 'http://test.com/'],
+                'fr' => ['name' => 'French', 'locale' => 'fr_FR', 'url' => 'http://fr.test.com/'],
+                'es' => ['name' => 'Spanish', 'locale' => 'es_ES', 'url' => 'http://test.com/es/'],
+                'de' => ['name' => 'German', 'locale' => 'de_DE', 'url' => 'http://test.com/de/'],
+            ],
+        ]);
+
+        $collection = (new Collection)
+            ->handle('pages')
+            ->propagate(true)
+            ->sites(['en', 'fr', 'de'])
+            ->save();
+
+        $entry = (new Entry)
+            ->id(1)
+            ->locale('en')
+            ->collection($collection);
+
+        $return = $entry->save();
+
+        $this->assertIsObject($fr = $entry->descendants()->get('fr'));
+        $this->assertIsObject($de = $entry->descendants()->get('de'));
+        $this->assertNull($entry->descendants()->get('es')); // collection not configured for this site
+    }
+
+    /** @test */
+    public function it_propagates_updating_origin_data_to_descendent_models()
+    {
+        Facades\Site::setConfig([
+            'default' => 'en',
+            'sites' => [
+                'en' => ['name' => 'English', 'locale' => 'en_US', 'url' => 'http://test.com/'],
+                'fr' => ['name' => 'French', 'locale' => 'fr_FR', 'url' => 'http://fr.test.com/'],
+                'es' => ['name' => 'Spanish', 'locale' => 'es_ES', 'url' => 'http://test.com/es/'],
+                'de' => ['name' => 'German', 'locale' => 'de_DE', 'url' => 'http://test.com/de/'],
+            ],
+        ]);
+
+        $blueprint = Facades\Blueprint::makeFromFields(['foo' => ['type' => 'test', 'localizable' => true]])->setHandle('test');
+        $blueprint->save();
+
+        BlueprintRepository::shouldReceive('in')->with('collections/pages')->andReturn(collect(['test' => $blueprint]));
+
+        $collection = (new Collection)
+            ->handle('pages')
+            ->propagate(true)
+            ->sites(['en', 'fr', 'de'])
+            ->save();
+
+        $entry = (new Entry)
+            ->id(1)
+            ->locale('en')
+            ->collection($collection)
+            ->blueprint('test')
+            ->data([
+                'foo' => 'bar',
+                'roo' => 'rar',
+            ]);
+
+        $return = $entry->save();
+
+        $this->assertNull($entry->descendants()->get('fr')->model()->data['too'] ?? null);
+        $this->assertNull($entry->descendants()->get('de')->model()->data['too'] ?? null);
+
+        $blueprint->ensureField('too', ['type' => 'test', 'localizable' => true]);
+        $entry->merge(['too' => 'tar']);
+
+        Facades\Entry::save($entry);
+
+        $this->assertNotNull($entry->descendants()->get('fr')->model()->data['too'] ?? null);
+        $this->assertNotNull($entry->descendants()->get('de')->model()->data['too'] ?? null);
+    }
+
+    /** @test */
+    public function it_propagates_origin_date_to_descendent_models()
+    {
+        Facades\Site::setConfig([
+            'default' => 'en',
+            'sites' => [
+                'en' => ['name' => 'English', 'locale' => 'en_US', 'url' => 'http://test.com/'],
+                'fr' => ['name' => 'French', 'locale' => 'fr_FR', 'url' => 'http://fr.test.com/'],
+                'es' => ['name' => 'Spanish', 'locale' => 'es_ES', 'url' => 'http://test.com/es/'],
+                'de' => ['name' => 'German', 'locale' => 'de_DE', 'url' => 'http://test.com/de/'],
+            ],
+        ]);
+
+        $blueprint = Facades\Blueprint::makeFromFields(['foo' => ['type' => 'test', 'localizable' => true]])->setHandle('test');
+        $blueprint->save();
+
+        BlueprintRepository::shouldReceive('in')->with('collections/pages')->andReturn(collect(['test' => $blueprint]));
+
+        $collection = (new Collection)
+            ->handle('pages')
+            ->dated(true)
+            ->propagate(true)
+            ->sites(['en', 'fr', 'de'])
+            ->save();
+
+        $entry = (new Entry)
+            ->id(1)
+            ->collection($collection)
+            ->date('2023-01-01')
+            ->locale('en')
+            ->blueprint('test');
+
+        $return = $entry->save();
+
+        $this->assertEquals($entry->descendants()->get('fr')->model()->date, '2023-01-01 00:00:00');
+
+        $blueprint->ensureField('too', ['type' => 'test', 'localizable' => true]);
+        $entry->date('2024-01-01');
+
+        Facades\Entry::save($entry);
+
+        $this->assertEquals($entry->descendants()->get('fr')->model()->date, '2024-01-01 00:00:00');
+    }
 }