Completed
Pull Request — master (#98)
by Hiraku
02:37
created

CurlMulti::__construct()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6

Importance

Changes 4
Bugs 2 Features 0
Metric Value
c 4
b 2
f 0
dl 0
loc 31
ccs 15
cts 15
cp 1
rs 8.439
cc 6
eloc 17
nc 3
nop 1
crap 6
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 = 6;
12
13
    /** @var resource<curl_multi> */
14
    private $mh;
15
16
    /** @var resource<curl_share> */
17
    private $sh;
18
19
    /** @var resource<curl>[] */
20
    private $unused = array();
21
22
    /** @var resource<curl>[] */
23
    private $using = array();
24
25
    /** @var CopyRequest[] */
26
    private $requests;
27
28
    /** @var CopyRequest[] */
29
    private $runningRequests;
30
31
    /** @var bool */
32
    private $permanent = true;
33
34
    private $blackhole;
35
36
    /**
37
     * @param bool $permanent
38
     */
39 4
    public function __construct($permanent = true)
40
    {
41 4
        static $mh_cache, $sh_cache, $ch_cache;
42
43 4
        if (!$permanent || !$mh_cache) {
44 1
            $mh_cache = curl_multi_init();
45
46 1
            $ch_cache = array();
47 1
            for ($i = 0; $i < self::MAX_CONNECTIONS; ++$i) {
48 1
                $ch_cache[] = curl_init();
49 1
            }
50
            // @codeCoverageIgnoreStart
51
            if (function_exists('curl_share_init')) {
52
                $sh_cache = curl_share_init();
53
                curl_share_setopt($sh_cache, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
54
55
                foreach ($ch_cache as $ch) {
56
                    curl_setopt($ch, CURLOPT_SHARE, $sh_cache);
57
                }
58
            }
59
            // @codeCoverageIgnoreEnd
60 1
        }
61
62 4
        $this->mh = $mh_cache;
63 4
        $this->sh = $sh_cache;
64 4
        $this->unused = $ch_cache;
65 4
        $this->permanent = $permanent;
66
67
        // for PHP<5.5 @see getFinishedResults()
68 4
        $this->blackhole = fopen('php://temp', 'wb');
69 4
    }
70
71
    /**
72
     * @codeCoverageIgnore
73
     */
74
    public function __destruct()
75
    {
76
        foreach ($this->using as $ch) {
77
            curl_multi_remove_handle($this->mh, $ch);
78
            $this->unused[] = $ch;
79
        }
80
81
        if ($this->permanent) {
82
            return; //don't close connection
83
        }
84
85
        foreach ($this->unused as $ch) {
86
            curl_close($ch);
87
        }
88
89
        curl_multi_close($this->mh);
90
    }
91
92
    /**
93
     * @param CopyRequest[] $requests
94
     */
95 4
    public function setRequests(array $requests)
96
    {
97 4
        $this->requests = $requests;
98 4
    }
99
100 4
    public function setupEventLoop()
101
    {
102 4
        while (count($this->unused) > 0 && count($this->requests) > 0) {
103 4
            $request = array_pop($this->requests);
104 4
            $ch = array_pop($this->unused);
105 4
            $index = (int)$ch;
106
107 4
            $this->using[$index] = $ch;
108 4
            $this->runningRequests[$index] = $request;
109
110 4
            curl_setopt_array($ch, $request->getCurlOptions());
111 4
            curl_multi_add_handle($this->mh, $ch);
112 4
        }
113 4
    }
114
115 4
    public function wait()
116
    {
117 4
        $expectRunning = count($this->using);
118 4
        $running = 0;
119 4
        $retryCnt = 0;
120
121
        do {
122
            do {
123 4
                $stat = curl_multi_exec($this->mh, $running);
124 4
            } while ($stat === CURLM_CALL_MULTI_PERFORM);
125 4
            if (-1 === curl_multi_select($this->mh)) {
126
                // @codeCoverageIgnoreStart
127
                if ($retryCnt++ > 100) {
128
                    throw new FetchException('curl_multi_select failure');
129
                }
130
                // @codeCoverageIgnoreEnd
131 4
                usleep(100000);
132 4
            }
133 4
        } while ($running > 0 && $running >= $expectRunning);
134 4
    }
135
136 4
    public function getFinishedResults()
137
    {
138 4
        $urls = array();
139 4
        $successCnt = $failureCnt = 0;
140
        do {
141 4
            if ($raised = curl_multi_info_read($this->mh, $remains)) {
142 4
                $ch = $raised['handle'];
143 4
                $errno = curl_errno($ch);
144 4
                $error = curl_error($ch);
145 4
                $info = curl_getinfo($ch);
146 4
                curl_setopt($ch, CURLOPT_FILE, $this->blackhole); //release file pointer
147 4
                $index = (int)$ch;
148 4
                $request = $this->runningRequests[$index];
149 4
                if (CURLE_OK === $errno && !$error && ('http' !== substr($info['url'], 0, 4) || 200 === $info['http_code'])) {
150 2
                    ++$successCnt;
151 2
                    $request->makeSuccess();
152 2
                    $urls[] = $request->getMaskedURL();
153 2
                } else {
154 2
                    ++$failureCnt;
155
                }
156 4
                unset($this->using[$index], $this->runningRequests[$index], $request);
157 4
                curl_multi_remove_handle($this->mh, $ch);
158 4
                $this->unused[] = $ch;
159 4
            }
160 4
        } while ($remains > 0);
161
162 4
        return compact('successCnt', 'failureCnt', 'urls');
163
    }
164
165 4
    public function remain()
166
    {
167 4
        return count($this->runningRequests) > 0;
168
    }
169
}
170