Completed
Push — misc ( cfaf83...3fa631 )
by Tony
02:42
created

Settings   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 270
Duplicated Lines 7.04 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 10
Bugs 3 Features 1
Metric Value
wmc 37
c 10
b 3
f 1
lcom 1
cbo 1
dl 19
loc 270
rs 8.6

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A collectionToArray() 0 19 4
A has() 0 4 3
A isReadOnly() 0 4 1
A forget() 0 14 2
A all() 0 7 1
A flush() 0 15 3
D set() 0 34 9
C get() 0 36 7
A prepend() 9 19 3
A push() 10 18 3

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/*
3
 * Copyright (C) 2016 Tony Murray <[email protected]>
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17
/**
18
 * Settings.php
19
 *
20
 * @package    LibreNMS
21
 * @author     Tony Murray <[email protected]>
22
 * @copyright  2016 Tony Murray
23
 * @license    @license http://opensource.org/licenses/GPL-3.0 GNU Public License v3 or later
24
 */
25
namespace App;
26
27
28
use App\Models\DbConfig;
29
use Cache;
30
use Config;
31
use Illuminate\Contracts\Config\Repository as ConfigContract;
32
33
// adds the possibility to replace the default Config facade
34
35
class Settings implements ConfigContract
36
{
37
    /**
38
     * The tag for the cache.
39
     *
40
     * @var string
41
     */
42
    public static $cache_tag = "Settings";
43
    /**
44
     * The amount of time in minutes to store values in cache
45
     *
46
     * @var int
47
     */
48
    private $cache_time;
49
50
    /**
51
     * Settings constructor.
52
     */
53
    public function __construct()
54
    {
55
        $this->cache_time = env('CACHE_LIFETIME', 60);
56
    }
57
58
    /**
59
     * Set a key value pair into the Settings store.
60
     *
61
     * @param string $key A . separated path to this setting
62
     * @param array|null $value A value or an array. If value is an array it will be converted to a . separate path(s) concatinated onto the given key
63
     * @throws \Exception
64
     */
65
    public function set($key, $value = null)
66
    {
67
        if (is_array($value)) {
68
            // repeat until value contains no arrays
69
            foreach ($value as $k => $v) {
70
                if (!empty($key)) {
71
                    if (is_string($k) && !str_contains($k, '.') && DbConfig::exactKey($key)->exists() && DbConfig::key($key)->count() == 1) {
0 ignored issues
show
Bug introduced by
The method exactKey() does not exist on App\Models\DbConfig. Did you maybe mean scopeExactKey()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
72
                        // check that we aren't trying to set an array onto an existing value only setting
73
                        throw new \Exception("Attempting to set array value to existing non-array value at the key '" . $key . "'");
74
                    }
75
                    else {
76
                        // we are not at the leaf yet, add this chunk to the key and recurse
77
                        $this->set($key . '.' . $k, $v);
78
                    }
79
                }
80
                else {
81
                    // a leaf, recurse one last time
82
                    $this->set($k, $v);
83
                }
84
            }
85
        }
86
        else {
87
            // make sure we can save this
88
            if ($this->isReadOnly($key)) {
89
                throw new \Exception("The setting '" . $key . "' is read only");
90
            }
91
92
            // flush the cache and save the value in db and cache
93
            $this->flush($key);
94
            DbConfig::updateOrCreate(['config_name' => $key], ['config_value' => $value]);
0 ignored issues
show
Bug introduced by
The method updateOrCreate() does not exist on App\Models\DbConfig. Did you maybe mean create()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
95
            Cache::tags(self::$cache_tag)->put($key, $value, $this->cache_time);
96
        }
97
98
    }
99
100
    /**
101
     * Get a value from the Settings store.
102
     *
103
     * @param string $key A full or partial . separated key.
104
     * @param null $default If the key isn't found, return this value. By default undefined keys return null.
105
     * @return mixed If the $key is a full path, a bare value will be returned.  If it is a partial path, a nested array will be retuned.
106
     */
107
    public function get($key, $default = null)
108
    {
109
        // return value from cache or fetch it and return it
110
        return Cache::tags(self::$cache_tag)->remember($key, $this->cache_time, function () use ($key, $default) {
111
            // fetch the value from config.php first
112
            if (Config::has('config.' . $key)) {
113
                $config_data = Config::get('config.' . $key, $default);
114
                if (!is_array($config_data)) {
115
                    // return the value from config.php if it is a value
116
                    return $config_data;
117
                }
118
            }
119
120
            // fetch the value from the database
121
            $db_data = DbConfig::key($key)->get(['config_name', 'config_value']);
122
123
            if (count($db_data) == 1 && $db_data->first()->config_name == $key) {
124
                // return a value if we are getting one item
125
                return $db_data->first()->config_value;
126
            }
127
            elseif (count($db_data) >= 1) {
128
                // convert the collection to an array
129
                $result = self::collectionToArray($db_data, $key);
130
131
                // if we have config_data, merge them
132
                if (isset($config_data)) {
133
                    return array_replace_recursive($result, $config_data);
134
                }
135
                else {
136
                    return $result;
137
                }
138
            }
139
            // we couldn't find the key, return the default
140
            return $default;
141
        });
142
    }
143
144
    /**
145
     * Convert an Eloquent Collection into a nested array
146
     *
147
     * @param $data \Illuminate\Database\Eloquent\Collection The Collection.
148
     * @param string $prefix Path to prepend. Do not include trailing .
149
     * @return array The resulting nested array.
150
     */
151
    private static function collectionToArray($data, $prefix = "")
152
    {
153
        $tree = array();
154
        foreach ($data as $item) {
155
            $key = $item->config_name;
156
            if (starts_with($key, $prefix)) {
157
                $key = substr($key, strlen($prefix));
158
            }
159
            $parts = explode('.', trim($key, '.'));
160
161
            $temp = &$tree;
162
            foreach ($parts as $part) {
163
                $temp = &$temp[$part];
164
            }
165
            $temp = $item->config_value;
166
            unset($temp);
167
        }
168
        return $tree;
169
    }
170
171
    /**
172
     * Check if the key is defined in the Settings store.
173
     *
174
     * @param string $key Only full paths will return true.
175
     * @return bool
176
     */
177
    public function has($key)
178
    {
179
        return (Cache::tags(self::$cache_tag)->has($key) || Config::has('config.' . $key) || DbConfig::key($key)->exists());
180
    }
181
182
    /**
183
     * Check if the key is read only.  This is when the setting is defined in config.php.
184
     *
185
     * @param string $key The path to check
186
     * @return bool
187
     */
188
    public function isReadOnly($key)
189
    {
190
        return Config::has('config.' . $key);
191
    }
192
193
    /**
194
     * Forget a key and all children
195
     * This cannot forget variables set in config.php
196
     *
197
     * @param $key string Explicit key to forget
198
     */
199
    public function forget($key)
200
    {
201
        // Cannot remove from config
202
203
        $count = DbConfig::key($key)->count();
204
        if ($count == 1) {
205
            $this->flush($key);
206
        }
207
        else {
208
            $this->flush(); // possible optimization: selective flush
209
        }
210
211
        DbConfig::key($key)->delete();
212
    }
213
214
    /**
215
     * Get all settings defined in the Settings store.
216
     *
217
     * @return array A nested array of all settings.
218
     */
219
    public function all()
220
    {
221
        // no caching :(
222
        $config_settings = Config::all()['config'];
223
        $db_settings = self::collectionToArray(DbConfig::all());
224
        return array_replace_recursive($config_settings, $db_settings);
225
    }
226
227
    // ---- Local Utility functions ----
228
229
    /**
230
     * Clear the settings cache.
231
     * If path is set, only clear the path and it's parents.
232
     * This will not clear children.
233
     *
234
     * @param string $key The path to clear.
235
     */
236
    public function flush($key = null)
237
    {
238
        if (is_null($key)) {
239
            // Clear all cache
240
            Cache::tags(self::$cache_tag)->flush();
241
        }
242
        else {
243
            // Clear specific path
244
            $path = [];
245
            foreach (explode('.', $key) as $element) {
246
                $path[] = $element;
247
                Cache::tags(self::$cache_tag)->forget(join('.', $path));
248
            }
249
        }
250
    }
251
252
    /**
253
     * Prepend a value onto an array configuration value.
254
     *
255
     * @param  string $key
256
     * @param  mixed $value
257
     * @return void
258
     */
259
    public function prepend($key, $value)
260
    {
261
        $var = $this->get($key);
262
263
        if (is_array($var)) {
264
            $this->forget($key);
265
            array_unshift($var, $value);
266
            $this->set($key, $var);
267
        }
268 View Code Duplication
        else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
269
            $arr = [$value];
270
            if (!is_null($var)) {
271
                $arr[] = $var;
272
            }
273
274
            $this->forget($key);
275
            $this->set($key, $arr);
276
        }
277
    }
278
279
    /**
280
     * Push a value onto an array configuration value.
281
     *
282
     * @param  string $key
283
     * @param  mixed $value
284
     * @return void
285
     */
286
    public function push($key, $value)
287
    {
288
        $var = $this->get($key);
289
        if (is_array($var)) {
290
            $var[] = $value;
291
            $this->set($key, $var);
292
        }
293 View Code Duplication
        else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
294
            $arr = array();
295
            if (!is_null($var)) {
296
                $arr[] = $var;
297
            }
298
            $arr[] = $value;
299
300
            $this->forget($key);
301
            $this->set($key, $arr);
302
        }
303
    }
304
}
305