d
Topic
Jason Member
Posts:
78
Last edited 12 months ago

PHP API Example This post is outdated

I'm having trouble getting authenticated with the API system -- I've granted access to it in the admin area. Does anyone have a basic example in PHP of accessing the api? Thanks.
i
Replies
Posts:
69
Last edited March 15, 2009

When I was working on pulling order information a few months back with PHP via the API, I was having trouble finding any good examples on these forums or in Shopify docs/wiki as well.

I actually ended up going to the API forums of some of the 37Signals apps (I picked 37signals because I knew Shopify would be very similar [Rails])....I found alot of activity and tons of examples using PHP and curl. Obviously you’ll have to modify the examples to authenticate and work with Shopify’s API…but it was very helpful for me.

Here’s the 37Signals APIs..(look on the right sidebar for the links to the discussion forums)

http://developer.37signals.com/

And of course you probably already know this but Shopify’s API documentation is here…

http://www.shopify.com/developers/api/

Hope this helps!

Cora Boutique - trendy and fashionable women's clothing www.shopcora.com
mykenism Member
Posts:
2
March 18, 2009

It took me a while to figure the API out as well. Here is some example code that may help you using Curl and SimpleXML. This will get a product and spit out the title and quantity.

<?php
  //Modify these
  $API_KEY = 'your-token-here';
  $SECRET = 'your-secret-here';
  $TOKEN = 'your-secret-here';
  $STORE_URL = 'yourestore.myshopify.com';
  $PRODUCT_ID = 'product-id-here';

  $url = 'https://' . $API_KEY . ':' . md5($SECRET . $TOKEN) . '@' . $STORE_URL . '/admin/products/' . $PRODUCT_ID . '.xml';

  $session = curl_init();

  curl_setopt($session, CURLOPT_URL, $url);
  curl_setopt($session, CURLOPT_HTTPGET, 1); 
  curl_setopt($session, CURLOPT_HEADER, false);
  curl_setopt($session, CURLOPT_HTTPHEADER, array('Accept: application/xml', 'Content-Type: application/xml'));
  curl_setopt($session, CURLOPT_RETURNTRANSFER, true);

  if(ereg("^(https)",$url)) curl_setopt($session,CURLOPT_SSL_VERIFYPEER,false);

  $response = curl_exec($session);
  curl_close($session);

  $product_xml = new SimpleXMLElement($response); 

  echo $product_xml->title;
  echo $product_xml->variants->variant->{'inventory-quantity'};
?>
Posts:
5634
March 18, 2009

Thank you, Mykenism!

Any good reference your recommend on getting up to speed with the CURL library? (Besides PHP.net).

Caroline from http://11heavens.com ∴ mllegeorgesand AT gmail DOT com
mykenism Member
Posts:
2
Jason Member
Posts:
78
Last edited March 20, 2009

Thanks shopcora and mykenism! Your help is most appreciated. I’m working with the CURL example (previously I had tried to use fopen() which apparently doesn’t play nice with the headers).

I’m still stuck ([API] Invalid or wrong permission (password).) and curious about the token?

How do I go about generating it? When I messed with this a couple of months ago what I saved as the token mid development isn’t working …

I tried using these instructions awhile back to get my first sample working and am now using CURL but still can’t get logged in. http://www.shopify.com/developers/api/authentication.html

Since I’m running the api via the commandline I wanted to save the api/secret/token in my script and not have to worry about negotiating creating it every time I call this, if I didn’t have to.

Thanks for the help.

Jason Member
Posts:
78
March 21, 2009

So I reread everything and did some searching to make sure I had the right thing as the token. I understand that now and know I have it right (or at least I’m 99% sure I do) ... the test code still isn’t working for me.

And I get the Invalid or wrong permission error.

Any ideas?

thcom Member
Posts:
25
April 26, 2009

How would I modify this to create a new product via php and the api?

thcom Member
Posts:
25
Last edited April 26, 2009

Can anyone spot why I’m getting a 500 error with this? I’m trying to add a new product. FYI, I successfully tested the key, secret, and token with a simple GET for a product.

