Completed
Pull Request — master (#98)
by Hiraku
09:04
created

CurlMulti::__construct()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6.1666

Importance

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