├── LICENSE
├── README.md
├── composer.json
├── database
└── migrations
│ └── 2020_03_04_044421_create_settings_table.php
└── src
├── Facades
└── Settings.php
├── Providers
└── SettingsServiceProvider.php
├── Setting.php
└── SettingsManager.php
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011-2018 by Nikita Popov.
2 |
3 | Some rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are
7 | met:
8 |
9 | * Redistributions of source code must retain the above copyright
10 | notice, this list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above
13 | copyright notice, this list of conditions and the following
14 | disclaimer in the documentation and/or other materials provided
15 | with the distribution.
16 |
17 | * The names of the contributors may not be used to endorse or
18 | promote products derived from this software without specific
19 | prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Laravel Settings Manager
2 | ==========
3 |
4 | This is a Laravel package provide an easy way to control application settings, which can easily integrated to application configuration to be used with config() function
5 |
6 |
7 | Compatibility
8 | ============
9 | This package was built for versions >= 7.0 but also compatible with versions >= 5.8.16
10 |
11 | Features
12 | ========
13 |
14 | * auto load all settings (with option to disable it)
15 | * cache loaded entries for current request
16 | * auto save entries to DB (with option to disable it)
17 | * auto create entries to DB if not exists (with option to disable it)
18 | * map setting entry from DB to config() key
19 | * changeable model.
20 | * customizable package configuration with manager.
21 | * macroable settings manager
22 | * compatible with PHPUnit testings
23 |
24 | Installation
25 | ==============
26 |
27 | ```
28 | composer require ibraheem-ghazi/laravel-settings-manager
29 | ```
30 | then:
31 | ```
32 | php artisan migrate
33 | ```
34 | if your installed laravel version does not support auto discover packages then:
35 |
36 | 1- add this provider to config:
37 |
38 | ```
39 | IbraheemGhazi\SettingsManager\Providers\SettingsServiceProvider::class,
40 | ```
41 |
42 | 2- then add alias:
43 | ```
44 | 'Settings' => IbraheemGhazi\SettingsManager\Facades\Settings::class,
45 | ```
46 |
47 |
48 | Configuration
49 | ================
50 |
51 | ### Attributes
52 | |func | description|
53 | |----------------------------------|----------------------|
54 | |$ignoreMigration | ignore auto register package migrations.
55 | |$AutoLoadFromDatabase | disable/enable auto loading configuration from database.
56 | |$AutoSaveOnSet | disable/enable auto save configuration to database.
57 | |$AutoCreateOnSave | disable/enable auto create configuration to database if not already exists.
58 | |$Model | change the model used to save settings - must have key, value fields where key is *primary key*.
59 |
60 | ### Methods
61 | |func | params | description|
62 | |----------------------------------|----------------|----------------------|
63 | |bind | string $settings_key, ?string $config_key = NULL | bind a settings entry from DB to application configuration key
64 | |unbind | string $settings_key | remove binding of a settings entry to application configuration key
65 |
66 | Other Available Functions
67 | =========================
68 |
69 | |func | params | return | description|
70 | |------------------------|---------------------|---------------|----------------------|
71 | |load | bool $force = false | ($this) | load settings from DB (or force reload it)
72 | |getModel | - | Model | return the model used to control DB entries
73 | |getBindings | - | Collection | get all configured settings to configurations bindings
74 | |all | - | Collection | return collection of strings of all entries.
75 | |get | $key, $default=NULL | mixed | return the value of specified key
76 | |set | $key, $value, $save = false, $should_create = true | ($this) | set the value for specified key, (with option to force enable/disable saving or creating)
77 | |forget | $key, $permanent_remove = true, $callback = NULL | ($this) | remove the specified key, (with option to force enable/disable removing entry from DB, and call a callback function when done removing)
78 |
79 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ibraheem-ghazi/laravel-settings-manager",
3 | "description": "Laravel settings management package using database and laravel config()",
4 | "keywords": ["php", "settings", "setting"],
5 | "type": "library",
6 | "license": "MIT",
7 | "minimum-stability": "dev",
8 | "authors": [
9 | {
10 | "name": "Ibraheem Ghazi"
11 | }
12 | ],
13 | "require": {
14 | "php": ">=5.6.0",
15 | "laravel/framework": ">=5.8.16"
16 | },
17 | "autoload": {
18 | "psr-4": {
19 | "IbraheemGhazi\\SettingsManager\\": "src/"
20 | }
21 | },
22 | "extra": {
23 | "laravel": {
24 | "providers": [
25 | "IbraheemGhazi\\SettingsManager\\Providers\\SettingsServiceProvider"
26 | ],
27 | "aliases": {
28 | "Settings": "IbraheemGhazi\\SettingsManager\\Facades\\Settings"
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/database/migrations/2020_03_04_044421_create_settings_table.php:
--------------------------------------------------------------------------------
1 | string('key',190)->primary();
18 | $table->text('value');
19 | });
20 | }
21 |
22 | /**
23 | * Reverse the migrations.
24 | *
25 | * @return void
26 | */
27 | public function down()
28 | {
29 | Schema::dropIfExists('settings');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Facades/Settings.php:
--------------------------------------------------------------------------------
1 | app->singleton('settings', function () {
18 | return new SettingsManager();
19 | });
20 | if($this->app->runningInConsole())
21 | $this->registerMigrations();
22 | }
23 |
24 | /**
25 | * Bootstrap services.
26 | *
27 | * @return void
28 | */
29 | public function boot()
30 | {
31 |
32 | }
33 |
34 | /**
35 | * Register migration files.
36 | *
37 | * @return void
38 | */
39 | protected function registerMigrations(){
40 | if(!SettingsManager::$ignoreMigration)
41 | $this->loadMigrationsFrom(__DIR__ . '/../../database/migrations');
42 |
43 | }
44 |
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/Setting.php:
--------------------------------------------------------------------------------
1 | cache = collect();
43 | App::booted(function () {
44 | if (static::$autoLoadFromDatabase) {
45 | $this->load();
46 | }
47 | });
48 |
49 | $this->getModel()->saved(function (Model $entry) {
50 | $this->applyBindingOnModel($entry);
51 | });
52 | }
53 |
54 | /**
55 | * load all entries from database then apply bindings if required
56 | * @param bool $force force loading data from database even it's already loaded
57 | * @return $this
58 | */
59 | public function load($force = false)
60 | {
61 | $execute = function () use ($force) {
62 | if ($this->cache->isNotEmpty() && !$force) return;
63 | $this->cache = $this->getModel()->get();
64 | $this->refreshBindings();
65 | };
66 |
67 | /**
68 | * if we are on PHPUnit never run load() before tables exists
69 | * @since 5.8.16
70 | */
71 | if (app()->runningUnitTests()) {
72 | app('events')->listen(MigrationsEnded::class, $execute);
73 | } else {
74 | $execute();
75 | }
76 | return $this;
77 | }
78 |
79 | /**
80 | * force refresh the bindings to have its value
81 | * @return $this
82 | */
83 | public function refreshBindings()
84 | {
85 | foreach ($this->cache as $entry) {
86 | $this->applyBindingOnModel($entry);
87 | }
88 | return $this;
89 | }
90 |
91 | /**
92 | * get settings model instance
93 | * @return Model
94 | */
95 | protected function getModel()
96 | {
97 | return $this->resolve(static::$model);
98 | }
99 |
100 | /**
101 | * resolve class string path to instance
102 | * @param $class
103 | * @return mixed
104 | */
105 | private function resolve($class)
106 | {
107 | return App::make($class);
108 | }
109 |
110 | /**
111 | * apply bindings using entry key and value
112 | * @param Model $entry
113 | */
114 | protected function applyBindingOnModel(Model $entry)
115 | {
116 | $this->applyBindingOn($this->getBinding($entry), $entry->getAttribute('value'));
117 |
118 | /**
119 | * the following code fix the dependency between bindings for example:
120 | * Settings::bind('seo.site_name', 'app.name')
121 | * will now work as expected and bind the app.name to seo.site_name
122 | * so both will have same value
123 | */
124 | $value = config($this->getBinding($entry));
125 | foreach ($this->getDependentBinding($entry) as $dep){
126 | config([$dep=>$value]);
127 | }
128 | }
129 |
130 | /**
131 | * apply bindings using key and value
132 | * @param string $key
133 | * @param $value
134 | */
135 | protected function applyBindingOn($key, $value)
136 | {
137 | if (is_string($key) && strlen($key) && array_key_exists($key, $this->configBindings)) {
138 | $config_key = $this->configBindings[$key];
139 | config([$config_key => $this->tryDecodeValue($value)]);
140 | }
141 | }
142 |
143 | protected function getBinding(Model $entry)
144 | {
145 | return $this->configBindings[$entry->getAttribute('key')] ?? NULL;
146 | }
147 |
148 | protected function getDependentBinding(Model $entry)
149 | {
150 | $temp = [];
151 | foreach ($this->configBindings as $k=>$v){
152 | if($k !== $v && $v === $entry->getAttribute('key')){
153 | $temp[] = $k;
154 | }
155 | }
156 | return $temp;
157 | }
158 |
159 | /**
160 | * change the Eloquent Model used to handle database connection
161 | * @param Model $model
162 | * @return $this
163 | */
164 | public function setModel(Model $model)
165 | {
166 | static::$model = $model;
167 |
168 | return $this;
169 | }
170 |
171 |
172 | /**
173 | * get all loaded settings
174 | */
175 | public function all()
176 | {
177 | return $this->cache->pluck('value', 'key');
178 | }
179 |
180 | /**
181 | * get specific settings entry or return default if not found
182 | * @param $key
183 | * @param null $default
184 | * @return mixed|null
185 | */
186 | public function get($key, $default = NULL)
187 | {
188 | if ($entry = $this->getEntry($key)) {
189 | return $this->tryDecodeValue( $entry->getAttribute('value') );
190 | }
191 | // if($this->applyBindingOnNonExists){
192 | // $this->applyBindingOn($key, $default);
193 | // }
194 | return $default;
195 | }
196 |
197 | /**
198 | * try to decode the input value, if it has any error
199 | * while decoding it will return the input value.
200 | * @param $value
201 | * @return mixed
202 | */
203 | protected function tryDecodeValue($value){
204 | try {
205 | $temp = json_decode($value, true);
206 | if(json_last_error() === JSON_ERROR_NONE){
207 | return $temp;
208 | }
209 | return $value;
210 | } catch (\Exception $ex) {
211 | return $value;
212 | }
213 | }
214 |
215 | /**
216 | * get Model entry from already loaded settings using key
217 | * @param $key
218 | * @return Model|null
219 | */
220 | protected function getEntry($key)
221 | {
222 | return $this->cache->where('key', $key)->first();
223 | }
224 |
225 | /**
226 | * set specific settings entry and save it if required
227 | * @param $key
228 | * @param $value
229 | * @param bool $save force saving this settings entry (update or create if $should_create == TRUE)
230 | * @param bool $should_create when forcing save entry, should create it if not exists ?
231 | * @return $this
232 | */
233 | public function set($key, $value, $save = false, $should_create = true)
234 | {
235 | $value = is_string($value) ? $value : json_encode($value, JSON_UNESCAPED_UNICODE);
236 | if ($entry = $this->getEntry($key)) {
237 | $entry->setAttribute('value', $value);
238 | if (static::$autoSaveOnSet || $save) {
239 | $entry->save();
240 | }
241 | return $this;
242 | }
243 |
244 | tap($this->getModel()->newInstance(), function (Model $entry) use ($key, $value, $save, $should_create) {
245 | $entry->setAttribute('key', $key);
246 | $entry->setAttribute('value', $value);
247 | if (static::$autoCreateOnSet || ($save && $should_create)) {
248 | $entry->save();
249 | }
250 | $this->cache->push($entry);
251 | });
252 |
253 | return $this;
254 | }
255 |
256 | /**
257 | * forget a key from settings
258 | * @param string $key setting key to delete
259 | * @param bool $permanent_remove determine if should also remove DB entry
260 | * @param null|mixed $callback once forgot entry, if has binding this will it's value
261 | * @return $this
262 | */
263 | public function forget($key, $permanent_remove = true, $callback = NULL)
264 | {
265 | $entry = $this->getEntry($key);
266 |
267 | if (!$entry) return $this;
268 |
269 | $this->removeEntry($entry);
270 | $permanent_remove && rescue(function () use ($entry) {
271 | $entry->exists && $entry->delete();
272 | });
273 |
274 | if ($this->hasBinding($entry) && !is_null($callback)) {
275 | $config_key = $this->getBinding($entry);
276 | $config_key && $this->applyBindingOn($config_key, value($callback));
277 | }
278 |
279 | return $this;
280 | }
281 |
282 | protected function removeEntry(Model $entry)
283 | {
284 | if ($key = $this->cache->search($entry) !== FALSE) {
285 | $this->cache->forget($key);
286 | }
287 | }
288 |
289 | protected function hasBinding(Model $entry)
290 | {
291 | return array_key_exists($entry->getAttribute('key'), $this->configBindings);
292 | }
293 |
294 | /**
295 | * get current registered config bindings
296 | * @return array
297 | */
298 | public function getBindings()
299 | {
300 | return $this->configBindings;
301 | }
302 |
303 | /**
304 | * bind settings to config, so when ever the settings key is
305 | * retrieved or saved it will change the assigned this setting
306 | * value to default app configuration.
307 | * example 1: bind('app.name')
will result to override config('app.name')
to have get('app.name')
value
308 | * example 2: bind('app.name','app_name')
will result to override config('app.name')
to have get('app_name')
value
309 | * @param string $settings_key
310 | * @param string|null $config_key
311 | * @return $this
312 | */
313 | public function bind(string $settings_key, ?string $config_key = NULL)
314 | {
315 | $this->configBindings[$settings_key] = $config_key ?: $settings_key;
316 | return $this;
317 | }
318 |
319 | /**
320 | * unbind already bound settings to config link
321 | * @param $settings_key
322 | * @return $this
323 | */
324 | public function unbind($settings_key)
325 | {
326 | Arr::forget($this->configBindings, $settings_key);
327 | return $this;
328 | }
329 |
330 |
331 | }
332 |
--------------------------------------------------------------------------------