April 15, 2018 —John Koster
The signature of the tap
function is:
1function tap(2 $value,3 $callback = null4);
The tap
helper function is used to call a given Closure (provided as an argument to the $callback
parameter) with the given $value
. The tap
helper function will return the reference to the original $value
as its return value. This allows you to call any number of methods with any type of return values within the $callback
while maintaining a reference to the original object. This behavior is particularly useful during method chaining. Another way to think of this functions behavior is that it allows you to specify and fix the return value of a series of method calls, regardless of what methods are called within the internal block.
The tap
helper function was added in Laravel version 5.3 and defined in the src/Illuminate/Support/helpers.php
file. This function can easily be integrated into older Laravel code bases as it does not depend on any new fundamental framework components.
The following example PHP classes will be used to demonstrate a trivial usage of the tap
helper function. The classes represent a crude "talker" system where one can add messages and retrieve the final message back:
In app/Message.php
:
1<?php 2 3namespace App; 4 5class Message 6{ 7 8 /** 9 * The actual message.10 *11 * @var string12 */13 protected $message = '';14 15 /**16 * Gets the original message.17 *18 * @return string19 */20 public function getOriginal()21 {22 return $this->message;23 }24 25 /**26 * Changes the message.27 *28 * @param $message29 * @return Message30 */31 public function change($message)32 {33 $this->message = $message;34 35 return $this;36 }37 38 public function __toString()39 {40 return strtolower($this->message);41 }42 43}
The Message
class if fairly simple. It is simply a value object that wraps PHP's native string data type. It provides a few methods that can be used to retrieve the original value and change (mutate) the internal value of the message. When the message is cast back to a string it is converted to its lower cased variant. The important thing to notice is that the change
method returns a reference back to itself.
In app/Talker.php
:
1<?php 2 3namespace App; 4 5class Talker 6{ 7 8 /** 9 * The actual parts of the message.10 *11 * @var array12 */13 protected $messageParts = [];14 15 /**16 * Adds a message part to the end of the message.17 *18 * @param $message19 * @return Message20 */21 public function atEnd(Message $message)22 {23 array_push($this->messageParts, $message);24 return $message;25 }26 27 /**28 * Adds a message part to the start of the message.29 *30 * @param $message31 * @return Message32 */33 public function atBeginning(Message $message)34 {35 array_unshift($this->messageParts, $message);36 return $message;37 }38 39 /**40 * Returns the string representation of the message.41 *42 * @return string43 */44 public function talk()45 {46 return ucfirst(47 implode(' ', $this->messageParts)48 );49 }50 51}
The Talker
class is also very simple. It maintains an array of Message
instances and allows you to add them to either the beginning or ending of the array of messages. In addition, it allows you to retrieve a string made up of all the individual messages with the first character upper cased. Annoyingly the atEnd
and atBeginning
methods return the Message
instance instead of the Talker
instance, which makes chaining rather difficult.
The following example will create a simple helper function to remove some of the steps of using the Talker
and Message
APIs:
1<?php 2 3use App\Talker; 4use App\Message; 5 6/** 7 * Say something. 8 * 9 * string $message10 * @return string11 */12function saySomething($message) {13 $talker = new Talker;14 $talker->atBeginning(new Message)->change($message);15 return $talker->talk();16}
Using the new helper function might look like this:
1<?php2 3echo saySomething('Hello World');
The user would see the following text in the browser:
1Hello world
It would be nice if we could return the value of the method chain from our function like so:
1<?php 2 3// ... 4 5function saySomething($message) { 6 $talker = new Talker; 7 return $talker-> 8 atBeginning(new Message) 9 ->change($message)->talk();10}11 12// ...
However, that code would not work. This is because the atBeginning
method on the Talker
class returns an instance of Message
. The talk
method does not exist on the Message
class, and if it did would probably not have the same behavior as the Talker
instance method. We can use the tap
function to get around this quite easily:
1<?php 2 3// ... 4 5function saySomething($message) { 6 $talker = new Talker; 7 return tap($talker, function($t) use ($message) { 8 $t->atBeginning(new Message)->change($message); 9 })->talk();10}11 12// ...
In the previous example the Closure defines a parameter named $t
. When the tap
function executes, the value of $talker
will be used as the inner value of $t
and they will refer to the same Talker
instance. The following diagram visually demonstrates the flow of data in the tap
helper function.
In the above example we are using the tap
function to keep our method chain going. In addition we are passing the $message
parameter into the inner callback function so that we can use it. The above example could actually be simplified further by using the with
helper function to return a new instance of Talker
:
1<?php 2 3// ... 4 5function saySomething($message) { 6 return tap(with(new Talker), function($t) use ($message) { 7 $t->atBeginning(new Message)->change($message); 8 })->talk(); 9}10 11// ...
The previous example is very contrived but does demonstrate a fairly basic use of the tap
helper function. More common use cases might be when passing around Eloquent models with extensive method chaining.
The following example will make use of the Eloquent model factory builder to create a new instance, modify an attribute, save the model and then return the created model instance:
1<?php2 3use App\User;4 5$user = tap(factory(User::class)->make(), function($user) {6 $user->setAttribute('name', 'Not such a random name')7 ->save();8});
After the above example has executed, the attributes of the $user
model instance might look similar to the following output:
1array [2 "name" => "Not such a random name"3 "email" => "Batz.Bridget@example.com"4 "password" => "$2y$10$aXsXCEx3pkgm..."5 "remember_token" => "nayR2BKwaQ"6 "updated_at" => "2016-06-03 22:29:01"7 "created_at" => "2016-06-03 22:29:01"8 "id" => 79 ]
The actual attribute values will differ as they are randomly assigned a value from the model factory. Because of the way we used the tap
helper function we were able to modify the name
attribute elegantly, save the model and get the exact return value we wanted.
tap
to Proxy Method CallsIf the $callback
parameter is omitted when invoking the tap
helper function, it will allow you to proxy method calls to the provided value. This is different from the with
helper function; instead of returning the return value of the method call, the tap
function will return the original $value
.
Imagine we had a class that required setup before it can be used, such as initializing a connection. Imagine we were interacting with a class like the example DatabaseConnection
class below:
1class DatabaseConnection { 2 3 /** 4 * The PDO Connection. 5 * 6 * @var PDO 7 */ 8 protected $connection; 9 10 /**11 * Establishes a connection with a database server.12 *13 * @param string $connectionString14 * @param string $userName15 * @param string $password16 */17 public function connect(18 $connectionString,19 $userName,20 $password21 )22 {23 $this->connection = new PDO(24 $connectionString,25 $userName,26 $password27 );28 }29 30 /**31 * Executes an SQL statement, returning a result set.32 *33 * @param string $query34 * @return PDOStatement35 */36 public function query($query)37 {38 return $this->connection->query($query);39 }40 41 /**42 * Closes the existing database server connection.43 */44 public function close()45 {46 $this->connection = null;47 }48 49}
The designers of the DatabaseConnection
class did not make their API fluent, meaning that we cannot instantiate the database connection and connect to the database server in one step; our code might look similar to the following:
1$connection = new DatabaseConnection;2$connection->connect(3 'mysql:host=localhost;dbname=forge',4 'username',5 'password'6 );7 8// Interact with the database server.
By leveraging method proxies using the tap
function, we could rewrite that code to the following:
1$connection = tap(new DatabaseConnection)->connect(2 'mysql:host=localhost;dbname=forge',3 'username',4 'password'5 );6 7// Interact with the database server.
∎
The following amazing people help support this site and my open source projects ♥️
If you're interesting in supporting my work and want to show up on this list, check out my GitHub Sponsors Profile.