Categories


Archives


Recent Posts


Categories


Scrubbing Bubbles for Magento’s SOAP

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

People often talk about Magento having a “SOAP” API. SOAP is a series of protocols that, from one point of view, is designed to allow programmers to call a function on one computer, and have it executed on another computer. XML-RPC is a simpler, earlier protocol that does similar things, and people talk about Magento having an XML-RPC API as well. These people, although well intentioned, aren’t telling the whole truth.

There’s no such thing as The Magento SOAP API, or The Magento XML-RPC API.

What Magento does have is The Core API. The Magento core teams loves abstractions. What they’ve done with the Magento Core API is define their own, abstract programming language. This language has resources. Each resource has a callable method which will perform some common task in Magento. This programming language is abstract because it has no native implementation.

There is no way to write a program that uses the Magento API. Instead, the core team has written “adapters” (also known as “connectors”) that allow you to access the API via known protocols. There’s an adapter for SOAP. There’s an adapter for XML-RPC. Once you know your way around the API, it would be possible to build a RESTful adapter, or an adapter for the janky unstructured XML based API you’ve cobbled together internally. (Apologies to people unable to see the difference between the last two).

This is a small quibble. At the end result end-user-programmers have the ability to do things with Magento via SOAP or XML-RPC. However, keeping this point of view in mind will help you understand the API implementation, and the techniques we’ll be exploring today.

Cost of Abstraction

This abstraction allowed the core team to build out a single API implementation in native PHP code, and then write single adapters for SOAP and XML-RPC. This guaranteed the current APIs, as well as any future APIs, would have identical methods. That is, there’s no easy way to create a method for the SOAP API that doesn’t exist for the XML-RPC API. This is a smart move for a startup that can’t anticipate how their system will be used out in the wild.

Like all things programming, this technique is not without its tradeoffs. The one we’re interested in today is performance. Magento has inserted two systems between the end-user-programmer and the non-API related PHP Magento code that does the work for the API methods. No matter how many optimizations happen, a call made through the API will always be slower than a call made via native PHP code.

What we’ll be looking at today is a technique for skipping the API adapter layer and making calls directly to the native PHP API models. This brings huge performance boosts, while still using the correct business logic encapsulated in the API methods.

The Business of Business Logic

If you’ve got a little Magento experience under your belt, you may be thinking

Why bother? If we’re writing native PHP code why not just use the Magento objects? Why bother with the API at all?

Although it’s often the only way to do what you want in Magento, the key disadvantage to using the native PHP objects in Magento is the lack of consistant rules for enforcing Magento’s business logic, particularly between versions of Magento.

For example, if you’re using the native sales/order object hierarchy to fetch order information in Magento, it’s up to you, as the end-programmer-user, to load the correct order support information (address, gift messages, etc.). Sometimes the native objects will have methods to do this, sometimes they won’t. This problem multiplies when it comes to saving object information into the database. When using the native PHP objects it’s possible to put Magento into a state not anticipated by the core team, or by their tests.

On the other hand, the API methods have been explicitly designed for use by outsiders. While they’re only a subset of Magento’s entire functionality, they’re a stable subset. Things that are tribal knowledge to a developer using the native PHP Magento objects are hard-coded into the API methods. For a company that’s as secretive about their development process as Magento (see also: Google, Apple, etc.), the API methods are the closest we’ll come to The Right™ way to do something.

As always, there’s no hard and fast rule when (if ever) you should use the techniques provided below over native PHP code, or the same calls made through the SOAP adapters. This is just another tool in the arsenal of a Magento developer. It’s up to you to decide the best course of action for your code and your company.

Anatomy of a Magento API Call

Every call to the Magento API is made up of a resource and a method. Think of API resources like a class, and API methods like methods on those classes.

For example, to create a customer using the API, you use the customer resource, and the create method. Each of these resource/method pairs resolves to a model/method pair on a special Magento API resource model object. These resource model objects are separate from the Magento CRUD resource models. You can use the Developer Manual extension to quickly jump to these method definitions. That said, it’s useful to know how these methods resolve. Below we’ll explore how the API resource

customer.create

is resolved to the native PHP object call

$results = Mage::getModel('customer/customer_api')->create(...);

Warning: We’re glossing over some important details here. While this is an adequate guide for tracking down where a specific API call resolves to, it’s not a complete guide for setting up a new API resource and method of your own.

Magento’s api.xml Files

In order to expose an API resource and method(s) in Magento, a module defines an api.xml configuration file.

$ find app/code/core -name 'api.xml'
app/code/core/Mage/Api/etc/api.xml
app/code/core/Mage/Catalog/etc/api.xml
app/code/core/Mage/CatalogInventory/etc/api.xml
app/code/core/Mage/Checkout/etc/api.xml
app/code/core/Mage/Core/etc/api.xml
app/code/core/Mage/Customer/etc/api.xml
app/code/core/Mage/Directory/etc/api.xml
app/code/core/Mage/Downloadable/etc/api.xml
app/code/core/Mage/GiftMessage/etc/api.xml
app/code/core/Mage/Sales/etc/api.xml
app/code/core/Mage/Tag/etc/api.xml

These files can be used to look up the Magento model that’s used to implement an API call. In each file, the nodes at

<config>
    <api>
        <resources>
            <...>
        </resources>
    </api>
</config>

represent an API resource. In our example above, the node for the customer resource can be found at

