From d910ece2e384d9fce6296a6e7a3f0249886255a3 Mon Sep 17 00:00:00 2001 From: Luis Arce Date: Fri, 24 May 2024 03:07:56 -0500 Subject: [PATCH 1/8] Upgrade docker-compose.yml --- .editorconfig | 2 +- docker-compose.yml | 64 +++++++++++++++++++++++++--------------------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/.editorconfig b/.editorconfig index dd9a2b5..5dcd7bc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,4 +12,4 @@ trim_trailing_whitespace = true trim_trailing_whitespace = false [*.{yml,yaml}] -indent_size = 2 +indent_size = 4 diff --git a/docker-compose.yml b/docker-compose.yml index 3eb976b..0ef5fc6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,30 +1,36 @@ -version: "3.6" - services: - app: - build: - context: . - dockerfile: docker/Dockerfile - container_name: laravel-query-builder-powered - volumes: - - ".:/var/www/html" - depends_on: - - mysql - mysql: - image: 'mysql/mysql-server:8.0' - ports: - - '${FORWARD_DB_PORT:-3306}:3306' - environment: - MYSQL_ROOT_PASSWORD: 'root' - MYSQL_ROOT_HOST: "%" - MYSQL_DATABASE: 'laravel-query-builder-powered' - MYSQL_USER: 'laravel' - MYSQL_PASSWORD: 'laravel' - MYSQL_ALLOW_EMPTY_PASSWORD: 1 - healthcheck: - test: - - CMD - - mysqladmin - - ping - retries: 3 - timeout: 5s + app: + build: + context: . + dockerfile: docker/Dockerfile + container_name: laravel-datatable-api + volumes: + - ".:/var/www/html" + depends_on: + - mysql + networks: + - laravel-datatable-net + mysql: + image: 'mysql/mysql-server:8.0' + container_name: laravel-datatable-db + ports: + - '${FORWARD_DB_PORT:-3306}:3306' + environment: + MYSQL_ROOT_PASSWORD: 'root' + MYSQL_ROOT_HOST: "%" + MYSQL_DATABASE: 'testing' + MYSQL_USER: 'laravel' + MYSQL_PASSWORD: 'laravel' + MYSQL_ALLOW_EMPTY_PASSWORD: 1 + healthcheck: + test: + - CMD + - mysqladmin + - ping + retries: 3 + timeout: 5s + networks: + - laravel-datatable-net +networks: + laravel-datatable-net: + driver: bridge From 33383555eb7834beba43f6d4f1d783ea202b057b Mon Sep 17 00:00:00 2001 From: Luis Arce Date: Fri, 24 May 2024 03:08:53 -0500 Subject: [PATCH 2/8] Upgrade database name on phpunit.xml.dist --- phpunit.xml.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c8ff637..1464af1 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -31,7 +31,7 @@ - + From 86a3592ce9ae833071250e8b1b2a583a170f1987 Mon Sep 17 00:00:00 2001 From: Luis Arce Date: Fri, 24 May 2024 03:09:31 -0500 Subject: [PATCH 3/8] Change name testsuite --- phpunit.xml.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 1464af1..03c3365 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -16,7 +16,7 @@ backupStaticProperties="false" > - + tests From 067f726eb9c76cea744e8d4e786b557c33b12908 Mon Sep 17 00:00:00 2001 From: Luis Arce Date: Fri, 24 May 2024 03:12:18 -0500 Subject: [PATCH 4/8] Format --- .editorconfig | 2 +- docker-compose.yml | 68 +++++++++++++++++++++++----------------------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/.editorconfig b/.editorconfig index 5dcd7bc..dd9a2b5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,4 +12,4 @@ trim_trailing_whitespace = true trim_trailing_whitespace = false [*.{yml,yaml}] -indent_size = 4 +indent_size = 2 diff --git a/docker-compose.yml b/docker-compose.yml index 0ef5fc6..f27d1f5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,36 +1,36 @@ services: - app: - build: - context: . - dockerfile: docker/Dockerfile - container_name: laravel-datatable-api - volumes: - - ".:/var/www/html" - depends_on: - - mysql - networks: - - laravel-datatable-net - mysql: - image: 'mysql/mysql-server:8.0' - container_name: laravel-datatable-db - ports: - - '${FORWARD_DB_PORT:-3306}:3306' - environment: - MYSQL_ROOT_PASSWORD: 'root' - MYSQL_ROOT_HOST: "%" - MYSQL_DATABASE: 'testing' - MYSQL_USER: 'laravel' - MYSQL_PASSWORD: 'laravel' - MYSQL_ALLOW_EMPTY_PASSWORD: 1 - healthcheck: - test: - - CMD - - mysqladmin - - ping - retries: 3 - timeout: 5s - networks: - - laravel-datatable-net + app: + build: + context: . + dockerfile: docker/Dockerfile + container_name: laravel-datatable-api + volumes: + - ".:/var/www/html" + depends_on: + - mysql + networks: + - laravel-datatable-net + mysql: + image: 'mysql/mysql-server:8.0' + container_name: laravel-datatable-db + ports: + - '${FORWARD_DB_PORT:-3306}:3306' + environment: + MYSQL_ROOT_PASSWORD: 'root' + MYSQL_ROOT_HOST: "%" + MYSQL_DATABASE: 'testing' + MYSQL_USER: 'laravel' + MYSQL_PASSWORD: 'laravel' + MYSQL_ALLOW_EMPTY_PASSWORD: 1 + healthcheck: + test: + - CMD + - mysqladmin + - ping + retries: 3 + timeout: 5s + networks: + - laravel-datatable-net networks: - laravel-datatable-net: - driver: bridge + laravel-datatable-net: + driver: bridge From fea0d6ba723e4357324f89d77d3b2a0e808e10cc Mon Sep 17 00:00:00 2001 From: Luis Arce Date: Fri, 24 May 2024 03:25:39 -0500 Subject: [PATCH 5/8] Upgrade dependencies --- .github/workflows/run-tests.yml | 11 +++++++---- composer.json | 8 ++++---- docker/Dockerfile | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 32782aa..6d8d26e 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -2,9 +2,9 @@ name: run-tests on: push: - branches: [main] + branches: [ main ] pull_request: - branches: [main] + branches: [ main ] jobs: test: @@ -13,13 +13,16 @@ jobs: fail-fast: true matrix: os: [ ubuntu-latest ] - php: [ 8.2 ] - laravel: [ 10.* ] + php: [ 8.2, 8.3 ] + laravel: [ 10.*, 11.* ] stability: [ prefer-stable ] include: - laravel: 10.* testbench: 8.* carbon: ^2.63 + - laravel: 11.* + testbench: 9.* + carbon: ^2.63 name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} diff --git a/composer.json b/composer.json index f9e5d6e..d1093ef 100644 --- a/composer.json +++ b/composer.json @@ -18,18 +18,18 @@ } ], "require": { - "php": "^8.2", - "illuminate/contracts": "^10.0", + "php": "^8.2|^8.3", + "illuminate/contracts": "^10.0|^11.0", "kirschbaum-development/eloquent-power-joins": "^3.2", "spatie/laravel-package-tools": "^1.14.0", - "spatie/laravel-query-builder": "^5.2" + "spatie/laravel-query-builder": "^5.2|^6.0" }, "require-dev": { "roave/security-advisories": "dev-latest", "laravel/pint": "^1.0", "nunomaduro/collision": "^7.9", "nunomaduro/larastan": "^2.0.1", - "orchestra/testbench": "^8.0", + "orchestra/testbench": "^8.0|^9.0", "pestphp/pest": "^2.0", "pestphp/pest-plugin-arch": "^2.0", "pestphp/pest-plugin-laravel": "^2.0", diff --git a/docker/Dockerfile b/docker/Dockerfile index 5d87581..003e8fb 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,5 @@ # Base image -FROM php:8.2-alpine +FROM php:8.3-alpine # Create app folder RUN mkdir -p /var/www/html/ From 14c9ed9fa174e58de39a3f255946c0003f851016 Mon Sep 17 00:00:00 2001 From: Luis Arce Date: Fri, 24 May 2024 03:32:17 -0500 Subject: [PATCH 6/8] Remove old dependencies --- composer.json | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/composer.json b/composer.json index d1093ef..62fb6a7 100644 --- a/composer.json +++ b/composer.json @@ -19,23 +19,15 @@ ], "require": { "php": "^8.2|^8.3", - "illuminate/contracts": "^10.0|^11.0", "kirschbaum-development/eloquent-power-joins": "^3.2", "spatie/laravel-package-tools": "^1.14.0", "spatie/laravel-query-builder": "^5.2|^6.0" }, "require-dev": { - "roave/security-advisories": "dev-latest", "laravel/pint": "^1.0", - "nunomaduro/collision": "^7.9", - "nunomaduro/larastan": "^2.0.1", "orchestra/testbench": "^8.0|^9.0", "pestphp/pest": "^2.0", - "pestphp/pest-plugin-arch": "^2.0", - "pestphp/pest-plugin-laravel": "^2.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1.0" + "pestphp/pest-plugin-laravel": "^2.0" }, "autoload": { "psr-4": { @@ -49,7 +41,6 @@ }, "scripts": { "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi", - "analyse": "vendor/bin/phpstan analyse", "test": "vendor/bin/pest", "test-coverage": "vendor/bin/pest --coverage", "format": "vendor/bin/pint" From 3df54d53e2f40db5f1d9a99de1762c96aa1fbf27 Mon Sep 17 00:00:00 2001 From: Luis Arce Date: Fri, 24 May 2024 04:38:14 -0500 Subject: [PATCH 7/8] Add docs --- README.md | 175 ++++++++++++++++++++++++++++++++--- src/Filters/DateFilter.php | 4 + src/Filters/Filter.php | 9 ++ src/Filters/NumberFilter.php | 4 + src/Filters/TextFilter.php | 4 + 5 files changed, 182 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 56da6db..c8c92dd 100644 --- a/README.md +++ b/README.md @@ -36,14 +36,77 @@ return [ ## Usage +### Pagination + +This package uses Laravel pagination by default; However, it allows you to specify through parameters of +queries, the number of records to obtain, you can even obtain all the records. + +```php +GET /books?per_page=30 +``` + +It is recommended to use the `result` method instead of `paginate` or `get`. Since `result` encapsulates the logic of +both yes, it is requested to show all, below `result` will use `get` but if you want to see a number of records +below will use `paginate` with the amount provided. + +```php +use Spatie\QueryBuilder\AllowedFilter; +use TeamQ\QueryBuilder\QueryBuilder; +use TeamQ\QueryBuilder\Filters\TextFilter; + +$query = QueryBuilder::for(Book::class); + +$query->result(); +``` + ### Filters -| Filter | Class | -|--------|-------------------------------------------| -| Text | `TeamQ\QueryBuilder\Filters\TextFilter` | -| Number | `TeamQ\QueryBuilder\Filters\NumberFilter` | -| Date | `TeamQ\QueryBuilder\Filters\DateFilter` | -| Global | `TeamQ\QueryBuilder\Filters\GlobalFilter` | +| Filter | Class | Operators | +|--------|-------------------------------------------|-----------| +| Text | `TeamQ\QueryBuilder\Filters\TextFilter` | Text | +| Number | `TeamQ\QueryBuilder\Filters\NumberFilter` | Number | +| Date | `TeamQ\QueryBuilder\Filters\DateFilter` | Number | +| Global | `TeamQ\QueryBuilder\Filters\GlobalFilter` | - | + +
+ Text comparison operators + +| Comparison operator | Key | +|---------------------|-----| +| Equal | 1 | +| Not Equal | 2 | +| Start With | 3 | +| Not Start With | 4 | +| End With | 5 | +| Not End With | 6 | +| Contains | 7 | +| Not Contains | 8 | +| In | 9 | +| Not In | 10 | +| Filled | 11 | +| Not Filled | 12 | + +
+ +
+ Number comparison operators + +| Comparison operator | Key | +|-----------------------|-----| +| Equal | 1 | +| Not Equal | 2 | +| Greater Than | 3 | +| Greater Than Or Equal | 4 | +| Less Than | 5 | +| Less Than Or Equal | 6 | +| Between | 7 | +| Not Between | 8 | +| In | 9 | +| Not In | 10 | +| Filled | 11 | +| Not Filled | 12 | + +
You can use advanced filters that have support for multiple comparison operators. The available comparison operators are located in `TeamQ\QueryBuilder\Enums\Comparators` @@ -56,7 +119,7 @@ GET /books?filter[isbn][value]=54213&filter[isbn][operator]=5 ```php use Spatie\QueryBuilder\AllowedFilter; -use Spatie\QueryBuilder\QueryBuilder; +use TeamQ\QueryBuilder\QueryBuilder; use TeamQ\QueryBuilder\Filters\TextFilter; QueryBuilder::for(Book::class) @@ -70,7 +133,7 @@ and pass the type of `join` to use. ```php use Spatie\QueryBuilder\AllowedFilter; -use Spatie\QueryBuilder\QueryBuilder; +use TeamQ\QueryBuilder\QueryBuilder; use TeamQ\QueryBuilder\Filters\TextFilter; use TeamQ\QueryBuilder\Enums\JoinType; @@ -80,7 +143,89 @@ QueryBuilder::for(Book::class) ]); ``` -#### Global Filter +#### _Text Filter_ + +The following example uses the comparison operator `5`, which is equivalent to asking if there is a book where +isbn `End With` 54213 + +```php +GET /books?filter[isbn][value]=54213&filter[isbn][operator]=5 +``` + +```sql +select * +from `books` +where lower(`isbn`) like '%54213' +``` + +```php +use Spatie\QueryBuilder\AllowedFilter; +use TeamQ\QueryBuilder\QueryBuilder; +use TeamQ\QueryBuilder\Filters\TextFilter; + +QueryBuilder::for(Book::class) + ->allowedFilters([ + AllowedFilter::custom('isbn', new TextFilter()), + ]); +``` + +#### _Number Filter_ + +The following example uses the comparison operator `9`, which is equivalent to asking if there is a book where +id `In` 1, 5 or 9 + +For this example an array of values was used. Arraying values is supported by all types of operators (text and number). + +```php +GET /books?filter[id][value][0]=1&filter[id][value][1]=5&filter[id][value][2]=9&filter[id][operator]=9 +``` + +```sql +select * +from `books` +where id in (1, 5, 9) +``` + +```php +use Spatie\QueryBuilder\AllowedFilter; +use TeamQ\QueryBuilder\QueryBuilder; +use TeamQ\QueryBuilder\Filters\NumberFilter; + +QueryBuilder::for(Book::class) + ->allowedFilters([ + AllowedFilter::custom('id', new NumberFilter()), + ]); +``` + +#### _Date Filter_ + +The following example uses the comparison operator `8`, which is equivalent to asking if there is a book where +created at `Not Between` 2019-08-01 and 2019-08-10 + +For this example an array of values was used. Arraying values is supported by all types of operators (text and number). + +```php +GET /books?filter[created_at][value][0]=2019-08-01&filter[created_at][value][1]=2019-08-10&filter[id][operator]=8 +``` + +```sql +select * +from `books` +where created_at not between '2019-08-01' and '2019-08-10' +``` + +```php +use Spatie\QueryBuilder\AllowedFilter; +use TeamQ\QueryBuilder\QueryBuilder; +use TeamQ\QueryBuilder\Filters\DateFilter; + +QueryBuilder::for(Book::class) + ->allowedFilters([ + AllowedFilter::custom('created_at', new DateFilter()), + ]); +``` + +#### _Global Filter_ The global filter implements general search functionality for the model and its relationships. @@ -91,7 +236,7 @@ To use this filter, you must pass the model fields to be filtered or their relat ```php use Spatie\QueryBuilder\AllowedFilter; -use Spatie\QueryBuilder\QueryBuilder; +use TeamQ\QueryBuilder\QueryBuilder; use TeamQ\QueryBuilder\Filters\GlobalFilter; QueryBuilder::for(Book::class) @@ -103,6 +248,8 @@ QueryBuilder::for(Book::class) ]); ``` +--- + ### Sorts | Sort | Class | @@ -110,14 +257,14 @@ QueryBuilder::for(Book::class) | Relation | `TeamQ\QueryBuilder\Sorts\RelationSort` | | Case | `TeamQ\QueryBuilder\Sorts\CaseSort` | -#### RelationSort +#### _RelationSort_ To sort by fields of related tables you must use `join`, there is no easy way to do it from eloquent, so you can use `RelationSort`, this class receives the type of `join` as a parameter. ```php use Spatie\QueryBuilder\AllowedFilter; -use Spatie\QueryBuilder\QueryBuilder; +use TeamQ\QueryBuilder\QueryBuilder; use TeamQ\QueryBuilder\Sorts\RelationSort; QueryBuilder::for(Book::class) @@ -126,7 +273,7 @@ QueryBuilder::for(Book::class) ]) ``` -#### CaseSort +#### _CaseSort_ If you use enums or states, where each enum or state is represented by a number, you may want to sort by name of that enum or state and not by the number, then you can use `CaseSort`. @@ -138,7 +285,7 @@ related model. By default, it is `Inner Join`. ```php use Spatie\QueryBuilder\AllowedFilter; -use Spatie\QueryBuilder\QueryBuilder; +use TeamQ\QueryBuilder\QueryBuilder; use TeamQ\QueryBuilder\Sorts\CaseSort; QueryBuilder::for(Book::class) diff --git a/src/Filters/DateFilter.php b/src/Filters/DateFilter.php index 380ab51..2399a8f 100644 --- a/src/Filters/DateFilter.php +++ b/src/Filters/DateFilter.php @@ -13,6 +13,10 @@ */ class DateFilter extends Filter { + /** + * Operators that receive values as arrays or should be filtered as an array. + * Ex: In, NotIn, Between, etc. + */ protected const ARRAY_OPERATORS = [ Comparators\Number::Between, Comparators\Number::NotBetween, diff --git a/src/Filters/Filter.php b/src/Filters/Filter.php index 374385a..19749dc 100644 --- a/src/Filters/Filter.php +++ b/src/Filters/Filter.php @@ -16,6 +16,10 @@ abstract class Filter implements IFilter { use HasPropertyRelationship; + /** + * Operators that receive values as arrays or should be filtered as an array. + * Ex: In, NotIn, Between, etc. + */ protected const ARRAY_OPERATORS = []; /** @@ -71,9 +75,14 @@ public function __invoke(Builder $query, $value, string $property): void if ($value !== false) { if (is_array($value)) { + // If the value received is an array of values and the operator we receive is an operator + // of arrays, then this follows the normal flow. if (in_array($operator, static::ARRAY_OPERATORS, true)) { $this->handle($query, $value, $property, $operator); } else { + // But if it is not an array operator, then we encapsulate the query logic in + // a subquery, applying the comparison operator provided for each of the + // y values connected by "or". $query->where(function (Builder $query) use ($value, $property, $operator) { foreach ($value as $item) { $this->handle($query, $item, $property, $operator, 'or'); diff --git a/src/Filters/NumberFilter.php b/src/Filters/NumberFilter.php index 5b3cf78..87826b4 100644 --- a/src/Filters/NumberFilter.php +++ b/src/Filters/NumberFilter.php @@ -13,6 +13,10 @@ */ class NumberFilter extends Filter { + /** + * Operators that receive values as arrays or should be filtered as an array. + * Ex: In, NotIn, Between, etc. + */ protected const ARRAY_OPERATORS = [ Comparators\Number::Between, Comparators\Number::NotBetween, diff --git a/src/Filters/TextFilter.php b/src/Filters/TextFilter.php index 2f4541a..cc67b4d 100644 --- a/src/Filters/TextFilter.php +++ b/src/Filters/TextFilter.php @@ -13,6 +13,10 @@ */ class TextFilter extends Filter { + /** + * Operators that receive values as arrays or should be filtered as an array. + * Ex: In, NotIn, Between, etc. + */ protected const ARRAY_OPERATORS = [ Comparators\Text::In, Comparators\Text::NotIn, From c3278a622c6588efd5da1c4540b13215be2e2b03 Mon Sep 17 00:00:00 2001 From: Luis Arce Date: Fri, 24 May 2024 04:43:47 -0500 Subject: [PATCH 8/8] Rename database on actions --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 6d8d26e..23e76cf 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -32,7 +32,7 @@ jobs: env: MYSQL_USER: user MYSQL_PASSWORD: secret - MYSQL_DATABASE: laravel-query-builder-powered + MYSQL_DATABASE: testing MYSQL_ROOT_PASSWORD: root ports: - 3306