《Zend Framework in Action》 《ZF实战》 中文翻译

第一章 Zend Framework介绍

PHP用来开发动态网页已经超过10年之久了。最初的PHP页面都是用HTML里嵌套PHP代码的形式写成的。这种方式一开始是很好的,因为它不仅是即时响应的并且对于简单脚本来说它能展现所有需要的功能。PHP通过版本3和4流行了起来,于是不可避免地将有越来越大的应用程序由PHP写成。很快人们便明显意识到对于大型站点来说,这种相互混杂的PHP和HTML代码编程模式不会是一种长期解决办法。
从日后的发展来看,最明显的问题是:可维护性和可扩展性。虽然混杂的PHP和HTML开发极其快速,但是从长期来看,它很难进行站点更新。一个真实严峻的事例表现在Web新闻方面,因为它们需要经常改变其内容和版面。大型站点一直都在改变,它们的外观会定期的更新,新的内容被添加,或者根据用户或广告商的需要重新分类,这些事总得有人去做!
Zend Framework被用来帮助人们确保在长时间内,那些基于PHP编写的站点的维护变得容易。它包括了丰富的可复用组件,从一整套MVC组件到PDF文档生成器,可以说是包罗万象。在本书中,我们将看到如何在真实的web环境中使用这些组件。

1.1 PHP站点的结构设计

解决错综复杂的PHP和HTML代码的办法是构建结构。最普遍的PHP结构化程序的设计采纳了“相关性分离”的概念,意思是用来执行显示功能的代码不应该如图1.1一样同执行连接数据库和获取数据的代码放在同一个文件中:
1-1
图1-1 典型的PHP文件,PHP代码和HTML按线性顺序排列
对于大部分开发者来说,设计站点代码结构的第一步是:复用性。一般这意味着连接数据库的代码被分离开来放到一个譬如叫做db.inc.php的文件中,而将那些显示通用的header和footer元素的代码分离开来做成模板看起来也是合乎逻辑的。函数被设计用来帮助解决全局变量相互影响的问题。
随着网站的壮大,功能类似的函数被分组进入同一个类,使程序更容易地维护和添加新的功能。但这一阶段只能持续很短的时间,因为你的网站正在不断地扩大,代码变得如此庞大,直到有一天你的脑海里无法想象出代码是如何工作的。
PHP程序员已经习惯于站在巨人的肩膀上,因为我们的语言提供了许多容易的接口譬如GD图像类,各种数据库的连接类,甚至对于操作系统也提供了一些特别类,比如用来操作Windows上COM的类。面向对象的编程思想也不可避免地进入了PHP的视野,虽然当初PHP4提供的类还只能比数组稍稍强大一些,但如今的PHP5已经能支持所有你能在面向对象语言中想象的到的功能,从此刻起,你使用的类成员(public,private和protected)将与interface,abstract这些关键字一起工作,而且支持异常处理。
对于面向对象机制的改进使创建更复杂的类库(被称为框架)成为可能,例如Zend Framework支持一整套组织web应用程序文件系统的机制,它是一种MVC设计模式。如下图1-2所示:

图1-2 典型的MVC架构模式
用MVC原理设计的应用程序会产生更多的文件,每一个文件各司其职,这使得维护变得容易许多。比方说,所有进行数据库查询操作的代码都存储在同一种类里,它们通常被叫做Models,而HTML代码的集合则被称为View(可能会包含一些简单的PHP代码),Controller文件调用Model,将数据反映在相对应的View上,这样便得到了想要的页面。
Zend Framework并不是唯一一个使用MVC原理组织网站架构的选择,在PHP的世界里还有许多其它选择,下面让我们来看看Zend Framework所包含的东西以及为什么我们应该考虑使用它。

1.2 为什么使用Zend Framework

当你手中拿着这本书的时候,你可能很想知道为什么你要对Zend Framework感兴趣,而不是其它的PHP框架。简而言之,Zend Framework提供了一套标准的组件集,帮助你方便地开发web应用程序。这些应用程序能被轻松地开发、维护和扩展。
Zend Framework的主要特点是:

  • 包罗万象,功能全面
  • 现代设计理念
  • 易学
  • 完备的文档
  • 易于开发
  • 快速开发
1.2.1 包罗万象,功能全面

