Dispatcher::dispatch()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.2
c 0
b 0
f 0
cc 4
eloc 6
nc 4
nop 0
1
<?php
2
/**
3
 * This file is part of the jyggen/curl library
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 *
8
 * @copyright Copyright (c) Jonas Stendahl <[email protected]>
9
 * @license http://opensource.org/licenses/MIT MIT
10
 * @link https://jyggen.com/projects/jyggen-curl Documentation
11
 * @link https://packagist.org/packages/jyggen/curl Packagist
12
 * @link https://github.com/jyggen/curl GitHub
13
 */
14
15
namespace Jyggen\Curl;
16
17
use Closure;
18
use Jyggen\Curl\DispatcherInterface;
19
use Jyggen\Curl\Exception\CurlErrorException;
20
use Jyggen\Curl\Exception\InvalidArgumentException;
21
22
/**
23
 * Sends HTTP requests asynchronously.
24
 */
25
class Dispatcher implements DispatcherInterface
26
{
27
    /**
28
     * The cURL multi handle.
29
     *
30
     * @var resource
31
     */
32
    protected $handle;
33
34
    /**
35
     * All added requests.
36
     *
37
     * @var array
38
     */
39
    protected $requests = [];
40
41
    /**
42
     * The size of each request stack.
43
     *
44
     * @var integer
45
     */
46
    protected $stackSize = 42;
47
48
    /**
49
     * Constructs a `Dispatcher` instance.
50
     */
51
    public function __construct()
52
    {
53
        $this->handle = curl_multi_init();
54
    }
55
56
    /**
57
     * {@inheritdoc}
58
     */
59
    public function add(RequestInterface $request)
60
    {
61
        $this->requests[] = $request;
62
        return (count($this->requests) - 1);
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68
    public function all()
69
    {
70
        return $this->requests;
71
    }
72
73
    /**
74
     * {@inheritdoc}
75
     */
76
    public function clear()
77
    {
78
        $this->requests = [];
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84
    public function execute(callable $callback = null)
85
    {
86
        $stacks = $this->buildStacks();
87
88
        foreach ($stacks as $requests) {
89
            // Tell each request to use this dispatcher.
90
            foreach ($requests as $request) {
91
                $status = curl_multi_add_handle($this->handle, $request->getHandle());
92
                if ($status !== CURLM_OK) {
93
                    throw new CurlErrorException(sprintf(
94
                        'Unable to add request to cURL multi handle (code #%u)',
95
                        $status
96
                    ));
97
                }
98
            }
99
100
            // Start dispatching the requests.
101
            $this->dispatch();
102
103
            // Loop through all requests and remove their relationship to our dispatcher.
104
            foreach ($requests as $request) {
105
                if ($request->isSuccessful() === false) {
106
                    throw new CurlErrorException($request->getErrorMessage());
107
                }
108
109
                $request->setRawResponse(curl_multi_getcontent($request->getHandle()));
110
                curl_multi_remove_handle($this->handle, $request->getHandle());
111
112
                if ($callback !== null) {
113
                    $callback($request->getResponse());
114
                }
115
            }
116
        }
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122
    public function get($key)
123
    {
124
        // Otherwise, if the key exists; return that request, else return null.
125
        return (isset($this->requests[$key])) ? $this->requests[$key] : null;
126
    }
127
128
    /**
129
     * Retrieves the maximum stack size.
130
     *
131
     * @return integer
132
     */
133
    public function getStackSize()
134
    {
135
        return $this->stackSize;
136
    }
137
138
    /**
139
     * {@inheritdoc}
140
     */
141
    public function remove($key)
142
    {
143
        // Make sure the request exists before we try to remove it.
144
        if (array_key_exists($key, $this->requests)) {
145
            $this->requests[$key]->removeMultiHandle($this->handle);
146
            unset($this->requests[$key]);
147
        }
148
    }
149
150
    /**
151
     * Sets the maximum stack size.
152
     *
153
     * @param integer $size
154
     */
155
    public function setStackSize($size)
156
    {
157
        if (gettype($size) !== 'integer') {
158
            throw new InvalidArgumentException('setStackSize() expected an integer, '.gettype($size).' received.');
159
        }
160
161
        $this->stackSize = $size;
162
    }
163
164
    /**
165
     * Builds stacks of requests.
166
     *
167
     * @return array
168
     */
169
    protected function buildStacks()
170
    {
171
        $stacks   = [];
172
        $stackNo  = 0;
173
        $currSize = 0;
174
175
        foreach ($this->requests as $request) {
176
            if ($currSize === $this->stackSize) {
177
                $currSize = 0;
178
                $stackNo++;
179
            }
180
181
            $stacks[$stackNo][] = $request;
182
            $currSize++;
183
        }
184
185
        return $stacks;
186
    }
187
188
    /**
189
     * Dispatches all requests in the stack.
190
     */
191
    protected function dispatch()
192
    {
193
        // Start processing the requests.
194
        list($mrc, $active) = $this->process();
195
196
        // Keep processing requests until we're done.
197
        while ($active and $mrc === CURLM_OK) {
198
            // Process the next request.
199
            list($mrc, $active) = $this->process();
200
        }
201
202
        // Throw an exception if something went wrong.
203
        if ($mrc !== CURLM_OK) {
204
            throw new CurlErrorException('cURL read error #'.$mrc);
205
        }
206
    }
207
208
    /**
209
     * Processes all requests.
210
     *
211
     * @return array
212
     */
213
    protected function process()
214
    {
215
        // Workaround for PHP Bug #61141.
216
        if (curl_multi_select($this->handle) === -1) {
217
            usleep(100);
218
        }
219
220
        do {
221
            $mrc = curl_multi_exec($this->handle, $active);
222
        } while ($mrc === CURLM_CALL_MULTI_PERFORM);
223
224
        return [$mrc, $active];
225
    }
226
}
227