• Stars
    star
    219
  • Rank 180,582 (Top 4 %)
  • Language
    PHP
  • License
    MIT License
  • Created over 8 years ago
  • Updated 9 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A simple API wrapper for Shopify using Guzzle for REST and GraphQL

Basic Shopify API

Tests codecov License

A simple, tested, API wrapper for Shopify using Guzzle.

It supports both the sync/async REST and GraphQL API provided by Shopify, basic rate limiting, and request retries.

It contains helpful methods for generating a installation URL, an authorize URL (offline and per-user), HMAC signature validation, call limits, and API requests.

It works with both OAuth and private API apps.

Table of Contents

Installation

The recommended way to install is through composer.

composer require gnikyt/basic-shopify-api

Usage

Public API

This assumes you properly have your app setup in the partner's dashboard with the correct keys and redirect URIs.

REST (sync)

For REST calls, the shop domain and access token are required.

use Gnikyt\BasicShopifyAPI\BasicShopifyAPI;
use Gnikyt\BasicShopifyAPI\Options;
use Gnikyt\BasicShopifyAPI\Session;

// Create options for the API
$options = new Options();
$options->setVersion('2020-01');

// Create the client and session
$api = new BasicShopifyAPI($options);
$api->setSession(new Session('example.myshopify.com', 'access-token-here'));

// Now run your requests...
$result = $api->rest(...);

REST (async)

For REST calls, the shop domain and access token are required.

use Gnikyt\BasicShopifyAPI\BasicShopifyAPI;
use Gnikyt\BasicShopifyAPI\Options;
use Gnikyt\BasicShopifyAPI\Session;

// Create options for the API
$options = new Options();
$options->setVersion('2020-01');

// Create the client and session
$api = new BasicShopifyAPI($options);
$api->setSession(new Session('example.myshopify.com', 'access-token-here'));

// Now run your requests...
$promise = $api->restAsync(...);
$promise->then(function (array $result) {
  // ...
});

GraphQL (sync)

For GraphQL calls, the shop domain and access token are required.

use Gnikyt\BasicShopifyAPI\BasicShopifyAPI;
use Gnikyt\BasicShopifyAPI\Options;
use Gnikyt\BasicShopifyAPI\Session;

// Create options for the API
$options = new Options();
$options->setVersion('2020-01');

// Create the client and session
$api = new BasicShopifyAPI($options);
$api->setSession(new Session('example.myshopify.com', 'access-token-here'));

// Now run your requests...
$result = $api->graph(...);

GraphQL (async)

For GraphQL calls, the shop domain and access token are required.

use Gnikyt\BasicShopifyAPI\BasicShopifyAPI;
use Gnikyt\BasicShopifyAPI\Options;
use Gnikyt\BasicShopifyAPI\Session;

// Create options for the API
$options = new Options();
$options->setVersion('2020-01');

// Create the client and session
$api = new BasicShopifyAPI($options);
$api->setSession(new Session('example.myshopify.com', 'access-token-here'));

// Now run your requests...
$promise = $api->graphAsync(...);
$promise->then(function (array $result) {
  // ...
});

Getting access (offline)

This is the default mode which returns a permanent token.

After obtaining the user's shop domain, to then direct them to the auth screen use getAuthUrl, as example (basic PHP):

// Create options for the API
$options = new Options();
$options->setVersion('2020-01');
$options->setApiKey(env('SHOPIFY_API_KEY'));
$options->setApiSecret(env('SHOPIFY_API_SECRET'));

// Create the client and session
$api = new BasicShopifyAPI($options);
$api->setSession(new Session($_SESSION['shop']));

$code = $_GET['code'];
if (!$code) {
  /**
   * No code, send user to authorize screen
   * Pass your scopes as an array for the first argument
   * Pass your redirect URI as the second argument
   */
  $redirect = $api->getAuthUrl(env('SHOPIFY_API_SCOPES'), env('SHOPIFY_API_REDIRECT_URI'));
  header("Location: {$redirect}");
  exit;
} else {
  // We now have a code, lets grab the access token
  $api->requestAndSetAccess($code);

  // You can now make API calls
  $request = $api->rest('GET', '/admin/shop.json'); // or GraphQL
}

