Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e87979b
feat: create Issue::fromHttpClient()
Art4 Jan 2, 2026
53ac09d
feat: deprecate Issue::__construct()
Art4 Jan 3, 2026
9f7de46
refactor: use Issue::fromHttpClient() in tests
Art4 Jan 3, 2026
a96f99e
fix: do not rely on anonymous class names
Art4 Jan 3, 2026
629a6fb
fix: replace missing constructor call
Art4 Jan 3, 2026
806ae63
chore: fix code style
Art4 Jan 3, 2026
b792ebe
feat: create Attachment::fromHttpClient()
Art4 Jan 4, 2026
c8b2db0
feat: create CustomField::fromHttpClient()
Art4 Jan 4, 2026
3aebdc8
feat: create Group::fromHttpClient()
Art4 Jan 4, 2026
67d5c27
feat: create IssueCategory::fromHttpClient()
Art4 Jan 4, 2026
0ab0d20
Merge branch 'v2.x' into create-named-constructors-in-api
Art4 Jan 6, 2026
4034f38
feat: create IssuePriority::fromHttpClient()
Art4 Feb 8, 2026
10ce81d
feat: create IssueRelation::fromHttpClient()
Art4 Feb 8, 2026
004ce57
tests: use fromHttpClient in tests
Art4 Feb 8, 2026
5630614
Merge branch 'v2.x' into create-named-constructors-in-api
Art4 Feb 8, 2026
0a946e6
feat:create IssueStatus::fromHttpClient()
Art4 Feb 11, 2026
3bc8992
test: replace IssueStatus constructor with fromHttpClient()
Art4 Feb 11, 2026
0f9c3a8
feat: create Membership::fromHttpClient()
Art4 Feb 12, 2026
563f84d
test: add test for consequtive call of Client::getApi()
Art4 Feb 12, 2026
48820a6
test: use explizite old client in test to ensure BC
Art4 Feb 12, 2026
95ddfea
chore: fix code style
Art4 Feb 12, 2026
15ca555
chore: add composer script rectify to run rector and php-cs-fixer wit…
Art4 Feb 12, 2026
5a0f0df
refactor: use Membership::fromHttpClient() in tests
Art4 Feb 12, 2026
269a996
feat: create News::fromHttpClient()
Art4 Feb 12, 2026
ae15675
refactor: use News::fromHttpClient() in tests
Art4 Feb 12, 2026
66d88be
feat: create News::fromHttpClient()
Art4 Feb 13, 2026
659ed35
refactor: use Project::fromHttpClient() in tests
Art4 Feb 13, 2026
96a3247
feat: create Query::fromHttpClient() and use it in tests
Art4 Feb 14, 2026
9938aab
feat: create Role::fromHttpClient() and use it in tests
Art4 Feb 14, 2026
60e4011
feat: create Search::fromHttpClient() and use it in tests
Art4 Feb 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,58 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- New method `Redmine\Api\Attachment::fromHttpClient()` for creating the class.
- New method `Redmine\Api\CustomField::fromHttpClient()` for creating the class.
- New method `Redmine\Api\Group::fromHttpClient()` for creating the class.
- New method `Redmine\Api\Issue::fromHttpClient()` for creating the class.
- New method `Redmine\Api\IssueCategory::fromHttpClient()` for creating the class.
- New method `Redmine\Api\IssuePriority::fromHttpClient()` for creating the class.
- New method `Redmine\Api\IssueRelation::fromHttpClient()` for creating the class.
- New method `Redmine\Api\IssueStatus::fromHttpClient()` for creating the class.
- New method `Redmine\Api\Membership::fromHttpClient()` for creating the class.
- New method `Redmine\Api\News::fromHttpClient()` for creating the class.
- New method `Redmine\Api\Project::fromHttpClient()` for creating the class.
- New method `Redmine\Api\Query::fromHttpClient()` for creating the class.
- New method `Redmine\Api\Role::fromHttpClient()` for creating the class.
- New method `Redmine\Api\Search::fromHttpClient()` for creating the class.
- Add support for PHP 8.5
- Add support for Redmine 6.1.

### Changed

- Behaviour-driven tests are run against Redmine 6.1.0, 6.0.7, 5.1.10.

### Deprecated

