*/ abstract class AbstractQueueBroker implements QueueBrokerInterface, PhpSerializable, ServiceLocatorAwareInterface { use LoggerAwareTrait; use ServiceLocatorAwareTrait; private $numberOfTasksToReceive; private $queueName; private $preFetchedQueue; /** * AbstractMessageBroker constructor. * * @param int $receiveTasks Maximum amount of tasks that can be received when polling the queue; Default is 1. */ public function __construct($receiveTasks = 1) { $this->numberOfTasksToReceive = $receiveTasks; $this->preFetchedQueue = new \SplQueue(); } public function __toPhpCode() { return 'new ' . get_called_class() . '(' . \common_Utils::toHumanReadablePhpString($this->numberOfTasksToReceive) . ')'; } /** * Do the specific pop mechanism related to the given broker. * Tasks need to be added to the internal pre-fetched queue. * * @return void */ abstract protected function doPop(); /** * Internal mechanism of deleting a message, specific for the given broker * * @param string $id * @param array $logContext * @return void */ abstract protected function doDelete($id, array $logContext = []); /** * @return null|TaskInterface */ public function pop() { // if there is item in the pre-fetched queue, let's return that if ($message = $this->popPreFetchedMessage()) { return $message; } $this->doPop(); return $this->popPreFetchedMessage(); } /** * Pop a task from the internal queue. * * @return TaskInterface|null */ private function popPreFetchedMessage() { if ($this->preFetchedQueue->count()) { return $this->preFetchedQueue->dequeue(); } return null; } /** * Add a task to the internal queue. * * @param TaskInterface $task */ protected function pushPreFetchedMessage(TaskInterface $task) { $this->preFetchedQueue->enqueue($task); } /** * Unserialize the given task JSON. * * If the json is not valid, it deletes the task straight away without processing it. * * @param string $taskJSON * @param string $idForDeletion An identification of the given task * @param array $logContext * @return null|TaskInterface */ protected function unserializeTask($taskJSON, $idForDeletion, array $logContext = []) { try { $basicData = json_decode($taskJSON, true); $this->assertValidJson($basicData); $task = TaskFactory::build($basicData); if ($task instanceof CallbackTaskInterface && is_string($task->getCallable())) { $this->handleCallbackTask($task, $logContext); } return $task; } catch (\Exception $e) { $this->doDelete($idForDeletion, $logContext); return null; } } /** * @param TaskInterface $task * @return mixed */ protected function serializeTask(TaskInterface $task) { return json_encode($task); } /** * @param $basicData * @throws \Exception */ protected function assertValidJson($basicData) { if ( ($basicData !== null && json_last_error() === JSON_ERROR_NONE && isset($basicData[TaskInterface::JSON_TASK_CLASS_NAME_KEY])) === false ) { throw new \Exception(); } } /** * @param CallbackTaskInterface $task * @param array $logContext * @throws \Exception */ protected function handleCallbackTask(CallbackTaskInterface $task, array $logContext) { try { $callable = $this->getActionResolver()->resolve($task->getCallable()); if ($callable instanceof ServiceLocatorAwareInterface) { $callable->setServiceLocator($this->getServiceLocator()); } $task->setCallable($callable); } catch (ResolutionException $e) { $this->logError('Callable/Action class ' . $task->getCallable() . ' does not exist', $logContext); throw new \Exception(); } } /** * @return ActionService|ConfigurableService|object */ protected function getActionResolver() { return $this->getServiceLocator()->get(ActionService::SERVICE_ID); } /** * @param string $name * @return $this */ public function setQueueName($name) { $this->queueName = $name; return $this; } /** * @return string */ protected function getQueueName() { return $this->queueName; } /** * @return string */ protected function getQueueNameWithPrefix() { return sprintf("%s_%s", QueueDispatcher::QUEUE_PREFIX, $this->getQueueName()); } /** * @inheritdoc */ public function getNumberOfTasksToReceive() { return abs((int) $this->numberOfTasksToReceive); } }