Passed
Pull Request — master (#52)
by Baptiste
05:42
created

AbstractContext   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 302
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 52
eloc 80
dl 0
loc 302
rs 7.44
c 0
b 0
f 0

28 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A should_be_an_array() 0 3 1
A the_json_path_should_be_greater_than() 0 3 1
A the_json_each_elements_in_collection_should_contain() 0 10 4
A the_json_path_should_be_a_valid_date() 0 6 2
A the_json_path_should_be_less_than() 0 3 1
A the_json_path_elements_in_collection_should_have_a_property() 0 6 3
A the_json_path_should_be_equal_to() 0 6 2
A the_json_each_elements_in_collection_should_be_equal_to() 0 10 4
A the_json_collection_should_have_at_least_elements() 0 6 1
A the_json_path_should_have_at_most_elements() 0 6 1
A the_json_path_should_be_null() 0 6 2
A the_json_path_should_be_less_or_equal_than() 0 3 1
A the_json_path_should_be_a_valid_json_encoded_string() 0 6 1
A the_json_each_elements_in_collection_should_be_bool() 0 10 4
A the_json_each_elements_in_collection_should_be_equal_to_int() 0 10 4
A the_json_path_should_be_empty() 0 6 2
A the_json_path_should_be() 0 6 2
A path_should_not_be_readable() 0 3 1
A the_json_path_collection_contains() 0 12 3
A the_json_path_should_not_match() 0 8 2
A the_json_path_contains() 0 6 2
A the_json_path_should_be_greater_or_equal_than() 0 3 1
A getValue() 0 5 2
A the_json_path_should_be_py_string() 0 3 1
A the_json_path_should_match() 0 3 1
A the_json_path_should_have_elements() 0 6 1
A path_should_be_readable() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like AbstractContext often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractContext, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
namespace Behapi\Json;
3
4
use stdClass;
5
use DateTime;
6
7
use Throwable;
8
use InvalidArgumentException;
9
10
use Behat\Gherkin\Node\PyStringNode;
11
use Behat\Behat\Context\Context as BehatContext;
12
13
use Symfony\Component\PropertyAccess\PropertyAccess;
14
use Symfony\Component\PropertyAccess\PropertyAccessor;
15
16
use Webmozart\Assert\Assert;
17
18
use function count;
19
use function preg_match;
20
use function json_last_error;
21
22
use const JSON_ERROR_NONE;
23
use const PREG_OFFSET_CAPTURE;
24
25
abstract class AbstractContext implements BehatContext
26
{
27
    /** @var PropertyAccessor */
28
    private $accessor;
29
30
    public function __construct()
31
    {
32
        $this->accessor = PropertyAccess::createPropertyAccessor();
33
    }
34
35
    abstract protected function getJson();
36
37
    protected function getValue(?string $path)
38
    {
39
        $json = $this->getJson();
40
41
        return $path === null ? $json : $this->accessor->getValue($json, $path);
42
    }
43
44
    /** @Then :path should be accessible in the latest json response */
45
    final public function path_should_be_readable(string $path): void
46
    {
47
        Assert::true($this->accessor->isReadable($this->getValue(null), $path), "The path $path should be a valid path");
48
    }
49
50
    /** @Then :path should not exist in the latest json response */
51
    final public function path_should_not_be_readable(string $path): void
52
    {
53
        Assert::false($this->accessor->isReadable($this->getValue(null), $path), "The path $path should not be a valid path");
54
    }
55
56
    /**
57
     * @Then in the json, :path should be equal to :expected
58
     * @Then in the json, :path should :not be equal to :expected
59
     */
60
    final public function the_json_path_should_be_equal_to(string $path, ?string $not = null, $expected): void
61
    {
62
        $assert = [Assert::class, $not !== null ? 'notEq' : 'eq'];
63
        assert(is_callable($assert));
64
65
        $assert($this->getValue($path), $expected);
66
    }
67
68
    /** @Then in the json, :path should be: */
69
    final public function the_json_path_should_be_py_string(string $path, PyStringNode $expected): void
70
    {
71
        Assert::same($this->getValue($path), $expected->getRaw());
72
    }
73
74
    /**
75
     * @Then /^in the json, "(?P<path>(?:[^"]|\\")*)" should be (?P<expected>true|false)$/
76
     * @Then /^in the json, "(?P<path>(?:[^"]|\\")*)" should :not be (?P<expected>true|false)$/
77
     */
78
    final public function the_json_path_should_be(string $path, ?string $not = null, string $expected): void
79
    {
80
        $assert = [Assert::class, $not !== null ? 'notSame' : 'same'];
81
        assert(is_callable($assert));
82
83
        $assert($this->getValue($path), 'true' === $expected);
84
    }
85
86
    /**
87
     * @Then in the json, :path should be null
88
     * @Then in the json, :path should :not be null
89
     */
90
    final public function the_json_path_should_be_null(string $path, ?string $not = null): void
91
    {
92
        $assert = [Assert::class, $not !== null ? 'notNull' : 'null'];
93
        assert(is_callable($assert));
94
95
        $assert($this->getValue($path));
96
    }
97
98
    /**
99
     * @Then in the json, :path should be empty
100
     * @Then in the json, :path should :not be empty
101
     */
102
    final public function the_json_path_should_be_empty(string $path, ?string $not = null): void
103
    {
104
        $assert = [Assert::class, $not !== null ? 'notEmpty' : 'isEmpty'];
105
        assert(is_callable($assert));
106
107
        $assert($this->getValue($path));
108
    }
109
110
    /**
111
     * @Then in the json, :path should contain :expected
112
     * @Then in the json, :path should :not contain :expected
113
     */
114
    final public function the_json_path_contains(string $path, ?string $not = null, $expected): void
115
    {
116
        $assert = [Assert::class, $not !== null ? 'notContains' : 'contains'];
117
        assert(is_callable($assert));
118
119
        $assert($this->getValue($path), $expected);
120
    }
121
122
    /** @Then in the json, :path collection should contain an element with :value equal to :expected */
123
    final public function the_json_path_collection_contains(string $path, string $value, $expected): void
124
    {
125
        $collection = $this->getValue($path);
126
        Assert::isIterable($collection);
127
128
        foreach ($collection as $element) {
129
            if ($expected === $this->accessor->getValue($element, $value)) {
130
                return;
131
            }
132
        }
133
134
        throw new InvalidArgumentException("$path collection does not contain an element with $value equal to $expected");
135
    }
136
137
    /** @Then in the json, :path should be a valid date(time) */
138
    final public function the_json_path_should_be_a_valid_date(string $path): void
139
    {
140
        try {
141
            new DateTime($this->getValue($path));
142
        } catch (Throwable $t) {
143
            throw new InvalidArgumentException("$path does not contain a valid date");
144
        }
145
    }
146
147
    /** @Then in the json, :path should be greater than :expected */
148
    final public function the_json_path_should_be_greater_than(string $path, int $expected): void
149
    {
150
        Assert::greaterThan($this->getValue($path), $expected);
151
    }
152
153
    /** @Then in the json, :path should be greater than or equal to :expected */
154
    final public function the_json_path_should_be_greater_or_equal_than(string $path, int $expected): void
155
    {
156
        Assert::greaterThanEq($this->getValue($path), $expected);
157
    }
158
159
    /** @Then in the json, :path should be less than :expected */
160
    final public function the_json_path_should_be_less_than(string $path, int $expected): void
161
    {
162
        Assert::lessThan($this->getValue($path), $expected);
163
    }
164
165
    /** @Then in the json, :path should be less than or equal to :expected */
166
    final public function the_json_path_should_be_less_or_equal_than(string $path, int $expected): void
167
    {
168
        Assert::lessThanEq($this->getValue($path), $expected);
169
    }
170
171
    /**
172
     * @Then in the json, the root should be an array
173
     * @Then in the json, :path should be an array
174
     */
175
    final public function should_be_an_array(?string $path = null): void
176
    {
177
        Assert::isArray($this->getValue($path));
178
    }
179
180
    /**
181
     * @Then in the json, the root collection should have at least :count element(s)
182
     * @Then in the json, :path collection should have at least :count element(s)
183
     */
184
    final public function the_json_collection_should_have_at_least_elements(?string $path = null, int $count): void
185
    {
186
        $value = $this->getValue($path);
187
188
        Assert::isCountable($value);
189
        Assert::minCount($value, $count);
190
    }
191
192
    /**
193
     * @Then in the json, the root collection should have :count element(s)
194
     * @Then in the json, :path collection should have :count element(s)
195
     */
196
    final public function the_json_path_should_have_elements(?string $path = null, int $count): void
197
    {
198
        $value = $this->getValue($path);
199
200
        Assert::isCountable($value);
201
        Assert::count($value, $count);
202
    }
203
204
    /**
205
     * @Then in the json, the root collection should have at most :count element(s)
206
     * @Then in the json, :path collection should have at most :count element(s)
207
     */
208
    final public function the_json_path_should_have_at_most_elements(?string $path = null, int $count): void
209
    {
210
        $value = $this->getValue($path);
211
212
        Assert::isCountable($value);
213
        Assert::maxCount($value, $count);
214
    }
215
216
    /** @Then in the json, :path should match :pattern */
217
    final public function the_json_path_should_match(string $path, string $pattern): void
218
    {
219
        Assert::regex($this->getValue($path), $pattern);
220
    }
221
222
    /**
223
     * @Then in the json, :path should not match :pattern
224
     *
225
     * -----
226
     *
227
     * Note :: The body of this assertion should be replaced by a
228
     * `Assert::notRegex` as soon as the Assert's PR
229
     * https://github.com/webmozart/assert/pull/58 is merged and released.
230
     */
231
    final public function the_json_path_should_not_match(string $path, string $pattern): void
232
    {
233
        if (!preg_match($pattern, $this->getValue($path), $matches, PREG_OFFSET_CAPTURE)) {
234
            // it's all good, it is supposed not to match. :}
235
            return;
236
        }
237
238
        throw new InvalidArgumentException("The value matches {$pattern} at offset {$matches[0][1]}");
239
    }
240
241
    /** @Then in the json, :path should be a valid json encoded string */
242
    final public function the_json_path_should_be_a_valid_json_encoded_string(string $path): void
243
    {
244
        $value = json_decode($this->getValue($path));
245
246
        Assert::notNull($value);
247
        Assert::same(json_last_error(), JSON_ERROR_NONE);
248
    }
249
250
    /**
251
     * @Then /^in the json, all the elements in the (?:root|\"(?P<path>(?:[^"]|\\")*)\") collection should have a(?:n?) "(?P<property>(?:[^"]|\\")*)" property$/
252
     * @Then /^in the json, all the elements in the (?:root|\"(?P<path>(?:[^"]|\\")*)\") collection should (?P<not>not) have a(?:n?) "(?P<property>(?:[^"]|\\")*)" property$/
253
     **/
254
    final public function the_json_path_elements_in_collection_should_have_a_property(string $path, ?string $not = null, string $property): void
255
    {
256
        $assert = [Assert::class, $not !== null ? 'allPropertyNotExists' : 'allPropertyExists'];
257
        assert(is_callable($assert));
258
259
        $assert($this->getValue(empty($path) ? null : $path), $property);
260
    }
261
262
    /**
263
     * @Then /^in the json, each "(?P<property>(?:[^"]|\\")*)" property in the (?:root|\"(?P<path>(?:[^"]|\\")*)\") collection should be equal to "(?P<expected>(?:[^"]|\\")*)"$/
264
     * @Then /^in the json, each "(?P<property>(?:[^"]|\\")*)" property in the (?:root|\"(?P<path>(?:[^"]|\\")*)\") collection should (?P<not>not) be equal to "(?P<expected>(?:[^"]|\\")*)"$/
265
     */
266
    final public function the_json_each_elements_in_collection_should_be_equal_to(string $path, string $property, ?string $not = null, string $expected): void
267
    {
268
        $values = $this->getValue(empty($path) ? null : $path);
269
        Assert::isIterable($values);
270
271
        $assert = [Assert::class, $not !== null ? 'notSame' : 'same'];
272
        assert(is_callable($assert));
273
274
        foreach ($values as $element) {
275
            $assert($this->accessor->getValue($element, $property), $expected);
276
        }
277
    }
278
279
    /**
280
     * @Then /^in the json, each "(?P<property>(?:[^"]|\\")*)" property in the (?:root|\"(?P<path>(?:[^"]|\\")*)\") collection should be (?P<expected>true|false)$/
281
     * @Then /^in the json, each "(?P<property>(?:[^"]|\\")*)" property in the (?:root|\"(?P<path>(?:[^"]|\\")*)\") collection should (?P<not>not) be (?P<expected>true|false)$/
282
     */
283
    final public function the_json_each_elements_in_collection_should_be_bool(string $path, string $property, ?string $not = null, string $expected): void
284
    {
285
        $values = $this->getValue(empty($path) ? null : $path);
286
        Assert::isIterable($values);
287
288
        $assert = [Assert::class, $not !== null ? 'notSame' : 'same'];
289
        assert(is_callable($assert));
290
291
        foreach ($values as $element) {
292
            $assert($this->accessor->getValue($element, $property), $expected === 'true');
293
        }
294
    }
295
296
    /**
297
     * @Then /^in the json, each "(?P<property>(?:[^"]|\\")*)" property in the (?:root|\"(?P<path>(?:[^"]|\\")*)\") collection should be equal to (?P<expected>[0-9]+)$/
298
     * @Then /^in the json, each "(?P<property>(?:[^"]|\\")*)" property in the (?:root|\"(?P<path>(?:[^"]|\\")*)\") collection should (?P<not>not) be equal to (?P<expected>[0-9]+)$/
299
     */
300
    final public function the_json_each_elements_in_collection_should_be_equal_to_int(string $path, string $property, ?string $not = null, int $expected): void
301
    {
302
        $values = $this->getValue(empty($path) ? null : $path);
303
        Assert::isIterable($values);
304
305
        $assert = [Assert::class, $not !== null ? 'notSame' : 'same'];
306
        assert(is_callable($assert));
307
308
        foreach ($values as $element) {
309
            $assert($this->accessor->getValue($element, $property), $expected);
310
        }
311
    }
312
313
    /**
314
     * @Then /^in the json, each "(?P<property>(?:[^"]|\\")*)" property in the (?:root|\"(?P<path>(?:[^"]|\\")*)\") collection should contain "(?P<expected>(?:[^"]|\\")*)"$/
315
     * @Then /^in the json, each "(?P<property>(?:[^"]|\\")*)" property in the (?:root|\"(?P<path>(?:[^"]|\\")*)\") collection should (?P<not>not) contain "(?P<expected>(?:[^"]|\\")*)"$/
316
     **/
317
    final public function the_json_each_elements_in_collection_should_contain(string $path, string $property, ?string $not = null, string $expected): void
318
    {
319
        $values = $this->getValue(empty($path) ? null : $path);
320
        Assert::isIterable($values);
321
322
        $assert = [Assert::class, $not !== null ? 'notSame' : 'same'];
323
        assert(is_callable($assert));
324
325
        foreach ($values as $element) {
326
            $assert($this->accessor->getValue($element, $property), $expected);
327
        }
328
    }
329
}
330