|
<?php |
|
|
|
namespace App\Support\Jobs; |
|
|
|
use Illuminate\Contracts\Queue\ShouldQueue; |
|
|
|
class DebouncedJob implements ShouldQueue |
|
{ |
|
use \Illuminate\Foundation\Bus\DispatchesJobs; |
|
use \App\Support\Cache\PrefixedCache; |
|
use \Illuminate\Queue\SerializesModels; |
|
use \Illuminate\Queue\InteractsWithQueue; |
|
use \Illuminate\Bus\Queueable; |
|
|
|
const DEFAULT_PREFIX = 'debounce'; |
|
const KEY_MAX_WAIT = 'maxWait'; |
|
const KEY_DEBOUNCE = 'debounce'; |
|
|
|
/** |
|
* @var array |
|
*/ |
|
protected $cacheKeys = [ |
|
self::KEY_DEBOUNCE, |
|
self::KEY_MAX_WAIT, |
|
]; |
|
|
|
/** |
|
* The Job that is being debounced, in serialized form. |
|
* |
|
* @var string |
|
*/ |
|
protected $debounced; |
|
|
|
/** |
|
* Amount of time (in seconds) to debounce the Job. |
|
* |
|
* @var int |
|
*/ |
|
protected $debounce; |
|
|
|
/** |
|
* Maximum amount of time (in seconds) to wait before this Job gets run. |
|
* |
|
* @var int|null |
|
*/ |
|
protected $maxWait; |
|
|
|
/** |
|
* This can be used to have separately debounced versions of the same Job. |
|
* |
|
* @var string |
|
*/ |
|
protected $cachePrefix; |
|
|
|
/** |
|
* Cache of the unserialized Job instance that was debounced. |
|
* |
|
* @var mixed|null |
|
*/ |
|
protected $unserialized; |
|
|
|
public function __construct( |
|
$debounced, |
|
$delay, |
|
$maxWait = null, |
|
$prefix = self::DEFAULT_PREFIX |
|
) { |
|
$this->debounced = serialize($debounced); |
|
$this->debounce = $delay; |
|
$this->maxWait = $maxWait; |
|
$this->cachePrefix = $prefix; |
|
|
|
$this->cacheDebounceTime(); |
|
$this->cacheMaxWaitTime(); |
|
$this->delay($delay+1); |
|
} |
|
|
|
/** |
|
* Handle the Job, if it is time to. |
|
*/ |
|
public function handle() |
|
{ |
|
// Check if this or a later DebouncedJob instance will handle this task. |
|
if (!$this->isReadyToHandle()) { |
|
return; |
|
} |
|
|
|
// Prevent any future debounced Jobs from being triggered, |
|
// until a new one is created. |
|
$this->clearCache(); |
|
|
|
// Then, dispatch the original Job that was debounced. |
|
$this->dispatch(unserialize($this->debounced)); |
|
} |
|
|
|
# Implementation for PrefixedCache |
|
/** |
|
* The key to store in the Cache for this Job. |
|
* |
|
* @param string $suffix |
|
* |
|
* @return string |
|
*/ |
|
protected function getCacheKey($suffix) |
|
{ |
|
return sprintf( |
|
'%s__%s__%s', |
|
$this->cachePrefix, |
|
$this->getJobName(), |
|
$suffix |
|
); |
|
} |
|
|
|
private function getJob() |
|
{ |
|
return $this->unserialized ?: |
|
$this->unserialized = unserialize($this->debounced); |
|
} |
|
|
|
private function getJobName() |
|
{ |
|
$job = $this->getJob(); |
|
if (is_a($job, \Illuminate\Contracts\Queue\Job::class)) { |
|
return $job->getName(); |
|
} |
|
|
|
return get_class($job); |
|
} |
|
|
|
/** |
|
* Determine if the requested Job should be processed immediately. |
|
* |
|
* @return boolean |
|
*/ |
|
private function isReadyToHandle() |
|
{ |
|
$isInPast = function ($_, $cacheKey) { |
|
return $this->isInPast( |
|
$this->getCache($cacheKey) |
|
); |
|
}; |
|
|
|
return (bool) collect($this->cacheKeys)->first($isInPast); |
|
} |
|
|
|
/** |
|
* @param int $time |
|
* |
|
* @return boolean |
|
*/ |
|
private function isInPast($time) |
|
{ |
|
print "Checking $time...\n"; |
|
return $time && time() >= $time; |
|
} |
|
|
|
/** |
|
* Store the debounce time in the Cache. |
|
*/ |
|
private function cacheDebounceTime() |
|
{ |
|
$this->setCache(static::KEY_DEBOUNCE, time() + $this->debounce); |
|
} |
|
|
|
/** |
|
* Store the max wait time in the Cache. |
|
*/ |
|
private function cacheMaxWaitTime() |
|
{ |
|
if (!$this->maxWait) { |
|
return; |
|
} |
|
|
|
if ($this->getCache(static::KEY_MAX_WAIT)) { |
|
// There is currently a max wait in place, |
|
// that has not been triggered yet. |
|
// We don't want to override that value. |
|
return; |
|
} |
|
|
|
$this->setCache(static::KEY_MAX_WAIT, time() + $this->maxWait); |
|
} |
|
|
|
/** |
|
* Clear all related Cache entries. |
|
*/ |
|
private function clearCache() |
|
{ |
|
collect($this->cacheKeys)->each(function ($cacheKey) { |
|
$this->forgetCache($cacheKey); |
|
}); |
|
} |
|
} |
Love this git! But for other readers, this is also helpful https://laravel.com/docs/7.x/cache#managing-locks-across-processes
You can just lock your job down and only when it's done you can release the lock so it can be fired again.