2016-05-24 18:47:14 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Amp;
|
|
|
|
|
2016-06-01 18:37:12 +02:00
|
|
|
/**
|
|
|
|
* Asynchronous iterator that can be used within a coroutine to iterate over the emitted values from an Observable.
|
|
|
|
*
|
|
|
|
* Example:
|
|
|
|
* $observer = new Observer($observable); // $observable is an instance of \Amp\Observable
|
|
|
|
* while (yield $observer->next()) {
|
|
|
|
* $emitted = $observer->getCurrent();
|
|
|
|
* }
|
|
|
|
* $result = $observer->getResult();
|
|
|
|
*/
|
2016-07-19 18:58:20 +02:00
|
|
|
class Observer {
|
2016-05-27 22:44:01 +02:00
|
|
|
/**
|
2016-06-02 17:35:41 +02:00
|
|
|
* @var \Amp\Subscriber
|
2016-05-27 22:44:01 +02:00
|
|
|
*/
|
|
|
|
private $subscriber;
|
|
|
|
|
2016-05-29 18:35:09 +02:00
|
|
|
/**
|
2016-05-30 06:49:44 +02:00
|
|
|
* @var mixed[]
|
2016-05-29 18:35:09 +02:00
|
|
|
*/
|
2016-05-30 06:49:44 +02:00
|
|
|
private $values = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var \Amp\Future[]
|
|
|
|
*/
|
|
|
|
private $futures = [];
|
2016-05-29 18:35:09 +02:00
|
|
|
|
|
|
|
/**
|
2016-05-30 06:49:44 +02:00
|
|
|
* @var int
|
2016-05-29 18:35:09 +02:00
|
|
|
*/
|
2016-05-30 06:49:44 +02:00
|
|
|
private $position = -1;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var \Amp\Deferred|null
|
|
|
|
*/
|
|
|
|
private $deferred;
|
2016-05-29 18:35:09 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var bool
|
|
|
|
*/
|
2016-05-30 17:24:03 +02:00
|
|
|
private $resolved = false;
|
2016-05-29 18:35:09 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var mixed
|
|
|
|
*/
|
2016-05-30 06:49:44 +02:00
|
|
|
private $result;
|
2016-05-29 18:35:09 +02:00
|
|
|
|
|
|
|
/**
|
2016-05-30 06:49:44 +02:00
|
|
|
* @var \Throwable|\Exception|null
|
2016-05-29 18:35:09 +02:00
|
|
|
*/
|
|
|
|
private $exception;
|
|
|
|
|
2016-05-27 22:44:01 +02:00
|
|
|
/**
|
|
|
|
* @param \Amp\Observable $observable
|
|
|
|
*/
|
|
|
|
public function __construct(Observable $observable) {
|
2016-05-29 18:35:09 +02:00
|
|
|
$deferred = &$this->deferred;
|
2016-05-30 06:49:44 +02:00
|
|
|
$values = &$this->values;
|
|
|
|
$futures = &$this->futures;
|
2016-05-29 18:35:09 +02:00
|
|
|
|
2016-05-30 06:49:44 +02:00
|
|
|
$this->subscriber = $observable->subscribe(static function ($value) use (&$deferred, &$values, &$futures) {
|
|
|
|
$values[] = $value;
|
|
|
|
$futures[] = $future = new Future;
|
2016-05-29 18:35:09 +02:00
|
|
|
|
2016-05-30 06:49:44 +02:00
|
|
|
if ($deferred !== null) {
|
|
|
|
$temp = $deferred;
|
|
|
|
$deferred = null;
|
|
|
|
$temp->resolve($value);
|
|
|
|
}
|
2016-05-29 18:35:09 +02:00
|
|
|
|
|
|
|
return $future;
|
|
|
|
});
|
|
|
|
|
2016-05-30 17:24:03 +02:00
|
|
|
$resolved = &$this->resolved;
|
2016-05-30 06:49:44 +02:00
|
|
|
$result = &$this->result;
|
2016-05-29 18:35:09 +02:00
|
|
|
$error = &$this->exception;
|
|
|
|
|
2016-07-19 19:36:09 +02:00
|
|
|
$observable->when(static function ($exception, $value) use (&$deferred, &$result, &$error, &$resolved) {
|
2016-05-30 17:24:03 +02:00
|
|
|
$resolved = true;
|
2016-05-29 18:35:09 +02:00
|
|
|
|
|
|
|
if ($exception) {
|
2016-05-30 06:49:44 +02:00
|
|
|
$result = null;
|
2016-05-29 18:35:09 +02:00
|
|
|
$error = $exception;
|
2016-05-30 06:49:44 +02:00
|
|
|
if ($deferred !== null) {
|
2016-05-29 18:35:09 +02:00
|
|
|
$deferred->fail($exception);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-05-30 06:49:44 +02:00
|
|
|
$result = $value;
|
|
|
|
if ($deferred !== null) {
|
2016-05-29 18:35:09 +02:00
|
|
|
$deferred->resolve(false);
|
|
|
|
}
|
|
|
|
});
|
2016-05-27 22:44:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-06-03 00:11:25 +02:00
|
|
|
* Unsubscribes the internal subscriber from the observable.
|
2016-05-27 22:44:01 +02:00
|
|
|
*/
|
|
|
|
public function __destruct() {
|
2016-05-30 17:24:03 +02:00
|
|
|
if (!$this->resolved) {
|
2016-06-02 17:35:41 +02:00
|
|
|
$this->subscriber->unsubscribe();
|
2016-05-30 06:49:44 +02:00
|
|
|
}
|
2016-05-29 18:35:09 +02:00
|
|
|
|
2016-05-30 06:49:44 +02:00
|
|
|
foreach ($this->futures as $future) {
|
|
|
|
$future->resolve();
|
2016-05-29 18:35:09 +02:00
|
|
|
}
|
2016-05-27 22:44:01 +02:00
|
|
|
}
|
|
|
|
|
2016-05-24 18:47:14 +02:00
|
|
|
/**
|
2016-05-30 17:24:03 +02:00
|
|
|
* Succeeds with true if an emitted value is available by calling getCurrent() or false if the observable has
|
|
|
|
* resolved. If the observable fails, the returned awaitable will fail with the same exception.
|
2016-05-24 18:47:14 +02:00
|
|
|
*
|
|
|
|
* @return \Interop\Async\Awaitable
|
|
|
|
*/
|
2016-05-30 17:24:03 +02:00
|
|
|
public function next() {
|
2016-05-30 06:49:44 +02:00
|
|
|
if (isset($this->futures[$this->position])) {
|
|
|
|
$future = $this->futures[$this->position];
|
|
|
|
unset($this->values[$this->position], $this->futures[$this->position]);
|
|
|
|
$future->resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
++$this->position;
|
|
|
|
|
|
|
|
if (isset($this->values[$this->position])) {
|
|
|
|
return new Success(true);
|
|
|
|
}
|
|
|
|
|
2016-05-30 17:24:03 +02:00
|
|
|
if ($this->resolved) {
|
2016-05-29 18:35:09 +02:00
|
|
|
if ($this->exception) {
|
|
|
|
return new Failure($this->exception);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Success(false);
|
|
|
|
}
|
|
|
|
|
2016-05-30 06:49:44 +02:00
|
|
|
$this->deferred = new Deferred;
|
2016-05-29 18:35:09 +02:00
|
|
|
return $this->deferred->getAwaitable();
|
2016-05-27 22:44:01 +02:00
|
|
|
}
|
2016-05-24 18:47:14 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the last emitted value or throws an exception if the observable has completed.
|
|
|
|
*
|
|
|
|
* @return mixed Value emitted from observable.
|
|
|
|
*
|
2016-05-30 17:24:03 +02:00
|
|
|
* @throws \LogicException If the observable has resolved or next() was not called before calling this method.
|
2016-05-24 18:47:14 +02:00
|
|
|
*/
|
2016-05-27 22:44:01 +02:00
|
|
|
public function getCurrent() {
|
2016-05-30 17:24:03 +02:00
|
|
|
if (empty($this->values) && $this->resolved) {
|
2016-05-30 06:49:44 +02:00
|
|
|
throw new \LogicException("The observable has completed");
|
2016-05-29 18:35:09 +02:00
|
|
|
}
|
|
|
|
|
2016-05-30 06:49:44 +02:00
|
|
|
if (!isset($this->values[$this->position])) {
|
2016-05-30 17:24:03 +02:00
|
|
|
throw new \LogicException("Awaitable returned from next() must resolve before calling this method");
|
2016-05-29 18:35:09 +02:00
|
|
|
}
|
|
|
|
|
2016-05-30 06:49:44 +02:00
|
|
|
return $this->values[$this->position];
|
2016-05-27 22:44:01 +02:00
|
|
|
}
|
2016-05-24 18:47:14 +02:00
|
|
|
|
|
|
|
/**
|
2016-05-30 17:24:03 +02:00
|
|
|
* Gets the result of the observable or throws the failure reason. Also throws an exception if the observable has
|
|
|
|
* not completed.
|
2016-05-24 18:47:14 +02:00
|
|
|
*
|
|
|
|
* @return mixed Final return value of the observable.
|
|
|
|
*
|
2016-05-29 18:35:09 +02:00
|
|
|
* @throws \LogicException If the observable has not completed.
|
|
|
|
* @throws \Throwable|\Exception The exception used to fail the observable.
|
2016-05-24 18:47:14 +02:00
|
|
|
*/
|
2016-05-30 17:24:03 +02:00
|
|
|
public function getResult() {
|
|
|
|
if (!$this->resolved) {
|
|
|
|
throw new \LogicException("The observable has not resolved");
|
2016-05-29 18:35:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->exception) {
|
|
|
|
throw $this->exception;
|
|
|
|
}
|
|
|
|
|
2016-05-30 06:49:44 +02:00
|
|
|
return $this->result;
|
2016-05-27 22:44:01 +02:00
|
|
|
}
|
2016-05-24 18:47:14 +02:00
|
|
|
}
|