<?php

  //Modify these
  $API_KEY = 'xxx';
  $SECRET = 'yyy';
  $TOKEN = 'zzz';
  $STORE_URL = 'store.myshopify.com';

    $url = 'https://' . $API_KEY . ':' . md5($SECRET . $TOKEN) . '@' . $STORE_URL . '/admin/products.xml';

    $product = new SimpleXMLElement('<product></product>');    
    $product->addChild('body','this is the description');
    $product->addChild('title','this is the title');
    $product->addChild('product-type','XHTML &amp; CSS');
    $variants = $product->addChild('variants');
    $variants->addAttribute('type','array');
    $variant = $variants->addChild('variant');
    $variant->addChild('price','3.00');
    $variant->addChild('option1','Single-Use');
    $variant = $variants->addChild('variant');
    $variant->addChild('price','333.00');
    $variant->addChild('option1','Buyout');
    $product->addChild('vendor','JLH');

    echo $product;

    $session = curl_init(); 
    curl_setopt($session, CURLOPT_URL, $url);
    curl_setopt($session, CURLOPT_POST, 1); 
    curl_setopt($session, CURLOPT_POSTFIELDS, $product); 
    curl_setopt($session, CURLOPT_HEADER, false);
    curl_setopt($session, CURLOPT_HTTPHEADER, array('Accept: application/xml', 'Content-Type: application/xml'));
    curl_setopt($session, CURLOPT_RETURNTRANSFER, true);

    if(ereg("^(https)",$url)) curl_setopt($session,CURLOPT_SSL_VERIFYPEER,false);

    $result = curl_exec($session); 

    if ( curl_errno($session) ) {
        $result = 'cURL ERROR -> ' . curl_errno($session) . ': ' . curl_error($session);
    } else {
        $returnCode = (int)curl_getinfo($session, CURLINFO_HTTP_CODE);
        switch($returnCode){
            case 200:
                break;
            default:
                $result = 'HTTP ERROR -> ' . $returnCode;
                break;
        }
    }

    curl_close($session);

    echo $result;   

?>
BBG Shopify Partner www.shopifyconcierge.com
Posts:
864
April 26, 2009

I’m not a PHP guy, but I’ve noticed the apache error log generally gives you good hints when you get a 500. What does it say?

www.shopifyconcierge.com ♥ www.searchifyapp.com ♥ www.bookthatapp.com ♥ www.shopifyassistant.com
thcom Member
Posts:
25
April 26, 2009

I changed the end of my script a little and was able to glean a little more information about the result. here’s what I’m getting back:

Temporary problem
500
We’re experiencing a technical problem and we are doing everything we can to resolve it as soon as possible.

We at localhost apologize for any inconvenience this may have caused.

thcom Member
Posts:
25
April 26, 2009

There are no curl errors coming back. perhaps an issue on shopify’s end?

thcom Member
Posts:
25
April 26, 2009

FYI, the text in bold two replies above is is the response returned by shopify to my curl request.

thcom Member
Posts:
25
April 27, 2009

I am continuing this issue with the Shopify folks via the Support ticket system. I will post the resolution here as well.

Posts:
2055
April 27, 2009

yea, this error means that Shopify is at a loss at what to do with the request.

Does curl have a debug mode? Can you dump the wire traffic that’s being sent? In any case i replied to the support ticket. I think the problem is that you sent the POST payload as POSTFIELDS instead of RAW_POST which CURL turns into a urlencode form.

Tobias Lütke - Shopify CEO // http://twitter.com/tobi
Posts:
2055
April 27, 2009

By the way, i just saw this project: http://code.google.com/p/phpactiveresource/

You should be able to use this with shopify.

From the source it seems like i was wrong about POSTFIELDS, it seems to be the correct header. See here:

http://code.google.com/p/phpactiveresource/source/browse/trunk/ActiveResource.php#318

Tobias Lütke - Shopify CEO // http://twitter.com/tobi
thcom Member
Posts:
25
Last edited April 27, 2009

