Skip to content

Conversation

@NikolaWd
Copy link

Add Route Disabling Feature

This PR introduces the ability to temporarily disable routes without removing them from the codebase. This feature is useful for maintenance mode, feature flags, A/B testing, gradual rollouts, and emergency response scenarios.

Usage

Basic Usage - Default Message

Route::get('/users', [UserController::class, 'index'])->disabled(); // Returns: "This route is temporarily disabled." with 503 status

Custom Message

Route::get('/users', [UserController::class, 'index']) ->disabled('User management is under maintenance'); // Returns: "User management is under maintenance" with 503 status

Custom Response with Callback

Route::get('/users', [UserController::class, 'index']) ->disabled(function ($request) { return response()->json([ 'message' => 'This feature is temporarily unavailable', 'retry_after' => now()->addHours(2)->timestamp, ], 503); });

Conditional Disabling

Route::get('/beta-feature', [BetaController::class, 'index']) ->disabled(config('features.beta_disabled') ? 'Beta features are disabled' : false);

Implementation Details

Files Added

  • src/Illuminate/Routing/Middleware/DisabledRoute.php - Middleware that handles disabled routes
  • tests/Integration/Routing/DisabledRouteTest.php - Integration tests (7 tests, 16 assertions)

Files Modified

  • src/Illuminate/Routing/Route.php - Added disabled() method (13 lines)
  • tests/Routing/RoutingRouteTest.php - Added unit tests (3 tests, 6 assertions)

How It Works

  1. The disabled() method on the Route class:

    • Accepts optional message (string), boolean, or callback (Closure)
    • Stores the value in the route action array under the 'disabled' key
    • Automatically registers the DisabledRoute middleware to the route
  2. The DisabledRoute middleware:

    • Checks if the current route has a 'disabled' action
    • If a callback is provided, executes it and returns the response
    • If a string message is provided, returns it with 503 status
    • If true or empty string, returns default message: "This route is temporarily disabled."
    • If false or null (from callback), allows the request to proceed normally

Use Cases

  1. Selective Maintenance Mode - Disable specific routes without putting the entire application in maintenance mode
  2. Feature Flags - Easily toggle features on/off based on configuration or environment
  3. Time-Based Availability - Enable routes only during specific time periods or business hours
  4. A/B Testing - Conditionally disable routes for specific user segments
  5. Gradual Rollouts - Keep new features disabled until ready, then enable for percentage of users
  6. Emergency Response - Quickly disable problematic endpoints in production

Real-World Examples

Feature Flags

Route::get('/new-feature', [FeatureController::class, 'index']) ->disabled(!config('features.new_feature_enabled'));

Time-Based Availability

Route::get('/christmas-sale', [SaleController::class, 'christmas']) ->disabled(function ($request) { $now = now(); if ($now->between('2025-12-24', '2025-12-26')) { return null; // Enable during Christmas } return 'Christmas sale is only available December 24-26'; });

Business Hours

Route::post('/trading/execute', [TradingController::class, 'execute']) ->disabled(function ($request) { if (now()->isWeekend() || now()->hour < 9 || now()->hour >= 17) { return response()->json([ 'message' => 'Trading is only available 9 AM - 5 PM on weekdays', ], 503); } return null; // Enable during business hours });

Gradual Rollout

Route::get('/new-dashboard', [DashboardController::class, 'new']) ->disabled(function ($request) { // Enable for 20% of users if (($request->user()->id % 5) === 0) { return null; } return 'New dashboard coming soon!'; });

Testing

All tests pass successfully:

  • 7 integration tests covering:
  • Default disabled message
  • Custom disabled message
  • Callback-based disabling
  • Enabled routes (verification)
  • POST method routes
  • Empty string handling
  • Route groups compatibility

-3 unit tests covering:

  • Middleware registration

  • Action array storage

  • Different parameter types (boolean, string, callback)

  • Total: 10 tests, 22 assertions

Backward Compatibility

This is a non-breaking change. It only adds new functionality without modifying any existing behavior. All existing routes continue to work exactly as before.

API Design Considerations

The disabled() method accepts three types of values:

  • Boolean (true/false) - Simple on/off switch
  • String - Custom message with automatic 503 response
  • Closure - Full control over the response logic

Returning null from a callback means "do not disable" - allowing dynamic enabling/disabling based on complex conditions.

Default HTTP status is 503 Service Unavailable, which is semantically correct for temporary unavailability scenarios.

@rodrigopedra
Copy link
Contributor

How does using a callback as a route property affect route serialization?

@NikolaWd
Copy link
Author

Great catch, the current implementation doesn't handle closure serialization for route caching.
I'm preparing an updated PR...
Thanks for catching this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants