1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Peridot\Leo\Matcher; |
4
|
|
|
|
5
|
|
|
use Exception; |
6
|
|
|
use Peridot\Leo\Matcher\Template\ArrayTemplate; |
7
|
|
|
use Peridot\Leo\Matcher\Template\TemplateInterface; |
8
|
|
|
use Throwable; |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* ExceptionMatcher executes a callable and determines if an exception of a given type was thrown. It optionally |
12
|
|
|
* matches the exception message. |
13
|
|
|
* |
14
|
|
|
* @package Peridot\Leo\Matcher |
15
|
|
|
*/ |
16
|
|
|
class ExceptionMatcher implements MatcherInterface |
17
|
|
|
{ |
18
|
|
|
use MatcherTrait; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* @param string $exceptionType |
22
|
|
|
*/ |
23
|
|
|
public function __construct($exceptionType) |
24
|
|
|
{ |
25
|
|
|
$this->expected = $exceptionType; |
|
|
|
|
26
|
|
|
} |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Set arguments to be passed to the callable. |
30
|
|
|
* |
31
|
|
|
* @param array $arguments |
32
|
|
|
* @return $this |
33
|
|
|
*/ |
34
|
|
|
public function setArguments(array $arguments) |
35
|
|
|
{ |
36
|
|
|
$this->arguments = $arguments; |
37
|
|
|
|
38
|
|
|
return $this; |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Set the expected message of the exception. |
43
|
|
|
* |
44
|
|
|
* @param string $message |
45
|
|
|
* @return $this |
46
|
|
|
*/ |
47
|
|
|
public function setExpectedMessage($message) |
48
|
|
|
{ |
49
|
|
|
$this->expectedMessage = $message; |
50
|
|
|
|
51
|
|
|
return $this; |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Set the message thrown from an exception resulting from the |
56
|
|
|
* callable being invoked. |
57
|
|
|
* |
58
|
|
|
* @param string $message |
59
|
|
|
*/ |
60
|
|
|
public function setMessage($message) |
61
|
|
|
{ |
62
|
|
|
$this->message = $message; |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Returns the arguments passed to the callable. |
67
|
|
|
* |
68
|
|
|
* @return array |
69
|
|
|
*/ |
70
|
|
|
public function getArguments() |
71
|
|
|
{ |
72
|
|
|
return $this->arguments; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Return the expected exception message. |
77
|
|
|
* |
78
|
|
|
* @return string |
79
|
|
|
*/ |
80
|
|
|
public function getExpectedMessage() |
81
|
|
|
{ |
82
|
|
|
return $this->expectedMessage; |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* Return the message thrown by an exception resulting from the callable |
87
|
|
|
* being invoked. |
88
|
|
|
* |
89
|
|
|
* @return string |
90
|
|
|
*/ |
91
|
|
|
public function getMessage() |
92
|
|
|
{ |
93
|
|
|
return $this->message; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* {@inheritdoc} |
98
|
|
|
* |
99
|
|
|
* If the expected message has been set, the message template will be used. |
100
|
|
|
* |
101
|
|
|
* @return TemplateInterface |
102
|
|
|
*/ |
103
|
|
|
public function getTemplate() |
104
|
|
|
{ |
105
|
|
|
if ($this->expectedMessage) { |
106
|
|
|
return $this->getMessageTemplate(); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
if (!isset($this->template)) { |
110
|
|
|
return $this->getDefaultTemplate(); |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
return $this->template; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Set the template to be used when an expected exception message is provided. |
118
|
|
|
* |
119
|
|
|
* @param TemplateInterface $template |
120
|
|
|
* @return $this |
121
|
|
|
*/ |
122
|
|
|
public function setMessageTemplate(TemplateInterface $template) |
123
|
|
|
{ |
124
|
|
|
$this->messageTemplate = $template; |
125
|
|
|
|
126
|
|
|
return $this; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Return a template for rendering exception message templates. |
131
|
|
|
* |
132
|
|
|
* return TemplateInterface |
133
|
|
|
*/ |
134
|
|
|
public function getMessageTemplate() |
135
|
|
|
{ |
136
|
|
|
if ($this->messageTemplate) { |
137
|
|
|
return $this->messageTemplate; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
return $this->getDefaultMessageTemplate(); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* {@inheritdoc} |
145
|
|
|
* |
146
|
|
|
* @return TemplateInterface |
147
|
|
|
*/ |
148
|
|
|
public function getDefaultTemplate() |
149
|
|
|
{ |
150
|
|
|
$template = new ArrayTemplate([ |
151
|
|
|
'default' => 'Expected exception of type {{expected}}', |
152
|
|
|
'negated' => 'Expected type of exception not to be {{expected}}', |
153
|
|
|
]); |
154
|
|
|
|
155
|
|
|
return $template; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* Return a default template for exception message assertions. |
160
|
|
|
* |
161
|
|
|
* @return ArrayTemplate |
162
|
|
|
*/ |
163
|
|
|
public function getDefaultMessageTemplate() |
164
|
|
|
{ |
165
|
|
|
return new ArrayTemplate([ |
166
|
|
|
'default' => 'Expected exception message {{expected}}, got {{actual}}', |
167
|
|
|
'negated' => 'Expected exception message {{actual}} not to equal {{expected}}', |
168
|
|
|
]); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* Executes the callable and matches the exception type and exception message. |
173
|
|
|
* |
174
|
|
|
* @param $actual |
175
|
|
|
* @return Match |
176
|
|
|
*/ |
177
|
|
|
public function match($actual) |
178
|
|
|
{ |
179
|
|
|
$this->validateCallable($actual); |
180
|
|
|
|
181
|
|
|
list($exception, $message) = $this->callableException($actual); |
182
|
|
|
$this->setMessage($message); |
183
|
|
|
|
184
|
|
|
return $this->matchMessage($actual, $exception, $message); |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* Validate that expected is indeed a valid callable. |
189
|
|
|
* |
190
|
|
|
* @throws \BadFunctionCallException |
191
|
|
|
*/ |
192
|
|
|
protected function validateCallable($callable) |
193
|
|
|
{ |
194
|
|
|
if (!is_callable($callable)) { |
195
|
|
|
$callable = rtrim(print_r($callable, true)); |
196
|
|
|
throw new \BadFunctionCallException('Invalid callable ' . $callable . ' given'); |
197
|
|
|
} |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
private function callableException($callable) |
201
|
|
|
{ |
202
|
|
|
$exception = null; |
203
|
|
|
$message = null; |
204
|
|
|
|
205
|
|
|
try { |
206
|
|
|
call_user_func_array($callable, $this->arguments); |
207
|
|
|
} catch (Exception $exception) { |
208
|
|
|
$message = $exception->getMessage(); |
209
|
|
|
// fall-through ... |
210
|
|
|
} catch (Throwable $exception) { |
211
|
|
|
$message = $exception->getMessage(); |
212
|
|
|
// fall-through ... |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
return array($exception, $message); |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
private function matchMessage($actual, $exception, $message) |
219
|
|
|
{ |
220
|
|
|
if (!$this->expectedMessage || $message === $this->expectedMessage) { |
221
|
|
|
return $this->matchType($actual, $exception); |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
$isNegated = $this->isNegated(); |
225
|
|
|
|
226
|
|
|
return new Match($isNegated, $this->expectedMessage, $message, $isNegated); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
private function matchType($actual, $exception) |
230
|
|
|
{ |
231
|
|
|
$isMatch = $exception instanceof $this->expected; |
|
|
|
|
232
|
|
|
$isNegated = $this->isNegated(); |
233
|
|
|
|
234
|
|
|
return new Match($isMatch xor $isNegated, $this->expected, $actual, $isNegated); |
|
|
|
|
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
/** |
238
|
|
|
* @var array |
239
|
|
|
*/ |
240
|
|
|
protected $arguments = []; |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* @var string |
244
|
|
|
*/ |
245
|
|
|
protected $expectedMessage = ''; |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* A captured exception message. |
249
|
|
|
* |
250
|
|
|
* @var string |
251
|
|
|
*/ |
252
|
|
|
protected $message; |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* @var TemplateInterface |
256
|
|
|
*/ |
257
|
|
|
protected $messageTemplate; |
258
|
|
|
} |
259
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.