Skip to content

Commit bf039e9

Browse files
authored
Make AssetQueryBuilder for performance gains (#218)
1 parent f3c2ee1 commit bf039e9

14 files changed

+615
-79
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ We have provided imports from file based content for each repository, which can
6161
- Revisions: `php please eloquent:import-revisions`
6262
- Taxonomies: `php please eloquent:import-taxonomies`
6363

64+
If your assets are eloquent driver and you are managing your assets outside of Statamic, we have provided a sync assets command which will check your container for updates and add database entries for any missing files, while removing any that no longer exist.
65+
66+
`php please eloquent:sync-assets`
67+
68+
6469
## Exporting back to file based content
6570

6671
We have provided exports from eloquent to file based content for each repository, which can be run as follows:

database/migrations/create_asset_table.php.stub

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@ return new class extends Migration {
99
{
1010
Schema::create($this->prefix('assets_meta'), function (Blueprint $table) {
1111
$table->id();
12-
$table->string('handle')->index();
13-
$table->jsonb('data')->nullable();
12+
$table->string('container')->index();
13+
$table->string('folder')->index();
14+
$table->string('basename')->index();
15+
$table->string('filename')->index();
16+
$table->char('extension', 10)->index();
17+
$table->string('path')->index();
18+
$table->jsonb('meta')->nullable();
1419
$table->timestamps();
20+
21+
$table->index(['container', 'folder', 'basename']);
1522
});
1623
}
1724

18-
1925
public function down()
2026
{
2127
Schema::dropIfExists($this->prefix('assets_meta'));
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
use Illuminate\Database\Schema\Blueprint;
4+
use Illuminate\Support\Facades\Schema;
5+
use Illuminate\Support\Str;
6+
use Statamic\Eloquent\Assets\AssetModel;
7+
use Statamic\Eloquent\Database\BaseMigration as Migration;
8+
9+
return new class extends Migration {
10+
public function up()
11+
{
12+
Schema::table($this->prefix('assets_meta'), function (Blueprint $table) {
13+
$table->string('container')->after('handle')->index();
14+
$table->string('folder')->after('container')->index();
15+
$table->string('basename')->after('folder')->index();
16+
$table->string('filename')->after('basename')->index();
17+
$table->char('extension', 10)->after('filename')->index();
18+
$table->string('path')->after('extension')->index();
19+
$table->jsonb('meta')->after('path')->nullable();
20+
21+
$table->index(['container', 'folder', 'basename']);
22+
});
23+
24+
AssetModel::all()
25+
->each(function ($model) {
26+
$path = Str::of($model->handle)->after('::')->replace('.meta/', '')->beforeLast('.yaml');
27+
28+
if ($path->startsWith('./')) {
29+
$path = $path->replaceFirst('./', '');
30+
}
31+
32+
$model->container = Str::before($model->handle, '::');
33+
$model->path = $path;
34+
$model->folder = $path->contains('/') ? $path->beforeLast('/') : '/';
35+
$model->basename = $path->afterLast('/');
36+
$model->extension = Str::of($model->basename)->afterLast('.');
37+
$model->filename = Str::of($model->basename)->beforeLast('.');
38+
$model->meta = $model->data;
39+
$model->save();
40+
});
41+
42+
Schema::table($this->prefix('assets_meta'), function (Blueprint $table) {
43+
$table->dropColumn('handle');
44+
});
45+
}
46+
47+
public function down()
48+
{
49+
Schema::table($this->prefix('assets_meta'), function (Blueprint $table) {
50+
$table->string('handle')->index();
51+
});
52+
53+
AssetModel::all()
54+
->each(function ($model) {
55+
$model->handle = $model->container.'::'.$model->folder.'/.meta/'.$model->basename.'.yaml';
56+
$model->data = $model->meta;
57+
$model->saveQuietly();
58+
});
59+
60+
Schema::table($this->prefix('assets_meta'), function (Blueprint $table) {
61+
$table->dropIndex(['container', 'folder', 'basename']);
62+
63+
$table->dropColumn('meta');
64+
$table->dropColumn('path');
65+
$table->dropColumn('basename');
66+
$table->dropColumn('filename');
67+
$table->dropColumn('extension');
68+
$table->dropColumn('folder');
69+
$table->dropColumn('container');
70+
});
71+
}
72+
};

src/Assets/Asset.php

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,27 @@
22

33
namespace Statamic\Eloquent\Assets;
44

5+
use Illuminate\Database\Eloquent\Model;
56
use Illuminate\Support\Facades\Cache;
67
use Statamic\Assets\Asset as FileAsset;
78
use Statamic\Assets\AssetUploader as Uploader;
8-
use Statamic\Facades\Blink;
99
use Statamic\Facades\Path;
1010
use Statamic\Support\Arr;
1111
use Statamic\Support\Str;
1212

1313
class Asset extends FileAsset
1414
{
15+
protected $existsOnDisk = false;
1516
protected $removedData = [];
1617

18+
public static function fromModel(Model $model)
19+
{
20+
return (new static())
21+
->container($model->container)
22+
->path(Str::replace('//', '/', $model->folder.'/'.$model->basename))
23+
->hydrateMeta($model->meta);
24+
}
25+
1726
public function meta($key = null)
1827
{
1928
if (func_num_args() === 1) {
@@ -37,27 +46,37 @@ public function meta($key = null)
3746

3847
return $this->meta = Cache::rememberForever($this->metaCacheKey(), function () {
3948
$handle = $this->container()->handle().'::'.$this->metaPath();
40-
if ($model = app('statamic.eloquent.assets.model')::where('handle', $handle)->first()) {
41-
return $model->data;
49+
if ($model = app('statamic.eloquent.assets.model')::where([
50+
'container' => $this->containerHandle(),
51+
'folder' => $this->folder(),
52+
'basename' => $this->basename(),
53+
])->first()) {
54+
return $model->meta;
4255
}
4356

4457
$this->writeMeta($meta = $this->generateMeta());
4558

59+
if (! $meta['data']) {
60+
$meta['data'] = [];
61+
}
62+
4663
return $meta;
4764
});
4865
}
4966

5067
public function exists()
5168
{
52-
$files = Blink::once($this->container()->handle().'::files', function () {
53-
return $this->container()->files();
54-
});
55-
56-
if (! $path = $this->path()) {
57-
return false;
58-
}
69+
return $this->existsOnDisk || $this->metaExists();
70+
}
5971

60-
return $files->contains($path);
72+
public function metaExists()
73+
{
74+
return app('statamic.eloquent.assets.model')::query()
75+
->where([
76+
'container' => $this->containerHandle(),
77+
'folder' => $this->folder(),
78+
'basename' => $this->basename(),
79+
])->count() > 0;
6180
}
6281

6382
private function metaValue($key)
@@ -71,13 +90,38 @@ private function metaValue($key)
7190
return Arr::get($this->generateMeta(), $key);
7291
}
7392

93+
public function generateMeta()
94+
{
95+
if (! $this->disk()->exists($this->path())) {
96+
return ['data' => $this->data->all()];
97+
}
98+
99+
$this->existsOnDisk = true;
100+
101+
return parent::generateMeta();
102+
}
103+
104+
public function hydrateMeta($meta)
105+
{
106+
$this->meta = $meta;
107+
108+
return $this;
109+
}
110+
74111
public function writeMeta($meta)
75112
{
76113
$meta['data'] = Arr::removeNullValues($meta['data']);
77114

78115
$model = app('statamic.eloquent.assets.model')::firstOrNew([
79-
'handle' => $this->container()->handle().'::'.$this->metaPath(),
80-
])->fill(['data' => $meta]);
116+
'container' => $this->containerHandle(),
117+
'folder' => $this->folder(),
118+
'basename' => $this->basename(),
119+
])->fill([
120+
'meta' => $meta,
121+
'filename' => $this->filename(),
122+
'extension' => $this->extension(),
123+
'path' => $this->path(),
124+
]);
81125

82126
// Set initial timestamps.
83127
if (empty($model->created_at) && isset($meta['last_modified'])) {
@@ -88,6 +132,11 @@ public function writeMeta($meta)
88132
$model->save();
89133
}
90134

135+
public function metaPath()
136+
{
137+
return $this->path();
138+
}
139+
91140
/**
92141
* Move the asset to a different location.
93142
*
@@ -100,6 +149,8 @@ public function move($folder, $filename = null)
100149
$filename = Uploader::getSafeFilename($filename ?: $this->filename());
101150
$oldPath = $this->path();
102151
$oldMetaPath = $this->metaPath();
152+
$oldFolder = $this->folder();
153+
$oldBasename = $this->basename();
103154
$newPath = Str::removeLeft(Path::tidy($folder.'/'.$filename.'.'.pathinfo($oldPath, PATHINFO_EXTENSION)), '/');
104155

105156
$this->hydrate();
@@ -108,11 +159,17 @@ public function move($folder, $filename = null)
108159
$this->save();
109160

110161
if ($oldMetaPath != $this->metaPath()) {
111-
$oldMetaModel = app('statamic.eloquent.assets.model')::whereHandle($this->container()->handle().'::'.$oldMetaPath)->first();
162+
$oldMetaModel = app('statamic.eloquent.assets.model')::where([
163+
'container' => $this->containerHandle(),
164+
'folder' => $oldFolder,
165+
'basename' => $oldBasename,
166+
])->first();
112167

113168
if ($oldMetaModel) {
169+
$meta = $oldMetaModel->meta;
114170
$oldMetaModel->delete();
115-
$this->writeMeta($oldMetaModel->data);
171+
172+
$this->writeMeta($meta);
116173
}
117174
}
118175

src/Assets/AssetContainer.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Statamic\Assets\AssetContainer as FileEntry;
77
use Statamic\Events\AssetContainerDeleted;
88
use Statamic\Events\AssetContainerSaved;
9+
use Statamic\Support\Str;
910

1011
class AssetContainer extends FileEntry
1112
{
@@ -106,4 +107,22 @@ public function delete()
106107

107108
return true;
108109
}
110+
111+
public function folders($folder = '/', $recursive = false)
112+
{
113+
return $this->disk()->getFolders($folder, $recursive);
114+
}
115+
116+
public function metaFiles($folder = '/', $recursive = false)
117+
{
118+
// When requesting files() as-is, we want all of them.
119+
if (func_num_args() === 0) {
120+
$recursive = true;
121+
}
122+
123+
return $this->queryAssets()
124+
->when($recursive, fn ($query) => $query->where('folder', $folder), fn ($query) => $query->where('folder', 'like', Str::replaceEnd('/', '', $folder).'/%'))
125+
->get()
126+
->pluck('path');
127+
}
109128
}

0 commit comments

Comments
 (0)