Settings::get()   D
last analyzed

Complexity

Conditions 10
Paths 1

Size

Total Lines 36
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 10

Importance

Changes 0
Metric Value
cc 10
eloc 16
nc 1
nop 3
dl 0
loc 36
ccs 15
cts 15
cp 1
crap 10
rs 4.8196
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
use App\Models\DbConfig;
28
use Cache;
29
use Config;
30
use Illuminate\Contracts\Config\Repository as ConfigContract;
31
32
// adds the possibility to replace the default Config facade
33
34
class Settings implements ConfigContract
35
{
36
    /**
37
     * The tag for the cache.
38
     *
39
     * @var string
40
     */
41
    public static $cache_tag = "Settings";
42
    /**
43
     * The amount of time in minutes to store values in cache
44
     *
45
     * @var int
46
     */
47
    private $cache_time;
48
49
    /**
50
     * Settings constructor.
51
     */
52 66
    public function __construct()
53
    {
54 66
        $this->cache_time = env('CACHE_LIFETIME', 60);
55 66
    }
56
57
    /**
58
     * Set a key value pair into the Settings store.
59
     *
60
     * @param string $key A . separated path to this setting
61
     * @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
62
     * @throws \Exception
63
     */
64 33
    public function set($key, $value = null)
65
    {
66 33
        if (is_array($value)) {
67
            // repeat until value contains no arrays
68 14
            foreach ($value as $k => $v) {
69 14
                if (!empty($key)) {
70 13
                    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 exists does only exist in Illuminate\Database\Query\Builder, but not in App\Models\DbConfig.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
Bug introduced by
The method count does only exist in Illuminate\Database\Query\Builder, but not in App\Models\DbConfig.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
71
                        // check that we aren't trying to set an array onto an existing value only setting
72 1
                        throw new \Exception("Attempting to set array value to existing non-array value at the key '".$key."'");
73
                    } else {
74
                        // we are not at the leaf yet, add this chunk to the key and recurse
75 12
                        $this->set($key.'.'.$k, $v);
76
                    }
77
                } else {
78
                    // a leaf, recurse one last time
79 13
                    $this->set($k, $v);
80
                }
81
            }
82
        } else {
83
            // make sure we can save this
84 33
            if ($this->isReadOnly($key)) {
85 1
                throw new \Exception("The setting '".$key."' is read only");
86
            }
87
88
            // flush the cache and save the value in db and cache
89 32
            $this->flush($key);
90 32
            DbConfig::updateOrCreate(['config_name' => $key], ['config_value' => $value]);
91 32
            Cache::tags(self::$cache_tag)->put($key, $value, $this->cache_time);
92
        }
93 32
    }
94
95
    /**
96
     * Get a value from the Settings store.
97
     *
98
     * @param string $key A full or partial . separated key.
99
     * @param null $default If the key isn't found, return this value. By default undefined keys return null.
100
     * @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.
101
     */
102
    public function get($key, $default = null, $is_array = false)
103
    {
104
        // return value from cache or fetch it and return it
105 60
        return Cache::tags(self::$cache_tag)->remember($key, $this->cache_time, function () use ($key, $default, $is_array) {
106
            // fetch the values from storage
107 52
            if (Config::has('config.'.$key)) {
108 5
                $config_data = Config::get('config.'.$key, $default);
109
            }
110 52
            $db_data = DbConfig::key($key)->get(['config_name', 'config_value']);
0 ignored issues
show
Bug introduced by
The method get does only exist in Illuminate\Database\Query\Builder, but not in App\Models\DbConfig.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
111
112 52
            if (count($db_data) == 1 && $db_data->first()->config_name == $key && $is_array !== true) {
113
                // return a value if we are getting one item and it is the requested item (not recursing)
114 6
                return $db_data->first()->config_value;
115 49
            } elseif (count($db_data) >= 1) {
116
                // convert the collection to an array
117 14
                $result = self::collectionToArray($db_data, $key);
118
119
                // if we have config_data, merge them
120 14
                if (isset($config_data)) {
121 2
                    return array_replace_recursive($config_data, $result);
122
                } else {
123 12
                    return $result;
124
                }
125
            }
126
127
            // fall back to config.php/defaults.php
128 37
            if (isset($config_data) && (!is_array($config_data) || count($db_data) == 0)) {
129
                // return the value from config.php if it is a value
130 2
                return $config_data;
131
            }
132
133
134
            // we couldn't find the key, return the default
135 35
            return $default;
136 60
        });
137
    }
138
139
    /**
140
     * Convert an Eloquent Collection into a nested array
141
     *
142
     * @param $data \Illuminate\Database\Eloquent\Collection The Collection.
143
     * @param string $prefix Path to prepend. Do not include trailing .
144
     * @return array The resulting nested array.
145
     */
