Completed
Push — master ( 45301f...e76351 )
by Joachim
07:16
created

CallWebhooksCommand::execute()   C

Complexity

Conditions 7
Paths 10

Size

Total Lines 38
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 38
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 21
nc 10
nop 2
1
<?php
2
3
namespace Loevgaard\DandomainAltapayBundle\Command;
4
5
use GuzzleHttp\Client;
6
use GuzzleHttp\Exception\TransferException;
7
use GuzzleHttp\RequestOptions;
8
use JMS\Serializer\SerializerInterface;
9
use Loevgaard\DandomainAltapayBundle\Entity\Event;
10
use Loevgaard\DandomainAltapayBundle\Entity\EventRepository;
11
use Loevgaard\DandomainAltapayBundle\Entity\WebhookExchange;
12
use Loevgaard\DandomainAltapayBundle\Entity\WebhookExchangeRepository;
13
use Psr\Log\LoggerInterface;
14
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
15
use Symfony\Component\Console\Input\InputInterface;
16
use Symfony\Component\Console\Output\OutputInterface;
17
18
class CallWebhooksCommand extends ContainerAwareCommand
19
{
20
    /**
21
     * @var WebhookExchangeRepository
22
     */
23
    private $webhookExchangeRepository;
24
25
    /**
26
     * @var EventRepository
27
     */
28
    private $eventRepository;
29
30
    /**
31
     * @var SerializerInterface
32
     */
33
    private $serializer;
34
35
    /**
36
     * @var LoggerInterface
37
     */
38
    private $logger;
39
40
    /**
41
     * @var Client
42
     */
43
    private $httpClient;
44
45
    public function __construct(
46
        WebhookExchangeRepository $webhookExchangeRepository,
47
        EventRepository $eventRepository,
48
        SerializerInterface $serializer,
49
        LoggerInterface $logger
50
    ) {
51
        $this->webhookExchangeRepository = $webhookExchangeRepository;
52
        $this->eventRepository = $eventRepository;
53
        $this->serializer = $serializer;
54
        $this->logger = $logger;
55
56
        parent::__construct();
57
    }
58
59
    protected function configure()
60
    {
61
        $this->setName('loevgaard:dandomain:altapay:call-webhooks')
62
            ->setDescription('Will call all the respective webhook URLs')
63
        ;
64
    }
65
66
    protected function execute(InputInterface $input, OutputInterface $output)
67
    {
68
        $maxCallsPerRun = 50;
69
70
        $webhookUrls = $this->getContainer()->getParameter('loevgaard_dandomain_altapay.webhook_urls');
71
        if(empty($webhookUrls)) {
72
            $output->writeln('No webhook URLs defined');
73
            return 0;
74
        }
75
76
        /** @var WebhookExchange[] $webhookExchanges */
77
        $webhookExchanges = [];
78
79
        foreach ($webhookUrls as $webhookUrl) {
80
            $webhookExchange = $this->webhookExchangeRepository->findByUrl($webhookUrl);
81
            if(!$webhookExchange) {
82
                $webhookExchange = new WebhookExchange($webhookUrl);
83
                $this->webhookExchangeRepository->save($webhookExchange);
84
            }
85
86
            $webhookExchanges[] = $webhookExchange;
87
        }
88
89
        $eventsPerExchange = ceil($maxCallsPerRun / count($webhookExchanges));
90
91
        foreach ($webhookExchanges as $webhookExchange) {
92
            $events = $this->eventRepository->findRecentEvents($webhookExchange->getLastEventId(), $eventsPerExchange);
93
94
            foreach ($events as $event) {
0 ignored issues
show
Bug introduced by
The expression $events of type array<integer,object<Loe...dle\Entity\Event>>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
95
                if(!$this->callWebhook($webhookExchange->getUrl(), $event)) {
96
                    break;
97
                }
98
99
                $webhookExchange->setLastEventId($event->getId());
100
                $this->webhookExchangeRepository->flush();
101
            }
102
        }
103
    }
104
105
    /**
106
     * @param string $url
107
     * @param Event $event
108
     *
109
     * @return bool
110
     */
111
    private function callWebhook(string $url, Event $event) : bool
112
    {
113
        if(!$this->httpClient) {
114
            $this->httpClient = new Client([
115
                RequestOptions::ALLOW_REDIRECTS => false,
116
                RequestOptions::CONNECT_TIMEOUT => 15,
117
                RequestOptions::TIMEOUT => 30,
118
                RequestOptions::HTTP_ERRORS => false,
119
                RequestOptions::HEADERS => [
120
                    'Content-Type' => 'application/json'
121
                ]
122
            ]);
123
        }
124
125
        $serializedEvent = $this->serializer->serialize($event, 'json');
126
127
        try {
128
            $response = $this->httpClient->post($url, [
129
                'body' => $serializedEvent
130
            ]);
131
        } catch (TransferException $e) {
132
            $this->logger->error('Webhook URL ('.$url.') returned an error: '.$e->getMessage(), [
133
                'event' => $serializedEvent
134
            ]);
135
136
            return false;
137
        }
138
139
        if($response->getStatusCode() !== 200) {
140
            $this->logger->error('Webhook URL ('.$url.') returned status code: '.$response->getStatusCode(), [
141
                'event' => $serializedEvent
142
            ]);
143
144
            return false;
145
        }
146
147
        return true;
148
    }
149
}
150