Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,13 @@ $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 HasFilterable;
use InteractsWithFilterable;

protected $filterable = PostFilter::class;
}
Expand Down
6 changes: 4 additions & 2 deletions docs/features/auto-binding.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
```
Expand All @@ -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().
Expand Down
8 changes: 4 additions & 4 deletions docs/features/auto-register-filterable-macro.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
}
```

Expand All @@ -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.
46 changes: 3 additions & 43 deletions src/Traits/HasFilterable.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
54 changes: 54 additions & 0 deletions src/Traits/InteractsWithFilterable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

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.
*
* @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 InteractsWithFilterable
{
/**
* Apply all relevant thread filters.
* @param \Illuminate\Contracts\Database\Eloquent\Builder $query
* @param \Kettasoft\Filterable\Filterable|string|array|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());
}
}
4 changes: 2 additions & 2 deletions tests/Models/Post.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

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;
use Kettasoft\Filterable\Tests\Database\Factories\PostFactory;

class Post extends Model
{
use HasFactory, HasFilterable;
use HasFactory, InteractsWithFilterable;

protected $fillable = ['title', 'status', 'content', 'views', 'is_featured', 'description', 'tags'];

Expand Down
4 changes: 2 additions & 2 deletions tests/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'];

Expand Down
15 changes: 13 additions & 2 deletions tests/Unit/Filterable/FilterableModelAutoBindingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
};

Expand All @@ -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());
}
}
Loading