8.6 KiB
Loop
danog/loop
provides a set of powerful async loop APIs for executing operations periodically or on demand, in background loops a-la threads.
A more flexible and powerful alternative to AMPHP's repeat function, allowing dynamically changeable repeat periods, resumes and signaling.
Installation
composer require danog/loop
API
- Basic
- Advanced
All loop APIs are defined by a set of interfaces: however, to use them, you would usually have to extend only one of the abstract class implementations.
Loop
A basic loop, capable of running in background (asynchronously) the code contained in the loop
function.
API:
namespace danog\Loop;
abstract class Loop
{
abstract public function loop();
abstract public function __toString(): string;
public function start(): bool;
public function isRunning(): bool;
protected function startedLoop(): void;
protected function exitedLoop(): void;
}
loop()
The loop
async coroutine will be run only once, every time the start
method is called.
__toString()
This method should return the loop's name.
It's useful for implementing the log methods.
start()
Asynchronously starts the loop
methods once in background.
Multiple calls to start
will be ignored, returning false
instead of true
.
isRunning()
You can use the isRunning
method to check if the loop is already running.
startedLoop()
Optionally, you can override this method to detect and log when the loop is started.
Make sure to always call the parent startedLoop()
method to avoid issues.
You can use directly $this
as loop name when logging, thanks to the custom __toString method.
exitedLoop()
Optionally, you can override this method to detect and log when the loop is ended.
Make sure to always call the parent exitedLoop()
method to avoid issues.
You can use directly $this
as loop name when logging, thanks to the custom __toString method.
ResumableLoop
A way more useful loop that exposes APIs to pause and resume the execution of the loop, both from outside of the loop, and in a cron-like manner from inside of the loop.
namespace danog\Loop;
abstract class ResumableLoop extends Loop
{
public function pause(?int $time = null): Promise;
public function resume(): Promise;
}
All methods from Loop, plus:
pause()
Pauses the loop for the specified number of milliseconds, or forever if null
is provided.
resume()
Forcefully resume the loop from the outside.
Returns a promise that is resolved when the loop is paused again.
SignalLoop
Yet another loop interface that exposes APIs to send signals to the loop, useful to force the termination of a loop from the outside, or to send data into it.
namespace danog\Loop;
abstract class SignalLoop extends Loop
{
public function signal($what): void;
public function waitSignal($promise): Promise;
}
All methods from Loop, plus:
signal()
Sends a signal to the loop: can be anything, but typically true
is often used as termination signal.
Signaling can be used as a message exchange mechanism a-la IPC, and can also be used to throw exceptions inside the loop.
waitSignal()
Resolve the provided promise or return|throw passed signal.
ResumableSignalLoop
This is what you would usually use to build a full async loop.
All loop interfaces and loop implementations are combined into a single class you can extend.
namespace danog\Loop;
abstract class ResumableSignalLoop extends SignalLoop, ResumableSignalLoop
{
}
The class is actually composited using traits to feature all methods from SignalLoop and ResumableSignalLoop.
GenericLoop
If you want a simpler way to use the ResumableSignalLoop
, you can use the GenericLoop.
namespace danog\Loop\Generic;
class GenericLoop extends ResumableSignalLoop
{
/**
* Stop the loop.
*/
const STOP = -1;
/**
* Pause the loop.
*/
const PAUSE = null;
/**
* Rerun the loop.
*/
const CONTINUE = 0;
/**
* Constructor.
*
* If possible, the callable will be bound to the current instance of the loop.
*
* @param callable $callable Callable to run
* @param string $name Loop name
*/
public function __construct(callable $callable, string $name);
/**
* Report pause, can be overriden for logging.
*
* @param integer $timeout Pause duration, 0 = forever
*
* @return void
*/
protected function reportPause(int $timeout): void;
/**
* Get loop name, provided to constructor.
*
* @return string
*/
public function __toString(): string;
}
The callback will be bound to the GenericLoop
instance: this means that you will be able to use $this
as if the callback were actually the loop
function (you can get the loop name by casting $this
to a string, use the pause/waitSignal methods & so on).
The return value of the callable can be:
- A number - the loop will be paused for the specified number of seconds
GenericLoop::STOP
- The loop will stopGenericLoop::PAUSE
- The loop will pause forever (or until theresume
method is called on the loop object from outside the loop)GenericLoop::CONTINUE
- Return this if you want to rerun the loop without waiting
If the callable does not return anything, the loop will behave is if GenericLoop::PAUSE
was returned.
PeriodicLoop
If you simply want to execute an action every N seconds, PeriodicLoop is the way to go.
namespace danog\Loop\Generic;
class PeriodicLoop extends ResumableSignalLoop
{
/**
* Constructor.
*
* If possible, the callable will be bound to the current instance of the loop.
*
* @param callable $callback Callback to call
* @param string $name Loop name
* @param ?int $interval Loop interval
*/
public function __construct(callable $callback, string $name, ?int $interval);
/**
* Get name of the loop, passed to the constructor.
*
* @return string
*/
public function __toString(): string;
}
PeriodicLoop
runs a callback at a periodic interval.
The callback will be bound to the PeriodicLoop
instance: this means that you will be able to use $this
as if the callback were actually the loop
function (you can get the loop name by casting $this
to a string, use the pause/waitSignal methods & so on).
The loop can be stopped from the outside or from the inside by signaling or returning true
.