phpclassic/php-shopify
Lightweight PHP SDK for the Shopify API. Configure with shop URL and API key/password or an app access token, then access resources in an object-oriented style. Uses cURL by default and lets you pass extra cURL options for requests.
## Getting Started
### Minimal Setup
1. **Installation**
```bash
composer require phpclassic/php-shopify
Add to config/services.php:
'shopify' => [
'api_key' => env('SHOPIFY_API_KEY'),
'api_secret' => env('SHOPIFY_API_SECRET'),
'store_url' => env('SHOPIFY_STORE_URL'),
'access_token' => env('SHOPIFY_ACCESS_TOKEN'),
'scope' => env('SHOPIFY_SCOPE', 'read_write'),
'debug' => env('SHOPIFY_DEBUG', false), // Enable for logging
],
First Use Case: Fetching Products (with Error Handling)
use Phpclassic\Shopify\Facades\Shopify;
try {
$products = Shopify::product()->all();
} catch (\Exception $e) {
// Errors now properly include JSON-encoded error info
\Log::error('Shopify API Error:', [
'message' => $e->getMessage(),
'response' => Shopify::getLastResponse()
]);
}
Key Files to Review
config/shopify.php (default settings, including debug mode)vendor/phpclassic/php-shopify/src/Shopify.php (core class with improved error handling)vendor/phpclassic/php-shopify/src/Facades/Shopify.php (facade usage)vendor/phpclassic/php-shopify/src/Exceptions/ShopifyException.php (new error handling logic)CRUD Operations with Error Handling
// Create with error handling
try {
$product = Shopify::product()->create([
'title' => 'New Product',
'body_html' => '<p>Description</p>',
]);
} catch (\Phpclassic\Shopify\Exceptions\ShopifyException $e) {
// Parse JSON error response if available
$errorData = json_decode($e->getResponse()->getBody(), true);
\Log::error('Shopify Error:', $errorData);
}
Webhook Handling (Updated Validation)
Route::post('/shopify/webhook', function (Request $request) {
// Verify HMAC signature (unchanged)
$hmac = $request->header('X-Shopify-Hmac-Sha256');
$data = $request->getContent();
$computedHmac = hash_hmac('sha256', $data, config('shopify.api_secret'));
if (!hash_equals($hmac, $computedHmac)) {
abort(401, 'Invalid webhook signature');
}
// Handle webhook with improved error logging
try {
$topic = $request->input('topic');
$data = $request->input('data');
// Process webhook...
} catch (\Exception $e) {
\Log::error('Webhook Processing Failed', [
'topic' => $topic,
'error' => $e->getMessage()
]);
}
});
Bulk Operations with Debugging
// Export products with debug logging
if (config('shopify.debug')) {
\Log::info('Starting product export');
}
$products = Shopify::product()->all();
$csv = new \League\Csv\Writer($stream);
$csv->insertOne(['Title', 'Vendor']);
foreach ($products as $product) {
$csv->insertOne([$product->title, $product->vendor]);
if (config('shopify.debug') && $csv->tell() % 10 === 0) {
\Log::debug('Exported ' . $csv->tell() . ' products');
}
}
GraphQL Queries with Error Handling
$query = '
query {
products(first: 10) {
edges {
node {
title
id
}
}
}
}
';
try {
$results = Shopify::graphql()->query($query);
} catch (\Phpclassic\Shopify\Exceptions\ShopifyException $e) {
$error = json_decode($e->getResponse()->getBody(), true);
\Log::error('GraphQL Error:', [
'query' => $query,
'error' => $error
]);
throw new \RuntimeException('GraphQL query failed', 0, $e);
}
Enhanced Debugging: Enable SHOPIFY_DEBUG=true in .env to log:
Error Handling Middleware: Create middleware to catch Shopify exceptions:
class HandleShopifyErrors
{
public function handle($request, Closure $next)
{
try {
return $next($request);
} catch (\Phpclassic\Shopify\Exceptions\ShopifyException $e) {
$errorData = json_decode($e->getResponse()->getBody(), true);
\Log::error('Shopify API Error', [
'status' => $e->getResponse()->getStatusCode(),
'error' => $errorData ?? $e->getMessage()
]);
abort(502, 'Shopify API Error');
}
}
}
Testing with Mocks: Use the improved error handling in tests:
$mock = Mockery::mock('overload:' . \Phpclassic\Shopify\Shopify::class);
$mock->shouldReceive('get')->andThrow(new \Phpclassic\Shopify\Exceptions\ShopifyException(
new \GuzzleHttp\Psr7\Response(422, [], '{"errors": ["Invalid field"]}')
));
try {
Shopify::product()->find(123);
$this->fail('Expected exception not thrown');
} catch (\Phpclassic\Shopify\Exceptions\ShopifyException $e) {
$this->assertEquals(['Invalid field'], json_decode($e->getResponse()->getBody(), true)['errors']);
}
HTTP Error Response Handling (FIXED)
try {
$product = Shopify::product()->find(999999); // Non-existent product
} catch (\Phpclassic\Shopify\Exceptions\ShopifyException $e) {
$error = json_decode($e->getResponse()->getBody(), true);
// Handle $error['errors'] array
}
Webhook Verification Still Required
$hmac = $request->header('X-Shopify-Hmac-Sha256');
$computedHmac = hash_hmac('sha256', $request->getContent(), config('shopify.api_secret'));
if (!hash_equals($hmac, $computedHmac)) {
abort(401);
}
Pagination Edge Cases
$allProducts = [];
$link = Shopify::product()->getLink();
do {
try {
$page = Shopify::product()->all(['link' => $link]);
$allProducts = array_merge($allProducts, $page);
$link = $page->getNextLink();
} catch (\Exception $e) {
\Log::error('Pagination failed', ['link' => $link, 'error' => $e->getMessage()]);
break;
}
} while ($link);
GraphQL vs REST Tradeoffs
// REST (recommended for simple cases)
$product = Shopify::product()->find(123);
// GraphQL (for complex nested data)
$query = 'query { products(first: 10) { edges { node { title variants { price } } } } }';
$results = Shopify::graphql()->query($query);
SHOPIFY_DEBUG=true
This logs:
How can I help you explore Laravel packages today?