diff --git a/docs/features/including-relationships.md b/docs/features/including-relationships.md index 7f5cc7e8..6c82f5ee 100644 --- a/docs/features/including-relationships.md +++ b/docs/features/including-relationships.md @@ -150,6 +150,23 @@ $posts = QueryBuilder::for(Post::class) // every post in $posts will contain a `comments_sum_votes` property ``` +## Callback includes + +If you want to define a tiny custom include, you can use a callback include. Using `AllowedInclude::callback(string $name, Closure $callback, ?string $internalName = null)` you can specify a Closure that will be executed when the includes is requested. + +You can modify the `Builder` object to add your own query constraints. + +For example: + +```php +QueryBuilder::for(User::class) + ->allowedIncludes([ + AllowedInclude::callback('latest_post', function (Builder $query) { + $query->latestOfMany(); + }), + ]); +``` + ## Selecting included fields You can select only some fields to be included using the [`allowedFields` method on the query builder](https://spatie.be/docs/laravel-query-builder/v5/features/selecting-fields/). diff --git a/src/AllowedInclude.php b/src/AllowedInclude.php index 0420d5a1..cce9b06b 100644 --- a/src/AllowedInclude.php +++ b/src/AllowedInclude.php @@ -2,8 +2,10 @@ namespace Spatie\QueryBuilder; +use Closure; use Illuminate\Support\Collection; use Illuminate\Support\Str; +use Spatie\QueryBuilder\Includes\IncludedCallback; use Spatie\QueryBuilder\Includes\IncludedCount; use Spatie\QueryBuilder\Includes\IncludedExists; use Spatie\QueryBuilder\Includes\IncludedRelationship; @@ -73,6 +75,13 @@ public static function exists(string $name, ?string $internalName = null): Colle ]); } + public static function callback(string $name, Closure $callback, ?string $internalName = null): Collection + { + return collect([ + new static($name, new IncludedCallback($callback), $internalName), + ]); + } + public static function custom(string $name, IncludeInterface $includeClass, ?string $internalName = null): Collection { return collect([ diff --git a/src/Includes/IncludedCallback.php b/src/Includes/IncludedCallback.php new file mode 100644 index 00000000..51e683f6 --- /dev/null +++ b/src/Includes/IncludedCallback.php @@ -0,0 +1,23 @@ +callback = $callback; + } + + public function __invoke(Builder $query, string $relation) + { + $query->with([ + $relation => $this->callback, + ]); + } +} diff --git a/tests/IncludeTest.php b/tests/IncludeTest.php index 5661a686..27311ebe 100644 --- a/tests/IncludeTest.php +++ b/tests/IncludeTest.php @@ -14,6 +14,7 @@ use Spatie\QueryBuilder\Includes\IncludeInterface; use Spatie\QueryBuilder\QueryBuilder; use Spatie\QueryBuilder\Tests\TestClasses\Models\MorphModel; +use Spatie\QueryBuilder\Tests\TestClasses\Models\RelatedModel; use Spatie\QueryBuilder\Tests\TestClasses\Models\TestModel; beforeEach(function () { @@ -69,6 +70,22 @@ assertRelationLoaded($models, 'relatedModels'); }); +it('can include an includes callback', function () { + $models = createQueryFromIncludeRequest('relatedModels') + ->allowedIncludes([ + AllowedInclude::callback('relatedModels', fn ($query) => $query->whereKey(RelatedModel::first())), + ]) + ->get(); + + assertRelationLoaded($models, 'relatedModels'); + + $models = $models->reverse(); + expect($models->pop()->relatedModels)->toHaveCount(1); + expect($models)->each( + fn ($model) => $model->relatedModels->toHaveCount(0) + ); +}); + it('can include an includes count', function () { $model = createQueryFromIncludeRequest('relatedModelsCount') ->allowedIncludes('relatedModelsCount')