Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hotfix Resolve #89

Merged
merged 6 commits into from
Oct 13, 2024
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
49 changes: 28 additions & 21 deletions src/Filters/Resolve.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public function __construct(FilterList $filterList, Model $model)
* @param array|string $values
*
* @throws Exception
* @throws Exception
*
* @return void
*/
Expand All @@ -64,6 +65,7 @@ public function apply(Builder $query, string $field, array|string $values): void
* @param Closure $closure
*
* @throws Exception
* @throws Exception
*
* @return bool
*/
Expand All @@ -76,9 +78,9 @@ private function safe(Closure $closure): bool
} catch (Exception $exception) {
if (config('purity.silent')) {
return false;
} else {
throw $exception;
}

throw $exception;
}
}

Expand All @@ -89,13 +91,11 @@ private function safe(Closure $closure): bool
*/
private function validate(array|string $values = [])
{
if (empty($values) or is_string($values)) {
if (empty($values) || is_string($values)) {
throw NoOperatorMatch::create($this->filterList->keys());
}

if (in_array(key($values), $this->filterList->keys())) {
return;
} else {
if (!in_array(key($values), $this->filterList->keys())) {
$this->validate(array_values($values)[0]);
}
}
Expand All @@ -108,15 +108,14 @@ private function validate(array|string $values = [])
* @param array|string|null $filters
*
* @throws Exception
* @throws Exception
*
* @return void
*/
private function filter(Builder $query, string $field, array|string|null $filters): void
{
// Ensure that the filter is an array
if (!is_array($filters)) {
$filters = [$filters];
}
$filters = is_array($filters) ? $filters : [$filters];

// Resolve the filter using the appropriate strategy
if ($this->filterList->get($field) !== null) {
Expand Down Expand Up @@ -186,11 +185,9 @@ private function applyRelations(Builder $query, Closure $callback): void
*/
private function relation(Builder $query, Closure $callback)
{
// remove last field until its empty
// remove the last field until its empty
$field = array_shift($this->fields);
$query->whereHas($field, function ($subQuery) use ($callback) {
$this->applyRelations($subQuery, $callback);
});
$query->whereHas($field, fn ($subQuery) => $this->applyRelations($subQuery, $callback));
}

/**
Expand All @@ -205,21 +202,31 @@ private function relation(Builder $query, Closure $callback)
private function applyRelationFilter(Builder $query, string $field, array $filters): void
{
foreach ($filters as $subField => $subFilter) {
$relation = end($this->fields);
if ($relation !== false) {
array_push($this->previousModels, $this->model);
$this->model = $this->model->$relation()->getRelated();
}
$this->prepareModelForRelation($field);
$this->validateField($field);
$this->validateOperator($field, $subField);

$this->fields[] = $this->model->getField($field);
$this->filter($query, $subField, $subFilter);
}
$this->restorePreviousModel();
}

private function prepareModelForRelation(string $field): void
{
$relation = end($this->fields);
if ($relation !== false) {
$this->previousModels[] = $this->model;

$this->model = $this->model->$relation()->getRelated();
}
}

private function restorePreviousModel(): void
{
array_pop($this->fields);
if (count($this->previousModels)) {
$this->model = end($this->previousModels);
array_pop($this->previousModels);
if (!empty($this->previousModels)) {
$this->model = array_pop($this->previousModels);
}
}

Expand Down
15 changes: 9 additions & 6 deletions src/Traits/Filterable.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
use ReflectionClass;

/**
* List of available filters, can be set on the model otherwise it will be read from config.
* The List of available filters can be set on the model otherwise it will be read from config.
*
* @property array $filters
*
* List of available fields, if not declared will accept every thing.
* List of available fields, if not declared, will accept everything.
* @property array $filterFields
*
* Fields will restrict to defined filters.
Expand Down Expand Up @@ -64,7 +64,7 @@ public function scopeFilter(Builder $query, array|null $params = null): Builder
// Apply each filter to the query builder instance

foreach ($params as $field => $value) {
app($this->getFilterResolver())->apply($query, $field, $value);
app(Resolve::class)->apply($query, $field, $value);
}

return $query;
Expand All @@ -81,7 +81,11 @@ private function bootFilter(): void
return (new FilterList())->only($this->getFilters());
});

app()->when($this->getFilterResolver())->needs(Model::class)->give(fn () => $this);
app()->bind(Resolve::class, function () {
$resolver = $this->getFilterResolver();

return new $resolver(app(FilterList::class), $this);
});
}

/**
Expand Down Expand Up @@ -210,8 +214,7 @@ public function getRestrictedFilters(): array
foreach ($this->restrictedFilters ?? $this->filterFields ?? [] as $key => $value) {
if (is_int($key) && Str::contains($value, ':')) {
$tKey = str($value)->before(':')->squish()->toString();
$tValue = str($value)->after(':')->squish()->explode(',')->all();
$restrictedFilters[$tKey] = $tValue;
$restrictedFilters[$tKey] = str($value)->after(':')->squish()->explode(',')->all();
}
if (is_string($key)) {
$restrictedFilters[$key] = Arr::wrap($value);
Expand Down
3 changes: 3 additions & 0 deletions tests/App/Migrations/0001_01_01_000000_create_test_tables.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use Abbasudo\Purity\Tests\App\Models\Author;
use Abbasudo\Purity\Tests\App\Models\Post;
use Abbasudo\Purity\Tests\App\Models\Product;
use Abbasudo\Purity\Tests\App\Models\Tag;
use Abbasudo\Purity\Tests\App\Models\User;
use Illuminate\Database\Migrations\Migration;
Expand Down Expand Up @@ -31,6 +32,7 @@ public function up(): void
$table->id();
$table->foreignIdFor(User::class)->nullable();
$table->string('title')->nullable();
$table->nullableMorphs('postable');
$table->timestamps();
});

Expand Down Expand Up @@ -60,6 +62,7 @@ public function up(): void
$table->foreignIdFor(Author::class)->nullable();
$table->string('name');
$table->string('description');
$table->foreignIdFor(Product::class)->nullable();
$table->timestamps();
});

Expand Down
6 changes: 6 additions & 0 deletions tests/App/Models/Book.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Abbasudo\Purity\Traits\Sortable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphMany;

class Book extends Model
{
Expand All @@ -26,4 +27,9 @@ public function author(): BelongsTo
{
return $this->belongsTo(Author::class);
}

public function posts(): MorphMany
{
return $this->morphMany(Post::class, 'postable');
}
}
7 changes: 7 additions & 0 deletions tests/App/Models/Post.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;

class Post extends Model
{
Expand All @@ -18,6 +19,7 @@ class Post extends Model

protected $fillable = [
'title',
'user_id',
];

public function comments(): HasMany
Expand All @@ -34,4 +36,9 @@ public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class);
}

public function postable(): MorphTo
{
return $this->morphTo();
}
}
12 changes: 12 additions & 0 deletions tests/App/Models/Product.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use Abbasudo\Purity\Traits\Sortable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;

class Product extends Model
{
Expand All @@ -26,4 +28,14 @@ protected static function newFactory()
'description',
'is_available',
];

public function posts(): MorphMany
{
return $this->morphMany(Post::class, 'postable');
}

public function book(): HasOne
{
return $this->hasOne(Book::class);
}
}
2 changes: 1 addition & 1 deletion tests/Feature/FilterableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ public function it_can_filter_with_and_operator(): void
]);

$response = $this->getJson(
'/posts?filters[$and][0][name][$eq]=laravel purity is the best&filters[$and][1][description][$eq]=laravel purity is the best'
'/products?filters[$and][0][name][$eq]=laravel purity&filters[$and][1][description][$eq]=laravel purity is the best'
)
->assertOk()
->assertJsonCount(1);
Expand Down
97 changes: 97 additions & 0 deletions tests/Feature/RelationFilterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

use Abbasudo\Purity\Tests\App\Models\Post;
use Abbasudo\Purity\Tests\App\Models\Product;
use Abbasudo\Purity\Tests\App\Models\User;
use Abbasudo\Purity\Tests\TestCase;
use Illuminate\Support\Facades\Route;

class RelationFilterTest extends TestCase
{
public function setUp(): void
{
parent::setUp();

Route::get('/posts', function () {
return Post::filter()->get();
});

Route::get('/products', function () {
return Product::filter()->get();
});

Post::create([
'title' => 'laravel purity is the best',
]);
}

/** @test */
public function it_can_filter_by_has_many_relation(): void
{
$post = Post::first();

$post->comments()->create([
'content' => 'first comment',
]);

$post->comments()->create([
'content' => 'second comment',
]);

$response = $this->getJson('/posts?filters[comments][content][$eq]=first comment');

$response->assertOk();
$response->assertJsonCount(1);
}

/** @test */
public function it_can_filter_by_belongs_to_relation(): void
{
$user = User::create([
'name' => 'Test',
]);

$post = Post::create([
'title' => 'laravel purity is the best',
'user_id' => $user->id,
]);

$response = $this->getJson('/posts?filters[user][name][$eq]=Test');

$response->assertOk();
$response->assertJsonCount(1);
}

/** @test */
public function it_can_filter_by_belongs_to_many_relation(): void
{
$post = Post::first();

$post->tags()->create([
'name' => 'Laravel',
]);

$response = $this->getJson('/posts?filters[tags][name][$eq]=Laravel');

$response->assertOk();
$response->assertJsonCount(1);
}

/** @test */
public function it_can_filter_by_has_one_relation(): void
{
$product = Product::factory([
'name' => 'Laravel Purity',
])->create();

$product->book()->create([
'name' => 'book',
'description' => 'book for product',
]);

$response = $this->getJson('/products?filters[book][name][$eq]=book');

$response->assertOk();
$response->assertJsonCount(1);
}
}
2 changes: 1 addition & 1 deletion tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ protected function getPackageProviders($app): array

protected function getEnvironmentSetUp($app)
{
//
$app['config']->set('purity.silent', false);
}
}
Loading