Completed
Pull Request — master (#208)
by Hiraku
03:00
created

CurlMulti   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 153
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 96.83%

Importance

Changes 0
Metric Value
wmc 27
lcom 1
cbo 3
dl 0
loc 153
ccs 61
cts 63
cp 0.9683
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __destruct() 0 17 4
A setRequests() 0 4 1
A __construct() 0 22 4
A setupEventLoop() 0 14 3
A remain() 0 4 1
B wait() 0 20 6
B getFinishedResults() 0 32 8
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
            $request = array_pop($this->requests);
92 4
            $ch = array_pop($this->unused);
93 4
            $index = (int)$ch;
94
95 4
            $this->using[$index] = $ch;
96 4
            $this->runningRequests[$index] = $request;
97
98 4
            curl_setopt_array($ch, $request->getCurlOptions());
99 4
            curl_multi_add_handle($this->mh, $ch);
100
        }
101 5
    }
102
103 4
    public function wait()
104
    {
105 4
        $expectRunning = count($this->using);
106 4
        $running = 0;
107 4
        $retryCnt = 0;
108
109
        do {
110
            do {
111 4
                $stat = curl_multi_exec($this->mh, $running);
112 4
            } while ($stat === CURLM_CALL_MULTI_PERFORM);
113 4
            if (-1 === curl_multi_select($this->mh)) {
114
                // @codeCoverageIgnoreStart
115
                if ($retryCnt++ > 100) {
116
                    throw new FetchException('curl_multi_select failure');
117
                }
118
                // @codeCoverageIgnoreEnd
119
                usleep(100000);
120
            }
121 4
        } while ($running > 0 && $running >= $expectRunning);
122 4
    }
123
124 4
    public function getFinishedResults()
125
    {
126 4
        $urls = $errors = array();
127 4
        $successCnt = $failureCnt = 0;
128
        do {
129 4
            if ($raised = curl_multi_info_read($this->mh, $remains)) {
130 4
                $ch = $raised['handle'];
131 4
                $errno = curl_errno($ch);
132 4
                if ($errno == CURLE_OK && $raised['result'] != CURLE_OK) {
133
                    $errno = $raised['result'];
134
                }
135 4
                $error = curl_error($ch);
136 4
                $info = curl_getinfo($ch);
137 4
                curl_setopt($ch, CURLOPT_FILE, $this->blackhole); //release file pointer
138 4
                $index = (int)$ch;
139 4
                $request = $this->runningRequests[$index];
140 4
                if (CURLE_OK === $errno && ('http' !== substr($info['url'], 0, 4) || 200 === $info['http_code'])) {
141 2
                    ++$successCnt;
142 2
                    $request->makeSuccess();
143 2
                    $urls[] = $request->getMaskedURL();
144
                } else {
145 2
                    ++$failureCnt;
146 2
                    $errors[$request->getMaskedURL()] = "$errno: $error";
147
                }
148 4
                unset($this->using[$index], $this->runningRequests[$index], $request);
149 4
                curl_multi_remove_handle($this->mh, $ch);
150 4
                $this->unused[] = $ch;
151
            }
152 4
        } while ($remains > 0);
153
154 4
        return compact('successCnt', 'failureCnt', 'urls', 'errors');
155
    }
156
157 5
    public function remain()
158
    {
159 5
        return count($this->runningRequests) > 0;
160
    }
161
}
162