Completed
Push — master ( 253118...4dbd68 )
by Hiraku
02:17
created

CurlMulti::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 3
Bugs 2 Features 0
Metric Value
c 3
b 2
f 0
dl 0
loc 10
ccs 7
cts 7
cp 1
rs 9.4285
cc 2
eloc 5
nc 2
nop 1
crap 2
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
    /** @var resource curl_multi */
12
    private $mh;
13
14
    /** @var resource[] curl */
15
    private $unused = array();
16
17
    /** @var resource[] curl */
18
    private $using = array();
19
20
    /** @var array {src: HttpGetRequest, dest: OutputFile}*/
21
    private $targets;
22
23
    /** @var array {src: HttpGetRequest, dest: OutputFile}*/
24
    private $runningTargets;
25
26
    private $blackhole;
27
28
    /**
29
     * @param int $maxConnections
30
     */
31 3
    public function __construct($maxConnections)
32
    {
33 3
        $this->mh = curl_multi_init();
34
35 3
        for ($i = 0; $i < $maxConnections; ++$i) {
36 3
            $this->unused[] = curl_init();
37 3
        }
38
39 3
        $this->blackhole = fopen('php://memory', 'wb');
40 3
    }
41
42 3
    public function __destruct()
43
    {
44 3
        foreach ($this->using as $ch) {
45
            curl_multi_remove_handle($this->mh, $ch);
46
            curl_close($ch);
47 3
        }
48
49 3
        foreach ($this->unused as $ch) {
50 3
            curl_close($ch);
51 3
        }
52
53 3
        curl_multi_close($this->mh);
54 3
    }
55
56
    /**
57
     * @codeCoverageIgnore
58
     */
59
    public function setupShareHandler()
60
    {
61
        if (function_exists('curl_share_init')) {
62
            $sh = curl_share_init();
63
            curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
64
65
            foreach ($this->unused as $ch) {
66
                curl_setopt($ch, CURLOPT_SHARE, $sh);
67
            }
68
        }
69
    }
70
71
    /**
72
     * @param array $targets {src: HttpGetRequest, dest: OutputFile}
73
     */
74 3
    public function setTargets(array $targets)
75
    {
76 3
        $this->targets = $targets;
77 3
    }
78
79 3
    public function setupEventLoop()
80
    {
81 3
        while (count($this->unused) > 0 && count($this->targets) > 0) {
82 2
            $target = array_pop($this->targets);
83 2
            $ch = array_pop($this->unused);
84 2
            $index = (int)$ch;
85
86 2
            $this->using[$index] = $ch;
87 2
            $this->runningTargets[$index] = $target;
88
89 2
            $opts = $target['src']->getCurlOpts();
90 2
            unset($opts[CURLOPT_ENCODING], $opts[CURLOPT_USERPWD]);
91 2
            curl_setopt_array($ch, $opts);
92 2
            curl_setopt($ch, CURLOPT_FILE, $target['dest']->getPointer());
93 2
            curl_multi_add_handle($this->mh, $ch);
94 2
        }
95 3
    }
96
97 3
    public function wait()
98
    {
99 3
        $expectRunning = count($this->using);
100 3
        $running = 0;
101 3
        $retryCnt = 0;
102
103
        do {
104
            do {
105 3
                $stat = curl_multi_exec($this->mh, $running);
106 3
            } while ($stat === CURLM_CALL_MULTI_PERFORM);
107 3
            if (-1 === curl_multi_select($this->mh)) {
108
                // @codeCoverageIgnoreStart
109
                if ($retryCnt++ > 100) {
110
                    throw new \RuntimeException('curl_multi_select failure');
111
                }
112
                // @codeCoverageIgnoreEnd
113 3
                usleep(100000);
114 3
            }
115 3
        } while ($running > 0 && $running >= $expectRunning);
116 3
    }
117
118 3
    public function getFinishedResults()
119
    {
120 3
        $results = array();
121 3
        $successCnt = $failureCnt = 0;
122
        do {
123 3
            if ($raised = curl_multi_info_read($this->mh, $remains)) {
124 2
                $ch = $raised['handle'];
125 2
                $errno = curl_errno($ch);
126 2
                $info = curl_getinfo($ch);
127 2
                curl_setopt($ch, CURLOPT_FILE, $this->blackhole);
128 2
                $index = (int)$ch;
129 2
                $target = $this->runningTargets[$index];
130 2
                if (CURLE_OK === $errno && 200 === $info['http_code']) {
131 1
                    ++$successCnt;
132 1
                    $target['dest']->setSuccess();
133 1
                    $results[] = $info['url'];
134 1
                } else {
135 1
                    ++$failureCnt;
136
                }
137 2
                unset($this->using[$index], $this->runningTargets[$index], $target);
138 2
                curl_multi_remove_handle($this->mh, $ch);
139 2
                $this->unused[] = $ch;
140 2
            }
141 3
        } while ($remains > 0);
142
143 3
        return compact('successCnt', 'failureCnt', 'results');
144
    }
145
146 3
    public function remain()
147
    {
148 3
        return count($this->runningTargets) > 0;
149
    }
150
}
151