Completed
Pull Request — master (#135)
by
unknown
04:53
created

ProfileClient   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 171
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 40%

Importance

Changes 0
Metric Value
wmc 15
lcom 1
cbo 9
dl 0
loc 171
ccs 2
cts 5
cp 0.4
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 3
A sendAsyncRequest() 0 20 1
A sendRequest() 0 20 2
A collectRequestInformations() 0 12 2
A collectResponseInformations() 0 10 2
A collectExceptionInformations() 0 13 3
A getStopwatchEventName() 0 12 2
1
<?php
2
3
namespace Http\HttplugBundle\Collector;
4
5
use Http\Client\Common\FlexibleHttpClient;
6
use Http\Client\Exception\HttpException;
7
use Http\Client\HttpAsyncClient;
8
use Http\Client\HttpClient;
9
use Psr\Http\Message\RequestInterface;
10
use Psr\Http\Message\ResponseInterface;
11
use Symfony\Component\Stopwatch\Stopwatch;
12
use Symfony\Component\Stopwatch\StopwatchEvent;
13
14
/**
15
 * The ProfileClient decorates any client that implement both HttpClient and HttpAsyncClient interfaces to gather target
16
 * url and response status code.
17
 *
18
 * @author Fabien Bourigault <[email protected]>
19
 *
20
 * @internal
21
 */
22
class ProfileClient implements HttpClient, HttpAsyncClient
23
{
24
    /**
25
     * @var HttpClient|HttpAsyncClient
26
     */
27
    private $client;
28
29
    /**
30
     * @var Collector
31
     */
32
    private $collector;
33
34
    /**
35
     * @var Formatter
36
     */
37
    private $formatter;
38
39
    /**
40
     * @var Stopwatch
41
     */
42
    private $stopwatch;
43
44
    /**
45
     * @var array
46
     */
47
    private $eventNames = [];
48
49
    /**
50
     * @param HttpClient|HttpAsyncClient $client    The client to profile. Client must implement both HttpClient and
51
     *                                              HttpAsyncClient interfaces.
52
     * @param Collector                  $collector
53
     * @param Formatter                  $formatter
54
     * @param Stopwatch                  $stopwatch
55
     */
56 6
    public function __construct($client, Collector $collector, Formatter $formatter, Stopwatch $stopwatch)
57
    {
58 6
        if (!($client instanceof HttpClient && $client instanceof HttpAsyncClient)) {
59
            throw new \RuntimeException(sprintf(
60
                '%s first argument must implement %s and %s. Consider using %s.',
61
                    __METHOD__,
62
                    HttpClient::class,
63
                    HttpAsyncClient::class,
64
                    FlexibleHttpClient::class
65
            ));
66
        }
67
        $this->client = $client;
68
        $this->collector = $collector;
69
        $this->formatter = $formatter;
70
        $this->stopwatch = $stopwatch;
71
    }
72
73
    /**
74
     * {@inheritdoc}
75
     */
76
    public function sendAsyncRequest(RequestInterface $request)
77
    {
78
        $stack = $this->collector->getCurrentStack();
79
        $this->collectRequestInformations($request, $stack);
0 ignored issues
show
Bug introduced by
It seems like $stack defined by $this->collector->getCurrentStack() on line 78 can also be of type boolean; however, Http\HttplugBundle\Colle...ctRequestInformations() does only seem to accept null|object<Http\HttplugBundle\Collector\Stack>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
80
        $event = $this->stopwatch->start($this->getStopwatchEventName($request));
81
82
        return $this->client->sendAsyncRequest($request)->then(
0 ignored issues
show
Bug introduced by
The method sendAsyncRequest does only exist in Http\Client\HttpAsyncClient, but not in Http\Client\HttpClient.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
83
            function (ResponseInterface $response) use ($event, $stack) {
84
                $event->stop();
85
                $this->collectResponseInformations($response, $event, $stack);
0 ignored issues
show
Bug introduced by
It seems like $stack defined by $this->collector->getCurrentStack() on line 78 can also be of type boolean; however, Http\HttplugBundle\Colle...tResponseInformations() does only seem to accept null|object<Http\HttplugBundle\Collector\Stack>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
86
87
                return $response;
88
            }, function (\Exception $exception) use ($event, $stack) {
89
                $event->stop();
90
                $this->collectExceptionInformations($exception, $event, $stack);
0 ignored issues
show
Bug introduced by
It seems like $stack defined by $this->collector->getCurrentStack() on line 78 can also be of type boolean; however, Http\HttplugBundle\Colle...ExceptionInformations() does only seem to accept null|object<Http\HttplugBundle\Collector\Stack>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
91
92
                throw $exception;
93
            }
94
        );
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100
    public function sendRequest(RequestInterface $request)
101
    {
102
        $stack = $this->collector->getCurrentStack();
103
        $this->collectRequestInformations($request, $stack);
0 ignored issues
show
Bug introduced by
It seems like $stack defined by $this->collector->getCurrentStack() on line 102 can also be of type boolean; however, Http\HttplugBundle\Colle...ctRequestInformations() does only seem to accept null|object<Http\HttplugBundle\Collector\Stack>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
104
        $event = $this->stopwatch->start($this->getStopwatchEventName($request));
105
106
        try {
107
            $response = $this->client->sendRequest($request);
0 ignored issues
show
Bug introduced by
The method sendRequest does only exist in Http\Client\HttpClient, but not in Http\Client\HttpAsyncClient.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
108
            $event->stop();
109
110
            $this->collectResponseInformations($response, $event, $stack);
0 ignored issues
show
Bug introduced by
It seems like $stack defined by $this->collector->getCurrentStack() on line 102 can also be of type boolean; however, Http\HttplugBundle\Colle...tResponseInformations() does only seem to accept null|object<Http\HttplugBundle\Collector\Stack>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
111
112
            return $response;
113
        } catch (\Exception $e) {
114
            $event->stop();
115
            $this->collectExceptionInformations($e, $event, $stack);
0 ignored issues
show
Bug introduced by
It seems like $stack defined by $this->collector->getCurrentStack() on line 102 can also be of type boolean; however, Http\HttplugBundle\Colle...ExceptionInformations() does only seem to accept null|object<Http\HttplugBundle\Collector\Stack>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
116
117
            throw $e;
118
        }
119
    }
120
121
    /**
122
     * @param RequestInterface $request
123
     * @param Stack|null       $stack
124
     */
125
    private function collectRequestInformations(RequestInterface $request, Stack $stack = null)
126
    {
127
        if (!$stack) {
128
            return;
129
        }
130
131
        $stack->setRequestTarget($request->getRequestTarget());
132
        $stack->setRequestMethod($request->getMethod());
133
        $stack->setRequestScheme($request->getUri()->getScheme());
134
        $stack->setRequestHost($request->getUri()->getHost());
135
        $stack->setClientRequest($this->formatter->formatRequest($request));
136
    }
137
138
    /**
139
     * @param ResponseInterface $response
140
     * @param StopwatchEvent    $event
141
     * @param Stack|null        $stack
142
     */
143
    private function collectResponseInformations(ResponseInterface $response, StopwatchEvent $event, Stack $stack = null)
144
    {
145
        if (!$stack) {
146
            return;
147
        }
148
149
        $stack->setDuration($event->getDuration());
150
        $stack->setResponseCode($response->getStatusCode());
151
        $stack->setClientResponse($this->formatter->formatResponse($response));
152
    }
153
154
    /**
155
     * @param \Exception     $exception
156
     * @param StopwatchEvent $event
157
     * @param Stack|null     $stack
158
     */
159
    private function collectExceptionInformations(\Exception $exception, StopwatchEvent $event, Stack $stack = null)
160
    {
161
        if ($exception instanceof HttpException) {
162
            $this->collectResponseInformations($exception->getResponse(), $event, $stack);
163
        }
164
165
        if (!$stack) {
166
            return;
167
        }
168
169
        $stack->setDuration($event->getDuration());
170
        $stack->setClientException($this->formatter->formatException($exception));
171
    }
172
173
    /**
174
     * Generates the event name.
175
     *
176
     * @param RequestInterface $request
177
     *
178
     * @return string
179
     */
180
    private function getStopwatchEventName(RequestInterface $request)
181
    {
182
        $name = sprintf('%s %s', $request->getMethod(), $request->getUri());
183
184
        if (isset($this->eventNames[$name])) {
185
            $name .= sprintf(' [#%d]', ++$this->eventNames[$name]);
186
        } else {
187
            $this->eventNames[$name] = 1;
188
        }
189
190
        return $name;
191
    }
192
}
193