RESOLUTION

It turns out there was a problem with the formatting of my xml request.

php’s addchild() function sticks everything on one line. there are not line breaks where you’d expect them in a typical xml file.

I manually formatted my xml, and everything works!!

attached is the final code, which actually inserts products into my store.

<?php

    //Modify these
      $API_KEY = 'xxx';
     $SECRET = 'yyy';
      $TOKEN = 'zzz';
      $STORE_URL = 'sss.myshopify.com';

        $url = 'https://' . $API_KEY . ':' . md5($SECRET . $TOKEN) . '@' . $STORE_URL . '/admin/products.xml';

$xmlsrc = <<<XML
<?xml version='1.0' encoding='UTF-8'?>
<product>
    <title>This is the title.</title>
    <body>This is the description.</body>
    <product-type>Photoshop</product-type>
    <variants type="array">
        <variant>
            <price>3.00</price>
            <option1>Single-Use</option1>
        </variant>
        <variant>
            <price>5.00</price>
            <option1>Buyout</option1>
        </variant>
    </variants>
    <vendor>JLH</vendor>
</product>
XML;

        $session = curl_init(); 
        curl_setopt($session, CURLOPT_URL, $url);
    curl_setopt($session, CURLOPT_POST, 1); 
    curl_setopt($session, CURLOPT_POSTFIELDS, $xmlsrc); 
    curl_setopt($session, CURLOPT_HEADER, false);
    curl_setopt($session, CURLOPT_HTTPHEADER, array('Accept: application/xml', 'Content-Type: application/xml'));
    curl_setopt($session, CURLOPT_RETURNTRANSFER, true);

    if(ereg("^(https)",$url)) curl_setopt($session,CURLOPT_SSL_VERIFYPEER,false);

    $result = curl_exec($session);

        curl_close($session);

        echo $result; // this shows exactly what you'd expect to see, as given in API documentation  

?>

thcom Member
Posts:
25
Last edited April 27, 2009

tobi,

sweet library. thanks for sharing. i’ll think i’ll just use it instead. :)

BBG Shopify Partner www.shopifyconcierge.com
Posts:
864
April 27, 2009

@thcom – thanks for posting the resolution. this is very helpful.

www.shopifyconcierge.com ♥ www.searchifyapp.com ♥ www.bookthatapp.com ♥ www.shopifyassistant.com
Posts:
2055
April 27, 2009

Awesome.

I’d love it if someone could port the standard Shopify API file to the PHP library:

http://github.com/Shopify/shopify_app/blob/6469015a7fa9f781b8f9b2d8b9b1fa000144fbdd/lib/shopify_api.rb

We would definitely sponsor such an open source project.

Tobias Lütke - Shopify CEO // http://twitter.com/tobi
thcom Member
Posts:
25
April 27, 2009

sounds of feet scattering and tires squeeling ;)

thcom Member
Posts:
25
April 28, 2009

@tobi

FYI, ActiveResource requires a lot of hacks to make it work with Shopify.

1) Its _build_xml method creates an xml string similar to what you’d get using php’s simplexml. not good enough for shopify.

2) The ActiveResource class does not support xml attributes like ‘type=”array”’ which is needed for <variants type="array"> when creating a new product for example.

If I get it working satisfactorily, I’ll post a hacked version of ActiveResource.php for Shopify.

thcom Member
Posts:
25
April 28, 2009

R.I.P. ActiveResource.php

Just not worth it. You’d have to totally rewrite the _build_xml method.

It’s currently setup as a recursive method to run through something like this:

array('product' => array(
    'body' => 'this is a description', 
    'title' => 'this is a new title', 
    'product-type' =>'Photoshop',
    'variants' => array(
        'variant' => array('price' => '3.00', 'option1' => 'single-use'),
        'variant' => array('price' => '367.00', 'option1' => 'buyout')
    ), 
    'vendor'=>'jlh'
    )
)

The problem with _build_xml is that it’s based on php’s foreach() function which will ignore the 2nd ‘variant.’ Any time you have an array with multiple elements with the same key, in this case ‘variant,’ all but the last are ignored.

