Skip to content

Add config for referencing Assets using model ids instead of container::path ids #319

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

Open
wants to merge 7 commits into
base: 4.x
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions config/eloquent-driver.php
Original file line number Diff line number Diff line change
@@ -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' => [
70 changes: 63 additions & 7 deletions src/Assets/Asset.php
Original file line number Diff line number Diff line change
@@ -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([
20 changes: 19 additions & 1 deletion src/Assets/AssetRepository.php
Original file line number Diff line number Diff line change
@@ -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)
@@ -38,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;
}
51 changes: 51 additions & 0 deletions src/Commands/UpdateAssetReferencesToUseModelKeys.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace Statamic\Eloquent\Commands;

use Illuminate\Console\Command;
use Statamic\Assets\AssetReferenceUpdater;
use Statamic\Console\RunsInPlease;
use Statamic\Contracts\Assets\AssetContainer;
use Statamic\Facades;

class UpdateAssetReferencesToUseModelKeys extends Command
{
use RunsInPlease;

/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'statamic:eloquent:update-asset-references-to-use-model-keys {--container=all}';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Update container::path references to container::asset_model_key';

/**
* Execute the console command.
*/
public function handle()
{
Facades\AssetContainer::all()
->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());
});
}
}
7 changes: 7 additions & 0 deletions src/ServiceProvider.php
Original file line number Diff line number Diff line change
@@ -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;

@@ -93,6 +94,7 @@ public function boot()
Commands\ImportRevisions::class,
Commands\ImportTaxonomies::class,
Commands\SyncAssets::class,
Commands\UpdateAssetReferencesToUseModelKeys::class,
]);

$this->addAboutCommandInfo();
@@ -256,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()
51 changes: 51 additions & 0 deletions tests/Assets/AssetTest.php
Original file line number Diff line number Diff line change
@@ -4,7 +4,9 @@

use Illuminate\Foundation\Testing\RefreshDatabase;
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;

@@ -52,4 +54,53 @@ 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();
}

#[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());

$asset = Facades\Asset::find('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);
}
}