Featured Posts

The 10 Worst Things about World of Warcraft - Mists... I've been playing WoW since vanilla version starting in 2006.  Except for a six-month hiatus in late 2011, I've been a daily player.  I've seen multiple patches come...

Read more

Best Breakfast Burritos, ever! I like eating a good breakfast, usually around lunchtime once I've had my fill of coffee and am awake enough to appreciate a good breakfast. This is my recipe for my ultimate...

Read more

Testing Arrays in PHP - Back to Basics... Sometimes, when you're wallowing through your abstraction class layers, you find yourself using code for simple functions that are normally the focus of an Intro to Programming...

Read more

PHP: Comparing Object Structures I'm working on a project where I am converting an established REST API over to a rabbitMQ service.  Because, you know, dinosaur, I'm continuing to use PHP as my language...

Read more

Mountain Lion and Tunnelblick - Playing Nice Together One of the things that requires some tweaking after the installation of Mac OS X (Mountain Lion) is Tunnelblick, a free and open-source GUI for openVPN.  I use Tunnelblick...

Read more

Subscribe

Web Services with PHP & nuSoap – Part 1.1

Category : Technical
No Gravatar

Introduction

[EDIT] – This is a re-hash of a document I wrote a couple years ago.  There’s been changes to the nuSOAP library and I wanted to document the updates relative to the tutorial series.  Also, I need to fix a lot of the broken-links and code listings since I’ve changes hosting providers since this article was first written.

This article is specific to nuSOAP release 0.9.5 on 2011-01-13.  This tutorial was updated on September 22, 2011.

Once upon a time, I was tasked with developing a web-services by my boss as the integration point between our production application and Sales Force.  At this point, although I’d heard of web services … kind of … didn’t Amazon use web services for something?  Still, I’d never coded a web-services based application before.

At this point, I have to assume you are unfamiliar with the concept of web services and why you may have to create and provide a web-services offering to your client base.  Web services allow a remote client to access functionality (as defined by you, the programmer) via the standard HTTP interface (normally, port:80) to your application and database services.

Back in the day, networking services (semaphores, pipes, streams, message queues, and other forms of IPC) were custom-written and assigned/slaved to unique networking ports for accessing specific service daemons.  Of course, the internet was a kinder, gentler place back then… and a given server may have had dozens, or even hundreds, of non-standard ports open, listening, waiting, for networking service requests.  Or hacking attempts.

Today, web-services is a replacement to dedicated networking apps – all handled by your web server, and all serviced over the same network port: port 80.  Since port 80 is a standard port and, usually, already open on a web-server, additional security risks by opening new non-standard ports for networking services are averted.  Your web-server, such as Apache, now has the responsibility of processing the request and delivering the results to the client.

The web-services component piece is a collection of functions that you’ve written that provide remote clients access to your system.  These functions are accessible only via the web services framework and while they may be duplicated from a dedicated and traditional web-based application, the web-services framework is designed as a stand-alone piece of software.  Think of the web-services piece as your application’s data-processing layer minus the presentation layer.  The “M” and “C” of the “MVC” model.

Initially, when I was tasked with a similar set of objectives, I initially tried xml-rpc.  This led me down a rabbit-hole that spanned nearly a week of my time with the end-result being abandonment.  I hit road-blocks with xml-rpc over server authentication and passing complex objects.  Exacerbating the issue overall is that xml-rpc seems to be dated technology – I had a hard time locating resources that were recent.

Then I stumbled across nuSOAP – it’s free via sourceForge, stable and, using a quote from sourceForge:

“NuSOAP is a rewrite of SOAPx4, provided by NuSphere and Dietrich Ayala. It is a set of PHP classes – no PHP extensions required – that allow developers to create and consume web services based on SOAP 1.1, WSDL 1.1 and HTTP 1.0/1.1.”

nuSOAP seemed to have more of everything available: tutorials, examples, articles, blog posts.  When I started my implementation with nuSOAP, the first thing I received help with was server-level authentication.  I was able to immediately get my remote requests validated by the web-server and handed off to the web-services module!

The major selling point, for me, on nuSOAP is that nuSOAP is self-documenting.  As part of the API functionality, nuSOAP generates HTML pages that documents the exposed services via the WSDL  and also provides you with a complete XSLT definition file!

First off, download and install the nuSOAP libraries – I provided a link to the sourceForge site a couple paragraphs ago – and unpack the tarball.  You’ll end-up with a directory (mine is called: ./nuSOAP) and, within that directory, is the one file you include: nusoap.php.

