Skip to content

Commit d751116

Browse files
Art4kbsali
andauthored
Replace ServerRequestFactory with RequestFactory (#321)
* remove need for ServerRequestFactoryInterface * Change tests to use RequestFactory * Allow RequestFactoryInterface in Psr18Client * Update CHANGELOG.md --------- Co-authored-by: Kevin Saliou <[email protected]>
1 parent 006e065 commit d751116

File tree

7 files changed

+105
-48
lines changed

7 files changed

+105
-48
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111

12+
- Allow `Psr\Http\Message\ServerRequestFactoryInterface` as Argument #2 ($requestFactory) in `Redmine\Client\Psr18Client::__construct()`
1213
- Added support for PHP 8.2
1314

1415
### Deprecated
1516

17+
- Providing Argument #2 ($requestFactory) in `Redmine\Client\Psr18Client::__construct()` as type `Psr\Http\Message\ServerRequestFactoryInterface` is deprecated, provide as type `Psr\Http\Message\RequestFactoryInterface` instead
1618
- `Redmine\Api\AbstractApi::attachCustomFieldXML()` is deprecated
1719
- `Redmine\Api\Project::prepareParamsXml()` is deprecated
1820

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ $client = new Redmine\Client\NativeCurlClient('https://redmine.example.com', '12
215215
The `Psr18Client` requires
216216

217217
- a `Psr\Http\Client\ClientInterface` implementation (like guzzlehttp/guzzle), [see](https://packagist.org/providers/psr/http-client-implementation)
218-
- a `Psr\Http\Message\ServerRequestFactoryInterface` implementation (like guzzlehttp/psr7), [see](https://packagist.org/providers/psr/http-factory-implementation)
218+
- a `Psr\Http\Message\RequestFactoryInterface` implementation (like guzzlehttp/psr7), [see](https://packagist.org/providers/psr/http-factory-implementation)
219219
- a `Psr\Http\Message\StreamFactoryInterface` implementation (like guzzlehttp/psr7), [see](https://packagist.org/providers/psr/http-message-implementation)
220220
- a URL to your Redmine instance
221221
- an Apikey or username

docs/migrate-to-psr18client.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ After this changes you should be able to test your code without errors.
130130
The `Redmine\Client\Psr18Client` requires:
131131

132132
- 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)
133+
- a `Psr\Http\Message\RequestFactoryInterface` implementation (like nyholm/psr7), [see packagist.org](https://packagist.org/providers/psr/http-factory-implementation)
134134
- a `Psr\Http\Message\StreamFactoryInterface` implementation (like nyholm/psr7), [see packagist.org](https://packagist.org/providers/psr/http-message-implementation)
135135
- a URL to your Redmine instance
136136
- an Apikey or username
@@ -151,20 +151,20 @@ The `Redmine\Client\Psr18Client` requires:
151151
);
152152
```
153153

154-
If you want more control over the PSR-17 ServerRequestFactory you can also create a anonymous class:
154+
If you want more control over the PSR-17 RequestFactory you can also create a anonymous class:
155155

156156
```diff
157-
+use Psr\Http\Message\ServerRequestFactoryInterface;
158-
+use Psr\Http\Message\ServerRequestInterface;
157+
+use Psr\Http\Message\RequestFactoryInterface;
158+
+use Psr\Http\Message\RequestInterface;
159159
+use Psr\Http\Message\StreamFactoryInterface;
160160
+use Psr\Http\Message\StreamInterface;
161161
+
162162
$guzzle = new \GuzzleHttp\Client();
163163
-$psr17Factory = new \GuzzleHttp\Psr7\HttpFactory();
164-
+$psr17Factory = new class() implements ServerRequestFactoryInterface, StreamFactoryInterface {
165-
+ public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface
164+
+$psr17Factory = new class() implements RequestFactoryInterface, StreamFactoryInterface {
165+
+ public function createRequest(string $method, $uri): RequestInterface
166166
+ {
167-
+ return new \GuzzleHttp\Psr7\ServerRequest($method, $uri);
167+
+ return new \GuzzleHttp\Psr7\Request($method, $uri);
168168
+ }
169169
+
170170
+ public function createStream(string $content = ''): StreamInterface

docs/usage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ $client = new \Redmine\Client('https://redmine.example.com', '1234567890abcdfgh'
107107
The `Psr18Client` requires
108108

109109
- a `Psr\Http\Client\ClientInterface` implementation (like guzzlehttp/guzzle) ([possible implementations](https://packagist.org/providers/psr/http-client-implementation))
110-
- a `Psr\Http\Message\ServerRequestFactoryInterface` implementation (like nyholm/psr7) ([possible implementations](https://packagist.org/providers/psr/http-factory-implementation))
110+
- a `Psr\Http\Message\RequestFactoryInterface` implementation (like nyholm/psr7) ([possible implementations](https://packagist.org/providers/psr/http-factory-implementation))
111111
- a `Psr\Http\Message\StreamFactoryInterface` implementation (like nyholm/psr7) ([possible implementations](https://packagist.org/providers/psr/http-message-implementation))
112112
- a URL to your Redmine instance
113113
- an Apikey or username

src/Redmine/Client/Psr18Client.php

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44

55
namespace Redmine\Client;
66

7+
use Exception;
78
use Psr\Http\Client\ClientExceptionInterface;
89
use Psr\Http\Client\ClientInterface;
910
use Psr\Http\Message\ResponseInterface;
11+
use Psr\Http\Message\RequestFactoryInterface;
12+
use Psr\Http\Message\RequestInterface;
1013
use Psr\Http\Message\ServerRequestFactoryInterface;
11-
use Psr\Http\Message\ServerRequestInterface;
1214
use Psr\Http\Message\StreamFactoryInterface;
1315
use Redmine\Exception\ClientException;
1416

@@ -24,22 +26,45 @@ final class Psr18Client implements Client
2426
private ?string $password;
2527
private ?string $impersonateUser = null;
2628
private ClientInterface $httpClient;
27-
private ServerRequestFactoryInterface $requestFactory;
29+
private RequestFactoryInterface $requestFactory;
2830
private StreamFactoryInterface $streamFactory;
2931
private ?ResponseInterface $lastResponse = null;
3032

3133
/**
32-
* $apikeyOrUsername should be your ApiKey, but it could also be your username.
33-
* $password needs to be set if a username is given (not recommended).
34+
* @param RequestFactoryInterface|ServerRequestFactoryInterface $requestFactory
35+
* @param string $apikeyOrUsername should be your ApiKey, but it could also be your username.
36+
* @param ?string $password needs to be set if a username is given (not recommended).
3437
*/
3538
public function __construct(
3639
ClientInterface $httpClient,
37-
ServerRequestFactoryInterface $requestFactory,
40+
$requestFactory,
3841
StreamFactoryInterface $streamFactory,
3942
string $url,
4043
string $apikeyOrUsername,
4144
string $password = null
4245
) {
46+
if ($requestFactory instanceof ServerRequestFactoryInterface) {
47+
@trigger_error(
48+
sprintf(
49+
'%s(): Providing Argument #2 ($requestFactory) as %s is deprecated since v2.3.0, please provide as %s instead.',
50+
__METHOD__,
51+
ServerRequestFactoryInterface::class,
52+
RequestFactoryInterface::class
53+
),
54+
E_USER_DEPRECATED
55+
);
56+
57+
$requestFactory = $this->handleServerRequestFactory($requestFactory);
58+
}
59+
60+
if (! $requestFactory instanceof RequestFactoryInterface) {
61+
throw new Exception(sprintf(
62+
'%s(): Argument #2 ($requestFactory) must be of type %s',
63+
__METHOD__,
64+
RequestFactoryInterface::class
65+
));
66+
}
67+
4368
$this->httpClient = $httpClient;
4469
$this->requestFactory = $requestFactory;
4570
$this->streamFactory = $streamFactory;
@@ -155,9 +180,9 @@ private function runRequest(string $method, string $path, string $body = ''): bo
155180
return $this->lastResponse->getStatusCode() < 400;
156181
}
157182

158-
private function createRequest(string $method, string $path, string $body = ''): ServerRequestInterface
183+
private function createRequest(string $method, string $path, string $body = ''): RequestInterface
159184
{
160-
$request = $this->requestFactory->createServerRequest(
185+
$request = $this->requestFactory->createRequest(
161186
$method,
162187
$this->url.$path
163188
);
@@ -215,4 +240,25 @@ private function createRequest(string $method, string $path, string $body = ''):
215240

216241
return $request;
217242
}
243+
244+
/**
245+
* We accept ServerRequestFactoryInterface for BC
246+
*/
247+
private function handleServerRequestFactory(ServerRequestFactoryInterface $factory): RequestFactoryInterface
248+
{
249+
return new class($factory) implements RequestFactoryInterface
250+
{
251+
private ServerRequestFactoryInterface $factory;
252+
253+
public function __construct(ServerRequestFactoryInterface $factory)
254+
{
255+
$this->factory = $factory;
256+
}
257+
258+
public function createRequest(string $method, $uri): RequestInterface
259+
{
260+
return $this->factory->createServerRequest($method, $uri);
261+
}
262+
};
263+
}
218264
}

tests/Integration/Psr18ClientRequestGenerationTest.php

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44

55
namespace Redmine\Tests\Integration;
66

7-
use GuzzleHttp\Psr7\ServerRequest;
7+
use GuzzleHttp\Psr7\Request;
88
use GuzzleHttp\Psr7\Utils;
99
use PHPUnit\Framework\TestCase;
1010
use Psr\Http\Client\ClientInterface;
1111
use Psr\Http\Message\ResponseInterface;
12-
use Psr\Http\Message\ServerRequestFactoryInterface;
12+
use Psr\Http\Message\RequestFactoryInterface;
1313
use Psr\Http\Message\StreamFactoryInterface;
1414
use Psr\Http\Message\StreamInterface;
1515
use Redmine\Client\Psr18Client;
@@ -41,16 +41,6 @@ public function testPsr18ClientCreatesCorrectRequests(
4141
$headers .= $k.': '.$request->getHeaderLine($k).\PHP_EOL;
4242
}
4343

44-
$cookies = [];
45-
46-
foreach ($request->getCookieParams() as $k => $v) {
47-
$cookies[] = $k.'='.$v;
48-
}
49-
50-
if (!empty($cookies)) {
51-
$headers .= 'Cookie: '.implode('; ', $cookies).\PHP_EOL;
52-
}
53-
5444
$fullRequest = sprintf(
5545
'%s %s HTTP/%s',
5646
$request->getMethod(),
@@ -67,10 +57,10 @@ public function testPsr18ClientCreatesCorrectRequests(
6757
})
6858
);
6959

70-
$requestFactory = $this->createMock(ServerRequestFactoryInterface::class);
71-
$requestFactory->method('createServerRequest')->will(
60+
$requestFactory = $this->createMock(RequestFactoryInterface::class);
61+
$requestFactory->method('createRequest')->will(
7262
$this->returnCallback(function ($method, $uri) {
73-
return new ServerRequest($method, $uri);
63+
return new Request($method, $uri);
7464
})
7565
);
7666

tests/Unit/Client/Psr18ClientTest.php

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
use InvalidArgumentException;
66
use PHPUnit\Framework\TestCase;
77
use Psr\Http\Client\ClientInterface;
8+
use Psr\Http\Message\RequestFactoryInterface;
9+
use Psr\Http\Message\RequestInterface;
810
use Psr\Http\Message\ResponseInterface;
911
use Psr\Http\Message\ServerRequestFactoryInterface;
10-
use Psr\Http\Message\ServerRequestInterface;
1112
use Psr\Http\Message\StreamFactoryInterface;
1213
use Psr\Http\Message\StreamInterface;
1314
use Redmine\Client\Client;
@@ -20,6 +21,24 @@ class Psr18ClientTest extends TestCase
2021
* @test
2122
*/
2223
public function shouldPassApiKeyToConstructor()
24+
{
25+
$client = new Psr18Client(
26+
$this->createMock(ClientInterface::class),
27+
$this->createMock(RequestFactoryInterface::class),
28+
$this->createMock(StreamFactoryInterface::class),
29+
'http://test.local',
30+
'access_token'
31+
);
32+
33+
$this->assertInstanceOf(Psr18Client::class, $client);
34+
$this->assertInstanceOf(Client::class, $client);
35+
}
36+
37+
/**
38+
* @covers \Redmine\Client\Psr18Client
39+
* @test
40+
*/
41+
public function acceptServerRequestFactoryInConstructorForBC()
2342
{
2443
$client = new Psr18Client(
2544
$this->createMock(ClientInterface::class),
@@ -41,7 +60,7 @@ public function shouldPassUsernameAndPasswordToConstructor()
4160
{
4261
$client = new Psr18Client(
4362
$this->createMock(ClientInterface::class),
44-
$this->createMock(ServerRequestFactoryInterface::class),
63+
$this->createMock(RequestFactoryInterface::class),
4564
$this->createMock(StreamFactoryInterface::class),
4665
'http://test.local',
4766
'username',
@@ -56,11 +75,11 @@ public function shouldPassUsernameAndPasswordToConstructor()
5675
* @covers \Redmine\Client\Psr18Client
5776
* @test
5877
*/
59-
public function testGetLastResponseStatusCodeIsInitialNull()
78+
public function testGetLastResponseStatusCodeIsInitialZero()
6079
{
6180
$client = new Psr18Client(
6281
$this->createMock(ClientInterface::class),
63-
$this->createMock(ServerRequestFactoryInterface::class),
82+
$this->createMock(RequestFactoryInterface::class),
6483
$this->createMock(StreamFactoryInterface::class),
6584
'http://test.local',
6685
'access_token'
@@ -77,7 +96,7 @@ public function testGetLastResponseContentTypeIsInitialEmpty()
7796
{
7897
$client = new Psr18Client(
7998
$this->createMock(ClientInterface::class),
80-
$this->createMock(ServerRequestFactoryInterface::class),
99+
$this->createMock(RequestFactoryInterface::class),
81100
$this->createMock(StreamFactoryInterface::class),
82101
'http://test.local',
83102
'access_token'
@@ -94,7 +113,7 @@ public function testGetLastResponseBodyIsInitialEmpty()
94113
{
95114
$client = new Psr18Client(
96115
$this->createMock(ClientInterface::class),
97-
$this->createMock(ServerRequestFactoryInterface::class),
116+
$this->createMock(RequestFactoryInterface::class),
98117
$this->createMock(StreamFactoryInterface::class),
99118
'http://test.local',
100119
'access_token'
@@ -109,7 +128,7 @@ public function testGetLastResponseBodyIsInitialEmpty()
109128
*/
110129
public function testStartAndStopImpersonateUser()
111130
{
112-
$request = $this->createMock(ServerRequestInterface::class);
131+
$request = $this->createMock(RequestInterface::class);
113132
$request->expects($this->exactly(4))
114133
->method('withHeader')
115134
->willReturnMap([
@@ -119,8 +138,8 @@ public function testStartAndStopImpersonateUser()
119138
['X-Redmine-API-Key', 'access_token', $request],
120139
]);
121140

122-
$requestFactory = $this->createMock(ServerRequestFactoryInterface::class);
123-
$requestFactory->method('createServerRequest')->willReturn($request);
141+
$requestFactory = $this->createMock(RequestFactoryInterface::class);
142+
$requestFactory->method('createRequest')->willReturn($request);
124143

125144
$client = new Psr18Client(
126145
$this->createMock(ClientInterface::class),
@@ -149,11 +168,11 @@ public function testRequestGetReturnsFalse()
149168
$httpClient = $this->createMock(ClientInterface::class);
150169
$httpClient->method('sendRequest')->willReturn($response);
151170

152-
$request = $this->createMock(ServerRequestInterface::class);
171+
$request = $this->createMock(RequestInterface::class);
153172
$request->method('withHeader')->willReturn($request);
154173

155-
$requestFactory = $this->createMock(ServerRequestFactoryInterface::class);
156-
$requestFactory->method('createServerRequest')->willReturn($request);
174+
$requestFactory = $this->createMock(RequestFactoryInterface::class);
175+
$requestFactory->method('createRequest')->willReturn($request);
157176

158177
$client = new Psr18Client(
159178
$httpClient,
@@ -184,12 +203,12 @@ public function testRequestsReturnsCorrectContent($method, $data, $boolReturn, $
184203
$httpClient = $this->createMock(ClientInterface::class);
185204
$httpClient->method('sendRequest')->willReturn($response);
186205

187-
$request = $this->createMock(ServerRequestInterface::class);
206+
$request = $this->createMock(RequestInterface::class);
188207
$request->method('withHeader')->willReturn($request);
189208
$request->method('withBody')->willReturn($request);
190209

191-
$requestFactory = $this->createMock(ServerRequestFactoryInterface::class);
192-
$requestFactory->method('createServerRequest')->willReturn($request);
210+
$requestFactory = $this->createMock(RequestFactoryInterface::class);
211+
$requestFactory->method('createRequest')->willReturn($request);
193212

194213
$client = new Psr18Client(
195214
$httpClient,
@@ -243,7 +262,7 @@ public function getApiShouldReturnApiInstance($apiName, $class)
243262
{
244263
$client = new Psr18Client(
245264
$this->createMock(ClientInterface::class),
246-
$this->createMock(ServerRequestFactoryInterface::class),
265+
$this->createMock(RequestFactoryInterface::class),
247266
$this->createMock(StreamFactoryInterface::class),
248267
'http://test.local',
249268
'access_token'
@@ -285,7 +304,7 @@ public function getApiShouldThrowException()
285304
{
286305
$client = new Psr18Client(
287306
$this->createMock(ClientInterface::class),
288-
$this->createMock(ServerRequestFactoryInterface::class),
307+
$this->createMock(RequestFactoryInterface::class),
289308
$this->createMock(StreamFactoryInterface::class),
290309
'http://test.local',
291310
'access_token'

0 commit comments

Comments
 (0)