简单的Blog系统,在这里我们只介绍Blog系统中的部分功能(用户的登录注册)。通过这个应用实例,我们将了解到:
  1. 自定义应用配置
  2. 如何应用视图模板
  3. 在这个例子中我们会小试视图布局
  4. 数据持久化存储
  5. 个性化的错误处理

文件目录说明

在helloworld的应用中,我们采用了windframework的默认配置。在这个实例中,我们将尝试自定义配置来构建 我们的应用。像helloworld一样,我们需要在根目录中创建一个文件夹blog作为应用目录,并把解压好的框架放到 blog/目录中。创建好的目录结构如下:

/var/www/blog/
  1. wind/ 框架目录
  2. data/ 可写目录,用于存放编译后的模板,系统日志,缓存等
  3. conf/ 配置文件目录目录
  4. controller/ 应用控制器目录
  5. service/ 存放业务服务
  6. template/ 模板目录
  7. index.php 程序入口脚本

从以上的目录结构我们可以看出,比helloworld应用多了‘config.php’,‘service/’,‘data/’。‘config.php’用于 存放应用的自定义配置。而‘service’目录则是我们为Blog的业务进行了简单的分层,分为‘业务服务层’和‘视 图逻辑处理层’。将‘业务服务’统一放到‘service’目录中。而‘controller’下面则存放‘视图逻辑相关的处 理’。当然这种分层处理,只是本实例的写法。在实际的工程项目中,可以灵活的选取分层方案。‘data/’ 是一 个可写目录,用于存放模板编译后的文件(如果采用WindViewer视图渲染引擎,则会产生中间编译模板。在默认情 况下编译文件会放在data/compile/下)以及缓存日志等。

应用配置config.php

在应用目录下创建应用配置文件config.php。应用配置支持多种格式(xml/ini/php/properties)。应用配置中包括 ‘components’,‘web-apps’两个基本属性字段。‘components’是组件配置信息,wind框架有一份默认的组件配置 ‘wind/components_config.php’。默认应用会按照默认的组件启动。当应用配置中有components项时,他会重载掉系统 的组件定义。 ‘web-apps’属性标签下定义了应用的运行相关信息。具体内容如下:

*应用配置的具体说明可以参见配置介绍
return array(
	//重载了系统组件中的db组件的定义,将db组件的config指向应用根目录下的db_config.php
	//我们可以通过这种方式重载任何系统组件的定义,也可以定义新的组件。组件名称不能重复。
	//支持resource的配置方式
	'components' => array(
		'db' => array(
			'config' => array(
			'resource' => 'conf.config.php'))),
	//应用配置,支持多个应用配置。一个应用支持多个modules(业务模块),每个modules都有一个别名用于访问。
	//当不输入任何modules时访问‘default’默认模块
	'web-apps' => array(
		'blog' => array(
			'modules' => array(
				'default' => array(
				//应用控制器访问路径定义,当前定义的路径是当前应用根目录下的‘controller/’
				'controller-path' => 'controller',
				//应用控制器后缀定义
				'controller-suffix' => 'Controller',
				//模板目录定义
				'template-dir' => 'template',
				//编译文件目录定义
				'compile-dir' => 'data.compile',
				//错误处理句柄定义
				'error-handler' => 'BLOG:controller.ErrorController',)),
			//过滤器配置,在这里部署了一个form表单过滤器
			'filters' => array(
				'user' => array(
				'class' => 'WIND:web.filter.WindFormFilter',
				'pattern' => 'default/Index/(login|dreg)',
				'form' => 'BLOG:model.UserForm')))));

我们在上面的应用配置中,db组件配置引入了一个外部的配置资源文件“db_config”。内容如下:

return array(
	'dsn' => 'mysql:host=localhost;dbname=test',
	'user' => 'root',
	'pwd' => 'phpwind.net',
	'charset' => 'utf8');
*这里db链接的表达形式采用pdo dsn方式。事实上我们的DB组件是基于PDO开发的。

入口脚本index.php

在应用目录下创建入口文件index.php,内容如下:

//定义错误报告级别
error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING); 
//定义框架运行模式,0为关闭debug
define('WIND_DEBUG', 1);  
//加载框架
require_once '../../wind/Wind.php'; 
//注册路径别名,为service目录注册访问别名
Wind::register('SER','../../service'); 
//创建并启动应用,在这里我们为应用定义了一个名称‘blog’。并将对应的应用配置文件载入。
Wind::application('blog', 'config.php')->run(); 

用户服务

