Config   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 311
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 0
Metric Value
wmc 28
lcom 1
cbo 9
dl 0
loc 311
rs 10
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 3
A get() 0 13 3
A has() 0 21 3
A set() 0 18 2
A setErrorType() 0 5 1
A loadConfig() 0 16 2
A loadByGroup() 0 16 4
A loadGlobal() 0 14 2
A getGroupName() 0 7 1
A referenceLookup() 0 11 2
A resolveUnknown() 0 10 1
A getReference() 0 4 1
A throwError() 0 13 3
1
<?php
2
/**
3
 * Phossa Project
4
 *
5
 * PHP version 5.4
6
 *
7
 * @category  Library
8
 * @package   Phossa2\Config
9
 * @copyright Copyright (c) 2016 phossa.com
10
 * @license   http://mit-license.org/ MIT License
11
 * @link      http://www.phossa.com/
12
 */
13
/*# declare(strict_types=1); */
14
15
namespace Phossa2\Config;
16
17
use Phossa2\Shared\Tree\Tree;
18
use Phossa2\Config\Message\Message;
19
use Phossa2\Config\Loader\DummyLoader;
20
use Phossa2\Shared\Tree\TreeInterface;
21
use Phossa2\Shared\Base\ObjectAbstract;
22
use Phossa2\Config\Traits\WritableTrait;
23
use Phossa2\Config\Traits\ArrayAccessTrait;
24
use Phossa2\Shared\Reference\ReferenceTrait;
25
use Phossa2\Config\Exception\LogicException;
26
use Phossa2\Config\Interfaces\ConfigInterface;
27
use Phossa2\Config\Loader\ConfigLoaderInterface;
28
use Phossa2\Shared\Reference\ReferenceInterface;
29
use Phossa2\Config\Interfaces\WritableInterface;
30
use Phossa2\Shared\Delegator\DelegatorAwareTrait;
31
use Phossa2\Shared\Delegator\DelegatorAwareInterface;
32
33
/**
34
 * Config
35
 *
36
 * @package Phossa2\Config
37
 * @author  Hong Zhang <[email protected]>
38
 * @see     ObjectAbstract
39
 * @see     ConfigInterface
40
 * @see     WritableInterface
41
 * @see     \ArrayAccess
42
 * @see     ReferenceInterface
43
 * @see     DelegatorAwareInterface
44
 * @version 2.1.0
45
 * @since   2.0.0 added
46
 * @since   2.0.7 changed DelegatorAware* stuff
47
 * @since   2.0.10 using recursive getDelegator
48
 * @since   2.0.12 changed `set()` return value
49
 */
