<?php
namespace Products\NotificationsBundle\Entity;
use Cms\TenantBundle\Entity\TenantedEntity;
use DateInterval;
use DateTime;
use Doctrine\ORM\Mapping as ORM;
use Products\NotificationsBundle\Entity\Jobs\Channels;
use Products\NotificationsBundle\Entity\Notifications\Channels\ChannelsInterface;
use Products\NotificationsBundle\Entity\Notifications\Channels\ServiceChannelsInterface;
use Products\NotificationsBundle\Entity\Notifications\Channels\TransactionalChannelsInterface;
use Reinder83\BinaryFlags\Bits;
/**
* Class Job
* @package Products\NotificationsBundle\Entity
*
* @ORM\Entity(
* repositoryClass = "Products\NotificationsBundle\Doctrine\Repository\JobRepository",
* )
* @ORM\Table(
* name = "notis__job",
* )
*/
class Job extends TenantedEntity
{
public const STATUSES__NEW = 0;
public const STATUSES__READY = Bits::BIT_1;
public const STATUSES__RUNNING = Bits::BIT_2;
public const STATUSES__COMPLETE = Bits::BIT_3;
public const STATUSES__ERROR = ~0;
public const STATUSES__ABORTED = ~Bits::BIT_1;
use Channels\AppChannelTrait;
use Channels\EmailChannelTrait;
use Channels\FacebookChannelTrait;
use Channels\InstagramChannelTrait;
use Channels\SmsChannelTrait;
use Channels\TwitterChannelTrait;
use Channels\VoiceChannelTrait;
use Channels\WebsiteChannelTrait;
/**
* @var array
*/
protected array $summaries = [];
/**
* The message that this job is for.
*
* @var AbstractNotification|null
*
* @ORM\ManyToOne(
* targetEntity = "Products\NotificationsBundle\Entity\AbstractNotification",
* inversedBy = "jobs",
* )
* @ORM\JoinColumn(
* name = "notification",
* referencedColumnName = "id",
* nullable = false,
* onDelete = "CASCADE",
* )
*/
protected ?AbstractNotification $notification = null;
/**
* @var int|null
*
* @ORM\Column(
* type = "bigint",
* nullable = false,
* options = {
* "default" = self::STATUSES__NEW,
* },
* )
*/
protected ?int $status = self::STATUSES__NEW;
/**
* Used to track progress of the sending process.
* This number reflects the number of outstanding MQ messages that are related to this job.
* This number should ALWAYS be updated atomically via methods like bulk UPDATE queries.
*
* @var int
*
* @ORM\Column(
* type = "integer",
* nullable = false,
* options = {
* "default" = 0,
* },
* )
*/
protected int $semaphore = 0;
/**
* Bitmask to track which channels have been triggered for this job.
* Can be used as an easy way to debug issues.
*
* @var int
*
* @ORM\Column(
* type = "bigint",
* nullable = false,
* options = {
* "default" = 0,
* },
* )
*/
protected int $channels = 0;
/**
* Tracks how many total posts have been attempted to the services such as social media, website, etc.
*
* @var int
*
* @ORM\Column(
* type = "integer",
* nullable = false,
* options = {
* "default" = 0,
* },
* )
*/
protected int $servicesTotal = 0;
/**
* Tracks how many service posts are currently being processed by the workers.
*
* @var int
*
* @ORM\Column(
* type = "integer",
* nullable = false,
* options = {
* "default" = 0,
* },
* )
*/
protected int $servicesProcessing = 0;
/**
* Tracks how many service posts were successful.
*
* @var int
*
* @ORM\Column(
* type = "integer",
* nullable = false,
* options = {
* "default" = 0,
* },
* )
*/
protected int $servicesSucceeded = 0;
/**
* Tracks how many service posts failed.
*
* @var int
*
* @ORM\Column(
* type = "integer",
* nullable = false,
* options = {
* "default" = 0,
* },
* )
*/
protected int $servicesFailed = 0;
/**
* General rules about the "messages" counters that follow.
* These rules also apply to the transaction channel counters in the traits.
*
* 1. No single value should be greater than total.
* 2. The sum of processing, promised, succeeded, and failed should always be less than or equal to total.
*/
/**
* Tracks how many total transactional messages have been attempted to the various contacts.
*
* @var int
*
* @ORM\Column(
* type = "integer",
* nullable = false,
* options = {
* "default" = 0,
* },
* )
*/
protected int $messagesTotal = 0;
/**
* Tracks how many messages are currently being processed by workers.
*
* @var int
*
* @ORM\Column(
* type = "integer",
* nullable = false,
* options = {
* "default" = 0,
* },
* )
*/
protected int $messagesProcessing = 0;
/**
* Tracks the number of successful transactional messages.
* Note that this does not necessarily detect whether the message was actually deliverable.
* This just simply tracks if the API calls to send the messages were successful.
*
* @var int
*
* @ORM\Column(
* type = "integer",
* nullable = false,
* options = {
* "default" = 0,
* },
* )
*/
protected int $messagesSucceeded = 0;
/**
* Tracks the number of successful transactional messages.
* Note that this does not necessarily detect whether the message was actually undeliverable.
* This just simply tracks if the API calls to send the messages failed.
*
* @var int
*
* @ORM\Column(
* type = "integer",
* nullable = false,
* options = {
* "default" = 0,
* },
* )
*/
protected int $messagesFailed = 0;
/**
* Tracks the number of delivered transactional messages.
* This tracker should NOT be updated during the sending process itself.
* As webhooks roll in, this tracker should be updated properly.
*
* @var int
*
* @ORM\Column(
* type = "integer",
* nullable = false,
* options = {
* "default" = 0,
* },
* )
*/
protected int $messagesDelivered = 0;
/**
* Tracks the number of undelivered transactional messages.
* This tracker should NOT be updated during the sending process itself.
* As webhooks roll in, this tracker should be updated properly.
*
* @var int
*
* @ORM\Column(
* type = "integer",
* nullable = false,
* options = {
* "default" = 0,
* },
* )
*/
protected int $messagesUndelivered = 0;
/**
* @var DateTime|null
*
* @ORM\Column(
* type = "datetime",
* nullable = true,
* )
*/
protected ?DateTime $firstActivityAt = null;
/**
* @var DateTime|null
*
* @ORM\Column(
* type = "datetime",
* nullable = true,
* )
*/
protected ?DateTime $lastActivityAt = null;
/**
* @var DateTime|null
*
* @ORM\Column(
* type = "datetime",
* nullable = true,
* )
*/
protected ?DateTime $lastDeliveryAt = null;
/**
* @var array|null
*
* @ORM\Column(
* type = "json",
* nullable = true,
* )
*/
protected ?array $error = null;
/**
* @var int
*
* @ORM\Column(
* type = "integer",
* nullable = false,
* options = {
* "default" = 0,
* },
* )
*/
protected int $errorCount = 0;
/**
* @return int|null
*/
public function getStatus(): ?int
{
return $this->status;
}
/**
* @param int $status
* @return $this
*/
public function setStatus(int $status): self
{
$this->status = $status;
return $this;
}
/**
* @return AbstractNotification|null
* @deprecated
*/
public function getMessage(): ?AbstractNotification
{
return $this->getNotification();
}
/**
* @return AbstractNotification|null
*/
public function getNotification(): ?AbstractNotification
{
return $this->notification;
}
/**
* @param AbstractNotification $notification
* @return $this
*/
public function setNotification(AbstractNotification $notification): self
{
$this->notification = $notification;
return $this;
}
/**
* @return bool
*/
public function hasChannels(): bool
{
return ($this->getChannels() !== 0);
}
/**
* @param int $channel
* @return bool
*/
public function hasChannel(int $channel): bool
{
return (($this->getChannels() & $channel) === $channel);
}
/**
* @return int
*/
public function getChannels(): int
{
return $this->channels;
}
/**
* @return array
*/
public function getProcessedChannels(): array
{
return array_filter(array_map(
function (int $channel) {
if ( ! $this->hasChannel($channel)) {
return null;
}
return $channel;
},
ChannelsInterface::USABLE_CHANNELS
));
}
/**
* @return array
*/
public function getUnprocessedChannels(): array
{
return array_filter(array_map(
function (int $channel) {
if ($this->hasChannel($channel)) {
return null;
}
return $channel;
},
ChannelsInterface::USABLE_CHANNELS
));
}
/**
* @return int
*/
public function getSemaphore(): int
{
return $this->semaphore;
}
/**
* @return int
*/
public function getServicesTotal(): int
{
return $this->servicesTotal;
}
/**
* @return int
*/
public function getServicesProcessing(): int
{
return $this->servicesProcessing;
}
/**
* @return int
*/
public function getServicesSucceeded(): int
{
return $this->servicesSucceeded;
}
/**
* @return int
*/
public function getServicesFailed(): int
{
return $this->servicesFailed;
}
/**
* @return int
*/
public function getMessagesTotal(): int
{
return $this->messagesTotal;
}
/**
* @return int
*/
public function getMessagesProcessing(): int
{
return $this->messagesProcessing;
}
/**
* @return int
*/
public function getMessagesSucceeded(): int
{
return $this->messagesSucceeded;
}
/**
* @return int
*/
public function getMessagesFailed(): int
{
return $this->messagesFailed;
}
/**
* @return int
*/
public function getMessagesDelivered(): int
{
return $this->messagesDelivered;
}
/**
* @return int
*/
public function getMessagesUndelivered(): int
{
return $this->messagesUndelivered;
}
/**
* @return DateTime|null
*/
public function getFirstActivityAt(): ?DateTime
{
return $this->firstActivityAt;
}
/**
* @return DateTime|null
*/
public function getLastActivityAt(): ?DateTime
{
return $this->lastActivityAt;
}
/**
* @return DateTime|null
*/
public function getLastDeliveryAt(): ?DateTime
{
return $this->lastDeliveryAt;
}
/**
* @return int|null
*/
public function getActivitySpan(): ?int
{
if ( ! $this->getFirstActivityAt()) {
return null;
}
return ($this->getLastActivityAt() ?? new DateTime())->getTimestamp() - $this->getFirstActivityAt()->getTimestamp();
}
/**
* @return DateInterval
*/
public function getActivityInterval(): DateInterval
{
if ( ! $this->getFirstActivityAt()) {
return new DateInterval('PT0S');
}
return ($this->getLastActivityAt() ?? new DateTime())->diff($this->getFirstActivityAt());
}
/**
* @return array|null
*/
public function getError(): ?array
{
return $this->error;
}
/**
* @return int
*/
public function getErrorCount(): int
{
return $this->errorCount;
}
/**
* @return array
*/
public function summarize(): array
{
if ( ! array_key_exists(__FUNCTION__, $this->summaries)) {
$this->summaries[__FUNCTION__] = [
'job' => [
'semaphore' => $this->getSemaphore(),
'mps' => ( ! $this->getActivitySpan()) ? $this->getMessagesTotal() : (( ! is_nan($this->getMessagesTotal() / $this->getActivitySpan())) ? ($this->getMessagesTotal() / $this->getActivitySpan()) : 0),
'activity_span' => $this->getActivitySpan(),
'first_activity' => $this->getFirstActivityAt() ? $this->getFirstActivityAt()->format('Y-m-d H:i:s') : null,
'last_activity' => $this->getLastActivityAt() ? $this->getLastActivityAt()->format('Y-m-d H:i:s') : null,
'last_delivery' => $this->getLastDeliveryAt() ? $this->getLastDeliveryAt()->format('Y-m-d H:i:s') : null,
'error' => $this->getError(),
],
'all' => [
'total' => $this->getServicesTotal() + $this->getMessagesTotal(),
'processing' => $this->getServicesProcessing() + $this->getMessagesProcessing(),
'succeeded' => $this->getServicesSucceeded() + $this->getMessagesSucceeded(),
'failed' => $this->getServicesFailed() + $this->getMessagesFailed(),
'delivered' => $this->getServicesSucceeded() + $this->getMessagesDelivered(),
'undelivered' => $this->getServicesFailed() + $this->getMessagesUndelivered(),
],
'services' => [
'total' => $this->getServicesTotal(),
'processing' => $this->getServicesProcessing(),
'succeeded' => $this->getServicesSucceeded(),
'failed' => $this->getServicesFailed(),
],
'messages' => [
'total' => $this->getMessagesTotal(),
'processing' => $this->getMessagesProcessing(),
'succeeded' => $this->getMessagesSucceeded(),
'failed' => $this->getMessagesFailed(),
'delivered' => $this->getMessagesDelivered(),
'undelivered' => $this->getMessagesUndelivered(),
],
'channels' => array_combine(
array_keys(ChannelsInterface::USABLE_CHANNELS),
array_map(
function (string $channel) {
$func = sprintf(
'summarize%s',
ucfirst($channel)
);
return $this->$func();
},
array_keys(ChannelsInterface::USABLE_CHANNELS)
)
),
'service_channels' => array_combine(
array_keys(ServiceChannelsInterface::SERVICE_CHANNELS),
array_map(
function (string $channel) {
$func = sprintf(
'summarize%s',
ucfirst($channel)
);
return $this->$func();
},
array_keys(ServiceChannelsInterface::SERVICE_CHANNELS)
)
),
'message_channels' => array_combine(
array_keys(TransactionalChannelsInterface::TRANSACTIONAL_CHANNELS),
array_map(
function (string $channel) {
$func = sprintf(
'summarize%s',
ucfirst($channel)
);
return $this->$func();
},
array_keys(TransactionalChannelsInterface::TRANSACTIONAL_CHANNELS)
)
),
];
}
return $this->summaries[__FUNCTION__];
}
}