Skip to content

Silent Failure Analyzer

Analyzer IDCategorySeverityTime To Fix
silent-failure⚡ Best PracticesHigh20 minutes

Detects empty catch blocks and error suppression that hide failures, making debugging impossible and masking critical production issues.

What This Checks

The Silent Failure Analyzer identifies three dangerous error handling patterns that hide exceptions and errors, making bugs impossible to debug in production:

Detected Patterns:

  1. Empty Catch Blocks (High Severity)

    • catch (Exception $e) { } - Completely swallows exceptions
    • No logging, no rethrow, no error handling
  2. Catch Without Logging or Rethrow (Medium Severity)

    • catch (Exception $e) { return null; } - Handles exception but doesn't log
    • Missing Log::error(), report(), or throw
    • Silently fails without any trace
  3. Error Suppression Operator (Medium Severity)

    • @file_get_contents($path) - Hides all errors and warnings
    • @mysql_query($sql) - Suppresses error messages
    • Makes debugging impossible when things go wrong

Why It Matters

Silent failures are one of the most dangerous anti-patterns in production applications:

1. Impossible Debugging

When exceptions are swallowed silently, you have no way to know what went wrong:

Before (❌):

php
class PaymentProcessor
{
    public function charge($amount)
    {
        try {
            $result = $this->gateway->charge($amount);
            return $result;
        } catch (Exception $e) {
            // Silent failure - payment fails but no one knows
            return false;
        }
    }
}

// Production: Customers can't checkout, no logs, no alerts, no idea why

After (✅):

