Repository: statamic/eloquent-driver (php)

Issue:
Assets can not be stached after being moved to database
I am migrating our site's content from flat files to a Postgres database to improve performance. The site works and the files are migrated, but every time I try to `run php please stache:refresh`, I get the following error when the `$handle = 'all'`:

```
 TypeError

  Statamic\Eloquent\Assets\AssetContainerRepository::findByHandle(): Return value must be of type ?Statamic\Contracts\Assets\AssetContainer, Illuminate\Support\Collection returned

  at vendor/statamic/eloquent-driver/src/Assets/AssetContainerRepository.php:41
     37▕                 return null;
     38▕             }
     39▕
     40▕             return app(AssetContainerContract::class)->fromModel($model);
  ➜  41▕         });
     42▕     }
     43▕
     44▕     public function make(string $handle = null): AssetContainerContract
     45▕     {

      +33 vendor frames
  34  please:37
      Illuminate\Foundation\Console\Kernel::handle(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
```
When I access the site without the stache is it able to get the assets using the individual $handles, and when I hardcode the $handle inside `AssetContainerRepository` it works fine but it fails on `$handle = 'all'`. I presume it is because it gets a collection of AssetContainers but is there a way to fix this?

I also get the following when trying to access assets in the control panel:

```
TypeError
Statamic\Eloquent\Assets\AssetContainerRepository::all(): Return value must be of type Illuminate\Support\Collection, Statamic\Eloquent\Assets\AssetContainer returned (View: /Users/martin/Sites/sd-statamic/vendor/statamic/cms/resources/views/layout.blade.php) 
```

**Setup:**

Statamic 3.4.5 Pro
Laravel 8.83.27
PHP 8.1.16
Stache Watcher Enabled
Static Caching Disabled
adapt/tfa 1.0.1
statamic/eloquent-driver 1.2.0
thehome/statamic-elasticsearch dev-ADP-1213-loadtide-statamic-update

Pull request description:
Change blink handle for 'all' as it clashes with handles of 'all'
Fixes #135

Patch:
diff --git a/README.md b/README.md
index 7e4e58e..e45607a 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ Since Statamic uses UUIDs within content files by default, we provide two soluti
 If you're starting from scratch, we can use traditional incrementing integers for IDs.
 
 - Delete `content/collections/pages/home.md`
-- Change the structure `tree` in `content/collections/pages.yaml` to `{}`.
+- Change the structure `tree` in `content/trees/collections/pages.yaml` to `{}`.
 - Run `php artisan vendor:publish --provider="Statamic\Eloquent\ServiceProvider" --tag=migrations`.
 - Run `php artisan vendor:publish --tag="statamic-eloquent-entries-table"`.
 - Run `php artisan migrate`.
diff --git a/config/eloquent-driver.php b/config/eloquent-driver.php
index bf1eb0b..e04c915 100644
--- a/config/eloquent-driver.php
+++ b/config/eloquent-driver.php
@@ -22,6 +22,7 @@ return [
         'model'      => \Statamic\Eloquent\Collections\CollectionModel::class,
         'tree'       => \Statamic\Eloquent\Structures\CollectionTree::class,
         'tree_model' => \Statamic\Eloquent\Structures\TreeModel::class,
+        'update_entry_order_queue' => 'default',
     ],
 
     'entries' => [
diff --git a/src/Assets/AssetContainer.php b/src/Assets/AssetContainer.php
index 2883085..a5e8291 100644
--- a/src/Assets/AssetContainer.php
+++ b/src/Assets/AssetContainer.php
@@ -48,6 +48,8 @@ class AssetContainer extends FileEntry
             ->allowRenaming($model->settings['allow_renaming'] ?? null)
             ->createFolders($model->settings['create_folders'] ?? null)
             ->searchIndex($model->settings['search_index'] ?? null)
+            ->sourcePreset($model->settings['source_preset'] ?? null)
+            ->warmPresets($model->settings['warm_presets'] ?? null)
             ->model($model);
 
         return $this;
@@ -67,6 +69,8 @@ class AssetContainer extends FileEntry
                 'allow_renaming'    => $this->allowRenaming(),
                 'create_folders'    => $this->createFolders(),
                 'search_index'      => $this->searchIndex(),
+                'source_preset'     => $this->sourcePreset,
+                'warm_presets'      => $this->warmPresets,
             ],
         ]);
     }