Getting access (per-user)

You can also change the grant mode to be per-user as outlined in Shopify documentation. This will receieve user info from the user of the app within the Shopify store. The token recieved will expire at a specific time.

// Create options for the API
$options = new Options();
$options->setVersion('2020-01');
$options->setApiKey(env('SHOPIFY_API_KEY'));
$options->setApiSecret(env('SHOPIFY_API_SECRET'));

// Create the client and session
$api = new BasicShopifyAPI($options);
$api->setSession(new Session($_SESSION['shop']));

$code = $_GET['code'];
if (!$code) {
  /**
   * No code, send user to authorize screen
   * Pass your scopes as an array for the first argument
   * Pass your redirect URI as the second argument
   * Pass your grant mode as the third argument
   */
  $redirect = $api->getAuthUrl(env('SHOPIFY_API_SCOPES'), env('SHOPIFY_API_REDIRECT_URI'), 'per-user');
  header("Location: {$redirect}");
  exit;
} else {
  // We now have a code, lets grab the access object
  $api->requestAndSetAccess($code);

  // You can now make API calls
  $request = $api->rest('GET', '/admin/shop.json'); // or GraphQL
}

Verifying HMAC signature

Simply pass in an array of GET params.

// Will return true or false if HMAC signature is good.
$valid = $api->verifyRequest($_GET);

Private API

This assumes you properly have your app setup in the partner's dashboard with the correct keys and redirect URIs.

REST

For REST (sync) calls, shop domain, API key, and API password are request

// Create options for the API
$options = new Options();
$options->setType(true); // Makes it private
$options->setVersion('2020-01');
$options->setApiKey(env('SHOPIFY_API_KEY'));
$options->setApiPassword(env('SHOPIFY_API_PASSWORD'));

// Create the client and session
$api = new BasicShopifyAPI($options);
$api->setSession(new Session($_SESSION['shop']));

// Now run your requests...
$result = $api->rest(...);

GraphQL

For GraphQL calls, shop domain and API password are required.

// Create options for the API
$options = new Options();
$options->setType(true); // Makes it private
$options->setVersion('2020-01');
$options->setApiPassword(env('SHOPIFY_API_PASSWORD'));

// Create the client and session
$api = new BasicShopifyAPI($options);
$api->setSession(new Session($_SESSION['shop']));

// Now run your requests...
$result = $api->graph(...);

Making requests

REST

Requests are made using Guzzle.

$api->rest(string $type, string $path, array $params = null, array $headers = [], bool $sync = true);
// or $api->getRestClient()->request(....);
  • type refers to GET, POST, PUT, DELETE, etc
  • path refers to the API path, example: /admin/products/1920902.json
  • params refers to an array of params you wish to pass to the path, examples: ['handle' => 'cool-coat']
  • headers refers to an array of custom headers you would like to optionally send with the request, example: ['X-Shopify-Test' => '123']
  • sync refers to if the request should be synchronous or asynchronous.

You can use the alias restAsync to skip setting sync to false.

If sync is true (regular rest call):

The return value for the request will be an array containing:

  • response the full Guzzle response object
  • body the JSON decoded response body (\Gnikyt\BasicShopifyAPI\ResponseAccess instance)
  • errors if any errors are detected, true/false
  • exception if errors are true, exception object is available
  • status the HTTP status code
  • link an array of previous/next pagination values, if available

Note: request() will alias to rest() as well.

If sync is false (restAsync call):

The return value for the request will be a Guzzle promise which you can handle on your own.

The return value for the promise will be an object containing:

  • response the full Guzzle response object
  • body the JSON decoded response body (\Gnikyt\BasicShopifyAPI\ResponseAccess instance)
  • errors if any errors are detected, true/false
  • exception if errors are true, exception object is available
  • status the HTTP status code
  • link an array of previous/next pagination values, if available
$promise = $api->restAsync(...);
$promise->then(function (array $result) {
  // `response` and `body`, etc are available in `$result`.
});
Overriding request type

If you require the need to force a query string for example on a non-GET endpoint, you can specify the type as a key.

$api->rest('PUT', '/admin/themes/12345/assets.json', ['query' => [...]]);

Valid keys are query and json.

Passing additional request options

If you'd like to pass additional request options to the Guzzle client created, pass them as the second argument of the constructor.

// Create options for the API
$options = new Options();
$options->setVersion('2020-01');
// ...
$options->setGuzzleOptions(['connect_timeout' => 3.0]);

// Create the client
$api = new BasicShopifyApi($options);

GraphQL

Requests are made using Guzzle.

$api->graph(string $query, array $variables = []);
  • query refers to the full GraphQL query
  • variables refers to the variables used for the query (if any)

The return value for the request will be an object containing:

  • response the full Guzzle response object
  • body the JSON decoded response body (\Gnikyt\BasicShopifyAPI\ResponseAccess instance)
  • errors if there was errors or not, will return the errors if any are found
  • status the HTTP status code

Example query:

$result = $api->graph('{ shop { product(first: 1) { edges { node { handle, id } } } } }');
echo $result['body']['shop']['products']['edges'][0]['node']['handle']; // test-product
// or echo $result['body']->shop->products->edges[0]->node->handle;

Example mutation:

$result = $api->graph(
    'mutation collectionCreate($input: CollectionInput!) { collectionCreate(input: $input) { userErrors { field message } collection { id } } }',
    ['input' => ['title' => 'Test Collection']]
);
echo $result['body']['collectionCreate']['collection']['id']; // gid://shopify/Collection/63171592234
// or echo $result['body']->collectionCreate->collection->id;

API Versioning

This library supports versioning the requests, example:

// Create options for the API
$options = new Options();
$options->setVersion('2020-01'); // YYYY-MM or "unstable" is accepted

// Create the client
$api = new BasicShopifyAPI($options);

You can override the versioning at anytime for specific API requests, example:

// Create options for the API
$options = new Options();
$options->setVersion('2020-01');

// Create the client
$api = new BasicShopifyAPI($options);
$api->rest('GET', '/admin/api/unstable/shop.json'); // Will ignore "2020-01" version and use "unstable" for this request

Rate limiting

This library comes with a built-in basic rate limiter which utilizes usleep between applicable calls.

  • For REST: it ensures you do not request more than the default of 2 calls per second.
  • For GraphQL: it ensures you do not use more than the default of 50 points per second.

To adjust the default limits, use the option class' setRestLimit and setGraphLimit.

Custom rate limiting

You simply need to disable the built-in rate limiter and push in a custom Guzzle middleware. Example:

$options = new Options();
// ...
$options->disableRateLimiting();

// ...
$api = new BasicShopifyAPI($options);
$api->addMiddleware(new CustomRateLimiter($api), 'rate:limiting');

page_info / pagination support

2019-07 API version introduced a new Link header which is used for pagination (explained here).

If an endpoint supports page_info, you can use $response->link to grab the page_info value to pass in your next request.

Example:

$response = $api->rest('GET', '/admin/products.json', ['limit' => 5]);
$link = $response['link']['next']; // eyJsYXN0X2lkIjo0MDkw
$link2 = $response['link']['previous']; // dkUIsk00wlskWKl
$response = $api->rest('GET', '/admin/products.json', ['limit' => 5, 'page_info' => $link]);

Isolated API calls

You can initialize the API once and use it for multiple shops. Each instance will be contained to not pollute the others. This is useful for something like background job processing.

$api->withSession(Session $newSession, Closure $closure);

$this will be binded to the closure. Example:

$api->withSession(new Session('someshop.myshopify.com', 'some-token'), function (): void {
  $request = $this->rest('GET', '/admin/shop.json');
  echo $request['body']['shop']['name']; // Some Shop
});

// $api->rest/graph will not be affected by the above code, it will use previously defined session

Retries

This library utilizes caseyamcl/guzzle_retry_middleware middleware package.

By default, 429, '500and503` errors will be retried twice.

For REST calls, it will utilize Shopify's X-Retry-After header to wait x seconds before retrying the call.

When all retries are exhasted, the standard response from the library will return where you can handle the error.

To change the status codes watched or the maximum number of retries, use the option class' setGuzzleOptions:

// Create options for the API
$options = new Options();
$options->setVersion('2020-01');
// ...
$options->setGuzzleOptions(
  'max_retry_attempts' => 3, // Was 2
  'retry_on_status'    => [429, 503, 400], // Was 439, 503, 500
);

// Create the client
$api = new BasicShopifyApi($options);

Errors

This library internally catches only 400-500 status range errors through Guzzle. You're able to check for an error of this type and get its response status code and body.

$call = $api->rest('GET', '/admin/non-existant-route-or-object.json');

if ($call['errors']) {
  echo "Oops! {$call['status']} error";
  var_dump($call['body']);

  // Original exception can be accessed via `$call['exception']`
  // Example, if response body was `{"error": "Not found"}`...
  /// then: `$call['body']` would return "Not Found"
}

Middleware

This library takes advantage of using Guzzle middleware for request/response checks and modifications. You're also able to inject middleware.

$api->addMiddleware([callable]);

See Guzzle's documentation on middleware. As well, you can browse this library's middleware for examples.

Storage

For storing the current request times, API limits, request costs, etc. A basic in-memory array store is used Gnikyt\BasicShopifyAPI\Store\Memory.

If you would like to implement a more advananced store such as one with Redis, simply implement Gnikyt\BasicShopifyAPI\Contracts\StateStorage and set the client to use it, example:

$timeStore = new RedisStore();
$limitStore = new RedisStore();

$api = new BasicShopifyAPI($options, $timeStore, $limitStore);

Documentation

Code documentation is available here from phpDocumentor via phpdoc -d src -t doc.

LICENSE

This project is released under the MIT license.

Misc

Using Python? Check out basic_shopify_api.

More Repositories

1

laravel-shopify

A full-featured Laravel package for aiding in Shopify App development
PHP
1,238
star
2

Blockchain-PHP

Blockchain example implemented in PHP
PHP
38
star
3

markdown-invoice

Personal tool I use to generate my Markdown invoices into HTML/PDF.
PHP
33
star
4

basic_shopify_api

A sync/async REST and GraphQL client for Shopify API using HTTPX
Python
19
star
5

Shopify-OptionSelectorsCustom

Custom option selectors implementation which supports templating.
JavaScript
12
star
6

http_shopify_webhook

A middleware for Go's net/http package for validating incoming Shopify webhooks
Go
6
star
7

movie-barcode

Creates a neat image representing the colors of a movie
Shell
6
star
8

with

A Python with-statement clone for PHP.
PHP
6
star
9

gtk-colorizer

CSS
5
star
10

Basic-Shopify-Resource

A basic resource wrapper for Basic-Shopify-API
PHP
4
star
11

shopify-theme-download

Download a complete copy of a Shopify theme via command line.
PHP
4
star
12

acf-simple_table

Creates rows and columns of text inputs and converts it into an array for an ACF form.
PHP
3
star
13

shopify_app_whitelist

Adds whitelisting ability to shopify_app
Ruby
3
star
14

backlight

A pointless PHP library for getting the mean and glow background colors of an image or icon.
PHP
3
star
15

activity

Simple activity stream using Predis.
PHP
2
star
16

droplr-bash

A simple Droplr uploader
Shell
2
star
17

wp-user-themes

Allows users pick a theme in their profile section
PHP
2
star
18

CloudApp-Bash

Basic CloudApp implementation for Linux written in Bash
Shell
1
star
19

lockscreen-quotes

Set a quote to Mac's lock screen.
Ruby
1
star
20

react-health

Easily check all your websites' health.
PHP
1
star
21

wordpress-save-override

CMD+S or CTRL+S for saving posts or pages
JavaScript
1
star
22

wordpress-docker-quickstart

A basic Wordpress-Docker-Grunt setup for theme development.
PHP
1
star
23

gnikyt.github.io

My crappy blog
HTML
1
star
24

react-chat

Just an experiment in a chat program with React
PHP
1
star
25

mLogger

PHP function for mLogger
PHP
1
star
26

evernote-to-pocket-bookmarks

Move your Evernote bookmarks to Pocket
Ruby
1
star