<!-- File: app/code/core/Mage/Customer/etc/api.xml -->
<config>
    <api>
        <resources>
            <customer translate="title" module="customer">
                <!-- ... -->
            </customer>
        </resources>
    </api>
</config>

In general, an API resource name (customer) usually indicates which Magento module its api.xml file will be located in (Mage_Customer), but this is only a convention and shouldn’t be trusted as a canonical way of finding a resource’s api.xml node.

API Resource, Meet Magento Model

Within each resource node, you’ll find a <model> node.

<!-- File: app/code/core/Mage/Customer/etc/api.xml -->
<customer translate="title" module="customer">
    <model>customer/customer_api</model>
    <!-- ... -->
</customer>

This model node contains a class alias for the Magento model that this particular resource uses. So, this tells us a Magento Core API customer resource is implemented with a Mage::getModel('customer/customer_api'); model. We’re half way there!

Before we move on to deriving a method name, there’s a gotcha to be careful of. If you were using the earliest version of the Magento Core API, you may have made an API call like

category.info

However, searching through the api.xml resource nodes yields no <category/> node. What gives?

In addition to the <resource/> node, each api.xml file may also contain a <resource_alias/> node.

<config>
    <api>
        <resources_alias>
                    <!-- ... -->
        </resource_alias>
    </api>
</config>    

Each sub-node here can be used to alias one resource to another. For example, in the Mage_Catalog module’s api.xml file, we can see there’s a resource alias for the category node

<!-- File: app/code/core/Mage/Catalog/etc/api.xml -->
<config>
    <api>
        <resources_alias>
                    <category>catalog_category</category>
                    <!-- ... -->
        </resource_alias>
    </api>
</config>    

The above configuration means the API call to

category.info

is equivalent to the API call to

catalog_category.info

meaning if we consult the api.xml file again

<!-- File: app/code/core/Mage/Catalog/etc/api.xml -->
<config>
    <api>
        <resources>
            <catalog_category translate="title" module="catalog">
                <model>catalog/category_api</model>
            </catalog>
        </resources>
    </api>
</config>

we’ll know that both API calls resolves to the following native PHP Magento model.

$api_resource_model = Mage::getModel('catalog/category_api');

Finding the Method

Now that we know which Magento model our API resource model resolves to, our next step is finding which PHP method our API method resolves to.

Looking again at our api.xml file, we can see that each distinct resource node (<customer>, <catalog_category>, etc.) has a <methods/> sub-node.

<!-- File: app/code/core/Mage/Customer/etc/api.xml -->
<config>
    <api>
        <resources>
            <customer>                    
                <methods>
                    <!-- ... -->
                </method>
            </customer>
        </resource>
    </api>
</config>

Each sub-node of methods will expose a method of the same name on the resource model object. For example, the customer.info‘s info method is exposed by the following configuration

<!-- File: app/code/core/Mage/Customer/etc/api.xml -->
<customer>                    
    <methods>
      <info><!-- ... --></info>
    </method>
</customer>

With this node in place, the API adapter will pass through the method name to the resource model object.

That is to say, if we consider our API call to

customer.create    

the API adapter will

  1. Translate the customer resource to the customer/customer_api model

  2. Instantiate a customer/customer_api model

  3. Take the API method, create, and look for a corresponding node under the <methods/> node

  4. If it find the node, call the create method of the just instantiated customer/customer_api model, and then serialize the results to return via the API

This means if you’d like to skip the overhead and authentication of the SOAP or XML-RPC APIs but still get the benefits of the API’s business logic, you can replicate the call yourself by using the following PHP code (in a Magento bootstrapped environment, of course)

$model = Mage::getModel('customer/customer_api');
$results = $model->create($array_of_customer_data);

Before we wrap up there’s one last “gotcha” regarding method names. Many Magento API resources contain a method named list. However, if you were to execute the following PHP code

$model = Mage::getModel('customer/customer_api');
$results = $model->list();

you’d get a PHP error. That’s because list is a reserved PHP keyword. You can’t use list as a constant, class name, function, or method name.

So how can Magento use list as an API method name? Let’s take a look at the api.xml configuration of a list method.

<!-- File: app/code/core/Mage/Customer/etc/api.xml -->
<methods>
    <list translate="title" module="customer">
        <title>Retrieve customers</title>
        <method>items</method>
        <acl>customer/info</acl>
    </list>
</methods>

As you can see above, the api.xml configuration supports a sub-node of an individual <methods/> sub-node named <method/>. (our apologies for that accurate, yet absysmal sentence).

So while the configuration exposes the customer.list API method, the actual PHP method called is the configured items method, making the PHP you should use

$model = Mage::getModel('customer/customer_api');
$results = $model->items($filters);

This syntax is not just reserved for the list method, so always make sure you check your XML for the optional <method/> node.

Wrap Up

It’s not always easy being a Magento developer. The ecommerce problem domain is sprawling and there’s few companies who’ve chosen to fully tackle and understand its complexities and share those results with the world. If you can push your way past Magento’s complicated abstractions you’ll find a wealth of proven programming models for ecommerce. The Magento Core API represents the safest, stablest way to interact with your Magento system. While it won’t solve every problem, by using it wherever possible you’ll have fewer problems due to working, but incomplete, custom code.

Originally published March 19, 2012
Series NavigationThe Magento API >>