Additional Blogs by SAP
cancel
Showing results for 
Search instead for 
Did you mean: 
0 Kudos

This is the first part of a 4-part blog series. Please also take a look at Part 2, Part 3 and Part 4.

Mashups are 'the big thing' in the web nowadays. Programmable Web has far over thousand mashups listed, with three new being added every day. They demonstrate in a nice way how easy it is to assemble new data presentation and user interaction while using information from different sources seamlessly. Most of these mashups, however, are not business related. Though a nice possibility, mashing up stock prices with weather data is not always useful, and for robust business solutions having control over all participating services (or at least a service contract) is key to success.

Mashups do not necessarily have to combine completely different types of services. Joining information from various backend systems inside the same company can also be of great help.

According to Wikipedia, "A mashup is a website or application that combines content from more than one source into an integrated experience." Actually this is just what the term composite application in the SAP world describes: "an application that reuses, integrates, and orchestrates Web services to support business processes or specific user experiences."

In this blog I want to show you how easy it is to create such an application using SAP Enterprise Services in PHP.

Summary what you'll need


Finding Enterprise Services

The Enterprise Services Bundles Wiki (a section of the SDN/BPX Wiki) has put together a very nice list of scenarios for composite applications (called ES Bundles). All ES Bundles descriptions come with an extensive description of the application scenario and documentation for all process components and Enterprise services used.

Out of the list of Enterprise Services Bundles we choose the Customer Fact Sheet (CFS) scenario for this blog. The CFS allows you to search for a customer by name (or city), getting back a result list. From this you can display the CFS in a popup (which allows viewing multiple customers at the same time by just opening multiple popups) which shows you basic data (like address, phone number, etc), Sales Orders, Sales Arrangements, Quotes, etc. It looks really simple on the surface - which is just what a good UI should do. No switching of windows and logging in to different systems to get all this data - just have it displayed in a summary page. Follow the link above for the more formal scenario description.

Since I didn't use WebDynpro but PHP I had to compromise - on the one hand I am free to design the visual feeling completely different than the classic SAP look, on the other hand I had to implement quite some basic functionality by hand, as I had no high-level modeling tool and framework, but just PHP and HTML with some libraries. I opted for only displaying some of the data and skipped the changing (editing basic data, adding rows, etc), as this would be enough to prove what can be done in PHP and give you a good starting point if you want to build upon it.



This is what it looks like: Upon loading of the page, a search field is displayed. It triggers a call to "Find Customers by Address" and returns the list of customers. Clicking on the customer name opens individual popups with the actual fact sheet:



In the popup, the customer basic data is displayed on top and always in view, whereas the individual tables are shown in tabs.

The list of found customers as well as the individual tabs of the popup are filled in using AJAX. This is a modern way of user interaction and prevents page flickering while giving early access to the information in the popup even before all tabs have been fully loaded.

The Web services

To retrieve the information to be shown in the CFS I used a whole bunch of Enterprise Services. More Information can be found in the ES Workplace or the ES Bundle Wiki, so I list them here only briefly:

There is a mySAP ERP ECC 600 system – named FU2 – available here on SDN inthe ES Workplace. If you don’t have a local system installed you can apply for free access to this system there.

If you follow the links in the ES Bundles Wiki you can directly click your way through to the 'backend WSDL': Open the CFS Bundle and scroll down to the Enterprise Services in Customer Fact Sheet. Choose the desired service and follow the "View this enterprise service" link, from which you can get both the internal ABAP name (called "Related Web Service Definition") as well as the WSDL including the binding to the backend. During stub generation only the generic WSDL is needed, but when actually calling the service a binding is necessary.

If you have the internal name for a service you can also directly look up the WSDL URL using the transaction WSADMIN from the webgui. Log on to the systems (follow the "access the systems" link on the ES Workplace), enter the WSADMIN transaction, select the desired service and click on the WSDL button. For more info please refer to the How to Use the ES Workplace document, section "retrieving the specific backend WSDL directly from the system".



PHP Web service support

Consuming the Enterprise Services in PHP is easy and straightforward.

PHP5 comes with native support for Web services through a SOAPClient class, so there is no need anymore to use external libraries like NuSOAP. The Soapclient retrieves the WSDL at runtime and creates stubs which allow a really easy calling of Web services from PHP. Unfortunately this means the signature of the stub is not present at design time and is thus unavailable for syntax checking or code completion in the IDE. Also each call to a Web service requires rebuilding the client stub, including the WSDL-fetching. Luckily the WSDLs are cached automatically.