So, it appears ActiveResource is a bad option for Shopify users. There’s not that much code in there anyway…

Far less work to build your own XML strings and use cURL directly.

Posts:
5
April 28, 2009

Hey,

I’m the guy who wrote the ActiveResource.php class. The reason for the class is I put it together originally for the slicehost.com API which doesn’t send the data in XML but rather in a standard POST body format. I wrote it by setting up a basic set of objects in Rails as an “ActiveResource” and made it work with those, then with slicehost too. I haven’t used Rails much, but I just followed their spec as best I could (it’s a bit inconsistent here and there, but overall not too bad). I didn’t realize that it could be switched to use XML until about a week or two ago when I tried to reuse it with the messagepub.com API which is ActiveResource-based but uses XML instead. I made the changes pretty quickly but got it working with their service at least.

thcom, you’re correct that ActiveResource.php doesn’t support XML attributes. But the latest code in the github repository (not sure the packaged download has it yet though) does support multiple elements like you describe in the _build_xml() method. Here’s how you can do it:

array ('product' => array (
    'body' => 'this is a description',
    'title' => this is a new title',
    'product-type' => 'Photoshop',
    'variants' => array (
        'variant' => array (
            array ('price' => '3.00', 'option1' => 'single-use'),
            array ('price' => '367.00', 'option1' => 'buyout')
        ),
    'vendor' => 'jlh'
    )
)

_build_xml() will see that ‘variant’ is a numeric array and not associative, and it will create the proper XML for it:

...
<variants>
    <variant>
        <price>3.00</price>
        <option1>single-use</option1>
    </variant>
    <variant>
        <price>367.00</price>
        <option1>buyout</option1>
    </variant>
</variants>
...

That’s something that could be added to the wiki for sure.

So it looks like the big thing still needed is attribute support for the <variants type="array">. That would probably require a bit of rewriting, but _build_xml() is only 17 lines of code so it’s not much to rework… If you have any suggestions as to how you’d like to see those parameters passed to mark them as attribute vs tag please let me know.

I really would like to make the class usable on any ActiveResource-based API, so that I know I can just include and go when I need to write new API clients myself, and that could also save other people some time as well.

Anyway, it’s still beta code and I’m totally open to suggestions so let me know what you think :)

Cheers!

Lux

Posts:
5
April 28, 2009

I just registered for a 30-day trial and used your curl-based code to do a quick test. I was able to post a product with or without the type=”array” attribute to the variants tag. So I’m going to try to get the same thing working with ActiveResource.php using the array structure I mentioned and see what happens. Wish me luck! ;)

Lux

Posts:
5
April 28, 2009

Partial success! Here’s my working code (api info removed), with no actual changes to ActiveResource.php so far:

<?php

include_once ('ActiveResource.php');

define ('SHOPIFY_URL', 'https://APIKEY:' . md5 ('SECRET' . 'TOKEN') . '@SHOPNAME.myshopify.com/admin/');

class Product extends ActiveResource {
    var $site = SHOPIFY_URL;
    var $request_format = 'xml';
}

$product = new Product (array (
    'title' => 'test 2',
    'body' => 'description',
    'product-type' => 'Photoshop',
    'variants' => array (
        'variant' => array (
            array ('price' => '3.00', 'option1' => 'Single-Use'),
            array ('price' => '5.00', 'option1' => 'Buyout'),
        ),
    ),
    'vendor' => 'test',
));
$product->save ();

header ('Content-Type: text/plain');
echo htmlentities ($product->response_body);

?>

The one thing I did notice is that I spoke too soon about the type=”array” not mattering. With it missing, it causes the API to ignore the variants and not insert them correctly. So the above does add a product, but we’ll need to modify _build_xml() after all to add attribute support. Otherwise, that appears to be the only change needed to get it working which is a plus!

Lux

Posts:
5
April 28, 2009

Okay, I just checked in support for attributes and made a new download copy for it as well. I’ve added docs on attributes and repeating elements to the wiki to avoid confusion as well:

http://wiki.github.com/lux/phpactiveresource/messagepub-and-xml-request-bodies

So all you need to change in my last code is the ‘variants’ array and you should be all set:

...
    'variants' => array (
        '@type' => 'array',
        'variant' => array (
            array ('price' => '3.00', 'option1' => 'Single-Use'),
            array ('price' => '5.00', 'option1' => 'Buyout'),
        ),
    ),
...

Hope that helps. In theory, you should be able to create a PHP version of the rest of the Ruby library with this now, although we may find additional tweaks necessary along the way…

Cheers!

Lux

Posts:
159
April 28, 2009

@Lux Awesome job, thanks for taking the time to do this. Should come in really handy.

John --- http://experts.shopify.com/patternhead --- www.rawsterne.co.uk --- http://twitter.com/patternhead --- A few Shopify sites that I've worked on... http://oreedesign.com/ ::: www.carstache.com ::: www.waltzingmousestamps.com ::: http://shoprustyknuckles.com ::: www.papermash.co.uk ::: http://shop.mulberryroad.com
BBG Shopify Partner www.shopifyconcierge.com
Posts:
864
April 28, 2009

wow, @lux, you work fast my friend. well done.

www.shopifyconcierge.com ♥ www.searchifyapp.com ♥ www.bookthatapp.com ♥ www.shopifyassistant.com
Posts:
2055
April 28, 2009

That’s really amazing. Thank you so much lux!

Tobias Lütke - Shopify CEO // http://twitter.com/tobi
thcom Member
Posts:
25
Last edited April 28, 2009

Lux, do you have some kind of beacon that alerts you whenever someone is talking bad about your software?

j/k guys! I invited him to take a look at our issue, and he kindly agreed to help out.

Thanks Lux!

Posts:
5634
Last edited April 28, 2009

@thcom,

Can you send Lux this link: http://github.com/blog/57-getting-paid-the-open-source-way

Ask him to activate ‘donations’ on his project. Some work done with Shopify is quite commercial indeed, i.e. involves a lot of money, and I’m sure that developers who will make use of the library will happily tip Lux, or have their client tip Lux.

Caroline from http://11heavens.com ∴ mllegeorgesand AT gmail DOT com
thcom Member
Posts:
25
April 28, 2009

@lux

I downloaded the latest version, and it worked as is. Interestingly, the xml is not perfectly formatted, which seemed to be the problem with SimpleXML’s addChild() function. There’s some threshold in the middle of being perfect and having everything on one line.

Nice to have a working library, thanks again.

Posts:
5
April 29, 2009

Yeah, I just formatted the XML so I could scan to make sure it’s valid, but I didn’t bother with indentation. Glad it’s working for you though! I seem to be running into Rails-based APIs more and more these days, so fixing it now should save me some time soon enough I’m sure :)

And thanks Caroline, I’ve added the donations link on the github page. I hadn’t seen that option before…

Cheers!

Lux

thcom Member
Posts:
25
Last edited May 18, 2009

All,

Lux was very helpful and made a couple more changes to the library, which were needed for the PUT (update) functionality. I think he’ll be committing a new version within a short time of this post.

I tested with getting/creating/updating/deleting products, variants, and images.

Posts:
1
Last edited February 24, 2010

FYI, If you are just making a private application for one of your stores, you do not need a Token, just the API Key and Password from http://yourstore.myshopify.com/admin/api

As a bonus for all, I started a PHP class for accessing a shopify store (compatible with PHP4 and PHP5):

 

