PHP is for more than building Web pages, even though that's the way it's normally looked on. Going back to the section on structure, the opportunity to use PHP as a Web service script is another important reason to separate functionality from HTML – really important in the current technology environment. Think about it this way: write code that returns the requested data, leaving it up to the caller to format that data for a Web page or returning as a Web service.
HTTP Content Types
When coding PHP server pages, the output of the script is HTML content. The first output from the script must begin an HTML document. In examples here, the PHP scripts often start with <!DOCTYPE html>, often followed immediately by the <head> tag. This results in a content type that tells the browser to display HTML. If any output gets to the Web server before the document headers, PHP gives errors that can be hard to find. That's why it's safer to not close PHP scripts with ?>, because any lines after that will go directly to the output.

Content type, Internet media type and MIME type all refer to the same thing and of course, HTML isn't the only one. The response from a PHP script could be returned as XML, JSON or any other type, even binary formats. All that is necessary is to use the header function to indicate the type of the output, or response body. (Requests and responses have headers and an optional body.)
<?php
header(“Content-Type: text/xml”);
echo “<?xml version="1.0" encoding="utf-8"?><foobar><hello>World</hello></foobar>”;
?>

The Web server will supply other necessary HTTP headers when it sends the response.

It's possible to add custom HTTP headers using header(). They can be used to pass information back about the request that is not part of the actual response, like timing information. However, the most important information – besides real data – to send back is the HTTP response. If you've ever seen a “404 Not Found” in your browser, 404 is the response code and signifies a particular error. Normally, when serving a Web page, Apache sets the default HTTP response as “200 OK”, but the response code and message can be set in PHP using header(). The most usual responses, other than “200 OK”, are “404 Not Found” and “500 Internal Server Error”. If a known HTTP error code is used, browsers will normally display something informative.
<?php
header("HTTP/1.0 503 Service Unavailable");

A PHP Web Service
PHP supplies function for converting to and from JSON. This makes PHP a great option for servicing AJAX requests, for instance. Let's write a fictitious service that returns customer data. Without a real database, the customer data will have to be mocked.
$customers = array (100 => array (
'name' => "Acme Inc.",
'addr' => array ('street' => "123 Main St",
'city' => "Boston",
'state' => "MA",
'zip' => "11111"
)
)
);
function getCustomer ($id) {
global $customers;
if (array_key_exists ($id, $customers))
return $customers[$id];
else
throw new OutOfBoundsException ("Customer not found: " . $id);
}
We have a customer array that could represent one of the rows returned by a database query for customers. There is only one customer with the customer ID of 100. The getCustomer function returns the customer that matches the argument $id. If the customer doesn't exist, the function throws an Exception that indicates the problem.
Now, add the code that will interpret the HTTP request and respond.
if ($_SERVER ['REQUEST_METHOD'] == 'GET') {
if ($customer_id = filter_input(INPUT_GET, 'cid', FILTER_SANITIZE_NUMBER_INT)) {
header ("Content-Type: text/plain");
print_r (getCustomer ($customer_id));
}
else {
throw new InvalidArgumentException ("Requires a customer id \"?cid=nn\"");
}
}
Since this service should only accept HTTP GET requests of the form http://my.url?cid=customer_id, we check for that and filter/sanitize the querystring parameter “cid”. filter_input returns false or null if it can't interpret an integer from the input. When run from the browser, this prints the returned customer array. Another way to call the service is from the command line with cURL. cURL is a useful tool for testing Web services, and performing a GET request is simple.
PS C:\Users\grantwparks> curl "http://vaio.web.gwp/public/customer.php?cid=100"
Array
(
[name] => Acme Inc.
[addr] => Array
(
[street] => 123 Main St
[city] => Boston
[state] => MA
[zip] => 11111
)
)
Of course, a string is not the best way to return data from a service. The two most common formats are XML and JSON. To return the customer data to a client as JSON, the PHP array needs to be converted to JSON format and the service needs to indicate the type of data being returned. All that requires is changing the two lines that return the response:
if ($customer_id = filter_input(INPUT_GET, 'cid', FILTER_SANITIZE_NUMBER_INT)) {
header ("Content-Type: application/json");
echo json_encode (getCustomer ($customer_id));
}
Called from cURL
PS C:\Users\grantwparks> curl "http://vaio.web.gwp/public/customer.php?cid=100"
{"name":"Acme Inc.","addr":{"street":"123 Main St","city":"Boston","state":"MA","zip":"11111"}}
The service can also be called from another PHP script on the server side.
$json = file_get_contents("http://vaio.web.gwp/public/customer.php?cid=100");
echo $json;
$customer = json_decode ($json, true); // turn it into an array
print_r ($customer);
{"name":"Acme Inc.","addr":{"street":"123 Main St","city":"Boston","state":"MA","zip":"11111"}}
Array (
[name] => Acme Inc.
[addr] => Array (
[street] => 123 Main St
[city] => Boston
[state] => MA
[zip] => 11111
)
)
The optional true argument to json_decode() returns an array instead of a PHP object. There is a problem in how the code is structured, though. Since this is a service, any errors have to be communicated back to the caller. If the service is called with an invalid customer ID, an Exception is thrown and the format of the script output is invalid. This means the header for JSON content should always be output and the code needs to be wrapped with try/catch.
header ("Content-Type: application/json");
try {
if ($_SERVER ['REQUEST_METHOD'] == 'GET') {
if ($customer_id = filter_input(INPUT_GET, 'cid', FILTER_SANITIZE_NUMBER_INT)) {
echo json_encode (getCustomer ($customer_id));
}
else {
throw new InvalidArgumentException ("Requires a customer id \"?cid=nn\"");
}
}
}
catch (Exception $Ex) {
printf ('{ "customer": {}, "error": {"message": %s} }', $Ex->getMessage());
}
PS C:\Users\grantwparks> curl -G "http://vaio.web.gwp/public/customer.php"
{ "customer": {}, "error": {"message": Requires a customer id "?cid=nn"} }
Now the response is in JSON format. You might be wondering about the HTTP response code discussed earlier. In the last few examples, it has defaulted to 200, even in this last case of an error. For some reason, many organizations prefer to communicate the error in the response body instead of using error codes. A more standard way of doing it would be something like:
catch (Exception $Ex) {
header ("HTTP/1.0 400 Bad Request " . $Ex->getMessage());
}
Using the header to communicate the error complies with standards and allows client libraries to deal with the error instead of an application. For instance, in a browser console:

