Skip to content

Commit 6571679

Browse files
committed
Merge branch 'v2.x' into documentation-for-low-level-api
2 parents 50ad3d0 + 8665629 commit 6571679

21 files changed

+543
-427
lines changed

.editorconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ charset = utf-8
77
trim_trailing_whitespace = true
88
insert_final_newline = true
99
end_of_line = lf
10+
11+
[*.yml]
12+
indent_size = 2

.github/workflows/tests.yml

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,23 @@ on:
55
pull_request:
66

77
jobs:
8-
98
tests:
109
name: Tests (PHP ${{ matrix.php-versions }} on ${{ matrix.operating-system }})
1110
runs-on: ubuntu-latest
1211

1312
strategy:
1413
fail-fast: false
1514
matrix:
16-
operating-system: ['ubuntu-latest']
17-
php-versions: ['7.4', '8.0', '8.1', '8.2']
15+
operating-system: ["ubuntu-latest"]
16+
php-versions: ["7.4", "8.0", "8.1", "8.2"]
1817

1918
steps:
2019
- name: Checkout
2120
uses: actions/checkout@v4
2221
with:
2322
fetch-depth: 2
2423

25-
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
24+
- run: echo '💡 The ${{ github.repository }} repository has been cloned to the runner.'
2625

2726
- name: Setup PHP, with composer and extensions
2827
uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php
@@ -37,7 +36,17 @@ jobs:
3736
- name: "Install Composer dependencies"
3837
uses: "ramsey/composer-install@v2"
3938

39+
- name: Run static code analysis
40+
if: ${{ matrix.php-versions == '8.2' }}
41+
run: composer run phpstan -- --error-format=github
42+
4043
- name: Run tests
41-
run: vendor/bin/phpunit --coverage-text
44+
run: vendor/bin/phpunit --coverage-clover .phpunit.cache/clover.xml
4245

43-
- run: echo "🍏 This job's status is ${{ job.status }}."
46+
- name: Upload coverage reports to Codecov
47+
if: ${{ success() && matrix.php-versions == '8.2' }}
48+
uses: codecov/codecov-action@v3
49+
with:
50+
files: ./.phpunit.cache/clover.xml
51+
fail_ci_if_error: true
52+
verbose: true

.phpstan.neon

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
parameters:
2+
level: 4
3+
4+
paths:
5+
- src/
6+
- tests/
7+
8+
scanDirectories:
9+
- vendor

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- New class `Redmine\Serializer\PathSerializer` to build an URL path with query parameters.
1313
- New class `Redmine\Serializer\JsonSerializer` to encode or normalize JSON data.
1414
- New class `Redmine\Serializer\XmlSerializer` to encode or normalize XML data.
15-
- Allow `Psr\Http\Message\ServerRequestFactoryInterface` as Argument #2 ($requestFactory) in `Redmine\Client\Psr18Client::__construct()`
15+
- Allow `Psr\Http\Message\RequestFactoryInterface` as Argument #2 ($requestFactory) in `Redmine\Client\Psr18Client::__construct()`
1616
- Added support for PHP 8.2
1717

1818
### Deprecated

composer.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
"name": "Kevin Saliou",
1414
"email": "[email protected]",
1515
"homepage": "http://kevin.saliou.name"
16+
},
17+
{
18+
"name": "Artur Weigandt",
19+
"email": "[email protected]",
20+
"homepage": "https://wlabs.de"
1621
}
1722
],
1823
"require": {
@@ -27,7 +32,8 @@
2732
"friendsofphp/php-cs-fixer": "^3",
2833
"phpunit/phpunit": "^9 || 10.2.*",
2934
"guzzlehttp/psr7": "^2",
30-
"php-mock/php-mock-phpunit": "^2.6"
35+
"php-mock/php-mock-phpunit": "^2.6",
36+
"phpstan/phpstan": "^1.10"
3137
},
3238
"autoload": {
3339
"psr-4": {
@@ -41,6 +47,7 @@
4147
},
4248
"scripts": {
4349
"coverage": "phpunit --coverage-html=\".phpunit.cache/code-coverage\"",
50+
"phpstan": "phpstan analyze --memory-limit 512M --configuration .phpstan.neon",
4451
"test": "phpunit"
4552
}
4653
}