There are two pieces to this tutorial — a server side piece and a client-side piece.  While you can execute both pieces off the same environment (machine), normally you’d use the client remotely to access the API server-side code.

What’s Not Covered:

Apache.  Apache configuration for your vhost.  Apache .htaccess.  This article assumes that you’ve a working server and that you’re able to install and access the server files via Apache.  Even if your Apache server is a localhost configuration, access the server-side files via localhost client, traversing the TCP stack locally, is still a valid method for testing your web-services server application.

 

Time to push up our sleeves and start working on the server code…

The Web-Services Server

Today, we’re going to write a ping server — where the server has an exposed service (method) named “ping” which takes a single argument (a string) and returns an array back to the calling client.  The return array contains two associative members: a boolean (which should always be true – otherwise there are other issues…) and a string which is the modification of the original string in order to prove that, yes, we went there and we came back.

Because my project, and I’m doing this project for my new company, is going to represent significant effort, size and complexity, I’ve broken out the components of nuSOAP request into exterior files because, later, these will become control file which will, in turn, load files that have been organized into a hierarchy friendly to the application’s data model.

So, if this file, which I’ve named index.php, seems small, remember that you’re not viewing the dependent files (yet).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<pre><?php
/**
 *
 */


// setup...
require('./nuSOAP/nusoap.php');
// set namespace and initiate soap_server instance
$namespace = "http://myapi/index.php";
$server = new soap_server('');
// name the api
$server->configureWSDL("myapi");
// assign namespace
$server->wsdl->schemaTargetNamespace = $namespace;
// register services
require('myServiceRegistrations.php');
// load WSDL...
include('mywsdl.php');
// load services code modules...
require('myServicesModules.php');
// create HHTP listener:
$request = isset($HTTP_RAW_POST_DATA) ? $HTTP_RAW_POST_DATA : '';
$server->service($request);
exit();
?></pre>

So far, so good – let’s take a look at what we’ve just done:

  • we’ve included the nu_soap library…
  • declared our namespace (which is the URL of the api server)
  • instantiated a new soap_server instance and assigned it to the variable $server
  • initialized the WSDL
  • assigned the namespace variable to the WSDL
  • load and register our exposed services
  • load the WSDL
  • load the service code
  • create the HTTP listener
  • invoke the requested service
  • exit

This (index.php) file is the server-side file that will be invoked for ALL future API calls to the service.  It invokes three control files which, in turn loads the services (WSDL definitions), the WSDL variable definitions (think of these as inputs and outputs to your exposed services), and the actual code for all of the services, and their supporting functions, that you’re going to expose via your API.

Side Note:  This is the file you’ll reference in Apache when you’re (preferably) creating a new Virtual Host for the API.  HTTP requests that resolve to your server will be serviced by Apache which will, in turn, serve the results of this program back to the client.

Next, we’re going to define the myServiceRegistrations.php file that is required by index.php.  This file contains the WSDL for each and every exposed service that the API serves.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<pre><?php
/**
 *
 *
 */


$server->register('ping', array('testString' => 'xsd:string'),
                          array('return' => 'tns:aryReturn'),
                  $namespace, false, 'rpc', 'encoded',
'<p><strong>Summary</strong>: returns response to a ping request from the client.  Response
includes testString submitted.  Used to test Server response/connectivity.
</p>
<p>
<strong>Input</strong>: $testString (type: string) and random collection suffices.
</p>
<p>
<strong>Response</strong>: Service returns an array named $aryReturn with two associative
members: &quot;status&quot; and &quot;data&quot;.<br />$aryReturn[&quot;status&quot;] should
<i>always</i> return true.  (A time-out is an implicit false.)<br />If no value was passed
to the service via $testString, then the value of $aryReturn[&quot;data&quot;] will be
empty.<br />Otherwise it will contain the string: &quot;Rcvd: {yourString} (count)&quot; to
show that the message was received and returned. (count) is a character count of the
passed string, also validating that the passed data was received and processed.
</p>
'
);</pre>

This PHP code registers a function called “ping” with the nuSOAP $server instance.  The second parameter is the input to function.  Note that all input (and output) parameters have to be declared as an array even if there’s only a single value being passed.  Also notice that you have to type-cast the variable being passed using XML datatypes.  For your data definitions, you use one of the 44 built-in datatypes defined in this document: http://www.w3.org/TR/2001/REC-xmlschema-2-20010502/.

