11 Using JSON and generating a JPEG image

This chapter shows how the JSON serialization format can be used for transferring data from a simple web service. The web service is called by a client that creates an image using PHP's GD extension.

This chapter contains the following topics:

Creating a Simple Web Service Returning JSON

Create a new PHP file ac_get_json.php containing:

<?php
 
/**
 * ac_get_json.php: Service returning equipment counts in JSON
 * @package WebService
 */
 
require('ac_db.inc.php');
 
if (!isset($_POST['username'])) {
    header('Location: index.php');
    exit;
}
 
$db = new \Oracle\Db("Equipment", $_POST['username']);
 
$sql = "select equip_name, count(equip_name) as cn
        from equipment
        group by equip_name";
$res = $db->execFetchAll($sql, "Get Equipment Counts");
 
$mydata = array();
foreach ($res as $row) {
    $mydata[$row['EQUIP_NAME']] = (int) $row['CN'];
}
 
echo json_encode($mydata);
 
?>

Note there is no authentication in this web service. It is "external" to the AnyCo application. All it requires is a username entry in the POST data.

The file queries the AnyCo Corp. equipment allocation and uses PHP's json_encode() to return the statistics in JSON format. The output returned by the web service is something like this, depending on which data you currently have in the EQUIPMENT table:

{"cardboard box":1,"pen":4,"computer":2,"telephone":3,"paper":3,"car":1}

Creating a JPEG image

Create a new PHP file ac_graph_img.php to call the web service and create a graph. The file initially contains:

<?php
 
/**
 * ac_graph_img.php: Create a JPEG image of the equipment allocation statistics
 *
 * Don't have any text or white space before the "<?php" tag because it will
 * be incorporated into the image stream and corrupt the picture.
 *
 * @package Graph
 */
 
define('WEB_SERVICE_URL', "http://localhost/ac_get_json.php");
 
session_start();
require('ac_equip.inc.php');
 
$sess = new \Equipment\Session;
$sess->getSession();
if (!isset($sess->username) || empty($sess->username)
    || !$sess->isPrivilegedUser()) {
    header('Location: index.php');
    exit;
}
$data = callservice($sess);
do_graph("Equipment Count", 600, $data);
 
// Functions
 
?>

Change the web service URL to match your system.

To this file add the callservice() function:

/**
 * Call the service and return its results
 *
 * @param Session $sess
 * @return array Equipment name/count array
 */
function callservice($sess) {
    // Call the web "service" to get the Equipment statistics
    // Change the URL to match your system configuration
    $calldata = array('username' => $sess->username);
    $options = array(
        'http' => array(
            'method'  => 'POST',
            'header'  => 'Content-type: application/x-www-form-urlencoded',
            'content' => http_build_query($calldata)
        )
    );
    $ctx = stream_context_create($options);
    $result = file_get_contents(WEB_SERVICE_URL, false, $ctx);
    if (!$result) {
        $data = null;
    } else {
        $data = json_decode($result, true);
 
        // Sort an array by keys using an anonymous function
        uksort($data, function($a, $b) {
            if ($a == $b)
                return 0;
            else
                return ($a < $b) ? -1 : 1;
            });
        }
    return($data);
}

This uses the PHP streams functionality to request the URL and get the statistics. The stream context includes the username as a post variable, which is required by the service.

The data is decoded from the JSON format and the array is sorted by name order. The second argument to PHP's uksort() function is an anonymous function that does the data comparison.

Edit ac_graph_img.php and add the function to create the image:

/**
 * Draw a bar graph, with bars projecting horizontally
 *
 * @param string $title The Graph's title
 * @param type $width Desired image width in pixels
 * @param array $items Array of (caption, value) tuples
 */
function do_graph($title, $width, $items) {
    $border = 50;             // border space around bars
    $caption_gap = 4;         // space between bar and its caption
    $bar_width = 20;          // width of each bar
    $bar_gap = 40;            // space between each bar
    $title_font_id = 5;       // font id for the main title
    $bar_caption_font_id = 5; // font id for each bar's title
 
    // Image height depends on the number of items
    $height = (2 * $border) + (count($items) * $bar_width) +
        ((count($items) - 1) * $bar_gap);
 
    // Find the horizontal distance unit for one item
    $unit = ($width - (2 * $border)) / max($items);
 
    // Create the image and add the title
    $im = ImageCreate($width, $height);
    if (!$im) {
        trigger_error("Cannot create image<br>\n", E_USER_ERROR);
    }
    $background_col = ImageColorAllocate($im, 255, 255, 255); // white
    $bar_col = ImageColorAllocate($im, 0, 64, 128);           // blue
    $letter_col = ImageColorAllocate($im, 0, 0, 0);           // black
    ImageFilledRectangle($im, 0, 0, $width, $height, $background_col);
    ImageString($im, $title_font_id, $border, 4, $title, $letter_col);

    // Draw each bar and add a caption
    $start_y = $border;
    foreach ($items as $caption => $value) {
        $end_x = $border + ($value * $unit);
        $end_y = $start_y + $bar_width;
        ImageFilledRectangle($im, $border, $start_y, $end_x, $end_y, $bar_col);
        ImageString($im, $bar_caption_font_id, $border,
            $start_y + $bar_width + $caption_gap, $caption, $letter_col);
        $start_y = $start_y + ($bar_width + $bar_gap);
    }
 
    // Output the complete image.
    // Any text, error message or even white space that appears before this
    // (including any white space before the "<?php" tag) will corrupt the
    // image data.  Comment out the "header" line to debug any issues.
    header("Content-type: image/jpg");
    ImageJpeg($im);
    ImageDestroy($im);
}

This function uses PHP's GD extension to create the graph. The default GD fonts are a bit clunky but new ones can be added. The output is a JPEG stream so the PHP file can be called anywhere in a web page's HTML code where you would otherwise include an image file.

In the AnyCo application, the image can be integrated by creating a new file ac_graph_page.php:

<?php
 
/**
 * ac_graph_page.php: Display a page containing the equipment graph
 * @package Graph
 */
 
session_start();
require('ac_equip.inc.php');
 
$sess = new \Equipment\Session;
$sess->getSession();
if (!isset($sess->username) || empty($sess->username)
         || !$sess->isPrivilegedUser()) {
    header('Location: index.php');
    exit;
}
 
$page = new \Equipment\Page;
$page->printHeader("AnyCo Corp. Equipment Graph");
$page->printMenu($sess->username, $sess->isPrivilegedUser());
 
echo <<<EOF
<div id='content'>
<img src='ac_graph_img.php' alt='Graph of office equipment'>
</div>
EOF;
 
$page->printFooter();
 
?>

Note:

The 'EOF;' token must be at the start of a line and not have trailing white space.

The image is included in a normal HTML img tag.

Load the AnyCo application in a browser and log in as Administrator. Click the Equipment Graph link in the left hand navigation menu. The graph is displayed.

JPEG Image for Equipment Graph

If the image doesn't display, it might be a problem in ac_graph_img.php due to text such as an error message or even because of white space before the <?php tag. This text will be included in the image stream and make the picture invalid. To help debug this kind of problem you could comment out the $session checks and also the header() call in ac_graph_img.php. Then to show the raw data of the image stream load the following link in a browser:

http://localhost/ac_graph_img.php

The JSON format is often used to efficiently transfer data between a browser and a PHP server. The ac_get_json.php web service could be used directly in many of the available JSON graphics libraries.