├── LICENSE ├── README.md ├── composer.json ├── database └── migrations │ └── 2020_03_04_044421_create_settings_table.php └── src ├── Facades └── Settings.php ├── Providers └── SettingsServiceProvider.php ├── Setting.php └── SettingsManager.php /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2018 by Nikita Popov. 2 | 3 | Some rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Laravel Settings Manager 2 | ========== 3 | 4 | This is a Laravel package provide an easy way to control application settings, which can easily integrated to application configuration to be used with config() function 5 | 6 | 7 | Compatibility 8 | ============ 9 | This package was built for versions >= 7.0 but also compatible with versions >= 5.8.16 10 | 11 | Features 12 | ======== 13 | 14 | * auto load all settings (with option to disable it) 15 | * cache loaded entries for current request 16 | * auto save entries to DB (with option to disable it) 17 | * auto create entries to DB if not exists (with option to disable it) 18 | * map setting entry from DB to config() key 19 | * changeable model. 20 | * customizable package configuration with manager. 21 | * macroable settings manager 22 | * compatible with PHPUnit testings 23 | 24 | Installation 25 | ============== 26 | 27 | ``` 28 | composer require ibraheem-ghazi/laravel-settings-manager 29 | ``` 30 | then: 31 | ``` 32 | php artisan migrate 33 | ``` 34 | if your installed laravel version does not support auto discover packages then: 35 | 36 | 1- add this provider to config: 37 | 38 | ``` 39 | IbraheemGhazi\SettingsManager\Providers\SettingsServiceProvider::class, 40 | ``` 41 | 42 | 2- then add alias: 43 | ``` 44 | 'Settings' => IbraheemGhazi\SettingsManager\Facades\Settings::class, 45 | ``` 46 | 47 | 48 | Configuration 49 | ================ 50 | 51 | ### Attributes 52 | |func | description| 53 | |----------------------------------|----------------------| 54 | |$ignoreMigration | ignore auto register package migrations. 55 | |$AutoLoadFromDatabase | disable/enable auto loading configuration from database. 56 | |$AutoSaveOnSet | disable/enable auto save configuration to database. 57 | |$AutoCreateOnSave | disable/enable auto create configuration to database if not already exists. 58 | |$Model | change the model used to save settings - must have key, value fields where key is *primary key*. 59 | 60 | ### Methods 61 | |func | params | description| 62 | |----------------------------------|----------------|----------------------| 63 | |bind | string $settings_key, ?string $config_key = NULL | bind a settings entry from DB to application configuration key 64 | |unbind | string $settings_key | remove binding of a settings entry to application configuration key 65 | 66 | Other Available Functions 67 | ========================= 68 | 69 | |func | params | return | description| 70 | |------------------------|---------------------|---------------|----------------------| 71 | |load | bool $force = false | ($this) | load settings from DB (or force reload it) 72 | |getModel | - | Model | return the model used to control DB entries 73 | |getBindings | - | Collection | get all configured settings to configurations bindings 74 | |all | - | Collection | return collection of strings of all entries. 75 | |get | $key, $default=NULL | mixed | return the value of specified key 76 | |set | $key, $value, $save = false, $should_create = true | ($this) | set the value for specified key, (with option to force enable/disable saving or creating) 77 | |forget | $key, $permanent_remove = true, $callback = NULL | ($this) | remove the specified key, (with option to force enable/disable removing entry from DB, and call a callback function when done removing) 78 | 79 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ibraheem-ghazi/laravel-settings-manager", 3 | "description": "Laravel settings management package using database and laravel config()", 4 | "keywords": ["php", "settings", "setting"], 5 | "type": "library", 6 | "license": "MIT", 7 | "minimum-stability": "dev", 8 | "authors": [ 9 | { 10 | "name": "Ibraheem Ghazi" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.6.0", 15 | "laravel/framework": ">=5.8.16" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "IbraheemGhazi\\SettingsManager\\": "src/" 20 | } 21 | }, 22 | "extra": { 23 | "laravel": { 24 | "providers": [ 25 | "IbraheemGhazi\\SettingsManager\\Providers\\SettingsServiceProvider" 26 | ], 27 | "aliases": { 28 | "Settings": "IbraheemGhazi\\SettingsManager\\Facades\\Settings" 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/2020_03_04_044421_create_settings_table.php: -------------------------------------------------------------------------------- 1 | string('key',190)->primary(); 18 | $table->text('value'); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | Schema::dropIfExists('settings'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Facades/Settings.php: -------------------------------------------------------------------------------- 1 | app->singleton('settings', function () { 18 | return new SettingsManager(); 19 | }); 20 | if($this->app->runningInConsole()) 21 | $this->registerMigrations(); 22 | } 23 | 24 | /** 25 | * Bootstrap services. 26 | * 27 | * @return void 28 | */ 29 | public function boot() 30 | { 31 | 32 | } 33 | 34 | /** 35 | * Register migration files. 36 | * 37 | * @return void 38 | */ 39 | protected function registerMigrations(){ 40 | if(!SettingsManager::$ignoreMigration) 41 | $this->loadMigrationsFrom(__DIR__ . '/../../database/migrations'); 42 | 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/Setting.php: -------------------------------------------------------------------------------- 1 | cache = collect(); 43 | App::booted(function () { 44 | if (static::$autoLoadFromDatabase) { 45 | $this->load(); 46 | } 47 | }); 48 | 49 | $this->getModel()->saved(function (Model $entry) { 50 | $this->applyBindingOnModel($entry); 51 | }); 52 | } 53 | 54 | /** 55 | * load all entries from database then apply bindings if required 56 | * @param bool $force force loading data from database even it's already loaded 57 | * @return $this 58 | */ 59 | public function load($force = false) 60 | { 61 | $execute = function () use ($force) { 62 | if ($this->cache->isNotEmpty() && !$force) return; 63 | $this->cache = $this->getModel()->get(); 64 | $this->refreshBindings(); 65 | }; 66 | 67 | /** 68 | * if we are on PHPUnit never run load() before tables exists 69 | * @since 5.8.16 70 | */ 71 | if (app()->runningUnitTests()) { 72 | app('events')->listen(MigrationsEnded::class, $execute); 73 | } else { 74 | $execute(); 75 | } 76 | return $this; 77 | } 78 | 79 | /** 80 | * force refresh the bindings to have its value 81 | * @return $this 82 | */ 83 | public function refreshBindings() 84 | { 85 | foreach ($this->cache as $entry) { 86 | $this->applyBindingOnModel($entry); 87 | } 88 | return $this; 89 | } 90 | 91 | /** 92 | * get settings model instance 93 | * @return Model 94 | */ 95 | protected function getModel() 96 | { 97 | return $this->resolve(static::$model); 98 | } 99 | 100 | /** 101 | * resolve class string path to instance 102 | * @param $class 103 | * @return mixed 104 | */ 105 | private function resolve($class) 106 | { 107 | return App::make($class); 108 | } 109 | 110 | /** 111 | * apply bindings using entry key and value 112 | * @param Model $entry 113 | */ 114 | protected function applyBindingOnModel(Model $entry) 115 | { 116 | $this->applyBindingOn($this->getBinding($entry), $entry->getAttribute('value')); 117 | 118 | /** 119 | * the following code fix the dependency between bindings for example: 120 | * Settings::bind('seo.site_name', 'app.name') 121 | * will now work as expected and bind the app.name to seo.site_name 122 | * so both will have same value 123 | */ 124 | $value = config($this->getBinding($entry)); 125 | foreach ($this->getDependentBinding($entry) as $dep){ 126 | config([$dep=>$value]); 127 | } 128 | } 129 | 130 | /** 131 | * apply bindings using key and value 132 | * @param string $key 133 | * @param $value 134 | */ 135 | protected function applyBindingOn($key, $value) 136 | { 137 | if (is_string($key) && strlen($key) && array_key_exists($key, $this->configBindings)) { 138 | $config_key = $this->configBindings[$key]; 139 | config([$config_key => $this->tryDecodeValue($value)]); 140 | } 141 | } 142 | 143 | protected function getBinding(Model $entry) 144 | { 145 | return $this->configBindings[$entry->getAttribute('key')] ?? NULL; 146 | } 147 | 148 | protected function getDependentBinding(Model $entry) 149 | { 150 | $temp = []; 151 | foreach ($this->configBindings as $k=>$v){ 152 | if($k !== $v && $v === $entry->getAttribute('key')){ 153 | $temp[] = $k; 154 | } 155 | } 156 | return $temp; 157 | } 158 | 159 | /** 160 | * change the Eloquent Model used to handle database connection 161 | * @param Model $model 162 | * @return $this 163 | */ 164 | public function setModel(Model $model) 165 | { 166 | static::$model = $model; 167 | 168 | return $this; 169 | } 170 | 171 | 172 | /** 173 | * get all loaded settings 174 | */ 175 | public function all() 176 | { 177 | return $this->cache->pluck('value', 'key'); 178 | } 179 | 180 | /** 181 | * get specific settings entry or return default if not found 182 | * @param $key 183 | * @param null $default 184 | * @return mixed|null 185 | */ 186 | public function get($key, $default = NULL) 187 | { 188 | if ($entry = $this->getEntry($key)) { 189 | return $this->tryDecodeValue( $entry->getAttribute('value') ); 190 | } 191 | // if($this->applyBindingOnNonExists){ 192 | // $this->applyBindingOn($key, $default); 193 | // } 194 | return $default; 195 | } 196 | 197 | /** 198 | * try to decode the input value, if it has any error 199 | * while decoding it will return the input value. 200 | * @param $value 201 | * @return mixed 202 | */ 203 | protected function tryDecodeValue($value){ 204 | try { 205 | $temp = json_decode($value, true); 206 | if(json_last_error() === JSON_ERROR_NONE){ 207 | return $temp; 208 | } 209 | return $value; 210 | } catch (\Exception $ex) { 211 | return $value; 212 | } 213 | } 214 | 215 | /** 216 | * get Model entry from already loaded settings using key 217 | * @param $key 218 | * @return Model|null 219 | */ 220 | protected function getEntry($key) 221 | { 222 | return $this->cache->where('key', $key)->first(); 223 | } 224 | 225 | /** 226 | * set specific settings entry and save it if required 227 | * @param $key 228 | * @param $value 229 | * @param bool $save force saving this settings entry (update or create if $should_create == TRUE) 230 | * @param bool $should_create when forcing save entry, should create it if not exists ? 231 | * @return $this 232 | */ 233 | public function set($key, $value, $save = false, $should_create = true) 234 | { 235 | $value = is_string($value) ? $value : json_encode($value, JSON_UNESCAPED_UNICODE); 236 | if ($entry = $this->getEntry($key)) { 237 | $entry->setAttribute('value', $value); 238 | if (static::$autoSaveOnSet || $save) { 239 | $entry->save(); 240 | } 241 | return $this; 242 | } 243 | 244 | tap($this->getModel()->newInstance(), function (Model $entry) use ($key, $value, $save, $should_create) { 245 | $entry->setAttribute('key', $key); 246 | $entry->setAttribute('value', $value); 247 | if (static::$autoCreateOnSet || ($save && $should_create)) { 248 | $entry->save(); 249 | } 250 | $this->cache->push($entry); 251 | }); 252 | 253 | return $this; 254 | } 255 | 256 | /** 257 | * forget a key from settings 258 | * @param string $key setting key to delete 259 | * @param bool $permanent_remove determine if should also remove DB entry 260 | * @param null|mixed $callback once forgot entry, if has binding this will it's value 261 | * @return $this 262 | */ 263 | public function forget($key, $permanent_remove = true, $callback = NULL) 264 | { 265 | $entry = $this->getEntry($key); 266 | 267 | if (!$entry) return $this; 268 | 269 | $this->removeEntry($entry); 270 | $permanent_remove && rescue(function () use ($entry) { 271 | $entry->exists && $entry->delete(); 272 | }); 273 | 274 | if ($this->hasBinding($entry) && !is_null($callback)) { 275 | $config_key = $this->getBinding($entry); 276 | $config_key && $this->applyBindingOn($config_key, value($callback)); 277 | } 278 | 279 | return $this; 280 | } 281 | 282 | protected function removeEntry(Model $entry) 283 | { 284 | if ($key = $this->cache->search($entry) !== FALSE) { 285 | $this->cache->forget($key); 286 | } 287 | } 288 | 289 | protected function hasBinding(Model $entry) 290 | { 291 | return array_key_exists($entry->getAttribute('key'), $this->configBindings); 292 | } 293 | 294 | /** 295 | * get current registered config bindings 296 | * @return array 297 | */ 298 | public function getBindings() 299 | { 300 | return $this->configBindings; 301 | } 302 | 303 | /** 304 | * bind settings to config, so when ever the settings key is 305 | * retrieved or saved it will change the assigned this setting 306 | * value to default app configuration.

307 | * example 1: bind('app.name') will result to override config('app.name') to have get('app.name') value 308 | * example 2: bind('app.name','app_name') will result to override config('app.name') to have get('app_name') value 309 | * @param string $settings_key 310 | * @param string|null $config_key 311 | * @return $this 312 | */ 313 | public function bind(string $settings_key, ?string $config_key = NULL) 314 | { 315 | $this->configBindings[$settings_key] = $config_key ?: $settings_key; 316 | return $this; 317 | } 318 | 319 | /** 320 | * unbind already bound settings to config link 321 | * @param $settings_key 322 | * @return $this 323 | */ 324 | public function unbind($settings_key) 325 | { 326 | Arr::forget($this->configBindings, $settings_key); 327 | return $this; 328 | } 329 | 330 | 331 | } 332 | --------------------------------------------------------------------------------