Skip to content

Commit

Permalink
add laravel FormRequest support
Browse files Browse the repository at this point in the history
  • Loading branch information
henzeb committed Feb 24, 2022
1 parent eec1a52 commit a55cccd
Show file tree
Hide file tree
Showing 27 changed files with 1,202 additions and 11 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

All notable changes to `Query Filter Builder` will be documented in this file

## 1.1.0 - 2022-02-24

- added FormRequest functionality to ease development in Laravel
- added asc/desc sorting functionality

## 1.0.0 - 2022-02-22

- initial release
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,19 @@ a nice and simple interface that allows you to add filters without the
need of a thousand parameters passed to your methods or writing SQL queries
inside your controllers.

This comes with support for Laravel's query builder. No other builders as of yet.
If you'd like to contribute, see [Contributing](CONTRIBUTING.md).
This comes with support for Laravel. If you'd like to contribute
for other frameworks, see [Contributing](CONTRIBUTING.md).

## Installation

You can install the package via composer:

```bash
composer require henzeb/query-filter-builder
```

## Usage
See [here](doc/LARAVEL.md) for Laravel specific usage.

In your controller you may build up something like this, based on parameters
given by the user of your application.

Expand Down
7 changes: 7 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,12 @@
},
"config": {
"sort-packages": true
},
"extra": {
"laravel": {
"providers": [
"Henzeb\\Query\\Illuminate\\Providers\\QueryFilterServiceProvider"
]
}
}
}
80 changes: 80 additions & 0 deletions doc/LARAVEL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Laravel

