El helper tap()
Laravel 5.3 introdujo este útil método que básicamente sirve para ejecutar una acción sobre un valor y devolver este valor, evitando la necesidad de emplear variables temporales. Puede que no tenga mucho sentido descrito de esa manera… pero vayamos con un ejemplo. Imaginemos que queremos actualizar el título de un post en específico desde el controlador. Normalmente haríamos lo siguiente:
$post = Post::find(25);$post->update(['title' => 'Mi nuevo título']);
return $post->fresh();¿Cómo lo haríamos utilizando tap()? Pues así:
return tap(Post::find(25), function ($post) { $post->update(['title' => 'Mi nuevo título']);});Entonces, ¿cuál es realmente el beneficio? Ahorrarnos el crear una variable temporal para almacenar $post. Si nos fijamos, en el primer argumento colocamos Post::find(25) y este se pasará al callback que estamos pasando como segundo argumento. Lo interesante es que en este callback podemos hacer cualquier operación, pues siempre se retornará el valor del primer argumento.
¿Cómo funciona?
Analicemos cómo funciona esto. Ignoremos las líneas comentadas por el momento, luego las explicaremos:
function tap($value, $callback = null){ // if (is_null($callback)) { // return new HigherOrderTapProxy($value); // }
$callback($value);
return $value;}Como puedes ver, simplemente toma la función $callback que pasamos como segundo parámetro y la aplica sobre la variable que pasamos como primer parámetro, retornando finalmente esta variable en cuestión.
Mensaje de orden superior
Ahora, ¿qué son esas líneas comentadas? Lo que hacen es verificar si el segundo parámetro es null:
function tap($value, $callback = null){ if (is_null($callback)) { return new HigherOrderTapProxy($value); }
$callback($value);
return $value;}Si esto es así, se retornará una instancia de la clase HigherOrderTapProxy (Laravel 5.4+), que no es más que una implementación de los mensajes de orden superior que ya habíamos visto en las colecciones en un artículo anterior. Entonces, aplicando esto a nuestro ejemplo inicial, veremos que podemos simplificarlo a lo siguiente:
return tap(Post::find(25))->update(['title' => 'Mi nuevo título']);Interesante, ¿cierto? Principalmente porque la función update() retorna un booleano; sin embargo, dado que estamos usando tap(), se retornará el valor inicial, por ende, una instancia de Post con id=25.
Ejemplos de uso
A lo largo de su código fuente, Laravel lo implementa en muchos lugares. Lo podemos encontrar, por ejemplo, en el método create() de Eloquent:
public function create(array $attributes = []){ return tap($this->newModelInstance($attributes), function ($instance) { $instance->save(); });}Así es como lucía antes de implementar tap():
public function create(array $attributes = []){ $instance = $this->newModelInstance($attributes);
$instance->save();
return $instance;}Otra aplicación de este útil helper está en el middleware AuthenticateSession:
public function handle($request, Closure $next){ return tap($next($request), function () use ($request) { $this->storePasswordHashInSession($request); });}Así es como lucía antes de tap():
public function handle($request, Closure $next){ $response = $next($request);
$this->storePasswordHashInSession($request);
return $response;}Este caso es interesante, pues aquí no se está efectuando ninguna operación sobre el valor pasado a la función tap. Esto nos dice que podemos hacer lo que deseemos dentro del $callback que pasamos a tap, aún con el beneficio de ahorrarnos las variables temporales (en este caso, la de $response).
tap() en colecciones
Este método también está disponible para las colecciones. Es particularmente útil aquí para efectuar operaciones sobre la colección sin necesidad de interrumpir su flujo. Veamos un ejemplo tomado de la documentación:
collect([2, 4, 3, 1, 5]) ->sort() ->tap(function ($collection) { Log::debug('Valores después de ordenar', $collection->values()->toArray()); }) ->shift();
// 1Como podemos apreciar, estamos incluyendo tap() para loggear los valores de la colección luego de haber sido ordenados. Esto no afecta para nada el resto del proceso que hemos detallado, en este caso la aplicación del método shift().
Cierre
Personalmente, considero que vale la pena emplear métodos, formas y técnicas que nos permitan escribir un código más eficiente y/o legible. Hay algunas personas que consideran el uso de tap como un paso atrás, pues aparentemente dificulta la legibilidad y les hace la tarea más difícil a los IDEs; sin embargo, este no ha sido mi caso y espero poder aplicarlo cada vez que pueda.
Varios de estos ejemplos vienen de Tap, Tap, Tap, el post de Taylor Otwell sobre este helper.