1: <?php
2:
3: namespace Alchemy\util\promise;
4:
5:
6: /**
7: * Abstraction for objects which can be checked (non-blocking) or waited upon
8: * to resolve (blocking, with optional timeout).
9: */
10: class Waitable {
11: private $resultType = null;
12: protected $result = null;
13:
14: protected $started = null;
15: protected $timeout = null;
16: protected $spin = 10;
17:
18:
19: public function __construct($type = null) {
20: $this->resultType = $type;
21: }
22:
23:
24: /**
25: * Immediately return the value of the Waitable, resolved or not
26: *
27: * @return mixed current value
28: */
29: public function __invoke() {
30: $this->check();
31: return $this->result;
32: }
33:
34:
35: /**
36: * Returns whether or not the Waitable is resolved.
37: *
38: * @return boolean resolved or not
39: */
40: public function check() {
41: $this->precheck();
42:
43: return $this->result !== null;
44: }
45:
46:
47: /**
48: * Subclasses override this to do something during check()
49: */
50: protected function precheck() {}
51:
52:
53: /**
54: * Resolve the Waitable to a value, Exception, or NULL to leave it unresolved.
55: *
56: * @param mixed $result
57: * @return this
58: */
59: public function resolve($result) {
60: if ($result === null
61: || $result instanceof \Exception
62: || $result instanceof Waitable) {
63: $this->result = $result;
64: } elseif ($this->resultType && !($result instanceof $this->resultType)) {
65: $type = is_object($result) ? get_class($result) : gettype($result);
66: $this->result = new TypeException("Expected {$this->resultType}, got {$type}");
67: } else {
68: $this->result = $result;
69: }
70:
71: return $this;
72: }
73:
74:
75: /**
76: * Sets the timeout for this Waitable.
77: * $obj->timeout() will cause the Waitable to not wait at all.
78: *
79: * @param integer $ms in milliseconds. 0 = immediately, null = never
80: * @param integer $spin delay between spinlock checks
81: * @param boolean $reset whether or not to reset the timer
82: */
83: public function timeout($ms = 0, $spin = 10, $reset = false) {
84: if ($reset || $this->started === null) {
85: $this->started = microtime(true);
86: }
87:
88: $this->timeout = ($ms === null) ? null : $this->started + $ms;
89: $this->spin = $spin;
90:
91: return $this;
92: }
93:
94:
95: /**
96: * Get the expected result type, if any, of this Waitable.
97: *
98: * @return string|null
99: */
100: public function type() {
101: return $this->resultType;
102: }
103:
104:
105: /**
106: * Blocks until either the Waitable resolves or times out.
107: *
108: * @return $result resolved value or an Exception
109: */
110: public function wait() {
111: while(!$this->check() && ($this->timeout && (microtime(true) < $this->timeout))) {
112: usleep($this->spin * 1000);
113: }
114:
115: if ($this->result === null) {
116: $this->result = new TimeoutException();
117: }
118:
119: return $this->result;
120: }
121:
122:
123: /**
124: * Same as wait(), except it throws Exceptions instead of returning them.
125: *
126: * @return $result resolved value
127: */
128: public function expect() {
129: if ($this->result === null) {
130: $this->wait();
131: }
132:
133: if ($this->result instanceof \Exception) {
134: throw $this->result;
135: }
136:
137: return $this->result;
138: }
139: }
140:
141: class SerializableException extends \Exception {
142: public function __sleep() {
143: return array('message', 'code', 'file', 'line');
144: }
145: }
146:
147: class TypeException extends SerializableException {}
148: class TimeoutException extends SerializableException {}