Passed
Pull Request — master (#9)
by Donald
01:36
created

Store::getOrdinal()   C

Complexity

Conditions 8
Paths 6

Size

Total Lines 26
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 8

Importance

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