Completed
Push — master ( 5700af...e14437 )
by Rudi
03:26
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 InvalidArgumentException;
6
use Serializable;
7
8
/**
9
 * Abstract configuration container used to manage a configuration array.
10
 */
11
abstract class AbstractContainer implements Serializable
12
{
13
    /**
14
     * @var mixed[] Provided configuration values.
15
     */
16
    protected $provided;
17
18
    /**
19
     * @var mixed[] Evaluated configuration values.
20
     */
21
    protected $evaluated = [];
22
23
    /**
24
     * @var array Expected configuration value types.
25
     */
26
    private $types;
27
28
    /**
29
     * @var array Default configuration 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
     * @return array
67
     */
68
    abstract protected function getDefaultValues();
69
70
    /**
71
     * Constructs this configuration container. Public access is not allowed as
72
     * a container should not be able to be re-constructed. Use ::make instead.
73
     *
74
     * @param mixed[] $provided
75
     */
76 22
    protected function __construct(array $provided)
77
    {
78 22
        $this->types    = $this->getExpectedTypes();
79 22
        $this->defaults = $this->getDefaultValues();
80
81 22
        $this->provided = array_replace_recursive($this->defaults, $provided);
0 ignored issues
show
Documentation Bug introduced by
It seems like array_replace_recursive(...s->defaults, $provided) of type array<integer|string,*> is incompatible with the declared type array<integer,*> of property $provided.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
82 22
    }
83
84
    /**
85
     * Creates an instance of this container using the provided values.
86
     *
87
     * @param array $provided
88
     *
89
     * @return AbstractContainer
90
     */
91 22
    public static function make(array $provided = [])
92
    {
93 22
        return new static($provided);
94
    }
95
96
    /**
97
     * Attempts to find a value by descending into an array, following a given
98
     * key path.
99
     *
100
     * @param array $haystack
101
     * @param array $path
102
     *
103
     * @return mixed
104
     *
105
     * @throws InvalidArgumentException if the path is not valid
106
     */
107 22
    private function find(array $haystack, array $path)
108
    {
109 22
        foreach ($path as $key) {
110 22
            if ( ! array_key_exists($key, $haystack)) {
111 3
                throw new InvalidArgumentException(
112 3
                    "Could not find value for " . implode('.', $path)
113 3
                );
114
            }
115
116 21
            $haystack = $haystack[$key];
117 21
        }
118
119
        // Should be the value we are looking for
120 20
        return $haystack;
121
    }
122
123
    /**
124
     * Finds the expected types for a given key path.
125
     *
126
     * @param array $path
127
     *
128
     * @return array
129
     *
130
     * @throws InvalidArgumentException if the types could not be found.
131
     */
132 20
    protected function findTypes(array $path)
133
    {
134 20
        $types = $this->find($this->types, $path);
135 19
        return is_array($types) ? $types : [$types];
136
    }
137
138
    /**
139
     * Finds the configuration value for a given key path.
140
     *
141
     * @param array $path
142
     *
143
     * @return mixed
144
     *
145
     * @throws InvalidArgumentException if the value could not be found.
146
     */
147 22
    protected function findValue(array $path)
148
    {
149 22
        return $this->find($this->provided, $path);
150
    }
151
152
    /**
153
     * Evaluates a configuration value according to an array of expected types.
154
     *
155
     * @param mixed $value The value to evaluate.
156
     * @param array $types The expected types for the value.
157
     *
158
     * @return mixed The evaluated configuration value.
159
     *
160
     * @throws UnexpectedValueException if the value doesn't match.
161
     */
162 19
    protected function evaluate($value, array $types)
163
    {
164 19
        if ($value === null && in_array($value, $types)) {
165 4
            return $value;
166
        }
167
168 15
        return Evaluator::evaluate($value, $types);
169
    }
170
171
    /**
172
     * Returns a configuration value defined by a path provided as arguments.
173
     *
174
     * @return mixed
175
     */
176 22
    public function get()
177
    {
178 22
        $path = func_get_args();
179 22
        $key  = implode('.', $path);
180
181
        // Allow dot-notation in first parameter.
182 22
        if (count($path) === 1 && strpos($path[0], '.') !== false) {
183 1
            $path = explode('.', $path[0]);
184 1
        }
185
186 22
        if ( ! array_key_exists($key, $this->evaluated)) {
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
    /**
197
     * @inheritdoc
198
     *
199
     * @return array
200
     */
201 1
    public function serialize()
202
    {
203 1
        return serialize([
204 1
            $this->provided,
205 1
            $this->evaluated,
206 1
            $this->types,
207 1
            $this->defaults,
208 1
        ]);
209
    }
210
211
    /**
212
     * @inheritdoc
213
     *
214
     * @param array $serialized
215
     */
216 1
    public function unserialize($serialized)
217
    {
218 1
        $data = unserialize($serialized);
219
220 1
        $this->provided = $data[0];
221 1
        $this->evaluated = $data[1];
222 1
        $this->types = $data[2];
223 1
        $this->defaults = $data[3];
224 1
    }
225
}
226