9.3. Creating AGI Scripts in PHP
We promised we'd cover several languages, so
let's go ahead and see what an AGI script in PHP looks like. The
fundamentals of AGI programming still apply; only the programming
language has changed. In this example, we'll write an AGI script to
download a weather report from the Internet and deliver the
temperature, wind direction, and wind speed back to the caller.
#!/usr/bin/php -q
<?php
The first line tells the system to use the PHP
interpreter to run this script. The -q option turns off
HTML error messages. You should ensure that there aren't any extra
lines between the first line and the opening PHP tag, as they'll
confuse Asterisk.
# change this to match the code of your particular city
# for a complete list of US cities, go to
# http://www.nws.noaa.gov/data/current_obs/
$weatherURL="http://www.nws.noaa.gov/data/current_obs/KMDQ.xml";
This tells our AGI script where to go to get the
current weather conditions. In this example, we're getting the
weather for Huntsville, Alabama. Feel free to visit the web site
listed above for a complete list of stations throughout the United
States of America.
# don't let this script run for more than 60 seconds
set_time_limit(60);
Here, we tell PHP not to let this program run
for more than 60 seconds. This is a safety net, which will end the
script if for some reason it takes more than 60 seconds to run.
# turn off output buffering
ob_implicit_flush(false);
This command turns off output buffering, meaning
that all data will be sent immediately to the AGI interface and
will not be buffered.
# turn off error reporting, as it will most likely interfere with
# the AGI interface
error_reporting(0);
This command turns off all error reporting, as
it can interfere with the AGI interface. (You might find it helpful
to comment out this line during testing.)
# create file handles if needed
if (!defined('STDIN'))
{
define('STDIN', fopen('php://stdin', 'r'));
}
if (!defined('STDOUT'))
{
define('STDOUT', fopen('php://stdout', 'w'));
}
if (!defined('STDERR'))
{
define('STDERR', fopen('php://stderr', 'w'));
}
This section of code ensures that we have open
file handles for STDIN, STDOUT, and
STDERR, which will handle all communication between
Asterisk and our script.
# retrieve all AGI variables from Asterisk
while (!feof(STDIN))
{
$temp = trim(fgets(STDIN,4096));
if (($temp == "") || ($temp == "\n"))
{
break;
}
$s = split(":",$temp);
$name = str_replace("agi_","",$s[0]);
$agi[$name] = trim($s[1]);
}
Next, we'll read in all of the AGI variables
passed to us by Asterisk. Using the fgets command in PHP
to read the data from STDIN, we'll save each variable in
the hash called $agi. Remember that we could use these
variables in the logic of our AGI script, although we won't in this
example.
# print all AGI variables for debugging purposes
foreach($agi as $key=>$value)
{
fwrite(STDERR,"-- $key = $value\n");
fflush(STDERR);
}
Here, we print the variables back out to
STDERR for debugging purposes.
#retrieve this web page
$weatherPage=file_get_contents($weatherURL);
This line of code retrieves the XML file from
the National Weather Service and puts the contents into the
variable called $weatherPage. This variable will be used
later on to extract out the pieces of the weather report that we
want.
#grab temperature in Fahrenheit
if (preg_match("/<temp_f>([0-9]+)<\/temp_f>/i",$weatherPage,$matches))
{
$currentTemp=$matches[1];
}
This section of code extracts the temperature
(in Fahrenheit) from the weather report, using the
preg_match command. This command uses Perl-compatible
regular expressions to extract out the needed
data.
#grab wind direction
if (preg_match("/<wind_dir>North<\/wind_dir>/i",$weatherPage))
{
$currentWindDirection='northerly';
}
elseif (preg_match("/<wind_dir>South<\/wind_dir>/i",$weatherPage))
{
$currentWindDirection='southerly';
}
elseif (preg_match("/<wind_dir>East<\/wind_dir>/i",$weatherPage))
{
$currentWindDirection='easterly';
}
elseif (preg_match("/<wind_dir>West<\/wind_dir>/i",$weatherPage))
{
$currentWindDirection='westerly';
}
elseif (preg_match("/<wind_dir>Northwest<\/wind_dir>/i",$weatherPage))
{
$currentWindDirection='northwesterly';
}
elseif (preg_match("/<wind_dir>Northeast<\/wind_dir>/i",$weatherPage))
{
$currentWindDirection='northeasterly';
}
elseif (preg_match("/<wind_dir>Southwest<\/wind_dir>/i",$weatherPage))
{
$currentWindDirection='southwesterly';
}
elseif (preg_match("/<wind_dir>Southeast<\/wind_dir>/i",$weatherPage))
{
$currentWindDirection='southeasterly';
}
The wind direction is found through the use of
preg_match (located in the wind_dir tags) and is
assigned to the variable $currentWindDirection.
#grab wind speed
if (preg_match("/<wind_mph>([0-9.]+)<\/wind_mph>/i",$weatherPage,$matches))
{
$currentWindSpeed = $matches[1];
}
Finally, we'll grab the current wind speed and
assign it to the $currentWindSpeed variable.
# tell the caller the current conditions
if ($currentTemp)
{
fwrite(STDOUT,"STREAM FILE temperature \"\"\n");
fflush(STDOUT);
$result = trim(fgets(STDIN,4096));
checkresult($result);
fwrite(STDOUT,"STREAM FILE is \"\"\n");
fflush(STDOUT);
$result = trim(fgets(STDIN,4096));
checkresult($result);
fwrite(STDOUT,"SAY NUMBER $currentTemp \"\"\n");
fflush(STDOUT);
$result = trim(fgets(STDIN,4096));
checkresult($result);
fwrite(STDOUT,"STREAM FILE degrees \"\"\n");
fflush(STDOUT);
$result = trim(fgets(STDIN,4096));
checkresult($result);
fwrite(STDOUT,"STREAM FILE fahrenheit \"\"\n");
fflush(STDOUT);
$result = trim(fgets(STDIN,4096));
checkresult($result);
}
if ($currentWindDirection && $currentWindSpeed)
{
fwrite(STDOUT,"STREAM FILE with \"\"\n");
fflush(STDOUT);
$result = trim(fgets(STDIN,4096));
checkresult($result);
fwrite(STDOUT,"STREAM FILE $currentWindDirection \"\"\n");
fflush(STDOUT);
$result = trim(fgets(STDIN,4096));
checkresult($result);
fwrite(STDOUT,"STREAM FILE wx/winds \"\"\n");
fflush(STDOUT);
$result = trim(fgets(STDIN,4096));
checkresult($result);
fwrite(STDOUT,"STREAM FILE at \"\"\n";)
fflush(STDOUT);
$result = trim(fgets(STDIN,4096));
checkresult($result);
fwrite(STDOUT,"SAY NUMBER $currentWindSpeed \"\"\n");
fflush(STDOUT);
$result = trim(fgets(STDIN,4096));
checkresult($result);
fwrite($STDOUT,"STREAM FILE miles-per-hour \"\"\n");
fflush(STDOUT);
$result = trim(fgets(STDIN,4096));
checkresult($result);
}
Now that we've collected our data, we can send
AGI commands to Asterisk (checking the results as we go) that will
deliver the current weather conditions to the caller. This will be
achieved through the use of the STREAM FILE and SAY
NUMBER AGI commands.
We've said it before, and we'll say it again:
when calling AGI commands, you must pass in all of the required
arguments. In this case, both STREAM FILE and SAY
NUMBER commands require a second argument; we'll pass empty
quotes escaped by the backslash character.
You should also notice that we call the
fflush command each time we write to STDOUT.
While this is arguably redundant, there's no harm in ensuring that
the AGI command is not buffered and is sent immediately to
Asterisk.
function checkresult($res)
{
trim($res);
if (preg_match('/^200/',$res))
{
if (! preg_match('/result=(-?\d+)/',$res,$matches))
{
fwrite(STDERR,"FAIL ($res)\n");
fflush(STDERR);
return 0;
}
else
{
fwrite(STDERR,"PASS (".$matches[1].")\n");
fflush(STDERR);
return $matches[1];
}
}
else
{
fwrite(STDERR,"FAIL (unexpected result '$res')\n");
fflush(STDERR);
return -1;
}
}
The checkresult function is identical
in purpose to the checkresult subroutine we saw in our
Perl example. As its name suggests, it checks the result that
Asterisk returns whenever we call an AGI command.
?>
At the end of the file, we have our closing PHP
tag. Don't place any whitespace after the closing PHP tag, as it
can confuse the AGI interface.
We've now covered two different languages, in
order to demonstrate the similarities and differences of
programming an AGI script in PHP as opposed to Perl. The following
things should be remembered when writing an AGI script in PHP:
-
Invoke PHP with the -q switch; it turns
off HTML in error messages.
-
Turn off the time limit, or set it to a
reasonable value (newer versions of PHP automatically disable the
time limit when PHP is invoked from the command line).
-
Turn off output buffering with the
ob_implicit_flush(false) command.
-
Open file handles to STDIN,
STDOUT, and STDERR (newer versions of PHP may
have one or more of these file handles already openedsee the code
above for a slick way of making this work across most versions of
PHP).
-
Read variables from STDIN using the
fgets function.
-
Use the fwrite function to write to
STDOUT and STDERR.
-
Always call the fflush function after
writing to either STDOUT or STDERR.
9.3.1. The PHP AGI Library
For advanced AGI programming in PHP, you may
want to check out the PHPAGI project at http://phpagi.sourceforge.net.
It was originally written by Matthew Asham and is being developed
by several other members of the Asterisk community.
|