Skip to content

Commit 94f0192

Browse files
[11.x] afterQuery hook (#50587)
* Add afterQuery hook * Fix linting * Some more linting fixes * Support belongstomany and hasmanythrough * formatting * formatting * formatting * Support cursor() and pluck() * Extra tests * More tests + formatting * Support filtering on cursor * Fix parameter name --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent d3a051a commit 94f0192

File tree

5 files changed

+487
-15
lines changed

5 files changed

+487
-15
lines changed

src/Illuminate/Database/Eloquent/Builder.php

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Illuminate\Database\UniqueConstraintViolationException;
1818
use Illuminate\Pagination\Paginator;
1919
use Illuminate\Support\Arr;
20+
use Illuminate\Support\Collection;
2021
use Illuminate\Support\Str;
2122
use Illuminate\Support\Traits\ForwardsCalls;
2223
use ReflectionClass;
@@ -137,6 +138,13 @@ class Builder implements BuilderContract
137138
*/
138139
protected $removedScopes = [];
139140

141+
/**
142+
* The callbacks that should be invoked after retrieving data from the database.
143+
*
144+
* @var array
145+
*/
146+
protected $afterQueryCallbacks = [];
147+
140148
/**
141149
* Create a new Eloquent query builder instance.
142150
*
@@ -723,7 +731,9 @@ public function get($columns = ['*'])
723731
$models = $builder->eagerLoadRelations($models);
724732
}
725733

726-
return $builder->getModel()->newCollection($models);
734+
return $this->applyAfterQueryCallbacks(
735+
$builder->getModel()->newCollection($models)
736+
);
727737
}
728738

729739
/**
@@ -852,6 +862,34 @@ protected function isNestedUnder($relation, $name)
852862
return str_contains($name, '.') && str_starts_with($name, $relation.'.');
853863
}
854864

865+
/**
866+
* Register a closure to be invoked after the query is executed.
867+
*
868+
* @param \Closure $callback
869+
* @return $this
870+
*/
871+
public function afterQuery(Closure $callback)
872+
{
873+
$this->afterQueryCallbacks[] = $callback;
874+
875+
return $this;
876+
}
877+
878+
/**
879+
* Invoke the "after query" modification callbacks.
880+
*
881+
* @param mixed $result
882+
* @return mixed
883+
*/
884+
public function applyAfterQueryCallbacks($result)
885+
{
886+
foreach ($this->afterQueryCallbacks as $afterQueryCallback) {
887+
$result = $afterQueryCallback($result) ?: $result;
888+
}
889+
890+
return $result;
891+
}
892+
855893
/**
856894
* Get a lazy collection for the given query.
857895
*
@@ -860,8 +898,10 @@ protected function isNestedUnder($relation, $name)
860898
public function cursor()
861899
{
862900
return $this->applyScopes()->query->cursor()->map(function ($record) {
863-
return $this->newModelInstance()->newFromBuilder($record);
864-
});
901+
$model = $this->newModelInstance()->newFromBuilder($record);
902+
903+
return $this->applyAfterQueryCallbacks($this->newModelInstance()->newCollection([$model]))->first();
904+
})->reject(fn ($model) => is_null($model));
865905
}
866906

867907
/**
@@ -900,9 +940,11 @@ public function pluck($column, $key = null)
900940
return $results;
901941
}
902942

903-
return $results->map(function ($value) use ($column) {
904-
return $this->model->newFromBuilder([$column => $value])->{$column};
905-
});
943+
return $this->applyAfterQueryCallbacks(
944+
$results->map(function ($value) use ($column) {
945+
return $this->model->newFromBuilder([$column => $value])->{$column};
946+
})
947+
);
906948
}
907949

908950
/**

src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,9 @@ public function get($columns = ['*'])
883883
$models = $builder->eagerLoadRelations($models);
884884
}
885885

886-
return $this->related->newCollection($models);
886+
return $this->query->applyAfterQueryCallbacks(
887+
$this->related->newCollection($models)
888+
);
887889
}
888890

889891
/**

src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,9 @@ public function get($columns = ['*'])
505505
$models = $builder->eagerLoadRelations($models);
506506
}
507507

508-
return $this->related->newCollection($models);
508+
return $this->query->applyAfterQueryCallbacks(
509+
$this->related->newCollection($models)
510+
);
509511
}
510512

511513
/**

src/Illuminate/Database/Query/Builder.php

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,13 @@ class Builder implements BuilderContract
208208
*/
209209
public $beforeQueryCallbacks = [];
210210

211+
/**
212+
* The callbacks that should be invoked after retrieving data from the database.
213+
*
214+
* @var array
215+
*/
216+
protected $afterQueryCallbacks = [];
217+
211218
/**
212219
* All of the available clause operators.
213220
*
@@ -2770,6 +2777,34 @@ public function applyBeforeQueryCallbacks()
27702777
$this->beforeQueryCallbacks = [];
27712778
}
27722779

2780+
/**
2781+
* Register a closure to be invoked after the query is executed.
2782+
*
2783+
* @param \Closure $callback
2784+
* @return $this
2785+
*/
2786+
public function afterQuery(Closure $callback)
2787+
{
2788+
$this->afterQueryCallbacks[] = $callback;
2789+
2790+
return $this;
2791+
}
2792+
2793+
/**
2794+
* Invoke the "after query" modification callbacks.
2795+
*
2796+
* @param mixed $result
2797+
* @return mixed
2798+
*/
2799+
public function applyAfterQueryCallbacks($result)
2800+
{
2801+
foreach ($this->afterQueryCallbacks as $afterQueryCallback) {
2802+
$result = $afterQueryCallback($result) ?: $result;
2803+
}
2804+
2805+
return $result;
2806+
}
2807+
27732808
/**
27742809
* Get the SQL representation of the query.
27752810
*
@@ -2884,9 +2919,9 @@ public function get($columns = ['*'])
28842919
return $this->processor->processSelect($this, $this->runSelect());
28852920
}));
28862921

2887-
return isset($this->groupLimit)
2888-
? $this->withoutGroupLimitKeys($items)
2889-
: $items;
2922+
return $this->applyAfterQueryCallbacks(
2923+
isset($this->groupLimit) ? $this->withoutGroupLimitKeys($items) : $items
2924+
);
28902925
}
28912926

28922927
/**
@@ -3114,11 +3149,13 @@ public function cursor()
31143149
$this->columns = ['*'];
31153150
}
31163151

3117-
return new LazyCollection(function () {
3152+
return (new LazyCollection(function () {
31183153
yield from $this->connection->cursor(
31193154
$this->toSql(), $this->getBindings(), ! $this->useWritePdo
31203155
);
3121-
});
3156+
}))->map(function ($item) {
3157+
return $this->applyAfterQueryCallbacks(collect([$item]))->first();
3158+
})->reject(fn ($item) => is_null($item));
31223159
}
31233160

31243161
/**
@@ -3167,9 +3204,11 @@ function () {
31673204

31683205
$key = $this->stripTableForPluck($key);
31693206

3170-
return is_array($queryResult[0])
3207+
return $this->applyAfterQueryCallbacks(
3208+
is_array($queryResult[0])
31713209
? $this->pluckFromArrayColumn($queryResult, $column, $key)
3172-
: $this->pluckFromObjectColumn($queryResult, $column, $key);
3210+
: $this->pluckFromObjectColumn($queryResult, $column, $key)
3211+
);
31733212
}
31743213

31753214
/**

0 commit comments

Comments
 (0)