Completed
Pull Request — master (#178)
by Markus
01:55 queued 15s
created

CurlMulti   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 153
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 97.14%

Importance

Changes 0
Metric Value
wmc 27
lcom 1
cbo 3
dl 0
loc 153
ccs 68
cts 70
cp 0.9714
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 25 5
A __destruct() 0 17 4
A setRequests() 0 4 1
A setupEventLoop() 0 14 3
B wait() 0 20 6
B getFinishedResults() 0 29 7
A remain() 0 4 1
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;
24
25
    /** @var CopyRequest[] */
26
    private $runningRequests;
27
28
    /** @var bool */
29
    private $permanent = true;
30
31
    private $blackhole;
32
33
    /**
34
     * @param bool $permanent
35
     */
36 4
    public function __construct($permanent = true)
37
    {
38 4
        static $mh_cache, $ch_cache;
39
40 4
        if (!$permanent || !$mh_cache) {
41 1
            $mh_cache = curl_multi_init();
42 1
            if (defined('CURLMOPT_MAX_HOST_CONNECTIONS')) {
43
                curl_multi_setopt($mh_cache, CURLMOPT_MAX_HOST_CONNECTIONS, 8);
44
            }
45
46 1
            $ch_cache = array();
47 1
            for ($i = 0; $i < self::MAX_CONNECTIONS; ++$i) {
48 1
                $ch = curl_init();
49 1
                Share::setup($ch);
50 1
                $ch_cache[] = $ch;
51 1
            }
52 1
        }
53
54 4
        $this->mh = $mh_cache;
55 4
        $this->unused = $ch_cache;
56 4
        $this->permanent = $permanent;
57
58
        // for PHP<5.5 @see getFinishedResults()
59 4
        $this->blackhole = fopen('php://temp', 'wb');
60 4
    }
61
62
    /**
63
     * @codeCoverageIgnore
64
     */
65
    public function __destruct()
66
    {
67
        foreach ($this->using as $ch) {
68
            curl_multi_remove_handle($this->mh, $ch);
69
            $this->unused[] = $ch;
70
        }
71
72
        if ($this->permanent) {
73
            return; //don't close connection
74
        }
75
76
        foreach ($this->unused as $ch) {
77
            curl_close($ch);
78
        }
79
80
        curl_multi_close($this->mh);
81
    }
82
83
    /**
84
     * @param CopyRequest[] $requests
85
     */
86 4
    public function setRequests(array $requests)
87
    {
88 4
        $this->requests = $requests;
89 4
    }
90
91 4
    public function setupEventLoop()
92
    {
93 4
        while (count($this->unused) > 0 && count($this->requests) > 0) {
94 4
            $request = 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 4
            curl_setopt_array($ch, $request->getCurlOptions());
102 4
            curl_multi_add_handle($this->mh, $ch);
103 4
        }
104 4
    }
105
106 4
    public function wait()
107
    {
108 4
        $expectRunning = count($this->using);
109 4
        $running = 0;
110 4
        $retryCnt = 0;
111
112
        do {
113
            do {
114 4
                $stat = curl_multi_exec($this->mh, $running);
115 4
            } while ($stat === CURLM_CALL_MULTI_PERFORM);
116 4
            if (-1 === curl_multi_select($this->mh)) {
117
                // @codeCoverageIgnoreStart
118
                if ($retryCnt++ > 100) {
119
                    throw new FetchException('curl_multi_select failure');
120
                }
121
                // @codeCoverageIgnoreEnd
122 4
                usleep(100000);
123 4
            }
124 4
        } while ($running > 0 && $running >= $expectRunning);
125 4
    }
126
127 4
    public function getFinishedResults()
128
    {
129 4
        $urls = $errors = array();
130 4
        $successCnt = $failureCnt = 0;
131
        do {
132 4
            if ($raised = curl_multi_info_read($this->mh, $remains)) {
133 4
                $ch = $raised['handle'];
134 4
                $errno = curl_errno($ch);
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 && !$error && ('http' !== substr($info['url'], 0, 4) || 200 === $info['http_code'])) {
141 2
                    ++$successCnt;
142 2
                    $request->makeSuccess();
143 2
                    $urls[] = $request->getMaskedURL();
144 2
                } 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 4
            }
152 4
        } while ($remains > 0);
153
154 4
        return compact('successCnt', 'failureCnt', 'urls', 'errors');
155
    }
156
157 4
    public function remain()
158
    {
159 4
        return count($this->runningRequests) > 0;
160
    }
161
}
162