diff --git a/app/Http/Controllers/Admin/FeatureFlagController.php b/app/Http/Controllers/Admin/FeatureFlagController.php new file mode 100644 index 0000000..09542f7 --- /dev/null +++ b/app/Http/Controllers/Admin/FeatureFlagController.php @@ -0,0 +1,127 @@ +getFilenameWithoutExtension(); + + if (class_exists($className)) { + $featureName = $this->getFeatureName($className); + + // Get the current state for null scope (global) + $isActive = Feature::for(null)->active($featureName); + + $features[] = [ + 'name' => $featureName, + 'class' => $className, + 'is_active' => $isActive, + 'description' => $this->getFeatureDescription($className), + ]; + } + } + } + + // Sort features by name + usort($features, fn($a, $b) => strcmp($a['name'], $b['name'])); + + return view('admin.feature-flags.index', compact('features')); + } + + /** + * Toggle a feature flag on or off. + */ + public function toggle(Request $request) + { + $validated = $request->validate([ + 'feature' => ['required', 'string'], + 'active' => ['required', 'boolean'], + ]); + + $featureName = $validated['feature']; + + // Activate or deactivate the feature for null scope (global) + if ($validated['active']) { + Feature::for(null)->activate($featureName); + $message = "Feature '{$featureName}' has been activated."; + } else { + Feature::for(null)->deactivate($featureName); + $message = "Feature '{$featureName}' has been deactivated."; + } + + return redirect()->route('admin.feature-flags.index') + ->with('success', $message); + } + + /** + * Purge a feature flag (remove all stored values). + */ + public function purge(Request $request) + { + $validated = $request->validate([ + 'feature' => ['required', 'string'], + ]); + + $featureName = $validated['feature']; + + Feature::purge($featureName); + + return redirect()->route('admin.feature-flags.index') + ->with('success', "Feature '{$featureName}' has been purged from storage."); + } + + /** + * Get the feature name from the class. + */ + private function getFeatureName(string $className): string + { + // Check if class has Name attribute + $reflection = new \ReflectionClass($className); + $attributes = $reflection->getAttributes(\Laravel\Pennant\Attributes\Name::class); + + if (!empty($attributes)) { + $nameAttribute = $attributes[0]->newInstance(); + return $nameAttribute->name; + } + + // Default to class basename + return class_basename($className); + } + + /** + * Get the feature description from class docblock. + */ + private function getFeatureDescription(string $className): ?string + { + $reflection = new \ReflectionClass($className); + $docComment = $reflection->getDocComment(); + + if ($docComment) { + // Extract the first line of the docblock as description + preg_match('/\/\*\*\s*\n\s*\*\s*(.+?)\n/s', $docComment, $matches); + if (isset($matches[1])) { + return trim($matches[1]); + } + } + + return null; + } +} diff --git a/resources/views/admin/feature-flags/index.blade.php b/resources/views/admin/feature-flags/index.blade.php new file mode 100644 index 0000000..85dc154 --- /dev/null +++ b/resources/views/admin/feature-flags/index.blade.php @@ -0,0 +1,186 @@ +@extends('layouts.app') + +@section('content') +
Manage application feature flags
++ Feature flags allow you to enable or disable features across the application without deploying new code. + Toggle flags on/off to control feature availability. +
+| + Feature + | ++ Status + | ++ Actions + | +
|---|---|---|
|
+
+
+
+
+
+ {{ $feature['name'] }}
+
+ @if($feature['description'])
+
+ {{ $feature['description'] }}
+
+ @endif
+
+ {{ $feature['class'] }}
+
+ |
+ + @if($feature['is_active']) + + + Active + + @else + + + Inactive + + @endif + | +
+
+
+
+
+
+
+
+ |
+
|
+
+
+
+ No feature flags found +
+ Add feature flag classes to the |
+ ||
Activate/Deactivate: Toggle a feature on or off globally for all users.
+Purge: Remove all stored values for a feature flag. The feature will revert to its default state defined in the code.
+Adding Features: Create new feature flag classes in app/Features directory.