arturnawrot / 4over-api-client
PHP api client for 4over
Requires
- guzzlehttp/guzzle: ^7.8
- kevinrob/guzzle-cache-middleware: ^4.1
- netresearch/jsonmapper: ^4.2
- psr/http-message: ^2.0
This package is auto-updated.
Last update: 2026-04-01 05:39:51 UTC
README
A PHP client library for the 4over API. Built around a clean, extensible service pattern - adding new endpoints is straightforward and consistent.
Note: Not all 4over API endpoints might be implemented. See Available Services for what's ready, and Adding a New Service for how to contribute the rest (very easy).
Installation
composer require arturnawrot/4over-api-client
Quick Start
use FourOver\FourOverApiClient; $client = new FourOverApiClient('PUBLIC_KEY', 'PRIVATE_KEY', 'SANDBOX'); $categories = $client->categories->getAllCategories();
Services are accessed as properties on the client. Pass 'LIVE' instead of 'SANDBOX' when you're ready to hit the real API.
Available Services
| Property | Service class | Methods |
|---|---|---|
$client->categories |
CategoryService |
getAllCategories() |
$client->products |
ProductService |
getAllProducts(), getProductsByCategory($uuid), getProduct($uuid) |
$client->shipping |
ShippingService |
getShippingQuote(...) |
$client->orders |
OrderService |
createOrder(...), getOrderStatus($jobUuid), getTrackingNumber($jobUuid) |
$client->files |
FileService |
createFile($url) |
$client->paymentProfiles |
PaymentProfileService |
getPaymentProfiles() |
Usage Examples
Get all categories
$categories = $client->categories->getAllCategories(); foreach ($categories as $category) { echo $category->getCategoryName(); }
Browse products
// All products $products = $client->products->getAllProducts(); // Products in a category $products = $client->products->getProductsByCategory($categoryUuid); // Single product with full option groups $product = $client->products->getProduct($productUuid); $optionGroups = $product->getProductOptionGroups(); $turnaroundOptions = $optionGroups->getTurnaroundOptionGroup()->getOptions(); $runsizeUuid = $turnaroundOptions[0]->getRunsizeUuid(); $colorspecUuid = $turnaroundOptions[0]->getColorspecUuid(); $turnaroundUuid = $turnaroundOptions[0]->getOptionUuid();
Get a shipping quote
$quote = $client->shipping->getShippingQuote( $productUuid, $runsizeUuid, $turnaroundUuid, $colorspecUuid, '4301 Washington Road.', '', 'Evans', 'GA', 'US', '30809', $sets = 1 ); $option = $quote->getShippingOptions()[0]; echo $option->getServiceName(); // "FedEx Ground" echo $option->getServicePrice(); // 12.50 echo $option->getServiceCode();
Create an order
See examples/createOrder.php for a full working example. The high-level flow is:
// 1. Upload your print files $front = $client->files->createFile('https://...dummy.pdf')->toArray(); $back = $client->files->createFile('https://...dummy.pdf')->toArray(); $files = [ 'set_001' => [ 'job_name' => 'job001-001', 'files' => [ 'fr' => $front['files'][0]['file_uuid'], 'bk' => $back['files'][0]['file_uuid'], ] ] ]; // 2. Create the order $response = $client->orders->createOrder( $orderId, $couponCode, $skipConfirmation, $sets, $productUuid, $runsizeUuid, $turnaroundUuid, $colorspecUuid, $optionUuids, $dropship, $files, // ship_to fields... // ship_from fields... $shippingMethod, $shippingCode ); $jobUuid = $response->getFirstJobUuid();
In sandbox mode, a dummy payment profile token (
1010101010) is used automatically if you don't provide one.
Track an order
// Order status $statusList = $client->orders->getOrderStatus($jobUuid); $latest = $statusList->getLatestStatus(); echo $latest->getStatus(); // "In Production" echo $latest->getDateSet(); // Tracking number (only available once shipped) $tracking = $client->orders->getTrackingNumber($jobUuid); echo $tracking->getTrackingNumber();
Payment profiles
$profiles = $client->paymentProfiles->getPaymentProfiles(); foreach ($profiles as $profile) { echo $profile->getLastFour(); // "4242" echo $profile->getType(); // "Visa" echo $profile->getProfileToken(); }
Caching (optional)
The client accepts any PSR-compatible HTTP client. A WordPress object cache integration is included out of the box:
use FourOver\FourOverApiClient; use FourOver\HttpClients\WordpressCacheHttpClientFactory; $client = new FourOverApiClient('PUBLIC_KEY', 'PRIVATE_KEY', 'LIVE'); $client->setHttpClient(WordpressCacheHttpClientFactory::get());
You can pass any other PSR-compatible HTTP client the same way - useful for adding custom caching, logging, or retry middleware via Guzzle's handler stack.
Adding a New Service
Every service follows the same pattern. Here's how to add one.
1. Create the entity
// src/Entities/MyEntity.php namespace FourOver\Entities; use FourOver\Entities\BaseEntity; class MyEntity extends BaseEntity { private string $uuid; private string $name; public function getUuid() : string { return $this->uuid; } public function getName() : string { return $this->name; } }
2. Create the entity list
PHP didn't support generics at the time this library was written, so typed collections are implemented manually. Each list class extends BaseList (which itself extends \ArrayObject) and declares the type it holds via getType(). This gives you an iterable collection with a guaranteed element type - the same guarantee that generics provide in languages like Java or C#.
// src/Entities/MyEntityList.php namespace FourOver\Entities; use FourOver\Entities\BaseList; class MyEntityList extends BaseList { public static function getType() : string { return MyEntity::class; } }
3. Create the service
// src/Services/MyNewService.php namespace FourOver\Services; use FourOver\Entities\MyEntity; use FourOver\Entities\MyEntityList; class MyNewService extends AbstractService { public function getAll() : MyEntityList { return $this->getResource( 'GET', '/my-endpoint', ['query' => ['max' => 100]], MyEntity::class, MyEntityList::class // omit this for single-entity responses ); } public function getOne(string $uuid) : MyEntity { return $this->getResource('GET', "/my-endpoint/{$uuid}", [], MyEntity::class); } public function create(string $name) : MyEntity { return $this->getResource( 'POST', '/my-endpoint', ['body' => json_encode(['name' => $name])], MyEntity::class ); } }
4. Register the service
Add it to the class map in src/Services/ServiceFactory.php:
private static $classMap = [ // ... existing entries 'myNew' => MyNewService::class, ];
Your service is now available as $client->myNew->getAll().
How the service layer works
When you call $client->categories->getAllCategories():
__get('categories')onFourOverApiClientlooks up'categories'inServiceFactory's class map and instantiatesCategoryServicewith the client.- The service calls
getResource(), which issues the HTTP request via the base client, passing the method, path, and any Guzzle options (query string, JSON body, etc.). - The base client builds the full URI and attaches API credentials in both places required by the 4over docs - as URL query parameters and as an
Authorizationheader - before sending the request through the PSR HTTP client. - The response JSON is cleaned up: URLs are stripped from the payload (they confuse the mapper), and wrapper keys like
entitiesorjobare unwrapped to expose the actual data. - The cleaned array is handed to
JsonMapper, which hydrates it into your entity class. For list responses,mapArray()is used and the result is placed into the typed list class. For single-entity responses,map()is used directly. Either way you get a fully typed return value.
Running the examples
composer install php examples/getAllCategories.php php examples/getShippingQuote.php php examples/createOrder.php
License
The MIT License (MIT). See LICENSE.md for details.