Zend Framework是一个综合性的大框架,包含了在开发web应用程序时所用到的各种工具,它包括一个健壮的MVC组件以确保网站架构能按实际需求搭建。与此同时,还有角色认证,搜索,本地化,PDF文档创建,email,web服务以及更多其它的神秘部件。如图1-3所示:

图1-3 Zend Framework被分为十个主要模块
这并不是说Zend Framework与其它的类库不友好,恰恰相反,该框架的核心设计是,你可以只使用框架的一部分来编写应用程序,而应用程序的其它部分可以使用诸如PEAR, Doctrine ORM或者Smarty模板类来完成。

1.2.2 现代设计理念

Zend Framework采用现代设计技术(也叫设计模式),用面向对象的PHP5编写。软件设计模式是公认的高层次设计问题的解决方案,但它不是一个方案的具体实现,具体实现取决于其他性质的设计。 Zend Framework利用多种设计模式并且它的实现被精心设计以保证为开发者提供最大的灵活性而不需要开发者做很多的工作。
这个框架能识别出PHP方式,并且不强迫你使用所有的组件,所以你能自由地在它们之间进行选择。 这一点尤其重要,因为它可以让您根据已存在的页面使用相应的组件。关键在于,这个框架的任意一个组件都几乎不依赖其它组件。

1.2.3 易学

如果你是像我这样的一个普通人,那么学习如此庞大的代码是多么的困难。幸运的是,Zend Framework是模块化的,它的设计目标是简单,这帮助我们能循序渐进地学习。每一个组件不依赖其它组件,因此,学习变得容易了。每一个组件的设计目的是,我们不需要明白组件在整个框架中是如何工作的,但却可以使用它并从中受益。一旦你有了使用这些组件的一些经验,便会自然而然地开始一步步使用它的一些高级特性,对于大部分用户来说,这是减少入门障碍的关键。
比方说,配置组件Zend_Config被用来向配置文件提供接口,它提供了两个高级特性:section overloading 和 nested keys,但是不需要为了使用而去理解它们的机制。一旦用户有了一个Zend_Config的工作实践,信心增加,使用高级特性便只是小事一桩。

1.2.4 完备的文档

无论代码写的多么好,文档的缺失会阻止一项工程获得通过。Zend Framework的目标瞄准那些不愿意深入专研框架源代码的开发者们,于是它把文档与代码的重要性放在了同一水平上,这意味着Zend核心团队不允许没有配套文档的新代码进入框架。
该框架提供两种类型的文档:框架API版和最终用户版。API文档使用phpDocumentor创建,它在源代码中自动生成特殊的“docblock”注释。这些注释通常能在每个类、函数和变量声明的开头找到。使用“docblock”的主要优势是在编码的时候,一些集成开发环境如PHPIDE in Eclipse或者Zend’s Studio可以提供自动完成工具提示,提高了工作效率。
Zend Framework还提供手册下载以及在线查询。手册提供了框架内所有组件的细节并且指明了哪些功能是可用的,还给出例子帮助你开始在程序中使用它们。更重要的是,对于更复杂的组件(如Zend_Controller),还给出了具体理论解释,以便于你理解组件的工作原理。该文档没有道出如何搭配所有的组件来制作一个完整的应用程序的过程,因此网络社区出现了许多教程来帮助开发者使用这个框架,它们被收集在framework’s wiki,网址是http://framework.zend.com/wiki/x/q。但这些教程点到为止,并不打算深入到每一个组件或者展示一些高级应用,这也是这本书存在的原因。

1.2.5 易于开发

我们注意到,PHP的一个长处在于用它开发简单动态页面是多么的容易,它使数百万人拥有了美妙的网站。PHP的一个能力是它的拥趸包括从编程初学者到需要加速工程进度的企业级开发员各类人群,Zend Framework的设计能降低各类人群的开发难度。
那么,它是如何使开发变得简单的呢?关键之处在于这个框架带来的是已在各种易于发现错误的调试代码中经测试而被证明是可靠的代码,你只要写你的应用程序逻辑所需要的代码,一些繁琐的底层代码已经为你准备好,这使得你的源码显得简洁整齐。

1.2.6 快速开发

