Skip to content
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

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

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
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
Expand Up @@ -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' => [
Expand Down
70 changes: 63 additions & 7 deletions src/Assets/Asset.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand All @@ -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(),
Expand Down Expand Up @@ -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);
}
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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([
Expand Down
20 changes: 19 additions & 1 deletion src/Assets/AssetRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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;
}
Expand Down
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());
});
}
}
1 change: 1 addition & 0 deletions src/ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ public function boot()
Commands\ImportRevisions::class,
Commands\ImportTaxonomies::class,
Commands\SyncAssets::class,
Commands\UpdateAssetReferencesToUseModelKeys::class,
]);

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

Expand Down Expand Up @@ -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);
}
}
Loading