Skip to content

Commit

Permalink
Merge pull request #89 from abbasudo/hotfix/resolve
Browse files Browse the repository at this point in the history
Hotfix Resolve
  • Loading branch information
abbasudo authored Oct 13, 2024
2 parents 7158cb6 + 5afa55a commit 5ad2361
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 29 deletions.
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);
}
}

0 comments on commit 5ad2361

Please sign in to comment.