Zend Framework使web应用程序更容易获得持续性或者添加新功能到当前站点,因为它为应用程序提供了最基本的组件,使开发者可以专注于应用程序的核心而不是那些最基础的部分,所以很容易快速入门某一个组件并能立刻看到结果。
另一个提升开发速度的地方在于该框架的组件所提供的默认用法是最常用的用法,就是说,你不必担心如何去为每个组件设置那庞大的配置参数而能直接使用它。例如,最简单的MVC引导程序只需以下几段代码:
require_once(’Zend/Loader.php’);
Zend_Loader::registerAuthoload();
Zend_Controller_Front::run(’/path/to/controllers’);
一旦启动并且运行了以后,为应用程序添加新页面犹如为类添加一个新函数那么简单,只是另外需要一个放在正确目录里的模板文件。同样地,Zend_Session提供了众多的可选参数来使它的表现符合你的想法,但其实大部分时候,没有一个参数是需要你去设置的。

1.2.7 易于维护的结构化代码

正如我们早先所看到的,结构化应用程序中不同职责的代码被分离开来。这也意味着寻找你所需要的代码块以及查找错误变得容易许多。类似的,当你要往显示代码中添加新特性的时候,所需要改变的是该显示代码所对应的逻辑部分,这避免了因为无意中修改了其它东西而导致的BUG。Zend Framework鼓励我们使用面向对象的编程方式,使工程更容易维护。

1.3 Zend Framework是什么?

Zend Framework是一个用来建立PHP应用程序的PHP类库。它提供一系列组件来帮助开发web应用程序并使应用程序在其生命周期内更容易维护和扩展。如此简单的描述不足以提供全面的信息,下面让我们来看看Zend Framework是来自何处以及大致了解它包含哪些内容。

1.3.1 它来自哪?

框架已出现多年,我在真实项目中所使用的第一个框架是Fusebox,它最初是为ColdFusion写的。自那以后出现了许多种框架,伴随而来的明星是用Java写的Struts。大量的Struts的PHP克隆版本出现了,但是都没有很好得翻译成PHP,最大的差异在于Java web应用程序运行在持续运转的虚拟机上面,因此对于每一个页面请求来说,程序的启动时间是不一样的。PHP从零状态开始初始化,因此大量的Struts克隆的初始化使它们变得相对缓慢。近日,一个新的框架来到世上,它基于相对不知名的叫Ruby的编程语言。Rails(或者Ruby on Rails,就像大家所称呼的)升华了配置的常规概念,在web开发世界里引起了风暴。在Rails出现后不久,大量的PHP版克隆,以及众多的被Rails激发灵感而创造的框架,而不仅仅是一个Rails的PHP拷贝。
在2005年年末,Zend Technologies,一个专注于PHP的公司,开始了它们的PHP协作工程来推动PHP的使用比例。这个项目有三个方向:一个叫做PDT的eclipse IDE插件,Zend Framework和Zend Developer Zone website(Zend开发者社区网站)。Zend Framework是一个制作PHP web开发框架的开源项目,寄期望于它能成为未来PHP应用程序所基于的标准框架。

1.3.2 它有些什么?

Zend Framework中不同的组件被分组形成了若干个模块,这一系列模块构成了框架。作为一个完整的框架,里面有你准备建立企业Web应用程序所需要的一切。但是系统是很灵活的,它的设计可以让你自由选择适合你的应用程序当前情况的组件。继先前已在图1-3中给出的组件之后,下面给出框架内所有的组件,如图1-4:

图1-4:Zend Framework包含了大量的组件足以满足企业级应用
框架的每一模块都包括许多组件,每一个组件又包括许多类,这些类我们将在后面的章节进行讲解。

核心组件

核心组件提供了一套功能全面的MVC系统,以建立视图与业务逻辑分离的应用程序。三个主要的类家族组成了MVC系统:Zend_Controller (Controller), Zend_View (View) 和Zend_Db(Model),如图1-5显示了基本的Zend Framework的MVC系统:

图1-5 Zend Framework方式的MVC
Zend_Controller类家族提供一个对controller行动(亦称命令)派遣请求的一个前面控制器设计,以便集中所有处理。视图模板被称为Zend_View,它提供一个基于PHP的模板系统。第一章先翻译到此,我发现作者很多废话,感觉像是在赚稿费,因此我打算先翻译第二章,实用万岁。

 

 

第二章 Hello Zend Framework!

 

