agenziasmart / swotto
PHP SDK for Swotto API
Requires
- php: >=8.1
- guzzlehttp/guzzle: ^7.5
- psr/event-dispatcher: ^1.0
- psr/http-client: ^1.0
- psr/http-message: ^1.0 || ^2.0
- psr/log: ^3.0
- psr/simple-cache: ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- mockery/mockery: ^1.4
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^9.0
- squizlabs/php_codesniffer: ^3.6
This package is auto-updated.
Last update: 2025-10-02 14:54:28 UTC
README
Official PHP client library for integrating with the SW4 API - a comprehensive B2B/ERP platform providing centralized access to:
- Customer & Supplier Management (Master Data/Anagrafiche)
- Inventory & Stock Management (Magazzino)
- Product Information Management (PIM)
- Document Management (Orders, Invoices, DDT, Agreements)
Swotto simplifies API integration with built-in authentication, error handling, file operations, and smart response parsing.
Why Swotto?
✅ Zero boilerplate - Reduces API integration code by 87.5% ✅ Type-safe - PHPStan Level 8 compliant ✅ Resilient - Built-in Circuit Breaker pattern ✅ Flexible - Dual authentication (DevApp + Bearer tokens) ✅ Smart responses - Auto-detect JSON, CSV, PDF formats ✅ Battle-tested - 78 comprehensive test cases
Table of Contents
- Installation
- Quick Start
- Authentication
- Basic Usage
- Advanced Features
- Common Use Cases
- Error Handling
- Configuration Reference
- Testing
- FAQ
- Advanced FAQ
- Support
- License
Installation
Install via Composer:
composer require agenziasmart/swotto
Requirements
- PHP 8.1 or higher
- Composer
- A valid SW4 API account with DevApp credentials
Quick Start
<?php require_once 'vendor/autoload.php'; use Swotto\Client; // Initialize the client $client = new Client([ 'url' => 'https://api.sw4.it', 'key' => 'YOUR_DEVAPP_TOKEN' ]); // Make your first API call $customers = $client->get('customers'); print_r($customers);
Authentication
Swotto supports dual authentication to identify both your application and end users.
DevApp Token (Application Authentication)
Identifies your third-party application to SW4. Required for all requests.
$client = new Client([ 'url' => 'https://api.sw4.it', 'key' => 'YOUR_DEVAPP_TOKEN' // Get from SW4 Dashboard ]);
Security Note: Never commit DevApp tokens to version control. Use environment variables:
$client = new Client([ 'url' => $_ENV['SW4_API_URL'], 'key' => $_ENV['SW4_DEVAPP_TOKEN'] ]);
Bearer Token (User Authentication)
Authenticates specific end users within your application. Optional but recommended.
// Set user token after login $client->setAccessToken($userBearerToken); // Now all requests are made on behalf of this user $userOrders = $client->get('orders'); // Clear token on logout $client->clearAccessToken(); // Get current token $token = $client->getAccessToken();
Complete Authentication Flow
Practical example combining DevApp authentication, Bearer token, and request context:
// 1. Initialize with DevApp token (identifies your application) $client = new Client([ 'url' => 'https://api.sw4.it', 'key' => 'YOUR_DEVAPP_TOKEN' ]); // 2. Set end-user context $client->setClientIp($_SERVER['REMOTE_ADDR']); $client->setClientUserAgent($_SERVER['HTTP_USER_AGENT']); // 3. User login in your application $loginResponse = $client->post('auth/login', [ 'email' => 'user@example.com', 'password' => 'password' ]); // 4. Set Bearer token for authenticated requests $client->setAccessToken($loginResponse['data']['token']); // 5. Now all requests are authenticated with full context $profile = $client->get('account/profile'); $customers = $client->getParsed('customers');
How It Works:
- DevApp token determines which organization's data you can access
- Bearer token identifies which user is making the request
- Data isolation: All responses are automatically filtered by organization ID
Basic Usage
HTTP Methods
// GET request $data = $client->get('customers'); $data = $client->get('customers', ['query' => ['limit' => 10]]); // POST request $result = $client->post('customers', [ 'name' => 'John Doe', 'email' => 'john@example.com' ]); // PUT request (full update) $result = $client->put('customers/123', [ 'name' => 'Jane Doe' ]); // PATCH request (partial update) $result = $client->patch('customers/123', [ 'email' => 'jane@example.com' ]); // DELETE request $result = $client->delete('customers/123');
Parsed Responses
Automatic response parsing with standardized structure:
// Returns: ['data' => [...], 'paginator' => [...], 'success' => true] $parsed = $client->getParsed('customers'); $parsed = $client->postParsed('customers', $data); $parsed = $client->patchParsed('customers/123', $data); $parsed = $client->putParsed('customers/123', $data); $parsed = $client->deleteParsed('customers/123'); // Access data and pagination $customers = $parsed['data']; $paginator = $parsed['paginator']; echo "Retrieved {$paginator['count']} of {$paginator['total']} customers";
File Uploads
Simple file upload with multipart form data:
// Upload single file $fileHandle = fopen('/path/to/document.pdf', 'r'); $result = $client->postFile('documents', $fileHandle, 'document', [ 'title' => 'Important Document', 'category' => 'contracts' ]); // Upload multiple files $files = [ 'attachment1' => fopen('/path/to/file1.pdf', 'r'), 'attachment2' => fopen('/path/to/file2.jpg', 'r'), ]; $result = $client->postFiles('documents/batch', $files, [ 'batch_name' => 'Monthly Reports' ]); // Update with file (PUT) $fileHandle = fopen('/path/to/updated.pdf', 'r'); $result = $client->putFile('documents/123', $fileHandle); // Patch with file $fileHandle = fopen('/path/to/partial.pdf', 'r'); $result = $client->patchFile('documents/123', $fileHandle);
Advanced Features
Multi-Format Responses
Handle JSON, CSV, PDF, and binary content:
// Get smart response wrapper $response = $client->getResponse('reports/monthly'); // Content type detection if ($response->isJson()) { $data = $response->asArray(); } elseif ($response->isCsv()) { $csv = $response->asString(); } elseif ($response->isPdf()) { $response->saveToFile('/path/to/report.pdf'); } // Direct file download $client->downloadToFile('exports/large-dataset.csv', '/path/to/data.csv');
Circuit Breaker Pattern
Automatic fail-fast behavior for unstable APIs:
use Psr\SimpleCache\CacheInterface; use Swotto\Http\GuzzleHttpClient; // Enable circuit breaker $config = [ 'url' => 'https://api.sw4.it', 'circuit_breaker_enabled' => true, 'circuit_breaker_failure_threshold' => 5, // Open after 5 failures 'circuit_breaker_recovery_timeout' => 30 // Retry after 30 seconds ]; $client = new Client( $config, $logger, GuzzleHttpClient::withCircuitBreaker( new \Swotto\Config\Configuration($config), $logger, $cache // PSR-16 cache implementation (Redis, Memcached, etc.) ) ); try { $result = $client->get('customers'); } catch (\Swotto\Exception\CircuitBreakerOpenException $e) { // API temporarily unavailable, circuit breaker is open echo "Service unavailable, retry after: " . $e->getRetryAfter() . " seconds"; }
POP Methods
Convenience methods for common lookup data:
// Country list $countries = $client->getCountryPop(); // Gender options $genders = $client->getGenderPop(); // System languages $languages = $client->getSysLanguagePop(); // Currency list $currencies = $client->getCurrencyPop(); // And many more: getCustomerPop(), getSupplierPop(), getWarehousePop(), etc.
Common Use Cases
Fetching Customers with Pagination
use Swotto\Client; $client = new Client([ 'url' => 'https://api.sw4.it', 'key' => 'YOUR_DEVAPP_TOKEN' ]); try { // Get parsed response with pagination metadata $response = $client->getParsed('customers', [ 'query' => [ 'page' => 1, 'limit' => 50 ] ]); $customers = $response['data']; $pagination = $response['paginator']; echo "Retrieved {$pagination['count']} of {$pagination['total']} customers\n"; foreach ($customers as $customer) { echo "- {$customer['name']} ({$customer['email']})\n"; } } catch (\Swotto\Exception\AuthenticationException $e) { echo "Authentication failed: " . $e->getMessage(); } catch (\Swotto\Exception\ApiException $e) { echo "API error: " . $e->getMessage(); }
Creating a New Order
$orderData = [ 'customer_id' => 'cust_123456', 'items' => [ ['product_id' => 'prod_789', 'quantity' => 2], ['product_id' => 'prod_456', 'quantity' => 1] ], 'shipping_address' => [ 'street' => 'Via Roma 123', 'city' => 'Milano', 'postal_code' => '20100' ] ]; try { $result = $client->post('orders', $orderData); echo "Order created: {$result['data']['order_id']}\n"; } catch (\Swotto\Exception\ValidationException $e) { echo "Validation errors:\n"; $errors = $e->getErrorData(); foreach ($errors as $field => $messages) { echo "- {$field}: " . implode(', ', (array)$messages) . "\n"; } }
Downloading a PDF Invoice
try { $response = $client->getResponse('invoices/INV-2024-001/pdf'); // Check content type if ($response->isPdf()) { // Save to file with security validations $filePath = $response->saveToFile('/path/to/downloads', 'invoice.pdf'); echo "Invoice saved to: {$filePath}\n"; } } catch (\Swotto\Exception\NotFoundException $e) { echo "Invoice not found"; } catch (\Swotto\Exception\FileOperationException $e) { echo "Failed to save file: " . $e->getMessage(); }
Error Handling
Exception Hierarchy
SwottoExceptionInterface (interface)
└── SwottoException (base class)
├── ApiException (HTTP 400-599)
│ ├── AuthenticationException (401)
│ ├── ForbiddenException (403)
│ ├── NotFoundException (404)
│ ├── ValidationException (422)
│ └── RateLimitException (429)
├── NetworkException (connection issues)
│ └── ConnectionException
├── SecurityException (security violations)
│ ├── FileOperationException
│ └── MemoryException
└── CircuitBreakerOpenException (service unavailable)
Best Practices
use Swotto\Exception\{ AuthenticationException, NotFoundException, ValidationException, RateLimitException, NetworkException, SwottoException }; try { $result = $client->post('customers', $data); } catch (ValidationException $e) { // Handle validation errors (422) $errors = $e->getErrorData(); foreach ($errors as $field => $messages) { echo "{$field}: " . implode(', ', (array)$messages); } } catch (AuthenticationException $e) { // Token expired or invalid (401) // Redirect to login or refresh token } catch (NotFoundException $e) { // Resource doesn't exist (404) // Show 404 page or create new resource } catch (RateLimitException $e) { // Too many requests (429) $retryAfter = $e->getRetryAfter(); // seconds echo "Rate limited. Retry after {$retryAfter} seconds."; } catch (NetworkException $e) { // Network connectivity issues // Retry with exponential backoff } catch (SwottoException $e) { // Catch-all for other API errors error_log("API Error: " . $e->getMessage()); }
Handling Rate Limits
use Swotto\Exception\RateLimitException; function fetchWithRetry($client, $endpoint, $maxRetries = 3) { $attempt = 0; while ($attempt < $maxRetries) { try { return $client->get($endpoint); } catch (RateLimitException $e) { $retryAfter = $e->getRetryAfter(); echo "Rate limited. Waiting {$retryAfter} seconds...\n"; sleep($retryAfter); $attempt++; } } throw new \Exception("Max retries exceeded"); }
Configuration Reference
Required Options
Option | Type | Description |
---|---|---|
url |
string |
SW4 API base URL (e.g., https://api.sw4.it ) |
Authentication Options
Option | Type | Default | Description |
---|---|---|---|
key |
string |
null |
DevApp token for application authentication |
access_token |
string |
null |
Bearer token for user authentication |
session_id |
string |
null |
Session ID for session-based auth |
HTTP Client Options
Option | Type | Default | Description |
---|---|---|---|
timeout |
int |
30 |
Request timeout in seconds |
connect_timeout |
int |
10 |
Connection timeout in seconds |
verify |
bool |
true |
Verify SSL certificates |
Circuit Breaker Options
Option | Type | Default | Description |
---|---|---|---|
circuit_breaker_enabled |
bool |
false |
Enable circuit breaker pattern |
circuit_breaker_failure_threshold |
int |
5 |
Failures before opening circuit |
circuit_breaker_recovery_timeout |
int |
30 |
Seconds before attempting recovery |
Client Metadata
Option | Type | Default | Description |
---|---|---|---|
client_user_agent |
string |
Auto | Custom User-Agent header |
client_ip |
string |
Auto | Client IP address for logging |
language |
string |
'it' |
Preferred response language |
accept |
string |
'application/json' |
Accept header |
Complete Example
$client = new Client([ // Required 'url' => 'https://api.sw4.it', // Authentication 'key' => $_ENV['SW4_DEVAPP_TOKEN'], 'access_token' => $userToken, // HTTP 'timeout' => 60, 'verify' => true, // Circuit Breaker 'circuit_breaker_enabled' => true, 'circuit_breaker_failure_threshold' => 5, 'circuit_breaker_recovery_timeout' => 30, // Metadata 'language' => 'en', 'client_ip' => $_SERVER['REMOTE_ADDR'] ]);
Testing
Run the test suite:
# Run all tests composer test # Run specific tests composer test -- --filter ClientTest # Code style check composer cs # Fix code style composer cs-fix # Static analysis composer phpstan
FAQ
How do I get DevApp credentials?
Contact SW4 support or visit your organization dashboard at https://app.sw4.it/settings/devapps
.
What's the difference between DevApp token and Bearer token?
- DevApp token: Identifies your application and determines data scope (organization)
- Bearer token: Identifies the end user making requests through your app
Can I use this SDK without authentication?
No. SW4 API requires at least a DevApp token for all requests.
Is circuit breaker required?
No, it's optional. Enable it for production environments to handle API failures gracefully.
What PHP versions are supported?
PHP 8.1 or higher. We recommend PHP 8.3 for best performance.
How do I debug API requests?
Inject a PSR-3 logger:
$client = new Client($config); $client->setLogger($yourPsr3Logger);
Can I use this with Laravel/Symfony/other frameworks?
Yes! Swotto is framework-agnostic and works with any PHP application.
Advanced FAQ
📊 Response Methods & Data Handling
What's the difference between get()
, getParsed()
, and getResponse()
?
get()
[RECOMMENDED]: Returns the response exactly as provided by the SW4 API. Pattern aligned with AWS SDK, Twilio, Stripe - maximum predictability.
$response = $client->get('customers'); $customers = $response['data']; $totalPages = $response['meta']['pagination']['total_pages'];
getParsed()
: Optional helper that transforms meta.pagination
into a paginator
object with additional calculated fields (e.g., range
array for UI pagination). Use only if you need this convenience.
$parsed = $client->getParsed('customers'); $paginator = $parsed['paginator']; // with 'range', 'current', 'last' pre-calculated
getResponse()
: For multi-format content (CSV, PDF, binary). Returns SwottoResponse
object with methods like isPdf()
, isCsv()
, saveToFile()
.
$response = $client->getResponse('exports/report'); if ($response->isPdf()) { $response->saveToFile('/path/to/file.pdf'); }
When should I use getParsed()
instead of get()
?
Use getParsed()
only if:
- You're building pagination UI and want pre-calculated
range
array - You prefer
paginator['current']
instead ofmeta['pagination']['current_page']
For all other cases, use get()
- ensures what you read in SW4 API documentation matches exactly what you receive in the SDK.
How do I handle pagination with get()
?
$response = $client->get('customers', ['query' => ['page' => 1, 'limit' => 50]]); $customers = $response['data']; $pagination = $response['meta']['pagination']; echo "Page {$pagination['current_page']} of {$pagination['total_pages']}"; echo "Total: {$pagination['total']} customers"; // Next page if ($pagination['current_page'] < $pagination['total_pages']) { $nextPage = $client->get('customers', [ 'query' => ['page' => $pagination['current_page'] + 1] ]); }
Can I automatically iterate through all pages?
function fetchAllPages($client, $endpoint) { $allData = []; $page = 1; do { $response = $client->get($endpoint, [ 'query' => ['page' => $page, 'limit' => 100] ]); $allData = array_merge($allData, $response['data']); $pagination = $response['meta']['pagination'] ?? null; $page++; } while ($pagination && $page <= $pagination['total_pages']); return $allData; }
🔐 Authentication & Multi-tenancy
Can I use the SDK without a DevApp token?
No. The DevApp token is required for all requests. It identifies your application and determines which SW4 organization you have access to.
// ❌ Fails with AuthenticationException $client = new Client(['url' => 'https://api.sw4.it']); // ✅ Correct $client = new Client([ 'url' => 'https://api.sw4.it', 'key' => $_ENV['SW4_DEVAPP_TOKEN'] ]);
How do I get a DevApp token?
- Log in to your SW4 account:
https://app.sw4.it
- Go to Settings → DevApps
- Create a new application and copy the token
- Never commit the token - use environment variables
What's the purpose of the Bearer token if I already have a DevApp token?
DevApp token = identifies YOUR application (required) Bearer token = identifies the END USER using your app (optional)
// Your SaaS app has 1000 end users $client = new Client([ 'key' => 'YOUR_APP_DEVAPP_TOKEN' // identifies your app ]); // User1 logs in to your app $client->setAccessToken($user1Token); // identifies User1 $orders1 = $client->get('orders'); // sees User1's orders // User2 logs in $client->setAccessToken($user2Token); // switch to User2 $orders2 = $client->get('orders'); // sees User2's orders
Is data isolated between organizations?
Yes! Multi-tenancy guaranteed:
- DevApp token determines the organization (
_oid
) - All data automatically filtered for that organization
- Impossible to access other organizations' data even with valid Bearer token
Isolation priority: DevApp _oid
always overrides User/Account _oid
.
📁 File Operations
How do I upload a single file with metadata?
$fileHandle = fopen('/path/to/document.pdf', 'r'); $response = $client->postFile( 'documents', $fileHandle, 'attachment', // form field name [ 'title' => 'Contract 2025', 'category' => 'legal', 'tags' => ['contract', 'important'] ] ); fclose($fileHandle); echo "Document ID: {$response['data']['id']}";
How do I upload multiple files simultaneously?
$files = [ 'invoice' => fopen('/path/to/invoice.pdf', 'r'), 'receipt' => fopen('/path/to/receipt.jpg', 'r'), 'contract' => fopen('/path/to/contract.pdf', 'r'), ]; $response = $client->postFiles('documents/batch', $files, [ 'batch_name' => 'January 2025 Documents', 'category' => 'accounting' ]); // Close all files foreach ($files as $handle) { fclose($handle); }
How does Swotto handle very large files?
Automatic streaming:
- < 10MB: in-memory loading (fast)
- > 10MB: automatic streaming (memory-safe)
- > 50MB:
MemoryException
thrown (protection)
For large downloads, use downloadToFile()
instead of get()
:
// ❌ 200MB file loaded in memory = crash $bigFile = $client->get('exports/huge-dataset.csv'); // ✅ Direct download to disk = memory-safe $client->downloadToFile('exports/huge-dataset.csv', '/path/to/file.csv');
Can I update only the file without touching metadata?
// PATCH = update only file, metadata remains $newFile = fopen('/path/to/updated.pdf', 'r'); $client->patchFile('documents/123', $newFile); // PUT = replace everything (file + metadata reset) $client->putFile('documents/123', $newFile);
⚡ Circuit Breaker Pattern
What is Circuit Breaker and when should I use it?
Resilience pattern that prevents "cascading failures" when the SW4 API has issues.
States:
- CLOSED: API ok → normal requests
- OPEN: API failed N times → block all requests (fail-fast)
- HALF_OPEN: after timeout → test with 1 request
When to enable:
- ✅ Production environment
- ✅ Critical external APIs where API downtime shouldn't crash your app
- ❌ Development/testing (unnecessary overhead)
How do I enable Circuit Breaker?
use Swotto\Client; use Swotto\Http\GuzzleHttpClient; use Swotto\Config\Configuration; $config = [ 'url' => 'https://api.sw4.it', 'key' => $_ENV['SW4_DEVAPP_TOKEN'], 'circuit_breaker_enabled' => true, 'circuit_breaker_failure_threshold' => 5, // OPEN after 5 failures 'circuit_breaker_recovery_timeout' => 30, // retry after 30s ]; // Requires PSR-16 cache (Redis, Memcached, etc.) $cache = new \Symfony\Component\Cache\Psr16Cache( new \Symfony\Component\Cache\Adapter\RedisAdapter($redisClient) ); $client = new Client( $config, $logger, GuzzleHttpClient::withCircuitBreaker( new Configuration($config), $logger, $cache ), $cache );
What happens when the circuit breaker is OPEN?
use Swotto\Exception\CircuitBreakerOpenException; try { $response = $client->get('customers'); } catch (CircuitBreakerOpenException $e) { // API temporarily unavailable $retryAfter = $e->getRetryAfter(); // seconds // Show user-friendly message echo "Service temporarily unavailable. Retry in {$retryAfter} seconds."; // Or use cached data $customers = $cachedData; }
📊 POP Methods (Lookup Data)
What are POPs and when should I use them?
POP = "lookup data" from SW4 (dropdowns, select options). Examples: countries, languages, currencies, genders.
27+ pre-configured helper methods with automatic 1-hour cache:
// Country list for dropdown $countries = $client->getCountryPop(); foreach ($countries as $country) { echo "<option value='{$country['code']}'>{$country['name']}</option>"; } // Gender options $genders = $client->getGenderPop(); // Currencies $currencies = $client->getCurrencyPop(); // System languages $languages = $client->getSysLanguagePop(); // Customer/Supplier for autocomplete $customers = $client->getCustomerPop(); $suppliers = $client->getSupplierPop();
Do POP methods make HTTP requests every time?
No! Automatic PSR-16 cache for 1 hour:
- First call: HTTP request → cache
- Subsequent calls (< 1h): read from cache
- After 1h: cache expired → new HTTP request
Requires: PSR-16 cache injected in the client (Redis, Memcached, APCu, File).
What's the complete list of available POPs?
// Geographic $client->getCountryPop(); $client->getContinentPop(); $client->getProvincePop(); // Master data $client->getGenderPop(); $client->getCustomerPop(); $client->getSupplierPop(); // System $client->getSysLanguagePop(); $client->getCurrencyPop(); $client->getWarehousePop(); $client->getDocumentTypePop(); // Business $client->getPaymentMethodPop(); $client->getShippingMethodPop(); $client->getTaxRatePop(); // ... total 27+ methods // Complete list: see PopTrait or SW4 API docs
🛡️ Error Handling
How do I handle validation errors (422)?
use Swotto\Exception\ValidationException; try { $response = $client->post('customers', [ 'email' => 'invalid-email', // missing @ 'age' => 'twenty' // must be number ]); } catch (ValidationException $e) { $errors = $e->getErrorData(); // $errors = [ // 'email' => ['The email field must be a valid email address.'], // 'age' => ['The age field must be a number.'] // ] foreach ($errors as $field => $messages) { echo "{$field}: " . implode(', ', (array)$messages) . "\n"; } }
How do I implement retry with exponential backoff?
use Swotto\Exception\{NetworkException, RateLimitException}; function retryRequest($client, $endpoint, $maxRetries = 3) { $attempt = 0; $baseDelay = 1; // seconds while ($attempt < $maxRetries) { try { return $client->get($endpoint); } catch (RateLimitException $e) { // Use API's Retry-After header sleep($e->getRetryAfter()); } catch (NetworkException $e) { // Exponential backoff: 1s, 2s, 4s, 8s $delay = $baseDelay * (2 ** $attempt); sleep($delay); $attempt++; } } throw new \Exception("Max retries ({$maxRetries}) exceeded"); }
What's the complete exception hierarchy?
SwottoExceptionInterface
└── SwottoException (base)
├── ApiException (HTTP 400-599)
│ ├── AuthenticationException (401)
│ ├── ForbiddenException (403)
│ ├── NotFoundException (404)
│ ├── ValidationException (422)
│ └── RateLimitException (429)
├── NetworkException (connectivity)
│ └── ConnectionException
├── SecurityException
│ ├── FileOperationException (path traversal, etc.)
│ └── MemoryException (>50MB)
└── CircuitBreakerOpenException
Best practice: catch from specific to generic.
How do I log all requests for debugging?
use Monolog\Logger; use Monolog\Handler\StreamHandler; $logger = new Logger('swotto'); $logger->pushHandler(new StreamHandler('/var/log/swotto.log', Logger::DEBUG)); $client = new Client(['url' => '...', 'key' => '...']); $client->setLogger($logger); // Automatically logs: // - Request: method, URI, headers (without tokens!), body // - Response: status, body // - Errors: complete exceptions
⚙️ Performance & Best Practices
How do I reduce the number of API calls?
- Local cache for static data (POPs, configurations)
- Batch requests where available (
postFiles
) - Efficient pagination (reasonable limits: 50-100 records/page)
- Circuit breaker in production (prevents retry storms)
- PSR-16 cache for POP methods (reduce 1000+ calls → 1 call/hour)
Can I make parallel HTTP requests?
Yes! Use Guzzle async:
use GuzzleHttp\Promise; // Prepare promises $promises = [ 'customers' => $httpClient->requestAsync('GET', 'customers'), 'orders' => $httpClient->requestAsync('GET', 'orders'), 'products' => $httpClient->requestAsync('GET', 'products'), ]; // Execute in parallel $results = Promise\Utils::unwrap($promises); // Use results $customers = $results['customers']; $orders = $results['orders'];
Note: Swotto Client doesn't expose async methods directly - you must use the underlying HttpClient.
How do I handle development vs production environments?
// config/swotto.php return [ 'development' => [ 'url' => 'https://api-dev.sw4.it', 'key' => $_ENV['SW4_DEV_TOKEN'], 'verify' => false, // self-signed SSL ok 'timeout' => 120, // longer debug timeout 'circuit_breaker_enabled' => false, ], 'production' => [ 'url' => 'https://api.sw4.it', 'key' => $_ENV['SW4_PROD_TOKEN'], 'verify' => true, // strict SSL 'timeout' => 30, 'circuit_breaker_enabled' => true, 'circuit_breaker_failure_threshold' => 5, 'circuit_breaker_recovery_timeout' => 30, ], ]; // Bootstrap $env = $_ENV['APP_ENV'] ?? 'production'; $config = require 'config/swotto.php'; $client = new Client($config[$env]);
Is Swotto thread-safe for multi-threaded applications?
Client object: No, not thread-safe. Each thread must create its own Client instance.
// ❌ Don't share between threads $sharedClient = new Client($config); // ✅ Create instance per thread function worker($config) { $client = new Client($config); // ... use $client only in this thread }
PSR-16 cache: Depends on implementation (Redis/Memcached = thread-safe, APCu = no).
How do I integrate Swotto with Laravel?
// config/services.php 'swotto' => [ 'url' => env('SW4_API_URL'), 'key' => env('SW4_DEVAPP_TOKEN'), ], // app/Providers/AppServiceProvider.php use Swotto\Client; public function register() { $this->app->singleton(Client::class, function ($app) { return new Client(config('services.swotto')); }); } // Controller usage public function index(Client $swotto) { $customers = $swotto->get('customers'); return view('customers', compact('customers')); }
Support
- Issues: GitHub Issues
- Email: support@sw4.it
Getting Help
- Check the FAQ
- Search existing issues
- Create a new issue with:
- SDK version (
composer show agenziasmart/swotto
) - PHP version (
php -v
) - Minimal code example
- Expected vs actual behavior
- SDK version (
License
MIT License. See LICENSE file for details.
Copyright © 2025 AgenziaSmart