src/Products/NotificationsBundle/Entity/Job.php line 26

Open in your IDE?
  1. <?php
  2. namespace Products\NotificationsBundle\Entity;
  3. use Cms\TenantBundle\Entity\TenantedEntity;
  4. use DateInterval;
  5. use DateTime;
  6. use Doctrine\ORM\Mapping as ORM;
  7. use Products\NotificationsBundle\Entity\Jobs\Channels;
  8. use Products\NotificationsBundle\Entity\Notifications\Channels\ChannelsInterface;
  9. use Products\NotificationsBundle\Entity\Notifications\Channels\ServiceChannelsInterface;
  10. use Products\NotificationsBundle\Entity\Notifications\Channels\TransactionalChannelsInterface;
  11. use Reinder83\BinaryFlags\Bits;
  12. /**
  13.  * Class Job
  14.  * @package Products\NotificationsBundle\Entity
  15.  *
  16.  * @ORM\Entity(
  17.  *     repositoryClass = "Products\NotificationsBundle\Doctrine\Repository\JobRepository",
  18.  * )
  19.  * @ORM\Table(
  20.  *     name = "notis__job",
  21.  * )
  22.  */
  23. class Job extends TenantedEntity
  24. {
  25.     public const STATUSES__NEW 0;
  26.     public const STATUSES__READY Bits::BIT_1;
  27.     public const STATUSES__RUNNING Bits::BIT_2;
  28.     public const STATUSES__COMPLETE Bits::BIT_3;
  29.     public const STATUSES__ERROR = ~0;
  30.     public const STATUSES__ABORTED = ~Bits::BIT_1;
  31.     use Channels\AppChannelTrait;
  32.     use Channels\EmailChannelTrait;
  33.     use Channels\FacebookChannelTrait;
  34.     use Channels\InstagramChannelTrait;
  35.     use Channels\SmsChannelTrait;
  36.     use Channels\TwitterChannelTrait;
  37.     use Channels\VoiceChannelTrait;
  38.     use Channels\WebsiteChannelTrait;
  39.     /**
  40.      * @var array
  41.      */
  42.     protected array $summaries = [];
  43.     /**
  44.      * The message that this job is for.
  45.      *
  46.      * @var AbstractNotification|null
  47.      *
  48.      * @ORM\ManyToOne(
  49.      *     targetEntity = "Products\NotificationsBundle\Entity\AbstractNotification",
  50.      *     inversedBy = "jobs",
  51.      * )
  52.      * @ORM\JoinColumn(
  53.      *     name = "notification",
  54.      *     referencedColumnName = "id",
  55.      *     nullable = false,
  56.      *     onDelete = "CASCADE",
  57.      * )
  58.      */
  59.     protected ?AbstractNotification $notification null;
  60.     /**
  61.      * @var int|null
  62.      *
  63.      * @ORM\Column(
  64.      *     type = "bigint",
  65.      *     nullable = false,
  66.      *     options = {
  67.      *         "default" = self::STATUSES__NEW,
  68.      *     },
  69.      * )
  70.      */
  71.     protected ?int $status self::STATUSES__NEW;
  72.     /**
  73.      * Used to track progress of the sending process.
  74.      * This number reflects the number of outstanding MQ messages that are related to this job.
  75.      * This number should ALWAYS be updated atomically via methods like bulk UPDATE queries.
  76.      *
  77.      * @var int
  78.      *
  79.      * @ORM\Column(
  80.      *     type = "integer",
  81.      *     nullable = false,
  82.      *     options = {
  83.      *         "default" = 0,
  84.      *     },
  85.      * )
  86.      */
  87.     protected int $semaphore 0;
  88.     /**
  89.      * Bitmask to track which channels have been triggered for this job.
  90.      * Can be used as an easy way to debug issues.
  91.      *
  92.      * @var int
  93.      *
  94.      * @ORM\Column(
  95.      *     type = "bigint",
  96.      *     nullable = false,
  97.      *     options = {
  98.      *         "default" = 0,
  99.      *     },
  100.      * )
  101.      */
  102.     protected int $channels 0;
  103.     /**
  104.      * Tracks how many total posts have been attempted to the services such as social media, website, etc.
  105.      *
  106.      * @var int
  107.      *
  108.      * @ORM\Column(
  109.      *     type = "integer",
  110.      *     nullable = false,
  111.      *     options = {
  112.      *         "default" = 0,
  113.      *     },
  114.      * )
  115.      */
  116.     protected int $servicesTotal 0;
  117.     /**
  118.      * Tracks how many service posts are currently being processed by the workers.
  119.      *
  120.      * @var int
  121.      *
  122.      * @ORM\Column(
  123.      *     type = "integer",
  124.      *     nullable = false,
  125.      *     options = {
  126.      *         "default" = 0,
  127.      *     },
  128.      * )
  129.      */
  130.     protected int $servicesProcessing 0;
  131.     /**
  132.      * Tracks how many service posts were successful.
  133.      *
  134.      * @var int
  135.      *
  136.      * @ORM\Column(
  137.      *     type = "integer",
  138.      *     nullable = false,
  139.      *     options = {
  140.      *         "default" = 0,
  141.      *     },
  142.      * )
  143.      */
  144.     protected int $servicesSucceeded 0;
  145.     /**
  146.      * Tracks how many service posts failed.
  147.      *
  148.      * @var int
  149.      *
  150.      * @ORM\Column(
  151.      *     type = "integer",
  152.      *     nullable = false,
  153.      *     options = {
  154.      *         "default" = 0,
  155.      *     },
  156.      * )
  157.      */
  158.     protected int $servicesFailed 0;
  159.     /**
  160.      * General rules about the "messages" counters that follow.
  161.      * These rules also apply to the transaction channel counters in the traits.
  162.      *
  163.      * 1. No single value should be greater than total.
  164.      * 2. The sum of processing, promised, succeeded, and failed should always be less than or equal to total.
  165.      */
  166.     /**
  167.      * Tracks how many total transactional messages have been attempted to the various contacts.
  168.      *
  169.      * @var int
  170.      *
  171.      * @ORM\Column(
  172.      *     type = "integer",
  173.      *     nullable = false,
  174.      *     options = {
  175.      *         "default" = 0,
  176.      *     },
  177.      * )
  178.      */
  179.     protected int $messagesTotal 0;
  180.     /**
  181.      * Tracks how many messages are currently being processed by workers.
  182.      *
  183.      * @var int
  184.      *
  185.      * @ORM\Column(
  186.      *     type = "integer",
  187.      *     nullable = false,
  188.      *     options = {
  189.      *         "default" = 0,
  190.      *     },
  191.      * )
  192.      */
  193.     protected int $messagesProcessing 0;
  194.     /**
  195.      * Tracks the number of successful transactional messages.
  196.      * Note that this does not necessarily detect whether the message was actually deliverable.
  197.      * This just simply tracks if the API calls to send the messages were successful.
  198.      *
  199.      * @var int
  200.      *
  201.      * @ORM\Column(
  202.      *     type = "integer",
  203.      *     nullable = false,
  204.      *     options = {
  205.      *         "default" = 0,
  206.      *     },
  207.      * )
  208.      */
  209.     protected int $messagesSucceeded 0;
  210.     /**
  211.      * Tracks the number of successful transactional messages.
  212.      * Note that this does not necessarily detect whether the message was actually undeliverable.
  213.      * This just simply tracks if the API calls to send the messages failed.
  214.      *
  215.      * @var int
  216.      *
  217.      * @ORM\Column(
  218.      *     type = "integer",
  219.      *     nullable = false,
  220.      *     options = {
  221.      *         "default" = 0,
  222.      *     },
  223.      * )
  224.      */
  225.     protected int $messagesFailed 0;
  226.     /**
  227.      * Tracks the number of delivered transactional messages.
  228.      * This tracker should NOT be updated during the sending process itself.
  229.      * As webhooks roll in, this tracker should be updated properly.
  230.      *
  231.      * @var int
  232.      *
  233.      * @ORM\Column(
  234.      *     type = "integer",
  235.      *     nullable = false,
  236.      *     options = {
  237.      *         "default" = 0,
  238.      *     },
  239.      * )
  240.      */
  241.     protected int $messagesDelivered 0;
  242.     /**
  243.      * Tracks the number of undelivered transactional messages.
  244.      * This tracker should NOT be updated during the sending process itself.
  245.      * As webhooks roll in, this tracker should be updated properly.
  246.      *
  247.      * @var int
  248.      *
  249.      * @ORM\Column(
  250.      *     type = "integer",
  251.      *     nullable = false,
  252.      *     options = {
  253.      *         "default" = 0,
  254.      *     },
  255.      * )
  256.      */
  257.     protected int $messagesUndelivered 0;
  258.     /**
  259.      * @var DateTime|null
  260.      *
  261.      * @ORM\Column(
  262.      *     type = "datetime",
  263.      *     nullable = true,
  264.      * )
  265.      */
  266.     protected ?DateTime $firstActivityAt null;
  267.     /**
  268.      * @var DateTime|null
  269.      *
  270.      * @ORM\Column(
  271.      *     type = "datetime",
  272.      *     nullable = true,
  273.      * )
  274.      */
  275.     protected ?DateTime $lastActivityAt null;
  276.     /**
  277.      * @var DateTime|null
  278.      *
  279.      * @ORM\Column(
  280.      *     type = "datetime",
  281.      *     nullable = true,
  282.      * )
  283.      */
  284.     protected ?DateTime $lastDeliveryAt null;
  285.     /**
  286.      * @var array|null
  287.      *
  288.      * @ORM\Column(
  289.      *     type = "json",
  290.      *     nullable = true,
  291.      * )
  292.      */
  293.     protected ?array $error null;
  294.     /**
  295.      * @var int
  296.      *
  297.      * @ORM\Column(
  298.      *     type = "integer",
  299.      *     nullable = false,
  300.      *     options = {
  301.      *         "default" = 0,
  302.      *     },
  303.      * )
  304.      */
  305.     protected int $errorCount 0;
  306.     /**
  307.      * @return int|null
  308.      */
  309.     public function getStatus(): ?int
  310.     {
  311.         return $this->status;
  312.     }
  313.     /**
  314.      * @param int $status
  315.      * @return $this
  316.      */
  317.     public function setStatus(int $status): self
  318.     {
  319.         $this->status $status;
  320.         return $this;
  321.     }
  322.     /**
  323.      * @return AbstractNotification|null
  324.      * @deprecated
  325.      */
  326.     public function getMessage(): ?AbstractNotification
  327.     {
  328.         return $this->getNotification();
  329.     }
  330.     /**
  331.      * @return AbstractNotification|null
  332.      */
  333.     public function getNotification(): ?AbstractNotification
  334.     {
  335.         return $this->notification;
  336.     }
  337.     /**
  338.      * @param AbstractNotification $notification
  339.      * @return $this
  340.      */
  341.     public function setNotification(AbstractNotification $notification): self
  342.     {
  343.         $this->notification $notification;
  344.         return $this;
  345.     }
  346.     /**
  347.      * @return bool
  348.      */
  349.     public function hasChannels(): bool
  350.     {
  351.         return ($this->getChannels() !== 0);
  352.     }
  353.     /**
  354.      * @param int $channel
  355.      * @return bool
  356.      */
  357.     public function hasChannel(int $channel): bool
  358.     {
  359.         return (($this->getChannels() & $channel) === $channel);
  360.     }
  361.     /**
  362.      * @return int
  363.      */
  364.     public function getChannels(): int
  365.     {
  366.         return $this->channels;
  367.     }
  368.     /**
  369.      * @return array
  370.      */
  371.     public function getProcessedChannels(): array
  372.     {
  373.         return array_filter(array_map(
  374.             function (int $channel) {
  375.                 if ( ! $this->hasChannel($channel)) {
  376.                     return null;
  377.                 }
  378.                 return $channel;
  379.             },
  380.             ChannelsInterface::USABLE_CHANNELS
  381.         ));
  382.     }
  383.     /**
  384.      * @return array
  385.      */
  386.     public function getUnprocessedChannels(): array
  387.     {
  388.         return array_filter(array_map(
  389.             function (int $channel) {
  390.                 if ($this->hasChannel($channel)) {
  391.                     return null;
  392.                 }
  393.                 return $channel;
  394.             },
  395.             ChannelsInterface::USABLE_CHANNELS
  396.         ));
  397.     }
  398.     /**
  399.      * @return int
  400.      */
  401.     public function getSemaphore(): int
  402.     {
  403.         return $this->semaphore;
  404.     }
  405.     /**
  406.      * @return int
  407.      */
  408.     public function getServicesTotal(): int
  409.     {
  410.         return $this->servicesTotal;
  411.     }
  412.     /**
  413.      * @return int
  414.      */
  415.     public function getServicesProcessing(): int
  416.     {
  417.         return $this->servicesProcessing;
  418.     }
  419.     /**
  420.      * @return int
  421.      */
  422.     public function getServicesSucceeded(): int
  423.     {
  424.         return $this->servicesSucceeded;
  425.     }
  426.     /**
  427.      * @return int
  428.      */
  429.     public function getServicesFailed(): int
  430.     {
  431.         return $this->servicesFailed;
  432.     }
  433.     /**
  434.      * @return int
  435.      */
  436.     public function getMessagesTotal(): int
  437.     {
  438.         return $this->messagesTotal;
  439.     }
  440.     /**
  441.      * @return int
  442.      */
  443.     public function getMessagesProcessing(): int
  444.     {
  445.         return $this->messagesProcessing;
  446.     }
  447.     /**
  448.      * @return int
  449.      */
  450.     public function getMessagesSucceeded(): int
  451.     {
  452.         return $this->messagesSucceeded;
  453.     }
  454.     /**
  455.      * @return int
  456.      */
  457.     public function getMessagesFailed(): int
  458.     {
  459.         return $this->messagesFailed;
  460.     }
  461.     /**
  462.      * @return int
  463.      */
  464.     public function getMessagesDelivered(): int
  465.     {
  466.         return $this->messagesDelivered;
  467.     }
  468.     /**
  469.      * @return int
  470.      */
  471.     public function getMessagesUndelivered(): int
  472.     {
  473.         return $this->messagesUndelivered;
  474.     }
  475.     /**
  476.      * @return DateTime|null
  477.      */
  478.     public function getFirstActivityAt(): ?DateTime
  479.     {
  480.         return $this->firstActivityAt;
  481.     }
  482.     /**
  483.      * @return DateTime|null
  484.      */
  485.     public function getLastActivityAt(): ?DateTime
  486.     {
  487.         return $this->lastActivityAt;
  488.     }
  489.     /**
  490.      * @return DateTime|null
  491.      */
  492.     public function getLastDeliveryAt(): ?DateTime
  493.     {
  494.         return $this->lastDeliveryAt;
  495.     }
  496.     /**
  497.      * @return int|null
  498.      */
  499.     public function getActivitySpan(): ?int
  500.     {
  501.         if ( ! $this->getFirstActivityAt()) {
  502.             return null;
  503.         }
  504.         return ($this->getLastActivityAt() ?? new DateTime())->getTimestamp() - $this->getFirstActivityAt()->getTimestamp();
  505.     }
  506.     /**
  507.      * @return DateInterval
  508.      */
  509.     public function getActivityInterval(): DateInterval
  510.     {
  511.         if ( ! $this->getFirstActivityAt()) {
  512.             return new DateInterval('PT0S');
  513.         }
  514.         return ($this->getLastActivityAt() ?? new DateTime())->diff($this->getFirstActivityAt());
  515.     }
  516.     /**
  517.      * @return array|null
  518.      */
  519.     public function getError(): ?array
  520.     {
  521.         return $this->error;
  522.     }
  523.     /**
  524.      * @return int
  525.      */
  526.     public function getErrorCount(): int
  527.     {
  528.         return $this->errorCount;
  529.     }
  530.     /**
  531.      * @return array
  532.      */
  533.     public function summarize(): array
  534.     {
  535.         if ( ! array_key_exists(__FUNCTION__$this->summaries)) {
  536.             $this->summaries[__FUNCTION__] = [
  537.                 'job' => [
  538.                     'semaphore' => $this->getSemaphore(),
  539.                     'mps' => ( ! $this->getActivitySpan()) ? $this->getMessagesTotal() : (( ! is_nan($this->getMessagesTotal() / $this->getActivitySpan())) ? ($this->getMessagesTotal() / $this->getActivitySpan()) : 0),
  540.                     'activity_span' => $this->getActivitySpan(),
  541.                     'first_activity' => $this->getFirstActivityAt() ? $this->getFirstActivityAt()->format('Y-m-d H:i:s') : null,
  542.                     'last_activity' => $this->getLastActivityAt() ? $this->getLastActivityAt()->format('Y-m-d H:i:s') : null,
  543.                     'last_delivery' => $this->getLastDeliveryAt() ? $this->getLastDeliveryAt()->format('Y-m-d H:i:s') : null,
  544.                     'error' => $this->getError(),
  545.                 ],
  546.                 'all' => [
  547.                     'total' => $this->getServicesTotal() + $this->getMessagesTotal(),
  548.                     'processing' => $this->getServicesProcessing() + $this->getMessagesProcessing(),
  549.                     'succeeded' => $this->getServicesSucceeded() + $this->getMessagesSucceeded(),
  550.                     'failed' => $this->getServicesFailed() + $this->getMessagesFailed(),
  551.                     'delivered' => $this->getServicesSucceeded() + $this->getMessagesDelivered(),
  552.                     'undelivered' => $this->getServicesFailed() + $this->getMessagesUndelivered(),
  553.                 ],
  554.                 'services' => [
  555.                     'total' => $this->getServicesTotal(),
  556.                     'processing' => $this->getServicesProcessing(),
  557.                     'succeeded' => $this->getServicesSucceeded(),
  558.                     'failed' => $this->getServicesFailed(),
  559.                 ],
  560.                 'messages' => [
  561.                     'total' => $this->getMessagesTotal(),
  562.                     'processing' => $this->getMessagesProcessing(),
  563.                     'succeeded' => $this->getMessagesSucceeded(),
  564.                     'failed' => $this->getMessagesFailed(),
  565.                     'delivered' => $this->getMessagesDelivered(),
  566.                     'undelivered' => $this->getMessagesUndelivered(),
  567.                 ],
  568.                 'channels' => array_combine(
  569.                     array_keys(ChannelsInterface::USABLE_CHANNELS),
  570.                     array_map(
  571.                         function (string $channel) {
  572.                             $func sprintf(
  573.                                 'summarize%s',
  574.                                 ucfirst($channel)
  575.                             );
  576.                             return $this->$func();
  577.                         },
  578.                         array_keys(ChannelsInterface::USABLE_CHANNELS)
  579.                     )
  580.                 ),
  581.                 'service_channels' => array_combine(
  582.                     array_keys(ServiceChannelsInterface::SERVICE_CHANNELS),
  583.                     array_map(
  584.                         function (string $channel) {
  585.                             $func sprintf(
  586.                                 'summarize%s',
  587.                                 ucfirst($channel)
  588.                             );
  589.                             return $this->$func();
  590.                         },
  591.                         array_keys(ServiceChannelsInterface::SERVICE_CHANNELS)
  592.                     )
  593.                 ),
  594.                 'message_channels' => array_combine(
  595.                     array_keys(TransactionalChannelsInterface::TRANSACTIONAL_CHANNELS),
  596.                     array_map(
  597.                         function (string $channel) {
  598.                             $func sprintf(
  599.                                 'summarize%s',
  600.                                 ucfirst($channel)
  601.                             );
  602.                             return $this->$func();
  603.                         },
  604.                         array_keys(TransactionalChannelsInterface::TRANSACTIONAL_CHANNELS)
  605.                     )
  606.                 ),
  607.             ];
  608.         }
  609.         return $this->summaries[__FUNCTION__];
  610.     }
  611. }