在第二章,我们将看到一个输出“Hello World!”的简单的Zend Framework应用程序,对于普通的PHP应用程序来讲,源码应该像这样,它只由一行代码构成:
echo ‘Hello World’;
Zend Framework需要更多的文件来创建一个基础架构,因为一个完整的网站是在此架构上建立的。结果,我们的“Hello World!”程序的代码也许看上去徒然地絮絮叨叨。我们也将考虑如何组织在硬盘上的站点文件来确保我们能找到我们所需的并来看看Zend Framework是如何为应用程序设计一个MVC样式的。下面让我们先来看看所谓的Model-View-Controller是什么吧。

2.1 MVC设计样式

为了理解Zend Framework应用程序的工作流程,我有必要先讲一点基础理论。我们所创建的这些文件将关联到许多框架类,因此我们得首先来学习Controller基础。Zend Framework的控制器系统是MVC软件设计模式的一个具体实现,如图2-1。软件设计模式是一个通用问题的标准解决方法,这表明具体的实现会有差异,但是解决同类问题所采用的设计概念基本是一样的。MVC模式描述了一种将应用程序分离成三个部分的方式。

MVC设计模式图展示了一个web应用程序的三个主要部分以及一个分发器(dispatcher),它能找到处理相应请求的控制器(controller)

2.1.1 模型(Model)

MVC模式的Model部分在幕后工作,跟专门的应用有关,被称为商业逻辑,这部分代码决定了如何向电子订单添加运费数据或者如何取得某个客户的名字和姓氏,因此检索和存储数据的数据库是在模型层。Zend Framework利用Zend_Db_Table类来提供表级别的数据库接入,并允许轻松操纵应用程序所使用的数据。

2.1.2 视图(View)

视图是应用程序的显示逻辑部分,对于一个web应用程序来说,它通常指的就是构成页面HTML代码,当然也可能包括其它的,比如XML,它被用来构造RSS提要功能。另外,如果网站允许以CSV格式导出,导出的CSV将是视图的一部分。视图文件本身被称为模板,因为它们通常用来显示由model创建的数据。通常把更复杂的模板代码做成函数,称之为视图助手(View Helper),视图助手改进了视图代码的复用性。默认情况下, Zend框架的视图类(Zend_View)使用PHP的模板文件,但其他模板引擎,如Smarty或PHPTAL也可取代之。

2.1.3 控制器(Controller)

上面两种之外,应用程序剩下的部分是控制器。对于web应用程序来说,控制器决定如何处理web请求。Zend Framework中的控制器系统是基于前端控制器设计模式的,它利用句柄(Zend_Controller_Front)和动作命令(Zend_Controller_Action)进行协同工作。

2.2 剖析Zend Framework应用程序

一个典型的Zend Framework应用程序包含很多目录,这是为了分离程序不同的组成部分,顶层文件系统结构如图2-2:

图2-2 标准Zend Framework应用程序的文件系统
一共有四个顶层目录:
1. application
2. library
3. tests
4. web_root

2.2.1 application目录

应用程式目录中包含所有该应用程序运行所需要的代码,web服务器不能直接访问它。为了进一步分离显示、业务和控制逻辑,application目录中包含了用于存放model, view和controller文件的次级目录。根据需要,可能还会有其它的次级目录,比如存放一个名叫settings.ini配置文件的config目录。

2.2.2 library目录

所有的应用程序都使用类库,它是事先已经写好的可复用代码。在一个Zend Framework应用程序里,zend框架本身就存放在library文件夹中,其它的类库或框架像是用户自己编写的框架,数据库ORM类Propel,还有Smarty模板引擎等等也都存放于此。
类库可存储在任何应用程序能找到的地方,无论是全局目录还是本地目录。全局目录能被该服务器上的所有应用程序访问,例如/usr/php_include (对于Windows来说,可能是 c:codephp_include),我们可以使用php.ini配置文件中的include_path setting对路径进行默认包含。另外,每个应用程序可以在其自身目录下存储类文件,这种情况下,我们往往把类库存放在一个叫library的文件夹里,当然有时也会命名为lib,include或者inc。

2.2.3 test目录

test目录用来存放所有的单元测试代码。单元测试是用来帮助确保代码在整个应用程序生命周期中随着它的增长和变化而能继续工作。随着程序的壮大,先前编写的代码常常需要因为新功能的加入而被更改(称为refactored)。在PHP的世界里,单元测试很少被认为是重要的,但是如果你对自己的代码进行了单元测试的话,你会很感谢自己的。

