Completed
Push — master ( e14437...c66bcc )
by Rudi
02:22
created

AbstractContainer::unserialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 9
ccs 7
cts 7
cp 1
rs 9.6667
nc 1
cc 1
eloc 6
nop 1
crap 1
1
<?php
2
3
namespace Concat\Config\Container;
4
5
use Concat\Config\Container\Value;
6
use InvalidArgumentException;
7
8
/**
9
 * Abstract configration container used to manage a configuration array.
10
 */
11
abstract class AbstractContainer
12
{
13
    /**
14
     * @var mixed[] Provided configuration values.
15
     */
16
    protected $provided;
17
18
    /**
19
     * @var mixed[] Evaluated configration values.
20
     */
21
    protected $evaluated = [];
22
23
    /**
24
     * @var array Expected configration value types.
25
     */
26
    private $types;
27
28
    /**
29
     * @var array Default configration values.
30
     */
31
    private $defaults;
32
33
    /**
34
     * Returns an associative array of expected types, with names mapped to
35
     * either a single type or an array of accepted types. These types can be
36
     * \Concat\Config\Container\Value constants or class names. You can also
37
     * specify `null` as an expected type, which allows null evaluations.
38
     *
39
     * eg.
40
     * return [
41
     *     'a' => Value::TYPE_INTEGER,
42
     *     'b' => [
43
     *         'c' => "\Class\Path",
44
     *     ],
45
     * ];
46
     *
47
     * @return array
48
     */
49
    abstract protected function getExpectedTypes();
50
51
    /**
52
     * Returns an associative array of optional default values, with names
53
     * mapped to a single value. This value can also be a class name, which will
54
     * be evaluated when first requested. You can also specify callables which
55
     * will evaluate to their result when the value is requested, however this
56
     * does not work if a callable is also an acceptable value type.
57
     *
58
     * eg.
59
     * return [
60
     *     'a' => 10,
61
     *     'b' => [
62
     *         'c' => new \Class\Path(),
63
     *     ],
64
     * ];
65
     *
66
     */
67
    abstract protected function getDefaultValues();
68
69
    /**
70
     * Constructs this configuration container. Public access is not allowed as
71
     * a container should not be able to be re-constructed. Use ::make instead.
72
     *
73
     * @param mixed[] $provided
74
     */
75
    protected function __construct(array $provided)
76 22
    {
77
        $this->types    = $this->getExpectedTypes();
78 22
        $this->defaults = $this->getDefaultValues();
79 22
80
        $this->provided = array_replace_recursive($this->defaults, $provided);
81 22
    }
82 22
83
    /**
84
     * Creates an instance of this container using the provided values.
85
     *
86
     * @param array $provided
87
     *
88
     * @return AbstractContainer
89
     */
90
    public static function make(array $provided = [])
91 22
    {
92
        return new static($provided);
93 22
    }
94
95
    /**
96
     * Attempts to find a value by descending into an array, following a given
97
     * key path.
98
     *
99
     * @param array $haystack
100
     * @param array $path
101
     *
102
     * @return mixed
103
     *
104
     * @throws InvalidArgumentException if the path is not valid
105
     */
106
    private function find(array $haystack, array $path)
107 22
    {
108
        foreach ($path as $key) {
109 22
            if ( ! array_key_exists($key, $haystack)) {
110 22
                throw new InvalidArgumentException(
111 3
                    "Could not find value for " . implode('.', $path)
112 3
                );
113 3
            }
114
115
            $haystack = $haystack[$key];
116 21
        }
117 21
118
        // Should be the value we are looking for
119
        return $haystack;
120 20
    }
121
122
    /**
123
     * Finds the expected types for a given key path.
124
     *
125
     * @param array $path
126
     *
127
     * @return array
128
     *
129
     * @throws InvalidArgumentException if the types could not be found.
130
     */
131
    protected function findTypes(array $path)
132 20
    {
133
        $types = $this->find($this->types, $path);
134 20
        return is_array($types) ? $types : [$types];
135 19
    }
136
137
    /**
138
     * Finds the configuration value for a given key path.
139
     *
140
     * @param array $path
141
     *
142
     * @return mixed
143
     *
144
     * @throws InvalidArgumentException if the value could not be found.
145
     */
146
    protected function findValue(array $path)
147 22
    {
148
        return $this->find($this->provided, $path);
149 22
    }
150
151
    /**
152
     * Evaluates a configuration value according to an array of expected types.
153
     *
154
     * @param mixed $value The value to evaluate.
155
     * @param array $types The expected types for the value.
156
     *
157
     * @return mixed The evaluated configuration value.
158
     *
159
     * @throws UnexpectedValueException if the value doesn't match.
160
     */
161
    protected function evaluate($value, array $types)
162 19
    {
163
        if ($value === null && in_array($value, $types)) {
164 19
            return $value;
165 4
        }
166
167
        return Evaluator::evaluate($value, $types);
168 15
    }
169
170
    /**
171
     * Returns a configuration value defined by a path provided as arguments.
172
     *
173
     * @return mixed
174
     */
175
    public function get()
176 22
    {
177
        $path = func_get_args();
178 22
        $key  = implode('.', $path);
179 22
180
        // Allow dot-notation in first parameter.
181
        if (count($path) === 1 && strpos($path[0], '.') !== false) {
182 22
            $path = explode('.', $path[0]);
183 1
        }
184 1
185
        if ( ! array_key_exists($key, $this->evaluated)) {
186 22
187 22
            $value = $this->findValue($path);
188 20
            $types = $this->findTypes($path);
189
190 19
            $this->evaluated[$key] = $this->evaluate($value, $types);
191 17
        }
192
193 17
        return $this->evaluated[$key];
194
    }
195
}
196