shimango / gophr
A PHP client for the Gophr Courier Service commercial API
Installs: 19
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/shimango/gophr
Requires
- php: >=8.0
- guzzlehttp/guzzle: ^7.5
- spatie/data-transfer-object: ^2.8
Requires (Dev)
- dg/bypass-finals: ^1.4
- phpunit/php-code-coverage: ^9
- phpunit/phpunit: ^9
- rector/rector: ^0.15.4
README
A comprehensive PHP client for the Gophr Courier Service API, enabling seamless integration of courier services into your PHP applications.
Table of Contents
- Requirements
- Installation
- Quick Start
- Configuration
- Core Concepts
- Usage Examples
- Response Handling
- Error Handling
- Testing
- API Reference
Requirements
- PHP 8.0 or higher
- Composer
- A Gophr API key (obtain from Gophr Authorization)
Installation
Using Composer (Recommended)
composer require shimango/gophr
Manual Installation
- Download this package from GitHub
- Run
composer installin the package directory - Require the autoloader in your project:
require_once 'vendor/autoload.php';
Quick Start
Here's a simple example to get you started:
<?php require_once 'vendor/autoload.php'; use Shimango\Gophr\Client; use Shimango\Gophr\Common\Configuration; // Initialize the client $config = new Configuration('YOUR_API_KEY'); $gophr = new Client($config); // List all jobs $response = $gophr->listJobs(); $jobs = $response->getContentsArray(); foreach ($jobs['data'] as $job) { echo "Job ID: {$job['id']} - Status: {$job['status']}\n"; }
Configuration
The Configuration class manages all settings for API communication.
Constructor Parameters
$config = new Configuration( $apiKey, // Required: Your Gophr API key $isSandbox, // Optional: Use sandbox environment (default: false) $apiVersion, // Optional: API version (default: "v2") $proxyPort, // Optional: Proxy port if needed (default: null) $proxyVerifySSL // Optional: Verify SSL for proxy (default: false) );
Configuration Examples
Production Environment:
$config = new Configuration('live_api_key_here');
Sandbox/Testing Environment:
$config = new Configuration('test_api_key_here', true);
Using a Proxy:
$config = new Configuration( 'your_api_key', false, // Production 'v2', 8080, // Proxy port true // Verify SSL );
Core Concepts
Jobs
A Job represents a complete courier assignment that may include multiple deliveries. All deliveries within a job are completed by the same courier in a single trip.
Deliveries
A Delivery consists of:
- One pickup location
- One dropoff location
- Associated parcels to be transported
Parcels
Parcels are the individual packages within a delivery. A delivery can contain multiple parcels.
Workflow
- Create a Job with a pickup, deliveries, and parcels. This can be done in two ways:
- All at once: Create and confirm the Job in one go.
- As a draft: Create the Job as a draft and edit it (e.g., add/remove/edit deliveries and parcels) until it is ready to be confirmed.
- Confirm the Job to dispatch the courier.
Note: Once a job is confirmed, deliveries and parcels cannot be modified.
Usage Examples
Working with Jobs
Creating a Job
use Shimango\Gophr\Client; use Shimango\Gophr\Common\Configuration; $config = new Configuration('YOUR_API_KEY', true); // Sandbox mode $gophr = new Client($config); $jobData = [ 'external_id' => '2112', 'is_confirmed' => 1, 'pickups' => [ [ 'earliest_pickup_time' => '2026-08-12T06:17:00+00:00', 'pickup_deadline' => '2026-08-12T07:13:00+00:00', 'pickup_address1' => '221B Baker Street', 'pickup_city' => 'London', 'pickup_postcode' => 'NW1 6TS', 'pickup_country_code' => 'GB', 'pickup_company_name' => 'Elementary Investigations', 'pickup_person_name' => 'Sherlock Holmes', 'pickup_email' => 'itiselementary@test.com', 'pickup_mobile_number' => '07700000000', 'parcels' => [ [ 'parcel_external_id' => '001', 'parcel_reference_number' => '0fc35278-60a1-4fb3-9791-4f45c492e120', 'parcel_description' => 'Sunglasses', 'width' => 10, 'length' => 15, 'height' => 5, 'weight' => 0.5, ], [ 'parcel_external_id' => '002', 'parcel_reference_number' => '0fc35278-60a1-4fb3-9791-4f45c492e120', 'parcel_description' => 'Folding chair', 'width' => 50, 'length' => 80, 'height' => 5, 'weight' => 1.5, ], ], ], ], 'dropoffs' => [ [ 'dropoff_company_name' => 'The Prime Minister\'s Office', 'dropoff_address1' => '10 Downing Street', 'dropoff_city' => 'London', 'dropoff_postcode' => 'SW1A 0AA', 'dropoff_country_code' => 'GB', 'dropoff_person_name' => 'The prime minister', 'dropoff_email' => 'pm@test.com', 'dropoff_mobile_number' => '07588000000', 'dropoff_deadline' => '2026-08-12T08:00:00+00:00', 'parcels' => [ [ 'parcel_external_id' => '001', ], [ 'parcel_external_id' => '002', ], ], ], ], ]; $response = $gophr->createJob($jobData); $job = $response->getContentsObject(); echo "Created Job ID: {$job->id}\n";
Retrieving a Job
$jobId = 'job_abc123'; $response = $gophr->getJob($jobId); $job = $response->getContentsArray(); print_r($job);
Updating a Job (Confirming)
$jobId = 'job_abc123'; $updateData = [ 'status' => 'confirmed' ]; $response = $gophr->updateJob($jobId, $updateData); $updatedJob = $response->getContentsObject(); echo "Job status: {$updatedJob->status}\n";
Cancelling a Job
$jobId = 'job_abc123'; $cancelData = [ 'reason' => 'Customer requested cancellation' ]; $response = $gophr->cancelJob($jobId, $cancelData); echo "Job cancelled successfully\n";
Listing Jobs with Pagination
// Get page 2, with 20 jobs per page, including finished jobs $page = 2; $count = 20; $includeFinished = true; $response = $gophr->listJobs($page, $count, $includeFinished); $jobs = $response->getContentsArray(); echo "Total jobs: {$jobs['total']}\n"; echo "Current page: {$jobs['current_page']}\n"; foreach ($jobs['data'] as $job) { echo "- Job {$job['id']}: {$job['status']}\n"; }
Working with Deliveries
Adding a Delivery to a Job
$jobId = 'job_abc123'; $deliveryData = [ 'pickup' => [ 'earliest_pickup_time' => '2026-08-12T06:17:00+00:00', 'pickup_deadline' => '2026-08-12T07:13:00+00:00', 'pickup_address1' => '221B Baker Street', 'pickup_city' => 'London', 'pickup_postcode' => 'NW1 6TS', 'pickup_country_code' => 'GB', 'pickup_company_name' => 'Gophr', 'pickup_person_name' => 'John Smith', 'pickup_email' => 'john@test.com', 'pickup_mobile_number' => '07700000000', ], 'dropoff' => [ 'dropoff_company_name' => 'Buckingham Palace', 'dropoff_address1' => 'Buckingham Palace', 'dropoff_city' => 'London', 'dropoff_postcode' => 'SW1A 1AA', 'dropoff_country_code' => 'GB', 'dropoff_person_name' => 'The Royal Household', 'dropoff_email' => 'yourmajesty@test.com', 'dropoff_mobile_number' => '07700000000', 'dropoff_deadline' => '2026-08-12T08:00:00+00:00', ], 'parcels' => [ [ 'parcel_external_id' => '003', 'parcel_reference_number' => '0fc35278-60a1-4fb3-9791-4f45c492e120', 'parcel_description' => 'Sunglass', 'width' => 10, 'length' => 15, 'height' => 5, 'weight' => 0.5, ], [ 'parcel_external_id' => '004', 'parcel_reference_number' => '0fc35278-60a1-4fb3-9791-4f45c492e120', 'parcel_description' => 'Folding chair', 'width' => 50, 'length' => 80, 'height' => 5, 'weight' => 1.5, ], ], ]; $response = $gophr->createDelivery($jobId, $deliveryData); $delivery = $response->getContentsObject(); echo "Created Delivery ID: {$delivery->id}\n";
Retrieving a Delivery
$jobId = 'job_abc123'; $deliveryId = 'delivery_xyz789'; $response = $gophr->getDelivery($jobId, $deliveryId); $delivery = $response->getContentsArray(); print_r($delivery);
Updating a Delivery
$jobId = 'job_abc123'; $deliveryId = 'delivery_xyz789'; $updateData = [ 'dropoff' => [ 'dropoff_phone_number' => '+44 20 7999 8888' // Update contact phone ] ]; $response = $gophr->updateDelivery($jobId, $deliveryId, $updateData); echo "Delivery updated successfully\n";
Listing Deliveries for a Job
$jobId = 'job_abc123'; $response = $gophr->listDeliveries($jobId); $deliveries = $response->getContentsArray(); foreach ($deliveries as $delivery) { echo "Delivery {$delivery['id']}: {$delivery['status']}\n"; }
Cancelling a Delivery
$jobId = 'job_abc123'; $deliveryId = 'delivery_xyz789'; $cancelData = [ 'reason' => 'Address incorrect' ]; $response = $gophr->cancelDelivery($jobId, $deliveryId, $cancelData); echo "Delivery cancelled\n";
Working with Parcels
Adding a Parcel to a Delivery
$jobId = 'job_abc123'; $deliveryId = 'delivery_xyz789'; $parcelData = [ 'parcel_external_id' => '005', 'description' => 'Electronics', 'weight' => 2.5, // kg 'length' => 40, // cm 'width' => 30, // cm 'height' => 20, // cm 'is_fragile' => 1 ]; $response = $gophr->createParcel($jobId, $deliveryId, $parcelData); $parcel = $response->getContentsObject(); echo "Created Parcel ID: {$parcel->id}\n";
Retrieving a Parcel
$jobId = 'job_abc123'; $deliveryId = 'delivery_xyz789'; $parcelId = 'parcel_def456'; $response = $gophr->getParcel($jobId, $deliveryId, $parcelId); $parcel = $response->getContentsArray(); print_r($parcel);
Updating a Parcel
$jobId = 'job_abc123'; $deliveryId = 'delivery_xyz789'; $parcelId = 'parcel_def456'; $updateData = [ 'weight' => 3.0, // Update weight 'is_not_rotatable' => 1 // Mark as non-rotatable ]; $response = $gophr->updateParcel($jobId, $deliveryId, $parcelId, $updateData); echo "Parcel updated\n";
Listing Parcels in a Delivery
$jobId = 'job_abc123'; $deliveryId = 'delivery_xyz789'; $response = $gophr->listParcels($jobId, $deliveryId); $parcels = $response->getContentsArray(); foreach ($parcels as $parcel) { echo "Parcel {$parcel['id']}: {$parcel['description']} - {$parcel['weight']}kg\n"; }
Deleting a Parcel
$jobId = 'job_abc123'; $deliveryId = 'delivery_xyz789'; $parcelId = 'parcel_def456'; $response = $gophr->deleteParcel($jobId, $deliveryId, $parcelId); echo "Parcel deleted\n"; // Note: If this was the last parcel, the delivery will be automatically cancelled
Response Handling
All API calls return a GophrResponse object that is PSR-7 compliant.
Getting Response Data
// As an array $data = $response->getContentsArray(); // As an object $data = $response->getContentsObject(); // Access specific fields $jobId = $data['id']; // Array access $jobStatus = $data->status; // Object access // Get raw response $rawBody = $response->getBody()->getContents(); // Get status code $statusCode = $response->getStatusCode();
Response Example
$response = $gophr->getJob('job_abc123'); // Check if successful if ($response->getStatusCode() === 200) { $job = $response->getContentsObject(); echo "Job ID: {$job->id}\n"; echo "Status: {$job->status}\n"; echo "Created: {$job->created_at}\n"; // Access nested data foreach ($job->deliveries as $delivery) { echo " Delivery: {$delivery->id}\n"; } }
Error Handling
Always wrap API calls in try-catch blocks to handle potential errors gracefully.
use Shimango\Gophr\Client; use Shimango\Gophr\Common\Configuration; $config = new Configuration('YOUR_API_KEY', true); $gophr = new Client($config); try { $response = $gophr->getJob('invalid_job_id'); $job = $response->getContentsArray(); } catch (\GuzzleHttp\Exception\ClientException $e) { // 4xx errors (client errors) $statusCode = $e->getResponse()->getStatusCode(); $errorBody = $e->getResponse()->getBody()->getContents(); echo "Client Error ({$statusCode}): {$errorBody}\n"; } catch (\GuzzleHttp\Exception\ServerException $e) { // 5xx errors (server errors) echo "Server Error: " . $e->getMessage() . "\n"; } catch (\Exception $e) { // Other errors echo "Error: " . $e->getMessage() . "\n"; }
Common Error Scenarios
// Validation error example try { $jobData = [ 'pickup' => [ // Missing required fields ] ]; $response = $gophr->createJob($jobData); } catch (\GuzzleHttp\Exception\ClientException $e) { if ($e->getResponse()->getStatusCode() === 422) { $errors = json_decode($e->getResponse()->getBody(), true); echo "Validation errors:\n"; print_r($errors['errors']); } }
Testing
Run the test suite:
composer test
Run tests with coverage:
composer test -- --coverage-html coverage
API Reference
For detailed API documentation, request/response schemas, and additional features, visit:
Best Practices
-
Always use sandbox mode during development:
$config = new Configuration('test_api_key', true);
-
Implement proper error handling: Wrap all API calls in try-catch blocks
-
Validate data before sending: Ensure all required fields are present before making API calls
-
Store job IDs: Keep track of job IDs in your database for future reference
-
Don't modify confirmed jobs: Remember that jobs, deliveries, and parcels cannot be modified after confirmation
-
Use pagination for lists: When retrieving lists of jobs, use pagination to manage large datasets
Support and Resources
- API Documentation: https://developers.gophr.com/docs
- Issue Tracker: https://github.com/shimango/gophr/issues
- Packagist: https://packagist.org/packages/shimango/gophr
License
This library is licensed under the MIT License. See the LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Changelog
See CHANGELOG.md for version history and changes.