<?php
declare(strict_types=1);
namespace WebBundle\Scenario;
use Psr\Cache\CacheItemInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use WebBundle\Dto\OneCRequestsDto;
/**
* Сценарий генерирует псевдо-очередь на редисе.
* - создаётся uuid для запроса;
* - если нет записи с orderHash добавляется структура с первым элементом (uuid) и ставится статус в обработке;
* - обработка пропускает дальше;
* - при поступлении нового запроса (2) - он ставится вторым и обработка становится в режим ожидания (TIME_FOR_DELAY);
* - если пришел следующий (3) запрос (первый ещё не кончился) - он затирает последний;
* - запрос 2 оживает, не видит себя на обоих местах и умирает;
* ... для остальных запросов также
* - первый сообщает, что закончил и удаляется ставя идущий за ним на первое место;
* - когда оживает этот запрос - он пропускает свою обработку дальше;
*
* При возникновении какой-либо ошибки (напр на коннекте редиса) - просто пропускаем задержку очереди
*/
class SendToOneCQueueScenario
{
// время, через которое проснётся следующий запрос и начнёт проверять
public const TIME_FOR_DELAY_IN_SEC = 3;
// максимально допустимое время на обработку, если запрос идёт дольше - можем запускать следующий
public const TIME_FOR_CRUSH_IN_SEC = 100;
public const MAX_COUNT_FOR_WAIT = 35;
public const CACHE_KEY = 'onec';
private AdapterInterface $cacheAdapter;
private ?CacheItemInterface $cacheItem = null;
private string $cacheKey;
protected string $uuidRequest;
public function __construct(AdapterInterface $cacheAdapter, string $orderHash, string $typeOfFunc)
{
$this->cacheAdapter = $cacheAdapter;
$this->cacheKey = self::CACHE_KEY . $typeOfFunc . $orderHash;
$this->uuidRequest = uniqid($orderHash, true);
$this->getFirstStructData();
}
public function checkForStart(): void
{
if (!$this->initCacheItem()) {
return;
}
$data = $this->getNormalizeStruct();
if ($this->isFirstRequest($data)) {
return;
}
$maxCountForWait = self::MAX_COUNT_FOR_WAIT;
while (!$this->checkStruct($data)) {
if ($maxCountForWait <= 0) {
break;
}
$this->getSleep();
$maxCountForWait--;
if (!$this->initCacheItem()) {
return;
}
$data = $this->getNormalizeStruct();
}
$this->setFirstRequest($data, $this->uuidRequest);
$data->setSecondRequest(null);
$this->saveToCache($data);
}
public function setFinish(): void
{
if (!$this->initCacheItem()) {
return;
}
if (!$this->cacheItem->isHit()) {
return;
}
/** @var OneCRequestsDto $data */
$data = $this->cacheItem->get();
if (null === $data) {
return;
}
if (null === $data->secondRequest
|| $data->secondRequest === $this->uuidRequest
) {
$this->saveToCache($data, 0);
return;
}
$this->setFirstRequest($data, $data->secondRequest);
$data->setSecondRequest(null);
$this->saveToCache($data);
}
private function getFirstStructData(): void
{
if (!$this->initCacheItem()) {
return;
}
$data = $this->getNormalizeStruct();
if ($data->firstRequest !== $this->uuidRequest) {
$data->setSecondRequest($this->uuidRequest);
}
$this->saveToCache($data);
}
private function getNormalizeStruct(): OneCRequestsDto
{
$data = null;
if ($this->cacheItem->isHit()) {
/** @var OneCRequestsDto $data */
$data = $this->cacheItem->get();
}
if (null === $data) {
$data = new OneCRequestsDto();
}
$this->normalizeStruct($data);
return $data;
}
private function normalizeStruct(OneCRequestsDto $data): void
{
if (null === $data->firstRequest || $this->isFirstRequestExpired($data)) {
$this->setFirstRequest($data, $this->uuidRequest);
$data->setSecondRequest(null);
}
if ($data->firstRequest === $data->secondRequest) {
$data->setSecondRequest(null);
}
}
protected function getSleep(): void
{
sleep(self::TIME_FOR_DELAY_IN_SEC);
}
private function saveToCache(OneCRequestsDto $data, int $timeForExpired = self::TIME_FOR_CRUSH_IN_SEC * 2): void
{
$this->cacheItem->expiresAfter((int) $timeForExpired);
$this->cacheItem->set($data);
$this->cacheAdapter->save($this->cacheItem);
}
private function checkStruct(OneCRequestsDto $data): bool
{
if ($data->firstRequest === $this->uuidRequest) {
return true;
}
if ($this->isFirstRequestExpired($data)) {
return true;
}
if ($data->secondRequest === $this->uuidRequest) {
return false;
}
$this->terminate();
return false; // %-))
}
protected function terminate(): void
{
die();
}
protected function getCurrentTime(): int
{
return time();
}
private function initCacheItem(): bool
{
try {
$this->cacheItem = $this->cacheAdapter->getItem($this->cacheKey);
return true;
} catch (InvalidArgumentException $e) {
return false;
}
}
private function setFirstRequest(OneCRequestsDto $data, ?string $request)
{
$data->setFirstRequest($request);
$data->setTimestampFirstRequest($this->getCurrentTime());
}
private function isFirstRequest(OneCRequestsDto $data): bool
{
return $data->firstRequest === $this->uuidRequest;
}
private function isFirstRequestExpired(OneCRequestsDto $data): bool
{
return $this->getCurrentTime() >= $data->timestampFirstRequest + static::TIME_FOR_CRUSH_IN_SEC;
}
}