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

ShieldCI Configuration

To configure whitelists, publish the config:

bash
php artisan vendor:publish --tag=shieldci-config

Then 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 (supports namespaced: @\unlink())
            '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
            ],

            // Static methods where @ operator is acceptable (e.g., @Storage::delete())
            // Wildcards supported: 'Storage::*', '*::delete'
            'whitelist_error_suppression_static_methods' => [
                'Storage::delete',          // File might not exist
                'Storage::deleteDirectory', // Directory might not exist
                'File::delete',             // File might not exist
                'File::deleteDirectory',    // Directory might not exist
            ],

            // Instance methods where @ operator is acceptable (e.g., @$file->delete())
            // Matches by method name only. Wildcards supported: 'remove*'
            'whitelist_error_suppression_instance_methods' => [
                'delete',  // Resource deletion
                'close',   // File handle closing
                'unlink',  // File unlinking
            ],
        ],
    ],
],

Error Suppression Whitelist Details

The error suppression whitelist supports three types of calls:

Call TypeExampleConfig Key
Function calls@unlink($path), @\unlink($path)whitelist_error_suppression_functions
Static method calls@Storage::delete($path)whitelist_error_suppression_static_methods
Instance method calls@$file->delete()whitelist_error_suppression_instance_methods

Wildcard patterns are supported in all whitelist keys:

  • Storage::* - matches any method on Storage
  • *::delete - matches delete on any class
  • remove* - matches removeFile, removeAll, etc.

Dynamic calls are always flagged for security reasons:

  • @$func($path) - dynamic function call
  • @$class::method() - dynamic class
  • @$obj->$method() - dynamic method name

References