From c28eca319f2e57c0b004e2fab6693c97bf88e2c2 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sat, 24 Feb 2024 19:19:44 +0800 Subject: [PATCH 001/140] feat: Laravel 11 --- composer.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 04b884df..b5a24f40 100644 --- a/composer.json +++ b/composer.json @@ -14,12 +14,12 @@ } ], "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" + "php": "^8.2", + "illuminate/database": "^11", + "illuminate/filesystem": "^11", + "illuminate/http": "^11", + "illuminate/support": "^11", + "illuminate/view": "^11" }, "require-dev": { "algolia/algoliasearch-client-php": "^3.4", @@ -51,7 +51,7 @@ }, "extra": { "branch-alias": { - "dev-master": "10.x-dev" + "dev-master": "11.x-dev" }, "laravel": { "providers": [ From 8893f356eb5f6e25c1b0088a45ecedb4c3f729c1 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Mon, 26 Feb 2024 23:16:43 +0800 Subject: [PATCH 002/140] test: use Test attribute --- composer.json | 3 +- .../Integration/BelongsToManyRelationTest.php | 7 +- tests/Integration/BelongsToRelationTest.php | 7 +- tests/Integration/CollectionDataTableTest.php | 27 +-- tests/Integration/CustomOrderTest.php | 3 +- tests/Integration/DeepRelationTest.php | 5 +- tests/Integration/EloquentDataTableTest.php | 25 +-- tests/Integration/EloquentJoinTest.php | 7 +- tests/Integration/HasManyRelationTest.php | 9 +- tests/Integration/HasOneRelationTest.php | 27 +-- tests/Integration/HasOneThroughTest.php | 11 +- tests/Integration/IgnoreGettersTest.php | 7 +- tests/Integration/MorphToRelationTest.php | 25 ++- tests/Integration/QueryDataTableTest.php | 171 +++++++++--------- tests/Unit/RequestTest.php | 33 ++-- 15 files changed, 194 insertions(+), 173 deletions(-) diff --git a/composer.json b/composer.json index b5a24f40..b1c14a5b 100644 --- a/composer.json +++ b/composer.json @@ -26,8 +26,7 @@ "laravel/scout": "^10.5", "meilisearch/meilisearch-php": "^1.4", "larastan/larastan": "^2.4", - "orchestra/testbench": "^8", - "yajra/laravel-datatables-html": "^9.3.4|^10" + "orchestra/testbench": "^9" }, "suggest": { "yajra/laravel-datatables-export": "Plugin for server-side exporting using livewire and queue worker.", diff --git a/tests/Integration/BelongsToManyRelationTest.php b/tests/Integration/BelongsToManyRelationTest.php index 4f2ad21f..f45b286d 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([ diff --git a/tests/Integration/BelongsToRelationTest.php b/tests/Integration/BelongsToRelationTest.php index 424ccac5..d99f50fb 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([ diff --git a/tests/Integration/CollectionDataTableTest.php b/tests/Integration/CollectionDataTableTest.php index 1538f7a5..9519922c 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 = [ diff --git a/tests/Integration/CustomOrderTest.php b/tests/Integration/CustomOrderTest.php index 467e8e4d..7513acb6 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([ 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..fad48a63 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(); diff --git a/tests/Integration/EloquentJoinTest.php b/tests/Integration/EloquentJoinTest.php index e0bfaed5..a867b4e4 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([ diff --git a/tests/Integration/HasManyRelationTest.php b/tests/Integration/HasManyRelationTest.php index 661bb8cf..28abdac1 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([ diff --git a/tests/Integration/HasOneRelationTest.php b/tests/Integration/HasOneRelationTest.php index e1ceb416..e5d39062 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([ @@ -125,15 +126,19 @@ protected function setUp(): void }); $this->app['router']->get('/relations/hasOneWithTrashed', function (DataTables $datatables) { - return $datatables->eloquent(User::with(['heart' => function ($query) { - $query->withTrashed(); - }])->select('users.*'))->toJson(); + return $datatables->eloquent(User::with([ + 'heart' => function ($query) { + $query->withTrashed(); + }, + ])->select('users.*'))->toJson(); }); $this->app['router']->get('/relations/hasOneOnlyTrashed', function (DataTables $datatables) { - return $datatables->eloquent(User::with(['heart' => function ($query) { - $query->onlyTrashed(); - }])->select('users.*'))->toJson(); + return $datatables->eloquent(User::with([ + 'heart' => function ($query) { + $query->onlyTrashed(); + }, + ])->select('users.*'))->toJson(); }); } } diff --git a/tests/Integration/HasOneThroughTest.php b/tests/Integration/HasOneThroughTest.php index f7347945..c21bd7a9 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([ diff --git a/tests/Integration/IgnoreGettersTest.php b/tests/Integration/IgnoreGettersTest.php index 2db7c2a7..92f111fe 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,7 +25,7 @@ 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) { @@ -46,7 +47,7 @@ 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) { diff --git a/tests/Integration/MorphToRelationTest.php b/tests/Integration/MorphToRelationTest.php index 51ca65fd..8937af1d 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([ @@ -103,15 +104,19 @@ protected function setUp(): void }); $this->app['router']->get('/relations/morphToWithTrashed', function (DataTables $datatables) { - return $datatables->eloquent(User::with(['user' => function ($query) { - $query->withTrashed(); - }])->select('users.*'))->toJson(); + return $datatables->eloquent(User::with([ + 'user' => function ($query) { + $query->withTrashed(); + }, + ])->select('users.*'))->toJson(); }); $this->app['router']->get('/relations/morphToOnlyTrashed', function (DataTables $datatables) { - return $datatables->eloquent(User::with(['user' => function ($query) { - $query->onlyTrashed(); - }])->select('users.*'))->toJson(); + return $datatables->eloquent(User::with([ + 'user' => function ($query) { + $query->onlyTrashed(); + }, + ])->select('users.*'))->toJson(); }); } } diff --git a/tests/Integration/QueryDataTableTest.php b/tests/Integration/QueryDataTableTest.php index 14aa280b..e2918f1a 100644 --- a/tests/Integration/QueryDataTableTest.php +++ b/tests/Integration/QueryDataTableTest.php @@ -7,6 +7,7 @@ use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\DB; +use PHPUnit\Framework\Attributes\Test; use Yajra\DataTables\DataTables; use Yajra\DataTables\Facades\DataTables as DatatablesFacade; use Yajra\DataTables\QueryDataTable; @@ -18,7 +19,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 +30,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 +41,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,7 +52,7 @@ public function it_can_set_total_filtered_records() ]); } - /** @test */ + #[Test] public function it_returns_all_records_when_no_parameters_is_passed() { $crawler = $this->call('GET', '/query/users'); @@ -62,7 +63,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', '/query/users', [ @@ -80,7 +81,7 @@ public function it_can_perform_global_search() ]); } - /** @test */ + #[Test] public function it_can_skip_total_records_count_query() { $crawler = $this->call('GET', '/query/simple', [ @@ -98,7 +99,7 @@ public function it_can_skip_total_records_count_query() ]); } - /** @test */ + #[Test] public function it_can_perform_multiple_term_global_search() { $crawler = $this->call('GET', '/query/users', [ @@ -116,7 +117,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 +126,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 +135,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 +144,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 +153,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 +162,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 +181,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,7 +189,7 @@ public function it_returns_only_the_selected_columns() $this->assertArrayHasKey('name', $json['data'][0]); } - /** @test */ + #[Test] public function it_edit_only_the_selected_columns_after_using_editOnlySelectedColumns() { $json = $this->call('GET', '/query/edit-columns', [ @@ -202,7 +203,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 +211,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 +219,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 +228,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 +249,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 +271,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 +292,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 +308,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 +333,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 +354,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'); @@ -385,8 +386,8 @@ protected function setUp(): void $router->get('/query/formatColumn', function (DataTables $dataTable) { return $dataTable->query(DB::table('users')) - ->formatColumn('created_at', new DateFormatter('Y-m-d')) - ->toJson(); + ->formatColumn('created_at', new DateFormatter('Y-m-d')) + ->toJson(); }); $router->get('/query/simple', function (DataTables $dataTable) { @@ -395,118 +396,118 @@ protected function setUp(): void $router->get('/query/addColumn', function (DataTables $dataTable) { return $dataTable->query(DB::table('users')) - ->addColumn('foo', 'bar') - ->toJson(); + ->addColumn('foo', 'bar') + ->toJson(); }); $router->get('/query/indexColumn', function (DataTables $dataTable) { return $dataTable->query(DB::table('users')) - ->addIndexColumn() - ->toJson(); + ->addIndexColumn() + ->toJson(); }); $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(); + ->addColumn('foo', 'bar') + ->filterColumn('foo', function (Builder $builder, $keyword) { + $builder->where('1', $keyword); + }) + ->toJson(); }); $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(); + ->addColumn('foo', 'bar') + ->filterColumn('foo', function (Builder $builder, $keyword) { + $builder->where('name', $keyword); + }) + ->blacklist(['foo']) + ->toJson(); }); $router->get('/query/only', function (DataTables $dataTable) { return $dataTable->query(DB::table('users')) - ->addColumn('foo', 'bar') - ->only(['name']) - ->toJson(); + ->addColumn('foo', 'bar') + ->only(['name']) + ->toJson(); }); $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(); + ->editColumn('id', function () { + return 'edited'; + }) + ->editOnlySelectedColumns() + ->editColumn('name', function () { + return 'edited'; + }) + ->editColumn('email', function () { + return 'edited'; + }) + ->toJson(); }); $router->get('/query/xss-add', function (DataTables $dataTable) { return $dataTable->query(DB::table('users')) - ->addColumn('foo', 'Allowed') - ->addColumn('bar', function () { - return 'Allowed'; - }) - ->toJson(); + ->addColumn('foo', 'Allowed') + ->addColumn('bar', function () { + return 'Allowed'; + }) + ->toJson(); }); $router->get('/query/xss-edit', function (DataTables $dataTable) { return $dataTable->query(DB::table('users')) - ->editColumn('name', 'Allowed') - ->editColumn('email', function () { - return 'Allowed'; - }) - ->toJson(); + ->editColumn('name', 'Allowed') + ->editColumn('email', function () { + return 'Allowed'; + }) + ->toJson(); }); $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(); + ->addColumn('foo', 'Allowed') + ->editColumn('name', 'Allowed') + ->editColumn('email', function () { + return '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(); + ->searchPane('id', $options) + ->toJson(); }); $router->get('/set-total-records', function (DataTables $dataTable) { return $dataTable->query(DB::table('users')) - ->setTotalRecords(10) - ->toJson(); + ->setTotalRecords(10) + ->toJson(); }); $router->get('/zero-total-records', function (DataTables $dataTable) { return $dataTable->query(DB::table('users')) - ->setTotalRecords(0) - ->toJson(); + ->setTotalRecords(0) + ->toJson(); }); $router->get('/set-filtered-records', function (DataTables $dataTable) { return $dataTable->query(DB::table('users')) - ->setFilteredRecords(10) - ->toJson(); + ->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(); + ->addColumn('name_di', function ($user, User $u) { + return $u->newQuery()->find($user->id)->name.'_di'; + }) + ->toJson(); }); } } diff --git a/tests/Unit/RequestTest.php b/tests/Unit/RequestTest.php index 76039f75..b93cb335 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(); - } } From 061c4f83bc38de676f6bc627e1dc134507d89f99 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Mon, 26 Feb 2024 23:29:13 +0800 Subject: [PATCH 003/140] fix: remove negated error --- phpstan.neon.dist | 1 - 1 file changed, 1 deletion(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index fc5fd64c..9971e78a 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -10,7 +10,6 @@ parameters: ignoreErrors: - '#Unsafe usage of new static\(\).#' - - '#Negated boolean expression is always false.#' excludePaths: - src/helper.php From b48bd0fd88a2fdc0b38f7e22ee93b65b9b238bec Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Mon, 26 Feb 2024 23:34:49 +0800 Subject: [PATCH 004/140] chore: remove 8.1 --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 9ad5f7f9..8ff98cc4 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: true matrix: - php: [8.1, 8.2, 8.3] + php: [8.2, 8.3] stability: [prefer-stable] name: PHP ${{ matrix.php }} - ${{ matrix.stability }} From 8117e7a981dbafdb1aa003dbebc00605d1d235df Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Mon, 26 Feb 2024 23:41:51 +0800 Subject: [PATCH 005/140] chore: remove 8.2+ --- .github/workflows/static-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 1f150293..8e9365da 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -29,7 +29,7 @@ jobs: strategy: fail-fast: true matrix: - php: [8.1] + php: [8.2, 8.3] stability: [prefer-stable] steps: From 8dce00e5b3c8a085281b7c694376f984a396975b Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Mon, 26 Feb 2024 23:56:54 +0800 Subject: [PATCH 006/140] fix: phpstan - remove html builder dep --- src/DataTables.php | 32 ++++++-------------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/src/DataTables.php b/src/DataTables.php index 2a45f909..486d05c1 100644 --- a/src/DataTables.php +++ b/src/DataTables.php @@ -19,13 +19,6 @@ class DataTables */ 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. @@ -109,10 +102,11 @@ public function getConfig() * * @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); @@ -125,10 +119,11 @@ public function query(QueryBuilder $builder): QueryDataTable * * @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); @@ -141,10 +136,11 @@ public function eloquent(EloquentBuilder $builder): EloquentDataTable * * @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); @@ -163,22 +159,6 @@ public function resource($resource) return ApiResourceDataTable::create($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 From f483023117f8c14515bf495ecb4e299b0318ad6b Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 27 Feb 2024 00:04:22 +0800 Subject: [PATCH 007/140] fix: phpstan error --- src/EloquentDataTable.php | 3 ++- src/QueryDataTable.php | 12 ++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/EloquentDataTable.php b/src/EloquentDataTable.php index fdd09937..2b3e82f7 100644 --- a/src/EloquentDataTable.php +++ b/src/EloquentDataTable.php @@ -266,7 +266,8 @@ protected function joinEagerLoadedColumn($relation, $relationColumn) 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/QueryDataTable.php b/src/QueryDataTable.php index b4beeafe..462bb3f1 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -105,17 +105,14 @@ class QueryDataTable extends DataTableAbstract * * @var bool */ - protected $disableUserOrdering = false; + protected bool $disableUserOrdering = false; - /** - * @param QueryBuilder $builder - */ public function __construct(QueryBuilder $builder) { $this->query = $builder; $this->request = app('datatables.request'); $this->config = app('datatables.config'); - $this->columns = $builder->columns; + $this->columns = $builder->getColumns(); if ($this->config->isDebugging()) { $this->getConnection()->enableQueryLog(); @@ -408,7 +405,7 @@ protected function applyFilterColumn($query, string $columnName, string $keyword * @param QueryBuilder|EloquentBuilder|null $instance * @return QueryBuilder */ - protected function getBaseQueryBuilder($instance = null) + protected function getBaseQueryBuilder($instance = null): QueryBuilder { if (! $instance) { $instance = $this->query; @@ -544,9 +541,8 @@ protected function addTablePrefix($query, string $column): string { if (! str_contains($column, '.')) { $q = $this->getBaseQueryBuilder($query); - $from = $q->from; + $from = $q->from ?? ''; - /** @phpstan-ignore-next-line */ if (! $from instanceof Expression) { if (str_contains($from, ' as ')) { $from = explode(' as ', $from)[1]; From e3d741ca96b994552ed25c6c69323fb750dbb42c Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Mon, 26 Feb 2024 16:05:23 +0000 Subject: [PATCH 008/140] Apply fixes from StyleCI --- src/DataTables.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/DataTables.php b/src/DataTables.php index 486d05c1..a2ca2114 100644 --- a/src/DataTables.php +++ b/src/DataTables.php @@ -102,6 +102,7 @@ public function getConfig() * * @param QueryBuilder $builder * @return \Yajra\DataTables\QueryDataTable + * * @throws \Yajra\DataTables\Exceptions\Exception */ public function query(QueryBuilder $builder): QueryDataTable @@ -119,6 +120,7 @@ public function query(QueryBuilder $builder): QueryDataTable * * @param \Illuminate\Contracts\Database\Eloquent\Builder $builder * @return \Yajra\DataTables\EloquentDataTable + * * @throws \Yajra\DataTables\Exceptions\Exception */ public function eloquent(EloquentBuilder $builder): EloquentDataTable @@ -136,6 +138,7 @@ public function eloquent(EloquentBuilder $builder): EloquentDataTable * * @param \Illuminate\Support\Collection|array $collection * @return \Yajra\DataTables\CollectionDataTable + * * @throws \Yajra\DataTables\Exceptions\Exception */ public function collection($collection): CollectionDataTable From fb5ff48f21d73ad77b3fdc819b39c4091bb1255a Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 27 Feb 2024 00:08:36 +0800 Subject: [PATCH 009/140] docs: laravel 11 --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f31f2175..652ddd7d 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# jQuery DataTables API for Laravel 4|5|6|7|8|9|10 +# jQuery DataTables API for Laravel 4|5|6|7|8|9|10|11 [![Join the chat at https://gitter.im/yajra/laravel-datatables](https://badges.gitter.im/yajra/laravel-datatables.svg)](https://gitter.im/yajra/laravel-datatables?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://www.paypal.me/yajra) [![Donate](https://img.shields.io/badge/donate-patreon-blue.svg)](https://www.patreon.com/bePatron?u=4521203) -[![Laravel 4.2|5.x|6|7|8|9|10](https://img.shields.io/badge/Laravel-4.2|5.x|6|7|8|9|10-orange.svg)](http://laravel.com) +[![Laravel 4.2|5.x|6|7|8|9|10|11](https://img.shields.io/badge/Laravel-4.2|5.x|6|7|8|9|10|11-orange.svg)](http://laravel.com) [![Latest Stable Version](https://img.shields.io/packagist/v/yajra/laravel-datatables-oracle.svg)](https://packagist.org/packages/yajra/laravel-datatables-oracle) [![Continuous Integration](https://github.com/yajra/laravel-datatables/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/yajra/laravel-datatables/actions/workflows/continuous-integration.yml) [![Static Analysis](https://github.com/yajra/laravel-datatables/actions/workflows/static-analysis.yml/badge.svg)](https://github.com/yajra/laravel-datatables/actions/workflows/static-analysis.yml) @@ -40,16 +40,15 @@ return datatables(User::all())->toJson(); ## 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/) +- [jQuery 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 @@ -70,6 +69,7 @@ return datatables(User::all())->toJson(); | 8.x.x | 9.x | | 9.x.x | 10.x | | 10.x.x | 10.x | +| 11.x.x | 11.x | ## Quick Installation From e39d908d2e9e2e7f5607e12208574c5d11fc1b98 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 27 Feb 2024 00:09:35 +0800 Subject: [PATCH 010/140] docs: version 11 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 652ddd7d..ef55b8a2 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ return datatables(User::all())->toJson(); ## Quick Installation ```bash -composer require yajra/laravel-datatables-oracle:"^10.0" +composer require yajra/laravel-datatables-oracle:"^11" ``` #### Service Provider & Facade (Optional on Laravel 5.5+) From 6cde65b6a0eb15dcf99b6a8810914218080e8a95 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 27 Feb 2024 09:58:28 +0800 Subject: [PATCH 011/140] docs: remove versions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ef55b8a2..0931f98e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# jQuery DataTables API for Laravel 4|5|6|7|8|9|10|11 +# jQuery DataTables API for Laravel [![Join the chat at https://gitter.im/yajra/laravel-datatables](https://badges.gitter.im/yajra/laravel-datatables.svg)](https://gitter.im/yajra/laravel-datatables?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://www.paypal.me/yajra) From 80c5cda6262e78b092c65d91f6b346d93e44c59d Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 27 Feb 2024 09:59:18 +0800 Subject: [PATCH 012/140] docs: fix badge spacing --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0931f98e..5418d624 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ [![Latest Stable Version](https://img.shields.io/packagist/v/yajra/laravel-datatables-oracle.svg)](https://packagist.org/packages/yajra/laravel-datatables-oracle) [![Continuous Integration](https://github.com/yajra/laravel-datatables/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/yajra/laravel-datatables/actions/workflows/continuous-integration.yml) [![Static Analysis](https://github.com/yajra/laravel-datatables/actions/workflows/static-analysis.yml/badge.svg)](https://github.com/yajra/laravel-datatables/actions/workflows/static-analysis.yml) + [![Total Downloads](https://poser.pugx.org/yajra/laravel-datatables-oracle/downloads.png)](https://packagist.org/packages/yajra/laravel-datatables-oracle) [![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://packagist.org/packages/yajra/laravel-datatables-oracle) From 01d9dae1376506f7f6d377f8748838a0f1e6e5ec Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 27 Feb 2024 10:22:28 +0800 Subject: [PATCH 013/140] feat: pint --- composer.json | 3 +- pint.json | 3 + src/CollectionDataTable.php | 20 ---- src/Contracts/DataTable.php | 14 --- src/DataTableAbstract.php | 102 +---------------- src/DataTables.php | 27 +---- src/EloquentDataTable.php | 11 +- src/Processors/DataProcessor.php | 63 +---------- src/Processors/RowProcessor.php | 2 - src/QueryDataTable.php | 116 -------------------- src/Utilities/Config.php | 17 --- src/Utilities/Helper.php | 8 -- src/Utilities/Request.php | 43 -------- tests/Integration/CustomOrderTest.php | 8 +- tests/Integration/EloquentDataTableTest.php | 16 +-- tests/Integration/EloquentJoinTest.php | 4 +- tests/Unit/QueryDataTableTest.php | 26 ++--- 17 files changed, 39 insertions(+), 444 deletions(-) create mode 100644 pint.json diff --git a/composer.json b/composer.json index b1c14a5b..30e70209 100644 --- a/composer.json +++ b/composer.json @@ -23,9 +23,10 @@ }, "require-dev": { "algolia/algoliasearch-client-php": "^3.4", + "larastan/larastan": "^2.4", + "laravel/pint": "^1.14", "laravel/scout": "^10.5", "meilisearch/meilisearch-php": "^1.4", - "larastan/larastan": "^2.4", "orchestra/testbench": "^9" }, "suggest": { 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/src/CollectionDataTable.php b/src/CollectionDataTable.php index 99e7da1a..515550f6 100644 --- a/src/CollectionDataTable.php +++ b/src/CollectionDataTable.php @@ -29,8 +29,6 @@ class CollectionDataTable extends DataTableAbstract /** * The offset of the first record in the full dataset. - * - * @var int */ private int $offset = 0; @@ -52,7 +50,6 @@ public function __construct(Collection $collection) * Serialize collection. * * @param mixed $collection - * @return array */ protected function serialize($collection): array { @@ -87,8 +84,6 @@ public static function create($source) /** * Count results. - * - * @return int */ public function count(): int { @@ -97,8 +92,6 @@ public function count(): int /** * Perform column search. - * - * @return void */ public function columnSearch(): void { @@ -143,8 +136,6 @@ function ($row) use ($column, $keyword, $regex) { /** * Perform pagination. - * - * @return void */ public function paging(): void { @@ -158,7 +149,6 @@ public function paging(): void * Organizes works. * * @param bool $mDataSupport - * @return \Illuminate\Http\JsonResponse * * @throws \Exception */ @@ -200,7 +190,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 +211,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 +222,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 +249,6 @@ protected function globalSearch(string $keyword): void /** * Perform default query orderBy clause. - * - * @return void */ protected function defaultOrdering(): void { @@ -291,9 +274,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..6e721fea 100644 --- a/src/Contracts/DataTable.php +++ b/src/Contracts/DataTable.php @@ -16,15 +16,11 @@ public function results(): Collection; /** * Count results. - * - * @return int */ public function count(): int; /** * Count total items. - * - * @return int */ public function totalCount(): int; @@ -32,7 +28,6 @@ public function totalCount(): int; * Set auto filter off and run your own filter. * Overrides global search. * - * @param callable $callback * @param bool $globalSearch * @return static */ @@ -40,29 +35,21 @@ public function filter(callable $callback, $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; @@ -70,7 +57,6 @@ public function ordering(): void; * Organizes works. * * @param bool $mDataSupport - * @return \Illuminate\Http\JsonResponse */ public function make($mDataSupport = true): JsonResponse; } diff --git a/src/DataTableAbstract.php b/src/DataTableAbstract.php index e3d6ca86..e2bec772 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,21 @@ 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; /** * Auto-filter flag. - * - * @var bool */ protected bool $autoFilter = true; @@ -99,8 +82,6 @@ abstract class DataTableAbstract implements DataTable /** * DT row templates container. - * - * @var array */ protected array $templates = [ 'DT_RowId' => '', @@ -118,31 +99,18 @@ 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; @@ -292,8 +260,6 @@ public function removeColumn(): static /** * Get columns definition. - * - * @return array */ protected function getColumnsDefinition(): array { @@ -306,7 +272,6 @@ protected function getColumnsDefinition(): array /** * Get only selected columns in response. * - * @param array $columns * @return $this */ public function only(array $columns = []): static @@ -332,7 +297,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 +310,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 +324,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 +372,6 @@ public function setRowId($content): static /** * Set DT_RowData templates. * - * @param array $data * @return $this */ public function setRowData(array $data): static @@ -438,7 +399,6 @@ public function addRowData($key, $value): static * Set DT_RowAttr templates. * result: . * - * @param array $data * @return $this */ public function setRowAttr(array $data): static @@ -483,8 +443,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 +455,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 +467,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 +479,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 +491,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 +503,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 +515,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 +527,6 @@ public function setMultiTerm(bool $multiTerm = true): static /** * Set total records manually. * - * @param int $total * @return $this */ public function setTotalRecords(int $total): static @@ -603,7 +554,6 @@ public function skipTotalRecords(): static /** * Set filtered records manually. * - * @param int $total * @return $this */ public function setFilteredRecords(int $total): static @@ -656,7 +606,6 @@ public function pushToBlacklist($column): static * Check if column is blacklisted. * * @param string $column - * @return bool */ protected function isBlacklisted($column): bool { @@ -675,8 +624,6 @@ protected function isBlacklisted($column): bool /** * Perform sorting of columns. - * - * @return void */ public function ordering(): void { @@ -696,8 +643,6 @@ abstract protected function resolveCallbackParameter(); /** * Perform default query orderBy clause. - * - * @return void */ abstract protected function defaultOrdering(): void; @@ -705,7 +650,6 @@ 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 */ @@ -737,10 +681,9 @@ public function toJson($options = 0) * * @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, $options, ?callable $builder = null): static { $options = value($options); @@ -756,8 +699,6 @@ public function searchPane($column, $options, callable $builder = null): static /** * Convert instance to array. - * - * @return array */ public function toArray(): array { @@ -766,8 +707,6 @@ public function toArray(): array /** * Perform necessary filters. - * - * @return void */ protected function filterRecords(): void { @@ -786,8 +725,6 @@ protected function filterRecords(): void /** * Perform global search. - * - * @return void */ public function filtering(): void { @@ -807,7 +744,6 @@ public function filtering(): void * individual words and searches for each of them. * * @param string $keyword - * @return void */ protected function smartGlobalSearch($keyword): void { @@ -822,16 +758,11 @@ 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 { @@ -840,8 +771,6 @@ protected function searchPanesSearch(): void /** * Count total items. - * - * @return int */ public function totalCount(): int { @@ -850,8 +779,6 @@ public function totalCount(): int /** * Count filtered items. - * - * @return int */ protected function filteredCount(): int { @@ -860,8 +787,6 @@ protected function filteredCount(): int /** * Apply pagination. - * - * @return void */ protected function paginate(): void { @@ -875,7 +800,6 @@ protected function paginate(): void * * @param iterable $results * @param array $processed - * @return array */ protected function transform($results, $processed): array { @@ -895,7 +819,6 @@ protected function transform($results, $processed): array * * @param iterable $results * @param bool $object - * @return array * * @throws \Exception */ @@ -913,9 +836,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 +864,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 +872,6 @@ protected function attachAppends(array $data): array /** * Append debug parameters on output. - * - * @param array $output - * @return array */ protected function showDebugger(array $output): array { @@ -969,7 +883,6 @@ protected function showDebugger(array $output): array /** * Return an error json response. * - * @param \Exception $exception * @return \Illuminate\Http\JsonResponse * * @throws \Yajra\DataTables\Exceptions\Exception|\Exception @@ -1010,7 +923,6 @@ public function getLogger() /** * Set monolog/logger instance. * - * @param \Psr\Log\LoggerInterface $logger * @return $this */ public function setLogger(LoggerInterface $logger): static @@ -1022,9 +934,6 @@ public function setLogger(LoggerInterface $logger): static /** * Setup search keyword. - * - * @param string $value - * @return string */ protected function setupKeyword(string $value): string { @@ -1043,10 +952,6 @@ 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 */ protected function getColumnName(int $index, bool $wantsAlias = false): ?string { @@ -1070,9 +975,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,8 +987,6 @@ protected function getColumnNameByIndex(int $index): string /** * If column name could not be resolved then use primary key. - * - * @return string */ protected function getPrimaryKeyName(): string { diff --git a/src/DataTables.php b/src/DataTables.php index a2ca2114..45ce4dc5 100644 --- a/src/DataTables.php +++ b/src/DataTables.php @@ -7,6 +7,8 @@ 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,8 +16,6 @@ class DataTables /** * DataTables request object. - * - * @var \Yajra\DataTables\Utilities\Request */ protected Utilities\Request $request; @@ -79,20 +79,16 @@ public static function make($source) /** * 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'); } @@ -100,9 +96,6 @@ public function getConfig() /** * DataTables using Query. * - * @param QueryBuilder $builder - * @return \Yajra\DataTables\QueryDataTable - * * @throws \Yajra\DataTables\Exceptions\Exception */ public function query(QueryBuilder $builder): QueryDataTable @@ -118,9 +111,6 @@ 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 @@ -137,7 +127,6 @@ public function eloquent(EloquentBuilder $builder): EloquentDataTable * DataTables using Collection. * * @param \Illuminate\Support\Collection|array $collection - * @return \Yajra\DataTables\CollectionDataTable * * @throws \Yajra\DataTables\Exceptions\Exception */ @@ -163,10 +152,6 @@ public function resource($resource) } /** - * @param string $engine - * @param string $parent - * @return void - * * @throws \Yajra\DataTables\Exceptions\Exception */ public function validateDataTable(string $engine, string $parent): void @@ -177,10 +162,6 @@ public function validateDataTable(string $engine, string $parent): void } /** - * @param string $engine - * @param string $parent - * @return void - * * @throws \Yajra\DataTables\Exceptions\Exception */ public function throwInvalidEngineException(string $engine, string $parent): void diff --git a/src/EloquentDataTable.php b/src/EloquentDataTable.php index 2b3e82f7..00f00846 100644 --- a/src/EloquentDataTable.php +++ b/src/EloquentDataTable.php @@ -19,8 +19,6 @@ class EloquentDataTable extends QueryDataTable { /** * EloquentEngine constructor. - * - * @param Model|EloquentBuilder $model */ public function __construct(Model|EloquentBuilder $model) { @@ -39,7 +37,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 +46,6 @@ public static function canCreate($source): bool /** * Add columns in collection. * - * @param array $names * @param bool|int $order * @return $this */ @@ -70,8 +66,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 +73,7 @@ protected function getPrimaryKeyName(): string } /** - * @inheritDoc + * {@inheritDoc} */ protected function compileQuerySearch($query, string $column, string $keyword, string $boolean = 'or', bool $nested = false): void { @@ -165,8 +159,6 @@ protected function isMorphRelation($relation) /** * Resolve the proper column name be used. * - * @param string $column - * @return string * * @throws \Yajra\DataTables\Exceptions\Exception */ @@ -261,7 +253,6 @@ 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 { diff --git a/src/Processors/DataProcessor.php b/src/Processors/DataProcessor.php index de7e3f2a..59dfb5b9 100644 --- a/src/Processors/DataProcessor.php +++ b/src/Processors/DataProcessor.php @@ -9,13 +9,8 @@ class DataProcessor { - /** - * @var int - */ protected int $start; - /** - * @var array - */ + protected array $output = []; /** @@ -28,14 +23,8 @@ class DataProcessor */ protected array $editColumns = []; - /** - * @var array - */ protected array $templates = []; - /** - * @var array - */ protected array $rawColumns = []; /** @@ -43,24 +32,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,26 +45,14 @@ 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) { @@ -110,7 +75,6 @@ public function __construct($results, array $columnDef, array $templates, int $s * Process data to output on browser. * * @param bool $object - * @return array */ public function process($object = false): array { @@ -138,9 +102,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 +127,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 +140,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 +155,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 +177,6 @@ protected function selectOnlyNeededColumns(array $data): array /** * Remove declared hidden columns. - * - * @param array $data - * @return array */ protected function removeExcessColumns(array $data): array { @@ -241,9 +189,6 @@ protected function removeExcessColumns(array $data): array /** * Flatten array with exceptions. - * - * @param array $array - * @return array */ public function flatten(array $array): array { @@ -261,9 +206,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 +227,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..1c6329f8 100644 --- a/src/Processors/RowProcessor.php +++ b/src/Processors/RowProcessor.php @@ -8,7 +8,6 @@ class RowProcessor { /** - * @param array $data * @param array|object $row */ public function __construct(protected array $data, protected $row) @@ -39,7 +38,6 @@ public function rowValue($attribute, $template) * Process DT Row Data and Attr. * * @param string $attribute - * @param array $template * @return $this */ public function rowData($attribute, array $template) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 462bb3f1..13325641 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -17,22 +17,16 @@ 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 +39,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,30 +66,22 @@ 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 bool $disableUserOrdering = false; @@ -119,9 +97,6 @@ public function __construct(QueryBuilder $builder) } } - /** - * @return \Illuminate\Database\Connection - */ public function getConnection(): Connection { /** @var Connection $connection */ @@ -134,7 +109,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 { @@ -145,7 +119,6 @@ public static function canCreate($source): bool * Organizes works. * * @param bool $mDataSupport - * @return \Illuminate\Http\JsonResponse * * @throws \Exception */ @@ -194,8 +167,6 @@ public function prepareQuery(): static /** * Counts current query. - * - * @return int */ public function count(): int { @@ -204,8 +175,6 @@ public function count(): int /** * Prepare count query builder. - * - * @return QueryBuilder */ public function prepareCountQuery(): QueryBuilder { @@ -241,7 +210,6 @@ public function prepareCountQuery(): QueryBuilder * Check if builder query uses complex sql. * * @param QueryBuilder|EloquentBuilder $query - * @return bool */ protected function isComplexQuery($query): bool { @@ -250,9 +218,6 @@ protected function isComplexQuery($query): bool /** * Wrap column with DB grammar. - * - * @param string $column - * @return string */ protected function wrap(string $column): string { @@ -273,8 +238,6 @@ public function keepSelectBindings(): static /** * Perform column search. - * - * @return void */ protected function filterRecords(): void { @@ -302,8 +265,6 @@ protected function filterRecords(): void /** * Perform column search. - * - * @return void */ public function columnSearch(): void { @@ -333,9 +294,6 @@ public function columnSearch(): void /** * Check if column has custom filter handler. - * - * @param string $columnName - * @return bool */ public function hasFilterColumn(string $columnName): bool { @@ -344,10 +302,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 { @@ -376,10 +330,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 { @@ -403,7 +353,6 @@ 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): QueryBuilder { @@ -420,8 +369,6 @@ protected function getBaseQueryBuilder($instance = null): QueryBuilder /** * Get query builder instance. - * - * @return QueryBuilder */ public function getQuery(): QueryBuilder { @@ -430,9 +377,6 @@ public function getQuery(): QueryBuilder /** * Resolve the proper column name be used. - * - * @param string $column - * @return string */ protected function resolveRelationColumn(string $column): string { @@ -441,11 +385,6 @@ protected function resolveRelationColumn(string $column): string /** * 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 { @@ -458,10 +397,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 { @@ -491,9 +426,6 @@ 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 { @@ -511,10 +443,6 @@ protected function castColumn(string $column): string * 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 { @@ -534,8 +462,6 @@ 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 { @@ -557,9 +483,6 @@ protected function addTablePrefix($query, string $column): string /** * Prepare search keyword based on configurations. - * - * @param string $keyword - * @return string */ protected function prepareKeyword(string $keyword): string { @@ -586,7 +509,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 @@ -599,7 +521,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 @@ -644,8 +565,6 @@ public function orderByNullsLast(): static /** * Perform pagination. - * - * @return void */ public function paging(): void { @@ -666,7 +585,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 @@ -694,7 +612,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 @@ -730,7 +647,6 @@ protected function resolveCallbackParameter(): array /** * Perform default query orderBy clause. * - * @return void * * @throws \Psr\Container\ContainerExceptionInterface * @throws \Psr\Container\NotFoundExceptionInterface @@ -764,9 +680,6 @@ protected function defaultOrdering(): void /** * Check if column has custom sort handler. - * - * @param string $column - * @return bool */ protected function hasOrderColumn(string $column): bool { @@ -775,9 +688,6 @@ protected function hasOrderColumn(string $column): bool /** * Apply orderColumn custom query. - * - * @param string $column - * @param array $orderable */ protected function applyOrderColumn(string $column, array $orderable): void { @@ -800,7 +710,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 @@ -819,9 +728,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 { @@ -854,7 +760,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 { @@ -868,9 +773,6 @@ protected function smartGlobalSearch($keyword): void /** * Append debug parameters on output. - * - * @param array $output - * @return array */ protected function showDebugger(array $output): array { @@ -889,9 +791,6 @@ protected function showDebugger(array $output): array /** * Attach custom with meta on response. - * - * @param array $data - * @return array */ protected function attachAppends(array $data): array { @@ -912,8 +811,6 @@ protected function attachAppends(array $data): array /** * Get filtered, ordered and paginated query. - * - * @return QueryBuilder */ public function getFilteredQuery(): QueryBuilder { @@ -936,8 +833,6 @@ public function ignoreSelectsInCountQuery(): static /** * Perform sorting of columns. - * - * @return void */ public function ordering(): void { @@ -953,8 +848,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 @@ -980,7 +873,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 @@ -992,9 +884,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 { @@ -1036,8 +925,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) @@ -1094,9 +981,6 @@ 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 */ diff --git a/src/Utilities/Config.php b/src/Utilities/Config.php index 99a2aa98..0487ea80 100644 --- a/src/Utilities/Config.php +++ b/src/Utilities/Config.php @@ -6,15 +6,10 @@ class Config { - /** - * @var \Illuminate\Contracts\Config\Repository - */ private Repository $repository; /** * Config constructor. - * - * @param \Illuminate\Contracts\Config\Repository $repository */ public function __construct(Repository $repository) { @@ -23,8 +18,6 @@ public function __construct(Repository $repository) /** * Check if config uses wild card search. - * - * @return bool */ public function isWildcard(): bool { @@ -33,8 +26,6 @@ public function isWildcard(): bool /** * Check if config uses smart search. - * - * @return bool */ public function isSmartSearch(): bool { @@ -43,8 +34,6 @@ public function isSmartSearch(): bool /** * Check if config uses case-insensitive search. - * - * @return bool */ public function isCaseInsensitive(): bool { @@ -53,8 +42,6 @@ public function isCaseInsensitive(): bool /** * Check if app is in debug mode. - * - * @return bool */ public function isDebugging(): bool { @@ -87,8 +74,6 @@ public function set($key, $value = null) /** * Check if dataTable config uses multi-term searching. - * - * @return bool */ public function isMultiTerm(): bool { @@ -97,8 +82,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..890d1d23 100644 --- a/src/Utilities/Helper.php +++ b/src/Utilities/Helper.php @@ -144,8 +144,6 @@ public static function compileBlade($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) @@ -165,9 +163,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 { @@ -237,7 +232,6 @@ public static function convertToArray($row, $filters = []) } /** - * @param array $data * @return array */ public static function transform(array $data) @@ -273,7 +267,6 @@ protected static function transformRow($row) /** * Build parameters depending on # of arguments passed. * - * @param array $args * @return array */ public static function buildParameters(array $args) @@ -297,7 +290,6 @@ public static function buildParameters(array $args) /** * Replace all pattern occurrences with keyword. * - * @param array $subject * @param string $keyword * @param string $pattern * @return array diff --git a/src/Utilities/Request.php b/src/Utilities/Request.php index f660e08c..89143add 100644 --- a/src/Utilities/Request.php +++ b/src/Utilities/Request.php @@ -9,9 +9,6 @@ */ class Request { - /** - * @var BaseRequest - */ protected BaseRequest $request; /** @@ -50,8 +47,6 @@ public function __get($name) /** * Get all columns request input. - * - * @return array */ public function columns(): array { @@ -60,8 +55,6 @@ public function columns(): array /** * Check if DataTables is searchable. - * - * @return bool */ public function isSearchable(): bool { @@ -70,9 +63,6 @@ public function isSearchable(): bool /** * Check if DataTables must uses regular expressions. - * - * @param int $index - * @return bool */ public function isRegex(int $index): bool { @@ -81,8 +71,6 @@ public function isRegex(int $index): bool /** * Get orderable columns. - * - * @return array */ public function orderableColumns(): array { @@ -109,8 +97,6 @@ public function orderableColumns(): array /** * Check if DataTables ordering is enabled. - * - * @return bool */ public function isOrderable(): bool { @@ -119,9 +105,6 @@ public function isOrderable(): bool /** * Check if a column is orderable. - * - * @param int $index - * @return bool */ public function isColumnOrderable(int $index): bool { @@ -148,10 +131,6 @@ 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 { @@ -173,9 +152,6 @@ public function isColumnSearchable(int $i, bool $column_search = true): bool /** * Get column's search value. - * - * @param int $index - * @return string */ public function columnKeyword(int $index): string { @@ -187,9 +163,6 @@ public function columnKeyword(int $index): string /** * Prepare keyword string value. - * - * @param float|array|int|string $keyword - * @return string */ protected function prepareKeyword(float|array|int|string $keyword): string { @@ -202,8 +175,6 @@ protected function prepareKeyword(float|array|int|string $keyword): string /** * Get global search keyword. - * - * @return string */ public function keyword(): string { @@ -215,9 +186,6 @@ public function keyword(): string /** * Get column name by index. - * - * @param int $i - * @return string|null */ public function columnName(int $i): ?string { @@ -229,8 +197,6 @@ public function columnName(int $i): ?string /** * Check if DataTables allow pagination. - * - * @return bool */ public function isPaginationable(): bool { @@ -239,9 +205,6 @@ public function isPaginationable(): bool $this->request->input('length') != -1; } - /** - * @return BaseRequest - */ public function getBaseRequest(): BaseRequest { return $this->request; @@ -249,8 +212,6 @@ public function getBaseRequest(): BaseRequest /** * Get starting record value. - * - * @return int */ public function start(): int { @@ -261,8 +222,6 @@ public function start(): int /** * Get per page length. - * - * @return int */ public function length(): int { @@ -273,8 +232,6 @@ public function length(): int /** * Get draw request. - * - * @return int */ public function draw(): int { diff --git a/tests/Integration/CustomOrderTest.php b/tests/Integration/CustomOrderTest.php index 7513acb6..357f88e6 100644 --- a/tests/Integration/CustomOrderTest.php +++ b/tests/Integration/CustomOrderTest.php @@ -55,10 +55,10 @@ protected function setUp(): void $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(); + ->orderColumn('user.id', function ($query, $order) { + $query->orderBy('users.id', $order == 'desc' ? 'asc' : 'desc'); + }) + ->toJson(); }); } } diff --git a/tests/Integration/EloquentDataTableTest.php b/tests/Integration/EloquentDataTableTest.php index fad48a63..c1b2bd06 100644 --- a/tests/Integration/EloquentDataTableTest.php +++ b/tests/Integration/EloquentDataTableTest.php @@ -184,26 +184,26 @@ protected function setUp(): void $router->get('/eloquent/only', function (DataTables $datatables) { return $datatables->eloquent(Post::with('user')) - ->only(['title', 'user.name']) - ->toJson(); + ->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(); + ->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(); + ->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(); + ->formatColumn('created_at', 'InvalidFormatter::class') + ->toJson(); }); } } diff --git a/tests/Integration/EloquentJoinTest.php b/tests/Integration/EloquentJoinTest.php index a867b4e4..4ec67b76 100644 --- a/tests/Integration/EloquentJoinTest.php +++ b/tests/Integration/EloquentJoinTest.php @@ -88,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/Unit/QueryDataTableTest.php b/tests/Unit/QueryDataTableTest.php index 177b6796..0fee6802 100644 --- a/tests/Unit/QueryDataTableTest.php +++ b/tests/Unit/QueryDataTableTest.php @@ -40,9 +40,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 +61,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') @@ -79,13 +79,13 @@ public function test_simple_queries_with_complexe_select_are_wrapped_without_sel /** @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()); From d832fc3ab4a57bbc2a041160043b6d2f076a1b73 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 27 Feb 2024 10:47:15 +0800 Subject: [PATCH 014/140] chore: add scripts --- composer.json | 158 +++++++++++++++++++++++++------------------------- 1 file changed, 80 insertions(+), 78 deletions(-) diff --git a/composer.json b/composer.json index 30e70209..ae9033f0 100644 --- a/composer.json +++ b/composer.json @@ -1,82 +1,84 @@ { - "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.2", - "illuminate/database": "^11", - "illuminate/filesystem": "^11", - "illuminate/http": "^11", - "illuminate/support": "^11", - "illuminate/view": "^11" - }, - "require-dev": { - "algolia/algoliasearch-client-php": "^3.4", - "larastan/larastan": "^2.4", - "laravel/pint": "^1.14", - "laravel/scout": "^10.5", - "meilisearch/meilisearch-php": "^1.4", - "orchestra/testbench": "^9" - }, - "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 4|5|6|7|8|9|10", + "keywords": [ + "laravel", + "dataTables", + "jquery" + ], + "license": "MIT", + "authors": [ + { + "name": "Arjay Angeles", + "email": "aqangeles@gmail.com" + } + ], + "require": { + "php": "^8.2", + "illuminate/database": "^11", + "illuminate/filesystem": "^11", + "illuminate/http": "^11", + "illuminate/support": "^11", + "illuminate/view": "^11" }, - "files": [ - "src/helper.php" - ] - }, - "autoload-dev": { - "psr-4": { - "Yajra\\DataTables\\Tests\\": "tests/" - } - }, - "extra": { - "branch-alias": { - "dev-master": "11.x-dev" + "require-dev": { + "algolia/algoliasearch-client-php": "^3.4", + "larastan/larastan": "^2.4", + "laravel/pint": "^1.14", + "laravel/scout": "^10.5", + "meilisearch/meilisearch-php": "^1.4", + "orchestra/testbench": "^9" + }, + "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": "11.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", + "stan": "./vendor/bin/phpstan analyse --memory-limit=2G --ansi --no-progress --no-interaction --configuration=phpstan.neon.dist" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/yajra" + } + ] } From 0049b2fa770db97a26e6b96e8ac684fa5f91b181 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 27 Feb 2024 10:54:04 +0800 Subject: [PATCH 015/140] chore: pint workflow --- .github/workflows/pint.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/pint.yml diff --git a/.github/workflows/pint.yml b/.github/workflows/pint.yml new file mode 100644 index 00000000..7844b882 --- /dev/null +++ b/.github/workflows/pint.yml @@ -0,0 +1,21 @@ +name: PHP Linting +on: + pull_request: + push: + branches: + - master +jobs: + phplint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: "laravel-pint" + uses: aglipanci/laravel-pint-action@2.0.0 + with: + preset: laravel + verboseMode: true + testMode: false + onlyDirty: true + - uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "fix: pint" From 14560d3fddc577f8746708053fb83287d13689d7 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 27 Feb 2024 10:58:06 +0800 Subject: [PATCH 016/140] chore: bump versions --- composer.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index ae9033f0..06ea46ac 100644 --- a/composer.json +++ b/composer.json @@ -22,11 +22,11 @@ "illuminate/view": "^11" }, "require-dev": { - "algolia/algoliasearch-client-php": "^3.4", - "larastan/larastan": "^2.4", + "algolia/algoliasearch-client-php": "^3.4.1", + "larastan/larastan": "^2.9.1", "laravel/pint": "^1.14", - "laravel/scout": "^10.5", - "meilisearch/meilisearch-php": "^1.4", + "laravel/scout": "^10.8.3", + "meilisearch/meilisearch-php": "^1.6.1", "orchestra/testbench": "^9" }, "suggest": { From 38fa98ac0624b74b840a3f7be59db2d9bb464d55 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 27 Feb 2024 10:58:50 +0800 Subject: [PATCH 017/140] chore: update meta --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 06ea46ac..06f48f3e 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,8 @@ { "name": "yajra/laravel-datatables-oracle", - "description": "jQuery DataTables API for Laravel 4|5|6|7|8|9|10", + "description": "jQuery DataTables API for Laravel", "keywords": [ + "yajra", "laravel", "dataTables", "jquery" From 08c0f77d058b65052c54149cf9e0dcbffdb2e19b Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 27 Feb 2024 13:56:00 +0800 Subject: [PATCH 018/140] fix: Unexpected input(s) 'onlyDirty' --- .github/workflows/pint.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pint.yml b/.github/workflows/pint.yml index 7844b882..2bdb8f9b 100644 --- a/.github/workflows/pint.yml +++ b/.github/workflows/pint.yml @@ -15,7 +15,6 @@ jobs: preset: laravel verboseMode: true testMode: false - onlyDirty: true - uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: "fix: pint" From 29ae17be2491a9c1bec05920efde5163f39dd7ae Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 27 Feb 2024 14:12:07 +0800 Subject: [PATCH 019/140] chore: pint pr --- .github/workflows/pint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pint.yml b/.github/workflows/pint.yml index 2bdb8f9b..f0783477 100644 --- a/.github/workflows/pint.yml +++ b/.github/workflows/pint.yml @@ -8,13 +8,13 @@ jobs: phplint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: "laravel-pint" uses: aglipanci/laravel-pint-action@2.0.0 with: preset: laravel verboseMode: true - testMode: false - uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: "fix: pint" + From 2a704d2f2268dcbe41941b9adf56701bfb0d7cd6 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 27 Feb 2024 18:16:54 +0800 Subject: [PATCH 020/140] chore: promote facade --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5418d624..91ec8e45 100644 --- a/README.md +++ b/README.md @@ -15,13 +15,15 @@ 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(User::query())->toJson(); -return datatables(DB::table('users'))->toJson(); -return datatables(User::all())->toJson(); +return DataTables::eloquent(User::query())->toJson(); +return DataTables::query(DB::table('users'))->toJson(); +return DataTables::collection(User::all())->toJson(); + +return DataTables::make(User::query())->toJson(); +return DataTables::make(DB::table('users'))->toJson(); +return DataTables::make(User::all())->toJson(); ``` ## Sponsors From 2862b3f1e95faa3860b4c3a2747aff8f19e27f72 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 27 Feb 2024 18:17:11 +0800 Subject: [PATCH 021/140] chore: rector and pint --- composer.json | 12 +- rector.php | 22 ++ src/CollectionDataTable.php | 22 +- src/Contracts/Formatter.php | 3 +- src/DataTableAbstract.php | 25 +-- src/DataTables.php | 2 +- src/DataTablesServiceProvider.php | 8 +- src/EloquentDataTable.php | 6 +- src/Processors/DataProcessor.php | 14 +- src/QueryDataTable.php | 50 ++--- src/Utilities/Config.php | 11 +- src/Utilities/Helper.php | 13 +- src/lumen.php | 2 +- tests/Formatters/DateFormatter.php | 5 +- .../Integration/BelongsToManyRelationTest.php | 4 +- tests/Integration/BelongsToRelationTest.php | 4 +- tests/Integration/CollectionDataTableTest.php | 8 +- tests/Integration/CustomOrderTest.php | 12 +- tests/Integration/EloquentDataTableTest.php | 44 ++-- tests/Integration/HasManyRelationTest.php | 24 +-- tests/Integration/HasOneRelationTest.php | 32 ++- tests/Integration/HasOneThroughTest.php | 30 +-- tests/Integration/IgnoreGettersTest.php | 8 +- tests/Integration/MorphToRelationTest.php | 32 ++- tests/Integration/QueryDataTableTest.php | 188 +++++++----------- tests/Unit/HelperTest.php | 4 +- 26 files changed, 225 insertions(+), 360 deletions(-) create mode 100644 rector.php diff --git a/composer.json b/composer.json index 06f48f3e..ac6d1273 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,8 @@ "laravel/pint": "^1.14", "laravel/scout": "^10.8.3", "meilisearch/meilisearch-php": "^1.6.1", - "orchestra/testbench": "^9" + "orchestra/testbench": "^9", + "rector/rector": "^1.0" }, "suggest": { "yajra/laravel-datatables-export": "Plugin for server-side exporting using livewire and queue worker.", @@ -72,7 +73,14 @@ "scripts": { "test": "./vendor/bin/phpunit", "pint": "./vendor/bin/pint", - "stan": "./vendor/bin/phpstan analyse --memory-limit=2G --ansi --no-progress --no-interaction --configuration=phpstan.neon.dist" + "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, 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/src/CollectionDataTable.php b/src/CollectionDataTable.php index 515550f6..e3a5c644 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. * @@ -37,21 +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 */ - protected function serialize($collection): array + protected function serialize(mixed $collection): array { return $collection instanceof Arrayable ? $collection->toArray() : (array) $collection; } @@ -257,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) { 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 e2bec772..26ea6a9b 100644 --- a/src/DataTableAbstract.php +++ b/src/DataTableAbstract.php @@ -120,10 +120,9 @@ abstract class DataTableAbstract implements DataTable /** * 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; } @@ -131,10 +130,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); } @@ -162,9 +160,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) ); } @@ -174,9 +170,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) ); } @@ -425,11 +419,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; @@ -680,10 +672,9 @@ public function toJson($options = 0) * Add a search pane options on response. * * @param string $column - * @param mixed $options * @return $this */ - public function searchPane($column, $options, ?callable $builder = null): static + public function searchPane($column, mixed $options, ?callable $builder = null): static { $options = value($options); @@ -748,9 +739,7 @@ public function filtering(): 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); }); diff --git a/src/DataTables.php b/src/DataTables.php index 45ce4dc5..f7518970 100644 --- a/src/DataTables.php +++ b/src/DataTables.php @@ -74,7 +74,7 @@ public static function make($source) } } - throw new Exception('No available engine for '.get_class($source)); + throw new Exception('No available engine for '.$source::class); } /** diff --git a/src/DataTablesServiceProvider.php b/src/DataTablesServiceProvider.php index 2b883663..579db87b 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); } diff --git a/src/EloquentDataTable.php b/src/EloquentDataTable.php index 00f00846..8a6f154e 100644 --- a/src/EloquentDataTable.php +++ b/src/EloquentDataTable.php @@ -56,9 +56,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; @@ -237,7 +235,7 @@ protected function joinEagerLoadedColumn($relation, $relationColumn) 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(); diff --git a/src/Processors/DataProcessor.php b/src/Processors/DataProcessor.php index 59dfb5b9..e2ae3087 100644 --- a/src/Processors/DataProcessor.php +++ b/src/Processors/DataProcessor.php @@ -9,8 +9,6 @@ class DataProcessor { - protected int $start; - protected array $output = []; /** @@ -23,8 +21,6 @@ class DataProcessor */ protected array $editColumns = []; - protected array $templates = []; - protected array $rawColumns = []; /** @@ -45,18 +41,12 @@ class DataProcessor */ protected mixed $escapeColumns = []; - protected iterable $results; - protected bool $includeIndex = false; protected bool $ignoreGetters = false; - /** - * @param iterable $results - */ - 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'] ?? []; @@ -67,8 +57,6 @@ 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; } /** diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 13325641..9f8b7a98 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -15,11 +15,6 @@ class QueryDataTable extends DataTableAbstract { - /** - * Builder object. - */ - protected QueryBuilder $query; - /** * Flag for ordering NULLS LAST option. */ @@ -85,12 +80,14 @@ class QueryDataTable extends DataTableAbstract */ protected bool $disableUserOrdering = false; - public function __construct(QueryBuilder $builder) + public function __construct(/** + * Builder object. + */ + protected QueryBuilder $query) { - $this->query = $builder; $this->request = app('datatables.request'); $this->config = app('datatables.config'); - $this->columns = $builder->getColumns(); + $this->columns = $this->query->getColumns(); if ($this->config->isDebugging()) { $this->getConnection()->enableQueryLog(); @@ -429,14 +426,11 @@ protected function regexColumnSearch(string $column, string $keyword): void */ 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, + }; } /** @@ -470,8 +464,8 @@ protected function addTablePrefix($query, string $column): string $from = $q->from ?? ''; if (! $from instanceof Expression) { - if (str_contains($from, ' as ')) { - $from = explode(' as ', $from)[1]; + if (str_contains((string) $from, ' as ')) { + $from = explode(' as ', (string) $from)[1]; } $column = $from.'.'.$column; @@ -659,9 +653,7 @@ 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']); @@ -699,7 +691,7 @@ protected function applyOrderColumn(string $column, array $orderable): void if (is_callable($sql)) { call_user_func($sql, $this->query, $orderable['direction']); } else { - $sql = str_replace('$1', $orderable['direction'], $sql); + $sql = str_replace('$1', $orderable['direction'], (string) $sql); $bindings = $this->columnDef['order'][$column]['bindings']; $this->query->orderByRaw($sql, $bindings); } @@ -738,13 +730,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'); @@ -935,9 +923,7 @@ 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 'mysql': @@ -986,7 +972,7 @@ protected function applyFixedOrderingToQuery(string $keyName, array $orderedKeys */ 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 0487ea80..8b3c6dce 100644 --- a/src/Utilities/Config.php +++ b/src/Utilities/Config.php @@ -6,14 +6,11 @@ class Config { - private Repository $repository; - /** * Config constructor. */ - public function __construct(Repository $repository) + public function __construct(private readonly Repository $repository) { - $this->repository = $repository; } /** @@ -52,10 +49,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); } @@ -64,10 +60,9 @@ 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); } diff --git a/src/Utilities/Helper.php b/src/Utilities/Helper.php index 890d1d23..75cc0d06 100644 --- a/src/Utilities/Helper.php +++ b/src/Utilities/Helper.php @@ -94,7 +94,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)); @@ -191,11 +191,10 @@ 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, $filters = []) { if (Arr::get($filters, 'ignore_getters') && is_object($row) && method_exists($row, 'getAttributes')) { $data = $row->getAttributes(); @@ -236,9 +235,7 @@ public static function convertToArray($row, $filters = []) */ public static function transform(array $data) { - return array_map(function ($row) { - return self::transformRow($row); - }, $data); + return array_map(fn ($row) => self::transformRow($row), $data); } /** @@ -301,7 +298,7 @@ public static function replacePatternWithKeyword(array $subject, $keyword, $patt if (is_array($param)) { $parameters[] = self::replacePatternWithKeyword($param, $keyword, $pattern); } else { - $parameters[] = str_replace($pattern, $keyword, $param); + $parameters[] = str_replace($pattern, $keyword, (string) $param); } } @@ -379,7 +376,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/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..516b901c 100644 --- a/tests/Formatters/DateFormatter.php +++ b/tests/Formatters/DateFormatter.php @@ -8,11 +8,8 @@ class DateFormatter implements Formatter { - public string $format; - - public function __construct(string $format = 'Y-m-d h:i a') + public function __construct(public string $format = 'Y-m-d h:i a') { - $this->format = $format; } public function format($value, $row): string diff --git a/tests/Integration/BelongsToManyRelationTest.php b/tests/Integration/BelongsToManyRelationTest.php index f45b286d..c11a4517 100644 --- a/tests/Integration/BelongsToManyRelationTest.php +++ b/tests/Integration/BelongsToManyRelationTest.php @@ -87,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 d99f50fb..ff962dcd 100644 --- a/tests/Integration/BelongsToRelationTest.php +++ b/tests/Integration/BelongsToRelationTest.php @@ -84,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 9519922c..6ce01576 100644 --- a/tests/Integration/CollectionDataTableTest.php +++ b/tests/Integration/CollectionDataTableTest.php @@ -253,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 357f88e6..d4664405 100644 --- a/tests/Integration/CustomOrderTest.php +++ b/tests/Integration/CustomOrderTest.php @@ -53,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) { + $query->orderBy('users.id', $order == 'desc' ? 'asc' : 'desc'); + }) + ->toJson()); } } diff --git a/tests/Integration/EloquentDataTableTest.php b/tests/Integration/EloquentDataTableTest.php index c1b2bd06..12664742 100644 --- a/tests/Integration/EloquentDataTableTest.php +++ b/tests/Integration/EloquentDataTableTest.php @@ -178,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/HasManyRelationTest.php b/tests/Integration/HasManyRelationTest.php index 28abdac1..41e1cff6 100644 --- a/tests/Integration/HasManyRelationTest.php +++ b/tests/Integration/HasManyRelationTest.php @@ -90,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 e5d39062..da83b66e 100644 --- a/tests/Integration/HasOneRelationTest.php +++ b/tests/Integration/HasOneRelationTest.php @@ -121,24 +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/hasOneWithTrashed', function (DataTables $datatables) { - return $datatables->eloquent(User::with([ - 'heart' => function ($query) { - $query->withTrashed(); - }, - ])->select('users.*'))->toJson(); - }); - - $this->app['router']->get('/relations/hasOneOnlyTrashed', function (DataTables $datatables) { - return $datatables->eloquent(User::with([ - 'heart' => function ($query) { - $query->onlyTrashed(); - }, - ])->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', fn (DataTables $datatables) => $datatables->eloquent(User::with([ + 'heart' => function ($query) { + $query->withTrashed(); + }, + ])->select('users.*'))->toJson()); + + $this->app['router']->get('/relations/hasOneOnlyTrashed', fn (DataTables $datatables) => $datatables->eloquent(User::with([ + 'heart' => function ($query) { + $query->onlyTrashed(); + }, + ])->select('users.*'))->toJson()); } } diff --git a/tests/Integration/HasOneThroughTest.php b/tests/Integration/HasOneThroughTest.php index c21bd7a9..075de4c1 100644 --- a/tests/Integration/HasOneThroughTest.php +++ b/tests/Integration/HasOneThroughTest.php @@ -123,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 92f111fe..18068b9c 100644 --- a/tests/Integration/IgnoreGettersTest.php +++ b/tests/Integration/IgnoreGettersTest.php @@ -28,9 +28,7 @@ public function it_return_the_default_value_when_attribute_is_null() #[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([ @@ -50,9 +48,7 @@ public function it_return_the_getter_value_without_ignore_getters() #[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/MorphToRelationTest.php b/tests/Integration/MorphToRelationTest.php index 8937af1d..44f07140 100644 --- a/tests/Integration/MorphToRelationTest.php +++ b/tests/Integration/MorphToRelationTest.php @@ -99,24 +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/morphToWithTrashed', function (DataTables $datatables) { - return $datatables->eloquent(User::with([ - 'user' => function ($query) { - $query->withTrashed(); - }, - ])->select('users.*'))->toJson(); - }); - - $this->app['router']->get('/relations/morphToOnlyTrashed', function (DataTables $datatables) { - return $datatables->eloquent(User::with([ - 'user' => function ($query) { - $query->onlyTrashed(); - }, - ])->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', fn (DataTables $datatables) => $datatables->eloquent(User::with([ + 'user' => function ($query) { + $query->withTrashed(); + }, + ])->select('users.*'))->toJson()); + + $this->app['router']->get('/relations/morphToOnlyTrashed', fn (DataTables $datatables) => $datatables->eloquent(User::with([ + 'user' => function ($query) { + $query->onlyTrashed(); + }, + ])->select('users.*'))->toJson()); } } diff --git a/tests/Integration/QueryDataTableTest.php b/tests/Integration/QueryDataTableTest.php index e2918f1a..5c8d9399 100644 --- a/tests/Integration/QueryDataTableTest.php +++ b/tests/Integration/QueryDataTableTest.php @@ -380,101 +380,65 @@ protected function setUp(): void $router = $this->app['router']; - $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(); - }); - - $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(); - }); - - $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(); - }); - - $router->get('/query/only', function (DataTables $dataTable) { - return $dataTable->query(DB::table('users')) - ->addColumn('foo', 'bar') - ->only(['name']) - ->toJson(); - }); - - $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(); - }); - - $router->get('/query/xss-add', function (DataTables $dataTable) { - return $dataTable->query(DB::table('users')) - ->addColumn('foo', 'Allowed') - ->addColumn('bar', function () { - return 'Allowed'; - }) - ->toJson(); - }); - - $router->get('/query/xss-edit', function (DataTables $dataTable) { - return $dataTable->query(DB::table('users')) - ->editColumn('name', 'Allowed') - ->editColumn('email', function () { - return 'Allowed'; - }) - ->toJson(); - }); - - $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/simple', fn (DataTables $dataTable) => $dataTable->query(DB::table('users'))->skipTotalRecords()->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(); @@ -484,30 +448,20 @@ protected function setUp(): void ->toJson(); }); - $router->get('/set-total-records', function (DataTables $dataTable) { - return $dataTable->query(DB::table('users')) - ->setTotalRecords(10) - ->toJson(); - }); + $router->get('/set-total-records', fn (DataTables $dataTable) => $dataTable->query(DB::table('users')) + ->setTotalRecords(10) + ->toJson()); - $router->get('/zero-total-records', function (DataTables $dataTable) { - return $dataTable->query(DB::table('users')) - ->setTotalRecords(0) - ->toJson(); - }); + $router->get('/zero-total-records', fn (DataTables $dataTable) => $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('/set-filtered-records', fn (DataTables $dataTable) => $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('/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()); } } diff --git a/tests/Unit/HelperTest.php b/tests/Unit/HelperTest.php index ef01e0b0..2e493453 100644 --- a/tests/Unit/HelperTest.php +++ b/tests/Unit/HelperTest.php @@ -103,9 +103,7 @@ 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->id = 2; From d34cbe37c6a785fdaa68fe1c61f0076d5a7630f4 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 27 Feb 2024 18:17:43 +0800 Subject: [PATCH 022/140] fix: typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 91ec8e45..c8c1dec2 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ 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 -use \Yajra\DataTables\Facades\DataTables; +use Yajra\DataTables\Facades\DataTables; return DataTables::eloquent(User::query())->toJson(); return DataTables::query(DB::table('users'))->toJson(); From c01873797aff4ee96ed1c9d41b921ee64bc2a0c3 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 27 Feb 2024 20:07:17 +0800 Subject: [PATCH 023/140] refactor --- src/DataTables.php | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/DataTables.php b/src/DataTables.php index f7518970..724b44b3 100644 --- a/src/DataTables.php +++ b/src/DataTables.php @@ -6,7 +6,6 @@ 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; @@ -94,7 +93,7 @@ public function getConfig(): Config } /** - * DataTables using Query. + * DataTables using query builder. * * @throws \Yajra\DataTables\Exceptions\Exception */ @@ -157,15 +156,7 @@ public function resource($resource) 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`."); } } - - /** - * @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}`."); - } } From b8104ae073df494a7dec86ab15c63d633b37dee6 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 27 Feb 2024 20:24:13 +0800 Subject: [PATCH 024/140] chore: type hint and doc blocks --- src/CollectionDataTable.php | 4 +- src/Contracts/DataTable.php | 7 +--- src/DataTableAbstract.php | 41 +++++++++---------- src/Processors/RowProcessor.php | 4 ++ src/QueryDataTable.php | 9 +---- src/Utilities/Helper.php | 70 +++++++-------------------------- 6 files changed, 43 insertions(+), 92 deletions(-) diff --git a/src/CollectionDataTable.php b/src/CollectionDataTable.php index e3a5c644..f8ff4082 100644 --- a/src/CollectionDataTable.php +++ b/src/CollectionDataTable.php @@ -138,11 +138,9 @@ public function paging(): void /** * Organizes works. * - * @param bool $mDataSupport - * * @throws \Exception */ - public function make($mDataSupport = true): JsonResponse + public function make(bool $mDataSupport = true): JsonResponse { try { $this->totalRecords = $this->totalCount(); diff --git a/src/Contracts/DataTable.php b/src/Contracts/DataTable.php index 6e721fea..0e5ac7b5 100644 --- a/src/Contracts/DataTable.php +++ b/src/Contracts/DataTable.php @@ -28,10 +28,9 @@ public function totalCount(): int; * Set auto filter off and run your own filter. * Overrides global search. * - * @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. @@ -55,8 +54,6 @@ public function ordering(): void; /** * Organizes works. - * - * @param bool $mDataSupport */ - public function make($mDataSupport = true): JsonResponse; + public function make(bool $mDataSupport = true): JsonResponse; } diff --git a/src/DataTableAbstract.php b/src/DataTableAbstract.php index 26ea6a9b..d0c3ba21 100644 --- a/src/DataTableAbstract.php +++ b/src/DataTableAbstract.php @@ -642,10 +642,9 @@ abstract protected function defaultOrdering(): void; * Set auto filter off and run your own filter. * Overrides global search. * - * @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; @@ -696,6 +695,21 @@ 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. */ @@ -758,14 +772,6 @@ protected function searchPanesSearch(): void // Add support for search pane. } - /** - * Count total items. - */ - public function totalCount(): int - { - return $this->totalRecords ??= $this->count(); - } - /** * Count filtered items. */ @@ -872,11 +878,9 @@ protected function showDebugger(array $output): array /** * Return an error json response. * - * @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'); @@ -893,7 +897,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(), ]); } @@ -940,7 +944,7 @@ protected function setupKeyword(string $value): string } /** - * Get column name to be use for filtering and sorting. + * Get column name to be used for filtering and sorting. */ protected function getColumnName(int $index, bool $wantsAlias = false): ?string { @@ -981,11 +985,4 @@ protected function getPrimaryKeyName(): string { return 'id'; } - - public function editOnlySelectedColumns(): static - { - $this->editOnlySelectedColumns = true; - - return $this; - } } diff --git a/src/Processors/RowProcessor.php b/src/Processors/RowProcessor.php index 1c6329f8..6b86d47c 100644 --- a/src/Processors/RowProcessor.php +++ b/src/Processors/RowProcessor.php @@ -20,6 +20,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,6 +41,8 @@ public function rowValue($attribute, $template) * * @param string $attribute * @return $this + * + * @throws \ReflectionException */ public function rowData($attribute, array $template) { diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 9f8b7a98..b4406c2d 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -80,10 +80,7 @@ class QueryDataTable extends DataTableAbstract */ protected bool $disableUserOrdering = false; - public function __construct(/** - * Builder object. - */ - protected QueryBuilder $query) + public function __construct(protected QueryBuilder $query) { $this->request = app('datatables.request'); $this->config = app('datatables.config'); @@ -115,11 +112,9 @@ public static function canCreate($source): bool /** * Organizes works. * - * @param bool $mDataSupport - * * @throws \Exception */ - public function make($mDataSupport = true): JsonResponse + public function make(bool $mDataSupport = true): JsonResponse { try { $results = $this->prepareQuery()->results(); diff --git a/src/Utilities/Helper.php b/src/Utilities/Helper.php index 75cc0d06..a42be6c7 100644 --- a/src/Utilities/Helper.php +++ b/src/Utilities/Helper.php @@ -14,12 +14,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 +40,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); } @@ -122,11 +114,10 @@ public static function compileContent(mixed $content, array $data, array|object /** * 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 */ @@ -143,10 +134,8 @@ public static function compileBlade($str, $data = []) /** * Get a mixed value of custom data and the parameters. - * - * @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); @@ -175,11 +164,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); @@ -190,11 +176,8 @@ public static function getOrMethod($method) /** * Converts array object values to associative array. - * - * @param array $filters - * @return array */ - public static function convertToArray(mixed $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(); @@ -230,10 +213,7 @@ public static function convertToArray(mixed $row, $filters = []) return $data; } - /** - * @return array - */ - public static function transform(array $data) + public static function transform(array $data): array { return array_map(fn ($row) => self::transformRow($row), $data); } @@ -242,9 +222,8 @@ public static function transform(array $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) { @@ -263,10 +242,8 @@ protected static function transformRow($row) /** * Build parameters depending on # of arguments passed. - * - * @return array */ - public static function buildParameters(array $args) + public static function buildParameters(array $args): array { $parameters = []; @@ -286,12 +263,8 @@ public static function buildParameters(array $args) /** * Replace all pattern occurrences with keyword. - * - * @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) { @@ -307,12 +280,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)); @@ -333,25 +302,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); From 488de04be2a9f9b60fdf809ab526e84a214126e7 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Wed, 28 Feb 2024 10:51:38 +0800 Subject: [PATCH 025/140] chore: fix matrix --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c8c1dec2..760b6873 100644 --- a/README.md +++ b/README.md @@ -67,12 +67,12 @@ return DataTables::make(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 | -| 11.x.x | 11.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 | ## Quick Installation From d96ccc355e072c1bba50536bfe1975f193c37c36 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Wed, 28 Feb 2024 12:54:23 +0800 Subject: [PATCH 026/140] fix: typo --- src/Facades/DataTables.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Facades/DataTables.php b/src/Facades/DataTables.php index ef2b91e3..7fbf7c72 100644 --- a/src/Facades/DataTables.php +++ b/src/Facades/DataTables.php @@ -7,7 +7,7 @@ /** * @mixin \Yajra\DataTables\DataTables * - * @method static \Yajra\DataTables\EloquentDatatable eloquent($builder) + * @method static \Yajra\DataTables\EloquentDataTable eloquent($builder) * @method static \Yajra\DataTables\QueryDataTable query($builder) * @method static \Yajra\DataTables\CollectionDataTable collection($collection) * From f4d8a8df5fd2ac26fb6384f9caa41596bd79c305 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Thu, 14 Mar 2024 12:05:21 +0800 Subject: [PATCH 027/140] chore: release v11.0.0 :rocket: --- CHANGELOG.md | 226 ++------------------------------------------------- 1 file changed, 5 insertions(+), 221 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a5bf9bd..35a7761f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,229 +1,13 @@ -# Laravel DataTables CHANGELOG +# Laravel DataTables -[![Latest Stable Version](https://poser.pugx.org/yajra/laravel-datatables-oracle/v/stable.png)](https://packagist.org/packages/yajra/laravel-datatables-oracle) -[![Total Downloads](https://poser.pugx.org/yajra/laravel-datatables-oracle/downloads.png)](https://packagist.org/packages/yajra/laravel-datatables-oracle) -[![Build Status](https://travis-ci.org/yajra/laravel-datatables.png?branch=master)](https://travis-ci.org/yajra/laravel-datatables) -[![Latest Unstable Version](https://poser.pugx.org/yajra/laravel-datatables-oracle/v/unstable.svg)](https://packagist.org/packages/yajra/laravel-datatables-oracle) -[![License](https://poser.pugx.org/yajra/laravel-datatables-oracle/license.svg)](https://packagist.org/packages/yajra/laravel-datatables-oracle) +## CHANGELOG ### [Unreleased] -### [v10.11.4] - 2024-02-28 +### [v11.0.0](https://github.com/yajra/laravel-datatables/compare/v11.0.0...master) - 2024-03-14 -- fix: EloquentDataTable return type typo #3123 +- Laravel 11 support -### [v10.11.3] - 2023-12-27 -- fix: Update composer.json to use Larastan Org #3107 +[Unreleased]: https://github.com/yajra/laravel-datatables/compare/v11.0.0...master -### [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 - -### [v10.6.1] - 2023-07-05 - -- fix: #3025 #3026 -- fix the error introduced in 10.4.4 as described in #3025. - -### [v10.6.0] - 2023-06-29 - -- feat: Expose autoFilter setter to disable post filtering #2981 - -### [v10.5.0] - 2023-06-29 - -- feat: Prevent editColumn when column is not shown #3018 - -### [v10.4.4] - 2023-06-27 - -- feat: Optimize countQuery with complex select #3008 -- fix: phpstan #3022 - -### [v10.4.3] - 2023-06-07 - -- Fix: Prevent the filteredCount() query if no filter is applied to the initial query #3007 - -### [v10.4.2] - 2023-05-31 - -- 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 - -### [v10.1.4] - 2022-09-27 - -- Fixed the search column for same table relations #2856 - -### [v10.1.3] - 2022-09-20 - -- Fix relation key name for BelongsToMany #2850 - -### [v10.1.2] - 2022-07-12 - -- Fix HasOneThrough #2818 - -### [v10.1.1] - 2022-06-24 - -- Fix null recordsFiltered on empty collection #2806 -- Fix #2793 - -### [v10.1.0] - 2022-06-21 - -- Add support for dependency injection when using closure. #2800 - -### [v10.0.8] - 2022-06-21 - -- Make canCreate at QueryDataTable accept QueryBuilder only #2798 - -### [v10.0.7] - 2022-05-23 - -- Fix create eloquent datatable from relation #2789 - -### [v10.0.6] - 2022-05-18 - -- Added null parameter type as allowed to handle default Action column from laravel-datatables-html #2787 - -### [v10.0.5] - 2022-05-17 - -- Fix Return value must be of type int, string returned. - -### [v10.0.4] - 2022-05-08 - -- Fix accidental formatter issue on eloquent -- Add formatColumn test for eloquent - -### [v10.0.3] - 2022-05-08 - -- Additional fix & test for zero total records - -### [v10.0.2] - 2022-05-08 - -- 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. - -### [v10.0.1] - 2022-05-08 - -- Code clean-up and several phpstan fixes - -### [v10.0.0] - 2022-05-08 - -- 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 - -[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 From 71befb2b0690b9dcf93271d740251a59c1c877a9 Mon Sep 17 00:00:00 2001 From: Mathieu FERRE Date: Fri, 12 Apr 2024 15:16:03 +0200 Subject: [PATCH 028/140] Optimize simple queries --- src/QueryDataTable.php | 169 +++++++++++++++--------------- tests/Unit/QueryDataTableTest.php | 44 +++++++- 2 files changed, 124 insertions(+), 89 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index b4406c2d..f60fb639 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -83,7 +83,7 @@ class QueryDataTable extends DataTableAbstract public function __construct(protected QueryBuilder $query) { $this->request = app('datatables.request'); - $this->config = app('datatables.config'); + $this->config = app('datatables.config'); $this->columns = $this->query->getColumns(); if ($this->config->isDebugging()) { @@ -102,11 +102,11 @@ public function getConnection(): Connection /** * Can the DataTable engine be created with these parameters. * - * @param mixed $source + * @param mixed $source */ public static function canCreate($source): bool { - return $source instanceof QueryBuilder && ! ($source instanceof EloquentBuilder); + return $source instanceof QueryBuilder && !($source instanceof EloquentBuilder); } /** @@ -117,9 +117,9 @@ public static function canCreate($source): bool public function make(bool $mDataSupport = true): JsonResponse { try { - $results = $this->prepareQuery()->results(); + $results = $this->prepareQuery()->results(); $processed = $this->processResults($results, $mDataSupport); - $data = $this->transform($results, $processed); + $data = $this->transform($results, $processed); return $this->render($data); } catch (\Exception $exception) { @@ -144,7 +144,7 @@ public function results(): Collection */ public function prepareQuery(): static { - if (! $this->prepared) { + if (!$this->prepared) { $this->totalRecords = $this->totalCount(); $this->filterRecords(); @@ -174,34 +174,35 @@ 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([]); + $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()); + ->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}")); - $row_count = $this->wrap('row_count'); - $builder->select($this->getConnection()->raw("'1' as {$row_count}")); - if (! $this->keepSelectBindings) { + if (!$this->keepSelectBindings) { $builder->setBindings([], 'select'); } + return $builder; } /** * Check if builder query uses complex sql. * - * @param QueryBuilder|EloquentBuilder $query + * @param QueryBuilder|EloquentBuilder $query */ protected function isComplexQuery($query): bool { @@ -269,7 +270,7 @@ public function columnSearch(): void continue; } - if (! $this->request->isColumnSearchable($index) || $this->isBlacklisted($column) && ! $this->hasFilterColumn($column)) { + if (!$this->request->isColumnSearchable($index) || $this->isBlacklisted($column) && !$this->hasFilterColumn($column)) { continue; } @@ -277,7 +278,7 @@ public function columnSearch(): void $keyword = $this->getColumnSearchKeyword($index, true); $this->applyFilterColumn($this->getBaseQueryBuilder(), $column, $keyword); } else { - $column = $this->resolveRelationColumn($column); + $column = $this->resolveRelationColumn($column); $keyword = $this->getColumnSearchKeyword($index); $this->compileColumnSearch($index, $column, $keyword); } @@ -321,11 +322,11 @@ protected function getColumnNameByIndex(int $index): string /** * Apply filterColumn api search. * - * @param QueryBuilder $query + * @param QueryBuilder $query */ protected function applyFilterColumn($query, string $columnName, string $keyword, string $boolean = 'and'): void { - $query = $this->getBaseQueryBuilder($query); + $query = $this->getBaseQueryBuilder($query); $callback = $this->columnDef['filter'][$columnName]['method']; if ($this->query instanceof EloquentBuilder) { @@ -344,11 +345,11 @@ protected function applyFilterColumn($query, string $columnName, string $keyword /** * Get the base query builder instance. * - * @param QueryBuilder|EloquentBuilder|null $instance + * @param QueryBuilder|EloquentBuilder|null $instance */ protected function getBaseQueryBuilder($instance = null): QueryBuilder { - if (! $instance) { + if (!$instance) { $instance = $this->query; } @@ -396,20 +397,20 @@ protected function regexColumnSearch(string $column, string $keyword): void switch ($this->getConnection()->getDriverName()) { case 'oracle': - $sql = ! $this->config->isCaseInsensitive() - ? 'REGEXP_LIKE( '.$column.' , ? )' - : 'REGEXP_LIKE( LOWER('.$column.') , ?, \'i\' )'; + $sql = !$this->config->isCaseInsensitive() + ? 'REGEXP_LIKE( ' . $column . ' , ? )' + : 'REGEXP_LIKE( LOWER(' . $column . ') , ?, \'i\' )'; break; case 'pgsql': $column = $this->castColumn($column); - $sql = ! $this->config->isCaseInsensitive() ? $column.' ~ ?' : $column.' ~* ? '; + $sql = !$this->config->isCaseInsensitive() ? $column . ' ~ ?' : $column . ' ~* ? '; break; default: - $sql = ! $this->config->isCaseInsensitive() - ? $column.' REGEXP ?' - : 'LOWER('.$column.') REGEXP ?'; + $sql = !$this->config->isCaseInsensitive() + ? $column . ' REGEXP ?' + : 'LOWER(' . $column . ') REGEXP ?'; $keyword = Str::lower($keyword); } @@ -422,8 +423,8 @@ protected function regexColumnSearch(string $column, string $keyword): void protected function castColumn(string $column): string { return match ($this->getConnection()->getDriverName()) { - 'pgsql' => 'CAST('.$column.' as TEXT)', - 'firebird' => 'CAST('.$column.' as VARCHAR(255))', + 'pgsql' => 'CAST(' . $column . ' as TEXT)', + 'firebird' => 'CAST(' . $column . ' as VARCHAR(255))', default => $column, }; } @@ -431,39 +432,39 @@ protected function castColumn(string $column): string /** * Compile query builder where clause depending on configurations. * - * @param QueryBuilder|EloquentBuilder $query + * @param QueryBuilder|EloquentBuilder $query */ protected function compileQuerySearch($query, string $column, string $keyword, string $boolean = 'or'): void { $column = $this->addTablePrefix($query, $column); $column = $this->castColumn($column); - $sql = $column.' LIKE ?'; + $sql = $column . ' LIKE ?'; if ($this->config->isCaseInsensitive()) { - $sql = 'LOWER('.$column.') LIKE ?'; + $sql = 'LOWER(' . $column . ') LIKE ?'; } - $query->{$boolean.'WhereRaw'}($sql, [$this->prepareKeyword($keyword)]); + $query->{$boolean . 'WhereRaw'}($sql, [$this->prepareKeyword($keyword)]); } /** * Patch for fix about ambiguous field. * Ambiguous field error will appear when query use join table and search with keyword. * - * @param QueryBuilder|EloquentBuilder $query + * @param QueryBuilder|EloquentBuilder $query */ protected function addTablePrefix($query, string $column): string { - if (! str_contains($column, '.')) { - $q = $this->getBaseQueryBuilder($query); + if (!str_contains($column, '.')) { + $q = $this->getBaseQueryBuilder($query); $from = $q->from ?? ''; - if (! $from instanceof Expression) { - if (str_contains((string) $from, ' as ')) { - $from = explode(' as ', (string) $from)[1]; + if (!$from instanceof Expression) { + if (str_contains((string)$from, ' as ')) { + $from = explode(' as ', (string)$from)[1]; } - $column = $from.'.'.$column; + $column = $from . '.' . $column; } } @@ -497,7 +498,7 @@ protected function prepareKeyword(string $keyword): string /** * Add custom filter handler for the give column. * - * @param string $column + * @param string $column * @return $this */ public function filterColumn($column, callable $callback): static @@ -510,8 +511,8 @@ public function filterColumn($column, callable $callback): static /** * Order each given columns versus the given custom sql. * - * @param string $sql - * @param array $bindings + * @param string $sql + * @param array $bindings * @return $this */ public function orderColumns(array $columns, $sql, $bindings = []): static @@ -526,9 +527,9 @@ public function orderColumns(array $columns, $sql, $bindings = []): static /** * Override default column ordering. * - * @param string $column - * @param string|\Closure $sql - * @param array $bindings + * @param string $column + * @param string|\Closure $sql + * @param array $bindings * @return $this * * @internal string $1 Special variable that returns the requested order direction of the column. @@ -557,7 +558,7 @@ public function orderByNullsLast(): static */ public function paging(): void { - $start = $this->request->start(); + $start = $this->request->start(); $length = $this->request->length(); $limit = $length > 0 ? $length : 10; @@ -586,9 +587,9 @@ public function limit(callable $callback): static /** * Add column in collection. * - * @param string $name - * @param string|callable $content - * @param bool|int $order + * @param string $name + * @param string|callable $content + * @param bool|int $order * @return $this */ public function addColumn($name, $content, $order = false): static @@ -648,7 +649,7 @@ protected function defaultOrdering(): void return $orderable; }) - ->reject(fn ($orderable) => $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']); @@ -658,8 +659,8 @@ protected function defaultOrdering(): void $this->applyOrderColumn($column, $orderable); } else { $nullsLastSql = $this->getNullsLastSql($column, $orderable['direction']); - $normalSql = $this->wrap($column).' '.$orderable['direction']; - $sql = $this->nullsLast ? $nullsLastSql : $normalSql; + $normalSql = $this->wrap($column) . ' ' . $orderable['direction']; + $sql = $this->nullsLast ? $nullsLastSql : $normalSql; $this->query->orderByRaw($sql); } }); @@ -686,7 +687,7 @@ protected function applyOrderColumn(string $column, array $orderable): void if (is_callable($sql)) { call_user_func($sql, $this->query, $orderable['direction']); } else { - $sql = str_replace('$1', $orderable['direction'], (string) $sql); + $sql = str_replace('$1', $orderable['direction'], (string)$sql); $bindings = $this->columnDef['order'][$column]['bindings']; $this->query->orderByRaw($sql, $bindings); } @@ -695,8 +696,8 @@ protected function applyOrderColumn(string $column, array $orderable): void /** * Get NULLS LAST SQL. * - * @param string $column - * @param string $direction + * @param string $column + * @param string $direction * * @throws \Psr\Container\ContainerExceptionInterface * @throws \Psr\Container\NotFoundExceptionInterface @@ -725,9 +726,9 @@ protected function globalSearch(string $keyword): void $this->query->where(function ($query) use ($keyword) { collect($this->request->searchableColumnIndex()) - ->map(fn ($index) => $this->getColumnName($index)) + ->map(fn($index) => $this->getColumnName($index)) ->filter() - ->reject(fn ($column) => $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'); @@ -742,7 +743,7 @@ protected function globalSearch(string $keyword): void * Perform multi-term search by splitting keyword into * individual words and searches for each of them. * - * @param string $keyword + * @param string $keyword */ protected function smartGlobalSearch($keyword): void { @@ -767,7 +768,7 @@ protected function showDebugger(array $output): array }); $output['queries'] = $query_log; - $output['input'] = $this->request->all(); + $output['input'] = $this->request->all(); return $output; } @@ -838,17 +839,17 @@ public function ordering(): void public function enableScoutSearch(string $model, int $max_hits = 1000): static { $scout_model = new $model; - if (! class_exists($model) || ! ($scout_model instanceof Model)) { + if (!class_exists($model) || !($scout_model instanceof Model)) { throw new \Exception("$model must be an Eloquent Model."); } - if (! method_exists($scout_model, 'searchableAs') || ! method_exists($scout_model, 'getScoutKeyName')) { + if (!method_exists($scout_model, 'searchableAs') || !method_exists($scout_model, 'getScoutKeyName')) { throw new \Exception("$model must use the Searchable trait."); } - $this->scoutModel = $scout_model; + $this->scoutModel = $scout_model; $this->scoutMaxHits = $max_hits; - $this->scoutIndex = $this->scoutModel->searchableAs(); - $this->scoutKey = $this->scoutModel->getScoutKeyName(); + $this->scoutIndex = $this->scoutModel->searchableAs(); + $this->scoutKey = $this->scoutModel->getScoutKeyName(); return $this; } @@ -916,13 +917,13 @@ protected function applyFixedOrderingToQuery(string $keyName, array $orderedKeys $driverName = $connection->getDriverName(); // Escape keyName and orderedKeys - $keyName = $connection->getQueryGrammar()->wrap($keyName); + $keyName = $connection->getQueryGrammar()->wrap($keyName); $orderedKeys = collect($orderedKeys) - ->map(fn ($value) => $connection->escape($value)); + ->map(fn($value) => $connection->escape($value)); switch ($driverName) { case 'mysql': - $this->query->orderByRaw("FIELD($keyName, ".$orderedKeys->implode(',').')'); + $this->query->orderByRaw("FIELD($keyName, " . $orderedKeys->implode(',') . ')'); return true; @@ -932,7 +933,7 @@ protected function applyFixedOrderingToQuery(string $keyName, array $orderedKeys 'CASE ' . $orderedKeys - ->map(fn ($value, $index) => "WHEN $keyName=$value THEN $index") + ->map(fn($value, $index) => "WHEN $keyName=$value THEN $index") ->implode(' ') . ' END' @@ -946,7 +947,7 @@ protected function applyFixedOrderingToQuery(string $keyName, array $orderedKeys "CASE $keyName " . $orderedKeys - ->map(fn ($value, $index) => "WHEN $value THEN $index") + ->map(fn($value, $index) => "WHEN $value THEN $index") ->implode(' ') . ' END' @@ -967,7 +968,7 @@ protected function applyFixedOrderingToQuery(string $keyName, array $orderedKeys */ protected function performScoutSearch(string $searchKeyword, mixed $searchFilters = []): array { - if (! class_exists(\Laravel\Scout\EngineManager::class)) { + if (!class_exists(\Laravel\Scout\EngineManager::class)) { throw new \Exception('Laravel Scout is not installed.'); } $engine = app(\Laravel\Scout\EngineManager::class)->engine(); @@ -977,9 +978,9 @@ protected function performScoutSearch(string $searchKeyword, mixed $searchFilter $search_results = $engine ->index($this->scoutIndex) ->rawSearch($searchKeyword, [ - 'limit' => $this->scoutMaxHits, + 'limit' => $this->scoutMaxHits, 'attributesToRetrieve' => [$this->scoutKey], - 'filter' => $searchFilters, + 'filter' => $searchFilters, ]); /** @var array> $hits */ @@ -993,11 +994,11 @@ protected function performScoutSearch(string $searchKeyword, mixed $searchFilter $algolia = $engine->initIndex($this->scoutIndex); $search_results = $algolia->search($searchKeyword, [ - 'offset' => 0, - 'length' => $this->scoutMaxHits, - 'attributesToRetrieve' => [$this->scoutKey], + 'offset' => 0, + 'length' => $this->scoutMaxHits, + 'attributesToRetrieve' => [$this->scoutKey], 'attributesToHighlight' => [], - 'filters' => $searchFilters, + 'filters' => $searchFilters, ]); /** @var array> $hits */ diff --git a/tests/Unit/QueryDataTableTest.php b/tests/Unit/QueryDataTableTest.php index 0fee6802..f834a45a 100644 --- a/tests/Unit/QueryDataTableTest.php +++ b/tests/Unit/QueryDataTableTest.php @@ -74,7 +74,29 @@ 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_complexe_queries_with_complexe_select_are_wrapped_without_selects() + { + /** @var \Yajra\DataTables\QueryDataTable $dataTable */ + $dataTable = app('datatables')->of( + DB::table('users') + ->select(['users.*', DB::raw('count(*) as posts_count')]) + ->addSelect([ + 'last_post_id' => DB::table('posts') + ->whereColumn('posts.user_id', 'users.id') + ->orderBy('created_at') + ->select('id'), + ]) + ->join('posts', 'posts.user_id', 'users.id') + ->groupBy('users.id') + ); + + + $this->assertQueryWrapped(true, $dataTable->prepareCountQuery()); + $this->assertQueryHasNoSelect(false, $dataTable->prepareCountQuery()); + $this->assertEquals(20, $dataTable->count()); + } + + public function test_simple_queries_with_complexe_select_are_not_wrapped() { /** @var \Yajra\DataTables\QueryDataTable $dataTable */ $dataTable = app('datatables')->of( @@ -88,8 +110,8 @@ public function test_simple_queries_with_complexe_select_are_wrapped_without_sel ]) ); - $this->assertQueryWrapped(true, $dataTable->prepareCountQuery()); - $this->assertQueryHasNoSelect(true, $dataTable->prepareCountQuery()); + $this->assertQueryWrapped(false, $dataTable->prepareCountQuery()); + $this->assertQueryIsFromSub(false, $dataTable->prepareCountQuery()); $this->assertEquals(20, $dataTable->count()); } @@ -134,8 +156,20 @@ protected function assertQueryWrapped($expected, $query) */ public function assertQueryHasNoSelect($expected, $query) { - $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"); + } + + /** + * @param $expected bool + * @param $query \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder + * @return void + */ + public function assertQueryIsFromSub($expected, $query) + { + $sql = $query->select(DB::raw('count(*)'))->toSql(); - $this->assertSame($expected, Str::startsWith($sql, 'select * from (select 1 as dt_row_count from'), "'{$sql}' is not wrapped"); + $this->assertSame($expected, Str::startsWith($sql, 'select count(*) from (select'), "'{$sql}' is from sub query"); } } From 7bdd78471f112460139577c35692f4653282d0f1 Mon Sep 17 00:00:00 2001 From: Mathieu FERRE Date: Fri, 12 Apr 2024 15:25:56 +0200 Subject: [PATCH 029/140] Update QueryDataTableTest.php --- tests/Unit/QueryDataTableTest.php | 32 ++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/tests/Unit/QueryDataTableTest.php b/tests/Unit/QueryDataTableTest.php index f834a45a..988c3ae1 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 Yajra\DataTables\Tests\Models\Post; use Yajra\DataTables\Tests\Models\User; use Yajra\DataTables\Tests\TestCase; @@ -111,10 +112,27 @@ public function test_simple_queries_with_complexe_select_are_not_wrapped() ); $this->assertQueryWrapped(false, $dataTable->prepareCountQuery()); - $this->assertQueryIsFromSub(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()->first()->row_count); + } + public function test_simple_queries_are_not_wrapped_and_countable() { /** @var \Yajra\DataTables\QueryDataTable $dataTable */ @@ -160,16 +178,4 @@ public function assertQueryHasNoSelect($expected, $query) $this->assertSame($expected, Str::startsWith($sql, 'select count(*) from (select 1 as dt_row_count from'), "'{$sql}' has select"); } - - /** - * @param $expected bool - * @param $query \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder - * @return void - */ - public function assertQueryIsFromSub($expected, $query) - { - $sql = $query->select(DB::raw('count(*)'))->toSql(); - - $this->assertSame($expected, Str::startsWith($sql, 'select count(*) from (select'), "'{$sql}' is from sub query"); - } } From e9623284024b0f35c9eb69151e7cf569babbfa9d Mon Sep 17 00:00:00 2001 From: Mathieu FERRE Date: Fri, 12 Apr 2024 15:49:15 +0200 Subject: [PATCH 030/140] Update QueryDataTableTest.php --- tests/Unit/QueryDataTableTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/QueryDataTableTest.php b/tests/Unit/QueryDataTableTest.php index 988c3ae1..294f4149 100644 --- a/tests/Unit/QueryDataTableTest.php +++ b/tests/Unit/QueryDataTableTest.php @@ -130,7 +130,7 @@ public function test_simple_queries_with_complexe_where_are_not_wrapped() ); $this->assertQueryWrapped(false, $dataTable->prepareCountQuery()); - $this->assertEquals(1, $dataTable->prepareCountQuery()->first()->row_count); + $this->assertEquals(1, $dataTable->prepareCountQuery()->count()); } public function test_simple_queries_are_not_wrapped_and_countable() From f814ae955df488d2d9d93ca788b75b39e11fd332 Mon Sep 17 00:00:00 2001 From: Mathieu FERRE Date: Fri, 12 Apr 2024 15:49:17 +0200 Subject: [PATCH 031/140] Update QueryDataTable.php --- src/QueryDataTable.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index f60fb639..b9001732 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -176,7 +176,12 @@ public function prepareCountQuery(): QueryBuilder $builder->select(DB::raw('1 as dt_row_count')); $clone = $builder->clone(); $clone->setBindings([]); - $clone->wheres = []; + if ($clone instanceof EloquentBuilder) { + $clone->getQuery()->wheres = []; + } else { + $clone->wheres = []; + } + if ($this->isComplexQuery($clone)) { if (!$this->ignoreSelectInCountQuery) { $builder = clone $this->query; @@ -188,8 +193,8 @@ public function prepareCountQuery(): QueryBuilder ->setBindings($builder->getBindings()); } } - $row_count = $this->wrap('row_count'); - $builder->select($this->getConnection()->raw("'1' as {$row_count}")); + $row_count = $this->wrap('row_count'); + $builder->select($this->getConnection()->raw("'1' as {$row_count}")); if (!$this->keepSelectBindings) { $builder->setBindings([], 'select'); From b3b14144f715775d8890cb830a97a389c46fcb8c Mon Sep 17 00:00:00 2001 From: Mathieu FERRE Date: Fri, 12 Apr 2024 15:59:46 +0200 Subject: [PATCH 032/140] Update QueryDataTableTest.php --- tests/Unit/QueryDataTableTest.php | 38 ++++++++++++++----------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/tests/Unit/QueryDataTableTest.php b/tests/Unit/QueryDataTableTest.php index 294f4149..7c7812a7 100644 --- a/tests/Unit/QueryDataTableTest.php +++ b/tests/Unit/QueryDataTableTest.php @@ -74,52 +74,48 @@ public function test_complex_query_can_ignore_select_in_count() $this->assertQueryHasNoSelect(true, $dataTable->prepareCountQuery()); $this->assertEquals(20, $dataTable->count()); } - - public function test_complexe_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.*', DB::raw('count(*) as posts_count')]) + DB::table('users') + ->select('users.*') ->addSelect([ 'last_post_id' => DB::table('posts') ->whereColumn('posts.user_id', 'users.id') ->orderBy('created_at') ->select('id'), ]) - ->join('posts', 'posts.user_id', 'users.id') - ->groupBy('users.id') ); - - $this->assertQueryWrapped(true, $dataTable->prepareCountQuery()); - $this->assertQueryHasNoSelect(false, $dataTable->prepareCountQuery()); + $this->assertQueryWrapped(false, $dataTable->prepareCountQuery()); $this->assertEquals(20, $dataTable->count()); } - public function test_simple_queries_with_complexe_select_are_not_wrapped() + 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.*') - ->addSelect([ - 'last_post_id' => DB::table('posts') + ->where( + DB::table('posts') ->whereColumn('posts.user_id', 'users.id') ->orderBy('created_at') - ->select('id'), - ]) + ->select('title'), 'User-1 Post-1' + ) ); $this->assertQueryWrapped(false, $dataTable->prepareCountQuery()); - $this->assertEquals(20, $dataTable->count()); + $this->assertEquals(1, $dataTable->prepareCountQuery()->count()); } - public function test_simple_queries_with_complexe_where_are_not_wrapped() + public function test_simple_eloquent_queries_with_complexe_where_are_not_wrapped() { /** @var \Yajra\DataTables\QueryDataTable $dataTable */ $dataTable = app('datatables')->of( - DB::table('users') + User::query() ->select('users.*') ->where( DB::table('posts') @@ -157,10 +153,10 @@ public function test_complexe_queries_can_be_wrapped_and_countable() /** * @param $expected bool - * @param $query \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder + * @param $query \Illuminate\Contracts\Database\Query\Builder * @return void */ - protected function assertQueryWrapped($expected, $query) + protected function assertQueryWrapped($expected, $query): void { $sql = $query->toSql(); @@ -169,10 +165,10 @@ protected function assertQueryWrapped($expected, $query) /** * @param $expected bool - * @param $query \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder + * @param $query \Illuminate\Contracts\Database\Query\Builder * @return void */ - public function assertQueryHasNoSelect($expected, $query) + public function assertQueryHasNoSelect($expected, $query): void { $sql = $query->select(DB::raw('count(*)'))->toSql(); From 89f785f1ee9bf22d2ae66e64b336e9a5bfddd69e Mon Sep 17 00:00:00 2001 From: yajra Date: Tue, 16 Apr 2024 04:54:54 +0000 Subject: [PATCH 033/140] fix: pint --- src/QueryDataTable.php | 151 +++++++++++++++--------------- tests/Unit/QueryDataTableTest.php | 9 +- 2 files changed, 78 insertions(+), 82 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index b9001732..2b5fbdbb 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -83,7 +83,7 @@ class QueryDataTable extends DataTableAbstract public function __construct(protected QueryBuilder $query) { $this->request = app('datatables.request'); - $this->config = app('datatables.config'); + $this->config = app('datatables.config'); $this->columns = $this->query->getColumns(); if ($this->config->isDebugging()) { @@ -102,11 +102,11 @@ public function getConnection(): Connection /** * Can the DataTable engine be created with these parameters. * - * @param mixed $source + * @param mixed $source */ public static function canCreate($source): bool { - return $source instanceof QueryBuilder && !($source instanceof EloquentBuilder); + return $source instanceof QueryBuilder && ! ($source instanceof EloquentBuilder); } /** @@ -117,9 +117,9 @@ public static function canCreate($source): bool public function make(bool $mDataSupport = true): JsonResponse { try { - $results = $this->prepareQuery()->results(); + $results = $this->prepareQuery()->results(); $processed = $this->processResults($results, $mDataSupport); - $data = $this->transform($results, $processed); + $data = $this->transform($results, $processed); return $this->render($data); } catch (\Exception $exception) { @@ -144,7 +144,7 @@ public function results(): Collection */ public function prepareQuery(): static { - if (!$this->prepared) { + if (! $this->prepared) { $this->totalRecords = $this->totalCount(); $this->filterRecords(); @@ -183,31 +183,30 @@ public function prepareCountQuery(): QueryBuilder } if ($this->isComplexQuery($clone)) { - if (!$this->ignoreSelectInCountQuery) { + if (! $this->ignoreSelectInCountQuery) { $builder = clone $this->query; } return $this->getConnection() - ->query() - ->fromRaw('(' . $builder->toSql() . ') count_row_table') - ->setBindings($builder->getBindings()); + ->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) { + if (! $this->keepSelectBindings) { $builder->setBindings([], 'select'); } - return $builder; } /** * Check if builder query uses complex sql. * - * @param QueryBuilder|EloquentBuilder $query + * @param QueryBuilder|EloquentBuilder $query */ protected function isComplexQuery($query): bool { @@ -275,7 +274,7 @@ public function columnSearch(): void continue; } - if (!$this->request->isColumnSearchable($index) || $this->isBlacklisted($column) && !$this->hasFilterColumn($column)) { + if (! $this->request->isColumnSearchable($index) || $this->isBlacklisted($column) && ! $this->hasFilterColumn($column)) { continue; } @@ -283,7 +282,7 @@ public function columnSearch(): void $keyword = $this->getColumnSearchKeyword($index, true); $this->applyFilterColumn($this->getBaseQueryBuilder(), $column, $keyword); } else { - $column = $this->resolveRelationColumn($column); + $column = $this->resolveRelationColumn($column); $keyword = $this->getColumnSearchKeyword($index); $this->compileColumnSearch($index, $column, $keyword); } @@ -327,11 +326,11 @@ protected function getColumnNameByIndex(int $index): string /** * Apply filterColumn api search. * - * @param QueryBuilder $query + * @param QueryBuilder $query */ protected function applyFilterColumn($query, string $columnName, string $keyword, string $boolean = 'and'): void { - $query = $this->getBaseQueryBuilder($query); + $query = $this->getBaseQueryBuilder($query); $callback = $this->columnDef['filter'][$columnName]['method']; if ($this->query instanceof EloquentBuilder) { @@ -350,11 +349,11 @@ protected function applyFilterColumn($query, string $columnName, string $keyword /** * Get the base query builder instance. * - * @param QueryBuilder|EloquentBuilder|null $instance + * @param QueryBuilder|EloquentBuilder|null $instance */ protected function getBaseQueryBuilder($instance = null): QueryBuilder { - if (!$instance) { + if (! $instance) { $instance = $this->query; } @@ -402,20 +401,20 @@ protected function regexColumnSearch(string $column, string $keyword): void switch ($this->getConnection()->getDriverName()) { case 'oracle': - $sql = !$this->config->isCaseInsensitive() - ? 'REGEXP_LIKE( ' . $column . ' , ? )' - : 'REGEXP_LIKE( LOWER(' . $column . ') , ?, \'i\' )'; + $sql = ! $this->config->isCaseInsensitive() + ? 'REGEXP_LIKE( '.$column.' , ? )' + : 'REGEXP_LIKE( LOWER('.$column.') , ?, \'i\' )'; break; case 'pgsql': $column = $this->castColumn($column); - $sql = !$this->config->isCaseInsensitive() ? $column . ' ~ ?' : $column . ' ~* ? '; + $sql = ! $this->config->isCaseInsensitive() ? $column.' ~ ?' : $column.' ~* ? '; break; default: - $sql = !$this->config->isCaseInsensitive() - ? $column . ' REGEXP ?' - : 'LOWER(' . $column . ') REGEXP ?'; + $sql = ! $this->config->isCaseInsensitive() + ? $column.' REGEXP ?' + : 'LOWER('.$column.') REGEXP ?'; $keyword = Str::lower($keyword); } @@ -428,8 +427,8 @@ protected function regexColumnSearch(string $column, string $keyword): void protected function castColumn(string $column): string { return match ($this->getConnection()->getDriverName()) { - 'pgsql' => 'CAST(' . $column . ' as TEXT)', - 'firebird' => 'CAST(' . $column . ' as VARCHAR(255))', + 'pgsql' => 'CAST('.$column.' as TEXT)', + 'firebird' => 'CAST('.$column.' as VARCHAR(255))', default => $column, }; } @@ -437,39 +436,39 @@ protected function castColumn(string $column): string /** * Compile query builder where clause depending on configurations. * - * @param QueryBuilder|EloquentBuilder $query + * @param QueryBuilder|EloquentBuilder $query */ protected function compileQuerySearch($query, string $column, string $keyword, string $boolean = 'or'): void { $column = $this->addTablePrefix($query, $column); $column = $this->castColumn($column); - $sql = $column . ' LIKE ?'; + $sql = $column.' LIKE ?'; if ($this->config->isCaseInsensitive()) { - $sql = 'LOWER(' . $column . ') LIKE ?'; + $sql = 'LOWER('.$column.') LIKE ?'; } - $query->{$boolean . 'WhereRaw'}($sql, [$this->prepareKeyword($keyword)]); + $query->{$boolean.'WhereRaw'}($sql, [$this->prepareKeyword($keyword)]); } /** * Patch for fix about ambiguous field. * Ambiguous field error will appear when query use join table and search with keyword. * - * @param QueryBuilder|EloquentBuilder $query + * @param QueryBuilder|EloquentBuilder $query */ protected function addTablePrefix($query, string $column): string { - if (!str_contains($column, '.')) { - $q = $this->getBaseQueryBuilder($query); + if (! str_contains($column, '.')) { + $q = $this->getBaseQueryBuilder($query); $from = $q->from ?? ''; - if (!$from instanceof Expression) { - if (str_contains((string)$from, ' as ')) { - $from = explode(' as ', (string)$from)[1]; + if (! $from instanceof Expression) { + if (str_contains((string) $from, ' as ')) { + $from = explode(' as ', (string) $from)[1]; } - $column = $from . '.' . $column; + $column = $from.'.'.$column; } } @@ -503,7 +502,7 @@ protected function prepareKeyword(string $keyword): string /** * Add custom filter handler for the give column. * - * @param string $column + * @param string $column * @return $this */ public function filterColumn($column, callable $callback): static @@ -516,8 +515,8 @@ public function filterColumn($column, callable $callback): static /** * Order each given columns versus the given custom sql. * - * @param string $sql - * @param array $bindings + * @param string $sql + * @param array $bindings * @return $this */ public function orderColumns(array $columns, $sql, $bindings = []): static @@ -532,9 +531,9 @@ public function orderColumns(array $columns, $sql, $bindings = []): static /** * Override default column ordering. * - * @param string $column - * @param string|\Closure $sql - * @param array $bindings + * @param string $column + * @param string|\Closure $sql + * @param array $bindings * @return $this * * @internal string $1 Special variable that returns the requested order direction of the column. @@ -563,7 +562,7 @@ public function orderByNullsLast(): static */ public function paging(): void { - $start = $this->request->start(); + $start = $this->request->start(); $length = $this->request->length(); $limit = $length > 0 ? $length : 10; @@ -592,9 +591,9 @@ public function limit(callable $callback): static /** * Add column in collection. * - * @param string $name - * @param string|callable $content - * @param bool|int $order + * @param string $name + * @param string|callable $content + * @param bool|int $order * @return $this */ public function addColumn($name, $content, $order = false): static @@ -654,7 +653,7 @@ protected function defaultOrdering(): void return $orderable; }) - ->reject(fn($orderable) => $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']); @@ -664,8 +663,8 @@ protected function defaultOrdering(): void $this->applyOrderColumn($column, $orderable); } else { $nullsLastSql = $this->getNullsLastSql($column, $orderable['direction']); - $normalSql = $this->wrap($column) . ' ' . $orderable['direction']; - $sql = $this->nullsLast ? $nullsLastSql : $normalSql; + $normalSql = $this->wrap($column).' '.$orderable['direction']; + $sql = $this->nullsLast ? $nullsLastSql : $normalSql; $this->query->orderByRaw($sql); } }); @@ -692,7 +691,7 @@ protected function applyOrderColumn(string $column, array $orderable): void if (is_callable($sql)) { call_user_func($sql, $this->query, $orderable['direction']); } else { - $sql = str_replace('$1', $orderable['direction'], (string)$sql); + $sql = str_replace('$1', $orderable['direction'], (string) $sql); $bindings = $this->columnDef['order'][$column]['bindings']; $this->query->orderByRaw($sql, $bindings); } @@ -701,8 +700,8 @@ protected function applyOrderColumn(string $column, array $orderable): void /** * Get NULLS LAST SQL. * - * @param string $column - * @param string $direction + * @param string $column + * @param string $direction * * @throws \Psr\Container\ContainerExceptionInterface * @throws \Psr\Container\NotFoundExceptionInterface @@ -731,9 +730,9 @@ protected function globalSearch(string $keyword): void $this->query->where(function ($query) use ($keyword) { collect($this->request->searchableColumnIndex()) - ->map(fn($index) => $this->getColumnName($index)) + ->map(fn ($index) => $this->getColumnName($index)) ->filter() - ->reject(fn($column) => $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'); @@ -748,7 +747,7 @@ protected function globalSearch(string $keyword): void * Perform multi-term search by splitting keyword into * individual words and searches for each of them. * - * @param string $keyword + * @param string $keyword */ protected function smartGlobalSearch($keyword): void { @@ -773,7 +772,7 @@ protected function showDebugger(array $output): array }); $output['queries'] = $query_log; - $output['input'] = $this->request->all(); + $output['input'] = $this->request->all(); return $output; } @@ -844,17 +843,17 @@ public function ordering(): void public function enableScoutSearch(string $model, int $max_hits = 1000): static { $scout_model = new $model; - if (!class_exists($model) || !($scout_model instanceof Model)) { + if (! class_exists($model) || ! ($scout_model instanceof Model)) { throw new \Exception("$model must be an Eloquent Model."); } - if (!method_exists($scout_model, 'searchableAs') || !method_exists($scout_model, 'getScoutKeyName')) { + if (! method_exists($scout_model, 'searchableAs') || ! method_exists($scout_model, 'getScoutKeyName')) { throw new \Exception("$model must use the Searchable trait."); } - $this->scoutModel = $scout_model; + $this->scoutModel = $scout_model; $this->scoutMaxHits = $max_hits; - $this->scoutIndex = $this->scoutModel->searchableAs(); - $this->scoutKey = $this->scoutModel->getScoutKeyName(); + $this->scoutIndex = $this->scoutModel->searchableAs(); + $this->scoutKey = $this->scoutModel->getScoutKeyName(); return $this; } @@ -922,13 +921,13 @@ protected function applyFixedOrderingToQuery(string $keyName, array $orderedKeys $driverName = $connection->getDriverName(); // Escape keyName and orderedKeys - $keyName = $connection->getQueryGrammar()->wrap($keyName); + $keyName = $connection->getQueryGrammar()->wrap($keyName); $orderedKeys = collect($orderedKeys) - ->map(fn($value) => $connection->escape($value)); + ->map(fn ($value) => $connection->escape($value)); switch ($driverName) { case 'mysql': - $this->query->orderByRaw("FIELD($keyName, " . $orderedKeys->implode(',') . ')'); + $this->query->orderByRaw("FIELD($keyName, ".$orderedKeys->implode(',').')'); return true; @@ -938,7 +937,7 @@ protected function applyFixedOrderingToQuery(string $keyName, array $orderedKeys 'CASE ' . $orderedKeys - ->map(fn($value, $index) => "WHEN $keyName=$value THEN $index") + ->map(fn ($value, $index) => "WHEN $keyName=$value THEN $index") ->implode(' ') . ' END' @@ -952,7 +951,7 @@ protected function applyFixedOrderingToQuery(string $keyName, array $orderedKeys "CASE $keyName " . $orderedKeys - ->map(fn($value, $index) => "WHEN $value THEN $index") + ->map(fn ($value, $index) => "WHEN $value THEN $index") ->implode(' ') . ' END' @@ -973,7 +972,7 @@ protected function applyFixedOrderingToQuery(string $keyName, array $orderedKeys */ protected function performScoutSearch(string $searchKeyword, mixed $searchFilters = []): array { - if (!class_exists(\Laravel\Scout\EngineManager::class)) { + if (! class_exists(\Laravel\Scout\EngineManager::class)) { throw new \Exception('Laravel Scout is not installed.'); } $engine = app(\Laravel\Scout\EngineManager::class)->engine(); @@ -983,9 +982,9 @@ protected function performScoutSearch(string $searchKeyword, mixed $searchFilter $search_results = $engine ->index($this->scoutIndex) ->rawSearch($searchKeyword, [ - 'limit' => $this->scoutMaxHits, + 'limit' => $this->scoutMaxHits, 'attributesToRetrieve' => [$this->scoutKey], - 'filter' => $searchFilters, + 'filter' => $searchFilters, ]); /** @var array> $hits */ @@ -999,11 +998,11 @@ protected function performScoutSearch(string $searchKeyword, mixed $searchFilter $algolia = $engine->initIndex($this->scoutIndex); $search_results = $algolia->search($searchKeyword, [ - 'offset' => 0, - 'length' => $this->scoutMaxHits, - 'attributesToRetrieve' => [$this->scoutKey], + 'offset' => 0, + 'length' => $this->scoutMaxHits, + 'attributesToRetrieve' => [$this->scoutKey], 'attributesToHighlight' => [], - 'filters' => $searchFilters, + 'filters' => $searchFilters, ]); /** @var array> $hits */ diff --git a/tests/Unit/QueryDataTableTest.php b/tests/Unit/QueryDataTableTest.php index 7c7812a7..4e7ee785 100644 --- a/tests/Unit/QueryDataTableTest.php +++ b/tests/Unit/QueryDataTableTest.php @@ -4,7 +4,6 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; -use Yajra\DataTables\Tests\Models\Post; use Yajra\DataTables\Tests\Models\User; use Yajra\DataTables\Tests\TestCase; @@ -74,7 +73,7 @@ public function test_complex_query_can_ignore_select_in_count() $this->assertQueryHasNoSelect(true, $dataTable->prepareCountQuery()); $this->assertEquals(20, $dataTable->count()); } - + public function test_simple_queries_with_complexe_select_are_not_wrapped() { /** @var \Yajra\DataTables\QueryDataTable $dataTable */ @@ -115,7 +114,7 @@ public function test_simple_eloquent_queries_with_complexe_where_are_not_wrapped { /** @var \Yajra\DataTables\QueryDataTable $dataTable */ $dataTable = app('datatables')->of( - User::query() + User::query() ->select('users.*') ->where( DB::table('posts') @@ -153,8 +152,7 @@ public function test_complexe_queries_can_be_wrapped_and_countable() /** * @param $expected bool - * @param $query \Illuminate\Contracts\Database\Query\Builder - * @return void + * @param $query \Illuminate\Contracts\Database\Query\Builder */ protected function assertQueryWrapped($expected, $query): void { @@ -166,7 +164,6 @@ protected function assertQueryWrapped($expected, $query): void /** * @param $expected bool * @param $query \Illuminate\Contracts\Database\Query\Builder - * @return void */ public function assertQueryHasNoSelect($expected, $query): void { From b1849508ca5dcabb6f375e45d9f00dbb94b79082 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 16 Apr 2024 12:58:34 +0800 Subject: [PATCH 034/140] chore: release v11.1.0 :rocket: --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35a7761f..ce6772b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ ### [Unreleased] +### [v11.1.0](https://github.com/yajra/laravel-datatables/compare/v11.1.0...v11.0.0) - 2024-04-16 + +- feat: Optimize simple queries #3135 +- fix: #3133 + ### [v11.0.0](https://github.com/yajra/laravel-datatables/compare/v11.0.0...master) - 2024-03-14 - Laravel 11 support From 9db53e2dcb787c0c99caae7981633bf39ebf2ebe Mon Sep 17 00:00:00 2001 From: Furkan Akkoc Date: Thu, 23 May 2024 14:21:17 +0200 Subject: [PATCH 035/140] Added mariadb support for scout search --- src/QueryDataTable.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 2b5fbdbb..b6bc0813 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -926,6 +926,7 @@ protected function applyFixedOrderingToQuery(string $keyName, array $orderedKeys ->map(fn ($value) => $connection->escape($value)); switch ($driverName) { + case 'mariadb': case 'mysql': $this->query->orderByRaw("FIELD($keyName, ".$orderedKeys->implode(',').')'); From 74a1d060418a5dd269ed38ca62cadaaed19b7124 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Thu, 23 May 2024 21:24:38 +0800 Subject: [PATCH 036/140] chore: release v11.1.1 :rocket: --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce6772b9..fbc6d52a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,11 @@ ### [Unreleased] -### [v11.1.0](https://github.com/yajra/laravel-datatables/compare/v11.1.0...v11.0.0) - 2024-04-16 +### [v11.1.1](https://github.com/yajra/laravel-datatables/compare/v11.1.0...v11.1.1) - 2024-04-16 + +- fix: mariadb support for scout search #3146 + +### [v11.1.0](https://github.com/yajra/laravel-datatables/compare/v11.0.0...v11.1.0) - 2024-04-16 - feat: Optimize simple queries #3135 - fix: #3133 From 14a1f1a96652187235d41611c423892a4e50c2b4 Mon Sep 17 00:00:00 2001 From: mgralikowski Date: Tue, 2 Jul 2024 11:13:59 +0200 Subject: [PATCH 037/140] Update Request.php Fix a notice error. --- src/Utilities/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utilities/Request.php b/src/Utilities/Request.php index 89143add..97150b87 100644 --- a/src/Utilities/Request.php +++ b/src/Utilities/Request.php @@ -86,7 +86,7 @@ public function orderableColumns(): array /** @var string $direction */ $direction = $this->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]; } From 8c68a838633337d33c65ee9f1a61cc4df91fc658 Mon Sep 17 00:00:00 2001 From: yajra Date: Wed, 3 Jul 2024 13:49:49 +0000 Subject: [PATCH 038/140] fix: pint --- src/Exceptions/Exception.php | 4 +--- src/Processors/RowProcessor.php | 4 +--- src/Utilities/Config.php | 4 +--- tests/Formatters/DateFormatter.php | 4 +--- 4 files changed, 4 insertions(+), 12 deletions(-) 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/RowProcessor.php b/src/Processors/RowProcessor.php index 6b86d47c..b402b419 100644 --- a/src/Processors/RowProcessor.php +++ b/src/Processors/RowProcessor.php @@ -10,9 +10,7 @@ class RowProcessor /** * @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. diff --git a/src/Utilities/Config.php b/src/Utilities/Config.php index 8b3c6dce..ae474368 100644 --- a/src/Utilities/Config.php +++ b/src/Utilities/Config.php @@ -9,9 +9,7 @@ class Config /** * Config constructor. */ - public function __construct(private readonly Repository $repository) - { - } + public function __construct(private readonly Repository $repository) {} /** * Check if config uses wild card search. diff --git a/tests/Formatters/DateFormatter.php b/tests/Formatters/DateFormatter.php index 516b901c..1a4e0abe 100644 --- a/tests/Formatters/DateFormatter.php +++ b/tests/Formatters/DateFormatter.php @@ -8,9 +8,7 @@ class DateFormatter implements Formatter { - public function __construct(public string $format = 'Y-m-d h:i a') - { - } + public function __construct(public string $format = 'Y-m-d h:i a') {} public function format($value, $row): string { From 393296da329cf728873b0c9ac2fe8515d07871f9 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Wed, 3 Jul 2024 21:51:32 +0800 Subject: [PATCH 039/140] chore: release v11.1.2 :rocket: --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbc6d52a..e0c74bb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ### [Unreleased] +### [v11.1.2](https://github.com/yajra/laravel-datatables/compare/v11.1.1...v11.1.2) - 2024-07-03 + +- fix: ErrorException when direction is null #3154 + ### [v11.1.1](https://github.com/yajra/laravel-datatables/compare/v11.1.0...v11.1.1) - 2024-04-16 - fix: mariadb support for scout search #3146 From 9f7169a1cea51ff22cf4154c5c0e6da35d19c558 Mon Sep 17 00:00:00 2001 From: Jurian Arie <28654085+JurianArie@users.noreply.github.com> Date: Tue, 9 Jul 2024 09:37:13 +0200 Subject: [PATCH 040/140] Make query for filteredRecords when totalRecords was manually set --- src/QueryDataTable.php | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index b6bc0813..aea42844 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -25,6 +25,11 @@ class QueryDataTable extends DataTableAbstract */ protected bool $prepared = false; + /** + * Flag to check if the total records count query has been performed. + */ + protected bool $performedTotalRecordsCount = false; + /** * Query callback for custom pagination using limit without offset. * @@ -157,6 +162,20 @@ public function prepareQuery(): static return $this; } + /** + * Count total items. + */ + public function totalCount(): int + { + if ($this->totalRecords !== null) { + return $this->totalRecords; + } + + $this->performedTotalRecordsCount = true; + + return $this->totalRecords = $this->count(); + } + /** * Counts current query. */ @@ -253,7 +272,7 @@ protected function filterRecords(): void // 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->query == $initialQuery && $this->performedTotalRecordsCount) { $this->filteredRecords ??= $this->totalRecords; } else { $this->filteredCount(); From 1298c5235475c54f63b6b81d21b33a769e6da069 Mon Sep 17 00:00:00 2001 From: Jurian Arie <28654085+JurianArie@users.noreply.github.com> Date: Tue, 9 Jul 2024 09:48:44 +0200 Subject: [PATCH 041/140] Update QueryDataTableTest.php --- tests/Integration/QueryDataTableTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Integration/QueryDataTableTest.php b/tests/Integration/QueryDataTableTest.php index 5c8d9399..8367a037 100644 --- a/tests/Integration/QueryDataTableTest.php +++ b/tests/Integration/QueryDataTableTest.php @@ -26,7 +26,7 @@ public function it_can_set_total_records() $crawler->assertJson([ 'draw' => 0, 'recordsTotal' => 10, - 'recordsFiltered' => 10, + 'recordsFiltered' => 20, ]); } @@ -37,7 +37,7 @@ public function it_can_set_zero_total_records() $crawler->assertJson([ 'draw' => 0, 'recordsTotal' => 0, - 'recordsFiltered' => 0, + 'recordsFiltered' => 20, ]); } From 4d69cdd289bccd904ffab18831747917cb3b5a70 Mon Sep 17 00:00:00 2001 From: JurianArie <28654085+JurianArie@users.noreply.github.com> Date: Thu, 11 Jul 2024 13:09:08 +0200 Subject: [PATCH 042/140] Add test to show the optimization still works --- tests/Integration/QueryDataTableTest.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/Integration/QueryDataTableTest.php b/tests/Integration/QueryDataTableTest.php index 8367a037..d2b4a028 100644 --- a/tests/Integration/QueryDataTableTest.php +++ b/tests/Integration/QueryDataTableTest.php @@ -55,12 +55,19 @@ public function it_can_set_total_filtered_records() #[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] From 78ecbc43025a24b92c307664c0cf395fea56c215 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Mon, 15 Jul 2024 12:47:52 +0800 Subject: [PATCH 043/140] chore: release v11.1.3 :rocket: --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0c74bb1..9a64d5f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ### [Unreleased] +### [v11.1.3](https://github.com/yajra/laravel-datatables/compare/v11.1.2...v11.1.3) - 2024-07-15 + +- fix: make query for filteredRecords when totalRecords was manually set #3157 + ### [v11.1.2](https://github.com/yajra/laravel-datatables/compare/v11.1.1...v11.1.2) - 2024-07-03 - fix: ErrorException when direction is null #3154 From 6c4f1e419b566f9725a20cd942be6704750d09dc Mon Sep 17 00:00:00 2001 From: Michael Newton Date: Fri, 9 Aug 2024 14:39:39 -0600 Subject: [PATCH 044/140] Ensure dates are not turned to arrays Resolves #3156 --- src/Utilities/Helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utilities/Helper.php b/src/Utilities/Helper.php index a42be6c7..bea49ac6 100644 --- a/src/Utilities/Helper.php +++ b/src/Utilities/Helper.php @@ -203,7 +203,7 @@ public static function convertToArray(mixed $row, array $filters = []): array $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); } From 11d30f1e9493b1d82d99896b68d281a7855c9c4b Mon Sep 17 00:00:00 2001 From: Michael Newton Date: Tue, 13 Aug 2024 12:30:17 -0600 Subject: [PATCH 045/140] fix code style complaints --- src/Utilities/Helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utilities/Helper.php b/src/Utilities/Helper.php index bea49ac6..a859f17d 100644 --- a/src/Utilities/Helper.php +++ b/src/Utilities/Helper.php @@ -203,7 +203,7 @@ public static function convertToArray(mixed $row, array $filters = []): array $data = $row instanceof Arrayable ? $row->toArray() : (array) $row; foreach ($data as &$value) { - if ((is_object($value) && !$value instanceof DateTime) || is_array($value)) { + if ((is_object($value) && ! $value instanceof DateTime) || is_array($value)) { $value = self::convertToArray($value); } From 2a8eae41f2042498afbee2e5acb6db361f4d230a Mon Sep 17 00:00:00 2001 From: yajra Date: Sat, 17 Aug 2024 03:54:56 +0000 Subject: [PATCH 046/140] fix: pint --- src/DataTablesServiceProvider.php | 2 +- tests/Unit/HelperTest.php | 18 +++++++++--------- tests/Unit/RequestTest.php | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/DataTablesServiceProvider.php b/src/DataTablesServiceProvider.php index 579db87b..59d29e24 100644 --- a/src/DataTablesServiceProvider.php +++ b/src/DataTablesServiceProvider.php @@ -45,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/tests/Unit/HelperTest.php b/tests/Unit/HelperTest.php index 2e493453..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); @@ -105,7 +105,7 @@ public function test_compile_content_function() { $content = fn ($obj) => $obj->id; $data = ['id' => 2]; - $obj = new stdClass(); + $obj = new stdClass; $obj->id = 2; $compiled = Helper::compileContent($content, $data, $obj); @@ -122,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); @@ -145,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; @@ -162,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); @@ -188,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/RequestTest.php b/tests/Unit/RequestTest.php index b93cb335..cbcdfaa8 100644 --- a/tests/Unit/RequestTest.php +++ b/tests/Unit/RequestTest.php @@ -21,7 +21,7 @@ public function it_can_get_the_base_request() */ protected function getRequest() { - return new Request(); + return new Request; } #[Test] From 43653f7fcf6668b8188f7b7c0c2b25481e4d4341 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sat, 17 Aug 2024 11:57:28 +0800 Subject: [PATCH 047/140] chore: cs --- .github/workflows/pint.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pint.yml b/.github/workflows/pint.yml index f0783477..2151cafa 100644 --- a/.github/workflows/pint.yml +++ b/.github/workflows/pint.yml @@ -17,4 +17,3 @@ jobs: - uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: "fix: pint" - From 178a25d9668ac822bc9b57f2dc1ea3abfb66ca96 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sat, 17 Aug 2024 11:57:36 +0800 Subject: [PATCH 048/140] chore: release v11.1.4 :rocket: --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a64d5f2..d8c76a34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ ### [Unreleased] +### [v11.1.4](https://github.com/yajra/laravel-datatables/compare/v11.1.3...v11.1.4) - 2024-08-17 + +- fix: Ensure dates are not turned into arrays by the processor #3163 +- fix: ##3156 + ### [v11.1.3](https://github.com/yajra/laravel-datatables/compare/v11.1.2...v11.1.3) - 2024-07-15 - fix: make query for filteredRecords when totalRecords was manually set #3157 From f3cb49738bed8fc4680d71273cb52a71d8f6776a Mon Sep 17 00:00:00 2001 From: JurianArie <28654085+JurianArie@users.noreply.github.com> Date: Thu, 22 Aug 2024 18:24:22 +0200 Subject: [PATCH 049/140] Add skip total records back --- src/DataTableAbstract.php | 8 ++++++-- src/QueryDataTable.php | 21 +-------------------- tests/Integration/QueryDataTableTest.php | 24 +++++++++++++++++++++++- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/DataTableAbstract.php b/src/DataTableAbstract.php index d0c3ba21..c4667d05 100644 --- a/src/DataTableAbstract.php +++ b/src/DataTableAbstract.php @@ -68,6 +68,11 @@ abstract class DataTableAbstract implements DataTable */ protected ?int $filteredRecords = null; + /** + * Flag to check if the total records count should be skipped. + */ + protected bool $skipTotalRecords = false; + /** * Auto-filter flag. */ @@ -533,12 +538,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; } diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index aea42844..7856a03f 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -25,11 +25,6 @@ class QueryDataTable extends DataTableAbstract */ protected bool $prepared = false; - /** - * Flag to check if the total records count query has been performed. - */ - protected bool $performedTotalRecordsCount = false; - /** * Query callback for custom pagination using limit without offset. * @@ -162,20 +157,6 @@ public function prepareQuery(): static return $this; } - /** - * Count total items. - */ - public function totalCount(): int - { - if ($this->totalRecords !== null) { - return $this->totalRecords; - } - - $this->performedTotalRecordsCount = true; - - return $this->totalRecords = $this->count(); - } - /** * Counts current query. */ @@ -272,7 +253,7 @@ protected function filterRecords(): void // If no modification between the original query and the filtered one has been made // the filteredRecords equals the totalRecords - if ($this->query == $initialQuery && $this->performedTotalRecordsCount) { + if (! $this->skipTotalRecords && $this->query == $initialQuery) { $this->filteredRecords ??= $this->totalRecords; } else { $this->filteredCount(); diff --git a/tests/Integration/QueryDataTableTest.php b/tests/Integration/QueryDataTableTest.php index d2b4a028..b5afba81 100644 --- a/tests/Integration/QueryDataTableTest.php +++ b/tests/Integration/QueryDataTableTest.php @@ -26,7 +26,7 @@ public function it_can_set_total_records() $crawler->assertJson([ 'draw' => 0, 'recordsTotal' => 10, - 'recordsFiltered' => 20, + 'recordsFiltered' => 10, ]); } @@ -34,11 +34,29 @@ public function it_can_set_total_records() public function it_can_set_zero_total_records() { $crawler = $this->call('GET', '/zero-total-records'); + $crawler->assertJson([ + 'draw' => 0, + 'recordsTotal' => 0, + 'recordsFiltered' => 0, + ]); + } + + #[Test] + public function it_can_set_skip_total_records() + { + DB::enableQueryLog(); + + $crawler = $this->call('GET', '/skip-total-records'); $crawler->assertJson([ 'draw' => 0, 'recordsTotal' => 0, 'recordsFiltered' => 20, ]); + + DB::disableQueryLog(); + $queryLog = DB::getQueryLog(); + + $this->assertCount(2, $queryLog); } #[Test] @@ -463,6 +481,10 @@ protected function setUp(): void ->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()); From 5a56e317a7eb485cd0cdba883d1613a08e0b6c7d Mon Sep 17 00:00:00 2001 From: JurianArie <28654085+JurianArie@users.noreply.github.com> Date: Sat, 24 Aug 2024 10:04:41 +0200 Subject: [PATCH 050/140] Set total records equal to filtered records when skipping total records --- src/QueryDataTable.php | 4 ++ tests/Integration/QueryDataTableTest.php | 49 +++++++++++++----------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 7856a03f..946f1ccf 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -257,6 +257,10 @@ protected function filterRecords(): void $this->filteredRecords ??= $this->totalRecords; } else { $this->filteredCount(); + + if ($this->skipTotalRecords) { + $this->totalRecords = $this->filteredRecords; + } } } diff --git a/tests/Integration/QueryDataTableTest.php b/tests/Integration/QueryDataTableTest.php index b5afba81..24aeb8f2 100644 --- a/tests/Integration/QueryDataTableTest.php +++ b/tests/Integration/QueryDataTableTest.php @@ -41,24 +41,6 @@ public function it_can_set_zero_total_records() ]); } - #[Test] - public function it_can_set_skip_total_records() - { - DB::enableQueryLog(); - - $crawler = $this->call('GET', '/skip-total-records'); - $crawler->assertJson([ - 'draw' => 0, - 'recordsTotal' => 0, - 'recordsFiltered' => 20, - ]); - - DB::disableQueryLog(); - $queryLog = DB::getQueryLog(); - - $this->assertCount(2, $queryLog); - } - #[Test] public function it_can_set_total_filtered_records() { @@ -109,7 +91,27 @@ public function it_can_perform_global_search() #[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'], @@ -119,9 +121,14 @@ 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] @@ -411,8 +418,6 @@ protected function setUp(): void ->formatColumn('created_at', new DateFormatter('Y-m-d')) ->toJson()); - $router->get('/query/simple', fn (DataTables $dataTable) => $dataTable->query(DB::table('users'))->skipTotalRecords()->toJson()); - $router->get('/query/addColumn', fn (DataTables $dataTable) => $dataTable->query(DB::table('users')) ->addColumn('foo', 'bar') ->toJson()); From 158f2e9cf76d500c707a0ebd6cd2079cd87b8d4a Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Mon, 26 Aug 2024 09:43:52 +0800 Subject: [PATCH 051/140] chore: release v11.1.5 :rocket: --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8c76a34..5463bb4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ ### [Unreleased] +### [v11.1.5](https://github.com/yajra/laravel-datatables/compare/v11.1.4...v11.1.5) - 2024-09-26 + +- Add skip total records back #3170 +- Alternative to #3169. +- Partially reverts #3157. + ### [v11.1.4](https://github.com/yajra/laravel-datatables/compare/v11.1.3...v11.1.4) - 2024-08-17 - fix: Ensure dates are not turned into arrays by the processor #3163 From 6185f960499983341183b846cb0ebbfd1cfb82b6 Mon Sep 17 00:00:00 2001 From: Jonathan Goode Date: Tue, 3 Sep 2024 16:19:06 +0100 Subject: [PATCH 052/140] Typos --- src/config/datatables.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config/datatables.php b/src/config/datatables.php index 08912643..da923af0 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, @@ -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' => '*', From c5a7147a1a235e5cf98f988c27338dc4a0baad7d Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Mon, 14 Oct 2024 21:24:48 +0800 Subject: [PATCH 053/140] docs: fix downloads badge (#3180) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 760b6873..28edd4bf 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![Continuous Integration](https://github.com/yajra/laravel-datatables/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/yajra/laravel-datatables/actions/workflows/continuous-integration.yml) [![Static Analysis](https://github.com/yajra/laravel-datatables/actions/workflows/static-analysis.yml/badge.svg)](https://github.com/yajra/laravel-datatables/actions/workflows/static-analysis.yml) -[![Total Downloads](https://poser.pugx.org/yajra/laravel-datatables-oracle/downloads.png)](https://packagist.org/packages/yajra/laravel-datatables-oracle) +[![Total Downloads](https://poser.pugx.org/yajra/laravel-datatables-oracle/d/total.svg)](https://packagist.org/packages/yajra/laravel-datatables-oracle) [![License](https://img.shields.io/github/license/mashape/apistatus.svg)](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. From ba27954cae9e00bd61d69eb70213ff5a3f0b1ed5 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Mon, 14 Oct 2024 21:39:37 +0800 Subject: [PATCH 054/140] docs: fix sponsors (#3182) --- README.md | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 28edd4bf..98b59be6 100644 --- a/README.md +++ b/README.md @@ -28,19 +28,31 @@ return DataTables::make(User::all())->toJson(); ## Sponsors - - DataTables - - - - JetBrains.com - - - - Blackfire.io - - - + + + + + + +
A big thank you to DataTables for supporting this project with a free DataTables Editor license.
+ + + + + + + + +
JetBrains LogoA big thank you to JetBrains for supporting this project with free open-source licenses of their IDEs.
+ + + + + + + + +
Blackfire.io LogoA big thank you to Blackfire.io for supporting this project with a free open-source license.
## Requirements - [PHP >= 8.2](http://php.net/) From 2624eed6936e1804419a8598487924e4bcfbb09c Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Mon, 14 Oct 2024 21:46:19 +0800 Subject: [PATCH 055/140] docs: add datatables logo (#3183) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 98b59be6..ff9ef62e 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ return DataTables::make(User::all())->toJson(); + From 36a54e91c20016452a992855d9119c923a7fbed0 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Wed, 16 Oct 2024 09:19:28 +0800 Subject: [PATCH 056/140] docs: quick installation --- README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ff9ef62e..fd75c131 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ return DataTables::make(User::all())->toJson(); ## Requirements - [PHP >= 8.2](http://php.net/) - [Laravel Framework](https://github.com/laravel/framework) -- [jQuery DataTables](http://datatables.net/) +- [DataTables](http://datatables.net/) ## Documentations @@ -89,13 +89,21 @@ return DataTables::make(User::all())->toJson(); ## Quick Installation +### Option 1: Install all DataTables libraries + +```bash +composer require yajra/laravel-datatables:"^11" +``` + +### Option 2: Install only this library + ```bash composer require yajra/laravel-datatables-oracle:"^11" ``` #### 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' => [ ..., @@ -120,11 +128,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. @@ -135,7 +144,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 From eb814992b2e7bccb7e0be16c937a4aa6102b5de2 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 21 Jan 2025 10:41:17 +0800 Subject: [PATCH 057/140] ci: add 8.4 --- .github/workflows/continuous-integration.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 8ff98cc4..5cb492da 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -14,14 +14,14 @@ jobs: strategy: fail-fast: true matrix: - php: [8.2, 8.3] + php: [8.2, 8.3, 8.4] stability: [prefer-stable] name: PHP ${{ matrix.php }} - ${{ matrix.stability }} steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 From 128c18ff4df926eb146c41f37bfb1609366eb282 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 21 Jan 2025 10:48:15 +0800 Subject: [PATCH 058/140] ci: pint workflow --- .github/workflows/pint.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pint.yml b/.github/workflows/pint.yml index 2151cafa..fbde1d8c 100644 --- a/.github/workflows/pint.yml +++ b/.github/workflows/pint.yml @@ -7,13 +7,21 @@ on: jobs: phplint: runs-on: ubuntu-latest + + permissions: + contents: write + steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + - name: "laravel-pint" - uses: aglipanci/laravel-pint-action@2.0.0 + uses: aglipanci/laravel-pint-action@latest with: preset: laravel verboseMode: true + - uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: "fix: pint" From 7743e320841800f8bb947aa23a2028bd9683726a Mon Sep 17 00:00:00 2001 From: yajra <2687997+yajra@users.noreply.github.com> Date: Tue, 21 Jan 2025 02:49:56 +0000 Subject: [PATCH 059/140] fix: pint --- src/config/datatables.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config/datatables.php b/src/config/datatables.php index 08912643..beef019f 100644 --- a/src/config/datatables.php +++ b/src/config/datatables.php @@ -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', ], /* From 4a693e7ce69df7c298b5d0a48a0eec2d5a64458d Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 21 Jan 2025 10:50:14 +0800 Subject: [PATCH 060/140] ci: remove styleci --- .styleci.yml | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .styleci.yml 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 From 4ff24a9720ebf3e61ca025fab2d435d8558e872d Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 21 Jan 2025 10:52:43 +0800 Subject: [PATCH 061/140] chore: export ignore --- .gitattributes | 3 ++- sonar-project.properties | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) delete mode 100644 sonar-project.properties 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/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/ From 6bc97f28b587e1a7c465974030447449290e71d4 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 21 Jan 2025 11:02:15 +0800 Subject: [PATCH 062/140] ci: 8.4 static analysis --- .github/workflows/static-analysis.yml | 4 ++-- phpstan.neon.dist | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 8e9365da..bdf913b3 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -29,12 +29,12 @@ jobs: strategy: fail-fast: true matrix: - php: [8.2, 8.3] + php: [8.2, 8.3, 8.4] stability: [prefer-stable] steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 9971e78a..6e0471da 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -10,8 +10,7 @@ parameters: ignoreErrors: - '#Unsafe usage of new static\(\).#' + - identifier: missingType.iterableValue excludePaths: - src/helper.php - - checkMissingIterableValueType: false From a17f045500d0fec4253d29a652f84990f629317f Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 21 Jan 2025 11:09:04 +0800 Subject: [PATCH 063/140] fix: static analysis Error: Method Yajra\DataTables\QueryDataTable::results() should return Illuminate\Support\Collection but returns Illuminate\Support\Collection. --- src/Contracts/DataTable.php | 2 +- src/QueryDataTable.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Contracts/DataTable.php b/src/Contracts/DataTable.php index 0e5ac7b5..d1320b4c 100644 --- a/src/Contracts/DataTable.php +++ b/src/Contracts/DataTable.php @@ -10,7 +10,7 @@ interface DataTable /** * Get results. * - * @return \Illuminate\Support\Collection + * @return \Illuminate\Support\Collection|\Illuminate\Support\Collection */ public function results(): Collection; diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 946f1ccf..f6610dad 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -130,7 +130,7 @@ public function make(bool $mDataSupport = true): JsonResponse /** * Get paginated results. * - * @return \Illuminate\Support\Collection + * @return \Illuminate\Support\Collection */ public function results(): Collection { From b48eb614d0474c23a9c8041563beef9dda39656d Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 21 Jan 2025 11:15:46 +0800 Subject: [PATCH 064/140] chore: release v11.1.6 :rocket: --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5463bb4d..4e564b6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ ### [Unreleased] +### [v11.1.6](https://github.com/yajra/laravel-datatables/compare/v11.1.5...v11.1.6) - 2025-01-21 + +- fix: static analysis #3213 +- ci: update workflow #3213 + ### [v11.1.5](https://github.com/yajra/laravel-datatables/compare/v11.1.4...v11.1.5) - 2024-09-26 - Add skip total records back #3170 From c8af15ed4fd21385229b40a4dafd84083e07d9a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alfaiate?= Date: Fri, 21 Feb 2025 15:50:17 +0700 Subject: [PATCH 065/140] fix: prevent duplicate table name errors --- src/EloquentDataTable.php | 48 ++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/EloquentDataTable.php b/src/EloquentDataTable.php index 8a6f154e..edcb9294 100644 --- a/src/EloquentDataTable.php +++ b/src/EloquentDataTable.php @@ -164,7 +164,7 @@ protected function resolveRelationColumn(string $column): string { $parts = explode('.', $column); $columnName = array_pop($parts); - $relation = implode('.', $parts); + $relation = str_replace('[]', '', implode('.', $parts)); if ($this->isNotEagerLoaded($relation)) { return $column; @@ -184,54 +184,56 @@ protected function resolveRelationColumn(string $column): string */ protected function joinEagerLoadedColumn($relation, $relationColumn) { - $table = ''; + $tableAlias = ''; $lastQuery = $this->query; foreach (explode('.', $relation) as $eachRelation) { $model = $lastQuery->getRelation($eachRelation); + $lastAlias = $tableAlias ?: $lastQuery->getModel()->getTable(); + $tableAlias = $tableAlias.'_'.$eachRelation; + $pivotAlias = $tableAlias.'_pivot'; switch (true) { case $model instanceof BelongsToMany: - $pivot = $model->getTable(); - $pivotPK = $model->getExistenceCompareKey(); - $pivotFK = $model->getQualifiedParentKeyName(); + $pivot = $model->getTable().' as '.$pivotAlias; + $pivotPK = $pivotAlias.'.'.$model->getForeignPivotKeyName(); + $pivotFK = $lastAlias.'.'.$model->getParentKeyName(); $this->performJoin($pivot, $pivotPK, $pivotFK); $related = $model->getRelated(); - $table = $related->getTable(); + $table = $related->getTable().' as '.$tableAlias; $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(); + $pivot = explode('.', $model->getQualifiedParentKeyName())[0].' as '.$pivotAlias; // extract pivot table from key + $pivotPK = $pivotAlias.'.'.$model->getFirstKeyName(); + $pivotFK = $lastAlias.'.'.$model->getLocalKeyName(); $this->performJoin($pivot, $pivotPK, $pivotFK); $related = $model->getRelated(); - $table = $related->getTable(); + $table = $related->getTable().' as '.$tableAlias; $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(); + $table = $model->getRelated()->getTable().' as '.$tableAlias; + $foreign = $tableAlias.'.'.$model->getForeignKeyName(); + $other = $lastAlias.'.'.$model->getLocalKeyName(); break; case $model instanceof BelongsTo: - $table = $model->getRelated()->getTable(); - $foreign = $model->getQualifiedForeignKeyName(); - $other = $model->getQualifiedOwnerKeyName(); + $table = $model->getRelated()->getTable().' as '.$tableAlias; + $foreign = $lastAlias.'.'.$model->getForeignKeyName(); + $other = $tableAlias.'.'.$model->getOwnerKeyName(); break; default: @@ -241,7 +243,7 @@ protected function joinEagerLoadedColumn($relation, $relationColumn) $lastQuery = $model->getQuery(); } - return $table.'.'.$relationColumn; + return $tableAlias.'.'.$relationColumn; } /** From 1b026ba1f190383d8863df88572e3fac5fbd95df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alfaiate?= Date: Mon, 24 Feb 2025 09:23:29 +0700 Subject: [PATCH 066/140] fix: applyOrderColumn with column alias --- src/QueryDataTable.php | 8 +++----- tests/Integration/CustomOrderTest.php | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index f6610dad..b8c359ab 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -662,8 +662,6 @@ protected function defaultOrdering(): void $column = $this->resolveRelationColumn($orderable['name']); if ($this->hasOrderColumn($orderable['name'])) { - $this->applyOrderColumn($orderable['name'], $orderable); - } elseif ($this->hasOrderColumn($column)) { $this->applyOrderColumn($column, $orderable); } else { $nullsLastSql = $this->getNullsLastSql($column, $orderable['direction']); @@ -687,16 +685,16 @@ protected function hasOrderColumn(string $column): bool */ protected function applyOrderColumn(string $column, 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'], $column); } else { $sql = str_replace('$1', $orderable['direction'], (string) $sql); - $bindings = $this->columnDef['order'][$column]['bindings']; + $bindings = $this->columnDef['order'][$orderable['name']]['bindings']; $this->query->orderByRaw($sql, $bindings); } } diff --git a/tests/Integration/CustomOrderTest.php b/tests/Integration/CustomOrderTest.php index d4664405..d1915d98 100644 --- a/tests/Integration/CustomOrderTest.php +++ b/tests/Integration/CustomOrderTest.php @@ -54,8 +54,8 @@ protected function setUp(): void parent::setUp(); $this->app['router']->get('/relations/belongsTo', fn (DataTables $datatables) => $datatables->eloquent(Post::with('user')->select('posts.*')) - ->orderColumn('user.id', function ($query, $order) { - $query->orderBy('users.id', $order == 'desc' ? 'asc' : 'desc'); + ->orderColumn('user.id', function ($query, $order, $column) { + $query->orderBy($column, $order == 'desc' ? 'asc' : 'desc'); }) ->toJson()); } From 86e0c92b4bf6691287335575a2d91abf86c93364 Mon Sep 17 00:00:00 2001 From: Greg <473258+swooningfish@users.noreply.github.com> Date: Wed, 26 Feb 2025 02:08:54 +0000 Subject: [PATCH 067/140] feat: Laravel v12 Compatibility (#3217) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Laravel v12 Compatibility * Update README.md * Pull request amends - Removed ^11 references for all the packages to have the same Laravel version constraint. + Added back in illuminate/view and set the version constraint to ^12 * Composer commands updated to ref ^12 * Readme.md badge updated * Update README.md Co-authored-by: Sébastien Alfaiate * orchestra/testbench bumped to v10 * larastan/larastan bumped to v3.1.0 * rector/rector bumped to v2.0 Due to phpstan/phpstan --------- Co-authored-by: Sébastien Alfaiate --- README.md | 7 ++++--- composer.json | 16 ++++++++-------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index fd75c131..89bccea1 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://www.paypal.me/yajra) [![Donate](https://img.shields.io/badge/donate-patreon-blue.svg)](https://www.patreon.com/bePatron?u=4521203) -[![Laravel 4.2|5.x|6|7|8|9|10|11](https://img.shields.io/badge/Laravel-4.2|5.x|6|7|8|9|10|11-orange.svg)](http://laravel.com) +[![Laravel 4.2|5.x|6|7|8|9|10|11|12](https://img.shields.io/badge/Laravel-4.2|5.x|6|7|8|9|10|11|12-orange.svg)](http://laravel.com) [![Latest Stable Version](https://img.shields.io/packagist/v/yajra/laravel-datatables-oracle.svg)](https://packagist.org/packages/yajra/laravel-datatables-oracle) [![Continuous Integration](https://github.com/yajra/laravel-datatables/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/yajra/laravel-datatables/actions/workflows/continuous-integration.yml) [![Static Analysis](https://github.com/yajra/laravel-datatables/actions/workflows/static-analysis.yml/badge.svg)](https://github.com/yajra/laravel-datatables/actions/workflows/static-analysis.yml) @@ -86,19 +86,20 @@ return DataTables::make(User::all())->toJson(); | 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:"^11" +composer require yajra/laravel-datatables:"^12" ``` ### Option 2: Install only this library ```bash -composer require yajra/laravel-datatables-oracle:"^11" +composer require yajra/laravel-datatables-oracle:"^12" ``` #### Service Provider & Facade (Optional on Laravel 5.5+) diff --git a/composer.json b/composer.json index ac6d1273..ed2cadf7 100644 --- a/composer.json +++ b/composer.json @@ -16,20 +16,20 @@ ], "require": { "php": "^8.2", - "illuminate/database": "^11", - "illuminate/filesystem": "^11", - "illuminate/http": "^11", - "illuminate/support": "^11", - "illuminate/view": "^11" + "illuminate/database": "^12", + "illuminate/filesystem": "^12", + "illuminate/http": "^12", + "illuminate/support": "^12", + "illuminate/view": "^12" }, "require-dev": { "algolia/algoliasearch-client-php": "^3.4.1", - "larastan/larastan": "^2.9.1", + "larastan/larastan": "^3.1.0", "laravel/pint": "^1.14", "laravel/scout": "^10.8.3", "meilisearch/meilisearch-php": "^1.6.1", - "orchestra/testbench": "^9", - "rector/rector": "^1.0" + "orchestra/testbench": "^10", + "rector/rector": "^2.0" }, "suggest": { "yajra/laravel-datatables-export": "Plugin for server-side exporting using livewire and queue worker.", From 3b9634545fb91c2b3402cb1aebfeef1e723b50f8 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Wed, 26 Feb 2025 10:12:50 +0800 Subject: [PATCH 068/140] chore: bump dev-master alias to 12.x-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ed2cadf7..6e034353 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,7 @@ }, "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" }, "laravel": { "providers": [ From b62689f719674e36c50048a6c8dfc2e95fca7ace Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Wed, 26 Feb 2025 10:45:17 +0800 Subject: [PATCH 069/140] fix: static analysis --- phpstan.neon.dist | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 6e0471da..26779953 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -10,7 +10,21 @@ parameters: ignoreErrors: - '#Unsafe usage of new static\(\).#' - - identifier: missingType.iterableValue + - 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 + + noEnvCallsOutsideOfConfig: false + treatPhpDocTypesAsCertain: false From 6861198d17c76a351fbeb1a091cd2982a115f57b Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Wed, 26 Feb 2025 16:32:24 +0800 Subject: [PATCH 070/140] ci: workflows --- .github/workflows/continuous-integration.yml | 3 +++ .github/workflows/pint.yml | 1 + 2 files changed, 4 insertions(+) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 5cb492da..a521b251 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -2,6 +2,9 @@ name: "Continuous Integration" on: push: + branches: + - master + - 11.x pull_request: schedule: - cron: '0 0 * * *' diff --git a/.github/workflows/pint.yml b/.github/workflows/pint.yml index fbde1d8c..bac3eabb 100644 --- a/.github/workflows/pint.yml +++ b/.github/workflows/pint.yml @@ -10,6 +10,7 @@ jobs: permissions: contents: write + pull-requests: write steps: - uses: actions/checkout@v4 From 4eec683a71b77ae2aa3c0976a90db07205ddfbc7 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Wed, 26 Feb 2025 16:49:57 +0800 Subject: [PATCH 071/140] ci: lint 11.x branch --- .github/workflows/pint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pint.yml b/.github/workflows/pint.yml index bac3eabb..d32c486a 100644 --- a/.github/workflows/pint.yml +++ b/.github/workflows/pint.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - 11.x jobs: phplint: runs-on: ubuntu-latest From c5d7cd991ba98c72c2c370f10775f3e16eb74028 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Wed, 26 Feb 2025 21:52:47 +0800 Subject: [PATCH 072/140] docs: upgrade to 12 --- UPGRADE.md | 4 ++++ 1 file changed, 4 insertions(+) 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. From bd64f4d801cb73c06c3d44b05330508a904b3de1 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Wed, 26 Feb 2025 21:54:38 +0800 Subject: [PATCH 073/140] docs: release v12.0.0 :rocket: --- CHANGELOG.md | 41 ++++------------------------------------- 1 file changed, 4 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e564b6c..543f330a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,43 +4,10 @@ ### [Unreleased] -### [v11.1.6](https://github.com/yajra/laravel-datatables/compare/v11.1.5...v11.1.6) - 2025-01-21 +### v12.0.0 - 2025-02-26 -- fix: static analysis #3213 -- ci: update workflow #3213 +- feat: Laravel v12 Compatibility #3217 +- fix: prevent duplicate table name errors #3216 -### [v11.1.5](https://github.com/yajra/laravel-datatables/compare/v11.1.4...v11.1.5) - 2024-09-26 - -- Add skip total records back #3170 -- Alternative to #3169. -- Partially reverts #3157. - -### [v11.1.4](https://github.com/yajra/laravel-datatables/compare/v11.1.3...v11.1.4) - 2024-08-17 - -- fix: Ensure dates are not turned into arrays by the processor #3163 -- fix: ##3156 - -### [v11.1.3](https://github.com/yajra/laravel-datatables/compare/v11.1.2...v11.1.3) - 2024-07-15 - -- fix: make query for filteredRecords when totalRecords was manually set #3157 - -### [v11.1.2](https://github.com/yajra/laravel-datatables/compare/v11.1.1...v11.1.2) - 2024-07-03 - -- fix: ErrorException when direction is null #3154 - -### [v11.1.1](https://github.com/yajra/laravel-datatables/compare/v11.1.0...v11.1.1) - 2024-04-16 - -- fix: mariadb support for scout search #3146 - -### [v11.1.0](https://github.com/yajra/laravel-datatables/compare/v11.0.0...v11.1.0) - 2024-04-16 - -- feat: Optimize simple queries #3135 -- fix: #3133 - -### [v11.0.0](https://github.com/yajra/laravel-datatables/compare/v11.0.0...master) - 2024-03-14 - -- Laravel 11 support - - -[Unreleased]: https://github.com/yajra/laravel-datatables/compare/v11.0.0...master +[Unreleased]: https://github.com/yajra/laravel-datatables/compare/v12.0.0...master From 14d52def4a0d0cdd635d83152e42b9036d6b30a0 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sat, 1 Mar 2025 13:53:48 +0800 Subject: [PATCH 074/140] ci: simplify --- .github/workflows/continuous-integration.yml | 19 +++++------ .github/workflows/static-analysis.yml | 35 ++++++-------------- 2 files changed, 19 insertions(+), 35 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index a521b251..275347d7 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -1,36 +1,35 @@ -name: "Continuous Integration" +name: Continuous Integration on: push: branches: - master - - 11.x + - '*.x' pull_request: schedule: - cron: '0 0 * * *' jobs: - phpunit: + tests: runs-on: ubuntu-latest strategy: fail-fast: true matrix: - php: [8.2, 8.3, 8.4] - stability: [prefer-stable] + php: [ 8.2, 8.3, 8.4 ] + stability: [ prefer-stable ] - name: PHP ${{ matrix.php }} - ${{ matrix.stability }} + name: PHP ${{ matrix.php }} - STABILITY ${{ matrix.stability }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v2 - name: Setup PHP 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 @@ -42,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 + run: vendor/bin/pest diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index bdf913b3..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,15 +14,9 @@ on: jobs: static-analysis-phpstan: - name: "Static Analysis with PHPStan" + name: Source Code runs-on: ubuntu-latest - strategy: - fail-fast: true - matrix: - php: [8.2, 8.3, 8.4] - stability: [prefer-stable] - steps: - name: Checkout code uses: actions/checkout@v4 @@ -39,16 +24,16 @@ jobs: - 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 From 9f59dd199b903240da015394429fed63bd36f7d4 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sat, 1 Mar 2025 13:54:13 +0800 Subject: [PATCH 075/140] ci: pint :robot: --- .github/workflows/pint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pint.yml b/.github/workflows/pint.yml index d32c486a..147b6e3a 100644 --- a/.github/workflows/pint.yml +++ b/.github/workflows/pint.yml @@ -26,4 +26,4 @@ jobs: - uses: stefanzweifel/git-auto-commit-action@v5 with: - commit_message: "fix: pint" + commit_message: "fix: pint :robot:" From 2f801d3fddc11a89b9a5f90f6d866aeb0dc72bb8 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sat, 1 Mar 2025 13:55:16 +0800 Subject: [PATCH 076/140] ci: phpunit --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 275347d7..357de9ad 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -44,4 +44,4 @@ jobs: command: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress - name: Execute tests - run: vendor/bin/pest + run: vendor/bin/phpunit From 17503aa02e315814fb8ef1444d16031d85d1f8da Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sat, 1 Mar 2025 13:57:28 +0800 Subject: [PATCH 077/140] docs: Laravel 12 badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 89bccea1..8c935a37 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://www.paypal.me/yajra) [![Donate](https://img.shields.io/badge/donate-patreon-blue.svg)](https://www.patreon.com/bePatron?u=4521203) -[![Laravel 4.2|5.x|6|7|8|9|10|11|12](https://img.shields.io/badge/Laravel-4.2|5.x|6|7|8|9|10|11|12-orange.svg)](http://laravel.com) +[![Laravel 12](https://img.shields.io/badge/Laravel-12-orange.svg)](http://laravel.com) [![Latest Stable Version](https://img.shields.io/packagist/v/yajra/laravel-datatables-oracle.svg)](https://packagist.org/packages/yajra/laravel-datatables-oracle) [![Continuous Integration](https://github.com/yajra/laravel-datatables/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/yajra/laravel-datatables/actions/workflows/continuous-integration.yml) [![Static Analysis](https://github.com/yajra/laravel-datatables/actions/workflows/static-analysis.yml/badge.svg)](https://github.com/yajra/laravel-datatables/actions/workflows/static-analysis.yml) From e5fd4e1582efe292f776e93f045ffb8c7ca0f940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alfaiate?= Date: Thu, 3 Apr 2025 12:44:13 +0700 Subject: [PATCH 078/140] fix: misc improvements --- src/DataTableAbstract.php | 2 +- src/QueryDataTable.php | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/DataTableAbstract.php b/src/DataTableAbstract.php index c4667d05..b599721f 100644 --- a/src/DataTableAbstract.php +++ b/src/DataTableAbstract.php @@ -779,7 +779,7 @@ protected function searchPanesSearch(): void /** * Count filtered items. */ - protected function filteredCount(): int + public function filteredCount(): int { return $this->filteredRecords ??= $this->count(); } diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index b8c359ab..09d4fa46 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -80,6 +80,13 @@ class QueryDataTable extends DataTableAbstract */ protected bool $disableUserOrdering = false; + /** + * Paginated results. + * + * @var Collection + */ + protected Collection $results; + public function __construct(protected QueryBuilder $query) { $this->request = app('datatables.request'); @@ -130,11 +137,11 @@ public function make(bool $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(); } /** From f975cff7bd8956d435b5845ee37bb9af9314367f Mon Sep 17 00:00:00 2001 From: yajra <2687997+yajra@users.noreply.github.com> Date: Mon, 7 Apr 2025 14:55:42 +0000 Subject: [PATCH 079/140] fix: pint :robot: --- tests/Integration/QueryDataTableTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Integration/QueryDataTableTest.php b/tests/Integration/QueryDataTableTest.php index 24aeb8f2..55b4dda7 100644 --- a/tests/Integration/QueryDataTableTest.php +++ b/tests/Integration/QueryDataTableTest.php @@ -222,7 +222,7 @@ public function it_returns_only_the_selected_columns() } #[Test] - public function it_edit_only_the_selected_columns_after_using_editOnlySelectedColumns() + public function it_edit_only_the_selected_columns_after_using_edit_only_selected_columns() { $json = $this->call('GET', '/query/edit-columns', [ 'columns' => [ From 9e6b686f04e4fab051c064992e1cb6275a0e97e9 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Mon, 7 Apr 2025 22:57:21 +0800 Subject: [PATCH 080/140] chore: release v12.0.1 :rocket: --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 543f330a..93e64932 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ### [Unreleased] +### v12.0.1 - 2025-04-07 + +- fix: query results improvements #3224 + ### v12.0.0 - 2025-02-26 - feat: Laravel v12 Compatibility #3217 From 11c710775b098586510bf4840e0cf224f5bae51b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alfaiate?= Date: Tue, 22 Apr 2025 13:26:05 +0700 Subject: [PATCH 081/140] feat: add relation resolver param to filter callbacks --- src/EloquentDataTable.php | 3 +-- src/QueryDataTable.php | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/EloquentDataTable.php b/src/EloquentDataTable.php index edcb9294..36495c52 100644 --- a/src/EloquentDataTable.php +++ b/src/EloquentDataTable.php @@ -155,8 +155,7 @@ protected function isMorphRelation($relation) } /** - * Resolve the proper column name be used. - * + * {@inheritDoc} * * @throws \Yajra\DataTables\Exceptions\Exception */ diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 09d4fa46..9c1eefe9 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -350,7 +350,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); @@ -384,7 +384,7 @@ public function getQuery(): QueryBuilder } /** - * Resolve the proper column name be used. + * Resolve the proper column name to be used. */ protected function resolveRelationColumn(string $column): string { @@ -646,7 +646,7 @@ protected function searchPanesSearch(): void */ protected function resolveCallbackParameter(): array { - return [$this->query, $this->scoutSearched]; + return [$this->query, $this->scoutSearched, fn ($column) => $this->resolveRelationColumn($column)]; } /** From f7c04c922df21d304c26ee24c4cbb88afca7e0be Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Mon, 28 Apr 2025 16:23:39 +0800 Subject: [PATCH 082/140] chore: release v12.1.0 :rocket: --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93e64932..d2e4b4a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ### [Unreleased] +### v12.1.0 - 2025-04-28 + +- feat: add relation resolver param to filter callbacks #3229 + ### v12.0.1 - 2025-04-07 - fix: query results improvements #3224 @@ -14,4 +18,3 @@ - fix: prevent duplicate table name errors #3216 [Unreleased]: https://github.com/yajra/laravel-datatables/compare/v12.0.0...master - From c8609475178d0e9747daf574b1969065f51a5285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alfaiate?= Date: Mon, 28 Apr 2025 17:25:59 +0700 Subject: [PATCH 083/140] fix: prevent ambiguous column names --- src/EloquentDataTable.php | 12 ++++++------ src/QueryDataTable.php | 33 +++++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/EloquentDataTable.php b/src/EloquentDataTable.php index edcb9294..bb16943b 100644 --- a/src/EloquentDataTable.php +++ b/src/EloquentDataTable.php @@ -167,7 +167,7 @@ protected function resolveRelationColumn(string $column): string $relation = str_replace('[]', '', implode('.', $parts)); if ($this->isNotEagerLoaded($relation)) { - return $column; + return parent::resolveRelationColumn($column); } return $this->joinEagerLoadedColumn($relation, $columnName); @@ -188,14 +188,14 @@ protected function joinEagerLoadedColumn($relation, $relationColumn) $lastQuery = $this->query; foreach (explode('.', $relation) as $eachRelation) { $model = $lastQuery->getRelation($eachRelation); - $lastAlias = $tableAlias ?: $lastQuery->getModel()->getTable(); + $lastAlias = $tableAlias ?: $this->getTablePrefix($lastQuery); $tableAlias = $tableAlias.'_'.$eachRelation; $pivotAlias = $tableAlias.'_pivot'; switch (true) { case $model instanceof BelongsToMany: $pivot = $model->getTable().' as '.$pivotAlias; $pivotPK = $pivotAlias.'.'.$model->getForeignPivotKeyName(); - $pivotFK = $lastAlias.'.'.$model->getParentKeyName(); + $pivotFK = ltrim($lastAlias.'.'.$model->getParentKeyName(), '.'); $this->performJoin($pivot, $pivotPK, $pivotFK); $related = $model->getRelated(); @@ -211,7 +211,7 @@ protected function joinEagerLoadedColumn($relation, $relationColumn) case $model instanceof HasOneThrough: $pivot = explode('.', $model->getQualifiedParentKeyName())[0].' as '.$pivotAlias; // extract pivot table from key $pivotPK = $pivotAlias.'.'.$model->getFirstKeyName(); - $pivotFK = $lastAlias.'.'.$model->getLocalKeyName(); + $pivotFK = ltrim($lastAlias.'.'.$model->getLocalKeyName(), '.'); $this->performJoin($pivot, $pivotPK, $pivotFK); $related = $model->getRelated(); @@ -227,12 +227,12 @@ protected function joinEagerLoadedColumn($relation, $relationColumn) case $model instanceof HasOneOrMany: $table = $model->getRelated()->getTable().' as '.$tableAlias; $foreign = $tableAlias.'.'.$model->getForeignKeyName(); - $other = $lastAlias.'.'.$model->getLocalKeyName(); + $other = ltrim($lastAlias.'.'.$model->getLocalKeyName(), '.'); break; case $model instanceof BelongsTo: $table = $model->getRelated()->getTable().' as '.$tableAlias; - $foreign = $lastAlias.'.'.$model->getForeignKeyName(); + $foreign = ltrim($lastAlias.'.'.$model->getForeignKeyName(), '.'); $other = $tableAlias.'.'.$model->getOwnerKeyName(); break; diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 09d4fa46..b1d0d681 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -388,7 +388,7 @@ public function getQuery(): QueryBuilder */ protected function resolveRelationColumn(string $column): string { - return $column; + return $this->addTablePrefix($this->query, $column); } /** @@ -451,7 +451,7 @@ protected function castColumn(string $column): string */ 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 ?'; @@ -471,19 +471,32 @@ protected function compileQuerySearch($query, string $column, string $keyword, s protected function addTablePrefix($query, string $column): string { if (! str_contains($column, '.')) { - $q = $this->getBaseQueryBuilder($query); - $from = $q->from ?? ''; + return ltrim($this->getTablePrefix($query).'.'.$column, '.'); + } - if (! $from instanceof Expression) { - if (str_contains((string) $from, ' as ')) { - $from = explode(' as ', (string) $from)[1]; - } + return $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; } /** From 562552b06c3e0af2594adb1300985aea867c7d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alfaiate?= Date: Tue, 29 Apr 2025 09:24:49 +0700 Subject: [PATCH 084/140] fix: skip prefix on alias columns --- src/QueryDataTable.php | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index b1d0d681..f70125b8 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -470,11 +470,23 @@ protected function compileQuerySearch($query, string $column, string $keyword, s */ protected function addTablePrefix($query, string $column): string { - if (! str_contains($column, '.')) { - return ltrim($this->getTablePrefix($query).'.'.$column, '.'); + // Column is already prefixed + if (str_contains($column, '.')) { + return $column; } - return $column; + $q = $this->getBaseQueryBuilder($query); + + // Column is an alias, no prefix required + foreach ($q->columns ?? [] as $select) { + $sql = trim($select instanceof Expression ? $select->getValue($this->getConnection()->getQueryGrammar()) : $select); + if (str_ends_with($sql, ' as '.$column) || str_ends_with($sql, ' as '.$this->wrap($column))) { + return $column; + } + } + + // Add table prefix to column + return $this->getTablePrefix($query).'.'.$column; } /** From 8f5c8b1307cf5b27f170d78beddbc2b664b45f31 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Mon, 5 May 2025 18:44:53 +0800 Subject: [PATCH 085/140] chore: release v12.1.1 :rocket: --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2e4b4a9..e1256b0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ### [Unreleased] +### v12.1.1 - 2025-05-05 + +- fix: prevent ambiguous column names #3227 + ### v12.1.0 - 2025-04-28 - feat: add relation resolver param to filter callbacks #3229 From f944a8be8ceb0f7e1510d12bd5a322a8aa8e3abd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alfaiate?= Date: Mon, 5 May 2025 18:11:13 +0700 Subject: [PATCH 086/140] feat: add relation resolver param to order callback --- src/QueryDataTable.php | 9 ++++----- tests/Integration/CustomOrderTest.php | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 3270ef5a..6a9144fe 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -691,11 +691,10 @@ protected function defaultOrdering(): void }) ->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($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; @@ -715,7 +714,7 @@ protected function hasOrderColumn(string $column): bool /** * Apply orderColumn custom query. */ - protected function applyOrderColumn(string $column, array $orderable): void + protected function applyOrderColumn(array $orderable): void { $sql = $this->columnDef['order'][$orderable['name']]['sql']; if ($sql === false) { @@ -723,7 +722,7 @@ protected function applyOrderColumn(string $column, array $orderable): void } if (is_callable($sql)) { - call_user_func($sql, $this->query, $orderable['direction'], $column); + call_user_func($sql, $this->query, $orderable['direction'], fn ($column) => $this->resolveRelationColumn($column)); } else { $sql = str_replace('$1', $orderable['direction'], (string) $sql); $bindings = $this->columnDef['order'][$orderable['name']]['bindings']; diff --git a/tests/Integration/CustomOrderTest.php b/tests/Integration/CustomOrderTest.php index d1915d98..f7281f3e 100644 --- a/tests/Integration/CustomOrderTest.php +++ b/tests/Integration/CustomOrderTest.php @@ -54,8 +54,8 @@ protected function setUp(): void parent::setUp(); $this->app['router']->get('/relations/belongsTo', fn (DataTables $datatables) => $datatables->eloquent(Post::with('user')->select('posts.*')) - ->orderColumn('user.id', function ($query, $order, $column) { - $query->orderBy($column, $order == 'desc' ? 'asc' : 'desc'); + ->orderColumn('user.id', function ($query, $order, $resolver) { + $query->orderBy($resolver('user.id'), $order == 'desc' ? 'asc' : 'desc'); }) ->toJson()); } From 6c0580356b4854b0fecff119cc9b68b1daa1b195 Mon Sep 17 00:00:00 2001 From: Emmanuel Joseph Beron Date: Wed, 7 May 2025 13:33:12 +0800 Subject: [PATCH 087/140] fix: prevent prefixing null/empty string --- src/QueryDataTable.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 3270ef5a..c43ec484 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -485,8 +485,12 @@ protected function addTablePrefix($query, string $column): string } } + if (! ($prefix = $this->getTablePrefix($query))) { + return $column; + } + // Add table prefix to column - return $this->getTablePrefix($query).'.'.$column; + return $prefix.'.'.$column; } /** From ad151a50a4d7ce7278ffb56677c0f482c55107d9 Mon Sep 17 00:00:00 2001 From: Emmanuel Joseph Beron Date: Wed, 7 May 2025 14:00:46 +0800 Subject: [PATCH 088/140] refactor: adhere to sonar cloud codeQL --- src/QueryDataTable.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index c43ec484..ac33e2ec 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -485,12 +485,10 @@ protected function addTablePrefix($query, string $column): string } } - if (! ($prefix = $this->getTablePrefix($query))) { - return $column; - } - - // Add table prefix to column - return $prefix.'.'.$column; + return collect([ + $this->getTablePrefix($query), + $column, + ])->filter()->implode('.'); } /** From 284e3798ea1dedee806bee9f48223394ebd0fc3a Mon Sep 17 00:00:00 2001 From: Emmanuel Joseph Beron Date: Wed, 7 May 2025 14:12:10 +0800 Subject: [PATCH 089/140] refactor: simplify expression --- src/QueryDataTable.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index ac33e2ec..dab55ff2 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -485,10 +485,9 @@ protected function addTablePrefix($query, string $column): string } } - return collect([ - $this->getTablePrefix($query), - $column, - ])->filter()->implode('.'); + $prefix = $this->getTablePrefix($query); + + return $prefix ? $prefix.$column : $column; } /** From 73b6eebc437ddb8f3cdac23113185cfadc2dec3a Mon Sep 17 00:00:00 2001 From: Emmanuel Joseph Beron Date: Wed, 7 May 2025 14:13:29 +0800 Subject: [PATCH 090/140] fix: concat --- src/QueryDataTable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index dab55ff2..e1b5ccb6 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -487,7 +487,7 @@ protected function addTablePrefix($query, string $column): string $prefix = $this->getTablePrefix($query); - return $prefix ? $prefix.$column : $column; + return $prefix ? $prefix.'.'.$column : $column; } /** From f305033bb4378ff723ad09d9ccb802dca3d42a60 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Wed, 7 May 2025 14:16:03 +0800 Subject: [PATCH 091/140] chore: release v12.1.2 :rocket: --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1256b0c..4445b940 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ### [Unreleased] +### v12.1.2 - 2025-05-07 + +- fix: prevent prefixing null/empty string #3233 + ### v12.1.1 - 2025-05-05 - fix: prevent ambiguous column names #3227 From de14b63f696e3b5f80224924cbc199605e85f9d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alfaiate?= Date: Thu, 8 May 2025 09:49:51 +0700 Subject: [PATCH 092/140] fix: improve column alias detection --- src/QueryDataTable.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index e1b5ccb6..fbe5f521 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -480,7 +480,8 @@ protected function addTablePrefix($query, string $column): string // Column is an alias, no prefix required foreach ($q->columns ?? [] as $select) { $sql = trim($select instanceof Expression ? $select->getValue($this->getConnection()->getQueryGrammar()) : $select); - if (str_ends_with($sql, ' as '.$column) || str_ends_with($sql, ' as '.$this->wrap($column))) { + $match = preg_quote($column).'\b|'.preg_quote($this->wrap($column)); + if (preg_match("/(\s)as(\s+)($match)/i", $sql)) { return $column; } } From 95a81bfb0870af050055938e8098f8138055732d Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Thu, 8 May 2025 15:12:58 +0800 Subject: [PATCH 093/140] chore: release v12.2.0 :rocket: --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4445b940..25c92549 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ ### [Unreleased] +### v12.2.0 - 2025-05-08 + +- feat: add relation resolver param to order callback #3232 +- fix: improve column alias detection #3236 +- fix: #3235 + ### v12.1.2 - 2025-05-07 - fix: prevent prefixing null/empty string #3233 From 8dc6a0f0d0a1903ee228f6cde621eccebdf634cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alfaiate?= Date: Fri, 9 May 2025 09:24:33 +0700 Subject: [PATCH 094/140] fix: improve prefix detection --- src/QueryDataTable.php | 69 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index dd09aabe..a0383215 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -475,20 +475,26 @@ protected function addTablePrefix($query, string $column): string return $column; } - $q = $this->getBaseQueryBuilder($query); + // Extract selected columns from the query + $selects = $this->getSelectedColumns($query); - // Column is an alias, no prefix required - foreach ($q->columns ?? [] as $select) { - $sql = trim($select instanceof Expression ? $select->getValue($this->getConnection()->getQueryGrammar()) : $select); - $match = preg_quote($column).'\b|'.preg_quote($this->wrap($column)); - if (preg_match("/(\s)as(\s+)($match)/i", $sql)) { - return $column; - } + // We have a match + if (isset($selects['columns'][$column])) { + return $selects['columns'][$column]; } - $prefix = $this->getTablePrefix($query); + // Multiple wildcards => Unable to determine prefix + if (in_array('*', $selects['wildcards']) || count(array_unique($selects['wildcards'])) > 1) { + return $column; + } - return $prefix ? $prefix.'.'.$column : $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, '.'); } /** @@ -513,6 +519,49 @@ protected function getTablePrefix($query): ?string return null; } + /** + * Get declared column names from the query. + * + * @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()) : $select); + // Remove expressions + $sql = preg_replace('/\s*\w*\((?:[^()]*|(?R))*\)/', '_', $sql); + // Remove multiple spaces + $sql = preg_replace('/\s+/', ' ', $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. */ From 8693c1402684db36ec681c6f94b9621a1fc2a7c5 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Fri, 9 May 2025 22:54:06 +0800 Subject: [PATCH 095/140] chore: release v12.2.1 :rocket: --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25c92549..61c6cead 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ ### [Unreleased] +### v12.2.1 - 2025-05-09 + +- fix: improve prefix detection #3238 +- fix: #3237 + ### v12.2.0 - 2025-05-08 - feat: add relation resolver param to order callback #3232 From 0a2a79573fad03c24c661b9ac62e5973cd1d39b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alfaiate?= Date: Thu, 15 May 2025 09:21:35 +0700 Subject: [PATCH 096/140] Add tests to cover prefix detection --- tests/Integration/QueryDataTableTest.php | 55 ++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tests/Integration/QueryDataTableTest.php b/tests/Integration/QueryDataTableTest.php index 55b4dda7..c6477f35 100644 --- a/tests/Integration/QueryDataTableTest.php +++ b/tests/Integration/QueryDataTableTest.php @@ -8,6 +8,7 @@ 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; @@ -406,6 +407,42 @@ public function it_can_return_added_column_with_dependency_injection() $this->assertEquals($user->name.'_di', $data['name_di']); } + #[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) + { + DB::enableQueryLog(); + + $crawler = $this->call('GET', '/query/aliases', [ + 'columns' => [ + ['data' => $column, 'name' => $column, 'orderable' => 'true'], + ], + 'order' => [['column' => 0, 'dir' => 'asc']], + ]); + + $crawler->assertJsonStructure([ + 'draw', + 'recordsTotal', + 'recordsFiltered', + ]); + + DB::disableQueryLog(); + $queryLog = DB::getQueryLog(); + + $this->assertCount(2, $queryLog); + + $sql = end($queryLog)['query']; + $this->assertStringContainsString("order by $expected asc", $sql); + } + protected function setUp(): void { parent::setUp(); @@ -497,5 +534,23 @@ protected function setUp(): void $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()); } } From 7a767b8a600aad221a2eada044f15690cef59490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alfaiate?= Date: Fri, 16 May 2025 08:32:36 +0700 Subject: [PATCH 097/140] feat: add option to enable alias on relation tables --- src/EloquentDataTable.php | 67 +++++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/src/EloquentDataTable.php b/src/EloquentDataTable.php index a19c7940..444caa15 100644 --- a/src/EloquentDataTable.php +++ b/src/EloquentDataTable.php @@ -17,6 +17,12 @@ */ 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. */ @@ -183,22 +189,34 @@ protected function resolveRelationColumn(string $column): string */ protected function joinEagerLoadedColumn($relation, $relationColumn) { - $tableAlias = ''; + $tableAlias = $pivotAlias = ''; $lastQuery = $this->query; foreach (explode('.', $relation) as $eachRelation) { $model = $lastQuery->getRelation($eachRelation); - $lastAlias = $tableAlias ?: $this->getTablePrefix($lastQuery); - $tableAlias = $tableAlias.'_'.$eachRelation; - $pivotAlias = $tableAlias.'_pivot'; + 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().' as '.$pivotAlias; + 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().' as '.$tableAlias; + if ($this->enableEagerJoinAliases) { + $table = $related->getTable().' as '.$tableAlias; + } else { + $table = $tableAlias = $related->getTable(); + } $tablePK = $model->getRelatedPivotKeyName(); $foreign = $pivotAlias.'.'.$tablePK; $other = $tableAlias.'.'.$related->getKeyName(); @@ -208,13 +226,21 @@ protected function joinEagerLoadedColumn($relation, $relationColumn) break; case $model instanceof HasOneThrough: - $pivot = explode('.', $model->getQualifiedParentKeyName())[0].' as '.$pivotAlias; // extract pivot table from key + 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().' as '.$tableAlias; + if ($this->enableEagerJoinAliases) { + $table = $related->getTable().' as '.$tableAlias; + } else { + $table = $tableAlias = $related->getTable(); + } $tablePK = $model->getSecondLocalKeyName(); $foreign = $pivotAlias.'.'.$tablePK; $other = $tableAlias.'.'.$related->getKeyName(); @@ -224,13 +250,21 @@ protected function joinEagerLoadedColumn($relation, $relationColumn) break; case $model instanceof HasOneOrMany: - $table = $model->getRelated()->getTable().' as '.$tableAlias; + 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().' as '.$tableAlias; + 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; @@ -245,6 +279,19 @@ protected function joinEagerLoadedColumn($relation, $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; + } + /** * Perform join query. * From 9590a79b865b9841d0515a2899cf84fb7dfb3286 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sat, 17 May 2025 11:40:31 +0800 Subject: [PATCH 098/140] chore: release v12.3.0 :rocket: --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61c6cead..97b73487 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ ### [Unreleased] +### v12.3.0 - 2025-05-17 + +- 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 + ### v12.2.1 - 2025-05-09 - fix: improve prefix detection #3238 From 341a7b09533d1c43906023f14e246d004ed62eea Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Mon, 2 Jun 2025 16:22:34 +0800 Subject: [PATCH 099/140] chore: update jetbrains logo --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8c935a37..3f4bc654 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,9 @@ return DataTables::make(User::all())->toJson();
DataTables Logo A big thank you to DataTables for supporting this project with a free DataTables Editor license.
- + @@ -40,7 +42,9 @@ return DataTables::make(User::all())->toJson();
DataTables Logo + DataTables Logo + A big thank you to DataTables for supporting this project with a free DataTables Editor license.
- + @@ -49,7 +53,7 @@ return DataTables::make(User::all())->toJson();
JetBrains Logo + JetBrains logo. + A big thank you to JetBrains for supporting this project with free open-source licenses of their IDEs.
- + From 14779dd6603768359a778c8835e20541430a721c Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Mon, 2 Jun 2025 16:25:16 +0800 Subject: [PATCH 100/140] chore: jb link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3f4bc654..32cff155 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ return DataTables::make(User::all())->toJson(); - +
Blackfire.io LogoBlackfire.io Logo A big thank you to Blackfire.io for supporting this project with a free open-source license.
JetBrains logo. A big thank you to JetBrains for supporting this project with free open-source licenses of their IDEs.A big thank you to JetBrains for supporting this project with free open-source licenses of their IDEs.
From c58f3a9490d7c3494afd48bd7a6ee9dbe1648979 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 3 Jun 2025 13:17:42 +0800 Subject: [PATCH 101/140] feat: add min search length control fix: #3241 --- src/CollectionDataTable.php | 2 ++ src/DataTableAbstract.php | 25 +++++++++++++++++++++++++ src/QueryDataTable.php | 2 ++ 3 files changed, 29 insertions(+) diff --git a/src/CollectionDataTable.php b/src/CollectionDataTable.php index f8ff4082..4392549e 100644 --- a/src/CollectionDataTable.php +++ b/src/CollectionDataTable.php @@ -143,6 +143,8 @@ public function paging(): void public function make(bool $mDataSupport = true): JsonResponse { try { + $this->validateMinLengthSearch(); + $this->totalRecords = $this->totalCount(); if ($this->totalRecords) { diff --git a/src/DataTableAbstract.php b/src/DataTableAbstract.php index b599721f..1b63b0cc 100644 --- a/src/DataTableAbstract.php +++ b/src/DataTableAbstract.php @@ -122,6 +122,8 @@ abstract class DataTableAbstract implements DataTable protected bool $editOnlySelectedColumns = false; + protected int $minSearchLength = 0; + /** * Can the DataTable engine be created with these parameters. * @@ -989,4 +991,27 @@ protected function getPrimaryKeyName(): string { return 'id'; } + + public function minSearchLength(int $length): static + { + $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/QueryDataTable.php b/src/QueryDataTable.php index a0383215..e183d555 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -124,6 +124,8 @@ public static function canCreate($source): bool public function make(bool $mDataSupport = true): JsonResponse { try { + $this->validateMinLengthSearch(); + $results = $this->prepareQuery()->results(); $processed = $this->processResults($results, $mDataSupport); $data = $this->transform($results, $processed); From b75d981256388352d8e2292b4f9feb8b3f300137 Mon Sep 17 00:00:00 2001 From: yajra <2687997+yajra@users.noreply.github.com> Date: Tue, 3 Jun 2025 05:20:13 +0000 Subject: [PATCH 102/140] fix: pint :robot: --- src/DataTableAbstract.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/DataTableAbstract.php b/src/DataTableAbstract.php index 1b63b0cc..bd84c235 100644 --- a/src/DataTableAbstract.php +++ b/src/DataTableAbstract.php @@ -1013,5 +1013,4 @@ protected function validateMinLengthSearch(): void ); } } - } From 53a04597cfc7a299d970f0096f45c97010125d31 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 3 Jun 2025 13:41:12 +0800 Subject: [PATCH 103/140] test: min search length --- .../MinSearchLengthDataTableTest.php | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 tests/Integration/MinSearchLengthDataTableTest.php diff --git a/tests/Integration/MinSearchLengthDataTableTest.php b/tests/Integration/MinSearchLengthDataTableTest.php new file mode 100644 index 00000000..0368cea7 --- /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()); + } +} From 7104ed7b0ad3ca2301f0ff81f17ffd9b518d492b Mon Sep 17 00:00:00 2001 From: yajra <2687997+yajra@users.noreply.github.com> Date: Tue, 3 Jun 2025 05:41:39 +0000 Subject: [PATCH 104/140] fix: pint :robot: --- tests/Integration/MinSearchLengthDataTableTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Integration/MinSearchLengthDataTableTest.php b/tests/Integration/MinSearchLengthDataTableTest.php index 0368cea7..fcab93d4 100644 --- a/tests/Integration/MinSearchLengthDataTableTest.php +++ b/tests/Integration/MinSearchLengthDataTableTest.php @@ -92,7 +92,7 @@ protected function setUp(): void { parent::setUp(); - Route::get('/eloquent/min-length', fn() => (new EloquentDataTable(User::query())) + Route::get('/eloquent/min-length', fn () => (new EloquentDataTable(User::query())) ->minSearchLength(5) ->toJson()); } From 1de63272b3f59b5935dd728def5c742c7c5e3f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Alfaiate?= Date: Mon, 9 Jun 2025 16:47:15 +0700 Subject: [PATCH 105/140] fix: support for array notation --- src/EloquentDataTable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EloquentDataTable.php b/src/EloquentDataTable.php index 444caa15..a7783d57 100644 --- a/src/EloquentDataTable.php +++ b/src/EloquentDataTable.php @@ -169,7 +169,7 @@ protected function resolveRelationColumn(string $column): string { $parts = explode('.', $column); $columnName = array_pop($parts); - $relation = str_replace('[]', '', implode('.', $parts)); + $relation = preg_replace('/\[.*?\]/', '', implode('.', $parts)); if ($this->isNotEagerLoaded($relation)) { return parent::resolveRelationColumn($column); From 12addca7eb179f1b136e74da89a76f84e06023e3 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 10 Jun 2025 10:00:25 +0800 Subject: [PATCH 106/140] chore: release v12.3.1 :rocket: --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97b73487..3a1e45fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ### [Unreleased] +### v12.3.1 - 2025-06-10 + +- fix: support for array notation #3243 + ### v12.3.0 - 2025-05-17 - feat: add option to enable alias on relation tables #3234 From 7250509b2621447583cab6c02c2d5b4fdfb8a151 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sun, 15 Jun 2025 10:58:07 +0800 Subject: [PATCH 107/140] chore: release v12.4.0 :rocket: --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a1e45fc..5a705e6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ ### [Unreleased] +### v12.4.0 - 2025-06-15 + +- feat: add min search length control #3242 +- fix: #3241 + ### v12.3.1 - 2025-06-10 - fix: support for array notation #3243 From 3a8e17563329719612cdf81280ebce9ba1581ab4 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Fri, 29 Aug 2025 15:54:42 +0800 Subject: [PATCH 108/140] fix: request handling with playwright / pest 4 --- src/Utilities/Request.php | 60 ++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/src/Utilities/Request.php b/src/Utilities/Request.php index 97150b87..203ed98c 100644 --- a/src/Utilities/Request.php +++ b/src/Utilities/Request.php @@ -9,16 +9,6 @@ */ class Request { - protected BaseRequest $request; - - /** - * Request constructor. - */ - public function __construct() - { - $this->request = app('request'); - } - /** * Proxy non-existing method calls to base request class. * @@ -28,7 +18,7 @@ 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); } @@ -42,7 +32,7 @@ public function __call($name, $arguments) */ public function __get($name) { - return $this->request->__get($name); + return request()->__get($name); } /** @@ -50,7 +40,7 @@ public function __get($name) */ public function columns(): array { - return (array) $this->request->input('columns'); + return (array) request()->input('columns'); } /** @@ -58,7 +48,7 @@ public function columns(): array */ public function isSearchable(): bool { - return $this->request->input('search.value') != ''; + return request()->input('search.value') != ''; } /** @@ -66,7 +56,7 @@ public function isSearchable(): bool */ public function isRegex(int $index): bool { - return $this->request->input("columns.$index.search.regex") === 'true'; + return request()->input("columns.$index.search.regex") === 'true'; } /** @@ -79,12 +69,12 @@ 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 = $direction && strtolower($direction) === 'asc' ? 'asc' : 'desc'; if ($this->isColumnOrderable($order_col)) { @@ -100,7 +90,7 @@ public function orderableColumns(): array */ 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; } /** @@ -108,7 +98,7 @@ public function isOrderable(): bool */ public function isColumnOrderable(int $index): bool { - return $this->request->input("columns.$index.orderable", 'true') == 'true'; + return request()->input("columns.$index.orderable", 'true') == 'true'; } /** @@ -119,7 +109,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; @@ -137,17 +127,17 @@ 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; } /** @@ -156,7 +146,7 @@ public function isColumnSearchable(int $i, bool $column_search = true): bool 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); } @@ -179,7 +169,7 @@ protected function prepareKeyword(float|array|int|string $keyword): string public function keyword(): string { /** @var string $keyword */ - $keyword = $this->request->input('search.value') ?? ''; + $keyword = request()->input('search.value') ?? ''; return $this->prepareKeyword($keyword); } @@ -190,7 +180,7 @@ public function keyword(): string 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']; } @@ -200,14 +190,14 @@ public function columnName(int $i): ?string */ 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; } public function getBaseRequest(): BaseRequest { - return $this->request; + return request(); } /** @@ -215,7 +205,7 @@ public function getBaseRequest(): BaseRequest */ public function start(): int { - $start = $this->request->input('start', 0); + $start = request()->input('start', 0); return is_numeric($start) ? intval($start) : 0; } @@ -225,7 +215,7 @@ public function start(): int */ public function length(): int { - $length = $this->request->input('length', 10); + $length = request()->input('length', 10); return is_numeric($length) ? intval($length) : 10; } @@ -235,7 +225,7 @@ public function length(): int */ public function draw(): int { - $draw = $this->request->input('draw', 0); + $draw = request()->input('draw', 0); return is_numeric($draw) ? intval($draw) : 0; } From f8354bb5187d78ad972778a5ca0be43b5e142bdb Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Fri, 29 Aug 2025 16:07:48 +0800 Subject: [PATCH 109/140] chore: release v12.4.1 :rocket: --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a705e6e..6c6035e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ### [Unreleased] +### v12.4.1 - 2025-08-29 + +- fix: request handling with playwright / pest 4 #3247 + ### v12.4.0 - 2025-06-15 - feat: add min search length control #3242 From 84c6afb2dd1cfdd16315dd2bc9ff0fca63f125af Mon Sep 17 00:00:00 2001 From: Tushar Nain <100490977+tusharnain@users.noreply.github.com> Date: Tue, 9 Sep 2025 17:16:58 +0530 Subject: [PATCH 110/140] fix: remove @internal annotation from orderColumn() method The orderColumn() method in QueryDataTable is documented and intended for public use (as shown in package docs), but was incorrectly marked as @internal. This caused static analyzers like PHPStan to report false positives when calling the method in userland code. Removing the @internal annotation clarifies that orderColumn() is part of the public API. --- src/QueryDataTable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index e183d555..89095051 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -625,7 +625,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 { From 7b5f1f5454ab46f9bb735c23f67c3a88b70e0e4a Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Tue, 9 Sep 2025 20:14:05 +0800 Subject: [PATCH 111/140] chore: release v12.4.2 :rocket: --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c6035e9..5ad441ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ### [Unreleased] +### v12.4.2 - 2025-09-09 + +- fix: remove @internal annotation from orderColumn() method #3248 + ### v12.4.1 - 2025-08-29 - fix: request handling with playwright / pest 4 #3247 From 2c2a3cc4a64279a42aefe25781ce4b06238967ae Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Fri, 19 Sep 2025 16:54:48 +0800 Subject: [PATCH 112/140] feat: column control --- src/QueryDataTable.php | 70 +++++++++++++++++++++++++++++++++++---- src/Utilities/Request.php | 5 +++ 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 89095051..cd583da9 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -258,6 +258,7 @@ protected function filterRecords(): void } $this->columnSearch(); + $this->columnControlSearch(); $this->searchPanesSearch(); // If no modification between the original query and the filtered one has been made @@ -281,23 +282,78 @@ 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']) { + continue; + } + + if ($this->isBlacklisted($columnName)) { + continue; + } + + $columnControl = $this->request->columnControlSearch($index); + $value = $columnControl['value']; + $logic = $columnControl['logic']; + // $type = $columnControl['type']; -- currently unused + + if ($value || str_contains($logic, 'empty')) { + $operator = match ($logic) { + 'contains', 'notContains', 'starts', 'ends' => 'LIKE', + 'greaterThan' => '>', + 'lessThan' => '<', + 'greaterThanOrEqual' => '>=', + 'lessThanOrEqual' => '<=', + 'empty', 'notEmpty' => null, + default => '=', + }; + + switch ($logic) { + case 'contains': + case 'notContains': + $value = '%'.$value.'%'; + break; + case 'starts': + $value = $value.'%'; + break; + case 'ends': + $value = '%'.$value; + break; + } + + if (str_contains($logic, 'empty')) { + $this->query->whereNull($columnName, not: str_contains($logic, 'not')); + } elseif (str_contains($logic, 'not')) { + $this->query->whereNot($columnName, $operator, $value); + } else { + $this->query->where($columnName, $operator, $value); + } } } } diff --git a/src/Utilities/Request.php b/src/Utilities/Request.php index 203ed98c..19c89571 100644 --- a/src/Utilities/Request.php +++ b/src/Utilities/Request.php @@ -151,6 +151,11 @@ public function columnKeyword(int $index): string return $this->prepareKeyword($keyword); } + public function columnControlSearch(int $index): array + { + return request()->array("columns.$index.columnControl.search"); + } + /** * Prepare keyword string value. */ From 4308015d9d432662b047be0c0d8880dfec95c62d Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Fri, 19 Sep 2025 17:18:18 +0800 Subject: [PATCH 113/140] fix: error on initial draw --- src/QueryDataTable.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index cd583da9..4e30303c 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -319,8 +319,8 @@ public function columnControlSearch(): void } $columnControl = $this->request->columnControlSearch($index); - $value = $columnControl['value']; - $logic = $columnControl['logic']; + $value = $columnControl['value'] ?? ''; + $logic = $columnControl['logic'] ?? 'equals'; // $type = $columnControl['type']; -- currently unused if ($value || str_contains($logic, 'empty')) { From ebd976cc81bb51a407453744ab65e2a89ce7c53d Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Fri, 19 Sep 2025 17:18:40 +0800 Subject: [PATCH 114/140] feat: support filterColumn --- src/QueryDataTable.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 4e30303c..942a440e 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -347,7 +347,9 @@ public function columnControlSearch(): void break; } - if (str_contains($logic, 'empty')) { + if ($this->hasFilterColumn($columnName)) { + $this->applyFilterColumn($this->getBaseQueryBuilder(), $columnName, $value); + } elseif (str_contains($logic, 'empty')) { $this->query->whereNull($columnName, not: str_contains($logic, 'not')); } elseif (str_contains($logic, 'not')) { $this->query->whereNot($columnName, $operator, $value); From 7955a00be7d5fa7893f192b8630b590d01719a1e Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sat, 20 Sep 2025 20:35:55 +0800 Subject: [PATCH 115/140] fix: lt and gt --- src/QueryDataTable.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 942a440e..8118de58 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -326,10 +326,10 @@ public function columnControlSearch(): void if ($value || str_contains($logic, 'empty')) { $operator = match ($logic) { 'contains', 'notContains', 'starts', 'ends' => 'LIKE', - 'greaterThan' => '>', - 'lessThan' => '<', - 'greaterThanOrEqual' => '>=', - 'lessThanOrEqual' => '<=', + 'greater' => '>', + 'less' => '<', + 'greaterOrEqual' => '>=', + 'lessOrEqual' => '<=', 'empty', 'notEmpty' => null, default => '=', }; From 87efac46f795e19e1ec42c900a69b9ba304e650e Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sat, 20 Sep 2025 23:44:52 +0800 Subject: [PATCH 116/140] feat: date search --- src/QueryDataTable.php | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 8118de58..5b1b89e2 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; @@ -321,7 +322,7 @@ public function columnControlSearch(): void $columnControl = $this->request->columnControlSearch($index); $value = $columnControl['value'] ?? ''; $logic = $columnControl['logic'] ?? 'equals'; - // $type = $columnControl['type']; -- currently unused + $type = $columnControl['type'] ?? 'string'; // string, num, date if ($value || str_contains($logic, 'empty')) { $operator = match ($logic) { @@ -331,6 +332,7 @@ public function columnControlSearch(): void 'greaterOrEqual' => '>=', 'lessOrEqual' => '<=', 'empty', 'notEmpty' => null, + 'notEqual' => '!=', default => '=', }; @@ -349,13 +351,29 @@ public function columnControlSearch(): void if ($this->hasFilterColumn($columnName)) { $this->applyFilterColumn($this->getBaseQueryBuilder(), $columnName, $value); - } elseif (str_contains($logic, 'empty')) { + + return; + } + + if (str_contains(strtolower($logic), 'empty')) { $this->query->whereNull($columnName, not: str_contains($logic, 'not')); - } elseif (str_contains($logic, 'not')) { + + return; + } + + if ($type === 'date') { + $this->query->whereDate($columnName, $operator, Carbon::parse($value)); + + return; + } + + if (str_contains($logic, 'not')) { $this->query->whereNot($columnName, $operator, $value); - } else { - $this->query->where($columnName, $operator, $value); + + return; } + + $this->query->where($columnName, $operator, $value); } } } From 1c1d3ae053abb84922b61869fcd2db9c9500899f Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sun, 21 Sep 2025 00:28:58 +0800 Subject: [PATCH 117/140] fix: notEmpty and date mask --- src/QueryDataTable.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 5b1b89e2..a68b50a5 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -322,9 +322,10 @@ public function columnControlSearch(): void $columnControl = $this->request->columnControlSearch($index); $value = $columnControl['value'] ?? ''; $logic = $columnControl['logic'] ?? 'equals'; - $type = $columnControl['type'] ?? 'string'; // string, num, date + $mask = $columnControl['mask'] ?? ''; // for date type + $type = $columnControl['type'] ?? 'text'; // text, num, date - if ($value || str_contains($logic, 'empty')) { + if ($value || str_contains(strtolower($logic), 'empty')) { $operator = match ($logic) { 'contains', 'notContains', 'starts', 'ends' => 'LIKE', 'greater' => '>', @@ -356,13 +357,14 @@ public function columnControlSearch(): void } if (str_contains(strtolower($logic), 'empty')) { - $this->query->whereNull($columnName, not: str_contains($logic, 'not')); + $this->query->whereNull($columnName, not: $logic === 'notEmpty'); return; } if ($type === 'date') { - $this->query->whereDate($columnName, $operator, Carbon::parse($value)); + $value = $mask ? Carbon::createFromFormat($mask, $value) : Carbon::parse($value); + $this->query->whereDate($columnName, $operator, $value); return; } From 8f2ffbd9580270820385c592157d30b5b784799e Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sun, 21 Sep 2025 00:34:01 +0800 Subject: [PATCH 118/140] chore: remove exceptions --- src/QueryDataTable.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index a68b50a5..e36d2bcb 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -772,10 +772,6 @@ public function addColumn($name, $content, $order = false): static /** * Perform search using search pane values. - * - * - * @throws \Psr\Container\ContainerExceptionInterface - * @throws \Psr\Container\NotFoundExceptionInterface */ protected function searchPanesSearch(): void { From 899187263d0c57206481ffa1aca4177db59e3dbc Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sun, 21 Sep 2025 00:36:45 +0800 Subject: [PATCH 119/140] chore: throw exception on unsupported engine --- src/DataTableAbstract.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/DataTableAbstract.php b/src/DataTableAbstract.php index bd84c235..2f262621 100644 --- a/src/DataTableAbstract.php +++ b/src/DataTableAbstract.php @@ -10,6 +10,7 @@ use Psr\Log\LoggerInterface; use Yajra\DataTables\Contracts\DataTable; use Yajra\DataTables\Contracts\Formatter; +use Yajra\DataTables\Exceptions\Exception; use Yajra\DataTables\Processors\DataProcessor; use Yajra\DataTables\Utilities\Helper; @@ -730,10 +731,19 @@ protected function filterRecords(): void } $this->columnSearch(); + $this->columnControlSearch(); $this->searchPanesSearch(); $this->filteredCount(); } + /** + * @throws \Yajra\DataTables\Exceptions\Exception + */ + public function columnControlSearch(): void + { + throw new Exception('Column control search is not supported by this engine.'); + } + /** * Perform global search. */ From 06378e12f45ddbcc25b956c408315afc26724c08 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sun, 21 Sep 2025 01:06:32 +0800 Subject: [PATCH 120/140] feat: searchDropdown list --- src/QueryDataTable.php | 26 +++++++++++++++++++------- src/Utilities/Request.php | 5 +++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index e36d2bcb..6c4d0c91 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -319,13 +319,15 @@ public function columnControlSearch(): void continue; } - $columnControl = $this->request->columnControlSearch($index); - $value = $columnControl['value'] ?? ''; - $logic = $columnControl['logic'] ?? 'equals'; - $mask = $columnControl['mask'] ?? ''; // for date type - $type = $columnControl['type'] ?? 'text'; // text, num, date - - if ($value || str_contains(strtolower($logic), 'empty')) { + $columnControl = $this->request->columnControl($index); + $list = $columnControl['list'] ?? []; + $search = $columnControl['search'] ?? []; + $value = $search['value'] ?? ''; + $logic = $search['logic'] ?? 'equals'; + $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' => '>', @@ -356,6 +358,16 @@ public function columnControlSearch(): void return; } + if (is_array($list) && count($list) > 0) { + if (str_contains($logic, 'not')) { + $this->query->whereNotIn($columnName, $list); + } else { + $this->query->whereIn($columnName, $list); + } + + return; + } + if (str_contains(strtolower($logic), 'empty')) { $this->query->whereNull($columnName, not: $logic === 'notEmpty'); diff --git a/src/Utilities/Request.php b/src/Utilities/Request.php index 19c89571..fa305b32 100644 --- a/src/Utilities/Request.php +++ b/src/Utilities/Request.php @@ -151,6 +151,11 @@ public function columnKeyword(int $index): string 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"); From 9f27b43aa8c219fc41154a883bc465dbdcdd834b Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sun, 21 Sep 2025 01:10:50 +0800 Subject: [PATCH 121/140] fix: searching of zero (0) --- src/QueryDataTable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 6c4d0c91..8c54afc1 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -327,7 +327,7 @@ public function columnControlSearch(): void $mask = $search['mask'] ?? ''; // for date type $type = $search['type'] ?? 'text'; // text, num, date - if ($value || str_contains(strtolower($logic), 'empty') || $list) { + if ($value != '' || str_contains(strtolower($logic), 'empty') || $list) { $operator = match ($logic) { 'contains', 'notContains', 'starts', 'ends' => 'LIKE', 'greater' => '>', From eb1e0d6dbe8695b57034c5998827258902af9384 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sun, 21 Sep 2025 14:58:46 +0800 Subject: [PATCH 122/140] fix: continue searching on remaining columns --- src/QueryDataTable.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 8c54afc1..5279d4ff 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -355,7 +355,7 @@ public function columnControlSearch(): void if ($this->hasFilterColumn($columnName)) { $this->applyFilterColumn($this->getBaseQueryBuilder(), $columnName, $value); - return; + continue; } if (is_array($list) && count($list) > 0) { @@ -365,26 +365,26 @@ public function columnControlSearch(): void $this->query->whereIn($columnName, $list); } - return; + continue; } if (str_contains(strtolower($logic), 'empty')) { $this->query->whereNull($columnName, not: $logic === 'notEmpty'); - return; + continue; } if ($type === 'date') { $value = $mask ? Carbon::createFromFormat($mask, $value) : Carbon::parse($value); $this->query->whereDate($columnName, $operator, $value); - return; + continue; } if (str_contains($logic, 'not')) { $this->query->whereNot($columnName, $operator, $value); - return; + continue; } $this->query->where($columnName, $operator, $value); From 619cb1cc84ad1833ecffbaa2fd465b9b6b136542 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sun, 21 Sep 2025 16:06:11 +0800 Subject: [PATCH 123/140] fix: notEqual with date --- src/QueryDataTable.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 5279d4ff..9d7953c7 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -323,7 +323,7 @@ public function columnControlSearch(): void $list = $columnControl['list'] ?? []; $search = $columnControl['search'] ?? []; $value = $search['value'] ?? ''; - $logic = $search['logic'] ?? 'equals'; + $logic = $search['logic'] ?? 'equal'; $mask = $search['mask'] ?? ''; // for date type $type = $search['type'] ?? 'text'; // text, num, date @@ -335,7 +335,6 @@ public function columnControlSearch(): void 'greaterOrEqual' => '>=', 'lessOrEqual' => '<=', 'empty', 'notEmpty' => null, - 'notEqual' => '!=', default => '=', }; @@ -376,7 +375,14 @@ public function columnControlSearch(): void if ($type === 'date') { $value = $mask ? Carbon::createFromFormat($mask, $value) : Carbon::parse($value); - $this->query->whereDate($columnName, $operator, $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); + } continue; } From f1ac789071b9c08526a8728a88c6cee638819331 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sun, 21 Sep 2025 17:21:38 +0800 Subject: [PATCH 124/140] fix: undefined variable searchable --- src/QueryDataTable.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 9d7953c7..079524b3 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -311,7 +311,7 @@ public function columnControlSearch(): void foreach ($columns as $index => $column) { $columnName = $this->getColumnName($index); - if (is_null($columnName) || ! $column['searchable']) { + if (is_null($columnName) || ! ($column['searchable'] ?? false)) { continue; } @@ -374,7 +374,11 @@ public function columnControlSearch(): void } if ($type === 'date') { - $value = $mask ? Carbon::createFromFormat($mask, $value) : Carbon::parse($value); + try { + $value = $mask ? Carbon::createFromFormat($mask, $value) : Carbon::parse($value); + } catch (\Exception $e) { + // can't parse date + } if ($logic === 'notEqual') { $this->query->where(function ($q) use ($columnName, $value) { From e85571a30f073c706511f0eef4edde062cd62e25 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sun, 21 Sep 2025 21:33:13 +0800 Subject: [PATCH 125/140] fix: allow custom filters --- src/QueryDataTable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 079524b3..e78c224d 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -315,7 +315,7 @@ public function columnControlSearch(): void continue; } - if ($this->isBlacklisted($columnName)) { + if ($this->isBlacklisted($columnName) && ! $this->hasFilterColumn($columnName)) { continue; } From b2aa1a6b192e150230cf42318e1657d1595d990d Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Wed, 24 Sep 2025 13:34:37 +0800 Subject: [PATCH 126/140] feat: convert list into comma separated keyword --- src/QueryDataTable.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index e78c224d..710efffa 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -352,12 +352,13 @@ public function columnControlSearch(): void } if ($this->hasFilterColumn($columnName)) { + $value = $list ? implode(', ', $list) : $value; $this->applyFilterColumn($this->getBaseQueryBuilder(), $columnName, $value); continue; } - if (is_array($list) && count($list) > 0) { + if ($list) { if (str_contains($logic, 'not')) { $this->query->whereNotIn($columnName, $list); } else { From 167e2aa3ad72ef4fb6ba70e76e2fe323afd40352 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Wed, 1 Oct 2025 10:14:55 +0800 Subject: [PATCH 127/140] chore: rector --- src/QueryDataTable.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 710efffa..ed507857 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -377,7 +377,7 @@ public function columnControlSearch(): void if ($type === 'date') { try { $value = $mask ? Carbon::createFromFormat($mask, $value) : Carbon::parse($value); - } catch (\Exception $e) { + } catch (\Exception) { // can't parse date } @@ -637,11 +637,11 @@ protected function getSelectedColumns($query): array ]; foreach ($q->columns ?? [] as $select) { - $sql = trim($select instanceof Expression ? $select->getValue($this->getConnection()->getQueryGrammar()) : $select); + $sql = trim((string) $select instanceof Expression ? $select->getValue($this->getConnection()->getQueryGrammar()) : $select); // Remove expressions $sql = preg_replace('/\s*\w*\((?:[^()]*|(?R))*\)/', '_', $sql); // Remove multiple spaces - $sql = preg_replace('/\s+/', ' ', $sql); + $sql = preg_replace('/\s+/', ' ', (string) $sql); // Remove wrappers $sql = str_replace(['`', '"', '[', ']'], '', $sql); // Loop on select columns From 0090df4460bf3b363358968c96597cd82f819002 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Wed, 1 Oct 2025 10:15:47 +0800 Subject: [PATCH 128/140] fix: static analysis --- src/QueryDataTable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index ed507857..279db784 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -637,7 +637,7 @@ protected function getSelectedColumns($query): array ]; foreach ($q->columns ?? [] as $select) { - $sql = trim((string) $select instanceof Expression ? $select->getValue($this->getConnection()->getQueryGrammar()) : $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 From 5c10fd312612e9a5d68de79813945beb851ec0fd Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Wed, 1 Oct 2025 10:17:30 +0800 Subject: [PATCH 129/140] fix: do not throw error if column control is not yet supported --- src/DataTableAbstract.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/DataTableAbstract.php b/src/DataTableAbstract.php index 2f262621..0e51ed2b 100644 --- a/src/DataTableAbstract.php +++ b/src/DataTableAbstract.php @@ -10,7 +10,6 @@ use Psr\Log\LoggerInterface; use Yajra\DataTables\Contracts\DataTable; use Yajra\DataTables\Contracts\Formatter; -use Yajra\DataTables\Exceptions\Exception; use Yajra\DataTables\Processors\DataProcessor; use Yajra\DataTables\Utilities\Helper; @@ -736,12 +735,9 @@ protected function filterRecords(): void $this->filteredCount(); } - /** - * @throws \Yajra\DataTables\Exceptions\Exception - */ public function columnControlSearch(): void { - throw new Exception('Column control search is not supported by this engine.'); + // Not implemented in the abstract class. } /** From b5a58e02d0d6365c5b3085f1edca254054505a85 Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Wed, 1 Oct 2025 10:26:06 +0800 Subject: [PATCH 130/140] chore: release v12.5.0 :rocket: --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ad441ed..5004cc7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ ### [Unreleased] +### v12.5.0 - 2025-10-01 + +- feat: server-side column control #3251 +- fix: https://github.com/yajra/laravel-datatables/issues/3250 + ### v12.4.2 - 2025-09-09 - fix: remove @internal annotation from orderColumn() method #3248 From 85ce44df09a2061e01bdfb1f05b962943c22b0c4 Mon Sep 17 00:00:00 2001 From: Emmanuel Joseph Beron Date: Thu, 2 Oct 2025 12:12:55 +0800 Subject: [PATCH 131/140] fix: fix ambiguous column in columnControlSearch() method --- src/QueryDataTable.php | 5 ++++ tests/Unit/QueryDataTableTest.php | 39 +++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 279db784..3e384962 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -358,6 +358,11 @@ public function columnControlSearch(): void 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); diff --git a/tests/Unit/QueryDataTableTest.php b/tests/Unit/QueryDataTableTest.php index 4e7ee785..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; @@ -171,4 +172,42 @@ public function assertQueryHasNoSelect($expected, $query): void $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') + ); + + $dataTable->columnControlSearch(); + + $this->assertStringContainsString( + '"users"."id" = \'123\'', + $dataTable->getQuery()->toRawSql() + ); + } } From 3fc34e8c5b2c2c700f46a2ae2644c2be1c20c8da Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Thu, 2 Oct 2025 14:57:54 +0800 Subject: [PATCH 132/140] chore: release v12.5.1 :rocket: --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5004cc7d..8b841c18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ### [Unreleased] +### v12.5.1 - 2025-10-02 + +- fix: ambiguous column in columnControlSearch() method #3252 + ### v12.5.0 - 2025-10-01 - feat: server-side column control #3251 From c1bdc8e13f4fb51a9b9feddcea709a8ef7aedbc8 Mon Sep 17 00:00:00 2001 From: Tushar Nain Date: Fri, 3 Oct 2025 04:49:31 +0530 Subject: [PATCH 133/140] chore: improve datatables() helper docblock for better return type clarity --- src/helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 */ From 3e7080c2d659075dd884b3d0498fcedc687e0732 Mon Sep 17 00:00:00 2001 From: Serhii Petrov Date: Sat, 4 Oct 2025 13:59:18 +0300 Subject: [PATCH 134/140] Test against php 8.5 --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 357de9ad..971c16c8 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: true matrix: - php: [ 8.2, 8.3, 8.4 ] + php: [ 8.2, 8.3, 8.4, 8.5 ] stability: [ prefer-stable ] name: PHP ${{ matrix.php }} - STABILITY ${{ matrix.stability }} From 8858cdb35f9cac17aa826f6e7e89378d8bdfbf87 Mon Sep 17 00:00:00 2001 From: Emmanuel Arturo Date: Sat, 4 Oct 2025 19:06:53 +0800 Subject: [PATCH 135/140] ci: add semantic release --- .github/workflows/semantic-release.yml | 41 ++++++++++++++++++++++++++ .releaserc.yml | 26 ++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 .github/workflows/semantic-release.yml create mode 100644 .releaserc.yml 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/.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" + ] +} From 7f46d5872b0324493c28ecc8d848c182e88f30e0 Mon Sep 17 00:00:00 2001 From: Tushar Nain Date: Tue, 7 Oct 2025 17:23:11 +0530 Subject: [PATCH 136/140] fix: replace unsafe eval() with Blade::render() in compileBlade --- src/Utilities/Helper.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Utilities/Helper.php b/src/Utilities/Helper.php index a859f17d..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; @@ -124,12 +125,7 @@ public static function compileBlade(string $str, array $data = []): false|string 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); } /** From 33f44d42d284d6ea0a054de81ad5a57c3050867d Mon Sep 17 00:00:00 2001 From: Tushar Nain Date: Tue, 7 Oct 2025 17:29:34 +0530 Subject: [PATCH 137/140] feat: add __isset() method to Request for attribute existence check --- src/Utilities/Request.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Utilities/Request.php b/src/Utilities/Request.php index fa305b32..c26d9102 100644 --- a/src/Utilities/Request.php +++ b/src/Utilities/Request.php @@ -24,6 +24,16 @@ public function __call($name, $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. * From 604b957de3776182bde1b7390018cffc3f6b947c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 8 Oct 2025 02:30:51 +0000 Subject: [PATCH 138/140] chore(release): 12.6.0 [skip ci] # [12.6.0](https://github.com/yajra/laravel-datatables/compare/v12.5.1...v12.6.0) (2025-10-08) ### Bug Fixes * replace unsafe eval() with Blade::render() in compileBlade ([7f46d58](https://github.com/yajra/laravel-datatables/commit/7f46d5872b0324493c28ecc8d848c182e88f30e0)) ### Features * add __isset() method to Request for attribute existence check ([33f44d4](https://github.com/yajra/laravel-datatables/commit/33f44d42d284d6ea0a054de81ad5a57c3050867d)) --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b841c18..b0668e58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# [12.6.0](https://github.com/yajra/laravel-datatables/compare/v12.5.1...v12.6.0) (2025-10-08) + + +### Bug Fixes + +* replace unsafe eval() with Blade::render() in compileBlade ([7f46d58](https://github.com/yajra/laravel-datatables/commit/7f46d5872b0324493c28ecc8d848c182e88f30e0)) + + +### Features + +* add __isset() method to Request for attribute existence check ([33f44d4](https://github.com/yajra/laravel-datatables/commit/33f44d42d284d6ea0a054de81ad5a57c3050867d)) + # Laravel DataTables ## CHANGELOG From c77190030c713e5b64c433bd161d9f33a210f22b Mon Sep 17 00:00:00 2001 From: Arjay Angeles Date: Sat, 11 Oct 2025 19:32:44 +0800 Subject: [PATCH 139/140] fix: value when mask uses "/" fix: do not execute date sql if we cannot parse the value --- src/QueryDataTable.php | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/QueryDataTable.php b/src/QueryDataTable.php index 3e384962..01cd5b71 100644 --- a/src/QueryDataTable.php +++ b/src/QueryDataTable.php @@ -381,19 +381,24 @@ public function columnControlSearch(): void 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 } - if ($logic === 'notEqual') { - $this->query->where(function ($q) use ($columnName, $value) { - $q->whereDate($columnName, '!=', $value)->orWhereNull($columnName); - }); - } else { - $this->query->whereDate($columnName, $operator, $value); - } - continue; } From 79c601a6c902ae5841054c2b29126356396f0d55 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 11 Oct 2025 12:26:40 +0000 Subject: [PATCH 140/140] chore(release): 12.6.1 [skip ci] ## [12.6.1](https://github.com/yajra/laravel-datatables/compare/v12.6.0...v12.6.1) (2025-10-11) ### Bug Fixes * value when mask uses "/" ([c771900](https://github.com/yajra/laravel-datatables/commit/c77190030c713e5b64c433bd161d9f33a210f22b)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0668e58..91ad2c0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [12.6.1](https://github.com/yajra/laravel-datatables/compare/v12.6.0...v12.6.1) (2025-10-11) + + +### Bug Fixes + +* value when mask uses "/" ([c771900](https://github.com/yajra/laravel-datatables/commit/c77190030c713e5b64c433bd161d9f33a210f22b)) + # [12.6.0](https://github.com/yajra/laravel-datatables/compare/v12.5.1...v12.6.0) (2025-10-08)