为您的Web项目构建一个简单的JSON控制器
您的下一个 PHP/MySQL 项目可能与您最近完成的十几个项目类似:建立一个 MySQL 数据库,创建包含 HTML 的 PHP 视图,根据需要添加 JavaScript 代码和 CSS 文件,连接到数据库,从数据库提取内容来填充视图,等等。如果您熟悉 web 开发,您一定知道分隔功能代码的好处。例如,您知道要避免直接在视图中输入原始 SQL 查询,不会在从数据库提取数据的函数或类中混淆 HTML 标记。
但是,有时,您的项目可能扩展到您的正常 PHP/MySQL 舒适水平之外。例如,您可能不仅拥有需要来自一个数据库的数据的常规 web 视图,还拥有外部应用程序(比如 Facebook),甚至还拥有访问相同数据的移动设备(比如智能手机)。
您可能会发现自己身陷这样一种情况:数据库更改,或者要求您处理某种类型的 XML 存储库。在这些情况下,您对 MySQL 的盲目依赖可能会阻碍您完成项目的工作。
可以考虑将一个 RESTful JSON 控制器放置到您的项目中,将它用作一个虚拟交通警察,负责发送请求并接收来自您的数据源的响应。本文将介绍 REST 和 JSON 的基础知识,并展示一种建立控制器的方法。其结果是从一个数据源检索数据的简单方法,检索的数据采用标准化的格式,可以使用 PHP 或 JavaScript 代码轻松解析。
什么是 REST?
在一个典型的 REST 架构中,一个客户机发送一个请求到服务器,服务器使用请求资源的一个表示来进行响应。资源可以是任何信息对象,比如数据库或文档,它的表示通常是一个格式化的文档(通常是 XML 或 JSON),充当它的当前或被请求状态的一个快照。
REST 资源通常使用有意义的 URLs 标识,这些 URLs 接受不同的请求动词 GET、POST、PUT 和 DELETE。这些动词有点类似于许多开发人员都熟悉的 create-retrieve-update-delete (CRUD) 模型。
例如,如果您想检索数据,则使用 GET 请求;要创建数据,则使用 POST 请求;要更新数据,则使用 PUT 请求;最后,要删除数据,则使用 DELETE 请求。
另一个需要考虑的重要因素是响应。RESTful 服务通常在它的响应中提供两个有意义的组件:响应主体本身和一个状态码。许多 REST 服务实际上允许用户指定一个响应格式(比如 XML、CSV、序列化的 PHP 对象或纯文本),方法有两种:一是发送一个 ACCEPT 参数;二是指定一个文件扩展名(例如,/api/users.xml 或 /api/users.json)。其他 REST 服务器,比如您将在这里实现的服务器,拥有硬编码的响应格式。这些格式同样可以接受,只要它们已经有文档记载。
响应代码往往是 HTTP 状态码。这种模式的优点是可以使用知名的现有状态码来标识错误或成功。状态码 201(CREATED)是一个成功 POST 请求的完美响应。错误码 500 表明在您所处的这端(服务端)上发生了错误,但错误码 400 表明客户端上出现了失败(BAD REQUEST)。如果服务器出现故障,将发送错误码 503(SERVICE UNAVAILABLE)。
研究一下下面这个示例:一个应用程序拥有的一个数据源包含一些用户信息,名、姓、邮件地址、以及Twitter 帐户。如果您正在设置一个典型的 PHP 应用程序,您需要创建一个 mysql_query() 包装器来使用一个 SQL 查询从数据库提取一个清单。您还需要编写一些 PHP 代码,用于调用那个函数并循环结果集,以便在应用程序视图中显示数据。
一个更简单的方法是设置一个简单的 REST 控制器,该控制器允许一个针对 /users/list 的、不带任何参数的 GET 请求,然后调用适当的数据库函数并返回一个 JSON 格式的清单。接下来,您的应用程序可以解码那个 JSON 数据,以任何必要的方式循环该数据,以便显示数据内容。
另外,您可以通过测试检查是否有任何参数被发送到 /users/list。例如,如果您发送一个 GET 请求到 /users/list/1,那么响应将只包含 ID 为 1 的用户的细节。除 JSON 格式外,您甚至可以允许其他格式,比如 XML、CSV 和的 PHP 对象。
一个 RESTful JSON 控制器对于您的开发工作的作用并非仅仅是在视图和数据源之间放置一个额外的功能层。想想看,您的基本 PHP 视图也许不是请求信息的惟一组件。例如,您可能会使用 jQuery 通过一个 Ajax 接口请求数据,或者,您的用户可能会通过一部智能手机或一个 Facebook 应用程序请求数据。
在这些情况下,一个接收请求并以一种容易理解(和预测)的格式提供响应的 RESTful 接口可能会极大地简化您的开发工作。作为负责 PHP 视图(或者甚至 iPhone 应用程序)的开发人员,您可以发送一些请求到一个 URL 并接收一组预期响应。在 JSON 控制器的另一面,应用程序可以被钩挂(hook)到 MySQL、PostgreSQL、一个 XML 文件存储库、或者什么也不挂。
什么是 JSON?
JSON 是一种基于文本的轻量级数据交换格式,便于人类和计算机轻松理解和使用。在其出现之初,JSON 设计用于表示简单数据结构。尽管它最初被视为用于传输特定的 JavaScript 友好数据的一种方法,但现在几乎每台计算机上都有针对它的解析器。在 PHP 中,一对原生 JSON 函数(json_encode 和 json_decode)将帮助您执行大量繁重的提升。只要将一组数据(或者甚至一个简单字符串)发送到 json_encode,一个 JSON 对象将出现(如 清单 1 所示)。
清单 1. 一个 PHP 数组 vs. 一个 JSON 对象
'firstname'=>'Tom',
'lastname'=>'Smith',
'age'=>40
);
print_r($data);
/* prints
Array(
[firstname] => Tom
[lastname] => Smith
[age] => 40
)
*/
echo json_encode($data);
/* prints
{ "firstname": "Tom",
"lastname": "Smith",
"age":40
}
*/
注意,一个 SQL 查询(其中键等于数据库字段名、值等于数据)生成的典型 PHP 数组可以作为一个 JSON 对象轻松传输。到达时,数据可以简单地使用 JavaScript 代码(例如,来自一个 Ajax 上下文中)来进行 eval(),或者使用 PHP 中的 json_decode() 解码,重新变为数据数组。
JSON 支持除对象之外的各种数据类型:字符串、空值、数字(整数或实数)、布尔值和数组(包含在方括号中的逗号分隔的值序列)。因此,JSON 用户在处理数据时体验到了巨大的灵活性。
本文将帮助您构建一个微型列表 JSON REST 控制器,您可以将其放置到您的模型和视图功能之间。构建完成后,您可以随意进行扩展以适应您的项目目标。
构建一个基本 JSON 控制器
想象一个提供事件信息的应用程序。所有事件信息都是公共的,因此身份验证问题不是这里的主要考虑。另外,这个应用程序的目标是查询今天发生的事件,并使用 JSON 将响应传输回请求发起者。现在,假定请求者是一个 PHP 视图页面。
首先创建一个简单的事件数据库架构,类似于 清单 2 中的架构。
清单 2. 数据库架构(MySQL)
`id` INT NOT NULL AUTO_INCREMENT PRIMARYKEY ,
`title` VARCHAR( 255 ) NOT NULL ,
`address` VARCHAR( 255 ) NOT NULL ,
`start_time` DATETIME NOT NULL ,
`description` TEXT NOT NULL
);
一旦数据库表开始发挥作用,输入几条模拟数据记录。确保至少有一天拥有多个为其安排的条目。接下来,创建一个典型 PHP 模型文件,它连接到这个数据库并使用一个 SQL 查询来识别事件。如果这是一个真实应用程序,您可能会分隔数据库连接脚本和其余部分,并在您的查询中进行各种数据验证。您的代码可能如清单 3 所示。
清单 3. 一个简单查询
$USER = 'username';
$PASS = 'pw';
$DATABASE = 'dbname';
if (!($mylink = mysql_connect($SERVER, $USER, $PASS)))
{
echo "Sorry, could not connect to DB. Contact your sysadmin for help!";
exit;
}
mysql_select_db( $DATABASE );
class Events{
function get_events($day){
$ret_array = array();
$sql ="select id,title,address,start_time,description
from events where start_time like '$day%'
order by start_time asc";
$result = mysql_query($sql);
while($data = mysql_fetch_object($result)){
$obj['id'] = $data->id;
$obj['title'] = $data->title;
$obj['address'] = $data->address;
$obj['start_time'] = $data->start_time;
$obj['description'] = $data->description;
$ret_array[] = $obj;
}
return $ret_array;
}
}
当您使用您知道将检索到一些事件的日期设置对这个函数的一个简单调用时,您将得到如清单 4 所示的结果。
清单 4. 运行查询的结果
$today = '2010-06-17';
$events = $EVENT->get_events($today);
print_r($events);
/* results in
Array
(
[0] => Array
(
[id] => 2
[title] => Event #2
[address] => 156 My Avenue, MyTown, USA 78727
[start_time] => 2010-06-17 11:30:00
[description] => Join us for lunch to hear
FABULOUS SPEAKER.
)
[1] => Array
(
[id] => 1
[title] => Event #1
[address] => 123 My Street, Anytown USA 78727
[start_time] => 2010-06-17 15:30:00
[description] => A great event! Hope to see you there!
)
)
*/
如果您通过 json_encode() 运行相同的代码,您将得到一个可移植的 JSON 对象(如 清单 5 所示)。
清单 5. JSON 数据对象
{"id":"2",
"title":"Event #2",
"address":"156 My Avenue, MyTown, USA 78727",
"start_time":"2010-06-17 11:30:00",
"description":"Join us for lunch to hear FABULOUS SPEAKER. "
},
{"id":"1",
"title":"Event #1",
"address":"123 My Street, Anytown USA 78727",
"start_time":"2010-06-17 15:30:00",
"description":"A great event! Hope to see you there!"
}
]
您的目标是构建这样一个简单的控制器:它知道应该运行哪个模型和函数,然后返回一个 JSON 对象作为响应,这个响应可用于事务的远端。这个控制器非常简单,看起来如清单 6 所示。将清单 6 中的所有代码粘贴到一个名为 json.php 的文件中。
清单 6. 一个简单的控制器
var $response = '';
function JSON($model,$function,$params){
$REQUEST = new $model;
$data = $REQUEST->$function($params);
$this->response = json_encode($data);
}
}
要使这段代码生效,就需要用到您想调用的模型,实例化 JSON 类,然后传入 3 个参数:模型的类名、要运行的函数、以及该函数的参数。这个类然后调用那个函数并获取一个响应,该响应通过 json_encode() 运行。
最后一步是创建包含对 JSON 数据的请求的文件。这个特殊的文件(您可以称之为 listing.php)可以设置为接收 3 个 GET 变量(模型、函数和参数各一个),然后将这些变量传递给 JSON 类(如清单 7 所示)。
清单 7. 请求代码
require 'events.php';
//this is the JSON controller
require 'json.php';
//pass in your three GET parameters
$MODEL = $_GET['model'];
$FUNCTION = $_GET['function'];
//check to see if param is passed in
//if not, use today's date in this instance
if (isset($_GET['param'])){
$PARAM = $_GET['param'];
}else{
$PARAM = date("Y-m-d");
}
//invoke
$JSON = new JSON($MODEL,$FUNCTION,$PARAM);
//access the response variable
echo $JSON->response;
此时,您可以将这个文件加载到一个浏览器中,并获取一个类似于清单 5 的 JSON 对象。您可以通过 json_decode() 将这个 JSON 对象发送回去,使用 JavaScript 代码处理它,或者让它保持原样。
整个这个流程的一个甚至更好的方法是创建一个更紧密模拟 RESTful 服务器的路径结构。例如,您可以创建一个名为 events/today 的目录结构,该结构包含一个名为 index.php 的文件。通过将您的浏览器指向 /events/today,无需传入任何 GET 变量,您就可以基于清单 8 中的代码取回一个 JSON feed。
清单 8. /events/today/index.php 中的代码
require '../../json.php';
$MODEL ="Events";
$FUNCTION ="get_events";
$PARAM = date("Y-m-d");
//invoke
$JSON =new JSON($MODEL,$FUNCTION,$PARAM);
echo $JSON->response;
//prints out
[
{"id":"3",
"title":"Test Event 3",
"address":"111 Main Street, Austin TX 78727",
"start_time":"2010-06-10 15:15:00",
"description":"Testing 456."
}
]
结束语
使用这种方法,您可以为您的视图和支持的应用程序简化一些数据提取要求。开发人员无需记住底层数据库的所有细节,相反,他们可以轻松命中 URLs 并接收他们寻找的响应来继续他们的工作。