PHP是作为Flex程序最佳的数据源
分类:Flash_Flex_AIR| 发布:camnprbubuol| 查看: | 发表时间:2010/11/5
现在,很多人似乎从网页开发转到了构建RIA-s 。但是我们也很轻易就看到新的客户端技术的紧急问题所在,随着诸如Flex ,Silverligth 或者JavaFX 等名字越来越频繁地出现在网页开发里,这种向着RIAs 转换的趋势也在影响着服务器端的程序员。
PHP 的角色变化
如果你有一个RIA 作为用户交互界面的话,服务器端将不再需要把视觉表现部分和数据混在一起了。这意味着PHP 程序员不再需要头疼用于风格化的数据,诸如携带着表格啊,网页元素还有大量的标签等等信息( 的数据) 。他们只需要做的一件事就是以某种便于使用的格式发送这些数据,而凑巧XML 是被Flex 所支持的。我们来看看用于开展Flex 与PHP 交流的一些代码。服务端的代码需要从数据库获取数据,用XML 标签包装之,反之的话就是剥离成代码反馈给它(数据库)。
- // file:DAO/BookDAO.php
- <?php
- class BookDAO {
-
- var $connection;
-
- function BookDAO(){
- $this -> connection = mysql_connect(config::$host,config::$login,
- config::$password);
- mysql_select_db( config::$database );
- }
-
- /* Returns all the books in the XML format */
- function getAllXML() {
- $sql = "SELECT * FROM books";
- $result = mysql_query( $sql, $this -> connection );
- $xml = "<books>";
- while( $row = mysql_fetch_array( $result ) ) {
- $xml .= $this->rowToXml($row);
- }
- $xml .= "</books>";
- return $xml;
- }
-
- /* Takes a database row as a parameter and returns an XML book node */
- function rowToXml($row)
- {
- $xml .= "<book>";
- $xml .= "<id>".$row["id"]."</id>";
- $xml .= "<author>".$row["author"]."</author>";
- $xml .= "<title>".$row["title"]."</title>";
- $xml .= "<genre>".$row["genre"]."</genre>";
- $xml .= "<price>".$row["price"]."</price>";
- $xml .= "<publish_date>".$row["publish_date"]."</publish_date>";
- $xml .= "<description>".$row["description"]."</description>";
- $xml .= "</book>";
- return $xml;
- }
- } ?>
除了这个类以外有个文件包含了所有数据库的配置信息(config.php) 服务器还有一个简易文件来调用getAllXML 函数。
并且返回结果:
- // file:sample1.php
- <?php
- require_once ’config.php’;
- require_once ’DAO/BookDAO.php’;
- $bookDAO = new BookDAO();
- echo ($bookDAO -> getAllXML());
- ?>
这是基本的PHP 了,让我们转向Flex 。假设我们构建了一个可以提供书籍列表的服务器,现在来连接它并用好看的表格来显示。我把代码黏贴出来并解释下都发生了什么。
- <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
- creationComplete="sampleService.send();" >
- <mx:HTTPService
- resultFormat="e4x"
- id="sampleService"
- url="http://localhost/librarySample/sample1.php" />
- <!-- You need to change the URL above to
- the one on your server -->
- <mx:DataGrid id="dataGrid" width="100%"
- dataProvider="{sampleService.lastResult.book}" >
- <mx:columns>
- <mx:DataGridColumn dataField="author" />
- <mx:DataGridColumn dataField="title" />
- <mx:DataGridColumn dataField="publish_date" />
- </mx:columns>
- </mx:DataGrid>
- </mx:Application>
我们在里面有两个组件。第一个是HTTPService ,众所周知是用来通过HTTP 方式下载数据的东东。你可能奇怪为什么当我试图得到XML 的时候会把“e4x ”当做结果。E4X 是“ECMAScript for XML ”的简称,意味着我们想把数据当成本地Flex XML 类,而那也将更容易操作。第二个就是DataGrid ,它有几行,它“关注”的是任何出现在数据sampleService 中的书。当程序创建完毕时,sampleService 发送调用到我们的服务器。结果就是一个小RIA ,像这样:
这对于只有几行的代码来说还行,不过我们可以让它更好些。
数据格式——XML 和AMF
XML 真的不错,易于使用,易于创建,你可以用任何编程语言解析它,甚至可以被阅读。不过有得必有失,当你必须考虑所传递信息本身的体积的时候,XML 就不是一个好的数据格式了;如果你的软件里有两部分之间的沟通可以通过非文本数据来进行的话,标签包裹的方式就显得累赘。幸运的是Flex 和PHP 均支持一种叫做AMF 的二进制数据格式。用它来发送数据的高效率让人震撼。眼见为实,James Ward 创建了一个用不同的数据格式发送大型数据来测试带宽的应用,你可以在这里查看结果。最爽的是它的易于使用,因为1.7 版本的Zend 框架包含了zend_amf ,可以让你随时发送AMF 。要运行以下例子你需要在服务器上安装它,如果没有的话到Zend 网站上下载。
从XML 到AMF
首先改变所有之前用的XML 代码,并用对象代替它。它是一个直译的过程,你可以对比以下的BookDAO 和我们之前使用的那个看是不是没多少改变。
- // file:DAO/BookDAO.php
- <?php
- class BookDAO {
-
- var $connection;
-
- function BookDAO(){
- $this -> connection = mysql_connect(config::$host,config::$login,
- config::$password);
- mysql_select_db( config::$database );
- }
-
- /* Returns all the books as an Array of objects*/
- function getAll()
- {
- $array = array();
- $sql = "SELECT * FROM books";
- $result = mysql_query( $sql, $this -> connection );
- while( $row = mysql_fetch_array( $result ) ) {
- array_push($array,$this->rowToBook($row));
- }
- return $array;
- }
-
- /* Takes a database row as a parameter and returns an object */
- private function rowToBook($row)
- {
- $book;
- $book->id = $row["id"];
- $book->author = $row["author"];
- $book->title = $row["title"];
- $book->genre = $row["genre"];
- $book->price = $row["price"];
- $book->publish_date = $row["publish_date"];
- $book->description = $row["description"];
- return $book;
- }
- } ?>
随着这个改变我们完成了一半。现在我们需要用输出我们AMF 服务器的某些东东取代sample1.php 。
- // file:sampleamf.php
- <?php
- require_once ’Zend/Amf/Server.php’;
- require_once ’DAO/BookDAO.php’;
- require_once ’config.php’;
-
- // Creates a new amf server
- $server = new Zend_Amf_Server();
- // Enables the errors for debugging
- $server->setProduction(false);
- // Exposes the BookDAO class to
- // the outside world
- $server->setClass(’BookDAO’);
- // Handles the calls
- echo($server->handle());
- ?>
我希望代码里的注释足够解释当前状况。如果你的浏览器打开这个文件并且一切正常的话你应该可以看到写着"Zend Amf Endpoint" 的空页面。
我们的下一个任务就是修改我们的Flex 小程序以获得从新建服务器过来的数据。这个修改简单到不易察觉。我们看看新的源码。
- <?xml version="1.0" encoding="utf-8"?>
- <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
- creationComplete="remoteObject.getAll();">
- <mx:ChannelSet id="chanelSet" >
- <mx:AMFChannel
- uri="http://localhost/librarySample/sampleamf.php" />
- </mx:ChannelSet>
-
- <mx:RemoteObject
- channelSet="{chanelSet}"
- id="remoteObject"
- source="BookDAO"
- destination="zend" >
- <mx:method
- name="getAll"
- result="dataGrid.dataProvider = event.result" />
- </mx:RemoteObject>
-
- <mx:DataGrid id="dataGrid" >
- <mx:columns>
- <mx:DataGridColumn dataField="author" />
- <mx:DataGridColumn dataField="title" />
- <mx:DataGridColumn dataField="publish_date" />
- </mx:columns>
- </mx:DataGrid>
- </mx:Application>
数据表格没有变,因为我们想用同样方式显示数据。解释细节超出了本文讨论范围,不过你需要做以下事情。
1. 定义一个AMFChannel 指向可以掌握requests 的文件。
2. 把它放进ChannelSet ,因为RemoteObject 不会把单独的channels 当做参数。
3. 建立一个RemoteObject 代替HTTPService( 此处原文可能有笔误”HTTPSerivce ”) 。
如果你经常使用GET 或者POST 参数的话,RemoteObject 是一个奇怪的东西。它为给定的对象创建了一种桥梁,在这个案例中叫“BookDAO ”,并
允许你调用它的函数,就好像它是本地文件一样。我们不把url 直接给它,取而代之的是我们创建的chanelSet 。最终目的是设置到“zend ”,如果你在用zend_amf 的话。所以这边我们有RemoteObject ,另一边PHP 就是BookDAO.php 。“源”定义了一个类来接桥,而且方法指向类里面单独的一个函数(btw ,你在那儿可以有多于一个的mx:Method 标签) 。一个简单的发展流程图如下:
你可能刚好注意到我们的getAll 方法在取回结果的时候做了些事情——它把从源获取的数据制成分页的数据表格,这让结果和之前一样却可以帮我们节省了很多带宽。
节省带宽只是你使用AMF 的第一个好处而已,同时你还可以看到你的PHP 编程不再臃肿和复杂。
组织数据模型
好的,现在我们开始节省带宽之旅。除了节省用户时间不说,使用AMF 也可以节省我们自己的时间。虽然PHP 和Actionscript 都允许你不经特定类就通过连线发送数据,就如之前我们在例子中所做的那样,可是这并不是最好的方式。说这些我主要是想指出还可以让数据进行某种收缩。要记得大部分实际情况中Flex 应用的作者和后端的作者并不是同一个人,所以最好在从服务器收到数据之前预先知道它长什么样。要实现这些( 预览) 的话,就要在两边创建拥有一样域的类,叫做Value Objects( 或者Data Transfer Objects) 。但是用AMF 的话还有额外的好处——我们建立了映射使得数据可以自动在PHP 和Actionscript 对象间协同翻译,不需要我们添加任何序列化和反序列化的代码。瞧瞧我们的value object 。
- <?php
- class BookVO {
- public $id;
- public $author;
- public $title;
- public $genre;
- public $price;
- public $publish_date;
- public $description;
- }
- ?>
- // file:vo/BookVO.as
- package vo
- {
- [RemoteClass(alias="BookVO")]
- public class BookVO
- {
- public var id:int;
- public var author:String;
- public var title:String;
- public var genre:String;
- public var price:int;
- public var publish_date:String;
- public var description:String;
- }
- }
这需要一些旁注。首先AS 变量需要声明而在PHP 不必。PHP 的问题是你不能明确的定义变量的类型,所以当试图定义一个数据协议的时候,在注释里留下PHP 变量类型是有用的。另一个不同是AS 类型声明之上的RemoteClass 元数据标签。它是定义计划的第一部分,说明标记为“BookVO ”来自PHP 的对象应该被翻译给这个类。PHP 里你不用标签,而应该在sampleamf.php 增加一个单独的行:
$server->setClassMap(" BookVO "," BookVO ");
而当你在PHP 想用新建的类的话,你也需要改变rowToBook 函数,所以它的第一行变成:
$book = new BookVO();
现在你应该从getAll 获得BookVO 的一个数组作为结果。剩下的唯一一件事就是把Actionscript 对应部分导入Flex project 里。因为Flex 编译器里不包括用在已编译swf 里的类,加强它最简便的方法就是在主文件里增加一个单独无用的变量。
private var bookDummy:BookVO;
这些在大部分时间里都不是必须的,因为任何一个常规项目里你都会在某些地方用到那些类,但知道这些可能会帮你找到计划失败的原因。
惰性装载
最后要担心的是发送数据的量,当使用AMF 会比XML 小得多的时候,它将会成比例的随着数据库条目数的增加而增加——就算用户只会阅读前面的一些条目,他也只能等全部都下载了之后才能看到。
这个问题的经典网页解决方案就是使用分页, 并不需要特别解释的一个方案。但是,在一个应用里使用分页的难处在于处理数据不够舒服,要不然你更愿意卷屏?这篇文章的余下部分将会简单的展示这用户拥有 所有数据而不下载的时候使用这两种方案的情形。这个技术称之为“懒惰装载”,是基于“不求勿施”的思路设计的。
我们首先做的就是询问数据库里储存的书的数量,创建一个相同长度的ArrayCollection 数组,当成我们DataGrid 的一个dataProvider 。接着我们将只下载在DataGrid 里显示的行值并把它们安放在我们ArrayCollection 里合适的空位上。这样的设置的前提是简单的假设数据不会在用户使用的时候发生变化。
我们从PHP 开始,因为这里改动不大。我们现在需要两个新的函数——一个获取条目数,也是显而易见的:
- function getCount()
- {
- $array = array();
- $sql = "SELECT COUNT(*) FROM books";
- $result = mysql_query( $sql, $this -> connection );
- $row = mysql_fetch_array( $result );
- return $row[0];
- }
还有另一个截取数据的一段,是通过MySql LIMIT 命令获得的:
- function getSome($minIndex,$maxIndex)
- {
- $array = array();
- $sql = "SELECT * FROM books LIMIT ".$minIndex.",".$maxIndex;
- $result = mysql_query( $sql, $this -> connection );
- while( $row = mysql_fetch_array( $result ) ) {
- array_push($array,$this->rowToBook($row));
- }
- return $array;
- }
惰性装载
最后要担心的是发送数据的量,当使用AMF 会比XML 小得多的时候,它将会成比例的随着数据库条目数的增加而增加——就算用户只会阅读前面的一些条目,他也只能等全部都下载了之后才能看到。
这个问题的经典网页解决方案就是使用分页, 并不需要特别解释的一个方案。但是,在一个应用里使用分页的难处在于处理数据不够舒服,要不然你更愿意卷屏?这篇文章的余下部分将会简单的展示这用户拥有 所有数据而不下载的时候使用这两种方案的情形。这个技术称之为“懒惰装载”,是基于“不求勿施”的思路设计的。
我们首先做的就是询问数据库里储存的书的数量,创建一个相同长度的ArrayCollection 数组,当成我们DataGrid 的一个dataProvider 。接着我们将只下载在DataGrid 里显示的行值并把它们安放在我们ArrayCollection 里合适的空位上。这样的设置的前提是简单的假设数据不会在用户使用的时候发生变化。
我们从PHP 开始,因为这里改动不大。我们现在需要两个新的函数——一个获取条目数,也是显而易见的:function getCount()
- <mx:RemoteObject
- channelSet="{chanelSet}"
- id="remoteObject"
- source="BookDAO"
- destination="zend" >
- <mx:method
- name="getSome"
- result="getSomeResult(event)"
- />
- <mx:method
- name="getCount"
- result="getCountResult(event)"
- />
- </mx:RemoteObject>
-
- <mx:DataGrid id="dataGrid"
- dataProvider="{dataProvider}"
- scroll="dataGrid_scrollHandler(event)" >
- <mx:columns>
- <mx:DataGridColumn dataField="author" />
- <mx:DataGridColumn dataField="title" />
- <mx:DataGridColumn dataField="publish_date" />
- </mx:columns>
- </mx:DataGrid>
-
- <mx:Script>
- <!--[CDATA[
- import mx.collections.ArrayCollection;
-
- [Bindable]
- private var dataProvider:ArrayCollection ;
- ]]-->
- </mx:Script>
第一个改动就是我们RemoteObject 方法的重定义,因为我们不再使用”getAll ”取而代之的是两个新创建的方法。第二个改动就是我们将会填充的dataProvider 的定义,还有我们DataGrid 滚动条——我们将会在每次用户使用滚动的时候检查是否需要下载新的数据。
两个函数持有的RemoteObject 的结果是简洁的。
- protected function getCountResult(event:ResultEvent):void
- {
- dataProvider = new ArrayCollection(new Array(int(event.result)));
- remoteObject.getSome(0,dataGrid.rowCount);
- }
- protected function getSomeResult(event:ResultEvent):void
- {
- for (var i:int = event.token.message.body[0]; i<event.token.message.body[1];i++)
- dataProvider.setItemAt(event.result[i-event.token.message.body[0]],i);
- }
一旦我们获取了数据就开始初始化dataProvider 并设它的长度为我们所需。之后我们下载第一段数据,包括了从dataGrid 显示的第一行到最后一行。调用反馈之后,getSomeResult 函数设置dataProvider 里的项目为刚从数据库取得的部分。它使用我们之前调用getSome 函数时使用的参数来适当的定位( 事件发生获取器(event.token.message.body) 掌握着参数) 。
剩下的事就是掌握DataGrid 滚动条的变化并继续下载我们缺少的数据。为了更有效率,我们在每次调用前都会做个检查,调整范围以便只下载未下载的那些行,那些是在checkWhichAreMissing 函数里完成的。
- protected function dataGrid_scrollHandler(event:ScrollEvent):void
- {
- if (dataProvider==null) return;
- var correctedRange:Array = checkWhichAreMissing(event.position,event.position+dataGrid.rowCount);
- if (correctedRange[0]<correctedRange[1])
- remoteObject.getSome(correctedRange[0],correctedRange[1]);
- }
- private function checkWhichAreMissing(from:uint,to:uint):Array
- {
- while ((from<to)&&(dataProvider.getItemAt(from)!=null)) from++;
- while ((to>from)&&(dataProvider.getItemAt(to-1)!=null)) to--;
- return [from,to];
- }
至此,我们就得到了一个完全有效的应用,就是当用户需要时能下载数据的应用。你可以从文末的链接处下载整个项目的文件以及附带的注释。希望这些代码可以成为你开始发掘使用Flex 和PHP 源的可能性的良好开端。
本文译自:http://insideria.com/2010/06/a-whole-lot-of-people.html