├── .gitattributes
├── .gitignore
├── src
├── Models
│ └── Setting.php
├── SettingsManager.php
├── Console
│ ├── stubs
│ │ └── database.stub
│ └── SettingsTableCommand.php
├── Facades
│ └── Settings.php
├── Providers
│ └── SettingsServiceProvider.php
├── Contracts
│ └── SettingsStore.php
└── DatabaseSettingsHandler.php
├── phpunit.xml
├── tests
├── database
│ └── migrations
│ │ └── 2019_04_04_160347_create_settings_table.php
└── LaravelSettingsTest.php
├── composer.json
├── config
└── laravel-settings.php
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | /.github export-ignore
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | composer.lock
3 | /vendor
4 | .phpunit.result.cache
--------------------------------------------------------------------------------
/src/Models/Setting.php:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | tests
10 |
11 |
12 |
13 |
14 | src/
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/SettingsManager.php:
--------------------------------------------------------------------------------
1 | $name]);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Console/stubs/database.stub:
--------------------------------------------------------------------------------
1 | id();
17 | $table->string('key')->index();
18 | $table->string('locale')->nullable()->index();
19 | $table->text('value')->nullable();
20 | $table->unique(['key', 'locale']);
21 | $table->timestamps();
22 | });
23 | }
24 |
25 | /**
26 | * Reverse the migrations.
27 | *
28 | * @return void
29 | */
30 | public function down()
31 | {
32 | Schema::dropIfExists('settings');
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/tests/database/migrations/2019_04_04_160347_create_settings_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
17 | $table->string('key')->index();
18 | $table->string('locale')->nullable()->index();
19 | $table->text('value')->nullable();
20 | $table->unique(['key', 'locale']);
21 | $table->timestamps();
22 | });
23 | }
24 |
25 | /**
26 | * Reverse the migrations.
27 | *
28 | * @return void
29 | */
30 | public function down()
31 | {
32 | Schema::dropIfExists('settings');
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/src/Facades/Settings.php:
--------------------------------------------------------------------------------
1 | =9.0"
14 | },
15 | "require-dev": {
16 | "orchestra/testbench": ">=3.0",
17 | "mockery/mockery": "^1.4"
18 | },
19 | "autoload": {
20 | "psr-4": {
21 | "Laraeast\\LaravelSettings\\": "src/"
22 | }
23 | },
24 | "autoload-dev": {
25 | "psr-4": {
26 | "Laraeast\\LaravelSettings\\Tests\\": "tests/"
27 | }
28 | },
29 | "extra": {
30 | "branch-alias": {
31 | "dev-master": "0.0.x-dev"
32 | },
33 | "laravel": {
34 | "providers": [
35 | "Laraeast\\LaravelSettings\\Providers\\SettingsServiceProvider"
36 | ],
37 | "aliases": {
38 | "Settings": "Laraeast\\LaravelSettings\\Facades\\Settings"
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Providers/SettingsServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->singleton('settings.manager', function ($app) {
19 | return new SettingsManager($app);
20 | });
21 | $this->app->singleton('settings', function ($app) {
22 | return $app->make('settings.manager')->driver();
23 | });
24 | }
25 |
26 | /**
27 | * Bootstrap any application services.
28 | *
29 | * @return void
30 | */
31 | public function boot()
32 | {
33 | $this->mergeConfigFrom(__DIR__.'/../../config/laravel-settings.php', 'laravel-settings');
34 |
35 | if ($this->app->runningInConsole()) {
36 | $this->publishes([
37 | __DIR__.'/../../config/laravel-settings.php' => config_path('laravel-settings.php'),
38 | ], 'settings:config');
39 |
40 | $this->commands([
41 | SettingsTableCommand::class,
42 | ]);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Contracts/SettingsStore.php:
--------------------------------------------------------------------------------
1 | createBaseMigration();
43 |
44 | $this->files->put($fullPath, $this->files->get(__DIR__.'/stubs/database.stub'));
45 |
46 | $this->info('Migration created successfully!');
47 |
48 | $this->composer->dumpAutoloads();
49 | }
50 |
51 | /**
52 | * Create a base migration file for the session.
53 | */
54 | protected function createBaseMigration(): string
55 | {
56 | $name = 'create_settings_table';
57 |
58 | $path = $this->laravel->databasePath().'/migrations';
59 |
60 | return $this->laravel['migration.creator']->create($name, $path);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/config/laravel-settings.php:
--------------------------------------------------------------------------------
1 | 'database',
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Cache Settings Query
23 | |--------------------------------------------------------------------------
24 | |
25 | | This option controls the settings performance.
26 | |
27 | */
28 |
29 | 'use_cache' => true,
30 |
31 | /*
32 | |--------------------------------------------------------------------------
33 | | Cache Expiration Time
34 | |--------------------------------------------------------------------------
35 | |
36 | | This option controls the expiration time in seconds.
37 | |
38 | */
39 |
40 | 'cache_expire' => 60 * 60 * 24 * 30, // 1 month
41 |
42 | /*
43 | |--------------------------------------------------------------------------
44 | | Setting Model Class
45 | |--------------------------------------------------------------------------
46 | |
47 | | If you want to customize model for application settings you should
48 | | add your custom settings model class name here.
49 | |
50 | | Used only in "database" driver.
51 | |
52 | */
53 |
54 | 'model_class' => null,
55 | ];
56 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | # Persistent Settings Manager for Laravel
10 |
11 | * Simple key-value storage
12 | * Localization supported.
13 | * Localization using [Astrotomic/laravel-translatable](https://github.com/Astrotomic/laravel-translatable) Structure
14 |
15 | ## Installation
16 |
17 | 1. Install package
18 |
19 | ```bash
20 | composer require laraeast/laravel-settings
21 | ```
22 |
23 | 1. Edit config/app.php (Skip this step if you are using laravel 5.5+)
24 |
25 | service provider:
26 |
27 | ```php
28 | Laraeast\LaravelSettings\Providers\SettingsServiceProvider::class,
29 | ```
30 |
31 | class aliases:
32 |
33 | ```php
34 | 'Settings' => Laraeast\LaravelSettings\Facades\Settings::class,
35 | ```
36 |
37 | 1. Create settings table for `database` driver
38 |
39 | ```bash
40 | php artisan settings:table
41 | php artisan migrate
42 | ```
43 |
44 | ## Usage
45 |
46 | ```php
47 | Settings::get('name', 'Computer');
48 | // get setting value with key 'name'
49 | // return 'Computer' if the key does not exists
50 |
51 | Settings::locale('en')->get('name', 'Computer');
52 | // get setting value with key and language
53 |
54 | Settings::get('name:en', 'Computer');
55 | // get setting value with key and language
56 |
57 | Settings::set('name', 'Computer');
58 | // set setting value by key
59 |
60 | Settings::locale('en')->set('name', 'Computer');
61 | // set setting value by key and language
62 |
63 | Settings::set('name:en', 'Computer');
64 | // set setting value by key and language
65 |
66 | Settings::has('name');
67 | // check the key exists, return boolean
68 |
69 | Settings::locale('en')->has('name');
70 | // check the key exists by language, return boolean
71 |
72 | Settings::has('name:en');
73 | // check the key exists by language, return boolean
74 |
75 | Settings::delete('name');
76 | // delete the setting by key
77 |
78 | Settings::locale('en')->delete('name');
79 | // delete the setting by key and language
80 |
81 | Settings::delete('name:en');
82 | // delete the setting by key and language
83 | ```
84 |
85 | ## Dealing with array
86 |
87 | ```php
88 | Settings::get('item');
89 | // return null;
90 |
91 | Settings::set('item', ['USB' => '8G', 'RAM' => '4G']);
92 | Settings::get('item');
93 | // return array(
94 | // 'USB' => '8G',
95 | // 'RAM' => '4G',
96 | // );
97 | ```
98 | ### Usage
99 | ```php
100 | Settings::locale('en')->set('title', 'Example Website');
101 |
102 | Settings::locale('en')->get('title');
103 | // return return 'Example Website';
104 |
105 | Settings::set('title:ar', 'عنوان الموقع');
106 |
107 | Settings::locale('ar')->get('title');
108 | // return return 'عنوان الموقع';
109 |
110 | Settings::locale('ar')->has('title') // bool
111 | Settings::locale('ar')->delete('title')
112 |
113 | App::setLocale('en');
114 |
115 | Settings::locale()->get('title');
116 | // return return 'Example Website';
117 | ```
118 | ### Extend Driver
119 | > You can extend your custom driver by adding this code in `register()` method of your `AppServiceProvier`
120 |
121 | ###### EX :
122 | ```php
123 | $this->app['settings.manager']->extend('file', function () {
124 | return new SettingsFileDriverHandler();
125 | });
126 | ```
127 | > Note : your custom driver `SettingsFileDriverHandler` should implements `Laraeast\LaravelSettings\Contracts\SettingsStore` contract
128 | ```
129 | fetchSettings();
21 | }
22 |
23 | /**
24 | * Set a new settings item.
25 | */
26 | public function set(string $key, mixed $value = null): Setting
27 | {
28 | Cache::forget('settings');
29 |
30 | $this->supportLocaledKey($key);
31 |
32 | $model = $this->getModelClassName();
33 |
34 | $model::updateOrCreate([
35 | 'key' => $key,
36 | 'locale' => $this->locale,
37 | ], [
38 | 'key' => $key,
39 | 'locale' => $this->locale,
40 | 'value' => serialize($value),
41 | ]);
42 |
43 | $this->fetchSettings();
44 |
45 | $value = $this->instance($key);
46 |
47 | $this->locale = null;
48 |
49 | return $value;
50 | }
51 |
52 | /**
53 | * Set the settings locale.
54 | */
55 | public function locale(?string $locale = null): self
56 | {
57 | $this->locale = $locale ?: $this->app->getLocale();
58 |
59 | return $this;
60 | }
61 |
62 | /**
63 | * Get the given item.
64 | *
65 | * @template TDefault
66 | *
67 | * @param string $key
68 | * @param TDefault|null $default
69 | *
70 | * @return ($default is null ? Setting : TDefault)
71 | */
72 | public function get(string $key, mixed $default = null): mixed
73 | {
74 | $instance = $this->instance($key);
75 |
76 | $this->locale = null;
77 |
78 | return $instance ? unserialize($instance->value) : $default;
79 | }
80 |
81 | /**
82 | * Fetch the settings collection.
83 | */
84 | private function fetchSettings(): void
85 | {
86 | $model = $this->getModelClassName();
87 |
88 | if ($this->app['config']->get('laravel-settings.use_cache')) {
89 | $expireSeconds = $this->app['config']->get('laravel-settings.cache_expire');
90 |
91 | $this->settings = Cache::remember(
92 | 'settings',
93 | Carbon::now()->addSeconds($expireSeconds),
94 | function () use ($model) {
95 | return $model::get();
96 | }
97 | );
98 | } else {
99 | $this->settings = $model::get();
100 | }
101 | }
102 |
103 | /**
104 | * Get the settings row.
105 | *
106 | * @template TDefault
107 | *
108 | * @param string $key
109 | * @param TDefault|null $default
110 | *
111 | * @return ($default is null ? Setting : TDefault)
112 | */
113 | public function instance(string $key, mixed $default = null): mixed
114 | {
115 | $this->supportLocaledKey($key);
116 |
117 | return $this->settings->where('key', $key)->where('locale', $this->locale)->first()
118 | ?: $default;
119 | }
120 |
121 | /**
122 | * Determine whether the key is already exists.
123 | */
124 | public function has(string $key): bool
125 | {
126 | return (bool) $this->instance($key);
127 | }
128 |
129 | /**
130 | * Delete the given key from storage.
131 | */
132 | public function delete(string $key): self
133 | {
134 | if ($this->instance($key)) {
135 | Cache::forget('settings');
136 |
137 | $this->instance($key)->delete();
138 |
139 | $this->fetchSettings();
140 | }
141 |
142 | return $this;
143 | }
144 |
145 | /**
146 | * Update locale if the key has the language.
147 | */
148 | private function supportLocaledKey(string &$key): void
149 | {
150 | if (str_contains($key, ':')) {
151 | $this->locale(explode(':', $key)[1]);
152 | $key = explode(':', $key)[0];
153 | }
154 | }
155 |
156 | /**
157 | * The model class name.
158 | *
159 | * @return class-string
160 | */
161 | private function getModelClassName(): string
162 | {
163 | $model = $this->app['config']->get('laravel-settings.model_class');
164 |
165 | return $model ?: Setting::class;
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/tests/LaravelSettingsTest.php:
--------------------------------------------------------------------------------
1 | loadLaravelMigrations(['--database' => 'testbench']);
20 |
21 | $this->loadMigrationsFrom(__DIR__.'/database/migrations');
22 | }
23 |
24 | /**
25 | * Load package service provider.
26 | */
27 | protected function getPackageProviders($app)
28 | {
29 | return [SettingsServiceProvider::class];
30 | }
31 |
32 | /**
33 | * Load package alias.
34 | */
35 | protected function getPackageAliases($app)
36 | {
37 | return [
38 | 'Settings' => Settings::class,
39 | ];
40 | }
41 |
42 | /**
43 | * Define environment setup.
44 | */
45 | protected function getEnvironmentSetUp($app)
46 | {
47 | // Setup default database to use sqlite :memory:
48 | $app['config']->set('database.default', 'testbench');
49 | $app['config']->set('database.connections.testbench', [
50 | 'driver' => 'sqlite',
51 | 'database' => ':memory:',
52 | 'prefix' => '',
53 | ]);
54 | }
55 |
56 | public function test_it_can_set_and_get_data()
57 | {
58 | Settings::set('name', 'Ahmed Fathy');
59 | Settings::set('phone', '021207687151');
60 | $this->assertEquals(Settings::get('phone'), '021207687151');
61 | $this->assertEquals(Settings::get('name'), 'Ahmed Fathy');
62 | }
63 |
64 | public function test_it_returns_default_value_if_the_key_does_not_exists()
65 | {
66 | $this->assertEquals(Settings::get('UndefindKey', 'FooBar'), 'FooBar');
67 | }
68 |
69 | public function test_it_returns_unique_value_of_localed_data()
70 | {
71 | Settings::locale('en')->set('language', 'English');
72 | Settings::locale('ar')->set('language', 'Arabic');
73 | $this->assertEquals(Settings::locale('en')->get('language'), 'English');
74 | $this->assertEquals(Settings::locale('ar')->get('language'), 'Arabic');
75 | Settings::locale('en')->delete('language');
76 | Settings::locale('ar')->delete('language');
77 | Settings::set('language:en', 'English');
78 | Settings::set('language:ar', 'Arabic');
79 | $this->assertEquals(Settings::locale('en')->get('language'), 'English');
80 | $this->assertEquals(Settings::locale('ar')->get('language'), 'Arabic');
81 | $this->assertEquals(Settings::get('language:en'), 'English');
82 | $this->assertEquals(Settings::get('language:ar'), 'Arabic');
83 | Settings::locale('en')->set('language', 'English');
84 | $this->assertEquals(Setting::where(['locale' => 'en', 'key' => 'language'])->count(), 1);
85 | }
86 |
87 | public function test_it_determine_if_the_value_exists()
88 | {
89 | Settings::set('name', 'Ahmed');
90 | $this->assertTrue(Settings::has('name'));
91 | Settings::locale('en')->set('language', 'English');
92 | $this->assertTrue(Settings::locale('en')->has('language'));
93 | $this->assertTrue(Settings::has('language:en'));
94 | }
95 |
96 | public function test_it_can_deleted_the_specific_key()
97 | {
98 | Settings::set('name', 'Ahmed');
99 | $this->assertTrue(Settings::has('name'));
100 | Settings::delete('name');
101 | $this->assertFalse(Settings::has('name'));
102 | $this->assertDatabaseMissing('settings', [
103 | 'key' => 'name',
104 | ]);
105 | Settings::locale('en')->set('name', 'Ahmed');
106 | Settings::locale('ar')->set('name', 'احمد');
107 | $this->assertTrue(Settings::locale('en')->has('name'));
108 | $this->assertTrue(Settings::locale('ar')->has('name'));
109 | Settings::locale('en')->delete('name');
110 | $this->assertFalse(Settings::locale('en')->has('name'));
111 | $this->assertTrue(Settings::locale('ar')->has('name'));
112 | $this->assertDatabaseMissing('settings', [
113 | 'key' => 'name',
114 | 'locale' => 'en',
115 | ]);
116 | Settings::delete('name:ar');
117 | $this->assertFalse(Settings::locale('ar')->has('name'));
118 | $this->assertFalse(Settings::has('name:ar'));
119 | $this->assertDatabaseMissing('settings', [
120 | 'key' => 'name',
121 | 'locale' => 'ar',
122 | ]);
123 | }
124 |
125 | public function test_it_returns_model_after_set_value()
126 | {
127 | $this->assertInstanceOf(Setting::class, Settings::set('foo', 'bar'));
128 | }
129 | }
130 |
--------------------------------------------------------------------------------