MongoDB 是数据库驱动的云应用程序的一个非常受欢迎的选择,因为它提供了灵活的、面向文档的模式和简单的可伸缩性。我第一次听说 MongoDB 是在 2012 年初,当时 NoSQL 运动刚刚兴起,MongoDB 公司仍然被称为 10gen。如今,MongoDB 是地球上最受欢迎的数据库管理系统之一,应用非常广泛,从实时分析到物联网 (IoT) 传感器数据可视化。但 MongoDB 最好的部分仍然未变:简单的配置、灵活的数据模型、易用性和分布式架构。
在这个由两部分组成的教程系列的第 1 部分中,我将展示如何利用这些 MongoDB 特性,以及如何构建功能强大的、可伸缩的、MongoDB 驱动的云应用程序。为了证明这是多么容易,我将带您亲历构建 PHP/jQuery 地址簿应用程序的过程,该应用程序允许您使用托管在 IBM® Bluemix™ 云中的 MongoDB 数据库来存储和管理联系人。不用说,这些都适用于移动设备,所以您可以将应用程序部署到 Bluemix,并立刻开始在平板电脑或智能手机上使用它(如第 2 部分 所示)。
听起来很有趣?请继续阅读。
在示例应用程序中,用户可以输入联系人记录,包括联系人的姓名、电子邮件地址和电话号码。应用程序运行在 Bluemix 云中,可以立即用于触摸设备,比如平板电脑和智能手机。它还通过允许用户使用其 Google + 凭证进行登录,从而支持多个用户。用户身份验证是通过使用 OAuth 2.0 和 Google + API 来执行的。
在客户端,我使用 jQuery Mobile 为应用程序创建了一个移动友好的用户界面。在服务器上,我使用了 Slim (一个 PHP 微框架)来管理应用程序流,并保存和检索来自 MongoDB 和 HybridAuth (一个开源社会登录库,用于用户身份验证)的数据。
在这里,有许多技术可供使用,以下是您需要做的准备工作:
备注:所有使用 Google + API 的应用程序都必须遵守 Google 的服务和隐私政策的条款。在开始您的项目之前,花几分钟阅读这些要求,确保您的应用程序符合这些要求。
运行应用程序
获取代码
第一步是创建一个包含 Slim PHP 微框架的最基本的应用程序存根。可以轻松下载该存根并使用 Composer(PHP 依赖关系管理器)来安装它。
切换到 Web 服务器的文档根目录(在 Linux 上通常是 /usr/local/apache/htdocs,在 Windows 上通常是 C:/Program Files/Apache/htdocs),并为该应用程序创建一个新目录:
shell> cd /usr/local/apache/htdocs shell> mkdir contacts
该目录在整个教程中被引用为 $APP_ROOT。
{ "require": { "slim/slim": "2.*" } }
shell> cd /usr/local/apache/htdocs/contacts shell> php composer.phar install
备注:按照这里的操作,您的应用程序是通过 URL http://localhost/contacts 进行直接访问的。您不应该尝试使用一个基于名称的虚拟主机(例如,http://contacts.localhost)来开发应用程序,因为除了用于 OAuth 身份验证的 http://localhost 之外,Google 不允许您使用私有域 URL。当然,这只适用于本地开发,如果您将应用程序部署到 Bluemix,那么您的 Bluemix 域 URL 可以公开访问,因此可以没有任何限制地用于 OAuth 重定向。
下一步是构建一个简单的用户界面,在其中列出可用的联系人,包括控制添加新联系人,编辑或删除现有的联系人。清单 1 包含此用户界面的基本结构,应该将它保存为 $APP_ROOT/templates/main.tpl.php。
点击查看代码清单
关闭 [x]
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.css" /> <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script> <script src="http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.js"></script> </head> <body> <div data-role="page"> <div data-role="header"> <h1>Contacts</h1> </div> <div data-role="content"> <a href="<?php echo $baseUri; ?>/save" data-ajax="false" data-inline="true" data-role="button" data-icon="plus" data-mini="true"data-theme="a">Add</a> </div> </div> </body> </html>
根据标准 jQuery Mobile 约定来格式化上面的页面。主要的页面元素是 <div> 元素与 data-role="page"
属性。在此页面中,单独的 <div> 元素被用于页面标题和内容。当前页面内容包括一个按钮,用于添加新的联系人;不过,随着应用程序的扩展,该页面被扩展到包含一个联系人列表页和一个搜索栏。
在服务器上,Slim 会在遇到针对 URL 路由(比如 / 或 /index)的请求时小心呈现这个模板。方法如下:
<?php // use Composer autoloader require 'vendor/autoload.php'; // configure Slim application instance // initialize application $app = new /Slim/Slim(array( 'debug' => true, 'templates.path' => './templates' )); // index page handlers $app->get('/', function () use ($app) { $app->redirect($app->urlFor('index')); }); $app->get('/index', function () use ($app) { $app->render('main.tpl.php'); })->name('index'); // hook to add request URI path as template variable $app->hook('slim.before.dispatch', function() use ($app) { $app->view()->appendData(array( 'baseUri' => $app->request()->getRootUri() )); }); $app->run();
应该讲上面的脚本保存为 $APP_ROOT/index.php,该脚本是应用程序的主要控制脚本。它首先加载 Slim 框架,并初始化一个新的 Slim 应用程序对象。应用程序对象是通过一个配置数组传递的,该数组中包括包含页面模板的目录的位置,包括先前创建的模板。
一旦配置好了 Slim 应用程序对象,该脚本就会为 /index 路由设置一个路由器回调,并定义在此路由与传入的请求相匹配时将会调用的函数。在上面的脚本中,/index 回调只呈现了主应用程序页面模板,其中包含各种 jQuery Mobile 页面元素。该脚本还为 / 路由设置了一个路由器回调,该回调只重定向到 /index 路由。
还要注意 slim.before.dispatch
钩子,它将检索当前请求的 URL(包括子目录路径,如果有的话),使它可以用作一个名为 $baseUri
的模板变量进行使用。这最大程度地提高了可移植性,因为它允许您将应用程序移动到 Web 服务器上的不同目录路径,不需要在您的视图中重写 URL 路径。您可以在前面的模板中看到这些(在 Add 按钮的 URL 超级链接中)。
Slim 取决于针对友好的 URL 的 URL 重写,所以现在也是一个配置 Web 服务器的 URL 重写规则的好时机。如果您使用的是 Apache,那么可以使用以下重写规则,应该将该规则保存到 $APP_ROOT/.htaccess:
<IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [L,QSA] </IfModule>
如果您使用的是 nginx
,请参阅 Slim 框架文档 ,获得有关如何配置 Slim 的 URL 重写的信息。
现在,当您在 http://localhost/contacts 或 http://localhost/contacts/index 访问您的应用程序时,您应该看到您在前面设置的 jQuery Mobile 页面模板:
阅读: MongoDB PHP 扩展文档
前面设置的页面模板已经包含一个 Add 按钮,可连接到 /save URL 路由。下一个逻辑步骤是通过添加一个表单和用于新联系人的表单处理程序来充实该功能。为此,请创建下面这个简单的 Web 表单,而且应该将它保存为 $APP_ROOT/templates/form.tpl.php:
点击查看代码清单
关闭 [x]
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.css" /> <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script> <script src="http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.js"></script> </head> <body> <div data-role="page"> <div data-role="header"> <h1>Add Contact</h1> </div> <div data-role="content"> <div data-role="collapsible-set" data-inset="false"> <form method="post" data-ajax="false" action="<?php echo $baseUri; ?>/save"> <label for="name">Name</label> <input name="name" id="name" data-clear-btn="true" type="text" /> <label for="email">Email address</label> <input name="email" id="email" data-clear-btn="true" type="text" /> <label for="phone">Phone</label> <input name="phone" id="phone" data-clear-btn="true" type="text" /> <input name="submit" value="Save" type="submit" data-icon="check" data-inline="true" data-mini="true" data-theme="a" /> <a href="<?php echo $baseUri; ?>/index" data-role="button" data-inline="true" data-icon="back" data-mini="true" data-theme="a">Back</a> </form> </div> </div> </body> </html>
这里没有什么非常特殊的地方——只有一个标准的表单,包含用于填写姓名、电子邮件地址和电话号码的字段:
在提交时,会(通过 POST
)将表单数据提交回 /save URL 端点。此时,Slim 需要处理表单输入,清理和验证输入,并将它们保存到数据库中。$APP_ROOT/index.php 的以下更改会设置必要的路由处理程序来实现此操作:
<?php // use Composer autoloader require 'vendor/autoload.php'; // configure credentials // ... for MongoDB $config["db"]["uri"] = 'mongodb://db2:db2@192.168.56.101:27017/db2'; $config["db"]["name"] = substr(parse_url($config["db"]["uri"], PHP_URL_PATH), 1); // configure Slim application instance // initialize application $app = new /Slim/Slim(array( 'debug' => true, 'templates.path' => './templates' )); // initialize Mongo client object $mongo = new MongoClient($config["db"]["uri"], array("connectTimeoutMS" => 30000)); $db = $mongo->selectDb($config["db"]["name"]); // index page handlers – snipped! // addhandlers $app->get('/save', function () use ($app) { $app->render('form.tpl.php'); }); $app->post('/save', function () use ($app, $db) { $collection = $db->contacts; $name = trim(strip_tags($app->request->params('name'))); $email = trim(strip_tags($app->request->params('email'))); $phone = trim(strip_tags($app->request->params('phone'))); $contact = new stdClass; if (!empty($name)) { $contact->name = $name; $contact->phone = $phone; $contact->email = $email; $collection->save($contact); } $app->redirect($app->urlFor('index')); }); // hook to add request URI path as template variable – snipped! $app->run();
这里有许多的附加内容,让我们详细了解它们:
GET
路由处理程序。可以想象,这非常简单,只需呈现之前显示的表单模板。 POST
路由处理程序,负责处理表单数据,这更加有趣。它首先使用 MongoClient 对象来初始化一个名为 contacts 的新的 MongoCollection 对象;此 MongoCollection 对象可以充当通过应用程序添加的所有联系人的存储库。然后,它会检索表单中提供的输入,使用 PHP 的 strip_tags()
函数清理它们(这也是一个验证输入的好地方,在这里,为了简便起见,我们省略了这一步)。检索到的姓名、电子邮件地址和电话号码都被作为 PHP 对象的属性进行添加,然后,使用 MongoCollection 对象的 save()
方法将这个对象保存到 MongoDB 数据库。一旦保存了该对象,客户端就会被重定向回 /index 页面。 因为主页模板(尚)未包含用来列出可提供的联系人的任何代码,所以在保存一个新联系人后,您仍会看到一个空白页。不过,您可以使用 MongoDB shell 来验证该联系人是否已添加:
点击查看大图
关闭 [x]
别担心,让信息进入应用程序只有一步之遥了!
在前面的步骤中,您可以将一个新的联系人保存到 MongoDB 数据库,但您仍然无法在应用程序中看到它。要纠正这个问题,请更新 /index 路由处理程序,以便从数据库中检索所有可用的联系人,并将它们作为一个数组传递给主页模板:
<?php // configuration and initialization – snipped! // index page handlers $app->get('/', function () use ($app) { $app->redirect($app->urlFor('index')); }); $app->get('/index', function () use ($app, $db) { $collection = $db->contacts; $contacts = $collection->find(); $app->render('main.tpl.php', array('contacts' => $contacts)); })->name('index'); // other handlers and hooks – snipped! $app->run();
在这里,/index 路由处理程序使用 MongoClient 对象从 “联系人” 集合中检索所有文档,然后将这些文档作为一个数组传递给主页模板。现在,剩下的工作就是更新模板,遍历整个数组,显示联系人的详细信息,以及用于修改和删除的控制按钮。
以下是 $APP_ROOT/templates/main.tpl.php 的修改后的代码:
点击查看代码清单
关闭 [x]
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.css" /> <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script> <script src="http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.js"></script> </head> <body> <div data-role="page"> <div data-role="header"> <h1>Contacts</h1> </div> <div data-role="content"> <ul data-role="listview" data-inset="true" data-filter="true" data-filter-placeholder="Search contacts..."> <?php foreach ($this->data['contacts'] as $c): ?> <li> <h3><?php echo $c['name']; ?></h3> <div data-type="horizontal"> <a data-inline="true" data-ajax="false" data-role="button" data-icon="mail" data-theme="b" data-mini="true" href="mailto:<?php echo $c['email']; ?>"><?php echo $c['email']; ?></a> <a data-inline="true" data-ajax="false" data-role="button" data-icon="phone" data-theme="b" data-mini="true" href="tel:<?php echo $c['phone']; ?>"><?php echo $c['phone']; ?></a> </div> <div data-type="horizontal"> <a href="<?php echo $baseUri; ?>/save/<?php echo $c['_id']; ?>" data-ajax="false" data-inline="true" data-role="button" data-icon="edit" data-mini="true" data-theme="a">Update</a> <a href="<?php echo $baseUri; ?>/delete/<?php echo $c['_id']; ?>" data-ajax="false" data-inline="true" data-role="button" data-icon="delete" data-mini="true" data-theme="a">Remove</a> </div> </li> <?php endforeach; ?> </ul> <a href="save" data-ajax="false" data-inline="true" data-role="button" data-icon="plus" data-mini="true"data-theme="a">Add</a> </div> </div> </body> </html>
在这里,一个 foreach()
循环在从 MongoDB 集合检索到的联系人信息组成的数组上进行迭代,并打印每个联系人的姓名、电子邮件地址和电话号码。此外,有两个按钮被添加到每个记录,用于修改和删除联系人,这两个按钮分别提供了到 /save 和 /delete URL 端点的超级链接。每个超级链接额外附带了针对联系人的惟一记录 ID(由 MongoDB 自动分配),该 ID 被用作一个路由参数,例如,/save/550927398ab33a6c1400002a and /delete/550927398ab33a6c1400002a。在下一节中,我们将了解如何使用这些信息。
前面的清单中另一个令人感兴趣的地方是列表视图中的 data-filter="true"
属性。该属性会自动在列表的顶部添加一个 jQuery Mobile 搜索栏小部件,允许您通过输入搜索字符串快速过滤您的联系人列表。下面有一个示例:
让我们首先查看联系人删除。如前面所述,每个联系人记录的旁边都有一个 Remove 按钮,提供了到 /delete URL 端点的超级链接,包含惟一的记录 ID。为此路由添加一个处理程序非常简单:读取记录 ID 并调用 MongoCollection 对象的 remove()
方法,从 MongoDB 中删除相应的记录。下面的代码将被添加到 $APP_ROOT/index.php:
<?php // configuration and initialization – snipped! // delete handler $app->get('/delete/:id', function ($id) use ($app, $db) { $collection = $db->contacts; $collection->remove(array('_id' => new MongoId($id))); $app->redirect($app->urlFor('index')); }); // other handlers and hooks – snipped! $app->run();
更新现有的联系人稍微有点复杂。首先,必须更新针对 /save URL 端点的 GET
处理程序,以便将记录 ID 作为一个路由参数进行读取,然后从 MongoDB 中检索相应的记录:
<?php // configuration and initialization – snipped! // add/update handlers $app->get('/save(/:id)', function ($id = null) use ($app, $db) { $collection = $db->contacts; $contact = $collection->findOne(array('_id' => new MongoId($id))); $app->render('form.tpl.php', array('contact' => $contact)); }); // other handlers and hooks – snipped! $app->run();
然后,检索到的联系人记录被作为一个数组传递到表单模板。表单模板现在可以使用现有的联系人值预先填充联系人表单。以下是完成此操作的修订代码:
点击查看代码清单
关闭 [x]
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.css" /> <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script> <script src="http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.js"></script> </head> <body> <div data-role="page"> <div data-role="header"> <h1>Add Contact</h1> </div> <div data-role="content"> <div data-role="collapsible-set" data-inset="false"> <form method="post" data-ajax="false" action="<?php echo $baseUri; ?>/save"> <input name="id" type="hidden" value="<?php echo $this->data['contact']['_id']; ?>" /> <label for="name">Name</label> <input name="name" id="name" data-clear-btn="true" type="text" value="<?php echo $this->data['contact']['name']; ?>" /> <label for="email">Email address</label> <input name="email" id="email" data-clear-btn="true" type="text" value="<?php echo $this->data['contact']['email']; ?>" /> <label for="phone">Phone</label> <input name="phone" id="phone" data-clear-btn="true" type="text" value="<?php echo $this->data['contact']['phone']; ?>" /> <input name="submit" value="Save" type="submit" data-icon="check" data-inline="true" data-mini="true" data-theme="a" /> <a href="<?php echo $baseUri; ?>/index" data-role="button" data-inline="true" data-icon="back" data-mini="true" data-theme="a">Back</a> </form> </div> </div> </body> </html>
请注意,现在的表单还包括一个额外的隐藏字段(针对记录 ID)。最后,需要更新针对 /save URL 端点的 POST
处理程序来检查隐藏的记录 ID,如果发现此 ID,则用它来更新数据库中的联系人记录。以下是修改后的处理程序:
<?php // configuration and initialization – snipped! $app->post('/save', function () use ($app, $db) { $collection = $db->contacts; $name = trim(strip_tags($app->request->params('name'))); $id = trim(strip_tags($app->request->params('id'))); $email = trim(strip_tags($app->request->params('email'))); $phone = trim(strip_tags($app->request->params('phone'))); $contact = new stdClass; if (!empty($name)) { $contact->name = $name; $contact->phone = $phone; $contact->email = $email; if (!empty($id)) { $contact->_id = new MongoId($id); } $collection->save($contact); } $app->redirect($app->urlFor('index')); }); // other handlers and hooks – snipped! $app->run();
所有界面按钮现在都应该是功能完备的,您应该能够通过单击相应的按钮来添加、编辑和删除联系人。当然,您还可以根据需要将额外的联系人字段添加到表单和列表视图,这取决于您的需求。
在本教程的第 2 部分,我将展示如何为多个用户添加支持,以及如何将应用程序部署到 Bluemix。