Introducting the Tappable trait to use 'tap' with any class

July 24, 2019

As you might know, the Laravel framework comes with a handy tap method which allows you to call the given closure with the given value and then return the value. Sounds confusing? It probably is until you've seen it! Let's take a look at this example:

// without tap:
function getServer($id) {
    $server = ServerModel::findOrFail($id);

    Log::debug('Server was found', $server->toArray());

    return $server;
}


// with tap:
function getServer($id) {
    return tap(ServerModel::findOrFail($id), function ($server) {
        Log::debug('Server was found', $server->toArray());
    });
}

Lately we've used a Tappable trait in one of our projects to avoid the global tap helper method. This allowed us to call tap directly on an instance. The example above could be rewritten like this:

function getServer($id) {
    return ServerModel::findOrFail($id)->tap(function ($server) {
        Log::debug('Server was found', $server->toArray());
    });
}

This might look like a small difference but we think it's more readable and it even gets better when the logic increases, for example when you use Eloquent scopes.

// before
tap(ServerModel::active()->paid()->withTrashed()->findOrFail($id), function ($server) {
    Log::debug('Server was found', $server->toArray());
});

// after
ServerModel::active()
    ->paid()
    ->withTrashed()
    ->findOrFail($id)
    ->tap(function ($server) {
        Log::debug('Server was found', $server->toArray());
    });

We've submitted a PR to make this trait available in the illuminate/support repository. Luckily it was accepted and is it's now available in Laravel 5.8.17!