Skip to content

Commit

Permalink
Merge pull request #324 from Art4/documentation-for-low-level-api
Browse files Browse the repository at this point in the history
Introducing the low-level API
  • Loading branch information
Art4 authored Oct 4, 2023
2 parents 8665629 + bcabc70 commit 2b7cfcd
Show file tree
Hide file tree
Showing 13 changed files with 219 additions and 84 deletions.
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- New class `Redmine\Serializer\PathSerializer` to build an URL path with query parameters.
- New class `Redmine\Serializer\JsonSerializer` to encode or normalize JSON data.
- New class `Redmine\Serializer\XmlSerializer` to encode or normalize XML data.
- Allow `Psr\Http\Message\RequestFactoryInterface` as Argument #2 ($requestFactory) in `Redmine\Client\Psr18Client::__construct()`
- Added support for PHP 8.2

### Deprecated

- 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
- `Redmine\Api\AbstractApi::attachCustomFieldXML()` is deprecated
- `Redmine\Api\Project::prepareParamsXml()` is deprecated
- `Redmine\Api\AbstractApi::attachCustomFieldXML()` is deprecated, use `Redmine\Serializer\XmlSerializer::createFromArray()` instead
- `Redmine\Api\Project::prepareParamsXml()` is deprecated, use `Redmine\Serializer\XmlSerializer::createFromArray()` instead

## [v2.2.0](https://github.com/kbsali/php-redmine-api/compare/v2.1.1...v2.2.0) - 2022-03-01

