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
[](https://github.com/akaunting/laravel-setting)
[](https://styleci.io/repos/101231817)
[](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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
>
<testsuites>
<testsuite name="Package Test Suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
</phpunit>
================================================
FILE: src/Config/setting.php
================================================
<?php
return [
/*
|--------------------------------------------------------------------------
| Enable / Disable auto save
|--------------------------------------------------------------------------
|
| Auto-save every time the application shuts down
|
*/
'auto_save' => 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
================================================
<?php
namespace Akaunting\Setting\Contracts;
use Akaunting\Setting\Support\Arr;
use Illuminate\Support\Facades\Cache;
abstract class Driver
{
/**
* The settings data.
*
* @var array
*/
protected $data = [];
/**
* Whether the store has changed since it was last loaded.
*
* @var bool
*/
protected $unsaved = false;
/**
* Whether the settings data are loaded.
*
* @var bool
*/
protected $loaded = false;
/**
* Include and merge with fallbacks
*
* @var bool
*/
protected $with_fallback = true;
/**
* Excludes fallback data
*/
public function withoutFallback()
{
$this->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
================================================
<?php
namespace Akaunting\Setting\Drivers;
use Akaunting\Setting\Contracts\Driver;
use Akaunting\Setting\Support\Arr;
use Closure;
use Illuminate\Database\Connection;
use Illuminate\Support\Arr as LaravelArr;
use Illuminate\Support\Facades\Crypt;
class Database extends Driver
{
/**
* The database connection instance.
*
* @var \Illuminate\Database\Connection
*/
protected $connection;
/**
* The table to query from.
*
* @var string
*/
protected $table;
/**
* The key column name to query from.
*
* @var string
*/
protected $key;
/**
* The value column name to query from.
*
* @var string
*/
protected $value;
/**
* Keys which should be encrypt automatically.
*
* @var array
*/
protected $encrypted_keys;
/**
* Any query constraints that should be applied.
*
* @var Closure|null
*/
protected $query_constraint;
/**
* Any extra columns that should be added to the rows.
*
* @var array
*/
protected $extra_columns = [];
/**
* @param \Illuminate\Database\Connection $connection
* @param string $table
*/
public function __construct(Connection $connection, $table = null, $key = null, $value = null, array $encrypted_keys = [])
{
$this->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
================================================
<?php
namespace Akaunting\Setting\Drivers;
use Akaunting\Setting\Contracts\Driver;
use Illuminate\Filesystem\Filesystem;
class Json extends Driver
{
/**
* @param \Illuminate\Filesystem\Filesystem $files
* @param string $path
*/
public function __construct(Filesystem $files, $path = null)
{
$this->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
================================================
<?php
namespace Akaunting\Setting\Drivers;
use Akaunting\Setting\Contracts\Driver;
class Memory extends Driver
{
/**
* @param array $data
*/
public function __construct(array $data = null)
{
if ($data) {
$this->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
================================================
<?php
namespace Akaunting\Setting;
use Illuminate\Support\Facades\Facade as BaseFacade;
class Facade extends BaseFacade
{
/**
* Get the registered name of the component.
*/
public static function getFacadeAccessor()
{
return 'setting';
}
}
================================================
FILE: src/Manager.php
================================================
<?php
namespace Akaunting\Setting;
use Akaunting\Setting\Drivers\Database;
use Akaunting\Setting\Drivers\Json;
use Akaunting\Setting\Drivers\Memory;
use Illuminate\Support\Manager as BaseManager;
class Manager extends BaseManager
{
/**
* The container instance.
*
* @var \Illuminate\Contracts\Container\Container
*/
protected $container;
/**
* The application instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
*/
public function __construct($app = null)
{
$this->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
================================================
<?php
namespace Akaunting\Setting\Middleware;
use Closure;
class AutoSaveSetting
{
/**
* Create a new save settings middleware.
*/
public function __construct()
{
$this->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
================================================
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateSettingsTable extends Migration
{
/**
* Set up the options.
*/
public function __construct()
{
$this->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
================================================
<?php
namespace Akaunting\Setting;
use Akaunting\Setting\Middleware\AutoSaveSetting;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Arr;
use Illuminate\View\Compilers\BladeCompiler;
class Provider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
$this->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 "<?php echo setting($expression); ?>";
});
});
}
/**
* 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
================================================
<?php
namespace Akaunting\Setting\Support;
class Arr
{
/**
* This class is a static class and should not be instantiated.
*/
private function __construct()
{
//
}
/**
* Get an element from an array.
*
* @param array $data
* @param string $key Specify a nested element by separating keys with full stops.
* @param mixed $default If the element is not found, return this.
*
* @return mixed
*/
public static function get(array $data, $key, $default = null)
{
if ($key === null) {
return $data;
}
if (is_array($key)) {
return static::getArray($data, $key, $default);
}
foreach (explode('.', $key) as $segment) {
if (!is_array($data)) {
return $default;
}
if (!array_key_exists($segment, $data)) {
return $default;
}
$data = $data[$segment];
}
return $data;
}
protected static function getArray(array $input, $keys, $default = null)
{
$output = [];
foreach ($keys as $key) {
static::set($output, $key, static::get($input, $key, $default));
}
return $output;
}
/**
* Determine if an array has a given key.
*
* @param array $data
* @param string $key
*
* @return bool
*/
public static function has(array $data, $key)
{
foreach (explode('.', $key) as $segment) {
if (!is_array($data)) {
return false;
}
if (!array_key_exists($segment, $data)) {
return false;
}
$data = $data[$segment];
}
return true;
}
/**
* Set an element of an array.
*
* @param array $data
* @param string $key Specify a nested element by separating keys with full stops.
* @param mixed $value
*/
public static function set(array &$data, $key, $value)
{
$segments = explode('.', $key);
$key = array_pop($segments);
// iterate through all of $segments except the last one
foreach ($segments as $segment) {
if (!array_key_exists($segment, $data)) {
$data[$segment] = array();
} elseif (!is_array($data[$segment])) {
throw new \UnexpectedValueException('Non-array segment encountered');
}
$data = &$data[$segment];
}
$data[$key] = $value;
}
/**
* Unset an element from an array.
*
* @param array &$data
* @param string $key Specify a nested element by separating keys with full stops.
*/
public static function forget(array &$data, $key)
{
$segments = explode('.', $key);
$key = array_pop($segments);
// iterate through all of $segments except the last one
foreach ($segments as $segment) {
if (!array_key_exists($segment, $data)) {
return;
} elseif (!is_array($data[$segment])) {
throw new \UnexpectedValueException('Non-array segment encountered');
}
$data = &$data[$segment];
}
unset($data[$key]);
}
/**
* Merge two multidimensional arrays recursive
*
* @param array $array_1
* @param array $array_2
*
* @return array
*/
public static function merge(array $array_1, array $array_2)
{
$merged = $array_1;
foreach ($array_2 as $key => $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
================================================
<?php
if (!function_exists('array_keys_exists')) {
/**
* Easily check if multiple array keys exist.
*
* @param array $keys
* @param array $arr
*
* @return boolean
*/
function array_keys_exists(array $keys, array $arr)
{
return !array_diff_key(array_flip($keys), $arr);
}
}
if (!function_exists('setting')) {
/**
* Get / set the specified setting value.
*
* If an array is passed as the key, we will assume you want to set an array of values.
*
* @param array|string $key
* @param mixed $default
*
* @return mixed
*/
function setting($key = null, $default = null)
{
$setting = app('setting');
if (is_null($key)) {
return $setting;
}
if (is_array($key)) {
$setting->set($key);
return $setting;
}
return $setting->get($key, $default);
}
}
================================================
FILE: tests/functional/AbstractFunctionalTest.php
================================================
<?php
use Akaunting\Setting\Drivers\Database;
abstract class AbstractFunctionalTest extends PHPUnit_Framework_TestCase
{
abstract protected function createStore(array $data = []);
protected function assertStoreEquals($store, $expected, $message = null)
{
$this->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
================================================
<?php
class DatabaseTest extends AbstractFunctionalTest
{
public function setUp()
{
$this->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
================================================
<?php
class JsonTest extends AbstractFunctionalTest
{
protected function createStore(array $data = null)
{
$path = dirname(__DIR__) . '/tmp/store.json';
if ($data !== null) {
if ($data) {
$json = json_encode($data);
} else {
$json = '{}';
}
file_put_contents($path, $json);
}
return new \Akaunting\Setting\Drivers\Json(
new \Illuminate\Filesystem\Filesystem(),
$path
);
}
public function tearDown()
{
$path = dirname(__DIR__) . '/tmp/store.json';
unlink($path);
}
}
================================================
FILE: tests/functional/MemoryTest.php
================================================
<?php
class MemoryTest extends AbstractFunctionalTest
{
protected function assertStoreEquals($store, $expected, $message = null)
{
$this->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
================================================
<?php
use Akaunting\Setting\Support\Arr;
class ArrayUtilityTest extends PHPUnit_Framework_TestCase
{
/**
* @test
* @dataProvider getGetData
*/
public function getReturnsCorrectValue(array $data, $key, $expected)
{
$this->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
================================================
<?php
use Mockery as m;
class DatabaseDriverTest extends PHPUnit_Framework_TestCase
{
public function tearDown()
{
m::close();
}
/** @test */
public function correct_data_is_inserted_and_updated()
{
$connection = $this->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
================================================
<?php
use Illuminate\Container\Container;
use Mockery as m;
class HelperTest extends PHPUnit_Framework_TestCase
{
public static $functions;
public function setUp()
{
self::$functions = m::mock();
Container::setInstance(new Container());
$store = m::mock('Akaunting\Setting\Contracts\Driver');
app()->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
================================================
<?php
use Mockery as m;
class JsonDriverTest extends PHPUnit_Framework_TestCase
{
public function tearDown()
{
m::close();
}
protected function mockFilesystem()
{
return m::mock('Illuminate\Filesystem\Filesystem');
}
protected function makeStore($files, $path = 'fakepath')
{
return new Akaunting\Setting\Drivers\Json($files, $path);
}
/**
* @test
* @expectedException InvalidArgumentException
*/
public function throws_exception_when_file_not_writeable()
{
$files = $this->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');
}
}
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
SYMBOL INDEX (128 symbols across 19 files)
FILE: src/Contracts/Driver.php
class Driver (line 8) | abstract class Driver
method withoutFallback (line 41) | public function withoutFallback()
method get (line 56) | public function get($key, $default = null)
method getFallback (line 75) | public function getFallback($key, $default = null)
method isEqualToFallback (line 92) | public function isEqualToFallback($key, $value)
method has (line 104) | public function has($key)
method set (line 121) | public function set($key, $value = null)
method forget (line 144) | public function forget($key)
method forgetAll (line 162) | public function forgetAll()
method all (line 181) | public function all()
method save (line 197) | public function save()
method load (line 222) | public function load($force = false)
method readData (line 244) | public function readData()
method readDataFromCache (line 258) | public function readDataFromCache()
method checkExtraColumns (line 270) | public function checkExtraColumns()
method getCacheKey (line 288) | public function getCacheKey()
method getExtraColumns (line 304) | abstract protected function getExtraColumns();
method read (line 311) | abstract protected function read();
method write (line 320) | abstract protected function write(array $data);
FILE: src/Drivers/Database.php
class Database (line 12) | class Database extends Driver
method __construct (line 67) | public function __construct(Connection $connection, $table = null, $ke...
method setTable (line 81) | public function setTable($table)
method setKey (line 91) | public function setKey($key)
method setValue (line 101) | public function setValue($value)
method setConstraint (line 111) | public function setConstraint(Closure $callback)
method setExtraColumns (line 123) | public function setExtraColumns(array $columns)
method getExtraColumns (line 133) | public function getExtraColumns()
method forget (line 141) | public function forget($key)
method write (line 167) | protected function write(array $data)
method prepareInsertData (line 233) | protected function prepareInsertData(array $data)
method prepareValue (line 277) | protected function prepareValue(string $key, $value)
method unpackValue (line 297) | protected function unpackValue(string $key, $value)
method read (line 311) | protected function read()
method parseReadData (line 323) | public function parseReadData($data)
method newQuery (line 355) | protected function newQuery($insert = false)
FILE: src/Drivers/Json.php
class Json (line 8) | class Json extends Driver
method __construct (line 14) | public function __construct(Filesystem $files, $path = null)
method setPath (line 26) | public function setPath($path)
method getExtraColumns (line 46) | protected function getExtraColumns()
method read (line 54) | protected function read()
method write (line 70) | protected function write(array $data)
FILE: src/Drivers/Memory.php
class Memory (line 7) | class Memory extends Driver
method __construct (line 12) | public function __construct(array $data = null)
method getExtraColumns (line 22) | protected function getExtraColumns()
method read (line 30) | protected function read()
method write (line 38) | protected function write(array $data)
FILE: src/Facade.php
class Facade (line 7) | class Facade extends BaseFacade
method getFacadeAccessor (line 12) | public static function getFacadeAccessor()
FILE: src/Manager.php
class Manager (line 10) | class Manager extends BaseManager
method __construct (line 24) | public function __construct($app = null)
method getDefaultDriver (line 31) | public function getDefaultDriver()
method createJsonDriver (line 36) | public function createJsonDriver()
method createDatabaseDriver (line 43) | public function createDatabaseDriver()
method createMemoryDriver (line 54) | public function createMemoryDriver()
method createArrayDriver (line 59) | public function createArrayDriver()
FILE: src/Middleware/AutoSaveSetting.php
class AutoSaveSetting (line 7) | class AutoSaveSetting
method __construct (line 12) | public function __construct()
method handle (line 25) | public function handle($request, Closure $next)
FILE: src/Migrations/2017_08_24_000000_create_settings_table.php
class CreateSettingsTable (line 7) | class CreateSettingsTable extends Migration
method __construct (line 12) | public function __construct()
method up (line 24) | public function up()
method down (line 38) | public function down()
FILE: src/Provider.php
class Provider (line 10) | class Provider extends ServiceProvider
method boot (line 17) | public function boot()
method register (line 45) | public function register()
method override (line 58) | private function override()
FILE: src/Support/Arr.php
class Arr (line 5) | class Arr
method __construct (line 10) | private function __construct()
method get (line 24) | public static function get(array $data, $key, $default = null)
method getArray (line 49) | protected static function getArray(array $input, $keys, $default = null)
method has (line 68) | public static function has(array $data, $key)
method set (line 92) | public static function set(array &$data, $key, $value)
method forget (line 118) | public static function forget(array &$data, $key)
method merge (line 146) | public static function merge(array $array_1, array $array_2)
FILE: src/helpers.php
function array_keys_exists (line 12) | function array_keys_exists(array $keys, array $arr)
function setting (line 29) | function setting($key = null, $default = null)
FILE: tests/functional/AbstractFunctionalTest.php
class AbstractFunctionalTest (line 5) | abstract class AbstractFunctionalTest extends PHPUnit_Framework_TestCase
method createStore (line 7) | abstract protected function createStore(array $data = []);
method assertStoreEquals (line 9) | protected function assertStoreEquals($store, $expected, $message = null)
method assertStoreKeyEquals (line 17) | protected function assertStoreKeyEquals($store, $key, $expected, $mess...
method store_is_initially_empty (line 26) | public function store_is_initially_empty()
method written_changes_are_saved (line 33) | public function written_changes_are_saved()
method nested_keys_are_nested (line 41) | public function nested_keys_are_nested()
method cannot_set_nested_key_on_non_array_member (line 49) | public function cannot_set_nested_key_on_non_array_member()
method can_forget_key (line 58) | public function can_forget_key()
method can_forget_nested_key (line 70) | public function can_forget_nested_key()
method can_forget_all (line 111) | public function can_forget_all()
FILE: tests/functional/DatabaseTest.php
class DatabaseTest (line 3) | class DatabaseTest extends AbstractFunctionalTest
method setUp (line 5) | public function setUp()
method tearDown (line 23) | public function tearDown()
method createStore (line 30) | protected function createStore(array $data = [])
FILE: tests/functional/JsonTest.php
class JsonTest (line 3) | class JsonTest extends AbstractFunctionalTest
method createStore (line 5) | protected function createStore(array $data = null)
method tearDown (line 25) | public function tearDown()
FILE: tests/functional/MemoryTest.php
class MemoryTest (line 3) | class MemoryTest extends AbstractFunctionalTest
method assertStoreEquals (line 5) | protected function assertStoreEquals($store, $expected, $message = null)
method assertStoreKeyEquals (line 11) | protected function assertStoreKeyEquals($store, $key, $expected, $mess...
method createStore (line 17) | protected function createStore(array $data = null)
FILE: tests/unit/ArrayUtilTest.php
class ArrayUtilityTest (line 5) | class ArrayUtilityTest extends PHPUnit_Framework_TestCase
method getReturnsCorrectValue (line 11) | public function getReturnsCorrectValue(array $data, $key, $expected)
method getGetData (line 16) | public function getGetData()
method setSetsCorrectKeyToValue (line 53) | public function setSetsCorrectKeyToValue(array $input, $key, $value, a...
method getSetData (line 59) | public function getSetData()
method setThrowsExceptionOnNonArraySegment (line 102) | public function setThrowsExceptionOnNonArraySegment()
method hasReturnsCorrectly (line 113) | public function hasReturnsCorrectly(array $input, $key, $expected)
method getHasData (line 118) | public function getHasData()
FILE: tests/unit/DatabaseDriverTest.php
class DatabaseDriverTest (line 5) | class DatabaseDriverTest extends PHPUnit_Framework_TestCase
method tearDown (line 7) | public function tearDown()
method correct_data_is_inserted_and_updated (line 13) | public function correct_data_is_inserted_and_updated()
method extra_columns_are_queried (line 44) | public function extra_columns_are_queried()
method extra_columns_are_inserted (line 60) | public function extra_columns_are_inserted()
method getDbData (line 79) | protected function getDbData()
method mockConnection (line 90) | protected function mockConnection()
method mockQuery (line 95) | protected function mockQuery($connection)
method makeStore (line 103) | protected function makeStore($connection)
FILE: tests/unit/HelperTest.php
class HelperTest (line 6) | class HelperTest extends PHPUnit_Framework_TestCase
method setUp (line 10) | public function setUp()
method helper_without_parameters_returns_store (line 24) | public function helper_without_parameters_returns_store()
method single_parameter_get_a_key_from_store (line 30) | public function single_parameter_get_a_key_from_store()
method two_parameters_return_a_default_value (line 37) | public function two_parameters_return_a_default_value()
method array_parameter_call_set_method_into_store (line 45) | public function array_parameter_call_set_method_into_store()
FILE: tests/unit/JsonDriverTest.php
class JsonDriverTest (line 5) | class JsonDriverTest extends PHPUnit_Framework_TestCase
method tearDown (line 7) | public function tearDown()
method mockFilesystem (line 12) | protected function mockFilesystem()
method makeStore (line 17) | protected function makeStore($files, $path = 'fakepath')
method throws_exception_when_file_not_writeable (line 26) | public function throws_exception_when_file_not_writeable()
method throws_exception_when_files_put_fails (line 38) | public function throws_exception_when_files_put_fails()
method throws_exception_when_file_contains_invalid_json (line 50) | public function throws_exception_when_file_contains_invalid_json()
Condensed preview — 29 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (63K chars).
[
{
"path": ".editorconfig",
"chars": 337,
"preview": "; This file is for unifying the coding style for different editors and IDEs.\n; More information at https://editorconfig."
},
{
"path": ".gitattributes",
"chars": 590,
"preview": "# Set the default behavior, in case people don't have core.autocrlf set.\n* text eol=lf\n\n# Explicitly declare text files "
},
{
"path": ".github/workflows/tests.yml",
"chars": 906,
"preview": "name: Tests\n\non: [push, pull_request]\n\njobs:\n tests:\n\n name: PHP ${{ matrix.php }}\n\n runs-on: ubuntu-latest\n\n "
},
{
"path": ".gitignore",
"chars": 111,
"preview": "/.idea\n/.history\n/.vscode\n/tests/databases\n/vendor\n.DS_Store\n.phpunit.result.cache\ncomposer.phar\ncomposer.lock\n"
},
{
"path": ".styleci.yml",
"chars": 44,
"preview": "preset: psr2\n\nenabled:\n - concat_with_spaces"
},
{
"path": "LICENSE.md",
"chars": 1110,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2015 Andreas Lutro\n\nCopyright (c) 2017 Akaunting\n\nPermission is hereby granted, fre"
},
{
"path": "README.md",
"chars": 5151,
"preview": "# Persistent settings package for Laravel\n\n[]("
},
{
"path": "composer.json",
"chars": 1109,
"preview": "{\n \"name\": \"akaunting/laravel-setting\",\n \"description\": \"Persistent settings package for Laravel\",\n \"keywords\":"
},
{
"path": "phpunit.xml",
"chars": 569,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit backupGlobals=\"false\"\n backupStaticAttributes=\"false\"\n b"
},
{
"path": "src/Config/setting.php",
"chars": 3481,
"preview": "<?php\n\nreturn [\n\n /*\n |--------------------------------------------------------------------------\n | Enable / D"
},
{
"path": "src/Contracts/Driver.php",
"chars": 6603,
"preview": "<?php\n\nnamespace Akaunting\\Setting\\Contracts;\n\nuse Akaunting\\Setting\\Support\\Arr;\nuse Illuminate\\Support\\Facades\\Cache;\n"
},
{
"path": "src/Drivers/Database.php",
"chars": 9635,
"preview": "<?php\n\nnamespace Akaunting\\Setting\\Drivers;\n\nuse Akaunting\\Setting\\Contracts\\Driver;\nuse Akaunting\\Setting\\Support\\Arr;\n"
},
{
"path": "src/Drivers/Json.php",
"chars": 1767,
"preview": "<?php\n\nnamespace Akaunting\\Setting\\Drivers;\n\nuse Akaunting\\Setting\\Contracts\\Driver;\nuse Illuminate\\Filesystem\\Filesyste"
},
{
"path": "src/Drivers/Memory.php",
"chars": 620,
"preview": "<?php\n\nnamespace Akaunting\\Setting\\Drivers;\n\nuse Akaunting\\Setting\\Contracts\\Driver;\n\nclass Memory extends Driver\n{\n "
},
{
"path": "src/Facade.php",
"chars": 277,
"preview": "<?php\n\nnamespace Akaunting\\Setting;\n\nuse Illuminate\\Support\\Facades\\Facade as BaseFacade;\n\nclass Facade extends BaseFaca"
},
{
"path": "src/Manager.php",
"chars": 1511,
"preview": "<?php\n\nnamespace Akaunting\\Setting;\n\nuse Akaunting\\Setting\\Drivers\\Database;\nuse Akaunting\\Setting\\Drivers\\Json;\nuse Aka"
},
{
"path": "src/Middleware/AutoSaveSetting.php",
"chars": 562,
"preview": "<?php\n\nnamespace Akaunting\\Setting\\Middleware;\n\nuse Closure;\n\nclass AutoSaveSetting\n{\n /**\n * Create a new save s"
},
{
"path": "src/Migrations/2017_08_24_000000_create_settings_table.php",
"chars": 903,
"preview": "<?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Fa"
},
{
"path": "src/Provider.php",
"chars": 2153,
"preview": "<?php\n\nnamespace Akaunting\\Setting;\n\nuse Akaunting\\Setting\\Middleware\\AutoSaveSetting;\nuse Illuminate\\Support\\ServicePro"
},
{
"path": "src/Support/Arr.php",
"chars": 4056,
"preview": "<?php\n\nnamespace Akaunting\\Setting\\Support;\n\nclass Arr\n{\n /**\n * This class is a static class and should not be i"
},
{
"path": "src/helpers.php",
"chars": 946,
"preview": "<?php\n\nif (!function_exists('array_keys_exists')) {\n /**\n * Easily check if multiple array keys exist.\n *\n "
},
{
"path": "tests/functional/AbstractFunctionalTest.php",
"chars": 3283,
"preview": "<?php\n\nuse Akaunting\\Setting\\Drivers\\Database;\n\nabstract class AbstractFunctionalTest extends PHPUnit_Framework_TestCase"
},
{
"path": "tests/functional/DatabaseTest.php",
"chars": 1161,
"preview": "<?php\n\nclass DatabaseTest extends AbstractFunctionalTest\n{\n public function setUp()\n {\n $this->container = "
},
{
"path": "tests/functional/JsonTest.php",
"chars": 655,
"preview": "<?php\n\nclass JsonTest extends AbstractFunctionalTest\n{\n protected function createStore(array $data = null)\n {\n "
},
{
"path": "tests/functional/MemoryTest.php",
"chars": 604,
"preview": "<?php\n\nclass MemoryTest extends AbstractFunctionalTest\n{\n protected function assertStoreEquals($store, $expected, $me"
},
{
"path": "tests/unit/ArrayUtilTest.php",
"chars": 3731,
"preview": "<?php\n\nuse Akaunting\\Setting\\Support\\Arr;\n\nclass ArrayUtilityTest extends PHPUnit_Framework_TestCase\n{\n /**\n * @t"
},
{
"path": "tests/unit/DatabaseDriverTest.php",
"chars": 3627,
"preview": "<?php\n\nuse Mockery as m;\n\nclass DatabaseDriverTest extends PHPUnit_Framework_TestCase\n{\n public function tearDown()\n "
},
{
"path": "tests/unit/HelperTest.php",
"chars": 1186,
"preview": "<?php\n\nuse Illuminate\\Container\\Container;\nuse Mockery as m;\n\nclass HelperTest extends PHPUnit_Framework_TestCase\n{\n "
},
{
"path": "tests/unit/JsonDriverTest.php",
"chars": 1756,
"preview": "<?php\n\nuse Mockery as m;\n\nclass JsonDriverTest extends PHPUnit_Framework_TestCase\n{\n public function tearDown()\n {"
}
]
About this extraction
This page contains the full source code of the akaunting/laravel-setting GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 29 files (57.1 KB), approximately 14.4k tokens, and a symbol index with 128 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.