The tap() helper
Laravel 5.3 introduced this handy helper, which basically lets you run an action on a value and then return that same value, no temporary variables needed. Put like that it may not make a lot of sense… so let’s go with an example. Say we want to update the title of a specific post from a controller. Normally we’d do this:
$post = Post::find(25);$post->update(['title' => 'My new title']);
return $post->fresh();How would we do it with tap()? Like this:
return tap(Post::find(25), function ($post) { $post->update(['title' => 'My new title']);});So, what’s the actual benefit? We skip creating a temporary variable to hold $post. Notice that we pass Post::find(25) as the first argument, and it gets handed to the callback we pass as the second one. The nice part is that we can do whatever we want inside that callback, because tap() always returns the value of the first argument.
How does it work?
Let’s take it apart. Ignore the commented-out lines for now, we’ll get to them in a minute:
function tap($value, $callback = null){ // if (is_null($callback)) { // return new HigherOrderTapProxy($value); // }
$callback($value);
return $value;}As you can see, it simply takes the $callback we pass as the second parameter, calls it with the variable we pass as the first one, and finally returns that same variable.
Higher order messages
Now, what about those commented-out lines? They check whether the second parameter is null:
function tap($value, $callback = null){ if (is_null($callback)) { return new HigherOrderTapProxy($value); }
$callback($value);
return $value;}If it is, you get back an instance of HigherOrderTapProxy (Laravel 5.4+), which is just an implementation of the higher order messages we already saw with collections in an earlier post. Applying that to our first example, we can boil it down to this:
return tap(Post::find(25))->update(['title' => 'My new title']);Interesting, right? Mostly because update() returns a boolean, but since we’re going through tap(), what comes back is the initial value instead: a Post instance with id=25.
In the wild
Laravel uses it all over its own source code. You’ll find it, for example, in Eloquent’s create() method:
public function create(array $attributes = []){ return tap($this->newModelInstance($attributes), function ($instance) { $instance->save(); });}This is how it looked before tap() came along:
public function create(array $attributes = []){ $instance = $this->newModelInstance($attributes);
$instance->save();
return $instance;}Another place this handy helper shows up is the AuthenticateSession middleware:
public function handle($request, Closure $next){ return tap($next($request), function () use ($request) { $this->storePasswordHashInSession($request); });}And this is how it looked before tap():
public function handle($request, Closure $next){ $response = $next($request);
$this->storePasswordHashInSession($request);
return $response;}This one is interesting because nothing is done to the value we pass to tap at all. That tells us we can do whatever we like inside the $callback, and still get to skip the temporary variable (here, $response).
tap() on collections
This method is also available on collections, where it’s especially useful for doing something with the collection without breaking the chain. Here’s an example straight from the docs:
collect([2, 4, 3, 1, 5]) ->sort() ->tap(function ($collection) { Log::debug('Values after sorting', $collection->values()->toArray()); }) ->shift();
// 1As you can see, we slip tap() in to log the collection’s values right after sorting them. It doesn’t affect the rest of the chain one bit, in this case the call to shift().
Wrapping up
Personally, I think methods and techniques that help you write more efficient and/or readable code are worth using. Some people see tap as a step backwards, since it supposedly hurts readability and makes life harder for IDEs. That hasn’t been my experience, though, and I plan to use it every chance I get.
Several of these examples come from Tap, Tap, Tap, Taylor Otwell’s post about this helper.