146 15
    private static function collectionToArray($data, $prefix = "")
147
    {
148 15
        $tree = array();
149 15
        foreach ($data as $item) {
150 15
            $key = $item->config_name;
151 15
            if (starts_with($key, $prefix)) {
152 14
                $key = substr($key, strlen($prefix));
153
            }
154 15
            $parts = explode('.', trim($key, '.'));
155
156 15
            $temp = &$tree;
157 15
            foreach ($parts as $part) {
158 15
                $temp = &$temp[$part];
159
            }
160 15
            $temp = $item->config_value;
161 15
            unset($temp);
162
        }
163 15
        return $tree;
164
    }
165
166
    /**
167
     * Check if the key is defined in the Settings store.
168
     *
169
     * @param string $key Only full paths will return true.
170
     * @return bool
171
     */
172 2
    public function has($key)
173
    {
174 2
        return (Cache::tags(self::$cache_tag)->has($key) || Config::has('config.'.$key) || DbConfig::key($key)->exists());
0 ignored issues
show
Bug introduced by
The method exists does only exist in Illuminate\Database\Query\Builder, but not in App\Models\DbConfig.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
175
    }
176
177
    /**
178
     * Check if the key is read only.
179
     * When the user is not a global admin.
180
     * Currently, $key is unused
181
     *
182
     * @param string $key The path to check
183
     * @return boolean false or the source: config | auth
184
     */
185 34
    public function isReadOnly($key)
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
186
    {
187 34
        $user = \Auth::user();
188 34
        return (is_null($user) || !$user->isAdmin());
189
    }
190
191
    /**
192
     * Forget a key and all children
193
     * This cannot forget variables set in config.php
194
     *
195
     * @param string $key Explicit key to forget
196
     */
197 3
    public function forget($key)
198
    {
199
        // Cannot remove from config
200
201 3
        $count = DbConfig::key($key)->count();
0 ignored issues
show
Bug introduced by
The method count does only exist in Illuminate\Database\Query\Builder, but not in App\Models\DbConfig.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
202 3
        if ($count == 1) {
203 2
            $this->flush($key);
204
        } else {
205 3
            $this->flush(); // possible optimization: selective flush
206
        }
207
208 3
        DbConfig::key($key)->delete();
209 3
    }
210
211
    /**
212
     * Get all settings defined in the Settings store.
213
     *
214
     * @return array A nested array of all settings.
215
     */
216 1
    public function all()
217
    {
218
        // no caching :(
219 1
        $config_settings = Config::all()['config'];
220 1
        $db_settings = self::collectionToArray(DbConfig::all());
221 1
        return array_replace_recursive($config_settings, $db_settings);
222
    }
223
224
    // ---- Local Utility functions ----
225
226
    /**
227
     * Clear the settings cache.
228
     * If path is set, only clear the path and it's parents.
229
     * This will not clear children.
230
     *
231
     * @param string $key The path to clear.
232
     */
233 35
    public function flush($key = null)
234
    {
235 35
        if (is_null($key)) {
236
            // Clear all cache
237 12
            Cache::tags(self::$cache_tag)->flush();
238
        } else {
239
            // Clear specific path
240 32
            $path = [];
241 32
            foreach (explode('.', $key) as $element) {
242 32
                $path[] = $element;
243 32
                Cache::tags(self::$cache_tag)->forget(join('.', $path));
244
            }
245
        }
246 35
    }
247
248
    /**
249
     * Prepend a value onto an array configuration value.
250
     *
251
     * @param  string $key
252
     * @param  mixed $value
253
     * @return void
254
     */
255 1
    public function prepend($key, $value)
256
    {
257 1
        $var = $this->get($key);
258
259 1
        if (is_array($var)) {
260 1
            $this->forget($key);
261 1
            array_unshift($var, $value);
262 1
            $this->set($key, $var);
263
        } else {
264 1
            $arr = [$value];
265 1
            if (!is_null($var)) {
266 1
                $arr[] = $var;
267
            }
268
269 1
            $this->forget($key);
270 1
            $this->set($key, $arr);
271
        }
272 1
    }
273
274
    /**
275
     * Push a value onto an array configuration value.
276
     *
277
     * @param  string $key
278
     * @param  mixed $value
279
     * @return void
280
     */
281 1
    public function push($key, $value)
282
    {
283 1
        $var = $this->get($key);
284 1
        if (is_array($var)) {
285 1
            $var[] = $value;
286 1
            $this->set($key, $var);
287
        } else {
288 1
            $arr = array();
289 1
            if (!is_null($var)) {
290 1
                $arr[] = $var;
291
            }
292 1
            $arr[] = $value;
293
294 1
            $this->forget($key);
295 1
            $this->set($key, $arr);
296
        }
297 1
    }
298
}
299