Framework Override Analyzer
| Analyzer ID | Category | Severity | Time To Fix |
|---|---|---|---|
framework-override | 🏅 Best Practices | High | 120 minutes |
What This Checks
Detects classes that extend Laravel core framework classes instead of using Laravel's own extension points. Checks for:
- Core foundation extensions (High): Classes extending
Foundation\Applicationor the HTTP and ConsoleKernel - HTTP layer extensions (High): Classes extending
Http\Request,Http\Response,Routing\Router, orRouting\UrlGenerator - Database extensions (High): Classes extending
Database\ConnectionorDatabase\Query\Builder - Service extensions (High): Classes extending
AuthManager,CacheManager,Queue\Worker,Validation\Validator, orView\View - Eloquent builder extensions (Medium): Classes extending
Eloquent\Builder, where a query scope or a custom builder returned fromnewEloquentBuilder()is usually the better route - Response subclass extensions (Medium): Classes extending
RedirectResponseorJsonResponse
Why It Matters
- Framework Upgrade Blockers: Extending core classes is the #1 reason Laravel upgrades fail
- Breaking Changes: Core framework classes change frequently between versions without deprecation warnings
- Maintenance Nightmare: Custom overrides require constant updates with each Laravel release
- Hidden Dependencies: Framework internals you depend on may be refactored or removed
- No Guarantee of Compatibility: Laravel doesn't guarantee backward compatibility for extended classes
- Testing Complexity: Overridden framework classes are difficult to test in isolation
Real-world upgrade failures:
- Laravel 9→10:
Requestclass constructor changed, breaking all custom request extensions - Laravel 8→9:
Routerinternals refactored, breaking custom router classes - Laravel 7→8:
Validatorrule handling changed, breaking custom validator extensions - Each major version: Multiple internal changes break framework class extensions
Statistics:
- 80% of failed Laravel upgrades involve custom framework class extensions
- Projects with framework overrides take 5-10x longer to upgrade
- Average time to refactor framework override: 2-4 hours per class
How to Fix
Quick Fix (45 minutes)
Replace framework extensions with Laravel's built-in extension points:
Scenario 1: Custom Request Methods
// ❌ BAD - Extending Request (BREAKS on Laravel upgrades)
namespace App\Http;
use Illuminate\Http\Request;
class CustomRequest extends Request
{
public function getCustomHeader()
{
return $this->header('X-Custom-Header');
}
public function isApiRequest()
{
return $this->is('api/*');
}
}
// ✅ GOOD - Use Request macros (upgrade-safe)
// app/Providers/AppServiceProvider.php
use Illuminate\Http\Request;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Request::macro('getCustomHeader', function () {
return $this->header('X-Custom-Header');
});
Request::macro('isApiRequest', function () {
return $this->is('api/*');
});
}
}
// Usage (same as before)
$header = request()->getCustomHeader();
$isApi = request()->isApiRequest();Scenario 2: Custom Response Methods
// ❌ BAD - Extending Response
use Illuminate\Http\Response;
class CustomResponse extends Response
{
public function withCustomHeader($value)
{
return $this->header('X-Custom', $value);
}
}
// ✅ GOOD - Use Response macros
use Illuminate\Http\Response;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Response::macro('withCustomHeader', function ($value) {
return $this->header('X-Custom', $value);
});
}
}
// Usage in controller
return response()->json($data)->withCustomHeader('value');Proper Fix (120 minutes)
Implement comprehensive refactoring using Laravel's extension points:
1. Eloquent Builder → Use Query Scopes
// ❌ BAD - Extending Eloquent Builder
namespace App\Database;
use Illuminate\Database\Eloquent\Builder;
class CustomBuilder extends Builder
{
public function whereActive()
{
return $this->where('active', true);
}
public function wherePremium()
{
return $this->where('account_type', 'premium');
}
}
// ✅ GOOD - Use query scopes on models
class User extends Model
{
public function scopeActive($query)
{
return $query->where('active', true);
}
public function scopePremium($query)
{
return $query->where('account_type', 'premium');
}
}
// Usage (cleaner and upgrade-safe)
$activeUsers = User::active()->get();
$premiumUsers = User::premium()->get();
$activePremium = User::active()->premium()->get();2. Router → Use Router Macros or Route Service Provider
// ❌ BAD - Extending Router (EXTREMELY DANGEROUS)
namespace App\Routing;
use Illuminate\Routing\Router;
class CustomRouter extends Router
{
public function apiResource($name, $controller)
{
// Custom API routing logic
}
}
// ✅ GOOD - Use Router macros
// app/Providers/RouteServiceProvider.php
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
public function boot()
{
Route::macro('apiResource', function ($name, $controller) {
Route::prefix('api')->group(function () use ($name, $controller) {
Route::get($name, [$controller, 'index']);
Route::post($name, [$controller, 'store']);
Route::get("$name/{id}", [$controller, 'show']);
Route::put("$name/{id}", [$controller, 'update']);
Route::delete("$name/{id}", [$controller, 'destroy']);
});
});
}
}
// Usage in routes/api.php
Route::apiResource('users', UserController::class);3. Validator → Use Custom Validation Rules
// ❌ BAD - Extending Validator
namespace App\Validation;
use Illuminate\Validation\Validator;
class CustomValidator extends Validator
{
public function validatePhone($attribute, $value, $parameters)
{
return preg_match('/^[0-9]{10}$/', $value);
}
}
// ✅ GOOD - Use Validator::extend() in service provider
// app/Providers/ValidationServiceProvider.php
use Illuminate\Support\Facades\Validator;
class ValidationServiceProvider extends ServiceProvider
{
public function boot()
{
Validator::extend('phone', function ($attribute, $value, $parameters, $validator) {
return preg_match('/^[0-9]{10}$/', $value);
});
// Or use Rule objects (even better)
Validator::extend('phone', PhoneValidationRule::class);
}
}
// Usage in FormRequest
public function rules()
{
return [
'phone' => 'required|phone',
];
}4. Query Builder → Use Database Macros
// ❌ BAD - Extending Query Builder
namespace App\Database;
use Illuminate\Database\Query\Builder;
class CustomQueryBuilder extends Builder
{
public function whereRecent($days = 7)
{
return $this->where('created_at', '>=', now()->subDays($days));
}
}
// ✅ GOOD - Use DB::macro() or query scopes
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
Builder::macro('whereRecent', function ($days = 7) {
return $this->where('created_at', '>=', now()->subDays($days));
});
}
}
// Usage
$recentPosts = DB::table('posts')->whereRecent(7)->get();5. Connection → Use Database Events
// ❌ BAD - Extending Connection
namespace App\Database;
use Illuminate\Database\Connection;
class CustomConnection extends Connection
{
public function logQuery($query)
{
// Custom query logging
}
}
// ✅ GOOD - Use database events
use Illuminate\Support\Facades\DB;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
DB::listen(function ($query) {
// Log query: $query->sql, $query->bindings, $query->time
\Log::info('Query executed', [
'sql' => $query->sql,
'bindings' => $query->bindings,
'time' => $query->time,
]);
});
}
}6. Additional Classes - Quick Migration Guide
// ❌ Extending RedirectResponse/JsonResponse
class CustomRedirectResponse extends RedirectResponse { }
class CustomJsonResponse extends JsonResponse { }
// ✅ Use Response macros or return custom objects
Response::macro('success', function ($data) {
return response()->json(['success' => true, 'data' => $data]);
});
// ❌ Extending AuthManager/CacheManager
class CustomAuthManager extends AuthManager { }
// ✅ Use service providers and custom drivers
// See Laravel docs on custom auth drivers and cache stores7. Customize ShieldCI Custom Settings (Optional)
To customize which classes are flagged by the analyzer, publish the config:
php artisan vendor:publish --tag=shieldci-configThen in config/shieldci.php:
return [
'analyzers' => [
'best-practices' => [
'enabled' => true,
'framework-override' => [
// Replaces the default never-extend list
'never_extend' => [
'MyApp\\Core\\BaseApplication',
'MyApp\\Http\\CoreRequest',
],
// Override rarely extend list (replaces defaults)
'rarely_extend' => [
'Illuminate\\Database\\Eloquent\\Builder',
],
// Allow specific framework classes for your project
'ok_to_extend' => [
'Illuminate\\Http\\Request', // If you have a good reason
],
],
],
],
];Custom Configuration
If your project has legacy code that extends framework classes, you can temporarily add them to ok_to_extend while you refactor. However, plan to migrate to Laravel's extension points during your next sprint.
Project-Specific Exceptions
Use configuration exceptions sparingly. Each framework class you extend becomes a potential upgrade blocker. Document WHY each exception exists and create tickets to refactor them.
References
- Laravel Macros - Official macro documentation
- Laravel Service Providers - Service provider patterns
- Eloquent Query Scopes - Model query scopes
- Custom Validation Rules - Validator extensions
- Database Events - Query event listeners
- Laravel Upgrade Guide - Official upgrade documentation
- Avoiding Framework Lock-in - Martin Fowler
Related Analyzers
- Fat Model - Detects models with too much business logic
- Service Container Resolution - Promotes dependency injection