osiset/basic-shopify-api
A lightweight PHP library for Shopify’s REST and GraphQL Admin APIs. Provides a clean client, request/response handling, pagination helpers, rate limit awareness, and easy authentication setup. Ideal for building Shopify apps or integrating stores in any PHP project.
## Getting Started
### **First Steps**
1. **Installation**
```bash
composer require osiset/basic-shopify-api:^10.0
Register the service provider in config/app.php:
'providers' => [
// ...
Osiset\BasicShopifyApi\ShopifyServiceProvider::class,
],
Configuration Publish the config file:
php artisan vendor:publish --provider="Osiset\BasicShopifyApi\ShopifyServiceProvider" --tag="config"
Update .env with your Shopify API credentials:
SHOPIFY_API_KEY=your_api_key
SHOPIFY_API_SECRET=your_api_secret
SHOPIFY_STORE_DOMAIN=your-store.myshopify.com
First API Call (REST Example)
use Osiset\BasicShopifyApi\Facades\Shopify;
// PHP 8.1+ iterable support
foreach (Shopify::get('/admin/api/2023-07/products.json') as $product) {
// Process each product
}
// Or with a model:
$product = Shopify::product()->find(123456789);
GraphQL Example (Private Calls)
$query = '
query {
privateMetafields {
edges {
node {
key
value
}
}
}
}
';
$result = Shopify::graphql()->query($query);
CRUD Operations with PHP 8.1 Iterables
// Create
$product = Shopify::product()->create([
'title' => 'New Product',
'body_html' => '<p>Description</p>',
]);
// Read (PHP 8.1 iterable support)
foreach (Shopify::product()->all() as $product) {
echo $product->title;
}
// Update
$product->update(['title' => 'Updated Title']);
// Delete
$product->delete();
Bulk Operations
// Bulk create (REST)
$products = Shopify::post('/admin/api/2023-07/products.json', [
'products' => [
['title' => 'Product 1'],
['title' => 'Product 2'],
],
]);
// GraphQL bulk operations
$query = '
mutation {
bulkOperationRunQuery(
query: "mutation { productCreate(input: {title: \"Bulk Product\"}) { product { id } } }"
) {
bulkOperation { id }
}
}
';
Shopify::graphql()->query($query);
Webhook Handling
Configure webhooks in config/shopify.php:
'webhooks' => [
'topics' => ['orders/create', 'products/update'],
'handlers' => [
'orders/create' => \App\Http\Controllers\ShopifyWebhookController::class . '@handleOrderCreate',
'products/update' => \App\Http\Controllers\ShopifyWebhookController::class . '@handleProductUpdate',
],
],
Pagination with PHP 8.1 Iterables
// REST pagination
foreach (Shopify::product()->all(['limit' => 50]) as $product) {
// Process each product
}
// GraphQL cursor-based pagination
$query = '
query {
products(first: 25, after: "cursor") {
edges { cursor }
pageInfo { hasNextPage }
}
}
';
$result = Shopify::graphql()->query($query);
Error Handling
try {
$product = Shopify::product()->find(999999999); // Non-existent product
} catch (\Osiset\BasicShopifyApi\Exceptions\ShopifyException $e) {
logger()->error($e->getMessage());
}
Laravel Eloquent Models Extend the package’s models to add custom logic:
use Osiset\BasicShopifyApi\Models\Product;
class CustomProduct extends Product {
public function getFormattedTitle(): string {
return strtoupper($this->title);
}
}
Caching Responses Cache API responses to reduce calls:
$products = Cache::remember('shopify_products', now()->addHours(1), function () {
return Shopify::product()->all();
});
Rate Limiting Use Laravel’s rate limiter middleware for Shopify API endpoints:
Route::middleware(['throttle:10,1'])->group(function () {
Route::get('/shopify/products', [ShopifyController::class, 'index']);
});
Testing with PHP 8.1 Types
Use the ShopifyMock facade for unit tests:
use Osiset\BasicShopifyApi\Facades\ShopifyMock;
public function test_product_creation(): void {
ShopifyMock::shouldReceive('post')
->once()
->with('/admin/api/2023-07/products.json', ['title' => 'Test'])
->andReturn(['product' => ['id' => 123]]);
$product = Shopify::product()->create(['title' => 'Test']);
$this->assertEquals(123, $product->id);
}
PHP 8.1 Type Changes
// Old way (may not work in PHP 8.1)
$products = Shopify::product()->all();
foreach ($products as $product) { ... }
// New way (explicit iterable handling)
foreach (Shopify::product()->all() as $product) { ... }
GraphQL Private Calls
privateMetafields) require explicit permission scopes. Ensure your API key has the correct access:
// Example of accessing private data
$query = '
query {
privateMetafields(first: 10) {
edges {
node {
namespace
key
}
}
}
}
';
API Versioning
2023-07. Update if needed:
Shopify::setApiVersion('2023-10'); // Update if required
Webhook Verification
use Osiset\BasicShopifyApi\Facades\Shopify;
$isValid = Shopify::verifyWebhook($request->header('X-Shopify-Hmac-Sha256'), $request->getContent());
Rate Limits
use Osiset\BasicShopifyApi\Exceptions\RateLimitExceededException;
try {
$response = Shopify::get('/admin/api/2023-07/products.json');
} catch (RateLimitExceededException $e) {
sleep($e->getRetryAfter());
retry();
}
Enable Debugging
Set debug to true in config/shopify.php to log raw responses:
'debug' => env('SHOPIFY_DEBUG', false),
Log Raw Responses Use a middleware to log requests/responses:
Shopify::extend(function ($client) {
$client->getEmitter()->attach(
Subscriber::fromCallable(function ($request, $options, $context) {
logger()->debug('Shopify Request:', [
'url' => (string) $request->getUri(),
'method' => $request->getMethod(),
'body' => $request->getBody() ? $request->getBody()->getContents() : null,
]);
})
);
});
Common Errors
404 Not Found: Verify endpoint URLs (e.g., /products vs. /products.json).422 Unprocessable Entity: Validate input data against Shopify’s schema.401 Unauthorized: Regenerate API credentials or check scopes.Shopify::extend(function ($client) {
$client->customEndpoint = function ($method, $uri, $data = []) {
How can I help you explore Laravel packages today?