Repository: akaunting/laravel-setting Branch: master Commit: 97e42cd18b65 Files: 29 Total size: 57.1 KB Directory structure: gitextract_oxuvf7rx/ ├── .editorconfig ├── .gitattributes ├── .github/ │ └── workflows/ │ └── tests.yml ├── .gitignore ├── .styleci.yml ├── LICENSE.md ├── README.md ├── composer.json ├── phpunit.xml ├── src/ │ ├── Config/ │ │ └── setting.php │ ├── Contracts/ │ │ └── Driver.php │ ├── Drivers/ │ │ ├── Database.php │ │ ├── Json.php │ │ └── Memory.php │ ├── Facade.php │ ├── Manager.php │ ├── Middleware/ │ │ └── AutoSaveSetting.php │ ├── Migrations/ │ │ └── 2017_08_24_000000_create_settings_table.php │ ├── Provider.php │ ├── Support/ │ │ └── Arr.php │ └── helpers.php └── tests/ ├── functional/ │ ├── AbstractFunctionalTest.php │ ├── DatabaseTest.php │ ├── JsonTest.php │ └── MemoryTest.php └── unit/ ├── ArrayUtilTest.php ├── DatabaseDriverTest.php ├── HelperTest.php └── JsonDriverTest.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ ; This file is for unifying the coding style for different editors and IDEs. ; More information at https://editorconfig.org root = true [*] charset = utf-8 indent_size = 4 indent_style = space end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false [*.yml] indent_size = 2 ================================================ FILE: .gitattributes ================================================ # Set the default behavior, in case people don't have core.autocrlf set. * text eol=lf # Explicitly declare text files you want to always be normalized and converted # to native line endings on checkout. *.c text *.h text # Declare files that will always have CRLF line endings on checkout. *.sln text eol=crlf # Denote all files that are truly binary and should not be modified. *.png binary *.jpg binary *.otf binary *.eot binary *.svg binary *.ttf binary *.woff binary *.woff2 binary *.css linguist-vendored *.scss linguist-vendored *.js linguist-vendored CHANGELOG.md export-ignore ================================================ FILE: .github/workflows/tests.yml ================================================ name: Tests on: [push, pull_request] jobs: tests: name: PHP ${{ matrix.php }} runs-on: ubuntu-latest strategy: matrix: php: ['7.3', '7.4', '8.0', '8.1'] steps: - name: Checkout code uses: actions/checkout@v2 - name: Cache composer uses: actions/cache@v1 with: path: ~/.composer/cache/files key: php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extension-csv: bcmath, ctype, dom, fileinfo, intl, gd, json, mbstring, pdo, pdo_sqlite, openssl, sqlite, xml, zip coverage: none - name: Install composer run: composer install --no-interaction --no-scripts --no-suggest --prefer-source - name: Execute tests run: vendor/bin/phpunit ================================================ FILE: .gitignore ================================================ /.idea /.history /.vscode /tests/databases /vendor .DS_Store .phpunit.result.cache composer.phar composer.lock ================================================ FILE: .styleci.yml ================================================ preset: psr2 enabled: - concat_with_spaces ================================================ FILE: LICENSE.md ================================================ The MIT License (MIT) Copyright (c) 2015 Andreas Lutro Copyright (c) 2017 Akaunting Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Persistent settings package for Laravel [![Downloads](https://poser.pugx.org/akaunting/laravel-setting/d/total.svg)](https://github.com/akaunting/laravel-setting) [![StyleCI](https://styleci.io/repos/101231817/shield?style=flat&branch=master)](https://styleci.io/repos/101231817) [![License](https://poser.pugx.org/akaunting/laravel-setting/license.svg)](LICENSE.md) This package allows you to save settings in a more persistent way. You can use the database and/or json file to save your settings. You can also override the Laravel config. * Driver support * Helper function * Blade directive * Override config values * Encryption * Custom file, table and columns * Auto save * Extra columns * Cache support ## Getting Started ### 1. Install Run the following command: ```bash composer require akaunting/laravel-setting ``` ### 2. Register (for Laravel < 5.5) Register the service provider in `config/app.php` ```php Akaunting\Setting\Provider::class, ``` Add alias if you want to use the facade. ```php 'Setting' => Akaunting\Setting\Facade::class, ``` ### 3. Publish Publish config file. ```bash php artisan vendor:publish --tag=setting ``` ### 4. Database Create table for database driver ```bash php artisan migrate ``` ### 5. Configure You can change the options of your app from `config/setting.php` file ## Usage You can either use the helper method like `setting('foo')` or the facade `Setting::get('foo')` ### Facade ```php Setting::get('foo', 'default'); Setting::get('nested.element'); Setting::set('foo', 'bar'); Setting::forget('foo'); $settings = Setting::all(); ``` ### Helper ```php setting('foo', 'default'); setting('nested.element'); setting(['foo' => 'bar']); setting()->forget('foo'); $settings = setting()->all(); ``` You can call the `save()` method to save the changes. ### Auto Save If you enable the `auto_save` option in the config file, settings will be saved automatically every time the application shuts down if anything has been changed. ### Blade Directive You can get the settings directly in your blade templates using the helper method or the blade directive like `@setting('foo')` ### Override Config Values You can easily override default config values by adding them to the `override` option in `config/setting.php`, thereby eliminating the need to modify the default config files and also allowing you to change said values during production. Ex: ```php 'override' => [ "app.name" => "app_name", "app.env" => "app_env", "mail.driver" => "app_mail_driver", "mail.host" => "app_mail_host", ], ``` The values on the left corresponds to the respective config value (Ex: config('app.name')) and the value on the right is the name of the `key` in your settings table/json file. ### Encryption If you like to encrypt the values for a given key, you can pass the key to the `encrypted_keys` option in `config/setting.php` and the rest is automatically handled by using Laravel's built-in encryption facilities. Ex: ```php 'encrypted_keys' => [ "payment.key", ], ``` ### JSON Storage You can modify the path used on run-time using `setting()->setPath($path)`. ### Database Storage If you want to use the database as settings storage then you should run the `php artisan migrate`. You can modify the table fields from the `create_settings_table` file in the migrations directory. #### Extra Columns If you want to store settings for multiple users/clients in the same database you can do so by specifying extra columns: ```php setting()->setExtraColumns(['user_id' => Auth::user()->id]); ``` where `user_id = x` will now be added to the database query when settings are retrieved, and when new settings are saved, the `user_id` will be populated. If you need more fine-tuned control over which data gets queried, you can use the `setConstraint` method which takes a closure with two arguments: - `$query` is the query builder instance - `$insert` is a boolean telling you whether the query is an insert or not. If it is an insert, you usually don't need to do anything to `$query`. ```php setting()->setConstraint(function($query, $insert) { if ($insert) return; $query->where(/* ... */); }); ``` ### Custom Drivers This package uses the Laravel `Manager` class under the hood, so it's easy to add your own storage driver. All you need to do is extend the abstract `Driver` class, implement the abstract methods and call `setting()->extend`. ```php class MyDriver extends Akaunting\Setting\Contracts\Driver { // ... } app('setting.manager')->extend('mydriver', function($app) { return $app->make('MyDriver'); }); ``` ## Changelog Please see [Releases](../../releases) for more information what has changed recently. ## Contributing Pull requests are more than welcome. You must follow the PSR coding standards. ## Security If you discover any security related issues, please email security@akaunting.com instead of using the issue tracker. ## Credits - [Denis Duliçi](https://github.com/denisdulici) - [All Contributors](../../contributors) ## License The MIT License (MIT). Please see [LICENSE](LICENSE.md) for more information. ================================================ FILE: composer.json ================================================ { "name": "akaunting/laravel-setting", "description": "Persistent settings package for Laravel", "keywords": [ "laravel", "persistent", "settings", "config" ], "license": "MIT", "authors": [ { "name": "Denis Duliçi", "email": "info@akaunting.com", "homepage": "https://akaunting.com", "role": "Developer" } ], "require": { "php": ">=5.5.9", "laravel/framework": ">=5.3" }, "require-dev": { "phpunit/phpunit": ">=4.8", "mockery/mockery": "0.9.*", "laravel/framework": ">=5.3" }, "autoload": { "psr-4": { "Akaunting\\Setting\\": "./src" }, "files": [ "src/helpers.php" ] }, "extra": { "laravel": { "providers": [ "Akaunting\\Setting\\Provider" ], "aliases": { "Setting": "Akaunting\\Setting\\Facade" } } }, "minimum-stability": "dev", "prefer-stable": true } ================================================ FILE: phpunit.xml ================================================ ./tests/ ================================================ FILE: src/Config/setting.php ================================================ false, /* |-------------------------------------------------------------------------- | Cache |-------------------------------------------------------------------------- | | Options for caching. Set whether to enable cache, its key, time to live | in seconds and whether to auto clear after save. | */ 'cache' => [ 'enabled' => false, 'key' => 'setting', 'ttl' => 3600, 'auto_clear' => true, ], /* |-------------------------------------------------------------------------- | Setting driver |-------------------------------------------------------------------------- | | Select where to store the settings. | | Supported: "database", "json", "memory" | */ 'driver' => 'database', /* |-------------------------------------------------------------------------- | Database driver |-------------------------------------------------------------------------- | | Options for database driver. Enter which connection to use, null means | the default connection. Set the table and column names. | */ 'database' => [ 'connection' => null, 'table' => 'settings', 'key' => 'key', 'value' => 'value', ], /* |-------------------------------------------------------------------------- | JSON driver |-------------------------------------------------------------------------- | | Options for json driver. Enter the full path to the .json file. | */ 'json' => [ 'path' => storage_path() . '/settings.json', ], /* |-------------------------------------------------------------------------- | Override application config values |-------------------------------------------------------------------------- | | If defined, settings package will override these config values. | | Sample: | "app.locale" => "settings.locale", | */ 'override' => [ ], /* |-------------------------------------------------------------------------- | Fallback |-------------------------------------------------------------------------- | | Define fallback settings to be used in case the default is null | | Sample: | "currency" => "USD", | */ 'fallback' => [ ], /* |-------------------------------------------------------------------------- | Required Extra Columns |-------------------------------------------------------------------------- | | The list of columns required to be set up | | Sample: | "user_id", | "tenant_id", | */ 'required_extra_columns' => [ ], /* |-------------------------------------------------------------------------- | Encryption |-------------------------------------------------------------------------- | | Define the keys which should be crypt automatically. | | Sample: | "payment.key" | */ 'encrypted_keys' => [ ], ]; ================================================ FILE: src/Contracts/Driver.php ================================================ with_fallback = false; return $this; } /** * Get a specific key from the settings data. * * @param string|array $key * @param mixed $default Optional default value. * * @return mixed */ public function get($key, $default = null) { if (!$this->checkExtraColumns()) { return false; } $this->load(); return Arr::get($this->data, $key, $default); } /** * Get the fallback value if default is null. * * @param string|array $key * @param mixed $default * * @return mixed */ public function getFallback($key, $default = null) { if (($default !== null) || is_array($key)) { return $default; } return Arr::get((array) config('setting.fallback'), $key); } /** * Check if the given value is same as fallback. * * @param string $key * @param string $value * * @return bool */ public function isEqualToFallback($key, $value) { return (string) $this->getFallback($key) == (string) $value; } /** * Determine if a key exists in the settings data. * * @param string $key * * @return bool */ public function has($key) { if (!$this->checkExtraColumns()) { return false; } $this->load(); return Arr::has($this->data, $key); } /** * Set a specific key to a value in the settings data. * * @param string|array $key Key string or associative array of key => value * @param mixed $value Optional only if the first argument is an array */ public function set($key, $value = null) { if (!$this->checkExtraColumns()) { return; } $this->load(); $this->unsaved = true; if (is_array($key)) { foreach ($key as $k => $v) { Arr::set($this->data, $k, $v); } } else { Arr::set($this->data, $key, $value); } } /** * Unset a key in the settings data. * * @param string $key */ public function forget($key) { if (!$this->checkExtraColumns()) { return; } $this->unsaved = true; if ($this->has($key)) { Arr::forget($this->data, $key); } } /** * Unset all keys in the settings data. * * @return void */ public function forgetAll() { if (!$this->checkExtraColumns()) { return; } if (config('setting.cache.enabled')) { Cache::forget($this->getCacheKey()); } $this->unsaved = true; $this->data = []; } /** * Get all settings data. * * @return array|bool */ public function all() { if (!$this->checkExtraColumns()) { return []; } $this->load(); return $this->data; } /** * Save any changes done to the settings data. * * @return void */ public function save() { if (!$this->checkExtraColumns()) { return; } if (!$this->unsaved) { // either nothing has been changed, or data has not been loaded, so // do nothing by returning early return; } if (config('setting.cache.enabled') && config('setting.cache.auto_clear')) { Cache::forget($this->getCacheKey()); } $this->write($this->data); $this->unsaved = false; } /** * Make sure data is loaded. * * @param $force Force a reload of data. Default false. */ public function load($force = false) { if (!$this->checkExtraColumns()) { return; } if ($this->loaded && !$force) { return; } $fallback_data = $this->with_fallback ? config('setting.fallback') : []; $driver_data = $this->readData(); $this->data = Arr::merge((array) $fallback_data, (array) $driver_data); $this->loaded = true; } /** * Read data from driver or cache * * @return array */ public function readData() { if (config('setting.cache.enabled')) { return $this->readDataFromCache(); } return $this->read(); } /** * Read data from cache * * @return array */ public function readDataFromCache() { return Cache::remember($this->getCacheKey(), config('setting.cache.ttl'), function () { return $this->read(); }); } /** * Check if extra columns are set up. * * @return boolean */ public function checkExtraColumns() { if (!$required_extra_columns = config('setting.required_extra_columns')) { return true; } if (array_keys_exists($required_extra_columns, $this->getExtraColumns())) { return true; } return false; } /** * Get cache key based on extra columns. * * @return string */ public function getCacheKey() { $key = config('setting.cache.key'); foreach ($this->getExtraColumns() as $name => $value) { $key .= '_' . $name . '_' . $value; } return $key; } /** * Get extra columns added to the rows. * * @return array */ abstract protected function getExtraColumns(); /** * Read data from driver. * * @return array */ abstract protected function read(); /** * Write data to driver. * * @param array $data * * @return void */ abstract protected function write(array $data); } ================================================ FILE: src/Drivers/Database.php ================================================ connection = $connection; $this->table = $table ?: 'settings'; $this->key = $key ?: 'key'; $this->value = $value ?: 'value'; $this->encrypted_keys = $encrypted_keys; } /** * Set the table to query from. * * @param string $table */ public function setTable($table) { $this->table = $table; } /** * Set the key column name to query from. * * @param string $key */ public function setKey($key) { $this->key = $key; } /** * Set the value column name to query from. * * @param string $value */ public function setValue($value) { $this->value = $value; } /** * Set the query constraint. * * @param Closure $callback */ public function setConstraint(Closure $callback) { $this->data = []; $this->loaded = false; $this->query_constraint = $callback; } /** * Set extra columns to be added to the rows. * * @param array $columns */ public function setExtraColumns(array $columns) { $this->extra_columns = $columns; } /** * Get extra columns added to the rows. * * @return array */ public function getExtraColumns() { return $this->extra_columns; } /** * {@inheritdoc} */ public function forget($key) { parent::forget($key); // because the database driver cannot store empty arrays, remove empty // arrays to keep data consistent before and after saving $segments = explode('.', $key); array_pop($segments); while ($segments) { $segment = implode('.', $segments); // non-empty array - exit out of the loop if ($this->get($segment)) { break; } // remove the empty array and move on to the next segment $this->forget($segment); array_pop($segments); } } /** * {@inheritdoc} */ protected function write(array $data) { // Get current data $db_data = $this->newQuery()->get([$this->key, $this->value])->toArray(); $insert_data = LaravelArr::dot($data); $update_data = []; $delete_keys = []; foreach ($db_data as $db_row) { $key = $db_row->{$this->key}; $value = $db_row->{$this->value}; $is_in_insert = $is_different_in_db = $is_same_as_fallback = false; if (isset($insert_data[$key])) { $is_in_insert = true; $is_different_in_db = (string) $insert_data[$key] != (string) $value; $is_same_as_fallback = $this->isEqualToFallback($key, $insert_data[$key]); } if ($is_in_insert) { if ($is_same_as_fallback) { // Delete if new data is same as fallback $delete_keys[] = $key; } elseif ($is_different_in_db) { // Update if new data is different from db $update_data[$key] = $insert_data[$key]; } } else { // Delete if current db not available in new data $delete_keys[] = $key; } unset($insert_data[$key]); } foreach ($update_data as $key => $value) { $value = $this->prepareValue($key, $value); $this->newQuery() ->where($this->key, '=', $key) ->update([$this->value => $value]); } if ($insert_data) { $this->newQuery(true) ->insert($this->prepareInsertData($insert_data)); } if ($delete_keys) { $this->newQuery() ->whereIn($this->key, $delete_keys) ->delete(); } } /** * Transforms settings data into an array ready to be insterted into the * database. Call array_dot on a multidimensional array before passing it * into this method! * * @param array $data Call array_dot on a multidimensional array before passing it into this method! * * @return array */ protected function prepareInsertData(array $data) { $db_data = []; if ($this->getExtraColumns()) { foreach ($data as $key => $value) { $value = $this->prepareValue($key, $value); // Don't insert if same as fallback if ($this->isEqualToFallback($key, $value)) { continue; } $db_data[] = array_merge( $this->getExtraColumns(), [$this->key => $key, $this->value => $value] ); } } else { foreach ($data as $key => $value) { $value = $this->prepareValue($key, $value); // Don't insert if same as fallback if ($this->isEqualToFallback($key, $value)) { continue; } $db_data[] = [$this->key => $key, $this->value => $value]; } } return $db_data; } /** * Checks if the provided key should be encrypted or not. * Also type casts the given value to a string so errors with booleans or integers are handeled. * Otherwise it returns the original value. * * @param string $key Key to check if it's inside the encryptedValues variable. * @param mixed $value Info: Encryption only supports strings. * * @return string */ protected function prepareValue(string $key, $value) { // Check if key should be encrypted if (in_array($key, $this->encrypted_keys)) { // Cast to string to avoid error when a user passes a boolean value return Crypt::encryptString((string) $value); } return $value; } /** * Checks if the provided key should be decrypted or not. * Otherwise it returns the original value. * * @param string $key Key to check if it's inside the encryptedValues variable. * @param mixed $value Info: Encryption only supports strings. * * @return string */ protected function unpackValue(string $key, $value) { // Check if key should be encrypted if (in_array($key, $this->encrypted_keys)) { // Cast to string to avoid error when a user passes a boolean value return Crypt::decryptString((string) $value); } return $value; } /** * {@inheritdoc} */ protected function read() { return $this->parseReadData($this->newQuery()->get()); } /** * Parse data coming from the database. * * @param array $data * * @return array */ public function parseReadData($data) { $results = []; foreach ($data as $row) { if (is_array($row)) { $key = $row[$this->key]; $value = $row[$this->value]; } elseif (is_object($row)) { $key = $row->{$this->key}; $value = $row->{$this->value}; } else { $msg = 'Expected array or object, got ' . gettype($row); throw new \UnexpectedValueException($msg); } // Encryption $value = $this->unpackValue($key, $value); Arr::set($results, $key, $value); } return $results; } /** * Create a new query builder instance. * * @param bool $insert * * @return \Illuminate\Database\Query\Builder */ protected function newQuery($insert = false) { $query = $this->connection->table($this->table); if (!$insert) { foreach ($this->getExtraColumns() as $key => $value) { $query->where($key, '=', $value); } } if ($this->query_constraint !== null) { $callback = $this->query_constraint; $callback($query, $insert); } return $query; } } ================================================ FILE: src/Drivers/Json.php ================================================ files = $files; $this->setPath($path ?: storage_path() . '/settings.json'); } /** * Set the path for the JSON file. * * @param string $path */ public function setPath($path) { // If the file does not already exist, we will attempt to create it. if (!$this->files->exists($path)) { $result = $this->files->put($path, '{}'); if ($result === false) { throw new \InvalidArgumentException("Could not write to $path."); } } if (!$this->files->isWritable($path)) { throw new \InvalidArgumentException("$path is not writable."); } $this->path = $path; } /** * {@inheritdoc} */ protected function getExtraColumns() { return []; } /** * {@inheritdoc} */ protected function read() { $contents = $this->files->get($this->path); $data = json_decode($contents, true); if ($data === null) { throw new \RuntimeException("Invalid JSON in {$this->path}"); } return $data; } /** * {@inheritdoc} */ protected function write(array $data) { if ($data) { $contents = json_encode($data); } else { $contents = '{}'; } $this->files->put($this->path, $contents); } } ================================================ FILE: src/Drivers/Memory.php ================================================ data = $data; } } /** * {@inheritdoc} */ protected function getExtraColumns() { return []; } /** * {@inheritdoc} */ protected function read() { return $this->data; } /** * {@inheritdoc} */ protected function write(array $data) { // do nothing } } ================================================ FILE: src/Facade.php ================================================ container = $app ?? app(); parent::__construct($this->container); } public function getDefaultDriver() { return config('setting.driver'); } public function createJsonDriver() { $path = config('setting.json.path'); return new Json($this->container['files'], $path); } public function createDatabaseDriver() { $connection = $this->container['db']->connection(config('setting.database.connection')); $table = config('setting.database.table'); $key = config('setting.database.key'); $value = config('setting.database.value'); $encryptedKeys = config('setting.encrypted_keys'); return new Database($connection, $table, $key, $value, $encryptedKeys); } public function createMemoryDriver() { return new Memory(); } public function createArrayDriver() { return $this->createMemoryDriver(); } } ================================================ FILE: src/Middleware/AutoSaveSetting.php ================================================ setting = app('setting'); } /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * * @return mixed */ public function handle($request, Closure $next) { $response = $next($request); $this->setting->save(); return $response; } } ================================================ FILE: src/Migrations/2017_08_24_000000_create_settings_table.php ================================================ table = config('setting.database.table'); $this->key = config('setting.database.key'); $this->value = config('setting.database.value'); } /** * Run the migrations. * * @return void */ public function up() { Schema::create($this->table, function (Blueprint $table) { $table->increments('id'); $table->string($this->key)->index(); $table->text($this->value); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop($this->table); } } ================================================ FILE: src/Provider.php ================================================ publishes([ __DIR__ . '/Config/setting.php' => config_path('setting.php'), __DIR__ . '/Migrations/2017_08_24_000000_create_settings_table.php' => database_path('migrations/2017_08_24_000000_create_settings_table.php'), ], 'setting'); // Auto save setting if (config('setting.auto_save')) { $kernel = $this->app['Illuminate\Contracts\Http\Kernel']; $kernel->pushMiddleware(AutoSaveSetting::class); } $this->override(); // Register blade directive $this->callAfterResolving('blade.compiler', function (BladeCompiler $compiler) { $compiler->directive('setting', function ($expression) { return ""; }); }); } /** * Register the application services. * * @return void */ public function register() { $this->app->singleton('setting.manager', function ($app) { return new Manager($app); }); $this->app->singleton('setting', function ($app) { return $app['setting.manager']->driver(); }); $this->mergeConfigFrom(__DIR__ . '/Config/setting.php', 'setting'); } private function override() { $override = config('setting.override', []); foreach (Arr::dot($override) as $config_key => $setting_key) { $config_key = is_string($config_key) ? $config_key : $setting_key; try { if (! is_null($value = setting($setting_key))) { config([$config_key => $value]); } } catch (\Exception $e) { continue; } } } } ================================================ FILE: src/Support/Arr.php ================================================ $value) { if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) { $merged[$key] = static::merge($merged[$key], $value); } elseif (is_numeric($key)) { if (!in_array($value, $merged)) { $merged[] = $value; } } else { $merged[$key] = $value; } } return $merged; } } ================================================ FILE: src/helpers.php ================================================ set($key); return $setting; } return $setting->get($key, $default); } } ================================================ FILE: tests/functional/AbstractFunctionalTest.php ================================================ assertEquals($expected, $store->all(), $message); $store->save(); $store = $this->createStore(); $this->assertEquals($expected, $store->all(), $message); } protected function assertStoreKeyEquals($store, $key, $expected, $message = null) { $this->assertEquals($expected, $store->get($key), $message); $store->save(); $store = $this->createStore(); $this->assertEquals($expected, $store->get($key), $message); } /** @test */ public function store_is_initially_empty() { $store = $this->createStore(); $this->assertEquals([], $store->all()); } /** @test */ public function written_changes_are_saved() { $store = $this->createStore(); $store->set('foo', 'bar'); $this->assertStoreKeyEquals($store, 'foo', 'bar'); } /** @test */ public function nested_keys_are_nested() { $store = $this->createStore(); $store->set('foo.bar', 'baz'); $this->assertStoreEquals($store, ['foo' => ['bar' => 'baz']]); } /** @test */ public function cannot_set_nested_key_on_non_array_member() { $store = $this->createStore(); $store->set('foo', 'bar'); $this->setExpectedException('UnexpectedValueException', 'Non-array segment encountered'); $store->set('foo.bar', 'baz'); } /** @test */ public function can_forget_key() { $store = $this->createStore(); $store->set('foo', 'bar'); $store->set('bar', 'baz'); $this->assertStoreEquals($store, ['foo' => 'bar', 'bar' => 'baz']); $store->forget('foo'); $this->assertStoreEquals($store, ['bar' => 'baz']); } /** @test */ public function can_forget_nested_key() { $store = $this->createStore(); $store->set('foo.bar', 'baz'); $store->set('foo.baz', 'bar'); $store->set('bar.foo', 'baz'); $this->assertStoreEquals($store, [ 'foo' => [ 'bar' => 'baz', 'baz' => 'bar', ], 'bar' => [ 'foo' => 'baz', ], ]); $store->forget('foo.bar'); $this->assertStoreEquals($store, [ 'foo' => [ 'baz' => 'bar', ], 'bar' => [ 'foo' => 'baz', ], ]); $store->forget('bar.foo'); $expected = [ 'foo' => [ 'baz' => 'bar', ], 'bar' => [ ], ]; if ($store instanceof Database) { unset($expected['bar']); } $this->assertStoreEquals($store, $expected); } /** @test */ public function can_forget_all() { $store = $this->createStore(['foo' => 'bar']); $this->assertStoreEquals($store, ['foo' => 'bar']); $store->forgetAll(); $this->assertStoreEquals($store, []); } } ================================================ FILE: tests/functional/DatabaseTest.php ================================================ container = new \Illuminate\Container\Container(); $this->capsule = new \Illuminate\Database\Capsule\Manager($this->container); $this->capsule->setAsGlobal(); $this->container['db'] = $this->capsule; $this->capsule->addConnection([ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ]); $this->capsule->schema()->create('settings', function ($t) { $t->string('key', 64)->unique(); $t->string('value', 4096); }); } public function tearDown() { $this->capsule->schema()->drop('settings'); unset($this->capsule); unset($this->container); } protected function createStore(array $data = []) { if ($data) { $store = $this->createStore(); $store->set($data); $store->save(); unset($store); } return new \Akaunting\Setting\Drivers\Database( $this->capsule->getConnection() ); } } ================================================ FILE: tests/functional/JsonTest.php ================================================ assertEquals($expected, $store->all(), $message); // removed persistance test assertions } protected function assertStoreKeyEquals($store, $key, $expected, $message = null) { $this->assertEquals($expected, $store->get($key), $message); // removed persistance test assertions } protected function createStore(array $data = null) { return new \Akaunting\Setting\Drivers\Memory($data); } } ================================================ FILE: tests/unit/ArrayUtilTest.php ================================================ assertEquals($expected, Arr::get($data, $key)); } public function getGetData() { return [ [[], 'foo', null], [['foo' => 'bar'], 'foo', 'bar'], [['foo' => 'bar'], 'bar', null], [['foo' => 'bar'], 'foo.bar', null], [['foo' => ['bar' => 'baz']], 'foo.bar', 'baz'], [['foo' => ['bar' => 'baz']], 'foo.baz', null], [['foo' => ['bar' => 'baz']], 'foo', ['bar' => 'baz']], [ ['foo' => 'bar', 'bar' => 'baz'], ['foo', 'bar'], ['foo' => 'bar', 'bar' => 'baz'], ], [ ['foo' => ['bar' => 'baz'], 'bar' => 'baz'], ['foo.bar', 'bar'], ['foo' => ['bar' => 'baz'], 'bar' => 'baz'], ], [ ['foo' => ['bar' => 'baz'], 'bar' => 'baz'], ['foo.bar'], ['foo' => ['bar' => 'baz']], ], [ ['foo' => ['bar' => 'baz'], 'bar' => 'baz'], ['foo.bar', 'baz'], ['foo' => ['bar' => 'baz'], 'baz' => null], ], ]; } /** * @test * @dataProvider getSetData */ public function setSetsCorrectKeyToValue(array $input, $key, $value, array $expected) { Arr::set($input, $key, $value); $this->assertEquals($expected, $input); } public function getSetData() { return [ [ ['foo' => 'bar'], 'foo', 'baz', ['foo' => 'baz'], ], [ [], 'foo', 'bar', ['foo' => 'bar'], ], [ [], 'foo.bar', 'baz', ['foo' => ['bar' => 'baz']], ], [ ['foo' => ['bar' => 'baz']], 'foo.baz', 'foo', ['foo' => ['bar' => 'baz', 'baz' => 'foo']], ], [ ['foo' => ['bar' => 'baz']], 'foo.baz.bar', 'baz', ['foo' => ['bar' => 'baz', 'baz' => ['bar' => 'baz']]], ], [ [], 'foo.bar.baz', 'foo', ['foo' => ['bar' => ['baz' => 'foo']]], ], ]; } /** @test */ public function setThrowsExceptionOnNonArraySegment() { $data = ['foo' => 'bar']; $this->setExpectedException('UnexpectedValueException', 'Non-array segment encountered'); Arr::set($data, 'foo.bar', 'baz'); } /** * @test * @dataProvider getHasData */ public function hasReturnsCorrectly(array $input, $key, $expected) { $this->assertEquals($expected, Arr::has($input, $key)); } public function getHasData() { return [ [[], 'foo', false], [['foo' => 'bar'], 'foo', true], [['foo' => 'bar'], 'bar', false], [['foo' => 'bar'], 'foo.bar', false], [['foo' => ['bar' => 'baz']], 'foo.bar', true], [['foo' => ['bar' => 'baz']], 'foo.baz', false], [['foo' => ['bar' => 'baz']], 'foo', true], [['foo' => null], 'foo', true], [['foo' => ['bar' => null]], 'foo.bar', true], ]; } } ================================================ FILE: tests/unit/DatabaseDriverTest.php ================================================ mockConnection(); $query = $this->mockQuery($connection); $query->shouldReceive('get')->once()->andReturn([ ['key' => 'nest.one', 'value' => 'old'], ]); $query->shouldReceive('lists')->atMost(1)->andReturn(['nest.one']); $query->shouldReceive('pluck')->atMost(1)->andReturn(['nest.one']); $dbData = $this->getDbData(); unset($dbData[1]); // remove the nest.one array member $query->shouldReceive('where')->with('key', '=', 'nest.one')->andReturn(m::self())->getMock() ->shouldReceive('update')->with(['value' => 'nestone']); $self = $this; // 5.3 compatibility $query->shouldReceive('insert')->once()->andReturnUsing(function ($arg) use ($dbData, $self) { $self->assertEquals(count($dbData), count($arg)); foreach ($dbData as $key => $value) { $self->assertContains($value, $arg); } }); $store = $this->makeStore($connection); $store->set('foo', 'bar'); $store->set('nest.one', 'nestone'); $store->set('nest.two', 'nesttwo'); $store->set('array', ['one', 'two']); $store->save(); } /** @test */ public function extra_columns_are_queried() { $connection = $this->mockConnection(); $query = $this->mockQuery($connection); $query->shouldReceive('where')->once()->with('foo', '=', 'bar') ->andReturn(m::self())->getMock() ->shouldReceive('get')->once()->andReturn([ ['key' => 'foo', 'value' => 'bar'], ]); $store = $this->makeStore($connection); $store->setExtraColumns(['foo' => 'bar']); $this->assertEquals('bar', $store->get('foo')); } /** @test */ public function extra_columns_are_inserted() { $connection = $this->mockConnection(); $query = $this->mockQuery($connection); $query->shouldReceive('where')->times(2)->with('extracol', '=', 'extradata') ->andReturn(m::self()); $query->shouldReceive('get')->once()->andReturn([]); $query->shouldReceive('lists')->atMost(1)->andReturn([]); $query->shouldReceive('pluck')->atMost(1)->andReturn([]); $query->shouldReceive('insert')->once()->with([ ['key' => 'foo', 'value' => 'bar', 'extracol' => 'extradata'], ]); $store = $this->makeStore($connection); $store->setExtraColumns(['extracol' => 'extradata']); $store->set('foo', 'bar'); $store->save(); } protected function getDbData() { return [ ['key' => 'foo', 'value' => 'bar'], ['key' => 'nest.one', 'value' => 'nestone'], ['key' => 'nest.two', 'value' => 'nesttwo'], ['key' => 'array.0', 'value' => 'one'], ['key' => 'array.1', 'value' => 'two'], ]; } protected function mockConnection() { return m::mock('Illuminate\Database\Connection'); } protected function mockQuery($connection) { $query = m::mock('Illuminate\Database\Query\Builder'); $connection->shouldReceive('table')->andReturn($query); return $query; } protected function makeStore($connection) { return new Akaunting\Setting\Drivers\Database($connection); } } ================================================ FILE: tests/unit/HelperTest.php ================================================ bind('setting', function () use ($store) { return $store; }); } /** @test */ public function helper_without_parameters_returns_store() { $this->assertInstanceOf('Akaunting\Setting\Contracts\Driver', setting()); } /** @test */ public function single_parameter_get_a_key_from_store() { app('setting')->shouldReceive('get')->with('foo', null)->once(); setting('foo'); } public function two_parameters_return_a_default_value() { app('setting')->shouldReceive('get')->with('foo', 'bar')->once(); setting('foo', 'bar'); } /** @test */ public function array_parameter_call_set_method_into_store() { app('setting')->shouldReceive('set')->with(['foo', 'bar'])->once(); setting(['foo', 'bar']); } } ================================================ FILE: tests/unit/JsonDriverTest.php ================================================ mockFilesystem(); $files->shouldReceive('exists')->once()->with('fakepath')->andReturn(true); $files->shouldReceive('isWritable')->once()->with('fakepath')->andReturn(false); $store = $this->makeStore($files); } /** * @test * @expectedException InvalidArgumentException */ public function throws_exception_when_files_put_fails() { $files = $this->mockFilesystem(); $files->shouldReceive('exists')->once()->with('fakepath')->andReturn(false); $files->shouldReceive('put')->once()->with('fakepath', '{}')->andReturn(false); $store = $this->makeStore($files); } /** * @test * @expectedException RuntimeException */ public function throws_exception_when_file_contains_invalid_json() { $files = $this->mockFilesystem(); $files->shouldReceive('exists')->once()->with('fakepath')->andReturn(true); $files->shouldReceive('isWritable')->once()->with('fakepath')->andReturn(true); $files->shouldReceive('get')->once()->with('fakepath')->andReturn('[[!1!11]'); $store = $this->makeStore($files); $store->get('foo'); } }