diff --git a/src/Assets/AssetContainerRepository.php b/src/Assets/AssetContainerRepository.php
index 8b27219..4cb2264 100644
--- a/src/Assets/AssetContainerRepository.php
+++ b/src/Assets/AssetContainerRepository.php
@@ -13,10 +13,10 @@ class AssetContainerRepository extends StacheRepository
 
     public function all(): Collection
     {
-        return Blink::once('eloquent-assetcontainers-all', function () {
+        return Blink::once('eloquent-assetcontainers', function () {
             return app('statamic.eloquent.assets.container_model')::all()
                 ->map(function ($model) {
-                    return Blink::once("eloquent-assetcontainers-{$model->handle}", function () use ($model) {
+                    return Blink::once("eloquent-assetcontainer-{$model->handle}", function () use ($model) {
                         return app(AssetContainerContract::class)->fromModel($model);
                     });
                 });
@@ -25,7 +25,7 @@ class AssetContainerRepository extends StacheRepository
 
     public function findByHandle(string $handle): ?AssetContainerContract
     {
-        return Blink::once("eloquent-assetcontainers-{$handle}", function () use ($handle) {
+        return Blink::once("eloquent-assetcontainer-{$handle}", function () use ($handle) {
             $model = app('statamic.eloquent.assets.container_model')::whereHandle($handle)->first();
 
             if (! $model) {
@@ -45,15 +45,15 @@ class AssetContainerRepository extends StacheRepository
     {
         $container->save();
 
-        Blink::forget("eloquent-assetcontainers-{$container->handle()}");
+        Blink::forget("eloquent-assetcontainer-{$container->handle()}");
     }
 
     public function delete($container)
     {
         $container->delete();
 
-        Blink::forget("eloquent-assetcontainers-{$container->handle()}");
-        Blink::forget('eloquent-assetcontainers-all');
+        Blink::forget("eloquent-assetcontainer-{$container->handle()}");
+        Blink::forget('eloquent-assetcontainers');
     }
 
     public static function bindings(): array
diff --git a/src/Assets/AssetRepository.php b/src/Assets/AssetRepository.php
index aac6c32..a7b5fed 100644
--- a/src/Assets/AssetRepository.php
+++ b/src/Assets/AssetRepository.php
@@ -15,7 +15,7 @@ class AssetRepository extends BaseRepository
         $asset->container()->contents()->forget($asset->path())->save();
 
         $handle = $asset->containerHandle().'::'.$asset->metaPath();
-        app('statamic.eloquent.assets.model')::where('handle', $handle)->first()->delete();
+        app('statamic.eloquent.assets.model')::where('handle', $handle)->first()?->delete();
 
         Stache::store('assets::'.$asset->containerHandle())->delete($asset);
     }
diff --git a/src/Collections/Collection.php b/src/Collections/Collection.php
index 113e43c..91a092c 100644
--- a/src/Collections/Collection.php
+++ b/src/Collections/Collection.php
@@ -65,8 +65,8 @@ class Collection extends FileEntry
                 'revisions'            => $source->revisionsEnabled(),
                 'default_status'       => $source->defaultPublishState,
                 'structure'            => $source->structureContents(),
-                'sort_dir'             => $source->sortDirection(),
-                'sort_field'           => $source->sortField(),
+                'sort_dir'             => $source->customSortDirection(),
+                'sort_field'           => $source->customSortField(),
                 'taxonomies'           => $source->taxonomies,
                 'propagate'            => $source->propagate(),
                 'past_date_behavior'   => $source->pastDateBehavior(),
@@ -100,4 +100,14 @@ class Collection extends FileEntry
             ->showSlugs($this->structureContents['slugs'] ?? false)
             ->maxDepth($this->structureContents['max_depth'] ?? null);
     }
+
+    public function customSortField()
+    {
+        return $this->sortField;
+    }
+
+    public function customSortDirection()
+    {
+        return $this->sortDirection;
+    }
 }
diff --git a/src/Collections/CollectionRepository.php b/src/Collections/CollectionRepository.php
index 4cf6f39..88f5ee4 100644
--- a/src/Collections/CollectionRepository.php
+++ b/src/Collections/CollectionRepository.php
@@ -4,6 +4,7 @@ namespace Statamic\Eloquent\Collections;
 
 use Illuminate\Support\Collection as IlluminateCollection;
 use Statamic\Contracts\Entries\Collection as CollectionContract;
+use Statamic\Eloquent\Jobs\UpdateCollectionEntryOrder;
 use Statamic\Facades\Blink;
 use Statamic\Stache\Repositories\CollectionRepository as StacheRepository;
 
@@ -24,7 +25,7 @@ class CollectionRepository extends StacheRepository
 
     public function all(): IlluminateCollection
     {
-        return Blink::once('eloquent-collections-all', function () {
+        return Blink::once('eloquent-collections', function () {
             return $this->transform(app('statamic.eloquent.collections.model')::all());
         });
     }
@@ -49,7 +50,7 @@ class CollectionRepository extends StacheRepository
         $model->save();
 
         Blink::forget("eloquent-collection-{$model->handle}");
-        Blink::forget('eloquent-collections-all');
+        Blink::forget('eloquent-collections');
 
         $entry->model($model->fresh());
     }
@@ -60,7 +61,7 @@ class CollectionRepository extends StacheRepository
         $model->delete();
 
         Blink::forget("eloquent-collection-{$model->handle}");
-        Blink::forget('eloquent-collections-all');
+        Blink::forget('eloquent-collections');
     }
 
     protected function transform($items, $columns = [])
@@ -81,6 +82,11 @@ class CollectionRepository extends StacheRepository
 
     public function updateEntryOrder(CollectionContract $collection, $ids = null)
     {
-        $collection->queryEntries()->get()->each->save();
+        $collection->queryEntries()
+            ->get(['id'])
+            ->each(function ($entry) {
+                UpdateCollectionEntryOrder::dispatch($entry->id())
+                    ->onQueue(config('statamic.eloquent-driver.collections.update_entry_order_queue', 'default'));
+            });
     }
 }
diff --git a/src/Commands/ImportCollections.php b/src/Commands/ImportCollections.php
index bb6f5de..911ca5b 100644
--- a/src/Commands/ImportCollections.php
+++ b/src/Commands/ImportCollections.php
@@ -10,7 +10,6 @@ use Statamic\Contracts\Entries\Collection as CollectionContract;
 use Statamic\Contracts\Entries\CollectionRepository as CollectionRepositoryContract;
 use Statamic\Contracts\Structures\CollectionTreeRepository as CollectionTreeRepositoryContract;
 use Statamic\Eloquent\Collections\Collection as EloquentCollection;
-use Statamic\Eloquent\Structures\CollectionTree as EloquentCollectionTree;
 use Statamic\Entries\Collection as StacheCollection;
 use Statamic\Facades\Collection as CollectionFacade;
 use Statamic\Stache\Repositories\CollectionRepository;
@@ -85,7 +84,7 @@ class ImportCollections extends Command
             if ($structure = $collection->structure()) {
                 $structure->trees()->each(function ($tree) {
                     $lastModified = $tree->fileLastModified();
-                    EloquentCollectionTree::makeModelFromContract($tree)
+                    app('statamic.eloquent.collections.tree')::makeModelFromContract($tree)
                         ->fill(['created_at' => $lastModified, 'updated_at' => $lastModified])
                         ->save();
                 });
diff --git a/src/Commands/ImportForms.php b/src/Commands/ImportForms.php
index 62a45b0..8a5b1e0 100644
--- a/src/Commands/ImportForms.php
+++ b/src/Commands/ImportForms.php
@@ -8,7 +8,6 @@ use Statamic\Console\RunsInPlease;
 use Statamic\Contracts\Forms\Form as FormContract;
 use Statamic\Contracts\Forms\Submission as SubmissionContract;
 use Statamic\Eloquent\Forms\Form;
-use Statamic\Eloquent\Forms\SubmissionModel;
 use Statamic\Facades\File;
 use Statamic\Forms\Form as StacheForm;
 use Statamic\Forms\FormRepository;
@@ -65,7 +64,7 @@ class ImportForms extends Command
             $model->save();
 
             $form->submissions()->each(function ($submission) use ($model) {
-                $timestamp = (new SubmissionModel())->fromDateTime($submission->date());
+                $timestamp = app('statamic.eloquent.forms.submission_model')::make()->fromDateTime($submission->date());
 
                 $model->submissions()->firstOrNew(['created_at' => $timestamp])->fill([
                     'data'       => $submission->data(),
diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php
index b94927b..d8a3137 100644
--- a/src/Entries/Entry.php
+++ b/src/Entries/Entry.php
@@ -28,7 +28,9 @@ class Entry extends FileEntry
         }
 
         if (config('statamic.system.track_last_update')) {
-            $entry->set('updated_at', $model->updated_at ?? $model->created_at);
+            if ($updatedAt = $model->updated_at ?? $model->created_at) {
+                $entry->set('updated_at', $updatedAt->timestamp);
+            }
         }
 
         return $entry;
diff --git a/src/Fields/BlueprintRepository.php b/src/Fields/BlueprintRepository.php
index 0ef80bb..4800d83 100644
--- a/src/Fields/BlueprintRepository.php
+++ b/src/Fields/BlueprintRepository.php
@@ -21,7 +21,7 @@ class BlueprintRepository extends StacheRepository
                 return null;
             }
 
-            $blueprintModel = ($namespace ? $this->filesIn($namespace) : BlueprintModel::whereNull('namespace'))
+            $blueprintModel = ($namespace ? $this->filesIn($namespace) : app('statamic.eloquent.blueprints.blueprint_model')::whereNull('namespace'))
                 ->where('handle', $handle)
                 ->first();
 
@@ -83,7 +83,7 @@ class BlueprintRepository extends StacheRepository
         return Blink::store(self::BLINK_NAMESPACE_PATHS)->once($namespace ?? 'none', function () use ($namespace) {
             $namespace = str_replace('/', '.', $namespace);
 
-            if (count($blueprintModels = BlueprintModel::where('namespace', $namespace)->get()) == 0) {
+            if (count($blueprintModels = app('statamic.eloquent.blueprints.blueprint_model')::where('namespace', $namespace)->get()) == 0) {
                 return collect();
             }
 
diff --git a/src/Fields/FieldsetRepository.php b/src/Fields/FieldsetRepository.php
index 798ddf0..8c054b8 100644
--- a/src/Fields/FieldsetRepository.php
+++ b/src/Fields/FieldsetRepository.php
@@ -11,7 +11,7 @@ class FieldsetRepository extends StacheRepository
 {
     public function all(): Collection
     {
-        return Blink::once('eloquent-fieldsets-all', function () {
+        return Blink::once('eloquent-fieldsets', function () {
             if (count($models = app('statamic.eloquent.blueprints.fieldset_model')::get() ?? collect()) === 0) {
                 return collect();
             }
@@ -66,6 +66,6 @@ class FieldsetRepository extends StacheRepository
         }
 
         Blink::forget("eloquent-fieldset-{$model->handle}");
-        Blink::forget('eloquent-fieldsets-all');
+        Blink::forget('eloquent-fieldsets');
     }
 }
diff --git a/src/Forms/FormModel.php b/src/Forms/FormModel.php
index d98f74a..b75970a 100644
--- a/src/Forms/FormModel.php
+++ b/src/Forms/FormModel.php
@@ -16,6 +16,6 @@ class FormModel extends BaseModel
 
     public function submissions()
     {
-        return $this->hasMany(SubmissionModel::class, 'form_id');
+        return $this->hasMany(app('statamic.eloquent.forms.submission_model'), 'form_id');
     }
 }
diff --git a/src/Forms/FormRepository.php b/src/Forms/FormRepository.php
index f5674f4..fa920fa 100644
--- a/src/Forms/FormRepository.php
+++ b/src/Forms/FormRepository.php
@@ -21,7 +21,7 @@ class FormRepository extends StacheRepository
 
     public function all()
     {
-        return FormModel::all()
+        return app('statamic.eloquent.forms.model')::all()
             ->map(function ($form) {
                 return app(FormContract::class)::fromModel($form);
             });
diff --git a/src/Forms/Submission.php b/src/Forms/Submission.php
index 0bd271c..0bc5276 100644
--- a/src/Forms/Submission.php
+++ b/src/Forms/Submission.php
@@ -4,8 +4,10 @@ namespace Statamic\Eloquent\Forms;
 
 use Illuminate\Support\Carbon;
 use Statamic\Eloquent\Forms\SubmissionModel as Model;
+use Statamic\Events\SubmissionCreated;
 use Statamic\Events\SubmissionDeleted;
 use Statamic\Events\SubmissionSaved;
+use Statamic\Events\SubmissionSaving;
 use Statamic\Forms\Submission as FileEntry;
 
 class Submission extends FileEntry
@@ -61,11 +63,19 @@ class Submission extends FileEntry
 
     public function save()
     {
+        if (SubmissionSaving::dispatch($this) === false) {
+            return false;
+        }
+
         $model = $this->toModel();
         $model->save();
+        $isNew = $model->wasRecentlyCreated;
 
         $this->model($model->fresh());
 
+        if ($isNew) {
+            SubmissionCreated::dispatch($this);
+        }
         SubmissionSaved::dispatch($this);
     }
 
diff --git a/src/Forms/SubmissionModel.php b/src/Forms/SubmissionModel.php
index d462b27..6d17008 100644
--- a/src/Forms/SubmissionModel.php
+++ b/src/Forms/SubmissionModel.php
@@ -18,6 +18,6 @@ class SubmissionModel extends BaseModel
 
     public function form()
     {
-        return $this->belongsTo(FormModel::class, 'id');
+        return $this->belongsTo(app('statamic.eloquent.forms.model'), 'id');
     }
 }
diff --git a/src/Globals/GlobalRepository.php b/src/Globals/GlobalRepository.php
index 9ebd0e9..aeb3bf4 100644
--- a/src/Globals/GlobalRepository.php
+++ b/src/Globals/GlobalRepository.php
@@ -38,7 +38,7 @@ class GlobalRepository extends StacheRepository
 
     public function all(): GlobalCollection
     {
-        return Blink::once('eloquent-globalsets-all', function () {
+        return Blink::once('eloquent-globalsets', function () {
             return $this->transform(app('statamic.eloquent.global_sets.model')::all());
         });
     }
@@ -58,7 +58,7 @@ class GlobalRepository extends StacheRepository
         $entry->model()->delete();
 
         Blink::forget("eloquent-globalsets-{$entry->handle()}");
-        Blink::forget('eloquent-globalsets-all');
+        Blink::forget('eloquent-globalsets');
     }
 
     public static function bindings(): array
diff --git a/src/Jobs/UpdateCollectionEntryOrder.php b/src/Jobs/UpdateCollectionEntryOrder.php
new file mode 100644
index 0000000..8cfdebc
--- /dev/null
+++ b/src/Jobs/UpdateCollectionEntryOrder.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Statamic\Eloquent\Jobs;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Foundation\Bus\Dispatchable;
+use Illuminate\Queue\InteractsWithQueue;
+use Statamic\Facades\Entry;
+
+class UpdateCollectionEntryOrder implements ShouldQueue
+{
+    use Dispatchable, InteractsWithQueue, Queueable;
+
+    public $entryId;
+
+    public function __construct($entryId)
+    {
+        $this->entryId = $entryId;
+    }
+
+    public function handle()
+    {
+        if ($entry = Entry::find($this->entryId)) {
+            $entry->save();
+        }
+    }
+}
diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php
index d7b05ee..becea3f 100644
--- a/src/ServiceProvider.php
+++ b/src/ServiceProvider.php
@@ -21,6 +21,7 @@ use Statamic\Eloquent\Entries\EntryQueryBuilder;
 use Statamic\Eloquent\Entries\EntryRepository;
 use Statamic\Eloquent\Forms\FormRepository;
 use Statamic\Eloquent\Globals\GlobalRepository;
+use Statamic\Eloquent\Listeners\UpdateStructuredEntryOrder;
 use Statamic\Eloquent\Revisions\RevisionRepository;
 use Statamic\Eloquent\Structures\CollectionTreeRepository;
 use Statamic\Eloquent\Structures\NavigationRepository;
@@ -28,6 +29,7 @@ use Statamic\Eloquent\Structures\NavTreeRepository;
 use Statamic\Eloquent\Taxonomies\TaxonomyRepository;
 use Statamic\Eloquent\Taxonomies\TermQueryBuilder;
 use Statamic\Eloquent\Taxonomies\TermRepository;
+use Statamic\Events\CollectionTreeSaved;
 use Statamic\Providers\AddonServiceProvider;
 use Statamic\Statamic;
 
@@ -41,6 +43,12 @@ class ServiceProvider extends AddonServiceProvider
         \Statamic\Eloquent\Updates\AddOrderToEntriesTable::class,
     ];
 
+    protected $listen = [
+        CollectionTreeSaved::class => [
+            UpdateStructuredEntryOrder::class,
+        ],
+    ];
+
     public function boot()
     {
         parent::boot();
diff --git a/src/Structures/NavigationRepository.php b/src/Structures/NavigationRepository.php
index 60a096f..7f83993 100644
--- a/src/Structures/NavigationRepository.php
+++ b/src/Structures/NavigationRepository.php
@@ -25,13 +25,13 @@ class NavigationRepository extends StacheRepository
 
     public function all(): Collection
     {
-        return $this->transform(NavModel::all());
+        return $this->transform(app('statamic.eloquent.navigations.model')::all());
     }
 
     public function findByHandle($handle): ?NavContract
     {
         return Blink::once("eloquent-nav-{$handle}", function () use ($handle) {
-            $model = NavModel::whereHandle($handle)->first();
+            $model = app('statamic.eloquent.navigations.model')::whereHandle($handle)->first();
 
             return $model ? app(NavContract::class)->fromModel($model) : null;
         });
diff --git a/src/Taxonomies/TaxonomyRepository.php b/src/Taxonomies/TaxonomyRepository.php
index 2a36e2d..6fd373f 100644
--- a/src/Taxonomies/TaxonomyRepository.php
+++ b/src/Taxonomies/TaxonomyRepository.php
@@ -27,7 +27,7 @@ class TaxonomyRepository extends StacheRepository
 
     public function all(): Collection
     {
-        $models = Blink::once('eloquent-taxonomies-all', function () {
+        $models = Blink::once('eloquent-taxonomies', function () {
             return app('statamic.eloquent.taxonomies.model')::all();
         })
             ->each(function ($model) {
@@ -92,6 +92,6 @@ class TaxonomyRepository extends StacheRepository
         $model->delete();
 
         Blink::forget("eloquent-taxonomies-{$model->handle}");
-        Blink::forget('eloquent-taxonomies-all');
+        Blink::forget('eloquent-taxonomies');
     }
 }


Test patch:
diff --git a/src/Listeners/UpdateStructuredEntryOrder.php b/src/Listeners/UpdateStructuredEntryOrder.php
new file mode 100644
index 0000000..65c2d55
--- /dev/null
+++ b/src/Listeners/UpdateStructuredEntryOrder.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Statamic\Eloquent\Listeners;
+
+use Statamic\Eloquent\Collections\CollectionRepository;
+use Statamic\Events\CollectionTreeSaved;
+
+class UpdateStructuredEntryOrder
+{
+    public function handle(CollectionTreeSaved $event)
+    {
+        $tree = $event->tree;
+        $collection = $tree->collection();
+
+        if (config('statamic.eloquent-driver.entries.driver', 'file') !== 'eloquent') {
+            return;
+        }
+
+        if (config('statamic.eloquent-driver.collections.driver') === 'eloquent') {
+            // If the collections are configured to use Eloquent, then the entry
+            // order will be updated through the regular event/listener flow.
+            return;
+        }
+
+        $diff = $tree->diff();
+
+        $ids = array_merge($diff->moved(), $diff->added());
+
+        if (empty($ids)) {
+            return;
+        }
+
+        app(CollectionRepository::class)->updateEntryOrder($collection, $ids);
+    }
+}