Passed
Pull Request — master (#6)
by Donald
01:32
created

Store::assertKeyValueContains()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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