Completed
Push — laravel-53 ( 86dfb1 )
by Tony
08:36
created

Settings::get()   D

Complexity

Conditions 9
Paths 1

Size

Total Lines 39
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 39
rs 4.909
c 0
b 0
f 0
cc 9
eloc 16
nc 1
nop 2
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 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...
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
112
            // fetch the values from storage
113
            if (Config::has('config.'.$key)) {
114
                $config_data = Config::get('config.'.$key, $default);
115
            }
116
            $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...
117
118
            if (count($db_data) == 1 && $db_data->first()->config_name == $key) {
119
                // return a value if we are getting one item and it is the requested item (not recursing)
120
                return $db_data->first()->config_value;
121
            }
122
            elseif (count($db_data) >= 1) {
123
                // convert the collection to an array
124
                $result = self::collectionToArray($db_data, $key);
125
126
                // if we have config_data, merge them
127
                if (isset($config_data)) {
128
                    return array_replace_recursive($config_data, $result);
129
                }
130
                else {
131
                    return $result;
132
                }
133
            }
134
135
            // fall back to config.php/defaults.php
136
            if (isset($config_data) && (!is_array($config_data) || count($db_data) == 0)) {
137
                // return the value from config.php if it is a value
138
                return $config_data;
139
            }
140
141
142
            // we couldn't find the key, return the default
143
            return $default;
144
        });
145
    }
146
147
    /**
148
     * Convert an Eloquent Collection into a nested array
149
     *
150
     * @param $data \Illuminate\Database\Eloquent\Collection The Collection.
151
     * @param string $prefix Path to prepend. Do not include trailing .
152
     * @return array The resulting nested array.
153
     */
154
    private static function collectionToArray($data, $prefix = "")
155
    {
156
        $tree = array();
157
        foreach ($data as $item) {
158
            $key = $item->config_name;
159
            if (starts_with($key, $prefix)) {
160
                $key = substr($key, strlen($prefix));
161
            }
162
            $parts = explode('.', trim($key, '.'));
163
164
            $temp = &$tree;
165
            foreach ($parts as $part) {
166
                $temp = &$temp[$part];
167
            }
168
            $temp = $item->config_value;
169
            unset($temp);
170
        }
171
        return $tree;
172
    }
173
174
    /**
175
     * Check if the key is defined in the Settings store.
176
     *
177
     * @param string $key Only full paths will return true.
178
     * @return bool
179
     */
180
    public function has($key)
181
    {
182
        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...
183
    }
184
185
    /**
186
     * Check if the key is read only.
187
     * When the user is not a global admin.
188
     * Currently, $key is unused
189
     *
190
     * @param string $key The path to check
191
     * @return boolean false or the source: config | auth
192
     */
193
    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...
194
    {
195
        $user = \Auth::user();
196
        return (is_null($user) || !$user->isAdmin());
197
    }
198
199
    /**
200
     * Forget a key and all children
201
     * This cannot forget variables set in config.php
202
     *
203
     * @param string $key Explicit key to forget
204
     */
205
    public function forget($key)
206
    {
207
        // Cannot remove from config
208
209
        $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...
210
        if ($count == 1) {
211
            $this->flush($key);
212
        }
213
        else {
214
            $this->flush(); // possible optimization: selective flush
215
        }
216
217
        DbConfig::key($key)->delete();
218
    }
219
220
    /**
221
     * Get all settings defined in the Settings store.
222
     *
223
     * @return array A nested array of all settings.
224
     */
225
    public function all()
226
    {
227
        // no caching :(
228
        $config_settings = Config::all()['config'];
229
        $db_settings = self::collectionToArray(DbConfig::all());
230
        return array_replace_recursive($config_settings, $db_settings);
231
    }
232
233
    // ---- Local Utility functions ----
234
235
    /**
236
     * Clear the settings cache.
237
     * If path is set, only clear the path and it's parents.
238
     * This will not clear children.
239
     *
240
     * @param string $key The path to clear.
241
     */
242
    public function flush($key = null)
243
    {
244
        if (is_null($key)) {
245
            // Clear all cache
246
            Cache::tags(self::$cache_tag)->flush();
247
        }
248
        else {
249
            // Clear specific path
250
            $path = [];
251
            foreach (explode('.', $key) as $element) {
252
                $path[] = $element;
253
                Cache::tags(self::$cache_tag)->forget(join('.', $path));
254
            }
255
        }
256
    }
257
258
    /**
259
     * Prepend a value onto an array configuration value.
260
     *
261
     * @param  string $key
262
     * @param  mixed $value
263
     * @return void
264
     */
265
    public function prepend($key, $value)
266
    {
267
        $var = $this->get($key);
268
269
        if (is_array($var)) {
270
            $this->forget($key);
271
            array_unshift($var, $value);
272
            $this->set($key, $var);
273
        }
274 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...
275
            $arr = [$value];
276
            if (!is_null($var)) {
277
                $arr[] = $var;
278
            }
279
280
            $this->forget($key);
281
            $this->set($key, $arr);
282
        }
283
    }
284
285
    /**
286
     * Push a value onto an array configuration value.
287
     *
288
     * @param  string $key
289
     * @param  mixed $value
290
     * @return void
291
     */
292
    public function push($key, $value)
293
    {
294
        $var = $this->get($key);
295
        if (is_array($var)) {
296
            $var[] = $value;
297
            $this->set($key, $var);
298
        }
299 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...
300
            $arr = array();
301
            if (!is_null($var)) {
302
                $arr[] = $var;
303
            }
304
            $arr[] = $value;
305
306
            $this->forget($key);
307
            $this->set($key, $arr);
308
        }
309
    }
310
}
311