|
| 1 | +# Migrate from `Redmine\Client` to `Redmine\Client\Psr18Client` |
| 2 | + |
| 3 | +Since `php-redmine-api` v1.7.0 there is a new PSR-18 based client `Redmine\Client\Psr18Client`. This guide will help you to migrate your code if you want to use an app-wide PSR-18 HTTP client. |
| 4 | + |
| 5 | +## 1. Use new client methods |
| 6 | + |
| 7 | +With the new interface `Redmine\Client\Client` there are now standarized methods for all clients. The new `Redmine\Client\Psr18Client` and the current `Redmine\Client` implementing this interface. |
| 8 | + |
| 9 | +### api() to getApi() |
| 10 | + |
| 11 | +Search in your code for the usage of `$client->api('issue')` and the magic getter like `$client->issue`. Then replace this calls with `$client->getApi('issue')`. |
| 12 | + |
| 13 | +```diff |
| 14 | +-$issue = $client->issue->show($issueId); |
| 15 | ++$issue = $client->getApi('issue')->show($issueId); |
| 16 | + |
| 17 | +-$client->api('issue')->create($data); |
| 18 | ++$client->getApi('issue')->create($data); |
| 19 | +``` |
| 20 | + |
| 21 | +### getResponseCode() to getLastResponseStatusCode() |
| 22 | + |
| 23 | +Replace every call for `$client->getResponseCode()` with `$client->getLastResponseStatusCode()`. |
| 24 | + |
| 25 | +```diff |
| 26 | +-if ($client->getResponseCode() === 500) |
| 27 | ++if ($client->getLastResponseStatusCode() === 500) |
| 28 | +{ |
| 29 | + throw new \Exception('Redmine call failed'); |
| 30 | +} |
| 31 | +``` |
| 32 | + |
| 33 | +### get() to requestGet() |
| 34 | + |
| 35 | +If you are using `$client->get()`, `$client->post()`, `$client->put()` or `$client->delete()` directly you will have to change your code. This methods parse a possible JSON or XML response but in future the parsing of the raw response body will be up to you. |
| 36 | + |
| 37 | +To help you with the parsing of the raw response the client interface introduces two new methods: `getLastResponseContentType()` and `getLastResponseBody()`. |
| 38 | + |
| 39 | +This example shows how you can parse the response body of a GET request. |
| 40 | + |
| 41 | +```diff |
| 42 | +-// We dont know if we will get json, xml or a string |
| 43 | +-$dataAsJsonOrXmlOrString = $this->client->get($path); |
| 44 | ++$this->client->requestGet($path); |
| 45 | ++// $body contains the raw http body of the response |
| 46 | ++$body = $this->client->getLastResponseBody(); |
| 47 | ++ |
| 48 | ++// if response is XML, create a SimpleXMLElement object |
| 49 | ++if ($body !== '' && 0 === strpos($this->client->getLastResponseContentType(), 'application/xml')) { |
| 50 | ++ $dataAsXML = new \SimpleXMLElement($body); |
| 51 | ++} else if ($body !== '' && 0 === strpos($this->client->getLastResponseContentType(), 'application/json')) { |
| 52 | ++ try { |
| 53 | ++ $dataAsJson = json_decode($body, true, 512, \JSON_THROW_ON_ERROR); |
| 54 | ++ } catch (\JsonException $e) { |
| 55 | ++ throw new \Exception('Error decoding body as JSON: '.$e->getMessage()); |
| 56 | ++ } |
| 57 | ++} else { |
| 58 | ++ $dataAsString = $body; |
| 59 | ++} |
| 60 | +``` |
| 61 | + |
| 62 | +### post() to requestPost() |
| 63 | + |
| 64 | +This example shows how you can parse the response body of a POST request. |
| 65 | + |
| 66 | +```diff |
| 67 | +-// We dont know if we will get xml or a string |
| 68 | +-$dataAsXmlOrString = $this->client->post($path, $data); |
| 69 | ++$this->client->requestPost($path, $data); |
| 70 | ++// $body contains the raw http body of the response |
| 71 | ++$body = $this->client->getLastResponseBody(); |
| 72 | ++ |
| 73 | ++// if response is XML, create a SimpleXMLElement object |
| 74 | ++if ($body !== '' && 0 === strpos($this->client->getLastResponseContentType(), 'application/xml')) { |
| 75 | ++ $dataAsXML = new \SimpleXMLElement($body); |
| 76 | ++} else { |
| 77 | ++ $dataAsString = $body; |
| 78 | ++} |
| 79 | +``` |
| 80 | + |
| 81 | +### put() to requestPut() |
| 82 | + |
| 83 | +This example shows how you can parse the response body of a PUT request. |
| 84 | + |
| 85 | +```diff |
| 86 | +-// We dont know if we will get xml or a string |
| 87 | +-$dataAsXmlOrString = $this->client->put($path, $data); |
| 88 | ++$this->client->requestPut($path, $data); |
| 89 | ++// $body contains the raw http body of the response |
| 90 | ++$body = $this->client->getLastResponseBody(); |
| 91 | ++ |
| 92 | ++// if response is XML, create a SimpleXMLElement object |
| 93 | ++if ($body !== '' && 0 === strpos($this->client->getLastResponseContentType(), 'application/xml')) { |
| 94 | ++ $dataAsXML = new \SimpleXMLElement($body); |
| 95 | ++} else { |
| 96 | ++ $dataAsString = $body; |
| 97 | ++} |
| 98 | +``` |
| 99 | + |
| 100 | +### delete() to requestDelete() |
| 101 | + |
| 102 | +This example shows how you can parse the response body of a DELETE request. |
| 103 | + |
| 104 | +```diff |
| 105 | +-$dataAsString = $this->client->delete($path); |
| 106 | ++$this->client->requestDelte($path); |
| 107 | ++$dataAsString = $this->client->getLastResponseBody(); |
| 108 | +``` |
| 109 | + |
| 110 | +### setImpersonateUser() to startImpersonateUser() |
| 111 | + |
| 112 | +If you are using the [Redmine user impersonation](https://www.redmine.org/projects/redmine/wiki/Rest_api#User-Impersonation) you have to change your code. |
| 113 | + |
| 114 | +```diff |
| 115 | +// impersonate the user `robin` |
| 116 | +-$client->setImpersonateUser('robin'); |
| 117 | ++$client->startImpersonateUser('robin'); |
| 118 | + |
| 119 | +$userData = $client->getApi('user')->getCurrentUser(); |
| 120 | + |
| 121 | +// Now stop impersonation |
| 122 | +-$client->setImpersonateUser(null); |
| 123 | ++$client->stopImpersonateUser(); |
| 124 | +``` |
| 125 | + |
| 126 | +After this changes you should be able to test your code without |
| 127 | + |
| 128 | +## 2. Switch to `Psr18Client` |
| 129 | + |
| 130 | +The `Redmine\Client\Psr18Client` requires: |
| 131 | + |
| 132 | +- a `Psr\Http\Client\ClientInterface` implementation (like guzzlehttp/guzzle), [see packagist.org](https://packagist.org/providers/psr/http-client-implementation) |
| 133 | +- a `Psr\Http\Message\ServerRequestFactoryInterface` implementation (like nyholm/psr7), [see packagist.org](https://packagist.org/providers/psr/http-factory-implementation) |
| 134 | +- a `Psr\Http\Message\StreamFactoryInterface` implementation (like nyholm/psr7), [see packagist.org](https://packagist.org/providers/psr/http-message-implementation) |
| 135 | +- a URL to your Redmine instance |
| 136 | +- an Apikey or username |
| 137 | +- and optional a password if you want to use username/password (not recommended). |
| 138 | + |
| 139 | +```diff |
| 140 | ++$guzzle = new \GuzzleHttp\Client(); |
| 141 | ++$psr17Factory = new \GuzzleHttp\Psr7\HttpFactory(); |
| 142 | ++ |
| 143 | +// Instantiate with ApiKey |
| 144 | +-$client = new \Redmine\Client( |
| 145 | ++$client = new \Redmine\Client\Prs18Client( |
| 146 | ++ $guzzle, |
| 147 | ++ $psr17Factory, |
| 148 | ++ $psr17Factory, |
| 149 | + 'https://redmine.example.com', |
| 150 | + '1234567890abcdfgh' |
| 151 | +); |
| 152 | +``` |
| 153 | + |
| 154 | +If you want more control over the PSR-17 ServerRequestFactory you can also create a anonymous class: |
| 155 | + |
| 156 | +```diff |
| 157 | ++use Psr\Http\Message\ServerRequestFactoryInterface; |
| 158 | ++use Psr\Http\Message\ServerRequestInterface; |
| 159 | ++use Psr\Http\Message\StreamFactoryInterface; |
| 160 | ++use Psr\Http\Message\StreamInterface; |
| 161 | ++ |
| 162 | +$guzzle = new \GuzzleHttp\Client(); |
| 163 | +-$psr17Factory = new \GuzzleHttp\Psr7\HttpFactory(); |
| 164 | ++$psr17Factory = new class() implements ServerRequestFactoryInterface, StreamFactoryInterface { |
| 165 | ++ public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface |
| 166 | ++ { |
| 167 | ++ return new \GuzzleHttp\Psr7\ServerRequest($method, $uri); |
| 168 | ++ } |
| 169 | ++ |
| 170 | ++ public function createStream(string $content = ''): StreamInterface |
| 171 | ++ { |
| 172 | ++ return \GuzzleHttp\Psr7\Utils::streamFor($content); |
| 173 | ++ } |
| 174 | ++ |
| 175 | ++ public function createStreamFromFile(string $file, string $mode = 'r'): StreamInterface |
| 176 | ++ { |
| 177 | ++ return \GuzzleHttp\Psr7\Utils::streamFor(\GuzzleHttp\Psr7\Utils::tryFopen($file, $mode)); |
| 178 | ++ } |
| 179 | ++ |
| 180 | ++ public function createStreamFromResource($resource): StreamInterface |
| 181 | ++ { |
| 182 | ++ return \GuzzleHttp\Psr7\Utils::streamFor($resource); |
| 183 | ++ } |
| 184 | ++}; |
| 185 | + |
| 186 | +// Instantiate with ApiKey |
| 187 | +$client = new \Redmine\Client\Prs18Client( |
| 188 | + $guzzle, |
| 189 | + $psr17Factory, |
| 190 | + $psr17Factory, |
| 191 | + 'https://redmine.example.com', |
| 192 | + '1234567890abcdfgh' |
| 193 | +); |
| 194 | +``` |
| 195 | + |
| 196 | +## 3. Set `cURL` options |
| 197 | + |
| 198 | +If you have set custom `cURL` options you now have to set them to `Guzzle`. Thanks to the HTTP client you can set them to every request: |
| 199 | + |
| 200 | +```diff |
| 201 | +$guzzle = new \GuzzleHttp\Client(); |
| 202 | +$psr17Factory = new \GuzzleHttp\Psr7\HttpFactory(); |
| 203 | + |
| 204 | ++$guzzleWrapper = new class(\GuzzleHttp\Client $guzzle) implements \Psr\Http\Client\ClientInterface |
| 205 | ++{ |
| 206 | ++ private $guzzle; |
| 207 | ++ |
| 208 | ++ public function __construct(\GuzzleHttp\Client $guzzle) |
| 209 | ++ { |
| 210 | ++ $this->guzzle = $guzzle; |
| 211 | ++ } |
| 212 | ++ |
| 213 | ++ public function sendRequest(\Psr\Http\Message\RequestInterface $request): \Psr\Http\Message\ResponseInterface |
| 214 | ++ { |
| 215 | ++ return $this->guzzle->send($request, [ |
| 216 | ++ // Set other the options for every request here |
| 217 | ++ 'auth' => ['username', 'password', 'digest'], |
| 218 | ++ 'cert' => ['/path/server.pem', 'password'], |
| 219 | ++ 'connect_timeout' => 3.14, |
| 220 | ++ // Set specific CURL options, see https://docs.guzzlephp.org/en/stable/faq.html#how-can-i-add-custom-curl-options |
| 221 | ++ 'curl' => [ |
| 222 | ++ CURLOPT_SSL_VERIFYPEER => 1, |
| 223 | ++ CURLOPT_SSL_VERIFYHOST => 2, |
| 224 | ++ CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2, |
| 225 | ++ ], |
| 226 | ++ ]); |
| 227 | ++ } |
| 228 | ++}; |
| 229 | ++ |
| 230 | +// Instantiate with ApiKey |
| 231 | +$client = new \Redmine\Client\Prs18Client( |
| 232 | +- $guzzle, |
| 233 | ++ $guzzleWrapper, |
| 234 | + $psr17Factory, |
| 235 | + $psr17Factory, |
| 236 | + 'https://redmine.example.com', |
| 237 | + '1234567890abcdfgh' |
| 238 | +); |
| 239 | +- |
| 240 | +-$client->setCheckSslCertificate(true); |
| 241 | +-$client->setCheckSslHost(true); |
| 242 | +-$client->setSslVersion(CURL_SSLVERSION_TLSv1_3); |
| 243 | +``` |
| 244 | + |
| 245 | +If you don't want `php-redmine-api` to use HTTP auth, you can disable it by removing the headers from the request. |
| 246 | + |
| 247 | +```diff |
| 248 | +$guzzle = new \GuzzleHttp\Client(); |
| 249 | +$psr17Factory = new \GuzzleHttp\Psr7\HttpFactory(); |
| 250 | + |
| 251 | +$guzzleWrapper = new class(\GuzzleHttp\Client $guzzle) implements ClientInterface |
| 252 | +{ |
| 253 | + private $guzzle; |
| 254 | + |
| 255 | + public function __construct(\GuzzleHttp\Client $guzzle) |
| 256 | + { |
| 257 | + $this->guzzle = $guzzle; |
| 258 | + } |
| 259 | + |
| 260 | + public function sendRequest(\Psr\Http\Message\RequestInterface $request): \Psr\Http\Message\ResponseInterface |
| 261 | + { |
| 262 | ++ // Remove the auth headers |
| 263 | ++ $request = $request->withoutHeader('X-Redmine-API-Key'); |
| 264 | ++ $request = $request->withoutHeader('Authorization'); |
| 265 | ++ |
| 266 | + return $this->guzzle->send($request, [ |
| 267 | + // Set other the options for every request here |
| 268 | + 'auth' => ['username', 'password', 'digest'], |
| 269 | + 'cert' => ['/path/server.pem', 'password'], |
| 270 | + 'connect_timeout' => 3.14, |
| 271 | + // Set specific CURL options, see https://docs.guzzlephp.org/en/stable/faq.html#how-can-i-add-custom-curl-options |
| 272 | + 'curl' => [ |
| 273 | + CURLOPT_SSL_VERIFYPEER => 1, |
| 274 | + CURLOPT_SSL_VERIFYHOST => 2, |
| 275 | + CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2, |
| 276 | + ], |
| 277 | + ]); |
| 278 | + } |
| 279 | +}; |
| 280 | + |
| 281 | +// Instantiate with ApiKey |
| 282 | +$client = new \Redmine\Client\Prs18Client( |
| 283 | + $guzzleWrapper, |
| 284 | + $psr17Factory, |
| 285 | + $psr17Factory, |
| 286 | + 'https://redmine.example.com', |
| 287 | + '1234567890abcdfgh' |
| 288 | +); |
| 289 | +- |
| 290 | +-$client->setUseHttpAuth(false); |
| 291 | +``` |
| 292 | + |
| 293 | +Now you should be ready. Please make sure that you are only using client methods that are defined in `Redmine\Client\Client` because all other methods will be removed or set to private in a future major release. Otherwise you will have to change your code in future again. |
0 commit comments