Laravel tap vs tap vs pipe

If you are wondering having tap 2 times in the article title is a mistake, apparently it is not. Laravel has large set of helper functions which are generic and helper functions specific to collections. Tap and pipe are very popular amongst laravel enthusiasts. Let's dive into each of of those to see what really makes them special and what differentiates them :

The tap() function :

The reason of having tap 2 times in the article header is that, there are 2 implementations of tap helper function in laravel. One is in generic context and one specific to handling collections.

  • The generic tap() function :

The name tap suggests that it basically does set of actions using the value and then returns the value as if nothing happened. In other words, the tap function accepts two arguments $value and a Closure. The $value will be passed to the closure function. You can perform your actions in the closure where the return value of the Closure is irrelevant. At the end tap returns the same $value at the end.

For example :

$user = tap($user, function ($user) {

    $user->notify(new SendWelcomeEmail());

});

In the above snippet, we are passing $user instance to tap as first argument which is then cascaded as a parameter to closure. The closure will execute and basically send a new welcome email. Then control will come back to tap which will return the $user instance back.

Even you can just eliminate the closure and call function on it directly like below.

$user = tap($user)->notify(new SendWelcomeEmail());

The above will still return the user instance. The beauty of it is, you can chain the tap calls. Let's say you need to update the user role, then send the welcome email and then assign user some permissions. You can chain it all like :

$user = tap($user)->update([
    'role' => 'Customer' 
])
->notify(new SendWelcomeEmail())
->assignPermissions('CustomerRole');

The use of this method can be handy when certain methods do not return the object as you want. A great example is the update method itself on the eloquent model. It returns integer when used otherwise. But if used as above with tap function, it returns the object it performed the update.

  • The collection tap() function :

The tap function for collection can be used in collection function chaining. I think of it similar to javascript debug breakpoints. You pin it to print something at a point of time in your javascript and then move ahead after removing the breakpoint.

For example consider following example :


$customers = collect([ ['name' => 'mihir', 'is_active' => true, 'category' => 'gold'], ['name' => 'george', 'is_active' => true, 'category' => 'silver'], ['name' => 'nathen', 'is_active' => false, 'category' => 'platinum'], ['name' => 'steve', 'is_active' => true, 'category' => 'gold'], ['name' => 'shawn', 'is_active' => false, 'category' => 'gold'] ]); $activeGoldCustomers = collect($customers) ->where('is_active', true) ->tap(function ($activeCustomers) { $this->sendActiveCustomerEmail($activeCustomers); }) ->where('category', 'gold') ->tap(function ($goldCustomers) { $this->sendOfferCode('100%Discount'); });

In above example, we are just returning the customers who are currently active and fall under category gold. However, using the tap function we can perform certain actions on the go as we filter those. When we filtered for is_active = true, we sent activation email. Then when we added another filter for category = gold, we sent offer code just for active customer from gold category.

You can also find this handy in dumping data for debugging during the developement cycle.

The fundamental concept of tap() is just use the value and perform actions but NEVER CHANGE the value. If you think then what can change the value? pipe() is the answer.

The pipe() function :

Pipe function is just applicable for collections. For example consider following example :


$customers = collect([ ['name' => 'mihir', 'is_active' => true, 'category' => 'gold'], ['name' => 'george', 'is_active' => true, 'category' => 'silver'], ['name' => 'nathen', 'is_active' => false, 'category' => 'platinum'], ['name' => 'steve', 'is_active' => true, 'category' => 'gold'], ['name' => 'shawn', 'is_active' => false, 'category' => 'gold'] ]); $counts = collect($customers) ->pipe(function($customers) { return [ 'active_count' => $customers->where('is_active', true)->count(), 'gold_count' => $customers->where('category', 'gold')->count(), 'silver_count' => $customers->where('category', 'silver')->count(), 'platinum_count' => $customers->where('category', 'platinum')->count() ]; });

Above will basicaly return [3, 3, 1, 1], but it is very clean and less code is always better.

Please note that the pipe() helper method is different than the laravel pipelines. However, if you read about pipelines, its is kind of similar conceptually to generic tap function where you can pass an object through multiple classes to perform set of task and then return the result value once all the tasks have been finished. A handy article for the same is here if you want a quick read.

Even if these methods are very handy, sometimes if you chain it to much it loses the element fo readability. I will suggest it is preferable to follow what the domain logic wants and group the similar processes together to tap or pipe.

 
 
By : Mihir Bhende Categories : laravel, php Tags : laravel, php, helpers, collections