PropertyMatcher   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 295
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 7
Bugs 0 Features 1
Metric Value
c 7
b 0
f 1
dl 0
loc 295
rs 10
wmc 28
lcom 1
cbo 5

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A getKey() 0 4 1
A setKey() 0 6 1
A getValue() 0 4 1
A setValue() 0 6 1
A getDefaultTemplate() 0 15 1
A getActualValue() 0 4 1
A setActualValue() 0 7 1
A isActualValueSet() 0 4 1
A setIsDeep() 0 6 1
A isDeep() 0 4 1
A doMatch() 0 12 2
A actualToArray() 0 8 2
A matchArrayIndex() 0 10 2
A validateActual() 0 6 3
A getTemplateStrings() 0 17 4
A matchDeep() 0 13 2
A isExpected() 0 10 2
1
<?php
2
3
namespace Peridot\Leo\Matcher;
4
5
use Peridot\Leo\Matcher\Template\ArrayTemplate;
6
use Peridot\Leo\Matcher\Template\TemplateInterface;
7
use Peridot\Leo\ObjectPath\ObjectPath;
8
9
/**
10
 * PropertyMatcher determines if the actual array or object has the expected property, and optionally matches
11
 * an expected value for that property.
12
 *
13
 * @package Peridot\Leo\Matcher
14
 */
