FoxyClient Example Walkthrough

This tutorial is an overview of the example application built using the FoxyClient library to manage coupons, categories and transactions. We recommend you clone the FoxyClient Example repo and follow along. Below is a quick video of what the example code does.

View FoxyClient Example on Github

Getting FoxyClient

First up, you'll want to add the FoxyClient to your application using whatever dependency injection or app container you're familiar with. This library is written in PHP, but if you're using a different language it should still serve as some helpful pseudo code for writing a library in your language of choice. The example application makes use of Composer for managing it's dependancies, and already includes the composer.json file that defines those.

To start off, we'll need to install Composer if it's not already included on your system, and use it to install our dependencies. There are a couple different ways to install Composer depending on your operating system. Refer to Composer's Getting Started docs for details for your set up. If installing via the command line, make sure you do that into the directory you've downloaded the example code into.

Depending on the installation approach that you take - make note of the way that it says to make calls to Composer as it does depend on how it's installed. It will either look like simply composer or php composer.phar.

Once installed, open the command line and within the folder where you have downloaded the example application, run the following command (and substitue the starting composer command to match how you were instructed to call it when installing):

composer install

When you run that command, it may take a little while to execute completely, as it will be downloading all the needed dependencies. You should see the output in the terminal though, showing that it's downloaded everything needed into a /vendor folder.

Authentication

The Foxy API uses OAuth 2.0 for authenticating access to a Foxy user or store, and depending on the type of integration with the API you're building and how it will be distributed, dictates how you need to add OAuth to your application. You can see a full overview of OAuth and the Foxy API within our authentication section.

Once running, the example application includes links to create an OAuth Client and authenticate access to your store for the application directly through the browser. Alternatively, you can also create an OAuth Client that is already authenticated to an existing Foxy store you have access to through the Foxy administration. Take a look at the quick start instructions on the authentication section for details on doing that.

Either approach is fine for authenticating with the example application, and we'll refer to hardcoding the instructions in the next section, and to creating the client and tokens manually in the section following.

Bootstrapping

Let's take a look at the bootstrap.php file in the example codebase. This file takes care of getting everything connected and some general housekeeping.

We start by including the autoloader file that Composer generated for us. If your php.ini doesn't include a default timezone, you can set one here. Our example uses the session for storing OAuth related tokens, but you could also use a database or some other secure persistence layer.

require __DIR__ . '/vendor/autoload.php';
date_default_timezone_set('America/Los_Angeles');
session_start();

We also include some use statements to simplify things:

use GuzzleHttp\Client;
use GuzzleHttp\Subscriber\Cache\CacheSubscriber;
use Foxy\FoxyClient\FoxyClient;

Next we set up our configuration for the FoxyClient. Set this to true if you want to connect to the sandbox (or you can just exclude the config to default to production). Note that the sandbox endpoint for the API does not have access to development stores created through the Foxy administration. To work with development or production stores through the API, you'll use the production API endpoint.

$config = array(
    'use_sandbox' => false
    );

If you have already created an OAuth Client with access to your store, you can hardcode the client_id, client_secret and refresh_token in the bootstrap.php file, though this should never be done on a public github repo. Ideally, the client_secret and refresh_token should be stored securely in a database (preferably encrypted) or in an environment variable. The example code also has an "Authenticate Client" link so you can add this information in dynamically per session once it's running.

$config['client_id'] = '';
$config['client_secret'] = '';

$config['refresh_token'] = '';

Next we set up Guzzle as a HTTP client, and also it's CacheSubscriber plugin. Within the Guzzle config you can turn on debug, which can sometimes be helpful to see what's going on under the hood. Using a HTTP client and cache like this is important for any HTTP REST client which should be sending appropriate ETags to avoid unnecessary payloads. See our caching overview for details on that.

$guzzle_config = array(
    'defaults' => array(
        'debug' => false,
        'exceptions' => false
    )
);

$guzzle = new Client($guzzle_config);
CacheSubscriber::attach($guzzle);

Now we wire up our FoxyClient.

$fc = new FoxyClient($guzzle, $config);

Finally we add in a little CSRF helper library that we use in our example to emphasize how important this type of security precaution is within your application. If you're not familiar with CSRF, please get familiar with it.

$csrf = new \Riimu\Kit\CSRF\CSRFHandler(false);
try {
    $csrf->validateRequest(true);
} catch (\Riimu\Kit\CSRF\InvalidCSRFTokenException $ex) {
    header('HTTP/1.0 400 Bad Request');
    exit('Bad CSRF Token!');
}
$token = $csrf->getToken();

The Application

