1: <?php
2: namespace Peridot\Leo;
3:
4: use Peridot\Leo\Matcher\MatcherInterface;
5: use Peridot\Leo\Responder\ResponderInterface;
6:
7: /**
8: * Assertion is responsible for asserting it's actual value
9: * against a MatcherInterface and responding to the results of a match.
10: *
11: * @method Assertion equal() equal(mixed $expected, string $message = "") Asserts that actual and expected values are strictly equal
12: * @method Assertion with() with(array $args) Stores an array of values to be used with callable assertions
13: * @method Assertion throw() throw(string $exceptionType, string $exceptionMessage = "", $message = "") Invokes the actual value as a function and matches the exception type was thrown, and optionally matches the exception message
14: * @method Assertion a() a(string $expected, string $message = "") Asserts that the actual value given has the same type as the expected type string
15: * @method Assertion an() an(string $expected, string $message = "") Asserts that the actual value given has the same type as the expected type string
16: * @method Assertion include() include(mixed $expected, string $message = "") Asserts that the actual string or array includes the expected value
17: * @method Assertion contain() contain(mixed $expected, string $message = "") Asserts that the actual string or array includes the expected value
18: * @method Assertion ok() ok(string $message = "") Asserts that the actual value is truthy - that is true when cast to (bool)
19: * @method Assertion true() true(string $message = "") Asserts that the actual value is strictly equal to true
20: * @method Assertion false() false(string $message = "") Asserts that the actual value is strictly equal to false
21: * @method Assertion null() null(string $message = "") Asserts that the actual value is null
22: * @method Assertion empty() empty(string $message = "") Asserts that the actual value is empty
23: * @method Assertion above() above(mixed $expected, string $message = "") Asserts that the actual value is greater than the expected value
24: * @method Assertion least() least(mixed $expected, string $message = "") Asserts that the actual value is greater than or equal to the expected value
25: * @method Assertion below() below(mixed $expected, string $message = "") Asserts that the actual value is less than the expected value
26: * @method Assertion most() most(mixed $expected, string $message = "") Asserts that the actual value is less than or equal to the expected value
27: * @method Assertion within() within(int $lowerBound, int $upperBound, string $message = "") Asserts that the actual value is between the upper and lower bounds (inclusive)
28: * @method Assertion instanceof() instanceof(string $expected, string $message = "") Asserts that the actual value is an instance of the expected class string
29: * @method Assertion property() property(string $name, mixed $value = "", string $message = "") Asserts that the actual array or object has the expected property and optionally asserts the property value against an expected value
30: * @method Assertion length() length(mixed $expected, string $message = "") Asserts the actual array, string, or Countable has the expected length
31: * @method Assertion match() match(string $pattern, string $message = "") Asserts that the actual value matches the expected regular expression
32: * @method Assertion string() string(string $expected, string $message = "") Asserts that the actual string contains the expected substring
33: * @method Assertion keys() keys(array $keys, string $message = "") Asserts the actual object or array has keys equivalent to the expected keys
34: * @method Assertion satisfy() satisfy(callable $predicate, string $message = "") Asserts that the actual value satisfies the expected predicate. The expected predicate will be passed the actual value and should return true or false
35: *
36: * @property-read Assertion $to a language chain
37: * @property-read Assertion $be a language chain
38: * @property-read Assertion $been a language chain
39: * @property-read Assertion $is a language chain
40: * @property-read Assertion $and a language chain
41: * @property-read Assertion $has a language chain
42: * @property-read Assertion $have a language chain
43: * @property-read Assertion $with a language chain
44: * @property-read Assertion $that a language chain
45: * @property-read Assertion $at a language chain
46: * @property-read Assertion $of a language chain
47: * @property-read Assertion $same a language chain
48: * @property-read Assertion $an a language chain
49: * @property-read Assertion $a a language chain
50: * @property-read Assertion $not flags the Assertion as negated
51: * @property-read Assertion $loosely enables loose equality assertion using the ->equal() assertion
52: * @property-read Assertion $contain enables the contain flag for use with the ->keys() assertion
53: * @property-read Assertion $include enables the contain flag for use with the ->keys() assertion
54: * @property-read Assertion $ok a lazy property that performs an ->ok() assertion
55: * @property-read Assertion $true a lazy property that performs a ->true() assertion
56: * @property-read Assertion $false a lazy property that performs a ->false() assertion
57: * @property-read Assertion $null a lazy property that performs a ->null() assertion
58: * @property-read Assertion $empty a lazy property that performs an ->empty() assertion
59: * @property-read Assertion $length enables the length flag for use with countable assertions such as ->above(), ->least(), ->below(), ->most(), and ->within()
60: * @property-read Assertion $deep enables the deep flag for use with assertions that need to traverse structures like the ->property() assertion
61: *
62: * @package Peridot\Leo
63: */
64: final class Assertion
65: {
66: use DynamicObjectTrait;
67:
68: /**
69: * A static cache for memoized properties.
70: *
71: * @var array
72: */
73: private static $propertyCache = [];
74:
75: /**
76: * @var ResponderInterface
77: */
78: protected $responder;
79:
80: /**
81: * @var mixed
82: */
83: protected $actual;
84:
85: /**
86: * @param ResponderInterface $responder
87: */
88: public function __construct(ResponderInterface $responder, $actual = null)
89: {
90: $this->responder = $responder;
91: $this->actual = $actual;
92: }
93:
94: /**
95: * Returns the current ResponderInterface assigned to this Assertion.
96: *
97: * @return ResponderInterface
98: */
99: public function getResponder()
100: {
101: return $this->responder;
102: }
103:
104: /**
105: * Delegate methods to assertion methods
106: *
107: * @param $method
108: * @param $args
109: * @return mixed
110: */
111: public function __call($method, $args)
112: {
113: if (!isset($this->methods[$method])) {
114: throw new \BadMethodCallException("Method $method does not exist");
115: }
116:
117: return $this->request($this->methods[$method], $args);
118: }
119:
120: /**
121: * @param $name
122: * @return mixed
123: */
124: public function __get($name)
125: {
126: if (!isset($this->properties[$name])) {
127: throw new \DomainException("Property $name not found");
128: }
129:
130: if (array_key_exists($name, self::$propertyCache)) {
131: return self::$propertyCache[$name];
132: }
133:
134: $property = $this->properties[$name];
135: $result = $this->request($property['factory']);
136:
137: if ($property['memoize']) {
138: self::$propertyCache[$name] = $result;
139: }
140:
141: return $result;
142: }
143:
144: /**
145: * A request to an Assertion will attempt to resolve
146: * the result as an assertion before returning the result.
147: *
148: * @param callable $fn
149: * @return mixed
150: */
151: public function request(callable $fn, array $arguments = [])
152: {
153: $result = call_user_func_array($fn, $arguments);
154:
155: if ($result instanceof MatcherInterface) {
156: return $this->assert($result);
157: }
158:
159: return $result;
160: }
161:
162: /**
163: * Extend calls the given callable - or file that returns a callable - and passes the current Assertion instance
164: * to it. Assertion can be extended via the ->addMethod(), ->flag(), and ->addProperty()
165: * methods.
166: *
167: * @param callable $fn
168: */
169: public function extend($fn)
170: {
171: if (is_string($fn) && file_exists($fn)) {
172: $fn = include $fn;
173: }
174:
175: if (is_callable($fn)) {
176: return call_user_func($fn, $this);
177: }
178:
179: throw new \InvalidArgumentException("Assertion::extend requires a callable or a file that returns one");
180: }
181:
182: /**
183: * Set the actual value used for matching expectations against.
184: *
185: * @param $actual
186: * @return $this
187: */
188: public function setActual($actual)
189: {
190: $this->actual = $actual;
191: return $this;
192: }
193:
194: /**
195: * Return the actual value being asserted against.
196: *
197: * @return mixed
198: */
199: public function getActual()
200: {
201: return $this->actual;
202: }
203:
204: /**
205: * Assert against the given matcher.
206: *
207: * @param $result
208: * @return $this
209: */
210: public function assert(MatcherInterface $matcher)
211: {
212: if ($this->flag('not')) {
213: $matcher->invert();
214: }
215:
216: $match = $matcher
217: ->setAssertion($this)
218: ->match($this->getActual());
219:
220: $message = $this->flag('message');
221:
222: $this->clearFlags();
223:
224: $this->responder->respond($match, $matcher->getTemplate(), $message);
225:
226: return $this;
227: }
228: }
229: