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