Completed
Pull Request — master (#23)
by Erin
03:28
created

PropertyMatcher::isDeep()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
namespace Peridot\Leo\Matcher;
3
4
use Peridot\Leo\Matcher\Template\ArrayTemplate;
5
use Peridot\Leo\Matcher\Template\TemplateInterface;
6
use Peridot\Leo\ObjectPath\ObjectPath;
7
use Peridot\Leo\ObjectPath\ObjectPathValue;
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
        return $this;
73
    }
74
75
    /**
76
     * Return the expected property value.
77
     *
78
     * @return mixed
79
     */
80
    public function getValue()
81
    {
82
        return $this->value;
83
    }
84
85
    /**
86
     * Set the expected property value.
87
     *
88
     * @param mixed $value
89
     * @return $this
90
     */
91
    public function setValue($value)
92
    {
93
        $this->value = $value;
94
        return $this;
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     *
100
     * @return TemplateInterface
101
     */
102
    public function getDefaultTemplate()
103
    {
104
        list($default, $negated) = $this->getTemplateStrings();
105
106
        $template = new ArrayTemplate([
107
            'default' => $default,
108
            'negated' => $negated
109
        ]);
110
111
        return $template->setTemplateVars([
112
            'key' => $this->getKey(),
113
            'value' => $this->getValue(),
114
            'actualValue' => $this->getActualValue()
115
        ]);
116
    }
117
118
    /**
119
     * Return the actual value given to the matcher.
120
     *
121
     * @return mixed
122
     */
123
    public function getActualValue()
124
    {
125
        return $this->actualValue;
126
    }
127
128
    /**
129
     * Set the actual value given to the matcher. Used to
130
     * store whether or not the actual value was set.
131
     *
132
     * @param mixed $actualValue
133
     * @return $this
134
     */
135
    public function setActualValue($actualValue)
136
    {
137
        $this->actualValue = $actualValue;
138
        $this->actualValueSet = true;
139
        return $this;
140
    }
141
142
    /**
143
     * Return if the actual value has been set.
144
     *
145
     * @return bool
146
     */
147
    public function isActualValueSet()
148
    {
149
        return $this->actualValueSet;
150
    }
151
152
    /**
153
     * Tell the property matcher to match deep properties.
154
     *
155
     * return $this
156
     */
157
    public function setIsDeep($isDeep)
158
    {
159
        $this->isDeep = $isDeep;
160
        return $this;
161
    }
162
163
    /**
164
     * Return whether or not the matcher is matching deep properties.
165
     *
166
     * @return bool
167
     */
168
    public function isDeep()
169
    {
170
        return $this->isDeep;
171
    }
172
173
    /**
174
     * Matches if the actual value has a property, optionally matching
175
     * the expected value of that property. If the deep flag is set,
176
     * the matcher will use the ObjectPath utility to parse deep expressions.
177
     *
178
     * @code
179
     *
180
     * $this->doMatch('child->name->first', 'brian');
181
     *
182
     * @endcode
183
     *
184
     * @param mixed $actual
185
     * @return mixed
186
     */
187
    protected function doMatch($actual)
188
    {
189
        $this->validateActual($actual);
190
191
        if ($this->isDeep()) {
192
            return $this->matchDeep($actual);
193
        }
194
195
        $actual = $this->actualToArray($actual);
196
197
        return $this->matchArrayIndex($actual);
198
    }
199
200
    /**
201
     * Convert the actual value to an array, whether it is an object or an array.
202
     *
203
     * @param object|array $actual
204
     * @return array|object
205
     */
206
    protected function actualToArray($actual)
207
    {
208
        if (is_object($actual)) {
209
            return get_object_vars($actual);
210
        }
211
        return $actual;
212
    }
213
214
    /**
215
     * Match that an array index exists, and matches
216
     * the expected value if set.
217
     *
218
     * @param $actual
219
     * @return bool
220
     */
221
    protected function matchArrayIndex($actual)
222
    {
223
        if (isset($actual[$this->getKey()])) {
224
            $this->assertion->setActual($actual[$this->getKey()]);
225
            return $this->isExpected($actual[$this->getKey()]);
226
        }
227
228
        return false;
229
    }
230
231
    /**
232
     * Uses ObjectPath to parse an expression if the deep flag
233
     * is set.
234
     *
235
     * @param $actual
236
     * @return bool
237
     */
238
    protected function matchDeep($actual)
239
    {
240
        $path = new ObjectPath($actual);
241
        $value = $path->get($this->getKey());
242
243
        if (is_null($value)) {
244
            return false;
245
        }
246
247
        $this->assertion->setActual($value->getPropertyValue());
248
249
        return $this->isExpected($value->getPropertyValue());
250
    }
251
252
    /**
253
     * Check if the given value is expected.
254
     *
255
     * @param $value
256
     * @return bool
257
     */
258
    protected function isExpected($value)
259
    {
260
        if ($expected = $this->getValue()) {
261
            $this->setActualValue($value);
262
            return $this->getActualValue() === $expected;
263
        }
264
265
        return true;
266
    }
267
268
    /**
269
     * Ensure that the actual value is an object or an array.
270
     *
271
     * @param $actual
272
     */
273
    protected function validateActual($actual)
274
    {
275
        if (!is_object($actual) && !is_array($actual)) {
276
            throw new \InvalidArgumentException("PropertyMatcher expects an object or an array");
277
        }
278
    }
279
280
    /**
281
     * Returns the strings used in creating the template for the matcher.
282
     *
283
     * @return array
284
     */
285
    protected function getTemplateStrings()
286
    {
287
        $default = "Expected {{actual}} to have a{{deep}}property {{key}}";
288
        $negated = "Expected {{actual}} to not have a{{deep}}property {{key}}";
289
        
290
        if ($this->getValue() && $this->isActualValueSet()) {
291
            $default = "Expected {{actual}} to have a{{deep}}property {{key}} of {{value}}, but got {{actualValue}}";
292
            $negated = "Expected {{actual}} to not have a{{deep}}property {{key}} of {{value}}";
293
        }
294
295
        $deep = ' ';
296
        if ($this->isDeep()) {
297
            $deep = ' deep ';
298
        }
299
300
        return str_replace('{{deep}}', $deep, [$default, $negated]);
301
    }
302
}
303