`
congfeng02
  • 浏览: 195482 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

PHP框架设计入门之四:表单和事件

阅读更多

PHP框架设计入门之四:表单和事件

PHP, framework

This is part 4 of a multi-part series on the design of a complete application framework written in PHP. In part 1, we covered the basic class structure of the framework and laid out the scope of the project. The second part described methods for managing users and session data. The third part described a practical implementation of page templates. In this fourth and final section, we will expand on our implementation of page templates to provide the web application persistent form data capabilities. We will also apply the framework to built an error page and a page dispatcher.

这是PHP框架设计入门系列教程的第四部分。在第一部分,我们已经介绍框架的基础类结构,并展示了项目的大体。第二部分叙述了管理用户和会话数据的一些方法。第三部分谈论了页面模板的具体实现。在第四部分这个最后的章节中,我们将详细地讨论如何通过页面模板来为我们的Web应用程序提供持久型的数据。同时,我们会用我们的框架来创建一个出错页面和一个页面分发器。

Forms

表单

If you've read Part 3 and felt a little gypped, you are right. The fact of the matter is that our use of templates as described in that article is not quite practical. One template for the entire application. Really? Does that mean each page has to have exactly the same layout? Of course not. The part that was not entirely stated was that the page templates described the general principle for separating the presentation layer out from the application logic. Now that this is established, we can look into applying these templates more practically. For starters, it is reasonable to have a global template as long as there is some provision for customizing each page. Ergo forms.

如果你已经读完第三部分,并且有点上当受骗的感觉,那就对了。(你有这样的感觉)主要的原因是,我们使用的那篇文章中所描述的模板,并没有多少可以动手实践的地方。整个应用程序中就只用一个模板。真的吗?那是不是意味着每个页面都有一个完全相同的外观?当然不是。这不能一概而论,页面模板只不过是描述了将表现层从应用逻辑层分离的一般方法。现在一切就绪,我们可以动手将模板付诸实践了。只要我们为定制页面的可能做好了准备,那么在开始的时候,有一个全局模板是合乎情理的。Ergo forms

Forms are actually nothing more than a template within a template. In fact the class_form.php class actually inherits from the class_template.php class. In the global template we put a place-holder for the form, <?=$form;?>, and then in the code set the $form variable of the page template to the result of fetching another template for the form. The form template itself does not necessarily have to have an HTML form element; but, since web application frequently deal with dynamic content, you will usually want to place the form tag into the the

template anyway. Now that we have solved that problem lets discuss the real power of forms: data.

表单其实只不过是模板中的模板。实际上,class_form.php类继承自class_template.php类。在全局模板中,我们放置一个表单的占位符:<?=$form?>,然后,在代码中将$form这个页面模板变量的值设置为指定的表单模板的内容。表单模板自身并非一定要有一个HTML的表单元素。然而,因为web应用程序总是要处理动态内容,所以,无论如何,你都会经常将表单标签放入到模板中。现在我们已经解决了那个问题,接下来我们来讨论一下表单真正的灵魂所在——数据。

Persistent Data

持久型数据

One of the difficulties in writing web applications is maintaining persistent data across multiple submits of the same page. This is not a problem for thick client applications because all of the data is stored in the client's memory. Since HTTP is a stateless protocol, however, we have to collect all the variables that were submitted using the POST or GET methods and send them back to the client with every single post back. Traditional web sites don't have to do this because every page is designed to do only one task so it gets submitted only once. The difference is that we are writing a web applications where each page is a modular entity. If we were to write a separate page for every button that's contained on the page then we would have an order of magnitude more pages to maintain. Furthermore, the very concept of a button (in a traditional thick client sense) doesn't exist in HTML. Yes you have something that looks like a button control but all it does is submit a page. Its up to us to connect the server side logic and have it execute when that button is clicked.

编写web应用程序一个困难的地方,就是在同个页面的多次提交中维护持久数据。这对富客户端应用程序(译者注:如桌面应用程序即可视为富客户端应用程序)来说并不是什么问题,因为所有的数据都存储在客户端的内存中。因为HTTP是一个无状态的协议,所以,我们不得不使用POST或者GET方法来收集所有提交过来的变量,然后在每一次回发(post back)中,再将其发回给客户端。传统的web站点并不需要做这件事,因为每个页面都被设计为仅处理单一的任务,所以,只需要提交一次就够了。而我们现在编写的这个web应用程序与之不同,每个页面都是一个模块实体。如果我们为每个按钮编写一个单独的页面,那么我们可能就会有一大堆的页面要维护了。此外,传统意义上的(富客户端中的)按钮概念,在HTML中并不存在。是的,你要说确实有一些看似按钮的控件,但它所能做的,仅仅是提交页面。按钮被点击的时候,连接服务端逻辑,并执行相应的操作,这些事情都取决于我们。

Another point about data is that not all of it gets submitted during a HTTP connection. What about the data that is generated at the server and needs to be maintained inside each page. For example, lets say you are displaying a table of data and want to remember which column was used to sort the data. You can store this in a hidden input control inside the form; but, if your page has many of these kinds of properties, then you may not want the overhead and burden of maintaining all of them this way. What other choice do you have?

另外,在一个HTTP连接中,并非所有的数据都是来自于页面提交。比如服务器产生的需要在每个页面中维护的数据。举个例子,你正在展示填满数据的表格,并且想要记住哪一列是被用来排序数据的。你可以将这个信息存储在表单里头一个隐藏的输入控件中,然而,如果你的页面有许多的属性,用这样的方法去维护它们,可能会使你耗费精力,负担过重。有没有其他的方法呢?

If you look at the template class carefully, you will notice that everything that we are considering data is just a value that needs to be embedded somewhere within our page template. If we store this data across post backs, then we don't have to worry about maintaining the other data that gets submitted with each page. This is precisely the what the form class does. We save all the variables just before we output the form to the client and load them back as soon as the form class gets instantiated. All the form variables are stored in a hidden input control named __FORMTATE. If you are familiar with ASP.NET way of doing things then you will realize that this a very similar approach. Note that when using the framework you do not have to worry about how this happens because the form class updates the information accordingly and even updates the values that were submitted during the last post-back. The one cumbersome issue with this approach is that any form property that corresponds to an actual control on the form (i.e. an item that sends a name/value pair with the page request) needs to be indicated to the form class so that the form properties can be automatically updated with the latest value send back. This is why we need to pass a $form_controls array when instantiating the form class. The code needed to maintain persistent form data is reproduced below.

如果你细心地看一看template class(译注:在class_template.php中定义了这个类),你会注意到,我们只是将数据当成一个值,这个值会被插入到我们页面模板的某个地方。如果我们在回发的过程中,一直保存这个数据,我们就不必为维护其他提交过来的数据操心。这正是form class所要做的(译注:原文链接有误,应该是form class)。在我们将表单输出给客户端之前,我们保存了所有的变量,在表单类实例化之后,我们立即将这些变量加载回来了。如果你熟悉ASP.NET的话,我们的这种处理方法与它是非常相似的。需要注意的是,当你使用这个框架的时候,你不必操心这一切是如何发生的,因为form class(译注:原文链接有误,应该是form class)会相应地更新信息,甚至会在最后一次回发时,更新那些提交过来的值。但是,这种方法有一个令人讨厌的缺点,和表单中实际的控件相关联的表单属性(比如:页面请求时发送一个键/值对的数据项),都必须在表单类中指明,以便表单的属性能够自动被更新为最新回发过来的值。这就是为什么在实例化form类的时候,我们需要传递一个$form_controls数组。维护持久型数据的代码如下:

function _saveState() {

$this->set("__IsPostback",true);

$state = chunk_split(encrypt(serialize($this->state_vars),session_id()));

$_POST["__FORMSTATE"] = $state;

}

function _restoreState() {

if(isset($_POST['__FORMSTATE'])) {

$state = decrypt($_POST['__FORMSTATE'],session_id());

$this->vars = unserialize($state);

$this->state_vars = $this->vars;

}

}

function _updateState() {

if (is_array($this->form_controls)) {

foreach ($this->form_controls as $state_var => $control_name) {

$this->set($state_var, $_POST[$control_name]);

}

}

}

Encryption

加密

HTTP is inherently a very insecure protocol. It was designed, as were many Internet protocols in the 1980's, with a security model based on user trust. Decades later, this model fell apart as the popularity of the Internet grew (and the users became less trustworthy). Although, we can still send data in clear text when implementing public forums, banking and e-commerce applications require a great deal more security. Why do I bring this up now? For starters, because SSL does not solve all of our problems. Yes we can encrypt the communication channel between the server and client with confidence but what happens to the data once it reaches the client. Once a client opens a web site, the HTML code used to generate the page is always available in clear text even if the communication channel was encrypted. That means that any data that we send to the client can remain in their browser cache or in their cookies indefinitely (and its all in clear text)[url=#notes]1[/url]. All of this makes it easier for mal-ware and spy-ware programs to gather data about its victims. Even worse is the situation where you don't want the user to see what properties are stored in the form.

HTTP天生就是一种很不安全的协议。和20世纪80年代被设计出来的众多internet协议一样,它将安全模型架构在对用户的信任之上。数十年后,随着Internet的增长扩大(同时,用户变得更加不可信),这样的模型便面临着崩溃的危险。虽然,对于公共论坛(这样的web程序),我们仍然可以发送明文数据。但是,对于银行、电子商务这样的应用程序来说,就需要更高的安全性。为什么现在我要谈论这些呢?首要的原因是,SSL并不能解决我们所有的问题。确实,我们完全可以对服务端和客户端之间的通信进行加密,但那些已经发送到客户端的数据呢?尽管我们加密了服务端和客户端之间的通信,但当一位用户打开一个web站点,HTML代码生成的页面总是以明文方式呈现。那就意味着,我们发送到客户端的任何数据都有可能被保存在客户端浏览器的缓存中,或者可能保存在cookie中(都是以明文的方式保存)。这会给那些恶意软件或间谍软件收集用户(受害者)的数据提供便利。当你不想让用户看到表单中存储了哪些属性值的时候,情况会更糟。

Now that I have hopefully convinced you that encryption of our form data is necessary, lets look in to how we can implement it. You will notice that the _saveState() and _restoreState() methods reproduced above call the encrypt() and decrypt() functions respectively. These functions are implemented in the functions.php file and require the mcrypt library for PHP to be installed on the server. Lets look at how these functions work.

现在,我希望你深信,加密表单数据是十分必要的,下面就让我们来看一下,如何来实现加密。你会注意到复制在上面的_saveState()和_restoreState()方法,都分别调用了encrypt()和decrypt()函数。这些函数在functions.php中编写好了,函数使用了安装在服务器上的PHP的mcrypt库。下面,让我们来看一看这些函数是如何运作的:

//encrypt plane text using a key and an mcrypt algorithm

function encrypt($pt, $key, $cipher='MCRYPT_BLOWFISH') {

$td = mcrypt_module_open(constant($cipher), "", MCRYPT_MODE_OFB, "");

$key = substr($key, 0, mcrypt_enc_get_key_size($td)); //truncate key to length

$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND); //create iv

mcrypt_generic_init($td, $key, $iv); //init encryption

$blob = mcrypt_generic($td, md5($pt).$pt); //encrypt

mcrypt_generic_end($td); //end encryption

return base64_encode($iv.$blob); //return as text

}

//decrypt ciphered text using a key and an mcrypt algorithm

function decrypt($blob, $key, $cipher='MCRYPT_BLOWFISH') {

$blob= base64_decode($blob); //convert to binary

$td = mcrypt_module_open(constant($cipher), "", MCRYPT_MODE_OFB, "");

$key = substr($key, 0, mcrypt_enc_get_key_size($td)); //truncate key to size

$iv = substr($blob, 0, mcrypt_enc_get_iv_size($td)); //extract the IV

$ct = substr($blob, mcrypt_enc_get_iv_size($td)); //extract cipher text

if (strlen($iv) < mcrypt_enc_get_iv_size($td)) //test for error

return FALSE;

mcrypt_generic_init($td, $key, $iv); //init decryption

$pt = mdecrypt_generic($td, $ct); //decrypt

mcrypt_generic_end($td); //end decryption

$check=substr($pt,0,32); //extract md5 hash

$pt =substr($pt,32); //extract text

if ($check != md5($pt)) //verify against hash

return FALSE;

else

return $pt;

}

复制代码

By default, we will encrypt the form data using the Blowfish algorithm with the client's unique (pseudorandom/dynamically generated) session ID as the key. The Blowfish algorithm was used because benchmarking showed that it ran in a reasonable time for encrypting both small and large amounts of data. Also, the cypher text produced is proportional in size to the plane text. These are all good qualities to have when we are planing to encrypt arbitrary amounts of data.

默认情况下,我们使用Blowfish算法,并用客户端唯一的(伪随机/动态产生的)会话ID作为加密密钥,来加密表单数据。之所以使用Blowfish算法,是因为基准测试(benchmarking)显示,在加密少量和大量数据时,这个算法所耗费的时间都比较合理。对于加密不确定数量的数据,这个算法给我们提供了良好的保证。

I will not get into the details of using the mcrypt library in this article (see the PHP manual for details) but will describe the general algorithm used for encryption. After the library is initialized using our key, we take the plane text and append the MD5 hash of the text prior to encryption. When we are decrypting the cypher text, we will take the decrypted text, separate out the MD5 hash that was appended (this is always 32 bytes in length), and compare that MD5 hash to the MD5 hash of the text just decrypted. This way even if someone manages to modify the the cypher text in a meaningful way, we will be able to see that it was changed. Also, since the MD5 hash contains 32 bytes of random data, adding it to the plain text will make cracking the code without the key very difficult. This approach will work well as long as the the hacker does not have access to the session ID of the client (which is not always a good assumption… expecially when the Session ID is stored in a session cookie on the client).

我不会在这篇文章中过多地谈论使用mcrypt库的细节(详细的使用方法请参考PHP手册),但我会谈论用来加密的常规算法。在mcrypt库用我们的密钥初始化后,我们将原始文本的MD5散列值添加到原始文本的前面,来进行加密。在我们解密的时候,我们从加密了的文本中分离出添加了的MD5散列值(MD5散列值总是32位的)和原始文本,然后将这个分离出来的MD5散列值与原始文本的MD5散列值进行比较。这样的话,即使有人能够修改加密后的文本值,我们也可以察觉出改动。同时,因为MD5散列值是32位无规律的数据,将它添加到明文中,如果没有解密密钥,就很难进行破译。只要黑客没有获取客户端的会话ID(我们不应该总做这样的假设,特别是当会话ID保存在客户端的会话cookie中的时候),那么我们的加密机制就会正常运作。

While we are on this subject, why not compress the data before/after it is encrypted and reduce the amount of data that is transmitted between the server and client. Well, to be honest, I tried. It turned out that the time it took to compress/decompress this data was longer that any amount of time that we would have saved by reducing the transition delay. In fact, the format of the serialized or encrypted data that we would be compressing is such that compression is not very effective in reducing the size (i.e. there are not many repeatable elements in the text).

既然我们谈到这里,那就顺便来谈谈为什么不在加密前(后)压缩一下数据,以减少服务端和客户端的数据传送量。老实说,我试过了。结果表明,压缩解压缩数据所耗费的时间比我们减少数据量所节约的传送时间还要长。实际上,串行化(serialize)或加密了的数据,压缩并不能有效地减小数据的大小(比如:文本中没有太多重复东西的)。

Form Events

表单事件

So we have our new page, it looks pretty, and is somewhat secure. What do we do when the user actually clicks on a button. If you look back to Part 1 of the series you will remember that the system base class has an abstract function handle FormEvents() which gets called as the page is processed. You can overwrite this function in each page and handle the events accordingly. For example, lets say you have a button on your page called cmdSearch and you want to see if it was clicked. All you have to do is test for isset($_POST["cmdSearch"]) inside the handleFormEvents() function.

现在我们的新页面看起来很漂亮,并且具有一定的安全性。那么,当用户真的点击一个按钮的时候,我们应该做什么呢。如果你回头看一看本系列教程的第一部分,你会记得系统的基础类中有一个抽象函数handleFormEvents(),在处理页面的时候会被调用。你可以在每个页面中覆盖(重写)这个函数,以更好地处理事件。举个例子来说,在你的页面上有一个按钮叫做cmdSearch,你想知道它是否被点击了。你所要做的事情,就是通过handleFormEvents()函数中的isset($_POST["cmdSearch"])语句来进行检测。

This approach looks simple enough but it only solves half of our problem. Any HTML element can trigger an onClick JavaScript event on the client. What if we want that event to be handled on the server side. Since the object clicked does not necessarily have to be a button, you wouldn't always be able to find out if it was clicked using the approach described above. To solve this problem, our form template will need two more hidden input objects: __FORMACTION and __FORMPARAM. We can test for these values when the page is submitted and simply set the values in JavaScript whenever any event is raised that needs to be handled by the server. To set these objects in JavaScript and then submit the form we would do something like:

这个方法看起来很简单,但它只是解决了我们问题的一半。在客户端的任何的HTML元素都可以触发一个JavaScript的onClick事件。如果我们想在服务端处理那样的onClick事件,那又该怎么办呢。因为被点击的对象并不一定是一个按钮,所以,如果你使用上面所描述的方法,并不总能知道那个对象是否被点击了。要解决这个问题,我们的表单模板需要两个额外的隐藏输入控件:__FORMACTION和__FORMPARAM。我们可以在页面提交的时候检测这些值,一旦(客户端)有任何需要服务端处理的事件产生,我们可以用JavaScript简单地 设置这些值。要在JavaScript设置这些对象,并提交表单,我们可以用像下面这样的代码:

function mySubmit(action, param) {

document.forms[0].__FORMACTION.value = action;

document.forms[0].__FORMPARAM.value = param;

document.forms[0].submit();

}

复制代码

Note that since our model supports only one form element per page, we can simply refer to the first form in the forms array when accessing the objects. In practice, you wouldn't want to have multiple forms on a single page anyway.

注意,因为我们的模型仅支持一个页面有一个表单,所以,在访问对象的时候,我们可以简单地通过forms数组来引用第一个表单(译注:即document.forms[0])。总的来说,在实际的应用中,我们并不会想在一个单一的页面上放置多个表单。

Error Handling

错误处理

Customized error pages are necessary for any web application in order to improve the users experience whenever the inevitable errors occur. So, since we have to build the pages anyway, lets demonstrate how to apply this framework to writing a custom error page which lets the user save the error that occurred into our database (whether you actually address the problem is up to you). Lets start with a file for the form template which will contain our user interface.

对任何一个web应用程序来说,总是不可避免地会产生错误,因此我们需要有自定义错误页面这样的功能,以便(发生错误的时候)提升用户体验。虽然我们已经建立过若干个页面了,但还是演示一下,如何使用这个框架来编写一个自定义的错误页面,这个错误页面可以让用户在遇到错误的时候,将所产生的错误保存到我们的数据库中(当然,是否考虑这个问题完全取决于你)。我们先创建一个表单模板文件,在文件中编写我们的用户界面,代码如下:

<form action="<?=$_SERVER['PHP_SELF']?>" method="POST" name="<?=$formname?>">

<input type="hidden" name="__FORMSTATE" value="<?=$_POST['__FORMSTATE']?>">

<div align="center" style="width: 100%">

<div style="height: 280px;">

<img src='<?=IMAGE_PATH."error/error".$err.".gif"?>' style="padding-top: 20px">

</div>

<div style="height: 120px">

<?=$message?><br><br>

<? if (isset($_POST["log"])) : ?>

Click <a href="#" onclick="history.go(-2)">here</a> to go back.

<? else : ?>

If you think there is something wrong with the site, <br>

please submit the error for review so that it can be fixed. <br><br>

<input type="submit" name="log" value="Submit">

<? endif; ?>

</div>

</div>

</form>

复制代码

As you can see there is a little logic embedded in the template file but so be it. I didn't want to over complicate things by doing everything by the book (this is after all an example of how to mold the application framework to various needs). Now lets look at the main PHP file. The first thing that you will notice is that the NO_DB constant is defined because, in the usual case, we will not need a database connection. If the user wants to log the error, then we can create the database connection ourselves [url=#notes]2[/url]. Also notice that this page does not use the same generic template as the rest of the web site is using (the “error.inc.php” template is used to give the error page a special look). The rest of the page is fairly straight forward. We have to include the system base class in every page and then we derive our current page from the base class. In the init() method we indicate that we want to use a form and specify where the form template is located. Then, we check if this is the fist time that this page loaded (i.e. the user has not yet clicked any buttons on this page which would cause it to post back to itself). If so, we set the appropriate form properties. When the user clicks on the submit button, the handleFormEvents() function records the error in our database. Note that you will probably want to log a lot more information in your error pages.[url=#notes]3[/url].

正如你所看到的,我们在模板文件中放入了一些逻辑处理代码。我不想墨守成规,一切都按书本行事,那样会把问题复杂化(毕竟这只是一个例子,只是用来说明如何来架构一个框架,以满足各种各样的需求)。现在我们来看一看主要的PHP文件。首先,你会注意到,我们定义了NO_DB常量,通常情况下,我们不需要一个数据库连接。如果用户想记录下错误,我们可以自己创建一个数据库连接。同时,这个页面没有使用与web站点其余部分相同的通用模板(error.inc.php模板用来为错误页面展现一个特别的外观)。页面的其余部分十分明了。我们在每个页面中include系统基础类,然后让当前的页面类继承系统基础类。在init()方法中,我们说明了我们要使用一个表单,并且指定了表单模板的位置。然后,我们检测这个页面是不是首次加载(比如:用户还没有点击这个页面上的任何按钮,从而导致页面内容的回发)。如果是,我们就设置好适当的表单属性。当用户点击了提交按钮,handleFormEvents()函数就会将错误记录到我们的数据库中。当然,你可能想在错误页面类中记录更多的信息。

define("NO_DB", 2);

include "include/class_system.php";

class Page extends SystemBase {

function init() {

$this->form = new FormTemplate("error.frm.php", "frmError", true);

if ( !$this->form->IsPostback() ) {

switch($_GET['err']) {

//specify all the error which you want to handle here

case 404:

$title = _("File not Found");

$message = _("The URL that you requested, '") . $_SERVER[REDIRECT_URL] .

_("', could not be found.");

$this->form->set("err","404");

break;

case 500:

$title = _("Server Error");

$message = _("The requested for ") . $_SERVER[REDIRECT_URL] .

_(" caused a server configuration error.");

$this->form->set("err","500");

break;

default:

$title = _("Unknown error");

$message = _("An unknown error occured at ") . $_SERVER[REDIRECT_URL];

break;

}

$this->form->set("notes",$_SERVER[REDIRECT_ERROR_NOTES]);

$this->form->set("redirectURL",$_SERVER[REDIRECT_URL]);

$this->form->set("redirectQueryString",$_SERVER[REDIRECT_QUERY_STRING]);

$this->form->set("remoteIP",$_SERVER[REMOTE_ADDR]);

$this->form->set("userAgent",$_SERVER[HTTP_USER_AGENT]);

$this->form->set("referer",$_SERVER[HTTP_REFERER]);

$this->form->set("lang","en");

} else {

$title = _("Error Page");

$message = _("Thank you for reporting the error.");

}

$this->page->set("title",$title);

$this->form->set("message",$message);

}

function handleFormEvents() {

if (isset($_POST["log"])) {

$db = db_connect();

$Number = $db->quote($this->form->get("err"));

$Notes = $db->quote($this->form->get("notes"));

$RequestedURL = $db->quote($this->form->get("redirectURL"));

$Referer = $db->quote($this->form->get("referer"));

$sql = "INSERT INTO tblErrorLog(Number, Notes, RequestedURL, Referer)

values($Number, $Notes, $RequestedURL, $Referer)";

$result = $db->query($sql);

if (DB::isError($result))

die($result->getMessage());

$db->disconnect();

}

}

}

$p = new Page("error.inc.php");

The Page Dispatcher

页面分发器

Here is another example of how to apply this framework to a page that doesn't quite fit the mold. Look at the URL of the current page. Notice anything interesting. The entire site is written in PHP and yet you are looking at a .html file. I'll give you a hint, the HTML page is not actually there. In fact, the page that you looking at is called articles. The web server was configured to interpret the file as a PHP file even though it does not have a .php extention. Also, using the AcceptPathInfo On directive, you can configure Apache to backtrack along the requested URL until it find the desired file. So in this case, the HTML file is not found and the file articles is found so everything after the valid file name (i.e. /article_name.html) is passed into the articles PHP file as a query string. Why would we want such a convoluted system. Well, for starters, this hides the server's internal folder structure from the users. More importantly, however, it makes the URL more human readable than something like "articles.php?ID=12345") and increases the chances that search engines will cache the page. Many search engines don't cache dynamic pages and having a query string like "?ID=12345" is a dead give away.

这儿是另一个例子,它演示了如何将这个框架应用于一个 看一看当前页面的URL。注意任何有趣的东西。整个网站都是用PHP编写的,然而你却看到一个.html文件(译注:即整个URL看起来是指向一个html文件)。我给你一个提示:其实那里并没有一个真正的HTML页面。实际上,你看到的页面是articles。web服务器被配置成将文件解析为一个PHP文件,尽管(被解析的)文件并没有一个.php的扩展名。同时,你可以将AcceptPathInfo设成On,这样apache就会沿着URL(所指示的路径)一直往回查找,直到找到所需要的文件为止。这样一来,没有找到HTML文件,但找到了articles文件,在有效文件名之后的字符串(比如:/article_name.html)就会作为查询字符串(query string)传递给articles这个PHP文件。为什么我们要用这样一个拐弯抹角,令人费解的机制呢?首先,它对用户隐藏了服务器内部真正的文件夹结构。更重要的是,比起"articles.php?ID=12345"这样的形式,它使URL更具可读性,并增加了搜索引擎缓存页面的机率。许多搜索引擎并不缓存动态页面,像通过传递"?ID=12345"这样的查询字符串才能看到的页面,搜索引擎会完全对它置之不理。

How does one implement articles using this application framework? Because of the template engine, its actually quite simple. Take a look.

如何使用本应用程序框架来实现一个articles呢?通过我们已有的模板引擎,实现起来相当简单。下面是实现代码:

class Page extends SystemBase {

function init() {

//determine page from query string

$page = substr($_SERVER['PATH_INFO'],1,strpos($_SERVER['PATH_INFO'], '.')-1);

if (file_exists("templates/_$page.frm.php")) {

$this->form = new FormTemplate("_$page.frm.php", "frmArtile");

} else { //article not found

$this->redirect(BASE_PATH."error.php?err=404");

}

}

}

$p = new Page("view.inc.php");

Wrapping up

装配

By now, we have covered all the necessary aspects of designing web applications in PHP. Although the framework described here may not be exactly what you need, hopefully this series of articles provided a basic idea of the issues that need to be considered when writing your own web applications in PHP. Feel free to contact me if you have any questions or comments.

到现在为止,我们已经谈论了设计PHP Web应用程序所需要的方方面面。虽然这里描述的这个框架可能并不是你确切想要的,但我希望这个系列的教程,能够在你编写自己的PHP Web应用程序时,给你一个基本的参考,知道应该考虑哪些问题。如果您有任何问题或看法,欢迎随时跟我联系。

分享到:
评论

相关推荐

    84PHP开源框架 v1.1.0

    4PHP是一套完全遵守APACHE开源协议的便捷PHP框架,独特的模块化设计,使得框架简洁易懂。一行代码即可完成在线支付、发送短信、分页等功能,前所未有的简单和高效!同时,84PHP还具有云服务功能,这也是国内首款与...

    PHP入门到精通

    9.5JavaScript事件 9.6JavaScript脚本嵌入方式 9.7在PHP中调用JavaScript脚本 日期和时间(教学录像:27分16秒) 10.1日期和时间的概述 10.2处理日期和时间 10.3Unix时间戳 10.4系统时区设置 10.5开发中遇到的日期和...

    PHP入门到精通02

    9.5JavaScript事件 9.6JavaScript脚本嵌入方式 9.7在PHP中调用JavaScript脚本 日期和时间(教学录像:27分16秒) 10.1日期和时间的概述 10.2处理日期和时间 10.3Unix时间戳 10.4系统时区设置 10.5开发中遇到的日期和...

    框架:使用Slim,Twig,Eloquent和Sentinel精心设计的时尚PHP应用程序框架,旨在在数分钟之内使您从克隆到生产

    Dappur PHP框架 一个时尚PHP应用程序框架,它使用Slim,Twig,Eloquent和Sentinel精心设计,旨在让您在数分钟内从克隆到生产。 建立在Slim PHP Micro Framework,Twig模板引擎,雄辩的ORM数据库交互,Phinx数据库...

    glowie:功能强大且轻量级PHP框架

    Glowie是一个PHP框架,旨在以最简单,快速,强大的方式轻松开发应用程序和动态网站。 特征 完整的MVC结构,组织简单 强大的数据库模型具有随时可用的数据处理功能 一套最安全的表单和数据验证工具 快速但强大的模板...

    ZnetDK 4 Mobile:PHP和JS中的响应式Web App开发全栈框架-开源

    通过易于定制的完全响应式和可安装(PWA)的入门应用程序,可以轻松地用PHP,MySQL和JavaScript开发适用于移动和平板电脑设备的企业Web应用程序。 无需从头开始。 您只需要添加满足您的业务需求的视图,并将它们连接...

    WEB开发必备几乎包含所有参考资料

    Linux常用命令全集,linux入门文档,MySQL中文参考手册,PHP_MySQL教程,PHP的一些例程,PHP的一些例程,prototype,Spring+in+Action中文版,SQL Server精华 (CHM),Struts快速学习指南,Validato表单验证,WINDOWS脚本技术,...

    Full-Stack-Tutorials

    全栈教程 Ahmed El-Nemr的前端和后端开发教程 入门 届会 ... CSS, Bootstrap3,4, 网络概念 ... PHP和HTML ... PHP表单, ... SQL和MySQL, ... Laravel PHP框架,以及 使用Laravel(Portofile)的CMS项目。

    Beginning-symfony-2-web-development:Tuts +课程的源代码-web development source code

    Symfony 2是最流行的现代PHP框架之一。 它具有模块化,可扩展和功能完善的优点。 Symfony 2 Web开发入门是为框架的首次用户设计的,将带您逐步了解其所有基本编码功能。 我们将介绍入门所需的所有主题,例如捆绑包...

    dayrui-xunruicms-master.zip

    CodeIgniter 安装包中包含《用户手册》,手册囊括了入门介绍、教程、“手把手”指导,还包括了框架组件的参考文档。 二、效率与安全 1、运用全新PHP7语法特性,设计时考虑到性能优化,运行效率高达4倍于PHP5系列...

    迅睿CMS免费开源系统-PHP

    CodeIgniter 安装包中包含《用户手册》,手册囊括了入门介绍、教程、“手把手”指导,还包括了框架组件的参考文档。 二、效率与安全 1、运用全新PHP8语法特性,设计时考虑到性能优化,运行效率高达4倍于PHP5系列开发...

    风越.Net代码生成器 [FireCode Creator] V1.3 精简版

    默认提供asp、aspx两种代码框架及多个界面设计模板,均可任意修改管理。通过自定义生成程序的界面风格与输出代码,用户可将其扩展为ASPX/ASP/PHP/JSP等各种程序的代码生成器。内置的.net代码框架,能建立C#语言的...

    JAVA上百实例源码以及开源项目

    例如,容易实现协议的设计。 Java EJB中有、无状态SessionBean的两个例子 两个例子,无状态SessionBean可会话Bean必须实现SessionBean,获取系统属性,初始化JNDI,取得Home对象的引用,创建EJB对象,计算利息等;...

    JAVA上百实例源码以及开源项目源代码

    例如,容易实现协议的设计。 Java EJB中有、无状态SessionBean的两个例子 两个例子,无状态SessionBean可会话Bean必须实现SessionBean,获取系统属性,初始化JNDI,取得Home对象的引用,创建EJB对象,计算利息等;在...

    POSCMS开源内容管理系统

    2、安装包中包含《用户手册》,手册囊括了入门介绍、教程、“手把手”指导,还包括了框架组件的参考文档。 3、只需 PHP 5.3.7 以上版,完美支持PHP7,几乎可以运行在任何虚拟主机或云平台上。 4、只需遵守几个注意...

    毕设&课设&项目&实训-基于go-restful+Vue3.0+lement-Plus的前后端分离开发.zip

    支持设备管控,规则链,云组态,可视化大屏,报表设计器,表单设计器,代码生成器,监控视频接入等功….zip所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间...

    迅睿CMS免费开源系统

    二、效率与安全1、运用全新PHP7语法特性,设计时考虑到性能优化,运行效率高达4倍于PHP5系列开发环境2、运用CI框架的扩展性和路由模式,加上ZF框架强大丰富的中间件和扩展包,大大提高系统的扩展性能3、Zend框架官方...

    HTML5移动Web开发指南.pdf

    内容简介编辑 ... 同时,如果你是如下几类人群之一,那么本书非常适合你...第9章 重量级富框架Sencha Touch入门 187 第10章 跨平台的PhoneGap应用介绍 273 第11章 构建基于HTML5的生活轨迹Web App 337 第12章 进阶之路 366

Global site tag (gtag.js) - Google Analytics