├── src ├── Models │ └── Setting.php ├── Stores │ ├── ArrayStore.php │ ├── RedisStore.php │ ├── JsonStore.php │ ├── AbstractStore.php │ └── DatabaseStore.php ├── Utilities │ └── Arr.php ├── Contracts │ ├── Manager.php │ └── Store.php ├── SettingsManager.php ├── Middleware │ └── SaveSettings.php └── SettingsServiceProvider.php ├── helpers.php ├── LICENSE.md ├── database └── migrations │ └── 2015_00_00_000000_create_settings_table.php ├── config └── settings.php ├── composer.json └── README.md /src/Models/Setting.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class Setting extends PrefixedModel 15 | { 16 | // 17 | } 18 | -------------------------------------------------------------------------------- /helpers.php: -------------------------------------------------------------------------------- 1 | driver($driver) : $manager; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) ARCANEDEV 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Stores/ArrayStore.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class ArrayStore extends AbstractStore 13 | { 14 | /* ----------------------------------------------------------------- 15 | | Post-Constructor 16 | | ----------------------------------------------------------------- 17 | */ 18 | 19 | /** 20 | * Fire the post options to customize the store. 21 | * 22 | * @param array $options 23 | */ 24 | protected function postOptions(array $options) 25 | { 26 | // Do nothing... 27 | } 28 | 29 | /* ----------------------------------------------------------------- 30 | | Main Methods 31 | | ----------------------------------------------------------------- 32 | */ 33 | 34 | /** 35 | * Read the data from the store. 36 | * 37 | * @return array 38 | */ 39 | protected function read() 40 | { 41 | return $this->data; 42 | } 43 | 44 | /** 45 | * Write the data into the store. 46 | * 47 | * @param array $data 48 | */ 49 | protected function write(array $data) 50 | { 51 | // Nothing to do... 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /database/migrations/2015_00_00_000000_create_settings_table.php: -------------------------------------------------------------------------------- 1 | setConnection(config('settings.drivers.database.options.connection')); 21 | $this->setTable(config('settings.drivers.database.options.table', 'settings')); 22 | } 23 | 24 | /* ----------------------------------------------------------------- 25 | | Main Methods 26 | | ----------------------------------------------------------------- 27 | */ 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function up(): void 33 | { 34 | $this->createSchema(function(Blueprint $table): void { 35 | $table->unsignedBigInteger('user_id')->default(0); 36 | $table->string('key'); 37 | $table->text('value'); 38 | $table->timestamps(); 39 | 40 | $table->unique(['user_id', 'key']); 41 | }); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /src/Utilities/Arr.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Arr extends BaseArr 16 | { 17 | /* ----------------------------------------------------------------- 18 | | Main Methods 19 | | ----------------------------------------------------------------- 20 | */ 21 | 22 | /** 23 | * Set an array item to a given value using "dot" notation. 24 | * 25 | * If no key is given to the method, the entire array will be replaced. 26 | * 27 | * @param array &$array 28 | * @param string $key 29 | * @param mixed $value 30 | * 31 | * @return array 32 | */ 33 | public static function set(&$array, $key, $value): array 34 | { 35 | $segments = explode('.', $key); 36 | $key = array_pop($segments); 37 | 38 | // iterate through all of $segments except the last one 39 | foreach ($segments as $segment) { 40 | if ( ! array_key_exists($segment, $array)) { 41 | $array[$segment] = []; 42 | } 43 | elseif ( ! is_array($array[$segment])) { 44 | throw new UnexpectedValueException('Non-array segment encountered'); 45 | } 46 | 47 | $array =& $array[$segment]; 48 | } 49 | 50 | $array[$key] = $value; 51 | 52 | return $array; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Contracts/Manager.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | interface Manager 15 | { 16 | /* ----------------------------------------------------------------- 17 | | Getters & Setters 18 | | ----------------------------------------------------------------- 19 | */ 20 | 21 | /** 22 | * Get the default driver name. 23 | * 24 | * @return string 25 | */ 26 | public function getDefaultDriver(); 27 | 28 | /** 29 | * Get all of the created "drivers". 30 | * 31 | * @return array 32 | */ 33 | public function getDrivers(); 34 | 35 | /* ----------------------------------------------------------------- 36 | | Main Methods 37 | | ----------------------------------------------------------------- 38 | */ 39 | 40 | /** 41 | * Get a driver instance. 42 | * 43 | * @param string|null $driver 44 | * 45 | * @return \Arcanedev\LaravelSettings\Contracts\Store 46 | */ 47 | public function driver($driver = null); 48 | 49 | /** 50 | * Register a custom driver creator Closure. 51 | * 52 | * @param string $driver 53 | * @param \Closure $callback 54 | * 55 | * @return $this 56 | */ 57 | public function extend($driver, Closure $callback); 58 | 59 | /** 60 | * Register a new store. 61 | * 62 | * @param string $driver 63 | * @param array $params 64 | * 65 | * @return $this 66 | */ 67 | public function registerStore(string $driver, array $params); 68 | } 69 | -------------------------------------------------------------------------------- /config/settings.php: -------------------------------------------------------------------------------- 1 | 'json', 12 | 13 | /* ----------------------------------------------------------------- 14 | | Drivers 15 | | ----------------------------------------------------------------- 16 | */ 17 | 18 | 'drivers' => [ 19 | 20 | 'array' => [ 21 | 'driver' => Arcanedev\LaravelSettings\Stores\ArrayStore::class, 22 | ], 23 | 24 | 'json' => [ 25 | 'driver' => Arcanedev\LaravelSettings\Stores\JsonStore::class, 26 | 27 | 'options' => [ 28 | 'path' => storage_path('app/settings.json'), 29 | ], 30 | ], 31 | 32 | 'database' => [ 33 | 'driver' => Arcanedev\LaravelSettings\Stores\DatabaseStore::class, 34 | 35 | 'options' => [ 36 | 'connection' => null, 37 | 'table' => 'settings', 38 | 'model' => Arcanedev\LaravelSettings\Models\Setting::class, 39 | ], 40 | ], 41 | 42 | 'redis' => [ 43 | 'driver' => Arcanedev\LaravelSettings\Stores\RedisStore::class, 44 | 45 | 'options' => [ 46 | 'client' => 'predis', 47 | 48 | 'default' => [ 49 | 'host' => env('REDIS_HOST', '127.0.0.1'), 50 | 'port' => env('REDIS_PORT', 6379), 51 | 'database' => env('REDIS_DB', 0), 52 | ], 53 | ], 54 | ], 55 | 56 | ], 57 | 58 | ]; 59 | -------------------------------------------------------------------------------- /src/SettingsManager.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class SettingsManager extends Manager implements SettingsManagerContract 17 | { 18 | /* ----------------------------------------------------------------- 19 | | Properties 20 | | ----------------------------------------------------------------- 21 | */ 22 | 23 | /** 24 | * Indicates if migrations will be run. 25 | * 26 | * @var bool 27 | */ 28 | public static $runsMigrations = true; 29 | 30 | /* ----------------------------------------------------------------- 31 | | Getters & Setters 32 | | ----------------------------------------------------------------- 33 | */ 34 | 35 | /** 36 | * Get the default driver name. 37 | * 38 | * @return string 39 | */ 40 | public function getDefaultDriver() 41 | { 42 | return $this->config->get('settings.default', 'json'); 43 | } 44 | 45 | /** 46 | * Register a new store. 47 | * 48 | * @param string $driver 49 | * @param array $params 50 | * 51 | * @return $this 52 | */ 53 | public function registerStore(string $driver, array $params) 54 | { 55 | return $this->extend($driver, function () use ($params) : StoreContract { 56 | return $this->container->make($params['driver'], [ 57 | 'options' => Arr::get($params, 'options', []), 58 | ]); 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Middleware/SaveSettings.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class SaveSettings 16 | { 17 | /* ----------------------------------------------------------------- 18 | | Properties 19 | | ----------------------------------------------------------------- 20 | */ 21 | 22 | /** @var \Arcanedev\LaravelSettings\Contracts\Store */ 23 | protected $settings; 24 | 25 | /* ----------------------------------------------------------------- 26 | | Constructor 27 | | ----------------------------------------------------------------- 28 | */ 29 | 30 | /** 31 | * SaveSettings constructor. 32 | * 33 | * @param \Arcanedev\LaravelSettings\Contracts\Store $settings 34 | */ 35 | public function __construct(Store $settings) 36 | { 37 | $this->settings = $settings; 38 | } 39 | 40 | /* ----------------------------------------------------------------- 41 | | Main Methods 42 | | ----------------------------------------------------------------- 43 | */ 44 | 45 | /** 46 | * Handle an incoming request. 47 | * 48 | * @param \Illuminate\Http\Request $request 49 | * @param Closure $next 50 | * 51 | * @return mixed 52 | */ 53 | public function handle($request, Closure $next) 54 | { 55 | return $next($request); 56 | } 57 | 58 | /** 59 | * Hasta la vista, baby. 60 | * 61 | * @param \Illuminate\Http\Request $request 62 | * @param \Symfony\Component\HttpFoundation\Response $response 63 | */ 64 | public function terminate($request, $response) 65 | { 66 | $this->settings->save(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Contracts/Store.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface Store 13 | { 14 | /* ----------------------------------------------------------------- 15 | | Main Methods 16 | | ----------------------------------------------------------------- 17 | */ 18 | 19 | /** 20 | * Get a specific key from the settings data. 21 | * 22 | * @param string $key 23 | * @param mixed $default 24 | * 25 | * @return mixed 26 | */ 27 | public function get($key, $default = null); 28 | 29 | /** 30 | * Determine if a key exists in the settings data. 31 | * 32 | * @param string $key 33 | * 34 | * @return bool 35 | */ 36 | public function has($key); 37 | 38 | /** 39 | * Set a specific key to a value in the settings data. 40 | * 41 | * @param string|array $key 42 | * @param mixed $value 43 | * 44 | * @return $this 45 | */ 46 | public function set($key, $value = null); 47 | 48 | /** 49 | * Unset a key in the settings data. 50 | * 51 | * @param string $key 52 | * 53 | * @return $this 54 | */ 55 | public function forget($key); 56 | 57 | /** 58 | * Flushing all data. 59 | * 60 | * @return $this 61 | */ 62 | public function flush(); 63 | 64 | /** 65 | * Get all settings data. 66 | * 67 | * @return array 68 | */ 69 | public function all(); 70 | 71 | /** 72 | * Save any changes done to the settings data. 73 | * 74 | * @return $this 75 | */ 76 | public function save(); 77 | 78 | /* ----------------------------------------------------------------- 79 | | Check Methods 80 | | ----------------------------------------------------------------- 81 | */ 82 | 83 | /** 84 | * Check if the data is saved. 85 | * 86 | * @return bool 87 | */ 88 | public function isSaved(); 89 | } 90 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arcanedev/laravel-settings", 3 | "description": "This package allows you to persists configs/settings for Laravel projects.", 4 | "homepage": "https://github.com/ARCANEDEV/LaravelSettings", 5 | "keywords": [ 6 | "arcanedev", "laravel", "config", "settings", "json", "array", "database", "db" 7 | ], 8 | "authors": [ 9 | { 10 | "name": "ARCANEDEV", 11 | "email": "arcanedev.maroc@gmail.com", 12 | "homepage": "https://github.com/arcanedev-maroc", 13 | "role": "Developer" 14 | } 15 | ], 16 | "type": "library", 17 | "license": "MIT", 18 | "require": { 19 | "php": "^8.2", 20 | "ext-json": "*", 21 | "arcanedev/support": "^11.0" 22 | }, 23 | "require-dev": { 24 | "laravel/framework": "^11.0", 25 | "mockery/mockery": "^1.6", 26 | "orchestra/testbench-core": "^9.0", 27 | "phpunit/phpunit": "^10.5|^11.0", 28 | "predis/predis": "^2.0.2" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Arcanedev\\LaravelSettings\\": "src/" 33 | }, 34 | "files": ["helpers.php"] 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "Arcanedev\\LaravelSettings\\Tests\\": "tests/" 39 | } 40 | }, 41 | "extra": { 42 | "branch-alias": { 43 | "dev-develop": "11.x-dev" 44 | }, 45 | "laravel": { 46 | "providers": [ 47 | "Arcanedev\\LaravelSettings\\SettingsServiceProvider" 48 | ] 49 | } 50 | }, 51 | "scripts": { 52 | "test": "phpunit --colors=always", 53 | "test:dox": "phpunit --testdox --colors=always", 54 | "test:cov": "phpunit --coverage-html coverage" 55 | }, 56 | "suggest": { 57 | "illuminate/redis": "This package allows you to persist the settings into a redis server." 58 | }, 59 | "config": { 60 | "optimize-autoloader": true, 61 | "preferred-install": "dist", 62 | "sort-packages": true 63 | }, 64 | "minimum-stability": "dev", 65 | "prefer-stable": true 66 | } 67 | -------------------------------------------------------------------------------- /src/SettingsServiceProvider.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class SettingsServiceProvider extends PackageServiceProvider implements DeferrableProvider 18 | { 19 | /* ----------------------------------------------------------------- 20 | | Properties 21 | | ----------------------------------------------------------------- 22 | */ 23 | 24 | /** 25 | * Package name. 26 | * 27 | * @var string 28 | */ 29 | protected $package = 'settings'; 30 | 31 | /* ----------------------------------------------------------------- 32 | | Main Methods 33 | | ----------------------------------------------------------------- 34 | */ 35 | 36 | /** 37 | * Register the service provider. 38 | */ 39 | public function register(): void 40 | { 41 | parent::register(); 42 | 43 | $this->registerConfig(); 44 | 45 | $this->registerSettingsManager(); 46 | } 47 | 48 | /** 49 | * Boot the service provider. 50 | */ 51 | public function boot(): void 52 | { 53 | SettingsManager::$runsMigrations ? $this->loadMigrations() : $this->publishMigrations(); 54 | 55 | if ($this->app->runningInConsole()) { 56 | $this->publishConfig(); 57 | } 58 | } 59 | 60 | /** 61 | * Get the services provided by the provider. 62 | * 63 | * @return array 64 | */ 65 | public function provides(): array 66 | { 67 | return [ 68 | ManagerContract::class, 69 | StoreContract::class, 70 | ]; 71 | } 72 | 73 | /* ----------------------------------------------------------------- 74 | | Other Methods 75 | | ----------------------------------------------------------------- 76 | */ 77 | 78 | /** 79 | * Register the Settings Manager & Store drivers. 80 | */ 81 | private function registerSettingsManager(): void 82 | { 83 | $this->singleton(ManagerContract::class, SettingsManager::class); 84 | 85 | $this->app->extend(ManagerContract::class, function (ManagerContract $manager, $app) { 86 | foreach ($app['config']->get('settings.drivers', []) as $driver => $params) { 87 | $manager->registerStore($driver, $params); 88 | } 89 | 90 | return $manager; 91 | }); 92 | 93 | $this->singleton(StoreContract::class, function ($app): StoreContract { 94 | return $app[ManagerContract::class]->driver(); 95 | }); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Stores/RedisStore.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class RedisStore extends AbstractStore 16 | { 17 | /* ----------------------------------------------------------------- 18 | | Properties 19 | | ----------------------------------------------------------------- 20 | */ 21 | 22 | /** 23 | * The redis manager. 24 | * 25 | * @var \Illuminate\Redis\RedisManager 26 | */ 27 | protected $manager; 28 | 29 | /* ----------------------------------------------------------------- 30 | | Post-Constructor 31 | | ----------------------------------------------------------------- 32 | */ 33 | 34 | /** 35 | * Fire the post options to customize the store. 36 | * 37 | * @param array $options 38 | */ 39 | protected function postOptions(array $options) 40 | { 41 | $this->manager = new RedisManager( 42 | $this->app, Arr::pull($options, 'client', 'predis'), $options 43 | ); 44 | } 45 | 46 | /* ----------------------------------------------------------------- 47 | | Main Methods 48 | | ----------------------------------------------------------------- 49 | */ 50 | 51 | /** 52 | * Read the data from the store. 53 | * 54 | * @return array 55 | */ 56 | protected function read() 57 | { 58 | $data = $this->command('get', ['settings']); 59 | 60 | return is_string($data) ? json_decode($data, true) : []; 61 | } 62 | 63 | /** 64 | * Write the data into the store. 65 | * 66 | * @param array $data 67 | */ 68 | protected function write(array $data) 69 | { 70 | $this->command('set', ['settings', json_encode($data)]); 71 | } 72 | 73 | /* ----------------------------------------------------------------- 74 | | Other Methods 75 | | ----------------------------------------------------------------- 76 | */ 77 | 78 | /** 79 | * Get a Redis connection by name. 80 | * 81 | * @param string|null $name 82 | * 83 | * @return \Illuminate\Redis\Connections\Connection 84 | */ 85 | protected function connection($name = null) 86 | { 87 | return $this->manager->connection($name); 88 | } 89 | 90 | /** 91 | * Run a command against the Redis database. 92 | * 93 | * @param string $method 94 | * @param array $parameters 95 | * 96 | * @return mixed 97 | */ 98 | protected function command(string $method, array $parameters = []) 99 | { 100 | return $this->connection()->command($method, $parameters); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Stores/JsonStore.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class JsonStore extends AbstractStore 16 | { 17 | /* ----------------------------------------------------------------- 18 | | Properties 19 | | ----------------------------------------------------------------- 20 | */ 21 | 22 | /** @var string */ 23 | protected $path; 24 | 25 | /* ----------------------------------------------------------------- 26 | | Post-Constructor 27 | | ----------------------------------------------------------------- 28 | */ 29 | 30 | /** 31 | * Fire the post options to customize the store. 32 | * 33 | * @param array $options 34 | */ 35 | protected function postOptions(array $options) 36 | { 37 | $this->setPath(Arr::get($options, 'path')); 38 | } 39 | 40 | /* ----------------------------------------------------------------- 41 | | Getters & Setters 42 | | ----------------------------------------------------------------- 43 | */ 44 | 45 | /** 46 | * Set the storage path for the json file. 47 | * 48 | * @param string $path 49 | * 50 | * @return self 51 | */ 52 | public function setPath($path) 53 | { 54 | $this->path = $path; 55 | 56 | return $this; 57 | } 58 | 59 | /* ----------------------------------------------------------------- 60 | | Main Methods 61 | | ----------------------------------------------------------------- 62 | */ 63 | 64 | /** 65 | * Read the data from the store. 66 | * 67 | * @return array 68 | */ 69 | protected function read() 70 | { 71 | $contents = $this->filesystem()->get($this->path); 72 | $data = json_decode($contents, true); 73 | 74 | if (is_null($data)) { 75 | throw new RuntimeException("Invalid JSON file in [{$this->path}]"); 76 | } 77 | 78 | return (array) $data; 79 | } 80 | 81 | /** 82 | * Write the data into the store. 83 | * 84 | * @param array $data 85 | */ 86 | protected function write(array $data) 87 | { 88 | $contents = $data ? json_encode($data) : '{}'; 89 | 90 | $this->filesystem()->put($this->path, $contents); 91 | } 92 | 93 | /* ----------------------------------------------------------------- 94 | | Other Methods 95 | | ----------------------------------------------------------------- 96 | */ 97 | 98 | /** 99 | * Get the filesystem instance. 100 | * 101 | * @return \Illuminate\Filesystem\Filesystem 102 | */ 103 | private function filesystem() 104 | { 105 | return $this->app['files']; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LaravelSettings [![Packagist License][badge_license]](LICENSE.md) [![For Laravel][badge_laravel]][link-github-repo] 2 | 3 | [![Github Workflow Status][badge_build]][link-github-status] 4 | [![Coverage Status][badge_coverage]][link-scrutinizer] 5 | [![Scrutinizer Code Quality][badge_quality]][link-scrutinizer] 6 | [![SensioLabs Insight][badge_insight]][link-insight] 7 | [![Github Issues][badge_issues]][link-github-issues] 8 | 9 | [![Packagist][badge_package]][link-packagist] 10 | [![Packagist Release][badge_release]][link-packagist] 11 | [![Packagist Downloads][badge_downloads]][link-packagist] 12 | 13 | *By [ARCANEDEV©](http://www.arcanedev.net/)* 14 | 15 | This package allows you to store/persists your settings data. 16 | 17 | Feel free to check out the [releases](https://github.com/ARCANEDEV/LaravelSettings/releases), [license](LICENSE.md), and [contribution guidelines](CONTRIBUTING.md). 18 | 19 | ### Features 20 | 21 | * Laravel `5.2` to `11.x` are supported. 22 | * Multiple store drivers: `array`, `json`, `database`, `redis`. 23 | * Easy setup & configuration. 24 | * Well documented & IDE Friendly. 25 | * Well tested with maximum code quality. 26 | * Made with :heart: & :coffee:. 27 | 28 | ## Table of contents 29 | 30 | 1. [Installation and Setup](_docs/1-Installation-and-Setup.md) 31 | 2. [Configuration](_docs/2-Configuration.md) 32 | 3. [Usage](_docs/3-Usage.md) 33 | 34 | ## Security 35 | 36 | If you discover any security related issues, please email arcanedev-maroc@gmail.com instead of using the issue tracker. 37 | 38 | ## Contribution 39 | 40 | Any ideas are welcome. Feel free to submit any issues or pull requests, please check the [contribution guidelines](CONTRIBUTING.md). 41 | 42 | ## Credits 43 | 44 | - [ARCANEDEV][link-author] 45 | - [All Contributors][link-contributors] 46 | 47 | [badge_license]: https://img.shields.io/packagist/l/arcanedev/laravel-settings.svg?style=flat-square 48 | [badge_laravel]: https://img.shields.io/badge/Laravel-5.2%20to%2011.x-orange.svg?style=flat-square 49 | [badge_build]: https://img.shields.io/github/workflow/status/ARCANEDEV/LaravelSettings/run-tests?style=flat-square 50 | [badge_coverage]: https://img.shields.io/scrutinizer/coverage/g/ARCANEDEV/LaravelSettings.svg?style=flat-square 51 | [badge_quality]: https://img.shields.io/scrutinizer/g/ARCANEDEV/LaravelSettings.svg?style=flat-square 52 | [badge_insight]: https://img.shields.io/sensiolabs/i/1ba3f1b4-4475-4db5-8f99-0af6fb6a80be.svg?style=flat-square 53 | [badge_issues]: https://img.shields.io/github/issues/ARCANEDEV/LaravelSettings.svg?style=flat-square 54 | [badge_package]: https://img.shields.io/badge/package-arcanedev/laravel--settings-blue.svg?style=flat-square 55 | [badge_release]: https://img.shields.io/packagist/v/arcanedev/laravel-settings.svg?style=flat-square 56 | [badge_downloads]: https://img.shields.io/packagist/dt/arcanedev/laravel-settings.svg?style=flat-square 57 | 58 | [link-author]: https://github.com/arcanedev-maroc 59 | [link-github-repo]: https://github.com/ARCANEDEV/LaravelSettings 60 | [link-github-status]: https://github.com/ARCANEDEV/LaravelSettings/actions 61 | [link-github-issues]: https://github.com/ARCANEDEV/LaravelSettings/issues 62 | [link-contributors]: https://github.com/ARCANEDEV/LaravelSettings/graphs/contributors 63 | [link-packagist]: https://packagist.org/packages/arcanedev/laravel-settings 64 | [link-scrutinizer]: https://scrutinizer-ci.com/g/ARCANEDEV/LaravelSettings/?branch=master 65 | [link-insight]: https://insight.sensiolabs.com/projects/1ba3f1b4-4475-4db5-8f99-0af6fb6a80be 66 | -------------------------------------------------------------------------------- /src/Stores/AbstractStore.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | abstract class AbstractStore implements Store 17 | { 18 | /* ----------------------------------------------------------------- 19 | | Properties 20 | | ----------------------------------------------------------------- 21 | */ 22 | 23 | /** 24 | * The laravel application instance. 25 | * 26 | * @var \Illuminate\Contracts\Foundation\Application 27 | */ 28 | protected $app; 29 | 30 | /** 31 | * The settings data. 32 | * 33 | * @var array 34 | */ 35 | protected $data = []; 36 | 37 | /** 38 | * Whether the store has changed since it was last loaded. 39 | * 40 | * @var bool 41 | */ 42 | protected $unsaved = false; 43 | 44 | /** 45 | * Whether the settings data are loaded. 46 | * 47 | * @var bool 48 | */ 49 | protected $loaded = false; 50 | 51 | /* ----------------------------------------------------------------- 52 | | Constructor 53 | | ----------------------------------------------------------------- 54 | */ 55 | 56 | /** 57 | * AbstractStore constructor. 58 | * 59 | * @param \Illuminate\Contracts\Foundation\Application $app 60 | * @param array $options 61 | */ 62 | public function __construct(Application $app, array $options = []) 63 | { 64 | $this->app = $app; 65 | $this->postOptions($options); 66 | } 67 | 68 | /** 69 | * Fire the post options to customize the store. 70 | * 71 | * @param array $options 72 | */ 73 | abstract protected function postOptions(array $options); 74 | 75 | /* ----------------------------------------------------------------- 76 | | Main Methods 77 | | ----------------------------------------------------------------- 78 | */ 79 | 80 | /** 81 | * Get a specific key from the settings data. 82 | * 83 | * @param string $key 84 | * @param mixed $default 85 | * 86 | * @return mixed 87 | */ 88 | public function get($key, $default = null) 89 | { 90 | $this->checkLoaded(); 91 | 92 | return Arr::get($this->data, $key, $default); 93 | } 94 | 95 | /** 96 | * Determine if a key exists in the settings data. 97 | * 98 | * @param string $key 99 | * 100 | * @return bool 101 | */ 102 | public function has($key) 103 | { 104 | $this->checkLoaded(); 105 | 106 | return Arr::has($this->data, $key); 107 | } 108 | 109 | /** 110 | * Set a specific key to a value in the settings data. 111 | * 112 | * @param string|array $key 113 | * @param mixed $value 114 | * 115 | * @return $this 116 | */ 117 | public function set($key, $value = null) 118 | { 119 | $this->checkLoaded(); 120 | $this->unsaved = true; 121 | 122 | if (is_array($key)) { 123 | foreach ($key as $k => $v) { 124 | Arr::set($this->data, $k, $v); 125 | } 126 | } 127 | else 128 | Arr::set($this->data, $key, $value); 129 | 130 | return $this; 131 | } 132 | 133 | /** 134 | * Unset a key in the settings data. 135 | * 136 | * @param string $key 137 | * 138 | * @return $this 139 | */ 140 | public function forget($key) 141 | { 142 | $this->checkLoaded(); 143 | 144 | $this->unsaved = true; 145 | 146 | Arr::forget($this->data, $key); 147 | 148 | return $this; 149 | } 150 | 151 | /** 152 | * Flushing all data. 153 | * 154 | * @return $this 155 | */ 156 | public function flush() 157 | { 158 | $this->unsaved = true; 159 | $this->data = []; 160 | 161 | return $this; 162 | } 163 | 164 | /** 165 | * Get all settings data. 166 | * 167 | * @return array 168 | */ 169 | public function all() 170 | { 171 | $this->checkLoaded(); 172 | 173 | return $this->data; 174 | } 175 | 176 | /** 177 | * Save any changes done to the settings data. 178 | * 179 | * @return $this 180 | */ 181 | public function save() 182 | { 183 | if ( ! $this->isSaved()) { 184 | $this->write($this->data); 185 | $this->unsaved = false; 186 | } 187 | 188 | return $this; 189 | } 190 | 191 | /** 192 | * Read the data from the store. 193 | * 194 | * @return array 195 | */ 196 | abstract protected function read(); 197 | 198 | /** 199 | * Write the data into the store. 200 | * 201 | * @param array $data 202 | * 203 | * @return void 204 | */ 205 | abstract protected function write(array $data); 206 | 207 | /* ----------------------------------------------------------------- 208 | | Check Methods 209 | | ----------------------------------------------------------------- 210 | */ 211 | 212 | /** 213 | * Check if the data is saved. 214 | * 215 | * @return bool 216 | */ 217 | public function isSaved() 218 | { 219 | return ! $this->unsaved; 220 | } 221 | 222 | /** 223 | * Check if the settings data has been loaded. 224 | */ 225 | protected function checkLoaded(): void 226 | { 227 | if ($this->isLoaded()) 228 | return; 229 | 230 | $this->data = $this->read(); 231 | $this->loaded = true; 232 | } 233 | 234 | /** 235 | * Reset the loaded status. 236 | */ 237 | protected function resetLoaded(): void 238 | { 239 | $this->loaded = false; 240 | } 241 | 242 | /** 243 | * Check if the data is loaded. 244 | * 245 | * @return bool 246 | */ 247 | protected function isLoaded(): bool 248 | { 249 | return (bool) $this->loaded; 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/Stores/DatabaseStore.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class DatabaseStore extends AbstractStore 19 | { 20 | /* ----------------------------------------------------------------- 21 | | Properties 22 | | ----------------------------------------------------------------- 23 | */ 24 | 25 | /** 26 | * The eloquent model. 27 | * 28 | * @var \Illuminate\Database\Eloquent\Model 29 | */ 30 | protected $model; 31 | 32 | /** 33 | * The key column name to query from. 34 | * 35 | * @var string 36 | */ 37 | protected $keyColumn; 38 | 39 | /** 40 | * The value column name to query from. 41 | * 42 | * @var string 43 | */ 44 | protected $valueColumn; 45 | 46 | /** 47 | * Any query constraints that should be applied. 48 | * 49 | * @var \Closure|null 50 | */ 51 | protected $queryConstraint; 52 | 53 | /** 54 | * Any extra columns that should be added to the rows. 55 | * 56 | * @var array 57 | */ 58 | protected $extraColumns = []; 59 | 60 | /* ----------------------------------------------------------------- 61 | | Post-Constructor 62 | | ----------------------------------------------------------------- 63 | */ 64 | 65 | /** 66 | * Fire the post options to customize the store. 67 | * 68 | * @param array $options 69 | */ 70 | protected function postOptions(array $options) 71 | { 72 | $this->model = $this->app->make( 73 | Arr::get($options, 'model', SettingModel::class) 74 | ); 75 | $this->setConnection(Arr::get($options, 'connection', null)); 76 | $this->setTable(Arr::get($options, 'table', 'settings')); 77 | $this->setKeyColumn(Arr::get($options, 'columns.key', 'key')); 78 | $this->setValueColumn(Arr::get($options, 'columns.value', 'value')); 79 | } 80 | 81 | /* ----------------------------------------------------------------- 82 | | Getters & Setters 83 | | ----------------------------------------------------------------- 84 | */ 85 | 86 | /** 87 | * Set the db connection to query from. 88 | * 89 | * @param string $name 90 | * 91 | * @return $this 92 | */ 93 | public function setConnection($name) 94 | { 95 | $this->model->setConnection($name); 96 | 97 | return $this; 98 | } 99 | 100 | /** 101 | * Set the table to query from. 102 | * 103 | * @param string $name 104 | * 105 | * @return $this 106 | */ 107 | public function setTable($name) 108 | { 109 | $this->model->setTable($name); 110 | 111 | return $this; 112 | } 113 | 114 | /** 115 | * Set the key column name to query from. 116 | * 117 | * @param string $name 118 | * 119 | * @return $this 120 | */ 121 | public function setKeyColumn($name) 122 | { 123 | $this->keyColumn = $name; 124 | 125 | return $this; 126 | } 127 | 128 | /** 129 | * Set the value column name to query from. 130 | * 131 | * @param string $name 132 | * 133 | * @return $this 134 | */ 135 | public function setValueColumn($name) 136 | { 137 | $this->valueColumn = $name; 138 | 139 | return $this; 140 | } 141 | 142 | /** 143 | * Set the query constraint. 144 | * 145 | * @param \Closure $callback 146 | * 147 | * @return $this 148 | */ 149 | public function setConstraint(Closure $callback) 150 | { 151 | $this->resetLoaded(); 152 | 153 | $this->queryConstraint = $callback; 154 | 155 | return $this; 156 | } 157 | 158 | /** 159 | * Set extra columns to be added to the rows. 160 | * 161 | * @param array $columns 162 | * 163 | * @return $this 164 | */ 165 | public function setExtraColumns(array $columns) 166 | { 167 | $this->resetLoaded(); 168 | 169 | $this->extraColumns = $columns; 170 | 171 | return $this; 172 | } 173 | 174 | /* ----------------------------------------------------------------- 175 | | Main Methods 176 | | ----------------------------------------------------------------- 177 | */ 178 | 179 | /** 180 | * Unset a key in the settings data. 181 | * 182 | * @param string $key 183 | * 184 | * @return $this 185 | */ 186 | public function forget($key) 187 | { 188 | parent::forget($key); 189 | 190 | // because the database store cannot store empty arrays, remove empty 191 | // arrays to keep data consistent before and after saving 192 | $segments = explode('.', $key); 193 | array_pop($segments); 194 | 195 | while ( ! empty($segments)) { 196 | $segment = implode('.', $segments); 197 | 198 | // non-empty array - exit out of the loop 199 | if ($this->get($segment)) break; 200 | 201 | // remove the empty array and move on to the next segment 202 | $this->forget($segment); 203 | array_pop($segments); 204 | } 205 | 206 | return $this; 207 | } 208 | 209 | /** 210 | * Read the data from the store. 211 | * 212 | * @return array 213 | */ 214 | protected function read(): array 215 | { 216 | return $this->newQuery() 217 | ->pluck($this->valueColumn, $this->keyColumn) 218 | ->toArray(); 219 | } 220 | 221 | /** 222 | * Write the data into the store. 223 | * 224 | * @param array $data 225 | */ 226 | protected function write(array $data): void 227 | { 228 | $changes = $this->getChanges($data); 229 | 230 | $this->syncUpdated($changes['updated']); 231 | $this->syncInserted($changes['inserted']); 232 | $this->syncDeleted($changes['deleted']); 233 | } 234 | 235 | /* ----------------------------------------------------------------- 236 | | Other Methods 237 | | ----------------------------------------------------------------- 238 | */ 239 | 240 | /** 241 | * Create a new query builder instance. 242 | * 243 | * @param $insert bool 244 | * 245 | * @return \Illuminate\Database\Eloquent\Builder 246 | */ 247 | protected function newQuery(bool $insert = false): Builder 248 | { 249 | $query = $this->model 250 | ->newQuery() 251 | ->unless($insert, function (Builder $q): void { 252 | $q->where($this->extraColumns); 253 | }); 254 | 255 | if ($this->hasQueryConstraint()) { 256 | $callback = $this->queryConstraint; 257 | $callback($query, $insert); 258 | } 259 | 260 | return $query; 261 | } 262 | 263 | /** 264 | * Transforms settings data into an array ready to be inserted into the database. 265 | * Call array_dot on a multidimensional array before passing it into this method! 266 | * 267 | * @param array $data 268 | * 269 | * @return array 270 | */ 271 | protected function prepareInsertData(array $data): array 272 | { 273 | $now = Carbon::now(); 274 | $dbData = []; 275 | $extraColumns = $this->extraColumns ? $this->extraColumns : []; 276 | 277 | foreach ($data as $key => $value) { 278 | $dbData[] = array_merge($extraColumns, [ 279 | $this->keyColumn => $key, 280 | $this->valueColumn => $value, 281 | $this->model->getCreatedAtColumn() => $now, 282 | $this->model->getUpdatedAtColumn() => $now, 283 | ]); 284 | } 285 | 286 | return $dbData; 287 | } 288 | 289 | /** 290 | * Check if the query constraint exists. 291 | * 292 | * @return bool 293 | */ 294 | protected function hasQueryConstraint(): bool 295 | { 296 | return ! is_null($this->queryConstraint) && is_callable($this->queryConstraint); 297 | } 298 | 299 | /** 300 | * Get the changed settings data. 301 | * 302 | * @param array $data 303 | * 304 | * @return array 305 | */ 306 | private function getChanges(array $data): array 307 | { 308 | $changes = [ 309 | 'inserted' => Arr::dot($data), 310 | 'updated' => [], 311 | 'deleted' => [], 312 | ]; 313 | 314 | foreach ($this->newQuery()->pluck($this->keyColumn) as $key) { 315 | if (Arr::has($changes['inserted'], $key)) 316 | $changes['updated'][$key] = $changes['inserted'][$key]; 317 | else 318 | $changes['deleted'][] = $key; 319 | 320 | Arr::forget($changes['inserted'], $key); 321 | } 322 | 323 | return $changes; 324 | } 325 | 326 | /** 327 | * Sync the updated records. 328 | * 329 | * @param array $updated 330 | */ 331 | private function syncUpdated(array $updated): void 332 | { 333 | foreach ($updated as $key => $value) { 334 | $this->newQuery() 335 | ->where($this->keyColumn, '=', $key) 336 | ->update([$this->valueColumn => $value]); 337 | } 338 | } 339 | 340 | /** 341 | * Sync the inserted records. 342 | * 343 | * @param array $inserted 344 | */ 345 | private function syncInserted(array $inserted): void 346 | { 347 | if ( ! empty($inserted)) { 348 | $this->newQuery(true)->insert( 349 | $this->prepareInsertData($inserted) 350 | ); 351 | } 352 | } 353 | 354 | /** 355 | * Sync the deleted records. 356 | * 357 | * @param array $deleted 358 | */ 359 | private function syncDeleted(array $deleted): void 360 | { 361 | if ( ! empty($deleted)) { 362 | $this->newQuery()->whereIn($this->keyColumn, $deleted)->delete(); 363 | } 364 | } 365 | } 366 | --------------------------------------------------------------------------------