Completed
Pull Request — master (#75)
by
unknown
01:19
created

ApiClientContext::removeHeader()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the Behat WebApiExtension.
5
 *
6
 * (c) Konstantin Kudryashov <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Behat\WebApiExtension\Context;
13
14
use Nyholm\Psr7\Factory\Psr17Factory;
15
use Nyholm\Psr7\Request;
16
use Psr\Http\Client\ClientExceptionInterface as PsrHttpClientExceptionInterface;
17
use Psr\Http\Client\ClientInterface;
18
use Psr\Http\Message\RequestInterface;
19
use Psr\Http\Message\ResponseInterface;
20
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface as SymfonyHttpClientExceptionInterface;
21
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
22
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
23
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
24
use Symfony\Contracts\HttpClient\ResponseInterface as SymfonyResponseInterface;
25
26
/**
27
 * Provides methods without Features methods.
28
 *
29
 * @author Keyclic team <[email protected]>
30
 */
31
abstract class ApiClientContext implements ApiClientContextInterface
32
{
33
    /**
34
     * @var string
35
     */
36
    protected $baseUri;
37
38
    /**
39
     * @var ClientInterface
40
     */
41
    private $client;
42
43
    /**
44
     * @var array
45
     */
46
    private $headers = [];
47
48
    /**
49
     * @var array
50
     */
51
    private $placeHolders = [];
52
53
    /**
54
     * @var RequestInterface
55
     */
56
    private $request;
57
58
    /**
59
     * @var ResponseInterface
60
     */
61
    private $response;
62
63
    public function setBaseUri(string $baseUri): ApiClientContextInterface
64
    {
65
        $this->baseUri = $baseUri;
66
67
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Behat\WebApiExtension\Context\ApiClientContext) is incompatible with the return type declared by the interface Behat\WebApiExtension\Co...xtInterface::setBaseUri of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
68
    }
69
70
    public function setClient(ClientInterface $client): ApiClientContextInterface
71
    {
72
        $this->client = $client;
73
74
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Behat\WebApiExtension\Context\ApiClientContext) is incompatible with the return type declared by the interface Behat\WebApiExtension\Co...extInterface::setClient of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
75
    }
76
77
    protected function getHeaders(): array
78
    {
79
        return $this->headers;
80
    }
81
82
    protected function addHeader(string $name, string $value): ApiClientContextInterface
83
    {
84
        if (false === isset($this->headers[$name])) {
85
            $this->headers[$name] = $value;
86
87
            return $this;
88
        }
89
90
        if (true === is_array($this->headers[$name])) {
91
            array_push($this->headers[$name], $value);
92
93
            return $this;
94
        }
95
96
        if (false === is_array($this->headers[$name])) {
97
            $this->headers[$name] = [
98
                $this->headers[$name],
99
                $value,
100
            ];
101
102
            return $this;
103
        }
104
105
        return $this;
106
    }
107
108
    protected function removeHeader(string $name): ApiClientContextInterface
109
    {
110
        if (array_key_exists($name, $this->headers)) {
111
            unset($this->headers[$name]);
112
        }
113
114
        return $this;
115
    }
116
117
    /**
118
     * Add place holder for replacement.
119
     *
120
     * you can specify placeholders, which will
121
     * be replaced in URL, request or response body.
122
     */
123
    protected function addPlaceholder(string $key, string $value): ApiClientContextInterface
124
    {
125
        $this->placeHolders[$key] = $value;
126
127
        return $this;
128
    }
129
130
    /**
131
     * Removes a placeholder identified by $key.
132
     */
133
    protected function removePlaceHolder(string $key): ApiClientContext
134
    {
135
        if (array_key_exists($key, $this->placeHolders)) {
136
            unset($this->placeHolders[$key]);
137
        }
138
139
        return $this;
140
    }
141
142
    protected function getRequest(): RequestInterface
143
    {
144
        return $this->request;
145
    }
146
147
    /**
148
     * @return ResponseInterface
149
     */
150
    protected function getResponse()
151
    {
152
        return $this->response;
153
    }
154
155
    /**
156
     * @throws PsrHttpClientExceptionInterface
157
     * @throws RedirectionExceptionInterface
158
     * @throws ServerExceptionInterface
159
     * @throws SymfonyHttpClientExceptionInterface
160
     * @throws TransportExceptionInterface
161
     */
162
    protected function sendRequest(string $method, string $uri, array $headers = [], ?string $body = null)
163
    {
164
        $this->request = new Request($method, $this->baseUri.$uri, $headers, $body);
165
166
        try {
167
            $this->response = $this->getClient()->sendRequest($this->request);
168
        }
169
        // This exception is returned when status code is superior to 400
170
        // Temporary fix, waiting for httpclient 4.3.2
171
        // TODO Remove after update
172
        catch (SymfonyHttpClientExceptionInterface $e) {
173
            /** @var SymfonyResponseInterface $response */
174
            $response = $e->getResponse();
175
176
            if (null === $response) {
177
                throw $e;
178
            }
179
180
            $psr17Factory = new Psr17Factory();
181
182
            $psrResponse = $psr17Factory->createResponse($response->getStatusCode());
183
184
            foreach ($response->getHeaders(false) as $name => $values) {
185
                $this->response = $e->getResponse();
0 ignored issues
show
Documentation Bug introduced by
It seems like $e->getResponse() of type object<Symfony\Contracts...ient\ResponseInterface> is incompatible with the declared type object<Psr\Http\Message\ResponseInterface> of property $response.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
186
            }
187
188
            $this->response = $psrResponse->withBody($psr17Factory->createStream($response->getContent(false)));
189
        }
190
    }
191
192
    /**
193
     * Replaces placeholders in provided text.
194
     */
195
    protected function replacePlaceHolder(string $string): string
196
    {
197
        foreach ($this->placeHolders as $key => $val) {
198
            $string = str_replace($key, $val, $string);
199
        }
200
201
        return $string;
202
    }
203
204
    /**
205
     * Prepare URL by replacing placeholders and trimming slashes.
206
     */
207
    protected function prepareUrl(string $url): string
208
    {
209
        return ltrim($this->replacePlaceHolder($url), '/');
210
    }
211
212
    protected function getClient(): ClientInterface
213
    {
214
        if (null === $this->client) {
215
            throw new \RuntimeException('Client has not been set in WebApiContext.');
216
        }
217
218
        return $this->client;
219
    }
220
}
221