From 02996a20436cfdc229cf2f210fe6145d234c3040 Mon Sep 17 00:00:00 2001 From: kettasoft Date: Mon, 7 Sep 2026 07:58:59 +0300 Subject: [PATCH 1/3] refactor: rename model filtering trait --- src/Traits/HasFilterable.php | 46 ++-------------- src/Traits/InteractsWithFilterable.php | 54 +++++++++++++++++++ tests/Models/Post.php | 4 +- tests/Models/User.php | 4 +- .../FilterableModelAutoBindingTest.php | 15 +++++- 5 files changed, 74 insertions(+), 49 deletions(-) create mode 100644 src/Traits/InteractsWithFilterable.php diff --git a/src/Traits/HasFilterable.php b/src/Traits/HasFilterable.php index 92660263..3c4cbf15 100644 --- a/src/Traits/HasFilterable.php +++ b/src/Traits/HasFilterable.php @@ -2,53 +2,13 @@ namespace Kettasoft\Filterable\Traits; -use Kettasoft\Filterable\Filterable; -use Illuminate\Contracts\Database\Eloquent\Builder; -use Kettasoft\Filterable\Support\FilterResolver; -use Kettasoft\Filterable\Exceptions\FilterClassNotResolvedException; -use Kettasoft\Filterable\Foundation\Contracts\QueryBuilderInterface; - /** - * Apply filters dynamically to Eloquent Query. - * - * This is not a typical Laravel Global Scope. + * @deprecated Use {@see InteractsWithFilterable} instead. * - * @method static \Kettasoft\Filterable\Foundation\Invoker|\Illuminate\Contracts\Database\Eloquent\Builder filter(\Kettasoft\Filterable\Filterable|string|null $filter = null) + * @method static \Kettasoft\Filterable\Foundation\Invoker|\Illuminate\Contracts\Database\Eloquent\Builder filter(\Kettasoft\Filterable\Filterable|string|array|null $filter = null) * @mixin \Illuminate\Database\Eloquent\Model */ trait HasFilterable { - /** - * Apply all relevant thread filters. - * @param \Illuminate\Contracts\Database\Eloquent\Builder $query - * @param \Kettasoft\Filterable\Filterable|string|null $filter - * @return \Illuminate\Contracts\Database\Eloquent\Builder - */ - public function scopeFilter(Builder $query, Filterable|string|array|null $filter = null): QueryBuilderInterface - { - return (new FilterResolver($query, $filter))->resolve(); - } - - /** - * Get defined filterable class from model. - * @throws \Kettasoft\Filterable\Exceptions\FilterClassNotResolvedException - */ - public function getFilterable() - { - if (! property_exists($this, 'filterable')) { - throw new FilterClassNotResolvedException(get_class($this)); - } - - return $this->filterable; - } - - /** - * Get the number of models to return per page. - * - * @return int - */ - public function getPerPage() - { - return config('filterable.paginate_limit') ?? request('perPage', parent::getPerPage()); - } + use InteractsWithFilterable; } diff --git a/src/Traits/InteractsWithFilterable.php b/src/Traits/InteractsWithFilterable.php new file mode 100644 index 00000000..547f26ff --- /dev/null +++ b/src/Traits/InteractsWithFilterable.php @@ -0,0 +1,54 @@ +resolve(); + } + + /** + * Get defined filterable class from model. + * @throws \Kettasoft\Filterable\Exceptions\FilterClassNotResolvedException + */ + public function getFilterable() + { + if (! property_exists($this, 'filterable')) { + throw new FilterClassNotResolvedException(get_class($this)); + } + + return $this->filterable; + } + + /** + * Get the number of models to return per page. + * + * @return int + */ + public function getPerPage() + { + return config('filterable.paginate_limit') ?? request('perPage', parent::getPerPage()); + } +} diff --git a/tests/Models/Post.php b/tests/Models/Post.php index 9578a798..5ba2a164 100644 --- a/tests/Models/Post.php +++ b/tests/Models/Post.php @@ -4,7 +4,7 @@ use Illuminate\Database\Eloquent\Model; use Kettasoft\Filterable\Tests\Models\Tag; -use Kettasoft\Filterable\Traits\HasFilterable; +use Kettasoft\Filterable\Traits\InteractsWithFilterable; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -12,7 +12,7 @@ class Post extends Model { - use HasFactory, HasFilterable; + use HasFactory, InteractsWithFilterable; protected $fillable = ['title', 'status', 'content', 'views', 'is_featured', 'description', 'tags']; diff --git a/tests/Models/User.php b/tests/Models/User.php index b41fcd36..c1bcf2ee 100644 --- a/tests/Models/User.php +++ b/tests/Models/User.php @@ -3,14 +3,14 @@ namespace Kettasoft\Filterable\Tests\Models; use Illuminate\Database\Eloquent\Model; -use Kettasoft\Filterable\Traits\HasFilterable; +use Kettasoft\Filterable\Traits\InteractsWithFilterable; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Factories\HasFactory; use Kettasoft\Filterable\Tests\Database\Factories\UserFactory; class User extends Model { - use HasFactory, HasFilterable; + use HasFactory, InteractsWithFilterable; protected $fillable = ['name', 'email', 'is_blocked', 'platform', 'password']; diff --git a/tests/Unit/Filterable/FilterableModelAutoBindingTest.php b/tests/Unit/Filterable/FilterableModelAutoBindingTest.php index a4baeb00..25282e56 100644 --- a/tests/Unit/Filterable/FilterableModelAutoBindingTest.php +++ b/tests/Unit/Filterable/FilterableModelAutoBindingTest.php @@ -5,6 +5,7 @@ use Illuminate\Database\Eloquent\Model; use Kettasoft\Filterable\Tests\TestCase; use Kettasoft\Filterable\Traits\HasFilterable; +use Kettasoft\Filterable\Traits\InteractsWithFilterable; use Illuminate\Contracts\Database\Query\Builder; use Kettasoft\Filterable\Tests\Http\Filters\PostFilter; use Kettasoft\Filterable\Exceptions\FilterClassNotResolvedException; @@ -14,7 +15,7 @@ class FilterableModelAutoBindingTest extends TestCase public function test_it_applies_filter_automatically_from_model_property() { $model = new class extends Model { - use HasFilterable; + use InteractsWithFilterable; protected $filterable = PostFilter::class; }; @@ -24,11 +25,21 @@ public function test_it_applies_filter_automatically_from_model_property() public function test_it_throws_exception_if_no_filter_class_and_no_model_property() { $model = new class extends Model { - use HasFilterable; + use InteractsWithFilterable; }; $this->expectException(FilterClassNotResolvedException::class); $model->filter(); } + + public function test_legacy_trait_remains_compatible() + { + $model = new class extends Model { + use HasFilterable; + protected $filterable = PostFilter::class; + }; + + $this->assertInstanceOf(Builder::class, $model->filter()); + } } From 89d8b0c4e99e0b78d577f0618ef5e57d3068442f Mon Sep 17 00:00:00 2001 From: kettasoft Date: Mon, 7 Sep 2026 08:00:08 +0300 Subject: [PATCH 2/3] docs: adopt InteractsWithFilterable trait --- README.md | 2 +- docs/features/auto-binding.md | 4 ++-- docs/features/auto-register-filterable-macro.md | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 43c07e63..b93fe498 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ $posts = Post::filter(PostFilter::class)->paginate(); ```php class Post extends Model { - use HasFilterable; + use InteractsWithFilterable; protected $filterable = PostFilter::class; } diff --git a/docs/features/auto-binding.md b/docs/features/auto-binding.md index 9d46935f..f9dffa00 100644 --- a/docs/features/auto-binding.md +++ b/docs/features/auto-binding.md @@ -12,11 +12,11 @@ Inside your Eloquent model, define a `$filterable` property and assign the class namespace App\Models; use Illuminate\Database\Eloquent\Model; -use Kettasoft\Filterable\Traits\HasFilterable; +use Kettasoft\Filterable\Traits\InteractsWithFilterable; class User extends Model { - use HasFilterable; + use InteractsWithFilterable; protected $filterable = \App\Http\Filters\UserFilter::class; } ``` diff --git a/docs/features/auto-register-filterable-macro.md b/docs/features/auto-register-filterable-macro.md index ccff98db..8a558c8a 100644 --- a/docs/features/auto-register-filterable-macro.md +++ b/docs/features/auto-register-filterable-macro.md @@ -2,7 +2,7 @@ ## Overview -By default, to use the **`filter()`** macro on Eloquent models, you must use the HasFilterable trait in each model. However, if you prefer to automatically register the **`filter()`** method on all Eloquent builders without modifying individual models, you can use the **`AutoRegisterFilterableServiceProvider`**. +By default, to use the **`filter()`** macro on Eloquent models, you must use the InteractsWithFilterable trait in each model. However, if you prefer to automatically register the **`filter()`** method on all Eloquent builders without modifying individual models, you can use the **`AutoRegisterFilterableServiceProvider`**. ### ✅ How to Use @@ -17,12 +17,12 @@ By default, to use the **`filter()`** macro on Eloquent models, you must use the ``` 2. Remove the Trait (Optional) - You can now remove the HasFilterable trait from your Eloquent models: + You can now remove the InteractsWithFilterable trait from your Eloquent models: ```php class User extends Model { - // No need for HasFilterable trait + // No need for InteractsWithFilterable trait } ``` @@ -48,4 +48,4 @@ $users = User::filter()->get(); - ✅ You want zero setup per model. - ✅ You prefer centralized control over all query filtering. -- ❌ You want explicit opt-in per model using HasFilterable. +- ❌ You want explicit opt-in per model using InteractsWithFilterable. From 227e8379357dc8a2ffdea53e629e19f139120fc9 Mon Sep 17 00:00:00 2001 From: kettasoft Date: Mon, 7 Sep 2026 08:01:34 +0300 Subject: [PATCH 3/3] docs: clarify filterable trait migration --- README.md | 4 ++++ docs/features/auto-binding.md | 2 ++ 2 files changed, 6 insertions(+) diff --git a/README.md b/README.md index b93fe498..63610e0b 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,10 @@ $posts = Post::filter(PostFilter::class)->paginate(); **4. Or bind the filter directly to the model** ```php +use App\Http\Filters\PostFilter; +use Illuminate\Database\Eloquent\Model; +use Kettasoft\Filterable\Traits\InteractsWithFilterable; + class Post extends Model { use InteractsWithFilterable; diff --git a/docs/features/auto-binding.md b/docs/features/auto-binding.md index f9dffa00..714b6e9a 100644 --- a/docs/features/auto-binding.md +++ b/docs/features/auto-binding.md @@ -29,6 +29,8 @@ $users = User::filter()->get(); The package will automatically resolve and use the UserFilter class defined in the model. +> **Upgrading:** The previous `HasFilterable` trait remains available as a deprecated compatibility wrapper. Existing models will continue to work, but new code should use `InteractsWithFilterable`. + ## 🧠 Notes - If no $filterable is set on the model, you will still need to pass the filter class manually to filter().