CurlMulti::__construct()   A
last analyzed

Complexity

Conditions 4
Paths 2

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 14
cts 14
cp 1
rs 9.568
c 0
b 0
f 0
cc 4
nc 2
nop 1
crap 4
1
<?php
2
/*
3
 * hirak/prestissimo
4
 * @author Hiraku NAKANO
5
 * @license MIT https://github.com/hirak/prestissimo
6
 */
7
namespace Hirak\Prestissimo;
8
9
class CurlMulti
10
{
11
    const MAX_CONNECTIONS = 10;
12
13
    /** @var resource<curl_multi> */
14
    private $mh;
15
16
    /** @var resource<curl>[] */
17
    private $unused = array();
18
19
    /** @var resource<curl>[] */
20
    private $using = array();
21
22
    /** @var CopyRequest[] */
23
    private $requests = array();
24
25
    /** @var CopyRequest[] */
26
    private $runningRequests = array();
27
28
    /** @var bool */
29
    private $permanent = true;
30
31
    private $blackhole;
32
33
    /**
34
     * @param bool $permanent
35
     */
36 5
    public function __construct($permanent = true)
37
    {
38 5
        static $mh_cache, $ch_cache;
39
40 5
        if (!$permanent || !$mh_cache) {
41 1
            $mh_cache = curl_multi_init();
42
43 1
            $ch_cache = array();
44 1
            for ($i = 0; $i < self::MAX_CONNECTIONS; ++$i) {
45 1
                $ch = curl_init();
46 1
                Share::setup($ch);
47 1
                $ch_cache[] = $ch;
48
            }
49
        }
50
51 5
        $this->mh = $mh_cache;
52 5
        $this->unused = $ch_cache;
53 5
        $this->permanent = $permanent;
54
55
        // for PHP<5.5 @see getFinishedResults()
56 5
        $this->blackhole = fopen('php://temp', 'wb');
57 5
    }
58
59
    /**
60
     * @codeCoverageIgnore
61
     */
62
    public function __destruct()
63
    {
64
        foreach ($this->using as $ch) {
65
            curl_multi_remove_handle($this->mh, $ch);
66
            $this->unused[] = $ch;
67
        }
68
69
        if ($this->permanent) {
70
            return; //don't close connection
71
        }
72
73
        foreach ($this->unused as $ch) {
74
            curl_close($ch);
75
        }
76
77
        curl_multi_close($this->mh);
78
    }
79
80
    /**
81
     * @param CopyRequest[] $requests
82
     */
83 4
    public function setRequests(array $requests)
84
    {
85 4
        $this->requests = $requests;
86 4
    }
87
88 5
    public function setupEventLoop()
89
    {
90 5
        while (count($this->unused) > 0 && count($this->requests) > 0) {
91 4
            // Process request using a clone to ensure any memory we cause it to allocate is freed after we unset
92 4
            // Otherwise, memory is still held for all requests due to it existing on the callstack
93 4
            // Callers should not need to know this though
94
            $request = clone array_pop($this->requests);
95 4
            $ch = array_pop($this->unused);
96 4
            $index = (int)$ch;
97
98 4
            $this->using[$index] = $ch;
99 4
            $this->runningRequests[$index] = $request;
100
101 5
            curl_setopt_array($ch, $request->getCurlOptions());
102
            curl_multi_add_handle($this->mh, $ch);
103 4
        }
104
    }
105 4
106 4
    public function wait()
107 4
    {
108
        $expectRunning = count($this->using);
109
        $running = 0;
110
        $retryCnt = 0;
111 4
112 4
        do {
113 4
            do {
114
                $stat = curl_multi_exec($this->mh, $running);
115
            } while ($stat === CURLM_CALL_MULTI_PERFORM);
116
            if (-1 === curl_multi_select($this->mh)) {
117
                // @codeCoverageIgnoreStart
118
                if ($retryCnt++ > 100) {
119
                    throw new FetchException('curl_multi_select failure');
120
                }
121 4
                // @codeCoverageIgnoreEnd
122 4
                usleep(100000);
123
            }
124 4
        } while ($running > 0 && $running >= $expectRunning);
125
    }
126 4
127 4
    public function getFinishedResults()
128
    {
129 4
        $urls = $errors = array();
130 4
        $successCnt = $failureCnt = 0;
131 4
        do {
132 4
            if ($raised = curl_multi_info_read($this->mh, $remains)) {
133
                $ch = $raised['handle'];
134
                $errno = curl_errno($ch);
135 4
                if ($errno == CURLE_OK && $raised['result'] != CURLE_OK) {
136 4
                    $errno = $raised['result'];
137 4
                }
138 4
                $error = curl_error($ch);
139 4
                $info = curl_getinfo($ch);
140 4
                curl_setopt($ch, CURLOPT_FILE, $this->blackhole); //release file pointer
141 4
                $index = (int)$ch;
142 2
                $request = $this->runningRequests[$index];
143 2
                $urls[] = $request->getMaskedURL();
144
                if (CURLE_OK === $errno && ('http' !== substr($info['url'], 0, 4) || 200 === $info['http_code'])) {
145 2
                    ++$successCnt;
146 2
                    $request->makeSuccess();
147
                } else {
148 4
                    ++$failureCnt;
149 4
                    $errors[$request->getMaskedURL()] = "$errno: $error";
150 4
                }
151
                unset($this->using[$index], $this->runningRequests[$index], $request);
152 4
                curl_multi_remove_handle($this->mh, $ch);
153
                $this->unused[] = $ch;
154 4
            }
155
        } while ($remains > 0);
156
157 5
        return compact('successCnt', 'failureCnt', 'urls', 'errors');
158
    }
159 5
160
    public function remain()
161
    {
162
        return count($this->runningRequests) > 0;
163
    }
164
}
165