├── .editorconfig ├── .phpstan.neon ├── CHANGELOG.md ├── LICENSE ├── README.md ├── TODO.md ├── composer.json ├── contracts ├── Authenticator │ ├── Authenticator.php │ └── Exceptions │ │ └── AuthenticationException.php ├── Authorizer │ ├── Authorizer.php │ └── Exceptions │ │ ├── AuthorizationException.php │ │ └── InvalidAuthorizationToken.php ├── Client │ ├── Client.php │ └── Exceptions │ │ └── ClientException.php ├── HttpClient │ ├── Exceptions │ │ └── HttpClientException.php │ └── HttpClient.php └── Response │ └── Response.php ├── docker-compose.yaml ├── examples ├── cookie-authorization.php └── token-authorization.php └── src ├── Authenticator └── CookieAuthenticator.php ├── Authorizer ├── CookieAuthorizer.php └── TokenAuthorizer.php ├── Client └── YouTrackClient.php ├── HttpClient └── GuzzleHttpClient.php └── Response └── YouTrackResponse.php /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 8 3 | paths: 4 | - src 5 | - tests 6 | checkMissingIterableValueType: false # Fix it 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `cybercog/youtrack-rest-php` are documented in [PHP YouTrack REST releases] page. 4 | 5 | [PHP YouTrack REST releases]: https://github.com/cybercog/youtrack-rest-php/releases 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024, Anton Komarev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP YouTrack REST 2 | 3 | ![cog-php-youtrack-rest](https://user-images.githubusercontent.com/1849174/34457236-ab5aa292-edbb-11e7-8555-e454255acd82.png) 4 | 5 |

6 | Build 7 | StyleCI 8 | Code Climate Maintainability 9 | Releases 10 | License 11 |