(For more information on XSD object and XML schema, please visit: http://ws.apache.org/axis/cpp/arch/XSD_Objects.html.)

The third argument to the method “register” is the data type returned.  This is a complex variable called “aryReturn” — don’t worry about right now, we’re going to define this complex-type variable in another configuration file.

The next four arguments are:

  • our $namespace variable we set in index.php
  • boolean false
  • ‘rpc’ for the call type
  • ‘encoded’
Use these values literally.
The last variable is a huge block of HTML.  This block of HTML can be as large, or as small, as you need it to be.  It’s the basis for the WSDL documentation that nuSOAP generates for your client-side developers.
When developers hit the server URL, they’ll be presented with your API documentation that nuSOAP generates from the WSDL file(s).  As your API grows, exposed methods will be listed in the blue-ish box on the left side of the screen.  Clicking on any of the methods will expose the details (requirements) about that method thus:

Nice, huh?

The second file (that we’ve included in our source: (mywsdl.php)) is the WSDL file that defines our data structures that are used as either inputs, outputs, or both, to the exposed services.  Another thing I like about SOAP and nuSOAP is that it introduces a layer of strong-typing to the PHP stack.  You can save a bit of debugging time by requiring that you call a method with EXACTLY this and return EXACTLY that.  Anything else tends to generate a system-level error:

1
2
3
4
<pre>[Thu Sep 22 14:36:59 2011] [error] [client ::1] PHP Fatal error:  Call to a member function addComplexType() on a non-object in /htdocs/LL2/trunk/services/mywsdl.php on line 9
[Thu Sep 22 14:36:59 2011] [error] [client ::1] PHP Stack trace:
[Thu Sep 22 14:36:59 2011] [error] [client ::1] PHP   1. {main}() /htdocs/LL2/trunk/services/ll2wsdl.php:0</pre>
<pre>

This error message, from the apache error log, is somewhat obfuscated in it's meaning.   I attempted to return only the string, by itself, instead of returning the array (of two elements) that I had told nuSOAP I would return for this service.  This error was generated because the types (between the code and the WSDL) of the return variable (structure) did not exactly match.

If you've only ever coded in a loosely-typed language, like PHP, than this part of SOAP is going to be a bit of a ... transition ... for you.  When we say that something, be it a variable, function, or exposed service, is strongly typed, we're declaring the type of that object and, if the type of the object during run-time does not match, then SOAP will force PHP to throw a fatal as shown in the error log snippet above.

Keep this in-mind as you develop exposed services that are increasingly complex.  Since the error messages tend to point you at your code, at the point of failure, it's easy to forget that that the requirements of the underpinnings (in this case, the WSDL), are the root cause of your PHP fatals.

That being said, let's take a look at the WSDL for our ping service:

1
2
3
4
5
6
7
8
9
10
11
<pre><?php
/**
 * WDSL control structures
 *
 * initially, while in dev, this will be one large file but, later, as the product
 * matures, the plan will be to break out the WSDL files into associative files based
 * on the object class being defined.
 */

$server->wsdl->addComplexType('aryReturn', 'complexType', 'struct', 'all', '',
            array('status' => array('name' => 'status', 'type' => 'xsd:boolean'),
                  'data'   => array('name' => 'data',   'type' => 'xsd:string')));</pre>

We're invoking the nuSOAP method addComplexType to define a structure to the WSDL in our Table Name Space (tns).   To do this, we first define the name of the structure that we're going to use: aryReturn and then we define the composition of that structure.

The declaration for this looks a lot like a standard PHP declaration for an array with the exception of the XSD (XML Schema Definition) appended at the end of each element's declaration.  (See the links I embedded above for explanations and examples of valid XSD.)

XSD provides part of the strongly-typed concept for our structure elements.  We're telling nuSOAP to expect a variable structure containing these named elements of this type.

What we have, then, is an associative array with two elements: 'status' and 'data'.  $aryReturn['status'] and $aryReturn['data'] and they're of type BOOL and STRING respectively.

Note, finally, that this variable structure isn't confined to single-use.  Once we've declared it within our tns, it's available to any exposed service where it's needed.  This is the model for my common error structure -- the boolean indicates success or fail on some service operation and the data component contains the relative diagnostic information.

The third and final file we're including into the server source is the code for the exposed service.  This is where you write the function handlers for your services.  Since you've already defined the input parameters, and the return types for the ping service, there's very little left to do.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<pre><?php
/**
 * ping
 *
 * service that confirms server availability
 *
 * input: any string
 *
 * output: reformatted string:  "Rcvd: {string} (charcount{string})"
 *
 * @param string $testString
 * @return string
 */

function ping($testString = '') {
    return(array('status' => true, 'data' => 'Rcvd: ' . $testString . '(' . strlen($testString) . ')'));
}</pre>

Note the following in the code for our exposed service:

  • our exposed method is named ping because that's how we registered the service (myServiceRegistrations.php)
  • we're providing a default type-cast for the input param of string in case the service is invoked without input params.
  • we're returning BOOL true and prepending "Rcvd: " to the received string, and appending a character count to prove that the service successfully responded to the client's request.
  • the return structure exactly matches the WSDL declaration: the name of the array elements, and the element types.
If you've correctly installed and referenced (within your PHP) the nuSOAP libraries, then you should be able to load the url of the new server source file into your web browser to see the nuSOAP-generated documentation for your new web services.  Click on the WSDL service function: ping to see a detailed description of the function.

If you're using IE, then clicking on the WSDL link will return the XML.  If you're using Firefox, Chrome or other browsers, clicking on WSDL will display the generated XML for your service.

Now that the server is working on it's own, it remains fairly useless until we can get a client to connect to it invoke it's methods.  Let's work on the client next...

The Web-Services Client

The web services client application will also be written in PHP.

The web client is an application that connects to remote server using the http port 80.  To do so, you'll need the client to be aware of certain bits of information that may, or may not, be required to access the remote server.

In our client, we're not going to require remote authentication -- but I'll take a quick aside andexplain how you would include this, client-side, if your server required .htaccess authentication.

nuSOAP has a client method called setCredentials which allows you to specify your .htaccess username and password and the authentication schema.  It's a single line of code which is normally used to require not only clients to login to access your API, but, once identified, you can limit the set of exposed methods available to individual clients or groups.

For example, if you have a product you've developed in-house, then you'd want full-access for your front-end web/applications servers.  Your PM later decides to open a subset of the API to the general public and a subscription-based set of exposed methods in order to monetize your product.  Finally, the PM also wants to "white-box" the product so that other companies can use it but with their branding and access to isolated or discrete data sets.

What you'd end-up with is several levels of client access to your web services.  Implementation of limiting exposed services would be handled server-side but it would be based on your client's authentication and possibly the subject of a future tutorial...

So, to the client-side code: (name this file: apiTestClient.php)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<pre><?php
// Pull in the NuSOAP code
require_once('./nuSOAP/nusoap.php');
$proxyhost = isset($_POST['proxyhost']) ? $_POST['proxyhost'] : '';
$proxyport = isset($_POST['proxyport']) ? $_POST['proxyport'] : '';
$proxyusername = isset($_POST['proxyusername']) ? $_POST['proxyusername'] : '';
$proxypassword = isset($_POST['proxypassword']) ? $_POST['proxypassword'] : '';
$useCURL = isset($_POST['usecurl']) ? $_POST['usecurl'] : '0';
$client = new nusoap_client('http://{YOURSERVERURLHERE}/index.php', false, $proxyhost, $proxyport, $proxyusername, $proxypassword);
$err = $client->getError();
if ($err) {
    echo '<h2>Constructor error</h2><pre>' . $err . '</pre>';
}
$client->setUseCurl($useCURL);
$client->useHTTPPersistentConnection();
// Call the SOAP method
$result = $client->call('ping', array('testString' => 'Argle'), 'http://localhost');
// Display the result
if (!$result) {
    echo "service returned false";
} else {
    print_r($result);
}
unset($client);</pre>
<pre>?></pre>

The first thing we do in our client-side code is to include the nuSOAP libraries.

The next five lines of code read from the local POST environment, testing if you've established a proxy for your web-services server and, if so, populating the proxy variables.

The next line instantiates a nuSOAP client and associates it with our remote server.  Change "YOURSERVERURLHERE" to the name of your apache web server URL where you have the server side code installed.  (e.g.: localhost, myserver.com, etc.)

Note the name of the function call: newsoap_client()...as opposed to using the function soapclient().  This function name is legacy-compatible with PHP 5.0's instantiation call:  new soapclient() - the PHP SOAP extension uses the same instantiation function name as the nuSOAP library.  If you have both installed, (PHP 5.0 SOAP extension, and the nuSOAP libraries), executing the client will return errors as you've overloaded the soapclient() function.  (You're calling the PHP SOAP function with the nuSOAP function parameters.)  Rename the soapclient() function to the back-compatible function: newsoap_client().

The next two lines tell the nuSOAP client to useCurl, when possible and if previously saved and, if possible, to use HTTP persistent connections.

Next, we're going to consume the web-services from the server by making a call to the nuSOAP method: call().  The arguments to this method are:

  1. the name of the service being consumed (ping)
  2. the input string (note:  all inputs must be passed as arrays!)
  3. the namespace URI (optional:  WSDL can override)

Store the results of the web-services to the aptly-named variable $results and evaluate it upon return.  If the client call was not successful, then display an error message.

If the client call was successful, then display the contents of the returned array.

Note that you can run this client code from either a browser using the "file://" option or, if the client source code is accessible to apache, then you can display using a browser.

When I run this client-side software in the browser, I get displayed back:

Array ( [status] => 1 [data] => Rcvd: Argle(5) )

So, how do we know that the client went out and successfully returned from the server with the data?  Simple - in the client source, you will not find the literal "Rcvd:" anywhere in the source.  This data was supplied by the server-side function and returned back to the client.  It's a simple example, but it provides proof-of-concept that we're successfully able to connect to a remote web-server and return data created on the remote server back to the client program.

Let's wrap this up...

Summary:

This tutorial (hopefully) explained what web-services are, and provided you with a practical example of a consumable service: ping().  Such a service would normally be invoked as a means of testing server availability.

We created a web-services server file using the nuSOAP library by defining a complex-structure (an associative array) and registering a method with the nuSOAP server.  The method takes an input parameter which, although it's only a single input parameter, must be built and passed as an array construct to the server method.  Next, we showed that by loading the server source into a browser, we learned the nuSOAP provides built-in documentation for your web-services structures and methods.  Which is a very nice-to-have when you're creating your developer documentation!  Next we created the web-services client source code file which connected to our remote server and invoked the server's method: hellow().  If all worked correctly, you displayed the string returned from the remote server within your browser window.

In the next installment, I'll cover the web-services server-side of this business and we'll see how to create the various methods for accessing a mySQL database, complex structures as input and output parameters to those methods, and general debugging techniques.

Thank-you for your patience - I hope this article helped you.

September 26, 2011

Web Services using PHP & nuSoap – Part 3

Category : Technical
No Gravatar

Welcome to Part III of creating Web-Services with nuSOAP and PHP – a tutorial of what’s turning out to be epic proportions…. In this installation we’re going to create a WSDL method for inserting a new record into our table: customers. (Please refer to part II for information on creating our mySQL table and it’s schema.) We’re going to jump right into the deep-end of the code pool as we’re going to create a web-services method that requires complex structures as both it’s input and output parameters. Additionally, we’re going to require a support function that will be invoked from the method that’s registered with the WSDL. Finally, we’ll test the web-services method by invoking it from a remote client and we’ll evaluate the results returned from the server.

Quick review: we’ve got a successful dog-washing business that provides it’s customers with access to a calendar scheduling system via the web. The inference is that customers register with the business by providing their first and last names, and an email address. We store this information in the table: customers. Corporate has asked us to develop a web-services interface into the customer’s data table as this table will become the authoritative repository for the entire organization.

In today’s installment, we’re going to create a web-services function called: createNewCustomers and we’ll register the function with the WSDL so that it may be invoked by remote clients. The function will be relatively robust, providing rudimentary error checking and parsing of selected data fields. The method will interface into the mySQL table via the mySQLi (improved) extension. We provided the source code for our db-interface library in part II so, please, refer to that article to obtain the source code.

After the jump, we’ll talk briefly about data table security and authentication before revealing the source code for our new method…

Site Upgrade…

Category : Site
No Gravatar

I upgraded the site hosting plan to something a wee bit more on-demand. This means that you’ll see faster load times… W00t!

Web Services using PHP & nuSOAP – Part 2

Category : Technical
No Gravatar

Hi and welcome to part 2 of my tutorial on implementing web-services via nuSOAP and PHP!

In the last article we set-up a small server and client app to test our connection to the web-services server. We invoked a method on the remote server via our client, passed a single parameter as input (wrapped in an array), and returned a complex-structure to the client application with data created from the server-method, and displayed that data in the browser screen.

That this was successful indicates that we’re able to establish communication with our remote server and successfully execute remote functions, returning data from the remote server for our own processing and usage.

In this part of the tutorial, we’re going to set-up the design and specifications for the server-side application by defining the mySQL data table we’ll be using, the methods of the web-services server, and the input and output parameters expected for each of the methods. Additionally, if required, other methods may need to be written to support our primary design. These methods may not be exposed to web-services, but are usable by those methods which are.

In any programming project, begin with the end in-mind. Define the scope and range of your project fully – anticipate problems and extremes and factor these issues into your design so that your application is robust, scalable and maintainable. Nothing is worse than stopping forward progress on a coding project because you have to back-track and re-factor your design. Take the time now to define what you want to accomplish and how you will do it.

So, let’s get started…

nuSOAP WSDL Review

Because we’re using the nuSOAP library, the server application is self-documenting. When we access the server application directly, we can view the WSDL method, and the corresponding XML definitions for the server. This is beneficial to our client-developers as it provides them with functional spec on how-to use our web services.

Although nuSOAP generates this documentation auto-magically, remember the quality of the documentation is controlled by you – the server-side developer. Pay close attention to your comments when defining your methods in the server-side code as thoroughness pays off when it comes to the client-side developers using your code. Provide as much information as you can, in a lay-out that follows form and convention. The time you spend now, in documentation, will be saved 10-fold when it comes time to release your webservices on the world at-large!

Remember that, when you click on the WSDL link (in the purple box), if you’re not using IE, you’ll see a mish-mash of generated text in a new page. Right click the browser background and select “View Source” and you’ll be presented with a formatted output of the XML generated for your server. If you’re using IE, then you’ll not need the intermediate step. The generated XML output will also be used by client developers for specification for input and output parameters as this data is defined explicitly by in XML. Specifically, the array structures (definitions) are masked in the top-level WSDL output – client-side developers will need to view the XML to see the atomic definitions of your complex structures either passed-into, or from, your web-services methods.

Let’s continue by defining the database table schema for our application…

Doggie-Bath Database Table

In the first part of this tutorial, we proposed a fictitious business set-up involving a corporate doggie-daycare type of business. Our web-services developer has to create a web-services portal for corporate that will provide access to the customers database (table) via web-services. For sake of simplicity, yet still providing a working example including complex structure passing, we’re going to provide three functions to corporate via web-services as methods that will allow remote clients to add users, edit users, and delete users from a mySQL database table.

Our customers table will be kept simple for the purposes of this tutorial – all customer data will be stored in a single table, and minimal information will be stored. If you’re re-creating the tutorial on your own server, then you can use the following sql code to create and populate your own table that we’ll be using in this example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
-- phpMyAdmin SQL Dump
-- version 2.11.6
-- http://www.phpmyadmin.net
--
-- Host: localhost
-- Generation Time: Apr 20, 2010 at 03:25 AM
-- Server version: 5.0.67
-- PHP Version: 5.2.9

SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";

-- --------------------------------------------------------
--
-- Table structure for table `customers`
--

CREATE TABLE IF NOT EXISTS `customers` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`customerFirstName` varchar(50) NOT NULL,
`customerLastName` varchar(50) NOT NULL,
`customerEmail` varchar(100) NOT NULL,
`customerJoined` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY  (`id`),
UNIQUE KEY `customerEmail` (`customerEmail`),
KEY `customerJoined` (`customerJoined`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 COMMENT='web services tutorial table' AUTO_INCREMENT=5 ;

--
-- Dumping data for table `customers`
--

INSERT INTO `customers` (`id`, `customerFirstName`, `customerLastName`, `customerEmail`, `customerJoined`) VALUES
(1, 'Marty', 'McFly', 'marty@boarders.com', '2010-04-20 03:24:22'),
(2, 'Emmet', 'Brown', 'docbrown@mensa.org', '2010-04-20 03:24:22'),
(3, 'Biff', 'Tannen', 'biff@butthead.com', '2010-04-20 03:25:22'),
(4, 'Marvin', 'Berry', 'marvin@chucksbro.com', '2010-04-20 03:25:22');

Next, we’re going to set-up and define our methods for the application and provide some initial specification…

Server Methods Defined

Remember, too, from the first installment of this tutorial, that we’re going to define the following methods for this table and managing our doggie-customer database. In addition to providing the names of the methods, we’ll also start documenting some requirements and specifications for the methods:

  • createNewCustomer() — function that will insert a new record into table: customers. Input passed as a complex structure and must contain the customers’ first and last name, and email address.
  • getCustomerData() — function will return data from the table in a complex structure containing all data from the table. Input parameter is optional – if no input parameter is specified, return all data. Otherwise integer input is expected corresponding to the customers.ID field of the table. If customer is located by ID, return a complex structure of all customer information for that id, otherwise return a NOTFOUND error message.
  • editCustomerData() — function that will allow changing/updating of customer data in the table. Input will be an integer value with a complex structure containing three fields: the customers first and last name, and the email address , in associative indices. If any of the contents of the array are set to null, then that data element will not be changed. Otherwise, update the record (specified by the integer value input parameter) using the named values in the complex structure that are not-null. If the keyword “DELETE” appears as the data element in all three cells, then delete the record. Return a simple structure indicating operation success or failure.

I didn’t specify this function in the first part of this tutorial. However, as in real-life, once you start designing your application, you’ll discover the need to add additional functionality to your design. That’s ok!

In our case, I think we’ll need to add a search function and expose that function to web services. The reason being is that we’re allowing edits to single records within the customer database that can be identified by the table’s primary key. Since the pk is an arbitrary integer value, we can’t logically expect applications outside our server to have access to the key values. Since email address are unique, we’ll all the client applications to search our table by email address and we’ll return the primary key of the record if the record is found.

Initially, I think you’d want to argue: “Hey, why not return the entire record?” Logically, that seems to make sense. However, I’d argue that if the client-side application already knows the customer’s email address, then the chances are better than good that they’d also already know the customer’s name. So, also in the interests of keeping it simple, we’re going to make the business case to return only the pk id value of the record if found in our standard structure: aryReturn.

Also, I’d think that the client application would want to call the search function before invoking an insert function – just to ensure that you’ll not be creating dups in the data table. That’s if your client application is well-formed and well-behaved. Since we can’t assume that the client application is going to be well-behaved, then we’ll want to invoke our own search method prior to an insert operation to ensure that we’re not creating duplicate records.

However, we did design the data table to prevent duplicate insertions based on the email address:

UNIQUE KEY `customerEmail` (`customerEmail`)

…so if a client application attempted to insert a new record into the table that had an email already in-use, mySQL will return an error preventing the insert operation from completing. Since we’re going to handle this from the application server code, we won’t be trapping and returning the mySQL-generated error.

So, add a new function to our specification:

  • searchCustomer() — function to search for an existing record based on the email address of the customer. Input parameter is a single value: the customer’s email address. function will search the table for email address – if found, return logical(true) in aryReturn['status'] and the record pk id in aryReturn['data']. If the record was not-found, return logical(false) in aryReturn['status']. If an error occurred during processing, return a logical(false) in aryReturn['status'] and the error message in aryReturn['data'].

Supporting Methods

In the course of designing the spec for our server methods, we’ve also identified the need for a function that will be used by the server-side methods, but will not be exposed to web-services. Think of this method as a private method – accessible only the web-services methods which are exposed to the client-side applications; the client-side applications will not be able to access methods that are not exposed to the WSDL, nor will these functions be visible on the nuSOAP server WSDL documentation.

The function we’re going to need is a function to validate the email address as a well-formed address:

  • validateEmail() — function not exposed to web-services that will validate the format of a user’s email address. Takes a single string variable as input and returns a boolean indicating if the input was well-formed or not.

This is a fairly standard function – no need to write it from scratch – we should be able to google this function and use it within our server code.

We’ve defined a total of five functions that we’ll need to write for the web-services server. The specification for each function is minimally defined but it’s enough for us to get started.

The next point of concern involves accessing the database and how, exactly, this will be accomplished…

Database Library

How we access the database is of critical importance. We’ll be using the mysqli extensions (improved mySQL interface) to take advantage of prepared statements. I’m going to provide you with a copy of the include file I use – this is a combination of a library provided by a user-comment from the documentation page on php.net’s mysqli_stmt::prepare and some of my own modifications. I’ll explain more about the library when we’re invoking it from our server-side source code. For now, please take it on-faith that this library is functional – it’s the one being called on the shallop.com site for this, and all other web-services tutorials:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/**
*   dbm.inc
*
*   This object class serves as a mysqli extenstion to the database
*   for obtaining and returning prepared statements as associative
*   arrays.
*/


include_once($_SERVER['DOCUMENT_ROOT'] . '/classes/env_config.inc');

class DB {
public $connection;
public $errorstring = "";
public $errorstate = false;

#establish db connection
public function __construct() {
# constants for connection stored in env_config.inc
$this->connection = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);

if(mysqli_connect_errno())  {
$this->errorstring = "Database Connect Error : " . mysqli_connect_error($mysqli);
$this->errorstate = true;
return false;
}
}

#store mysqli object
public function connect() {
return $this->connection;
}

#run a simple query and return via input param
public function singleValueSimpleQuery($query, $connection, &$result) {
$statement = $connection->stmt_init();
if (!$statement->prepare($query)) {
$this->errorstate = true;
$this->errorstring = printf("mysqli error: %s
"
, mysql_error());
return false;
} else {
$statement->execute();
$statement->bind_result($result);
$statement->fetch();
$statement->close();
}
return true;
}

#run a prepared query
public function runPreparedQuery($query, $params_r) {
$stmt = $this->connection->prepare($query);
$this->bindParameters($stmt, $params_r);

if ($stmt->execute()) {
return $stmt;
} else {
$this->errorstring = ("Error in stmt: " . mysqli_error($this->connection));
$this->errorstate = true;
return false;
}
}

# To run a select statement with bound parameters and bound results.
# Returns an associative array two dimensional array which u can easily
# manipulate with array functions.

public function preparedSelect($query, $bind_params_r, $doFetch="true") {
$select = $this->runPreparedQuery($query, $bind_params_r);
if ($doFetch) {
$fields_r = $this->fetchFields($select);

foreach ($fields_r as $field) {
$bind_result_r[] = &${$field};
}

$this->bindResult($select, $bind_result_r);

$result_r = array();
$i = 0;
while ($select->fetch()) {
foreach ($fields_r as $field) {
$result_r[$i][$field] = $$field;
}
$i++;
}
$select->close();
return $result_r;
} else {
# if doFetch was called with a false value, assume this
# was an insert or update operation and return the insert id
return $select->insert_id;
}
}

#takes in array of bind parameters and binds them to result of
#executed prepared stmt

private function bindParameters(&$obj, &$bind_params_r) {
call_user_func_array(array($obj, "bind_param"), $bind_params_r);
}

private function bindResult(&$obj, &$bind_result_r) {
call_user_func_array(array($obj, "bind_result"), $bind_result_r);
}

#returns a list of the selected field names

private function fetchFields($selectStmt) {
$metadata = $selectStmt->result_metadata();
$fields_r = array();
while ($field = $metadata->fetch_field()) {
# assign column names to array - convert column names to lower case.
$fields_r[] = strtolower($field->name);
}
return $fields_r;
}
} #end of class

/*

# An example of the DB class in use

$DB = new DB();
$var = 5;
$query = "SELECT abbr, name from books where id > ?" ;
$bound_params_r = array("i", $var);

$result_r = $DB->preparedSelect($query, $bound_params_r);\
$class->array = $result_r[0];

# for multiple bound parameters in the query:
# $result_r = $DB->preparedSelect($query, array("ss", "string1", "string2");

#loop thru result array and display result

foreach ($result_r as $result) {
echo $result['abbr'] . " : " . $result['name'] . "
" ;
}

*/


?>

Copy-and-paste this above code and save it to your include directory as dbi.class.inc — this how it will be referenced in the tutorial. If you change the name of the source file on your server, please make sure you identify the corresponding include statements in the code modules and make the corresponding name changes there as well.

This pretty much wraps things up for this phase of the tutorial so, let’s recap…

Summary

Since we successfully developed proof-of-concept in part 1 of this tutorial series, we’ve moved-on to program design and specification in part 2.

We’ve defined our database table that we’ll be using throughout the rest of the tutorial and we’ve defined the necessary methods/functions for our server-side code. We’ve spec’d out the functions in terms of scope, input and output parameters and error handling. We also identified the need for a support function that won’t be exposed to web-services, but will be used by the web services functions.

Finally, I provided you with the code for the database object using the mySQL improved extension. This code will be invoked by our server function and will be the access portal to our database for the server-side method calls.

We’re ready to start writing the server-side code methods and WSDL structures which we’ll do in part 3 of this series.

Until then, thank-you for stopping by and I hope this helps you!

Page optimized by WP Minify WordPress Plugin

Weather forecast by WP Wunderground & Denver Snow Removal