kbsali/redmine-api
PHP client for the Redmine REST API that simplifies integrating Redmine into your applications. Access projects, issues, users, time entries, versions, and more through a clean, object-oriented interface with straightforward authentication and response handling.
Architecture fit. Unchanged, and marginally reinforced. kbsali/php-redmine-api remains a framework-agnostic PHP SDK over the Redmine REST API with no Laravel coupling — no service provider, facade, config publishing, or Eloquent models. It belongs behind a domain-owned interface (RedmineGateway / IssueRepository) as an infrastructure adapter, injected via the container and consumed by application services and queued jobs. v2.9.1 adds a fromHttpClient construction path (via ClientApiTrait), which means the SDK can be built around a client instance the application already owns rather than one it constructs internally — a meaningful architectural improvement because transport concerns (middleware, retry, logging, stubbing) can now be centralized in the app's container binding instead of duplicated around the SDK. Fit verdict is unchanged: good for a bounded Redmine integration, poor if the team expects a batteries-included Laravel package.
Integration feasibility. Slightly better than assessed. Installation is still composer require plus config plus a singleton binding plus a thin wrapper, and the SDK still performs its own HTTP transport, so Http::fake() will not intercept it. The change is that tests and production wiring can now share a single injected client: a mock/stub handler stack for adapter tests, and middleware for structured logging and retries at the transport layer rather than wrapped around every call site. The one-time design cost (mock at the wrapper interface, or inject a mock client) is unchanged in kind but now cheaper to pay. v2.9.1 is patch-level with no breaking changes and no deprecations, so the bump itself carries no integration work.
Technical risk. Medium, with two exposures reduced and one clarified:
fromHttpClient signature and the SDK's minimum client contract during the spike; Guzzle version skew remains the most likely install-time failure but is now one version decision instead of two.Issue::update() can now unset fixed_version_id (fixes #466). This closes a real write-path defect: previously a partial update could not distinguish "leave field unchanged" from "clear the field," so optional assignments silently stuck. Treat this as evidence of a bug class rather than a one-off — verify whether other optional fields (assignee, category, parent, due date) have the same null-vs-absent problem before designing write-back flows around them.Art4) driving cadence, an outside contributor merged (@martinvonwittich, first contribution), a bugfix accepted from the community, and an explicit housekeeping PR staged "for v2.10.0." That is a healthier signal than a lone-maintainer project coasting on a single release, and it answers the previously open question about commit cadence. Treat the star/follower data-quality caveat as unchanged; it was never the verdict.Key questions. Which Redmine version(s) must be supported, and who owns the server side? Read-only or write-back (issue creation, status transitions, time logging)? Which auth model is permitted by the Redmine admins? Is Redmine a system of record or transitional? What data volume and latency tolerance? Are custom fields in scope, and is their schema stable? Two additions: does the write path need to clear optional fields (now supported for fixed_version_id only — what else)? And does upstream's stated "housekeeping for v2.10.0" signal deprecations we should plan around?
Stack fit. Laravel 9+ on PHP 8.1+ remains a clean fit. Register a single bound instance as a singleton in a dedicated service provider, configured from config/redmine.php reading services.redmine.* env vars. Prefer the fromHttpClient path: resolve the app's own PSR-18/Guzzle client from the container and hand it to the SDK, so retry policy, timeouts, and structured logging live in one transport configuration. Wrap the instance in an application-owned interface so controllers, jobs, and console commands never touch the SDK directly. Writes through queued jobs; reads behind a cached repository where data is not time-critical.
Migration path. Unchanged, and the v2.9.1 bump does not perturb it. Do not rewrite call sites: introduce the interface plus a legacy adapter delegating to existing code, get tests green, then implement the new adapter and swap the container binding. If Redmine is a retirement candidate, the interface boundary is what makes that a later, cheaper decision. The fixed_version_id fix means any existing workaround (direct API calls or post-update cleanup) can be retired and consolidated into the adapter.
Compatibility. v2.9.1 is a patch release — no breaking changes, no deprecations, drop-in. Pin to 2.9.1 and treat it as the settled baseline for the integration. The real compatibility gate now sits one release ahead: upstream has already landed a housekeeping PR "for v2.10.0," so read that changelog carefully for deprecations before bumping, and re-run the dependency dry-run (composer update --dry-run resolving SDK, Guzzle, and existing app HTTP dependencies together) at that point rather than at every patch. Confirm PHP constraint coverage against the runtime matrix and PSR-3 logging alignment.
Sequencing. Six steps, roughly one to two sprints:
2.9.1, run the existing adapter test suite, confirm fromHttpClient behaves as expected against a non-production Redmine.Maintenance. Low and slightly improved. The library is thin, MIT-licensed, and this release shows a maintainer actively triaging and merging community fixes rather than letting the project drift — which raises confidence in the "review upstream releases" allocation to something genuinely routine. The recurring burden remains the wrapper and the schema assumptions, both owned internally. Keep a fork-and-patch contingency, but the urgency is lower than a dormant single-maintainer project would justify. Schedule one calendar review at the v2.10.0 release rather than tracking every patch.
Support. Unchanged. No vendor SLA, no commercial support path, community issues best-effort. Internal ownership remains mandatory: a named directly responsible engineer, a documented runbook, and no tribal knowledge. Escalation for Redmine-side problems runs through whoever operates the Redmine instance, identified up front. The visible external-contributor activity slightly raises the odds that an upstream issue gets a response, but that is not a commitment to plan against.
Scaling. Unchanged. Redmine is the bottleneck, not PHP. Each call is a synchronous round trip, and issue lists with associations are slow under load. Design for explicit page sizes, aggressive caching of slow-moving reference data (projects, users, trackers, statuses), batching where allowed, and queue workers with a concurrency cap so a backfill does not degrade the instance for human users. Never call the API in a user-facing request path where a cached or precomputed result would do.
Failure modes. Plan explicitly for: credential rotation and 401s; 403 on insufficient permissions; 404 on deleted or inaccessible records; 422 on validation and required custom fields; 5xx and timeouts under load; partial writes where a create succeeds and a later update fails; duplicate records on retry, since Redmine offers no idempotency keys — dedupe on your side using a natural key or stored external ID; pagination drift when records are created mid-sync; attachment size limits; and timezone mismatch between application and Redmine configuration. One item can be closed: "cannot clear an optional field" is fixed for fixed_version_id as of v2.9.1. Do not generalize it — the null-vs-absent semantics of other optional fields are unverified and should be treated as a live failure mode until tested. Each of these needs a defined behavior and an alert, not an unhandled exception.
Ramp-up. Unchanged. A mid-level Laravel developer with no Redmine exposure should be productive in one to two days; the API concepts are conventional and the SDK's method names are self-describing. The genuine learning curve is Redmine's permission and workflow model, not the PHP library. The TPM's leverage is unchanged: insist on the adapter boundary, the dependency-compatibility spike, and the deduplication strategy before the first write path ships. Add one item — require explicit test coverage for field-clearing behavior on every optional field the write path touches, since v2.9.1 demonstrates that this class of bug ships silently and surfaces as stale data in production rather than as an error.
How can I help you explore Laravel packages today?