- `Redmine\Api\Attachment::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\Attachment::fromHttpClient()` instead.
- Extending `Redmine\Api\Attachment` is deprecated and will be set to final in future, create a wrapper class instead.
- `Redmine\Api\CustomField::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\CustomField::fromHttpClient()` instead.
- Extending `Redmine\Api\CustomField` is deprecated and will be set to final in future, create a wrapper class instead.
- `Redmine\Api\Group::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\Group::fromHttpClient()` instead.
- Extending `Redmine\Api\Group` is deprecated and will be set to final in future, create a wrapper class instead.
- `Redmine\Api\Issue::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\Issue::fromHttpClient()` instead.
- Extending `Redmine\Api\Issue` is deprecated and will be set to final in future, create a wrapper class instead.
- `Redmine\Api\IssueCategory::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\IssueCategory::fromHttpClient()` instead.
- Extending `Redmine\Api\IssueCategory` is deprecated and will be set to final in future, create a wrapper class instead.
- `Redmine\Api\IssuePriority::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\IssuePriority::fromHttpClient()` instead.
- Extending `Redmine\Api\IssuePriority` is deprecated and will be set to final in future, create a wrapper class instead.
- `Redmine\Api\IssueRelation::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\IssueRelation::fromHttpClient()` instead.
- Extending `Redmine\Api\IssueRelation` is deprecated and will be set to final in future, create a wrapper class instead.
- `Redmine\Api\IssueStatus::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\IssueStatus::fromHttpClient()` instead.
- Extending `Redmine\Api\IssueStatus` is deprecated and will be set to final in future, create a wrapper class instead.
- `Redmine\Api\Membership::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\Membership::fromHttpClient()` instead.
- Extending `Redmine\Api\Membership` is deprecated and will be set to final in future, create a wrapper class instead.
- `Redmine\Api\News::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\News::fromHttpClient()` instead.
- Extending `Redmine\Api\News` is deprecated and will be set to final in future, create a wrapper class instead.
- `Redmine\Api\Project::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\Project::fromHttpClient()` instead.
- Extending `Redmine\Api\Project` is deprecated and will be set to final in future, create a wrapper class instead.
- `Redmine\Api\Query::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\Query::fromHttpClient()` instead.
- Extending `Redmine\Api\Query` is deprecated and will be set to final in future, create a wrapper class instead.
- `Redmine\Api\Role::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\Role::fromHttpClient()` instead.
- Extending `Redmine\Api\Role` is deprecated and will be set to final in future, create a wrapper class instead.
- `Redmine\Api\Search::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\Search::fromHttpClient()` instead.
- Extending `Redmine\Api\Search` is deprecated and will be set to final in future, create a wrapper class instead.

### Removed

- Drop support for Redmine 5.0.x.
Expand Down
4 changes: 4 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
"coverage": "phpunit -c tests/phpunit-coverage.xml.dist --coverage-html=\".phpunit.cache/code-coverage\"",
"phpstan": "phpstan analyze --memory-limit 512M --configuration .phpstan.neon",
"phpunit": "phpunit -c phpunit.xml.dist",
"rectify": [
"@rector",
"@codestyle"
],
"rector": "rector -c .rector.php",
"test": [
"@phpstan",
Expand Down
33 changes: 33 additions & 0 deletions src/Redmine/Api/Attachment.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

namespace Redmine\Api;

use Redmine\Client\Client;
use Redmine\Exception\SerializerException;
use Redmine\Exception\UnexpectedResponseException;
use Redmine\Http\HttpClient;
use Redmine\Http\HttpFactory;
use Redmine\Serializer\JsonSerializer;
use Redmine\Serializer\PathSerializer;
Expand All @@ -17,6 +19,37 @@
*/
class Attachment extends AbstractApi
{
final public static function fromHttpClient(HttpClient $httpClient): self
{
return new self($httpClient, true);
}

/**
* @deprecated v2.9.0 Use fromHttpClient() instead.
* @see Attachment::fromHttpClient()
*
* @param Client|HttpClient $client
*/
public function __construct($client/*, bool $privatelyCalled = false*/)
{
$privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false;

if ($privatelyCalled === true) {
parent::__construct($client);

return;
}

if (static::class !== self::class) {
$className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`';
@trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED);
} else {
@trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED);
}

parent::__construct($client);
}

/**
* Get extended information about an attachment.
*
Expand Down
33 changes: 33 additions & 0 deletions src/Redmine/Api/CustomField.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

namespace Redmine\Api;

use Redmine\Client\Client;
use Redmine\Exception;
use Redmine\Exception\SerializerException;
use Redmine\Exception\UnexpectedResponseException;
use Redmine\Http\HttpClient;

/**
* Listing custom fields.
Expand All @@ -15,6 +17,11 @@
*/
class CustomField extends AbstractApi
{
final public static function fromHttpClient(HttpClient $httpClient): self
{
return new self($httpClient, true);
}

/**
* @var null|array<mixed>
*/
Expand All @@ -25,6 +32,32 @@ class CustomField extends AbstractApi
*/
private ?array $customFieldNames = null;

/**
* @deprecated v2.9.0 Use fromHttpClient() instead.
* @see CustomField::fromHttpClient()
*
* @param Client|HttpClient $client
*/
public function __construct($client/*, bool $privatelyCalled = false*/)
{
$privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false;

if ($privatelyCalled === true) {
parent::__construct($client);

return;
}

if (static::class !== self::class) {
$className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`';
@trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED);
} else {
@trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED);
}

parent::__construct($client);
}

