Completed
Pull Request — master (#75)
by
unknown
03:58 queued 02:22
created

ApiClientContext::addPlaceholder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 6
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\Request;
15
use Psr\Http\Client\ClientExceptionInterface as PsrClientExceptionInterface;
16
use Psr\Http\Client\ClientInterface;
17
use Psr\Http\Message\RequestInterface;
18
use Psr\Http\Message\ResponseInterface;
19
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface as ClientExceptionInterface;
20
21
/**
22
 * Provides methods without Features methods.
23
 *
24
 * @author Keyclic team <[email protected]>
25
 */
26
abstract class ApiClientContext implements ApiClientContextInterface
27
{
28
    /**
29
     * @var string
30
     */
31
    protected $baseUri;
32
33
    /**
34
     * @var ClientInterface
35
     */
36
    private $client;
37
38
    /**
39
     * @var array
40
     */
41
    private $headers = [];
42
43
    /**
44
     * @var array
45
     */
46
    private $placeHolders = [];
47
48
    /**
49
     * @var RequestInterface
50
     */
51
    private $request;
52
53
    /**
54
     * @var ResponseInterface
55
     */
56
    private $response;
57
58
    public function setBaseUri(string $baseUri): ApiClientContextInterface
59
    {
60
        $this->baseUri = $baseUri;
61
62
        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...
63
    }
64
65
    public function setClient(ClientInterface $client): ApiClientContextInterface
66
    {
67
        $this->client = $client;
68
69
        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...
70
    }
71
72
    protected function getHeaders(): array
73
    {
74
        return $this->headers;
75
    }
76
77
    protected function addHeader(string $name, string $value): ApiClientContextInterface
78
    {
79
        if (false === isset($this->headers[$name])) {
80
            $this->headers[$name] = $value;
81
82
            return $this;
83
        }
84
85
        if (true === is_array($this->headers[$name])) {
86
            array_push($this->headers[$name], $value);
87
88
            return $this;
89
        }
90
91
        if (false === is_array($this->headers[$name])) {
92
            $this->headers[$name] = [
93
                $this->headers[$name],
94
                $value,
95
            ];
96
97
            return $this;
98
        }
99
100
        return $this;
101
    }
102
103
    protected function removeHeader(string $name): ApiClientContextInterface
104
    {
105
        if (array_key_exists($name, $this->headers)) {
106
            unset($this->headers[$name]);
107
        }
108
109
        return $this;
110
    }
111
112
    /**
113
     * Add place holder for replacement.
114
     *
115
     * you can specify placeholders, which will
116
     * be replaced in URL, request or response body.
117
     */
118
    protected function addPlaceholder(string $key, string $value): ApiClientContextInterface
119
    {
120
        $this->placeHolders[$key] = $value;
121
122
        return $this;
123
    }
124
125
    /**
126
     * Removes a placeholder identified by $key.
127
     */
128
    protected function removePlaceHolder(string $key): ApiClientContext
129
    {
130
        if (array_key_exists($key, $this->placeHolders)) {
131
            unset($this->placeHolders[$key]);
132
        }
133
134
        return $this;
135
    }
136
137
    protected function getRequest(): RequestInterface
138
    {
139
        return $this->request;
140
    }
141
142
    /**
143
     * @return ResponseInterface
144
     */
145
    protected function getResponse()
146
    {
147
        return $this->response;
148
    }
149
150
    /**
151
     * @throws PsrClientExceptionInterface
152
     * @throws ClientExceptionInterface
153
     */
154
    protected function sendRequest(string $method, string $uri, array $headers = [], ?string $body = null)
155
    {
156
        $this->request = new Request($method, $this->baseUri.$uri, $headers, $body);
157
158
        try {
159
            $this->response = $this->getClient()->sendRequest($this->request);
160
        } catch (ClientExceptionInterface $e) {
161
            $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...
162
163
            if (null === $this->response) {
164
                throw $e;
165
            }
166
        }
167
    }
168
169
    /**
170
     * Replaces placeholders in provided text.
171
     */
172
    protected function replacePlaceHolder(string $string): string
173
    {
174
        foreach ($this->placeHolders as $key => $val) {
175
            $string = str_replace($key, $val, $string);
176
        }
177
178
        return $string;
179
    }
180
181
    /**
182
     * Prepare URL by replacing placeholders and trimming slashes.
183
     */
184
    protected function prepareUrl(string $url): string
185
    {
186
        return ltrim($this->replacePlaceHolder($url), '/');
187
    }
188
189
    protected function getClient(): ClientInterface
190
    {
191
        if (null === $this->client) {
192
            throw new \RuntimeException('Client has not been set in WebApiContext.');
193
        }
194
195
        return $this->client;
196
    }
197
}
198