Passed
Push — master ( e7bd4c...c7ad2c )
by Donald
01:47
created

Store   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 283
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 283
ccs 58
cts 58
cp 1
rs 10
c 0
b 0
f 0
wmc 27

12 Methods

Rating   Name   Duplication   Size   Complexity  
A keyValueContains() 0 7 2
A getAll() 0 7 2
A assertKeyExists() 0 9 2
A getOrdinal() 0 7 4
A keyExists() 0 5 2
A buildKey() 0 9 2
A assertKeyValueIs() 0 9 2
A assertKeyValueContains() 0 9 2
A get() 0 9 3
A set() 0 3 1
A parseKey() 0 14 4
A reset() 0 3 1
1
<?php namespace Chekote\NounStore;
2
3
use InvalidArgumentException;
4
use OutOfBoundsException;
5
use RuntimeException;
6
7
class Store
8
{
9
    /** @var array */
10
    protected $nouns;
11
12
    const ORDINAL_ST = 'st';
13
    const ORDINAL_ND = 'nd';
14
    const ORDINAL_RD = 'rd';
15
    const ORDINAL_TH = 'th';
16
17
    protected static $ordinals = [
18
        0 => self::ORDINAL_TH,
19
        1 => self::ORDINAL_ST,
20
        2 => self::ORDINAL_ND,
21
        3 => self::ORDINAL_RD,
22
        4 => self::ORDINAL_TH,
23
        5 => self::ORDINAL_TH,
24
        6 => self::ORDINAL_TH,
25
        7 => self::ORDINAL_TH,
26
        8 => self::ORDINAL_TH,
27
        9 => self::ORDINAL_TH,
28
    ];
29
30
    /**
31
     * Asserts that a value has been stored for the specified key.
32
     *
33
     * @param  string                   $key   The key to check. @see self::get() for formatting options.
34
     * @param  int                      $index [optional] The index of the key entry to check. If not specified, the
35
     *                                         method will ensure that at least one item is stored for the key.
36
     * @throws OutOfBoundsException     if a value has not been stored for the specified key.
37
     * @throws InvalidArgumentException if both an $index and $key are provided, but the $key contains an nth value
38
     *                                        that does not match the index.
39
     * @return mixed                    The value.
40
     */
41 23
    public function assertKeyExists($key, $index = null)
42
    {
43 23
        list($key, $index) = $this->parseKey($key, $index);
44
45 22
        if (!$this->keyExists($key, $index)) {
46 6
            throw new OutOfBoundsException("Entry '{$this->buildKey($key, $index)}' was not found in the store.");
47
        }
48
49 16
        return $this->get($key, $index);
50
    }
51
52
    /**
53
     * Asserts that the key's value matches the specified value.
54
     *
55
     * @param  string                   $key   The key to check. @see self::get() for formatting options.
56
     * @param  mixed                    $value The expected value.
57
     * @param  int                      $index [optional] The index of the key entry to retrieve. If not specified, the
58
     *                                         method will check the most recent value stored under the key.
59
     * @throws OutOfBoundsException     If a value has not been stored for the specified key.
60
     * @throws InvalidArgumentException if both an $index and $key are provided, but the $key contains an nth value
61
     *                                        that does not match the index.
62
     */
63 10
    public function assertKeyValueIs($key, $value, $index = null)
64
    {
65 10
        list($key, $index) = $this->parseKey($key, $index);
66
67 9
        $this->assertKeyExists($key, $index);
68
69 7
        if ($this->get($key, $index) != $value) {
70 2
            throw new RuntimeException(
71 2
                "Entry '{$this->buildKey($key, $index)}' does not match '" . print_r($value, true) . "'"
72
            );
73
        }
74 5
    }
75
76
    /**
77
     * Asserts that the key's value contains the specified string.
78
     *
79
     * @param  string                   $key   The key to check. @see self::get() for formatting options.
80
     * @param  string                   $value The value expected to be contained within the key's value.
81
     * @param  int                      $index [optional] The index of the key entry to retrieve. If not specified, the
82
     *                                         method will check the most recent value stored under the key.
83
     * @throws OutOfBoundsException     If a value has not been stored for the specified key.
84
     * @throws InvalidArgumentException if both an $index and $key are provided, but the $key contains an nth value
85
     *                                        that does not match the index.
86
     */
87 10
    public function assertKeyValueContains($key, $value, $index = null)
88
    {
89 10
        list($key, $index) = $this->parseKey($key, $index);
90
91 9
        $this->assertKeyExists($key, $index);
92
93 7
        if (!$this->keyValueContains($key, $value, $index)) {
94 2
            throw new RuntimeException(
95 2
                "Entry '{$this->buildKey($key, $index)}' does not contain '$value'"
96
            );
97
        }
98 5
    }
99
100
    /**
101
     * Removes all entries from the store.
102
     *
103
     * @return void
104
     */
105 1
    public function reset()
106
    {
107 1
        $this->nouns = [];
108 1
    }
109
110
    /**
111
     * Retrieves a value for the specified key.
112
     *
113
     * Each key is actually a collection. If you do not specify which item in the collection you want,
114
     * the method will return the most recent entry. You can specify the entry you want by either
115
     * using the plain english 1st, 2nd, 3rd etc in the $key param, or by specifying 0, 1, 2 etc in
116
     * the $index param. For example:
117
     *
118
     * Retrieve the most recent entry "Thing" collection:
119
     *   retrieve("Thing")
120
     *
121
     * Retrieve the 1st entry in the "Thing" collection:
122
     *   retrieve("1st Thing")
123
     *   retrieve("Thing", 0)
124
     *
125
     * Retrieve the 3rd entry in the "Thing" collection:
126
     *   retrieve("3rd Thing")
127
     *   retrieve("Thing", 2)
128
     *
129
     * Please note: The nth value in the string key is indexed from 1. In that "1st" is the first item stored.
130
     * The index parameter is indexed from 0. In that 0 is the first item stored.
131
     *
132
     * Please Note: If you specify both an $index param and an nth in the $key, they must both reference the same index.
133
     * If they do not, the method will throw an InvalidArgumentException.
134
     *
135
     * retrieve("1st Thing", 1);
136
     *
137
     * @param  string                   $key   The key to retrieve the value for. Can be prefixed with an nth descriptor.
138
     * @param  int                      $index [optional] The index of the key entry to retrieve. If not specified, the
139
     *                                         method will return the most recent value stored under the key.
140
     * @throws InvalidArgumentException if both an $index and $key are provided, but the $key contains an nth value
141
     *                                        that does not match the index.
142
     * @return mixed                    The value, or null if no value exists for the specified key/index combination.
143
     */
144 32
    public function get($key, $index = null)
145
    {
146 32
        list($key, $index) = $this->parseKey($key, $index);
147
148 31
        if (!$this->keyExists($key, $index)) {
149 5
            return;
150
        }
151
152 26
        return $index !== null ? $this->nouns[$key][$index] : end($this->nouns[$key]);
153
    }
154
155
    /**
156
     * Retrieves all values for the specified key.
157
     *
158
     * @param  string               $key The key to retrieve the values for.
159
     * @throws OutOfBoundsException if the specified $key does not exist in the store.
160
     * @return array                The values.
161
     */
162 2
    public function getAll($key)
163
    {
164 2
        if (!isset($this->nouns[$key])) {
165 1
            throw new OutOfBoundsException("'$key' does not exist in the store");
166
        }
167
168 1
        return $this->nouns[$key];
169
    }
170
171
    /**
172
     * Determines if a value has been stored for the specified key.
173
     *
174
     * @param  string                   $key   The key to check.
175
     * @param  int                      $index [optional] The index of the key entry to check. If not specified, the
176
     *                                         method will ensure that at least one item is stored for the key.
177
     * @throws InvalidArgumentException if both an $index and $key are provided, but the $key contains an nth value
178
     *                                        that does not match the index.
179
     * @return bool                     True if the a value has been stored, false if not.
180
     */
181 42
    public function keyExists($key, $index = null)
182
    {
183 42
        list($key, $index) = $this->parseKey($key, $index);
184
185 41
        return $index !== null ? isset($this->nouns[$key][$index]) : isset($this->nouns[$key]);
186
    }
187
188
    /**
189
     * Asserts that the key's value contains the specified string.
190
     *
191
     * @param  string                   $key   The key to check. @see self::get() for formatting options.
192
     * @param  string                   $value The value expected to be contained within the key's value.
193
     * @param  int                      $index [optional] The index of the key entry to retrieve. If not specified, the
194
     *                                         method will check the most recent value stored under the key.
195
     * @throws InvalidArgumentException if both an $index and $key are provided, but the $key contains an nth value
196
     *                                        that does not match the index.
197
     * @return bool                     True if the key's value contains the specified string, false if not.
198
     */
199 17
    public function keyValueContains($key, $value, $index = null)
200
    {
201 17
        list($key, $index) = $this->parseKey($key, $index);
202
203 16
        $actual = $this->get($key, $index);
204
205 16
        return is_string($actual) && strpos($actual, $value) !== false;
206
    }
207
208
    /**
209
     * Stores a value for the specified key.
210
     *
211
     * @param string $key   The key to store the value under.
212
     * @param mixed  $value The value to store.
213
     */
214 97
    public function set($key, $value)
215
    {
216 97
        $this->nouns[$key][] = $value;
217 97
    }
218
219
    /**
220
     * Parses a key into the separate key and index value.
221
     *
222
     * @example parseKey("Item"): ["Item", null]
223
     * @example parseKey("Item", 1): ["Item", 1]
224
     * @example parseKey("1st Item"): ["Item", 0]
225
     * @example parseKey("2nd Item"): ["Item", 1]
226
     * @example parseKey("3rd Item"): ["Item", 2]
227
     *
228
     * @param  string                   $key   the key to parse.
229
     * @param  int                      $index [optional] the index to return if the key does not contain one.
230
     * @throws InvalidArgumentException if both an $index and $key are provided, but the $key contains an nth value
231
     *                                        that does not match the index.
232
     * @return array                    a tuple, the 1st being the key with the nth removed, and the 2nd being the
233
     *                                        index.
234
     */
235 61
    protected function parseKey($key, $index = null)
236
    {
237 61
        if (preg_match('/^([1-9][0-9]*)(?:st|nd|rd|th) (.+)$/', $key, $matches)) {
238 38
            if ($index !== null && $index != $matches[1] - 1) {
239 11
                throw new InvalidArgumentException(
240 11
                    "$index was provided for index param when key '$key' contains an nth value, but they do not match"
241
                );
242
            }
243
244 27
            $index = $matches[1] - 1;
245 27
            $key = $matches[2];
246
        }
247
248 50
        return [$key, $index];
249
    }
250
251
    /**
252
     * Builds a key from it's separate key and index values.
253
     *
254
     * @example buildKey("Item", null): "Item"
255
     * @example buildKey("Item", 0): "1st Item"
256
     * @example buildKey("Item", 1): "2nd Item"
257
     * @example buildKey("Item", 2): "3rd Item"
258
     *
259
     * @param  string                   $key   The key to check.
260
     * @param  int                      $index The index (zero indexed) value for the key. If not specified, the method
261
     *                                         will not add an index notation to the key.
262
     * @throws InvalidArgumentException if $key is not a string.
263
     * @throws InvalidArgumentException if $index is not an int.
264
     * @return string                   the key with the index, or just the key if index is null.
265
     */
266 17
    protected function buildKey($key, $index)
267
    {
268 17
        if ($index === null) {
269 3
            return $key;
270
        }
271
272 14
        $nth = $index + 1;
273
274 14
        return $nth . $this->getOrdinal($nth) . ' ' . $key;
275
    }
276
277
    /**
278
     * Provides the ordinal notation for the specified nth number.
279
     *
280
     * @param  int    $nth the number to determine the ordinal for
281
     * @return string the ordinal
282
     */
283 38
    protected function getOrdinal($nth)
284
    {
285 38
        if ($nth < 0) {
286 1
            throw new InvalidArgumentException('$nth must be a positive number');
287
        }
288
289 37
        return $nth > 9 && $nth < 20 ? self::ORDINAL_TH : self::$ordinals[substr($nth, -1)];
290
    }
291
}
292