50
class Config extends ObjectAbstract implements ConfigInterface, WritableInterface, \ArrayAccess, ReferenceInterface, DelegatorAwareInterface
51
{
52
    use ReferenceTrait, DelegatorAwareTrait, ArrayAccessTrait, WritableTrait;
53
54
    /**
55
     * error type
56
     *
57
     * @var    int
58
     */
59
    const ERROR_IGNORE    = 0;
60
    const ERROR_WARNING   = 1;
61
    const ERROR_EXCEPTION = 2;
62
63
    /**
64
     * the config loader
65
     *
66
     * @var    ConfigLoaderInterface
67
     * @access protected
68
     */
69
    protected $loader;
70
71
    /**
72
     * the config tree
73
     *
74
     * @var    TreeInterface
75
     * @access protected
76
     */
77
    protected $config;
78
79
    /**
80
     * cache loaded group names
81
     *
82
     * @var    array
83
     * @access protected
84
     */
85
    protected $loaded = [];
86
87
    /**
88
     * How to dealing with error, ignore/trigger_error/exception etc.
89
     *
90
     * @var    int
91
     * @access protected
92
     */
93
    protected $error_type = self::ERROR_WARNING;
94
95
    /**
96
     * @var    string
97
     * @access private
98
     */
99
    private $cached_id;
100
101
    /**
102
     * @var    mixed
103
     * @access private
104
     */
105
    private $cached_value;
106
107
    /**
108
     * Constructor
109
     *
110
     * @param  ConfigLoaderInterface $loader config loader if any
111
     * @param  TreeInterface $configTree config tree if any
112
     * @param  array $configData config data for the tree
113
     * @access public
114
     * @api
115
     */
116
    public function __construct(
117
        ConfigLoaderInterface $loader = null,
118
        TreeInterface $configTree = null,
119
        array $configData = []
120
    ) {
121
        $this->loader = $loader ?: new DummyLoader();
122
        $this->config = $configTree ?: new Tree($configData);
123
    }
124
125
    /**
126
     * {@inheritDoc}
127
     */
128
    public function get(/*# string */ $id, $default = null)
129
    {
130
        if ($this->has($id)) {
131
            // cached from has()
132
            $val = $this->cached_value;
133
134
            // dereference
135
            $this->deReferenceArray($val);
136
137
            return null === $val ? $default : $val;
138
        }
139
        return $default;
140
    }
141
142
    /**
143
     * {@inheritDoc}
144
     */
145
    public function has(/*# string */ $id)/*# : bool */
146
    {
147
        // checked already
148
        if ($id === $this->cached_id) {
149
            return null !== $this->cached_value;
150
        }
151
152
        // default result
153
        $this->cached_id = $id;
154
        $this->cached_value = null;
155
156
        // try get config
157
        try {
158
            $this->loadConfig((string) $id);
159
            $this->cached_value = $this->config->getNode((string) $id);
160
            return null !== $this->cached_value;
161
        } catch (\Exception $e) {
162
            $this->throwError($e->getMessage(), $e->getCode());
163
            return false;
164
        }
165
    }
166
167
    /**
168
     * {@inheritDoc}
169
     */
170
    public function set(/*# string */ $id, $value)/*# : bool */
171
    {
172
        if ($this->isWritable()) {
173
            // lazy load, no dereference
174
            $this->loadConfig((string) $id);
175
176
            // replace the node
177
            $this->cached_id = null;
178
            $this->config->addNode($id, $value);
179
180
            return $this->has($id);
181
        } else {
182
            $this->throwError(
183
                Message::get(Message::CONFIG_NOT_WRITABLE),
184
                Message::CONFIG_NOT_WRITABLE
185
            );
186
        }
187
    }
188
189
    /**
190
     * Set error type
191
     *
192
     * @param  int $type
193
     * @return $this
194
     * @access public
195
     * @api
196
     */
197
    public function setErrorType(/*# int */ $type)
198
    {
199
        $this->error_type = (int) $type;
200
        return $this;
201
    }
202
203
    /**
204
     * Load config
205
     *
206
     * @param  string $id
207
     * @return $this
208
     * @throws LogicException if current $error_type is to throw exception
209
     * @access protected
210
     */
211
    protected function loadConfig(/*# string */ $id)
212
    {
213
        // get group name
214
        $group = $this->getGroupName($id);
215
216
        // $group loaded ?
217
        if (isset($this->loaded[$group])) {
218
            return $this;
219
        }
220
221
        // mark as loaded
222
        $this->loaded[$group] = true;
223
224
        // loading the group
225
        return $this->loadByGroup($group);
226
    }
227
228
    /**
229
     * Load one group config, force loading all groups if $group == ''
230
     *
231
     * @param  string $group
232
     * @return $this
233
     * @throws \Exception group loading issues
234
     * @access protected
235
     */
236
    protected function loadByGroup(/*# string */ $group)
237
    {
238
        // load super global
239
        if ('' !== $group && '_' === $group[0]) {
240
            return $this->loadGlobal($group);
241
        }
242
243
        // load from config
244
        $conf = $this->loader->load($group);
245
246
        foreach ($conf as $grp => $data) {
247
            $this->config->addNode($grp, $data);
248
        }
249
250
        return $this;
251
    }
252
253
    /**
254
     * Load super globals
255
     *
256
     * @param  string $group
257
     * @return $this
258
     * @throws LogicException if super global unknown
259
     * @access protected
260
     */
261
    protected function loadGlobal(/*# string */ $group)
0 ignored issues
show
Coding Style introduced by
loadGlobal uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
262
    {
263
        if (!isset($GLOBALS[$group])) {
264
            $this->throwError(
265
                Message::get(Message::CONFIG_GLOBAL_UNKNOWN, $group),
266
                Message::CONFIG_GLOBAL_UNKNOWN
267
            );
268
        }
269
270
        // load super global
271
        $this->config->addNode($group, $GLOBALS[$group]);
272
273
        return $this;
274
    }
275
276
    /**
277
     * Get group name
278
     *
279
     * - returns 'system' from $id 'system.dir.tmp'
280
     * - '.system.tmpdir' is invalid
281
     *
282
     * @param  string $id
283
     * @return string
284
     * @access protected
285
     */
286
    protected function getGroupName(/*# string */ $id)/*# : string */
287
    {
288
        return explode(
289
            $this->config->getDelimiter(),
290
            ltrim($id, $this->config->getDelimiter())
291
        )[0];
292
    }
293
294
    /**
295
     * Override 'referenceLookup()' in ReferenceTrait.
296
     *
297
     * Delegator support goes here
298
     *
299
     * @since 2.0.10 using recursive getDelegator
300
     * {@inheritDoc}
301
     */
302
    protected function referenceLookup(/*# string */ $name)
303
    {
304
        if ($this->hasDelegator()) {
305
            // get delegator recursively
306
            $delegator = $this->getDelegator(true);
307
            $val = $delegator->get($name);
308
        } else {
309
            $val = $this->getReference($name);
310
        }
311
        return $val;
312
    }
313
314
    /**
315
     * throw exception if current $error_type is to throw exception
316
     *
317
     * {@inheritDoc}
318
     */
319
    protected function resolveUnknown(/*# string */ $name)
320
    {
321
        // warn if reference unknown
322
        $this->throwError(
323
            Message::get(Message::CONFIG_REFERENCE_UNKNOWN, $name),
324
            Message::CONFIG_REFERENCE_UNKNOWN
325
        );
326
327
        return null;
328
    }
329
330
    /**
331
     * {@inheritDoc}
332
     */
333
    protected function getReference(/*# string */ $name)
334
    {
335
        return $this->get($name);
336
    }
337
338
    /**
339
     * Dealing errors
340
     *
341
     * @param  string $message
342
     * @param  int $code
343
     * @return $this
344
     * @throws LogicException if current $error_type is to throw exception
345
     * @access protected
346
     */
347
    protected function throwError(/*# string */ $message, /*# int */ $code)
348
    {
349
        switch ($this->error_type) {
350
            case self::ERROR_WARNING:
351
                trigger_error($message, \E_USER_WARNING);
352
                break;
353
            case self::ERROR_EXCEPTION:
354
                throw new LogicException($message, $code);
355
            default:
356
                break;
357
        }
358
        return $this;
359
    }
360
}
361