I prefer to have stubs already at design time, which would make it easier to have my own naming scheme or to implement global options, authentication mechanism or even client object caching. Using our Scripting Tool (available here on SDN) I imported the individual WSDL files and generated small stubs, which I could then import into my PHP files (with require_once). After importing the backend WSDL (which contains the binding information) I generated the individual files with the "WS PHP with HTML Testscript Generator", but chose to not generate the testscript but only the stub. In my case I also had to add a proxy.



For more information on the Scripting Tool please read my article about using it.

PHP caveats

There is one important thing to know about the PHP SOAP implementation: it eats nesting levels. This means when according to the WSDL definition there is an array with some data (i.e. maxOccurs != 1) then this array is omitted when there is actually only a single element in it. This is the case for both input and output parameters, but the additional array seems to be accepted during input, so everything's ok here. But for output parameters this is quite an important point: instead of using a foreach structure you have to actually dynamically (during runtime) check whether an array or only the single element of this array is returned.

So I used the following code to overcome this issue:
    $res = CustomerSimpleByNameAndAddressQuery ($parameters);
        if (isset($res->Customer)) {
            foreach((is_array($res->Customer) ? $res->Customer : array($res->Customer)) as $Customer) {
            // ...
        }
    }

This is a solution which worked for me, but far from being perfect. I wonder what happens in the case there are nested arrays - how should the program tell which one was omitted?

Unfortunately this is how the developer intended it to be and he even declared it as a feature, according to this closed bug report. The presented solution is to add the option SOAP_SINGLE_ELEMENT_ARRAYS to get the (in my opinion correct) behavior - instead of making it the default.

The Scripting Tool PHP generator automatically adds this option, to save its users the frustration having the foreach fail with 'array expected' during runtime. However, since the currently released version of PHP doesn't yet include this constant I deleted this line from the generated files and added the above workaround code snippet.

Converting the XML result to JSON

I didn't call the Enterprise Service directly from the browser (in JavaScript), but processed it in PHP as a kind of proxy. The main task which is done there is the transformation from an XML tree (which is rather complicated and often only partially filled out, depending on the backend availability of the data) to a simple 2D array which can be processed more easily.

Accessing values deep inside the XML result using the PHP SoapClient is a trivial matter, as most things are in PHP. Given the search result from a query, I got this XML back (shortened):


 
   
     
        0000001175
       
         
           
              Elektromarkt Bamby
           
         
       
     
   
 


Which results in this PHP object structure, as returned by the SoapClient (using var_dump() to print it):

object(stdClass)#2 (2) {
  ["Customer"]=>
  object(stdClass)#3 (2) {
    ["ID"]=>
    object(stdClass)#4 (1) {
      ["_"]=>
      string(10) "0000001175"
    }
    ["BasicData"]=>
    object(stdClass)#5 (1) {
      ["Common"]=>
      object(stdClass)#6 (3) {
        ["Name"]=>
        object(stdClass)#7 (2) {
          ["FirstLineName"]=>
          string(18) "Elektromarkt Bamby"
        }
      }
    }
  }
}

Accessing the customer name could then be done with this single line (assuming a foreach-loop over the result with $Customer as variable)
$name = $Customer->BasicData->Common->Name->FirstLineName;

An important thing to note is that what XML calls SimpleContent - a complexType having not only child elements but data directly inside itself - is represented with the name '_' (underscore) as the object field name. In the example above the value of the customer ID is such a case.

Quite often when PHP tries to make life easier it simplifies away some parts which might be important. In our context this is the possibility that the XML returned is not in the way we expected it - in the Enterprise Services this happens if some fields are not filled in. The above single-liner would then just fail, trying to dereference a null object. So I created a small function which would catch those cases and just return a default value (empty string). I named it XPath(), though it only resembles the idea, it's not really XPath being processed here 🙂

Using this, the backend.php file creates a simple, flat array with the results from the service call. This array is then transported to the client in JSON format and displayed in a JavaScript-generated table.

Client-side processing

The AJAX client - JavaScript in the browser - gets the JSONified data and creates the HTML table. I used the Script.alico.us library for the AJAX calls and to provide the hovering effects.

I will write more about AJAX and how I used it in the second part of this blog.

Security

Since this was only a demo application I used hardcoded user credentials in the PHP files. Of course in real world scenarios these would be replaced by user-entered information, either by an own logon screen or using a Single-Sign-On ticket.
2 Comments