<?php

  class Shopify {
      var $_storeURL = null;
      var $_apiKey = null;
      var $_secret = null;
      
      var $xml = null;
      
      function Shopify( $storeURL, $apiKey, $secret ) {
          $this->__construct( $storeURL, $apiKey, $secret );
      }
      
      function __construct( $storeURL, $apiKey, $secret ) {
          $this->_storeURL = $storeURL;
          $this->_apiKey = $apiKey;
          $this->_secret = $secret;
      }
      
      /**
       * Retrieve an array of orders.
       *
       * @param array Associative array of filters
       * @returns        An array of order data
       */
      function getOrders( $filters = array() ) {
          $filter[] = "";
          foreach ( $filters as $key=>$value ) {
              $filter[] = "{$key}=".urlencode($value);
          }
          $url = "https://" . "{$this->_apiKey}:{$this->_secret}@{$this->_storeURL}" .
                      "/admin/orders.xml".(count($filter)?'?'.join('&',$filter):'');
          
          $session = curl_init();

          curl_setopt($session, CURLOPT_URL, $url);
          curl_setopt($session, CURLOPT_HTTPGET, 1);
          curl_setopt($session, CURLOPT_HEADER, false);
          curl_setopt($session, CURLOPT_HTTPHEADER, 
                 array('Accept: application/xml', 'Content-Type: application/xml'));
          curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
        
          if(ereg("^(https)",$url)) curl_setopt($session,CURLOPT_SSL_VERIFYPEER,false);
        
          $this->xml = curl_exec($session);
          curl_close($session);
                    
          $xml = $this->_getDOM($this->xml);
          $result = $this->_xpath($xml,"/orders/order");
          
          $orders = array();
          if( isset($result->nodeset) ) {
              foreach ( $result->nodeset as $orderNode ) {
                  $orders[] = $this->_xmlToArray( $orderNode );
              }
          } else {
              foreach ( $result as $orderNode ) {
                  $orders[] = $this->_xmlToArray( $orderNode );
              }
          }
          return $orders;
      }
      
      function _getDOM( $xmlDoc ) {
          if( function_exists("domxml_open_mem") )
              return domxml_open_mem( $xmlDoc );
          else if ( method_exists("DOMDocument","loadXML") )
              return DOMDocument::loadXML( $xmlDoc );
          else
              return null;
      }
      
      function _xpath( $dom, $query ) {
          if ( method_exists( $dom, "xpath_new_context" ) ) {
              $xpath = $dom->xpath_new_context();
              return $xpath->xpath_eval("/orders/order");
          } else if ( class_exists( "DOMXPath" ) ) {
              $xpath = new DOMXPath( $dom );
              return $xpath->query( $query );
          } else {
              return null;
          }
      }
      
      function _xmlToArray( $xmlNode ) {
          $result = array();
          if ( method_exists($xmlNode,"child_nodes") ) {
              foreach ( $xmlNode->child_nodes() as $childNode ) {
                  if ( $childNode->node_name() == '#text' )
                      ;//Do nothing
                  else if ( count($childNode->child_nodes())<2 )
                      $result[$childNode->node_name()] = $childNode->get_content();
                  else 
                      $xmlNode->get_attribute('type') == "array" ? 
                          $result[] = $this->_xmlToArray( $childNode ) :
                          $result[$childNode->node_name()] = $this->_xmlToArray( $childNode );
              }
          } else if ( isset($xmlNode->nodeName) ) {
              foreach ( $xmlNode->childNodes as $childNode ) {
                  if ( $childNode->nodeName == '#text' )
                      ;//Do nothing
                  else if ( $childNode->childNodes->length<2 )
                      $result[$childNode->nodeName] = $childNode->nodeValue;
                  else 
                      $xmlNode->getAttribute('type') == "array" ? 
                          $result[] = $this->_xmlToArray( $childNode ) :
                          $result[$childNode->nodeName] = $this->_xmlToArray( $childNode );
              }
          }
          return $result;
      }
  }
?>

 

 

frank | web: http://www.globalthinking.com | twitter: http://twitter.com/strube
Posts:
1
November 02, 2010

@fstrube - You should create a Github project for that class. I am using this on my next project and I think it would be great to have a centralized place to improve upon and update it.

Jamie Chief Officer of Funness www.evolvedesign.co
Posts:
5840
November 02, 2010
http://evolvedesign.co ::: http://twitter.com/bacchus

Log in or sign up for an account to reply.

This thread has been closed! You will not be able to reply.