Magento: solving ‘invalid API path’ errors

I’ve noticed a bug with Magento that affects the code I wrote for the ‘Extending the API’ tutorial so I thought I’d post a fix for it here.  Well, I assume it’s a bug since I don’t see how it could be anything else…

Here’s the article in question

Here’s the api.xml file from that tutorial:

<config>
    <api>
        <resources>
            <modulename_category translate="title" module="modulename">
                <model>namespace_modulename_model_category_api</model>
                <title>New Category API</title>
                <methods>
                	<getID translate="title" module="modulename">
                		<title>Retrieve Category ID from its name</title>
                		<acl>catalog/category</acl>
                	</getID>
                </methods>
            </modulename_category>
            </resources>
        	<v2>
            	<resources_function_prefix>
                	<modulename_category>catalogCategory</soapcatalog_category>
            	</resources_function_prefix>
        	</v2>
    </api>
</config>

You’ll notice that I declare a ‘resources_function_prefix’ of ‘catalogCategory’ to fit in with the existing Magento API.  this means that my new method called ‘getID’ can be accessed via a soap call to ‘catalogCategoryGetID’.  This all seems straightforward… or so I thought.

It appears that during execution of soap calls, that resource prefix is mapped directly to a magento module.  Hence, if you have a prefix of ‘catalogCategory’ defined in your module any methods with that prefix will be mapped to your module..including the methods defined in the Magento class we’re overriding with our module.  So for all the standard Magento API calls you’ll receive a nice ‘invalid APi path’ error.

The workaround for this is pretty obvious, we need to declare our new methods with a different function prefix and then update the wsi.xml (the wsdl file) accordingly.

This of course means you’ll be accessing your methods with a different prefix than the standard magento one (so something like myModuleCatalogCategoryGetID rather than catalogCategoryGetID) but that’s a small price to pay for it actually, you know, working.

 

Categories: magento, PHP | Leave a comment

FUSE ESB Development: the build environment

The first thing we need to do when starting to work with fuse ESB is set up our build environment.  For this we’ll be using Eclipse, with the FUSE IDE plugin and maven. I’ll also cover downloading and installing Servicemix, and then generating our first FUSE project.

I’ll be using a windows box for the majority of this tutorial, but it should be fairly straightforward to get this working on your favoured flavour of *nix if you’re so inclined.

There’s a lot to get through here so lets get started.

Maven.

Download the latest version of maven from here: http://maven.apache.org/download.html

Extract the downloaded zip somewhere sensible (I use c:\program files\maven)

To make sure we can use maven from the command line we need to add it to our ‘Path’ environment variable.  On windows 7  you can do this by right clicking ‘Computer’, selecting ‘advanced system properties’ then click the ‘environment variables’ button.  Once the box opens scroll down until you find ‘Path’ under system variables, click edit and add ‘c:\program files\maven\bin’ to your string.

That’s maven setup, lets move onto something a little more challenging.

JDK installation

Before we can install our FUSE products we need to grab the Java Development Kit (JDK) and ensure that Eclipse and servicemix know where to find it by setting our ‘JAVA_HOME’ environment variable.

grab the JDK from here: http://www.oracle.com/technetwork/java/javase/downloads/jdk-6u25-download-346242.html and run the installer.

Once it’s installed you need to go back into the ‘Environment variables’ box we were in during the Maven installation and add a new variable called ‘JAVA_HOME’ pointing at the Directory your just installed the JDK into (by default it should be something like ‘C:\Program Files\Java\jdk1.6.0_25′).

Eclipse

Download Eclipse Helios from here: http://www.eclipse.org/downloads/packages/eclipse-ide-java-developers/heliossr2

Personally I’ve had no problems using the later ‘Indigo’ version of eclipse, but FUSE recommend Helios for working with their IDE so we’ll stick with that.  Run the installer as usual.  Once it’s installed we need to get the FUSE IDE plugin.

Go here: http://fusesource.com/products/fuse-ide/ to download it.  You need to register as part of the ‘Fuse community’, then add the update site listed on that page to your eclipse installation to add the IDE plugin.

