minhchieng/redmine-client

Lightweight PHP client for connecting to Redmine.

Installs: 1

Dependents: 0

Suggesters: 0

Security: 0

pkg:composer/minhchieng/redmine-client

v1.0.0 2026-02-12 17:06 UTC

README

A lightweight PHP client to connect to Redmine via REST API.

Install

composer require minhchieng/redmine-client

Professional library baseline

This library follows:

  • PSR-4 autoloading (src/)
  • PSR-12 coding style (PHPCS)
  • PSR-18 HTTP client + PSR-17 factories support
  • PHPUnit + PHPStan quality checks
  • Domain-based API organization (src/Api/<Domain>)
  • API contracts via interfaces (*ApiInterface)
  • CI + release validation via Bitbucket Pipelines

Run quality checks:

composer install
composer cs
composer stan
composer test
composer check

Release workflow

  • feature/<short-name>
  • fix/<short-name>
  • release/x.y.z
  • hotfix/x.y.z

Create a release tag:

git checkout master
git pull origin master
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin master
git push origin v1.0.0

When a v*.*.* tag is pushed, bitbucket-pipelines.yml runs release validation:

  1. Validate and install dependencies
  2. Run full checks (composer check)
  3. Ensure tag build is releasable before publishing notes/package updates

For Bitbucket repositories:

  • Configure branch permissions and default reviewers in repository settings
  • Use CODEOWNERS in this repo as ownership documentation
  • Use renovate.json with Renovate (Dependabot alternative)

Usage

<?php

require __DIR__ . '/vendor/autoload.php';

use Minhchieng\Redmine\RedmineClient;

$client = new RedmineClient('https://your-redmine.example.com', 'YOUR_API_KEY');

if ($client->canConnect()) {
    echo "Connected to Redmine" . PHP_EOL;
}

$users = $client->users();
$projects = $client->projects();
$timeEntries = $client->timeEntries();
$memberships = $client->projectMemberships();
$issues = $client->issues();
$myAccount = $client->myAccount();

$newUser = $users->create([
    'login' => 'jane.doe',
    'firstname' => 'Jane',
    'lastname' => 'Doe',
    'mail' => 'jane.doe@example.com',
    'password' => 'StrongPassword123!',
    'must_change_passwd' => true,
]);

print_r($newUser);

$allUsers = $users->getAll(['status' => 1]);
print_r($allUsers);

$user = $users->getById(10);
print_r($user);

$users->update(10, [
    'firstname' => 'Jane Updated',
]);

$users->delete(11);

$newProject = $projects->create([
    'name' => 'New Project',
    'identifier' => 'new-project',
    'description' => 'Project created via API',
    'is_public' => true,
]);

print_r($newProject);

$allProjects = $projects->getAll();
print_r($allProjects);

$project = $projects->getById('new-project');
print_r($project);

$projects->update('new-project', [
    'description' => 'Updated description',
]);

$projects->delete('old-project');

$newTimeEntry = $timeEntries->create([
    'project_id' => 1,
    'issue_id' => 100,
    'hours' => 2.5,
    'comments' => 'Implementation work',
    'spent_on' => '2026-02-13',
    'activity_id' => 9,
]);

print_r($newTimeEntry);

$allTimeEntries = $timeEntries->getAll(['user_id' => 'me']);
print_r($allTimeEntries);

$timeEntry = $timeEntries->getById(123);
print_r($timeEntry);

$timeEntries->update(123, [
    'hours' => 3.0,
    'comments' => 'Updated work log',
]);

$timeEntries->delete(124);

$allMemberships = $memberships->getAll('new-project');
print_r($allMemberships);

$newMembership = $memberships->create('new-project', [
    'user_id' => 10,
    'role_ids' => [3],
]);
print_r($newMembership);

$membership = $memberships->getById(200);
print_r($membership);

$memberships->update(200, [
    'role_ids' => [3, 4],
]);

$memberships->delete(201);

$newIssue = $issues->create([
    'project_id' => 1,
    'tracker_id' => 1,
    'status_id' => 1,
    'priority_id' => 2,
    'subject' => 'Create API integration',
    'description' => 'Implement Redmine issue API support',
    'category_id' => 3,
    'fixed_version_id' => 4,
    'assigned_to_id' => 10,
    'parent_issue_id' => 99,
    'custom_fields' => [
        ['id' => 1, 'value' => 'Value A'],
    ],
    'watcher_user_ids' => [10, 11],
    'is_private' => false,
    'estimated_hours' => 6.5,
]);
print_r($newIssue);

$allIssues = $issues->getAll(['project_id' => 1]);
print_r($allIssues);

$issue = $issues->getById(300);
print_r($issue);

$issues->update(300, [
    'subject' => 'Create API integration (updated)',
    'status_id' => 2,
]);

$issues->delete(301);

$account = $myAccount->get();
print_r($account);

$myAccount->update([
    'firstname' => 'Jane',
    'lastname' => 'Doe Updated',
    'mail' => 'jane.doe.updated@example.com',
]);

Backward-compatible helpers

These methods are still available on RedmineClient:

  • $client->createUser([...])
  • $client->createProject([...])
  • $client->createTimeEntry([...])
  • $client->createProjectMembership($projectId, [...])
  • $client->createIssue([...])
  • $client->updateMyAccount([...])

Issue POST attributes (/issues.json)

For $issues->create([...]) or $client->createIssue([...]), you can pass:

  • project_id
  • tracker_id
  • status_id
  • priority_id
  • subject
  • description
  • category_id
  • fixed_version_id
  • assigned_to_id
  • parent_issue_id
  • custom_fields
  • watcher_user_ids
  • is_private
  • estimated_hours

Usage with .env

Add variables to your .env:

REDMINE_BASE_URL=https://your-redmine.example.com
REDMINE_API_KEY=your_api_key

Then initialize from environment variables:

<?php

require __DIR__ . '/vendor/autoload.php';

use Minhchieng\Redmine\RedmineClient;

$client = RedmineClient::fromEnv();

Use with PSR HTTP client (PSR-7/17/18)

If you want to use your own HTTP stack instead of cURL fallback, pass:

  • Psr\Http\Client\ClientInterface
  • Psr\Http\Message\RequestFactoryInterface
  • Psr\Http\Message\StreamFactoryInterface
<?php

use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Psr7\HttpFactory;
use Minhchieng\Redmine\RedmineClient;

$httpClient = new GuzzleClient();
$httpFactory = new HttpFactory();

$client = new RedmineClient(
    'https://your-redmine.example.com',
    'YOUR_API_KEY',
    $httpClient,
    $httpFactory,
    $httpFactory
);

Seed localhost example

Create 10 random users, 3 projects, then add all created users to each created project:

REDMINE_BASE_URL=http://localhost:3001 \
REDMINE_API_KEY=your_api_key \
php examples/seed_redmine.php

Optional:

  • SEED_USER_COUNT (default: 10)
  • SEED_PROJECT_COUNT (default: 3)

Notes

  • Ensure your Redmine account has API access enabled.
  • API key can be found in your Redmine account settings.