Helper Function Abuse Analyzer
| Analyzer ID | Category | Severity | Time To Fix |
|---|---|---|---|
helper-function-abuse | ✨ Best Practices | Low | 25 minutes |
What This Checks
Detects excessive use of Laravel helper functions that hide dependencies and violate SOLID principles. Tracks:
- Helper function calls in classes/traits: Counts calls to common Laravel helpers like
auth(),request(),cache(),config(),session(), etc. - Threshold violations: Flags classes exceeding the configured threshold (default: 5 helpers)
- Severity escalation: Low severity for moderate violations, Medium for 10+ over threshold, High for 20+ over threshold
- Per-helper tracking: Shows which specific helpers are used and how many times
Tracked Helpers (36 by default): app, auth, cache, config, cookie, event, logger, old, redirect, request, response, route, session, storage_path, url, view, abort, abort_if, abort_unless, bcrypt, collect, dd, dispatch, info, now, optional, policy, resolve, retry, tap, throw_if, throw_unless, today, validator, value, report
Why It Matters
- Hidden Dependencies: Helper functions hide what a class actually depends on, making dependencies invisible
- Testing Difficulty: Impossible to mock or stub helper functions, making unit testing extremely difficult
- Violates SOLID: Breaks Dependency Inversion Principle by depending on global functions instead of abstractions
- Maintenance Burden: Difficult to track what services a class uses when they're hidden behind helpers
- Coupling: Creates tight coupling to Laravel's global scope instead of explicit dependencies
- Refactoring Resistance: Hard to refactor when dependencies aren't explicit in constructors
Real-world impact:
- Controllers with 10+ helper calls become untestable without full application bootstrap
- Services using helpers can't be instantiated independently for unit testing
- Code reviews miss dependency violations because helpers hide them
- Refactoring becomes risky without comprehensive integration tests
How to Fix
Quick Fix (10 minutes)
Scenario 1: Inject Common Dependencies
// ❌ BAD - Helper abuse (6 helpers)
class UserController
{
public function update($id)
{
$user = auth()->user();
$data = request()->validate([...]);
cache()->forget("user.{$id}");
event(new UserUpdated($user));
session()->flash('success', 'Updated!');
return redirect()->back();
}
}
// ✅ GOOD - Dependency injection
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
class UserController
{
public function update(Request $request, $id)
{
$user = Auth::user(); // Or inject AuthManager
$data = $request->validate([...]);
Cache::forget("user.{$id}");
event(new UserUpdated($user));
return redirect()->back()
->with('success', 'Updated!');
}
}Scenario 2: Use Facades for Global Services
// ❌ BAD - Helper functions everywhere
class ReportGenerator
{
public function generate()
{
$start = now()->startOfMonth();
$end = now()->endOfMonth();
$data = cache()->remember('report', 3600, function() {
return collect($this->getData())->filter(...);
});
logger()->info('Report generated');
return $data;
}
}
// ✅ GOOD - Use Facades (still testable via fake())
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Carbon\Carbon;
class ReportGenerator
{
public function generate()
{
$start = Carbon::now()->startOfMonth();
$end = Carbon::now()->endOfMonth();
$data = Cache::remember('report', 3600, function() {
return collect($this->getData())->filter(...);
});
Log::info('Report generated');
return $data;
}
}Proper Fix (25 minutes)
Implement comprehensive dependency injection across your application:
1. Constructor Injection for Core Dependencies
// ❌ BAD - Heavy helper usage
class OrderService
{
public function process($order)
{
$user = auth()->user();
if (!$user->can('process', $order)) {
abort(403);
}
cache()->put("order.{$order->id}", $order, 3600);
event(new OrderProcessed($order));
logger()->info("Order {$order->id} processed");
return response()->json($order);
}
}
// ✅ GOOD - Explicit dependencies
use Illuminate\Contracts\Auth\Access\Gate;
use Illuminate\Contracts\Cache\Repository as Cache;
use Illuminate\Contracts\Events\Dispatcher;
use Psr\Log\LoggerInterface;
class OrderService
{
public function __construct(
private Gate $gate,
private Cache $cache,
private Dispatcher $events,
private LoggerInterface $logger
) {}
public function process(User $user, Order $order)
{
if (!$this->gate->allows('process', $order)) {
throw new AuthorizationException();
}
$this->cache->put("order.{$order->id}", $order, 3600);
$this->events->dispatch(new OrderProcessed($order));
$this->logger->info("Order {$order->id} processed");
return $order;
}
}2. Acceptable Helper Usage
Some helpers are fine to use:
// ✅ ACCEPTABLE - View helpers in Blade templates
@foreach($posts as $post)
{{ $post->title }}
<small>{{ $post->created_at->diffForHumans() }}</small>
@endforeach
// ✅ ACCEPTABLE - Collection helpers for data manipulation
public function transform(array $data)
{
return collect($data)
->map(fn($item) => $this->process($item))
->filter()
->values()
->all();
}
// ✅ ACCEPTABLE - Response helpers at controller return
public function store(Request $request)
{
$result = $this->service->create($request->validated());
return response()->json($result, 201); // OK - just for response formatting
}
// ✅ ACCEPTABLE - Route helpers in routes file
Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
Route::redirect('/home', '/dashboard');3. Configuration for Legitimate Thresholds and Functions
Customize the analyzer for your project, publish the config:
php artisan vendor:publish --tag=shieldci-configThen in config/shieldci.php:
'analyzers' => [
'best-practices' => [
'enabled' => true,
'helper-function-abuse' => [
// Threshold before flagging (default: 5)
'threshold' => 5,
// Customize which helpers to track
'helper_functions' => [
'auth', 'request', 'cache', 'config', 'session',
'event', 'logger', 'app', 'resolve',
// Add or remove helpers as needed
],
],
],
],4. Gradual Migration Strategy
For legacy codebases:
// Step 1: Identify worst offenders
// Run analyzer to find classes with 15+ helpers
// Step 2: Start with services layer
// Refactor services first as they're easiest to test
// Step 3: Move to controllers
// Inject Request, use Facades for occasional needs
// Step 4: Use baseline to ignore legacy code
php artisan shield:baseline
// Step 5: Enforce on new code
// Set lower threshold for new classesReferences
- Laravel Dependency Injection - Official guide to DI in Laravel
- SOLID Principles - Dependency Inversion Principle
- Laravel Testing - Why explicit dependencies matter for testing
- Laravel Facades - Alternative to helpers with better testability
- Service Container - Understanding Laravel's DI container
Related Analyzers
- Fat Model Analyzer - Detects models with too much business logic
- Framework Override Analyzer - Detects dangerous framework method overrides
- Config Outside Config - Detects hardcoded configuration values