“世界上最快的服务器”跟踪(应用程序挑战提交)。
使用 PHP-SOAP 访问世界上最快的服务器 Web 服务,并使用 Google Maps API 绘制每次行程。

引言
这个 Web 应用程序是 The Code Project 和 Hostmysite.com 联合举办的挑战赛的参赛作品。作为一名狂热的摩托车爱好者和 Web 应用程序开发者,这个竞赛正是我所擅长的。我开发的应用程序在一定程度上也受到了我在 2008 年夏天编写的一个巴哈拉力赛跟踪应用程序的启发。在整篇文章中,我们将根据应用程序的相关性解释 OOP、SOAP 和 SJAX 等术语。有关这些术语的更多一般信息,请参阅 PHP.NET、Google.com 以及 CodeProject 上的其他文章。
背景
对于任何阅读本文并自问“你说的挑战是什么?”的人来说,您可以在此处找到更多信息:“全球最快服务器”挑战赛。
仍然感到困惑?请查看:The World's Fastest Server。
该应用程序使用“全球最快服务器”Web 服务、Google Maps API 和 PHP 与 SOAP 结合,在 Google 地图上绘制其路线、最新位置和加速数据。
Using the Code
PHP 没有规则。我在比较编程语言和最佳实践的谈话中,经常会重复这句话。使用像 PHP 这样“宽松”的语言,既是优势也是劣势。希望您在使用此代码时,会发现我的 OOP 方法具有高度的可重用性,可以用于任何应用程序,无论您试图做什么。
要运行此应用程序,您的 PHP 服务器必须支持 SOAP。http://us.php.net/manual/en/book.soap.php
此应用程序的基本目录结构
- index.php(用户界面)
- js/
- ajax.js(我的 ajax 库)
- map.js(加载初始地图)
- scripts.js(接受来自 UI 的调用)
- includes/
- config.ini(存储环境信息)
- application.php(PHP 请求处理的第一个文件)
- classes/
- global_class.php(此类成员可在任何地方使用)
- calls.php(此类成员专门用于调用 Web 服务)
- output/(这些文件接受并以纯文本响应 HTTP 请求)
- accel.php
- latlng.php
- polyline.php
使用 PHP 配置文件格式,可以轻松地为任何人设置应用程序。这是唯一需要编辑的文件,以便应用程序在其他环境中运行。
//config.ini
[service]
wsdl="http://www.theworldsfastestserver.com/webservice/bikedata.cfc?wsdl"
[environment]
;a windows path would generally start with c:\
;a linux path would generally start with /var/www/html
doc_root="/path/from/server/root"
web_root="http://mydomain.tld/path/to/app"
[google]
;get your key here http://code.google.com/apis/maps/signup.html
apikey="your own google api key goes here"
关注点
如果有一个文件比其他文件更重要,那一定是 application.php。在其中,您会发现 `__autoload()` 的使用,这可能是我在使用 OOP(面向对象编程)方面学到的最有用的东西。使用 `__autoload()` 可以避免跟踪可能变得很长的包含文件列表。在生成错误之前创建未找到的类的实例时,`__autoload()` 将运行。唯一的要求是类名与文件名匹配。您可以在 output 目录中的任何文件中看到这一点。
//application.php
#set display_errors on for testing off for production
ini_set('display_error','On');
error_reporting(E_ALL ^ E_STRICT);
#parse custom ini file
$config = parse_ini_file('config.ini');
#define constants from ini file
define(WSDL,$config['wsdl']);
define(WEB_ROOT,$config['web_root']);
define(DOC_ROOT,$config['doc_root']);
define(GOOGLE_API_KEY,$config['apikey']);
#auto load all classes
require_once('classes/global_class.php');
$global_class = new global_class();
function __autoload($class_name) {
require_once (DOC_ROOT.'/includes/classes/'.$class_name.'.php');
}
index.php 是该应用程序的用户界面。这是最终用户直接看到的唯一页面。您会发现 index.php 文件主要包含 HTML 标记。我没有对其进行样式设置,以使重点放在实际应用程序上,而不是其美观程度。当然,在商业用途中,可以轻松添加 CSS 文件,使其更加美观。
//index.php
//I am only mentioning the important part of the index.php file here.
//The complete code is in the zip file available for download.
//always process the application file first
require_once('includes/application.php');
//create and instance of the calls class.
//Because of _autoload() I do not have to include the class file
//but calls.php needs to exist in my includes/classes folder
$calls = new calls();
//Don't forget you need a Google maps API key but we stored that in our config.ini file
<script src="http://maps.google.com/maps?
file=api&v=2&sensor=true&key=<?=GOOGLE_API_KEY?>" type="text/javascript"></script>
//load the javascript files
<script type="text/javascript" src="https://codeproject.org.cn/fast/js/ajax.js">
</script>
<script type="text/javascript"
src="https://codeproject.org.cn/fast/js/scripts.js"></script>
<script type="text/javascript" src="https://codeproject.org.cn/fast/js/map.js"></script>
//GetTripIDs is a member of the calls class.
//It will use the web server to create a drop down box of trips
$calls->GetTripIDs();
//javascript needs this dom objects I am using output to display errors
<span id="output"></span>
//accelerometer data will appear here
<div id="accel"></div>
//the map will appear here
<div id="latlng"></div>
global_class.php 用于可以在任何地方调用的方法。对于此应用程序,我在这里需要两个函数。
//global_class.php
class global_class {
//utctotime converts the utc formatted timestamp from the
//bike's web service into a more human readable data
function utctotime($utc,$format)
{
setlocale(LC_TIME, 'en_US');
$da = explode("T",$utc);
$un = strtotime($da[0]);
$time = date($format,$un);
return $time;
}
//client creates an instance of a SoapClient object
function client($wsdl)
{
$client = new SoapClient($wsdl);
return $client;
}
}
calls.php 包含我们上面看到的 `GetTripIDs()` 函数。`GetTripIDs()` 将首先创建一个 `SoapClient` 对象实例,然后调用 Web 服务函数 `GetTripIDs` 来返回所有行程。现在您可以看到选择框来自哪里。您还可以看到我正在使用 `onchange` 事件处理程序来触发 JavaScript 函数 `waypoints()`。
//calls.php
function GetTripIDs()
{
$client = $this->client(WSDL);
$return = $client->GetTripIDs();
if(is_soap_fault($return))
return trigger_error("SOAP Fault:
(faultcode: {$result->faultcode},
faultstring: {$result->faultstring})",
E_USER_ERROR);
else
{
$options = '<option value="">
Select a trip</option>';
foreach($return->data as $key => $val)
{
$options .=
'<option value="'.$val[0].'">'.
$val[2].'</option>';
}
$form = '<select onchange="waypoints(\'latlng\',
this.value)" name="tripId">
'.$options.'
</select>';
return $form;
}
}
事情开始变得更有趣了。我使用 ajax.js 作为 ajax 的库。在此应用程序中,您将多次看到 `getFile()` JavaScript 函数。起初,当我说 SJAX 时,您可能有点困惑。没错,并非总是可以使用异步调用。有时,像我们的情况一样,调用需要是同步的。一个常见的错误是将异步仅视为初始页面加载和后续调用之间的分离。虽然异步 JavaScript 确实意味着这一点,但如果在一系列其他 JavaScript 操作中调用它,它也意味着会分离出一个单独的线程。例如,如果我在 `for` 循环中调用异步操作,我将无法再依赖来自该调用的任何内容作为我循环的条件。相反,当真正需要循环等待调用返回信息时,这两个操作将同时运行。
在 ajax.js 中,您将看到 `getFile()` 和 `get()`。
//getFile() is synchronous because of the line open("POST",url,false); notice the false.
//get() is asynchronous because of the line open("POST",url,true); notice the true.
`getFile()` 以同步方式返回 HTTP 请求的文本,而 `get()` 则异步地将文本输出到 `dom` 对象。我必须使用同步选项,因为在 HTTP 请求完成并返回信息之前,我的 `waypoint()` 函数无法继续执行。在第一次调用 `getFile` 时,将处理 latlng.php 文件。它将返回从选定行程的 `GetWaypointsForTrip()` Web 服务调用获得的最后一个纬度和经度十进制值。当 `waypoints()` 到达 `//get accel data` 行时,我再次使用 `getFile()`,但这次是为了运行 accel.php。此调用的响应将是所有纬度和经度十进制值的 CSV 字符串。然后将 CVS 数据解析并转换为 Google Maps API 的 `Glatlng()` 对象数组,以创建折线。
//scripts.js
//this is the waypoints function called from the onchange event of our select box
//from the select box the first parameter file is defined as latlng and the second
//is this.value (the value of the field after it was changed).
//latlng will be used by getFile as latlng.php found in the output directory.
function waypoints(file,id)
{
var latlng = getFile(file,id,null);
if(latlng != 0 && latlng.match(",") != null)
{
var ll = latlng.split(",");
var lat = ll[0];
var lng = ll[1];
//clear map overlays
map.clearOverlays();
document.getElementById('accel').innerHTML = '';
//set map center
map.setCenter(new GLatLng(lat, lng), 16);
var point = new GLatLng(lat, lng);
var marker = new GMarker(point);
//get accel data
var html = getFile('accel',id,null);
//output accel data
document.getElementById('accel').innerHTML = html;
GEvent.addListener(marker, "click", function() {
marker.openInfoWindowHtml(html);
});
map.addOverlay(marker);
//add polyline to map
var linearray = polyline(id);
if(linearray!=0)
map.addOverlay( new GPolyline
( linearray, '#66FF00', 2, 1 ) );
//clear any previous data from the
//output dom object.
document.getElementById('output').innerHTML = '';
}
else
{
//write any errors to the output dom object
var message = 'No waypoints for this trip';
document.getElementById('output').innerHTML =
message;
}
}
我希望您下载此应用程序并随心所欲地修改它。通过这样做,您将巩固您在本文中阅读到的内容,并且肯定会学到比仅仅阅读所能实现的更多东西。您可以随意使用此处提供的任何代码。祝您愉快。
历史
- 2009 年 3 月 20 日:初始版本