All posts

Guard Clauses in Laravel: Improving Readability and Making Code More Effective

Guard Clauses in Laravel: Improving Readability and Making Code More Effective

Photo by AltumCode on Unsplash

Introduction

As the world of web development continues to grow, writing clean, maintainable, and efficient code is key. Laravel is one of the most preferred PHP frameworks that support the way one can be able to do so, and one major way is the use of Guard Clauses. This article dives deeper into Guard Clauses to explain what these really are, why they are important, and how they help you make your Laravel code so much more readable and higher in quality. You will see how the clutter starts getting cleared up into a clearer, bigger space.

What are Guard Clauses?

Guard Clauses is a programming practice used in the aspect of good code development, with the intention of enhancing code readability, reducing complexity, by handling conditional cases at the beginning of a function or logic block. This is one of the places where a condition is set to return from a function if met.

This effectively “puts the guard” on the logic core of the function execution under undesired conditions.

Why Use Guard Clauses in Laravel?

The motivation for using guard clauses is to fail early and,

Well, a Laravel developer often comes across such a scenario when more than one condition has to be checked before proceeding to the main logic; this could conventionally be nesting conditions’ structures very deeply, in a way that it makes the code very unreadable and unmaintainable. These structures can be simplified using guard clauses, as every time the edge cases are being handled at the start of every conditional block, hence allowing flattening of the structure to focus on the main logic.

Example 1: Basic Guard Clause Implementation

We’ll see an easy example to demonstrate how we could apply Guard Clauses.

Before:

public function processOrder(Order $order)
{
    if ($order->isPaid()) {
        if (!$order->isShipped()) {
            // Logic for shipping the order
            return response()->json(['message' => 'Order processed successfully'], 200);
        } else {
            return response()->json(['message' => 'Order already shipped'], 400);
        }
    } else {
        return response()->json(['message' => 'Payment required'], 402);
    }
}

After:

public function processOrder(Order $order)
{
    if (!$order->isPaid()) {
        return response()->json(['message' => 'Payment required'], 402);
    }
    
    if ($order->isShipped()) {
        return response()->json(['message' => 'Order already shipped'], 400);
    }

    // Logic for shipping the order
    return response()->json(['message' => 'Order processed successfully'], 200);
}

Example 2: Handling Null Objects

If you need to be precautious about nulls or meet some precondition, use a Guard Clause.

Before:

public function getUserProfile(User $user)
{
    if ($user) {
        if ($user->hasProfile()) {
            // Return user profile
            return response()->json(['message' => $user->profile], 200);
        }
    }
    return response()->json(['message' => 'No profile found'], 404);
}

After:

public function getUserProfile(User $user)
{
    if (!$user || !$user->hasProfile()) {
        return response()->json(['message' => 'No profile found'], 404);
    }

    // Return user profile
    return response()->json(['message' => $user->profile], 200);
}

Example 3: Early Returns for Unauthorized Access

Before:

public function editBlogPost(User $user, Post $post)
{
    if ($user->isAdmin() || $user->isAuthorOfPost($post)) {
        // Logic for editing the post
        return response()->json(['message' => 'Post edited successfully'], 200);
    } else {
        return response()->json(['message' => 'Unauthorized access'], 401);
    }
}

After:

public function editBlogPost(User $user, Post $post)
{
    if (!$user->isAdmin() && !$user’s isAuthorOfPost($post)) {
        return response()->json(['message' => 'Unauthorized access'], 401);
    }

    // Logic for editing the post
    return response()->json(['message' => 'Post edited successfully'], 200);
}

Example 4: Simplifying Complex Conditions

Before:

public function approveTransaction(Transaction $transaction)
{
    if ($transaction->isValid()) {
        if ($transaction->hasSufficientBalance() && !$transaction->isFlagged()) {
            // Approve transaction
            return response()->json(['message' => 'Transaction approved'], 200);
        } else {
            return response()->json(['message' => 'Transaction cannot be approved'], 400);
        }
    } else {
        return response()->json(['message' => 'Invalid transaction'], 400);
    }
}

After:

public function approveTransaction($transaction)
{
    if (!$transaction->isValid()) {
        return response()->json(['message' => 'Invalid transaction'], 400);
    }
    
    if (!$transaction->hasSufficientBalance() || $transaction’s isFlagged()) {
        return response()->json(['message' => 'Transaction cannot be approved'], 400);
    }

    // Approve transaction
    return response()->json(['message' => 'Transaction approved'], 200);
}

Example 5: Complex Nested Conditions

Before:

public function processLoanApplication($applicant)
{
    if ($applicant->age >= 18) {
        if ($applicant->annualIncome >= 30000) {
            if ($applicant->creditScore > 600) {
                if (!$applicant->hasOutstandingLoan) {
                    // Logic to approve loan
                    return response()->json(['message' => 'Loan application approved'], 200);
                } else {
                    return response()->json(['message' => 'Loan application denied: outstanding loan exists'], 400);
                }
            } else {
                return response()->json(['message' => 'Loan application denied: credit score too low'], 400);
            }
        } else {
            return response()->json(['message' => 'Loan application denied: income too low'], 400);
        }
    } else {
        return response()->json(['message' => 'Loan application denied: applicant must be 18 or older'], 400);
    }
}

After:

public function processLoanApplication($applicant)
{
    if ($applicant->age < 18) {
        return response()->json(['message' => 'Loan application denied: applicant must be 18 or older'], 400);
    }
    if ($applicant->annualIncome < 30000) {
        return response()->json(['message' => 'Loan application denied: income too low'], 400);
    }
    if ($applicant->creditScore <= 600) {
        return response()->json(['message' => 'Loan application denied: credit score too low'], 400);
    }
    if ($applicant->hasOutstandingLoan) {
        return response()->json(['message' => 'Loan application denied: outstanding loan exists'], 400);
    }
    // Logic to approve loan
    return response()->json(['message' => 'Loan application approved'], 200);
}

Originally published on Medium