2.2.4 web_root目录

为了提高web程序的安全性,从服务器里应该只能存取用户可直接访问的文件。正如Zend Framework所使用的前端控制模式,所有的web请求都要从一个单一的文件通过,这个文件通常是index.php,这个文件是唯一一个能让用户访问的php文件(译者:也就是说访问其它php文件的时候必定会事先访问index.php,这时会进行一些预处理),因此它存放在web_root目录下,其它的普通文件例如images,CSS和Javascript这些允许用户直接访问的文件也在此处设立了自己的分目录。

2.5 Hello World:File By File

(译者:我不知道怎么直接跳到2.5节了,原书上是这样的)
我们需要新建4个文件来创建我们那简单的Hello World应用程序:一个启动文件(index.php),一个Apache配置文件,一个控制器(Controller)文件,一个视图(View)模板,当然Zend Framework类库肯定已经在library文件里头了,最终程序会是这个样子,如图2-3:

一个最小化的Zend Framework应用程序

2.5.1 启动(Bootstrapping)

启动是指开始一个程序,在前端控制器模式中,这是唯一存在于根目录的php文件,通常就是index.php。所有的web请求都将用到这个文件,因此它被用来设置整个应用程序的环境,设置Zend Framework的控制器系统,然后启动整个应用程序。过程如代码清单2-1所示:
Listing 2.1: web_root/index.php

 


error_reporting(E_ALL|E_STRICT); #1 设置错误报告
ini_set(’display_errors’, true);
date_default_timezone_set(’Europe/London’);
$rootDir = dirname(dirname(__FILE__)); //index.php文件的上级目录的上级目录,在这个文件架构中就是根目录
set_include_path($rootDir . ‘/library’ #2 设置默认的包含路径,PATH_SEPARATOR是分隔符,若服务器操作系统为Linux,它就是指’/',而在
. PATH_SEPARATOR . get_include_path()); #Windows系统中,它是指”,读者可以自己试着输出get_include_path(),看看能得到什么
require_once ‘Zend/Loader.php’;
Zend_Loader::loadClass(’Zend_Debug’);
Zend_Loader::loadClass(’Zend_Controller_Front’);
// 设置controller
$frontController = Zend_Controller_Front::getInstance(); #3 获得Zend_Controller_Front实例
$frontController->throwExceptions(true); #4 抛出错误信息,在正式产品中不要这么干
$frontController->setControllerDirectory(’../application/controllers’);
// 启动!
$frontController->dispatch();