Once that’s done the final part of the Eclipse installation is to make Eclipse use our previously installed maven repository rather than the one that comes bundled with Eclipse.  In the Eclipse main menu open ‘Window’->’Preferences’.  Then select ‘Maven’->’Installations’.  Click ‘Add’ and browse to your maven directory (in my case it’s ‘c:\program files\maven’).  Click OK and you’re done.

ServiceMix

For our ServiceMix installation I’m going to use the packages provided by FuseSource as ‘Fuse ESB#.  You can get the latest version from here: http://fusesource.com/products/enterprise-servicemix/.

Download the latest Zip file and extract it somewhere obvious (c:\servicemix for example).

And…that’s pretty much it, you should now be able to start Servicemix from the command line by changing to the servicemix directory and running the command ‘bin\servicemix.bat‘.  If everything goes well you should see something like this:

Generating our first project

Now everything’s setup lets try generating and deploying a simple test project.

To generate our project structure we can use the maven archetype generator as follows:

First create a new, empty directory where we’ll be working (I’m using c:\testproject)

Next run the command ‘mvn archetype:generate‘. this will prompt maven to download the latest list of archetypes and display them as a huge list.  Luckily we can filter this list if we know the name of the archetype we want. Type ‘karaf-blueprint-archetype‘ to return our filtered list, which should have 1 entry.  press 1 to select this entry and then from the list of versions that follows select the latest one (at time of writing this is 2.2.7).

You’ll now be prompted for some information about the project, use the following (or replace with your own values as required):

Once you’ve entered the details a simple ‘Y’ will confirm them and generate our project.

Import project into eclipse

The next stage is to import our project into Eclipse.  In the Eclipse main menu select ‘File’->’Import’, then from the import box scroll down and select ‘Maven’->’Existing Maven Projects’.

In the Import window browse to the location of your test project (notice maven has generated it in a subdirectory of our root directory) as shown:

Click ‘Finish’ to import the project.  You may get a ‘NullPointerException’ here for some reason, but the project should still import.

Configure a ‘Hello World’ Route

Now let’s make our project actually do something.

If you open up your project structure, you should see something like this:

There’s a few files and folders here to take note of, and we’ll go into these in more detail in further installments of this series, but for now let’s just jump in and start hacking around.

First of all we can remove the sample java beans that Maven gives us, so delete the two files under your java class path.  In my case this is ‘test-project\src\main\java\uk\co\tall-paul’.

We can also get rid of the sample camel context (in the file ‘my-service.xml’) and replace it with our own.  So remove that file and create a new one in the same place called ‘hello-world.xml’ containing the following:

<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:camel="http://camel.apache.org/schema/blueprint"
    xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
    xmlns:cxf="http://camel.apache.org/schema/blueprint/cxf"
    xsi:schemaLocation="
       http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
       http://camel.apache.org/schema/blueprint http://camel.apache.org/schema/spring/camel-blueprint.xsd">       

	<camelContext id="hello-world-context" xmlns="http://camel.apache.org/schema/blueprint">

	<route id="hello-world-route">
		<from uri="timer:test?period=5000"/>
		<log message="hello world!"/>
		<to uri="mock:result"/>
	</route>

	</camelContext>
</blueprint>

For more info on what that xml actually does have a look at the camel website, and specifically the information abou the various components which you can find here: http://camel.apache.org/components.html

This simple route basically uses a timer to fire off a message every 5 seconds, then logs ‘hello world’ to the console.

Build the Project

To build the project we can run maven from the command line or from within eclipse.

