Completed
Pull Request — master (#267)
by Guilherme
04:53 queued 23s
created

SmsStatusService::progressFinish()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of the login-cidadao project or it's bundles.
4
 *
5
 * (c) Guilherme Donato <guilhermednt on github>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace LoginCidadao\PhoneVerificationBundle\Service;
12
13
use Doctrine\ORM\EntityManagerInterface;
14
use Doctrine\ORM\Internal\Hydration\IterableResult;
15
use LoginCidadao\PhoneVerificationBundle\Entity\SentVerificationRepository;
16
use LoginCidadao\PhoneVerificationBundle\Event\UpdateStatusEvent;
17
use LoginCidadao\PhoneVerificationBundle\Model\SentVerificationInterface;
18
use LoginCidadao\PhoneVerificationBundle\Model\SmsStatusInterface;
19
use LoginCidadao\PhoneVerificationBundle\PhoneVerificationEvents;
20
use Symfony\Component\Console\Style\SymfonyStyle;
21
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
22
23
class SmsStatusService
24
{
25
    /** @var EntityManagerInterface */
26
    private $em;
27
28
    /** @var EventDispatcherInterface */
29
    private $dispatcher;
30
31
    /** @var SentVerificationRepository */
32
    private $sentVerificationRepo;
33
34
    /** @var SymfonyStyle */
35
    private $io;
36
37
    /**
38
     * SmsStatusUpdater constructor.
39
     * @param EntityManagerInterface $em
40
     * @param EventDispatcherInterface $dispatcher
41
     * @param SentVerificationRepository $sentVerificationRepo
42
     */
43 9
    public function __construct(
44
        EntityManagerInterface $em,
45
        EventDispatcherInterface $dispatcher,
46
        SentVerificationRepository $sentVerificationRepo
47
    ) {
48 9
        $this->em = $em;
49 9
        $this->dispatcher = $dispatcher;
50 9
        $this->sentVerificationRepo = $sentVerificationRepo;
51 9
    }
52
53
    /**
54
     * @param SymfonyStyle $io
55
     * @return SmsStatusService
56
     */
57 8
    public function setSymfonyStyle(SymfonyStyle $io)
58
    {
59 8
        $this->io = $io;
60
61 8
        return $this;
62
    }
63
64
    /**
65
     * @param $transactionId
66
     * @return SmsStatusInterface
67
     */
68 1
    public function getSmsStatus($transactionId)
69
    {
70 1
        $event = $this->getStatus($transactionId);
71
72 1
        return $event->getDeliveryStatus();
73
    }
74
75
    /**
76
     * @param int $batchSize
77
     * @return array
78
     */
79 5
    public function updateSentVerificationStatus($batchSize = 1)
80
    {
81 5
        $count = $this->sentVerificationRepo->countPendingUpdateSentVerification();
82
83 5
        if ($count === 0) {
84 1
            $this->comment('No messages pending update.');
85
86 1
            return [];
87
        }
88
89 4
        $this->em->getConnection() ? $this->em->getConnection()->getConfiguration()->setSQLLogger(null) : null;
90 4
        gc_enable();
91 4
        $this->progressStart($count);
92 4
        $transactionsUpdated = [];
93 4
        foreach ($this->iterateSentVerifications() as $row) {
94 3
            $transactionsUpdated[] = $this->handleSentVerificationStatusUpdate($row[0]);
95 3
            array_filter($transactionsUpdated);
96
97 3
            if ((count($transactionsUpdated) % $batchSize) === 0) {
98 3
                $this->flushAndClear();
99 3
                gc_collect_cycles();
100
            }
101 3
            $this->progressAdvance(1);
102
        }
103
104 4
        $this->flushAndClear();
105 4
        $this->progressFinish();
106
107 4
        $countUpdated = count($transactionsUpdated);
108 4
        $this->comment("Updated {$countUpdated} transactions.");
109
110 4
        if ($countUpdated === 0) {
111 1
            $this->comment("It's possible the SMS-sending service you are using doesn't implement status updates.");
112
        }
113
114 4
        return $transactionsUpdated;
115
    }
116
117
    /**
118
     * @param SentVerificationInterface $sentVerification
119
     * @return SentVerificationInterface|null
120
     */
121 3
    private function handleSentVerificationStatusUpdate(SentVerificationInterface $sentVerification)
122
    {
123
        try {
124 3
            $event = $this->getStatus($sentVerification->getTransactionId());
125 3
            if (false === $event->isUpdated()) {
126 2
                $this->progressAdvance(1);
127 2
                unset($event, $sentVerification);
128 2
                gc_collect_cycles();
129
130 2
                return null;
131
            }
132
133 1
            return $this->updateSentVerification($sentVerification, $event);
134
        } finally {
135 3
            unset($event, $sentVerification);
136
        }
137
    }
138
139
    /**
140
     * @param $amount
141
     * @return float average delivery time in seconds (abs value)
142
     */
143 3
    public function getAverageDeliveryTime($amount)
144
    {
145
        /** @var SentVerificationInterface[] $sentVerifications */
146 3
        $sentVerifications = $this->sentVerificationRepo->getLastDeliveredVerifications($amount);
147
148 3
        if (count($sentVerifications) === 0) {
149 2
            return 0;
150
        }
151
152 1
        $times = [];
153 1
        foreach ($sentVerifications as $sentVerification) {
154 1
            $times[] = abs(
155 1
                $sentVerification->getDeliveredAt()->format('U') - $sentVerification->getSentAt()->format('U')
156
            );
157
        }
158 1
        $sum = array_sum($times);
159
160 1
        $avg = $sum / count($times);
161
162 1
        return $avg;
163
    }
164
165
    /**
166
     * @param int $maxDeliverySeconds
167
     * @return array
168
     */
169 1
    public function getDelayedDeliveryTransactions($maxDeliverySeconds = 0)
170
    {
171 1
        $date = new \DateTime("-{$maxDeliverySeconds} seconds");
172 1
        $notDelivered = $this->sentVerificationRepo->getNotDeliveredSince($date);
173
174 1
        $transactions = [];
175 1
        foreach ($notDelivered as $sentVerification) {
176 1
            $transactions[] = [
177 1
                'transaction_id' => $sentVerification->getTransactionId(),
178 1
                'sent_at' => $sentVerification->getSentAt()->format('c'),
179
            ];
180
        }
181
182 1
        return $transactions;
183
    }
184
185
    /**
186
     * @param $transactionId
187
     * @return UpdateStatusEvent
188
     */
189 4
    private function getStatus($transactionId)
190
    {
191 4
        $event = new UpdateStatusEvent($transactionId);
192 4
        $this->dispatcher->dispatch(PhoneVerificationEvents::PHONE_VERIFICATION_GET_SENT_VERIFICATION_STATUS, $event);
193
194 4
        return $event;
195
    }
196
197 5
    private function comment($message)
198
    {
199 5
        if (!$this->io) {
200 1
            return;
201
        }
202 4
        $this->io->comment($message);
203 4
    }
204
205 4
    private function progressStart($max = 0)
206
    {
207 4
        if (!$this->io) {
208 1
            return;
209
        }
210 3
        $this->io->progressStart($max);
211 3
    }
212
213 3
    private function progressAdvance($step = 1)
214
    {
215 3
        if (!$this->io) {
216 1
            return;
217
        }
218 2
        $this->io->progressAdvance($step);
219 2
    }
220
221 4
    private function progressFinish()
222
    {
223 4
        if (!$this->io) {
224 1
            return;
225
        }
226 3
        $this->io->progressFinish();
227 3
    }
228
229
    /**
230
     * @return IterableResult
231
     */
232 4
    private function iterateSentVerifications()
233
    {
234 4
        return $this->sentVerificationRepo->getPendingUpdateSentVerificationQuery()->iterate();
235
    }
236
237 4
    private function flushAndClear()
238
    {
239 4
        $this->em->flush();
240 4
        $this->em->clear();
241 4
    }
242
243 1
    private function updateSentVerification(SentVerificationInterface $sentVerification, UpdateStatusEvent $event)
244
    {
245 1
        $deliveredAt = $event->getDeliveredAt();
246 1
        $sentVerification->setActuallySentAt($event->getSentAt())
247 1
            ->setDeliveredAt($deliveredAt)
248 1
            ->setFinished($deliveredAt instanceof \DateTime || $event->getDeliveryStatus()->isFinal());
249
250 1
        return $sentVerification;
251
    }
252
}
253