From c78ed0b78baf5d2e1b7444a9882ba039c70a3887 Mon Sep 17 00:00:00 2001 From: Adri Date: Fri, 13 Dec 2024 15:04:25 +0000 Subject: [PATCH] feat(laravel): boolean filter (#6806) * feat: Adding boolean filter * refactor: return early if wrong values * tests: boolean filter test * refactor(BooleanFilter): cs fixer * add test --------- Co-authored-by: adriafigueres Co-authored-by: soyuka --- src/Laravel/ApiPlatformProvider.php | 3 +- .../Extension/FilterQueryExtension.php | 2 +- src/Laravel/Eloquent/Filter/BooleanFilter.php | 49 +++++++++++++++++++ ...ationResourceMetadataCollectionFactory.php | 4 ++ .../Eloquent/Filter/BooleanFilterTest.php | 29 +++++++++++ src/Laravel/Tests/EloquentTest.php | 11 +++++ src/Laravel/workbench/app/Models/Book.php | 4 +- .../database/factories/BookFactory.php | 1 + .../2023_07_15_231244_create_book_table.php | 1 + 9 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 src/Laravel/Eloquent/Filter/BooleanFilter.php create mode 100644 src/Laravel/Tests/Eloquent/Filter/BooleanFilterTest.php diff --git a/src/Laravel/ApiPlatformProvider.php b/src/Laravel/ApiPlatformProvider.php index 85add4c5e8f..e90d2687d2d 100644 --- a/src/Laravel/ApiPlatformProvider.php +++ b/src/Laravel/ApiPlatformProvider.php @@ -81,6 +81,7 @@ use ApiPlatform\Laravel\Controller\EntrypointController; use ApiPlatform\Laravel\Eloquent\Extension\FilterQueryExtension; use ApiPlatform\Laravel\Eloquent\Extension\QueryExtensionInterface; +use ApiPlatform\Laravel\Eloquent\Filter\BooleanFilter; use ApiPlatform\Laravel\Eloquent\Filter\DateFilter; use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter; use ApiPlatform\Laravel\Eloquent\Filter\FilterInterface as EloquentFilterInterface; @@ -422,7 +423,7 @@ public function register(): void $this->app->bind(OperationMetadataFactoryInterface::class, OperationMetadataFactory::class); - $this->app->tag([EqualsFilter::class, PartialSearchFilter::class, DateFilter::class, OrderFilter::class, RangeFilter::class], EloquentFilterInterface::class); + $this->app->tag([BooleanFilter::class, EqualsFilter::class, PartialSearchFilter::class, DateFilter::class, OrderFilter::class, RangeFilter::class], EloquentFilterInterface::class); $this->app->bind(FilterQueryExtension::class, function (Application $app) { $tagged = iterator_to_array($app->tagged(EloquentFilterInterface::class)); diff --git a/src/Laravel/Eloquent/Extension/FilterQueryExtension.php b/src/Laravel/Eloquent/Extension/FilterQueryExtension.php index a02effd5b71..20a4d7200b5 100644 --- a/src/Laravel/Eloquent/Extension/FilterQueryExtension.php +++ b/src/Laravel/Eloquent/Extension/FilterQueryExtension.php @@ -40,7 +40,7 @@ public function apply(Builder $builder, array $uriVariables, Operation $operatio $context['operation'] = $operation; foreach ($operation->getParameters() ?? [] as $parameter) { - if (!($values = $parameter->getValue()) || $values instanceof ParameterNotFound) { + if (null === ($values = $parameter->getValue()) || $values instanceof ParameterNotFound) { continue; } diff --git a/src/Laravel/Eloquent/Filter/BooleanFilter.php b/src/Laravel/Eloquent/Filter/BooleanFilter.php new file mode 100644 index 00000000000..9879c2212f6 --- /dev/null +++ b/src/Laravel/Eloquent/Filter/BooleanFilter.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Laravel\Eloquent\Filter; + +use ApiPlatform\Metadata\JsonSchemaFilterInterface; +use ApiPlatform\Metadata\Parameter; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; + +final class BooleanFilter implements FilterInterface, JsonSchemaFilterInterface +{ + use QueryPropertyTrait; + + private const BOOLEAN_VALUES = [ + 'true' => true, + 'false' => false, + '1' => true, + '0' => false, + ]; + + /** + * @param Builder $builder + * @param array $context + */ + public function apply(Builder $builder, mixed $values, Parameter $parameter, array $context = []): Builder + { + if (!\is_string($values) || !\array_key_exists($values, self::BOOLEAN_VALUES)) { + return $builder; + } + + return $builder->{$context['whereClause'] ?? 'where'}($this->getQueryProperty($parameter), $values); + } + + public function getSchema(Parameter $parameter): array + { + return ['type' => 'boolean']; + } +} diff --git a/src/Laravel/Metadata/ParameterValidationResourceMetadataCollectionFactory.php b/src/Laravel/Metadata/ParameterValidationResourceMetadataCollectionFactory.php index a92fd20535d..e3741201582 100644 --- a/src/Laravel/Metadata/ParameterValidationResourceMetadataCollectionFactory.php +++ b/src/Laravel/Metadata/ParameterValidationResourceMetadataCollectionFactory.php @@ -144,6 +144,10 @@ private function addSchemaValidation(Parameter $parameter): Parameter $assertions[] = 'array'; } + if (isset($schema['type']) && 'boolean' === $schema['type']) { + $assertions[] = 'boolean'; + } + if (!$assertions) { return $parameter; } diff --git a/src/Laravel/Tests/Eloquent/Filter/BooleanFilterTest.php b/src/Laravel/Tests/Eloquent/Filter/BooleanFilterTest.php new file mode 100644 index 00000000000..b3f837c85b9 --- /dev/null +++ b/src/Laravel/Tests/Eloquent/Filter/BooleanFilterTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Laravel\Tests\Eloquent\Filter; + +use ApiPlatform\Laravel\Eloquent\Filter\BooleanFilter; +use ApiPlatform\Metadata\QueryParameter; +use Illuminate\Database\Eloquent\Builder; +use PHPUnit\Framework\TestCase; + +final class BooleanFilterTest extends TestCase +{ + public function testOperator(): void + { + $f = new BooleanFilter(); + $builder = $this->createStub(Builder::class); + $this->assertEquals($builder, $f->apply($builder, ['is_active' => 'true'], new QueryParameter(key: 'isActive', property: 'is_active'))); + } +} diff --git a/src/Laravel/Tests/EloquentTest.php b/src/Laravel/Tests/EloquentTest.php index ad847eeced9..27c2ebde415 100644 --- a/src/Laravel/Tests/EloquentTest.php +++ b/src/Laravel/Tests/EloquentTest.php @@ -406,4 +406,15 @@ public function testWithAccessor(): void $res = $this->get('/api/with_accessors/1', ['Accept' => ['application/ld+json']]); $this->assertArraySubset(['name' => 'test'], $res->json()); } + + public function testBooleanFilter(): void + { + BookFactory::new()->has(AuthorFactory::new())->count(10)->create(); + $res = $this->get('/api/books?published=notabool', ['Accept' => ['application/ld+json']]); + $this->assertEquals($res->getStatusCode(), 422); + + $res = $this->get('/api/books?published=0', ['Accept' => ['application/ld+json']]); + $this->assertEquals($res->getStatusCode(), 200); + $this->assertEquals($res->json()['totalItems'], 0); + } } diff --git a/src/Laravel/workbench/app/Models/Book.php b/src/Laravel/workbench/app/Models/Book.php index 421d21bd7ff..42aa1b0473b 100644 --- a/src/Laravel/workbench/app/Models/Book.php +++ b/src/Laravel/workbench/app/Models/Book.php @@ -13,6 +13,7 @@ namespace Workbench\App\Models; +use ApiPlatform\Laravel\Eloquent\Filter\BooleanFilter; use ApiPlatform\Laravel\Eloquent\Filter\DateFilter; use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter; use ApiPlatform\Laravel\Eloquent\Filter\OrderFilter; @@ -71,12 +72,13 @@ property: 'name' )] #[QueryParameter(key: 'properties', filter: PropertyFilter::class)] +#[QueryParameter(key: 'published', filter: BooleanFilter::class)] class Book extends Model { use HasFactory; use HasUlids; - protected $visible = ['name', 'author', 'isbn', 'publication_date', 'is_available']; + protected $visible = ['name', 'author', 'isbn', 'publication_date', 'published', 'is_available']; protected $fillable = ['name', 'is_available']; protected $casts = [ 'is_available' => 'boolean', diff --git a/src/Laravel/workbench/database/factories/BookFactory.php b/src/Laravel/workbench/database/factories/BookFactory.php index d94b3f281c9..98a2367f507 100644 --- a/src/Laravel/workbench/database/factories/BookFactory.php +++ b/src/Laravel/workbench/database/factories/BookFactory.php @@ -39,6 +39,7 @@ public function definition(): array 'publication_date' => fake()->optional()->date(), 'is_available' => 1 === random_int(0, 1), 'internal_note' => fake()->text(), + 'published' => fake()->boolean(100), ]; } } diff --git a/src/Laravel/workbench/database/migrations/2023_07_15_231244_create_book_table.php b/src/Laravel/workbench/database/migrations/2023_07_15_231244_create_book_table.php index 0137554453c..341223e12d8 100644 --- a/src/Laravel/workbench/database/migrations/2023_07_15_231244_create_book_table.php +++ b/src/Laravel/workbench/database/migrations/2023_07_15_231244_create_book_table.php @@ -34,6 +34,7 @@ public function up(): void $table->date('publication_date')->nullable(); $table->boolean('is_available')->default(true); $table->text('internal_note')->nullable(); + $table->boolean('published')->nullable(); $table->integer('author_id')->unsigned(); $table->foreign('author_id')->references('id')->on('authors'); $table->timestamps();