Last active
July 20, 2025 12:29
-
-
Save sushantaryal/eb19479c4c83b24fe36761607000a72b to your computer and use it in GitHub Desktop.
Laravel Settings
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| use Illuminate\Database\Migrations\Migration; | |
| use Illuminate\Database\Schema\Blueprint; | |
| use Illuminate\Support\Facades\Schema; | |
| return new class extends Migration | |
| { | |
| public function up(): void | |
| { | |
| Schema::create('settings', function (Blueprint $table) { | |
| $table->id(); | |
| $table->string('key')->unique(); | |
| $table->text('value')->nullable(); | |
| $table->string('type')->default('string'); | |
| $table->string('group')->default('general'); | |
| $table->text('description')->nullable(); | |
| $table->timestamps(); | |
| $table->index('group'); | |
| }); | |
| } | |
| public function down(): void | |
| { | |
| Schema::dropIfExists('settings'); | |
| } | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| use App\Http\Requests\SaveSettingRequest; | |
| use App\Services\SettingService; | |
| use Illuminate\Support\Facades\Route; | |
| Route::get('settings', function () { | |
| $settings = app(SettingService::class)->all(); | |
| return $settings; | |
| }); | |
| Route::put('settings', function (SaveSettingRequest $request) { | |
| app(SettingService::class)->updateSettings($request->validated()); | |
| return app(SettingService::class)->all(); | |
| }); | |
| Route::get('settings/{key}', function (string $key) { | |
| return app(SettingService::class)->get($key); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace App\Http\Requests; | |
| use Illuminate\Foundation\Http\FormRequest; | |
| class SaveSettingRequest extends FormRequest | |
| { | |
| /** | |
| * Determine if the user is authorized to make this request. | |
| */ | |
| public function authorize(): bool | |
| { | |
| return true; | |
| } | |
| /** | |
| * Get the validation rules that apply to the request. | |
| * | |
| * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string> | |
| */ | |
| public function rules(): array | |
| { | |
| return [ | |
| 'site_name' => 'required|string|max:255', | |
| 'site_description' => 'nullable|string|max:500', | |
| 'site_keywords' => 'nullable|string|max:255', | |
| 'contact_email' => 'required|email|max:255', | |
| 'contact_phone' => 'nullable|string|max:20', | |
| 'contact_address' => 'nullable|string|max:500', | |
| 'facebook_url' => 'nullable|url|max:255', | |
| 'twitter_url' => 'nullable|url|max:255', | |
| 'linkedin_url' => 'nullable|url|max:255', | |
| 'instagram_url' => 'nullable|url|max:255', | |
| 'youtube_url' => 'nullable|url|max:255', | |
| 'logo' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048', | |
| 'favicon' => 'nullable|image|mimes:ico,png|max:1024', | |
| 'conference_date_start' => 'nullable|date', | |
| 'conference_date_end' => 'nullable|date|after_or_equal:conference_date_start', | |
| 'conference_venue' => 'nullable|string|max:255', | |
| 'conference_address' => 'nullable|string|max:500', | |
| 'registration_enabled' => 'boolean', | |
| 'registration_deadline' => 'nullable|date', | |
| 'early_bird_deadline' => 'nullable|date', | |
| 'google_analytics_id' => 'nullable|string|max:255', | |
| 'google_maps_api_key' => 'nullable|string|max:255', | |
| ]; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace App\Models; | |
| use Illuminate\Database\Eloquent\Model; | |
| class Setting extends Model | |
| { | |
| /** | |
| * The attributes that are mass assignable. | |
| * | |
| * @var array<int, string> | |
| */ | |
| protected $fillable = ['key', 'value']; | |
| public function getLabel(): string | |
| { | |
| return ucwords(str_replace('_', ' ', $this->key)); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!doctype html> | |
| <html> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Settings</title> | |
| <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script> | |
| </head> | |
| <body class="dark:bg-gray-900 dark:text-white"> | |
| <div class="flex flex-col items-center justify-center min-h-screen px-4 py-12 sm:px-6 lg:px-8"> | |
| <div class="w-full max-w-7xl space-y-8"> | |
| <h1 class="text-3xl font-bold text-gray-900 dark:text-gray-100">Settings</h1> | |
| <form action="{{ route('settings.update') }}" method="post" enctype="multipart/form-data"> | |
| @csrf | |
| @method('put') | |
| @foreach($settings as $group => $settings) | |
| <input type="hidden" name="group" value="{{ $group }}"> | |
| <div class="w-full bg-white border border-gray-200 mb-8 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700"> | |
| <div class="p-4"> | |
| <h5 class="text-xl font-medium text-gray-900 dark:text-white">{{ ucfirst($group) }}</h5> | |
| </div> | |
| <div class="p-4 border-t border-gray-200 dark:border-gray-700 grid md:grid-cols-2 gap-4"> | |
| @foreach($settings as $setting) | |
| <div> | |
| <label for="{{ $setting->key }}" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{ $setting->getLabel() }}</label> | |
| @if($setting->type == 'string') | |
| <input type="text" id="{{ $setting->key }}" name="{{ $setting->key }}" value="{{ old($setting->key, $setting->value) }}" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"> | |
| @elseif($setting->type == 'date') | |
| <input type="date" id="{{ $setting->key }}" name="{{ $setting->key }}" value="{{ old($setting->key, $setting->value) }}" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"> | |
| @elseif($setting->type == 'boolean') | |
| <label class="inline-flex items-center cursor-pointer"> | |
| <input type="checkbox" id="{{ $setting->key }}" name="{{ $setting->key }}" value="1" class="sr-only peer" {{ $setting->value ? 'checked' : '' }}> | |
| <div class="relative w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600 dark:peer-checked:bg-blue-600"></div> | |
| </label> | |
| @elseif($setting->type == 'file') | |
| <label for="{{ $setting->key }}" class="flex flex-col items-center justify-center w-full h-42 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-gray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600"> | |
| <div class="flex flex-col items-center justify-center pt-5 pb-6"> | |
| <svg class="w-8 h-8 mb-4 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16"> | |
| <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"/> | |
| </svg> | |
| <p class="mb-2 text-sm text-gray-500 dark:text-gray-400"><span class="font-semibold">Click to upload</span> or drag and drop</p> | |
| @if($setting->key == 'logo') | |
| <p class="text-xs text-gray-500 dark:text-gray-400">SVG, PNG, JPG or GIF (MAX. 1MB)</p> | |
| @elseif($setting->key == 'favicon') | |
| <p class="text-xs text-gray-500 dark:text-gray-400">ICO, PNG (MAX. 1MB)</p> | |
| @endif | |
| </div> | |
| <input id="{{ $setting->key }}" name="{{ $setting->key }}" type="file" class="hidden" accept="image/svg+xml, image/png, image/jpeg, image/gif"> | |
| </label> | |
| @endif | |
| @error($setting->key) | |
| <p class="mt-2 text-sm text-red-600 dark:text-red-500">{{ $message }}</p> | |
| @enderror | |
| @if($setting->type == 'file' && $setting->value) | |
| <img src="{{ Storage::url($setting->value) }}" alt="{{ $setting->getLabel() }}" class="w-auto h-64 rounded-lg mt-4"> | |
| @endif | |
| </div> | |
| @endforeach | |
| </div> | |
| </div> | |
| @endforeach | |
| <button type="submit" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center cursor-pointer dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Submit</button> | |
| </form> | |
| </div> | |
| </div> | |
| </body> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace App\Services; | |
| use App\Models\Setting; | |
| use Illuminate\Database\Eloquent\Collection; | |
| use Illuminate\Http\UploadedFile; | |
| use Illuminate\Support\Facades\Cache; | |
| use Illuminate\Support\Facades\Storage; | |
| class SettingService | |
| { | |
| protected string $cacheKey = 'app-settings'; | |
| public function all(): array | |
| { | |
| return Cache::rememberForever($this->cacheKey, function () { | |
| return Setting::pluck('value', 'key')->toArray(); | |
| }); | |
| } | |
| public function allGrouped(): Collection | |
| { | |
| return Cache::rememberForever($this->cacheKey . '-grouped', function () { | |
| return Setting::select('key', 'value', 'type', 'group')->get()->groupBy('group'); | |
| }); | |
| } | |
| public function get(string $key, $default = null) | |
| { | |
| return $this->all()[$key] ?? $default; | |
| } | |
| public function set(string $key, $value): void | |
| { | |
| $this->setRecord($key, $value); | |
| $this->clearCache(); | |
| } | |
| public function updateSettings(array $data): void | |
| { | |
| foreach ($data as $key => $value) { | |
| $this->setRecord($key, $value); | |
| } | |
| $this->clearCache(); | |
| } | |
| public function forget(string $key): void | |
| { | |
| Setting::where('key', $key)->update(['value' => null]); | |
| $this->clearCache(); | |
| } | |
| private function clearCache(): void | |
| { | |
| Cache::forget($this->cacheKey); | |
| Cache::forget($this->cacheKey . '-grouped'); | |
| } | |
| private function setRecord(string $key, $value): void | |
| { | |
| if ($value instanceof UploadedFile) { | |
| $this->removeFileIfExists($key); | |
| $value = $this->uploadFile($value); | |
| } | |
| Setting::updateOrCreate(['key' => $key], ['value' => $value]); | |
| } | |
| private function uploadFile(UploadedFile $file): string | |
| { | |
| $file->store('settings', 'public'); | |
| return 'settings/' . $file->hashName(); | |
| } | |
| private function removeFileIfExists(string $key): void | |
| { | |
| $setting = Setting::where('key', $key)->first(); | |
| if ($setting && $setting->value && Storage::exists($setting->value)) { | |
| Storage::delete($setting->value); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| use App\Http\Requests\SaveSettingRequest; | |
| use App\Services\SettingService; | |
| use Illuminate\Support\Facades\Route; | |
| Route::get('/', function () { | |
| return view('welcome'); | |
| }); | |
| Route::get('settings', function () { | |
| $settings = app(SettingService::class)->allGrouped(); | |
| return view('settings', compact('settings')); | |
| }); | |
| Route::put('settings', function (SaveSettingRequest $request) { | |
| app(SettingService::class)->updateSettings($request->validated()); | |
| return back()->with('success', 'Settings updated successfully.'); | |
| })->name('settings.update'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment