As developers, we're always trying to find ways to write cleaner, more efficient, and more maintainable code. As Laravel developers, we're incredibly lucky to have a framework that has an abundance of helper functions and fluent methods to help us do just that.
But I think it's safe to say that most Laravel developers, intentionally or not, write more code than they need to on a regular basis. We see it all the time in our day-to-day work: extra if/else statements, entire custom helper classes for things Laravel already does for us, etc.
In this post, we're going to be talking about some of these common (yet rarely used) Laravel features such as when(), unless(), tap(), rescue(), retry(), data_get(), and filled(). We will also be taking a look at how using these features can improve our code quality and make us more productive as Laravel developers.
Why Knowing Your Framework Matters
Before jumping into the list of helpers, let's understand why knowing them is important in the first place. Laravel is a framework that values developer productivity. It means that its API is designed in a way that lets you write less code while keeping it expressive. The same goes for helpers — they're not just convenient, they're the result of years of experience and hard-earned lessons. Whenever you can use an existing solution, it's better to use that instead of inventing your own wheels. Not doing that might lead to:
- Creating buggy code
- Poorly documented code that other Laravel developers will have a harder time reading
- Missing out on performance gains
- Having to write code that already exists elsewhere
By using Laravel's built-in features, you'll be able to write more maintainable code and reduce the surface area for errors.
Conditional Execution with when() and unless()
We must have written conditional queries inside database query to apply filter logics in our career something as shown below.
Traditional if/else for conditional queries
Consider adding a where clause to a query based on a condition:
// Common way
$users = User::query();
if ($request->has('role')) {
$users->where('role', $request->input('role'));
}
if ($request->has('search')) {
$users->where('name', 'like', '%' . $request->input('search') . '%');
}
$filteredUsers = $users->get();
Using when() for cleaner conditional logic
Laravel's when() method lets you apply query constraints or take any other action conditionally. It receives a boolean condition as its first parameter and a closure as its second. The closure is called only if the given condition evaluates to true.
// Using when()
$filteredUsers = User::query()
->when($request->has('role'), function ($query) use ($request) {
$query->where('role', $request->input('role'));
})
->when($request->has('search'), function ($query) use ($request) {
$query->where('name', 'like', '%' . $request->input('search') . '%');
})
->get();
unless(): The inverse of when()
The unless() is just the opposite of when(). when the callback is not satisfied then only it execute the logical block.
// Using unless()
$articles = Article::query()
->unless($request->has('published'), function ($query) {
// If 'published' is not present in request, only show published articles
$query->where('is_published', true);
})
->get();
Tapping into Method Chains with tap()
Sometimes you need to perform an operation on an object within a method chain, but without affecting the chain itself. This is where tap() comes in picture.
Traditional approach with temporary variables
// Traditional way: breaking the chain
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt($request->password),
]);
event(new UserCreated($user)); // Perform an action
$user->assignRole('customer'); // Continue with the object
return $user;
Using tap() for fluent actions
The tap() helper accepts a value and passes it to a closure, then returns the value. This allows you to "tap into" an object mid-chain to perform side effects without breaking the fluency.
// Using tap()
return User::create([
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt($request->password),
])->tap(fn ($user) => event(new UserCreated($user))) // Tap into the object to dispatch event
->assignRole('customer'); // Continue with the chained method
This keeps our logic clean and readable.
Graceful Error Handling with rescue()
Dealing with exceptions is one of the most important part of the development process. The rescue() helper makes it easier by allowing you to catch exceptions and return a default value or perform a cleanup task, thus avoiding the need for lengthy try...catch blocks.
Traditional try/catch
// Traditional try/catch
try {
$order = Order::findOrFail($orderId);
$order->processPayment();
return $order->status;
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
Log::warning('Order not found: ' . $orderId);
return 'not_found';
} catch (\Stripe\Exception\CardException $e) {
Log::error('Payment failed for order ' . $orderId . ': ' . $e->getMessage());
return 'payment_failed';
}
Using rescue()
The rescue() function takes a closure as an argument, executes it, and catches any exceptions during its execution. If an exception occurred, it returns the default value you have specified as the second argument. You can also pass a third closure to report the error.
// Using rescue()
$status = rescue(function () use ($orderId) {
$order = Order::findOrFail($orderId);
$order->processPayment();
return $order->status;
}, 'not_found', function (Throwable $e) use ($orderId) {
// This callback is executed only if an exception occurs
Log::error('An error occurred processing order ' . $orderId . ': ' . $e->getMessage());
});
return $status;
rescue() makes error handling very compact in cases where you simply want to return a default or log an error without complex recovery logic.
Retrying Failed Operations with retry()
Sometimes you may need to retry an operation that failed due to an external service, request, or something that may retry after a short delay. Laravel's retry() helper makes it easy to do.
Manual retry logic
// Manual retry loop
$attempts = 0;
$maxAttempts = 3;
$result = null;
while ($attempts < $maxAttempts) {
try {
$result = Http::get('https://api.example.com/data');
break; // Success, exit loop
} catch (\Exception $e) {
$attempts++;
if ($attempts < $maxAttempts) {
sleep(1); // Wait for 1 second before retrying
} else {
throw $e; // Re-throw if all attempts fail
}
}
}
return $result;
Using retry()
The retry() function take the number of retries, the time between retries (in milliseconds or a callback) and a closure to execute. You can also pass a fourth argument to indicate which Exceptions cause the retry.
// Using retry()
use Illuminate\Http\Client\ConnectionException;
$response = retry(3, function () {
// This closure will be attempted 3 times
return Http::get('https://api.example.com/data')->json();
}, 100, function (Exception $exception) {
// Only retry for specific exceptions
return $exception instanceof ConnectionException;
});
return $response;
This greatly reduces the amount of code in your script needed to make robust external API calls or any other operation that has the possibility of failing unexpectedly. The third argument (100) specifies a 100 millisecond wait between retries.
Accessing Nested Data with data_get()
Accessing deeply nested data structures with optional keys or values can result in ugly "Undefined index" or "Trying to get property of non-object" errors, especially if you're not sure if the key or array element exists, or if it's an object, array or some other type. The function data_get() will help you avoid these errors.
Verbose checks for nested data
// Manual check for nested data
$data = [
'user' => [
'profile' => [
'address' => '123 Main St',
'city' => 'Anytown'
],
'email' => 'test@example.com'
]
];
$address = null;
if (isset($data['user']) && isset($data['user']['profile']) && isset($data['user']['profile']['address'])) {
$address = $data['user']['profile']['address'];
}
$zip = null;
if (isset($data['user']) && isset($data['user']['profile']) && isset($data['user']['profile']['zip_code'])) {
$zip = $data['user']['profile']['zip_code'];
} else {
$zip = 'N/A';
}
echo $address; // 123 Main St
echo $zip; // N/A
Using data_get()
The data_get() helper retrieves a value from a nested array or object using "dot" notation. If the specified key does not exist, a default value is returned.
// Using data_get()
$data = [
'user' => [
'profile' => [
'address' => '123 Main St',
'city' => 'Anytown'
],
'email' => 'test@example.com'
]
];
$address = data_get($data, 'user.profile.address'); // 123 Main St
$zip = data_get($data, 'user.profile.zip_code', 'N/A'); // N/A (default value)
echo $address;
echo $zip;
This is incredibly useful when processing API responses or dynamic configurations, saving you from writing lengthy conditional checks.
Checking for Non-Empty Values with filled()
Often, we need to check if a variable or a request input has a value that is not merely present, but also not empty. Laravel's filled() helper simplifies this.
Traditional empty/isset checks
// Traditional checks
$name = $request->input('name');
$email = '';
$age = 0;
if (isset($name) && !empty($name)) {
// $name has a real value
}
if (isset($email) && !empty($email)) {
// This will be false for empty string
}
if (isset($age) && !empty($age)) {
// This will be false for 0
}
Using filled()
The filled() helper checks if a given value is not blank/null, not empty string/array and not 0. It acts like !empty() but is used in case when we need to make sure that some value has been actually passed.
// Using filled()
$name = $request->input('name'); // e.g., 'John Doe'
$email = '';
$age = 0;
$city = null;
if (filled($name)) {
echo "Name is present and meaningful."; // This will execute
}
if (filled($email)) {
echo "Email is present and meaningful."; // This will NOT execute
}
if (filled($age)) {
echo "Age is present and meaningful."; // This will NOT execute
}
if (filled($city)) {
echo "City is present and meaningful."; // This will NOT execute
}
$country = 'India';
if (filled($country)) {
echo "Country is present and meaningful."; // This will execute
}
filled() is particularly useful for validating user inputs or processing optional data fields where an empty string or 0 might be considered "not present."
Conclusion
Laravel is an amazing productivity booster, and a big part of this is due to the useful helpers and fluent APIs that the framework introduces. By employing the methods such as when(), unless(), tap(), rescue(), retry(), data_get(), filled() and others in our day-to-day development, we can make our code concise, readable, and closer to the Laravel style.
Being a proficient Laravel developer means being able to deliver business logic effectively and utilize the framework to the fullest. And one way to do that is to constantly learn new Laravel specific patterns and techniques. So, next time you need to implement a certain functionality, think about using Laravel Helpers before jumping into creating new if/else statements or building a utility class.
Thank you for reading this article 😊
For any query, do not hesitate to comment 💬
-compressed.jpg)