PageRenderTime 110ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Carica/Io/Deferred.php

https://bitbucket.org/tobmaster/carica-io
PHP | 369 lines | 189 code | 24 blank | 156 comment | 35 complexity | e6bd05b0a584e14d571d354ce90073b4 MD5 | raw file
  1. <?php
  2. namespace Carica\Io {
  3. /**
  4. * A deferred object implementation, allows to schedule callbacks for
  5. * execution after a condition is meet or not.
  6. *
  7. */
  8. class Deferred {
  9. /**
  10. * Default state - not yet finalized
  11. * @var string
  12. */
  13. const STATE_PENDING = 'pending';
  14. /**
  15. * Final state, object was resolved, the action was successful
  16. * @var string
  17. */
  18. const STATE_RESOLVED = 'resolved';
  19. /**
  20. * Final state, object was rejected, the action failed
  21. *
  22. * @var string
  23. */
  24. const STATE_REJECTED = 'rejected';
  25. /**
  26. * current state
  27. * .
  28. * @var string
  29. */
  30. private $_state = self::STATE_PENDING;
  31. /**
  32. * An promise for this object
  33. * .
  34. * @var \Carica\Io\Deferred\Promise
  35. */
  36. private $_promise = NULL;
  37. /**
  38. * Callbacks if the object is resolved
  39. * .
  40. * @var \Carica\Io\Callbacks
  41. */
  42. private $_done = NULL;
  43. /**
  44. * Callbacks if the object is rejected
  45. * .
  46. * @var \Carica\Io\Callbacks
  47. */
  48. private $_failed = NULL;
  49. /**
  50. * Callbacks if the object is notified about a progress
  51. * .
  52. * @var \Carica\Io\Callbacks
  53. */
  54. private $_progress = NULL;
  55. /**
  56. * buffer for the arguments of the resolve/reject function,
  57. * used to execute functions, that are added after the object was finalized
  58. * .
  59. * @var array
  60. */
  61. private $_finishArguments = array();
  62. /**
  63. * Buffer for the last progress notification arguments, used
  64. * to bring new callback up to date.
  65. * .
  66. * @var NULL|array
  67. */
  68. private $_progressArguments = NULL;
  69. /**
  70. * Create object and intialize callback lists
  71. */
  72. public function __construct() {
  73. $this->_done = new Callbacks();
  74. $this->_failed = new Callbacks();
  75. $this->_progress = new Callbacks();
  76. }
  77. /**
  78. * Add a callback that will be execute if the object is finalized with
  79. * resolved or reject
  80. *
  81. * @param Callable $callback
  82. * @return \Carica\Io\Deferred
  83. */
  84. public function always(Callable $callback) {
  85. $this->_done->add($callback);
  86. $this->_failed->add($callback);
  87. if ($this->_state != self::STATE_PENDING) {
  88. call_user_func_array($callback, $this->_finishArguments);
  89. }
  90. return $this;
  91. }
  92. /**
  93. * Add a callback that will be executed if the object is resolved
  94. *
  95. * @param Callable $callback
  96. * @return \Carica\Io\Deferred
  97. */
  98. public function done(Callable $callback) {
  99. $this->_done->add($callback);
  100. if ($this->_state == self::STATE_RESOLVED) {
  101. call_user_func_array($callback, $this->_finishArguments);
  102. }
  103. return $this;
  104. }
  105. /**
  106. * Add a callback that will be eecuted if the object was rejected
  107. *
  108. * @param Callable $callback
  109. * @return \Carica\Io\Deferred
  110. */
  111. public function fail(Callable $callback) {
  112. $this->_failed->add($callback);
  113. if ($this->_state == self::STATE_REJECTED) {
  114. call_user_func_array($callback, $this->_finishArguments);
  115. }
  116. return $this;
  117. }
  118. /**
  119. * Validate if the object was finilized using reject.
  120. *
  121. * @return string
  122. */
  123. public function isRejected() {
  124. return $this->_state == self::STATE_REJECTED;
  125. }
  126. /**
  127. * Validate if the object was finilized using resolve.
  128. *
  129. * @return string
  130. */
  131. public function isResolved() {
  132. return $this->_state == self::STATE_RESOLVED;
  133. }
  134. /**
  135. * Notify the object about the progress
  136. */
  137. public function notify() {
  138. if ($this->_state == self::STATE_PENDING) {
  139. $this->_progressArguments = func_get_args();
  140. call_user_func_array($this->_progress, $this->_progressArguments);
  141. }
  142. return $this;
  143. }
  144. /**
  145. * Utility method to filter and/or chain Deferreds.
  146. *
  147. * @param Callable $doneFilter
  148. * @param Callable $failFilter
  149. * @param Callable $progressFilter
  150. * @return \Carica\Io\Deferred\Promise
  151. */
  152. public function pipe(
  153. Callable $doneFilter = NULL,
  154. Callable $failFilter = NULL,
  155. Callable $progressFilter = NULL
  156. ) {
  157. $defer = new Deferred();
  158. $this->done(
  159. function () use ($defer, $doneFilter){
  160. if ($doneFilter) {
  161. $defer->resolve(call_user_func_array($doneFilter, func_get_args()));
  162. } else {
  163. call_user_func_array(array($defer, 'resolve'), func_get_args());
  164. }
  165. }
  166. );
  167. $this->fail(
  168. function () use ($defer, $failFilter){
  169. if ($failFilter) {
  170. $defer->reject(call_user_func_array($failFilter, func_get_args()));
  171. } else {
  172. call_user_func_array(array($defer, 'reject'), func_get_args());
  173. }
  174. }
  175. );
  176. $this->progress(
  177. function () use ($defer, $progressFilter){
  178. if ($progressFilter) {
  179. $defer->notify(call_user_func_array($progressFilter, func_get_args()));
  180. } else {
  181. call_user_func_array(array($defer, 'notify'), func_get_args());
  182. }
  183. }
  184. );
  185. return $defer->promise();
  186. }
  187. /**
  188. * Add a callback that will be executed if the object is notified about progress
  189. *
  190. * @param Callable $callback
  191. * @return \Carica\Io\Deferred
  192. */
  193. public function progress(Callable $callback) {
  194. $this->_progress->add($callback);
  195. if (NULL !== $this->_progressArguments) {
  196. call_user_func_array($callback, $this->_progressArguments);
  197. }
  198. return $this;
  199. }
  200. /**
  201. * Creates and returns a promise attached to this object, a promise is used to
  202. * attach callbacks and validate the status. But has no methods to change the status.
  203. *
  204. * @return \Carica\Io\Deferred\Promise
  205. */
  206. public function promise() {
  207. if (NULL === $this->_promise) {
  208. $this->_promise = new Deferred\Promise($this);
  209. }
  210. return $this->_promise;
  211. }
  212. /**
  213. * Finalize the object and set the status to rejected - the action has failed.
  214. * This will execute all callbacks attached with fail() or always()
  215. *
  216. * @return \Carica\Io\Deferred
  217. */
  218. public function reject() {
  219. if ($this->_state == self::STATE_PENDING) {
  220. $this->_finishArguments = func_get_args();
  221. $this->_state = self::STATE_REJECTED;
  222. call_user_func_array($this->_failed, $this->_finishArguments);
  223. }
  224. return $this;
  225. }
  226. /**
  227. * Finalize the object and set the status to rejected - the action was successful.
  228. * This will execute all callbacks attached with done() or always()
  229. *
  230. * @return \Carica\Io\Deferred
  231. */
  232. public function resolve() {
  233. if ($this->_state == self::STATE_PENDING) {
  234. $this->_finishArguments = func_get_args();
  235. $this->_state = self::STATE_RESOLVED;
  236. call_user_func_array($this->_done, $this->_finishArguments);
  237. }
  238. return $this;
  239. }
  240. /**
  241. * Return the state string. Here are constants for each state, too.
  242. *
  243. * @return string
  244. */
  245. public function state() {
  246. return $this->_state;
  247. }
  248. /**
  249. * Add handlers to be called when the Deferred object is resolved
  250. * or rejected or notified about progress. Basically a shortcut for
  251. * done(), fail() and progress().
  252. *
  253. * @param Callable|array(Callable) $done
  254. * @param Callable|array(Callable) $fail
  255. * @param Callable|array(Callable) $progress
  256. * @return \Carica\Io\Deferred
  257. */
  258. public function then($done = NULL, $fail = NULL, $progress = NULL) {
  259. $this->addCallbacksIfProvided(array($this, 'done'), $done);
  260. $this->addCallbacksIfProvided(array($this, 'fail'), $fail);
  261. $this->addCallbacksIfProvided(array($this, 'progress'), $progress);
  262. return $this;
  263. }
  264. /**
  265. * Check if $callbacks is a single callback or an array of callbacks.
  266. * The $add parameter is the method used to add the actual callbacks to the
  267. * deferred object.
  268. *
  269. * @param Callable $add
  270. * @param Callable|array(Callable) $callbacks
  271. */
  272. private function addCallbacksIfProvided($add, $callbacks) {
  273. if (is_callable($callbacks)) {
  274. $add($callbacks);
  275. } elseif (is_array($callbacks)) {
  276. foreach ($callbacks as $callback) {
  277. $add($callback);
  278. }
  279. }
  280. }
  281. /**
  282. * Static method to the create a new Deferred object.
  283. *
  284. * @return \Carica\Io\Deferred
  285. */
  286. public static function create() {
  287. return new Deferred();
  288. }
  289. /**
  290. * Provides a way to execute callback functions based on one or more
  291. * objects, usually Deferred objects that represent asynchronous events.
  292. *
  293. * @return \Carica\Io\Deferred\Promise
  294. */
  295. public static function when() {
  296. $arguments = func_get_args();
  297. $counter = count($arguments);
  298. if ($counter == 1) {
  299. $argument = $arguments[0];
  300. if ($argument instanceOf Deferred) {
  301. return $argument->promise();
  302. } elseif ($argument instanceOf Deferred\Promise) {
  303. return $argument;
  304. } else {
  305. $defer = new Deferred();
  306. $defer->resolve($argument);
  307. return $defer->promise();
  308. }
  309. } elseif ($counter > 0) {
  310. $master = new Deferred();
  311. $resolveArguments = array();
  312. foreach ($arguments as $index => $argument) {
  313. if ($argument instanceOf Deferred || $argument instanceOf Deferred\Promise) {
  314. $argument
  315. ->done(
  316. function() use ($master, $index, &$counter, &$resolveArguments) {
  317. $resolveArguments[$index] = func_get_args();
  318. if (--$counter == 0) {
  319. ksort($resolveArguments);
  320. call_user_func_array(array($master, 'resolve'), $resolveArguments);
  321. }
  322. }
  323. )
  324. ->fail(
  325. function() use ($master) {
  326. call_user_func_array(array($master, 'reject'), func_get_args());
  327. }
  328. );
  329. } else {
  330. $resolveArguments[$index] = array($argument);
  331. if (--$counter == 0) {
  332. ksort($resolveArguments);
  333. call_user_func_array(array($master, 'resolve'), $resolveArguments);
  334. }
  335. }
  336. }
  337. return $master->promise();
  338. } else {
  339. $defer = new Deferred();
  340. $defer->resolve();
  341. return $defer->promise();
  342. }
  343. }
  344. }
  345. }