src/Redmine/Api/Group.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public function listing($forceUpdate = false)
6363
*
6464
* @throws MissingParameterException Missing mandatory parameters
6565
*
66-
* @return \SimpleXMLElement
66+
* @return string|false
6767
*/
6868
public function create(array $params = [])
6969
{

src/Redmine/Api/Issue.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public function show($id, array $params = [])
7676
*
7777
* @param array $params the new issue data
7878
*
79-
* @return \SimpleXMLElement
79+
* @return string|false
8080
*/
8181
public function create(array $params = [])
8282
{
@@ -174,6 +174,7 @@ public function removeWatcher($id, $watcherUserId)
174174
*/
175175
public function setIssueStatus($id, $status)
176176
{
177+
/** @var IssueStatus */
177178
$api = $this->client->getApi('issue_status');
178179

179180
return $this->update($id, [
@@ -204,31 +205,37 @@ public function addNoteToIssue($id, $note, $privateNote = false)
204205
private function cleanParams(array $params = [])
205206
{
206207
if (isset($params['project'])) {
208+
/** @var Project */
207209
$apiProject = $this->client->getApi('project');
208210
$params['project_id'] = $apiProject->getIdByName($params['project']);
209211
unset($params['project']);
210212
if (isset($params['category'])) {
213+
/** @var IssueCategory */
211214
$apiIssueCategory = $this->client->getApi('issue_category');
212215
$params['category_id'] = $apiIssueCategory->getIdByName($params['project_id'], $params['category']);
213216
unset($params['category']);
214217
}
215218
}
216219
if (isset($params['status'])) {
220+
/** @var IssueStatus */
217221
$apiIssueStatus = $this->client->getApi('issue_status');
218222
$params['status_id'] = $apiIssueStatus->getIdByName($params['status']);
219223
unset($params['status']);
220224
}
221225
if (isset($params['tracker'])) {
226+
/** @var Tracker */
222227
$apiTracker = $this->client->getApi('tracker');
223228
$params['tracker_id'] = $apiTracker->getIdByName($params['tracker']);
224229
unset($params['tracker']);
225230
}
226231
if (isset($params['assigned_to'])) {
232+
/** @var User */
227233
$apiUser = $this->client->getApi('user');
228234
$params['assigned_to_id'] = $apiUser->getIdByUsername($params['assigned_to']);
229235
unset($params['assigned_to']);
230236
}
231237
if (isset($params['author'])) {
238+
/** @var User */
232239
$apiUser = $this->client->getApi('user');
233240
$params['author_id'] = $apiUser->getIdByUsername($params['author']);
234241
unset($params['author']);

src/Redmine/Api/Membership.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ public function removeMember($projectId, $userId, array $params = [])
121121
{
122122
$memberships = $this->all($projectId, $params);
123123
if (!isset($memberships['memberships']) || !is_array($memberships['memberships'])) {
124-
return;
124+
return false;
125125
}
126126
$removed = false;
127127
foreach ($memberships['memberships'] as $membership) {

src/Redmine/Api/Project.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public function show($id, array $params = [])
106106
*
107107
* @throws MissingParameterException
108108
*
109-
* @return \SimpleXMLElement
109+
* @return string|false
110110
*/
111111
public function create(array $params = [])
112112
{

src/Redmine/Client/NativeCurlClient.php

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

55
namespace Redmine\Client;
66

7-
use Redmine\Api;
87
use Redmine\Exception\ClientException;
98

109
/**
@@ -246,7 +245,9 @@ private function request(string $method, string $path, string $body = ''): bool
246245
/**
247246
* Prepare the request by setting the cURL options.
248247
*
249-
* @return resource a cURL handle on success, <b>FALSE</b> on errors
248+
* BC for PHP 7.4: Do not add the return type because CurlHandle was introduced in PHP 8.0
249+
*
250+
* @return \CurlHandle a cURL handle on success, <b>FALSE</b> on errors
250251
*/
251252
private function createCurl(string $method, string $path, string $body = '')
252253
{
@@ -281,13 +282,13 @@ private function createCurl(string $method, string $path, string $body = '')
281282
$curlOptions[CURLOPT_POSTFIELDS] = $filedata;
282283
$curlOptions[CURLOPT_INFILE] = $file;
283284
$curlOptions[CURLOPT_INFILESIZE] = $size;
284-
} elseif (isset($body)) {
285+
} elseif ($body !== '') {
285286
$curlOptions[CURLOPT_POSTFIELDS] = $body;
286287
}
287288
break;
288289
case 'put':
289290
$curlOptions[CURLOPT_CUSTOMREQUEST] = 'PUT';
290-
if (isset($body)) {
291+
if ($body !== '') {
291292
$curlOptions[CURLOPT_POSTFIELDS] = $body;
292293
}
293294
break;

src/Redmine/Client/Psr18Client.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public function __construct(
4343
string $apikeyOrUsername,
4444
string $password = null
4545
) {
46-
if ($requestFactory instanceof ServerRequestFactoryInterface) {
46+
if (! $requestFactory instanceof RequestFactoryInterface && $requestFactory instanceof ServerRequestFactoryInterface) {
4747
@trigger_error(
4848
sprintf(
4949
'%s(): Providing Argument #2 ($requestFactory) as %s is deprecated since v2.3.0, please provide as %s instead.',

tests/Fixtures/MockClient.php

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,15 @@
1212
* The runRequest method of this client class just returns the value of
1313
* the path, method and data or the $runRequestReturnValue value if set.
1414
*/
15-
class MockClient implements Client
15+
final class MockClient implements Client
1616
{
1717
use ClientApiTrait;
1818

19+
public static function create()
20+
{
21+
return new self();
22+
}
23+
1924
/**
2025
* Return value the mocked runRequest method should return.
2126
*
@@ -34,22 +39,7 @@ class MockClient implements Client
3439
public $responseCodeMock;
3540
public $responseContentTypeMock;
3641

37-
private string $url;
38-
private string $apikeyOrUsername;
39-
private ?string $password;
40-
41-
/**
42-
* $apikeyOrUsername should be your ApiKey, but it could also be your username.
43-
* $password needs to be set if a username is given (not recommended).
44-
*/
45-
public function __construct(
46-
string $url,
47-
string $apikeyOrUsername,
48-
string $password = null
49-
) {
50-
$this->url = $url;
51-
$this->apikeyOrUsername = $apikeyOrUsername;
52-
$this->password = $password;
42+
private function __construct() {
5343
}
5444

5545
/**

tests/Integration/GroupXmlTest.php

Lines changed: 24 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,17 @@
22

33
namespace Redmine\Tests\Integration;
44

5-
use DOMDocument;
65
use Exception;
76
use PHPUnit\Framework\TestCase;
87
use Redmine\Exception\MissingParameterException;
98
use Redmine\Tests\Fixtures\MockClient;
10-
use SimpleXMLElement;
119

1210
class GroupXmlTest extends TestCase
1311
{
14-
/**
15-
* @var MockClient
16-
*/
17-
private $client;
18-
19-
public function setup(): void
20-
{
21-
$this->client = new MockClient('http://test.local', 'asdf');
22-
}
23-
2412
public function testCreateBlank()
2513
{
26-
$api = $this->client->getApi('group');
14+
/** @var \Redmine\Api\Group */
15+
$api = MockClient::create()->getApi('group');
2716
$this->assertInstanceOf('Redmine\Api\Group', $api);
2817

2918
$this->expectException(MissingParameterException::class);
@@ -34,42 +23,40 @@ public function testCreateBlank()
3423

3524
public function testCreateComplex()
3625
{
37-
$res = $this->client->getApi('group')->create([
26+
/** @var \Redmine\Api\Group */
27+
$api = MockClient::create()->getApi('group');
28+
$res = $api->create([
3829
'name' => 'Developers',
3930
'user_ids' => [3, 5],
4031
]);
41-
$res = json_decode($res, true);
42-
43-
$xml = '<?xml version="1.0"?>
44-
<group>
45-
<name>Developers</name>
46-
<user_ids type="array">
47-
<user_id>3</user_id>
48-
<user_id>5</user_id>
49-
</user_ids>
50-
</group>
51-
';
52-
$this->assertEquals($this->formatXml($xml), $this->formatXml($res['data']));
32+
$response = json_decode($res, true);
33+
34+
$this->assertEquals('POST', $response['method']);
35+
$this->assertEquals('/groups.xml', $response['path']);
36+
$this->assertXmlStringEqualsXmlString(
37+
<<< XML
38+
<?xml version="1.0"?>
39+
<group>
40+
<name>Developers</name>
41+
<user_ids type="array">
42+
<user_id>3</user_id>
43+
<user_id>5</user_id>
44+
</user_ids>
45+
</group>
46+
XML,
47+
$response['data']
48+
);
5349
}
5450

5551
public function testUpdateNotImplemented()
5652
{
57-
$api = $this->client->getApi('group');
53+
/** @var \Redmine\Api\Group */
54+
$api = MockClient::create()->getApi('group');
5855
$this->assertInstanceOf('Redmine\Api\Group', $api);
5956

6057
$this->expectException(Exception::class);
6158
$this->expectExceptionMessage('Not implemented');
6259

6360
$api->update(1);
6461
}
65-
66-
private function formatXml($xml)
67-
{
68-
$dom = new DOMDocument('1.0');
69-
$dom->preserveWhiteSpace = false;
70-
$dom->formatOutput = true;
71-
$dom->loadXML((new SimpleXMLElement($xml))->asXML());
72-
73-
return $dom->saveXML();
74-
}
7562
}

0 commit comments

Comments
 (0)