PHP Web Service Proxy
Let's combine some of the things from this and the last section to do something fun. Let’s write a service that returns this year's NFL Football schedule for a particular team in JSON format. We'll pass the team as a querystring argument and respond with a JSON array of dates and opponents. This page gave me the idea http://sports.yahoo.com/nfl/teams/den/schedule. The script will have to load this page (for the chosen team) and report the dates and teams. (There's no error handling in this code. It's just an example of how easy it can be to create useful scripts.)
DOMDocument can be used to load the HTML into a DOM, after turning off warnings:
error_reporting(error_reporting() & ~E_WARNING);
$dom = new DOMDocument ();
$dom->loadHTMLFile ("http://sports.yahoo.com/nfl/teams/" . $_GET['team'] . "/schedule");
The $_GET array is an associative array of the querystring arguments, so team= has to be one of the understood abbreviations on the Yahoo Web site. And, briefly introduce the DOMXPath class:
$xpath = new DOMXPath ($dom);
$weeks = $xpath->query ('//table[@summary="Regular Season Games"]//tbody/tr');
A DOMXPath is created from an existing DOMDocument and allows querying the document using Xpath. The query expression usually takes a little trial and error, and this one finds all the games in the regular season. To be more specific, it finds all the <tr> tags in the body of the HTML table with “Regular Season” in the attribute named “summary”.
The Xpath query returns a DOMNodeList that can be iterated through. From each table row, a regular PHP array can be built.
$weekArray = array ();
foreach ($weeks as $week) {
$date = $week->childNodes->item(2);
$opponent = $week->getElementsByTagName ('a')->item(0);
$weekArray[] = array (
'date' => $date ? $date->nodeValue : "Bye",
'opponent' => $opponent ? $opponent->nodeValue : "Bye"
);
}
Finding the interesting data in the cells of each row took a little trial and error also. getELementsByTagName and childNodes return DOMNodeLists. There's a special check for what's known as the “bye” week when a team gets the week off. Then json_encode() can be used to turn that array into the data we want to return.
header ("HTTP/1.0 200 OK");
header ("Content-Type: application/json");
echo json_encode ($weekArray);
If this code is put together and called like this:
PS C:\Users\grantwparks> curl "http://vaio.web.gwp/public/nfl.php?team=buf"
[{"date":"Sun, Sep 9","opponent":"NY Jets"},{"date":"Sun, Sep 16","opponent":"Kansas City"},{"date":"Sun, Sep 23","opponent":"Cleveland"},{"date":"Sun, Sep 30","opponent":"New England"},{"date":"Sun, Oct 7","opponent":"San Francisco"},{"date":"Sun, Oct 14","opponent":"Arizona"},{"date":"Sun, Oct 21","opponent":"Tennessee"},{"date":"Bye","opponent":"Bye"},{"date":"Sun, Nov 4","opponent":"Houston"
},{"date":"Sun, Nov 11","opponent":"New England"},{"date":"Thu, Nov 15","opponent":"Miami"},{"date":"Sun, Nov 25","opponent":"Indianapolis"},{"date":"Sun, Dec 2","opponent":"Jacksonville"},{"date":"Su
n, Dec 9","opponent":"St. Louis"},{"date":"Sun, Dec 16","opponent":"Seattle"},{"date":"Sun, Dec 23","opponent":"Miami"},{"date":"Sun, Dec 30","opponent":"NY Jets"}]
A very small script to return useful data from another Web page.