A comprehensive Laravel SDK for the Fairu GraphQL API with dynamic fragments, caching, and full type support.
- PHP 8.2+
- Laravel 10, 11, or 12
composer require sushidev/fairu-sdkThe package will auto-register its service provider and facade.
php artisan vendor:publish --tag=fairu-configAdd the following to your .env file:
FAIRU_URL=https://fairu.app
FAIRU_TOKEN=your-api-token// config/fairu.php
return [
'base_url' => env('FAIRU_URL', 'https://fairu.app'),
'token' => env('FAIRU_TOKEN'),
'timeout' => 30,
'retry' => [
'times' => 3,
'sleep' => 100,
],
'cache' => [
'enabled' => true,
'store' => null, // null = default cache store
'prefix' => 'fairu_',
'ttl' => [
'tenant' => 3600,
'roles' => 3600,
'assets' => 300,
'default' => 600,
],
],
];All IDs in the Fairu API are UUIDs (Universally Unique Identifiers). This applies to all resources:
- Assets
- Folders
- Galleries
- Copyrights
- Licenses
- Users
- Roles
- Disks
- Tenants
Example:
$assetId = '550e8400-e29b-41d4-a716-446655440000';
$asset = Fairu::assets()->find($assetId);use SushiDev\Fairu\Facades\Fairu;
// Health Check
$status = Fairu::health()->check();
// Get single asset
$asset = Fairu::assets()->find('uuid');
$asset = Fairu::assets()->findByPath('/images/logo.png');
// Get multiple assets
$assets = Fairu::assets()->findMany(['uuid-1', 'uuid-2']);
// Search assets
$results = Fairu::assets()->search('logo', page: 1, perPage: 20);
// List assets in folder
$assets = Fairu::assets()->all(folderId: 'folder-uuid', page: 1, perPage: 20);
// Get folder content (folders + assets)
$content = Fairu::folders()->content('folder-uuid');
$content->folders; // Array of Folder objects
$content->assets; // Array of Asset objects
// Get tenant info
$tenant = Fairu::tenant()->get();
// List sub-tenants of the current tenant
$subTenants = Fairu::tenant()->subTenants();
foreach ($subTenants as $subTenant) {
$subTenant->getId();
$subTenant->getName();
$subTenant->getParentId();
$subTenant->isSubTenant(); // true
}
// List galleries
$galleries = Fairu::galleries()->all(['tenant-uuid'], from: '2024-01-01');
// Other queries
$copyrights = Fairu::copyrights()->all();
$licenses = Fairu::licenses()->all();
$workflows = Fairu::workflows()->all();
$users = Fairu::users()->all();
$roles = Fairu::roles()->all();
$disks = Fairu::disks()->all();use SushiDev\Fairu\Facades\Fairu;
use SushiDev\Fairu\DTOs\FileDTO;
use SushiDev\Fairu\DTOs\FolderDTO;
use SushiDev\Fairu\DTOs\GalleryDTO;
use SushiDev\Fairu\Enums\UploadType;
// Upload a file
$uploadLink = Fairu::uploads()->createLink(
filename: 'photo.jpg',
type: UploadType::STANDARD,
folderId: 'folder-uuid',
alt: 'Photo description'
);
// Use $uploadLink->getUrl() to upload your file via PUT request
// Update asset metadata
$asset = Fairu::assetMutations()->update(
FileDTO::make()
->id('asset-uuid')
->alt('New alt text')
->description('Updated description')
->copyrightIds(['copyright-uuid'])
);
// Delete asset
Fairu::assetMutations()->delete('asset-uuid');
// Move asset to another folder
Fairu::assetMutations()->move('asset-uuid', 'new-folder-uuid');
// Create folder
$folder = Fairu::folderMutations()->create(
FolderDTO::make()
->name('New Folder')
->parent('parent-uuid')
);
// Create gallery
$gallery = Fairu::galleryMutations()->create(
GalleryDTO::make()
->name('Event 2024')
->folderId('folder-uuid')
->date(now())
->location('Berlin')
);
// Create a sub-tenant of the current tenant.
// The returned API key is only shown once - store it securely.
$result = Fairu::tenantMutations()->createSubTenant('Client Workspace');
$subTenantId = $result->getId();
$apiKey = $result->getApiKey();
$createdAt = $result->getCreatedAt();
// Detach a sub-tenant, making it a fully independent tenant
$detached = Fairu::tenantMutations()->detachSubTenant($subTenantId);Fragments allow you to customize which fields are returned from the API.
Each resource type has three predefined variants: minimal, default, and full.
use SushiDev\Fairu\Facades\Fairu;
// Use predefined fragments
$asset = Fairu::assets()->find('uuid', Fairu::fragments()->asset('minimal'));
$asset = Fairu::assets()->find('uuid', Fairu::fragments()->asset('full'));use SushiDev\Fairu\Fragments\FragmentBuilder;
// Build custom fragment
$fragment = FragmentBuilder::for('FairuAsset')
->select(['id', 'name', 'mime', 'url', 'width', 'height', 'blurhash'])
->with('copyrights', fn($f) => $f->select(['id', 'name', 'email']))
->with('licenses', fn($f) => $f->select(['id', 'name', 'type', 'start', 'end']))
->build();
$asset = Fairu::assets()->find('uuid', $fragment);
// With arguments (e.g., for URL parameters)
$fragment = FragmentBuilder::for('FairuAsset')
->select(['id', 'name'])
->withArguments('url', ['width' => 800, 'height' => 600, 'quality' => 80], [])
->build();// In a service provider
Fairu::fragments()->register('my_asset_card',
FragmentBuilder::for('FairuAsset')
->select(['id', 'name', 'url', 'blurhash', 'width', 'height'])
->build()
);
// Use it later
$asset = Fairu::assets()->find('uuid', Fairu::fragments()->get('my_asset_card'));The SDK supports Laravel's cache system for API responses.
// Enable caching for a query (uses config TTL)
$tenant = Fairu::tenant()->cached()->get();
// Custom TTL (in seconds)
$roles = Fairu::roles()->cached(ttl: 3600)->all();
// Force fresh data (bypass cache)
$tenant = Fairu::tenant()->fresh()->get();
// Clear cached data
Fairu::tenant()->forget('cache-key');All input types have fluent DTOs for type-safe data handling.
use SushiDev\Fairu\DTOs\FileDTO;
use SushiDev\Fairu\DTOs\FolderDTO;
use SushiDev\Fairu\DTOs\CopyrightDTO;
use SushiDev\Fairu\DTOs\LicenseDTO;
use SushiDev\Fairu\DTOs\GalleryDTO;
use SushiDev\Fairu\DTOs\DiskDTO;
use SushiDev\Fairu\DTOs\DiskCredentialsDTO;
use SushiDev\Fairu\Enums\DiskType;
use SushiDev\Fairu\Enums\LicenseType;
// File DTO
$file = FileDTO::make()
->id('uuid')
->name('photo.jpg')
->alt('Description')
->caption('Caption text')
->description('Full description')
->focalPoint('50-50')
->copyrightIds(['cr-1', 'cr-2'])
->licenseIds(['lic-1']);
// Copyright DTO
$copyright = CopyrightDTO::make()
->name('John Doe Photography')
->email('[email protected]')
->phone('+1234567890')
->website('https://johndoe.com')
->active(true);
// License DTO
$license = LicenseDTO::make()
->name('Annual License')
->type(LicenseType::PERIOD)
->copyrightId('copyright-uuid')
->start(now())
->end(now()->addYear())
->days(365);
// Disk DTO with credentials
$credentials = DiskCredentialsDTO::make()
->key('aws-access-key')
->secret('aws-secret')
->bucket('my-bucket')
->region('eu-west-1');
$disk = DiskDTO::make()
->name('S3 Backup')
->type(DiskType::S3)
->folderId('folder-uuid')
->credentials($credentials)
->active(true);All API responses are wrapped in typed response objects.
$asset = Fairu::assets()->find('uuid');
// Access properties
$asset->id;
$asset->name;
$asset->mime;
$asset->url;
$asset->width;
$asset->height;
$asset->blurhash;
// Helper methods
$asset->isImage(); // true for image/* mime types
$asset->isVideo(); // true for video/* mime types
$asset->isPdf(); // true for application/pdf
$asset->getAspectRatio(); // width/height ratio
// Nested relations
$asset->getCopyrights(); // Array of Copyright objects
$asset->getLicenses(); // Array of License objects
// Array access
$asset['name'];
$asset['url'];
// JSON serialization
json_encode($asset);$results = Fairu::assets()->search('logo');
// Access items
$results->items(); // Array of Asset objects
$results->first(); // First item
$results->last(); // Last item
$results->isEmpty(); // Check if empty
$results->count(); // Items on current page
// Pagination info
$results->total(); // Total items across all pages
$results->currentPage(); // Current page number
$results->lastPage(); // Last page number
$results->perPage(); // Items per page
$results->hasMorePages(); // Has more pages?
// Iteration
foreach ($results as $asset) {
echo $asset->name;
}
// Collection-like methods
$ids = $results->pluck('id');
$filtered = $results->filter(fn($a) => $a->isImage());
$mapped = $results->map(fn($a) => $a->name);All GraphQL enums are available as PHP 8.1 backed enums.
use SushiDev\Fairu\Enums\UploadType;
use SushiDev\Fairu\Enums\SortingDirection;
use SushiDev\Fairu\Enums\LicenseType;
use SushiDev\Fairu\Enums\WorkflowStatus;
use SushiDev\Fairu\Enums\WorkflowType;
use SushiDev\Fairu\Enums\UserStatus;
use SushiDev\Fairu\Enums\DiskType;
use SushiDev\Fairu\Enums\WebhookType;
use SushiDev\Fairu\Enums\CustomDomainStatus;
use SushiDev\Fairu\Enums\GallerySortingField;
use SushiDev\Fairu\Enums\VideoVersions;
use SushiDev\Fairu\Enums\DmcaStatus;
use SushiDev\Fairu\Enums\UploadShareLinkExpiration;
use SushiDev\Fairu\Enums\PdfSignatureRequestStatus;
// Usage
$type = UploadType::STANDARD;
$direction = SortingDirection::DESC;
// From string
$status = WorkflowStatus::from('PROCESSING');
$status = WorkflowStatus::tryFrom('INVALID'); // nulluse SushiDev\Fairu\Exceptions\FairuException;
use SushiDev\Fairu\Exceptions\AuthenticationException;
use SushiDev\Fairu\Exceptions\GraphQLException;
try {
$asset = Fairu::assets()->find('uuid');
} catch (AuthenticationException $e) {
// Invalid or missing token (401)
} catch (GraphQLException $e) {
// GraphQL errors
$errors = $e->getGraphQLErrors();
$first = $e->getFirstError();
if ($e->hasValidationErrors()) {
$validation = $e->getValidationErrors();
}
} catch (FairuException $e) {
// General API errors
}The SDK dispatches events for debugging and logging.
use SushiDev\Fairu\Events\QueryExecuted;
use SushiDev\Fairu\Events\MutationExecuted;
// In EventServiceProvider
protected $listen = [
QueryExecuted::class => [
LogQueryListener::class,
],
MutationExecuted::class => [
LogMutationListener::class,
],
];
// Listener
class LogQueryListener
{
public function handle(QueryExecuted $event)
{
Log::debug('Fairu Query', [
'query' => $event->query,
'variables' => $event->variables,
'response' => $event->response,
]);
}
}For large files, use multipart uploads.
// Initialize multipart upload
$init = Fairu::uploads()->initMultipart(
filename: 'large-video.mp4',
folderId: 'folder-uuid',
fileSize: 104857600, // 100MB
contentType: 'video/mp4'
);
$fileId = $init->getId();
$uploadId = $init->getUploadId();
// Get upload URL for each part
$parts = [];
for ($i = 1; $i <= $totalParts; $i++) {
$partInfo = Fairu::uploads()->getMultipartPartUrl($fileId, $uploadId, $i);
// Upload part to $partInfo['url'] via PUT
// Collect ETag from response
$parts[] = [
'partNumber' => $i,
'etag' => $etag,
];
}
// Complete upload
$result = Fairu::uploads()->completeMultipart($fileId, $uploadId, $parts);
// Or abort if needed
Fairu::uploads()->abortMultipart($fileId, $uploadId);The File Proxy provides image transformation and optimized file delivery.
FAIRU_FILE_PROXY_URL=https://files.fairu.appuse SushiDev\Fairu\Facades\Fairu;
// Generate a URL for an asset
$url = Fairu::fileProxy()->url('asset-uuid', 'image.jpg')->toUrl();
// From an Asset object
$asset = Fairu::assets()->find('asset-uuid');
$url = Fairu::fileProxy()->fromAsset($asset)->toUrl();use SushiDev\Fairu\Facades\Fairu;
use SushiDev\Fairu\Enums\FileProxyFit;
use SushiDev\Fairu\Enums\FileProxyFormat;
// Resize image
$url = Fairu::fileProxy()
->url('asset-uuid', 'photo.jpg')
->width(800)
->height(600)
->toUrl();
// Set dimensions with aspect ratio
$url = Fairu::fileProxy()
->url('asset-uuid', 'photo.jpg')
->dimensions(1200, 800)
->toUrl();
// Change format and quality
$url = Fairu::fileProxy()
->url('asset-uuid', 'photo.jpg')
->format(FileProxyFormat::WEBP)
->quality(85)
->toUrl();
// Shorthand format methods
$url = Fairu::fileProxy()->url('asset-uuid', 'photo.jpg')->webp()->toUrl();
$url = Fairu::fileProxy()->url('asset-uuid', 'photo.jpg')->jpg()->toUrl();
$url = Fairu::fileProxy()->url('asset-uuid', 'photo.jpg')->png()->toUrl();
// Fit modes
$url = Fairu::fileProxy()
->url('asset-uuid', 'photo.jpg')
->dimensions(400, 400)
->cover() // Scale and crop to fill (default)
->toUrl();
$url = Fairu::fileProxy()
->url('asset-uuid', 'photo.jpg')
->dimensions(400, 400)
->contain() // Maintain aspect ratio within dimensions
->toUrl();
// Focal point for smart cropping
$url = Fairu::fileProxy()
->url('asset-uuid', 'photo.jpg')
->dimensions(400, 400)
->focal(50, 30) // x=50%, y=30%
->toUrl();
// With zoom level
$url = Fairu::fileProxy()
->url('asset-uuid', 'photo.jpg')
->focal(50, 50, 2.0) // x, y, zoom
->toUrl();use SushiDev\Fairu\Enums\VideoVersions;
// Extract video frame at timestamp
$url = Fairu::fileProxy()
->url('asset-uuid', 'video.mp4')
->timestamp('00:00:05.000')
->toUrl();
// Video quality version
$url = Fairu::fileProxy()
->url('asset-uuid', 'video.mp4')
->videoVersion(VideoVersions::HIGH)
->toUrl();
// HLS streaming URL
$hlsUrl = Fairu::fileProxy()->hlsUrl('tenant-uuid', 'asset-uuid');// Raw file download (no processing)
$url = Fairu::fileProxy()
->url('asset-uuid', 'document.eps')
->raw()
->toUrl();
// Process SVG to raster
$url = Fairu::fileProxy()
->url('asset-uuid', 'logo.svg')
->processSvg()
->width(200)
->toUrl();
// Signed URLs for restricted content
$url = Fairu::fileProxy()
->url('asset-uuid', 'file.jpg')
->signature($hmacSignature, $signatureDate)
->toUrl();// Check if file exists
$exists = Fairu::fileProxy()->exists('asset-uuid');
// Get image dimensions
$meta = Fairu::fileProxy()->meta('asset-uuid');
// Returns: ['width' => 1920, 'height' => 1080]
// Health check
$healthy = Fairu::fileProxy()->health();| Parameter | Range | Description |
|---|---|---|
width |
1-6000 | Output width in pixels |
height |
1-6000 | Output height in pixels |
quality |
1-100 | JPEG/WebP quality (default: 95) |
format |
jpg, png, webp | Output format (default: webp) |
fit |
cover, contain | Resize mode (default: cover) |
focal |
x-y-zoom | Smart crop focal point |
composer testMIT License. See LICENSE for details.