ReactAwareCurlFactory::getHandler()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Nopolabs;
4
5
6
use Exception;
7
use GuzzleHttp\Handler\CurlFactory;
8
use GuzzleHttp\Handler\CurlFactoryInterface;
9
use GuzzleHttp\Handler\CurlMultiHandler;
10
use GuzzleHttp\Handler\EasyHandle;
11
use Psr\Http\Message\RequestInterface;
12
use Psr\Log\LoggerInterface;
13
use Psr\Log\NullLogger;
14
use React\EventLoop\LoopInterface;
15
use React\EventLoop\Timer\TimerInterface;
16
17
class ReactAwareCurlFactory implements CurlFactoryInterface
18
{
19
    private $eventLoop;
20
    private $logger;
21
    private $factory;
22
    private $count;
23
24
    /** @var CurlMultiHandler */
25
    private $handler;
26
27
    /** @var TimerInterface|null */
28
    private $timer;
29
30
    /**
31
     * ReactAwareCurlFactory wraps an instance of CurlFactory and keeps
32
     * track of active curl handles. When there are active curl handles it starts
33
     * a periodic task on the React event loop that calls CurlMultiHandler::tick()
34
     * allowing tasks in the Guzzle task queue to be processed.
35
     * The periodic task is stopped when there are no more active curl handles
36
     * and the Guzzle task queue is empty.
37
     *
38
     * @param LoopInterface $eventLoop
39
     * @param CurlFactory $curlFactory
40
     * @param LoggerInterface|null $logger
41
     */
42
    public function __construct(
43
        LoopInterface $eventLoop,
44
        CurlFactory $curlFactory,
45
        LoggerInterface $logger = null
46
    )
47
    {
48
        $this->eventLoop = $eventLoop;
49
        $this->factory = $curlFactory;
50
        $this->logger = $logger ?? new NullLogger();
51
52
        $this->count = 0;
53
    }
54
55
    public function setHandler(CurlMultiHandler $handler)
56
    {
57
        $this->handler = $handler;
58
    }
59
60
    public function getHandler() : CurlMultiHandler
61
    {
62
        return $this->handler;
63
    }
64
65
    /**
66
     * {@inheritDoc}
67
     */
68
    public function create(RequestInterface $request, array $options)
69
    {
70
        $this->incrementCount();
71
72
        return $this->factory->create($request, $options);
73
    }
74
75
    /**
76
     * {@inheritDoc}
77
     */
78
    public function release(EasyHandle $easy)
79
    {
80
        $this->factory->release($easy);
81
82
        $this->decrementCount();
83
    }
84
85
    public function tick()
86
    {
87
        try {
88
            $this->getHandler()->tick();
89
        } catch (Exception $exception) {
90
            $this->logger->warning('ReactAwareCurlFactory::tick() '.$exception->getMessage());
91
        }
92
93
        if ($this->noMoreWork()) {
94
            $this->stopTimer();
95
        }
96
    }
97
98
    public function isTimerActive() : bool
99
    {
100
        if ($this->timer) {
101
            return $this->timer->isActive();
102
        }
103
104
        return false;
105
    }
106
107
    public function noMoreWork() : bool
108
    {
109
        return $this->noActiveHandles() && $this->queueIsEmpty();
110
    }
111
112
    protected function noActiveHandles() : bool
113
    {
114
        return $this->count === 0;
115
    }
116
117
    protected function queueIsEmpty() : bool
118
    {
119
        return \GuzzleHttp\Promise\queue()->isEmpty();
120
    }
121
122
    protected function incrementCount()
123
    {
124
        if ($this->noActiveHandles()) {
125
            $this->startTimer();
126
        }
127
128
        $this->count++;
129
    }
130
131
    protected function decrementCount()
132
    {
133
        $this->count--;
134
    }
135
136
    protected function startTimer()
137
    {
138
        if ($this->timer === null) {
139
            $this->timer = $this->eventLoop->addPeriodicTimer(0, [$this, 'tick']);
140
141
            $this->logger->debug('ReactAwareCurlFactory started periodic queue processing');
142
        }
143
    }
144
145
    protected function stopTimer()
146
    {
147
        if ($this->timer !== null) {
148
            $this->timer->cancel();
149
            $this->timer = null;
150
151
            $this->logger->debug('ReactAwareCurlFactory stopped periodic queue processing');
152
        }
153
    }
154
}
155