diff --git a/README.md b/README.md index 829402d0..b1400b3a 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,11 @@ We have provided imports from file based content for each repository, which can - Revisions: `php please eloquent:import-revisions` - Taxonomies: `php please eloquent:import-taxonomies` +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. + +`php please eloquent:sync-assets` + + ## Exporting back to file based content We have provided exports from eloquent to file based content for each repository, which can be run as follows: diff --git a/database/migrations/create_asset_table.php.stub b/database/migrations/create_asset_table.php.stub index 68ebfb66..592ed4cd 100644 --- a/database/migrations/create_asset_table.php.stub +++ b/database/migrations/create_asset_table.php.stub @@ -9,13 +9,19 @@ return new class extends Migration { { Schema::create($this->prefix('assets_meta'), function (Blueprint $table) { $table->id(); - $table->string('handle')->index(); - $table->jsonb('data')->nullable(); + $table->string('container')->index(); + $table->string('folder')->index(); + $table->string('basename')->index(); + $table->string('filename')->index(); + $table->char('extension', 10)->index(); + $table->string('path')->index(); + $table->jsonb('meta')->nullable(); $table->timestamps(); + + $table->index(['container', 'folder', 'basename']); }); } - public function down() { Schema::dropIfExists($this->prefix('assets_meta')); diff --git a/database/migrations/updates/update_assets_table.php.stub b/database/migrations/updates/update_assets_table.php.stub new file mode 100644 index 00000000..da00a478 --- /dev/null +++ b/database/migrations/updates/update_assets_table.php.stub @@ -0,0 +1,72 @@ +prefix('assets_meta'), function (Blueprint $table) { + $table->string('container')->after('handle')->index(); + $table->string('folder')->after('container')->index(); + $table->string('basename')->after('folder')->index(); + $table->string('filename')->after('basename')->index(); + $table->char('extension', 10)->after('filename')->index(); + $table->string('path')->after('extension')->index(); + $table->jsonb('meta')->after('path')->nullable(); + + $table->index(['container', 'folder', 'basename']); + }); + + AssetModel::all() + ->each(function ($model) { + $path = Str::of($model->handle)->after('::')->replace('.meta/', '')->beforeLast('.yaml'); + + if ($path->startsWith('./')) { + $path = $path->replaceFirst('./', ''); + } + + $model->container = Str::before($model->handle, '::'); + $model->path = $path; + $model->folder = $path->contains('/') ? $path->beforeLast('/') : '/'; + $model->basename = $path->afterLast('/'); + $model->extension = Str::of($model->basename)->afterLast('.'); + $model->filename = Str::of($model->basename)->beforeLast('.'); + $model->meta = $model->data; + $model->save(); + }); + + Schema::table($this->prefix('assets_meta'), function (Blueprint $table) { + $table->dropColumn('handle'); + }); + } + + public function down() + { + Schema::table($this->prefix('assets_meta'), function (Blueprint $table) { + $table->string('handle')->index(); + }); + + AssetModel::all() + ->each(function ($model) { + $model->handle = $model->container.'::'.$model->folder.'/.meta/'.$model->basename.'.yaml'; + $model->data = $model->meta; + $model->saveQuietly(); + }); + + Schema::table($this->prefix('assets_meta'), function (Blueprint $table) { + $table->dropIndex(['container', 'folder', 'basename']); + + $table->dropColumn('meta'); + $table->dropColumn('path'); + $table->dropColumn('basename'); + $table->dropColumn('filename'); + $table->dropColumn('extension'); + $table->dropColumn('folder'); + $table->dropColumn('container'); + }); + } +}; diff --git a/src/Assets/Asset.php b/src/Assets/Asset.php index d6218817..78874c18 100644 --- a/src/Assets/Asset.php +++ b/src/Assets/Asset.php @@ -2,18 +2,27 @@ namespace Statamic\Eloquent\Assets; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Cache; use Statamic\Assets\Asset as FileAsset; use Statamic\Assets\AssetUploader as Uploader; -use Statamic\Facades\Blink; use Statamic\Facades\Path; use Statamic\Support\Arr; use Statamic\Support\Str; class Asset extends FileAsset { + protected $existsOnDisk = false; protected $removedData = []; + public static function fromModel(Model $model) + { + return (new static()) + ->container($model->container) + ->path(Str::replace('//', '/', $model->folder.'/'.$model->basename)) + ->hydrateMeta($model->meta); + } + public function meta($key = null) { if (func_num_args() === 1) { @@ -37,27 +46,37 @@ public function meta($key = null) return $this->meta = Cache::rememberForever($this->metaCacheKey(), function () { $handle = $this->container()->handle().'::'.$this->metaPath(); - if ($model = app('statamic.eloquent.assets.model')::where('handle', $handle)->first()) { - return $model->data; + if ($model = app('statamic.eloquent.assets.model')::where([ + 'container' => $this->containerHandle(), + 'folder' => $this->folder(), + 'basename' => $this->basename(), + ])->first()) { + return $model->meta; } $this->writeMeta($meta = $this->generateMeta()); + if (! $meta['data']) { + $meta['data'] = []; + } + return $meta; }); } public function exists() { - $files = Blink::once($this->container()->handle().'::files', function () { - return $this->container()->files(); - }); - - if (! $path = $this->path()) { - return false; - } + return $this->existsOnDisk || $this->metaExists(); + } - return $files->contains($path); + public function metaExists() + { + return app('statamic.eloquent.assets.model')::query() + ->where([ + 'container' => $this->containerHandle(), + 'folder' => $this->folder(), + 'basename' => $this->basename(), + ])->count() > 0; } private function metaValue($key) @@ -71,13 +90,38 @@ private function metaValue($key) return Arr::get($this->generateMeta(), $key); } + public function generateMeta() + { + if (! $this->disk()->exists($this->path())) { + return ['data' => $this->data->all()]; + } + + $this->existsOnDisk = true; + + return parent::generateMeta(); + } + + public function hydrateMeta($meta) + { + $this->meta = $meta; + + return $this; + } + public function writeMeta($meta) { $meta['data'] = Arr::removeNullValues($meta['data']); $model = app('statamic.eloquent.assets.model')::firstOrNew([ - 'handle' => $this->container()->handle().'::'.$this->metaPath(), - ])->fill(['data' => $meta]); + 'container' => $this->containerHandle(), + 'folder' => $this->folder(), + 'basename' => $this->basename(), + ])->fill([ + 'meta' => $meta, + 'filename' => $this->filename(), + 'extension' => $this->extension(), + 'path' => $this->path(), + ]); // Set initial timestamps. if (empty($model->created_at) && isset($meta['last_modified'])) { @@ -88,6 +132,11 @@ public function writeMeta($meta) $model->save(); } + public function metaPath() + { + return $this->path(); + } + /** * Move the asset to a different location. * @@ -100,6 +149,8 @@ public function move($folder, $filename = null) $filename = Uploader::getSafeFilename($filename ?: $this->filename()); $oldPath = $this->path(); $oldMetaPath = $this->metaPath(); + $oldFolder = $this->folder(); + $oldBasename = $this->basename(); $newPath = Str::removeLeft(Path::tidy($folder.'/'.$filename.'.'.pathinfo($oldPath, PATHINFO_EXTENSION)), '/'); $this->hydrate(); @@ -108,11 +159,17 @@ public function move($folder, $filename = null) $this->save(); if ($oldMetaPath != $this->metaPath()) { - $oldMetaModel = app('statamic.eloquent.assets.model')::whereHandle($this->container()->handle().'::'.$oldMetaPath)->first(); + $oldMetaModel = app('statamic.eloquent.assets.model')::where([ + 'container' => $this->containerHandle(), + 'folder' => $oldFolder, + 'basename' => $oldBasename, + ])->first(); if ($oldMetaModel) { + $meta = $oldMetaModel->meta; $oldMetaModel->delete(); - $this->writeMeta($oldMetaModel->data); + + $this->writeMeta($meta); } } diff --git a/src/Assets/AssetContainer.php b/src/Assets/AssetContainer.php index e9b4b6c0..ba98a66b 100644 --- a/src/Assets/AssetContainer.php +++ b/src/Assets/AssetContainer.php @@ -6,6 +6,7 @@ use Statamic\Assets\AssetContainer as FileEntry; use Statamic\Events\AssetContainerDeleted; use Statamic\Events\AssetContainerSaved; +use Statamic\Support\Str; class AssetContainer extends FileEntry { @@ -106,4 +107,22 @@ public function delete() return true; } + + public function folders($folder = '/', $recursive = false) + { + return $this->disk()->getFolders($folder, $recursive); + } + + public function metaFiles($folder = '/', $recursive = false) + { + // When requesting files() as-is, we want all of them. + if (func_num_args() === 0) { + $recursive = true; + } + + return $this->queryAssets() + ->when($recursive, fn ($query) => $query->where('folder', $folder), fn ($query) => $query->where('folder', 'like', Str::replaceEnd('/', '', $folder).'/%')) + ->get() + ->pluck('path'); + } } diff --git a/src/Assets/AssetContainerContents.php b/src/Assets/AssetContainerContents.php new file mode 100644 index 00000000..605ace3c --- /dev/null +++ b/src/Assets/AssetContainerContents.php @@ -0,0 +1,116 @@ +where('container', $this->container->handle()); + } + + public function all() + { + // for performance reasons we only return directories to + // avoid returning thousands of models + return $this->directories()->keyBy('path'); + } + + public function files() + { + return $this->query() + ->select(['path']) + ->get() + ->keyBy('path'); + } + + public function directories() + { + if ($this->folders && ! Statamic::isWorker()) { + return $this->folders; + } + + $this->folders = Cache::remember($this->key(), $this->ttl(), function () { + return $this->container->disk()->getFolders('/', true) + ->map(fn ($dir) => ['path' => $dir]); + }); + + return $this->folders; + } + + public function metaFilesIn($folder, $recursive) + { + return $this->query() + ->where(fn ($query) => $query + ->where('folder', $folder) + ->when($recursive, fn ($query) => $query->orWhere('folder', 'like', ($folder != '/' ? $folder : '').'/%')) + ) + ->get() + ->keyBy('path'); + } + + public function filteredFilesIn($folder, $recursive) + { + return $this->metaFilesIn($folder, $recursive); + } + + public function filteredDirectoriesIn($folder, $recursive) + { + $folder = $folder == '/' ? '' : $folder; + + return $this->directories()->filter(function ($dir) use ($folder, $recursive) { + if ($folder && ! Str::startsWith($dir, $folder)) { + return false; + } + + return ! $recursive ? Str::of($dir)->after($folder.'/')->contains('/') == false : true; + })->flip(); + } + + public function save() + { + Cache::put($this->key(), $this->folders, $this->ttl()); + } + + public function forget($path) + { + $this->directories(); + + $this->folders = $this->folders->reject(fn ($dir) => $dir['path'] == $path); + + return $this; + } + + public function add($path) + { + $this->directories(); + + // Add parent directories + if (($dir = dirname($path)) !== '.') { + $this->add($dir); + } + + $this->folders->push(['path' => $path]); + + return $this; + } + + private function key() + { + return 'asset-folder-contents-'.$this->container->handle(); + } + + private function ttl() + { + return config('statamic.stache.watcher') ? 0 : null; + } +} diff --git a/src/Assets/AssetModel.php b/src/Assets/AssetModel.php index 8a73e982..4316e38f 100644 --- a/src/Assets/AssetModel.php +++ b/src/Assets/AssetModel.php @@ -12,5 +12,6 @@ class AssetModel extends BaseModel protected $casts = [ 'data' => 'json', + 'meta' => 'json', ]; } diff --git a/src/Assets/AssetQueryBuilder.php b/src/Assets/AssetQueryBuilder.php new file mode 100644 index 00000000..56ccdf25 --- /dev/null +++ b/src/Assets/AssetQueryBuilder.php @@ -0,0 +1,42 @@ +'.$column; + } elseif (! in_array($column, self::COLUMNS)) { + $column = 'meta->data->'.$column; + } + + return $column; + } + + protected function transform($items, $columns = []) + { + return AssetCollection::make($items)->map(function ($model) use ($columns) { + return app('statamic.eloquent.assets.asset')::fromModel($model) + ->selectedQueryColumns($this->selectedQueryColumns ?? $columns); + }); + } + + public function with($relations, $callback = null) + { + return $this; + } +} diff --git a/src/Assets/AssetRepository.php b/src/Assets/AssetRepository.php index cbb9ddaa..7da7308b 100644 --- a/src/Assets/AssetRepository.php +++ b/src/Assets/AssetRepository.php @@ -3,28 +3,82 @@ namespace Statamic\Eloquent\Assets; use Statamic\Assets\AssetRepository as BaseRepository; -use Statamic\Assets\QueryBuilder; use Statamic\Contracts\Assets\Asset as AssetContract; use Statamic\Contracts\Assets\QueryBuilder as QueryBuilderContract; -use Statamic\Facades\Stache; +use Statamic\Facades\Blink; +use Statamic\Facades\Site; +use Statamic\Support\Str; class AssetRepository extends BaseRepository { - public function delete($asset) + public function findById($id): ?AssetContract + { + [$container, $path] = explode('::', $id); + + $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) + ->where('folder', $folder) + ->where('basename', $filename) + ->first(); + }); + + if (! $item) { + Blink::forget($blinkKey); + + return null; + } + + return $item; + } + + public function findByUrl(string $url) { - $asset->container()->contents()->forget($asset->path())->save(); + if (! $container = $this->resolveContainerFromUrl($url)) { + return null; + } + + $siteUrl = rtrim(Site::current()->absoluteUrl(), '/'); + $containerUrl = $container->url(); - $handle = $asset->containerHandle().'::'.$asset->metaPath(); - app('statamic.eloquent.assets.model')::where('handle', $handle)->first()?->delete(); + if (starts_with($containerUrl, '/')) { + $containerUrl = $siteUrl.$containerUrl; + } - Stache::store('assets::'.$asset->containerHandle())->delete($asset); + if (starts_with($containerUrl, $siteUrl)) { + $url = $siteUrl.$url; + } + + $path = str_after($url, $containerUrl); + + return $this->findById("{$container}::{$path}"); + } + + public function delete($asset) + { + $this->query() + ->where([ + 'container' => $asset->container(), + 'folder' => $asset->folder(), + 'basename' => $asset->basename(), + ]) + ->delete(); + } + + public function save($asset) + { + $asset->writeMeta($asset->generateMeta()); } public static function bindings(): array { return [ AssetContract::class => app('statamic.eloquent.assets.asset'), - QueryBuilderContract::class => QueryBuilder::class, + QueryBuilderContract::class => AssetQueryBuilder::class, ]; } } diff --git a/src/Commands/SyncAssets.php b/src/Commands/SyncAssets.php new file mode 100644 index 00000000..61802905 --- /dev/null +++ b/src/Commands/SyncAssets.php @@ -0,0 +1,92 @@ +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()}"); + + $this->processFolder($container); + } + + private function processFolder(AssetContainer $container, $folder = '') + { + $this->line("Processing folder: {$folder}"); + + // get raw listing of this folder, avoiding any of statamic's asset container caching + $files = collect($container->disk()->filesystem()->listContents($folder)) + ->reject(fn ($item) => $item['type'] != 'file') + ->pluck('path'); + + // ensure we have an asset for any paths + $files->each(function ($file) use ($container) { + $this->info($file); + + if (Str::startsWith($file, '.')) { + return; + } + + $asset = Facades\Asset::make() + ->container($container->handle()) + ->path($file) + ->saveQuietly(); + }); + + // delete any assets we have a db entry for that no longer exist + AssetModel::query() + ->where('container', $container->handle()) + ->where('folder', $folder) + ->chunk(100, function ($assets) use ($files) { + $assets->each(function ($asset) use ($files) { + if (! $files->contains($asset->path)) { + $this->error("Deleting {$asset->path}"); + + $asset->delete(); + } + }); + }); + + // process any sub-folders of this folder + $container->folders($folder) + ->each(function ($folder) use ($container) { + $this->processFolder($container, $folder); + }); + } +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 5aa34bf0..92885542 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -3,6 +3,7 @@ namespace Statamic\Eloquent; use Illuminate\Foundation\Console\AboutCommand; +use Statamic\Assets\AssetContainerContents; use Statamic\Contracts\Assets\AssetContainerRepository as AssetContainerRepositoryContract; use Statamic\Contracts\Assets\AssetRepository as AssetRepositoryContract; use Statamic\Contracts\Entries\CollectionRepository as CollectionRepositoryContract; @@ -16,7 +17,9 @@ use Statamic\Contracts\Structures\NavTreeRepository as NavTreeRepositoryContract; use Statamic\Contracts\Taxonomies\TaxonomyRepository as TaxonomyRepositoryContract; use Statamic\Contracts\Taxonomies\TermRepository as TermRepositoryContract; +use Statamic\Eloquent\Assets\AssetContainerContents as EloquentAssetContainerContents; use Statamic\Eloquent\Assets\AssetContainerRepository; +use Statamic\Eloquent\Assets\AssetQueryBuilder; use Statamic\Eloquent\Assets\AssetRepository; use Statamic\Eloquent\Collections\CollectionRepository; use Statamic\Eloquent\Entries\EntryQueryBuilder; @@ -43,6 +46,7 @@ class ServiceProvider extends AddonServiceProvider protected $migrationCount = 0; protected $updateScripts = [ + \Statamic\Eloquent\Updates\AddMetaAndIndexesToAssetsTable::class, \Statamic\Eloquent\Updates\AddOrderToEntriesTable::class, \Statamic\Eloquent\Updates\AddBlueprintToEntriesTable::class, \Statamic\Eloquent\Updates\ChangeDefaultBlueprint::class, @@ -114,6 +118,7 @@ public function boot() Commands\ImportNavs::class, Commands\ImportRevisions::class, Commands\ImportTaxonomies::class, + Commands\SyncAssets::class, ]); $this->addAboutCommandInfo(); @@ -170,6 +175,16 @@ private function registerAssets() return config('statamic.eloquent-driver.assets.asset', \Statamic\Eloquent\Assets\Asset::class); }); + $this->app->bind(AssetQueryBuilder::class, function ($app) { + return new AssetQueryBuilder( + $app['statamic.eloquent.assets.model']::query() + ); + }); + + $this->app->bind(AssetContainerContents::class, function ($app) { + return new EloquentAssetContainerContents(); + }); + Statamic::repository(AssetRepositoryContract::class, AssetRepository::class); } diff --git a/src/Updates/AddMetaAndIndexesToAssetsTable.php b/src/Updates/AddMetaAndIndexesToAssetsTable.php new file mode 100644 index 00000000..03ff70db --- /dev/null +++ b/src/Updates/AddMetaAndIndexesToAssetsTable.php @@ -0,0 +1,25 @@ +files->copy($source, $dest); + + $this->console()->info('Migration created'); + $this->console()->comment('Remember to run `php artisan migrate` to apply it to your database.'); + } +} diff --git a/tests/Data/Assets/AssetQueryBuilderTest.php b/tests/Data/Assets/AssetQueryBuilderTest.php index 1d052c14..d2d03a13 100644 --- a/tests/Data/Assets/AssetQueryBuilderTest.php +++ b/tests/Data/Assets/AssetQueryBuilderTest.php @@ -19,19 +19,31 @@ public function setUp(): void Storage::fake('test', ['url' => '/assets']); + $this->container = tap(AssetContainer::make('test')->disk('test'))->save(); + Storage::disk('test')->put('a.jpg', ''); + Asset::make()->container('test')->path('a.jpg')->save(); + Storage::disk('test')->put('b.txt', ''); + Asset::make()->container('test')->path('b.txt')->save(); + Storage::disk('test')->put('c.txt', ''); + Asset::make()->container('test')->path('c.txt')->save(); + Storage::disk('test')->put('d.jpg', ''); + Asset::make()->container('test')->path('d.jpg')->save(); + Storage::disk('test')->put('e.jpg', ''); + Asset::make()->container('test')->path('e.jpg')->save(); + Storage::disk('test')->put('f.jpg', ''); - $this->container = tap(AssetContainer::make('test')->disk('test'))->save(); + Asset::make()->container('test')->path('f.jpg')->save(); } /** @test */ public function it_can_get_assets() { - $assets = $this->container->queryAssets()->get(); + $assets = Asset::query()->get(); $this->assertCount(6, $assets); $this->assertEquals(['a', 'b', 'c', 'd', 'e', 'f'], $assets->map->filename()->all()); @@ -40,7 +52,7 @@ public function it_can_get_assets() /** @test **/ public function assets_are_found_using_or_where() { - $assets = $this->container->queryAssets()->where('filename', 'a')->orWhere('filename', 'c')->get(); + $assets = Asset::query()->where('filename', 'a')->orWhere('filename', 'c')->get(); $this->assertCount(2, $assets); $this->assertEquals(['a', 'c'], $assets->map->filename()->all()); @@ -49,7 +61,7 @@ public function assets_are_found_using_or_where() /** @test **/ public function assets_are_found_using_or_where_in() { - $assets = $this->container->queryAssets() + $assets = Asset::query() ->whereIn('filename', ['a', 'b']) ->orWhereIn('filename', ['a', 'd']) ->orWhereIn('extension', ['jpg']) @@ -60,12 +72,12 @@ public function assets_are_found_using_or_where_in() } /** @test **/ - public function assets_are_found_using_or_where_not_in() + public function assets_are_found_using_where_not_in() { - $assets = $this->container->queryAssets() + $assets = Asset::query() ->whereNotIn('filename', ['a', 'b']) - ->orWhereNotIn('filename', ['a', 'f']) - ->orWhereNotIn('extension', ['txt']) + ->whereNotIn('filename', ['a', 'f']) + ->whereNotIn('extension', ['txt']) ->get(); $this->assertCount(2, $assets); @@ -89,17 +101,17 @@ public function assets_are_found_using_where_date() { $this->createWhereDateTestAssets(); - $assets = $this->container->queryAssets()->whereDate('test_date', '2021-11-15')->get(); + $assets = Asset::query()->whereDate('test_date', '2021-11-15')->get(); $this->assertCount(2, $assets); $this->assertEquals(['a', 'c'], $assets->map->filename()->all()); - $assets = $this->container->queryAssets()->whereDate('test_date', 1637000264)->get(); + $assets = Asset::query()->whereDate('test_date', 1637000264)->get(); $this->assertCount(2, $assets); $this->assertEquals(['a', 'c'], $assets->map->filename()->all()); - $assets = $this->container->queryAssets()->whereDate('test_date', '>=', '2021-11-15')->get(); + $assets = Asset::query()->whereDate('test_date', '>=', '2021-11-15')->get(); $this->assertCount(2, $assets); $this->assertEquals(['a', 'c'], $assets->map->filename()->all()); @@ -110,12 +122,12 @@ public function assets_are_found_using_where_month() { $this->createWhereDateTestAssets(); - $assets = $this->container->queryAssets()->whereMonth('test_date', 11)->get(); + $assets = Asset::query()->whereMonth('test_date', 11)->get(); $this->assertCount(3, $assets); $this->assertEquals(['a', 'b', 'c'], $assets->map->filename()->all()); - $assets = $this->container->queryAssets()->whereMonth('test_date', '<', 11)->get(); + $assets = Asset::query()->whereMonth('test_date', '<', 11)->get(); $this->assertCount(1, $assets); $this->assertEquals(['d'], $assets->map->filename()->all()); @@ -126,12 +138,12 @@ public function assets_are_found_using_where_day() { $this->createWhereDateTestAssets(); - $assets = $this->container->queryAssets()->whereDay('test_date', 15)->get(); + $assets = Asset::query()->whereDay('test_date', 15)->get(); $this->assertCount(2, $assets); $this->assertEquals(['a', 'c'], $assets->map->filename()->all()); - $assets = $this->container->queryAssets()->whereDay('test_date', '<', 15)->get(); + $assets = Asset::query()->whereDay('test_date', '<', 15)->get(); $this->assertCount(2, $assets); $this->assertEquals(['b', 'd'], $assets->map->filename()->all()); @@ -142,12 +154,12 @@ public function assets_are_found_using_where_year() { $this->createWhereDateTestAssets(); - $assets = $this->container->queryAssets()->whereYear('test_date', 2021)->get(); + $assets = Asset::query()->whereYear('test_date', 2021)->get(); $this->assertCount(3, $assets); $this->assertEquals(['a', 'b', 'c'], $assets->map->filename()->all()); - $assets = $this->container->queryAssets()->whereYear('test_date', '<', 2021)->get(); + $assets = Asset::query()->whereYear('test_date', '<', 2021)->get(); $this->assertCount(1, $assets); $this->assertEquals(['d'], $assets->map->filename()->all()); @@ -158,12 +170,12 @@ public function assets_are_found_using_where_time() { $this->createWhereDateTestAssets(); - $assets = $this->container->queryAssets()->whereTime('test_date', '09:00')->get(); + $assets = Asset::query()->whereTime('test_date', '09:00:00')->get(); $this->assertCount(1, $assets); $this->assertEquals(['b'], $assets->map->filename()->all()); - $assets = $this->container->queryAssets()->whereTime('test_date', '>', '09:00')->get(); + $assets = Asset::query()->whereTime('test_date', '>', '09:00:00')->get(); $this->assertCount(2, $assets); $this->assertEquals(['a', 'd'], $assets->map->filename()->all()); @@ -178,7 +190,7 @@ public function assets_are_found_using_where_null() Asset::find('test::e.jpg')->data([])->save(); Asset::find('test::f.jpg')->data([])->save(); - $assets = $this->container->queryAssets()->whereNull('text')->get(); + $assets = Asset::query()->whereNull('text')->get(); $this->assertCount(3, $assets); $this->assertEquals(['c', 'e', 'f'], $assets->map->filename()->all()); @@ -194,7 +206,7 @@ public function assets_are_found_using_where_not_null() Asset::find('test::e.jpg')->data([])->save(); Asset::find('test::f.jpg')->data([])->save(); - $assets = $this->container->queryAssets()->whereNotNull('text')->get(); + $assets = Asset::query()->whereNotNull('text')->get(); $this->assertCount(3, $assets); $this->assertEquals(['a', 'b', 'd'], $assets->map->filename()->all()); @@ -210,10 +222,10 @@ public function assets_are_found_using_or_where_null() Asset::find('test::e.jpg')->data([])->save(); Asset::find('test::f.jpg')->data([])->save(); - $assets = $this->container->queryAssets()->whereNull('text')->orWhereNull('content')->get(); + $assets = Asset::query()->whereNull('text')->orWhereNull('content')->get(); $this->assertCount(5, $assets); - $this->assertEquals(['c', 'e', 'f', 'b', 'd'], $assets->map->filename()->all()); + $this->assertEquals(['b', 'c', 'd', 'e', 'f'], $assets->map->filename()->all()); } /** @test **/ @@ -226,16 +238,16 @@ public function assets_are_found_using_or_where_not_null() Asset::find('test::e.jpg')->data([])->save(); Asset::find('test::f.jpg')->data([])->save(); - $assets = $this->container->queryAssets()->whereNotNull('content')->orWhereNotNull('text')->get(); + $assets = Asset::query()->whereNotNull('content')->orWhereNotNull('text')->get(); $this->assertCount(4, $assets); - $this->assertEquals(['a', 'c', 'b', 'd'], $assets->map->filename()->all()); + $this->assertEquals(['a', 'b', 'c', 'd'], $assets->map->filename()->all()); } /** @test **/ public function assets_are_found_using_nested_where() { - $assets = $this->container->queryAssets() + $assets = Asset::query() ->where(function ($query) { $query->where('filename', 'a'); }) @@ -252,7 +264,7 @@ public function assets_are_found_using_nested_where() /** @test **/ public function assets_are_found_using_nested_where_in() { - $assets = $this->container->queryAssets() + $assets = Asset::query() ->where(function ($query) { $query->whereIn('filename', ['a', 'b']); }) @@ -277,7 +289,7 @@ public function assets_are_found_using_where_between() Asset::find('test::e.jpg')->data(['number_field' => 12])->save(); Asset::find('test::f.jpg')->data([])->save(); - $assets = $this->container->queryAssets()->whereBetween('number_field', [9, 11])->get(); + $assets = Asset::query()->whereBetween('number_field', [9, 11])->get(); $this->assertCount(3, $assets); $this->assertEquals(['b', 'c', 'd'], $assets->map->filename()->all()); @@ -293,10 +305,10 @@ public function assets_are_found_using_where_not_between() Asset::find('test::e.jpg')->data(['number_field' => 12])->save(); Asset::find('test::f.jpg')->data([])->save(); - $assets = $this->container->queryAssets()->whereNotBetween('number_field', [9, 11])->get(); + $assets = Asset::query()->whereNotBetween('number_field', [9, 11])->get(); - $this->assertCount(3, $assets); - $this->assertEquals(['a', 'e', 'f'], $assets->map->filename()->all()); + $this->assertCount(2, $assets); + $this->assertEquals(['a', 'e'], $assets->map->filename()->all()); } /** @test **/ @@ -309,7 +321,7 @@ public function assets_are_found_using_or_where_between() Asset::find('test::e.jpg')->data(['number_field' => 12])->save(); Asset::find('test::f.jpg')->data([])->save(); - $assets = $this->container->queryAssets()->whereBetween('number_field', [9, 10])->orWhereBetween('number_field', [11, 12])->get(); + $assets = Asset::query()->whereBetween('number_field', [9, 10])->orWhereBetween('number_field', [11, 12])->get(); $this->assertCount(4, $assets); $this->assertEquals(['b', 'c', 'd', 'e'], $assets->map->filename()->all()); @@ -325,27 +337,31 @@ public function assets_are_found_using_or_where_not_between() Asset::find('test::e.jpg')->data(['text' => 'e', 'number_field' => 12])->save(); Asset::find('test::f.jpg')->data([])->save(); - $assets = $this->container->queryAssets()->where('text', 'e')->orWhereNotBetween('number_field', [10, 12])->get(); + $assets = Asset::query()->where('text', 'e')->orWhereNotBetween('number_field', [10, 12])->get(); - $this->assertCount(4, $assets); - $this->assertEquals(['e', 'a', 'b', 'f'], $assets->map->filename()->all()); + $this->assertCount(3, $assets); + $this->assertEquals(['a', 'b', 'e'], $assets->map->filename()->all()); } /** @test **/ public function assets_are_found_using_where_json_contains() { + if ($this->isUsingSqlite()) { + $this->markTestSkipped('SQLite doesn\'t support JSON contains queries'); + } + Asset::find('test::a.jpg')->data(['test_taxonomy' => ['taxonomy-1', 'taxonomy-2']])->save(); Asset::find('test::b.txt')->data(['test_taxonomy' => ['taxonomy-3']])->save(); Asset::find('test::c.txt')->data(['test_taxonomy' => ['taxonomy-1', 'taxonomy-3']])->save(); Asset::find('test::d.jpg')->data(['test_taxonomy' => ['taxonomy-3', 'taxonomy-4']])->save(); Asset::find('test::e.jpg')->data(['test_taxonomy' => ['taxonomy-5']])->save(); - $assets = $this->container->queryAssets()->whereJsonContains('test_taxonomy', ['taxonomy-1', 'taxonomy-5'])->get(); + $assets = Asset::query()->whereJsonContains('test_taxonomy', ['taxonomy-1', 'taxonomy-5'])->get(); $this->assertCount(3, $assets); $this->assertEquals(['a', 'c', 'e'], $assets->map->filename()->all()); - $assets = $this->container->queryAssets()->whereJsonContains('test_taxonomy', 'taxonomy-1')->get(); + $assets = Asset::query()->whereJsonContains('test_taxonomy', 'taxonomy-1')->get(); $this->assertCount(2, $assets); $this->assertEquals(['a', 'c'], $assets->map->filename()->all()); @@ -354,6 +370,10 @@ public function assets_are_found_using_where_json_contains() /** @test **/ public function assets_are_found_using_where_json_doesnt_contain() { + if ($this->isUsingSqlite()) { + $this->markTestSkipped('SQLite doesn\'t support JSON contains queries'); + } + Asset::find('test::a.jpg')->data(['test_taxonomy' => ['taxonomy-1', 'taxonomy-2']])->save(); Asset::find('test::b.txt')->data(['test_taxonomy' => ['taxonomy-3']])->save(); Asset::find('test::c.txt')->data(['test_taxonomy' => ['taxonomy-1', 'taxonomy-3']])->save(); @@ -361,12 +381,12 @@ public function assets_are_found_using_where_json_doesnt_contain() Asset::find('test::e.jpg')->data(['test_taxonomy' => ['taxonomy-5']])->save(); Asset::find('test::f.jpg')->data(['test_taxonomy' => ['taxonomy-1']])->save(); - $assets = $this->container->queryAssets()->whereJsonDoesntContain('test_taxonomy', ['taxonomy-1'])->get(); + $assets = Asset::query()->whereJsonDoesntContain('test_taxonomy', ['taxonomy-1'])->get(); $this->assertCount(3, $assets); $this->assertEquals(['b', 'd', 'e'], $assets->map->filename()->all()); - $assets = $this->container->queryAssets()->whereJsonDoesntContain('test_taxonomy', 'taxonomy-1')->get(); + $assets = Asset::query()->whereJsonDoesntContain('test_taxonomy', 'taxonomy-1')->get(); $this->assertCount(3, $assets); $this->assertEquals(['b', 'd', 'e'], $assets->map->filename()->all()); @@ -375,13 +395,17 @@ public function assets_are_found_using_where_json_doesnt_contain() /** @test **/ public function assets_are_found_using_or_where_json_contains() { + if ($this->isUsingSqlite()) { + $this->markTestSkipped('SQLite doesn\'t support JSON contains queries'); + } + Asset::find('test::a.jpg')->data(['test_taxonomy' => ['taxonomy-1', 'taxonomy-2']])->save(); Asset::find('test::b.txt')->data(['test_taxonomy' => ['taxonomy-3']])->save(); Asset::find('test::c.txt')->data(['test_taxonomy' => ['taxonomy-1', 'taxonomy-3']])->save(); Asset::find('test::d.jpg')->data(['test_taxonomy' => ['taxonomy-3', 'taxonomy-4']])->save(); Asset::find('test::e.jpg')->data(['test_taxonomy' => ['taxonomy-5']])->save(); - $assets = $this->container->queryAssets()->whereJsonContains('test_taxonomy', ['taxonomy-1'])->orWhereJsonContains('test_taxonomy', ['taxonomy-5'])->get(); + $assets = Asset::query()->whereJsonContains('test_taxonomy', ['taxonomy-1'])->orWhereJsonContains('test_taxonomy', ['taxonomy-5'])->get(); $this->assertCount(3, $assets); $this->assertEquals(['a', 'c', 'e'], $assets->map->filename()->all()); @@ -390,6 +414,10 @@ public function assets_are_found_using_or_where_json_contains() /** @test **/ public function assets_are_found_using_or_where_json_doesnt_contain() { + if ($this->isUsingSqlite()) { + $this->markTestSkipped('SQLite doesn\'t support JSON contains queries'); + } + Asset::find('test::a.jpg')->data(['test_taxonomy' => ['taxonomy-1', 'taxonomy-2']])->save(); Asset::find('test::b.txt')->data(['test_taxonomy' => ['taxonomy-3']])->save(); Asset::find('test::c.txt')->data(['test_taxonomy' => ['taxonomy-1', 'taxonomy-3']])->save(); @@ -397,7 +425,7 @@ public function assets_are_found_using_or_where_json_doesnt_contain() Asset::find('test::e.jpg')->data(['test_taxonomy' => ['taxonomy-5']])->save(); Asset::find('test::f.jpg')->data(['test_taxonomy' => ['taxonomy-5']])->save(); - $assets = $this->container->queryAssets()->whereJsonContains('test_taxonomy', ['taxonomy-1'])->orWhereJsonDoesntContain('test_taxonomy', ['taxonomy-5'])->get(); + $assets = Asset::query()->whereJsonContains('test_taxonomy', ['taxonomy-1'])->orWhereJsonDoesntContain('test_taxonomy', ['taxonomy-5'])->get(); $this->assertCount(4, $assets); $this->assertEquals(['a', 'c', 'b', 'd'], $assets->map->filename()->all()); @@ -406,13 +434,17 @@ public function assets_are_found_using_or_where_json_doesnt_contain() /** @test **/ public function assets_are_found_using_where_json_length() { + if ($this->isUsingSqlite()) { + $this->markTestSkipped('SQLite doesn\'t support JSON contains queries'); + } + Asset::find('test::a.jpg')->data(['test_taxonomy' => ['taxonomy-1', 'taxonomy-2']])->save(); Asset::find('test::b.txt')->data(['test_taxonomy' => ['taxonomy-3']])->save(); Asset::find('test::c.txt')->data(['test_taxonomy' => ['taxonomy-1', 'taxonomy-3']])->save(); Asset::find('test::d.jpg')->data(['test_taxonomy' => ['taxonomy-3', 'taxonomy-4']])->save(); Asset::find('test::e.jpg')->data(['test_taxonomy' => ['taxonomy-5']])->save(); - $assets = $this->container->queryAssets()->whereJsonLength('test_taxonomy', 1)->get(); + $assets = Asset::query()->whereJsonLength('test_taxonomy', 1)->get(); $this->assertCount(2, $assets); $this->assertEquals(['b', 'e'], $assets->map->filename()->all()); @@ -421,7 +453,7 @@ public function assets_are_found_using_where_json_length() /** @test **/ public function assets_are_found_using_array_of_wheres() { - $assets = $this->container->queryAssets() + $assets = Asset::query() ->where([ 'filename' => 'a', ['extension', 'jpg'], @@ -443,12 +475,12 @@ public function results_are_found_using_where_with_json_value() Asset::find('test::e.jpg')->data(['content' => 'string'])->save(); Asset::find('test::f.jpg')->data(['content' => 123])->save(); - $assets = $this->container->queryAssets()->where('content->value', 1)->get(); + $assets = Asset::query()->where('content->value', 1)->get(); $this->assertCount(2, $assets); $this->assertEquals(['a', 'c'], $assets->map->filename()->all()); - $assets = $this->container->queryAssets()->where('content->value', '!=', 1)->get(); + $assets = Asset::query()->where('content->value', '!=', 1)->orWhereNull('content->value')->get(); $this->assertCount(4, $assets); $this->assertEquals(['b', 'd', 'e', 'f'], $assets->map->filename()->all()); @@ -464,12 +496,12 @@ public function assets_are_found_using_where_column() Asset::find('test::e.jpg')->data(['foo' => 'Post 5', 'other_foo' => 'Not Post 5'])->save(); Asset::find('test::f.jpg')->data(['foo' => 'Post 6', 'other_foo' => 'Not Post 6'])->save(); - $entries = $this->container->queryAssets()->whereColumn('foo', 'other_foo')->get(); + $entries = Asset::query()->whereColumn('foo', 'other_foo')->get(); $this->assertCount(2, $entries); $this->assertEquals(['Post 3', 'Post 4'], $entries->map->foo->all()); - $entries = $this->container->queryAssets()->whereColumn('foo', '!=', 'other_foo')->get(); + $entries = Asset::query()->whereColumn('foo', '!=', 'other_foo')->get(); $this->assertCount(4, $entries); $this->assertEquals(['Post 1', 'Post 2', 'Post 5', 'Post 6'], $entries->map->foo->all()); @@ -478,14 +510,14 @@ public function assets_are_found_using_where_column() /** @test */ public function it_can_get_assets_using_when() { - $assets = $this->container->queryAssets()->when(true, function ($query) { + $assets = Asset::query()->when(true, function ($query) { $query->where('filename', 'a'); })->get(); $this->assertCount(1, $assets); $this->assertEquals(['a'], $assets->map->filename()->all()); - $assets = $this->container->queryAssets()->when(false, function ($query) { + $assets = Asset::query()->when(false, function ($query) { $query->where('filename', 'a'); })->get(); @@ -496,14 +528,14 @@ public function it_can_get_assets_using_when() /** @test */ public function it_can_get_assets_using_unless() { - $assets = $this->container->queryAssets()->unless(true, function ($query) { + $assets = Asset::query()->unless(true, function ($query) { $query->where('filename', 'a'); })->get(); $this->assertCount(6, $assets); $this->assertEquals(['a', 'b', 'c', 'd', 'e', 'f'], $assets->map->filename()->all()); - $assets = $this->container->queryAssets()->unless(false, function ($query) { + $assets = Asset::query()->unless(false, function ($query) { $query->where('filename', 'a'); })->get(); @@ -514,7 +546,7 @@ public function it_can_get_assets_using_unless() /** @test */ public function it_can_get_assets_using_tap() { - $assets = $this->container->queryAssets()->tap(function ($query) { + $assets = Asset::query()->tap(function ($query) { $query->where('filename', 'a'); })->get(); diff --git a/tests/Data/Entries/EntryQueryBuilderTest.php b/tests/Data/Entries/EntryQueryBuilderTest.php index 46ce9ba6..609c5e67 100644 --- a/tests/Data/Entries/EntryQueryBuilderTest.php +++ b/tests/Data/Entries/EntryQueryBuilderTest.php @@ -466,7 +466,7 @@ public function entries_are_found_using_or_where_json_doesnt_contain() /** @test **/ public function entries_are_found_using_where_json_length() { - if (config('database.default') === 'sqlite') { + if ($this->isUsingSqlite()) { $this->markTestSkipped('SQLite doesn\'t support JSON contains queries'); }