Completed
Push — master ( b56f56...8f0ba7 )
by Tobias
02:28
created

MultiCurl   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 146
Duplicated Lines 0 %

Test Coverage

Coverage 93.94%

Importance

Changes 0
Metric Value
wmc 20
dl 0
loc 146
ccs 62
cts 66
cp 0.9394
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A sendRequest() 0 14 1
A count() 0 3 1
A sendAsyncRequest() 0 5 1
A configureOptions() 0 7 1
A flush() 0 4 2
C proceed() 0 74 14
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Buzz\Client;
6
7
use Buzz\Configuration\ParameterBag;
8
use Buzz\Exception\ExceptionInterface;
9
use Buzz\Exception\ClientException;
10
use Buzz\Message\ResponseBuilder;
11
use Psr\Http\Message\RequestInterface;
12
use Psr\Http\Message\ResponseInterface;
13
use Symfony\Component\OptionsResolver\OptionsResolver;
14
15
class MultiCurl extends AbstractCurl implements BatchClientInterface, BuzzClientInterface
16
{
17
    private $queue = [];
18
19
    private $curlm;
20
21
    /**
22
     * Populates the supplied response with the response for the supplied request.
23
     *
24
     * The array of options will be passed to curl_setopt_array().
25
     *
26
     * If a "callback" option is supplied, its value will be called when the
27
     * request completes. The callable should have the following signature:
28
     *
29
     *     $callback = function($request, $response, $exception) {
30
     *         if (!$exception) {
31
     *             // success
32
     *         } else {
33
     *             // error ($error is one of the CURLE_* constants)
34
     *         }
35
     *     };
36
     */
37 9
    public function sendAsyncRequest(RequestInterface $request, array $options = []): void
38
    {
39 9
        $options = $this->validateOptions($options);
40
41 9
        $this->queue[] = [$request, $options];
42 9
    }
43
44 56
    public function sendRequest(RequestInterface $request, array $options = []): ResponseInterface
45
    {
46 56
        $options = $this->validateOptions($options);
47 55
        $originalCallback = $options->get('callback');
48 55
        $responseToReturn = null;
49 55
        $options = $options->add(['callback' => function (RequestInterface $request, ResponseInterface $response = null, ClientException $e = null) use (&$responseToReturn, $originalCallback) {
50 55
            $responseToReturn = $response;
51 55
            $originalCallback($request, $response, $e);
52 55
        }]);
53
54 55
        $this->queue[] = [$request, $options];
55 55
        $this->flush();
56
57 52
        return $responseToReturn;
58
    }
59
60 59
    protected function configureOptions(OptionsResolver $resolver): void
61
    {
62 48
        parent::configureOptions($resolver);
63
64 59
        $resolver->setDefault('callback', function (RequestInterface $request, ResponseInterface $response = null, ClientException $e = null) {
3 ignored issues
show
Unused Code introduced by
The parameter $response is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

64
        $resolver->setDefault('callback', function (RequestInterface $request, /** @scrutinizer ignore-unused */ ResponseInterface $response = null, ClientException $e = null) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

64
        $resolver->setDefault('callback', function (/** @scrutinizer ignore-unused */ RequestInterface $request, ResponseInterface $response = null, ClientException $e = null) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $e is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

64
        $resolver->setDefault('callback', function (RequestInterface $request, ResponseInterface $response = null, /** @scrutinizer ignore-unused */ ClientException $e = null) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
65 59
        });
66 48
        $resolver->setAllowedTypes('callback', 'callable');
67 48
    }
68
69
    public function count(): int
70
    {
71
        return count($this->queue);
72
    }
73
74
    /**
75
     * @throws ClientException
76
     */
77 64
    public function flush(): void
78
    {
79 64
        while (!empty($this->queue)) {
80 64
            $this->proceed();
81
        }
82 61
    }
83
84
    /**
85
     * @throws ClientException
86
     */
87 64
    public function proceed(): void
88
    {
89 64
        if (empty($this->queue)) {
90
            return;
91
        }
92
93 64
        if (!$this->curlm && false === $this->curlm = curl_multi_init()) {
94
            throw new ClientException('Unable to create a new cURL multi handle');
95
        }
96
97 64
        foreach ($this->queue as $i => $queueItem) {
98 64
            if (2 !== count($queueItem)) {
99
                // We have already prepared this curl
100 64
                continue;
101
            }
102
            // prepare curl handle
103
            /** @var $request RequestInterface */
104
            /** @var $options ParameterBag */
105 64
            list($request, $options) = $queueItem;
106 64
            $curl = $this->createHandle();
107 64
            $responseBuilder = $this->prepare($curl, $request, $options);
108 64
            $this->queue[$i][] = $curl;
109 64
            $this->queue[$i][] = $responseBuilder;
110 64
            curl_multi_add_handle($this->curlm, $curl);
111
        }
112
113
        // process outstanding perform
114 64
        $active = null;
115
        do {
116 64
            $mrc = curl_multi_exec($this->curlm, $active);
117 64
        } while ($active && CURLM_CALL_MULTI_PERFORM == $mrc);
118
119 64
        $exception = null;
120
        // handle any completed requests
121 64
        while ($done = curl_multi_info_read($this->curlm)) {
122 64
            foreach (array_keys($this->queue) as $i) {
123
                /** @var $request RequestInterface */
124
                /** @var $options ParameterBag */
125
                /** @var $responseBuilder ResponseBuilder */
126 64
                list($request, $options, $curl, $responseBuilder) = $this->queue[$i];
127
128 64
                if ($curl !== $done['handle']) {
129 1
                    continue;
130
                }
131
132
                try {
133 64
                    $response = null;
134 64
                    $this->parseError($request, $done['result'], $curl);
135
                    // populate the response object
136 61
                    curl_multi_getcontent($curl);
137 61
                    $response = $responseBuilder->getResponse();
138 3
                } catch (ExceptionInterface $e) {
139 3
                    if (null === $exception) {
140 3
                        $exception = $e;
141
                    }
142
                }
143
144
                // remove from queue
145 64
                curl_multi_remove_handle($this->curlm, $curl);
146 64
                $this->releaseHandle($curl);
147 64
                unset($this->queue[$i]);
148
149
                // callback
150 64
                call_user_func($options->get('callback'), $request, $response, $exception);
151
            }
152
        }
153
154
        // cleanup
155 64
        if (empty($this->queue)) {
156 64
            curl_multi_close($this->curlm);
157 64
            $this->curlm = null;
158
159 64
            if (null !== $exception) {
160 3
                throw $exception;
161
            }
162
        }
163 64
    }
164
}
165