确定了工程目录结构,层次结构,应用配置等问题,我们需要来构建我们的用户服务。用户的登录,注册都是用户 相关的服务,我们暂且用一个 UserService 服务来进行封装。代码如下:

/**
* @author Qiong Wu  2012-3-15
* @copyright © 2003-2103 phpwind.com
* @license http://www.windframework.com
* @version $Id$
* @package demos
* @subpackage blog.service
*/
class UserService {
	protected $cookieName = 'blogloginUser';
	/**
	* 判断用户是否登录
	*
	* @return boolean|UserForm
	*/
	public function isLogin() {
		/* @var $user UserForm */
		$user = WindCookie::get($this->cookieName);
		if (!$user) return false;
		$stmt = $this->_getConnecion()->createStatement('SELECT * FROM user WHERE username=:username');
		if (!$stmt->getValue(array('username' => $user->getUsername()))) return false;
		return $user;
	}
	/**
	* 用户退出
	*
	* @return boolean
	*/
	public function logout() {
		return WindCookie::set($this->cookieName, '', -1);
	}
	/**
	* 用户登录服务
	*
	* @param UserForm $userInfo
	* @return boolean
	*/
	public function login($userInfo) {
		$db = $this->_getConnecion();
		$stmt = $db->createStatement('SELECT * FROM user WHERE username=:username AND password =:password');
		if (!$stmt->getValue(array('username' => $userInfo->getUsername(), 'password' => $userInfo->getPassword()))) {
			return false;
		}
	}
	/**
	* 用户注册服务
	*
	*@param UserForm $userInfo
	*@return boolean
	*/
	public function register($userInfo) {
		$db = $this->_getConnecion();
		$stmt = $db->createStatement('SELECT * FROM user WHERE username=:username');
		if ($stmt->getOne(array(':username' => $userInfo->getUsername()))) $this->showMessage('该用户已经注册.');
		return $db->execute(
		"INSERT INTO user SET " . $db->sqlSingle(
		array('username' => $userInfo->getUsername(), 'password' => $userInfo->getPassword())));
	}
	/**
	* @return WindConnection
	*/
	private function _getConnecion() {
		return Wind::getApp()->getWindFactory()->getInstance('db');
	}
}

上面代码中的UserForm,实际上是一个用户表单实体对象。在这里没有复杂的用户业务,而对于用户数据也只是建 立了简单的 “username”,“password”两个字段。所以并没有建立复杂的用户实体对象,而是共用了用户form 实体。Userform实体,还承担了用户数据的表单验证职责。UserForm.php 代码如下:

/**
* 用户表单,供登录和注册时验证用
*
* @author Shi Long 
* @copyright © 2003-2103 phpwind.com
* @license http://www.windframework.com
* @version $Id: UserForm.php 3403 2012-03-15 11:21:02Z yishuo $
* @package wind
*/
class UserForm extends WindEnableValidateModule {
	private $username;
	private $password;
	/**
	* @return string 返回用户名
	*/
	public function getUsername() {
		return $this->username;
	}
	/**
	* @return string 返回用户密码
	*/
	public function getPassword() {
		return $this->password;
	}
	/**
	* @param string $username
	*/
	public function setUsername($username) {
		$this->username = $username;
	}
	/**
	* @param string $password
	*/
	public function setPassword($password) {
		$this->password = $password ? md5($password) : $password;
	}
	/* (non-PHPdoc)
	* @see WindEnableValidateModule::validateRules()
	*/
	public function validateRules() {
		return array(
			WindUtility::buildValidateRule("username", "isRequired"),
			WindUtility::buildValidateRule("password", "isRequired"));
	}
}

视图逻辑处理

实际上视图逻辑部分我们统一封装在controller层。我们在controller包中创建IndexController.php类文件,在这里 IndexController继承自WindController。WindController支持多action处理,即在一个controller里面封装可以封装一组 相关的acion处理,除了run方法以外(run是所有的controller的默认处理方法,无论是WindController类型还是 WindSimplerController类型)其它的方法均采用Action后缀方式命名,当然Action后缀不算作action名称的一部分,这 样处理只是出于安全的考虑。在具体的章节我们会讲解如何重载掉这个默认的行为。IndexController中我们定义了 “run”,“login”,“logout”,“reg”,“dreg”五个action,命名上似乎有些抽象。

run 默认首页
login 登录业务处理
logout 退出业务处理
reg 引导用户进入注册界面
dreg 处理用户注册业务

IndexController代码如下。在controller的基类中我们定义了“beforeAction”,“afterAction”两个空方法,开发者可以通过重载这两个方法的实现,在每次请求的action之前或之后进行一些公共业务逻辑的处理。在这里我们重载了“beforeAction”这个方法,用于实现公共布局管理,全局变量定义等。

定义布局 $this->setLayout('layout') 这个方法用于定义布局管理,其中的参数‘layout’指向布局文件。它可以是一个绝对的路径地址,也可以是一个相对的路径地址,相对路径地址是相对当前定义的模板目录而言
设置模板 $this->setTemplate('index') 设置当前请求响应的模板文件,其中的参数‘index’表示模板文件名的路径地址,也可以是一个相对地址,相对地称。它可以是一个绝对的路径地址,也可以是一个相对地址,相对地址是相对当前定义的模板目录而言。默认的模板文件后缀为‘htm’。可以通过修改‘WindView’组件的相关配置来改变默认的模板文件后缀。*在之前的应用配置中我们统一配置了模板目录,编译目录。
设置变量输出 $this->setOutput($userInfo,'userInfo'); 变量输出,将变量输出到模板中。可以接受一个字符串(需指定key),数组,对象(没有指定key的情况下,数组的key作为变量访问的key) 例如:
// 第一参数为变量值,第二个为key值,当指定了key值,则在模板中通过key值访问变量 $this->setOutput($userInfo, 'userInfokey');
//模板中访问如下
{$userInfokey}
$array = array('a' => 'aaaaa' , 'b' => 'bbbbb');
$this->setOutput($array); //没有设置key值
//模板中访问如下
{$a} {$b}
获取变量输入 $this->getInput('userInfo'); 获取输入变量,通过该方法可以获取 get,post,cookie,session的变量,支持类型指定。支持批量获取。 例如:
$this->getInput('userInfo');
$this->getInput('userInfo','post'); //制定post类型的变量
list($a,$b,$c) = $this->getInput(array('a','b','c'));
错误消息输出,与自定义错误处理句柄 $this->showMessage('登录失 当我们发生一个错误时,可以调用该方法显示一段错误信息。我们在败.'); 当我们发生一个错误时,可以调用该方法显示一段错误信息。我们在上面的应用配置中自定义了错误处理句柄‘ErrorController’。自定义错误句柄的实现需要继承自‘WindErrorHandler’类。如果不自定义错误处理,系统会调用‘WindErrorHandler’进行处理。
*上表介绍了我们在本应用实例中用到的一些特性,事实上还有很多controller特性未被触及。
/**
* 默认的 controller
*
* @author Shi Long 
* @copyright © 2003-2103 phpwind.com
* @license http://www.windframework.com
* @version $Id: IndexController.php 3403 2012-03-15 11:21:02Z yishuo $
* @package demos.blog.controller
*/
class IndexController extends WindController {
	/* (non-PHPdoc)
	* @see WindSimpleController::beforeAction()
	*/
	public function beforeAction($handlerAdapter) {
		parent::beforeAction($handlerAdapter);
		$this->setLayout('layout');
		$this->setOutput('utf8', 'charset');
		$this->setGlobal($this->getRequest()->getBaseUrl(true) . '/template/images', 'images');
		$this->setGlobal($this->getRequest()->getBaseUrl(true) . '/template/images', 'css');
	}
	/* (non-PHPdoc)
	* @see WindController::run()
	*/
	public function run() {
		Wind::import('service.UserForm');
		$userService = $this->load();
		$userInfo = $userService->isLogin();
		$this->setOutput($userInfo, 'userInfo');
		$this->setTemplate('index');
	}
	/**
	* 访问用户注册页面
	*/
	public function regAction() {
		$this->setTemplate('reg');
	}
	/**
	* 用户登录
	*/
	public function loginAction() {
		$userService = $this->load();
		$userInfo = $userService->isLogin();
		if ($userInfo) $this->showMessage('已登录~');
		/* @var $userForm UserForm */
		$userForm = $this->getInput("userForm");
		if (!$userForm) $this->showMessage('获取用户登录数据失败');
		if (!$userService->login($userForm)) $this->showMessage('登录失败.');
		$this->forwardRedirect(WindUrlHelper::createUrl('run'));
	}
	/**
	* 处理用户注册表单
	*/
	public function dregAction() {
		$userService = $this->load();
		$userForm = $this->getInput("userForm");
		if (!$userService->register($userForm)) $this->showMessage('注册失败.');
		$this->setOutput($userForm, 'userInfo');
		$this->setTemplate('reg');
	}
	/**
	* 用户退出
	*/
	public function logoutAction() {
		$this->load()->logout();
		$this->forwardRedirect(WindUrlHelper::createUrl('run'));
	}
	/**
	* @return UserService
	*/
	private function load() {
		return Wind::getApp()->getWindFactory()->createInstance(Wind::import('service.UserService'));
	}
}


