From 7b93d2463d03f5866cbf63e33841f2c496a54791 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Mon, 15 Jul 2024 09:25:47 +0100 Subject: [PATCH 1/7] Add config for referencing Assets using model ids instead of container::path ids --- config/eloquent-driver.php | 1 + src/Assets/Asset.php | 70 ++++++++++++++++++++++++++++++++++---- tests/Assets/AssetTest.php | 35 +++++++++++++++++++ 3 files changed, 99 insertions(+), 7 deletions(-) diff --git a/config/eloquent-driver.php b/config/eloquent-driver.php index b6901f5a..7465d975 100644 --- a/config/eloquent-driver.php +++ b/config/eloquent-driver.php @@ -14,6 +14,7 @@ 'driver' => 'file', 'model' => \Statamic\Eloquent\Assets\AssetModel::class, 'asset' => \Statamic\Eloquent\Assets\Asset::class, + 'use_model_keys_for_ids' => false, ], 'blueprints' => [ diff --git a/src/Assets/Asset.php b/src/Assets/Asset.php index 5d917cab..ca2aa540 100644 --- a/src/Assets/Asset.php +++ b/src/Assets/Asset.php @@ -30,12 +30,15 @@ public function syncOriginal() protected $existsOnDisk = false; protected $removedData = []; + protected $model; + public static function fromModel(Model $model) { return (new static()) ->container($model->container) ->path(Str::replace('//', '/', $model->folder.'/'.$model->basename)) ->hydrateMeta($model->meta) + ->model($model) ->syncOriginal(); } @@ -60,6 +63,20 @@ public function meta($key = null) return $meta; } + if (config('statamic.eloquent-driver.assets.use_model_keys_for_ids', false)) { + if ($this->model) { + return $this->model->meta; + } + + $meta = $this->generateMeta(); + + if (! $meta['data']) { + $meta['data'] = []; + } + + return $meta; + } + return Blink::once($this->metaCacheKey(), function () { if ($model = app('statamic.eloquent.assets.model')::where([ 'container' => $this->containerHandle(), @@ -129,7 +146,11 @@ public function writeMeta($meta) { $meta['data'] = Arr::removeNullValues($meta['data']); - self::makeModelFromContract($this, $meta)?->save(); + if ($model = self::makeModelFromContract($this, $meta)) { + $model->save(); + + $this->model = $model; + } Blink::put('eloquent-asset-meta-exists-'.$this->id(), true); } @@ -147,11 +168,21 @@ public static function makeModelFromContract(AssetContract $source, $meta = []) $original = $source->getOriginal(); - $model = app('statamic.eloquent.assets.model')::firstOrNew([ - 'container' => $source->containerHandle(), - 'folder' => Arr::get($original, 'folder', $source->folder()), - 'basename' => Arr::get($original, 'basename', $source->basename()), - ])->fill([ + $model = false; + + if (config('statamic.eloquent-driver.assets.use_model_keys_for_ids', false)) { + $model = $source->model(); + } + + if (! $model) { + $model = app('statamic.eloquent.assets.model')::firstOrNew([ + 'container' => $source->containerHandle(), + 'folder' => Arr::get($original, 'folder', $source->folder()), + 'basename' => Arr::get($original, 'basename', $source->basename()), + ]); + } + + $model->fill([ 'meta' => $meta, 'filename' => $source->filename(), 'extension' => $extension, @@ -195,7 +226,8 @@ public function move($folder, $filename = null) $this->path($newPath); $this->save(); - if ($oldMetaPath != $this->metaPath()) { + // if we arent referencing assets by the database model id, then we need to find any old models by the previous path and update them + if (! config('statamic.eloquent-driver.assets.use_model_keys_for_ids', false) && ($oldMetaPath != $this->metaPath())) { $oldMetaModel = app('statamic.eloquent.assets.model')::where([ 'container' => $this->containerHandle(), 'folder' => $oldFolder, @@ -213,6 +245,30 @@ public function move($folder, $filename = null) return $this; } + public function model($model = null) + { + if (func_num_args() === 0) { + return $this->model; + } + + $this->model = $model; + + return $this; + } + + public function id($id = null) + { + if ($id || ! config('statamic.eloquent-driver.assets.use_model_keys_for_ids', false)) { + return parent::id($id); + } + + if (! $this->model) { + throw new \Exception('ID is not available until asset is saved'); + } + + return $this->model->getKey(); + } + public function getCurrentDirtyStateAttributes(): array { return array_merge([ diff --git a/tests/Assets/AssetTest.php b/tests/Assets/AssetTest.php index 6f763cc1..332703e2 100644 --- a/tests/Assets/AssetTest.php +++ b/tests/Assets/AssetTest.php @@ -4,6 +4,7 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Storage; +use Orchestra\Testbench\Attributes\DefineEnvironment; use PHPUnit\Framework\Attributes\Test; use Statamic\Facades; use Tests\TestCase; @@ -52,4 +53,38 @@ public function saving_an_asset_clears_the_eloquent_blink_cache() $this->assertFalse(Facades\Blink::has("eloquent-asset-{$asset->id()}")); } + + #[Test] + public function not_referencing_by_id_gives_a_container_and_path_id() + { + $asset = Facades\Asset::find('test::f.jpg'); + + $this->assertNotSame($asset->id(), $asset->model()->getKey()); + $this->assertStringContainsString("::", $asset->id()); + } + + #[Test] + #[DefineEnvironment('setUseModelKeysConfig')] + public function referencing_by_id_gives_a_model_id() + { + $asset = Facades\Asset::find('test::f.jpg'); + + $this->assertSame($asset->id(), $asset->model()->getKey()); + } + + #[Test] + #[DefineEnvironment('setUseModelKeysConfig')] + public function an_error_is_thrown_when_getting_id_before_asset_is_saved() + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage('ID is not available until asset is saved'); + + Storage::disk('test')->put('new.jpg', ''); + Facades\Asset::make()->container('test')->path('new.jpg')->id(); + } + + protected function setUseModelKeysConfig($app) + { + $app['config']->set('statamic.eloquent-driver.assets.use_model_keys_for_ids', true); + } } From 0d4caaa2d6a7150d33ca595b918ff9c187c5b8fd Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Mon, 15 Jul 2024 09:28:01 +0100 Subject: [PATCH 2/7] :beer: --- tests/Assets/AssetTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Assets/AssetTest.php b/tests/Assets/AssetTest.php index 332703e2..4c88f45f 100644 --- a/tests/Assets/AssetTest.php +++ b/tests/Assets/AssetTest.php @@ -60,7 +60,7 @@ public function not_referencing_by_id_gives_a_container_and_path_id() $asset = Facades\Asset::find('test::f.jpg'); $this->assertNotSame($asset->id(), $asset->model()->getKey()); - $this->assertStringContainsString("::", $asset->id()); + $this->assertStringContainsString('::', $asset->id()); } #[Test] From 56e49822be78168742c10fa28d5e45d856942735 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Mon, 15 Jul 2024 10:06:01 +0100 Subject: [PATCH 3/7] Update repository queries --- src/Assets/AssetRepository.php | 15 ++++++++++++++- tests/Assets/AssetTest.php | 11 +++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Assets/AssetRepository.php b/src/Assets/AssetRepository.php index 9b2da69f..224530ba 100644 --- a/src/Assets/AssetRepository.php +++ b/src/Assets/AssetRepository.php @@ -13,12 +13,25 @@ class AssetRepository extends BaseRepository { public function findById($id): ?AssetContract { + $blinkKey = "eloquent-asset-{$id}"; + [$container, $path] = explode('::', $id); + if (config('statamic.eloquent-driver.assets.use_model_keys_for_ids', false) && str_contains($path, '.') === false) { + $item = Blink::once($blinkKey, fn () => $this->query()->where('id', $path)->first()); + + if (! $item) { + Blink::forget($blinkKey); + + return null; + } + + return $item; + } + $filename = Str::afterLast($path, '/'); $folder = str_contains($path, '/') ? Str::beforeLast($path, '/') : '/'; - $blinkKey = "eloquent-asset-{$id}"; $item = Blink::once($blinkKey, function () use ($container, $filename, $folder) { return $this->query() ->where('container', $container) diff --git a/tests/Assets/AssetTest.php b/tests/Assets/AssetTest.php index 4c88f45f..46faacf3 100644 --- a/tests/Assets/AssetTest.php +++ b/tests/Assets/AssetTest.php @@ -6,6 +6,7 @@ use Illuminate\Support\Facades\Storage; use Orchestra\Testbench\Attributes\DefineEnvironment; use PHPUnit\Framework\Attributes\Test; +use Statamic\Eloquent\Assets\Asset; use Statamic\Facades; use Tests\TestCase; @@ -83,6 +84,16 @@ public function an_error_is_thrown_when_getting_id_before_asset_is_saved() Facades\Asset::make()->container('test')->path('new.jpg')->id(); } + #[Test] + #[DefineEnvironment('setUseModelKeysConfig')] + public function using_find_with_an_id_returns_an_asset() + { + $asset = Facades\Asset::find('test::6'); + + $this->assertInstanceOf(Asset::class, $asset); + $this->assertSame('f.jpg', $asset->basename()); + } + protected function setUseModelKeysConfig($app) { $app['config']->set('statamic.eloquent-driver.assets.use_model_keys_for_ids', true); From 21f6a91d6a5416ce964589aa2c3bf7e7360a3d6a Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Mon, 15 Jul 2024 10:08:49 +0100 Subject: [PATCH 4/7] Also handle finding by just id, no asset container --- src/Assets/AssetRepository.php | 4 ++++ tests/Assets/AssetTest.php | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/Assets/AssetRepository.php b/src/Assets/AssetRepository.php index 224530ba..a5340b23 100644 --- a/src/Assets/AssetRepository.php +++ b/src/Assets/AssetRepository.php @@ -18,6 +18,10 @@ public function findById($id): ?AssetContract [$container, $path] = explode('::', $id); if (config('statamic.eloquent-driver.assets.use_model_keys_for_ids', false) && str_contains($path, '.') === false) { + if (! $path) { + $path = $container; + } + $item = Blink::once($blinkKey, fn () => $this->query()->where('id', $path)->first()); if (! $item) { diff --git a/tests/Assets/AssetTest.php b/tests/Assets/AssetTest.php index 46faacf3..0dbcecaf 100644 --- a/tests/Assets/AssetTest.php +++ b/tests/Assets/AssetTest.php @@ -92,6 +92,11 @@ public function using_find_with_an_id_returns_an_asset() $this->assertInstanceOf(Asset::class, $asset); $this->assertSame('f.jpg', $asset->basename()); + + $asset = Facades\Asset::find('6'); + + $this->assertInstanceOf(Asset::class, $asset); + $this->assertSame('f.jpg', $asset->basename()); } protected function setUseModelKeysConfig($app) From dd8c40f409df95fabf184fe113261e08114dd8bd Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Mon, 15 Jul 2024 10:21:25 +0100 Subject: [PATCH 5/7] Fix test --- src/Assets/AssetRepository.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Assets/AssetRepository.php b/src/Assets/AssetRepository.php index a5340b23..a37a1d47 100644 --- a/src/Assets/AssetRepository.php +++ b/src/Assets/AssetRepository.php @@ -17,14 +17,10 @@ public function findById($id): ?AssetContract [$container, $path] = explode('::', $id); - if (config('statamic.eloquent-driver.assets.use_model_keys_for_ids', false) && str_contains($path, '.') === false) { - if (! $path) { - $path = $container; - } - - $item = Blink::once($blinkKey, fn () => $this->query()->where('id', $path)->first()); + if (config('statamic.eloquent-driver.assets.use_model_keys_for_ids', false) && (str_contains($path, '.') === false)) { + $item = Blink::once($blinkKey, fn() => $this->query()->where('id', $path)->first()); - if (! $item) { + if (!$item) { Blink::forget($blinkKey); return null; @@ -55,6 +51,11 @@ public function findById($id): ?AssetContract public function findByUrl(string $url) { + // handle find('model-key'), with no container + if (! str_contains('.', $url)) { + return $this->findById('::'.$url); + } + if (! $container = $this->resolveContainerFromUrl($url)) { return null; } From 552c3ce195da806f8e6a2122b9c494af2b4c123f Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Mon, 15 Jul 2024 10:26:53 +0100 Subject: [PATCH 6/7] Add conversion command --- src/Assets/AssetRepository.php | 4 +- .../UpdateAssetReferencesToUseModelKeys.php | 51 +++++++++++++++++++ src/ServiceProvider.php | 1 + 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 src/Commands/UpdateAssetReferencesToUseModelKeys.php diff --git a/src/Assets/AssetRepository.php b/src/Assets/AssetRepository.php index a37a1d47..c28e5535 100644 --- a/src/Assets/AssetRepository.php +++ b/src/Assets/AssetRepository.php @@ -18,9 +18,9 @@ public function findById($id): ?AssetContract [$container, $path] = explode('::', $id); if (config('statamic.eloquent-driver.assets.use_model_keys_for_ids', false) && (str_contains($path, '.') === false)) { - $item = Blink::once($blinkKey, fn() => $this->query()->where('id', $path)->first()); + $item = Blink::once($blinkKey, fn () => $this->query()->where('id', $path)->first()); - if (!$item) { + if (! $item) { Blink::forget($blinkKey); return null; diff --git a/src/Commands/UpdateAssetReferencesToUseModelKeys.php b/src/Commands/UpdateAssetReferencesToUseModelKeys.php new file mode 100644 index 00000000..6685130f --- /dev/null +++ b/src/Commands/UpdateAssetReferencesToUseModelKeys.php @@ -0,0 +1,51 @@ +reject(fn ($container) => $this->option('container') != 'all' && $this->option('container') != $container->handle()) + ->each(fn ($container) => $this->processContainer($container)); + + $this->info('Complete'); + } + + private function processContainer(AssetContainer $container) + { + $this->info("Container: {$container->handle()}"); + + $container->queryAssets()->get()->each(function ($item) use ($container) { + return AssetReferenceUpdater::item($item) + ->filterByContainer($container) + ->updateReferences($item->path(), $item->id()); + }); + } +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index dbc4a73c..0bf099d9 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -93,6 +93,7 @@ public function boot() Commands\ImportRevisions::class, Commands\ImportTaxonomies::class, Commands\SyncAssets::class, + Commands\UpdateAssetReferencesToUseModelKeys::class, ]); $this->addAboutCommandInfo(); From b95f8c4bd671d72d69c0c357a65e2b0213e29935 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Fri, 22 Nov 2024 07:13:14 +0000 Subject: [PATCH 7/7] Disabled UpdateAssetReferences when config is active --- src/ServiceProvider.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 0bf099d9..297471b4 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -39,6 +39,7 @@ use Statamic\Eloquent\Taxonomies\TermQueryBuilder; use Statamic\Eloquent\Taxonomies\TermRepository; use Statamic\Eloquent\Tokens\TokenRepository; +use Statamic\Listeners\UpdateAssetReferences; use Statamic\Providers\AddonServiceProvider; use Statamic\Statamic; @@ -257,6 +258,11 @@ private function registerAssets() }); Statamic::repository(AssetRepositoryContract::class, AssetRepository::class); + + // we dont need asset references to run on asset save if we are linking by id, as the references dont update + if (config('statamic.eloquent-driver.assets.use_model_keys_for_ids', false)) { + UpdateAssetReferences::disable(); + } } private function registerBlueprints()