/**
* List custom fields.
*
Expand Down
33 changes: 33 additions & 0 deletions src/Redmine/Api/Group.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

namespace Redmine\Api;

use Redmine\Client\Client;
use Redmine\Exception;
use Redmine\Exception\MissingParameterException;
use Redmine\Exception\SerializerException;
use Redmine\Exception\UnexpectedResponseException;
use Redmine\Http\HttpClient;
use Redmine\Http\HttpFactory;
use Redmine\Serializer\JsonSerializer;
use Redmine\Serializer\PathSerializer;
Expand All @@ -21,6 +23,11 @@
*/
class Group extends AbstractApi
{
final public static function fromHttpClient(HttpClient $httpClient): self
{
return new self($httpClient, true);
}

/**
* @var null|array<mixed>
*/
Expand All @@ -31,6 +38,32 @@ class Group extends AbstractApi
*/
private ?array $groupNames = null;

/**
* @deprecated v2.9.0 Use fromHttpClient() instead.
* @see Group::fromHttpClient()
*
* @param Client|HttpClient $client
*/
public function __construct($client/*, bool $privatelyCalled = false*/)
{
$privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false;

if ($privatelyCalled === true) {
parent::__construct($client);

return;
}

if (static::class !== self::class) {
$className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`';
@trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED);
} else {
@trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED);
}

parent::__construct($client);
}

/**
* List groups.
*
Expand Down
39 changes: 36 additions & 3 deletions src/Redmine/Api/Issue.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

namespace Redmine\Api;

use Redmine\Client\Client;
use Redmine\Client\NativeCurlClient;
use Redmine\Client\Psr18Client;
use Redmine\Exception;
use Redmine\Exception\SerializerException;
use Redmine\Exception\UnexpectedResponseException;
use Redmine\Http\HttpClient;
use Redmine\Http\HttpFactory;
use Redmine\Serializer\JsonSerializer;
use Redmine\Serializer\PathSerializer;
Expand Down Expand Up @@ -47,6 +49,11 @@ class Issue extends AbstractApi
*/
public const PRIO_IMMEDIATE = 5;

final public static function fromHttpClient(HttpClient $httpClient): self
{
return new self($httpClient, true);
}

/**
* @var null|IssueCategory
*/
Expand All @@ -72,6 +79,32 @@ class Issue extends AbstractApi
*/
private $userApi = null;

/**
* @deprecated v2.9.0 Use fromHttpClient() instead.
* @see Issue::fromHttpClient()
*
* @param Client|HttpClient $client
*/
public function __construct($client/*, bool $privatelyCalled = false*/)
{
$privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false;

if ($privatelyCalled === true) {
parent::__construct($client);

return;
}

if (static::class !== self::class) {
$className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`';
@trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED);
} else {
@trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED);
}

parent::__construct($client);
}

/**
* List issues.
*
Expand Down Expand Up @@ -489,7 +522,7 @@ private function getIssueCategoryApi()
/** @var IssueCategory */
$issueCategoryApi = $this->client->getApi('issue_category');
} else {
$issueCategoryApi = new IssueCategory($this->getHttpClient());
$issueCategoryApi = IssueCategory::fromHttpClient($this->getHttpClient());
}

$this->issueCategoryApi = $issueCategoryApi;
Expand All @@ -508,7 +541,7 @@ private function getIssueStatusApi()
/** @var IssueStatus */
$issueStatusApi = $this->client->getApi('issue_status');
} else {
$issueStatusApi = new IssueStatus($this->getHttpClient());
$issueStatusApi = IssueStatus::fromHttpClient($this->getHttpClient());
}

$this->issueStatusApi = $issueStatusApi;
Expand All @@ -527,7 +560,7 @@ private function getProjectApi()
/** @var Project */
$projectApi = $this->client->getApi('project');
} else {
$projectApi = new Project($this->getHttpClient());
$projectApi = Project::fromHttpClient($this->getHttpClient());
}

$this->projectApi = $projectApi;
Expand Down
33 changes: 33 additions & 0 deletions src/Redmine/Api/IssueCategory.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

namespace Redmine\Api;

use Redmine\Client\Client;
use Redmine\Exception;
use Redmine\Exception\InvalidParameterException;
use Redmine\Exception\MissingParameterException;
use Redmine\Exception\SerializerException;
use Redmine\Exception\UnexpectedResponseException;
use Redmine\Http\HttpClient;
use Redmine\Http\HttpFactory;
use Redmine\Serializer\JsonSerializer;
use Redmine\Serializer\PathSerializer;
Expand All @@ -22,6 +24,11 @@
*/
class IssueCategory extends AbstractApi
{
final public static function fromHttpClient(HttpClient $httpClient): self
{
return new self($httpClient, true);
}

/**
* @var null|array<mixed>
*/
Expand All @@ -32,6 +39,32 @@ class IssueCategory extends AbstractApi
*/
private array $issueCategoriesNames = [];

/**
* @deprecated v2.9.0 Use fromHttpClient() instead.
* @see IssueCategory::fromHttpClient()
*
* @param Client|HttpClient $client
*/
public function __construct($client/*, bool $privatelyCalled = false*/)
{
$privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false;

if ($privatelyCalled === true) {
parent::__construct($client);

return;
}

if (static::class !== self::class) {
$className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`';
@trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED);
} else {
@trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED);
}

parent::__construct($client);
}

/**
* List issue categories for a given project.
*
Expand Down
Loading