The ultimate goal of using these APIs is to allow our clients who manage online businesses to automatically order postage and print shipping labels for several hundred incoming orders within a matter of minutes; all from their own websites and online control-panels. The shipping details, parcel weight, size, mail class, etc. for each incoming order are sent to Endicia's Label Server APIs with postage already paid. Endicia then sends back a bunch of images (labels), which we then compile into a PDF that can be fed directly to the client's printer (ideally a label printer).
The set of classes I've written that allow PHP to communicate with the Endicia Label Server's APIs totals over 5000 lines of code. Obviously, that's too large to bother posting here. Instead, I've decided to highly-condense the concept of these classes down to a bare minimum in hopes that it might provide the reader with some insight into proper OO techniques.
I'm going to start by posting a “condensed” class by itself, and will then explain each method (i.e. function). I've had to break a few of my own coding standards with this condensed class so that it would be suitable for posting. But for the record, under normal production circumstances I strictly follow the PEAR Coding Standards.
In a nutshell, this class:
- * Takes a bunch of data we provide it.
* Verifies the data is correct.
* Cleans the data if necessary.
* Compiles data into an XML statement.
* Sends XML statement to the applicable server through cURL's POST method.
* Parses the cURL response.
Ultimately, our goal would be to send shipment details to the API server, and receive a positive response back that contains our label image (base64 encoded), postage rate, tracking number, etc.
- Code: Select all
<?php
class GetPostageLabel
{
// URL to send cURL request to
private $_url = 'http://www.example-address-to-api.com';
// Data to send
private $_data = array();
/**
* Return address.
*/
public function setFrom($name, $address1, $address2, $city, $state, $postal, $country)
{
$this->_data['FromName'] = $name;
$this->_data['FromAddress1'] = $address1;
$this->_data['FromAddress2'] = $address2;
$this->_data['FromCity'] = $city;
$this->_data['FromState'] = $state;
$this->_data['FromPostal'] = $postal;
$this->_data['FromCountry'] = $country;
}
/**
* Recipient's address.
*/
public function setTo($name, $address1, $address2, $city, $state, $postal, $country)
{
$this->_data['ToName'] = $name;
$this->_data['ToAddress1'] = $address1;
$this->_data['ToAddress2'] = $address2;
$this->_data['ToCity'] = $city;
$this->_data['ToState'] = $state;
$this->_data['ToPostal'] = $postal;
$this->_data['ToCountry'] = $country;
}
/**
* Send the address data.
*/
public function sendRequest()
{
// Valid data
if ($this->_verifyData()) {
// Scrub data
$this->_scrubData();
// Compile XML request
$request = $this->_compileXML();
// Execute cURL, send XML request
$response = $this->_sendCURL($request);
// Successfully sent
if ($response) {
// PARSE RESPONSE
// RETURN TRUE IF RESPONSE ACCEPTABLE
// RETURN FALSE IF RESPONSE CONTAINS ERROR
// Failed to send
} else {
return false;
}
// Invalid data
} else {
return false;
}
}
/**
* Verify we have all the data we need and that it's correct.
*/
private function _verifyData()
{
if (strlen($this->_data['FromCountry']) != 2) {
return false;
}
// MORE DATA VERIFICATION HERE
return true;
}
/**
* This method is called after we've verified the data. Now let's clean the data.
*/
private function _scrubData()
{
// Convert ZIP+4 (i.e. 20500-0003) to ZIP5 (i.e. 20500)
$this->_data['ToPostal'] = substr($this->_data['ToPostal'], 0, 5);
// MORE DATA SCRUBBING HERE
}
/**
* Compile the XML data for the cURL request.
*/
private function _compileXML()
{
$request = '
<LabelRequest>
<FromName>' . $this->_data['FromName'] . '</FromName>
<FromAddress1>' . $this->_data['FromAddress1'] . '</FromAddress1>
<FromAddress2>' . $this->_data['FromAddress2'] . '</FromAddress2>
<FromCity>' . $this->_data['FromCity'] . '</FromCity>
<FromState>' . $this->_data['FromState'] . '</FromState>
<FromPostal>' . $this->_data['FromPostal'] . '</FromPostal>
<FromCountry>' . $this->_data['FromCountry'] . '</FromCountry>
<ToName>' . $this->_data['ToName'] . '</ToName>
<ToAddress1>' . $this->_data['ToAddress1'] . '</ToAddress1>
<ToAddress2>' . $this->_data['ToAddress2'] . '</ToAddress2>
<ToCity>' . $this->_data['ToCity'] . '</ToCity>
<ToState>' . $this->_data['ToState'] . '</ToState>
<ToPostal>' . $this->_data['ToPostal'] . '</ToPostal>
<ToCountry>' . $this->_data['ToCountry'] . '</ToCountry>
</LabelRequest>
';
// Return XML data
return $request;
}
/**
* Initiate cURL and send the actual request.
*/
private function _sendCURL($request)
{
// Start cURL
$handle = curl_init();
// Prepare cURL
curl_setopt($handle, CURLOPT_URL, $this->_url);
curl_setopt($handle, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($handle, CURLOPT_POST, true);
curl_setopt($handle, CURLOPT_POSTFIELDS, $request);
// Execute cURL
$response = curl_exec($handle);
// Close cURL
curl_close($handle);
// Return false or $response (XML)
return $response;
}
}
?>
Don't panic, that's only 150 lines of code. I recommend that you copy and paste the code above into your editor/IDE so you can see it with proper syntax highlighting. Then after a few minutes of study it should start to make sense.
- Code: Select all
<?php
/**
* Return address.
*/
public function setFrom($name, $address1, $address2, $city, $state, $postal, $country)
{
$this->_data['FromName'] = $name;
$this->_data['FromAddress1'] = $address1;
$this->_data['FromAddress2'] = $address2;
$this->_data['FromCity'] = $city;
$this->_data['FromState'] = $state;
$this->_data['FromPostal'] = $postal;
$this->_data['FromCountry'] = $country;
}
?>
setFrom()
setTo()
This is a pretty crippled function, but it'll do for the purpose of this tutorial. We're basically storing everything in the private $_data array of the class. The setTo() function works just like setFrom().
Another option would be instead of storing everything in the $_data array (which you'll see at the top of the class), we could declare a bunch of private variables at the top of the class, like such:
private $_fromName;
private $_fromAddress1;
private $_fromAddress2;
private $_fromCity;
etc...
The reason I've opted for using a single array ($_data) instead of a bunch of variables is so we instantly know that anything inside this array is to be sent through cURL. If I made each item a variable instead of an array key, its purpose as data for the API request could easily get confused with things like the $_url, and about a dozen or so other variables that I've hidden for the sake of keeping this tutorial simple.
- Code: Select all
<?php
/**
* Send the address data.
*/
public function sendRequest()
{
// Valid data
if ($this->_verifyData()) {
// Scrub data
$this->_scrubData();
// Compile XML request
$request = $this->_compileXML();
// Execute cURL, send XML request
$response = $this->_sendCURL($request);
// Successfully sent
if ($response) {
// PARSE RESPONSE
// RETURN TRUE IF RESPONSE ACCEPTABLE
// RETURN FALSE IF RESPONSE CONTAINS ERROR
// Failed to send
} else {
return false;
}
// Invalid data
} else {
return false;
}
}
?>
sendRequest()
_verifyData()
_scrubData()
_compileXML()
_sendCURL()
1. We verify the data using the _verifyData() method.
- a) If the data is invalid, we return false.
- b) If the data is valid, we continue...
2. Scrub the data using the _scrubData() method.
3. Compile the data into an XML statement using the _compileXML() method.
4. Attempt to send the XML statement through cURL to the server using the _sendCURL() method.
- a) If the cURL request failed, we return false.
- b) If the cURL request was successful, we continue...
5. Parse the cURL response to see if the API server sent back any errors or problems.
- a) If we're happy with the API server's reply, we store whatever data we need (i.e. store our tracking numbers, postage cost, label images, etc.) and return true.
- b) If we're not happy with the API server's reply, we return false.
An uber simple usage of what we just made:
- Code: Select all
<?php
$label = new GetPostageLabel();
$label->setFrom('Zorro', '555 No Street', 'Morristown', 'TN', '37814', 'US')
$label->setTo('Obamanation', '1600 Pennsylvania Avenue NW', 'Washington', 'DC', '20500-0003', 'US');
if ($label->sendRequest()) {
$label->putLabel('myfolder/label.png'); // Function not shown in examples
// Do other stuff
} else {
$label->getErrors(); // Function not shown in examples
}
?>
Note: Obviously, none of this code will actually “work.” We need a lot more code to actually communicate with the real API.
What You Can Learn From This Example
Any PHP programmer will tell you that the sendRequest() function could also contain all the code of the _verifyData(), _scrubData(), and _sendCURL() functions. But doing that would simply be bad coding. Proper object oriented programming separates code based upon the code's functionality and purpose. Think of The Matrix movies where each “program” had a “purpose” in the overall scope of things.
Summary
- * Each function serves a distinct purpose.
* We've separated public and private functions correctly (think of private functions as “helpers” to public functions).
* No public variables inside class. We've used set() and get() functions to insert and retrieve data from the scope of the class.