php
class PaymentProcessor
{
    public function charge($amount)
    {
        try {
            $result = $this->gateway->charge($amount);
            return $result;
        } catch (Exception $e) {
            // Log the exception for debugging
            Log::error('Payment processing failed', [
                'amount' => $amount,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            // Report to error tracking service
            report($e);

            // Still return false, but now we know what happened
            return false;
        }
    }
}

2. Hidden Security Vulnerabilities

Silent failures can mask security issues:

Before (❌):

php
class AuthService
{
    public function validateToken($token)
    {
        try {
            return JWT::decode($token, $this->key);
        } catch (Exception $e) {
            // Token validation fails silently
            // Attacker has no idea their attack failed
            return null;
        }
    }
}

After (✅):

php
class AuthService
{
    public function validateToken($token)
    {
        try {
            return JWT::decode($token, $this->key);
        } catch (Exception $e) {
            // Log potential security issue
            Log::warning('Invalid JWT token attempted', [
                'token' => substr($token, 0, 20) . '...',
                'ip' => request()->ip(),
            ]);

            return null;
        }
    }
}

3. Data Loss and Corruption

Without error reporting, data operations fail silently:

Before (❌):

php
class DataImporter
{
    public function import($file)
    {
        try {
            $data = $this->parse($file);
            $this->save($data);
        } catch (Exception $e) {
            // Import fails, data is lost, no one knows
        }
    }
}

After (✅):

php
class DataImporter
{
    public function import($file)
    {
        try {
            $data = $this->parse($file);
            $this->save($data);
        } catch (Exception $e) {
            Log::error('Data import failed', [
                'file' => $file,
                'error' => $e->getMessage(),
            ]);

            // Optionally rethrow to bubble up
            throw new ImportException('Failed to import data', 0, $e);
        }
    }
}

How to Fix

Quick Fix (~5 minutes per catch block)

For simple cases, add logging to catch blocks:

Before (❌):

php
class OrderService
{
    public function createOrder($data)
    {
        try {
            return Order::create($data);
        } catch (Exception $e) {
            return null;
        }
    }
}

After (✅):

php
class OrderService
{
    public function createOrder($data)
    {
        try {
            return Order::create($data);
        } catch (Exception $e) {
            Log::error('Order creation failed', [
                'data' => $data,
                'error' => $e->getMessage(),
            ]);

            return null;
        }
    }
}

Steps:

  1. Add use Illuminate\Support\Facades\Log; at the top
  2. Call Log::error() in catch block with context
  3. Include relevant data for debugging
  4. Consider using report($e) for error tracking services

Proper Fix (~20 minutes per class)

For production code, implement comprehensive error handling:

Before (❌):

php
class EmailService
{
    public function send($user, $message)
    {
        try {
            Mail::to($user)->send($message);
        } catch (Exception $e) {
            // Silent failure
        }
    }

    public function readTemplate($path)
    {
        return @file_get_contents($path);
    }
}

After (✅):

php
class EmailService
{
    public function send($user, $message)
    {
        try {
            Mail::to($user)->send($message);
        } catch (Exception $e) {
            // Log with full context
            Log::error('Email sending failed', [
                'user_id' => $user->id,
                'email' => $user->email,
                'message_class' => get_class($message),
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            // Report to error tracking (Sentry, Bugsnag, etc.)
            report($e);

            // Optionally rethrow for critical failures
            if ($message instanceof CriticalNotification) {
                throw $e;
            }
        }
    }

    public function readTemplate($path)
    {
        // Replace error suppression with proper error handling
        if (!file_exists($path)) {
            Log::warning('Email template not found', ['path' => $path]);
            return $this->getDefaultTemplate();
        }

        $content = file_get_contents($path);

        if ($content === false) {
            Log::error('Failed to read email template', ['path' => $path]);
            throw new TemplateException("Cannot read template: {$path}");
        }

        return $content;
    }
}

Steps:

  1. Add comprehensive logging with context
  2. Integrate error tracking (Sentry, Bugsnag, Rollbar)
  3. Replace @ operator with proper error handling
  4. Add graceful fallbacks where appropriate
  5. Rethrow critical exceptions
  6. Document expected exceptions

Configuration

To reduce false positives, configure whitelists in config/shieldci.php:

php
'analyzers' => [
    'best-practices' => [
        'enabled' => true,

        'silent-failure' => [
            // Directories to skip (tests can have empty catches)
            'whitelist_dirs' => [
                'tests',
                'database/seeders',
                'database/factories',
            ],

            // Class patterns to skip
            'whitelist_classes' => [
                '*Test',          // PHPUnit tests
                '*TestCase',      // Test base classes
                '*Seeder',        // Database seeders
                'DatabaseSeeder', // Main seeder
            ],

            // Exception types that are expected and can be safely caught
            'whitelist_exceptions' => [
                'ModelNotFoundException',    // Expected when using findOrFail
                'NotFoundException',          // Custom not found exceptions
                'NotFoundHttpException',      // HTTP 404 exceptions
                'ValidationException',        // Validation failures
            ],

            // Functions where @ operator is acceptable
            'whitelist_error_suppression_functions' => [
                'unlink',            // File might not exist
                'fopen',             // File might not be readable
                'file_get_contents', // File might not exist
                'mkdir',             // Directory might exist
                'rmdir',             // Directory might not exist
            ],
        ],
    ],
],

Common Mistakes to Avoid

1. Logging Without Context

Before (❌):

php
catch (Exception $e) {
    Log::error('Error');  // What error? Where? Why?
}

After (✅):

php
catch (Exception $e) {
    Log::error('Payment processing failed', [
        'order_id' => $order->id,
        'amount' => $amount,
        'gateway' => $gateway,
        'error' => $e->getMessage(),
        'trace' => $e->getTraceAsString(),
    ]);
}

2. Catching Too Broadly

Before (❌):

php
try {
    $user = User::findOrFail($id);
    $this->sendEmail($user);
    $this->logActivity($user);
} catch (Exception $e) {
    // Which operation failed? User lookup, email, or logging?
    Log::error('Something failed');
}

After (✅):

php
try {
    $user = User::findOrFail($id);
} catch (ModelNotFoundException $e) {
    Log::warning('User not found', ['id' => $id]);
    return null;
}

try {
    $this->sendEmail($user);
} catch (Exception $e) {
    Log::error('Email sending failed', [
        'user_id' => $user->id,
        'error' => $e->getMessage(),
    ]);
}

try {
    $this->logActivity($user);
} catch (Exception $e) {
    // Activity logging is non-critical, just log the error
    Log::warning('Activity logging failed', [
        'user_id' => $user->id,
        'error' => $e->getMessage(),
    ]);
}

3. Using @ Instead of Proper Error Handling

Before (❌):

php
$config = @json_decode($json, true);
if ($config === null) {
    // Was it invalid JSON or valid null? Who knows!
    return [];
}

After (✅):

php
try {
    $config = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    Log::error('Invalid JSON configuration', [
        'json' => substr($json, 0, 100),
        'error' => $e->getMessage(),
    ]);

    return [];
}

4. Not Using Error Tracking Services

Before (❌):

php
catch (Exception $e) {
    Log::error($e->getMessage());
    // Logs buried in files, no alerts, no aggregation
}

After (✅):

php
catch (Exception $e) {
    // Use Laravel's report() which integrates with Sentry, Bugsnag, etc.
    report($e);

    // Or use service directly
    \Sentry\captureException($e);

    // Still log locally
    Log::error('Payment failed', [
        'exception' => $e,
        'context' => $additionalContext,
    ]);
}

5. Graceful Fallback Without Logging

Before (❌):

php
try {
    return $this->fetchUserAvatar($userId);
} catch (NotFoundException $e) {
    // Graceful fallback is good, but we should still log
    return $this->getDefaultAvatar();
}

After (✅):

php
try {
    return $this->fetchUserAvatar($userId);
} catch (NotFoundException $e) {
    // Log even for expected exceptions
    Log::info('User avatar not found, using default', [
        'user_id' => $userId,
    ]);

    return $this->getDefaultAvatar();
}

6. Empty Catch for Performance

Before (❌):

php
// "I don't care about errors, just skip invalid items"
foreach ($items as $item) {
    try {
        $this->process($item);
    } catch (Exception $e) {
        // Silent skip
    }
}

After (✅):

php
$skipped = 0;
foreach ($items as $item) {
    try {
        $this->process($item);
    } catch (Exception $e) {
        $skipped++;

        // Log first few errors, then summarize
        if ($skipped <= 5) {
            Log::warning('Item processing failed', [
                'item' => $item,
                'error' => $e->getMessage(),
            ]);
        }
    }
}

if ($skipped > 0) {
    Log::warning("Skipped {$skipped} invalid items during batch processing");
}

Error Reporting Best Practices

1. Use Laravel's report() Helper

Laravel's report() function integrates with error tracking services:

php
catch (Exception $e) {
    // Automatically sends to configured service (Sentry, Bugsnag, etc.)
    report($e);
}

2. Integrate Error Tracking Services

Sentry:

php
catch (Exception $e) {
    \Sentry\captureException($e);

    // Add context
    \Sentry\configureScope(function ($scope) use ($user, $order) {
        $scope->setUser(['id' => $user->id, 'email' => $user->email]);
        $scope->setExtra('order_id', $order->id);
    });
}

Bugsnag:

php
catch (Exception $e) {
    Bugsnag::notifyException($e, function ($report) use ($context) {
        $report->setMetaData(['context' => $context]);
    });
}

3. Use Appropriate Log Levels

php
// CRITICAL: Application is unusable
Log::critical('Database connection lost');

// ERROR: Runtime errors that need immediate attention
Log::error('Payment processing failed', ['order_id' => $order->id]);

// WARNING: Exceptional but handled, should be investigated
Log::warning('Cache server unavailable, using fallback');

// INFO: Interesting events
Log::info('User avatar not found, using default');

// DEBUG: Detailed debug information (development only)
Log::debug('API response', ['response' => $data]);

4. Structure Log Messages

php
Log::error('Operation failed', [
    'operation' => 'payment_processing',
    'user_id' => $user->id,
    'amount' => $amount,
    'currency' => 'USD',
    'gateway' => $gateway,
    'error' => $e->getMessage(),
    'error_code' => $e->getCode(),
    'trace' => $e->getTraceAsString(),
    'timestamp' => now()->toIso8601String(),
]);

References