cybercog/youtrack-rest-php
A PHP client for the YouTrack REST API that wraps issues, projects, users, and time tracking in an object-oriented interface. Works with YouTrack Cloud and standalone instances, using PSR-compatible HTTP clients and typed response models. Ideal for integrating YouTrack issue tracking into PHP applic...
Install and wire it up. This is a framework-agnostic PSR-7 library, so you install it with Composer and wire it manually rather than through a service provider:
composer require cybercog/youtrack-rest-php
You need three things before you write a line of code: a YouTrack base URI (e.g. https://youtrack.example.com), an auth strategy, and an HTTP client implementation. The fastest path is a permanent API token, which means TokenAuthorizer + GuzzleHttpClient:
use Cog\YouTrack\Rest\Authorizer\TokenAuthorizer;
use Cog\YouTrack\Rest\Client\GuzzleHttpClient;
use Cog\YouTrack\Rest\YouTrackClient;
$client = new YouTrackClient(
new GuzzleHttpClient(/* guzzle options: base_uri, timeouts, etc. */),
new TokenAuthorizer($token),
);
Where to look first. Skim the contracts/ directory before the src/ directory. The library is interface-driven — Cog\Contracts\YouTrack\Rest\ defines every collaborator (HttpClient, Authorizer, Response), and src/ has the concrete implementations. Reading the contracts tells you what is safely swappable and what your application should depend on. Then look at YouTrackClient and YouTrackResponse — those are the two classes you will touch on almost every request.
First real use case: pull issues for a project. The typical "day one" script is a read against the issues endpoint, then $response->toArray() to work with decoded JSON. Because responses come back wrapped in YouTrackResponse, use $response->isSuccess() for a quick branch and $response->toArray() when you want associative arrays. Do not reach for the underlying PSR-7 stream unless you actually need raw bytes.
Get an integration test token early. The repo's own integration tests expect YOUTRACK_BASE_URI, YOUTRACK_TOKEN, YOUTRACK_AUTH, and YOUTRACK_PROJECT. Mirror that pattern in your own app: keep a token in .env, never in code, and give yourself a scratch project on a dev YouTrack instance so you can iterate without polluting real boards.
Depend on the contracts, not the concretes. In a Laravel app, bind HttpClient and Authorizer in a service provider and inject YouTrackClient into a thin application-level service (e.g. YouTrackGateway). That keeps your domain code free of Guzzle specifics and makes faking trivial in tests. The library itself is deliberately framework-agnostic; your integration layer is where framework concerns belong.
Choose the authorizer deliberately. TokenAuthorizer is the right default for server-to-server work — no session state, no refresh dance. Reach for CookieAuthorizer + CookieAuthenticator only when you must authenticate as a human user with username/password (e.g. impersonated automation). The authenticator handles the login flow and caches cookies, which is convenient but introduces state you now have to think about (see Gotchas).
Wrap each YouTrack call in an application-level method. YouTrack's REST API is broad; the library gives you a capable HTTP client, not a full API surface. The pattern that holds up is a narrow gateway: createIssue(), transitionIssue(), findIssueByQuery(), attachFile(). Each method builds the request, calls the client, checks isSuccess(), and returns a typed domain object. This is where you absorb YouTrack's quirks (custom fields, workflow states, project short-names) so the rest of your code never sees them.
Treat YouTrackResponse as the unit of work. toArray() for JSON bodies, isSuccess() for branching, and the PSR-7 helpers for anything else (headers, status, raw body for file downloads). If you're returning YouTrack data up the stack, decode once at the gateway boundary and hand typed objects to the rest of the app — don't leak YouTrackResponse into controllers.
Catch domain exceptions, not Guzzle exceptions. The GuzzleHttpClient adapter translates transport failures, and the auth path raises InvalidAuthorizationToken (401), AuthenticationException (403), and ClientException for everything else. Your gateway should catch these specific types and translate them into your own domain errors (or rethrow as a 502/503 at the controller boundary). Avoid catching \Throwable — you'll lose the distinction between "bad token", "bad permissions", and "YouTrack is down".
Test against mocked responses, not a live instance. The library's own tests use Guzzle's MockHandler via the HasFakeHttpResponses trait, with fixtures under tests/stubs/server-responses/. Copy that pattern: construct a GuzzleHttpClient backed by a MockHandler, assert on the requests your gateway makes, and keep a small set of real integration tests gated behind the YOUTRACK_* env vars. You get fast unit tests and occasional confidence that the contract still matches the real server.
Integrate with queues and schedulers, not request lifecycles. YouTrack calls are network I/O against an external system — do them in jobs, not in web requests. The library has no async story, so a queue worker doing one call per job is the cleanest scaling story. If you need to fan out over many issues, use Bus::batch() and a per-issue job.
Small ecosystem, thin docs. 37 stars and no dedicated docs site means you will be reading the contracts/ interfaces and the tests to answer most "how do I…" questions. That's fine — the contracts are short — but budget time for it on day one instead of expecting a quickstart tutorial. The source is your reference manual.
No first-party Laravel package. You will write your own service provider and facade if you want them. Budget maybe 30–60 lines. Resist the urge to publish a wrapper package for internal use unless you actually have multiple consumers; a single service provider plus a gateway class is usually enough.
Cookie authentication is stateful. CookieAuthenticator caches cookies, which is great for throughput but bad for long-lived workers — cookies expire, and a cached cookie that has gone stale will surface as auth failures mid-job. If you use cookie auth in a queue worker, either re-authenticate on InvalidAuthorizationToken or construct the client per job. Token auth avoids this class of bug entirely; prefer it where you can.
Exception mapping is auth-shaped, not resource-shaped. The 401/403 mapping is helpful, but a 404 (missing issue), a 400 (bad custom-field payload), and a 500 all fall into ClientException. Inspect the wrapped response's status code inside your gateway if you need to distinguish "not found" from "YouTrack is having a bad day" — otherwise you'll end up retrying things that will never succeed and failing to retry things that will.
declare(strict_types=1) and PHP 8.1+ are enforced. The library is PHPStan level 8 and PSR-12 clean, so it will behave predictably under strict types — but your consuming code needs to match. If your app is on PHP 8.0 or uses loose typing conventions, this package will surface type errors at the boundary. Treat PHP 8.1 as a hard floor.
PSR-7 response is the escape hatch. For anything the wrapper doesn't cover — streaming a large attachment, reading non-JSON error bodies, inspecting YouTrack's X- rate-limit or pagination headers — drop to the underlying PSR-7 response. It's there by design; use it rather than re-implementing it.
Extension points are the contracts, not inheritance. HttpClient, Authorizer, and the response wrapper are all interfaces. If you need retries with exponential backoff, a circuit breaker, or request logging, implement them as a decorator around HttpClient rather than subclassing GuzzleHttpClient. Same for auth — wrap or replace Authorizer, never fork TokenAuthorizer. This keeps you upgrade-safe and matches how the library's own tests substitute collaborators.
Version pinning matters more than usual here. With a small maintainer base and a last release dated 2026, pin to a specific minor ("cybercog/youtrack-rest-php": "^X.Y") and run your integration tests against the pinned version in CI. You want a bump to be a deliberate decision, not a Tuesday surprise.
How can I help you explore Laravel packages today?