├── .gitattributes ├── .gitignore ├── src ├── Models │ └── Setting.php ├── SettingsManager.php ├── Console │ ├── stubs │ │ └── database.stub │ └── SettingsTableCommand.php ├── Facades │ └── Settings.php ├── Providers │ └── SettingsServiceProvider.php ├── Contracts │ └── SettingsStore.php └── DatabaseSettingsHandler.php ├── phpunit.xml ├── tests ├── database │ └── migrations │ │ └── 2019_04_04_160347_create_settings_table.php └── LaravelSettingsTest.php ├── composer.json ├── config └── laravel-settings.php └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | /.github export-ignore 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | composer.lock 3 | /vendor 4 | .phpunit.result.cache -------------------------------------------------------------------------------- /src/Models/Setting.php: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | tests 10 | 11 | 12 | 13 | 14 | src/ 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/SettingsManager.php: -------------------------------------------------------------------------------- 1 | $name]); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Console/stubs/database.stub: -------------------------------------------------------------------------------- 1 | id(); 17 | $table->string('key')->index(); 18 | $table->string('locale')->nullable()->index(); 19 | $table->text('value')->nullable(); 20 | $table->unique(['key', 'locale']); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('settings'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /tests/database/migrations/2019_04_04_160347_create_settings_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 17 | $table->string('key')->index(); 18 | $table->string('locale')->nullable()->index(); 19 | $table->text('value')->nullable(); 20 | $table->unique(['key', 'locale']); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('settings'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/Facades/Settings.php: -------------------------------------------------------------------------------- 1 | =9.0" 14 | }, 15 | "require-dev": { 16 | "orchestra/testbench": ">=3.0", 17 | "mockery/mockery": "^1.4" 18 | }, 19 | "autoload": { 20 | "psr-4": { 21 | "Laraeast\\LaravelSettings\\": "src/" 22 | } 23 | }, 24 | "autoload-dev": { 25 | "psr-4": { 26 | "Laraeast\\LaravelSettings\\Tests\\": "tests/" 27 | } 28 | }, 29 | "extra": { 30 | "branch-alias": { 31 | "dev-master": "0.0.x-dev" 32 | }, 33 | "laravel": { 34 | "providers": [ 35 | "Laraeast\\LaravelSettings\\Providers\\SettingsServiceProvider" 36 | ], 37 | "aliases": { 38 | "Settings": "Laraeast\\LaravelSettings\\Facades\\Settings" 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Providers/SettingsServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton('settings.manager', function ($app) { 19 | return new SettingsManager($app); 20 | }); 21 | $this->app->singleton('settings', function ($app) { 22 | return $app->make('settings.manager')->driver(); 23 | }); 24 | } 25 | 26 | /** 27 | * Bootstrap any application services. 28 | * 29 | * @return void 30 | */ 31 | public function boot() 32 | { 33 | $this->mergeConfigFrom(__DIR__.'/../../config/laravel-settings.php', 'laravel-settings'); 34 | 35 | if ($this->app->runningInConsole()) { 36 | $this->publishes([ 37 | __DIR__.'/../../config/laravel-settings.php' => config_path('laravel-settings.php'), 38 | ], 'settings:config'); 39 | 40 | $this->commands([ 41 | SettingsTableCommand::class, 42 | ]); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Contracts/SettingsStore.php: -------------------------------------------------------------------------------- 1 | createBaseMigration(); 43 | 44 | $this->files->put($fullPath, $this->files->get(__DIR__.'/stubs/database.stub')); 45 | 46 | $this->info('Migration created successfully!'); 47 | 48 | $this->composer->dumpAutoloads(); 49 | } 50 | 51 | /** 52 | * Create a base migration file for the session. 53 | */ 54 | protected function createBaseMigration(): string 55 | { 56 | $name = 'create_settings_table'; 57 | 58 | $path = $this->laravel->databasePath().'/migrations'; 59 | 60 | return $this->laravel['migration.creator']->create($name, $path); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /config/laravel-settings.php: -------------------------------------------------------------------------------- 1 | 'database', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Cache Settings Query 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option controls the settings performance. 26 | | 27 | */ 28 | 29 | 'use_cache' => true, 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Cache Expiration Time 34 | |-------------------------------------------------------------------------- 35 | | 36 | | This option controls the expiration time in seconds. 37 | | 38 | */ 39 | 40 | 'cache_expire' => 60 * 60 * 24 * 30, // 1 month 41 | 42 | /* 43 | |-------------------------------------------------------------------------- 44 | | Setting Model Class 45 | |-------------------------------------------------------------------------- 46 | | 47 | | If you want to customize model for application settings you should 48 | | add your custom settings model class name here. 49 | | 50 | | Used only in "database" driver. 51 | | 52 | */ 53 | 54 | 'model_class' => null, 55 | ]; 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | tests 3 | StyleCI 4 | Total Downloads 5 | Latest Stable Version 6 | License 7 |

8 | 9 | # Persistent Settings Manager for Laravel 10 | 11 | * Simple key-value storage 12 | * Localization supported. 13 | * Localization using [Astrotomic/laravel-translatable](https://github.com/Astrotomic/laravel-translatable) Structure 14 | 15 | ## Installation 16 | 17 | 1. Install package 18 | 19 | ```bash 20 | composer require laraeast/laravel-settings 21 | ``` 22 | 23 | 1. Edit config/app.php (Skip this step if you are using laravel 5.5+) 24 | 25 | service provider: 26 | 27 | ```php 28 | Laraeast\LaravelSettings\Providers\SettingsServiceProvider::class, 29 | ``` 30 | 31 | class aliases: 32 | 33 | ```php 34 | 'Settings' => Laraeast\LaravelSettings\Facades\Settings::class, 35 | ``` 36 | 37 | 1. Create settings table for `database` driver 38 | 39 | ```bash 40 | php artisan settings:table 41 | php artisan migrate 42 | ``` 43 | 44 | ## Usage 45 | 46 | ```php 47 | Settings::get('name', 'Computer'); 48 | // get setting value with key 'name' 49 | // return 'Computer' if the key does not exists 50 | 51 | Settings::locale('en')->get('name', 'Computer'); 52 | // get setting value with key and language 53 | 54 | Settings::get('name:en', 'Computer'); 55 | // get setting value with key and language 56 | 57 | Settings::set('name', 'Computer'); 58 | // set setting value by key 59 | 60 | Settings::locale('en')->set('name', 'Computer'); 61 | // set setting value by key and language 62 | 63 | Settings::set('name:en', 'Computer'); 64 | // set setting value by key and language 65 | 66 | Settings::has('name'); 67 | // check the key exists, return boolean 68 | 69 | Settings::locale('en')->has('name'); 70 | // check the key exists by language, return boolean 71 | 72 | Settings::has('name:en'); 73 | // check the key exists by language, return boolean 74 | 75 | Settings::delete('name'); 76 | // delete the setting by key 77 | 78 | Settings::locale('en')->delete('name'); 79 | // delete the setting by key and language 80 | 81 | Settings::delete('name:en'); 82 | // delete the setting by key and language 83 | ``` 84 | 85 | ## Dealing with array 86 | 87 | ```php 88 | Settings::get('item'); 89 | // return null; 90 | 91 | Settings::set('item', ['USB' => '8G', 'RAM' => '4G']); 92 | Settings::get('item'); 93 | // return array( 94 | // 'USB' => '8G', 95 | // 'RAM' => '4G', 96 | // ); 97 | ``` 98 | ### Usage 99 | ```php 100 | Settings::locale('en')->set('title', 'Example Website'); 101 | 102 | Settings::locale('en')->get('title'); 103 | // return return 'Example Website'; 104 | 105 | Settings::set('title:ar', 'عنوان الموقع'); 106 | 107 | Settings::locale('ar')->get('title'); 108 | // return return 'عنوان الموقع'; 109 | 110 | Settings::locale('ar')->has('title') // bool 111 | Settings::locale('ar')->delete('title') 112 | 113 | App::setLocale('en'); 114 | 115 | Settings::locale()->get('title'); 116 | // return return 'Example Website'; 117 | ``` 118 | ### Extend Driver 119 | > You can extend your custom driver by adding this code in `register()` method of your `AppServiceProvier` 120 | 121 | ###### EX : 122 | ```php 123 | $this->app['settings.manager']->extend('file', function () { 124 | return new SettingsFileDriverHandler(); 125 | }); 126 | ``` 127 | > Note : your custom driver `SettingsFileDriverHandler` should implements `Laraeast\LaravelSettings\Contracts\SettingsStore` contract 128 | ``` 129 | fetchSettings(); 21 | } 22 | 23 | /** 24 | * Set a new settings item. 25 | */ 26 | public function set(string $key, mixed $value = null): Setting 27 | { 28 | Cache::forget('settings'); 29 | 30 | $this->supportLocaledKey($key); 31 | 32 | $model = $this->getModelClassName(); 33 | 34 | $model::updateOrCreate([ 35 | 'key' => $key, 36 | 'locale' => $this->locale, 37 | ], [ 38 | 'key' => $key, 39 | 'locale' => $this->locale, 40 | 'value' => serialize($value), 41 | ]); 42 | 43 | $this->fetchSettings(); 44 | 45 | $value = $this->instance($key); 46 | 47 | $this->locale = null; 48 | 49 | return $value; 50 | } 51 | 52 | /** 53 | * Set the settings locale. 54 | */ 55 | public function locale(?string $locale = null): self 56 | { 57 | $this->locale = $locale ?: $this->app->getLocale(); 58 | 59 | return $this; 60 | } 61 | 62 | /** 63 | * Get the given item. 64 | * 65 | * @template TDefault 66 | * 67 | * @param string $key 68 | * @param TDefault|null $default 69 | * 70 | * @return ($default is null ? Setting : TDefault) 71 | */ 72 | public function get(string $key, mixed $default = null): mixed 73 | { 74 | $instance = $this->instance($key); 75 | 76 | $this->locale = null; 77 | 78 | return $instance ? unserialize($instance->value) : $default; 79 | } 80 | 81 | /** 82 | * Fetch the settings collection. 83 | */ 84 | private function fetchSettings(): void 85 | { 86 | $model = $this->getModelClassName(); 87 | 88 | if ($this->app['config']->get('laravel-settings.use_cache')) { 89 | $expireSeconds = $this->app['config']->get('laravel-settings.cache_expire'); 90 | 91 | $this->settings = Cache::remember( 92 | 'settings', 93 | Carbon::now()->addSeconds($expireSeconds), 94 | function () use ($model) { 95 | return $model::get(); 96 | } 97 | ); 98 | } else { 99 | $this->settings = $model::get(); 100 | } 101 | } 102 | 103 | /** 104 | * Get the settings row. 105 | * 106 | * @template TDefault 107 | * 108 | * @param string $key 109 | * @param TDefault|null $default 110 | * 111 | * @return ($default is null ? Setting : TDefault) 112 | */ 113 | public function instance(string $key, mixed $default = null): mixed 114 | { 115 | $this->supportLocaledKey($key); 116 | 117 | return $this->settings->where('key', $key)->where('locale', $this->locale)->first() 118 | ?: $default; 119 | } 120 | 121 | /** 122 | * Determine whether the key is already exists. 123 | */ 124 | public function has(string $key): bool 125 | { 126 | return (bool) $this->instance($key); 127 | } 128 | 129 | /** 130 | * Delete the given key from storage. 131 | */ 132 | public function delete(string $key): self 133 | { 134 | if ($this->instance($key)) { 135 | Cache::forget('settings'); 136 | 137 | $this->instance($key)->delete(); 138 | 139 | $this->fetchSettings(); 140 | } 141 | 142 | return $this; 143 | } 144 | 145 | /** 146 | * Update locale if the key has the language. 147 | */ 148 | private function supportLocaledKey(string &$key): void 149 | { 150 | if (str_contains($key, ':')) { 151 | $this->locale(explode(':', $key)[1]); 152 | $key = explode(':', $key)[0]; 153 | } 154 | } 155 | 156 | /** 157 | * The model class name. 158 | * 159 | * @return class-string 160 | */ 161 | private function getModelClassName(): string 162 | { 163 | $model = $this->app['config']->get('laravel-settings.model_class'); 164 | 165 | return $model ?: Setting::class; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /tests/LaravelSettingsTest.php: -------------------------------------------------------------------------------- 1 | loadLaravelMigrations(['--database' => 'testbench']); 20 | 21 | $this->loadMigrationsFrom(__DIR__.'/database/migrations'); 22 | } 23 | 24 | /** 25 | * Load package service provider. 26 | */ 27 | protected function getPackageProviders($app) 28 | { 29 | return [SettingsServiceProvider::class]; 30 | } 31 | 32 | /** 33 | * Load package alias. 34 | */ 35 | protected function getPackageAliases($app) 36 | { 37 | return [ 38 | 'Settings' => Settings::class, 39 | ]; 40 | } 41 | 42 | /** 43 | * Define environment setup. 44 | */ 45 | protected function getEnvironmentSetUp($app) 46 | { 47 | // Setup default database to use sqlite :memory: 48 | $app['config']->set('database.default', 'testbench'); 49 | $app['config']->set('database.connections.testbench', [ 50 | 'driver' => 'sqlite', 51 | 'database' => ':memory:', 52 | 'prefix' => '', 53 | ]); 54 | } 55 | 56 | public function test_it_can_set_and_get_data() 57 | { 58 | Settings::set('name', 'Ahmed Fathy'); 59 | Settings::set('phone', '021207687151'); 60 | $this->assertEquals(Settings::get('phone'), '021207687151'); 61 | $this->assertEquals(Settings::get('name'), 'Ahmed Fathy'); 62 | } 63 | 64 | public function test_it_returns_default_value_if_the_key_does_not_exists() 65 | { 66 | $this->assertEquals(Settings::get('UndefindKey', 'FooBar'), 'FooBar'); 67 | } 68 | 69 | public function test_it_returns_unique_value_of_localed_data() 70 | { 71 | Settings::locale('en')->set('language', 'English'); 72 | Settings::locale('ar')->set('language', 'Arabic'); 73 | $this->assertEquals(Settings::locale('en')->get('language'), 'English'); 74 | $this->assertEquals(Settings::locale('ar')->get('language'), 'Arabic'); 75 | Settings::locale('en')->delete('language'); 76 | Settings::locale('ar')->delete('language'); 77 | Settings::set('language:en', 'English'); 78 | Settings::set('language:ar', 'Arabic'); 79 | $this->assertEquals(Settings::locale('en')->get('language'), 'English'); 80 | $this->assertEquals(Settings::locale('ar')->get('language'), 'Arabic'); 81 | $this->assertEquals(Settings::get('language:en'), 'English'); 82 | $this->assertEquals(Settings::get('language:ar'), 'Arabic'); 83 | Settings::locale('en')->set('language', 'English'); 84 | $this->assertEquals(Setting::where(['locale' => 'en', 'key' => 'language'])->count(), 1); 85 | } 86 | 87 | public function test_it_determine_if_the_value_exists() 88 | { 89 | Settings::set('name', 'Ahmed'); 90 | $this->assertTrue(Settings::has('name')); 91 | Settings::locale('en')->set('language', 'English'); 92 | $this->assertTrue(Settings::locale('en')->has('language')); 93 | $this->assertTrue(Settings::has('language:en')); 94 | } 95 | 96 | public function test_it_can_deleted_the_specific_key() 97 | { 98 | Settings::set('name', 'Ahmed'); 99 | $this->assertTrue(Settings::has('name')); 100 | Settings::delete('name'); 101 | $this->assertFalse(Settings::has('name')); 102 | $this->assertDatabaseMissing('settings', [ 103 | 'key' => 'name', 104 | ]); 105 | Settings::locale('en')->set('name', 'Ahmed'); 106 | Settings::locale('ar')->set('name', 'احمد'); 107 | $this->assertTrue(Settings::locale('en')->has('name')); 108 | $this->assertTrue(Settings::locale('ar')->has('name')); 109 | Settings::locale('en')->delete('name'); 110 | $this->assertFalse(Settings::locale('en')->has('name')); 111 | $this->assertTrue(Settings::locale('ar')->has('name')); 112 | $this->assertDatabaseMissing('settings', [ 113 | 'key' => 'name', 114 | 'locale' => 'en', 115 | ]); 116 | Settings::delete('name:ar'); 117 | $this->assertFalse(Settings::locale('ar')->has('name')); 118 | $this->assertFalse(Settings::has('name:ar')); 119 | $this->assertDatabaseMissing('settings', [ 120 | 'key' => 'name', 121 | 'locale' => 'ar', 122 | ]); 123 | } 124 | 125 | public function test_it_returns_model_after_set_value() 126 | { 127 | $this->assertInstanceOf(Setting::class, Settings::set('foo', 'bar')); 128 | } 129 | } 130 | --------------------------------------------------------------------------------