arturnawrot/4over-api-client

PHP api client for 4over

Maintainers

Package info

github.com/arturnawrot/4over-api-php

pkg:composer/arturnawrot/4over-api-client

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

dev-main 2026-04-01 05:39 UTC

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():

  1. __get('categories') on FourOverApiClient looks up 'categories' in ServiceFactory's class map and instantiates CategoryService with the client.
  2. 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.).
  3. 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 Authorization header - before sending the request through the PSR HTTP client.
  4. The response JSON is cleaned up: URLs are stripped from the payload (they confuse the mapper), and wrapper keys like entities or job are unwrapped to expose the actual data.
  5. 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.