From the command line, navigate to the project folder (c:\testproject\test-project) and run the command ‘mvn clean install‘ (you can find more information about various maven commands here: http://maven.apache.org/maven-1.x/reference/command-line.html , but really ‘mvn clean install’ is all you’ll need for now).  If all goes well you should (eventually) see something like the following:

From Eclipse you can do pretty much the same thing by right clicking on your project and selecting ‘Run as’->’Maven install’ as shown here:

Our project has now been compiled and installed into our maven repository, which you can see if you browse to the location of the repository (by default this is at c:\users\YOURUSERNAME\.m2\repository):

Deploy the project into servicemix

Thanks to the integration of maven into servicemix, deploying our project is trivial using the ‘install’ command withing servicemix.

From your servicemix consolerun the following command:

install mvn:\uk.co.tall-paul\test-project\1.0-SNAPSHOT

Hopefully you should be given a bundle ID, something like ‘bundle ID: 200′.  Now type the following command (substituting the bundle ID you were given for the 200 shown here)

start 200

And that should be it.  As long as you didn’t see any error messages, your bundle should now be running.  To see the output from our route type the following commands (the first clears the log file for clarity, the second displays the current log)

log:clear
log:tail

after a few seconds you should see something like this:

You can see that every 5 seconds our timer is going off and logging our message to the console. You can also see here the name of the component the message originated from (remember we put ‘test’ as the first parameter for our timer component) and the route in which the message is running (here it’s ‘hello-world=route’), which indicates the importance of naming your routes and components sensibly.

Conclusion

Hopefully this brief tutorial has shown how easy it is to get up and running with Fuse ESB development.  Next time we’ll look at how to use the environment we’ve set up here to do some real integration work.

Categories: coding, FUSE ESB, techy stuff | Leave a comment

FUSE ESB Development Introduction

Today I’ve decided to move on from the magento stuff I’ve been posting recently, and start looking at working with FUSE ESB.  This is an enterprise service bus based on apaches servicemix platform.

There’s a lot of new stuff here, the majority of the code for servicemix is written in Java and plugged together using the Blueprint XML language and apache camel.  For the build environment we’ll be using apache maven, eclipse and subversion (for source control).  Over the coming days (weeks) I’ll describe how to set all this up from scratch and develop a web services API that will allow you to access data from a lot of different systems using one interface.  I’d never used Java or Maven before getting started on this project, so that should be a good indicatoof how easy this stuff is to pick up.

For more info on what fuse ESB is, and what it does you should go and read my post from a few weeks ago: Overview of FUSE ESB.  As a quick summary the idea of an ESB is to allow various systems to interoperate in a standard, maintainable way.  There’s a lot more to it than that of course, but that’s the basic idea.

As a starting point for this series of tutorials, lets have a look at what we’ll be covering (subject to change, I’ll update this list if I change things around)

1: Setting up the build environment

1a: downloads

1b: maven setup

1c: subversion setup

1d: eclipse setup

1e: servicemix / FUSE ESB setup

1f: creating our project structure

2: Defining our APIs

2a: creating a wsdl

2b: generating our API code

3c: generating code from a 3rd party wsdl (magento)

3: Implementing our API

3a: creating our camel route

3b: plugging it all together

4: Useful resources

Okay, that’s all for today, check back over the weekend when I’ll hopfully have written up the ‘setting up the build environment’ section.

Categories: FUSE ESB | Leave a comment

jNag for IOs binary

As promised last week I’ve uploaded the IOS binary for jNag, so you can deploy it to a local device.  I’ve no idea how you go about doing this, and no-one’s gifted me a free Macbook yet so I can’t test this in any way.  Hopefully this helps out if you want to get jNag on your iDevice now it’s been removed from the App store.

enjoy

Categories: jNag | Leave a comment

Magento: Block users using a group

One of the most glaring omissions of magento is a way of blocking users. Currently your only option when faced with a user you wish to ban from the site is to do some kind of IP blocking in your web server configuration, or delete the user account. Neither of those options is really useful, IP blocks are difficult to maintain and if you delete a user account there’s nothing to stop the miscreant just creating a new account.

Here’s how you can create a simple module that allows you to set up a ‘blocked’ group. When customers are placed in this group they can still log in, however they’re not able to add anything to their cart and thus can’t actually place an order.

First up we need to create our module folder structure. Here I’m using a namespace of ‘Tallpaul’ and a module name of ‘bangroup’, but you can change these according to your requirements (just make sure you change them throughout, or you’ll get missing classpaths and all sorts of fun).

 

 

 

 

 

 

You can see we only have a couple of files in here.  ‘Config.xml’ is a configuration file that defines how our module is going to behave, and’CartController.php’ is our code that’s going to stop blocked users from adding items to their cart.

Lets take a look at ‘config.xml’ first.

<config>
 <modules>
 <Tallpaul_Bangroup>
 <version>0.1.0</version>
 </Tallpaul_Bangroup>
 </modules>
 <frontend>
 <routers>
 <checkout>
 <args>
 <modules>
 <tallpaul_bangroup before="Mage_Checkout_CartController">Tallpaul_Bangroup_Frontend_Checkout</tallpaul_bangroup>
 </modules>
 </args>
 </checkout>
 </routers>
 </frontend>
</config>

After the usual configuration stuff of defining a version for our module, there’s only one line here that does anything.  We’re defining that our module (tallpaul_bangroup) is overriding a specific controller (Mage_Checkout_CartController) with a new controller (Tallpaul_Bangroup_Frontend_Checkout).  And that’s pretty much it.  You can see here that controller overrides are dfined in a different way to bloc / helper / etc overrides.  I’m not entirely sure why, but this is just the way magento does it.  It usually takes me a bit of trial and error to work out how to override a specific controller, but once you’ve done it a few times it becomes second nature to map these files to a classpath.

Anyway, now we’ve got our configuration out of the way, lets have a look at CartController.php.  In here we’re going to extend the magento checkout controller class, and override a couple of functions.

/**
 * Shopping cart controller
 */
require_once Mage::getModuleDir('controllers', 'Mage_Checkout').DS.'CartController.php';

class Tallpaul_Bangroup_Frontend_Checkout_CartController extends Mage_Checkout_CartController
{

  /**
   * Check if user is blocked (ie: if they're in the 'Blocked' group)
   */
  public function isBlocked(){
  		if(Mage::getSingleton('customer/session')->isLoggedIn())
		{
  			$groupId = Mage::getSingleton('customer/session')->getCustomerGroupId();
			$group = Mage::getModel('customer/group')->load($groupId);
			$groupName = $group->getCode();
			if ($groupName == "Blocked"){
				return true;
			}
		}
		return false; //user can't be blocked if they're not logged in
  }

  /**
   * Empty current user cart
   */
  public function emptyCart(){
  		$cartHelper = Mage::helper('checkout/cart');
     	$items = $cartHelper->getCart()->getItems();
        foreach ($items as $item) {
            	$itemId = $item->getItemId();
            	$cartHelper->getCart()->removeItem($itemId)->save();
        }
  }

   /**
     * Shopping cart display action
     */
   public function indexAction()
   {
   		if ($this->isBlocked()){
   			$this->emptyCart();
		}
		parent::indexAction();
   }

    /**
     * Add product to shopping cart action
     */
    public function addAction()
    {
			if ($this->isBlocked()){
				$this->emptyCart();
				$this->_goBack();
                return;
			}
		parent::addAction();

    }

}

 

The actions we’re overriding are ‘indexAction’ (which is used to display a user’s cart) and ‘addAction’ (called whenever an item is added to the cart.  The other 2 functions here ‘isBlocked’ and ‘emptyCart’ are simple helper functions I’ve defined to keep things tidy.

The basic logic here is that when a user adds an item to their cart we first check if they’re in a specific group (here I’ve used ‘Blocked’ as the name of the group, but you can call it something else as long as you update the code in ‘isBlocked’).  If they are we empty their cart, and then return.  The effect of this is to take the user to an empty cart with nothing added to it and no error message to indicate what the problem might be.

As a doublecheck against a user who already has items in their cart when they’re placed in the ‘Blocked’ group, we also add a check to the ‘indexAction’ to empty their cart when they view it.

At the end of our indexAction and addAction we use the nifty trick of calling the relevant method in the ‘parent’ class.  This means our code remains clean and simple, while allowing requests that we don’t want to block using our code to ‘fall through’ to the parent implementation and be dealt with by the standard Magento logic.

Now all that’s required is an ‘activation’ xml file in app/code/etc and we’re good to go.  Create a group called ‘Blocked’, add your rogue users to it and laugh as you imagine their confusion at being unable to add items to their basket.

You can download the code for this article here:

 

Categories: magento, Uncategorized | Leave a comment

jNag IOS local provisioning

I’ve had a request that I make the IOS version of jNag available so that people can deploy it using a local provisioning server. I’m still trying to find the last build I did before losing access to my Mac platform, but once I dig it out of my backups I’ll upload it here. You can then do what you will with it. I’ve not looked into local deployment of IOS apps myself, but apparently you can do it…

Categories: jNag | Leave a comment

jNag removed from Apple App Store

I’ve just been notified that jNag for iPhone / iPad has been removed from the App store due to my apple developer subscription expiring. Since I no longer have access to a Mac development environment, I didn’t see the point of paying to renew my subscription. Since there’s no fees to pay to keep apps in the Android market, jNag for android will continue to be available.

If anyone wants to donate a Macbook pro / air for me to restart development on I’d be more than happy to oblige. Hell, I’ll even cover the apple developer subscription myself :D

Categories: jNag | Leave a comment

Create a bootable USB Drive in windows

This is more of a memo for me than a post for the rest of you, but might be useful if you want to create a bootable USB disk from a windows box (for an example, I used it to create a windows 8 installation drive)

 

Information taken verbatim from this post on the microsoft answers site

1. Plug in a USB device that is 4GB or bigger.
2. Find the disk number of the USB device:
    Open up an elevated command shell, run diskpart.exe.
    Within diskpart, run 'list disk'. The output will list all disks on the computer.
    From looking at the size, infer which one is your USB disk and note down the
    disk number.
3. Correctly format the USB device:
    Run diskpart.exe and enter the below commands one by one.
        select disk <disknumber of your USB disk>
        clean
        create partition primary
        format fs=ntfs quick label=Win764
        active
        assign letter=Q
        exit
    Now you should see a "Q:" drive in Windows Explorer. This is your USB disk.
4. Mount the 64-bit Win7 ISO:
        Windows does not have a built-in ISO mount feature. So you will have
        to use a third-party tool. I suggest "Virtual CloneDrive". Once you have
        downloaded and installed Virtual Clonedrive, navigate to the folder that
        your ISO file is in, in Windows Explorer. Right-click on the ISO file and
        select "Mount". After this, all your ISO files should show up in a drive
        called "I:".
5. Populate the USB disk:
    Copy all files and folders from I: to Q:.
6. UnMount the ISO:
    In Explorer, navigate to the folder that has the ISO, right-click and select UnMount.
7. Boot from USB:
    Turn off the target computer, plug in the USB device, turn on the computer,
    get to the BIOS boot options (using F9 or some other key, depends on your computer),
    select USB boot, and you should see setup run.
Categories: Uncategorized | Leave a comment

Google Glasses: ‘Rainbows end’ is coming.

After a lot of rumours, and some imaginative concept art Google finally announced the latest idea from the Google-X labs yesterday:  Google Glasses. Touted as ‘terminator glasses’ as a callback to the onscreen display seen in the Terminator films (why terminator?  Robocop would be a much better example of this) these glasses are the first step down the road to google’s vision of a truly augmented reality. Here’s the video showcasing Google’s future palns for this technology:

Someone at google has obviously been reading Verner Vinge’s superlative ‘Rainbows end’ (yes, the missing apostrophe is intentional).  In it he describes a future in which reality augmenting contact lenses are as ubiquitous as the mobile ‘phone is today and everyone is used to constantly swimming in a sea of information that overlays and enhances the everyday reality we’re used to living in. The glasses that Google are currently working on are obviously a long way away from something you’d want to wear all the time, however researchers are already working on contact lenses with displays built in, I’d be suprised if we don’t see a reasonable (say SVGA resolution) contact lens display reaching commercial availability in the next 5 to 10 years.  That may seem optimistic, but if you look at where smartphone displays were 10 years ago and compare them to  today I think 10 years is being conservative if anything.

The video from google showcasing what Google Glasses will be able to do is only a taster of this new way of looking at the world, but this is one of those rare concepts that really does offer us a glimpse of the future and I for one can’t wait to see where google goes with it.

Categories: techy stuff, the real | Leave a comment

Magento: Extending the API (v2)

One of the nice features of Magento is the extensive SOAP api it provides for integrating magento with your own or third party applications.  However, when it comes to extending that API the documentation is pretty sparse.  I’ve spent the last few days struggling to get to grips with this and, after spending some time debugging in the Varien autoloader class I’ve finally got it working.

Before we start we need to define some terms.  This is where a lot of the magento tutorials you’ll find fall down, because they use class names like ‘customer_customer_api_v2′  and when you try to adapt this to your own module you never know which ‘customer’ to replace with your module name… Anyway, for the purposes of the tutorial I’ll be sticking to the following naming convention:

Namespace:  Magento allows us to keep all our modules in a ‘namespace’, to separate them out from modules produced by other companies.  For this tutorial I’ll be using the namespace ‘Namespace’.

Modulename: We’ll be using a module name of ‘Modulename’ for this tutorial.

We’re going to extend the ‘Catalog/Category’ Api and add the ability to query magento for a category ID using its name.  So we’re expecting to be able to pass in something like ‘Furniture’ and get back Magento’s internal category id for the ‘Furniture’ product category.

The first thing we need to do, as with any magento module is work out which class we’ll be overriding.  The catalog/category api class resides in:

/App/Code/Core/Mage/Catalog/Model/Category/Api/V2

From this we can work out that its classname is Mage_Catalog_Model_Category_Api_V2.  Extending an API class is much like extending any other type of class in Magento.  We create our folder structure (note our namespace and module names)

 

 

 

 

 

 

 

Once we have our folder structure we need to tell magento what our module is going to do, namely that it’s extending the magento class we identified earlier.  We do this with an xml file: ‘app\code\local\Namespace\Modulename\etc\config.xml’

<config>
    <modules>
        <Namespace_Modulename>
            <version>0.1.0</version>
        </Namespace_Modulename>
    </modules>
    <global>
        <models>
            <catalog>
                <rewrite>
                    <category_api_v2>Namespace_Modulename_Model_Category_Api_V2</category_api_v2>
                </rewrite>
            </catalog>
        </models>
    </global>
</config>

 

This is pretty standard stuff that you should recognise if you’ve read my earlier blog posts about extending magento classes.  Suffice it to say, we’re basically declaring our module and its version, then telling magento that out module extends the ‘Mage_Catalog_Model_Category_Api_V2′ class and we’re extending it with our class ‘Namespace_Modulename_Model_Category_Api_V2′.  If you’re familiar with how magento maps classnames to directory structures, you can already see where we need to put our code file, so lets get that created:

/app/code/local/Namespace/Modulename/Model/Category/Api/V2.php

class Namespace_Modulename_Model_Category_Api_V2 extends Mage_Catalog_Model_Category_Api_V2
{
 /**
 * retrieves Category ID based on category name
 *
 */
 public function getID($category_name){
 $category_model = Mage::getModel('catalog/category')->loadByAttribute('name',$category_name);
 $result = array();
 $result['category_id'] = $category_model->getId();
 $result['category_name'] = $category_name;
 return $result;
 }
}

Again, nothing new here if you’re familiar with extending magento classes.  We’re extending from the class we found earlier, and adding in our own function (getID) which takes one parameter ($category_name) and returns an array of the category ID and the category name (as a way of checking the response you get back is what you’re expecting).

Now we’ve declared our class and written the code for it, all that’s left to do is the magical SOAP bit that makes our function accessible through the magento SOAP API.  There’s several files needed for this, all of which live in our module’s ‘etc’ folder with the config.xml we created earlier. The first (and in some ways the one that causes the most problems in finding documentation) is ‘api.xml’ which maps our PHP function to a SOAP call and tells magento how to handle it.

<config>
    <api>
        <resources>
            <modulename_category translate="title" module="modulename">
                <model>namespace_modulename_model_category_api</model>
                <title>New Category API</title>
                <methods>
                	<getID translate="title" module="modulename">
                		<title>Retrieve Category ID from its name</title>
                		<acl>catalog/category</acl>
                	</getID>
                </methods>
            </modulename_category>
            </resources>
        	<v2>
            	<resources_function_prefix>
                	<modulename_category>catalogCategory</soapcatalog_category>
            	</resources_function_prefix>
        	</v2>
    </api>
</config>

This is the most basic api.xml I could come up with that works, it doesn’t include any error handling but simply maps our class and its function(s) to SOAP calls.  You  can see how we declare our SOAP ‘resource’ , declare its methods (here we’re only declaring one method, but you can add other nodes for as many methods as you like) then under the V2 node we declare a prefix to use for these methods.  Using this prefix our final SOAP call will be ‘catalogCategoryGetID’ which fits in nicely with the other Catalog/Category API calls.  If you’re creating your own API rather than extending a pre-existing one you can use your own prefix to distinguish its calls from other Magento ones.  NOTE: Since writing this article I’ve found that you MUST use your own prefix for new methods on the API.  See this article for an explanation.

You’ll also notice that we’re only interested in version 2 of the magento API here.  I try to stay away from v1 of the API as it can’t be used with code generation tools such as JAX-WS for java or the PHP soap classes.

Moving on, now we’ve mapped our class and its methods to SOAP calls we need to provide a wsdl file that will be used as a ‘contract’ when constructing clients to communicate with the API.  If you’re not sure what a wsdl file is, or what it does I suggest reading the SOAP documentation here.

As with most things in Magento, we only need to declare our little section of the wsdl, Magento then merges it into the correct nodes in the overall wsdl at runtime.  One complication is that we actually need to declare 2 wsdl files, one for the the API running in WSI compliance mode and one without.  I assume if you’re only ever using the API in one mode you can just create the wsdl for that mode… but for completeness I’ll show both.

The WSI mode wsdl is called ‘wsi.xml’ and ours looks like this (I’ve commented this file since it’s quite complex, hopefully the comments make it easy to follow)

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:typens="urn:{{var wsdl.name}}"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
             xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
             xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
             name="{{var wsdl.name}}"
             targetNamespace="urn:{{var wsdl.name}}">
    <wsdl:types>
        <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:{{var wsdl.name}}">
        <!---Declare our types-->
        	<xsd:complexType name="catalogCategoryID">
                <xsd:sequence>
                    <xsd:element name="category_id" type="xsd:string" />
                    <xsd:element name="category_name" type="xsd:string" />
                </xsd:sequence>
            </xsd:complexType>
	   <!---Declare our input and output parameters-->
            <xsd:element name="catalogCategoryGetIDRequestParam">
                <xsd:complexType>
                    <xsd:sequence>
                    	<xsd:element minOccurs="1" maxOccurs="1" name="sessionId" type="xsd:string" />
                        <xsd:element minOccurs="1" maxOccurs="1" name="category_name" type="xsd:string" />
                    </xsd:sequence>
                </xsd:complexType>
            </xsd:element>
            <xsd:element name="catalogCategoryGetIDResponseParam">
                <xsd:complexType>
                    <xsd:sequence>
			<!---here we use the type we declared earlier as our output parameter-->
                        <xsd:element minOccurs="1" maxOccurs="1" name="result" type="typens:catalogCategoryID" />
                    </xsd:sequence>
                </xsd:complexType>
            </xsd:element>
            <!---Boohoo-->
           </xsd:schema>
  </wsdl:types>
   <!---here we declare our messages, in and out, for our method-->
    <wsdl:message name="catalogCategoryGetIDRequest">
        <wsdl:part name="parameters" element="typens:catalogCategoryGetIDRequestParam" />
    </wsdl:message>
    <wsdl:message name="catalogCategoryGetIDResponse">
        <wsdl:part name="parameters" element="typens:catalogCategoryGetIDResponseParam" />
    </wsdl:message>
    <wsdl:portType name="{{var wsdl.handler}}PortType">
    	<!---And here's our method, note we need to add the prefix we delcared in api.xml-->
    	<wsdl:operation name="catalogCategoryGetID">
            <wsdl:documentation>Set_Get current store view</wsdl:documentation>
            <wsdl:input message="typens:catalogCategoryGetIDRequest" />
            <wsdl:output message="typens:catalogCategoryGetIDResponse" />
        </wsdl:operation>
     </wsdl:portType>
     <wsdl:binding name="{{var wsdl.handler}}Binding" type="typens:{{var wsdl.handler}}PortType">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
        <!---Here we bind our method to a SOAP document type-->
        <wsdl:operation name="catalogCategoryGetID">
            <soap:operation soapAction="" />
            <wsdl:input>
                <soap:body use="literal" />
            </wsdl:input>
            <wsdl:output>
                <soap:body use="literal" />
            </wsdl:output>
        </wsdl:operation>
        </wsdl:binding>
    <wsdl:service name="{{var wsdl.name}}Service">
        <wsdl:port name="{{var wsdl.handler}}Port" binding="typens:{{var wsdl.handler}}Binding">
            <soap:address location="{{var wsdl.url}}" />
        </wsdl:port>
    </wsdl:service>
 </wsdl:definitions>

You can see from the comments how we build up our definitions: starting with our complex type (which corresponds to the array we output from our PHP ‘getID’ function), we then bundle that type into a responseparams object, which we then declare as the output for our method.  I confess I don’t really understand all this myself, some trial and error went into the final version you see here.

For the API in non WSI compliant mode, we need a separate wsdl file called simply ‘wsdl.xml’  This is very similar to the wsi.xml file we’ve already created, only the XML tags really change.  Follow the comments above to see how this file is built up:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns:typens="urn:{{var wsdl.name}}" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
             xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
             xmlns="http://schemas.xmlsoap.org/wsdl/"
             name="{{var wsdl.name}}" targetNamespace="urn:{{var wsdl.name}}">
    <types>
        <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:Magento">
            <import namespace="http://schemas.xmlsoap.org/soap/encoding/"
                    schemaLocation="http://schemas.xmlsoap.org/soap/encoding/"/>
            <complexType name="categoryID">
                <all>
                    <element name="category_id" type="xsd:string"/>
                    <element name="category_name" type="xsd:string"/>
                </all>
            </complexType>
        </schema>
    </types>
    <message name="getCategoryIDRequest">
     	<part name="category_name" type="xsd:string"/>
     </message>
	 <message name="getCategoryIDResponse">
     	<part name="category_info" type="xsd:categoryID"/>
     </message>
	<portType name="{{var wsdl.handler}}PortType">
    	<operation name="getCategoryID">
            <documentation>get category ID from its name</documentation>
            <input message="typens:getIDRequest"/>
            <output message="typens:getIDResponse"/>
        </operation>
   </portType>
   <binding name="{{var wsdl.handler}}Binding" type="typens:{{var wsdl.handler}}PortType">
        <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
        <operation name="catalogCategoryGetID">
            <soap:operation soapAction="urn:{{var wsdl.handler}}Action"/>
            <input>
                <soap:body namespace="urn:{{var wsdl.name}}" use="encoded"
                           encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
            </input>
            <output>
                <soap:body namespace="urn:{{var wsdl.name}}" use="encoded"
                           encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
            </output>
        </operation>
   </binding>
   <service name="{{var wsdl.name}}Service">
        <port name="{{var wsdl.handler}}Port" binding="typens:{{var wsdl.handler}}Binding">
            <soap:address location="{{var wsdl.url}}"/>
        </port>
    </service>
 </definitions>

 With the addition of those files our module is almost complete, all that’s left to do is add a module activation file in ‘/app/code/etc/modules/Namespace_Modulename.xml’ that looks like this:

<?xml version="1.0"?>
   <config>
     <modules>
       <Namespace_Modulename>
         <active>true</active>
         <codePool>local</codePool>
       </Namespace_Modulename>
     </modules>
   </config>

and your new API calls should be waiting for all your integration needs :)

Categories: coding, magento | Leave a comment