15
class PropertyMatcher extends AbstractMatcher
16
{
17
    /**
18
     * @var string|int
19
     */
20
    protected $key;
21
22
    /**
23
     * @var mixed
24
     */
25
    protected $value;
26
27
    /**
28
     * @var mixed
29
     */
30
    protected $actualValue;
31
32
    /**
33
     * @var bool
34
     */
35
    protected $actualValueSet = false;
36
37
    /**
38
     * @var bool
39
     */
40
    protected $isDeep = false;
41
42
    /**
43
     * @param mixed  $key
44
     * @param string $value
45
     */
46
    public function __construct($key, $value = '')
47
    {
48
        $this
49
            ->setKey($key)
50
            ->setValue($value);
51
    }
52
53
    /**
54
     * Return the expected object or array key.
55
     *
56
     * @return int|string
57
     */
58
    public function getKey()
59
    {
60
        return $this->key;
61
    }
62
63
    /**
64
     * Set the expected object or array key.
65
     *
66
     * @param  int|string $key
67
     * @return $this
68
     */
69
    public function setKey($key)
70
    {
71
        $this->key = $key;
72
73
        return $this;
74
    }
75
76
    /**
77
     * Return the expected property value.
78
     *
79
     * @return mixed
80
     */
81
    public function getValue()
82
    {
83
        return $this->value;
84
    }
85
86
    /**
87
     * Set the expected property value.
88
     *
89
     * @param  mixed $value
90
     * @return $this
91
     */
92
    public function setValue($value)
93
    {
94
        $this->value = $value;
95
96
        return $this;
97
    }
98
99
    /**
100
     * {@inheritdoc}
101
     *
102
     * @return TemplateInterface
103
     */
104
    public function getDefaultTemplate()
105
    {
106
        list($default, $negated) = $this->getTemplateStrings();
107
108
        $template = new ArrayTemplate([
109
            'default' => $default,
110
            'negated' => $negated,
111
        ]);
112
113
        return $template->setTemplateVars([
114
            'key' => $this->getKey(),
115
            'value' => $this->getValue(),
116
            'actualValue' => $this->getActualValue(),
117
        ]);
118
    }
119
120
    /**
121
     * Return the actual value given to the matcher.
122
     *
123
     * @return mixed
124
     */
125
    public function getActualValue()
126
    {
127
        return $this->actualValue;
128
    }
129
130
    /**
131
     * Set the actual value given to the matcher. Used to
132
     * store whether or not the actual value was set.
133
     *
134
     * @param  mixed $actualValue
135
     * @return $this
136
     */
137
    public function setActualValue($actualValue)
138
    {
139
        $this->actualValue = $actualValue;
140
        $this->actualValueSet = true;
141
142
        return $this;
143
    }
144
145
    /**
146
     * Return if the actual value has been set.
147
     *
148
     * @return bool
149
     */
150
    public function isActualValueSet()
151
    {
152
        return $this->actualValueSet;
153
    }
154
155
    /**
156
     * Tell the property matcher to match deep properties.
157
     *
158
     * return $this
159
     */
160
    public function setIsDeep($isDeep)
161
    {
162
        $this->isDeep = $isDeep;
163
164
        return $this;
165
    }
166
167
    /**
168
     * Return whether or not the matcher is matching deep properties.
169
     *
170
     * @return bool
171
     */
172
    public function isDeep()
173
    {
174
        return $this->isDeep;
175
    }
176
177
    /**
178
     * Matches if the actual value has a property, optionally matching
179
     * the expected value of that property. If the deep flag is set,
180
     * the matcher will use the ObjectPath utility to parse deep expressions.
181
     *
182
     * @code
183
     *
184
     * $this->doMatch('child->name->first', 'brian');
185
     *
186
     * @endcode
187
     *
188
     * @param  mixed $actual
189
     * @return mixed
190
     */
191
    protected function doMatch($actual)
192
    {
193
        $this->validateActual($actual);
194
195
        if ($this->isDeep()) {
196
            return $this->matchDeep($actual);
197
        }
198
199
        $actual = $this->actualToArray($actual);
200
201
        return $this->matchArrayIndex($actual);
202
    }
203
204
    /**
205
     * Convert the actual value to an array, whether it is an object or an array.
206
     *
207
     * @param  object|array $actual
208
     * @return array|object
209
     */
210
    protected function actualToArray($actual)
211
    {
212
        if (is_object($actual)) {
213
            return get_object_vars($actual);
214
        }
215
216
        return $actual;
217
    }
218
219
    /**
220
     * Match that an array index exists, and matches
221
     * the expected value if set.
222
     *
223
     * @param $actual
224
     * @return bool
225
     */
226
    protected function matchArrayIndex($actual)
227
    {
228
        if (isset($actual[$this->getKey()])) {
229
            $this->assertion->setActual($actual[$this->getKey()]);
230
231
            return $this->isExpected($actual[$this->getKey()]);
232
        }
233
234
        return false;
235
    }
236
237
    /**
238
     * Uses ObjectPath to parse an expression if the deep flag
239
     * is set.
240
     *
241
     * @param $actual
242
     * @return bool
243
     */
244
    protected function matchDeep($actual)
245
    {
246
        $path = new ObjectPath($actual);
247
        $value = $path->get($this->getKey());
248
249
        if ($value === null) {
250
            return false;
251
        }
252
253
        $this->assertion->setActual($value->getPropertyValue());
254
255
        return $this->isExpected($value->getPropertyValue());
256
    }
257
258
    /**
259
     * Check if the given value is expected.
260
     *
261
     * @param $value
262
     * @return bool
263
     */
264
    protected function isExpected($value)
265
    {
266
        if ($expected = $this->getValue()) {
267
            $this->setActualValue($value);
268
269
            return $this->getActualValue() === $expected;
270
        }
271
272
        return true;
273
    }
274
275
    /**
276
     * Ensure that the actual value is an object or an array.
277
     *
278
     * @param $actual
279
     */
280
    protected function validateActual($actual)
281
    {
282
        if (!is_object($actual) && !is_array($actual)) {
283
            throw new \InvalidArgumentException('PropertyMatcher expects an object or an array');
284
        }
285
    }
286
287
    /**
288
     * Returns the strings used in creating the template for the matcher.
289
     *
290
     * @return array
291
     */
292
    protected function getTemplateStrings()
293
    {
294
        $default = 'Expected {{actual}} to have a{{deep}}property {{key}}';
295
        $negated = 'Expected {{actual}} to not have a{{deep}}property {{key}}';
296
297
        if ($this->getValue() && $this->isActualValueSet()) {
298
            $default = 'Expected {{actual}} to have a{{deep}}property {{key}} of {{value}}, but got {{actualValue}}';
299
            $negated = 'Expected {{actual}} to not have a{{deep}}property {{key}} of {{value}}';
300
        }
301
302
        $deep = ' ';
303
        if ($this->isDeep()) {
304
            $deep = ' deep ';
305
        }
306
307
        return str_replace('{{deep}}', $deep, [$default, $negated]);
308
    }
309
}
310