From a2ebf0994c4a399248c241284d815ef97338402e Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Fri, 17 Jan 2025 20:52:46 +0300 Subject: [PATCH 01/23] add lint and support laravel 9 --- composer.json | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index eb5eaea..d9c9544 100644 --- a/composer.json +++ b/composer.json @@ -10,12 +10,13 @@ } ], "require": { - "php": "^8.1", + "php": "^8.0", "ext-json": "*", - "laravel/framework": "^10.0|^11.0" + "laravel/framework": "^9.0|^10.0|^11.0" }, "require-dev": { - "orchestra/testbench": "^8.0|^9.0", + "laravel/pint": "^1.0", + "orchestra/testbench": "^7.0|^8.0|^9.0", "pestphp/pest-plugin-laravel": "^2.4" }, "autoload": { @@ -44,7 +45,8 @@ }, "scripts": { "test": "vendor/bin/pest", - "test:coverage": "vendor/bin/pest --coverage" + "test:coverage": "vendor/bin/pest --coverage", + "lint": "vendor/bin/pint" }, "keywords": [ "laravel", From b3cec9d1f49146ba974472c973af7dafc9ba7419 Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Fri, 17 Jan 2025 20:54:21 +0300 Subject: [PATCH 02/23] added helper to container service of app --- src/Helpers/Helper.php | 25 +++++++++++++++++++++++ src/Providers/MetadataServiceProvider.php | 8 ++++---- 2 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 src/Helpers/Helper.php diff --git a/src/Helpers/Helper.php b/src/Helpers/Helper.php new file mode 100644 index 0000000..be84d48 --- /dev/null +++ b/src/Helpers/Helper.php @@ -0,0 +1,25 @@ +toArray(); + } + + return is_array($value) && empty($value); + } +} diff --git a/src/Providers/MetadataServiceProvider.php b/src/Providers/MetadataServiceProvider.php index 131fb80..abcb873 100644 --- a/src/Providers/MetadataServiceProvider.php +++ b/src/Providers/MetadataServiceProvider.php @@ -3,23 +3,23 @@ namespace Waad\Metadata\Providers; use Illuminate\Support\ServiceProvider; +use Waad\Metadata\Helpers\Helper; class MetadataServiceProvider extends ServiceProvider { public function register() { - // + $this->app->singleton(Helper::class); } public function boot() { - if (!$this->app->runningInConsole()) { + if (! $this->app->runningInConsole()) { return; } $this->publishes([ - __DIR__ . '/../../migrations/1_create_model_meta_data_table.php' => - database_path('migrations/'. date('Y_m_d_His', time()) .'_create_model_meta_data_table.php'), + __DIR__.'/../../migrations/1_create_model_meta_data_table.php' => database_path('migrations/'.date('Y_m_d_His', time()).'_create_model_meta_data_table.php'), ], 'metadata-migrations'); } } From 6acc4526e75a74a997558598b8249a46d74fc3fa Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Fri, 17 Jan 2025 20:55:45 +0300 Subject: [PATCH 03/23] add asJson method to support uni code utf8 --- src/Models/Metadata.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Models/Metadata.php b/src/Models/Metadata.php index 87940ca..b5904aa 100644 --- a/src/Models/Metadata.php +++ b/src/Models/Metadata.php @@ -10,7 +10,7 @@ class Metadata extends Model { use HasUlids; - + protected $table = 'model_metadata'; protected $guarded = []; @@ -34,4 +34,9 @@ protected function asDateTime($value) return $value; } -} \ No newline at end of file + + protected function asJson($value) + { + return json_encode($value, JSON_UNESCAPED_UNICODE); + } +} From d265fa2e5e9482c30b275fd598b36338cc90f229 Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Fri, 17 Jan 2025 20:58:08 +0300 Subject: [PATCH 04/23] fix fromat cs php psr4 laravel using lint(pint) --- migrations/1_create_model_meta_data_table.php | 2 +- src/Traits/HasManyMetadata.php | 16 ++++------------ tests/App/Models/Company.php | 2 +- tests/App/Models/Post.php | 2 +- .../2025_01_02_211322_create_companies_table.php | 2 +- .../2025_01_02_211322_create_posts_table.php | 2 +- tests/Feature/HasManyMetadataTest.php | 4 ++-- tests/Pest.php | 10 ++++++---- tests/TestCase.php | 6 +++--- 9 files changed, 20 insertions(+), 26 deletions(-) diff --git a/migrations/1_create_model_meta_data_table.php b/migrations/1_create_model_meta_data_table.php index 4137852..1f70f66 100644 --- a/migrations/1_create_model_meta_data_table.php +++ b/migrations/1_create_model_meta_data_table.php @@ -27,4 +27,4 @@ public function down(): void { Schema::dropIfExists('model_metadata'); } -}; \ No newline at end of file +}; diff --git a/src/Traits/HasManyMetadata.php b/src/Traits/HasManyMetadata.php index 467e789..cb973b7 100644 --- a/src/Traits/HasManyMetadata.php +++ b/src/Traits/HasManyMetadata.php @@ -9,33 +9,26 @@ trait HasManyMetadata { /** * Create a new metadata record for the model - * - * @param array|Collection $metadata */ public function createMetadata(array|Collection $metadata): Metadata { return $this->metadata()->create([ - 'metadata' => $metadata instanceof Collection ? $metadata->toArray() : $metadata + 'metadata' => $metadata instanceof Collection ? $metadata->toArray() : $metadata, ]); } /** * Update an existing metadata record - * - * @param string $id - * @param array|Collection $metadata */ public function updateMetadata(string $id, array|Collection $metadata): bool { return (bool) $this->metadata()->where('id', $id)->update([ - 'metadata' => $metadata instanceof Collection ? $metadata->toArray() : $metadata + 'metadata' => $metadata instanceof Collection ? $metadata->toArray() : $metadata, ]); } /** * Delete a metadata record - * - * @param string $id */ public function deleteMetadata(string $id): bool { @@ -44,8 +37,6 @@ public function deleteMetadata(string $id): bool /** * Get a metadata record by ID - * - * @param string $id */ public function getMetadataById(string $id): ?Metadata { @@ -55,7 +46,7 @@ public function getMetadataById(string $id): ?Metadata /** * Search metadata records containing the given term * - * @param mixed $searchTerm + * @param mixed $searchTerm */ public function searchMetadata($searchTerm): \Illuminate\Database\Eloquent\Collection { @@ -76,6 +67,7 @@ public function getMetadata(): ?array public function getMetadataCollection(): ?Collection { $metadata = $this->getMetadata(); + return $metadata ? collect($metadata) : null; } diff --git a/tests/App/Models/Company.php b/tests/App/Models/Company.php index f357c7e..9af92c8 100644 --- a/tests/App/Models/Company.php +++ b/tests/App/Models/Company.php @@ -16,6 +16,6 @@ class Company extends Model ]; protected $casts = [ - 'status' => 'boolean' + 'status' => 'boolean', ]; } diff --git a/tests/App/Models/Post.php b/tests/App/Models/Post.php index 573dd1e..8521386 100644 --- a/tests/App/Models/Post.php +++ b/tests/App/Models/Post.php @@ -16,6 +16,6 @@ class Post extends Model ]; protected $casts = [ - 'status' => 'boolean' + 'status' => 'boolean', ]; } diff --git a/tests/App/migrations/2025_01_02_211322_create_companies_table.php b/tests/App/migrations/2025_01_02_211322_create_companies_table.php index 9a5c8cc..3dabbfd 100644 --- a/tests/App/migrations/2025_01_02_211322_create_companies_table.php +++ b/tests/App/migrations/2025_01_02_211322_create_companies_table.php @@ -27,4 +27,4 @@ public function down(): void { Schema::dropIfExists('companies'); } -}; \ No newline at end of file +}; diff --git a/tests/App/migrations/2025_01_02_211322_create_posts_table.php b/tests/App/migrations/2025_01_02_211322_create_posts_table.php index 474cf17..ca357c2 100644 --- a/tests/App/migrations/2025_01_02_211322_create_posts_table.php +++ b/tests/App/migrations/2025_01_02_211322_create_posts_table.php @@ -27,4 +27,4 @@ public function down(): void { Schema::dropIfExists('posts'); } -}; \ No newline at end of file +}; diff --git a/tests/Feature/HasManyMetadataTest.php b/tests/Feature/HasManyMetadataTest.php index 440ff7c..8f20151 100644 --- a/tests/Feature/HasManyMetadataTest.php +++ b/tests/Feature/HasManyMetadataTest.php @@ -1,7 +1,7 @@ and($searchResults->count())->toBe(1) ->and($searchResults->first()->metadata['language'])->toBeString()->toBe('German') ->and($searchResults->first()->metadata['is_visible'])->toBeTrue(); -}); \ No newline at end of file +}); diff --git a/tests/Pest.php b/tests/Pest.php index 816cd98..3024162 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -4,9 +4,10 @@ use Waad\Metadata\Tests\App\Models\Post; use Waad\Metadata\Tests\TestCase; -uses(TestCase::class)->in('Feature'); +uses(TestCase::class)->in('Feature'); -function createCompany() { +function createCompany() +{ return Company::query()->create([ 'name' => fake()->name(), 'address' => fake()->address(), @@ -14,10 +15,11 @@ function createCompany() { ]); } -function createPost() { +function createPost() +{ return Post::query()->create([ 'title' => fake()->title(), 'content' => fake()->text(), 'status' => fake()->boolean(75), ]); -} \ No newline at end of file +} diff --git a/tests/TestCase.php b/tests/TestCase.php index fa2c32a..dcfe579 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -21,7 +21,7 @@ protected function getPackageProviders($app): array protected function defineDatabaseMigrations(): void { - $this->loadMigrationsFrom(__DIR__ . '/App/migrations'); - $this->loadMigrationsFrom(__DIR__ . '/../migrations'); + $this->loadMigrationsFrom(__DIR__.'/App/migrations'); + $this->loadMigrationsFrom(__DIR__.'/../migrations'); } -} \ No newline at end of file +} From de335ff7645f3cd0007b3c98ba5eec2b5cceffd5 Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Fri, 17 Jan 2025 21:12:46 +0300 Subject: [PATCH 05/23] add more methods to HasOneMetadata with testing all done --- src/Traits/HasOneMetadata.php | 195 +++++++++++++- tests/Feature/HasOneMetadataTest.php | 365 +++++++++++++++++++++------ 2 files changed, 469 insertions(+), 91 deletions(-) diff --git a/src/Traits/HasOneMetadata.php b/src/Traits/HasOneMetadata.php index c56fa65..21689a7 100644 --- a/src/Traits/HasOneMetadata.php +++ b/src/Traits/HasOneMetadata.php @@ -2,31 +2,68 @@ namespace Waad\Metadata\Traits; +use Illuminate\Support\Arr; use Illuminate\Support\Collection; +use Waad\Metadata\Helpers\Helper; use Waad\Metadata\Models\Metadata; trait HasOneMetadata { /** * Create a new metadata record for the model - * - * @param array|Collection $metadata */ public function createMetadata(array|Collection $metadata): ?Metadata { - if ($this->metadata()->exists()) { + if ($this->hasMetadata()) { return null; } return $this->metadata()->create([ - 'metadata' => $metadata instanceof Collection ? $metadata->toArray() : $metadata + 'metadata' => $metadata instanceof Collection ? $metadata->toArray() : $metadata, ]); } + /** + * Add specific values by keys from metadata field + */ + public function addMetadataByKeys(array|Collection|string|int|null $keys, array|Collection|string|int|float|bool|null $value = null): bool + { + $helper = app(Helper::class); + + if ($helper->isNullOrStringEmptyOrWhitespaceOrEmptyArray($keys)) { + return false; + } + + $metadata = $this->getMetadata() ?? []; + $keys = $keys instanceof Collection ? $keys->toArray() : (! is_array($keys) ? [$keys => $value] : $keys); + + return $this->syncMetadata(array_merge($metadata, $keys)); + } + + /** + * Add one specific value by key from metadata field + */ + public function addMetadataByKey(string|int|null $key, array|Collection|string|int|float|bool|null $value = null): bool + { + return $this->addMetadataByKeys($key, $value); + } + + /** + * Sync metadata + */ + public function syncMetadata(array|Collection $metadata): bool + { + $metadataArray = $metadata instanceof Collection ? $metadata->toArray() : $metadata; + + if ($this->hasMetadata()) { + return $this->updateMetadata($metadataArray); + } + + return $this->createMetadata($metadataArray) !== null; + } + /** * Update an existing metadata record - * - * @param array|Collection $metadata */ public function updateMetadata(array|Collection $metadata): bool { @@ -35,6 +72,31 @@ public function updateMetadata(array|Collection $metadata): bool return (bool) $this->metadata()->first()?->update(['metadata' => $metadataArray]); } + /** + * Update specific values by keys from metadata field + */ + public function updateMetadataByKeys(array|Collection|string|int|null $keys, array|Collection|string|int|float|bool|null $value = null): bool + { + $helper = app(Helper::class); + + if ($helper->isNullOrStringEmptyOrWhitespaceOrEmptyArray($keys)) { + return false; + } + + $metadata = $this->getMetadata() ?? []; + $keys = $keys instanceof Collection ? $keys->toArray() : (! is_array($keys) ? [$keys => $value] : $keys); + + return $this->syncMetadata(array_merge($metadata, $keys)); + } + + /** + * Update one specific value by key from metadata field + */ + public function updateMetadataByKey(string|int|null $key, array|Collection|string|int|float|bool|null $value = null): bool + { + return $this->updateMetadataByKeys($key, $value); + } + /** * Delete a metadata record */ @@ -43,20 +105,133 @@ public function deleteMetadata(): bool return (bool) $this->metadata()->first()?->delete(); } + /** + * Emptying metadata field make it null + */ + public function clearMetadata(): bool + { + return (bool) $this->metadata()->first()?->update(['metadata' => null]); + } + + /** + * delete specific value by key from metadata field + */ + public function clearMetadataByKeys(array|Collection|string|int|null $keys = null): bool + { + if (app(Helper::class)->isNullOrStringEmptyOrWhitespaceOrEmptyArray($keys)) { + return false; + } + + $metadata = $this->getMetadata(); + if (is_null($metadata)) { + return false; + } + + if ($keys instanceof Collection) { + $keys = $keys->toArray(); + } + + $keys = Arr::wrap($keys); + + return $this->syncMetadata(Arr::except($metadata, $keys)); + } + + /** + * delete specific value by key from metadata field + */ + public function clearMetadataByKey(string|int|null $key = null): bool + { + return $this->clearMetadataByKeys($key); + } + + /** + * Check if metadata exists + */ + public function hasMetadata(): bool + { + return $this->metadata()->exists(); + } + + /** + * Check if metadata exists all keys + */ + public function hasMetadataAllKeys(array|Collection|string|int|null $keys): bool + { + if (app(Helper::class)->isNullOrStringEmptyOrWhitespaceOrEmptyArray($keys)) { + return false; + } + + $keys = $keys instanceof Collection ? $keys->toArray() : (! is_array($keys) ? [$keys] : $keys); + $metadata = $this->getMetadata() ?? []; + + return Arr::has($metadata, $keys); + } + + /** + * Check if metadata exists by key + */ + public function hasMetadataByKey(string|int|null $key): bool + { + return $this->hasMetadataAllKeys($key); + } + + /** + * Check if metadata exists any keys + */ + public function hasMetadataAnyKeys(array|Collection|string|int|null $keys): bool + { + if (is_array($keys) || $keys instanceof Collection) { + foreach ($keys as $key) { + if ($this->hasMetadataByKey($key)) { + return true; + } + } + + return false; + } + + return $this->hasMetadataByKey($keys); + } + + /** + * Check if metadata exists and is not empty + */ + public function hasFilledMetadata(): bool + { + return $this->hasMetadata() && filled($this->getMetadata()); + } + /** * Get metadata column as Array */ - public function getMetadata(): ?array + public function getMetadata(array|Collection|string|int|null $keys = null): ?array + { + $metadata = $this->metadata()->first()?->metadata; + + if (app(Helper::class)->isNullOrStringEmptyOrWhitespaceOrEmptyArray($keys)) { + return $metadata; + } + + $keys = Arr::wrap($keys); + + return Arr::only($metadata, $keys); + } + + /** + * Get individual metadata + */ + public function getMetadataByKey(string|int $key): string|int|float|bool|array|null { - return $this->metadata()->first()?->metadata; + return $this->getMetadata($key)[$key] ?? null; } /** * Get metadata column as collection */ - public function getMetadataCollection(): ?Collection + public function getMetadataCollection(array|Collection|string|int|null $keys = null): ?Collection { - $metadata = $this->getMetadata(); + $metadata = $this->getMetadata($keys); + return $metadata ? collect($metadata) : null; } diff --git a/tests/Feature/HasOneMetadataTest.php b/tests/Feature/HasOneMetadataTest.php index bbb7190..513bcf4 100644 --- a/tests/Feature/HasOneMetadataTest.php +++ b/tests/Feature/HasOneMetadataTest.php @@ -1,27 +1,28 @@ company = createCompany(); + expect($this->company)->toBeInstanceOf(Company::class); +}); // Test to ensure a company can be created it('can create a company', function () { - $company = createCompany(); - // Check that the created company is an instance of Company - expect($company) + expect($this->company) ->toBeInstanceOf(Company::class) - ->and($company->name)->not->toBeEmpty() // Ensure the name is not empty - ->and($company->address)->not->toBeEmpty() // Ensure the address is not empty - ->and($company->status)->toBeBool(); // Ensure the status is a boolean + ->and($this->company->name)->not->toBeEmpty() // Ensure the name is not empty + ->and($this->company->address)->not->toBeEmpty() // Ensure the address is not empty + ->and($this->company->status)->toBeBool(); // Ensure the status is a boolean }); -// Test to ensure metadata can be attached to a company -it('can attach metadata to company', function () { - $company = createCompany(); - expect($company)->toBeInstanceOf(Company::class); - +// Test to ensure metadata can be attached to a company using createMetadata +it('can create metadata to company using createMetadata', function () { // Create metadata for the company - $metadata = $company->createMetadata([ + $metadata = $this->company->createMetadata([ 'language' => 'English', 'is_visible' => true, 'phone' => '', @@ -33,74 +34,72 @@ expect($metadata)->toBeInstanceOf(Metadata::class); }); -// Test to ensure company metadata can be updated -it('can update company metadata', function () { - $company = createCompany(); - expect($company)->toBeInstanceOf(Company::class); - - // Create initial metadata - $metadata = $company->createMetadata([ +// Test to ensure metadata can be retrieved using getMetadata and getMetadataByKey +it('can retrieve metadata using getMetadata and getMetadataByKey', function () { + // Create metadata for the company + $this->company->createMetadata([ 'theme' => 'dark', + 'language' => 'English', + 'views' => 100, + 'settings' => ['notifications' => true], ]); - expect($metadata)->toBeInstanceOf(Metadata::class); - - // Update the metadata - $status = $company->updateMetadata([ - 'theme' => 'light', - ]); - expect($status)->toBeTrue(); // Ensure the update was successful - expect($company->getMetadata())->toBe(['theme' => 'light']); // Verify the updated metadata -}); -// Test to ensure existing company metadata can be updated -it('can update existing company metadata', function () { - $company = createCompany(); - expect($company)->toBeInstanceOf(Company::class); + // Test getMetadata with no parameters (get all metadata) + $metadata = $this->company->getMetadata(); + expect($metadata)->toBeArray() + ->and($metadata['theme'])->toBe('dark') + ->and($metadata['language'])->toBe('English') + ->and($metadata['views'])->toBe(100) + ->and($metadata['settings'])->toBeArray() + ->and($metadata['settings']['notifications'])->toBeTrue(); - // Create initial metadata - $metadata = $company->createMetadata([ - 'theme' => 'light', - ]); - expect($metadata)->toBeInstanceOf(Metadata::class); + // Test getMetadata with specific keys + $partialMetadata = $this->company->getMetadata(['theme', 'language']); + expect($partialMetadata)->toBeArray() + ->and($partialMetadata)->toHaveCount(2) + ->and($partialMetadata['theme'])->toBe('dark') + ->and($partialMetadata['language'])->toBe('English'); - // Update the metadata - $status = $company->updateMetadata([ - 'theme' => 'dark', - 'language' => 'French', - ]); - expect($status)->toBeTrue(); // Ensure the update was successful + // Test getMetadata with specific key + $partialMetadata = $this->company->getMetadata('theme'); + expect($partialMetadata)->toBeArray() + ->and($partialMetadata)->toHaveCount(1) + ->and($partialMetadata['theme'])->toBe('dark'); - // Verify the updated metadata - $updatedMetadata = $company->getMetadata(); - expect($updatedMetadata)->toBeArray() - ->and($updatedMetadata['theme'])->toBeString()->toBe('dark') // Check theme - ->and($updatedMetadata['language'])->toBeString()->toBe('French'); // Check language + // Test getMetadataByKey for individual values + expect($this->company->getMetadataByKey('theme'))->toBe('dark') + ->and($this->company->getMetadataByKey('views'))->toBe(100) + ->and($this->company->getMetadataByKey('settings'))->toBeArray() + ->and($this->company->getMetadataByKey('nonexistent'))->toBeNull(); }); -// Test to ensure company metadata can be deleted -it('can delete company metadata', function () { - $company = createCompany(); - expect($company)->toBeInstanceOf(Company::class); - +// Test to ensure metadata can be retrieved as a collection +it('can retrieve metadata as a collection using getMetadataCollection', function () { // Create metadata for the company - $metadata = $company->createMetadata([ - 'theme' => 'dark', - ]); + $metadata = $this->company->createMetadata(['theme' => 'dark', 'language' => 'French']); expect($metadata)->toBeInstanceOf(Metadata::class); - // Delete the metadata - $status = $company->deleteMetadata(); - expect($status)->toBeTrue(); // Ensure the deletion was successful - expect($company->getMetadata())->toBeNull(); // Verify that metadata is now null + // Retrieve the metadata collection + $metadataCollection = $this->company->getMetadataCollection(); + expect($metadataCollection)->toBeInstanceOf(Collection::class) + ->and($metadataCollection->get('theme'))->toBeString()->toBe('dark') + ->and($metadataCollection->get('language'))->toBeString()->toBe('French'); + + // Test getMetadataCollection with specific keys + $partialMetadataCollection = $this->company->getMetadataCollection(['theme']); + expect($partialMetadataCollection)->toBeInstanceOf(Collection::class) + ->and($partialMetadataCollection->get('theme'))->toBeString()->toBe('dark'); + + // Test getMetadataCollection with specific key + $partialMetadataCollection = $this->company->getMetadataCollection('theme'); + expect($partialMetadataCollection)->toBeInstanceOf(Collection::class) + ->and($partialMetadataCollection->get('theme'))->toBeString()->toBe('dark'); }); // Test to ensure multiple types of metadata can be attached to a company -it('can attach multiple types of metadata to company', function () { - $company = createCompany(); - expect($company)->toBeInstanceOf(Company::class); - +it('can create multiple types of metadata to company', function () { // Create multiple types of metadata for the company - $metadata = $company->createMetadata([ + $metadata = $this->company->createMetadata([ 'language' => 'English', 'is_visible' => true, 'phone' => '', @@ -108,12 +107,13 @@ 'theme' => 'dark', 'views' => 100, 'rating' => 4.5, + 'sports' => ['football', 'basketball'], ]); expect($metadata)->toBeInstanceOf(Metadata::class); // Retrieve the attached metadata - $attachedMetadata = $company->getMetadata(); + $attachedMetadata = $this->company->getMetadata(); // Verify the attached metadata expect($attachedMetadata)->toBeArray() @@ -123,24 +123,227 @@ ->and($attachedMetadata['slug'])->toBeNull() ->and($attachedMetadata['theme'])->toBeString()->toBe('dark') ->and($attachedMetadata['views'])->toBeInt()->toBe(100) - ->and($attachedMetadata['rating'])->toBeFloat()->toBe(4.5); + ->and($attachedMetadata['rating'])->toBeFloat()->toBe(4.5) + ->and($attachedMetadata['sports'])->toBeArray()->not->toBeEmpty(); }); -// Test to ensure metadata can be retrieved as a collection -it('can retrieve metadata as a collection', function () { - $company = createCompany(); - expect($company)->toBeInstanceOf(Company::class); +// Test to ensure metadata can be added using addMetadataByKeys +it('can add multiple values by keys to metadata field using addMetadataByKeys', function () { + // Create initial metadata + $this->company->createMetadata(['theme' => 'dark']); + + // Add multiple metadata keys + $status = $this->company->addMetadataByKeys([ + 'language' => 'English', + 'is_visible' => true, + ]); + expect($status)->toBeTrue(); + // Verify the combined metadata + $updatedMetadata = $this->company->getMetadata(); + expect($updatedMetadata)->toBeArray() + ->and($updatedMetadata['theme'])->toBe('dark') + ->and($updatedMetadata['language'])->toBe('English') + ->and($updatedMetadata['is_visible'])->toBeTrue(); +}); + +// Test to ensure metadata can be added using addMetadataByKey +it('can add value by one key to metadata field using addMetadataByKey', function () { + // Create initial metadata + $this->company->createMetadata(['theme' => 'dark']); + + // Add single metadata key + $status = $this->company->addMetadataByKey('language', 'French'); + expect($status)->toBeTrue(); + + // Verify the combined metadata + $updatedMetadata = $this->company->getMetadata(); + expect($updatedMetadata)->toBeArray() + ->and($updatedMetadata['theme'])->toBe('dark') + ->and($updatedMetadata['language'])->toBe('French'); + + // Test adding another key + $status = $this->company->addMetadataByKey('is_visible', true); + expect($status)->toBeTrue(); + + // Verify all metadata + $finalMetadata = $this->company->getMetadata(); + expect($finalMetadata)->toBeArray() + ->and($finalMetadata['theme'])->toBe('dark') + ->and($finalMetadata['language'])->toBe('French') + ->and($finalMetadata['is_visible'])->toBeTrue(); +}); + +// Test to ensure company metadata can be updated using updateMetadata +it('can update company metadata using updateMetadata', function () { + // Create initial metadata + $this->company->createMetadata(['theme' => 'dark']); + + // Update the metadata + $status = $this->company->updateMetadata(['theme' => 'light']); + expect($status)->toBeTrue(); // Ensure the update was successful + expect($this->company->getMetadata())->toBe(['theme' => 'light']); // Verify the updated metadata + + // Update the metadata + $status = $this->company->updateMetadata(['theme' => 'dark', 'language' => 'French']); + expect($status)->toBeTrue(); // Ensure the update was successful + + // Verify the updated metadata + $updatedMetadata = $this->company->getMetadata(); + expect($updatedMetadata)->toBeArray() + ->and($updatedMetadata['theme'])->toBeString()->toBe('dark') // Check theme + ->and($updatedMetadata['language'])->toBeString()->toBe('French'); // Check language +}); + +// Test to ensure metadata can be updated using updateMetadataByKeys +it('can update values by multiple keys in metadata field using updateMetadataByKeys', function () { + // Create initial metadata + $this->company->createMetadata(['theme' => 'dark', 'language' => 'English', 'is_visible' => true]); + + // Update multiple metadata keys + $status = $this->company->updateMetadataByKeys(['theme' => 'light', 'language' => 'Arabic']); + expect($status)->toBeTrue(); + + // Verify the updated metadata + $updatedMetadata = $this->company->getMetadata(); + expect($updatedMetadata)->toBeArray() + ->and($updatedMetadata['theme'])->toBe('light') + ->and($updatedMetadata['language'])->toBe('Arabic') + ->and($updatedMetadata['is_visible'])->toBeTrue(); + + // Test updating with array + $status = $this->company->updateMetadataByKeys(['views' => 100, 'rating' => 4.5]); + expect($status)->toBeTrue(); + + // Verify final metadata + $finalMetadata = $this->company->getMetadata(); + expect($finalMetadata)->toBeArray() + ->and($finalMetadata['theme'])->toBe('light') + ->and($finalMetadata['language'])->toBe('Arabic') + ->and($finalMetadata['is_visible'])->toBeTrue() + ->and($finalMetadata['views'])->toBe(100) + ->and($finalMetadata['rating'])->toBe(4.5); +}); + +// Test to ensure metadata can be updated using updateMetadataByKey +it('can update value by one key in metadata field using updateMetadataByKey', function () { + // Create initial metadata + $this->company->createMetadata(['theme' => 'dark', 'language' => 'English']); + + // Update single metadata key + $status = $this->company->updateMetadataByKey('theme', 'light'); + expect($status)->toBeTrue(); + + // Verify the updated metadata + $updatedMetadata = $this->company->getMetadata(); + expect($updatedMetadata)->toBeArray() + ->and($updatedMetadata['theme'])->toBe('light') + ->and($updatedMetadata['language'])->toBe('English'); + + // Test updating with different type + $status = $this->company->updateMetadataByKey('language', 'Arabic'); + expect($status)->toBeTrue(); + + // Verify final metadata + $finalMetadata = $this->company->getMetadata(); + expect($finalMetadata)->toBeArray() + ->and($finalMetadata['theme'])->toBe('light') + ->and($finalMetadata['language'])->toBe('Arabic'); +}); + +// Test to ensure company metadata can be deleted +it('can delete company metadata using deleteMetadata', function () { // Create metadata for the company - $metadata = $company->createMetadata([ + $this->company->createMetadata(['theme' => 'dark']); + + // Delete the metadata + $status = $this->company->deleteMetadata(); + expect($status)->toBeTrue(); // Ensure the deletion was successful + expect($this->company->getMetadata())->toBeNull(); // Verify that metadata is now null +}); + +// Test to ensure metadata can be cleared using clearMetadata, clearMetadataByKeys and clearMetadataByKey +it('can clear content of metadata using clearMetadata, clearMetadataByKeys, clearMetadataByKey', function () { + // Create initial metadata + $this->company->createMetadata([ 'theme' => 'dark', - 'language' => 'French', + 'language' => 'English', + 'views' => 100, + 'settings' => ['notifications' => true], ]); - expect($metadata)->toBeInstanceOf(Metadata::class); - // Retrieve the metadata collection - $metadataCollection = $company->getMetadataCollection(); - expect($metadataCollection)->toBeInstanceOf(\Illuminate\Support\Collection::class) - ->and($metadataCollection->get('theme'))->toBeString()->toBe('dark') // Check theme - ->and($metadataCollection->get('language'))->toBeString()->toBe('French'); // Check language -}); \ No newline at end of file + // Test clearMetadataByKey + $status = $this->company->clearMetadataByKey('theme'); + expect($status)->toBeTrue(); + + $metadata = $this->company->getMetadata(); + expect($metadata)->toBeArray() + ->and($metadata)->not->toHaveKey('theme') + ->and($metadata['language'])->toBe('English') + ->and($metadata['views'])->toBe(100); + + // Test clearMetadataByKeys with array + $status = $this->company->clearMetadataByKeys(['language', 'views']); + expect($status)->toBeTrue(); + + $metadata = $this->company->getMetadata(); + expect($metadata)->toBeArray() + ->and($metadata)->not->toHaveKey('language') + ->and($metadata)->not->toHaveKey('views') + ->and($metadata['settings'])->toBeArray(); + + // Test clearMetadata to empty all metadata + $status = $this->company->clearMetadata(); + expect($status)->toBeTrue(); + + $metadata = $this->company->getMetadata(); + expect($metadata)->toBeNull(); +}); + +// Test to ensure metadata is exists using hasMetadata, hasFilledMetadata +it('can check metadata is exists using hasMetadata, hasFilledMetadata', function () { + // Create metadata for the company + $this->company->createMetadata(['theme' => 'dark', 'language' => 'French']); + + // Check if metadata exists + $status = $this->company->hasMetadata(); + expect($status)->toBeBool()->toBeTrue(); + + // Check if metadata is filled + $status = $this->company->hasFilledMetadata(); + expect($status)->toBeBool()->toBeTrue(); + + // Check if metadata is not filled + $this->company->clearMetadata(); + $status = $this->company->hasFilledMetadata(); + expect($status)->toBeBool()->toBeFalse(); +}); + +// Test to ensure specific metadata key existence can be checked +it('can check if specific metadata key exists using hasMetadataAllKeys, hasMetadataByKey, hasMetadataAnyKeys', function () { + // Create metadata for the company + $this->company->createMetadata(['theme' => 'dark', 'views' => 100]); + + // Using hasMetadataByKey + // Check existing keys + expect($this->company->hasMetadataByKey('theme'))->toBeTrue() + ->and($this->company->hasMetadataByKey('views'))->toBeTrue(); + // Check non-existing keys + expect($this->company->hasMetadataByKey('invalid_key'))->toBeFalse() + ->and($this->company->hasMetadataByKey(''))->toBeFalse() + ->and($this->company->hasMetadataByKey(null))->toBeFalse(); + + // Using hasMetadataAllKeys + // Check existing keys + expect($this->company->hasMetadataAllKeys(['theme', 'views']))->toBeTrue(); + // Check non-existing keys + expect($this->company->hasMetadataAllKeys(['theme', 'invalid_key']))->toBeFalse(); + + // Using hasMetadataAnyKeys + // Check existing keys + expect($this->company->hasMetadataAnyKeys(['theme', 'views']))->toBeTrue(); + // Check one existing key + expect($this->company->hasMetadataAnyKeys(['theme', 'invalid_key']))->toBeTrue(); + // Check non-existing keys + expect($this->company->hasMetadataAnyKeys(['invalid_key1', 'invalid_key2']))->toBeFalse(); +}); From 02beb1c59fa7fb045d5bfc245b11cbfa24e10f75 Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Thu, 23 Jan 2025 00:09:51 +0300 Subject: [PATCH 06/23] Add pipMetadataToClearKeyNameId method to Helper class for metadata processing --- src/Helpers/Helper.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Helpers/Helper.php b/src/Helpers/Helper.php index be84d48..09e7e0c 100644 --- a/src/Helpers/Helper.php +++ b/src/Helpers/Helper.php @@ -2,6 +2,7 @@ namespace Waad\Metadata\Helpers; +use Illuminate\Support\Arr; use Illuminate\Support\Collection; class Helper @@ -22,4 +23,17 @@ public function isNullOrStringEmptyOrWhitespaceOrEmptyArray(mixed $value): bool return is_array($value) && empty($value); } + + public function pipMetadataToClearKeyNameId(array|Collection $metadata, string $keyNameId = 'id'): array + { + $metadata = $metadata instanceof Collection ? $metadata->toArray() : $metadata; + $firstItem = Arr::first($metadata); + + // Check if metadata is nested (array of arrays) + if (filled($firstItem) && is_array($firstItem)) { + return array_map(fn ($item) => Arr::except($item, $keyNameId), $metadata); + } + + return Arr::except($metadata, $keyNameId); + } } From db52dadcc834bf44edf0de8f04bcff23173e1ae6 Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Thu, 23 Jan 2025 00:10:17 +0300 Subject: [PATCH 07/23] Add mergeIdToMetadata method to Metadata class for enhanced metadata handling --- src/Models/Metadata.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Models/Metadata.php b/src/Models/Metadata.php index b5904aa..f0d1304 100644 --- a/src/Models/Metadata.php +++ b/src/Models/Metadata.php @@ -39,4 +39,11 @@ protected function asJson($value) { return json_encode($value, JSON_UNESCAPED_UNICODE); } + + public function mergeIdToMetadata(string $keyNameId = 'id'): self + { + $this->metadata = [$keyNameId => $this->id, ...$this->metadata]; + + return $this; + } } From c292709c259b954d720f0de50a0b4f81348deb48 Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Thu, 23 Jan 2025 00:11:51 +0300 Subject: [PATCH 08/23] add properties $metadataNameIdEnabled, $metadataNameId add method --- src/Traits/HasManyMetadata.php | 144 +++++++++++-- tests/Feature/HasManyMetadataTest.php | 295 ++++++++++++++++---------- 2 files changed, 312 insertions(+), 127 deletions(-) diff --git a/src/Traits/HasManyMetadata.php b/src/Traits/HasManyMetadata.php index cb973b7..a91b4f0 100644 --- a/src/Traits/HasManyMetadata.php +++ b/src/Traits/HasManyMetadata.php @@ -2,11 +2,42 @@ namespace Waad\Metadata\Traits; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Support\Collection; +use Waad\Metadata\Helpers\Helper; use Waad\Metadata\Models\Metadata; trait HasManyMetadata { + public $metadataNameIdEnabled = true; + + public $metadataNameId = 'id'; + + public function setMetadataNameIdEnabled(bool $metadataNameIdEnabled): self + { + $this->metadataNameIdEnabled = $metadataNameIdEnabled; + + return $this; + } + + public function getMetadataNameIdEnabled(): bool + { + return $this->metadataNameIdEnabled; + } + + public function setMetadataNameId(string $metadataNameId): self + { + $this->metadataNameId = $metadataNameId; + + return $this; + } + + public function getMetadataNameId(): string + { + return $this->metadataNameId; + } + /** * Create a new metadata record for the model */ @@ -17,64 +48,139 @@ public function createMetadata(array|Collection $metadata): Metadata ]); } + /** + * Create multiple metadata records for the model + */ + public function createManyMetadata(array|Collection $metadatas): Collection + { + $metadatas = is_array($metadatas) ? collect($metadatas) : $metadatas; + + return $metadatas->map(fn ($data) => $this->metadata()->create(['metadata' => $data])); + } + /** * Update an existing metadata record */ - public function updateMetadata(string $id, array|Collection $metadata): bool + public function updateMetadataById(string $id, array|Collection $metadata): bool { - return (bool) $this->metadata()->where('id', $id)->update([ - 'metadata' => $metadata instanceof Collection ? $metadata->toArray() : $metadata, + return (bool) $this->queryById($id)->update([ + 'metadata' => app(Helper::class)->pipMetadataToClearKeyNameId($metadata, $this->getMetadataNameId()), ]); } /** - * Delete a metadata record + * Sync metadata records by deleting existing ones and creating new ones + */ + public function syncMetadata(array|Collection $metadata): bool + { + if ($this->deleteMetadata()) { + return (bool) $this->createManyMetadata( + app(Helper::class)->pipMetadataToClearKeyNameId($metadata, $this->getMetadataNameId()) + ); + } + + return false; + } + + /** + * Delete a metadata record By ID */ - public function deleteMetadata(string $id): bool + public function deleteMetadataById(string $id): bool { - return (bool) $this->metadata()->where('id', $id)->delete(); + return (bool) $this->queryById($id)->delete(); } /** - * Get a metadata record by ID + * Delete all metadata records */ - public function getMetadataById(string $id): ?Metadata + public function deleteMetadata(): bool { - return $this->metadata()->find($id); + return (bool) $this->metadata()->delete(); } /** - * Search metadata records containing the given term + * Get a metadata array by ID + */ + public function getMetadataById(string $id): array + { + return $this->getMetadataNameIdEnabled() ? + $this->metadata()->find($id)?->mergeIdToMetadata($this->getMetadataNameId())->metadata ?? [] : + $this->metadata()->find($id)?->metadata ?? []; + } + + /** + * Search metadata records by exact value match or partial string match * * @param mixed $searchTerm */ - public function searchMetadata($searchTerm): \Illuminate\Database\Eloquent\Collection + public function searchMetadataCollection($searchTerm): Collection { - return $this->metadata()->whereJsonContains('metadata', $searchTerm)->get(); + $collection = $this->metadata()->whereJsonContains('metadata', $searchTerm)->get(); + + return $collection->map(fn ($item) => $this->getMetadataNameIdEnabled() ? + $item->mergeIdToMetadata($this->getMetadataNameId())->metadata : + $item->metadata + ); } /** - * Get metadata column as Array + * Search metadata records by exact value match or partial string match + * + * @param mixed $searchTerm */ - public function getMetadata(): ?array + public function searchMetadata($searchTerm): array { - return $this->metadata()->pluck('metadata')->toArray() ?: null; + return $this->searchMetadataCollection($searchTerm)->toArray(); } /** * Get metadata column as collection */ - public function getMetadataCollection(): ?Collection + public function getMetadataCollection(): Collection { - $metadata = $this->getMetadata(); + return $this->metadata()->get() + ->map(fn ($item) => $this->getMetadataNameIdEnabled() ? + $item->mergeIdToMetadata($this->getMetadataNameId())->metadata : + $item->metadata + ); + } - return $metadata ? collect($metadata) : null; + /** + * Get metadata column as Array + */ + public function getMetadata(): array + { + return $this->getMetadataCollection()->toArray(); + } + + /** + * Query metadata by ID + */ + public function queryById(string $id): Builder|MorphMany + { + return $this->metadata()->whereKey($id); + } + + /** + * Check if model has any metadata + */ + public function hasMetadata(): bool + { + return $this->metadata()->exists(); + } + + /** + * Check if model has metadata by ID + */ + public function hasMetadataById(string $id): bool + { + return $this->queryById($id)->exists(); } /** * Get the metadata relationship */ - public function metadata(): \Illuminate\Database\Eloquent\Relations\MorphMany + public function metadata(): MorphMany { return $this->morphMany(Metadata::class, 'metadatable'); } diff --git a/tests/Feature/HasManyMetadataTest.php b/tests/Feature/HasManyMetadataTest.php index 8f20151..1734308 100644 --- a/tests/Feature/HasManyMetadataTest.php +++ b/tests/Feature/HasManyMetadataTest.php @@ -1,27 +1,28 @@ post = createPost(); + expect($this->post)->toBeInstanceOf(Post::class); +}); + // Test to ensure a post can be created it('can create a post', function () { - $post = createPost(); - // Check that the created post is an instance of Post - expect($post) + expect($this->post) ->toBeInstanceOf(Post::class) - ->and($post->title)->not->toBeEmpty() // Ensure the title is not empty - ->and($post->content)->not->toBeEmpty() // Ensure the content is not empty - ->and($post->status)->toBeBool(); // Ensure the status is a boolean + ->and($this->post->title)->not->toBeEmpty() // Ensure the title is not empty + ->and($this->post->content)->not->toBeEmpty() // Ensure the content is not empty + ->and($this->post->status)->toBeBool(); // Ensure the status is a boolean }); // Test to ensure metadata can be attached to a post -it('can attach metadata to post', function () { - $post = createPost(); - expect($post)->toBeInstanceOf(Post::class); - +it('can create metadata to post using createMetadata', function () { // Create metadata for the post - $metadata = $post->createMetadata([ + $metadata = $this->post->createMetadata([ 'language' => 'English', 'is_visible' => true, 'author' => '', @@ -33,74 +34,46 @@ expect($metadata)->toBeInstanceOf(Metadata::class); }); -// Test to ensure post metadata can be updated -it('can update post metadata', function () { - $post = createPost(); - expect($post)->toBeInstanceOf(Post::class); - - // Create initial metadata - $metadata = $post->createMetadata([ - 'theme' => 'dark', - ]); - expect($metadata)->toBeInstanceOf(Metadata::class); - - // Update the metadata - $status = $post->updateMetadata($metadata->id, [ - 'theme' => 'light', +// Test to ensure multiple metadata records can be created for a post +it('can create multiple metadata records using createManyMetadata', function () { + // Create multiple metadata records + $metadataRecords = $this->post->createManyMetadata([ + [ + 'language' => 'English', + 'theme' => 'light', + ], + [ + 'language' => 'French', + 'theme' => 'dark', + ], + [ + 'language' => 'Spanish', + 'theme' => 'auto', + ], ]); - expect($status)->toBeTrue(); // Ensure the update was successful - expect($post->getMetadata())->toBeArray()->toContain(['theme' => 'light']); // Verify the updated metadata -}); -// Test to ensure existing post metadata can be updated -it('can update existing post metadata', function () { - $post = createPost(); - expect($post)->toBeInstanceOf(Post::class); + // Check that we got back a collection of Metadata instances + expect($metadataRecords) + ->toBeCollection() + ->toHaveCount(3) + ->each->toBeInstanceOf(Metadata::class); - // Create initial metadata - $metadata = $post->createMetadata([ - 'theme' => 'light', - ]); - expect($metadata)->toBeInstanceOf(Metadata::class); + // Verify the metadata values were stored correctly + $storedMetadata = $this->post->getMetadata(); + expect($storedMetadata)->toBeArray()->toHaveCount(3); - // Update the metadata - $status = $post->updateMetadata($metadata->id, [ - 'theme' => 'dark', - 'language' => 'French', - ]); - expect($status)->toBeTrue(); // Ensure the update was successful - - // Verify the updated metadata - $updatedMetadata = $post->getMetadata(); - expect($updatedMetadata)->toBeArray() - ->and($updatedMetadata[0]['theme'])->toBeString()->toBe('dark') // Check theme - ->and($updatedMetadata[0]['language'])->toBeString()->toBe('French'); // Check language + expect($storedMetadata[0]) + ->toMatchArray(['language' => 'English', 'theme' => 'light']); + expect($storedMetadata[1]) + ->toMatchArray(['language' => 'French', 'theme' => 'dark']); + expect($storedMetadata[2]) + ->toMatchArray(['language' => 'Spanish', 'theme' => 'auto']); }); -// Test to ensure post metadata can be deleted -it('can delete post metadata', function () { - $post = createPost(); - expect($post)->toBeInstanceOf(Post::class); - - // Create metadata for the post - $metadata = $post->createMetadata([ - 'theme' => 'dark', - ]); - expect($metadata)->toBeInstanceOf(Metadata::class); - - // Delete the metadata - $status = $post->deleteMetadata($metadata->id); - expect($status)->toBeTrue(); // Ensure the deletion was successful - expect($post->getMetadata())->toBeEmpty(); // Verify that metadata is now empty -}); - -// Test to ensure multiple types of metadata can be attached to a post -it('can attach multiple types of metadata to post', function () { - $post = createPost(); - expect($post)->toBeInstanceOf(Post::class); - +// Test metadata supports multiple data types (string, bool, null, int, float) +it('can add one metadata with multiple types', function () { // Create multiple types of metadata for the post - $metadata = $post->createMetadata([ + $metadata = $this->post->createMetadata([ 'language' => 'English', 'is_visible' => true, 'author' => '', @@ -113,7 +86,7 @@ expect($metadata)->toBeInstanceOf(Metadata::class); // Retrieve the attached metadata - $attachedMetadata = $post->getMetadata(); + $attachedMetadata = $this->post->getMetadata(); // Verify the attached metadata expect($attachedMetadata)->toBeArray() @@ -126,61 +99,167 @@ ->and($attachedMetadata[0]['rating'])->toBeFloat()->toBe(4.5); }); -// Test to ensure metadata can be retrieved as a collection -it('can retrieve metadata as a collection', function () { - $post = createPost(); - expect($post)->toBeInstanceOf(Post::class); +// Test to ensure post metadata can be updated +it('can update post metadata using updateMetadataById', function () { + // Create initial metadata + $metadata = $this->post->createMetadata([ + 'theme' => 'dark', + ]); + expect($metadata)->toBeInstanceOf(Metadata::class); + + // Update the metadata + $status = $this->post->updateMetadataById($metadata->id, [ + 'theme' => 'light', + ]); + expect($status)->toBeTrue(); + + // Get updated metadata and verify changes + $updatedMetadata = $this->post->getMetadataById($metadata->id); + expect($updatedMetadata)->toMatchArray(['id' => $metadata->id, 'theme' => 'light']); +}); + +// Test to ensure post metadata can be synced +it('can sync post metadata using syncMetadata', function () { + // Create initial metadata + $this->post->createManyMetadata([ + [ + 'language' => 'English', + 'theme' => 'light', + ], + [ + 'language' => 'Arabic', + 'theme' => 'dark', + ], + ]); + expect($this->post->getMetadata()) + ->toBeArray() + ->toHaveCount(2); + + // Sync with new metadata + $status = $this->post->syncMetadata([ + [ + 'language' => 'Spanish', + 'theme' => 'auto', + ], + ]); + expect($status)->toBeTrue(); + + // Verify old metadata was deleted and new metadata was created + $syncedMetadata = $this->post->getMetadata(); + expect($syncedMetadata) + ->toBeArray() + ->toHaveCount(1) + ->and($syncedMetadata[0]) + ->toMatchArray([ + 'language' => 'Spanish', + 'theme' => 'auto', + ]); +}); + +// Test to ensure post metadata can be deleted +it('can delete post metadata using deleteMetadataById and deleteMetadata', function () { // Create metadata for the post - $metadata = $post->createMetadata([ + $metadata = $this->post->createMetadata([ 'theme' => 'dark', - 'language' => 'French', ]); expect($metadata)->toBeInstanceOf(Metadata::class); - // Retrieve the metadata collection - $metadataCollection = $post->getMetadataCollection(); - expect($metadataCollection)->toBeInstanceOf(\Illuminate\Support\Collection::class) - ->and($metadataCollection->get(0)['theme'])->toBeString()->toBe('dark') // Check theme - ->and($metadataCollection->get(0)['language'])->toBeString()->toBe('French'); // Check language -}); + // Delete the metadata + $status = $this->post->deleteMetadataById($metadata->id); + expect($status)->toBeTrue(); // Ensure the deletion was successful + expect($this->post->getMetadata())->toBeEmpty(); // Verify that metadata is now empty -it('can retrieve metadata by ID', function () { - $post = createPost(); - expect($post)->toBeInstanceOf(Post::class); + // Create multiple metadata records + $this->post->createManyMetadata([ + [ + 'theme' => 'light', + 'language' => 'English', + ], + [ + 'theme' => 'dark', + 'language' => 'Spanish', + ], + ]); + expect($this->post->getMetadata())->toHaveCount(2); + + // Delete all metadata + $status = $this->post->deleteMetadata(); + expect($status)->toBeTrue(); + expect($this->post->getMetadata())->toBeEmpty(); +}); +// Test to ensure metadata can be retrieved as a collection +it('can retrieve metadata as a collection using getMetadataCollection', function () { // Create metadata for the post - $metadata = $post->createMetadata([ - 'language' => 'Spanish', - 'is_visible' => true, + $metadata = $this->post->createMetadata([ + 'theme' => 'dark', + 'language' => 'Arabic', ]); expect($metadata)->toBeInstanceOf(Metadata::class); - // Retrieve the metadata by ID - $retrievedMetadata = $post->getMetadataById($metadata->id); - expect($retrievedMetadata)->toBeInstanceOf(Metadata::class) - ->and($retrievedMetadata->metadata['language'])->toBeString()->toBe('Spanish') - ->and($retrievedMetadata->metadata['is_visible'])->toBeTrue(); + // Retrieve the metadata collection + $metadataCollection = $this->post->getMetadataCollection(); + expect($metadataCollection)->toBeInstanceOf(Collection::class) + ->and($metadataCollection->get(0)['theme'])->toBeString()->toBe('dark') + ->and($metadataCollection->get(0)['language'])->toBeString()->toBe('Arabic'); }); -it('can search metadata by language', function () { - $post = createPost(); - expect($post)->toBeInstanceOf(Post::class); +it('can retrieve metadata by ID using getMetadataById', function () { + // Create metadata for the post + $metadata = $this->post->createMetadata([ + 'language' => 'Arabic', + 'is_visible' => true, + ]); + // Retrieve metadata by ID and verify contents + $retrievedMetadata = $this->post->getMetadataById($metadata->id); + expect($retrievedMetadata) + ->toBeArray() + ->toHaveCount(3) + ->and($retrievedMetadata)->toMatchArray([ + 'language' => 'Arabic', + 'is_visible' => true, + ]) + ->and($retrievedMetadata['id'])->toBeString(); +}); + +it('can search metadata using searchMetadataCollection, searchMetadata', function () { // Create metadata for the post - $post->createMetadata([ - 'language' => 'German', + $this->post->createMetadata([ + 'language' => 'Arabic', 'is_visible' => true, ]); - $post->createMetadata([ - 'language' => 'Italian', + $this->post->createMetadata([ + 'language' => 'English', 'is_visible' => false, ]); - // Search for metadata by language - $searchResults = $post->searchMetadata('German'); - expect($searchResults)->toBeInstanceOf(\Illuminate\Support\Collection::class) + // Search for metadata by language to result collection + $searchResults = $this->post->searchMetadataCollection('Arabic'); + expect($searchResults)->toBeInstanceOf(Collection::class) ->and($searchResults->count())->toBe(1) - ->and($searchResults->first()->metadata['language'])->toBeString()->toBe('German') - ->and($searchResults->first()->metadata['is_visible'])->toBeTrue(); + ->and($searchResults->first()['language'])->toBeString()->toBe('Arabic') + ->and($searchResults->first()['is_visible'])->toBeTrue(); + + // Search for metadata by language to result array + $searchResults = $this->post->searchMetadata('Arabic'); + expect($searchResults)->toBeArray()->toHaveCount(1) + ->and($searchResults[0]['language'])->toBeString()->toBe('Arabic') + ->and($searchResults[0]['is_visible'])->toBeTrue(); +}); + +// Test to ensure model has metadata using hasMetadata, hasMetadataById +it('can check if model has metadata using hasMetadata, hasMetadataById', function () { + // check if model has metadata using hasMetadata + $this->post->createMetadata([ + 'language' => 'Arabic', + 'is_visible' => true, + ]); + expect($this->post->hasMetadata())->toBeTrue(); + + // check if model has metadata using hasMetadataById + $metadata = $this->post->getMetadata(); + expect($metadata)->toBeArray()->toHaveCount(1); + expect($this->post->hasMetadataById($metadata[0]['id']))->toBeTrue(); }); From a657bbf57b642c9530f520430bc85946cb92c15a Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Thu, 23 Jan 2025 14:23:14 +0300 Subject: [PATCH 09/23] Add methods to manage metadata keys by ID in HasManyMetadata trait - Introduced `addKeysMetadataById` for adding multiple keys to metadata. - Added `addKeyMetadataById` for adding a single key to metadata. - Enhanced tests to cover new methods, including handling of invalid inputs. --- src/Traits/HasManyMetadata.php | 26 ++++++++++ tests/Feature/HasManyMetadataTest.php | 68 +++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/src/Traits/HasManyMetadata.php b/src/Traits/HasManyMetadata.php index a91b4f0..19961b7 100644 --- a/src/Traits/HasManyMetadata.php +++ b/src/Traits/HasManyMetadata.php @@ -58,6 +58,32 @@ public function createManyMetadata(array|Collection $metadatas): Collection return $metadatas->map(fn ($data) => $this->metadata()->create(['metadata' => $data])); } + /** + * Add specific values by keys to metadata field by ID + */ + public function addKeysMetadataById(string $id, array|Collection|string|int|null $keys, array|Collection|string|int|float|bool|null $value = null): bool + { + $helper = app(Helper::class); + + if ($helper->isNullOrStringEmptyOrWhitespaceOrEmptyArray($keys)) { + return false; + } + + $metadata = $this->getMetadataById($id); + $keys = $keys instanceof Collection ? $keys->toArray() : (! is_array($keys) ? [$keys => $value] : $keys); + $newMetadata = $helper->pipMetadataToClearKeyNameId(array_merge($metadata, $keys)); + + return $this->updateMetadataById($id, $newMetadata); + } + + /** + * Add one specific value by key to metadata field by ID + */ + public function addKeyMetadataById(string $id, string|int|null $key, array|Collection|string|int|float|bool|null $value = null): bool + { + return $this->addKeysMetadataById($id, $key, $value); + } + /** * Update an existing metadata record */ diff --git a/tests/Feature/HasManyMetadataTest.php b/tests/Feature/HasManyMetadataTest.php index 1734308..bc5a225 100644 --- a/tests/Feature/HasManyMetadataTest.php +++ b/tests/Feature/HasManyMetadataTest.php @@ -99,6 +99,74 @@ ->and($attachedMetadata[0]['rating'])->toBeFloat()->toBe(4.5); }); +// Test adding multiple keys to metadata using addKeysMetadataById +it('can add multiple keys to metadata using addKeysMetadataById', function () { + // Create initial metadata + $metadata = $this->post->createMetadata([ + 'language' => 'English', + 'theme' => 'light', + ]); + expect($metadata)->toBeInstanceOf(Metadata::class); + + // Add multiple keys to metadata + $status = $this->post->addKeysMetadataById($metadata->id, [ + 'is_visible' => true, + 'views' => 100, + ]); + expect($status)->toBeTrue(); + + // Verify the metadata was updated correctly + $updatedMetadata = $this->post->getMetadataById($metadata->id); + expect($updatedMetadata) + ->toMatchArray([ + 'language' => 'English', + 'theme' => 'light', + 'is_visible' => true, + 'views' => 100, + ]); +}); + +// Test adding a single key to metadata using addKeyMetadataById +it('can add single key to metadata using addKeyMetadataById', function () { + // Create initial metadata + $metadata = $this->post->createMetadata([ + 'language' => 'English', + ]); + + // Add single key to metadata + $status = $this->post->addKeyMetadataById($metadata->id, 'is_visible', true); + expect($status)->toBeTrue(); + + // Verify the metadata was updated correctly + $updatedMetadata = $this->post->getMetadataById($metadata->id); + expect($updatedMetadata) + ->toMatchArray([ + 'language' => 'English', + 'is_visible' => true, + ]); +}); + +// Test adding keys to metadata with invalid inputs +it('handles invalid inputs when adding metadata keys', function () { + // Create initial metadata + $metadata = $this->post->createMetadata([ + 'language' => 'English', + ]); + + // Test with null key + expect($this->post->addKeysMetadataById($metadata->id, null))->toBeFalse(); + + // Test with empty string key + expect($this->post->addKeyMetadataById($metadata->id, ''))->toBeFalse(); + + // Test with empty array + expect($this->post->addKeysMetadataById($metadata->id, []))->toBeFalse(); + + // Verify original metadata remains unchanged + $unchangedMetadata = $this->post->getMetadataById($metadata->id); + expect($unchangedMetadata)->toMatchArray(['language' => 'English']); +}); + // Test to ensure post metadata can be updated it('can update post metadata using updateMetadataById', function () { // Create initial metadata From 39f1fb677b102356107e22611c0184dad6becd65 Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Thu, 23 Jan 2025 14:30:14 +0300 Subject: [PATCH 10/23] Add update methods for metadata in HasManyMetadata trait - Introduced `updateKeysMetadataById` for updating multiple metadata keys by ID. - Added `updateKeyMetadataById` for updating a single metadata key by ID. - Enhanced tests to validate the new update methods for both multiple and single key updates. --- src/Traits/HasManyMetadata.php | 16 +++++++++ tests/Feature/HasManyMetadataTest.php | 49 +++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/src/Traits/HasManyMetadata.php b/src/Traits/HasManyMetadata.php index 19961b7..db686e6 100644 --- a/src/Traits/HasManyMetadata.php +++ b/src/Traits/HasManyMetadata.php @@ -94,6 +94,22 @@ public function updateMetadataById(string $id, array|Collection $metadata): bool ]); } + /** + * Update specific values by keys in metadata field by ID + */ + public function updateKeysMetadataById(string $id, array|Collection|string|int|null $keys, array|Collection|string|int|float|bool|null $value = null): bool + { + return $this->addKeysMetadataById($id, $keys, $value); + } + + /** + * Update one specific value by key in metadata field by ID + */ + public function updateKeyMetadataById(string $id, string|int|null $key, array|Collection|string|int|float|bool|null $value = null): bool + { + return $this->updateKeysMetadataById($id, $key, $value); + } + /** * Sync metadata records by deleting existing ones and creating new ones */ diff --git a/tests/Feature/HasManyMetadataTest.php b/tests/Feature/HasManyMetadataTest.php index bc5a225..f7ce349 100644 --- a/tests/Feature/HasManyMetadataTest.php +++ b/tests/Feature/HasManyMetadataTest.php @@ -186,6 +186,55 @@ expect($updatedMetadata)->toMatchArray(['id' => $metadata->id, 'theme' => 'light']); }); +// Test updating multiple keys in metadata using updateKeysMetadataById +it('can update multiple keys in metadata using updateKeysMetadataById', function () { + // Create initial metadata + $metadata = $this->post->createMetadata([ + 'language' => 'English', + 'theme' => 'light', + 'views' => 100, + ]); + expect($metadata)->toBeInstanceOf(Metadata::class); + + // Update multiple keys in metadata + $status = $this->post->updateKeysMetadataById($metadata->id, [ + 'theme' => 'dark', + 'views' => 200, + ]); + expect($status)->toBeTrue(); + + // Verify the metadata was updated correctly + $updatedMetadata = $this->post->getMetadataById($metadata->id); + expect($updatedMetadata) + ->toMatchArray([ + 'language' => 'English', + 'theme' => 'dark', + 'views' => 200, + ]); +}); + +// Test updating a single key in metadata using updateKeyMetadataById +it('can update single key in metadata using updateKeyMetadataById', function () { + // Create initial metadata + $metadata = $this->post->createMetadata([ + 'language' => 'English', + 'theme' => 'light', + ]); + expect($metadata)->toBeInstanceOf(Metadata::class); + + // Update single key in metadata + $status = $this->post->updateKeyMetadataById($metadata->id, 'theme', 'dark'); + expect($status)->toBeTrue(); + + // Verify the metadata was updated correctly + $updatedMetadata = $this->post->getMetadataById($metadata->id); + expect($updatedMetadata) + ->toMatchArray([ + 'language' => 'English', + 'theme' => 'dark', + ]); +}); + // Test to ensure post metadata can be synced it('can sync post metadata using syncMetadata', function () { // Create initial metadata From 19ab7992adc329913f0ee5f785ee1266d9b6f638 Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Thu, 23 Jan 2025 20:41:42 +0300 Subject: [PATCH 11/23] add methods for hasManyMetadata - forgetMetadataById - forgetKeysMetadataById - forgetKeyMetadataById --- src/Models/Metadata.php | 2 +- src/Traits/HasManyMetadata.php | 43 +++++++++++++++++++++++++++ tests/Feature/HasManyMetadataTest.php | 36 ++++++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/Models/Metadata.php b/src/Models/Metadata.php index f0d1304..10dfa16 100644 --- a/src/Models/Metadata.php +++ b/src/Models/Metadata.php @@ -42,7 +42,7 @@ protected function asJson($value) public function mergeIdToMetadata(string $keyNameId = 'id'): self { - $this->metadata = [$keyNameId => $this->id, ...$this->metadata]; + $this->metadata = array_merge([$keyNameId => $this->id], $this->metadata ?? []); return $this; } diff --git a/src/Traits/HasManyMetadata.php b/src/Traits/HasManyMetadata.php index db686e6..73c783d 100644 --- a/src/Traits/HasManyMetadata.php +++ b/src/Traits/HasManyMetadata.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\MorphMany; +use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Waad\Metadata\Helpers\Helper; use Waad\Metadata\Models\Metadata; @@ -140,6 +141,48 @@ public function deleteMetadata(): bool return (bool) $this->metadata()->delete(); } + /** + * Forget content of metadata by ID + */ + public function forgetMetadataById(string $id): bool + { + return (bool) $this->queryById($id)->update(['metadata' => null]); + } + + /** + * Forget content of Keys for metadata by ID + */ + public function forgetKeysMetadataById(string $id, array|Collection|string|int|null $keys = null): bool + { + if (app(Helper::class)->isNullOrStringEmptyOrWhitespaceOrEmptyArray($keys)) { + return false; + } + + $oldIsWithId = $this->getMetadataNameIdEnabled(); + $this->setMetadataNameIdEnabled(false); + $metadata = $this->getMetadataById($id); + $this->setMetadataNameIdEnabled($oldIsWithId); + if (is_null($metadata)) { + return false; + } + + if ($keys instanceof Collection) { + $keys = $keys->toArray(); + } + + $keys = Arr::wrap($keys); + + return $this->updateMetadataById($id, Arr::except($metadata, $keys)); + } + + /** + * Forget content of Key for metadata by ID + */ + public function forgetKeyMetadataById(string $id, string|int|null $key = null): bool + { + return $this->forgetKeysMetadataById($id, $key); + } + /** * Get a metadata array by ID */ diff --git a/tests/Feature/HasManyMetadataTest.php b/tests/Feature/HasManyMetadataTest.php index f7ce349..ac3f5d9 100644 --- a/tests/Feature/HasManyMetadataTest.php +++ b/tests/Feature/HasManyMetadataTest.php @@ -306,6 +306,42 @@ expect($this->post->getMetadata())->toBeEmpty(); }); +// Test to ensure metadata content can be forgotten +it('can forget metadata content using forgetMetadataById, forgetKeysMetadataById, forgetKeyMetadataById', function () { + // Create metadata for testing + $metadata = $this->post->createMetadata([ + 'theme' => 'dark', + 'language' => 'English', + 'notifications' => true, + ]); + expect($metadata)->toBeInstanceOf(Metadata::class); + + // Test forgetMetadataById - sets metadata to null + $status = $this->post->forgetMetadataById($metadata->id); + expect($status)->toBeTrue(); + expect($this->post->getMetadataById($metadata->id))->toBeArray()->toHaveCount(1); + + // Create new metadata for testing keys + $metadata = $this->post->createMetadata([ + 'theme' => 'light', + 'language' => 'Arabic', + 'notifications' => false, + ]); + + // Test forgetKeysMetadataById - removes specific keys + $status = $this->post->forgetKeysMetadataById($metadata->id, ['theme', 'notifications']); + expect($status)->toBeTrue(); + expect($this->post->getMetadataById($metadata->id)) + ->toBeArray() + ->toHaveCount(2) + ->toMatchArray(['language' => 'Arabic']); + + // Test forgetKeyMetadataById - removes single key + $status = $this->post->forgetKeyMetadataById($metadata->id, 'language'); + expect($status)->toBeTrue(); + expect($this->post->getMetadataById($metadata->id))->toBeArray()->toHaveCount(1); +}); + // Test to ensure metadata can be retrieved as a collection it('can retrieve metadata as a collection using getMetadataCollection', function () { // Create metadata for the post From 28552ef0bec01aeff92eee2758c6be1d547548ba Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Thu, 23 Jan 2025 22:16:07 +0300 Subject: [PATCH 12/23] Enhance `getMetadataById` method in HasManyMetadata trait - Updated `getMetadataById` to accept an optional parameter for specifying keys to retrieve specific metadata fields. - Added logic to handle various input types for the keys parameter, including arrays and single values. - Introduced new tests to validate the functionality of retrieving metadata by ID with and without specified keys. - Improved existing tests for consistency and clarity. --- src/Traits/HasManyMetadata.php | 16 +++++- tests/Feature/HasManyMetadataTest.php | 81 ++++++++++++++++----------- 2 files changed, 63 insertions(+), 34 deletions(-) diff --git a/src/Traits/HasManyMetadata.php b/src/Traits/HasManyMetadata.php index 73c783d..95b6f05 100644 --- a/src/Traits/HasManyMetadata.php +++ b/src/Traits/HasManyMetadata.php @@ -186,11 +186,23 @@ public function forgetKeyMetadataById(string $id, string|int|null $key = null): /** * Get a metadata array by ID */ - public function getMetadataById(string $id): array + public function getMetadataById(string $id, array|Collection|string|int|null $keys = null): array { - return $this->getMetadataNameIdEnabled() ? + $metadata = $this->getMetadataNameIdEnabled() ? $this->metadata()->find($id)?->mergeIdToMetadata($this->getMetadataNameId())->metadata ?? [] : $this->metadata()->find($id)?->metadata ?? []; + + if (app(Helper::class)->isNullOrStringEmptyOrWhitespaceOrEmptyArray($keys)) { + return $metadata; + } + + $keys = Arr::wrap($keys); + + if ($this->getMetadataNameIdEnabled()) { + $keys = array_merge([$this->getMetadataNameId()], $keys); + } + + return Arr::only($metadata, $keys); } /** diff --git a/tests/Feature/HasManyMetadataTest.php b/tests/Feature/HasManyMetadataTest.php index ac3f5d9..5fed3f7 100644 --- a/tests/Feature/HasManyMetadataTest.php +++ b/tests/Feature/HasManyMetadataTest.php @@ -53,21 +53,15 @@ ]); // Check that we got back a collection of Metadata instances - expect($metadataRecords) - ->toBeCollection() - ->toHaveCount(3) - ->each->toBeInstanceOf(Metadata::class); + expect($metadataRecords)->toBeCollection()->toHaveCount(3)->each->toBeInstanceOf(Metadata::class); // Verify the metadata values were stored correctly $storedMetadata = $this->post->getMetadata(); expect($storedMetadata)->toBeArray()->toHaveCount(3); - expect($storedMetadata[0]) - ->toMatchArray(['language' => 'English', 'theme' => 'light']); - expect($storedMetadata[1]) - ->toMatchArray(['language' => 'French', 'theme' => 'dark']); - expect($storedMetadata[2]) - ->toMatchArray(['language' => 'Spanish', 'theme' => 'auto']); + expect($storedMetadata[0])->toMatchArray(['language' => 'English', 'theme' => 'light']); + expect($storedMetadata[1])->toMatchArray(['language' => 'French', 'theme' => 'dark']); + expect($storedMetadata[2])->toMatchArray(['language' => 'Spanish', 'theme' => 'auto']); }); // Test metadata supports multiple data types (string, bool, null, int, float) @@ -82,7 +76,6 @@ 'views' => 100, 'rating' => 4.5, ]); - expect($metadata)->toBeInstanceOf(Metadata::class); // Retrieve the attached metadata @@ -99,6 +92,44 @@ ->and($attachedMetadata[0]['rating'])->toBeFloat()->toBe(4.5); }); +// Test retrieving metadata by ID using getMetadataById +it('can get metadata by ID using getMetadataById', function () { + // Create metadata record + $metadata = $this->post->createMetadata([ + 'language' => 'English', + 'theme' => 'light', + 'is_visible' => true, + 'views' => 100, + ]); + expect($metadata)->toBeInstanceOf(Metadata::class); + + // Get metadata by ID without specifying keys + $retrievedMetadata = $this->post->getMetadataById($metadata->id); + + // Verify all metadata was retrieved correctly + expect($retrievedMetadata)->toBeArray() + ->toMatchArray([ + 'language' => 'English', + 'theme' => 'light', + 'is_visible' => true, + 'views' => 100, + ]); + + // Get only specific keys from metadata + $retrievedMetadata = $this->post->getMetadataById($metadata->id, ['language', 'theme']); + + // Verify only requested keys were retrieved + expect($retrievedMetadata)->toBeArray()->toHaveCount(3) + ->toMatchArray([ + 'language' => 'English', + 'theme' => 'light', + ]); + + // Test retrieving single key + $singleKeyMetadata = $this->post->getMetadataById($metadata->id, 'views'); + expect($singleKeyMetadata)->toBeArray()->toHaveCount(2)->toMatchArray(['views' => 100]); +}); + // Test adding multiple keys to metadata using addKeysMetadataById it('can add multiple keys to metadata using addKeysMetadataById', function () { // Create initial metadata @@ -153,14 +184,9 @@ 'language' => 'English', ]); - // Test with null key - expect($this->post->addKeysMetadataById($metadata->id, null))->toBeFalse(); - - // Test with empty string key - expect($this->post->addKeyMetadataById($metadata->id, ''))->toBeFalse(); - - // Test with empty array - expect($this->post->addKeysMetadataById($metadata->id, []))->toBeFalse(); + expect($this->post->addKeysMetadataById($metadata->id, null))->toBeFalse(); // Test with null key + expect($this->post->addKeyMetadataById($metadata->id, ''))->toBeFalse(); // Test with empty string key + expect($this->post->addKeysMetadataById($metadata->id, []))->toBeFalse(); // Test with empty array // Verify original metadata remains unchanged $unchangedMetadata = $this->post->getMetadataById($metadata->id); @@ -249,9 +275,7 @@ ], ]); - expect($this->post->getMetadata()) - ->toBeArray() - ->toHaveCount(2); + expect($this->post->getMetadata())->toBeArray()->toHaveCount(2); // Sync with new metadata $status = $this->post->syncMetadata([ @@ -264,9 +288,7 @@ // Verify old metadata was deleted and new metadata was created $syncedMetadata = $this->post->getMetadata(); - expect($syncedMetadata) - ->toBeArray() - ->toHaveCount(1) + expect($syncedMetadata)->toBeArray()->toHaveCount(1) ->and($syncedMetadata[0]) ->toMatchArray([ 'language' => 'Spanish', @@ -331,10 +353,7 @@ // Test forgetKeysMetadataById - removes specific keys $status = $this->post->forgetKeysMetadataById($metadata->id, ['theme', 'notifications']); expect($status)->toBeTrue(); - expect($this->post->getMetadataById($metadata->id)) - ->toBeArray() - ->toHaveCount(2) - ->toMatchArray(['language' => 'Arabic']); + expect($this->post->getMetadataById($metadata->id))->toBeArray()->toHaveCount(2)->toMatchArray(['language' => 'Arabic']); // Test forgetKeyMetadataById - removes single key $status = $this->post->forgetKeyMetadataById($metadata->id, 'language'); @@ -367,9 +386,7 @@ // Retrieve metadata by ID and verify contents $retrievedMetadata = $this->post->getMetadataById($metadata->id); - expect($retrievedMetadata) - ->toBeArray() - ->toHaveCount(3) + expect($retrievedMetadata)->toBeArray()->toHaveCount(3) ->and($retrievedMetadata)->toMatchArray([ 'language' => 'Arabic', 'is_visible' => true, From 2a65927e92d0af23f0e6cfa659be110a46278ee0 Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Thu, 23 Jan 2025 22:38:11 +0300 Subject: [PATCH 13/23] Enhance metadata retrieval tests in HasManyMetadataTest - Added a test to verify that setting `$metadataNameIdEnabled` to false results in an empty array when retrieving metadata by ID. - Improved the overall test coverage for metadata management methods, ensuring robust validation of functionality. --- tests/Feature/HasManyMetadataTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Feature/HasManyMetadataTest.php b/tests/Feature/HasManyMetadataTest.php index 5fed3f7..8329358 100644 --- a/tests/Feature/HasManyMetadataTest.php +++ b/tests/Feature/HasManyMetadataTest.php @@ -359,6 +359,7 @@ $status = $this->post->forgetKeyMetadataById($metadata->id, 'language'); expect($status)->toBeTrue(); expect($this->post->getMetadataById($metadata->id))->toBeArray()->toHaveCount(1); + expect($this->post->setMetadataNameIdEnabled(false)->getMetadataById($metadata->id))->toBeArray()->toBeEmpty(); }); // Test to ensure metadata can be retrieved as a collection From aa752ad56ba2373a19b28d18c6ffd7b722349f6b Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Thu, 23 Jan 2025 22:47:39 +0300 Subject: [PATCH 14/23] Update Laravel framework version constraint in composer.json - Changed the version constraint for "laravel/framework" to require "^9.30.1|^10.0|^11.0" to ensure compatibility with the latest Laravel 9.x releases. to support ulid for v9 in laravel --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d9c9544..0904d87 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "require": { "php": "^8.0", "ext-json": "*", - "laravel/framework": "^9.0|^10.0|^11.0" + "laravel/framework": "^9.30.1|^10.0|^11.0" }, "require-dev": { "laravel/pint": "^1.0", From f10bb9194a05448c2370c9f240644f057c5b4db0 Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Thu, 23 Jan 2025 22:48:38 +0300 Subject: [PATCH 15/23] Add `getKeyMetadataById` method to HasManyMetadata trait and corresponding tests - Introduced `getKeyMetadataById` method for retrieving individual metadata values by ID and key, supporting various data types. - Added tests in `HasManyMetadataTest` to validate the functionality of the new method, ensuring correct retrieval of different metadata types and handling of non-existent keys. --- src/Traits/HasManyMetadata.php | 5 +++++ tests/Feature/HasManyMetadataTest.php | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/Traits/HasManyMetadata.php b/src/Traits/HasManyMetadata.php index 95b6f05..23ec53e 100644 --- a/src/Traits/HasManyMetadata.php +++ b/src/Traits/HasManyMetadata.php @@ -205,6 +205,11 @@ public function getMetadataById(string $id, array|Collection|string|int|null $ke return Arr::only($metadata, $keys); } + public function getKeyMetadataById(string $id, string|int $key): string|int|float|bool|array|null + { + return $this->getMetadataById($id, $key)[$key] ?? null; + } + /** * Search metadata records by exact value match or partial string match * diff --git a/tests/Feature/HasManyMetadataTest.php b/tests/Feature/HasManyMetadataTest.php index 8329358..c62e01a 100644 --- a/tests/Feature/HasManyMetadataTest.php +++ b/tests/Feature/HasManyMetadataTest.php @@ -395,6 +395,27 @@ ->and($retrievedMetadata['id'])->toBeString(); }); +it('can get individual metadata value by ID and key using getKeyMetadataById', function () { + // Create metadata with multiple fields + $metadata = $this->post->createMetadata([ + 'language' => 'Arabic', + 'is_visible' => true, + 'views' => 100, + 'rating' => 4.5, + 'theme' => null, + ]); + + // Test retrieving different data types + expect($this->post->getKeyMetadataById($metadata->id, 'language'))->toBeString()->toBe('Arabic'); + expect($this->post->getKeyMetadataById($metadata->id, 'is_visible'))->toBeBool()->toBeTrue(); + expect($this->post->getKeyMetadataById($metadata->id, 'views'))->toBeInt()->toBe(100); + expect($this->post->getKeyMetadataById($metadata->id, 'rating'))->toBeFloat()->toBe(4.5); + expect($this->post->getKeyMetadataById($metadata->id, 'theme'))->toBeNull(); + + // Test retrieving non-existent key + expect($this->post->getKeyMetadataById($metadata->id, 'non_existent'))->toBeNull(); +}); + it('can search metadata using searchMetadataCollection, searchMetadata', function () { // Create metadata for the post $this->post->createMetadata([ From ef837a3b927103056de6a0e32e93075cd37b863e Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Thu, 23 Jan 2025 22:50:51 +0300 Subject: [PATCH 16/23] Enhance metadata tests in HasManyMetadataTest - Added a new test case to validate retrieval of 'actors' metadata by ID using the `getKeyMetadataById` method. - Ensured that the method correctly returns an array of actors, improving test coverage for metadata retrieval functionality. --- tests/Feature/HasManyMetadataTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Feature/HasManyMetadataTest.php b/tests/Feature/HasManyMetadataTest.php index c62e01a..dfbd1bd 100644 --- a/tests/Feature/HasManyMetadataTest.php +++ b/tests/Feature/HasManyMetadataTest.php @@ -403,6 +403,7 @@ 'views' => 100, 'rating' => 4.5, 'theme' => null, + 'actors' => ['John Doe', 'Jane Smith'], ]); // Test retrieving different data types @@ -411,6 +412,7 @@ expect($this->post->getKeyMetadataById($metadata->id, 'views'))->toBeInt()->toBe(100); expect($this->post->getKeyMetadataById($metadata->id, 'rating'))->toBeFloat()->toBe(4.5); expect($this->post->getKeyMetadataById($metadata->id, 'theme'))->toBeNull(); + expect($this->post->getKeyMetadataById($metadata->id, 'actors'))->toBeArray()->toBe(['John Doe', 'Jane Smith']); // Test retrieving non-existent key expect($this->post->getKeyMetadataById($metadata->id, 'non_existent'))->toBeNull(); From 3b73d9aa1730d3a72269389599ca3577be46dbd5 Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Thu, 23 Jan 2025 23:01:06 +0300 Subject: [PATCH 17/23] Refactor metadata retrieval methods in HasOneMetadata trait - Updated `getMetadata` and `getMetadataCollection` methods to ensure they return an empty array instead of null when no metadata is found, improving consistency in return types. - Enhanced tests in `HasOneMetadataTest` to verify that metadata retrieval methods return an empty array when no metadata is present, ensuring robust validation of functionality. --- src/Traits/HasOneMetadata.php | 10 ++++------ tests/Feature/HasOneMetadataTest.php | 7 +++++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Traits/HasOneMetadata.php b/src/Traits/HasOneMetadata.php index 21689a7..ad595e4 100644 --- a/src/Traits/HasOneMetadata.php +++ b/src/Traits/HasOneMetadata.php @@ -204,12 +204,12 @@ public function hasFilledMetadata(): bool /** * Get metadata column as Array */ - public function getMetadata(array|Collection|string|int|null $keys = null): ?array + public function getMetadata(array|Collection|string|int|null $keys = null): array { $metadata = $this->metadata()->first()?->metadata; if (app(Helper::class)->isNullOrStringEmptyOrWhitespaceOrEmptyArray($keys)) { - return $metadata; + return $metadata ?? []; } $keys = Arr::wrap($keys); @@ -228,11 +228,9 @@ public function getMetadataByKey(string|int $key): string|int|float|bool|array|n /** * Get metadata column as collection */ - public function getMetadataCollection(array|Collection|string|int|null $keys = null): ?Collection + public function getMetadataCollection(array|Collection|string|int|null $keys = null): Collection { - $metadata = $this->getMetadata($keys); - - return $metadata ? collect($metadata) : null; + return collect($this->getMetadata($keys)); } /** diff --git a/tests/Feature/HasOneMetadataTest.php b/tests/Feature/HasOneMetadataTest.php index 513bcf4..61469b5 100644 --- a/tests/Feature/HasOneMetadataTest.php +++ b/tests/Feature/HasOneMetadataTest.php @@ -71,6 +71,9 @@ ->and($this->company->getMetadataByKey('views'))->toBe(100) ->and($this->company->getMetadataByKey('settings'))->toBeArray() ->and($this->company->getMetadataByKey('nonexistent'))->toBeNull(); + + $this->company->syncMetadata([]); + expect($this->company->getMetadata())->toBeArray()->toBeEmpty(); }); // Test to ensure metadata can be retrieved as a collection @@ -259,7 +262,7 @@ // Delete the metadata $status = $this->company->deleteMetadata(); expect($status)->toBeTrue(); // Ensure the deletion was successful - expect($this->company->getMetadata())->toBeNull(); // Verify that metadata is now null + expect($this->company->getMetadata())->toBeArray()->toBeEmpty(); // Verify that metadata is now empty array }); // Test to ensure metadata can be cleared using clearMetadata, clearMetadataByKeys and clearMetadataByKey @@ -297,7 +300,7 @@ expect($status)->toBeTrue(); $metadata = $this->company->getMetadata(); - expect($metadata)->toBeNull(); + expect($metadata)->toBeArray()->toBeEmpty(); }); // Test to ensure metadata is exists using hasMetadata, hasFilledMetadata From 2303aaeaf8c6fa1afd667387179fe47c51d095b4 Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Thu, 23 Jan 2025 23:16:46 +0300 Subject: [PATCH 18/23] Refactor metadata methods in HasOneMetadata trait for clarity and consistency - Renamed several methods to improve clarity, including `addMetadataByKeys` to `addKeysMetadata`, `addMetadataByKey` to `addKeyMetadata`, and similar changes for update, clear, and check methods. - Updated corresponding test cases in `HasOneMetadataTest` to reflect the new method names, ensuring all tests accurately validate the functionality of the refactored methods. - Enhanced readability and maintainability of the codebase by standardizing method naming conventions. --- src/Traits/HasOneMetadata.php | 34 ++++----- tests/Feature/HasOneMetadataTest.php | 101 +++++++++++++-------------- 2 files changed, 64 insertions(+), 71 deletions(-) diff --git a/src/Traits/HasOneMetadata.php b/src/Traits/HasOneMetadata.php index ad595e4..aef9ab3 100644 --- a/src/Traits/HasOneMetadata.php +++ b/src/Traits/HasOneMetadata.php @@ -26,7 +26,7 @@ public function createMetadata(array|Collection $metadata): ?Metadata /** * Add specific values by keys from metadata field */ - public function addMetadataByKeys(array|Collection|string|int|null $keys, array|Collection|string|int|float|bool|null $value = null): bool + public function addKeysMetadata(array|Collection|string|int|null $keys, array|Collection|string|int|float|bool|null $value = null): bool { $helper = app(Helper::class); @@ -43,9 +43,9 @@ public function addMetadataByKeys(array|Collection|string|int|null $keys, array| /** * Add one specific value by key from metadata field */ - public function addMetadataByKey(string|int|null $key, array|Collection|string|int|float|bool|null $value = null): bool + public function addKeyMetadata(string|int|null $key, array|Collection|string|int|float|bool|null $value = null): bool { - return $this->addMetadataByKeys($key, $value); + return $this->addKeysMetadata($key, $value); } /** @@ -75,7 +75,7 @@ public function updateMetadata(array|Collection $metadata): bool /** * Update specific values by keys from metadata field */ - public function updateMetadataByKeys(array|Collection|string|int|null $keys, array|Collection|string|int|float|bool|null $value = null): bool + public function updateKeysMetadata(array|Collection|string|int|null $keys, array|Collection|string|int|float|bool|null $value = null): bool { $helper = app(Helper::class); @@ -92,9 +92,9 @@ public function updateMetadataByKeys(array|Collection|string|int|null $keys, arr /** * Update one specific value by key from metadata field */ - public function updateMetadataByKey(string|int|null $key, array|Collection|string|int|float|bool|null $value = null): bool + public function updateKeyMetadata(string|int|null $key, array|Collection|string|int|float|bool|null $value = null): bool { - return $this->updateMetadataByKeys($key, $value); + return $this->updateKeysMetadata($key, $value); } /** @@ -108,7 +108,7 @@ public function deleteMetadata(): bool /** * Emptying metadata field make it null */ - public function clearMetadata(): bool + public function forgetMetadata(): bool { return (bool) $this->metadata()->first()?->update(['metadata' => null]); } @@ -116,7 +116,7 @@ public function clearMetadata(): bool /** * delete specific value by key from metadata field */ - public function clearMetadataByKeys(array|Collection|string|int|null $keys = null): bool + public function forgetKeysMetadata(array|Collection|string|int|null $keys = null): bool { if (app(Helper::class)->isNullOrStringEmptyOrWhitespaceOrEmptyArray($keys)) { return false; @@ -139,9 +139,9 @@ public function clearMetadataByKeys(array|Collection|string|int|null $keys = nul /** * delete specific value by key from metadata field */ - public function clearMetadataByKey(string|int|null $key = null): bool + public function forgetKeyMetadata(string|int|null $key = null): bool { - return $this->clearMetadataByKeys($key); + return $this->forgetKeysMetadata($key); } /** @@ -155,7 +155,7 @@ public function hasMetadata(): bool /** * Check if metadata exists all keys */ - public function hasMetadataAllKeys(array|Collection|string|int|null $keys): bool + public function hasAllKeysMetadata(array|Collection|string|int|null $keys): bool { if (app(Helper::class)->isNullOrStringEmptyOrWhitespaceOrEmptyArray($keys)) { return false; @@ -170,19 +170,19 @@ public function hasMetadataAllKeys(array|Collection|string|int|null $keys): bool /** * Check if metadata exists by key */ - public function hasMetadataByKey(string|int|null $key): bool + public function hasKeyMetadata(string|int|null $key): bool { - return $this->hasMetadataAllKeys($key); + return $this->hasAllKeysMetadata($key); } /** * Check if metadata exists any keys */ - public function hasMetadataAnyKeys(array|Collection|string|int|null $keys): bool + public function hasAnyKeysMetadata(array|Collection|string|int|null $keys): bool { if (is_array($keys) || $keys instanceof Collection) { foreach ($keys as $key) { - if ($this->hasMetadataByKey($key)) { + if ($this->hasKeyMetadata($key)) { return true; } } @@ -190,7 +190,7 @@ public function hasMetadataAnyKeys(array|Collection|string|int|null $keys): bool return false; } - return $this->hasMetadataByKey($keys); + return $this->hasKeyMetadata($keys); } /** @@ -220,7 +220,7 @@ public function getMetadata(array|Collection|string|int|null $keys = null): arra /** * Get individual metadata */ - public function getMetadataByKey(string|int $key): string|int|float|bool|array|null + public function getKeyMetadata(string|int $key): string|int|float|bool|array|null { return $this->getMetadata($key)[$key] ?? null; } diff --git a/tests/Feature/HasOneMetadataTest.php b/tests/Feature/HasOneMetadataTest.php index 61469b5..0417711 100644 --- a/tests/Feature/HasOneMetadataTest.php +++ b/tests/Feature/HasOneMetadataTest.php @@ -34,8 +34,8 @@ expect($metadata)->toBeInstanceOf(Metadata::class); }); -// Test to ensure metadata can be retrieved using getMetadata and getMetadataByKey -it('can retrieve metadata using getMetadata and getMetadataByKey', function () { +// Test to ensure metadata can be retrieved using getMetadata and getKeyMetadata +it('can retrieve metadata using getMetadata and getKeyMetadata', function () { // Create metadata for the company $this->company->createMetadata([ 'theme' => 'dark', @@ -66,11 +66,11 @@ ->and($partialMetadata)->toHaveCount(1) ->and($partialMetadata['theme'])->toBe('dark'); - // Test getMetadataByKey for individual values - expect($this->company->getMetadataByKey('theme'))->toBe('dark') - ->and($this->company->getMetadataByKey('views'))->toBe(100) - ->and($this->company->getMetadataByKey('settings'))->toBeArray() - ->and($this->company->getMetadataByKey('nonexistent'))->toBeNull(); + // Test getKeyMetadata for individual values + expect($this->company->getKeyMetadata('theme'))->toBe('dark') + ->and($this->company->getKeyMetadata('views'))->toBe(100) + ->and($this->company->getKeyMetadata('settings'))->toBeArray() + ->and($this->company->getKeyMetadata('nonexistent'))->toBeNull(); $this->company->syncMetadata([]); expect($this->company->getMetadata())->toBeArray()->toBeEmpty(); @@ -130,13 +130,13 @@ ->and($attachedMetadata['sports'])->toBeArray()->not->toBeEmpty(); }); -// Test to ensure metadata can be added using addMetadataByKeys -it('can add multiple values by keys to metadata field using addMetadataByKeys', function () { +// Test to ensure metadata can be added using addKeysMetadata +it('can add multiple values by keys to metadata field using addKeysMetadata', function () { // Create initial metadata $this->company->createMetadata(['theme' => 'dark']); // Add multiple metadata keys - $status = $this->company->addMetadataByKeys([ + $status = $this->company->addKeysMetadata([ 'language' => 'English', 'is_visible' => true, ]); @@ -150,13 +150,13 @@ ->and($updatedMetadata['is_visible'])->toBeTrue(); }); -// Test to ensure metadata can be added using addMetadataByKey -it('can add value by one key to metadata field using addMetadataByKey', function () { +// Test to ensure metadata can be added using addKeyMetadata +it('can add value by one key to metadata field using addKeyMetadata', function () { // Create initial metadata $this->company->createMetadata(['theme' => 'dark']); // Add single metadata key - $status = $this->company->addMetadataByKey('language', 'French'); + $status = $this->company->addKeyMetadata('language', 'French'); expect($status)->toBeTrue(); // Verify the combined metadata @@ -166,7 +166,7 @@ ->and($updatedMetadata['language'])->toBe('French'); // Test adding another key - $status = $this->company->addMetadataByKey('is_visible', true); + $status = $this->company->addKeyMetadata('is_visible', true); expect($status)->toBeTrue(); // Verify all metadata @@ -198,13 +198,13 @@ ->and($updatedMetadata['language'])->toBeString()->toBe('French'); // Check language }); -// Test to ensure metadata can be updated using updateMetadataByKeys -it('can update values by multiple keys in metadata field using updateMetadataByKeys', function () { +// Test to ensure metadata can be updated using updateKeysMetadata +it('can update values by multiple keys in metadata field using updateKeysMetadata', function () { // Create initial metadata $this->company->createMetadata(['theme' => 'dark', 'language' => 'English', 'is_visible' => true]); // Update multiple metadata keys - $status = $this->company->updateMetadataByKeys(['theme' => 'light', 'language' => 'Arabic']); + $status = $this->company->updateKeysMetadata(['theme' => 'light', 'language' => 'Arabic']); expect($status)->toBeTrue(); // Verify the updated metadata @@ -215,7 +215,7 @@ ->and($updatedMetadata['is_visible'])->toBeTrue(); // Test updating with array - $status = $this->company->updateMetadataByKeys(['views' => 100, 'rating' => 4.5]); + $status = $this->company->updateKeysMetadata(['views' => 100, 'rating' => 4.5]); expect($status)->toBeTrue(); // Verify final metadata @@ -228,13 +228,13 @@ ->and($finalMetadata['rating'])->toBe(4.5); }); -// Test to ensure metadata can be updated using updateMetadataByKey -it('can update value by one key in metadata field using updateMetadataByKey', function () { +// Test to ensure metadata can be updated using updateKeyMetadata +it('can update value by one key in metadata field using updateKeyMetadata', function () { // Create initial metadata $this->company->createMetadata(['theme' => 'dark', 'language' => 'English']); // Update single metadata key - $status = $this->company->updateMetadataByKey('theme', 'light'); + $status = $this->company->updateKeyMetadata('theme', 'light'); expect($status)->toBeTrue(); // Verify the updated metadata @@ -244,7 +244,7 @@ ->and($updatedMetadata['language'])->toBe('English'); // Test updating with different type - $status = $this->company->updateMetadataByKey('language', 'Arabic'); + $status = $this->company->updateKeyMetadata('language', 'Arabic'); expect($status)->toBeTrue(); // Verify final metadata @@ -265,8 +265,8 @@ expect($this->company->getMetadata())->toBeArray()->toBeEmpty(); // Verify that metadata is now empty array }); -// Test to ensure metadata can be cleared using clearMetadata, clearMetadataByKeys and clearMetadataByKey -it('can clear content of metadata using clearMetadata, clearMetadataByKeys, clearMetadataByKey', function () { +// Test to ensure metadata can be cleared using forgetMetadata, forgetKeysMetadata and forgetKeysMetadata +it('can clear content of metadata using forgetMetadata, forgetKeysMetadata, forgetKeysMetadata', function () { // Create initial metadata $this->company->createMetadata([ 'theme' => 'dark', @@ -275,8 +275,8 @@ 'settings' => ['notifications' => true], ]); - // Test clearMetadataByKey - $status = $this->company->clearMetadataByKey('theme'); + // Test forgetKeysMetadata + $status = $this->company->forgetKeysMetadata('theme'); expect($status)->toBeTrue(); $metadata = $this->company->getMetadata(); @@ -285,8 +285,8 @@ ->and($metadata['language'])->toBe('English') ->and($metadata['views'])->toBe(100); - // Test clearMetadataByKeys with array - $status = $this->company->clearMetadataByKeys(['language', 'views']); + // Test forgetKeysMetadata with array + $status = $this->company->forgetKeysMetadata(['language', 'views']); expect($status)->toBeTrue(); $metadata = $this->company->getMetadata(); @@ -295,8 +295,8 @@ ->and($metadata)->not->toHaveKey('views') ->and($metadata['settings'])->toBeArray(); - // Test clearMetadata to empty all metadata - $status = $this->company->clearMetadata(); + // Test forgetMetadata to empty all metadata + $status = $this->company->forgetMetadata(); expect($status)->toBeTrue(); $metadata = $this->company->getMetadata(); @@ -317,36 +317,29 @@ expect($status)->toBeBool()->toBeTrue(); // Check if metadata is not filled - $this->company->clearMetadata(); + $this->company->forgetMetadata(); $status = $this->company->hasFilledMetadata(); expect($status)->toBeBool()->toBeFalse(); }); // Test to ensure specific metadata key existence can be checked -it('can check if specific metadata key exists using hasMetadataAllKeys, hasMetadataByKey, hasMetadataAnyKeys', function () { +it('can check if specific metadata key exists using hasAllKeysMetadata, hasKeyMetadata, hasAnyKeysMetadata', function () { // Create metadata for the company $this->company->createMetadata(['theme' => 'dark', 'views' => 100]); - // Using hasMetadataByKey - // Check existing keys - expect($this->company->hasMetadataByKey('theme'))->toBeTrue() - ->and($this->company->hasMetadataByKey('views'))->toBeTrue(); - // Check non-existing keys - expect($this->company->hasMetadataByKey('invalid_key'))->toBeFalse() - ->and($this->company->hasMetadataByKey(''))->toBeFalse() - ->and($this->company->hasMetadataByKey(null))->toBeFalse(); - - // Using hasMetadataAllKeys - // Check existing keys - expect($this->company->hasMetadataAllKeys(['theme', 'views']))->toBeTrue(); - // Check non-existing keys - expect($this->company->hasMetadataAllKeys(['theme', 'invalid_key']))->toBeFalse(); - - // Using hasMetadataAnyKeys - // Check existing keys - expect($this->company->hasMetadataAnyKeys(['theme', 'views']))->toBeTrue(); - // Check one existing key - expect($this->company->hasMetadataAnyKeys(['theme', 'invalid_key']))->toBeTrue(); - // Check non-existing keys - expect($this->company->hasMetadataAnyKeys(['invalid_key1', 'invalid_key2']))->toBeFalse(); + // Using hasKeyMetadata + expect($this->company->hasKeyMetadata('theme'))->toBeTrue() + ->and($this->company->hasKeyMetadata('views'))->toBeTrue(); // Check existing keys + expect($this->company->hasKeyMetadata('invalid_key'))->toBeFalse() + ->and($this->company->hasKeyMetadata(''))->toBeFalse() + ->and($this->company->hasKeyMetadata(null))->toBeFalse(); // Check non-existing keys + + // Using hasAllKeysMetadata + expect($this->company->hasAllKeysMetadata(['theme', 'views']))->toBeTrue(); // Check existing keys + expect($this->company->hasAllKeysMetadata(['theme', 'invalid_key']))->toBeFalse(); // Check non-existing keys + + // Using hasAnyKeysMetadata + expect($this->company->hasAnyKeysMetadata(['theme', 'views']))->toBeTrue(); // Check existing keys + expect($this->company->hasAnyKeysMetadata(['theme', 'invalid_key']))->toBeTrue(); // Check one existing key + expect($this->company->hasAnyKeysMetadata(['invalid_key1', 'invalid_key2']))->toBeFalse(); // Check non-existing keys }); From 1d111c12646447b169bebb9151c75f78fbaee4cc Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Thu, 23 Jan 2025 23:34:46 +0300 Subject: [PATCH 19/23] Add hasFilledMetadataById method to HasManyMetadata trait and corresponding tests - Introduced `hasFilledMetadataById` method to check if metadata exists and is not empty by ID, enhancing metadata validation capabilities. - Updated tests in `HasManyMetadataTest` to include checks for the new method, ensuring accurate validation of filled metadata status. - Improved overall test coverage for metadata management functionality. --- src/Traits/HasManyMetadata.php | 13 +++++++++++++ tests/Feature/HasManyMetadataTest.php | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/src/Traits/HasManyMetadata.php b/src/Traits/HasManyMetadata.php index 23ec53e..e516a43 100644 --- a/src/Traits/HasManyMetadata.php +++ b/src/Traits/HasManyMetadata.php @@ -255,6 +255,19 @@ public function getMetadata(): array return $this->getMetadataCollection()->toArray(); } + /** + * Check if metadata exists and is not empty by ID + */ + public function hasFilledMetadataById(string $id): bool + { + $oldIsWithId = $this->getMetadataNameIdEnabled(); + $this->setMetadataNameIdEnabled(false); + $status = $this->hasMetadataById($id) && filled($this->getMetadataById($id)); + $this->setMetadataNameIdEnabled($oldIsWithId); + + return $status; + } + /** * Query metadata by ID */ diff --git a/tests/Feature/HasManyMetadataTest.php b/tests/Feature/HasManyMetadataTest.php index dfbd1bd..b01f206 100644 --- a/tests/Feature/HasManyMetadataTest.php +++ b/tests/Feature/HasManyMetadataTest.php @@ -456,4 +456,9 @@ $metadata = $this->post->getMetadata(); expect($metadata)->toBeArray()->toHaveCount(1); expect($this->post->hasMetadataById($metadata[0]['id']))->toBeTrue(); + + // check if model has filled metadata using hasFilledMetadataById + expect($this->post->hasFilledMetadataById($metadata[0]['id']))->toBeTrue(); + $this->post->forgetMetadataById($metadata[0]['id']); + expect($this->post->hasFilledMetadataById($metadata[0]['id']))->toBeFalse(); }); From 67ed6d8a5b507ae14f13d5d5a7c1271df33bcdb4 Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Wed, 29 Jan 2025 21:07:16 +0300 Subject: [PATCH 20/23] Refactor HasManyMetadata trait to simplify metadata handling - Removed unnecessary metadata ID and name ID related logic in several methods - Simplified `getMetadataById` to return metadata without merging ID - Updated test cases to reflect changes in metadata retrieval and manipulation - Removed redundant method calls and simplified method implementations --- src/Traits/HasManyMetadata.php | 27 +++++---------------------- tests/Feature/HasManyMetadataTest.php | 17 ++++++++--------- 2 files changed, 13 insertions(+), 31 deletions(-) diff --git a/src/Traits/HasManyMetadata.php b/src/Traits/HasManyMetadata.php index e516a43..411e54f 100644 --- a/src/Traits/HasManyMetadata.php +++ b/src/Traits/HasManyMetadata.php @@ -72,9 +72,8 @@ public function addKeysMetadataById(string $id, array|Collection|string|int|null $metadata = $this->getMetadataById($id); $keys = $keys instanceof Collection ? $keys->toArray() : (! is_array($keys) ? [$keys => $value] : $keys); - $newMetadata = $helper->pipMetadataToClearKeyNameId(array_merge($metadata, $keys)); - return $this->updateMetadataById($id, $newMetadata); + return $this->updateMetadataById($id, array_merge($metadata, $keys)); } /** @@ -158,10 +157,7 @@ public function forgetKeysMetadataById(string $id, array|Collection|string|int|n return false; } - $oldIsWithId = $this->getMetadataNameIdEnabled(); - $this->setMetadataNameIdEnabled(false); $metadata = $this->getMetadataById($id); - $this->setMetadataNameIdEnabled($oldIsWithId); if (is_null($metadata)) { return false; } @@ -184,25 +180,17 @@ public function forgetKeyMetadataById(string $id, string|int|null $key = null): } /** - * Get a metadata array by ID + * Get a content of metadata as array by ID */ public function getMetadataById(string $id, array|Collection|string|int|null $keys = null): array { - $metadata = $this->getMetadataNameIdEnabled() ? - $this->metadata()->find($id)?->mergeIdToMetadata($this->getMetadataNameId())->metadata ?? [] : - $this->metadata()->find($id)?->metadata ?? []; + $metadata = $this->metadata()->find($id)?->metadata ?? []; if (app(Helper::class)->isNullOrStringEmptyOrWhitespaceOrEmptyArray($keys)) { return $metadata; } - $keys = Arr::wrap($keys); - - if ($this->getMetadataNameIdEnabled()) { - $keys = array_merge([$this->getMetadataNameId()], $keys); - } - - return Arr::only($metadata, $keys); + return Arr::only($metadata, Arr::wrap($keys)); } public function getKeyMetadataById(string $id, string|int $key): string|int|float|bool|array|null @@ -260,12 +248,7 @@ public function getMetadata(): array */ public function hasFilledMetadataById(string $id): bool { - $oldIsWithId = $this->getMetadataNameIdEnabled(); - $this->setMetadataNameIdEnabled(false); - $status = $this->hasMetadataById($id) && filled($this->getMetadataById($id)); - $this->setMetadataNameIdEnabled($oldIsWithId); - - return $status; + return $this->hasMetadataById($id) && filled($this->getMetadataById($id)); } /** diff --git a/tests/Feature/HasManyMetadataTest.php b/tests/Feature/HasManyMetadataTest.php index b01f206..98bc272 100644 --- a/tests/Feature/HasManyMetadataTest.php +++ b/tests/Feature/HasManyMetadataTest.php @@ -119,7 +119,7 @@ $retrievedMetadata = $this->post->getMetadataById($metadata->id, ['language', 'theme']); // Verify only requested keys were retrieved - expect($retrievedMetadata)->toBeArray()->toHaveCount(3) + expect($retrievedMetadata)->toBeArray()->toHaveCount(2) ->toMatchArray([ 'language' => 'English', 'theme' => 'light', @@ -127,7 +127,7 @@ // Test retrieving single key $singleKeyMetadata = $this->post->getMetadataById($metadata->id, 'views'); - expect($singleKeyMetadata)->toBeArray()->toHaveCount(2)->toMatchArray(['views' => 100]); + expect($singleKeyMetadata)->toBeArray()->toHaveCount(1)->toMatchArray(['views' => 100]); }); // Test adding multiple keys to metadata using addKeysMetadataById @@ -209,7 +209,7 @@ // Get updated metadata and verify changes $updatedMetadata = $this->post->getMetadataById($metadata->id); - expect($updatedMetadata)->toMatchArray(['id' => $metadata->id, 'theme' => 'light']); + expect($updatedMetadata)->toMatchArray(['theme' => 'light']); }); // Test updating multiple keys in metadata using updateKeysMetadataById @@ -341,7 +341,7 @@ // Test forgetMetadataById - sets metadata to null $status = $this->post->forgetMetadataById($metadata->id); expect($status)->toBeTrue(); - expect($this->post->getMetadataById($metadata->id))->toBeArray()->toHaveCount(1); + expect($this->post->getMetadataById($metadata->id))->toBeArray()->toBeEmpty(); // Create new metadata for testing keys $metadata = $this->post->createMetadata([ @@ -353,12 +353,12 @@ // Test forgetKeysMetadataById - removes specific keys $status = $this->post->forgetKeysMetadataById($metadata->id, ['theme', 'notifications']); expect($status)->toBeTrue(); - expect($this->post->getMetadataById($metadata->id))->toBeArray()->toHaveCount(2)->toMatchArray(['language' => 'Arabic']); + expect($this->post->getMetadataById($metadata->id))->toBeArray()->toHaveCount(1)->toMatchArray(['language' => 'Arabic']); // Test forgetKeyMetadataById - removes single key $status = $this->post->forgetKeyMetadataById($metadata->id, 'language'); expect($status)->toBeTrue(); - expect($this->post->getMetadataById($metadata->id))->toBeArray()->toHaveCount(1); + expect($this->post->getMetadataById($metadata->id))->toBeArray()->toBeEmpty(); expect($this->post->setMetadataNameIdEnabled(false)->getMetadataById($metadata->id))->toBeArray()->toBeEmpty(); }); @@ -387,12 +387,11 @@ // Retrieve metadata by ID and verify contents $retrievedMetadata = $this->post->getMetadataById($metadata->id); - expect($retrievedMetadata)->toBeArray()->toHaveCount(3) + expect($retrievedMetadata)->toBeArray()->toHaveCount(2) ->and($retrievedMetadata)->toMatchArray([ 'language' => 'Arabic', 'is_visible' => true, - ]) - ->and($retrievedMetadata['id'])->toBeString(); + ]); }); it('can get individual metadata value by ID and key using getKeyMetadataById', function () { From e520b300bf470cab063b66455e9b7a538e952bf0 Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Wed, 29 Jan 2025 23:25:11 +0300 Subject: [PATCH 21/23] Add nested metadata validation and improve metadata creation methods - Introduced `isNestedMetadata` method in Helper class to validate nested metadata structure - Updated `createManyMetadata` and `syncMetadata` methods to handle nested metadata validation - Added test cases to verify nested metadata handling, including edge cases with empty or invalid inputs - Improved type checking and input validation for metadata creation and synchronization --- src/Helpers/Helper.php | 8 +++++ src/Traits/HasManyMetadata.php | 12 ++++++- tests/Feature/HasManyMetadataTest.php | 50 +++++++++++++++++++++++++-- 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/Helpers/Helper.php b/src/Helpers/Helper.php index 09e7e0c..974d205 100644 --- a/src/Helpers/Helper.php +++ b/src/Helpers/Helper.php @@ -36,4 +36,12 @@ public function pipMetadataToClearKeyNameId(array|Collection $metadata, string $ return Arr::except($metadata, $keyNameId); } + + public function isNestedMetadata(array|Collection $metadata): bool + { + $metadata = $metadata instanceof Collection ? $metadata->toArray() : $metadata; + $firstItem = Arr::first($metadata); + + return filled($firstItem) && is_array($firstItem); + } } diff --git a/src/Traits/HasManyMetadata.php b/src/Traits/HasManyMetadata.php index 411e54f..a85d62b 100644 --- a/src/Traits/HasManyMetadata.php +++ b/src/Traits/HasManyMetadata.php @@ -52,8 +52,12 @@ public function createMetadata(array|Collection $metadata): Metadata /** * Create multiple metadata records for the model */ - public function createManyMetadata(array|Collection $metadatas): Collection + public function createManyMetadata(array|Collection $metadatas): Collection|false { + if (blank($metadatas) || ! app(Helper::class)->isNestedMetadata($metadatas)) { + return false; + } + $metadatas = is_array($metadatas) ? collect($metadatas) : $metadatas; return $metadatas->map(fn ($data) => $this->metadata()->create(['metadata' => $data])); @@ -115,6 +119,12 @@ public function updateKeyMetadataById(string $id, string|int|null $key, array|Co */ public function syncMetadata(array|Collection $metadata): bool { + if (! app(Helper::class)->isNestedMetadata($metadata) && filled($metadata)) { + return false; + } elseif (! app(Helper::class)->isNestedMetadata($metadata) && blank($metadata)) { + return $this->deleteMetadata() || true; + } + if ($this->deleteMetadata()) { return (bool) $this->createManyMetadata( app(Helper::class)->pipMetadataToClearKeyNameId($metadata, $this->getMetadataNameId()) diff --git a/tests/Feature/HasManyMetadataTest.php b/tests/Feature/HasManyMetadataTest.php index 98bc272..71be1e3 100644 --- a/tests/Feature/HasManyMetadataTest.php +++ b/tests/Feature/HasManyMetadataTest.php @@ -51,8 +51,6 @@ 'theme' => 'auto', ], ]); - - // Check that we got back a collection of Metadata instances expect($metadataRecords)->toBeCollection()->toHaveCount(3)->each->toBeInstanceOf(Metadata::class); // Verify the metadata values were stored correctly @@ -62,6 +60,35 @@ expect($storedMetadata[0])->toMatchArray(['language' => 'English', 'theme' => 'light']); expect($storedMetadata[1])->toMatchArray(['language' => 'French', 'theme' => 'dark']); expect($storedMetadata[2])->toMatchArray(['language' => 'Spanish', 'theme' => 'auto']); + + // testing as collection + $metadataRecords = $this->post->createManyMetadata(collect([ + collect([ + 'language' => 'English', + 'theme' => 'light', + ]), + collect([ + 'language' => 'French', + 'theme' => 'dark', + ]), + ])); + expect($metadataRecords)->toBeCollection()->toHaveCount(2)->each->toBeInstanceOf(Metadata::class); + + // Test to ensure nested metadata is not allowed + expect($this->post->createManyMetadata([ + 'language' => 'Arabic', + 'theme' => 'dark', + ]))->toBeFalse(); + expect($this->post->createManyMetadata(collect([ + 'language' => 'Arabic', + 'theme' => 'dark', + ])))->toBeFalse(); + + expect($this->post->createManyMetadata([]))->toBeFalse(); + expect($this->post->createManyMetadata([[]]))->toBeFalse(); + expect($this->post->createManyMetadata([null]))->toBeFalse(); + expect($this->post->createManyMetadata([collect([])]))->toBeFalse(); + expect($this->post->createManyMetadata([collect([''])]))->toBeFalse(); }); // Test metadata supports multiple data types (string, bool, null, int, float) @@ -294,6 +321,25 @@ 'language' => 'Spanish', 'theme' => 'auto', ]); + + // testing as collection + $status = $this->post->syncMetadata(collect([ + collect([ + 'language' => 'Spanish', + 'theme' => 'auto', + ]), + ])); + expect($status)->toBeTrue(); + + // Test to ensure syncMetadata handles empty arrays + expect($this->post->syncMetadata([]))->toBeTrue(); + expect($this->post->syncMetadata([[]]))->toBeFalse(); + expect($this->post->syncMetadata([null]))->toBeFalse(); + expect($this->post->syncMetadata(['']))->toBeFalse(); + expect($this->post->syncMetadata(collect([])))->toBeTrue(); + expect($this->post->syncMetadata([collect([])]))->toBeFalse(); + expect($this->post->syncMetadata(collect([collect([])])))->toBeFalse(); + expect($this->post->syncMetadata(collect([collect([''])])))->toBeFalse(); }); // Test to ensure post metadata can be deleted From 761613ebcc1ae08af7568b14b7edf069766de2e4 Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Wed, 29 Jan 2025 23:40:49 +0300 Subject: [PATCH 22/23] Update README.md with comprehensive documentation and usage examples - Revamped README structure with clearer sections and formatting - Updated requirements to support PHP 8.0 and Laravel 9.30.1+ - Simplified code examples for HasManyMetadata and HasOneMetadata traits - Added testing and contributor information - Linked to external documentation for more detailed guidance --- README.md | 79 ++++++++++++++++++++++++------------------------------- 1 file changed, 34 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index c087660..8b50497 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,17 @@ Laravel Model Metadata is a package designed to manage metadata with JSON support for multiple data types. It allows you to easily attach, manage, and query metadata on your Laravel models using the `HasManyMetadata` or `HasOneMetadata` traits. -## โœจ Requirements +# ๐Ÿ“š Documentation -- PHP 8.1 or higher -- Laravel 10.0 or higher +For detailed documentation, including usage examples and best practices, please refer to the [Documentation](https://waad-mawlood.gitbook.io/model-metadata). + +# โœจ Requirements + +- PHP 8.0 or higher +- Laravel framework 9.30.1 or higher - JSON extension enabled -## ๐Ÿ’ผ Installation +# ๐Ÿ’ผ Installation 1. Install the package using Composer: ```bash @@ -27,46 +31,34 @@ Laravel Model Metadata is a package designed to manage metadata with JSON suppor php artisan migrate ``` -## ๐ŸŽˆ Usage +# ๐ŸŽˆ Usage -### ๐Ÿ”ฅ HasManyMetadata Trait +## ๐Ÿ”ฅ HasManyMetadata Trait -This trait allows a model to have multiple metadata records. Add the trait to your model: +Add the HasManyMetadata trait to your model to enable multiple metadata records: ```php use Waad\Metadata\Traits\HasManyMetadata; class Post extends Model { - use HasManyMetadata; + use HasManyMetadata; // <--- Add this trait to your model } ``` -Available methods: +#### Some methods: -#### Creating Metadata ```php -// Create metadata with array -$post->createMetadata(['key' => 'value', 'another_key' => 'another_value']); - -// Create metadata with collection -$post->createMetadata(collect(['key' => 'value'])); -``` +// Create metadata with array or collection +$post->createMetadata(['key1' => 'value1', 'key2' => 'value2']); +$post->createMetadata(collect(['key1' => 'value1', 'key2' => 'value2'])); -#### Updating Metadata -```php // Update metadata by ID -$post->updateMetadata('metadata_id', ['new_key' => 'new_value']); -``` +$post->updateMetadata('{metadata_id}', ['new_key' => 'new_value']); -#### Deleting Metadata -```php // Delete metadata by ID -$post->deleteMetadata('metadata_id'); -``` +$post->deleteMetadata('{metadata_id}'); -#### Retrieving Metadata -```php // Get all metadata objects $metadata = $post->metadata; // or @@ -85,44 +77,36 @@ $metadataCollection = $post->getMetadataCollection(); $searchResults = $post->searchMetadata('search_term'); ``` -### ๐Ÿ”ฅ HasOneMetadata Trait +--------- + +## ๐Ÿ”ฅ HasOneMetadata Trait -This trait allows a model to have a single metadata record. Add the trait to your model: +Add the HasOneMetadata trait to your model to enable a single metadata record: ```php use Waad\Metadata\Traits\HasOneMetadata; class Company extends Model { - use HasOneMetadata; + use HasOneMetadata; // <--- Add this trait to your model } ``` -Available methods: +#### Some methods: -#### Creating Metadata ```php // Create metadata with array (only works if no metadata exists) $company->createMetadata(['key' => 'value', 'another_key' => 'another_value']); // Create metadata with collection $company->createMetadata(collect(['key' => 'value'])); -``` -#### Updating Metadata -```php // Update existing metadata $company->updateMetadata(['new_key' => 'new_value']); -``` -#### Deleting Metadata -```php // Delete the metadata $company->deleteMetadata(); -``` -#### Retrieving Metadata -```php // Get metadata as array $metadata = $company->getMetadata(); @@ -130,17 +114,22 @@ $metadata = $company->getMetadata(); $metadataCollection = $company->getMetadataCollection(); ``` -Both traits use JSON casting for the metadata column, allowing you to store complex data structures. The metadata is stored in a polymorphic relationship, making it flexible and reusable across different models. +------------- +# ๐Ÿงช Testing -## ๐Ÿ‘จโ€๐Ÿ’ป Contributors +To run the tests, use the following command: + +```bash +php artisan test +``` + +# ๐Ÿ‘จโ€๐Ÿ’ป Contributors - **Waad Mawlood** - Email: waad_mawlood@outlook.com - Role: Developer - - -## ๐Ÿ“ License +# ๐Ÿ“ License This package is open-sourced software licensed under the [MIT license](LICENSE). From 8bde0549a3fc63826df85d995236a627969933a5 Mon Sep 17 00:00:00 2001 From: Waad Mawlood Date: Wed, 29 Jan 2025 23:54:57 +0300 Subject: [PATCH 23/23] Update README.md with HasManyMetadata trait configuration and testing instructions - Added configuration options for HasManyMetadata trait, including `$metadataNameIdEnabled` and `$metadataNameId` - Updated testing section with development test command using `composer test` - Reordered sections for better readability and flow - Added link to external documentation for configuration details --- README.md | 84 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 8b50497..801fe24 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,43 @@ For detailed documentation, including usage examples and best practices, please # ๐ŸŽˆ Usage +## ๐Ÿ”ฅ HasOneMetadata Trait + +Add the HasOneMetadata trait to your model to enable a single metadata record: + +```php +use Waad\Metadata\Traits\HasOneMetadata; + +class Company extends Model +{ + use HasOneMetadata; // <--- Add this trait to your model +} +``` + +#### Some methods: + +```php +// Create metadata with array (only works if no metadata exists) +$company->createMetadata(['key' => 'value', 'another_key' => 'another_value']); + +// Create metadata with collection +$company->createMetadata(collect(['key' => 'value'])); + +// Update existing metadata +$company->updateMetadata(['new_key' => 'new_value']); + +// Delete the metadata +$company->deleteMetadata(); + +// Get metadata as array +$metadata = $company->getMetadata(); + +// Get metadata as collection +$metadataCollection = $company->getMetadataCollection(); +``` + +------------- + ## ๐Ÿ”ฅ HasManyMetadata Trait Add the HasManyMetadata trait to your model to enable multiple metadata records: @@ -43,8 +80,15 @@ use Waad\Metadata\Traits\HasManyMetadata; class Post extends Model { use HasManyMetadata; // <--- Add this trait to your model + + // Enabled Append id with content metadata (default) + public $metadataNameIdEnabled = true; + + // Custom Append key of id with metadata (default) + public $metadataNameId = 'id'; } ``` +see [Configuration Append Id](https://waad-mawlood.gitbook.io/model-metadata/basics/markdown-1/use-in-model) for more details #### Some methods: @@ -77,51 +121,15 @@ $metadataCollection = $post->getMetadataCollection(); $searchResults = $post->searchMetadata('search_term'); ``` ---------- - -## ๐Ÿ”ฅ HasOneMetadata Trait - -Add the HasOneMetadata trait to your model to enable a single metadata record: - -```php -use Waad\Metadata\Traits\HasOneMetadata; - -class Company extends Model -{ - use HasOneMetadata; // <--- Add this trait to your model -} -``` - -#### Some methods: - -```php -// Create metadata with array (only works if no metadata exists) -$company->createMetadata(['key' => 'value', 'another_key' => 'another_value']); - -// Create metadata with collection -$company->createMetadata(collect(['key' => 'value'])); - -// Update existing metadata -$company->updateMetadata(['new_key' => 'new_value']); - -// Delete the metadata -$company->deleteMetadata(); - -// Get metadata as array -$metadata = $company->getMetadata(); - -// Get metadata as collection -$metadataCollection = $company->getMetadataCollection(); -``` ------------- # ๐Ÿงช Testing -To run the tests, use the following command: +To run the tests for development, use the following command: ```bash -php artisan test +composer test ``` # ๐Ÿ‘จโ€๐Ÿ’ป Contributors