Now that we have our bootstrap file in place, we can dive into the example application. The index.php file is the core of the application, and includes additional files to add in it's functionality. As mentioned above, this example uses the session to store OAuth token information, but this could also be stored in a database or some other system. Within index.php, we update our client on each page load based on what we have in the session:

if (isset($_SESSION['access_token']) && $fc->getAccessToken() != $_SESSION['access_token']) {
    if ($fc->getAccessToken() == '') {
        $fc->setAccessToken($_SESSION['access_token']);
    }
}
if (isset($_SESSION['refresh_token']) && $fc->getRefreshToken() != $_SESSION['refresh_token']) {
    if ($fc->getRefreshToken() == '') {
        $fc->setRefreshToken($_SESSION['refresh_token']);
    }
}
if (isset($_SESSION['client_id']) && $fc->getClientId() != $_SESSION['client_id']) {
    if ($fc->getClientId() == '') {
        $fc->setClientId($_SESSION['client_id']);
    }
}
if (isset($_SESSION['client_secret']) && $fc->getClientSecret() != $_SESSION['client_secret']) {
    if ($fc->getClientSecret() == '') {
        $fc->setClientSecret($_SESSION['client_secret']);
    }
}
if (isset($_SESSION['access_token_expires']) && $fc->getAccessTokenExpires() != $_SESSION['access_token_expires']) {
    if ($fc->getAccessTokenExpires() == '') {
        $fc->setAccessTokenExpires($_SESSION['access_token_expires']);
    }
}

If you've configured the bootstrap file with a client_id, client_secret, and refresh_token, the following call will ensure you get an active OAuth access_token.

$fc->refreshTokenAsNeeded();

When working with Hypermedia APIs, it's important to never hardcode to URL's, but instead code to link relationships and obtain the URI's you want from them. The hypermedia constraint is an important concept in REST which allows for the server to scale and stay decoupled from the client. This bit of code uses the session to bookmark the store URI along with a couple other URI's used by the example code.

// let's see if we have access to a store... if so, bookmark some stuff
if ($fc->getAccessToken() != '') {
    if (!isset($_SESSION['store_uri']) || $_SESSION['store_uri'] == ''
        || !isset($_SESSION['store_name']) || $_SESSION['store_name'] == '') {
        $result = $fc->get();
        $store_uri = $fc->getLink('fx:store');
        if ($store_uri != '') {
            $_SESSION['store_uri'] = $store_uri;
            $result = $fc->get($store_uri);
            $errors = $fc->getErrors($result);
            if (!count($errors)) {
                $_SESSION['store_name'] = $result['store_name'];
                $_SESSION['item_categories_uri'] = $result['_links']['fx:item_categories']['href'];
                $_SESSION['coupons_uri'] = $result['_links']['fx:coupons']['href'];
                $_SESSION['transactions_uri'] = $result['_links']['fx:transactions']['href'];
            }
        }
    }
}

Further down we have a return from an OAuth Authorization Code grant. If your client is properly set up with a redirect_uri of http://localhost:8000/?action=authorization_code_grant, the $fc->getAccessTokenFromAuthorizationCode($_GET['code']); call will handle everything for you and update your client accordingly.

if ($action == 'authorization_code_grant') {
    $result = $fc->getAccessTokenFromAuthorizationCode($_GET['code']);
    $errors = array_merge($errors,$fc->getErrors($result));
    if (!count($errors)) {
        $_SESSION['access_token'] = $result['access_token'];
        $_SESSION['access_token_expires'] = time() + $result['expires_in'];
        $_SESSION['refresh_token'] = $result['refresh_token'];
        $action = "view_coupons";
    }
}

We also automatically redirect to the Authorization end point if you don't currently have an access token or refresh token.

if ($fc->getAccessToken() == '' && $fc->getRefreshToken() == '') {
    $authorizaiton_url = $fc->getAuthorizationEndpoint() . '?client_id=' . $fc->getClientId();
    $authorizaiton_url .= '&scope=store_full_access&state=' . $token;
    $authorizaiton_url .= '&response_type=code';
    header("location: " . $authorizaiton_url);
    die();
}

Running the Application

At this point - if you haven't already, you can fire up the application to play around with it. If you have PHP installed on your computer, you can start a local server by running the following command in your command line when looking at the folder the code is in:

php -S localhost:8000

You can then access the application in your browser at http://localhost:8000.

If you haven't hardcoded an OAuth Client and refresh token in the bootstrap.php file, then you can create one from the homepage of the application. First follow the link to "Register your application" to create an OAuth Client. Then either create a user and store through the application by following the respective links, or if you already have a Foxy store you've created through the administration, you can use the "OAuth Authorization Code grant" link to connect the example application to that store by completing an OAuth Authorization Grant.