12 | 13 | ## Introduction 14 | 15 | YouTrack REST API PHP Client uses [PSR-7 (HTTP Message Interface)](http://www.php-fig.org/psr/psr-7/) to connect with [JetBrains YouTrack REST API](https://www.jetbrains.com/help/youtrack/standalone/2017.2/Resources-for-Developers.html). 16 | 17 | Part of the [PHP YouTrack SDK](https://github.com/cybercog/youtrack-php-sdk#readme). 18 | 19 | ## Contents 20 | 21 | - [Features](#features) 22 | - [Requirements](#requirements) 23 | - [Related packages](#related-packages) 24 | - [Frameworks support](#frameworks-support) 25 | - [Installation](#installation) 26 | - [Usage](#usage) 27 | - [Change log](#change-log) 28 | - [Contributing](#contributing) 29 | - [Testing](#testing) 30 | - [Security](#security) 31 | - [Contributors](#contributors) 32 | - [Alternatives](#alternatives) 33 | - [License](#license) 34 | - [About CyberCog](#about-cybercog) 35 | 36 | ## Features 37 | 38 | - Framework agnostic. 39 | - Using contracts to keep high customization capabilities. 40 | - Multiple authorization strategies: Token, Cookie. 41 | - Following PHP Standard Recommendations: 42 | - [PSR-1 (Basic Coding Standard)](http://www.php-fig.org/psr/psr-1/). 43 | - [PSR-2 (Coding Style Guide)](http://www.php-fig.org/psr/psr-2/). 44 | - [PSR-4 (Autoloading Standard)](http://www.php-fig.org/psr/psr-4/). 45 | - [PSR-7 (HTTP Message Interface)](http://www.php-fig.org/psr/psr-7/). 46 | - Covered with unit tests. 47 | 48 | ## Requirements 49 | 50 | - YouTrack >= 3.0 with REST-API enabled (always enabled, by default) 51 | - PHP >= 8.1 52 | - Guzzle HTTP Client >= 7.0 53 | 54 | ## Related packages 55 | 56 | - [PHP YouTrack SDK](https://github.com/cybercog/youtrack-php-sdk#readme) maintained by [Anton Komarev](https://github.com/antonkomarev) 57 | 58 | **Share your packages! [We are open](CONTRIBUTING.md) for Pull Requests!** 59 | 60 | ## Frameworks support 61 | 62 | PHP YouTrack REST is framework agnostic package and could be easily used in any PHP framework you want. 63 | 64 | ### Framework integrations list 65 | 66 | - [Laravel YouTrack SDK](https://github.com/cybercog/laravel-youtrack-sdk#readme) maintained by [Anton Komarev](https://github.com/antonkomarev) 67 | 68 | **Haven't found your favorite framework in the list? [We are open](CONTRIBUTING.md) for Pull Requests!** 69 | 70 | ## Installation 71 | 72 | The preferred method is via [composer](https://getcomposer.org). Follow the 73 | [installation instructions](https://getcomposer.org/doc/00-intro.md) if you do not already have 74 | composer installed. 75 | 76 | Once composer is installed, execute the following command in your project root to install this library: 77 | 78 | ```shell 79 | composer require cybercog/youtrack-rest-php 80 | ``` 81 | 82 | ### Without framework 83 | 84 | Be sure to include the autoloader in your project: 85 | 86 | ```php 87 | require_once '/path/to/your-project/vendor/autoload.php'; 88 | ``` 89 | 90 | ## Usage 91 | 92 | [Usage Documentation](https://github.com/cybercog/youtrack-php-sdk/wiki/PHP-YouTrack-REST) 93 | 94 | ## Change log 95 | 96 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 97 | 98 | ## Contributing 99 | 100 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 101 | 102 | ## Testing 103 | 104 | Run the tests in terminal with: 105 | 106 | ```shell 107 | composer test 108 | ``` 109 | 110 | ## Security 111 | 112 | If you discover any security related issues, please email open@cybercog.su instead of using the issue tracker. 113 | 114 | ## Contributors 115 | 116 | | ![@antonkomarev](https://avatars.githubusercontent.com/u/1849174?s=110)
Anton Komarev
| ![@adam187](https://avatars.githubusercontent.com/u/156628?s=110)
Adam Misiorny
|
dmkdev
|
asteisiunas
| ![@MarcHagen](https://avatars.githubusercontent.com/u/980978?s=110)
Marc
| 117 | | :---: | :---: | :---: | :---: |:--:| 118 | 119 | [PHP YouTrack REST contributors list](../../contributors) 120 | 121 | ## Alternatives 122 | 123 | ### PHP 124 | 125 | - [samson/youtrack](https://github.com/SamsonIT/YouTrack#readme) 126 | - [jsto/youtrack_api_client_php](https://github.com/jsto/youtrack_api_client_php#readme) 127 | - [nepda/youtrack-client](https://github.com/nepda/youtrack-client#readme) 128 | 129 | ### Python 130 | 131 | - [JetBrains/youtrack-rest-python-library](https://github.com/JetBrains/youtrack-rest-python-library#readme) 132 | 133 | ### .NET 134 | 135 | - [JetBrains/YouTrackSharp](https://github.com/JetBrains/YouTrackSharp#readme) 136 | 137 | ### Java 138 | 139 | - [byte-imagination/ytapi](https://github.com/byte-imagination/ytapi#readme) 140 | 141 | *Feel free to add more alternatives as Pull Request.* 142 | 143 | ## License 144 | 145 | - `PHP YouTrack REST` package is open-sourced software licensed under the [MIT License](LICENSE) by [Anton Komarev]. 146 | 147 | ## About CyberCog 148 | 149 | [CyberCog](https://cybercog.su) is a Social Unity of enthusiasts. Research the best solutions in product & software development is our passion. 150 | 151 | - [Follow us on Twitter](https://twitter.com/cybercog) 152 | - [Read our articles on Medium](https://medium.com/cybercog) 153 | 154 | CyberCog 155 | 156 | [Anton Komarev]: https://komarev.com 157 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # To do 2 | 3 | ## Exceptions 4 | 5 | - [ ] (?) Design exceptions (use static or not) 6 | 7 | ## Client 8 | 9 | - [x] Authorization strategy 10 | - [x] Design `YouTrackClient` 11 | - [ ] (?) `YouTrackClient` should has `Endpoints` entity 12 | - [ ] (?) Ignore `Guzzle` exceptions to cleanup code? What if guzzle will be initialized with exceptions flag? 13 | - [x] Move `authenticate()` method out from the constructor. It should be called before each request, but prevent authentication call recursion. 14 | - [x] Remove `authenticate()` method from the Authorizer contract? 15 | 16 | ## Testing 17 | 18 | - [x] Snapshot\Mock\Stub\Fake testing for API calls 19 | 20 | ## Continuous Integration 21 | 22 | - [x] Automate unit tests 23 | - [ ] Coverage analysis 24 | - [x] Code mess detector 25 | - [x] Code style checker 26 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cybercog/youtrack-rest-php", 3 | "description": "YouTrack REST API PHP Client.", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "cybercog", 8 | "cog", 9 | "laravel", 10 | "youtrack", 11 | "yt", 12 | "rest", 13 | "api", 14 | "client", 15 | "jetbrains", 16 | "issues", 17 | "bugtracker", 18 | "helpdesk", 19 | "pm", 20 | "tickets" 21 | ], 22 | "authors": [ 23 | { 24 | "name": "Anton Komarev", 25 | "email": "anton@komarev.com", 26 | "homepage": "https://komarev.com", 27 | "role": "Developer" 28 | } 29 | ], 30 | "homepage": "https://komarev.com/sources/php-youtrack-rest", 31 | "support": { 32 | "email": "open@cybercog.su", 33 | "issues": "https://github.com/cybercog/youtrack-rest-php/issues", 34 | "wiki": "https://github.com/cybercog/youtrack-rest-php/wiki", 35 | "source": "https://github.com/cybercog/youtrack-rest-php", 36 | "docs": "https://github.com/cybercog/youtrack-rest-php/wiki" 37 | }, 38 | "require": { 39 | "php": "^8.1", 40 | "ext-json": "*", 41 | "guzzlehttp/guzzle": "^7.8" 42 | }, 43 | "require-dev": { 44 | "ext-curl": "*", 45 | "mockery/mockery": "^1.6", 46 | "phpstan/phpstan": "^1.10", 47 | "phpunit/phpunit": "^10.5" 48 | }, 49 | "autoload": { 50 | "psr-4": { 51 | "Cog\\Contracts\\YouTrack\\Rest\\": "contracts/", 52 | "Cog\\YouTrack\\Rest\\": "src/" 53 | } 54 | }, 55 | "autoload-dev": { 56 | "psr-4": { 57 | "Cog\\YouTrack\\Rest\\Tests\\": "tests/" 58 | } 59 | }, 60 | "suggest": { 61 | "cybercog/youtrack-php-sdk": "PHP YouTrack SDK.", 62 | "cybercog/laravel-youtrack-sdk": "Laravel integration with PHP YouTrack SDK." 63 | }, 64 | "scripts": { 65 | "phpstan": "vendor/bin/phpstan analyse -c .phpstan.neon", 66 | "test": "vendor/bin/phpunit" 67 | }, 68 | "config": { 69 | "sort-packages": true 70 | }, 71 | "minimum-stability": "dev", 72 | "prefer-stable": true 73 | } 74 | -------------------------------------------------------------------------------- /contracts/Authenticator/Authenticator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Cog\Contracts\YouTrack\Rest\Authenticator; 15 | 16 | use Cog\Contracts\YouTrack\Rest\Client\Client as ClientInterface; 17 | 18 | interface Authenticator 19 | { 20 | /** 21 | * Authenticate API Client. 22 | * 23 | * @throws \Cog\Contracts\YouTrack\Rest\Authenticator\Exceptions\AuthenticationException 24 | */ 25 | public function authenticate( 26 | ClientInterface $client, 27 | ): void; 28 | 29 | /** 30 | * Retrieve authentication token. 31 | */ 32 | public function token(): string; 33 | } 34 | -------------------------------------------------------------------------------- /contracts/Authenticator/Exceptions/AuthenticationException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Cog\Contracts\YouTrack\Rest\Authenticator\Exceptions; 15 | 16 | use Cog\Contracts\YouTrack\Rest\Client\Exceptions\ClientException; 17 | 18 | class AuthenticationException extends ClientException {} 19 | -------------------------------------------------------------------------------- /contracts/Authorizer/Authorizer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Cog\Contracts\YouTrack\Rest\Authorizer; 15 | 16 | use Cog\Contracts\YouTrack\Rest\Client\Client as ClientInterface; 17 | 18 | interface Authorizer 19 | { 20 | /** 21 | * Append authorization headers to REST client. 22 | */ 23 | public function appendHeadersTo( 24 | ClientInterface $client, 25 | ): void; 26 | } 27 | -------------------------------------------------------------------------------- /contracts/Authorizer/Exceptions/AuthorizationException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Cog\Contracts\YouTrack\Rest\Authorizer\Exceptions; 15 | 16 | use Cog\Contracts\YouTrack\Rest\Client\Exceptions\ClientException; 17 | 18 | class AuthorizationException extends ClientException {} 19 | -------------------------------------------------------------------------------- /contracts/Authorizer/Exceptions/InvalidAuthorizationToken.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Cog\Contracts\YouTrack\Rest\Authorizer\Exceptions; 15 | 16 | class InvalidAuthorizationToken extends AuthorizationException {} 17 | -------------------------------------------------------------------------------- /contracts/Client/Client.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Cog\Contracts\YouTrack\Rest\Client; 15 | 16 | use Cog\Contracts\YouTrack\Rest\Response\Response as ResponseInterface; 17 | 18 | interface Client 19 | { 20 | /** 21 | * Version of PHP YouTrack REST client. 22 | */ 23 | public const VERSION = '8.0.0'; 24 | 25 | /** 26 | * Create and send an HTTP request. 27 | * 28 | * @param string $method 29 | * @param string $uri 30 | * @param array $params 31 | * @param array $options 32 | * @return \Cog\Contracts\YouTrack\Rest\Response\Response 33 | * 34 | * @throws \Cog\Contracts\YouTrack\Rest\Authenticator\Exceptions\AuthenticationException 35 | * @throws \Cog\Contracts\YouTrack\Rest\Authorizer\Exceptions\InvalidAuthorizationToken 36 | * @throws \Cog\Contracts\YouTrack\Rest\Client\Exceptions\ClientException 37 | */ 38 | public function request( 39 | string $method, 40 | string $uri, 41 | array $params = [], 42 | array $options = [], 43 | ): ResponseInterface; 44 | 45 | /** 46 | * Create and send an GET HTTP request. 47 | * 48 | * @param string $uri 49 | * @param array $params 50 | * @param array $options 51 | * @return \Cog\Contracts\YouTrack\Rest\Response\Response 52 | * 53 | * @throws \Cog\Contracts\YouTrack\Rest\Authenticator\Exceptions\AuthenticationException 54 | * @throws \Cog\Contracts\YouTrack\Rest\Authorizer\Exceptions\InvalidAuthorizationToken 55 | * @throws \Cog\Contracts\YouTrack\Rest\Client\Exceptions\ClientException 56 | */ 57 | public function get( 58 | string $uri, 59 | array $params = [], 60 | array $options = [], 61 | ): ResponseInterface; 62 | 63 | /** 64 | * Create and send an POST HTTP request. 65 | * 66 | * @param string $uri 67 | * @param array $params 68 | * @param array $options 69 | * @return \Cog\Contracts\YouTrack\Rest\Response\Response 70 | * 71 | * @throws \Cog\Contracts\YouTrack\Rest\Authenticator\Exceptions\AuthenticationException 72 | * @throws \Cog\Contracts\YouTrack\Rest\Authorizer\Exceptions\InvalidAuthorizationToken 73 | * @throws \Cog\Contracts\YouTrack\Rest\Client\Exceptions\ClientException 74 | */ 75 | public function post( 76 | string $uri, 77 | array $params = [], 78 | array $options = [], 79 | ): ResponseInterface; 80 | 81 | /** 82 | * Create and send an PUT HTTP request. 83 | * 84 | * @param string $uri 85 | * @param array $params 86 | * @param array $options 87 | * @return \Cog\Contracts\YouTrack\Rest\Response\Response 88 | * 89 | * @throws \Cog\Contracts\YouTrack\Rest\Authenticator\Exceptions\AuthenticationException 90 | * @throws \Cog\Contracts\YouTrack\Rest\Authorizer\Exceptions\InvalidAuthorizationToken 91 | * @throws \Cog\Contracts\YouTrack\Rest\Client\Exceptions\ClientException 92 | */ 93 | public function put( 94 | string $uri, 95 | array $params = [], 96 | array $options = [], 97 | ): ResponseInterface; 98 | 99 | /** 100 | * Create and send an DELETE HTTP request. 101 | * 102 | * @param string $uri 103 | * @param array $params 104 | * @param array $options 105 | * @return \Cog\Contracts\YouTrack\Rest\Response\Response 106 | * 107 | * @throws \Cog\Contracts\YouTrack\Rest\Authenticator\Exceptions\AuthenticationException 108 | * @throws \Cog\Contracts\YouTrack\Rest\Authorizer\Exceptions\InvalidAuthorizationToken 109 | * @throws \Cog\Contracts\YouTrack\Rest\Client\Exceptions\ClientException 110 | */ 111 | public function delete( 112 | string $uri, 113 | array $params = [], 114 | array $options = [], 115 | ): ResponseInterface; 116 | 117 | /** 118 | * Write header value. 119 | */ 120 | public function withHeader( 121 | string $key, 122 | string $value, 123 | ): void; 124 | 125 | /** 126 | * Write header values. 127 | * 128 | * @param array $headers 129 | * @return void 130 | */ 131 | public function withHeaders( 132 | array $headers, 133 | ): void; 134 | } 135 | -------------------------------------------------------------------------------- /contracts/Client/Exceptions/ClientException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Cog\Contracts\YouTrack\Rest\Client\Exceptions; 15 | 16 | use RuntimeException; 17 | 18 | class ClientException extends RuntimeException {} 19 | -------------------------------------------------------------------------------- /contracts/HttpClient/Exceptions/HttpClientException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Cog\Contracts\YouTrack\Rest\HttpClient\Exceptions; 15 | 16 | use RuntimeException; 17 | 18 | class HttpClientException extends RuntimeException {} 19 | -------------------------------------------------------------------------------- /contracts/HttpClient/HttpClient.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Cog\Contracts\YouTrack\Rest\HttpClient; 15 | 16 | use Psr\Http\Message\ResponseInterface as PsrResponseInterface; 17 | 18 | interface HttpClient 19 | { 20 | /** 21 | * Send request to the server and fetch the raw response. 22 | * 23 | * @param string $method Request Method 24 | * @param string $uri URI/Endpoint to send the request to 25 | * @param array $options Additional Options 26 | * @return \Psr\Http\Message\ResponseInterface Raw response from the server 27 | * 28 | * @throws \Cog\Contracts\YouTrack\Rest\HttpClient\Exceptions\HttpClientException 29 | */ 30 | public function request( 31 | string $method, 32 | string $uri, 33 | array $options = [], 34 | ): PsrResponseInterface; 35 | } 36 | -------------------------------------------------------------------------------- /contracts/Response/Response.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Cog\Contracts\YouTrack\Rest\Response; 15 | 16 | use Psr\Http\Message\ResponseInterface as PsrResponseInterface; 17 | 18 | interface Response 19 | { 20 | /** 21 | * Returns original HTTP client response. 22 | */ 23 | public function httpResponse(): PsrResponseInterface; 24 | 25 | /** 26 | * Returns the response status code. 27 | * 28 | * The status code is a 3-digit integer result code of the server's attempt 29 | * to understand and satisfy the request. 30 | */ 31 | public function statusCode(): int; 32 | 33 | /** 34 | * Retrieves a comma-separated string of the values for a single header. 35 | */ 36 | public function header( 37 | string $header, 38 | ): string; 39 | 40 | /** 41 | * Transform response cookie headers to string. 42 | */ 43 | public function cookie(): string; 44 | 45 | /** 46 | * Returns response location header. 47 | */ 48 | public function location(): string; 49 | 50 | /** 51 | * Returns body of the response. 52 | */ 53 | public function body(): string; 54 | 55 | /** 56 | * Transform response body to array. 57 | * 58 | * @return array 59 | */ 60 | public function toArray(): array; 61 | 62 | /** 63 | * Assert the status code of the response. 64 | */ 65 | public function isStatusCode( 66 | int $code, 67 | ): bool; 68 | 69 | /** 70 | * Determine if response has successful status code. 71 | */ 72 | public function isSuccess(): bool; 73 | 74 | /** 75 | * Determine if response has redirect status code. 76 | */ 77 | public function isRedirect(): bool; 78 | 79 | /** 80 | * Determine if response has client error status code. 81 | */ 82 | public function isClientError(): bool; 83 | 84 | /** 85 | * Determine if response has server error status code. 86 | */ 87 | public function isServerError(): bool; 88 | } 89 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | php81: 3 | container_name: php-youtrack-rest-81 4 | image: php-youtrack-rest 5 | build: 6 | context: ./ 7 | dockerfile: ./.docker/php/php81/Dockerfile 8 | tty: true 9 | working_dir: /app 10 | volumes: 11 | - ./:/app 12 | 13 | php82: 14 | container_name: php-youtrack-rest-82 15 | image: php-youtrack-rest 16 | build: 17 | context: ./ 18 | dockerfile: ./.docker/php/php82/Dockerfile 19 | tty: true 20 | working_dir: /app 21 | volumes: 22 | - ./:/app 23 | 24 | php83: 25 | container_name: php-youtrack-rest-83 26 | image: php-youtrack-rest 27 | build: 28 | context: ./ 29 | dockerfile: ./.docker/php/php83/Dockerfile 30 | tty: true 31 | working_dir: /app 32 | volumes: 33 | - ./:/app 34 | 35 | php84: 36 | container_name: php-youtrack-rest-84 37 | image: php-youtrack-rest 38 | build: 39 | context: ./ 40 | dockerfile: ./.docker/php/php84/Dockerfile 41 | tty: true 42 | working_dir: /app 43 | volumes: 44 | - ./:/app 45 | -------------------------------------------------------------------------------- /examples/cookie-authorization.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | // Boot third party libraries 15 | require_once __DIR__ . '/../vendor/autoload.php'; 16 | 17 | use Cog\YouTrack\Rest; 18 | 19 | // Application configuration (replace with your YouTrack server values) 20 | $apiBaseUri = 'https://write-youtrack-domain.here'; 21 | $apiUsername = 'YOUTRACK_USERNAME'; 22 | $apiPassword = 'YOUTRACK_PASSWORD'; 23 | 24 | // Instantiate PSR-7 HTTP Client 25 | $psrHttpClient = new \GuzzleHttp\Client([ 26 | 'base_uri' => $apiBaseUri, 27 | 'debug' => true, 28 | ]); 29 | 30 | // Instantiate YouTrack API HTTP Client 31 | $httpClient = new Rest\HttpClient\GuzzleHttpClient($psrHttpClient); 32 | 33 | // Instantiate YouTrack API Cookie Authenticator 34 | $authenticator = new Rest\Authenticator\CookieAuthenticator($apiUsername, $apiPassword); 35 | 36 | // Instantiate YouTrack API Cookie Authorizer 37 | $authorizer = new Rest\Authorizer\CookieAuthorizer($authenticator); 38 | 39 | // Instantiate YouTrack API Client 40 | $client = new Rest\Client\YouTrackClient($httpClient, $authorizer); 41 | 42 | // Do request to the API 43 | $response = $client->get('/admin/project'); 44 | 45 | // Convert response to array 46 | $projects = $response->toArray(); 47 | 48 | // Render projects one by one 49 | echo 'Project list:'; 50 | foreach ($projects as $project) { 51 | echo ' #' . $project['id']; 52 | } 53 | -------------------------------------------------------------------------------- /examples/token-authorization.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | // Boot third party libraries 15 | require_once __DIR__ . '/../vendor/autoload.php'; 16 | 17 | use Cog\YouTrack\Rest; 18 | 19 | // Application configuration (replace with your YouTrack server values) 20 | $apiBaseUri = 'https://write-youtrack-domain.here'; 21 | $apiToken = 'YOUTRACK_PERMANENT_TOKEN'; 22 | 23 | // Instantiate PSR-7 HTTP Client 24 | $psrHttpClient = new \GuzzleHttp\Client([ 25 | 'base_uri' => $apiBaseUri, 26 | 'debug' => true, 27 | ]); 28 | 29 | // Instantiate YouTrack API HTTP Client 30 | $httpClient = new Rest\HttpClient\GuzzleHttpClient($psrHttpClient); 31 | 32 | // Instantiate YouTrack API Token Authorizer 33 | $authorizer = new Rest\Authorizer\TokenAuthorizer($apiToken); 34 | 35 | // Instantiate YouTrack API Client 36 | $client = new Rest\Client\YouTrackClient($httpClient, $authorizer); 37 | 38 | // Do request to the API 39 | $response = $client->get('/admin/project'); 40 | 41 | // Convert response to array 42 | $projects = $response->toArray(); 43 | 44 | // Render projects one by one 45 | echo 'Project list:'; 46 | foreach ($projects as $project) { 47 | echo ' #' . $project['id']; 48 | } 49 | -------------------------------------------------------------------------------- /src/Authenticator/CookieAuthenticator.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Cog\YouTrack\Rest\Authenticator; 15 | 16 | use Cog\Contracts\YouTrack\Rest\Authenticator\Authenticator as AuthenticatorInterface; 17 | use Cog\Contracts\YouTrack\Rest\Client\Client as ClientInterface; 18 | 19 | class CookieAuthenticator implements 20 | AuthenticatorInterface 21 | { 22 | /** 23 | * Determine is trying to authenticate. 24 | */ 25 | private bool $isAuthenticating = false; 26 | 27 | private string $cookie = ''; 28 | 29 | public function __construct( 30 | private readonly string $username, 31 | private readonly string $password, 32 | ) {} 33 | 34 | /** 35 | * Authenticate client and returns cookie on success login. 36 | * 37 | * @throws \Cog\Contracts\YouTrack\Rest\Authenticator\Exceptions\AuthenticationException 38 | */ 39 | public function authenticate( 40 | ClientInterface $client, 41 | ): void { 42 | if ($this->cookie !== '' || $this->isAuthenticating) { 43 | return; 44 | } 45 | 46 | $this->isAuthenticating = true; 47 | $response = $client->request('POST', '/user/login', [ 48 | 'login' => $this->username, 49 | 'password' => $this->password, 50 | ]); 51 | $this->isAuthenticating = false; 52 | 53 | if ($response->isStatusCode(200)) { 54 | $this->cookie = $response->cookie(); 55 | } 56 | } 57 | 58 | /** 59 | * Retrieve authentication token. 60 | */ 61 | public function token(): string 62 | { 63 | return $this->cookie; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Authorizer/CookieAuthorizer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Cog\YouTrack\Rest\Authorizer; 15 | 16 | use Cog\Contracts\YouTrack\Rest\Authenticator\Authenticator as AuthenticatorInterface; 17 | use Cog\Contracts\YouTrack\Rest\Authorizer\Authorizer as AuthorizerInterface; 18 | use Cog\Contracts\YouTrack\Rest\Client\Client as ClientInterface; 19 | 20 | class CookieAuthorizer implements 21 | AuthorizerInterface 22 | { 23 | public function __construct( 24 | private readonly AuthenticatorInterface $authenticator, 25 | ) { 26 | } 27 | 28 | /** 29 | * Append authorization headers to REST client. 30 | * 31 | * @throws \Cog\Contracts\YouTrack\Rest\Authenticator\Exceptions\AuthenticationException 32 | */ 33 | public function appendHeadersTo( 34 | ClientInterface $client, 35 | ): void { 36 | $this->authenticator->authenticate($client); 37 | 38 | $client->withHeader('Cookie', $this->authenticator->token()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Authorizer/TokenAuthorizer.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Cog\YouTrack\Rest\Authorizer; 15 | 16 | use Cog\Contracts\YouTrack\Rest\Authorizer\Authorizer as AuthorizerInterface; 17 | use Cog\Contracts\YouTrack\Rest\Client\Client as ClientInterface; 18 | 19 | /** 20 | * @see https://www.jetbrains.com/help/youtrack/standalone/2017.2/Log-in-to-YouTrack.html. 21 | */ 22 | class TokenAuthorizer implements 23 | AuthorizerInterface 24 | { 25 | public function __construct( 26 | private readonly string $token, 27 | ) { 28 | } 29 | 30 | /** 31 | * Append authorization headers to REST client. 32 | */ 33 | public function appendHeadersTo( 34 | ClientInterface $client, 35 | ): void { 36 | $client->withHeader('Authorization', "Bearer {$this->token}"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Client/YouTrackClient.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Cog\YouTrack\Rest\Client; 15 | 16 | use Cog\Contracts\YouTrack\Rest\Authenticator\Exceptions\AuthenticationException; 17 | use Cog\Contracts\YouTrack\Rest\Authorizer\Authorizer as AuthorizerInterface; 18 | use Cog\Contracts\YouTrack\Rest\Authorizer\Exceptions\InvalidAuthorizationToken; 19 | use Cog\Contracts\YouTrack\Rest\Client\Client as RestClientInterface; 20 | use Cog\Contracts\YouTrack\Rest\Client\Exceptions\ClientException; 21 | use Cog\Contracts\YouTrack\Rest\HttpClient\Exceptions\HttpClientException; 22 | use Cog\Contracts\YouTrack\Rest\HttpClient\HttpClient as HttpClientInterface; 23 | use Cog\Contracts\YouTrack\Rest\Response\Response as ResponseInterface; 24 | use Cog\YouTrack\Rest\Response\YouTrackResponse; 25 | 26 | /** 27 | * @see https://www.jetbrains.com/help/youtrack/standalone/2017.2/YouTrack-REST-API-Reference.html 28 | */ 29 | class YouTrackClient implements 30 | RestClientInterface 31 | { 32 | /** 33 | * Endpoint path prefix. 34 | * 35 | * @todo test it 36 | */ 37 | private string $endpointPathPrefix; 38 | 39 | /** 40 | * Request headers. 41 | * 42 | * @var array 43 | */ 44 | private array $headers = []; 45 | 46 | public function __construct( 47 | private readonly HttpClientInterface $httpClient, 48 | private readonly AuthorizerInterface $authorizer, 49 | string | null $endpointPathPrefix = null, 50 | ) { 51 | $this->endpointPathPrefix = $endpointPathPrefix !== null 52 | ? $endpointPathPrefix 53 | : 'api'; 54 | } 55 | 56 | /** 57 | * Create and send an HTTP request. 58 | * 59 | * @param string $method 60 | * @param string $uri 61 | * @param array $params 62 | * @param array $options 63 | * @return \Cog\Contracts\YouTrack\Rest\Response\Response 64 | * 65 | * @throws \Cog\Contracts\YouTrack\Rest\Authenticator\Exceptions\AuthenticationException 66 | * @throws \Cog\Contracts\YouTrack\Rest\Authorizer\Exceptions\InvalidAuthorizationToken 67 | * @throws \Cog\Contracts\YouTrack\Rest\Client\Exceptions\ClientException 68 | */ 69 | public function request( 70 | string $method, 71 | string $uri, 72 | array $params = [], 73 | array $options = [], 74 | ): ResponseInterface { 75 | try { 76 | $response = $this->httpClient->request( 77 | $method, 78 | $this->buildUri($uri), 79 | $this->buildOptions($params, $options), 80 | ); 81 | } catch (HttpClientException $e) { 82 | switch ($e->getCode()) { 83 | case 401: 84 | throw new InvalidAuthorizationToken($e->getMessage(), $e->getCode()); 85 | case 403: 86 | throw new AuthenticationException($e->getMessage(), $e->getCode()); 87 | default: 88 | throw new ClientException($e->getMessage(), $e->getCode(), $e); 89 | } 90 | } 91 | 92 | return new YouTrackResponse($response); 93 | } 94 | 95 | /** 96 | * Create and send an GET HTTP request. 97 | * 98 | * @param string $uri 99 | * @param array $params 100 | * @param array $options 101 | * @return \Cog\Contracts\YouTrack\Rest\Response\Response 102 | * 103 | * @throws \Cog\Contracts\YouTrack\Rest\Authenticator\Exceptions\AuthenticationException 104 | * @throws \Cog\Contracts\YouTrack\Rest\Authorizer\Exceptions\InvalidAuthorizationToken 105 | * @throws \Cog\Contracts\YouTrack\Rest\Client\Exceptions\ClientException 106 | */ 107 | public function get( 108 | string $uri, 109 | array $params = [], 110 | array $options = [], 111 | ): ResponseInterface { 112 | return $this->request('GET', $uri, $params, $options); 113 | } 114 | 115 | /** 116 | * Create and send an POST HTTP request. 117 | * 118 | * @param string $uri 119 | * @param array $params 120 | * @param array $options 121 | * @return \Cog\Contracts\YouTrack\Rest\Response\Response 122 | * 123 | * @throws \Cog\Contracts\YouTrack\Rest\Authenticator\Exceptions\AuthenticationException 124 | * @throws \Cog\Contracts\YouTrack\Rest\Authorizer\Exceptions\InvalidAuthorizationToken 125 | * @throws \Cog\Contracts\YouTrack\Rest\Client\Exceptions\ClientException 126 | */ 127 | public function post( 128 | string $uri, 129 | array $params = [], 130 | array $options = [], 131 | ): ResponseInterface { 132 | return $this->request('POST', $uri, $params, $options); 133 | } 134 | 135 | /** 136 | * Create and send an PUT HTTP request. 137 | * 138 | * @param string $uri 139 | * @param array $params 140 | * @param array $options 141 | * @return \Cog\Contracts\YouTrack\Rest\Response\Response 142 | * 143 | * @throws \Cog\Contracts\YouTrack\Rest\Authenticator\Exceptions\AuthenticationException 144 | * @throws \Cog\Contracts\YouTrack\Rest\Authorizer\Exceptions\InvalidAuthorizationToken 145 | * @throws \Cog\Contracts\YouTrack\Rest\Client\Exceptions\ClientException 146 | */ 147 | public function put( 148 | string $uri, 149 | array $params = [], 150 | array $options = [], 151 | ): ResponseInterface { 152 | return $this->request('PUT', $uri, $params, $options); 153 | } 154 | 155 | /** 156 | * Create and send an DELETE HTTP request. 157 | * 158 | * @param string $uri 159 | * @param array $params 160 | * @param array $options 161 | * @return \Cog\Contracts\YouTrack\Rest\Response\Response 162 | * 163 | * @throws \Cog\Contracts\YouTrack\Rest\Authenticator\Exceptions\AuthenticationException 164 | * @throws \Cog\Contracts\YouTrack\Rest\Authorizer\Exceptions\InvalidAuthorizationToken 165 | * @throws \Cog\Contracts\YouTrack\Rest\Client\Exceptions\ClientException 166 | */ 167 | public function delete( 168 | string $uri, 169 | array $params = [], 170 | array $options = [], 171 | ): ResponseInterface { 172 | return $this->request('DELETE', $uri, $params, $options); 173 | } 174 | 175 | /** 176 | * Write header value. 177 | */ 178 | public function withHeader( 179 | string $key, 180 | string $value, 181 | ): void { 182 | $this->headers[$key] = $value; 183 | } 184 | 185 | /** 186 | * Write header values. 187 | * 188 | * @param array $headers 189 | * @return void 190 | */ 191 | public function withHeaders( 192 | array $headers, 193 | ): void { 194 | $this->headers = array_merge_recursive($this->headers, $headers); 195 | } 196 | 197 | /** 198 | * Build endpoint URI. 199 | */ 200 | protected function buildUri( 201 | string $uri, 202 | ): string { 203 | return $this->endpointPathPrefix . '/' . ltrim($uri, '/'); 204 | } 205 | 206 | /** 207 | * Build request options. 208 | * 209 | * @param array $params 210 | * @param array $options 211 | * @return array 212 | */ 213 | protected function buildOptions( 214 | array $params = [], 215 | array $options = [], 216 | ): array { 217 | if ($params) { 218 | if (isset($options['multipart'])) { 219 | foreach ($params as $key => $value) { 220 | $options['multipart'][] = [ 221 | 'name' => $key, 222 | 'contents' => $value, 223 | ]; 224 | } 225 | } else { 226 | $options['form_params'] = array_merge($params, $options['form_params'] ?? []); 227 | } 228 | } 229 | 230 | $options['headers'] = array_merge($this->buildHeaders(), $options['headers'] ?? []); 231 | 232 | return $options; 233 | } 234 | 235 | /** 236 | * Build request headers. 237 | * 238 | * @return array 239 | */ 240 | protected function buildHeaders(): array 241 | { 242 | $this->headers = [ 243 | 'User-Agent' => 'Cog-YouTrack-REST-PHP/' . self::VERSION, 244 | 'Accept' => 'application/json', 245 | ]; 246 | 247 | $this->authorizer->appendHeadersTo($this); 248 | 249 | return $this->headers; 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/HttpClient/GuzzleHttpClient.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Cog\YouTrack\Rest\HttpClient; 15 | 16 | use Cog\Contracts\YouTrack\Rest\HttpClient\Exceptions\HttpClientException; 17 | use Cog\Contracts\YouTrack\Rest\HttpClient\HttpClient as HttpClientInterface; 18 | use GuzzleHttp\ClientInterface; 19 | use GuzzleHttp\Exception\BadResponseException; 20 | use GuzzleHttp\Exception\RequestException; 21 | use GuzzleHttp\Utils; 22 | use Psr\Http\Message\ResponseInterface; 23 | use Throwable; 24 | 25 | class GuzzleHttpClient implements 26 | HttpClientInterface 27 | { 28 | public function __construct( 29 | private readonly ClientInterface $httpClient, 30 | ) {} 31 | 32 | /** 33 | * Send request to the server and fetch the raw response. 34 | * 35 | * @param string $method Request Method 36 | * @param string $uri URI/Endpoint to send the request to 37 | * @param array $options Additional Options 38 | * @return \Psr\Http\Message\ResponseInterface Raw response from the server 39 | * 40 | * @throws \Cog\Contracts\YouTrack\Rest\HttpClient\Exceptions\HttpClientException 41 | */ 42 | public function request( 43 | string $method, 44 | string $uri, 45 | array $options = [], 46 | ): ResponseInterface { 47 | try { 48 | return $this->httpClient->request($method, $uri, $this->buildOptions($options)); 49 | } catch (BadResponseException | RequestException $e) { 50 | $rawResponse = $e->getResponse(); 51 | if (!$rawResponse instanceof ResponseInterface) { 52 | throw new HttpClientException($e->getMessage(), $e->getCode(), $e); 53 | } 54 | throw new HttpClientException($rawResponse->getBody()->getContents(), $e->getCode(), $e); 55 | } catch (Throwable $e) { 56 | throw new HttpClientException($e->getMessage(), $e->getCode(), $e); 57 | } 58 | } 59 | 60 | /** 61 | * Build Http Client Request options. 62 | * 63 | * @param array $options 64 | * @return array 65 | */ 66 | private function buildOptions( 67 | array $options, 68 | ): array { 69 | return $this->appendUserAgent($options); 70 | } 71 | 72 | /** 73 | * Append User-Agent header to Request options. 74 | * 75 | * @param array $options 76 | * @return array 77 | */ 78 | private function appendUserAgent( 79 | array $options, 80 | ): array { 81 | $defaultAgent = Utils::defaultUserAgent(); 82 | if (extension_loaded('curl') && function_exists('curl_version')) { 83 | $curlVersion = \curl_version(); 84 | if (\is_array($curlVersion)) { 85 | $defaultAgent .= ' curl/' . $curlVersion['version']; 86 | } 87 | } 88 | $defaultAgent .= ' PHP/' . PHP_VERSION; 89 | 90 | if (!isset($options['headers']['User-Agent'])) { 91 | $options['headers']['User-Agent'] = $defaultAgent; 92 | } 93 | 94 | $options['headers']['User-Agent'] .= ' ' . $defaultAgent; 95 | 96 | return $options; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Response/YouTrackResponse.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Cog\YouTrack\Rest\Response; 15 | 16 | use Cog\Contracts\YouTrack\Rest\Response\Response as ResponseInterface; 17 | use Psr\Http\Message\ResponseInterface as PsrResponseInterface; 18 | 19 | class YouTrackResponse implements 20 | ResponseInterface 21 | { 22 | public function __construct( 23 | private readonly PsrResponseInterface $response, 24 | ) {} 25 | 26 | /** 27 | * Returns original HTTP client response. 28 | */ 29 | public function httpResponse(): PsrResponseInterface 30 | { 31 | return $this->response; 32 | } 33 | 34 | /** 35 | * Returns the response status code. 36 | * 37 | * The status code is a 3-digit integer result code of the server's attempt 38 | * to understand and satisfy the request. 39 | */ 40 | public function statusCode(): int 41 | { 42 | return $this->response->getStatusCode(); 43 | } 44 | 45 | /** 46 | * Retrieves a comma-separated string of the values for a single header. 47 | */ 48 | public function header( 49 | string $header, 50 | ): string { 51 | return $this->response->getHeaderLine($header); 52 | } 53 | 54 | /** 55 | * Transform response cookie headers to string. 56 | */ 57 | public function cookie(): string 58 | { 59 | return $this->header('Set-Cookie'); 60 | } 61 | 62 | /** 63 | * Returns response location header. 64 | */ 65 | public function location(): string 66 | { 67 | return $this->header('Location'); 68 | } 69 | 70 | /** 71 | * Returns body of the response. 72 | */ 73 | public function body(): string 74 | { 75 | return (string)$this->response->getBody(); 76 | } 77 | 78 | /** 79 | * Transform response body to array. 80 | * 81 | * @return array 82 | */ 83 | public function toArray(): array 84 | { 85 | return json_decode($this->response->getBody()->getContents(), true); 86 | } 87 | 88 | /** 89 | * Assert the status code of the response. 90 | */ 91 | public function isStatusCode( 92 | int $code, 93 | ): bool { 94 | return $this->response->getStatusCode() === $code; 95 | } 96 | 97 | /** 98 | * Determine if response has successful status code. 99 | */ 100 | public function isSuccess(): bool 101 | { 102 | return $this->statusCode() >= 200 && $this->statusCode() < 300; 103 | } 104 | 105 | /** 106 | * Determine if response has redirect status code. 107 | */ 108 | public function isRedirect(): bool 109 | { 110 | return $this->statusCode() >= 300 && $this->statusCode() < 400; 111 | } 112 | 113 | /** 114 | * Determine if response has client error status code. 115 | */ 116 | public function isClientError(): bool 117 | { 118 | return $this->statusCode() >= 400 && $this->statusCode() < 500; 119 | } 120 | 121 | /** 122 | * Determine if response has server error status code. 123 | */ 124 | public function isServerError(): bool 125 | { 126 | return $this->statusCode() >= 500; 127 | } 128 | } 129 | --------------------------------------------------------------------------------