Expand Down
51 changes: 16 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,41 +15,8 @@ Uses [Redmine API](http://www.redmine.org/projects/redmine/wiki/Rest_api/).
* Choose between using native `cURL` function or any
[PSR-18](https://www.php-fig.org/psr/psr-18/) http client like
[Guzzle](https://github.com/guzzle/guzzle) for handling http connections
* API entry points implementation state:
* :heavy_check_mark: Attachments
* :heavy_check_mark: Groups
* :heavy_check_mark: Custom Fields
* :heavy_check_mark: Issues
* :heavy_check_mark: Issue Categories
* :heavy_check_mark: Issue Priorities
* :x: *Issue Relations - only partially implemented*
* :heavy_check_mark: Issue Statuses
* :heavy_check_mark: News
* :heavy_check_mark: Projects
* :heavy_check_mark: Project Memberships
* :heavy_check_mark: Queries
* :heavy_check_mark: Roles
* :heavy_check_mark: Time Entries
* :heavy_check_mark: Time Entry Activities
* :heavy_check_mark: Trackers
* :heavy_check_mark: Users
* :heavy_check_mark: Versions
* :heavy_check_mark: Wiki

## Todo

* Check header's response code (especially for POST/PUT/DELETE requests)
* See http://stackoverflow.com/questions/9183178/php-curl-retrieving-response-headers-and-body-in-a-single-request/9183272#9183272

## Limitations / Missing Redmine-API

Redmine is missing some APIs for a full remote management of the data:
* List of activities & roles: http://www.redmine.org/issues/11464
* Closing a project: https://www.redmine.org/issues/13725

A possible solution to this would be to create an extra APIs implementing the
missing entry points. See existing effort in doing so:
https://github.com/rschobbert/redmine-miss-api
* [mid-level API](docs/usage.md#mid-level-api) e.g. `$client->getApi('issue')->create($data)`
* [low-level API](docs/usage.md#low-level-api) e.g. `$client->requestPost('/issues.json', $data)`

## Requirements

Expand All @@ -65,6 +32,20 @@ https://github.com/rschobbert/redmine-miss-api
* The PHP [cURL](http://php.net/manual/en/book.curl.php) extension if you want to use the native `cURL` functions.
* [PHPUnit](https://phpunit.de/) >= 9.0 (optional) to run the test suite

## Todo

* Check header's response code (especially for POST/PUT/DELETE requests)
* See http://stackoverflow.com/questions/9183178/php-curl-retrieving-response-headers-and-body-in-a-single-request/9183272#9183272

## Limitations / Missing Redmine-API

Redmine is missing some APIs for a full remote management of the data:
* List of activities & roles: http://www.redmine.org/issues/11464

A possible solution to this would be to create an extra APIs implementing the
missing entry points. See existing effort in doing so:
https://github.com/rschobbert/redmine-miss-api

## Install

### Composer
Expand Down
102 changes: 101 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,9 @@ try {

## API

You can now use the `getApi()` method to create and get a specific Redmine API.
### Mid-level API

You can now use the `getApi()` method to create and get a specific Redmine API. This simplifies common use-cases and gives you some features like caching and assigning a user to an issue by username instead of the user ID.

To check for failed requests you can afterwards check the status code via `$client->getLastResponseStatusCode()`.

Expand Down Expand Up @@ -553,3 +555,101 @@ Array
// Search
$client->getApi('search')->search('Myproject', ['limit' => 100]);
```

#### API entry points implementation state:

* :heavy_check_mark: Attachments
* :heavy_check_mark: Groups
* :heavy_check_mark: Custom Fields
* :heavy_check_mark: Issues
* :heavy_check_mark: Issue Categories
* :heavy_check_mark: Issue Priorities
* :x: *Issue Relations - only partially implemented*
* :heavy_check_mark: Issue Statuses
* :heavy_check_mark: News
* :heavy_check_mark: Projects
* :heavy_check_mark: Project Memberships
* :heavy_check_mark: Queries
* :heavy_check_mark: Roles
* :heavy_check_mark: Time Entries
* :heavy_check_mark: Time Entry Activities
* :heavy_check_mark: Trackers
* :heavy_check_mark: Users
* :heavy_check_mark: Versions
* :heavy_check_mark: Wiki

If some features are missing in `getApi()` you are welcome to create a PR. Besides, it is always possible to use the low-level API.

### Low-level API

The low-level API allows you to send highly customized requests to the Redmine server.

> :bulb: See the [Redmine REST-API docs](https://www.redmine.org/projects/redmine/wiki/Rest_api) for available endpoints and required parameters.
The client has 4 methods for requests:

- `requestGet()`
- `requestPost()`
- `requestPut()`
- `requestDelete()`

Using this methods you can use every Redmine API endpoint. The following example shows you how to rename a project and add a custom field. To build the XML body you can use the `XmlSerializer`.

```php
$client->requestPut(
'/projects/1.xml',
(string) \Redmine\Serializer\XmlSerializer::createFromArray([
'project' => [
'name' => 'renamed project',
'custom_fields' => [
[
'id' => 123,
'name' => 'cf_name',
'field_format' => 'string',
'value' => [1, 2, 3],
],
],
]
])
);
```

> :bulb: Use `\Redmine\Serializer\JsonSerializer` if you want to use the JSON endpoint.
Or to fetch data with complex query parameters you can use `requestGet()` with the `PathSerializer`:

```php
$client->requestGet(
(string) \Redmine\Serializer\PathSerializer::create(
'/time_entries.json',
[
'f' => ['spent_on'],
'op' => ['spent_on' => '><'],
'v' => [
'spent_on' => [
'2016-01-18',
'2016-01-22',
],
],
],
)
);
```

After the request you can use these 3 methods to work with the response:

- `getLastResponseStatusCode()`
- `getLastResponseContentType()`
- `getLastResponseBody()`

To parse the response body from the last example to an array you can use `XmlSerializer`:

```php
if ($client->getLastResponseStatusCode() === 200) {
$responseAsArray = \Redmine\Serializer\XmlSerializer::createFromString(
$client->getLastResponseBody()
)->getNormalized();
}
```

> :bulb: Use `\Redmine\Serializer\JsonSerializer` if you have send the request as JSON.
12 changes: 6 additions & 6 deletions src/Redmine/Api/AbstractApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ public function __construct(Client $client)
*
* @return bool
*
* @deprecated This method does not correctly handle 2xx codes that are not 200 or 201, use \Redmine\Client\Client::getLastResponseStatusCode() instead
* @deprecated since v2.1.0, because it does not correctly handle 2xx codes that are not 200 or 201, use \Redmine\Client\Client::getLastResponseStatusCode() instead
* @see Client::getLastResponseStatusCode() for checking the status code directly
*/
public function lastCallFailed()
{
@trigger_error('The '.__METHOD__.' method is deprecated, use \Redmine\Client\Client::getLastResponseStatusCode() instead.', E_USER_DEPRECATED);
@trigger_error('`'.__METHOD__.'()` is deprecated since v2.1.0, use \Redmine\Client\Client::getLastResponseStatusCode() instead.', E_USER_DEPRECATED);

$code = $this->client->getLastResponseStatusCode();

Expand Down Expand Up @@ -164,7 +164,7 @@ protected function sanitizeParams(array $defaults, array $params)
* Retrieves all the elements of a given endpoint (even if the
* total number of elements is greater than 100).
*
* @deprecated the `retrieveAll()` method is deprecated, use `retrieveData()` instead
* @deprecated since v2.2.0, use `retrieveData()` instead
*
* @param string $endpoint API end point
* @param array $params optional parameters to be passed to the api (offset, limit, ...)
Expand All @@ -173,7 +173,7 @@ protected function sanitizeParams(array $defaults, array $params)
*/
protected function retrieveAll($endpoint, array $params = [])
{
@trigger_error('The '.__METHOD__.' method is deprecated, use `retrieveData()` instead.', E_USER_DEPRECATED);
@trigger_error('`'.__METHOD__.'()` is deprecated since v2.2.0, use `retrieveData()` instead.', E_USER_DEPRECATED);

try {
$data = $this->retrieveData(strval($endpoint), $params);
Expand Down Expand Up @@ -253,7 +253,7 @@ protected function retrieveData(string $endpoint, array $params = []): array
/**
* Attaches Custom Fields to a create/update query.
*
* @deprecated the `attachCustomFieldXML()` method is deprecated.
* @deprecated since v2.3.0, use `\Redmine\Serializer\XmlSerializer::createFromArray()` instead
*
* @param SimpleXMLElement $xml XML Element the custom fields are attached to
* @param array $fields array of fields to attach, each field needs name, id and value set
Expand All @@ -264,7 +264,7 @@ protected function retrieveData(string $endpoint, array $params = []): array
*/
protected function attachCustomFieldXML(SimpleXMLElement $xml, array $fields)
{
@trigger_error('The '.__METHOD__.' method is deprecated.', E_USER_DEPRECATED);
@trigger_error('`'.__METHOD__.'()` is deprecated since v2.3.0, use `\Redmine\Serializer\XmlSerializer::createFromArray()` instead.', E_USER_DEPRECATED);

$_fields = $xml->addChild('custom_fields');
$_fields->addAttribute('type', 'array');
Expand Down
4 changes: 2 additions & 2 deletions src/Redmine/Api/Project.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,15 @@ public function update($id, array $params)
}

/**
* @deprecated the `prepareParamsXml()` method is deprecated, use `\Redmine\Serializer\XmlSerializer::createFromArray()` instead.
* @deprecated since v2.3.0, use `\Redmine\Serializer\XmlSerializer::createFromArray()` instead.
*
* @param array $params
*
* @return \SimpleXMLElement
*/
protected function prepareParamsXml($params)
{
@trigger_error('The '.__METHOD__.' method is deprecated, use `\Redmine\Serializer\XmlSerializer::createFromArray()` instead.', E_USER_DEPRECATED);
@trigger_error('`'.__METHOD__.'()` is deprecated since v2.3.0, use `\Redmine\Serializer\XmlSerializer::createFromArray()` instead.', E_USER_DEPRECATED);

return new \SimpleXMLElement(
XmlSerializer::createFromArray(['project' => $params])->getEncoded()
Expand Down
10 changes: 7 additions & 3 deletions src/Redmine/Serializer/JsonSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@

use JsonException;
use Redmine\Exception\SerializerException;
use Stringable;

/**
* JsonSerializer.
*
* @internal
*/
final class JsonSerializer
final class JsonSerializer implements Stringable
{
/**
* @throws SerializerException if $data is not valid JSON
Expand Down Expand Up @@ -57,6 +56,11 @@ public function getEncoded(): string
return $this->encoded;
}

public function __toString(): string
{
return $this->getEncoded();
}

private function decode(string $encoded): void
{
$this->encoded = $encoded;
Expand Down
13 changes: 9 additions & 4 deletions src/Redmine/Serializer/PathSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

namespace Redmine\Serializer;

use Stringable;

/**
* PathSerializer.
*
* @internal
* PathSerializer to handle query parameters.
*/
final class PathSerializer
final class PathSerializer implements Stringable
{
public static function create(string $path, array $queryParams = []): self
{
Expand Down Expand Up @@ -40,4 +40,9 @@ public function getPath(): string

return $this->path.$queryString;
}

public function __toString(): string
{
return $this->getPath();
}
}
10 changes: 7 additions & 3 deletions src/Redmine/Serializer/XmlSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
use JsonException;
use Redmine\Exception\SerializerException;
use SimpleXMLElement;
use Stringable;
use Throwable;

/**
* XmlSerializer.
*
* @internal
*/
final class XmlSerializer
final class XmlSerializer implements Stringable
{
/**
* @throws SerializerException if $data is not valid XML
Expand Down Expand Up @@ -61,6 +60,11 @@ public function getEncoded(): string
return $this->encoded;
}

public function __toString(): string
{
return $this->getEncoded();
}

private function deserialize(string $encoded): void
{
$this->encoded = $encoded;
Expand Down
Loading

0 comments on commit 2b7cfcd

Please sign in to comment.