diff --git a/.gitattributes b/.gitattributes
index 47ea9b35..be4c5df3 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -6,6 +6,7 @@
.gitattributes export-ignore
.gitignore export-ignore
.scrutinizer.yml export-ignore
-.styleci.yml export-ignore
phpstan.neon.dist export-ignore
phpunit.xml.dist export-ignore
+pint.json export-ignore
+rector.php export-ignore
diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index 9ad5f7f9..971c16c8 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -1,23 +1,26 @@
-name: "Continuous Integration"
+name: Continuous Integration
on:
push:
+ branches:
+ - master
+ - '*.x'
pull_request:
schedule:
- cron: '0 0 * * *'
jobs:
- phpunit:
+ tests:
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
- php: [8.1, 8.2, 8.3]
- stability: [prefer-stable]
+ php: [ 8.2, 8.3, 8.4, 8.5 ]
+ stability: [ prefer-stable ]
- name: PHP ${{ matrix.php }} - ${{ matrix.stability }}
+ name: PHP ${{ matrix.php }} - STABILITY ${{ matrix.stability }}
steps:
- name: Checkout code
@@ -27,7 +30,6 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
- extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, gd, memcached
tools: composer:v2
coverage: none
@@ -39,7 +41,7 @@ jobs:
with:
timeout_minutes: 5
max_attempts: 5
- command: COMPOSER_ROOT_VERSION=dev-master composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress
+ command: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress
- name: Execute tests
run: vendor/bin/phpunit
diff --git a/.github/workflows/pint.yml b/.github/workflows/pint.yml
new file mode 100644
index 00000000..147b6e3a
--- /dev/null
+++ b/.github/workflows/pint.yml
@@ -0,0 +1,29 @@
+name: PHP Linting
+on:
+ pull_request:
+ push:
+ branches:
+ - master
+ - 11.x
+jobs:
+ phplint:
+ runs-on: ubuntu-latest
+
+ permissions:
+ contents: write
+ pull-requests: write
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.head_ref }}
+
+ - name: "laravel-pint"
+ uses: aglipanci/laravel-pint-action@latest
+ with:
+ preset: laravel
+ verboseMode: true
+
+ - uses: stefanzweifel/git-auto-commit-action@v5
+ with:
+ commit_message: "fix: pint :robot:"
diff --git a/.github/workflows/semantic-release.yml b/.github/workflows/semantic-release.yml
new file mode 100644
index 00000000..3581d7cf
--- /dev/null
+++ b/.github/workflows/semantic-release.yml
@@ -0,0 +1,41 @@
+name: Semantic Releases
+
+on:
+ workflow_dispatch:
+
+permissions:
+ contents: write
+ pull-requests: write
+ issues: write
+ packages: write
+ statuses: write
+
+jobs:
+ run:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Code
+ uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - name: Setup Configuration
+ run: |
+ if [ -n "$GH_TOKEN_SECRET" ]; then
+ echo "GH_TOKEN=$GH_TOKEN_SECRET" >> $GITHUB_ENV
+ else
+ echo "GH_TOKEN=$GITHUB_TOKEN" >> $GITHUB_ENV
+ fi
+ env:
+ GH_TOKEN_SECRET: ${{ secrets.GH_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 'lts/*'
+
+ - name: Semantic Release
+ uses: cycjimmy/semantic-release-action@9cc899c47e6841430bbaedb43de1560a568dfd16
+ env:
+ GH_TOKEN: ${{ secrets.GH_TOKEN }}
diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml
index 1f150293..b6933cea 100644
--- a/.github/workflows/static-analysis.yml
+++ b/.github/workflows/static-analysis.yml
@@ -1,21 +1,12 @@
-name: "Static Analysis"
+name: Static Analysis
on:
push:
- paths:
- - .github/workflows/static-analysis.yml
- - composer.*
- - phpstan.neon.dist
- - src/**
- - tests/**
+ branches:
+ - master
+ - '*.x'
pull_request:
- paths:
- - .github/workflows/static-analysis.yml
- - composer.*
- - phpstan.neon.dist
- - src/**
- - tests/**
schedule:
- cron: '0 0 * * *'
@@ -23,32 +14,26 @@ on:
jobs:
static-analysis-phpstan:
- name: "Static Analysis with PHPStan"
+ name: Source Code
runs-on: ubuntu-latest
- strategy:
- fail-fast: true
- matrix:
- php: [8.1]
- stability: [prefer-stable]
-
steps:
- name: Checkout code
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
- php-version: ${{ matrix.php }}
+ php-version: 8.2
tools: composer:v2
coverage: none
- name: Install dependencies
- uses: nick-invision/retry@v1
+ uses: nick-fields/retry@v3
with:
timeout_minutes: 5
max_attempts: 5
- command: COMPOSER_ROOT_VERSION=dev-master composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress
+ command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress
- - name: "Run a static analysis with phpstan/phpstan"
- run: "vendor/bin/phpstan --error-format=table"
+ - name: Run Static Analysis
+ run: vendor/bin/phpunit
diff --git a/.releaserc.yml b/.releaserc.yml
new file mode 100644
index 00000000..e96c554c
--- /dev/null
+++ b/.releaserc.yml
@@ -0,0 +1,26 @@
+{
+ "branches": [
+ "main",
+ "master",
+ "*.x"
+ ],
+ "plugins": [
+ "@semantic-release/commit-analyzer",
+ "@semantic-release/release-notes-generator",
+ [
+ "@semantic-release/changelog",
+ {
+ "changelogFile": "CHANGELOG.md"
+ }
+ ],
+ [
+ "@semantic-release/git",
+ {
+ "assets": [
+ "CHANGELOG.md"
+ ]
+ }
+ ],
+ "@semantic-release/github"
+ ]
+}
diff --git a/.styleci.yml b/.styleci.yml
deleted file mode 100644
index 0285f179..00000000
--- a/.styleci.yml
+++ /dev/null
@@ -1 +0,0 @@
-preset: laravel
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8a5bf9bd..91ad2c0a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,229 +1,90 @@
-# Laravel DataTables CHANGELOG
+## [12.6.1](https://github.com/yajra/laravel-datatables/compare/v12.6.0...v12.6.1) (2025-10-11)
-[](https://packagist.org/packages/yajra/laravel-datatables-oracle)
-[](https://packagist.org/packages/yajra/laravel-datatables-oracle)
-[](https://travis-ci.org/yajra/laravel-datatables)
-[](https://packagist.org/packages/yajra/laravel-datatables-oracle)
-[](https://packagist.org/packages/yajra/laravel-datatables-oracle)
-### [Unreleased]
-
-### [v10.11.4] - 2024-02-28
-
-- fix: EloquentDataTable return type typo #3123
-
-### [v10.11.3] - 2023-12-27
-
-- fix: Update composer.json to use Larastan Org #3107
-
-### [v10.11.2] - 2023-12-12
-
-- fix: scout search with smart search #3105
-
-### [v10.11.1] - 2023-11-25
-
-- fix: Prevent error when PHP extension iconv not enabled. #3098
-
-### [v10.11.0] - 2023-11-04
-
-- feat: Scout Search Implementation #3082
-- feat: Add scout fixed ordering for pgsql and oracle #3090
-
-### [v10.10.0] - 2023-10-04
-
-- feat: allow closure on formatColumn #3073
-
-### [v10.9.0] - 2023-09-29
-
-- feat: Ability to pass static data to a blade render #3067
-
-### [v10.8.0] - 2023-08-12
-
-- feat: convert prepareQuery from protected to public #3045
-
-### [v10.7.0] - 2023-07-31
-
-- feat: add ability to disable eloquent getter mutator #3009
-- feat: Ability to use deep relations for searching #3035
-
-### [v10.6.2] - 2023-07-15
-
-- fix: #3010 - convert expressions to strings #3029
+### Bug Fixes
-### [v10.6.1] - 2023-07-05
+* value when mask uses "/" ([c771900](https://github.com/yajra/laravel-datatables/commit/c77190030c713e5b64c433bd161d9f33a210f22b))
-- fix: #3025 #3026
-- fix the error introduced in 10.4.4 as described in #3025.
+# [12.6.0](https://github.com/yajra/laravel-datatables/compare/v12.5.1...v12.6.0) (2025-10-08)
-### [v10.6.0] - 2023-06-29
-- feat: Expose autoFilter setter to disable post filtering #2981
-
-### [v10.5.0] - 2023-06-29
+### Bug Fixes
-- feat: Prevent editColumn when column is not shown #3018
+* replace unsafe eval() with Blade::render() in compileBlade ([7f46d58](https://github.com/yajra/laravel-datatables/commit/7f46d5872b0324493c28ecc8d848c182e88f30e0))
-### [v10.4.4] - 2023-06-27
-- feat: Optimize countQuery with complex select #3008
-- fix: phpstan #3022
+### Features
-### [v10.4.3] - 2023-06-07
+* add __isset() method to Request for attribute existence check ([33f44d4](https://github.com/yajra/laravel-datatables/commit/33f44d42d284d6ea0a054de81ad5a57c3050867d))
-- Fix: Prevent the filteredCount() query if no filter is applied to the initial query #3007
+# Laravel DataTables
-### [v10.4.2] - 2023-05-31
+## CHANGELOG
-- Fix return type for setTransformer() and setSerializer() #3003
-
-### [v10.4.1] - 2023-05-27
-
-- fix: Error when setting language config for "editor" #2983
-
-### [v10.4.0] - 2023-03-28
-
-- feat: Allow any callable in ->addColumn() #2977
-- fix: #2976
-
-### [v10.3.1] - 2023-02-20
-
-- fix: Fix anonymous resource collection data formatting #2944
-- fix: phpunit 10 deprecation #2955
-- fix: bump orchestra/testbench to 8 #2949
-
-### [v10.3.0] - 2023-02-07
-
-- Add Laravel 10 compatibility #2948
-
-### [v10.2.3] - 2023-01-18
-
-- fix: Custom Order on eager loaded relationships was not working
-- fix #2905
-
-### [v10.2.2] - 2023-01-11
-
-- fix: prevent deprecation errors in php 8.1+ #2931
-- fixes #2930
-
-### [v10.2.1] - 2022-12-07
-
-- fix: case insensitive starts with search #2917 #2916
-
-### [v10.2.0] - 2022-11-03
-
-- PHP 8.1 Depreciation Fix #2877
-- Methods pointing to the "uncustomizable" classes. #2861
-
-### [v10.1.6] - 2022-10-10
-
-- Fix anonymous resource collection #2870
-- Fix #2827
-- Add stale workflow
-
-### [v10.1.5] - 2022-10-06
-
-- Fix with method error with static analysis #2865
+### [Unreleased]
-### [v10.1.4] - 2022-09-27
+### v12.5.1 - 2025-10-02
-- Fixed the search column for same table relations #2856
+- fix: ambiguous column in columnControlSearch() method #3252
-### [v10.1.3] - 2022-09-20
+### v12.5.0 - 2025-10-01
-- Fix relation key name for BelongsToMany #2850
+- feat: server-side column control #3251
+- fix: https://github.com/yajra/laravel-datatables/issues/3250
-### [v10.1.2] - 2022-07-12
+### v12.4.2 - 2025-09-09
-- Fix HasOneThrough #2818
+- fix: remove @internal annotation from orderColumn() method #3248
-### [v10.1.1] - 2022-06-24
+### v12.4.1 - 2025-08-29
-- Fix null recordsFiltered on empty collection #2806
-- Fix #2793
+- fix: request handling with playwright / pest 4 #3247
-### [v10.1.0] - 2022-06-21
+### v12.4.0 - 2025-06-15
-- Add support for dependency injection when using closure. #2800
+- feat: add min search length control #3242
+- fix: #3241
-### [v10.0.8] - 2022-06-21
+### v12.3.1 - 2025-06-10
-- Make canCreate at QueryDataTable accept QueryBuilder only #2798
+- fix: support for array notation #3243
-### [v10.0.7] - 2022-05-23
+### v12.3.0 - 2025-05-17
-- Fix create eloquent datatable from relation #2789
+- feat: add option to enable alias on relation tables #3234
+- tests: Add tests to cover prefix detection #3239
+- fix: https://github.com/yajra/laravel-datatables/pull/1782
-### [v10.0.6] - 2022-05-18
+### v12.2.1 - 2025-05-09
-- Added null parameter type as allowed to handle default Action column from laravel-datatables-html #2787
+- fix: improve prefix detection #3238
+- fix: #3237
-### [v10.0.5] - 2022-05-17
+### v12.2.0 - 2025-05-08
-- Fix Return value must be of type int, string returned.
+- feat: add relation resolver param to order callback #3232
+- fix: improve column alias detection #3236
+- fix: #3235
-### [v10.0.4] - 2022-05-08
+### v12.1.2 - 2025-05-07
-- Fix accidental formatter issue on eloquent
-- Add formatColumn test for eloquent
+- fix: prevent prefixing null/empty string #3233
-### [v10.0.3] - 2022-05-08
+### v12.1.1 - 2025-05-05
-- Additional fix & test for zero total records
+- fix: prevent ambiguous column names #3227
-### [v10.0.2] - 2022-05-08
+### v12.1.0 - 2025-04-28
-- Fix set total & filtered records count https://github.com/yajra/laravel-datatables/pull/2778
-- Fix set total & filtered records count
-- Fix #1453 #1454 #2050 #2609
-- Add feature test
-- Deprecate `skipTotalRecords`, just use `setTotalRecords` directly.
+- feat: add relation resolver param to filter callbacks #3229
-### [v10.0.1] - 2022-05-08
+### v12.0.1 - 2025-04-07
-- Code clean-up and several phpstan fixes
+- fix: query results improvements #3224
-### [v10.0.0] - 2022-05-08
+### v12.0.0 - 2025-02-26
-- Laravel DataTables v10.x to support Laravel 9.x
-- Added PHPStan with max level static analysis
-- Drop `queryBuilder()` method
-- Drop support for `ApiResourceDataTable`
-- PHP8 syntax / method signature changed
+- feat: Laravel v12 Compatibility #3217
+- fix: prevent duplicate table name errors #3216
-[Unreleased]: https://github.com/yajra/laravel-datatables/compare/v10.11.3...10.x
-[v10.11.3]: https://github.com/yajra/laravel-datatables/compare/v10.11.3...v10.11.2
-[v10.11.2]: https://github.com/yajra/laravel-datatables/compare/v10.11.2...v10.11.1
-[v10.11.1]: https://github.com/yajra/laravel-datatables/compare/v10.11.1...v10.11.0
-[v10.11.0]: https://github.com/yajra/laravel-datatables/compare/v10.11.0...v10.10.0
-[v10.10.0]: https://github.com/yajra/laravel-datatables/compare/v10.10.0...v10.9.0
-[v10.9.0]: https://github.com/yajra/laravel-datatables/compare/v10.9.0...v10.8.0
-[v10.8.0]: https://github.com/yajra/laravel-datatables/compare/v10.8.0...v10.7.0
-[v10.7.0]: https://github.com/yajra/laravel-datatables/compare/v10.7.0...v10.6.2
-[v10.6.2]: https://github.com/yajra/laravel-datatables/compare/v10.6.2...v10.6.1
-[v10.6.1]: https://github.com/yajra/laravel-datatables/compare/v10.6.1...v10.6.0
-[v10.6.0]: https://github.com/yajra/laravel-datatables/compare/v10.6.0...v10.5.0
-[v10.5.0]: https://github.com/yajra/laravel-datatables/compare/v10.5.0...v10.4.4
-[v10.4.4]: https://github.com/yajra/laravel-datatables/compare/v10.4.4...v10.4.3
-[v10.3.1]: https://github.com/yajra/laravel-datatables/compare/v10.3.1...v10.3.0
-[v10.3.1]: https://github.com/yajra/laravel-datatables/compare/v10.3.1...v10.3.0
-[v10.3.0]: https://github.com/yajra/laravel-datatables/compare/v10.3.0...v10.2.3
-[v10.2.3]: https://github.com/yajra/laravel-datatables/compare/v10.2.3...v10.2.2
-[v10.2.2]: https://github.com/yajra/laravel-datatables/compare/v10.2.2...v10.2.1
-[v10.2.1]: https://github.com/yajra/laravel-datatables/compare/v10.2.1...v10.2.0
-[v10.2.0]: https://github.com/yajra/laravel-datatables/compare/v10.2.0...v10.1.6
-[v10.1.6]: https://github.com/yajra/laravel-datatables/compare/v10.1.6...v10.1.5
-[v10.1.5]: https://github.com/yajra/laravel-datatables/compare/v10.1.5...v10.1.4
-[v10.1.4]: https://github.com/yajra/laravel-datatables/compare/v10.1.4...v10.1.3
-[v10.1.3]: https://github.com/yajra/laravel-datatables/compare/v10.1.3...v10.1.2
-[v10.1.2]: https://github.com/yajra/laravel-datatables/compare/v10.1.2...v10.1.1
-[v10.1.1]: https://github.com/yajra/laravel-datatables/compare/v10.1.1...v10.1.0
-[v10.1.0]: https://github.com/yajra/laravel-datatables/compare/v10.1.0...v10.0.8
-[v10.0.8]: https://github.com/yajra/laravel-datatables/compare/v10.0.8...v10.0.7
-[v10.0.7]: https://github.com/yajra/laravel-datatables/compare/v10.0.7...v10.0.6
-[v10.0.6]: https://github.com/yajra/laravel-datatables/compare/v10.0.6...v10.0.5
-[v10.0.5]: https://github.com/yajra/laravel-datatables/compare/v10.0.5...v10.0.4
-[v10.0.4]: https://github.com/yajra/laravel-datatables/compare/v10.0.4...v10.0.3
-[v10.0.3]: https://github.com/yajra/laravel-datatables/compare/v10.0.3...v10.0.2
-[v10.0.2]: https://github.com/yajra/laravel-datatables/compare/v10.0.2...v10.0.1
-[v10.0.1]: https://github.com/yajra/laravel-datatables/compare/v10.0.1...v10.0.0
-[v10.0.0]: https://github.com/yajra/laravel-datatables/compare/v10.0.0...10.x
+[Unreleased]: https://github.com/yajra/laravel-datatables/compare/v12.0.0...master
diff --git a/README.md b/README.md
index f31f2175..32cff155 100644
--- a/README.md
+++ b/README.md
@@ -1,55 +1,74 @@
-# jQuery DataTables API for Laravel 4|5|6|7|8|9|10
+# jQuery DataTables API for Laravel
[](https://gitter.im/yajra/laravel-datatables?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[](https://www.paypal.me/yajra)
[](https://www.patreon.com/bePatron?u=4521203)
-[](http://laravel.com)
+[](http://laravel.com)
[](https://packagist.org/packages/yajra/laravel-datatables-oracle)
[](https://github.com/yajra/laravel-datatables/actions/workflows/continuous-integration.yml)
[](https://github.com/yajra/laravel-datatables/actions/workflows/static-analysis.yml)
-[](https://packagist.org/packages/yajra/laravel-datatables-oracle)
+
+[](https://packagist.org/packages/yajra/laravel-datatables-oracle)
[](https://packagist.org/packages/yajra/laravel-datatables-oracle)
Laravel package for handling [server-side](https://www.datatables.net/manual/server-side) works of [DataTables](http://datatables.net) jQuery Plugin via [AJAX option](https://datatables.net/reference/option/ajax) by using Eloquent ORM, Fluent Query Builder or Collection.
```php
-return datatables()->eloquent(User::query())->toJson();
-return datatables()->query(DB::table('users'))->toJson();
-return datatables()->collection(User::all())->toJson();
+use Yajra\DataTables\Facades\DataTables;
+
+return DataTables::eloquent(User::query())->toJson();
+return DataTables::query(DB::table('users'))->toJson();
+return DataTables::collection(User::all())->toJson();
-return datatables(User::query())->toJson();
-return datatables(DB::table('users'))->toJson();
-return datatables(User::all())->toJson();
+return DataTables::make(User::query())->toJson();
+return DataTables::make(DB::table('users'))->toJson();
+return DataTables::make(User::all())->toJson();
```
## Sponsors
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ |
+ A big thank you to DataTables for supporting this project with a free DataTables Editor license. |
+
+
+
+
+
+
+
+
+
+ |
+ A big thank you to JetBrains for supporting this project with free open-source licenses of their IDEs. |
+
+
+
+
+
+
+
+  |
+ A big thank you to Blackfire.io for supporting this project with a free open-source license. |
+
+
+
## Requirements
-- [PHP >= 8.0.2](http://php.net/)
+- [PHP >= 8.2](http://php.net/)
- [Laravel Framework](https://github.com/laravel/framework)
-- [jQuery DataTables v1.10.x](http://datatables.net/)
+- [DataTables](http://datatables.net/)
## Documentations
- [Github Docs](https://github.com/yajra/laravel-datatables-docs)
- [Laravel DataTables Quick Starter](https://yajrabox.com/docs/laravel-datatables/master/quick-starter)
- [Laravel DataTables Documentation](https://yajrabox.com/docs/laravel-datatables)
-- [Laravel 5.0 - 5.3 Demo Application](https://datatables.yajrabox.com)
## Laravel Version Compatibility
@@ -65,21 +84,31 @@ return datatables(User::all())->toJson();
| 5.6.x | 8.x |
| 5.7.x | 8.x |
| 5.8.x | 9.x |
-| 6.x.x | 9.x |
-| 7.x.x | 9.x |
-| 8.x.x | 9.x |
-| 9.x.x | 10.x |
-| 10.x.x | 10.x |
+| 6.x | 9.x |
+| 7.x | 9.x |
+| 8.x | 9.x |
+| 9.x | 10.x |
+| 10.x | 10.x |
+| 11.x | 11.x |
+| 12.x | 12.x |
## Quick Installation
+### Option 1: Install all DataTables libraries
+
+```bash
+composer require yajra/laravel-datatables:"^12"
+```
+
+### Option 2: Install only this library
+
```bash
-composer require yajra/laravel-datatables-oracle:"^10.0"
+composer require yajra/laravel-datatables-oracle:"^12"
```
#### Service Provider & Facade (Optional on Laravel 5.5+)
-Register provider and facade on your `config/app.php` file.
+Register the provider and facade on your `config/app.php` file.
```php
'providers' => [
...,
@@ -104,11 +133,12 @@ And that's it! Start building out some awesome DataTables!
To enable debugging mode, just set `APP_DEBUG=true` and the package will include the queries and inputs used when processing the table.
-**IMPORTANT:** Please make sure that APP_DEBUG is set to false when your app is on production.
+> [!IMPORTANT]
+> Please ensure that the `APP_DEBUG` config is set to false when your app is in production.
## PHP ARTISAN SERVE BUG
-Please avoid using `php artisan serve` when developing with the package.
+Please avoid using `php artisan serve` when developing the package.
There are known bugs when using this where Laravel randomly returns a redirect and 401 (Unauthorized) if the route requires authentication and a 404 NotFoundHttpException on valid routes.
It is advised to use [Homestead](https://laravel.com/docs/5.4/homestead) or [Valet](https://laravel.com/docs/5.4/valet) when working with the package.
@@ -119,7 +149,7 @@ Please see [CONTRIBUTING](https://github.com/yajra/laravel-datatables/blob/maste
## Security
-If you discover any security related issues, please email [aqangeles@gmail.com](mailto:aqangeles@gmail.com) instead of using the issue tracker.
+If you discover any security-related issues, please email [aqangeles@gmail.com](mailto:aqangeles@gmail.com) instead of using the issue tracker.
## Credits
diff --git a/UPGRADE.md b/UPGRADE.md
index 9ddec173..36a386b5 100644
--- a/UPGRADE.md
+++ b/UPGRADE.md
@@ -1,5 +1,9 @@
# UPGRADE GUIDE
+## Upgrading from v11.x to v12.x
+
+- See PR https://github.com/yajra/laravel-datatables/pull/3216
+
## Upgrading from v9.x to v10.x
- `ApiResourceDataTable` support dropped, use `CollectionDataTable` instead.
diff --git a/composer.json b/composer.json
index 04b884df..6e034353 100644
--- a/composer.json
+++ b/composer.json
@@ -1,82 +1,93 @@
{
- "name": "yajra/laravel-datatables-oracle",
- "description": "jQuery DataTables API for Laravel 4|5|6|7|8|9|10",
- "keywords": [
- "laravel",
- "dataTables",
- "jquery"
- ],
- "license": "MIT",
- "authors": [
- {
- "name": "Arjay Angeles",
- "email": "aqangeles@gmail.com"
- }
- ],
- "require": {
- "php": "^8.0.2",
- "illuminate/database": "^9|^10",
- "illuminate/filesystem": "^9|^10",
- "illuminate/http": "^9|^10",
- "illuminate/support": "^9|^10",
- "illuminate/view": "^9|^10"
- },
- "require-dev": {
- "algolia/algoliasearch-client-php": "^3.4",
- "laravel/scout": "^10.5",
- "meilisearch/meilisearch-php": "^1.4",
- "larastan/larastan": "^2.4",
- "orchestra/testbench": "^8",
- "yajra/laravel-datatables-html": "^9.3.4|^10"
- },
- "suggest": {
- "yajra/laravel-datatables-export": "Plugin for server-side exporting using livewire and queue worker.",
- "yajra/laravel-datatables-buttons": "Plugin for server-side exporting of dataTables.",
- "yajra/laravel-datatables-html": "Plugin for server-side HTML builder of dataTables.",
- "yajra/laravel-datatables-fractal": "Plugin for server-side response using Fractal.",
- "yajra/laravel-datatables-editor": "Plugin to use DataTables Editor (requires a license)."
- },
- "autoload": {
- "psr-4": {
- "Yajra\\DataTables\\": "src/"
+ "name": "yajra/laravel-datatables-oracle",
+ "description": "jQuery DataTables API for Laravel",
+ "keywords": [
+ "yajra",
+ "laravel",
+ "dataTables",
+ "jquery"
+ ],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Arjay Angeles",
+ "email": "aqangeles@gmail.com"
+ }
+ ],
+ "require": {
+ "php": "^8.2",
+ "illuminate/database": "^12",
+ "illuminate/filesystem": "^12",
+ "illuminate/http": "^12",
+ "illuminate/support": "^12",
+ "illuminate/view": "^12"
},
- "files": [
- "src/helper.php"
- ]
- },
- "autoload-dev": {
- "psr-4": {
- "Yajra\\DataTables\\Tests\\": "tests/"
- }
- },
- "extra": {
- "branch-alias": {
- "dev-master": "10.x-dev"
+ "require-dev": {
+ "algolia/algoliasearch-client-php": "^3.4.1",
+ "larastan/larastan": "^3.1.0",
+ "laravel/pint": "^1.14",
+ "laravel/scout": "^10.8.3",
+ "meilisearch/meilisearch-php": "^1.6.1",
+ "orchestra/testbench": "^10",
+ "rector/rector": "^2.0"
+ },
+ "suggest": {
+ "yajra/laravel-datatables-export": "Plugin for server-side exporting using livewire and queue worker.",
+ "yajra/laravel-datatables-buttons": "Plugin for server-side exporting of dataTables.",
+ "yajra/laravel-datatables-html": "Plugin for server-side HTML builder of dataTables.",
+ "yajra/laravel-datatables-fractal": "Plugin for server-side response using Fractal.",
+ "yajra/laravel-datatables-editor": "Plugin to use DataTables Editor (requires a license)."
+ },
+ "autoload": {
+ "psr-4": {
+ "Yajra\\DataTables\\": "src/"
+ },
+ "files": [
+ "src/helper.php"
+ ]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Yajra\\DataTables\\Tests\\": "tests/"
+ }
},
- "laravel": {
- "providers": [
- "Yajra\\DataTables\\DataTablesServiceProvider"
- ],
- "aliases": {
- "DataTables": "Yajra\\DataTables\\Facades\\DataTables"
- }
- }
- },
- "config": {
- "sort-packages": true,
- "allow-plugins": {
- "php-http/discovery": true
- }
- },
- "scripts": {
- "test": "vendor/bin/phpunit"
- },
- "minimum-stability": "dev",
- "prefer-stable": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/yajra"
- }
- ]
+ "extra": {
+ "branch-alias": {
+ "dev-master": "12.x-dev"
+ },
+ "laravel": {
+ "providers": [
+ "Yajra\\DataTables\\DataTablesServiceProvider"
+ ],
+ "aliases": {
+ "DataTables": "Yajra\\DataTables\\Facades\\DataTables"
+ }
+ }
+ },
+ "config": {
+ "sort-packages": true,
+ "allow-plugins": {
+ "php-http/discovery": true
+ }
+ },
+ "scripts": {
+ "test": "./vendor/bin/phpunit",
+ "pint": "./vendor/bin/pint",
+ "rector": "./vendor/bin/rector",
+ "stan": "./vendor/bin/phpstan analyse --memory-limit=2G --ansi --no-progress --no-interaction --configuration=phpstan.neon.dist",
+ "pr": [
+ "@rector",
+ "@pint",
+ "@stan",
+ "@test"
+ ]
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/yajra"
+ }
+ ]
}
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 9971e78a..26779953 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -10,8 +10,21 @@ parameters:
ignoreErrors:
- '#Unsafe usage of new static\(\).#'
+ - identifier: missingType.iterableValue
+ - identifier: argument.type
+ - identifier: cast.string
+ - identifier: foreach.nonIterable
+ - identifier: binaryOp.invalid
+ - identifier: offsetAccess.nonOffsetAccessible
+ - identifier: return.type
+ - identifier: method.nonObject
+ - identifier: varTag.nativeType
+ - identifier: assign.propertyType
+ - identifier: callable.nonCallable
+ - identifier: property.nonObject
excludePaths:
- src/helper.php
- checkMissingIterableValueType: false
+ noEnvCallsOutsideOfConfig: false
+ treatPhpDocTypesAsCertain: false
diff --git a/pint.json b/pint.json
new file mode 100644
index 00000000..93061b6b
--- /dev/null
+++ b/pint.json
@@ -0,0 +1,3 @@
+{
+ "preset": "laravel"
+}
diff --git a/rector.php b/rector.php
new file mode 100644
index 00000000..ca7ca051
--- /dev/null
+++ b/rector.php
@@ -0,0 +1,22 @@
+paths([
+ __DIR__.'/src',
+ __DIR__.'/tests',
+ ]);
+
+ // register a single rule
+ $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class);
+
+ // define sets of rules
+ $rectorConfig->sets([
+ LevelSetList::UP_TO_PHP_82,
+ ]);
+};
diff --git a/sonar-project.properties b/sonar-project.properties
deleted file mode 100644
index 8edc4cdc..00000000
--- a/sonar-project.properties
+++ /dev/null
@@ -1,3 +0,0 @@
-# Define separate root directories for sources and tests
-sonar.sources = src/
-sonar.tests = tests/
diff --git a/src/CollectionDataTable.php b/src/CollectionDataTable.php
index 99e7da1a..4392549e 100644
--- a/src/CollectionDataTable.php
+++ b/src/CollectionDataTable.php
@@ -13,13 +13,6 @@
class CollectionDataTable extends DataTableAbstract
{
- /**
- * Collection object.
- *
- * @var \Illuminate\Support\Collection
- */
- public Collection $collection;
-
/**
* Collection object.
*
@@ -29,8 +22,6 @@ class CollectionDataTable extends DataTableAbstract
/**
* The offset of the first record in the full dataset.
- *
- * @var int
*/
private int $offset = 0;
@@ -39,22 +30,18 @@ class CollectionDataTable extends DataTableAbstract
*
* @param \Illuminate\Support\Collection $collection
*/
- public function __construct(Collection $collection)
+ public function __construct(public Collection $collection)
{
$this->request = app('datatables.request');
$this->config = app('datatables.config');
- $this->collection = $collection;
- $this->original = $collection;
- $this->columns = array_keys($this->serialize($collection->first()));
+ $this->original = $this->collection;
+ $this->columns = array_keys($this->serialize($this->collection->first()));
}
/**
* Serialize collection.
- *
- * @param mixed $collection
- * @return array
*/
- protected function serialize($collection): array
+ protected function serialize(mixed $collection): array
{
return $collection instanceof Arrayable ? $collection->toArray() : (array) $collection;
}
@@ -87,8 +74,6 @@ public static function create($source)
/**
* Count results.
- *
- * @return int
*/
public function count(): int
{
@@ -97,8 +82,6 @@ public function count(): int
/**
* Perform column search.
- *
- * @return void
*/
public function columnSearch(): void
{
@@ -143,8 +126,6 @@ function ($row) use ($column, $keyword, $regex) {
/**
* Perform pagination.
- *
- * @return void
*/
public function paging(): void
{
@@ -157,14 +138,13 @@ public function paging(): void
/**
* Organizes works.
*
- * @param bool $mDataSupport
- * @return \Illuminate\Http\JsonResponse
- *
* @throws \Exception
*/
- public function make($mDataSupport = true): JsonResponse
+ public function make(bool $mDataSupport = true): JsonResponse
{
try {
+ $this->validateMinLengthSearch();
+
$this->totalRecords = $this->totalCount();
if ($this->totalRecords) {
@@ -200,7 +180,6 @@ public function results(): Collection
* Revert transformed DT_RowIndex back to its original values.
*
* @param bool $mDataSupport
- * @return void
*/
private function revertIndexColumn($mDataSupport): void
{
@@ -222,7 +201,6 @@ private function revertIndexColumn($mDataSupport): void
* the FULL dataset the collection was sliced from. It effectively allows the
* collection to be "pre-sliced".
*
- * @param int $offset
* @return static
*/
public function setOffset(int $offset): self
@@ -234,9 +212,6 @@ public function setOffset(int $offset): self
/**
* Perform global search for the given keyword.
- *
- * @param string $keyword
- * @return void
*/
protected function globalSearch(string $keyword): void
{
@@ -264,8 +239,6 @@ protected function globalSearch(string $keyword): void
/**
* Perform default query orderBy clause.
- *
- * @return void
*/
protected function defaultOrdering(): void
{
@@ -274,9 +247,7 @@ protected function defaultOrdering(): void
$sorter = $this->getSorter($criteria);
$this->collection = $this->collection
- ->map(function ($data) {
- return Arr::dot($data);
- })
+ ->map(fn ($data) => Arr::dot($data))
->sort($sorter)
->map(function ($data) {
foreach ($data as $key => $value) {
@@ -291,9 +262,6 @@ protected function defaultOrdering(): void
/**
* Get array sorter closure.
- *
- * @param array $criteria
- * @return \Closure
*/
protected function getSorter(array $criteria): Closure
{
diff --git a/src/Contracts/DataTable.php b/src/Contracts/DataTable.php
index 29e167e4..d1320b4c 100644
--- a/src/Contracts/DataTable.php
+++ b/src/Contracts/DataTable.php
@@ -10,21 +10,17 @@ interface DataTable
/**
* Get results.
*
- * @return \Illuminate\Support\Collection
+ * @return \Illuminate\Support\Collection|\Illuminate\Support\Collection
*/
public function results(): Collection;
/**
* Count results.
- *
- * @return int
*/
public function count(): int;
/**
* Count total items.
- *
- * @return int
*/
public function totalCount(): int;
@@ -32,45 +28,32 @@ public function totalCount(): int;
* Set auto filter off and run your own filter.
* Overrides global search.
*
- * @param callable $callback
- * @param bool $globalSearch
* @return static
*/
- public function filter(callable $callback, $globalSearch = false): self;
+ public function filter(callable $callback, bool $globalSearch = false): self;
/**
* Perform global search.
- *
- * @return void
*/
public function filtering(): void;
/**
* Perform column search.
- *
- * @return void
*/
public function columnSearch(): void;
/**
* Perform pagination.
- *
- * @return void
*/
public function paging(): void;
/**
* Perform sorting of columns.
- *
- * @return void
*/
public function ordering(): void;
/**
* Organizes works.
- *
- * @param bool $mDataSupport
- * @return \Illuminate\Http\JsonResponse
*/
- public function make($mDataSupport = true): JsonResponse;
+ public function make(bool $mDataSupport = true): JsonResponse;
}
diff --git a/src/Contracts/Formatter.php b/src/Contracts/Formatter.php
index 52c38e5b..9c9f50a0 100644
--- a/src/Contracts/Formatter.php
+++ b/src/Contracts/Formatter.php
@@ -5,9 +5,8 @@
interface Formatter
{
/**
- * @param mixed $value
* @param array|\Illuminate\Database\Eloquent\Model|object $row
* @return string
*/
- public function format($value, $row);
+ public function format(mixed $value, $row);
}
diff --git a/src/DataTableAbstract.php b/src/DataTableAbstract.php
index e3d6ca86..0e51ed2b 100644
--- a/src/DataTableAbstract.php
+++ b/src/DataTableAbstract.php
@@ -28,27 +28,18 @@ abstract class DataTableAbstract implements DataTable
/**
* DataTables Request object.
- *
- * @var \Yajra\DataTables\Utilities\Request
*/
public Utilities\Request $request;
- /**
- * @var \Psr\Log\LoggerInterface|null
- */
protected ?LoggerInterface $logger = null;
/**
* Array of result columns/fields.
- *
- * @var array|null
*/
protected ?array $columns = [];
/**
* DT columns definitions container (add/edit/remove/filter/order/escape).
- *
- * @var array
*/
protected array $columnDef = [
'index' => false,
@@ -64,29 +55,26 @@ abstract class DataTableAbstract implements DataTable
/**
* Extra/Added columns.
- *
- * @var array
*/
protected array $extraColumns = [];
/**
* Total records.
- *
- * @var int|null
*/
protected ?int $totalRecords = null;
/**
* Total filtered records.
- *
- * @var int|null
*/
protected ?int $filteredRecords = null;
+ /**
+ * Flag to check if the total records count should be skipped.
+ */
+ protected bool $skipTotalRecords = false;
+
/**
* Auto-filter flag.
- *
- * @var bool
*/
protected bool $autoFilter = true;
@@ -99,8 +87,6 @@ abstract class DataTableAbstract implements DataTable
/**
* DT row templates container.
- *
- * @var array
*/
protected array $templates = [
'DT_RowId' => '',
@@ -118,44 +104,32 @@ abstract class DataTableAbstract implements DataTable
/**
* Skip pagination as needed.
- *
- * @var bool
*/
protected bool $skipPaging = false;
/**
* Array of data to append on json response.
- *
- * @var array
*/
protected array $appends = [];
- /**
- * @var \Yajra\DataTables\Utilities\Config
- */
protected Utilities\Config $config;
- /**
- * @var mixed
- */
protected mixed $serializer;
- /**
- * @var array
- */
protected array $searchPanes = [];
protected mixed $transformer;
protected bool $editOnlySelectedColumns = false;
+ protected int $minSearchLength = 0;
+
/**
* Can the DataTable engine be created with these parameters.
*
- * @param mixed $source
* @return bool
*/
- public static function canCreate($source)
+ public static function canCreate(mixed $source)
{
return false;
}
@@ -163,10 +137,9 @@ public static function canCreate($source)
/**
* Factory method, create and return an instance for the DataTable engine.
*
- * @param mixed $source
* @return static
*/
- public static function create($source)
+ public static function create(mixed $source)
{
return new static($source);
}
@@ -194,9 +167,7 @@ public function formatColumn($columns, $formatter): static
foreach ((array) $columns as $column) {
$this->addColumn(
$column.'_formatted',
- function ($row) use ($column, $formatter) {
- return $formatter(data_get($row, $column), $row);
- }
+ fn ($row) => $formatter(data_get($row, $column), $row)
);
}
@@ -206,9 +177,7 @@ function ($row) use ($column, $formatter) {
foreach ((array) $columns as $column) {
$this->addColumn(
$column.'_formatted',
- function ($row) use ($column) {
- return data_get($row, $column);
- }
+ fn ($row) => data_get($row, $column)
);
}
@@ -292,8 +261,6 @@ public function removeColumn(): static
/**
* Get columns definition.
- *
- * @return array
*/
protected function getColumnsDefinition(): array
{
@@ -306,7 +273,6 @@ protected function getColumnsDefinition(): array
/**
* Get only selected columns in response.
*
- * @param array $columns
* @return $this
*/
public function only(array $columns = []): static
@@ -332,7 +298,6 @@ public function escapeColumns($columns = '*'): static
/**
* Add a makeHidden() to the row object.
*
- * @param array $attributes
* @return $this
*/
public function makeHidden(array $attributes = []): static
@@ -346,7 +311,6 @@ public function makeHidden(array $attributes = []): static
/**
* Add a makeVisible() to the row object.
*
- * @param array $attributes
* @return $this
*/
public function makeVisible(array $attributes = []): static
@@ -361,7 +325,6 @@ public function makeVisible(array $attributes = []): static
* Set columns that should not be escaped.
* Optionally merge the defaults from config.
*
- * @param array $columns
* @param bool $merge
* @return $this
*/
@@ -410,7 +373,6 @@ public function setRowId($content): static
/**
* Set DT_RowData templates.
*
- * @param array $data
* @return $this
*/
public function setRowData(array $data): static
@@ -438,7 +400,6 @@ public function addRowData($key, $value): static
* Set DT_RowAttr templates.
* result: .
*
- * @param array $data
* @return $this
*/
public function setRowAttr(array $data): static
@@ -465,11 +426,9 @@ public function addRowAttr($key, $value): static
/**
* Append data on json response.
*
- * @param mixed $key
- * @param mixed $value
* @return $this
*/
- public function with($key, $value = ''): static
+ public function with(mixed $key, mixed $value = ''): static
{
if (is_array($key)) {
$this->appends = $key;
@@ -483,8 +442,6 @@ public function with($key, $value = ''): static
/**
* Add with query callback value on response.
*
- * @param string $key
- * @param callable $value
* @return $this
*/
public function withQuery(string $key, callable $value): static
@@ -497,7 +454,6 @@ public function withQuery(string $key, callable $value): static
/**
* Override default ordering method with a closure callback.
*
- * @param callable $closure
* @return $this
*/
public function order(callable $closure): static
@@ -510,7 +466,6 @@ public function order(callable $closure): static
/**
* Update list of columns that is not allowed for search/sort.
*
- * @param array $blacklist
* @return $this
*/
public function blacklist(array $blacklist): static
@@ -523,7 +478,6 @@ public function blacklist(array $blacklist): static
/**
* Update list of columns that is allowed for search/sort.
*
- * @param string|array $whitelist
* @return $this
*/
public function whitelist(array|string $whitelist = '*'): static
@@ -536,7 +490,6 @@ public function whitelist(array|string $whitelist = '*'): static
/**
* Set smart search config at runtime.
*
- * @param bool $state
* @return $this
*/
public function smart(bool $state = true): static
@@ -549,7 +502,6 @@ public function smart(bool $state = true): static
/**
* Set starts_with search config at runtime.
*
- * @param bool $state
* @return $this
*/
public function startsWithSearch(bool $state = true): static
@@ -562,7 +514,6 @@ public function startsWithSearch(bool $state = true): static
/**
* Set multi_term search config at runtime.
*
- * @param bool $multiTerm
* @return $this
*/
public function setMultiTerm(bool $multiTerm = true): static
@@ -575,7 +526,6 @@ public function setMultiTerm(bool $multiTerm = true): static
/**
* Set total records manually.
*
- * @param int $total
* @return $this
*/
public function setTotalRecords(int $total): static
@@ -590,12 +540,11 @@ public function setTotalRecords(int $total): static
* This will improve the performance by skipping the total count query.
*
* @return $this
- *
- * @deprecated Just use setTotalRecords instead.
*/
public function skipTotalRecords(): static
{
$this->totalRecords = 0;
+ $this->skipTotalRecords = true;
return $this;
}
@@ -603,7 +552,6 @@ public function skipTotalRecords(): static
/**
* Set filtered records manually.
*
- * @param int $total
* @return $this
*/
public function setFilteredRecords(int $total): static
@@ -656,7 +604,6 @@ public function pushToBlacklist($column): static
* Check if column is blacklisted.
*
* @param string $column
- * @return bool
*/
protected function isBlacklisted($column): bool
{
@@ -675,8 +622,6 @@ protected function isBlacklisted($column): bool
/**
* Perform sorting of columns.
- *
- * @return void
*/
public function ordering(): void
{
@@ -696,8 +641,6 @@ abstract protected function resolveCallbackParameter();
/**
* Perform default query orderBy clause.
- *
- * @return void
*/
abstract protected function defaultOrdering(): void;
@@ -705,11 +648,9 @@ abstract protected function defaultOrdering(): void;
* Set auto filter off and run your own filter.
* Overrides global search.
*
- * @param callable $callback
- * @param bool $globalSearch
* @return $this
*/
- public function filter(callable $callback, $globalSearch = false): static
+ public function filter(callable $callback, bool $globalSearch = false): self
{
$this->autoFilter = $globalSearch;
$this->filterCallback = $callback;
@@ -736,11 +677,9 @@ public function toJson($options = 0)
* Add a search pane options on response.
*
* @param string $column
- * @param mixed $options
- * @param callable|null $builder
* @return $this
*/
- public function searchPane($column, $options, callable $builder = null): static
+ public function searchPane($column, mixed $options, ?callable $builder = null): static
{
$options = value($options);
@@ -756,18 +695,29 @@ public function searchPane($column, $options, callable $builder = null): static
/**
* Convert instance to array.
- *
- * @return array
*/
public function toArray(): array
{
return (array) $this->make()->getData(true);
}
+ /**
+ * Count total items.
+ */
+ public function totalCount(): int
+ {
+ return $this->totalRecords ??= $this->count();
+ }
+
+ public function editOnlySelectedColumns(): static
+ {
+ $this->editOnlySelectedColumns = true;
+
+ return $this;
+ }
+
/**
* Perform necessary filters.
- *
- * @return void
*/
protected function filterRecords(): void
{
@@ -780,14 +730,18 @@ protected function filterRecords(): void
}
$this->columnSearch();
+ $this->columnControlSearch();
$this->searchPanesSearch();
$this->filteredCount();
}
+ public function columnControlSearch(): void
+ {
+ // Not implemented in the abstract class.
+ }
+
/**
* Perform global search.
- *
- * @return void
*/
public function filtering(): void
{
@@ -807,14 +761,11 @@ public function filtering(): void
* individual words and searches for each of them.
*
* @param string $keyword
- * @return void
*/
protected function smartGlobalSearch($keyword): void
{
collect(explode(' ', $keyword))
- ->reject(function ($keyword) {
- return trim($keyword) === '';
- })
+ ->reject(fn ($keyword) => trim((string) $keyword) === '')
->each(function ($keyword) {
$this->globalSearch($keyword);
});
@@ -822,46 +773,27 @@ protected function smartGlobalSearch($keyword): void
/**
* Perform global search for the given keyword.
- *
- * @param string $keyword
- * @return void
*/
abstract protected function globalSearch(string $keyword): void;
/**
* Perform search using search pane values.
- *
- * @return void
*/
protected function searchPanesSearch(): void
{
// Add support for search pane.
}
- /**
- * Count total items.
- *
- * @return int
- */
- public function totalCount(): int
- {
- return $this->totalRecords ??= $this->count();
- }
-
/**
* Count filtered items.
- *
- * @return int
*/
- protected function filteredCount(): int
+ public function filteredCount(): int
{
return $this->filteredRecords ??= $this->count();
}
/**
* Apply pagination.
- *
- * @return void
*/
protected function paginate(): void
{
@@ -875,7 +807,6 @@ protected function paginate(): void
*
* @param iterable $results
* @param array $processed
- * @return array
*/
protected function transform($results, $processed): array
{
@@ -895,7 +826,6 @@ protected function transform($results, $processed): array
*
* @param iterable $results
* @param bool $object
- * @return array
*
* @throws \Exception
*/
@@ -913,9 +843,6 @@ protected function processResults($results, $object = false): array
/**
* Render json response.
- *
- * @param array $data
- * @return \Illuminate\Http\JsonResponse
*/
protected function render(array $data): JsonResponse
{
@@ -944,9 +871,6 @@ protected function render(array $data): JsonResponse
/**
* Attach custom with meta on response.
- *
- * @param array $data
- * @return array
*/
protected function attachAppends(array $data): array
{
@@ -955,9 +879,6 @@ protected function attachAppends(array $data): array
/**
* Append debug parameters on output.
- *
- * @param array $output
- * @return array
*/
protected function showDebugger(array $output): array
{
@@ -969,12 +890,9 @@ protected function showDebugger(array $output): array
/**
* Return an error json response.
*
- * @param \Exception $exception
- * @return \Illuminate\Http\JsonResponse
- *
* @throws \Yajra\DataTables\Exceptions\Exception|\Exception
*/
- protected function errorResponse(\Exception $exception)
+ protected function errorResponse(\Exception $exception): JsonResponse
{
/** @var string $error */
$error = $this->config->get('datatables.error');
@@ -991,7 +909,7 @@ protected function errorResponse(\Exception $exception)
'recordsTotal' => $this->totalRecords,
'recordsFiltered' => 0,
'data' => [],
- 'error' => $error ? __($error) : "Exception Message:\n\n".$exception->getMessage(),
+ 'error' => $error ? __($error) : 'Exception Message:'.PHP_EOL.PHP_EOL.$exception->getMessage(),
]);
}
@@ -1010,7 +928,6 @@ public function getLogger()
/**
* Set monolog/logger instance.
*
- * @param \Psr\Log\LoggerInterface $logger
* @return $this
*/
public function setLogger(LoggerInterface $logger): static
@@ -1022,9 +939,6 @@ public function setLogger(LoggerInterface $logger): static
/**
* Setup search keyword.
- *
- * @param string $value
- * @return string
*/
protected function setupKeyword(string $value): string
{
@@ -1042,11 +956,7 @@ protected function setupKeyword(string $value): string
}
/**
- * Get column name to be use for filtering and sorting.
- *
- * @param int $index
- * @param bool $wantsAlias
- * @return string|null
+ * Get column name to be used for filtering and sorting.
*/
protected function getColumnName(int $index, bool $wantsAlias = false): ?string
{
@@ -1070,9 +980,6 @@ protected function getColumnName(int $index, bool $wantsAlias = false): ?string
/**
* Get column name by order column index.
- *
- * @param int $index
- * @return string
*/
protected function getColumnNameByIndex(int $index): string
{
@@ -1085,18 +992,31 @@ protected function getColumnNameByIndex(int $index): string
/**
* If column name could not be resolved then use primary key.
- *
- * @return string
*/
protected function getPrimaryKeyName(): string
{
return 'id';
}
- public function editOnlySelectedColumns(): static
+ public function minSearchLength(int $length): static
{
- $this->editOnlySelectedColumns = true;
+ $this->minSearchLength = $length;
return $this;
}
+
+ protected function validateMinLengthSearch(): void
+ {
+ if ($this->request->isSearchable()
+ && $this->minSearchLength > 0
+ && Str::length($this->request->keyword()) < $this->minSearchLength
+ ) {
+ $this->totalRecords = 0;
+ $this->filteredRecords = 0;
+ throw new \Exception(
+ __('Please enter at least :length characters to search.', ['length' => $this->minSearchLength]),
+ 400
+ );
+ }
+ }
}
diff --git a/src/DataTables.php b/src/DataTables.php
index 2a45f909..724b44b3 100644
--- a/src/DataTables.php
+++ b/src/DataTables.php
@@ -6,7 +6,8 @@
use Illuminate\Contracts\Database\Query\Builder as QueryBuilder;
use Illuminate\Support\Traits\Macroable;
use Yajra\DataTables\Exceptions\Exception;
-use Yajra\DataTables\Html\Builder;
+use Yajra\DataTables\Utilities\Config;
+use Yajra\DataTables\Utilities\Request;
class DataTables
{
@@ -14,18 +15,9 @@ class DataTables
/**
* DataTables request object.
- *
- * @var \Yajra\DataTables\Utilities\Request
*/
protected Utilities\Request $request;
- /**
- * HTML builder instance.
- *
- * @var \Yajra\DataTables\Html\Builder|null
- */
- protected ?Builder $html = null;
-
/**
* Make a DataTable instance from source.
* Alias of make for backward compatibility.
@@ -81,38 +73,33 @@ public static function make($source)
}
}
- throw new Exception('No available engine for '.get_class($source));
+ throw new Exception('No available engine for '.$source::class);
}
/**
* Get request object.
- *
- * @return \Yajra\DataTables\Utilities\Request
*/
- public function getRequest()
+ public function getRequest(): Request
{
return app('datatables.request');
}
/**
* Get config instance.
- *
- * @return \Yajra\DataTables\Utilities\Config
*/
- public function getConfig()
+ public function getConfig(): Config
{
return app('datatables.config');
}
/**
- * DataTables using Query.
+ * DataTables using query builder.
*
- * @param QueryBuilder $builder
- * @return \Yajra\DataTables\QueryDataTable
+ * @throws \Yajra\DataTables\Exceptions\Exception
*/
public function query(QueryBuilder $builder): QueryDataTable
{
- /** @var string */
+ /** @var string $dataTable */
$dataTable = config('datatables.engines.query');
$this->validateDataTable($dataTable, QueryDataTable::class);
@@ -123,12 +110,11 @@ public function query(QueryBuilder $builder): QueryDataTable
/**
* DataTables using Eloquent Builder.
*
- * @param \Illuminate\Contracts\Database\Eloquent\Builder $builder
- * @return \Yajra\DataTables\EloquentDataTable
+ * @throws \Yajra\DataTables\Exceptions\Exception
*/
public function eloquent(EloquentBuilder $builder): EloquentDataTable
{
- /** @var string */
+ /** @var string $dataTable */
$dataTable = config('datatables.engines.eloquent');
$this->validateDataTable($dataTable, EloquentDataTable::class);
@@ -140,11 +126,12 @@ public function eloquent(EloquentBuilder $builder): EloquentDataTable
* DataTables using Collection.
*
* @param \Illuminate\Support\Collection|array $collection
- * @return \Yajra\DataTables\CollectionDataTable
+ *
+ * @throws \Yajra\DataTables\Exceptions\Exception
*/
public function collection($collection): CollectionDataTable
{
- /** @var string */
+ /** @var string $dataTable */
$dataTable = config('datatables.engines.collection');
$this->validateDataTable($dataTable, CollectionDataTable::class);
@@ -164,44 +151,12 @@ public function resource($resource)
}
/**
- * Get html builder instance.
- *
- * @return \Yajra\DataTables\Html\Builder
- *
- * @throws \Yajra\DataTables\Exceptions\Exception
- */
- public function getHtmlBuilder()
- {
- if (! class_exists(Builder::class)) {
- throw new Exception('Please install yajra/laravel-datatables-html to be able to use this function.');
- }
-
- return $this->html ?: $this->html = app('datatables.html');
- }
-
- /**
- * @param string $engine
- * @param string $parent
- * @return void
- *
* @throws \Yajra\DataTables\Exceptions\Exception
*/
public function validateDataTable(string $engine, string $parent): void
{
if (! ($engine == $parent || is_subclass_of($engine, $parent))) {
- $this->throwInvalidEngineException($engine, $parent);
+ throw new Exception("The given datatable engine `$engine` is not compatible with `$parent`.");
}
}
-
- /**
- * @param string $engine
- * @param string $parent
- * @return void
- *
- * @throws \Yajra\DataTables\Exceptions\Exception
- */
- public function throwInvalidEngineException(string $engine, string $parent): void
- {
- throw new Exception("The given datatable engine `{$engine}` is not compatible with `{$parent}`.");
- }
}
diff --git a/src/DataTablesServiceProvider.php b/src/DataTablesServiceProvider.php
index 2b883663..59d29e24 100644
--- a/src/DataTablesServiceProvider.php
+++ b/src/DataTablesServiceProvider.php
@@ -23,13 +23,9 @@ public function register()
$this->setupAssets();
$this->app->alias('datatables', DataTables::class);
- $this->app->singleton('datatables', function () {
- return new DataTables;
- });
+ $this->app->singleton('datatables', fn () => new DataTables);
- $this->app->singleton('datatables.request', function () {
- return new Request;
- });
+ $this->app->singleton('datatables.request', fn () => new Request);
$this->app->singleton('datatables.config', Config::class);
}
@@ -49,7 +45,7 @@ public function boot()
DataTables::macro($engine, function () use ($class) {
$canCreate = [$class, 'canCreate'];
if (is_callable($canCreate) && ! call_user_func_array($canCreate, func_get_args())) {
- throw new \InvalidArgumentException();
+ throw new \InvalidArgumentException;
}
$create = [$class, 'create'];
diff --git a/src/EloquentDataTable.php b/src/EloquentDataTable.php
index fdd09937..a7783d57 100644
--- a/src/EloquentDataTable.php
+++ b/src/EloquentDataTable.php
@@ -17,10 +17,14 @@
*/
class EloquentDataTable extends QueryDataTable
{
+ /**
+ * Flag to enable the generation of unique table aliases on eagerly loaded join columns.
+ * You may want to enable it if you encounter a "Not unique table/alias" error when performing a search or applying ordering.
+ */
+ protected bool $enableEagerJoinAliases = false;
+
/**
* EloquentEngine constructor.
- *
- * @param Model|EloquentBuilder $model
*/
public function __construct(Model|EloquentBuilder $model)
{
@@ -39,7 +43,6 @@ public function __construct(Model|EloquentBuilder $model)
* Can the DataTable engine be created with these parameters.
*
* @param mixed $source
- * @return bool
*/
public static function canCreate($source): bool
{
@@ -49,7 +52,6 @@ public static function canCreate($source): bool
/**
* Add columns in collection.
*
- * @param array $names
* @param bool|int $order
* @return $this
*/
@@ -60,9 +62,7 @@ public function addColumns(array $names, $order = false)
$name = $attribute;
}
- $this->addColumn($name, function ($model) use ($attribute) {
- return $model->getAttribute($attribute);
- }, is_int($order) ? $order++ : $order);
+ $this->addColumn($name, fn ($model) => $model->getAttribute($attribute), is_int($order) ? $order++ : $order);
}
return $this;
@@ -70,8 +70,6 @@ public function addColumns(array $names, $order = false)
/**
* If column name could not be resolved then use primary key.
- *
- * @return string
*/
protected function getPrimaryKeyName(): string
{
@@ -79,7 +77,7 @@ protected function getPrimaryKeyName(): string
}
/**
- * @inheritDoc
+ * {@inheritDoc}
*/
protected function compileQuerySearch($query, string $column, string $keyword, string $boolean = 'or', bool $nested = false): void
{
@@ -163,10 +161,7 @@ protected function isMorphRelation($relation)
}
/**
- * Resolve the proper column name be used.
- *
- * @param string $column
- * @return string
+ * {@inheritDoc}
*
* @throws \Yajra\DataTables\Exceptions\Exception
*/
@@ -174,10 +169,10 @@ protected function resolveRelationColumn(string $column): string
{
$parts = explode('.', $column);
$columnName = array_pop($parts);
- $relation = implode('.', $parts);
+ $relation = preg_replace('/\[.*?\]/', '', implode('.', $parts));
if ($this->isNotEagerLoaded($relation)) {
- return $column;
+ return parent::resolveRelationColumn($column);
}
return $this->joinEagerLoadedColumn($relation, $columnName);
@@ -194,64 +189,107 @@ protected function resolveRelationColumn(string $column): string
*/
protected function joinEagerLoadedColumn($relation, $relationColumn)
{
- $table = '';
+ $tableAlias = $pivotAlias = '';
$lastQuery = $this->query;
foreach (explode('.', $relation) as $eachRelation) {
$model = $lastQuery->getRelation($eachRelation);
+ if ($this->enableEagerJoinAliases) {
+ $lastAlias = $tableAlias ?: $this->getTablePrefix($lastQuery);
+ $tableAlias = $tableAlias.'_'.$eachRelation;
+ $pivotAlias = $tableAlias.'_pivot';
+ } else {
+ $lastAlias = $tableAlias ?: $lastQuery->getModel()->getTable();
+ }
switch (true) {
case $model instanceof BelongsToMany:
- $pivot = $model->getTable();
- $pivotPK = $model->getExistenceCompareKey();
- $pivotFK = $model->getQualifiedParentKeyName();
+ if ($this->enableEagerJoinAliases) {
+ $pivot = $model->getTable().' as '.$pivotAlias;
+ } else {
+ $pivot = $pivotAlias = $model->getTable();
+ }
+ $pivotPK = $pivotAlias.'.'.$model->getForeignPivotKeyName();
+ $pivotFK = ltrim($lastAlias.'.'.$model->getParentKeyName(), '.');
$this->performJoin($pivot, $pivotPK, $pivotFK);
$related = $model->getRelated();
- $table = $related->getTable();
+ if ($this->enableEagerJoinAliases) {
+ $table = $related->getTable().' as '.$tableAlias;
+ } else {
+ $table = $tableAlias = $related->getTable();
+ }
$tablePK = $model->getRelatedPivotKeyName();
- $foreign = $pivot.'.'.$tablePK;
- $other = $related->getQualifiedKeyName();
+ $foreign = $pivotAlias.'.'.$tablePK;
+ $other = $tableAlias.'.'.$related->getKeyName();
- $lastQuery->addSelect($table.'.'.$relationColumn);
- $this->performJoin($table, $foreign, $other);
+ $lastQuery->addSelect($tableAlias.'.'.$relationColumn);
break;
case $model instanceof HasOneThrough:
- $pivot = explode('.', $model->getQualifiedParentKeyName())[0]; // extract pivot table from key
- $pivotPK = $pivot.'.'.$model->getFirstKeyName();
- $pivotFK = $model->getQualifiedLocalKeyName();
+ if ($this->enableEagerJoinAliases) {
+ $pivot = explode('.', $model->getQualifiedParentKeyName())[0].' as '.$pivotAlias;
+ } else {
+ $pivot = $pivotAlias = explode('.', $model->getQualifiedParentKeyName())[0];
+ }
+ $pivotPK = $pivotAlias.'.'.$model->getFirstKeyName();
+ $pivotFK = ltrim($lastAlias.'.'.$model->getLocalKeyName(), '.');
$this->performJoin($pivot, $pivotPK, $pivotFK);
$related = $model->getRelated();
- $table = $related->getTable();
+ if ($this->enableEagerJoinAliases) {
+ $table = $related->getTable().' as '.$tableAlias;
+ } else {
+ $table = $tableAlias = $related->getTable();
+ }
$tablePK = $model->getSecondLocalKeyName();
- $foreign = $pivot.'.'.$tablePK;
- $other = $related->getQualifiedKeyName();
+ $foreign = $pivotAlias.'.'.$tablePK;
+ $other = $tableAlias.'.'.$related->getKeyName();
$lastQuery->addSelect($lastQuery->getModel()->getTable().'.*');
break;
case $model instanceof HasOneOrMany:
- $table = $model->getRelated()->getTable();
- $foreign = $model->getQualifiedForeignKeyName();
- $other = $model->getQualifiedParentKeyName();
+ if ($this->enableEagerJoinAliases) {
+ $table = $model->getRelated()->getTable().' as '.$tableAlias;
+ } else {
+ $table = $tableAlias = $model->getRelated()->getTable();
+ }
+ $foreign = $tableAlias.'.'.$model->getForeignKeyName();
+ $other = ltrim($lastAlias.'.'.$model->getLocalKeyName(), '.');
break;
case $model instanceof BelongsTo:
- $table = $model->getRelated()->getTable();
- $foreign = $model->getQualifiedForeignKeyName();
- $other = $model->getQualifiedOwnerKeyName();
+ if ($this->enableEagerJoinAliases) {
+ $table = $model->getRelated()->getTable().' as '.$tableAlias;
+ } else {
+ $table = $tableAlias = $model->getRelated()->getTable();
+ }
+ $foreign = ltrim($lastAlias.'.'.$model->getForeignKeyName(), '.');
+ $other = $tableAlias.'.'.$model->getOwnerKeyName();
break;
default:
- throw new Exception('Relation '.get_class($model).' is not yet supported.');
+ throw new Exception('Relation '.$model::class.' is not yet supported.');
}
$this->performJoin($table, $foreign, $other);
$lastQuery = $model->getQuery();
}
- return $table.'.'.$relationColumn;
+ return $tableAlias.'.'.$relationColumn;
+ }
+
+ /**
+ * Enable the generation of unique table aliases on eagerly loaded join columns.
+ * You may want to enable it if you encounter a "Not unique table/alias" error when performing a search or applying ordering.
+ *
+ * @return $this
+ */
+ public function enableEagerJoinAliases(): static
+ {
+ $this->enableEagerJoinAliases = true;
+
+ return $this;
}
/**
@@ -261,12 +299,12 @@ protected function joinEagerLoadedColumn($relation, $relationColumn)
* @param string $foreign
* @param string $other
* @param string $type
- * @return void
*/
protected function performJoin($table, $foreign, $other, $type = 'left'): void
{
$joins = [];
- foreach ((array) $this->getBaseQueryBuilder()->joins as $key => $join) {
+ $builder = $this->getBaseQueryBuilder();
+ foreach ($builder->joins ?? [] as $join) {
$joins[] = $join->table;
}
diff --git a/src/Exceptions/Exception.php b/src/Exceptions/Exception.php
index e5e27dca..1f698541 100644
--- a/src/Exceptions/Exception.php
+++ b/src/Exceptions/Exception.php
@@ -2,6 +2,4 @@
namespace Yajra\DataTables\Exceptions;
-class Exception extends \Exception
-{
-}
+class Exception extends \Exception {}
diff --git a/src/Processors/DataProcessor.php b/src/Processors/DataProcessor.php
index de7e3f2a..e2ae3087 100644
--- a/src/Processors/DataProcessor.php
+++ b/src/Processors/DataProcessor.php
@@ -9,13 +9,6 @@
class DataProcessor
{
- /**
- * @var int
- */
- protected int $start;
- /**
- * @var array
- */
protected array $output = [];
/**
@@ -28,14 +21,6 @@ class DataProcessor
*/
protected array $editColumns = [];
- /**
- * @var array
- */
- protected array $templates = [];
-
- /**
- * @var array
- */
protected array $rawColumns = [];
/**
@@ -43,24 +28,12 @@ class DataProcessor
*/
protected array $exceptions = ['DT_RowId', 'DT_RowClass', 'DT_RowData', 'DT_RowAttr'];
- /**
- * @var array
- */
protected array $onlyColumns = [];
- /**
- * @var array
- */
protected array $makeHidden = [];
- /**
- * @var array
- */
protected array $makeVisible = [];
- /**
- * @var array
- */
protected array $excessColumns = [];
/**
@@ -68,30 +41,12 @@ class DataProcessor
*/
protected mixed $escapeColumns = [];
- /**
- * @var iterable
- */
- protected iterable $results;
-
- /**
- * @var bool
- */
protected bool $includeIndex = false;
- /**
- * @var bool
- */
protected bool $ignoreGetters = false;
- /**
- * @param iterable $results
- * @param array $columnDef
- * @param array $templates
- * @param int $start
- */
- public function __construct($results, array $columnDef, array $templates, int $start = 0)
+ public function __construct(protected iterable $results, array $columnDef, protected array $templates, protected int $start = 0)
{
- $this->results = $results;
$this->appendColumns = $columnDef['append'] ?? [];
$this->editColumns = $columnDef['edit'] ?? [];
$this->excessColumns = $columnDef['excess'] ?? [];
@@ -102,15 +57,12 @@ public function __construct($results, array $columnDef, array $templates, int $s
$this->makeHidden = $columnDef['hidden'] ?? [];
$this->makeVisible = $columnDef['visible'] ?? [];
$this->ignoreGetters = $columnDef['ignore_getters'] ?? false;
- $this->templates = $templates;
- $this->start = $start;
}
/**
* Process data to output on browser.
*
* @param bool $object
- * @return array
*/
public function process($object = false): array
{
@@ -138,9 +90,7 @@ public function process($object = false): array
/**
* Process add columns.
*
- * @param array $data
* @param array|object|\Illuminate\Database\Eloquent\Model $row
- * @return array
*/
protected function addColumns(array $data, $row): array
{
@@ -165,10 +115,6 @@ protected function addColumns(array $data, $row): array
/**
* Process edit columns.
- *
- * @param array $data
- * @param array|object $row
- * @return array
*/
protected function editColumns(array $data, object|array $row): array
{
@@ -182,10 +128,6 @@ protected function editColumns(array $data, object|array $row): array
/**
* Setup additional DT row variables.
- *
- * @param array $data
- * @param array|object $row
- * @return array
*/
protected function setupRowVariables(array $data, object|array $row): array
{
@@ -201,9 +143,6 @@ protected function setupRowVariables(array $data, object|array $row): array
/**
* Get only needed columns.
- *
- * @param array $data
- * @return array
*/
protected function selectOnlyNeededColumns(array $data): array
{
@@ -226,9 +165,6 @@ protected function selectOnlyNeededColumns(array $data): array
/**
* Remove declared hidden columns.
- *
- * @param array $data
- * @return array
*/
protected function removeExcessColumns(array $data): array
{
@@ -241,9 +177,6 @@ protected function removeExcessColumns(array $data): array
/**
* Flatten array with exceptions.
- *
- * @param array $array
- * @return array
*/
public function flatten(array $array): array
{
@@ -261,9 +194,6 @@ public function flatten(array $array): array
/**
* Escape column values as declared.
- *
- * @param array $output
- * @return array
*/
protected function escapeColumns(array $output): array
{
@@ -285,9 +215,6 @@ protected function escapeColumns(array $output): array
/**
* Escape all string or Htmlable values of row.
- *
- * @param array $row
- * @return array
*/
protected function escapeRow(array $row): array
{
diff --git a/src/Processors/RowProcessor.php b/src/Processors/RowProcessor.php
index 315b7216..b402b419 100644
--- a/src/Processors/RowProcessor.php
+++ b/src/Processors/RowProcessor.php
@@ -8,12 +8,9 @@
class RowProcessor
{
/**
- * @param array $data
* @param array|object $row
*/
- public function __construct(protected array $data, protected $row)
- {
- }
+ public function __construct(protected array $data, protected $row) {}
/**
* Process DT RowId and Class value.
@@ -21,6 +18,8 @@ public function __construct(protected array $data, protected $row)
* @param string $attribute
* @param string|callable $template
* @return $this
+ *
+ * @throws \ReflectionException
*/
public function rowValue($attribute, $template)
{
@@ -39,8 +38,9 @@ public function rowValue($attribute, $template)
* Process DT Row Data and Attr.
*
* @param string $attribute
- * @param array $template
* @return $this
+ *
+ * @throws \ReflectionException
*/
public function rowData($attribute, array $template)
{
diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php
index b4beeafe..01cd5b71 100644
--- a/src/QueryDataTable.php
+++ b/src/QueryDataTable.php
@@ -8,6 +8,7 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Query\Expression;
use Illuminate\Http\JsonResponse;
+use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
@@ -15,24 +16,13 @@
class QueryDataTable extends DataTableAbstract
{
- /**
- * Builder object.
- *
- * @var QueryBuilder
- */
- protected QueryBuilder $query;
-
/**
* Flag for ordering NULLS LAST option.
- *
- * @var bool
*/
protected bool $nullsLast = false;
/**
* Flag to check if query preparation was already done.
- *
- * @var bool
*/
protected bool $prepared = false;
@@ -45,29 +35,21 @@ class QueryDataTable extends DataTableAbstract
/**
* Flag to keep the select bindings.
- *
- * @var bool
*/
protected bool $keepSelectBindings = false;
/**
* Flag to ignore the selects in count query.
- *
- * @var bool
*/
protected bool $ignoreSelectInCountQuery = false;
/**
* Enable scout search and use this model for searching.
- *
- * @var Model|null
*/
protected ?Model $scoutModel = null;
/**
* Maximum number of hits to return from scout.
- *
- * @var int
*/
protected int $scoutMaxHits = 1000;
@@ -80,51 +62,43 @@ class QueryDataTable extends DataTableAbstract
/**
* Flag if scout search was performed.
- *
- * @var bool
*/
protected bool $scoutSearched = false;
/**
* Scout index name.
- *
- * @var string
*/
protected string $scoutIndex;
/**
* Scout key name.
- *
- * @var string
*/
protected string $scoutKey;
/**
* Flag to disable user ordering if a fixed ordering was performed (e.g. scout search).
* Only works with corresponding javascript listener.
- *
- * @var bool
*/
- protected $disableUserOrdering = false;
+ protected bool $disableUserOrdering = false;
/**
- * @param QueryBuilder $builder
+ * Paginated results.
+ *
+ * @var Collection
*/
- public function __construct(QueryBuilder $builder)
+ protected Collection $results;
+
+ public function __construct(protected QueryBuilder $query)
{
- $this->query = $builder;
$this->request = app('datatables.request');
$this->config = app('datatables.config');
- $this->columns = $builder->columns;
+ $this->columns = $this->query->getColumns();
if ($this->config->isDebugging()) {
$this->getConnection()->enableQueryLog();
}
}
- /**
- * @return \Illuminate\Database\Connection
- */
public function getConnection(): Connection
{
/** @var Connection $connection */
@@ -137,7 +111,6 @@ public function getConnection(): Connection
* Can the DataTable engine be created with these parameters.
*
* @param mixed $source
- * @return bool
*/
public static function canCreate($source): bool
{
@@ -147,14 +120,13 @@ public static function canCreate($source): bool
/**
* Organizes works.
*
- * @param bool $mDataSupport
- * @return \Illuminate\Http\JsonResponse
- *
* @throws \Exception
*/
- public function make($mDataSupport = true): JsonResponse
+ public function make(bool $mDataSupport = true): JsonResponse
{
try {
+ $this->validateMinLengthSearch();
+
$results = $this->prepareQuery()->results();
$processed = $this->processResults($results, $mDataSupport);
$data = $this->transform($results, $processed);
@@ -168,11 +140,11 @@ public function make($mDataSupport = true): JsonResponse
/**
* Get paginated results.
*
- * @return \Illuminate\Support\Collection
+ * @return Collection
*/
public function results(): Collection
{
- return $this->query->get();
+ return $this->results ??= $this->query->get();
}
/**
@@ -197,8 +169,6 @@ public function prepareQuery(): static
/**
* Counts current query.
- *
- * @return int
*/
public function count(): int
{
@@ -207,8 +177,6 @@ public function count(): int
/**
* Prepare count query builder.
- *
- * @return QueryBuilder
*/
public function prepareCountQuery(): QueryBuilder
{
@@ -216,23 +184,28 @@ public function prepareCountQuery(): QueryBuilder
if ($this->isComplexQuery($builder)) {
$builder->select(DB::raw('1 as dt_row_count'));
- if ($this->ignoreSelectInCountQuery || ! $this->isComplexQuery($builder)) {
+ $clone = $builder->clone();
+ $clone->setBindings([]);
+ if ($clone instanceof EloquentBuilder) {
+ $clone->getQuery()->wheres = [];
+ } else {
+ $clone->wheres = [];
+ }
+
+ if ($this->isComplexQuery($clone)) {
+ if (! $this->ignoreSelectInCountQuery) {
+ $builder = clone $this->query;
+ }
+
return $this->getConnection()
->query()
->fromRaw('('.$builder->toSql().') count_row_table')
->setBindings($builder->getBindings());
}
-
- $builder = clone $this->query;
-
- return $this->getConnection()
- ->query()
- ->fromRaw('('.$builder->toSql().') count_row_table')
- ->setBindings($builder->getBindings());
}
-
$row_count = $this->wrap('row_count');
$builder->select($this->getConnection()->raw("'1' as {$row_count}"));
+
if (! $this->keepSelectBindings) {
$builder->setBindings([], 'select');
}
@@ -244,7 +217,6 @@ public function prepareCountQuery(): QueryBuilder
* Check if builder query uses complex sql.
*
* @param QueryBuilder|EloquentBuilder $query
- * @return bool
*/
protected function isComplexQuery($query): bool
{
@@ -253,9 +225,6 @@ protected function isComplexQuery($query): bool
/**
* Wrap column with DB grammar.
- *
- * @param string $column
- * @return string
*/
protected function wrap(string $column): string
{
@@ -276,8 +245,6 @@ public function keepSelectBindings(): static
/**
* Perform column search.
- *
- * @return void
*/
protected function filterRecords(): void
{
@@ -292,53 +259,162 @@ protected function filterRecords(): void
}
$this->columnSearch();
+ $this->columnControlSearch();
$this->searchPanesSearch();
// If no modification between the original query and the filtered one has been made
// the filteredRecords equals the totalRecords
- if ($this->query == $initialQuery) {
+ if (! $this->skipTotalRecords && $this->query == $initialQuery) {
$this->filteredRecords ??= $this->totalRecords;
} else {
$this->filteredCount();
+
+ if ($this->skipTotalRecords) {
+ $this->totalRecords = $this->filteredRecords;
+ }
}
}
/**
* Perform column search.
- *
- * @return void
*/
public function columnSearch(): void
{
$columns = $this->request->columns();
foreach ($columns as $index => $column) {
- $column = $this->getColumnName($index);
+ $columnName = $this->getColumnName($index);
- if (is_null($column)) {
+ if (is_null($columnName)) {
continue;
}
- if (! $this->request->isColumnSearchable($index) || $this->isBlacklisted($column) && ! $this->hasFilterColumn($column)) {
+ if (! $this->request->isColumnSearchable($index) || $this->isBlacklisted($columnName) && ! $this->hasFilterColumn($columnName)) {
continue;
}
- if ($this->hasFilterColumn($column)) {
+ if ($this->hasFilterColumn($columnName)) {
$keyword = $this->getColumnSearchKeyword($index, true);
- $this->applyFilterColumn($this->getBaseQueryBuilder(), $column, $keyword);
+ $this->applyFilterColumn($this->getBaseQueryBuilder(), $columnName, $keyword);
} else {
- $column = $this->resolveRelationColumn($column);
+ $columnName = $this->resolveRelationColumn($columnName);
$keyword = $this->getColumnSearchKeyword($index);
- $this->compileColumnSearch($index, $column, $keyword);
+ $this->compileColumnSearch($index, $columnName, $keyword);
+ }
+ }
+ }
+
+ public function columnControlSearch(): void
+ {
+ $columns = $this->request->columns();
+
+ foreach ($columns as $index => $column) {
+ $columnName = $this->getColumnName($index);
+
+ if (is_null($columnName) || ! ($column['searchable'] ?? false)) {
+ continue;
+ }
+
+ if ($this->isBlacklisted($columnName) && ! $this->hasFilterColumn($columnName)) {
+ continue;
+ }
+
+ $columnControl = $this->request->columnControl($index);
+ $list = $columnControl['list'] ?? [];
+ $search = $columnControl['search'] ?? [];
+ $value = $search['value'] ?? '';
+ $logic = $search['logic'] ?? 'equal';
+ $mask = $search['mask'] ?? ''; // for date type
+ $type = $search['type'] ?? 'text'; // text, num, date
+
+ if ($value != '' || str_contains(strtolower($logic), 'empty') || $list) {
+ $operator = match ($logic) {
+ 'contains', 'notContains', 'starts', 'ends' => 'LIKE',
+ 'greater' => '>',
+ 'less' => '<',
+ 'greaterOrEqual' => '>=',
+ 'lessOrEqual' => '<=',
+ 'empty', 'notEmpty' => null,
+ default => '=',
+ };
+
+ switch ($logic) {
+ case 'contains':
+ case 'notContains':
+ $value = '%'.$value.'%';
+ break;
+ case 'starts':
+ $value = $value.'%';
+ break;
+ case 'ends':
+ $value = '%'.$value;
+ break;
+ }
+
+ if ($this->hasFilterColumn($columnName)) {
+ $value = $list ? implode(', ', $list) : $value;
+ $this->applyFilterColumn($this->getBaseQueryBuilder(), $columnName, $value);
+
+ continue;
+ }
+
+ // Only resolve relation after checking for a custom filter.
+ // Because the custom filter for a column might not be found, e.g., $this->hasFilterColumn($columnName)
+ // and applyFilterColumn() already resolves relations
+ $columnName = $this->resolveRelationColumn($columnName);
+
+ if ($list) {
+ if (str_contains($logic, 'not')) {
+ $this->query->whereNotIn($columnName, $list);
+ } else {
+ $this->query->whereIn($columnName, $list);
+ }
+
+ continue;
+ }
+
+ if (str_contains(strtolower($logic), 'empty')) {
+ $this->query->whereNull($columnName, not: $logic === 'notEmpty');
+
+ continue;
+ }
+
+ if ($type === 'date') {
+ try {
+ // column control replaces / with - on date value
+ if ($mask && str_contains($mask, '/')) {
+ $value = str_replace('-', '/', $value);
+ }
+
+ $value = $mask ? Carbon::createFromFormat($mask, $value) : Carbon::parse($value);
+
+ if ($logic === 'notEqual') {
+ $this->query->where(function ($q) use ($columnName, $value) {
+ $q->whereDate($columnName, '!=', $value)->orWhereNull($columnName);
+ });
+ } else {
+ $this->query->whereDate($columnName, $operator, $value);
+ }
+ } catch (\Exception) {
+ // can't parse date
+ }
+
+ continue;
+ }
+
+ if (str_contains($logic, 'not')) {
+ $this->query->whereNot($columnName, $operator, $value);
+
+ continue;
+ }
+
+ $this->query->where($columnName, $operator, $value);
}
}
}
/**
* Check if column has custom filter handler.
- *
- * @param string $columnName
- * @return bool
*/
public function hasFilterColumn(string $columnName): bool
{
@@ -347,10 +423,6 @@ public function hasFilterColumn(string $columnName): bool
/**
* Get column keyword to use for search.
- *
- * @param int $i
- * @param bool $raw
- * @return string
*/
protected function getColumnSearchKeyword(int $i, bool $raw = false): string
{
@@ -379,10 +451,6 @@ protected function getColumnNameByIndex(int $index): string
* Apply filterColumn api search.
*
* @param QueryBuilder $query
- * @param string $columnName
- * @param string $keyword
- * @param string $boolean
- * @return void
*/
protected function applyFilterColumn($query, string $columnName, string $keyword, string $boolean = 'and'): void
{
@@ -395,7 +463,7 @@ protected function applyFilterColumn($query, string $columnName, string $keyword
$builder = $this->query->newQuery();
}
- $callback($builder, $keyword);
+ $callback($builder, $keyword, fn ($column) => $this->resolveRelationColumn($column));
/** @var \Illuminate\Database\Query\Builder $baseQueryBuilder */
$baseQueryBuilder = $this->getBaseQueryBuilder($builder);
@@ -406,9 +474,8 @@ protected function applyFilterColumn($query, string $columnName, string $keyword
* Get the base query builder instance.
*
* @param QueryBuilder|EloquentBuilder|null $instance
- * @return QueryBuilder
*/
- protected function getBaseQueryBuilder($instance = null)
+ protected function getBaseQueryBuilder($instance = null): QueryBuilder
{
if (! $instance) {
$instance = $this->query;
@@ -423,8 +490,6 @@ protected function getBaseQueryBuilder($instance = null)
/**
* Get query builder instance.
- *
- * @return QueryBuilder
*/
public function getQuery(): QueryBuilder
{
@@ -432,23 +497,15 @@ public function getQuery(): QueryBuilder
}
/**
- * Resolve the proper column name be used.
- *
- * @param string $column
- * @return string
+ * Resolve the proper column name to be used.
*/
protected function resolveRelationColumn(string $column): string
{
- return $column;
+ return $this->addTablePrefix($this->query, $column);
}
/**
* Compile queries for column search.
- *
- * @param int $i
- * @param string $column
- * @param string $keyword
- * @return void
*/
protected function compileColumnSearch(int $i, string $column, string $keyword): void
{
@@ -461,10 +518,6 @@ protected function compileColumnSearch(int $i, string $column, string $keyword):
/**
* Compile regex query column search.
- *
- * @param string $column
- * @param string $keyword
- * @return void
*/
protected function regexColumnSearch(string $column, string $keyword): void
{
@@ -494,34 +547,24 @@ protected function regexColumnSearch(string $column, string $keyword): void
/**
* Wrap a column and cast based on database driver.
- *
- * @param string $column
- * @return string
*/
protected function castColumn(string $column): string
{
- switch ($this->getConnection()->getDriverName()) {
- case 'pgsql':
- return 'CAST('.$column.' as TEXT)';
- case 'firebird':
- return 'CAST('.$column.' as VARCHAR(255))';
- default:
- return $column;
- }
+ return match ($this->getConnection()->getDriverName()) {
+ 'pgsql' => 'CAST('.$column.' as TEXT)',
+ 'firebird' => 'CAST('.$column.' as VARCHAR(255))',
+ default => $column,
+ };
}
/**
* Compile query builder where clause depending on configurations.
*
* @param QueryBuilder|EloquentBuilder $query
- * @param string $column
- * @param string $keyword
- * @param string $boolean
- * @return void
*/
protected function compileQuerySearch($query, string $column, string $keyword, string $boolean = 'or'): void
{
- $column = $this->addTablePrefix($query, $column);
+ $column = $this->wrap($this->addTablePrefix($query, $column));
$column = $this->castColumn($column);
$sql = $column.' LIKE ?';
@@ -537,33 +580,103 @@ protected function compileQuerySearch($query, string $column, string $keyword, s
* Ambiguous field error will appear when query use join table and search with keyword.
*
* @param QueryBuilder|EloquentBuilder $query
- * @param string $column
- * @return string
*/
protected function addTablePrefix($query, string $column): string
{
- if (! str_contains($column, '.')) {
- $q = $this->getBaseQueryBuilder($query);
- $from = $q->from;
-
- /** @phpstan-ignore-next-line */
- if (! $from instanceof Expression) {
- if (str_contains($from, ' as ')) {
- $from = explode(' as ', $from)[1];
- }
+ // Column is already prefixed
+ if (str_contains($column, '.')) {
+ return $column;
+ }
+
+ // Extract selected columns from the query
+ $selects = $this->getSelectedColumns($query);
+
+ // We have a match
+ if (isset($selects['columns'][$column])) {
+ return $selects['columns'][$column];
+ }
+
+ // Multiple wildcards => Unable to determine prefix
+ if (in_array('*', $selects['wildcards']) || count(array_unique($selects['wildcards'])) > 1) {
+ return $column;
+ }
+
+ // Use the only wildcard available
+ if (! empty($selects['wildcards'])) {
+ return $selects['wildcards'][0].'.'.$column;
+ }
+
+ // Fallback on table prefix
+ return ltrim($this->getTablePrefix($query).'.'.$column, '.');
+ }
+
+ /**
+ * Try to get the base table prefix.
+ * To be used to prevent ambiguous field name.
+ *
+ * @param QueryBuilder|EloquentBuilder $query
+ */
+ protected function getTablePrefix($query): ?string
+ {
+ $q = $this->getBaseQueryBuilder($query);
+ $from = $q->from ?? '';
- $column = $from.'.'.$column;
+ if (! $from instanceof Expression) {
+ if (str_contains((string) $from, ' as ')) {
+ $from = explode(' as ', (string) $from)[1];
}
+
+ return $from;
}
- return $this->wrap($column);
+ return null;
}
/**
- * Prepare search keyword based on configurations.
+ * Get declared column names from the query.
*
- * @param string $keyword
- * @return string
+ * @param QueryBuilder|EloquentBuilder $query
+ */
+ protected function getSelectedColumns($query): array
+ {
+ $q = $this->getBaseQueryBuilder($query);
+
+ $selects = [
+ 'wildcards' => [],
+ 'columns' => [],
+ ];
+
+ foreach ($q->columns ?? [] as $select) {
+ $sql = trim($select instanceof Expression ? $select->getValue($this->getConnection()->getQueryGrammar()) : (string) $select);
+ // Remove expressions
+ $sql = preg_replace('/\s*\w*\((?:[^()]*|(?R))*\)/', '_', $sql);
+ // Remove multiple spaces
+ $sql = preg_replace('/\s+/', ' ', (string) $sql);
+ // Remove wrappers
+ $sql = str_replace(['`', '"', '[', ']'], '', $sql);
+ // Loop on select columns
+ foreach (explode(',', $sql) as $column) {
+ $column = trim($column);
+ if (preg_match('/[\w.]+\s+(?:as\s+)?([a-zA-Z0-9_]+)$/i', $column, $matches)) {
+ // Column with alias
+ $selects['columns'][$matches[1]] = $matches[1];
+ } elseif (preg_match('/^([\w.]+)$/i', $column)) {
+ // Column without alias
+ [$table, $name] = str_contains($column, '.') ? explode('.', $column) : [null, $column];
+ if ($name === '*') {
+ $selects['wildcards'][] = $table ?? '*';
+ } else {
+ $selects['columns'][$name] = $column;
+ }
+ }
+ }
+ }
+
+ return $selects;
+ }
+
+ /**
+ * Prepare search keyword based on configurations.
*/
protected function prepareKeyword(string $keyword): string
{
@@ -590,7 +703,6 @@ protected function prepareKeyword(string $keyword): string
* Add custom filter handler for the give column.
*
* @param string $column
- * @param callable $callback
* @return $this
*/
public function filterColumn($column, callable $callback): static
@@ -603,7 +715,6 @@ public function filterColumn($column, callable $callback): static
/**
* Order each given columns versus the given custom sql.
*
- * @param array $columns
* @param string $sql
* @param array $bindings
* @return $this
@@ -625,7 +736,7 @@ public function orderColumns(array $columns, $sql, $bindings = []): static
* @param array $bindings
* @return $this
*
- * @internal string $1 Special variable that returns the requested order direction of the column.
+ * string $1 Special variable that returns the requested order direction of the column.
*/
public function orderColumn($column, $sql, $bindings = []): static
{
@@ -648,8 +759,6 @@ public function orderByNullsLast(): static
/**
* Perform pagination.
- *
- * @return void
*/
public function paging(): void
{
@@ -670,7 +779,6 @@ public function paging(): void
* Paginate dataTable using limit without offset
* with additional where clause via callback.
*
- * @param callable $callback
* @return $this
*/
public function limit(callable $callback): static
@@ -697,11 +805,6 @@ public function addColumn($name, $content, $order = false): static
/**
* Perform search using search pane values.
- *
- * @return void
- *
- * @throws \Psr\Container\ContainerExceptionInterface
- * @throws \Psr\Container\NotFoundExceptionInterface
*/
protected function searchPanesSearch(): void
{
@@ -728,13 +831,12 @@ protected function searchPanesSearch(): void
*/
protected function resolveCallbackParameter(): array
{
- return [$this->query, $this->scoutSearched];
+ return [$this->query, $this->scoutSearched, fn ($column) => $this->resolveRelationColumn($column)];
}
/**
* Perform default query orderBy clause.
*
- * @return void
*
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
@@ -747,17 +849,12 @@ protected function defaultOrdering(): void
return $orderable;
})
- ->reject(function ($orderable) {
- return $this->isBlacklisted($orderable['name']) && ! $this->hasOrderColumn($orderable['name']);
- })
+ ->reject(fn ($orderable) => $this->isBlacklisted($orderable['name']) && ! $this->hasOrderColumn($orderable['name']))
->each(function ($orderable) {
- $column = $this->resolveRelationColumn($orderable['name']);
-
if ($this->hasOrderColumn($orderable['name'])) {
- $this->applyOrderColumn($orderable['name'], $orderable);
- } elseif ($this->hasOrderColumn($column)) {
- $this->applyOrderColumn($column, $orderable);
+ $this->applyOrderColumn($orderable);
} else {
+ $column = $this->resolveRelationColumn($orderable['name']);
$nullsLastSql = $this->getNullsLastSql($column, $orderable['direction']);
$normalSql = $this->wrap($column).' '.$orderable['direction'];
$sql = $this->nullsLast ? $nullsLastSql : $normalSql;
@@ -768,9 +865,6 @@ protected function defaultOrdering(): void
/**
* Check if column has custom sort handler.
- *
- * @param string $column
- * @return bool
*/
protected function hasOrderColumn(string $column): bool
{
@@ -779,22 +873,19 @@ protected function hasOrderColumn(string $column): bool
/**
* Apply orderColumn custom query.
- *
- * @param string $column
- * @param array $orderable
*/
- protected function applyOrderColumn(string $column, array $orderable): void
+ protected function applyOrderColumn(array $orderable): void
{
- $sql = $this->columnDef['order'][$column]['sql'];
+ $sql = $this->columnDef['order'][$orderable['name']]['sql'];
if ($sql === false) {
return;
}
if (is_callable($sql)) {
- call_user_func($sql, $this->query, $orderable['direction']);
+ call_user_func($sql, $this->query, $orderable['direction'], fn ($column) => $this->resolveRelationColumn($column));
} else {
- $sql = str_replace('$1', $orderable['direction'], $sql);
- $bindings = $this->columnDef['order'][$column]['bindings'];
+ $sql = str_replace('$1', $orderable['direction'], (string) $sql);
+ $bindings = $this->columnDef['order'][$orderable['name']]['bindings'];
$this->query->orderByRaw($sql, $bindings);
}
}
@@ -804,7 +895,6 @@ protected function applyOrderColumn(string $column, array $orderable): void
*
* @param string $column
* @param string $direction
- * @return string
*
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
@@ -823,9 +913,6 @@ protected function getNullsLastSql($column, $direction): string
/**
* Perform global search for the given keyword.
- *
- * @param string $keyword
- * @return void
*/
protected function globalSearch(string $keyword): void
{
@@ -836,13 +923,9 @@ protected function globalSearch(string $keyword): void
$this->query->where(function ($query) use ($keyword) {
collect($this->request->searchableColumnIndex())
- ->map(function ($index) {
- return $this->getColumnName($index);
- })
+ ->map(fn ($index) => $this->getColumnName($index))
->filter()
- ->reject(function ($column) {
- return $this->isBlacklisted($column) && ! $this->hasFilterColumn($column);
- })
+ ->reject(fn ($column) => $this->isBlacklisted($column) && ! $this->hasFilterColumn($column))
->each(function ($column) use ($keyword, $query) {
if ($this->hasFilterColumn($column)) {
$this->applyFilterColumn($query, $column, $keyword, 'or');
@@ -858,7 +941,6 @@ protected function globalSearch(string $keyword): void
* individual words and searches for each of them.
*
* @param string $keyword
- * @return void
*/
protected function smartGlobalSearch($keyword): void
{
@@ -872,9 +954,6 @@ protected function smartGlobalSearch($keyword): void
/**
* Append debug parameters on output.
- *
- * @param array $output
- * @return array
*/
protected function showDebugger(array $output): array
{
@@ -893,9 +972,6 @@ protected function showDebugger(array $output): array
/**
* Attach custom with meta on response.
- *
- * @param array $data
- * @return array
*/
protected function attachAppends(array $data): array
{
@@ -916,8 +992,6 @@ protected function attachAppends(array $data): array
/**
* Get filtered, ordered and paginated query.
- *
- * @return QueryBuilder
*/
public function getFilteredQuery(): QueryBuilder
{
@@ -940,8 +1014,6 @@ public function ignoreSelectsInCountQuery(): static
/**
* Perform sorting of columns.
- *
- * @return void
*/
public function ordering(): void
{
@@ -957,8 +1029,6 @@ public function ordering(): void
* Enable scout search and use provided model for searching.
* $max_hits is the maximum number of hits to return from scout.
*
- * @param string $model
- * @param int $max_hits
* @return $this
*
* @throws \Exception
@@ -984,7 +1054,6 @@ public function enableScoutSearch(string $model, int $max_hits = 1000): static
/**
* Add dynamic filters to scout search.
*
- * @param callable $callback
* @return $this
*/
public function scoutFilter(callable $callback): static
@@ -996,9 +1065,6 @@ public function scoutFilter(callable $callback): static
/**
* Apply scout search to query if enabled.
- *
- * @param string $search_keyword
- * @return bool
*/
protected function applyScoutSearch(string $search_keyword): bool
{
@@ -1040,8 +1106,6 @@ protected function applyScoutSearch(string $search_keyword): bool
*
* Currently supported drivers: MySQL
*
- * @param string $keyName
- * @param array $orderedKeys
* @return bool
*/
protected function applyFixedOrderingToQuery(string $keyName, array $orderedKeys)
@@ -1052,11 +1116,10 @@ protected function applyFixedOrderingToQuery(string $keyName, array $orderedKeys
// Escape keyName and orderedKeys
$keyName = $connection->getQueryGrammar()->wrap($keyName);
$orderedKeys = collect($orderedKeys)
- ->map(function ($value) use ($connection) {
- return $connection->escape($value);
- });
+ ->map(fn ($value) => $connection->escape($value));
switch ($driverName) {
+ case 'mariadb':
case 'mysql':
$this->query->orderByRaw("FIELD($keyName, ".$orderedKeys->implode(',').')');
@@ -1098,15 +1161,12 @@ protected function applyFixedOrderingToQuery(string $keyName, array $orderedKeys
/**
* Perform a scout search with the configured engine and given parameters. Return matching model IDs.
*
- * @param string $searchKeyword
- * @param mixed $searchFilters
- * @return array
*
* @throws \Exception
*/
protected function performScoutSearch(string $searchKeyword, mixed $searchFilters = []): array
{
- if (! class_exists('\Laravel\Scout\EngineManager')) {
+ if (! class_exists(\Laravel\Scout\EngineManager::class)) {
throw new \Exception('Laravel Scout is not installed.');
}
$engine = app(\Laravel\Scout\EngineManager::class)->engine();
diff --git a/src/Utilities/Config.php b/src/Utilities/Config.php
index 99a2aa98..ae474368 100644
--- a/src/Utilities/Config.php
+++ b/src/Utilities/Config.php
@@ -6,25 +6,13 @@
class Config
{
- /**
- * @var \Illuminate\Contracts\Config\Repository
- */
- private Repository $repository;
-
/**
* Config constructor.
- *
- * @param \Illuminate\Contracts\Config\Repository $repository
*/
- public function __construct(Repository $repository)
- {
- $this->repository = $repository;
- }
+ public function __construct(private readonly Repository $repository) {}
/**
* Check if config uses wild card search.
- *
- * @return bool
*/
public function isWildcard(): bool
{
@@ -33,8 +21,6 @@ public function isWildcard(): bool
/**
* Check if config uses smart search.
- *
- * @return bool
*/
public function isSmartSearch(): bool
{
@@ -43,8 +29,6 @@ public function isSmartSearch(): bool
/**
* Check if config uses case-insensitive search.
- *
- * @return bool
*/
public function isCaseInsensitive(): bool
{
@@ -53,8 +37,6 @@ public function isCaseInsensitive(): bool
/**
* Check if app is in debug mode.
- *
- * @return bool
*/
public function isDebugging(): bool
{
@@ -65,10 +47,9 @@ public function isDebugging(): bool
* Get the specified configuration value.
*
* @param string $key
- * @param mixed $default
* @return mixed
*/
- public function get($key, $default = null)
+ public function get($key, mixed $default = null)
{
return $this->repository->get($key, $default);
}
@@ -77,18 +58,15 @@ public function get($key, $default = null)
* Set a given configuration value.
*
* @param array|string $key
- * @param mixed $value
* @return void
*/
- public function set($key, $value = null)
+ public function set($key, mixed $value = null)
{
$this->repository->set($key, $value);
}
/**
* Check if dataTable config uses multi-term searching.
- *
- * @return bool
*/
public function isMultiTerm(): bool
{
@@ -97,8 +75,6 @@ public function isMultiTerm(): bool
/**
* Check if dataTable config uses starts_with searching.
- *
- * @return bool
*/
public function isStartsWithSearch(): bool
{
diff --git a/src/Utilities/Helper.php b/src/Utilities/Helper.php
index c3f5108a..b0e87127 100644
--- a/src/Utilities/Helper.php
+++ b/src/Utilities/Helper.php
@@ -6,6 +6,7 @@
use DateTime;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Arr;
+use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Str;
use ReflectionFunction;
use ReflectionMethod;
@@ -14,12 +15,8 @@ class Helper
{
/**
* Places item of extra columns into results by care of their order.
- *
- * @param array $item
- * @param array $array
- * @return array
*/
- public static function includeInArray($item, $array)
+ public static function includeInArray(array $item, array $array): array
{
if (self::isItemOrderInvalid($item, $array)) {
return array_merge($array, [$item['name'] => $item['content']]);
@@ -44,12 +41,8 @@ public static function includeInArray($item, $array)
/**
* Check if item order is valid.
- *
- * @param array $item
- * @param array $array
- * @return bool
*/
- protected static function isItemOrderInvalid($item, $array)
+ protected static function isItemOrderInvalid(array $item, array $array): bool
{
return $item['order'] === false || $item['order'] >= count($array);
}
@@ -94,7 +87,7 @@ private static function reflectCallableParameters($callable)
*
* @throws \ReflectionException
*/
- public static function compileContent($content, array $data, array|object $param)
+ public static function compileContent(mixed $content, array $data, array|object $param)
{
if (is_string($content)) {
return static::compileBlade($content, static::getMixedValue($data, $param));
@@ -122,33 +115,23 @@ public static function compileContent($content, array $data, array|object $param
/**
* Parses and compiles strings by using Blade Template System.
*
- * @param string $str
- * @param array $data
- * @return false|string
+ *
+ * @throws \Throwable
*/
- public static function compileBlade($str, $data = [])
+ public static function compileBlade(string $str, array $data = []): false|string
{
if (view()->exists($str)) {
/** @var view-string $str */
return view($str, $data)->render();
}
- ob_start() && extract($data, EXTR_SKIP);
- eval('?>'.app('blade.compiler')->compileString($str));
- $str = ob_get_contents();
- ob_end_clean();
-
- return $str;
+ return Blade::render($str, $data);
}
/**
* Get a mixed value of custom data and the parameters.
- *
- * @param array $data
- * @param array|object $param
- * @return array
*/
- public static function getMixedValue(array $data, array|object $param)
+ public static function getMixedValue(array $data, array|object $param): array
{
$casted = self::castToArray($param);
@@ -165,9 +148,6 @@ public static function getMixedValue(array $data, array|object $param)
/**
* Cast the parameter into an array.
- *
- * @param array|object $param
- * @return array
*/
public static function castToArray(array|object $param): array
{
@@ -180,11 +160,8 @@ public static function castToArray(array|object $param): array
/**
* Get equivalent or method of query builder.
- *
- * @param string $method
- * @return string
*/
- public static function getOrMethod($method)
+ public static function getOrMethod(string $method): string
{
if (! Str::contains(Str::lower($method), 'or')) {
return 'or'.ucfirst($method);
@@ -195,12 +172,8 @@ public static function getOrMethod($method)
/**
* Converts array object values to associative array.
- *
- * @param mixed $row
- * @param array $filters
- * @return array
*/
- public static function convertToArray($row, $filters = [])
+ public static function convertToArray(mixed $row, array $filters = []): array
{
if (Arr::get($filters, 'ignore_getters') && is_object($row) && method_exists($row, 'getAttributes')) {
$data = $row->getAttributes();
@@ -226,7 +199,7 @@ public static function convertToArray($row, $filters = [])
$data = $row instanceof Arrayable ? $row->toArray() : (array) $row;
foreach ($data as &$value) {
- if (is_object($value) || is_array($value)) {
+ if ((is_object($value) && ! $value instanceof DateTime) || is_array($value)) {
$value = self::convertToArray($value);
}
@@ -236,24 +209,17 @@ public static function convertToArray($row, $filters = [])
return $data;
}
- /**
- * @param array $data
- * @return array
- */
- public static function transform(array $data)
+ public static function transform(array $data): array
{
- return array_map(function ($row) {
- return self::transformRow($row);
- }, $data);
+ return array_map(fn ($row) => self::transformRow($row), $data);
}
/**
* Transform row data into an array.
*
* @param array $row
- * @return array
*/
- protected static function transformRow($row)
+ protected static function transformRow($row): array
{
foreach ($row as $key => $value) {
if ($value instanceof DateTime) {
@@ -272,11 +238,8 @@ protected static function transformRow($row)
/**
* Build parameters depending on # of arguments passed.
- *
- * @param array $args
- * @return array
*/
- public static function buildParameters(array $args)
+ public static function buildParameters(array $args): array
{
$parameters = [];
@@ -296,20 +259,15 @@ public static function buildParameters(array $args)
/**
* Replace all pattern occurrences with keyword.
- *
- * @param array $subject
- * @param string $keyword
- * @param string $pattern
- * @return array
*/
- public static function replacePatternWithKeyword(array $subject, $keyword, $pattern = '$1')
+ public static function replacePatternWithKeyword(array $subject, string $keyword, string $pattern = '$1'): array
{
$parameters = [];
foreach ($subject as $param) {
if (is_array($param)) {
$parameters[] = self::replacePatternWithKeyword($param, $keyword, $pattern);
} else {
- $parameters[] = str_replace($pattern, $keyword, $param);
+ $parameters[] = str_replace($pattern, $keyword, (string) $param);
}
}
@@ -318,12 +276,8 @@ public static function replacePatternWithKeyword(array $subject, $keyword, $patt
/**
* Get column name from string.
- *
- * @param string $str
- * @param bool $wantsAlias
- * @return string
*/
- public static function extractColumnName($str, $wantsAlias)
+ public static function extractColumnName(string $str, bool $wantsAlias): string
{
$matches = explode(' as ', Str::lower($str));
@@ -344,25 +298,16 @@ public static function extractColumnName($str, $wantsAlias)
/**
* Adds % wildcards to the given string.
- *
- * @param string $str
- * @param bool $lowercase
- * @return string
*/
- public static function wildcardLikeString($str, $lowercase = true)
+ public static function wildcardLikeString(string $str, bool $lowercase = true): string
{
return static::wildcardString($str, '%', $lowercase);
}
/**
* Adds wildcards to the given string.
- *
- * @param string $str
- * @param string $wildcard
- * @param bool $lowercase
- * @return string
*/
- public static function wildcardString($str, $wildcard, $lowercase = true)
+ public static function wildcardString(string $str, string $wildcard, bool $lowercase = true): string
{
$wild = $wildcard;
$chars = (array) preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY);
@@ -387,7 +332,7 @@ public static function toJsonScript(array $parameters, int $options = 0): string
foreach (Arr::dot($parameters) as $key => $value) {
if (self::isJavascript($value, $key)) {
- $values[] = trim($value);
+ $values[] = trim((string) $value);
Arr::set($parameters, $key, '%'.$key.'%');
$replacements[] = '"%'.$key.'%"';
}
diff --git a/src/Utilities/Request.php b/src/Utilities/Request.php
index f660e08c..c26d9102 100644
--- a/src/Utilities/Request.php
+++ b/src/Utilities/Request.php
@@ -9,19 +9,6 @@
*/
class Request
{
- /**
- * @var BaseRequest
- */
- protected BaseRequest $request;
-
- /**
- * Request constructor.
- */
- public function __construct()
- {
- $this->request = app('request');
- }
-
/**
* Proxy non-existing method calls to base request class.
*
@@ -31,12 +18,22 @@ public function __construct()
*/
public function __call($name, $arguments)
{
- $callback = [$this->request, $name];
+ $callback = [request(), $name];
if (is_callable($callback)) {
return call_user_func_array($callback, $arguments);
}
}
+ /**
+ * Determine if an attribute exists on the base request.
+ *
+ * @param string $name
+ */
+ public function __isset($name): bool
+ {
+ return isset(request()->$name);
+ }
+
/**
* Get attributes from request instance.
*
@@ -45,44 +42,35 @@ public function __call($name, $arguments)
*/
public function __get($name)
{
- return $this->request->__get($name);
+ return request()->__get($name);
}
/**
* Get all columns request input.
- *
- * @return array
*/
public function columns(): array
{
- return (array) $this->request->input('columns');
+ return (array) request()->input('columns');
}
/**
* Check if DataTables is searchable.
- *
- * @return bool
*/
public function isSearchable(): bool
{
- return $this->request->input('search.value') != '';
+ return request()->input('search.value') != '';
}
/**
* Check if DataTables must uses regular expressions.
- *
- * @param int $index
- * @return bool
*/
public function isRegex(int $index): bool
{
- return $this->request->input("columns.$index.search.regex") === 'true';
+ return request()->input("columns.$index.search.regex") === 'true';
}
/**
* Get orderable columns.
- *
- * @return array
*/
public function orderableColumns(): array
{
@@ -91,14 +79,14 @@ public function orderableColumns(): array
}
$orderable = [];
- for ($i = 0, $c = count((array) $this->request->input('order')); $i < $c; $i++) {
+ for ($i = 0, $c = count((array) request()->input('order')); $i < $c; $i++) {
/** @var int $order_col */
- $order_col = $this->request->input("order.$i.column");
+ $order_col = request()->input("order.$i.column");
/** @var string $direction */
- $direction = $this->request->input("order.$i.dir");
+ $direction = request()->input("order.$i.dir");
- $order_dir = strtolower($direction) === 'asc' ? 'asc' : 'desc';
+ $order_dir = $direction && strtolower($direction) === 'asc' ? 'asc' : 'desc';
if ($this->isColumnOrderable($order_col)) {
$orderable[] = ['column' => $order_col, 'direction' => $order_dir];
}
@@ -109,23 +97,18 @@ public function orderableColumns(): array
/**
* Check if DataTables ordering is enabled.
- *
- * @return bool
*/
public function isOrderable(): bool
{
- return $this->request->input('order') && count((array) $this->request->input('order')) > 0;
+ return request()->input('order') && count((array) request()->input('order')) > 0;
}
/**
* Check if a column is orderable.
- *
- * @param int $index
- * @return bool
*/
public function isColumnOrderable(int $index): bool
{
- return $this->request->input("columns.$index.orderable", 'true') == 'true';
+ return request()->input("columns.$index.orderable", 'true') == 'true';
}
/**
@@ -136,7 +119,7 @@ public function isColumnOrderable(int $index): bool
public function searchableColumnIndex()
{
$searchable = [];
- $columns = (array) $this->request->input('columns');
+ $columns = (array) request()->input('columns');
for ($i = 0, $c = count($columns); $i < $c; $i++) {
if ($this->isColumnSearchable($i, false)) {
$searchable[] = $i;
@@ -148,48 +131,48 @@ public function searchableColumnIndex()
/**
* Check if a column is searchable.
- *
- * @param int $i
- * @param bool $column_search
- * @return bool
*/
public function isColumnSearchable(int $i, bool $column_search = true): bool
{
if ($column_search) {
return
(
- $this->request->input("columns.$i.searchable", 'true') === 'true'
+ request()->input("columns.$i.searchable", 'true') === 'true'
||
- $this->request->input("columns.$i.searchable", 'true') === true
+ request()->input("columns.$i.searchable", 'true') === true
)
&& $this->columnKeyword($i) != '';
}
return
- $this->request->input("columns.$i.searchable", 'true') === 'true'
+ request()->input("columns.$i.searchable", 'true') === 'true'
||
- $this->request->input("columns.$i.searchable", 'true') === true;
+ request()->input("columns.$i.searchable", 'true') === true;
}
/**
* Get column's search value.
- *
- * @param int $index
- * @return string
*/
public function columnKeyword(int $index): string
{
/** @var string $keyword */
- $keyword = $this->request->input("columns.$index.search.value") ?? '';
+ $keyword = request()->input("columns.$index.search.value") ?? '';
return $this->prepareKeyword($keyword);
}
+ public function columnControl(int $index): array
+ {
+ return request()->array("columns.$index.columnControl");
+ }
+
+ public function columnControlSearch(int $index): array
+ {
+ return request()->array("columns.$index.columnControl.search");
+ }
+
/**
* Prepare keyword string value.
- *
- * @param float|array|int|string $keyword
- * @return string
*/
protected function prepareKeyword(float|array|int|string $keyword): string
{
@@ -202,83 +185,67 @@ protected function prepareKeyword(float|array|int|string $keyword): string
/**
* Get global search keyword.
- *
- * @return string
*/
public function keyword(): string
{
/** @var string $keyword */
- $keyword = $this->request->input('search.value') ?? '';
+ $keyword = request()->input('search.value') ?? '';
return $this->prepareKeyword($keyword);
}
/**
* Get column name by index.
- *
- * @param int $i
- * @return string|null
*/
public function columnName(int $i): ?string
{
/** @var string[] $column */
- $column = $this->request->input("columns.$i");
+ $column = request()->input("columns.$i");
return (isset($column['name']) && $column['name'] != '') ? $column['name'] : $column['data'];
}
/**
* Check if DataTables allow pagination.
- *
- * @return bool
*/
public function isPaginationable(): bool
{
- return ! is_null($this->request->input('start')) &&
- ! is_null($this->request->input('length')) &&
- $this->request->input('length') != -1;
+ return ! is_null(request()->input('start')) &&
+ ! is_null(request()->input('length')) &&
+ request()->input('length') != -1;
}
- /**
- * @return BaseRequest
- */
public function getBaseRequest(): BaseRequest
{
- return $this->request;
+ return request();
}
/**
* Get starting record value.
- *
- * @return int
*/
public function start(): int
{
- $start = $this->request->input('start', 0);
+ $start = request()->input('start', 0);
return is_numeric($start) ? intval($start) : 0;
}
/**
* Get per page length.
- *
- * @return int
*/
public function length(): int
{
- $length = $this->request->input('length', 10);
+ $length = request()->input('length', 10);
return is_numeric($length) ? intval($length) : 10;
}
/**
* Get draw request.
- *
- * @return int
*/
public function draw(): int
{
- $draw = $this->request->input('draw', 0);
+ $draw = request()->input('draw', 0);
return is_numeric($draw) ? intval($draw) : 0;
}
diff --git a/src/config/datatables.php b/src/config/datatables.php
index 08912643..bdb963fe 100644
--- a/src/config/datatables.php
+++ b/src/config/datatables.php
@@ -42,7 +42,7 @@
/*
* List of available builders for DataTables.
- * This is where you can register your custom dataTables builder.
+ * This is where you can register your custom DataTables builder.
*/
'engines' => [
'eloquent' => Yajra\DataTables\EloquentDataTable::class,
@@ -57,10 +57,10 @@
* Note, only change this if you know what you are doing!
*/
'builders' => [
- //Illuminate\Database\Eloquent\Relations\Relation::class => 'eloquent',
- //Illuminate\Database\Eloquent\Builder::class => 'eloquent',
- //Illuminate\Database\Query\Builder::class => 'query',
- //Illuminate\Support\Collection::class => 'collection',
+ // Illuminate\Database\Eloquent\Relations\Relation::class => 'eloquent',
+ // Illuminate\Database\Eloquent\Builder::class => 'eloquent',
+ // Illuminate\Database\Query\Builder::class => 'query',
+ // Illuminate\Support\Collection::class => 'collection',
],
/*
@@ -79,7 +79,7 @@
'error' => env('DATATABLES_ERROR', null),
/*
- * Default columns definition of dataTable utility functions.
+ * Default columns definition of DataTable utility functions.
*/
'columns' => [
/*
@@ -105,7 +105,7 @@
'blacklist' => ['password', 'remember_token'],
/*
- * List of columns that are only allowed fo search/sort.
+ * List of columns that are only allowed for search/sort.
* If set to *, all columns are allowed.
*/
'whitelist' => '*',
diff --git a/src/helper.php b/src/helper.php
index ec17d6f1..7aa58a1a 100644
--- a/src/helper.php
+++ b/src/helper.php
@@ -6,7 +6,7 @@
* Or return the factory if source is not set.
*
* @param \Illuminate\Contracts\Database\Query\Builder|\Illuminate\Contracts\Database\Eloquent\Builder|\Illuminate\Support\Collection|array|null $source
- * @return \Yajra\DataTables\DataTables|\Yajra\DataTables\DataTableAbstract
+ * @return ($source is null ? \Yajra\DataTables\DataTables : \Yajra\DataTables\DataTableAbstract)
*
* @throws \Yajra\DataTables\Exceptions\Exception
*/
diff --git a/src/lumen.php b/src/lumen.php
index a8167231..bfb2d376 100644
--- a/src/lumen.php
+++ b/src/lumen.php
@@ -22,6 +22,6 @@ function config_path($path = '')
*/
function public_path($path = null)
{
- return rtrim(app()->basePath('public/'.$path), '/');
+ return rtrim((string) app()->basePath('public/'.$path), '/');
}
}
diff --git a/tests/Formatters/DateFormatter.php b/tests/Formatters/DateFormatter.php
index f4bc82b1..1a4e0abe 100644
--- a/tests/Formatters/DateFormatter.php
+++ b/tests/Formatters/DateFormatter.php
@@ -8,12 +8,7 @@
class DateFormatter implements Formatter
{
- public string $format;
-
- public function __construct(string $format = 'Y-m-d h:i a')
- {
- $this->format = $format;
- }
+ public function __construct(public string $format = 'Y-m-d h:i a') {}
public function format($value, $row): string
{
diff --git a/tests/Integration/BelongsToManyRelationTest.php b/tests/Integration/BelongsToManyRelationTest.php
index 4f2ad21f..c11a4517 100644
--- a/tests/Integration/BelongsToManyRelationTest.php
+++ b/tests/Integration/BelongsToManyRelationTest.php
@@ -3,6 +3,7 @@
namespace Yajra\DataTables\Tests\Integration;
use Illuminate\Foundation\Testing\DatabaseTransactions;
+use PHPUnit\Framework\Attributes\Test;
use Yajra\DataTables\DataTables;
use Yajra\DataTables\Tests\Models\User;
use Yajra\DataTables\Tests\TestCase;
@@ -11,7 +12,7 @@ class BelongsToManyRelationTest extends TestCase
{
use DatabaseTransactions;
- /** @test */
+ #[Test]
public function it_returns_all_records_with_the_relation_when_called_without_parameters()
{
$response = $this->call('GET', '/relations/belongsToMany');
@@ -25,7 +26,7 @@ public function it_returns_all_records_with_the_relation_when_called_without_par
$this->assertCount(20, $response->json()['data']);
}
- /** @test */
+ #[Test]
public function it_can_perform_global_search_on_the_relation()
{
$response = $this->getJsonResponse([
@@ -54,7 +55,7 @@ protected function getJsonResponse(array $params = [])
return $this->call('GET', '/relations/belongsToMany', array_merge($data, $params));
}
- /** @test */
+ #[Test]
public function it_can_sort_using_the_relation_with_pagination()
{
$response = $this->getJsonResponse([
@@ -86,8 +87,6 @@ protected function setUp(): void
{
parent::setUp();
- $this->app['router']->get('/relations/belongsToMany', function (DataTables $datatables) {
- return $datatables->eloquent(User::with('roles')->select('users.*'))->toJson();
- });
+ $this->app['router']->get('/relations/belongsToMany', fn (DataTables $datatables) => $datatables->eloquent(User::with('roles')->select('users.*'))->toJson());
}
}
diff --git a/tests/Integration/BelongsToRelationTest.php b/tests/Integration/BelongsToRelationTest.php
index 424ccac5..ff962dcd 100644
--- a/tests/Integration/BelongsToRelationTest.php
+++ b/tests/Integration/BelongsToRelationTest.php
@@ -3,6 +3,7 @@
namespace Yajra\DataTables\Tests\Integration;
use Illuminate\Foundation\Testing\DatabaseTransactions;
+use PHPUnit\Framework\Attributes\Test;
use Yajra\DataTables\DataTables;
use Yajra\DataTables\Tests\Models\Post;
use Yajra\DataTables\Tests\TestCase;
@@ -11,7 +12,7 @@ class BelongsToRelationTest extends TestCase
{
use DatabaseTransactions;
- /** @test */
+ #[Test]
public function it_returns_all_records_with_the_relation_when_called_without_parameters()
{
$response = $this->call('GET', '/relations/belongsTo');
@@ -25,7 +26,7 @@ public function it_returns_all_records_with_the_relation_when_called_without_par
$this->assertCount(60, $response->json()['data']);
}
- /** @test */
+ #[Test]
public function it_can_perform_global_search_on_the_relation()
{
$response = $this->getJsonResponse([
@@ -54,7 +55,7 @@ protected function getJsonResponse(array $params = [])
return $this->call('GET', '/relations/belongsTo', array_merge($data, $params));
}
- /** @test */
+ #[Test]
public function it_can_sort_using_the_relation_with_pagination()
{
$response = $this->getJsonResponse([
@@ -83,8 +84,6 @@ protected function setUp(): void
{
parent::setUp();
- $this->app['router']->get('/relations/belongsTo', function (DataTables $datatables) {
- return $datatables->eloquent(Post::with('user')->select('posts.*'))->toJson();
- });
+ $this->app['router']->get('/relations/belongsTo', fn (DataTables $datatables) => $datatables->eloquent(Post::with('user')->select('posts.*'))->toJson());
}
}
diff --git a/tests/Integration/CollectionDataTableTest.php b/tests/Integration/CollectionDataTableTest.php
index 1538f7a5..6ce01576 100644
--- a/tests/Integration/CollectionDataTableTest.php
+++ b/tests/Integration/CollectionDataTableTest.php
@@ -4,6 +4,7 @@
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Http\JsonResponse;
+use PHPUnit\Framework\Attributes\Test;
use Yajra\DataTables\CollectionDataTable;
use Yajra\DataTables\DataTables;
use Yajra\DataTables\Facades\DataTables as DatatablesFacade;
@@ -14,7 +15,7 @@ class CollectionDataTableTest extends TestCase
{
use DatabaseTransactions;
- /** @test */
+ #[Test]
public function it_returns_all_records_when_no_parameters_is_passed()
{
$crawler = $this->call('GET', '/collection/users');
@@ -25,7 +26,7 @@ public function it_returns_all_records_when_no_parameters_is_passed()
]);
}
- /** @test */
+ #[Test]
public function it_returns_zero_filtered_records_on_empty_collection()
{
$crawler = $this->call('GET', '/collection/empty');
@@ -38,7 +39,7 @@ public function it_returns_zero_filtered_records_on_empty_collection()
]);
}
- /** @test */
+ #[Test]
public function it_can_perform_global_search()
{
$crawler = $this->call('GET', '/collection/users', [
@@ -56,7 +57,7 @@ public function it_can_perform_global_search()
]);
}
- /** @test */
+ #[Test]
public function it_accepts_a_model_collection_using_of_factory()
{
$dataTable = DataTables::of(User::all());
@@ -65,7 +66,7 @@ public function it_accepts_a_model_collection_using_of_factory()
$this->assertInstanceOf(JsonResponse::class, $response);
}
- /** @test */
+ #[Test]
public function it_accepts_a_collection_using_of_factory()
{
$dataTable = DataTables::of(collect());
@@ -74,7 +75,7 @@ public function it_accepts_a_collection_using_of_factory()
$this->assertInstanceOf(JsonResponse::class, $response);
}
- /** @test */
+ #[Test]
public function it_accepts_a_model_collection_using_facade()
{
$dataTable = DatatablesFacade::of(User::all());
@@ -83,7 +84,7 @@ public function it_accepts_a_model_collection_using_facade()
$this->assertInstanceOf(JsonResponse::class, $response);
}
- /** @test */
+ #[Test]
public function it_accepts_a_collection_using_facade()
{
$dataTable = DatatablesFacade::of(collect());
@@ -92,7 +93,7 @@ public function it_accepts_a_collection_using_facade()
$this->assertInstanceOf(JsonResponse::class, $response);
}
- /** @test */
+ #[Test]
public function it_accepts_a_model_using_ioc_container()
{
$dataTable = app('datatables')->collection(User::all());
@@ -101,7 +102,7 @@ public function it_accepts_a_model_using_ioc_container()
$this->assertInstanceOf(JsonResponse::class, $response);
}
- /** @test */
+ #[Test]
public function it_can_sort_case_insensitive_strings()
{
config()->set('app.debug', false);
@@ -143,7 +144,7 @@ public function it_can_sort_case_insensitive_strings()
], $response->getData(true));
}
- /** @test */
+ #[Test]
public function it_can_sort_numeric_strings()
{
config()->set('app.debug', false);
@@ -185,7 +186,7 @@ public function it_can_sort_numeric_strings()
], $response->getData(true));
}
- /** @test */
+ #[Test]
public function it_accepts_a_model_using_ioc_container_factory()
{
$dataTable = app('datatables')->of(User::all());
@@ -194,7 +195,7 @@ public function it_accepts_a_model_using_ioc_container_factory()
$this->assertInstanceOf(JsonResponse::class, $response);
}
- /** @test */
+ #[Test]
public function it_can_search_on_added_columns()
{
config()->set('app.debug', false);
@@ -235,7 +236,7 @@ public function it_can_search_on_added_columns()
], $response->getData(true));
}
- /** @test */
+ #[Test]
public function it_accepts_array_data_source()
{
$source = [
@@ -252,12 +253,8 @@ protected function setUp(): void
{
parent::setUp();
- $this->app['router']->get('/collection/users', function (DataTables $datatables) {
- return $datatables->collection(User::all())->toJson();
- });
+ $this->app['router']->get('/collection/users', fn (DataTables $datatables) => $datatables->collection(User::all())->toJson());
- $this->app['router']->get('/collection/empty', function (DataTables $datatables) {
- return $datatables->collection([])->toJson();
- });
+ $this->app['router']->get('/collection/empty', fn (DataTables $datatables) => $datatables->collection([])->toJson());
}
}
diff --git a/tests/Integration/CustomOrderTest.php b/tests/Integration/CustomOrderTest.php
index 467e8e4d..f7281f3e 100644
--- a/tests/Integration/CustomOrderTest.php
+++ b/tests/Integration/CustomOrderTest.php
@@ -3,6 +3,7 @@
namespace Yajra\DataTables\Tests\Integration;
use Illuminate\Foundation\Testing\DatabaseTransactions;
+use PHPUnit\Framework\Attributes\Test;
use Yajra\DataTables\DataTables;
use Yajra\DataTables\Tests\Models\Post;
use Yajra\DataTables\Tests\TestCase;
@@ -11,7 +12,7 @@ class CustomOrderTest extends TestCase
{
use DatabaseTransactions;
- /** @test */
+ #[Test]
public function it_can_order_with_custom_order()
{
$response = $this->getJsonResponse([
@@ -52,12 +53,10 @@ protected function setUp(): void
{
parent::setUp();
- $this->app['router']->get('/relations/belongsTo', function (DataTables $datatables) {
- return $datatables->eloquent(Post::with('user')->select('posts.*'))
- ->orderColumn('user.id', function ($query, $order) {
- $query->orderBy('users.id', $order == 'desc' ? 'asc' : 'desc');
- })
- ->toJson();
- });
+ $this->app['router']->get('/relations/belongsTo', fn (DataTables $datatables) => $datatables->eloquent(Post::with('user')->select('posts.*'))
+ ->orderColumn('user.id', function ($query, $order, $resolver) {
+ $query->orderBy($resolver('user.id'), $order == 'desc' ? 'asc' : 'desc');
+ })
+ ->toJson());
}
}
diff --git a/tests/Integration/DeepRelationTest.php b/tests/Integration/DeepRelationTest.php
index 9ebe9578..4ad9c861 100644
--- a/tests/Integration/DeepRelationTest.php
+++ b/tests/Integration/DeepRelationTest.php
@@ -3,6 +3,7 @@
namespace Yajra\DataTables\Tests\Integration;
use Illuminate\Foundation\Testing\DatabaseTransactions;
+use PHPUnit\Framework\Attributes\Test;
use Yajra\DataTables\DataTables;
use Yajra\DataTables\Tests\Models\Post;
use Yajra\DataTables\Tests\TestCase;
@@ -11,7 +12,7 @@ class DeepRelationTest extends TestCase
{
use DatabaseTransactions;
- /** @test */
+ #[Test]
public function it_returns_all_records_with_the_relation_when_called_without_parameters()
{
$response = $this->getJsonResponse();
@@ -24,7 +25,7 @@ public function it_returns_all_records_with_the_relation_when_called_without_par
$this->assertCount(60, $response->json()['data']);
}
- /** @test */
+ #[Test]
public function it_can_perform_global_search_on_the_relation()
{
$response = $this->getJsonResponse([
diff --git a/tests/Integration/EloquentDataTableTest.php b/tests/Integration/EloquentDataTableTest.php
index afaa5560..12664742 100644
--- a/tests/Integration/EloquentDataTableTest.php
+++ b/tests/Integration/EloquentDataTableTest.php
@@ -5,6 +5,7 @@
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Http\JsonResponse;
+use PHPUnit\Framework\Attributes\Test;
use Yajra\DataTables\DataTables;
use Yajra\DataTables\EloquentDataTable;
use Yajra\DataTables\Facades\DataTables as DatatablesFacade;
@@ -17,7 +18,7 @@ class EloquentDataTableTest extends TestCase
{
use DatabaseTransactions;
- /** @test */
+ #[Test]
public function it_returns_all_records_when_no_parameters_is_passed()
{
$crawler = $this->call('GET', '/eloquent/users');
@@ -28,7 +29,7 @@ public function it_returns_all_records_when_no_parameters_is_passed()
]);
}
- /** @test */
+ #[Test]
public function it_can_perform_global_search()
{
$crawler = $this->call('GET', '/eloquent/users', [
@@ -46,7 +47,7 @@ public function it_can_perform_global_search()
]);
}
- /** @test */
+ #[Test]
public function it_accepts_a_model_using_of_factory()
{
$dataTable = DataTables::of(User::query());
@@ -55,7 +56,7 @@ public function it_accepts_a_model_using_of_factory()
$this->assertInstanceOf(JsonResponse::class, $response);
}
- /** @test */
+ #[Test]
public function it_accepts_a_model_using_facade()
{
$dataTable = DatatablesFacade::of(User::query());
@@ -64,7 +65,7 @@ public function it_accepts_a_model_using_facade()
$this->assertInstanceOf(JsonResponse::class, $response);
}
- /** @test */
+ #[Test]
public function it_accepts_a_model_using_facade_eloquent_method()
{
$dataTable = DatatablesFacade::eloquent(User::query());
@@ -73,7 +74,7 @@ public function it_accepts_a_model_using_facade_eloquent_method()
$this->assertInstanceOf(JsonResponse::class, $response);
}
- /** @test */
+ #[Test]
public function it_accepts_a_model_using_ioc_container()
{
$dataTable = app('datatables')->eloquent(User::query());
@@ -82,7 +83,7 @@ public function it_accepts_a_model_using_ioc_container()
$this->assertInstanceOf(JsonResponse::class, $response);
}
- /** @test */
+ #[Test]
public function it_accepts_a_model_using_ioc_container_factory()
{
$dataTable = app('datatables')->of(User::query());
@@ -91,7 +92,7 @@ public function it_accepts_a_model_using_ioc_container_factory()
$this->assertInstanceOf(JsonResponse::class, $response);
}
- /** @test */
+ #[Test]
public function it_returns_only_the_selected_columns_with_dotted_notation()
{
$json = $this->call('GET', '/eloquent/only')->json();
@@ -101,7 +102,7 @@ public function it_returns_only_the_selected_columns_with_dotted_notation()
$this->assertArrayHasKey('name', $json['data'][0]['user']);
}
- /** @test */
+ #[Test]
public function it_can_return_formatted_columns()
{
$crawler = $this->call('GET', '/eloquent/formatColumn');
@@ -121,7 +122,7 @@ public function it_can_return_formatted_columns()
$this->assertEquals(Carbon::parse($user->created_at)->format('Y-m-d'), $data['created_at_formatted']);
}
- /** @test */
+ #[Test]
public function it_can_return_formatted_column_using_closure()
{
$crawler = $this->call('GET', '/eloquent/formatColumn-closure');
@@ -141,7 +142,7 @@ public function it_can_return_formatted_column_using_closure()
$this->assertEquals(Carbon::parse($user->created_at)->format('Y-m-d'), $data['created_at_formatted']);
}
- /** @test */
+ #[Test]
public function it_can_return_formatted_column_on_invalid_formatter()
{
$crawler = $this->call('GET', '/eloquent/formatColumn-fallback');
@@ -161,7 +162,7 @@ public function it_can_return_formatted_column_on_invalid_formatter()
$this->assertEquals($user->created_at, $data['created_at_formatted']);
}
- /** @test */
+ #[Test]
public function it_accepts_a_relation()
{
$user = User::first();
@@ -177,32 +178,22 @@ protected function setUp(): void
parent::setUp();
$router = $this->app['router'];
- $router->get('/eloquent/users', function (DataTables $datatables) {
- return $datatables->eloquent(User::query())->toJson();
- });
-
- $router->get('/eloquent/only', function (DataTables $datatables) {
- return $datatables->eloquent(Post::with('user'))
- ->only(['title', 'user.name'])
- ->toJson();
- });
-
- $router->get('/eloquent/formatColumn', function (DataTables $dataTable) {
- return $dataTable->eloquent(User::query())
- ->formatColumn('created_at', new DateFormatter('Y-m-d'))
- ->toJson();
- });
-
- $router->get('/eloquent/formatColumn-closure', function (DataTables $dataTable) {
- return $dataTable->eloquent(User::query())
- ->formatColumn('created_at', fn ($value, $row) => Carbon::parse($value)->format('Y-m-d'))
- ->toJson();
- });
-
- $router->get('/eloquent/formatColumn-fallback', function (DataTables $dataTable) {
- return $dataTable->eloquent(User::query())
- ->formatColumn('created_at', 'InvalidFormatter::class')
- ->toJson();
- });
+ $router->get('/eloquent/users', fn (DataTables $datatables) => $datatables->eloquent(User::query())->toJson());
+
+ $router->get('/eloquent/only', fn (DataTables $datatables) => $datatables->eloquent(Post::with('user'))
+ ->only(['title', 'user.name'])
+ ->toJson());
+
+ $router->get('/eloquent/formatColumn', fn (DataTables $dataTable) => $dataTable->eloquent(User::query())
+ ->formatColumn('created_at', new DateFormatter('Y-m-d'))
+ ->toJson());
+
+ $router->get('/eloquent/formatColumn-closure', fn (DataTables $dataTable) => $dataTable->eloquent(User::query())
+ ->formatColumn('created_at', fn ($value, $row) => Carbon::parse($value)->format('Y-m-d'))
+ ->toJson());
+
+ $router->get('/eloquent/formatColumn-fallback', fn (DataTables $dataTable) => $dataTable->eloquent(User::query())
+ ->formatColumn('created_at', 'InvalidFormatter::class')
+ ->toJson());
}
}
diff --git a/tests/Integration/EloquentJoinTest.php b/tests/Integration/EloquentJoinTest.php
index e0bfaed5..4ec67b76 100644
--- a/tests/Integration/EloquentJoinTest.php
+++ b/tests/Integration/EloquentJoinTest.php
@@ -3,6 +3,7 @@
namespace Yajra\DataTables\Tests\Integration;
use Illuminate\Foundation\Testing\DatabaseTransactions;
+use PHPUnit\Framework\Attributes\Test;
use Yajra\DataTables\DataTables;
use Yajra\DataTables\Tests\Models\Post;
use Yajra\DataTables\Tests\TestCase;
@@ -11,7 +12,7 @@ class EloquentJoinTest extends TestCase
{
use DatabaseTransactions;
- /** @test */
+ #[Test]
public function it_returns_all_records_with_the_relation_when_called_without_parameters()
{
$response = $this->getJsonResponse();
@@ -27,7 +28,7 @@ public function it_returns_all_records_with_the_relation_when_called_without_par
$this->assertCount(60, $response->json()['data']);
}
- /** @test */
+ #[Test]
public function it_can_perform_global_search_on_the_relation()
{
$response = $this->getJsonResponse([
@@ -56,7 +57,7 @@ protected function getJsonResponse(array $params = [])
return $this->call('GET', '/eloquent/join', array_merge($data, $params));
}
- /** @test */
+ #[Test]
public function it_can_sort_using_the_relation_with_pagination()
{
$response = $this->getJsonResponse([
@@ -87,8 +88,8 @@ protected function setUp(): void
$this->app['router']->get('/eloquent/join', function (DataTables $datatables) {
$builder = Post::query()
- ->join('users', 'users.id', '=', 'posts.user_id')
- ->select('users.name', 'users.email', 'posts.title');
+ ->join('users', 'users.id', '=', 'posts.user_id')
+ ->select('users.name', 'users.email', 'posts.title');
return $datatables->eloquent($builder)->toJson();
});
diff --git a/tests/Integration/HasManyRelationTest.php b/tests/Integration/HasManyRelationTest.php
index 661bb8cf..41e1cff6 100644
--- a/tests/Integration/HasManyRelationTest.php
+++ b/tests/Integration/HasManyRelationTest.php
@@ -3,6 +3,7 @@
namespace Yajra\DataTables\Tests\Integration;
use Illuminate\Foundation\Testing\DatabaseTransactions;
+use PHPUnit\Framework\Attributes\Test;
use Yajra\DataTables\DataTables;
use Yajra\DataTables\Tests\Models\Post;
use Yajra\DataTables\Tests\Models\User;
@@ -12,7 +13,7 @@ class HasManyRelationTest extends TestCase
{
use DatabaseTransactions;
- /** @test */
+ #[Test]
public function it_returns_all_records_with_the_relation_when_called_without_parameters()
{
$response = $this->call('GET', '/relations/hasMany');
@@ -26,7 +27,7 @@ public function it_returns_all_records_with_the_relation_when_called_without_par
$this->assertCount(20, $response->json()['data']);
}
- /** @test */
+ #[Test]
public function it_returns_all_records_with_deleted_relations_when_called_with_withtrashed_parameter()
{
Post::find(1)->delete();
@@ -42,7 +43,7 @@ public function it_returns_all_records_with_deleted_relations_when_called_with_w
$this->assertCount(3, $response->json()['data'][0]['posts']);
}
- /** @test */
+ #[Test]
public function it_returns_all_records_with_only_deleted_relations_when_called_with_onlytrashed_parameter()
{
Post::find(1)->delete();
@@ -57,7 +58,7 @@ public function it_returns_all_records_with_only_deleted_relations_when_called_w
$this->assertCount(1, $response->json()['data'][0]['posts']);
}
- /** @test */
+ #[Test]
public function it_can_perform_global_search_on_the_relation()
{
$response = $this->getJsonResponse([
@@ -89,20 +90,14 @@ protected function setUp(): void
{
parent::setUp();
- $this->app['router']->get('/relations/hasMany', function (DataTables $datatables) {
- return $datatables->eloquent(User::with('posts')->select('users.*'))->toJson();
- });
-
- $this->app['router']->get('/relations/hasManyWithTrashed', function (DataTables $datatables) {
- return $datatables->eloquent(User::with(['posts' => function ($query) {
- $query->withTrashed();
- }])->select('users.*'))->toJson();
- });
-
- $this->app['router']->get('/relations/hasManyOnlyTrashed', function (DataTables $datatables) {
- return $datatables->eloquent(User::with(['posts' => function ($query) {
- $query->onlyTrashed();
- }])->select('users.*'))->toJson();
- });
+ $this->app['router']->get('/relations/hasMany', fn (DataTables $datatables) => $datatables->eloquent(User::with('posts')->select('users.*'))->toJson());
+
+ $this->app['router']->get('/relations/hasManyWithTrashed', fn (DataTables $datatables) => $datatables->eloquent(User::with(['posts' => function ($query) {
+ $query->withTrashed();
+ }])->select('users.*'))->toJson());
+
+ $this->app['router']->get('/relations/hasManyOnlyTrashed', fn (DataTables $datatables) => $datatables->eloquent(User::with(['posts' => function ($query) {
+ $query->onlyTrashed();
+ }])->select('users.*'))->toJson());
}
}
diff --git a/tests/Integration/HasOneRelationTest.php b/tests/Integration/HasOneRelationTest.php
index e1ceb416..da83b66e 100644
--- a/tests/Integration/HasOneRelationTest.php
+++ b/tests/Integration/HasOneRelationTest.php
@@ -3,6 +3,7 @@
namespace Yajra\DataTables\Tests\Integration;
use Illuminate\Foundation\Testing\DatabaseTransactions;
+use PHPUnit\Framework\Attributes\Test;
use Yajra\DataTables\DataTables;
use Yajra\DataTables\Tests\Models\Heart;
use Yajra\DataTables\Tests\Models\User;
@@ -12,7 +13,7 @@ class HasOneRelationTest extends TestCase
{
use DatabaseTransactions;
- /** @test */
+ #[Test]
public function it_returns_all_records_with_the_relation_when_called_without_parameters()
{
$response = $this->call('GET', '/relations/hasOne');
@@ -26,7 +27,7 @@ public function it_returns_all_records_with_the_relation_when_called_without_par
$this->assertCount(20, $response->json()['data']);
}
- /** @test */
+ #[Test]
public function it_returns_all_records_with_the_deleted_relation_when_called_with_withtrashed_parameter()
{
Heart::find(1)->delete();
@@ -44,7 +45,7 @@ public function it_returns_all_records_with_the_deleted_relation_when_called_wit
$this->assertNotEmpty($response->json()['data'][1]['heart']);
}
- /** @test */
+ #[Test]
public function it_returns_all_records_with_the_only_deleted_relation_when_called_with_onlytrashed_parameter()
{
Heart::find(1)->delete();
@@ -62,7 +63,7 @@ public function it_returns_all_records_with_the_only_deleted_relation_when_calle
$this->assertEmpty($response->json()['data'][1]['heart']);
}
- /** @test */
+ #[Test]
public function it_can_perform_global_search_on_the_relation()
{
$response = $this->getJsonResponse([
@@ -91,7 +92,7 @@ protected function getJsonResponse(array $params = [])
return $this->call('GET', '/relations/hasOne', array_merge($data, $params));
}
- /** @test */
+ #[Test]
public function it_can_sort_using_the_relation_with_pagination()
{
$response = $this->getJsonResponse([
@@ -120,20 +121,18 @@ protected function setUp(): void
{
parent::setUp();
- $this->app['router']->get('/relations/hasOne', function (DataTables $datatables) {
- return $datatables->eloquent(User::with('heart')->select('users.*'))->toJson();
- });
+ $this->app['router']->get('/relations/hasOne', fn (DataTables $datatables) => $datatables->eloquent(User::with('heart')->select('users.*'))->toJson());
- $this->app['router']->get('/relations/hasOneWithTrashed', function (DataTables $datatables) {
- return $datatables->eloquent(User::with(['heart' => function ($query) {
+ $this->app['router']->get('/relations/hasOneWithTrashed', fn (DataTables $datatables) => $datatables->eloquent(User::with([
+ 'heart' => function ($query) {
$query->withTrashed();
- }])->select('users.*'))->toJson();
- });
+ },
+ ])->select('users.*'))->toJson());
- $this->app['router']->get('/relations/hasOneOnlyTrashed', function (DataTables $datatables) {
- return $datatables->eloquent(User::with(['heart' => function ($query) {
+ $this->app['router']->get('/relations/hasOneOnlyTrashed', fn (DataTables $datatables) => $datatables->eloquent(User::with([
+ 'heart' => function ($query) {
$query->onlyTrashed();
- }])->select('users.*'))->toJson();
- });
+ },
+ ])->select('users.*'))->toJson());
}
}
diff --git a/tests/Integration/HasOneThroughTest.php b/tests/Integration/HasOneThroughTest.php
index f7347945..075de4c1 100644
--- a/tests/Integration/HasOneThroughTest.php
+++ b/tests/Integration/HasOneThroughTest.php
@@ -3,6 +3,7 @@
namespace Yajra\DataTables\Tests\Integration;
use Illuminate\Foundation\Testing\DatabaseTransactions;
+use PHPUnit\Framework\Attributes\Test;
use Yajra\DataTables\DataTables;
use Yajra\DataTables\Tests\Models\Heart;
use Yajra\DataTables\Tests\Models\Post;
@@ -12,7 +13,7 @@ class HasOneThroughTest extends TestCase
{
use DatabaseTransactions;
- /** @test */
+ #[Test]
public function it_returns_all_records_with_the_relation_when_called_without_parameters()
{
$response = $this->call('GET', '/relations/hasOneThrough');
@@ -26,7 +27,7 @@ public function it_returns_all_records_with_the_relation_when_called_without_par
$this->assertCount(60, $response->json()['data']);
}
- /** @test */
+ #[Test]
public function it_can_search_has_one_through_relation()
{
$response = $this->call('GET', '/relations/hasOneThroughSearchRelation', [
@@ -51,7 +52,7 @@ public function it_can_search_has_one_through_relation()
$this->assertCount(33, $response->json()['data']);
}
- /** @test */
+ #[Test]
public function it_returns_all_records_with_the_deleted_relation_when_called_with_withtrashed_parameter()
{
Heart::find(1)->delete();
@@ -69,7 +70,7 @@ public function it_returns_all_records_with_the_deleted_relation_when_called_wit
$this->assertNotEmpty($response->json()['data'][1]['heart']);
}
- /** @test */
+ #[Test]
public function it_returns_all_records_with_the_only_deleted_relation_when_called_with_onlytrashed_parameter()
{
Heart::find(1)->delete();
@@ -90,7 +91,7 @@ public function it_returns_all_records_with_the_only_deleted_relation_when_calle
$this->assertEmpty($response->json()['data'][3]['heart']);
}
- /** @test */
+ #[Test]
public function it_can_perform_global_search_on_the_relation()
{
$response = $this->getJsonResponse([
@@ -122,24 +123,16 @@ protected function setUp(): void
{
parent::setUp();
- $this->app['router']->get('/relations/hasOneThrough', function (DataTables $datatables) {
- return $datatables->eloquent(Post::with('heart')->select('posts.*'))->toJson();
- });
-
- $this->app['router']->get('/relations/hasOneThroughSearchRelation', function (DataTables $datatables) {
- return $datatables->eloquent(Post::with('heart'))->addColumns(['hearts.size'])->toJson();
- });
-
- $this->app['router']->get('/relations/hasOneThroughWithTrashed', function (DataTables $datatables) {
- return $datatables->eloquent(Post::with(['heart' => function ($query) {
- $query->withTrashed();
- }])->select('posts.*'))->toJson();
- });
-
- $this->app['router']->get('/relations/hasOneThroughOnlyTrashed', function (DataTables $datatables) {
- return $datatables->eloquent(Post::with(['heart' => function ($query) {
- $query->onlyTrashed();
- }])->select('posts.*'))->toJson();
- });
+ $this->app['router']->get('/relations/hasOneThrough', fn (DataTables $datatables) => $datatables->eloquent(Post::with('heart')->select('posts.*'))->toJson());
+
+ $this->app['router']->get('/relations/hasOneThroughSearchRelation', fn (DataTables $datatables) => $datatables->eloquent(Post::with('heart'))->addColumns(['hearts.size'])->toJson());
+
+ $this->app['router']->get('/relations/hasOneThroughWithTrashed', fn (DataTables $datatables) => $datatables->eloquent(Post::with(['heart' => function ($query) {
+ $query->withTrashed();
+ }])->select('posts.*'))->toJson());
+
+ $this->app['router']->get('/relations/hasOneThroughOnlyTrashed', fn (DataTables $datatables) => $datatables->eloquent(Post::with(['heart' => function ($query) {
+ $query->onlyTrashed();
+ }])->select('posts.*'))->toJson());
}
}
diff --git a/tests/Integration/IgnoreGettersTest.php b/tests/Integration/IgnoreGettersTest.php
index 2db7c2a7..18068b9c 100644
--- a/tests/Integration/IgnoreGettersTest.php
+++ b/tests/Integration/IgnoreGettersTest.php
@@ -3,6 +3,7 @@
namespace Yajra\DataTables\Tests\Integration;
use Illuminate\Foundation\Testing\DatabaseTransactions;
+use PHPUnit\Framework\Attributes\Test;
use Yajra\DataTables\DataTables;
use Yajra\DataTables\Tests\Models\User;
use Yajra\DataTables\Tests\TestCase;
@@ -11,7 +12,7 @@ class IgnoreGettersTest extends TestCase
{
use DatabaseTransactions;
- /** @test */
+ #[Test]
public function it_return_the_default_value_when_attribute_is_null()
{
$user = User::create([
@@ -24,12 +25,10 @@ public function it_return_the_default_value_when_attribute_is_null()
$this->assertEquals('#000000', $user->refresh()->toArray()['color']);
}
- /** @test */
+ #[Test]
public function it_return_the_getter_value_without_ignore_getters()
{
- $this->app['router']->get('/ignore-getters', function (DataTables $datatables) {
- return $datatables->eloquent(User::with('posts.user')->select('users.*'))->toJson();
- });
+ $this->app['router']->get('/ignore-getters', fn (DataTables $datatables) => $datatables->eloquent(User::with('posts.user')->select('users.*'))->toJson());
$response = $this->call('GET', '/ignore-getters');
$response->assertJson([
@@ -46,12 +45,10 @@ public function it_return_the_getter_value_without_ignore_getters()
$this->assertCount(20, $response->json()['data']);
}
- /** @test */
+ #[Test]
public function it_ignore_the_getter_value_with_ignore_getters()
{
- $this->app['router']->get('/ignore-getters', function (DataTables $datatables) {
- return $datatables->eloquent(User::with('posts.user')->select('users.*'))->ignoreGetters()->toJson();
- });
+ $this->app['router']->get('/ignore-getters', fn (DataTables $datatables) => $datatables->eloquent(User::with('posts.user')->select('users.*'))->ignoreGetters()->toJson());
$response = $this->call('GET', '/ignore-getters');
$response->assertJson([
diff --git a/tests/Integration/MinSearchLengthDataTableTest.php b/tests/Integration/MinSearchLengthDataTableTest.php
new file mode 100644
index 00000000..fcab93d4
--- /dev/null
+++ b/tests/Integration/MinSearchLengthDataTableTest.php
@@ -0,0 +1,99 @@
+call('GET', '/eloquent/min-length', [
+ 'start' => 0,
+ 'length' => 10,
+ 'columns' => [
+ ['data' => 'id'],
+ ['data' => 'name'],
+ ['data' => 'email'],
+ ],
+ 'search' => [
+ 'value' => '',
+ 'regex' => false,
+ ],
+ ]);
+
+ $crawler->assertJson([
+ 'draw' => 0,
+ 'recordsTotal' => 20,
+ 'recordsFiltered' => 20,
+ ]);
+ }
+
+ #[Test]
+ public function it_returns_an_error_when_search_keyword_length_is_less_than_required()
+ {
+ $crawler = $this->call('GET', '/eloquent/min-length', [
+ 'start' => 0,
+ 'length' => 10,
+ 'columns' => [
+ ['data' => 'id'],
+ ['data' => 'name'],
+ ['data' => 'email'],
+ ],
+ 'search' => [
+ 'value' => 'abc',
+ 'regex' => false,
+ ],
+ ]);
+
+ $crawler->assertJson([
+ 'draw' => 0,
+ 'recordsTotal' => 0,
+ 'recordsFiltered' => 0,
+ 'data' => [],
+ 'error' => "Exception Message:\n\nPlease enter at least 5 characters to search.",
+ ]);
+ }
+
+ #[Test]
+ public function it_returns_filtered_records_when_search_keyword_length_is_met()
+ {
+ $crawler = $this->call('GET', '/eloquent/min-length', [
+ 'draw' => 1,
+ 'start' => 0,
+ 'length' => 10,
+ 'columns' => [
+ ['data' => 'id'],
+ ['data' => 'name'],
+ ['data' => 'email'],
+ ],
+ 'search' => [
+ 'value' => 'Record-17',
+ 'regex' => false,
+ ],
+ ]);
+
+ $crawler->assertJson([
+ 'draw' => 1,
+ 'recordsTotal' => 20,
+ 'recordsFiltered' => 1,
+ ]);
+ }
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ Route::get('/eloquent/min-length', fn () => (new EloquentDataTable(User::query()))
+ ->minSearchLength(5)
+ ->toJson());
+ }
+}
diff --git a/tests/Integration/MorphToRelationTest.php b/tests/Integration/MorphToRelationTest.php
index 51ca65fd..44f07140 100644
--- a/tests/Integration/MorphToRelationTest.php
+++ b/tests/Integration/MorphToRelationTest.php
@@ -3,6 +3,7 @@
namespace Yajra\DataTables\Tests\Integration;
use Illuminate\Foundation\Testing\DatabaseTransactions;
+use PHPUnit\Framework\Attributes\Test;
use Yajra\DataTables\DataTables;
use Yajra\DataTables\Tests\Models\HumanUser;
use Yajra\DataTables\Tests\Models\User;
@@ -15,7 +16,7 @@ class MorphToRelationTest extends TestCase
{
use DatabaseTransactions;
- /** @test */
+ #[Test]
public function it_returns_all_records_with_the_relation_when_called_without_parameters()
{
$response = $this->call('GET', '/relations/morphTo');
@@ -29,7 +30,7 @@ public function it_returns_all_records_with_the_relation_when_called_without_par
$this->assertCount(20, $response->json()['data']);
}
- /** @test */
+ #[Test]
public function it_returns_all_records_with_the_deleted_relation_when_called_with_withtrashed_parameter()
{
HumanUser::find(1)->delete();
@@ -47,7 +48,7 @@ public function it_returns_all_records_with_the_deleted_relation_when_called_wit
$this->assertNotEmpty($response->json()['data'][1]['user']);
}
- /** @test */
+ #[Test]
public function it_returns_all_records_with_the_only_deleted_relation_when_called_with_onlytrashed_parameter()
{
HumanUser::find(1)->delete();
@@ -65,7 +66,7 @@ public function it_returns_all_records_with_the_only_deleted_relation_when_calle
$this->assertEmpty($response->json()['data'][1]['user']);
}
- /** @test */
+ #[Test]
public function it_can_perform_global_search_on_the_relation()
{
$response = $this->getJsonResponse([
@@ -98,20 +99,18 @@ protected function setUp(): void
{
parent::setUp();
- $this->app['router']->get('/relations/morphTo', function (DataTables $datatables) {
- return $datatables->eloquent(User::with('user')->select('users.*'))->toJson();
- });
+ $this->app['router']->get('/relations/morphTo', fn (DataTables $datatables) => $datatables->eloquent(User::with('user')->select('users.*'))->toJson());
- $this->app['router']->get('/relations/morphToWithTrashed', function (DataTables $datatables) {
- return $datatables->eloquent(User::with(['user' => function ($query) {
+ $this->app['router']->get('/relations/morphToWithTrashed', fn (DataTables $datatables) => $datatables->eloquent(User::with([
+ 'user' => function ($query) {
$query->withTrashed();
- }])->select('users.*'))->toJson();
- });
+ },
+ ])->select('users.*'))->toJson());
- $this->app['router']->get('/relations/morphToOnlyTrashed', function (DataTables $datatables) {
- return $datatables->eloquent(User::with(['user' => function ($query) {
+ $this->app['router']->get('/relations/morphToOnlyTrashed', fn (DataTables $datatables) => $datatables->eloquent(User::with([
+ 'user' => function ($query) {
$query->onlyTrashed();
- }])->select('users.*'))->toJson();
- });
+ },
+ ])->select('users.*'))->toJson());
}
}
diff --git a/tests/Integration/QueryDataTableTest.php b/tests/Integration/QueryDataTableTest.php
index 14aa280b..c6477f35 100644
--- a/tests/Integration/QueryDataTableTest.php
+++ b/tests/Integration/QueryDataTableTest.php
@@ -7,6 +7,8 @@
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
+use PHPUnit\Framework\Attributes\Test;
+use PHPUnit\Framework\Attributes\TestWith;
use Yajra\DataTables\DataTables;
use Yajra\DataTables\Facades\DataTables as DatatablesFacade;
use Yajra\DataTables\QueryDataTable;
@@ -18,7 +20,7 @@ class QueryDataTableTest extends TestCase
{
use DatabaseTransactions;
- /** @test */
+ #[Test]
public function it_can_set_total_records()
{
$crawler = $this->call('GET', '/set-total-records');
@@ -29,7 +31,7 @@ public function it_can_set_total_records()
]);
}
- /** @test */
+ #[Test]
public function it_can_set_zero_total_records()
{
$crawler = $this->call('GET', '/zero-total-records');
@@ -40,7 +42,7 @@ public function it_can_set_zero_total_records()
]);
}
- /** @test */
+ #[Test]
public function it_can_set_total_filtered_records()
{
$crawler = $this->call('GET', '/set-filtered-records');
@@ -51,18 +53,25 @@ public function it_can_set_total_filtered_records()
]);
}
- /** @test */
+ #[Test]
public function it_returns_all_records_when_no_parameters_is_passed()
{
+ DB::enableQueryLog();
+
$crawler = $this->call('GET', '/query/users');
$crawler->assertJson([
'draw' => 0,
'recordsTotal' => 20,
'recordsFiltered' => 20,
]);
+
+ DB::disableQueryLog();
+ $queryLog = DB::getQueryLog();
+
+ $this->assertCount(2, $queryLog);
}
- /** @test */
+ #[Test]
public function it_can_perform_global_search()
{
$crawler = $this->call('GET', '/query/users', [
@@ -80,10 +89,30 @@ public function it_can_perform_global_search()
]);
}
- /** @test */
+ #[Test]
public function it_can_skip_total_records_count_query()
{
- $crawler = $this->call('GET', '/query/simple', [
+ DB::enableQueryLog();
+
+ $crawler = $this->call('GET', '/skip-total-records');
+ $crawler->assertJson([
+ 'draw' => 0,
+ 'recordsTotal' => 20,
+ 'recordsFiltered' => 20,
+ ]);
+
+ DB::disableQueryLog();
+ $queryLog = DB::getQueryLog();
+
+ $this->assertCount(2, $queryLog);
+ }
+
+ #[Test]
+ public function it_can_skip_total_records_count_query_with_filter_applied()
+ {
+ DB::enableQueryLog();
+
+ $crawler = $this->call('GET', '/skip-total-records', [
'columns' => [
['data' => 'name', 'name' => 'name', 'searchable' => 'true', 'orderable' => 'true'],
['data' => 'email', 'name' => 'email', 'searchable' => 'true', 'orderable' => 'true'],
@@ -93,12 +122,17 @@ public function it_can_skip_total_records_count_query()
$crawler->assertJson([
'draw' => 0,
- 'recordsTotal' => 0,
+ 'recordsTotal' => 1,
'recordsFiltered' => 1,
]);
+
+ DB::disableQueryLog();
+ $queryLog = DB::getQueryLog();
+
+ $this->assertCount(2, $queryLog);
}
- /** @test */
+ #[Test]
public function it_can_perform_multiple_term_global_search()
{
$crawler = $this->call('GET', '/query/users', [
@@ -116,7 +150,7 @@ public function it_can_perform_multiple_term_global_search()
]);
}
- /** @test */
+ #[Test]
public function it_accepts_a_query_using_of_factory()
{
$dataTable = DataTables::of(DB::table('users'));
@@ -125,7 +159,7 @@ public function it_accepts_a_query_using_of_factory()
$this->assertInstanceOf(JsonResponse::class, $response);
}
- /** @test */
+ #[Test]
public function it_accepts_a_query_using_facade()
{
$dataTable = DatatablesFacade::of(DB::table('users'));
@@ -134,7 +168,7 @@ public function it_accepts_a_query_using_facade()
$this->assertInstanceOf(JsonResponse::class, $response);
}
- /** @test */
+ #[Test]
public function it_accepts_a_query_using_facade_query_method()
{
$dataTable = DatatablesFacade::query(DB::table('users'));
@@ -143,7 +177,7 @@ public function it_accepts_a_query_using_facade_query_method()
$this->assertInstanceOf(JsonResponse::class, $response);
}
- /** @test */
+ #[Test]
public function it_accepts_a_query_using_ioc_container()
{
$dataTable = app('datatables')->query(DB::table('users'));
@@ -152,7 +186,7 @@ public function it_accepts_a_query_using_ioc_container()
$this->assertInstanceOf(JsonResponse::class, $response);
}
- /** @test */
+ #[Test]
public function it_accepts_a_query_using_ioc_container_factory()
{
$dataTable = app('datatables')->of(DB::table('users'));
@@ -161,7 +195,7 @@ public function it_accepts_a_query_using_ioc_container_factory()
$this->assertInstanceOf(JsonResponse::class, $response);
}
- /** @test */
+ #[Test]
public function it_does_not_allow_search_on_added_columns()
{
$crawler = $this->call('GET', '/query/addColumn', [
@@ -180,7 +214,7 @@ public function it_does_not_allow_search_on_added_columns()
]);
}
- /** @test */
+ #[Test]
public function it_returns_only_the_selected_columns()
{
$json = $this->call('GET', '/query/only')->json();
@@ -188,8 +222,8 @@ public function it_returns_only_the_selected_columns()
$this->assertArrayHasKey('name', $json['data'][0]);
}
- /** @test */
- public function it_edit_only_the_selected_columns_after_using_editOnlySelectedColumns()
+ #[Test]
+ public function it_edit_only_the_selected_columns_after_using_edit_only_selected_columns()
{
$json = $this->call('GET', '/query/edit-columns', [
'columns' => [
@@ -202,7 +236,7 @@ public function it_edit_only_the_selected_columns_after_using_editOnlySelectedCo
$this->assertNotEquals('edited', $json['data'][0]['email']);
}
- /** @test */
+ #[Test]
public function it_does_not_allow_raw_html_on_added_columns()
{
$json = $this->call('GET', '/query/xss-add')->json();
@@ -210,7 +244,7 @@ public function it_does_not_allow_raw_html_on_added_columns()
$this->assertNotEquals('Allowed', $json['data'][0]['bar']);
}
- /** @test */
+ #[Test]
public function it_does_not_allow_raw_html_on_edited_columns()
{
$json = $this->call('GET', '/query/xss-edit')->json();
@@ -218,7 +252,7 @@ public function it_does_not_allow_raw_html_on_edited_columns()
$this->assertNotEquals('Allowed', $json['data'][0]['email']);
}
- /** @test */
+ #[Test]
public function it_allows_raw_html_on_specified_columns()
{
$json = $this->call('GET', '/query/xss-raw')->json();
@@ -227,7 +261,7 @@ public function it_allows_raw_html_on_specified_columns()
$this->assertEquals('Allowed', $json['data'][0]['email']);
}
- /** @test */
+ #[Test]
public function it_can_return_auto_index_column()
{
$crawler = $this->call('GET', '/query/indexColumn', [
@@ -248,7 +282,7 @@ public function it_can_return_auto_index_column()
$this->assertArrayHasKey('DT_RowIndex', $crawler->json()['data'][0]);
}
- /** @test */
+ #[Test]
public function it_allows_search_on_added_column_with_custom_filter_handler()
{
$crawler = $this->call('GET', '/query/filterColumn', [
@@ -270,7 +304,7 @@ public function it_allows_search_on_added_column_with_custom_filter_handler()
$this->assertStringContainsString('"1" = ?', $queries[1]['query']);
}
- /** @test */
+ #[Test]
public function it_returns_search_panes_options()
{
$crawler = $this->call('GET', '/query/search-panes');
@@ -291,7 +325,7 @@ public function it_returns_search_panes_options()
$this->assertEquals(count($options['id']), 20);
}
- /** @test */
+ #[Test]
public function it_performs_search_using_search_panes()
{
$crawler = $this->call('GET', '/query/search-panes', [
@@ -307,7 +341,7 @@ public function it_performs_search_using_search_panes()
]);
}
- /** @test */
+ #[Test]
public function it_allows_column_search_added_column_with_custom_filter_handler()
{
$crawler = $this->call('GET', '/query/blacklisted-filter', [
@@ -332,7 +366,7 @@ public function it_allows_column_search_added_column_with_custom_filter_handler(
]);
}
- /** @test */
+ #[Test]
public function it_can_return_formatted_columns()
{
$crawler = $this->call('GET', '/query/formatColumn');
@@ -353,7 +387,7 @@ public function it_can_return_formatted_columns()
$this->assertEquals(Carbon::parse($user->created_at)->format('Y-m-d'), $data['created_at_formatted']);
}
- /** @test */
+ #[Test]
public function it_can_return_added_column_with_dependency_injection()
{
$crawler = $this->call('GET', '/closure-di');
@@ -373,140 +407,150 @@ public function it_can_return_added_column_with_dependency_injection()
$this->assertEquals($user->name.'_di', $data['name_di']);
}
- protected function setUp(): void
+ #[Test]
+ #[TestWith(['title', '"posts"."title"'])] // column from base table wildcard posts.*
+ #[TestWith(['email', '"users"."email"'])] // column from join table added to select without alias
+ #[TestWith(['alias_field_with_as', '"alias_field_with_as"'])]
+ #[TestWith(['alias_field_without_as', '"alias_field_without_as"'])]
+ #[TestWith(['alias_expression_with_as', '"alias_expression_with_as"'])]
+ #[TestWith(['alias_expression_without_as', '"alias_expression_without_as"'])]
+ #[TestWith(['alias_case_with_as', '"alias_case_with_as"'])]
+ #[TestWith(['alias_case_without_as', '"alias_case_without_as"'])]
+ #[TestWith(['sub_query', '"sub_query"'])]
+ public function it_can_detect_column_alias(string $column, string $expected)
{
- parent::setUp();
-
- $router = $this->app['router'];
+ DB::enableQueryLog();
- $router->get('/query/users', function (DataTables $dataTable) {
- return $dataTable->query(DB::table('users'))->toJson();
- });
-
- $router->get('/query/formatColumn', function (DataTables $dataTable) {
- return $dataTable->query(DB::table('users'))
- ->formatColumn('created_at', new DateFormatter('Y-m-d'))
- ->toJson();
- });
-
- $router->get('/query/simple', function (DataTables $dataTable) {
- return $dataTable->query(DB::table('users'))->skipTotalRecords()->toJson();
- });
-
- $router->get('/query/addColumn', function (DataTables $dataTable) {
- return $dataTable->query(DB::table('users'))
- ->addColumn('foo', 'bar')
- ->toJson();
- });
-
- $router->get('/query/indexColumn', function (DataTables $dataTable) {
- return $dataTable->query(DB::table('users'))
- ->addIndexColumn()
- ->toJson();
- });
+ $crawler = $this->call('GET', '/query/aliases', [
+ 'columns' => [
+ ['data' => $column, 'name' => $column, 'orderable' => 'true'],
+ ],
+ 'order' => [['column' => 0, 'dir' => 'asc']],
+ ]);
- $router->get('/query/filterColumn', function (DataTables $dataTable) {
- return $dataTable->query(DB::table('users'))
- ->addColumn('foo', 'bar')
- ->filterColumn('foo', function (Builder $builder, $keyword) {
- $builder->where('1', $keyword);
- })
- ->toJson();
- });
+ $crawler->assertJsonStructure([
+ 'draw',
+ 'recordsTotal',
+ 'recordsFiltered',
+ ]);
- $router->get('/query/blacklisted-filter', function (DataTables $dataTable) {
- return $dataTable->query(DB::table('users'))
- ->addColumn('foo', 'bar')
- ->filterColumn('foo', function (Builder $builder, $keyword) {
- $builder->where('name', $keyword);
- })
- ->blacklist(['foo'])
- ->toJson();
- });
+ DB::disableQueryLog();
+ $queryLog = DB::getQueryLog();
- $router->get('/query/only', function (DataTables $dataTable) {
- return $dataTable->query(DB::table('users'))
- ->addColumn('foo', 'bar')
- ->only(['name'])
- ->toJson();
- });
+ $this->assertCount(2, $queryLog);
- $router->get('/query/edit-columns', function (DataTables $dataTable) {
- return $dataTable->query(DB::table('users'))
- ->editColumn('id', function () {
- return 'edited';
- })
- ->editOnlySelectedColumns()
- ->editColumn('name', function () {
- return 'edited';
- })
- ->editColumn('email', function () {
- return 'edited';
- })
- ->toJson();
- });
+ $sql = end($queryLog)['query'];
+ $this->assertStringContainsString("order by $expected asc", $sql);
+ }
- $router->get('/query/xss-add', function (DataTables $dataTable) {
- return $dataTable->query(DB::table('users'))
- ->addColumn('foo', 'Allowed')
- ->addColumn('bar', function () {
- return 'Allowed';
- })
- ->toJson();
- });
+ protected function setUp(): void
+ {
+ parent::setUp();
- $router->get('/query/xss-edit', function (DataTables $dataTable) {
- return $dataTable->query(DB::table('users'))
- ->editColumn('name', 'Allowed')
- ->editColumn('email', function () {
- return 'Allowed';
- })
- ->toJson();
- });
+ $router = $this->app['router'];
- $router->get('/query/xss-raw', function (DataTables $dataTable) {
- return $dataTable->query(DB::table('users'))
- ->addColumn('foo', 'Allowed')
- ->editColumn('name', 'Allowed')
- ->editColumn('email', function () {
- return 'Allowed';
- })
- ->rawColumns(['name', 'email'])
- ->toJson();
- });
+ $router->get('/query/users', fn (DataTables $dataTable) => $dataTable->query(DB::table('users'))->toJson());
+
+ $router->get('/query/formatColumn', fn (DataTables $dataTable) => $dataTable->query(DB::table('users'))
+ ->formatColumn('created_at', new DateFormatter('Y-m-d'))
+ ->toJson());
+
+ $router->get('/query/addColumn', fn (DataTables $dataTable) => $dataTable->query(DB::table('users'))
+ ->addColumn('foo', 'bar')
+ ->toJson());
+
+ $router->get('/query/indexColumn', fn (DataTables $dataTable) => $dataTable->query(DB::table('users'))
+ ->addIndexColumn()
+ ->toJson());
+
+ $router->get('/query/filterColumn', fn (DataTables $dataTable) => $dataTable->query(DB::table('users'))
+ ->addColumn('foo', 'bar')
+ ->filterColumn('foo', function (Builder $builder, $keyword) {
+ $builder->where('1', $keyword);
+ })
+ ->toJson());
+
+ $router->get('/query/blacklisted-filter', fn (DataTables $dataTable) => $dataTable->query(DB::table('users'))
+ ->addColumn('foo', 'bar')
+ ->filterColumn('foo', function (Builder $builder, $keyword) {
+ $builder->where('name', $keyword);
+ })
+ ->blacklist(['foo'])
+ ->toJson());
+
+ $router->get('/query/only', fn (DataTables $dataTable) => $dataTable->query(DB::table('users'))
+ ->addColumn('foo', 'bar')
+ ->only(['name'])
+ ->toJson());
+
+ $router->get('/query/edit-columns', fn (DataTables $dataTable) => $dataTable->query(DB::table('users'))
+ ->editColumn('id', fn () => 'edited')
+ ->editOnlySelectedColumns()
+ ->editColumn('name', fn () => 'edited')
+ ->editColumn('email', fn () => 'edited')
+ ->toJson());
+
+ $router->get('/query/xss-add', fn (DataTables $dataTable) => $dataTable->query(DB::table('users'))
+ ->addColumn('foo', 'Allowed')
+ ->addColumn('bar', fn () => 'Allowed')
+ ->toJson());
+
+ $router->get('/query/xss-edit', fn (DataTables $dataTable) => $dataTable->query(DB::table('users'))
+ ->editColumn('name', 'Allowed')
+ ->editColumn('email', fn () => 'Allowed')
+ ->toJson());
+
+ $router->get('/query/xss-raw', fn (DataTables $dataTable) => $dataTable->query(DB::table('users'))
+ ->addColumn('foo', 'Allowed')
+ ->editColumn('name', 'Allowed')
+ ->editColumn('email', fn () => 'Allowed')
+ ->rawColumns(['name', 'email'])
+ ->toJson());
$router->get('/query/search-panes', function (DataTables $dataTable) {
$options = User::select('id as value', 'name as label')->get();
return $dataTable->query(DB::table('users'))
- ->searchPane('id', $options)
- ->toJson();
- });
-
- $router->get('/set-total-records', function (DataTables $dataTable) {
- return $dataTable->query(DB::table('users'))
- ->setTotalRecords(10)
- ->toJson();
+ ->searchPane('id', $options)
+ ->toJson();
});
- $router->get('/zero-total-records', function (DataTables $dataTable) {
- return $dataTable->query(DB::table('users'))
- ->setTotalRecords(0)
- ->toJson();
- });
-
- $router->get('/set-filtered-records', function (DataTables $dataTable) {
- return $dataTable->query(DB::table('users'))
- ->setFilteredRecords(10)
- ->toJson();
- });
-
- $router->get('/closure-di', function (DataTables $dataTable) {
- return $dataTable->query(DB::table('users'))
- ->addColumn('name_di', function ($user, User $u) {
- return $u->newQuery()->find($user->id)->name.'_di';
- })
- ->toJson();
- });
+ $router->get('/set-total-records', fn (DataTables $dataTable) => $dataTable->query(DB::table('users'))
+ ->setTotalRecords(10)
+ ->toJson());
+
+ $router->get('/zero-total-records', fn (DataTables $dataTable) => $dataTable->query(DB::table('users'))
+ ->setTotalRecords(0)
+ ->toJson());
+
+ $router->get('/skip-total-records', fn (DataTables $dataTable) => $dataTable->query(DB::table('users'))
+ ->skipTotalRecords()
+ ->toJson());
+
+ $router->get('/set-filtered-records', fn (DataTables $dataTable) => $dataTable->query(DB::table('users'))
+ ->setFilteredRecords(10)
+ ->toJson());
+
+ $router->get('/closure-di', fn (DataTables $dataTable) => $dataTable->query(DB::table('users'))
+ ->addColumn('name_di', fn ($user, User $u) => $u->newQuery()->find($user->id)->name.'_di')
+ ->toJson());
+
+ $router->get('/query/aliases', fn (DataTables $dataTable) => $dataTable->query(
+ DB::table('posts')
+ ->join('users', 'users.id', '=', 'posts.user_id')
+ ->select([
+ 'posts.*',
+ 'users.email', // From join table, without alias
+ 'posts.id as alias_field_with_as',
+ DB::raw('posts.id alias_field_without_as'),
+ DB::raw('(1 + 1) as alias_expression_with_as'),
+ DB::raw('(SELECT 1) alias_expression_without_as'),
+ DB::raw('CASE WHEN 1 THEN 1 ELSE 2 END as alias_case_with_as'),
+ DB::raw('CASE WHEN 0 THEN 1 ELSE 2 END alias_case_without_as'),
+ 'sub_query' => DB::table('users')
+ ->whereColumn('posts.user_id', 'users.id')
+ ->select('name'),
+ ])
+ )->toJson());
}
}
diff --git a/tests/Unit/HelperTest.php b/tests/Unit/HelperTest.php
index ef01e0b0..65ac587c 100644
--- a/tests/Unit/HelperTest.php
+++ b/tests/Unit/HelperTest.php
@@ -72,7 +72,7 @@ public function test_compile_content_blade()
{
$content = '{!! $id !!}';
$data = ['id' => 2];
- $obj = new stdClass();
+ $obj = new stdClass;
$obj->id = 2;
$compiled = Helper::compileContent($content, $data, $obj);
@@ -83,7 +83,7 @@ public function test_compile_content_string()
{
$content = 'string';
$data = ['id' => 2];
- $obj = new stdClass();
+ $obj = new stdClass;
$obj->id = 2;
$compiled = Helper::compileContent($content, $data, $obj);
@@ -94,7 +94,7 @@ public function test_compile_content_integer()
{
$content = 1;
$data = ['id' => 2];
- $obj = new stdClass();
+ $obj = new stdClass;
$obj->id = 2;
$compiled = Helper::compileContent($content, $data, $obj);
@@ -103,11 +103,9 @@ public function test_compile_content_integer()
public function test_compile_content_function()
{
- $content = function ($obj) {
- return $obj->id;
- };
+ $content = fn ($obj) => $obj->id;
$data = ['id' => 2];
- $obj = new stdClass();
+ $obj = new stdClass;
$obj->id = 2;
$compiled = Helper::compileContent($content, $data, $obj);
@@ -124,7 +122,7 @@ public function __invoke($obj)
}
};
$data = ['id' => 2];
- $obj = new stdClass();
+ $obj = new stdClass;
$obj->id = 2;
$compiled = Helper::compileContent($content, $data, $obj);
@@ -147,7 +145,7 @@ public function test_get_mixed_value()
'name' => 'John',
'created_at' => '1234',
];
- $class = new stdClass();
+ $class = new stdClass;
$class->id = 1;
$class->name = 'John';
$class->created_at = $carbon;
@@ -164,7 +162,7 @@ public function test_get_mixed_value()
public function test_cast_to_array_an_object()
{
- $class = new stdClass();
+ $class = new stdClass;
$class->id = 1;
$compiled = Helper::castToArray($class);
$this->assertEquals(['id' => 1], $compiled);
@@ -190,11 +188,11 @@ public function test_get_or_method()
public function test_convert_to_array()
{
- $row = new stdClass();
+ $row = new stdClass;
$row->id = 1;
$row->name = 'John';
$row->posts = ['id' => 1, 'title' => 'Demo'];
- $author = new stdClass();
+ $author = new stdClass;
$author->name = 'Billy';
$row->author = $author;
diff --git a/tests/Unit/QueryDataTableTest.php b/tests/Unit/QueryDataTableTest.php
index 177b6796..20864a72 100644
--- a/tests/Unit/QueryDataTableTest.php
+++ b/tests/Unit/QueryDataTableTest.php
@@ -4,6 +4,7 @@
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
+use PHPUnit\Framework\Attributes\Test;
use Yajra\DataTables\Tests\Models\User;
use Yajra\DataTables\Tests\TestCase;
@@ -40,9 +41,9 @@ public function test_complex_query_use_select_in_count()
->select('users.*')
->addSelect([
'last_post_id' => DB::table('posts')
- ->whereColumn('posts.user_id', 'users.id')
- ->orderBy('created_at')
- ->select('id'),
+ ->whereColumn('posts.user_id', 'users.id')
+ ->orderBy('created_at')
+ ->select('id'),
])
->orderBy(
DB::table('posts')->whereColumn('posts.user_id', 'users.id')->orderBy('created_at')->select('created_at')
@@ -61,9 +62,9 @@ public function test_complex_query_can_ignore_select_in_count()
->select('users.*')
->addSelect([
'last_post_id' => DB::table('posts')
- ->whereColumn('posts.user_id', 'users.id')
- ->orderBy('created_at')
- ->select('id'),
+ ->whereColumn('posts.user_id', 'users.id')
+ ->orderBy('created_at')
+ ->select('id'),
])
->orderBy(
DB::table('posts')->whereColumn('posts.user_id', 'users.id')->orderBy('created_at')->select('created_at')
@@ -74,25 +75,60 @@ public function test_complex_query_can_ignore_select_in_count()
$this->assertEquals(20, $dataTable->count());
}
- public function test_simple_queries_with_complexe_select_are_wrapped_without_selects()
+ public function test_simple_queries_with_complexe_select_are_not_wrapped()
{
/** @var \Yajra\DataTables\QueryDataTable $dataTable */
$dataTable = app('datatables')->of(
DB::table('users')
- ->select('users.*')
- ->addSelect([
- 'last_post_id' => DB::table('posts')
- ->whereColumn('posts.user_id', 'users.id')
- ->orderBy('created_at')
- ->select('id'),
- ])
+ ->select('users.*')
+ ->addSelect([
+ 'last_post_id' => DB::table('posts')
+ ->whereColumn('posts.user_id', 'users.id')
+ ->orderBy('created_at')
+ ->select('id'),
+ ])
);
- $this->assertQueryWrapped(true, $dataTable->prepareCountQuery());
- $this->assertQueryHasNoSelect(true, $dataTable->prepareCountQuery());
+ $this->assertQueryWrapped(false, $dataTable->prepareCountQuery());
$this->assertEquals(20, $dataTable->count());
}
+ public function test_simple_queries_with_complexe_where_are_not_wrapped()
+ {
+ /** @var \Yajra\DataTables\QueryDataTable $dataTable */
+ $dataTable = app('datatables')->of(
+ DB::table('users')
+ ->select('users.*')
+ ->where(
+ DB::table('posts')
+ ->whereColumn('posts.user_id', 'users.id')
+ ->orderBy('created_at')
+ ->select('title'), 'User-1 Post-1'
+ )
+ );
+
+ $this->assertQueryWrapped(false, $dataTable->prepareCountQuery());
+ $this->assertEquals(1, $dataTable->prepareCountQuery()->count());
+ }
+
+ public function test_simple_eloquent_queries_with_complexe_where_are_not_wrapped()
+ {
+ /** @var \Yajra\DataTables\QueryDataTable $dataTable */
+ $dataTable = app('datatables')->of(
+ User::query()
+ ->select('users.*')
+ ->where(
+ DB::table('posts')
+ ->whereColumn('posts.user_id', 'users.id')
+ ->orderBy('created_at')
+ ->select('title'), 'User-1 Post-1'
+ )
+ );
+
+ $this->assertQueryWrapped(false, $dataTable->prepareCountQuery());
+ $this->assertEquals(1, $dataTable->prepareCountQuery()->count());
+ }
+
public function test_simple_queries_are_not_wrapped_and_countable()
{
/** @var \Yajra\DataTables\QueryDataTable $dataTable */
@@ -117,10 +153,9 @@ public function test_complexe_queries_can_be_wrapped_and_countable()
/**
* @param $expected bool
- * @param $query \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
- * @return void
+ * @param $query \Illuminate\Contracts\Database\Query\Builder
*/
- protected function assertQueryWrapped($expected, $query)
+ protected function assertQueryWrapped($expected, $query): void
{
$sql = $query->toSql();
@@ -129,13 +164,50 @@ protected function assertQueryWrapped($expected, $query)
/**
* @param $expected bool
- * @param $query \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder
- * @return void
+ * @param $query \Illuminate\Contracts\Database\Query\Builder
*/
- public function assertQueryHasNoSelect($expected, $query)
+ public function assertQueryHasNoSelect($expected, $query): void
{
- $sql = $query->toSql();
+ $sql = $query->select(DB::raw('count(*)'))->toSql();
+
+ $this->assertSame($expected, Str::startsWith($sql, 'select count(*) from (select 1 as dt_row_count from'), "'{$sql}' has select");
+ }
+
+ #[Test]
+ public function test_column_name_is_resolved_in_column_control(): void
+ {
+ app('datatables.request')->merge([
+ 'columns' => [
+ [
+ 'name' => 'id',
+ 'data' => 'id',
+ 'searchable' => 'true',
+ 'orderable' => 'true',
+ 'search' => ['value' => null, 'regex' => 'false'],
+ 'columnControl' => [
+ 'search' => [
+ 'value' => '123',
+ 'logic' => 'equal',
+ 'type' => 'num',
+ ],
+ ],
+ ],
+ ],
+ ]);
+
+ /** @var \Yajra\DataTables\QueryDataTable $dataTable */
+ $dataTable = app('datatables')->of(
+ User::query()
+ ->select('users.*')
+ ->join('role_user', 'users.id', '=', 'role_user.user_id')
+ ->join('roles', 'role_user.role_id', '=', 'roles.id')
+ );
- $this->assertSame($expected, Str::startsWith($sql, 'select * from (select 1 as dt_row_count from'), "'{$sql}' is not wrapped");
+ $dataTable->columnControlSearch();
+
+ $this->assertStringContainsString(
+ '"users"."id" = \'123\'',
+ $dataTable->getQuery()->toRawSql()
+ );
}
}
diff --git a/tests/Unit/RequestTest.php b/tests/Unit/RequestTest.php
index 76039f75..cbcdfaa8 100644
--- a/tests/Unit/RequestTest.php
+++ b/tests/Unit/RequestTest.php
@@ -2,12 +2,13 @@
namespace Yajra\DataTables\Tests\Unit;
+use PHPUnit\Framework\Attributes\Test;
use Yajra\DataTables\Tests\TestCase;
use Yajra\DataTables\Utilities\Request;
class RequestTest extends TestCase
{
- /** @test */
+ #[Test]
public function it_can_get_the_base_request()
{
$request = $this->getRequest();
@@ -15,7 +16,15 @@ public function it_can_get_the_base_request()
$this->assertInstanceOf(\Illuminate\Http\Request::class, $request->getBaseRequest());
}
- /** @test */
+ /**
+ * @return \Yajra\DataTables\Utilities\Request
+ */
+ protected function getRequest()
+ {
+ return new Request;
+ }
+
+ #[Test]
public function it_is_searchable()
{
$_GET['search']['value'] = '';
@@ -34,7 +43,7 @@ public function it_is_searchable()
$this->assertTrue($request->isSearchable());
}
- /** @test */
+ #[Test]
public function it_can_get_column_keyword()
{
$_GET['columns'] = [];
@@ -55,7 +64,7 @@ public function it_can_get_column_keyword()
$this->assertEquals('bar', $request->columnKeyword(1));
}
- /** @test */
+ #[Test]
public function it_has_orderable_columns()
{
$_GET['columns'] = [];
@@ -80,7 +89,7 @@ public function it_has_orderable_columns()
$this->assertTrue($request->isColumnOrderable(0));
}
- /** @test */
+ #[Test]
public function it_has_will_set_descending_on_other_values_on_orderable_columns()
{
$_GET['columns'] = [];
@@ -105,7 +114,7 @@ public function it_has_will_set_descending_on_other_values_on_orderable_columns(
$this->assertTrue($request->isColumnOrderable(0));
}
- /** @test */
+ #[Test]
public function it_has_searchable_column_index()
{
$_GET['columns'] = [];
@@ -125,7 +134,7 @@ public function it_has_searchable_column_index()
$this->assertEquals('bar', $request->columnName(1));
}
- /** @test */
+ #[Test]
public function it_has_keyword()
{
$_GET['search'] = [];
@@ -135,7 +144,7 @@ public function it_has_keyword()
$this->assertEquals('foo', $request->keyword());
}
- /** @test */
+ #[Test]
public function it_is_paginationable()
{
$_GET['start'] = 1;
@@ -156,12 +165,4 @@ public function it_is_paginationable()
$request = $this->getRequest();
$this->assertFalse($request->isPaginationable());
}
-
- /**
- * @return \Yajra\DataTables\Utilities\Request
- */
- protected function getRequest()
- {
- return new Request();
- }
}