## Usage
you can use this package easily with laravel FormRequests. Out of the box
the package will parse and validate query-parameters for sorting and
paginating based on the
[JSON:API specification](https://jsonapi.org/format/1.1/).

### configuration
The configuration file can be published using `php artisan vendor:publish`
```php
return [
/**
* @see https://jsonapi.org/format/1.1/#fetching-filtering
*/
'key' => 'filter', /** The key used as query-family for filtering. like filter[]= */
/**
* @see https://jsonapi.org/format/1.1/#fetching-pagination
*/
'pagination' => [
'auto' => true, /** automatically adds pagination to your filters if set to true */
'key' => 'page', /** The key used as query-family for sorting. like page[]= */
'limit' => 'size', /** The key used for query-family-member limit, like: page[size]=1 */
'offset' => 'number', /** The key used for query-family-member offset, like: page[number]=1 */
'defaults' => [
'limit' => 50, /** the default limit */
'max_limit' => 100 /** The maximum allowed limit */
]
],
/**
* @see https://jsonapi.org/format/1.1/#fetching-sorting
*/
'sorting' => [
'auto'=> true, /** automatically adds sorting to your filters if set to true */
'key'=> 'sort', /** the key used as query-family for sorting, like sort= */
]
];
```
In your FormRequest, you also have some control.
```php
class YourFormRequest extends FormRequest {
protected bool $enablePagination = false; /** disable or enable pagination */

protected bool $enableSorting = false; /** disable or enable sorting */
protected array $allowSorting = []; /** fields that are allowed for sorting */
protected int $defaultLimit = 10; /** The default limit */
protected int $maxLimit = 50; /** The maximum allowed limit */
}
```
By default, the package does not allow any fields for sorting.
You have to add the fields you want to allow into the `$allowSorting` property.
The format is the same as specified in the
[JSON:API specification: Sorting](https://jsonapi.org/format/1.1/#fetching-sorting),
except it's listed as an array:

```php
protected array $allowSorting = [
'animal', /** ascending */
'-animal' /** descending */
];
```
### Add your own filters
To add your own filters, simply add the following method in your FormRequest.
You can use `filter` and `hasFilter` methods as shortcut to the filter
query parameter family as specified in [JSON:API specification: Filtering](https://jsonapi.org/format/1.1/#fetching-filtering)


```php
private function filters(Query $query): void
{
if($this->hasFilter('animal')) {
$query->is('animal_field', $this->filter('animal'));
}
}
```
Note: You need to add your own validations in your rules method.




4 changes: 4 additions & 0 deletions src/Builders/Contracts/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,8 @@ public function orNest(QueryFilter $query): void;
public function limit(int $limit): void;

public function offset(int $offset): void;

public function asc(string $key): void;

public function desc(string $key): void;
}
4 changes: 4 additions & 0 deletions src/Filters/Contracts/QueryFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ public function limit(int $limit): self;

public function offset(int $offset): self;

public function asc(string $key): self;

public function desc(string $key): self;

public function and(): self;

public function or(): self;
Expand Down
10 changes: 10 additions & 0 deletions src/Filters/Query.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,16 @@ public function offset(int $offset): QueryFilter
return $this->addFilter(__FUNCTION__, get_defined_vars());
}

public function asc(string $key): QueryFilter
{
return $this->addFilter(__FUNCTION__, get_defined_vars());
}

public function desc(string $key): QueryFilter
{
return $this->addFilter(__FUNCTION__, get_defined_vars());
}

public function and(): QueryFilter
{
return $this->addFilter(__FUNCTION__);
Expand Down
10 changes: 10 additions & 0 deletions src/Illuminate/Builders/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,16 @@ public function offset(int $offset): void
$this->getBuilder()->offset($offset);
}

public function asc(string $key): void
{
$this->getBuilder()->orderBy($key);
}

public function desc(string $key): void
{
$this->getBuilder()->orderByDesc($key);
}

public function nest(QueryFilter $query): void
{
$this->getBuilder()->where(
Expand Down
27 changes: 27 additions & 0 deletions src/Illuminate/Config/filter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
return [
/**
* @see https://jsonapi.org/format/1.1/#fetching-filtering
*/
'key' => 'filter', /** The key used as query-family for filtering. like filter[]= */
/**
* @see https://jsonapi.org/format/1.1/#fetching-pagination
*/
'pagination' => [
'auto' => true, /** automatically adds pagination to your filters if set to true */
'key' => 'page', /** The key used as query-family for sorting. like page[]= */
'limit' => 'size', /** The key used for query-family-member limit, like: page[size]=1 */
'offset' => 'number', /** The key used for query-family-member offset, like: page[number]=1 */
'defaults' => [
'limit' => 50, /** the default limit */
'max_limit' => 100 /** The maximum allowed limit */
]
],
/**
* @see https://jsonapi.org/format/1.1/#fetching-sorting
*/
'sorting' => [
'auto'=> true, /** automatically adds sorting to your filters if set to true */
'key'=> 'sort', /** the key used as query-family for sorting, like sort= */
]
];
12 changes: 12 additions & 0 deletions src/Illuminate/Facades/Filter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Henzeb\Query\Illuminate\Facades;

use Illuminate\Support\Facades\Facade;

class Filter extends Facade
{
protected static function getFacadeAccessor(): string {
return 'filter.query';
}
}
79 changes: 79 additions & 0 deletions src/Illuminate/Factories/FilterFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

namespace Henzeb\Query\Illuminate\Factories;

use Arr;
use Henzeb\Query\Filters\Query;
use Symfony\Component\HttpFoundation\InputBag;

class FilterFactory
{
private function __construct(private Query $query, private array $parameters)
{
}

public static function get(
InputBag $inputBag,
bool $enableSorting,
bool $enablePagination,
int $defaultLimit = null
): Query
{
$query = new Query;

$filterFactory = new self($query, $inputBag->all());

if ($enablePagination || config('filter.pagination.auto')) {
$filterFactory->parsePagination($defaultLimit);
}

if ($enableSorting || config('filter.sorting.auto')) {
$filterFactory->parseSorting();
}

return $query;
}

private function parsePagination(int $defaultLimit = null): void
{
$key = config('filter.pagination.key');

$limit = $this->createConfigKey([$key, config('filter.pagination.limit')]);
$defaultLimit = $defaultLimit ?? config('filter.pagination.defaults.limit');

$offset = $this->createConfigKey([$key, config('filter.pagination.offset')]);

if (Arr::has($this->parameters, $limit) || $defaultLimit) {
$this->query->limit(Arr::get($this->parameters, $limit, $defaultLimit));
}

if (Arr::has($this->parameters, $offset)) {
$this->query->offset(Arr::get($this->parameters, $offset));
}
}

private function createConfigKey(array $paths): string
{
return join('.', array_filter($paths));
}

private function parseSorting(): void
{
$key = config('filter.sorting.key', 'sort');

if (Arr::has($this->parameters, $key)) {
$sorts = explode(',', $this->parameters[$key]);

foreach ($sorts as $sort) {

if (str_starts_with($sort, '-')) {
$this->query->desc(ltrim($sort, '-'));
continue;
}
$this->query->asc($sort);
}

}
}

}
60 changes: 60 additions & 0 deletions src/Illuminate/Factories/RulesFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

namespace Henzeb\Query\Illuminate\Factories;

use Henzeb\Query\Illuminate\Validation\Decorators\Rules;
use Henzeb\Query\Illuminate\Validation\Contracts\RuleSet;
use Henzeb\Query\Illuminate\Validation\Decorators\SortingValidation;
use Henzeb\Query\Illuminate\Validation\Decorators\PaginationValidation;

class RulesFactory
{
private function __construct()
{
}

public static function get(
bool $enableSorting,
array $allowedSorting,
bool $enablePagination,
?int $maxLimit,
): RuleSet
{
$factory = new self();
$rules = new Rules();

$rules = $factory->decoratePaginationValidation($rules, $maxLimit, $enablePagination);

return $factory->decorateSortingValidation($rules, $enableSorting, $allowedSorting);
}

private function decoratePaginationValidation(
RuleSet $rules,
?int $maxLimit,
bool $enablePagination,
): RuleSet
{
if ($enablePagination && config('filter.pagination.auto')) {

return new PaginationValidation(
$rules,
$maxLimit
);
}

return $rules;
}

public function decorateSortingValidation(RuleSet $rules, bool $enableSorting, array $allowedSorting): RuleSet
{
if ($enableSorting && config('filter.sorting.auto')) {

return new SortingValidation(
$rules,
$allowedSorting
);
}

return $rules;
}
}
Loading

0 comments on commit a55cccd

Please sign in to comment.