├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── .styleci.yml ├── .travis.yml ├── LICENSE ├── changelog.md ├── composer.json ├── config └── setting.php ├── contributing.md ├── database └── migrations │ └── 2019_01_15_160652_create_settings_table.php ├── phpunit.xml ├── readme.md ├── src ├── Contracts │ └── SettingStorageContract.php ├── EloquentStorage.php ├── Facades │ └── Setting.php ├── Providers │ └── SettingServiceProvider.php └── Setting.php └── tests └── SettingTest.php /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock 3 | .phpunit.result.cache 4 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.2 5 | - 7.3 6 | - nightly 7 | 8 | matrix: 9 | allow_failures: 10 | - php: nightly 11 | 12 | before_script: 13 | - travis_retry composer self-update 14 | - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-source 15 | 16 | script: 17 | - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Janis Kelemen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `Setting` will be documented in this file. 4 | 5 | ## Version 1.0 6 | 7 | ### Added 8 | - Everything 9 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "janiskelemen/laravel-setting", 3 | "description": "Laravel Settings Manager", 4 | "license": "MIT", 5 | "minimum-stability": "dev", 6 | "prefer-stable": true, 7 | "authors": [ 8 | { 9 | "name": "Janis Kelemen", 10 | "email": "janis@helpspace.com", 11 | "homepage": "https://helpspace.com" 12 | } 13 | ], 14 | "homepage": "https://github.com/janiskelemen/laravel-setting", 15 | "keywords": [ 16 | "Laravel", 17 | "Setting", 18 | "Config", 19 | "JanisKelemen" 20 | ], 21 | "require": { 22 | "illuminate/support": "^8|^9|^10|^11", 23 | "adbario/php-dot-notation": "^3.1.1" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "^9.0", 27 | "mockery/mockery": "^1.5", 28 | "orchestra/testbench": "~7.0", 29 | "orchestra/database": "dev-master", 30 | "sempro/phpunit-pretty-print": "^1.4" 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "JanisKelemen\\Setting\\": "src/" 35 | } 36 | }, 37 | "autoload-dev": { 38 | "psr-4": { 39 | "JanisKelemen\\Setting\\Tests\\": "tests" 40 | } 41 | }, 42 | "extra": { 43 | "laravel": { 44 | "providers": [ 45 | "JanisKelemen\\Setting\\Providers\\SettingServiceProvider" 46 | ], 47 | "aliases": { 48 | "Setting": "JanisKelemen\\Setting\\Facades\\Setting" 49 | } 50 | } 51 | }, 52 | "scripts": { 53 | "test": "phpunit --testdox" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /config/setting.php: -------------------------------------------------------------------------------- 1 | [ 5 | // 'type' => 'text', /* Optional config values */ 6 | // 'max' => 255, /* Optional config values */ 7 | // 'default_value' => 'My Application' /* <- This value will be returned by Setting::get('app_name') if key is not found in DB */ 8 | // ], 9 | // 'version' => '1.0.0', 10 | // 'user_limit' => 10, 11 | ]; 12 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are welcome and will be fully credited. 4 | 5 | Contributions are accepted via Pull Requests on [Github](https://github.com/janiskelemen/laravel-setting). 6 | 7 | ## Pull Requests 8 | 9 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 10 | 11 | - **Document any change in behaviour** - Make sure the `readme.md` and any other relevant documentation are kept up-to-date. 12 | 13 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 14 | 15 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 16 | 17 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 18 | 19 | **Happy coding**! 20 | -------------------------------------------------------------------------------- /database/migrations/2019_01_15_160652_create_settings_table.php: -------------------------------------------------------------------------------- 1 | string('key')->index(); 18 | $table->longtext('value')->nullable(); 19 | $table->string('locale')->nullable(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::dropIfExists('settings'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Advanced Settings Manager for Laravel 2 | 3 | [![Total Downloads][ico-downloads]][link-downloads] 4 | [![Build Status][ico-travis]][link-travis] 5 | 6 | - Simple key-value storage 7 | - Optional config file for default settings supported 8 | - Support multi-level array (dot delimited keys) structure 9 | - Supports storing individual user settings 10 | - Localization supported 11 | - Settings are cached 12 | 13 | ## Installation 14 | 15 | Via Composer 16 | 17 | ```bash 18 | composer require janiskelemen/laravel-setting 19 | ``` 20 | 21 | ## Publish config and migration 22 | 23 | ```bash 24 | php artisan vendor:publish --tag=setting 25 | php artisan migrate 26 | ``` 27 | 28 | ## Setup your settings config 29 | 30 | After publishing the setting files you will find a new configuration file: config/setting.php 31 | In this config you can define your basic settings like below. 32 | 33 | ```php 34 | # config/setting.php 35 | return [ 36 | 'app_name' => 'My Application', 37 | 'user_limit' => 10, 38 | ]; 39 | ``` 40 | 41 | ```php 42 | Setting::get('app_name'); 43 | //retruns 44 | 'My Application' 45 | ``` 46 | 47 | ### You can also use multi level arrays 48 | 49 | ```php 50 | # config/setting.php 51 | return [ 52 | 'priorities' => [ 53 | 'low' => 1, 54 | 'medium' => 2, 55 | 'hight' => 3 56 | ], 57 | ]; 58 | ``` 59 | 60 | ```php 61 | Setting::get('priorities.medium'); 62 | //retruns 63 | 2 64 | ``` 65 | 66 | ### Defining optional config values 67 | 68 | If you want to store additional data for a particular setting you can do so using an array and name one of the parameters 69 | 'default_value' which will be the default for the setting and is what gets returned by Settings::get('app_name') in this case. 70 | 71 | ```php 72 | # config/setting.php 73 | return [ 74 | 'app_name' => [ 75 | 'type' => 'text', /* Optional config values */ 76 | 'max' => 255, /* Optional config values */ 77 | 'default_value' => 'My Application' /* <- This value will be returned by Setting::get('app_name') if key is not found in DB */ 78 | ], 79 | 'user_limit' => 10, 80 | ]; 81 | ``` 82 | 83 | ```php 84 | Setting::get('app_name'); 85 | //retruns 86 | 'My Application' 87 | 88 | // You can still access the optional parameters 89 | Setting::get('app_name.max'); 90 | //retruns 91 | 255 92 | ``` 93 | 94 | ### Get full structure with default_value keys 95 | 96 | By suffixing your key name with a dot or by using the `Setting::getWithDefaultSubKeys('app_name')` method will return all default parameters including the current value 97 | 98 | ```php 99 | Setting::get('app_name.'); 100 | //retruns 101 | [ 102 | 'type' => 'text', 103 | 'max' => 255, 104 | 'default_value' => 'My Application', 105 | 'value' => 'My Custom Application Name' //the value key will be added with the current value saved in the database (or default if not in database yet) 106 | ] 107 | ``` 108 | 109 | ### Scoped settings 110 | 111 | You might want to save some settings only for a certain user. 112 | You can do this using a placeholder (\_\*) inside your config key name. 113 | 114 | ```php 115 | # config/setting.php 116 | return [ 117 | 'user_*' => [ 118 | 'dark_mode' => false, 119 | 'permissions' => [ 120 | 'read' => true, 121 | 'write' => false, 122 | ] 123 | ], 124 | ]; 125 | ``` 126 | 127 | Set save the new setting on runtime: 128 | 129 | ```php 130 | // Save a new setting under user_1.dark_mode with a value of true 131 | Setting::set("user_{$user->id}.dark_mode", true); 132 | ``` 133 | 134 | Now you can get the value: 135 | 136 | ```php 137 | Setting::get("user_{$user->id}.dark_mode"); 138 | //returns 139 | true 140 | ``` 141 | 142 | The above will return null if the setting does not exist for this user. 143 | In order to return something else you can set a default as the second parameter: 144 | 145 | ```php 146 | Setting::get("user_{$otherUser->id}.dark_mode"); 147 | //returns 148 | false 149 | ``` 150 | 151 | Get only the changed user settings 152 | 153 | ```php 154 | Setting::set("user_{$otherUser->id}.dark_mode", true); 155 | Setting::set("user_{$otherUser->id}.permissions.write", true); 156 | 157 | Setting::get("user_{$otherUser->id}"); 158 | //returns 159 | [ 160 | 'dark_mode' => true, 161 | 'permissions' => [ 162 | 'write' => false 163 | ] 164 | ] 165 | ``` 166 | 167 | In order to get all user settings you can use the `getWithDefaultSubKeys()` method or suffix the main key with a dot. The result will return a merged array with the default values from and the config while the changed values from the database will overwrite the default values. 168 | 169 | ```php 170 | Setting::get("user_{$otherUser->id}."); 171 | // same as 172 | Setting::getWithDefaultSubKeys("user_{$otherUser->id}"); 173 | 174 | //returns 175 | [ 176 | 'dark_mode' => true, // this value comes from the database 177 | 'permissions' => [ 178 | 'read' => true, // this value is the default from the config 179 | 'write' => false, // this value is the default from the config 180 | ] 181 | ] 182 | ``` 183 | 184 | ## Usage 185 | 186 | ```php 187 | Setting::get('name'); 188 | // get setting value with key 'name' 189 | // If this key is not found in DB then it will return the value defined from the config file or null if the key is also not defined in the config file. 190 | 191 | Setting::get('name', 'Joe'); 192 | // get setting value with key 'name' 193 | // return 'Joe' if the key does not exists. This will overwrite the default coming from the config file. 194 | 195 | Setting::all(); 196 | // get all settings. 197 | // This will merge the setting.php config file with the values (only where lang is null) found in the database and returns a collection. 198 | 199 | Setting::lang('zh-TW')->get('name', 'Joe'); 200 | // get setting value with key and language 201 | 202 | Setting::set('name', 'Joe'); 203 | // set setting value by key 204 | 205 | Setting::lang('zh-TW')->set('name', 'Joe'); 206 | // set setting value by key and language 207 | 208 | Setting::has('name'); 209 | // check the key exists in database, return boolean 210 | 211 | Setting::lang('zh-TW')->has('name'); 212 | // check the key exists by language in database, return boolean 213 | 214 | Setting::forget('name'); 215 | // delete the setting from database by key 216 | 217 | Setting::lang('zh-TW')->forget('name'); 218 | // delete the setting from database by key and language 219 | ``` 220 | 221 | ## Dealing with locale 222 | 223 | By default language parameter are being resets every set or get calls. You could disable that and set your own long term language parameter forever using any route service provider or other method. 224 | 225 | ```php 226 | Setting::lang(App::getLocale())->langResetting(false); 227 | ``` 228 | 229 | ## Custom Setting Model 230 | 231 | The Setting model can be overwritten by creating a /config/laravel-setting.php config and adding: 232 | 233 | ```php 234 | 'model' => \App\YourModelName::class, 235 | ``` 236 | 237 | Your custom model needs to extend the **\JanisKelemen\Setting\EloquentStorage** class. 238 | 239 | 240 | ## Change log 241 | 242 | Please see the [changelog](changelog.md) for more information on what has changed recently. 243 | 244 | ## Testing 245 | 246 | ```bash 247 | $ composer test 248 | ``` 249 | 250 | ## Contributing 251 | 252 | Please see [contributing.md](contributing.md) for details. 253 | 254 | ## Security 255 | 256 | If you discover any security related issues, please send me a DM on Twitter [@janiskelemen](https://twitter.com/janiskelemen) instead of using the issue tracker. 257 | 258 | ## Credits 259 | 260 | This package is mostly a fork of [UniSharp/laravel-settings](https://github.com/UniSharp/laravel-settings) 261 | 262 | - [HelpSpace.io](https://helpspace.io/?ref=laravel-setting) 263 | - [Janis Kelemen](https://twitter.com/janiskelemen) 264 | - [All Contributors][link-contributors] 265 | 266 | ## License 267 | 268 | MIT. Please see the [license file](LICENSE) for more information. 269 | 270 | [ico-downloads]: https://img.shields.io/packagist/dt/janiskelemen/laravel-setting.svg?style=flat-square 271 | [ico-travis]: https://img.shields.io/travis/janiskelemen/laravel-setting/master.svg?style=flat-square 272 | [ico-styleci]: https://github.styleci.io/repos/166064246/shield?branch=master 273 | [link-packagist]: https://packagist.org/packages/janiskelemen/laravel-setting 274 | [link-downloads]: https://packagist.org/packages/janiskelemen/laravel-setting 275 | [link-travis]: https://travis-ci.org/janiskelemen/laravel-setting 276 | [link-styleci]: https://github.styleci.io/repos/166064246 277 | [link-author]: https://github.com/janiskelemen 278 | 279 | [link-contributors]: ../../contributors] 280 | -------------------------------------------------------------------------------- /src/Contracts/SettingStorageContract.php: -------------------------------------------------------------------------------- 1 | where('locale', $lang); 26 | } else { 27 | $setting = $setting->whereNull('locale'); 28 | } 29 | 30 | return $setting->first(); 31 | } 32 | 33 | /** 34 | * Save setting to database. 35 | * 36 | * @param string $key 37 | * @param string|array $value 38 | * @param string $lang 39 | * @return void 40 | */ 41 | public function store($key, $value, $lang) 42 | { 43 | $setting = ['key' => $key, 'value' => $value]; 44 | if (! is_null($lang)) { 45 | $setting['locale'] = $lang; 46 | } 47 | static::create($setting); 48 | } 49 | 50 | /** 51 | * Change setting in database. 52 | * 53 | * @param string $key 54 | * @param string|array $value 55 | * @param string $lang 56 | * @return void 57 | */ 58 | public function modify($key, $value, $lang) 59 | { 60 | if (! is_null($lang)) { 61 | $setting = static::where('locale', $lang); 62 | } else { 63 | $setting = new static(); 64 | } 65 | $setting->where('key', $key)->update(['value' => $value]); 66 | } 67 | 68 | /** 69 | * Delete setting from database. 70 | * 71 | * @param string $key 72 | * @param string $lang 73 | * @return void 74 | */ 75 | public function forget($key, $lang) 76 | { 77 | $setting = static::where('key', $key); 78 | if (! is_null($lang)) { 79 | $setting = $setting->where('locale', $lang); 80 | } else { 81 | $setting = $setting->whereNull('locale'); 82 | } 83 | $setting->delete(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Facades/Setting.php: -------------------------------------------------------------------------------- 1 | publishes([ 17 | __DIR__.'/../../database/migrations/' => database_path('migrations'), 18 | ], 'setting'); 19 | 20 | // Publishing the configuration file. 21 | $this->publishes([ 22 | __DIR__.'/../../config/setting.php' => config_path('setting.php'), 23 | ], 'setting'); 24 | } 25 | 26 | /** 27 | * Register any package services. 28 | * 29 | * @return void 30 | */ 31 | public function register() 32 | { 33 | $this->mergeConfigFrom( 34 | __DIR__.'/../../config/setting.php', 35 | 'setting' 36 | ); 37 | $settingModel = config('laravel-setting.model', \JanisKelemen\Setting\EloquentStorage::class); 38 | // Register the service the package provides. 39 | $this->app->bind('Setting', \JanisKelemen\Setting\Setting::class); 40 | $this->app->bind(\JanisKelemen\Setting\Contracts\SettingStorageContract::class, $settingModel); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Setting.php: -------------------------------------------------------------------------------- 1 | storage = $storage; 20 | $this->cache = $cache; 21 | } 22 | 23 | /** 24 | * Get all settings from database and merge into default config. 25 | * 26 | * @return Collection 27 | */ 28 | public function all() 29 | { 30 | $configSettings = collect(config('setting')); 31 | $configSettings->transform(function ($item, $key) { 32 | return is_array($item) && isset($item['default_value']) ? $item['default_value'] : $item; 33 | }); 34 | $all = $this->storage->whereLocale(null)->get()->pluck('value', 'key'); 35 | 36 | Facade::clearResolvedInstance('Setting'); 37 | 38 | return collect($all)->union($configSettings); 39 | } 40 | 41 | /** 42 | * Get default values from setting config file. 43 | * 44 | * @return string 45 | */ 46 | public function default($key) 47 | { 48 | $key = $this->dynamic_key($key); 49 | return is_array(config('setting.' . $key)) ? config('setting.' . $key . '.default_value', config('setting.' . $key)) : config('setting.' . $key); 50 | } 51 | 52 | /** 53 | * Check if the first key is present as dynamic inside the config. 54 | * For example: 55 | * Setting::get('user_1.dark_mode'); 56 | * Will return the default from the config named user_*.dark_mode if 57 | * no value is set for the user with id 1. 58 | * @return string 59 | */ 60 | public function dynamic_key($key) 61 | { 62 | $firstPartOfKey = explode('.', $key)[0]; 63 | $endOfFirstKeyIsNumber = is_numeric(substr($firstPartOfKey, -1)); 64 | $keyHasSeparator = isset(explode('_', $firstPartOfKey)[1]); 65 | 66 | if ($keyHasSeparator && $endOfFirstKeyIsNumber) { 67 | $dynamicKey = explode('_', $firstPartOfKey)[0] . '_*'; 68 | $key = config('setting.' . $dynamicKey) ? str_replace($firstPartOfKey, $dynamicKey, $key) : $key; 69 | } 70 | return $key; 71 | } 72 | 73 | /** 74 | * Return setting with all default sub keys. 75 | * 76 | * @param string $key 77 | * @param string $value 78 | * 79 | * @return string|null 80 | */ 81 | public function getWithDefaultSubKeys($key, $default_value = null) 82 | { 83 | return $this->get($key . '.', $default_value); 84 | } 85 | 86 | /** 87 | * Return setting value or default value by key. 88 | * 89 | * @param string $key 90 | * @param string $value 91 | * @param bool $skip_cache 92 | * 93 | * @return string|null 94 | */ 95 | public function get($key, $default_value = null, $skip_cache = false) 96 | { 97 | if (substr($key, -1) == '.') { 98 | $key = substr($key, 0, -1); 99 | $setting = $this->getByKey($key, $skip_cache); 100 | $setting = $this->mergeValues($key, $setting) ?: $setting; 101 | } elseif (strpos($key, '.') !== false) { 102 | $setting = $this->getSubValue($key, $skip_cache); 103 | } else { 104 | if ($this->hasByKey($key)) { 105 | $setting = $this->getByKey($key, $skip_cache); 106 | } else { 107 | $setting = $default_value; 108 | } 109 | } 110 | $this->resetLang(); 111 | if (is_null($setting)) { 112 | $setting = is_null($default_value) ? $this->default($key) : $default_value; 113 | } 114 | 115 | Facade::clearResolvedInstance('Setting'); 116 | 117 | return $setting; 118 | } 119 | 120 | /** 121 | * Returns the data in value field only 122 | * 123 | * @param string $key 124 | * @param string $value 125 | * @return void 126 | */ 127 | public function getValuesOnly($key, $default_value = null) 128 | { 129 | $values = $this->get($key, $default_value, true); 130 | return collect($values)->map(function ($item, $key) { 131 | if (is_array($item) && array_key_exists('value', $item)) { 132 | return $item['value']; 133 | } 134 | return $item; 135 | })->all(); 136 | } 137 | 138 | /** 139 | * Recursivly merge array values of a given key with config file values. 140 | * 141 | * @param string $key 142 | * @param mixed $value 143 | * 144 | * @return void 145 | */ 146 | public function mergeValues($mainkey, $value) 147 | { 148 | $setting = config('setting.' . $this->dynamic_key($mainkey)); 149 | 150 | if (!is_array($setting)) { 151 | return false; 152 | } 153 | if (Arr::exists($setting, 'default_value')) { 154 | $setting['value'] = $value; 155 | return $setting; 156 | } 157 | 158 | $dot = new \Adbar\Dot($setting); 159 | 160 | foreach ($dot->flatten() as $key => $value) { 161 | $keyParts = explode('.', $key); 162 | $lastKey = end($keyParts); 163 | 164 | if ($lastKey == 'default_value') { 165 | $parentKey = str_replace('.default_value', '', $key); 166 | $newValueKey = $parentKey . '.value'; 167 | $dot->set($newValueKey, $this->get($mainkey . '.' . $parentKey, $value, true)); 168 | } else { 169 | $dot->set($key, $this->get($mainkey . '.' . $key, $value, true)); 170 | } 171 | } 172 | 173 | $setting = $dot->all(); 174 | 175 | Facade::clearResolvedInstance('Setting'); 176 | 177 | return $setting; 178 | } 179 | 180 | /** 181 | * Set the setting by key and value. 182 | * 183 | * @param string $key 184 | * @param mixed $value 185 | * 186 | * @return void 187 | */ 188 | public function set($key, $value) 189 | { 190 | if (strpos($key, '.') !== false) { 191 | $this->setSubValue($key, $value); 192 | } else { 193 | $this->setByKey($key, $value); 194 | } 195 | $this->resetLang(); 196 | 197 | Facade::clearResolvedInstance('Setting'); 198 | } 199 | 200 | /** 201 | * Check if the setting exists. 202 | * 203 | * @param string $key 204 | * 205 | * @return bool 206 | */ 207 | public function has($key) 208 | { 209 | $exists = $this->hasByKey($key); 210 | $this->resetLang(); 211 | 212 | return $exists; 213 | } 214 | 215 | /** 216 | * Delete a setting. 217 | * 218 | * @param string $key 219 | * 220 | * @return void 221 | */ 222 | public function forget($key) 223 | { 224 | if (strpos($key, '.') !== false) { 225 | $this->forgetSubKey($key); 226 | } else { 227 | $this->forgetByKey($key); 228 | } 229 | $this->resetLang(); 230 | } 231 | 232 | /** 233 | * Should language parameter auto retested ? 234 | * 235 | * @param bool $option 236 | * 237 | * @return instance of Setting 238 | */ 239 | public function langResetting($option = false) 240 | { 241 | $this->autoResetLang = $option; 242 | 243 | return $this; 244 | } 245 | 246 | /** 247 | * Set the language to work with other functions. 248 | * 249 | * @param string $language 250 | * 251 | * @return instance of Setting 252 | */ 253 | public function lang($language) 254 | { 255 | if (empty($language)) { 256 | $this->resetLang(); 257 | } else { 258 | $this->lang = $language; 259 | } 260 | 261 | return $this; 262 | } 263 | 264 | /** 265 | * Reset the language so we could switch to other local. 266 | * 267 | * @param bool $force 268 | * 269 | * @return instance of Setting 270 | */ 271 | protected function resetLang($force = false) 272 | { 273 | if ($this->autoResetLang || $force) { 274 | $this->lang = null; 275 | } 276 | 277 | return $this; 278 | } 279 | 280 | protected function getByKey($key, $skip_cache = false) 281 | { 282 | if (strpos($key, '.') !== false) { 283 | $main_key = explode('.', $key)[0]; 284 | } else { 285 | $main_key = $key; 286 | } 287 | if (!$skip_cache && $this->cache->has($main_key . '@' . $this->lang)) { 288 | $setting = $this->cache->get($main_key . '@' . $this->lang); 289 | } else { 290 | $setting = $this->storage->retrieve($main_key, $this->lang); 291 | if (!is_null($setting)) { 292 | $setting = $setting->value; 293 | } 294 | $setting_array = json_decode($setting, true); 295 | if (is_array($setting_array)) { 296 | $setting = $setting_array; 297 | } 298 | $this->cache->add($main_key . '@' . $this->lang, $setting, 60); 299 | } 300 | 301 | return $setting; 302 | } 303 | 304 | protected function setByKey($key, $value) 305 | { 306 | if (is_array($value)) { 307 | $value = json_encode($value); 308 | } 309 | $main_key = explode('.', $key)[0]; 310 | if ($this->hasByKey($main_key)) { 311 | $this->storage->modify($main_key, $value, $this->lang); 312 | } else { 313 | $this->storage->store($main_key, $value, $this->lang); 314 | } 315 | if ($this->cache->has($main_key . '@' . $this->lang)) { 316 | $this->cache->forget($main_key . '@' . $this->lang); 317 | } 318 | } 319 | 320 | /** 321 | * Check if key exists. 322 | * 323 | * @param string $key 324 | * @return bool 325 | */ 326 | protected function hasByKey($key) 327 | { 328 | if (strpos($key, '.') !== false) { 329 | $setting = $this->getSubValue($key, true); 330 | } else { 331 | if ($this->cache->has($key . '@' . $this->lang)) { 332 | $setting = $this->cache->get($key . '@' . $this->lang); 333 | } else { 334 | $setting = $this->storage->retrieve($key, $this->lang); 335 | } 336 | } 337 | 338 | return ($setting === null) ? false : true; 339 | } 340 | 341 | /** 342 | * Remove key from db and cache. 343 | * 344 | * @param string $key 345 | * @return void 346 | */ 347 | protected function forgetByKey($key) 348 | { 349 | $this->storage->forget($key, $this->lang); 350 | $this->cache->forget($key . '@' . $this->lang); 351 | } 352 | 353 | /** 354 | * Get sub value. 355 | * 356 | * @param string $key 357 | * @return void 358 | */ 359 | protected function getSubValue($key, $skip_cache = false) 360 | { 361 | $setting = $this->getByKey($key, $skip_cache); 362 | $subkey = $this->removeMainKey($key); 363 | $setting = Arr::get($setting, $subkey); 364 | 365 | return $setting; 366 | } 367 | 368 | /** 369 | * Set sub value. 370 | * 371 | * @param string $key 372 | * @return void 373 | */ 374 | protected function setSubValue($key, $new_value) 375 | { 376 | $setting = $this->getByKey($key, true); 377 | $subkey = $this->removeMainKey($key); 378 | Arr::set($setting, $subkey, $new_value); 379 | $this->setByKey($key, $setting); 380 | } 381 | 382 | /** 383 | * Remove sub value. 384 | * 385 | * @param string $key 386 | * @return void 387 | */ 388 | protected function forgetSubKey($key) 389 | { 390 | $setting = $this->getByKey($key, true); 391 | $subkey = $this->removeMainKey($key); 392 | Arr::forget($setting, $subkey); 393 | $this->setByKey($key, $setting); 394 | } 395 | 396 | /** 397 | * Remove main key. 398 | * 399 | * @param string $key 400 | * @return string 401 | */ 402 | protected function removeMainKey($key) 403 | { 404 | $pos = strpos($key, '.'); 405 | $subkey = substr($key, $pos + 1); 406 | 407 | return $subkey; 408 | } 409 | } 410 | -------------------------------------------------------------------------------- /tests/SettingTest.php: -------------------------------------------------------------------------------- 1 | loadMigrationsFrom(__DIR__ . '/../database/migrations'); 21 | } 22 | 23 | protected function getPackageProviders($app) 24 | { 25 | return [ 26 | 'JanisKelemen\Setting\Providers\SettingServiceProvider', 27 | \Orchestra\Database\ConsoleServiceProvider::class, 28 | ]; 29 | } 30 | 31 | /** 32 | * Define environment setup. 33 | * 34 | * @param \Illuminate\Foundation\Application $app 35 | * @return void 36 | */ 37 | protected function getEnvironmentSetUp($app) 38 | { 39 | // Register some setting defaults for testing 40 | $app['config']->set('setting.version', '1.0'); 41 | $app['config']->set('setting.app_name', [ 42 | 'type' => 'text', 43 | 'default_value' => 'My Application', 44 | ]); 45 | } 46 | 47 | /** 48 | * @test 49 | */ 50 | public function getSetWithoutCache() 51 | { 52 | $cache = Mockery::mock(CacheContract::class); 53 | $cache->shouldReceive('has')->andReturn(false); 54 | $cache->shouldReceive('add')->andReturn(true); 55 | 56 | $setting = new Setting(new EloquentStorage(), $cache); 57 | 58 | $setting->set('key', 'value'); 59 | 60 | $this->assertSame('value', $setting->get('key')); 61 | } 62 | 63 | /** 64 | * @test 65 | */ 66 | public function getSetWithCache() 67 | { 68 | $cache = Mockery::mock(CacheContract::class); 69 | $cache->shouldReceive('has')->andReturn(true); 70 | $cache->shouldReceive('get')->with('key@')->andReturn('value'); 71 | $cache->shouldReceive('forget')->andReturn(true); 72 | 73 | $setting = new Setting(new EloquentStorage(), $cache); 74 | 75 | $setting->set('key', 'value'); 76 | 77 | $this->assertSame('value', $setting->get('key')); 78 | } 79 | 80 | /** 81 | * @test 82 | */ 83 | public function getSetDotValueWithoutCache() 84 | { 85 | $cache = Mockery::mock(CacheContract::class); 86 | $cache->shouldReceive('has')->andReturn(false); 87 | $cache->shouldReceive('add')->andReturn(true); 88 | 89 | $setting = new Setting(new EloquentStorage(), $cache); 90 | 91 | $setting->set('key', $arr = ['a' => 'va', 'b' => 'vb']); 92 | 93 | $this->assertSame($arr, $setting->get('key')); 94 | $this->assertSame($arr['a'], $setting->get('key.a')); 95 | 96 | $setting->set('key2.c', 'val2c'); 97 | 98 | $this->assertSame('val2c', $setting->get('key2.c')); 99 | $this->assertSame(['c' => 'val2c'], $setting->get('key2')); 100 | } 101 | 102 | /** 103 | * @test 104 | */ 105 | public function setValueWithLanguage() 106 | { 107 | $cache = Mockery::mock(CacheContract::class); 108 | $cache->shouldReceive('has')->andReturn(false); 109 | $cache->shouldReceive('add')->andReturn(true); 110 | 111 | $setting = new Setting(new EloquentStorage(), $cache); 112 | 113 | $setting->lang('lang1')->set('key', 'val1'); 114 | 115 | $this->assertSame('val1', $setting->lang('lang1')->get('key')); 116 | $this->assertNull($setting->get('key')); 117 | } 118 | 119 | /** 120 | * @test 121 | */ 122 | public function forgetSetting() 123 | { 124 | $cache = Mockery::mock(CacheContract::class); 125 | $cache->shouldReceive('has')->andReturn(false); 126 | $cache->shouldReceive('add')->andReturn(true); 127 | $cache->shouldReceive('forget')->andReturn(true); 128 | 129 | $setting = new Setting(new EloquentStorage(), $cache); 130 | 131 | $setting->set('key', 'value'); 132 | 133 | $this->assertSame('value', $setting->get('key')); 134 | 135 | $setting->forget('key'); 136 | 137 | $this->assertNull($setting->get('key')); 138 | } 139 | 140 | /** 141 | * @test 142 | */ 143 | public function getDefaultOfSetting() 144 | { 145 | $cache = Mockery::mock(CacheContract::class); 146 | $cache->shouldReceive('has')->andReturn(false); 147 | $cache->shouldReceive('add')->andReturn(true); 148 | 149 | $setting = new Setting(new EloquentStorage(), $cache); 150 | 151 | $setting->set('key', 'value'); 152 | 153 | $this->assertSame('value', $setting->get('key', 'new-value')); 154 | $this->assertSame('new-value', $setting->get('not-exists', 'new-value')); 155 | 156 | $setting->set('key2.a', 'value-a'); 157 | 158 | $this->assertSame(['a' => 'value-a'], $setting->get('key2', 'new-value')); 159 | $this->assertSame('value-a', $setting->get('key2.a', 'new-value')); 160 | $this->assertSame('new-value', $setting->get('key2.b', 'new-value')); 161 | } 162 | 163 | /** 164 | * @test 165 | */ 166 | public function getDefaultOfSettingFromConfigIfNotFoundInDatabase() 167 | { 168 | $cache = Mockery::mock(CacheContract::class); 169 | $cache->shouldReceive('has')->andReturn(false); 170 | $cache->shouldReceive('add')->andReturn(true); 171 | 172 | $setting = new Setting(new EloquentStorage(), $cache); 173 | 174 | // Get setting from setting config file 175 | $this->assertSame('My Application', $setting->get('app_name')); 176 | 177 | $setting->set('app_name', 'Test Application'); 178 | 179 | $this->assertSame('Test Application', $setting->get('app_name')); 180 | } 181 | 182 | /** 183 | * @test 184 | */ 185 | public function getValueOfSettingFromDatabaseOfMultiArrayConfig() 186 | { 187 | config(['setting.field' => ['type' => 'input', 'default_value' => 'not set']]); 188 | $cache = Mockery::mock(CacheContract::class); 189 | $cache->shouldReceive('has')->andReturn(false); 190 | $cache->shouldReceive('add')->andReturn(true); 191 | 192 | $setting = new Setting(new EloquentStorage(), $cache); 193 | 194 | // Get setting from setting config file 195 | $this->assertSame('not set', $setting->get('field')); 196 | 197 | $setting->set('field', 'value field overwrite'); 198 | 199 | $this->assertSame('value field overwrite', $setting->get('field')); 200 | $this->assertArrayHasKey('value', $setting->get('field.')); 201 | $this->assertArrayHasKey('default_value', $setting->get('field.')); 202 | $this->assertArrayHasKey('type', $setting->get('field.')); 203 | } 204 | 205 | /** 206 | * @test 207 | */ 208 | public function getAllValuesOfSettingFromDatabaseOfMultiArrayConfig() 209 | { 210 | config([ 211 | 'setting.fields' => [ 212 | 'field1' => [ 213 | 'type' => 'input', 214 | 'default_value' => 'field 1 not set', 215 | ], 216 | 'field2' => [ 217 | 'type' => 'input', 218 | 'default_value' => 'field 2 not set', 219 | ], 220 | ], 221 | ]); 222 | $cache = Mockery::mock(CacheContract::class); 223 | $cache->shouldReceive('has')->andReturn(false); 224 | $cache->shouldReceive('add')->andReturn(true); 225 | 226 | $setting = new Setting(new EloquentStorage(), $cache); 227 | 228 | $setting->set('fields.field1', 'field 1 overwrite'); 229 | 230 | $this->assertSame('field 1 overwrite', $setting->get('fields.field1')); 231 | 232 | $this->assertSame('field 1 overwrite', $setting->get('fields.')['field1']['value']); 233 | $this->assertArrayHasKey('value', $setting->get('fields.')['field1']); 234 | $this->assertArrayHasKey('default_value', $setting->get('fields.')['field1']); 235 | $this->assertArrayHasKey('type', $setting->get('fields.')['field1']); 236 | } 237 | 238 | /** 239 | * @test 240 | */ 241 | public function getAllValuesOfSettingFromDatabaseOfDynamicArrayConfig() 242 | { 243 | config([ 244 | 'setting.user_*' => [ 245 | 'permissions' => [ 246 | 'read' => true, 247 | 'write' => false, 248 | ], 249 | 'language' => 'en', 250 | 'dark_mode' => true 251 | ], 252 | ]); 253 | $cache = Mockery::mock(CacheContract::class); 254 | $cache->shouldReceive('has')->andReturn(false); 255 | $cache->shouldReceive('add')->andReturn(true); 256 | 257 | $setting = new Setting(new EloquentStorage(), $cache); 258 | 259 | $this->assertSame($setting->get('user_1.permissions.read'), true); 260 | $this->assertSame($setting->get('user_1.language'), 'en'); 261 | 262 | $setting->set('user_1.permissions.write', true); 263 | 264 | $this->assertSame($setting->get('user_1.permissions.write'), true); 265 | $this->assertSame($setting->get('user_1.permissions.read'), true); 266 | 267 | $setting->set('user_1.permissions.read', false); 268 | $setting->set('user_1.language', 'de'); 269 | 270 | $this->assertSame($setting->get('user_1.permissions.read'), false); 271 | $this->assertSame($setting->get('user_1.language'), 'de'); 272 | 273 | $this->assertArrayHasKey('dark_mode', $setting->getWithDefaultSubKeys('user_1')); 274 | $this->assertArrayHasKey('dark_mode', $setting->get('user_1.')); 275 | } 276 | 277 | /** 278 | * @test 279 | */ 280 | public function getOptionalAttributeFromConfigFile() 281 | { 282 | $cache = Mockery::mock(CacheContract::class); 283 | $cache->shouldReceive('has')->andReturn(false); 284 | $cache->shouldReceive('add')->andReturn(true); 285 | 286 | $setting = new Setting(new EloquentStorage(), $cache); 287 | 288 | // Get setting from setting config file 289 | $this->assertSame('text', $setting->get('app_name.type')); 290 | } 291 | 292 | /** 293 | * @test 294 | */ 295 | public function getAllMergedSettings() 296 | { 297 | $cache = Mockery::mock(CacheContract::class); 298 | $cache->shouldReceive('has')->andReturn(false); 299 | $cache->shouldReceive('add')->andReturn(true); 300 | 301 | $setting = new Setting(new EloquentStorage(), $cache); 302 | 303 | $this->assertSame('My Application', $setting->all()->get('app_name')); 304 | $setting->set('app_name', 'Test Application'); 305 | 306 | $this->assertSame('Test Application', $setting->all()->get('app_name')); 307 | } 308 | 309 | /** 310 | * @test 311 | */ 312 | public function getAlwaysStringOfDefaultSetting() 313 | { 314 | $cache = Mockery::mock(CacheContract::class); 315 | $cache->shouldReceive('has')->andReturn(false); 316 | $cache->shouldReceive('add')->andReturn(true); 317 | 318 | $setting = new Setting(new EloquentStorage(), $cache); 319 | 320 | $this->assertTrue(is_array(config('setting.app_name'))); 321 | 322 | $this->assertFalse(is_array($setting->get('app_name'))); 323 | } 324 | 325 | /** 326 | * @test 327 | */ 328 | public function settingCanHaveNullValue() 329 | { 330 | $cache = Mockery::mock(CacheContract::class); 331 | $cache->shouldReceive('has')->andReturn(false); 332 | $cache->shouldReceive('add')->andReturn(true); 333 | 334 | $setting = new Setting(new EloquentStorage(), $cache); 335 | $setting->set('a', null); 336 | $this->assertTrue($setting->get('a') === null); 337 | $this->assertTrue($setting->get('b') === null); 338 | 339 | $setting->set('foo.bar', null); 340 | $this->assertTrue($setting->get('foo.bar') === null); 341 | 342 | $this->assertTrue($setting->get('foo.xxx') === null); 343 | 344 | $setting->set('foo.zzz', 0); 345 | $this->assertTrue($setting->get('foo.zzz') === 0); 346 | 347 | $setting->set('foo.yyy', []); 348 | $this->assertTrue($setting->get('foo.yyy') === []); 349 | } 350 | } 351 | --------------------------------------------------------------------------------