diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c0ae319..cd62de49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to `laravel-query-builder` will be documented in this file +## 3.6.0 - 2021-09-06 + +- add callback sorts (#654) + +## 3.5.0 - 2021-07-05 + +- add support for cursor pagination + ## 3.4.3 - 2021-07-05 - fix unexpected lowercase appends (#637) diff --git a/composer.json b/composer.json index 4d7c37d5..b676271a 100644 --- a/composer.json +++ b/composer.json @@ -22,8 +22,8 @@ "require": { "php": "^7.3|^8.0", "illuminate/database": "^6.20.13|^7.30.4|^8.22.2", - "illuminate/http": "^6.20.13|7.30.4|^8.22.2", - "illuminate/support": "^6.20.13|7.30.4|^8.22.2" + "illuminate/http": "^6.20.13|^7.30.4|^8.22.2", + "illuminate/support": "^6.20.13|^7.30.4|^8.22.2" }, "require-dev": { "ext-json": "*", diff --git a/docs/_index.md b/docs/_index.md index 3e88c8f7..c2ae5699 100644 --- a/docs/_index.md +++ b/docs/_index.md @@ -2,5 +2,5 @@ title: v3 slogan: Easily build Eloquent queries from API requests. githubUrl: https://github.com/spatie/laravel-query-builder -branch: master +branch: v3 --- diff --git a/docs/installation-setup.md b/docs/installation-setup.md index ae8f98fb..af998bc6 100644 --- a/docs/installation-setup.md +++ b/docs/installation-setup.md @@ -6,7 +6,7 @@ weight: 4 You can install the package via composer: ```bash -composer require spatie/laravel-query-builder +composer require spatie/laravel-query-builder "^3.0" ``` The package will automatically register its service provider. diff --git a/src/AllowedSort.php b/src/AllowedSort.php index b702eb0b..8d78081f 100644 --- a/src/AllowedSort.php +++ b/src/AllowedSort.php @@ -5,6 +5,7 @@ use Spatie\QueryBuilder\Enums\SortDirection; use Spatie\QueryBuilder\Exceptions\InvalidDirection; use Spatie\QueryBuilder\Sorts\Sort; +use Spatie\QueryBuilder\Sorts\SortsCallback; use Spatie\QueryBuilder\Sorts\SortsField; class AllowedSort @@ -54,6 +55,11 @@ public static function custom(string $name, Sort $sortClass, ?string $internalNa return new static($name, $sortClass, $internalName); } + public static function callback(string $name, $callback, ?string $internalName = null): self + { + return new static($name, new SortsCallback($callback), $internalName); + } + public function getName(): string { return $this->name; diff --git a/src/Concerns/AppendsAttributesToResults.php b/src/Concerns/AppendsAttributesToResults.php index daa1d8e8..0fddd216 100644 --- a/src/Concerns/AppendsAttributesToResults.php +++ b/src/Concerns/AppendsAttributesToResults.php @@ -29,6 +29,13 @@ protected function addAppendsToResults(Collection $results) }); } + protected function addAppendsToCursor($results) + { + return $results->each(function (Model $result) { + return $result->append($this->request->appends()->toArray()); + }); + } + protected function ensureAllAppendsExist() { $appends = $this->request->appends(); diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 6c25d4d3..3b019346 100755 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -3,6 +3,7 @@ namespace Spatie\QueryBuilder; use ArrayAccess; +use Illuminate\Contracts\Pagination\CursorPaginator; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; @@ -125,7 +126,7 @@ public function __call($name, $arguments) $this->addAppendsToResults($result); } - if ($result instanceof LengthAwarePaginator || $result instanceof Paginator) { + if ($result instanceof LengthAwarePaginator || $result instanceof Paginator || $result instanceof CursorPaginator) { $this->addAppendsToResults(collect($result->items())); } @@ -152,22 +153,22 @@ public function __set($name, $value) $this->subject->{$name} = $value; } - public function offsetExists($offset) + public function offsetExists($offset): bool { return isset($this->subject[$offset]); } - public function offsetGet($offset) + public function offsetGet($offset): mixed { return $this->subject[$offset]; } - public function offsetSet($offset, $value) + public function offsetSet($offset, $value): void { $this->subject[$offset] = $value; } - public function offsetUnset($offset) + public function offsetUnset($offset): void { unset($this->subject[$offset]); } diff --git a/src/QueryBuilderRequest.php b/src/QueryBuilderRequest.php index 059d0a4a..6a5d8adb 100644 --- a/src/QueryBuilderRequest.php +++ b/src/QueryBuilderRequest.php @@ -38,7 +38,7 @@ public function includes(): Collection $includeParts = $this->getRequestData($includeParameterName); - if (! is_array($includeParts)) { + if (is_string($includeParts)) { $includeParts = explode(static::getIncludesArrayValueDelimiter(), $this->getRequestData($includeParameterName)); } @@ -53,7 +53,7 @@ public function appends(): Collection $appendParts = $this->getRequestData($appendParameterName); - if (! is_array($appendParts)) { + if (! is_array($appendParts) && ! is_null($appendParts)) { $appendParts = explode(static::getAppendsArrayValueDelimiter(), $appendParts); } diff --git a/src/Sorts/SortsCallback.php b/src/Sorts/SortsCallback.php new file mode 100644 index 00000000..e7fe8e73 --- /dev/null +++ b/src/Sorts/SortsCallback.php @@ -0,0 +1,25 @@ +callback = $callback; + } + + /** {@inheritdoc} */ + public function __invoke(Builder $query, bool $descending, string $property) + { + return call_user_func($this->callback, $query, $descending, $property); + } +} diff --git a/tests/AppendTest.php b/tests/AppendTest.php index ff0fc17b..c97607ec 100644 --- a/tests/AppendTest.php +++ b/tests/AppendTest.php @@ -4,7 +4,6 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Request; -use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; use Spatie\QueryBuilder\Exceptions\InvalidAppendQuery; use Spatie\QueryBuilder\QueryBuilder; @@ -73,6 +72,28 @@ public function it_can_append_paginates() $this->assertPaginateAttributeLoaded($models, 'FullName'); } + /** @test */ + public function it_can_append_simple_paginates() + { + $models = $this + ->createQueryFromAppendRequest('FullName') + ->allowedAppends('FullName') + ->simplePaginate(); + + $this->assertPaginateAttributeLoaded($models, 'FullName'); + } + + /** @test */ + public function it_can_append_cursor_paginates() + { + $models = $this + ->createQueryFromAppendRequest('FullName') + ->allowedAppends('FullName') + ->cursorPaginate(); + + $this->assertPaginateAttributeLoaded($models, 'FullName'); + } + /** @test */ public function it_guards_against_invalid_appends() { @@ -150,7 +171,11 @@ protected function assertCollectionAttributeLoaded(Collection $collection, strin $this->assertFalse($hasModelWithoutAttributeLoaded, "The `{$attribute}` attribute was expected but not loaded."); } - protected function assertPaginateAttributeLoaded(LengthAwarePaginator $collection, string $attribute) + /** + * @param \Illuminate\Pagination\LengthAwarePaginator|\Illuminate\Contracts\Pagination\Paginator $collection + * @param string $attribute + */ + protected function assertPaginateAttributeLoaded($collection, string $attribute) { $hasModelWithoutAttributeLoaded = $collection ->contains(function (Model $model) use ($attribute) { diff --git a/tests/SortsCallbackTest.php b/tests/SortsCallbackTest.php new file mode 100644 index 00000000..ae7f9ad6 --- /dev/null +++ b/tests/SortsCallbackTest.php @@ -0,0 +1,77 @@ +models = factory(TestModel::class, 5)->create(); + } + + /** @test */ + public function it_should_sort_by_closure() + { + $sortedModels = $this + ->createQueryFromSortRequest('callback') + ->allowedSorts(AllowedSort::callback('callback', function (Builder $query, $descending) { + $query->orderBy('name', $descending ? 'DESC' : 'ASC'); + })) + ->get(); + + $this->assertQueryExecuted('select * from `test_models` order by `name` asc'); + $this->assertSortedAscending($sortedModels, 'name'); + } + + /** @test */ + public function it_should_sort_by_array_callback() + { + $sortedModels = $this + ->createQueryFromSortRequest('callback') + ->allowedSorts(AllowedSort::callback('callback', [$this, 'sortCallback'])) + ->get(); + + $this->assertQueryExecuted('select * from `test_models` order by `name` asc'); + $this->assertSortedAscending($sortedModels, 'name'); + } + + public function sortCallback(Builder $query, $descending) + { + $query->orderBy('name', $descending ? 'DESC' : 'ASC'); + } + + protected function createQueryFromSortRequest(string $sort): QueryBuilder + { + $request = new Request([ + 'sort' => $sort, + ]); + + return QueryBuilder::for(TestModel::class, $request); + } + + protected function assertQueryExecuted(string $query) + { + $queries = array_map(function ($queryLogItem) { + return $queryLogItem['query']; + }, DB::getQueryLog()); + + $this->assertContains($query, $queries); + } +}