让我们来看看这个文件更多的细节。这个文件大部分的工作就是初始化。起初,设置错误报告(#1) 以确保所有的错误或警告能显示。PHP5.1所采用的一套新的时间和日期函数需要知道我们处在世界的哪个时区,有很多种方法来设置时区,最简便的是使用date_default_timezone_set()函数。

 

在编写Zend Framework应用程序时我们要求library目录被包含在php_include里,有好几种方法来做这件事。若要在整个服务器范围内都能使用library目录下的内容,最快的方法是直接在Php.ini里修改include_path设置。一个更具有可移植性的方法是(特别是当你在同一台服务器里使用了多种版本的框架时),像上面那样在启动文件里设置包含路径(#2)。

Zend Framework应用程序不依赖任何特殊的文件,然而事先装载几个帮助类还是很有用的。Zend_Loader::loadClass()是根据类的名字来包含正确的文件,它的功能是将类名中的下划线转化为目录分隔符,经过检查发现没有错误后再包含这个文件。因此Zend_Loader::loadClass(’Zend_Controller_Front’); 和include_once ‘Zend/Controller/Front.php’;这两行代码将获得相同的效果。Zend_Debug::dump()会以var_dump()格式输出一段有关变量的调试信息。

Bootstrap的最后一部分设置前端控制器然后启动。前端控制器类Zend_Controller_Front实现了单入口设计模式(#3),类的定义本身意味着只能允许有一个实例对象。单入口设计模式适合前端控制的理由是他确保总是只有一个类在处理请求,这种设计导致的一种结果是我们无法使用new操作符来创建一个新对象而必须使用getInstance()静态成员函数。前端控制器有一个特点是他能捕捉所有默认抛出(throw)的例外(exception)并把它们保存在由它创造的一个响应对象(response object)中。这个响应对象保存所有有关针对URL请求和HTML的响应信息,它们是HTML头信息,页面内容和任何被抛出的例外。当处理完请求后,前端控制器自动发送头信息并且显示页面内容。

在我们的Hello World应用程序中,我将命令前端控制器抛出所有已发生的例外(#4)。对于刚接触Zend Framework的新手来说,在响应对象中存储例外的默认行为使人感到迷惑,因此我们把它关掉并且迫使错误信息显示出来。当然了,在一个正式产品中,你绝对不可以把错误显示给用户看,因此你应该让控制器捕捉错误信息或者用try/catch块包住index.php中的代码。

我们调用前端控制器的dispatch()函数来启动应用程序,这个函数将自动创建一个请求和响应对象来为我们encapsulate应用程序的输入输出。然后它将创建一个路由器来获得用户请求的控制器和动作,接着一个分发器对象载入正确的controller类和action成员函数来做”真正的”工作。

最后,正如我们上面所提到的,前段控制器把数据输出到响应对象于是一个web页面呈现在大家面前了。

2.5.2 Apache .htaccess

为了保证除那些images,javascripts和CSS以外的web请求都能被引导至启动(bootstrap)文件,这里,我们会使用到Apache的mod_rewrite模块。重写规则能直接在Apache的httpd.conf文件中配置或者被存放在”web_root/”目录下的一个叫.htaccess文件中进行配置,代码清单2-2所示的为.htaccess文件中的内容:
Listing 2.2:web_root/.htaccess

 


# Rewrite rules for Zend Framework
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f #1
RewriteRule .* index.php #2
(注释) <#1 Only continue if requested URL is not a file on disk.>
(注释) <#2 Redirect request to index.php.>

幸运的是,这个重写规则并不是很复杂。上面的命令指示Apache把大部分的请求都路由至index.php来显示相应页面,除非那些请求是映射到实际存在于web_root/目录下的文件,如images,javascripts,CSS(都说了几遍了…)。

 

2.5.3 Index Controller

前端控制模式把URL请求映射到对应类中对应的成员函数(the action),这个过程叫做路由(routing)和分发(dispatching)。Controller类有严格的名字转换规则来使分发器找到正确的成员函数。控制器类有严格的命名规则,在此规则下,分发器能自动找到正确的函数。比如,若路由想要自动调用在{ControllerName}类里的{actionName}动作,那么这个名叫{ControllerName}的类必须放在一个叫{ControllerName}.php的php文件里,如果请求中类和动作都没给出,那么将其默认为index。因此,http://zfia.example.com/这样一个请求将启动index控制器中的index动作。类似的,
http://zfia.example.com/test这样一个请求将触发test控制器里的index动作。我们以后会发现,映射是很灵活的,尽管大多数时候我们采用的是常规手段。
在前端控制系统中,分发器期望在application/controllers目录中找到名为IndexController.php的文件,这个文件必须包含一个叫做IndexController的类,并且至少、起码要包含一个叫做indexAction()的函数。在我们的Hello World程序中,代码清单2-3显示了所必需的IndexController.php的内容:
Listing 2.3: The index controller: application/controllers/IndexController.php

 


Zend::LoadClass(’Zend_View’);
class IndexController extends Zend_Controller_Action
{
public function indexAction()
{
$ this->view->assign(’title’, ‘Hello World!’); #1
}
}
(注释) <#1 把title属性指配给视图>

正如你所见到的,类IndexController是类Zend_Controller_Action的子类,它包括一个用来接入action函数的请求响应对象。以及一些有用的helper函数来控制程序流程。我们这个简单程序里,indexAction()函数只需要给视图属性一个值,这个属性是由名为ViewRenderer的一个动作助手所提供的。
注意!
动作助手是一个类,它提供了针对动作的相关具体服务。

 

ViewRenderer动作助手为我们展示了两个有用的特性。首先,在动作被调用之前,ViewRenderer创建一个Zend_View对象并把它设置为动作的$view属性,使我们能把值赋给视图。其次,在我们的动作结束后,它会把视图模板赋给响应对象,这将确保我们的控制器动作函数可以集中在真正的工作上,而不是

 

 

转载于:https://www.cnblogs.com/yaoliang11/archive/2009/06/23/1509777.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值