Once you've connected the example to a store, you'll then be able to interact with the store's coupons, categories and transactions. The homepage of the example application will update to display links to working with those resources now, following them will provide forms to add/edit/delete them as allowed.

Digging in futher

The rest of the code should be a pretty straightforward example of forms and form processing. To view how it interacts with the different aspects of the store, you can review the respective files within the /includes folder.

Worth noting, the view_coupon action uses the zoom feature to zoom in on the coupon_codes child resource of each coupon and include them in the returning payload.

$result = $fc->get($resouce_uri,array('zoom' => 'coupon_codes'));

That gives us access to the embedded coupon codes:

if ($field == '_embedded') {
    $embedded_data = $value['fx:coupon_codes'];
}

Which we can then display beneath the coupon details:

foreach($embedded_data as $coupon_code) {
    foreach($coupon_code as $cc_field => $cc_value) {
        if ($cc_field == 'code') {
            print '<code>' . $cc_value . '</code>';
        }
        if ($cc_field == 'number_of_uses_to_date') {
            print ' (' . $cc_value . ' uses so far)<br />';
        }
    }
}

Another piece of code worth pointing out is the use of pagination when displaying coupons:

$result = $fc->get($coupons_uri, array("limit" => 5));

The default limit is 20, but when we set it to 5, we quickly see how the next and prev link relationships can come in handy, along with the collection properties such as returnuned_items and total_items.

<h3>Coupons for <?php print $_SESSION['store_name']; ?></h3>
<?php
print '<p>Displaying ' . $result['returned_items'] . ' (' . ($result['offset']+1) . ' through ' . min($result['total_items'],($result['limit']+$result['offset'])) . ') of ' . $result['total_items'] . ' total coupons.</p>'
?>
<nav>
  <ul class="pagination">
    <li>
      <a href="/?action=view_coupons&coupons_uri=<?php print urlencode($result['_links']['prev']['href']); ?>" aria-label="Previous">
        <span aria-hidden="true">«</span>
      </a>
    </li>
    <li>
      <a href="/?action=view_coupons&coupons_uri=<?php print urlencode($result['_links']['next']['href']); ?>" aria-label="Next">
        <span aria-hidden="true">»</span>
      </a>
    </li>
  </ul>
</nav>

This pagination example could be expanded to include page numbers and more.

Since the FoxyClient automatically refreshes the OAuth Access token as needed, the example demonstrates how you can check to see if the token has been updated during the page load and, if needed, update your own persistent copy of the token:

if (isset($_SESSION['access_token']) && $fc->getAccessToken() != $_SESSION['access_token']) {
    // This can happen after a token refresh.
    if ($fc->getAccessToken() != '') {
        $_SESSION['access_token'] = $fc->getAccessToken();
    }
}
if (isset($_SESSION['access_token_expires']) && $fc->getAccessTokenExpires() != $_SESSION['access_token_expires']) {
    // This can happen after a token refresh.
    if ($fc->getAccessTokenExpires() != '') {
        $_SESSION['access_token_expires'] = $fc->getAccessTokenExpires();
    }
}

One useful concept demonstrated throughout the example appliation is the use of URI's (Uniform Resource Identifier) for working with the resources. Many systems rely on guid's or integers, but those don't provide as much value. When using a URI, you have all the information your application needs to manipulate a resource (or a child resource). In some cases we pass around the parent resource uri when working with a child resource (such as the relationship between coupons and coupon codes) so we can easily get back to the parent resource.

Within our system, Coupons can be used for any category or they can be restricted to specific categories. We represent this relationship with the coupon_item_categories resource. It's a little bit tricky, but the way we represent this is by pulling all of the possible store categories from the fx:item_categories link relationship which is a child of the fx:store link relationship. When we list them out on the add coupon or edit coupon screens, we then figure out which coupon_item_category relationships exist and check the checkboxes accordingly. When the form is submitted, we then figure out which coupon_item_category relationships need to be created and which ones need to be removed.

It can be very helpful when working through this example or any projects with the hAPI to take advantage of the HAL Browser and Link Relationship diagram. For example, to get a feel for the link relationships available to you and the relationships between the resources, use the link relationship diagram. Here are the links associated with coupons and coupon codes:

You can also use the HAL Browser to better understand the data structure and what properties are available to you. The docs icon near the link relationship you're interested in will load the documentation for that link relationship in the Inspector on the top right of the HAL Browser. Here's the HAL Browser response for a coupon:

We hope this tutorial and example code is helpful for you and gives you launching point for building your own application with the Foxy Hypermedia API. If you have any questions at all, please let us know!