/**
* 自定义errorController
*
* @author Shi Long 
* @copyright © 2003-2103 phpwind.com
* @license http://www.windframework.com
* @version $Id: ErrorController.php 3403 2012-03-15 11:21:02Z yishuo $
* @package wind
*/
class ErrorController extends WindErrorHandler {
	/**
	* (non-PHPdoc)
	* @see WindErrorHandler::run()
	*/
	public function run() {
		$this->setLayout('layout');
		$this->setGlobal($this->getRequest()->getBaseUrl(true) . '/template/images', 'images');
		$this->setGlobal($this->getRequest()->getBaseUrl(true) . '/template/images', 'css');
		$topic = "Blog Error";
		$this->setOutput($topic, "errorHeader");
		$this->setOutput($this->urlReferer, "baseUrl");
		$this->setOutput($this->error, "errors");
		$this->setTemplate('error');
	}
}

视图模板与页面布局

关于视图模板上面讲到了,统一存放到‘template’目录,包括布局文件‘layout’。默认的模板后缀是‘htm’。我们 支持自定义的模板目录(力度细到你可以为你的每个module定义不同的模板路径),也支持绝对路径寻址。我想着几个 概念应该相对简单。下面我们给出一个布局文件和模板文件的例子:


<div class="wrap">
<div id="header" class="mb10">
<div class="header">
<table width="100%">
	<tr>
	<td><h2 class="fl logo"><a href="{@WindUrlHelper::createUrl('default/index/run')}"><img
	src="{@G:images}/logo.png" width="198" height="80" class="fl" /></a></h2></td>
	<td align="right"><div class="login_header fr">
			<dl class="cc login_dlA">
			<dt></dt>
			<dd></dd>
			<dd> </dd>
			</dl>
			<dl class="cc login_dlA">
			<dt>{@index_run:userInfo.username}</dt>
			</dl>
		</div></td>
	</tr>
</table>
	<div id="navA">
		<div class="navA">
			<ul class="cc">
			<li class="current"><a href="{@WindUrlHelper::createUrl('default/index/run')}">
			首页</a></li>
			<li><a href="{@WindUrlHelper::createUrl('default/index/run')}">关于本demo</a></li>
			<li class="tail"> </li>
			</ul>
		</div>
	</div>
</div>
</div>
<div class="main">

</div>
<div id="footer">
<div class="footer">
<p class="f10">Powered by phpwind windframework group 
©2003-2103 http://www.windframework.com</p>
</div>
</div>
</div>

上面是一个布局文件的示例代码。布局文件实际上也是一个html模板文件,不同的是通过设置布局文件,我们可以为一组页面设置公共的页面结构(统一的头部,尾部)。我们展示的这个布局文件,并未进行非常细粒度的页面切片。事实上我们可以将页面中的 ‘head’,‘header’,‘footer’用子模板切片的方式进行包含访问和管理,达到更灵活的部署需求。我们应该注意到很高亮的几行代码,它描述的是加载模板的主体内容。上面这份代码示例中还为我们展示了几个很重要的模板标签用法(关于模板标签是一个很有意思的概念,虽然系统默认只提供了少数的几个标签供使用,但是它提供了非常易用的扩展方案实现方案。它给模板布局,业务部署带来了非常大 的灵活和便利)。一下介绍本模板涉及到的标签:

模板变量访问 {$var}
{@$var}
{@G:var1.var2}
这是几个模板变量访问方式,{$var} 这个是最常用的方式。{@$var} 这个是对模板变量访问的一个加强实现,他不仅支持模板变量,而且支持常用表达式。{@G:var1} 这种标签是指定了一个模板域来进行变量查找访问,支持跨模板的变来那个访问,‘G’可以用一个模板名称替换。在这里‘G’代表全局变量。 实际上该布局模板中访问了几个全局变量,这几个全局变量在IndexController中的beforeAction中进行了定义。 例如: {@index_run:userInfo.username} 的username变量。

总结: 以上我们基于blog应用,介绍了一些框架的用法。我们来介绍这个blog应用,不是想去说明一个web应用如何去开发。针对于应用业务逻辑处理方面我们没有做过多的设计和介绍。我们意在通过这个实例展示wind框架的使用方式。问题反馈:https://github.com/phpwind/windframework/issues

(作者:yishuo 最后更新日期:2012-05-18 )