名词解释:
项目:你要开发的系统,称之为项目。
入口文件:你可以理解为这个项目的唯一的一道门,以后所有的操作都会通过这道门去执行处理。不必理会什么意思,你甚至可以先把它看成是index.php就是入口文件
TP: ThinkPHP框架的简称
1 下载TP1.5正式版
2 拟好你的项目名称,我们这里以 Myapp 为项目名称
3 在www根目录下,将TP框架所有文件全部复制过去,文件夹名称是ThinkPHP
4 与ThinkPHP同级新建一个文件夹,起名为 Myapp,也就是项目名称
5 在www根目录下,创建一个PHP文件,起名index.php,这就是入口文件
入口文件index.php代码:
<?php
// 定义ThinkPHP路径
define(‘THINK_PATH‘,‘./ThinkPHP‘);
// 定义项目名称
define(‘APP_NAME‘,‘Myapp‘);
// 定义项目路径
define(‘APP_PATH‘,‘./Myapp‘);
// 加载入口文件
require(THINK_PATH.‘/ThinkPHP.php‘);
// 实例化这个项目
$App = new App();
// 执行初始化
$App->run();
?>
备注TP3.1.2:
//index.php <?php //1、确定应用名称 Home define(‘APP_NAME‘, ‘Home‘); //2、确定应用路径 define(‘APP_PATH‘, ‘./Home/‘); //3、开启调试模式 define(‘APP_DEBUG‘, true); //4、应用核心文件 require ‘./ThinkPHP/ThinkPHP.php‘; ?> |
1.开启调试功能
//3.开启调试模式
define(‘APP_DEBUG‘,true);
2.我们需要设置配置文件,开启页面trace
‘SHOW_PAGE_TRACE‘=>true,//开启页面Trace
就这么简单几行,然后打开浏览器,输入http://127.0.0.1/
一个TP项目就这样构建出来了。你会看到
^_^ Hello,欢迎使用ThinkPHP!
这行字。并自动为你创建好项目的目录。接下来,我们这个项目添砖加瓦。
打开Myapp文件夹,里面TP已让你很省心地构建了最基本的目录。其中:
Cache文件夹:项目自动生成的模版缓存会出现在这里
Common文件夹:你的项目中要用到的自己写的函数,可以在这个文件夹下创建一个名为common.php文件,在这个文件中书写函数,这些函数可以用在你项目的各个类,同时也可以在模板变量中使用,TP框架会自动加载。
‘TMPL_L_DELIM‘=>‘<{‘, //修改左定界符 ‘TMPL_R_DELIM‘=>‘}>‘, //修改右定界符 |
Data文件夹:TP会把项目的数据库表字段生成到这里,另外。。。那些先不必理会。
Lang文件夹:项目的语言设置目录,先不必理会。
应用类库目录,在这文件夹内还有两个文件夹:Action和Model,Action目录放置命名为xxxAction.class.php的控制器文件,Model目录放置对应数据库表的命名为xxxModel.class.php的类文件。
Logs文件夹:项目中自动产生的日志文件会存放在这里。暂不必理会。
Temp文件夹:数据缓存目录,存放项目中自动生成的项目运行缓存文件等,以及使用文件方式时的缓存文件等
模板文件目录,内有一个default文件夹,也就是默认的风格
模板主题设置
‘DEFAULT_THEME‘=>‘your‘,//设置默认模板主题 需要在TPL下面新建一个your文件夹作为模板主题文件夹 如何动态修改模板主题? 1、在后台准备一个功能,修改config.php文件中的默认模板项 2、通过url传递 t=主题 参数可以修改不同的模板 ‘DEFAULT_THEME‘=>‘your‘,//设置默认模板主题 ‘TMPL_DETECT_THEME‘=>true,//自动侦测模板主题 ‘THEME_LIST‘=>‘your,my‘,//支持的模板主题列表
|
项目生成的目录结构就先简单介绍到这里,下一步我们要简单地对项目进行最基本的配置。
1 我们这里使用的是Mysql数据库,利用PhpMyadmin新建一个数据库,名称为myapp。
使用示例中心中的sql如下:
CREATE TABLE `think_form` (
`id` smallint(4) unsigned NOT NULL auto_increment,
`title` varchar(255) NOT NULL,
`content` varchar(255) NOT NULL,
`create_time` int(11) unsigned NOT NULL,
`update_time` int(11) unsigned NOT NULL,
`status` tinyint(1) unsigned NOT NULL,
`email` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ;
2 在Conf文件夹内,建立一个config.php文件。这个文件,就是Myapp项目的配置文件。系统会自动加载
config.php文件中的配置是以数组返回方式进行定义,会覆盖TP框架中的common目录下convention.php的默认配置。没有设置的配置,就以默认为准。配置设置可以利用C函数动态改变,暂且不必理会。
而我们连接到数据库的设置也是在这个文件中书写。
‘DB_TYPE‘=>‘mysql‘,
‘DB_HOST‘=>‘localhost‘,
‘DB_NAME‘=>‘thinkphp‘,
‘DB_USER‘=>‘root‘,
‘DB_PWD‘=>‘‘,
‘DB_PORT‘=>‘3306‘,
‘DB_PREFIX‘=>‘tp_‘
或:
‘DB_PREFIX‘=>‘tp_‘,
‘DB_DSN‘=>‘mysql://root:@localhost:3306/thinkphp‘,
如果两种方式同时存在,以DSN方式为优先
config.php代码如下:
<?php
if (!defined(‘THINK_PATH‘)) exit();
return array(
‘DB_TYPE‘=>‘mysql‘, // 使用的数据库是mysql
‘DB_HOST‘=>‘localhost‘,
‘DB_NAME‘=>‘myapp‘,// 数据库名
‘DB_USER‘=>‘root‘,
‘DB_PWD‘=>‘123456‘,// 填写你连接数据库的密码
‘DB_PORT‘=>‘3306‘,
‘DB_PREFIX‘=>‘think_‘, // 数据表表名的前缀请参看http://thinkphp.cn/Article/10
);
?>
暂时就是这样,后面会再根据需要进一步添加和讲解。
3 在Lib/Model目录下,创建一个文件,命名为FormModel.class.php。命名规则刚才你已看了两次。
这里就不再多说,我们可以看到,数据表名是think_form,由于我们配置了数据表表名的前缀为think_,所以文件的命名直接使用
"不含前缀的数据表表名+Model.class.php"就行了。
在这个文件中书写代码:
<?php
class FormModel extends Model {
}
?>
就这样定义一个类名就行了。该类继承了Model类。至于自动验证,自动过滤,自动填充这些,暂且不理会。
示例
<?php /** * 添加新闻的模型 * @author [ShangguanEcho] <[shangguanecho@qq.com]> */ class NewsListModel extends RelationModel {
protected $fields = array( ‘title‘, //新闻标题 ‘categoryID‘, //新闻章类型 ‘author‘, //新闻作者 ‘content‘, //新闻内容 ‘publicTime‘, //新闻发布时间 ‘viewTime‘ , //新闻查看次数 ‘publisher‘, //新闻发布者 ‘_autoinc_‘=>true,//自动增长 ‘_pk‘=>‘id‘ //日志ID );
protected $_link = array( ‘category‘ => array( ‘mapping_type‘=> BELONGS_TO, ‘class_name‘ =>‘NewsCategory‘, ‘foreign_key‘ =>‘categoryID‘, ), ); /** * 添加新闻 * @return 如果此新闻存在,返回exits * @return 如果添加成功,返回success * @return 如果添加失败,返回failed * @return 如果$newsList为空,返回null */ public function addOneNews($title =‘‘, $categoryID =‘‘, $content =‘‘, $author=‘‘) { if($title != ‘‘ && $categoryID !=‘‘ && $content != ‘‘ && $author != ‘‘) { $row = $this->where(array(‘title‘=>$title))->find(); if($row != null) return ‘exist‘;
$data[‘title‘] = $title; $data[‘categoryID‘] = $categoryID; $data[‘author‘] = $author; $data[‘content‘] = $content; $data[‘publicTime‘] = time(); $data[‘viewTime‘] = 0; $data[‘publisher‘] = $_SESSION[‘username‘];
if($this->relation(true)->add($data)) return ‘success‘; return ‘failed‘; } return ‘null‘; }
/** *更新数据库的相关信息 */ public function updateNews($id = ‘‘, $categoryID = ‘‘, $title = ‘‘, $content = ‘‘, $author = ‘‘) { if($id != ‘‘ && $categoryID != ‘‘ && $title != ‘‘ && $content != ‘‘ && $author != ‘‘) { $news = $this->where(‘id=‘.$id)->find(); if($news == null) return ‘not exist‘; $data[‘id‘] = $id; $data[‘title‘] = $title; $data[‘categoryID‘] = $categoryID; $data[‘content‘] = $content; $data[‘author‘] = $author;
if($this->relation(true)->save($data)) return ‘success‘; return ‘failed‘; } return ‘null‘; }
/** *@description 根据id,删除某条记录 */ public function deleteNews($id = ‘‘) { if($id != ‘‘) { if($this->delete($id)) return ‘success‘; return ‘failed‘; } return ‘null‘; }
/** *@description 获取新闻列表,根据时间的倒序,也就是把最新的新闻防御开头 */ public function getNews() { $result = $this->relation(true)->order(‘publicTime desc‘)->select(); return $result; }
public function getNewsCount($category = 0) { if($category = 0) $count = $this->count(); else $count = $this->where(‘category=‘.$category)->count(); return $count; }
public function getNewsListByID($id) { $result = $this->relation(true)->where(‘id=‘.$id)->find(); return $result; }
public function searchNews($keyword = ‘‘) { if(keyword != ‘‘) { $condition[‘title‘] = array(‘like‘, ‘%‘.$keyword.‘%‘); $result = $this->relation(true)->where($condition)->order(‘publicTime desc‘)->select(); return $result; } return null; }
/** * 获取查看次数最多的10条新闻 * @return 相应的新闻内容 */ public function getMostPopularNews() { $news = $this->relation(true)->limit(10)->order(‘viewTime desc‘)->getField(‘id,title,content‘); return $news; }
/** * 获取最新发布的10条新闻 * @return 相应的新闻内容 */ public function getNewestNews() { $news = $this->relation(true)->limit(10)->order(‘publicTime desc‘)->getField(‘id,title,content‘); return $news; }
public function addViewTime($id) { $news = $this->find($id); $data[‘id‘] = $id; $data[‘viewTime‘] = $news[‘viewTime‘] + 1; $this->save($data); }
} |
4 提前先爽一下吧。再次利用PhpMyAdmin,在该表中插入一些数据
sql如下:
INSERT INTO `think_form` (`id`, `title`, `content`, `create_time`, `update_time`, `status`, `email`) VALUES
(1, ‘这是测试数据‘, ‘dfdf‘, 1212724876, 0, 1, ‘dddd@ddd.com‘);
然后我们打开Myapp/Lib/Action/IndexAction.class.php文件,将里面的内容删掉。改成下面这样:
<?php
class IndexAction extends Action{
public function index(){
$form = D(‘Form‘)->findall();
dump ($form);
exit;
}
}
?>
打开浏览器,输入http://127.0.0.1/,看,数据给输出来了。
PHP开发,无非是对数据库使用了逻辑控制的增删改查和使用模板输出数据内容。
通常数据的插入都是通过表单来进行添加。表单提交涉及到页面显示,
所以这一节我们暂时放下对数据库的操作讲解,先来简单学习一下TP的模板引擎的变量输出。
<?php
// 本类由系统自动生成,仅供测试用途
class IndexAction extends Action {
public function index(){
$name=‘陈黎栋‘;
$this->assign(‘data‘,$name);
$this->display();
}
public function show(){
$this->display();
}
}
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>Index</title>
</head>
<body>
<h1>Hello <{$data}>!!</h1>
</body>
</html>
[./Home/Tpl/Index/show.html] Index与模块同名,show.html与方法同名
$m=new Model(‘user‘); //还有一种简单实用模型的方式 M() 等效为 new Model();
$arr=$m->select();
var_dump($arr);
$name=‘陈黎栋‘;
$this->assign(‘data‘,$arr[0][‘username‘]);
$this->display();
上一章节我们提及到, TP中的每一个xxxAction.class.php文件代表着一个应用模块,此Action中的每一个方法(function)代表着一个操作,操作分为有输出到模板的操作和只具执行不需要输出的操作。
打开Myapp/Lib/Action/IndexAction.class.php文件,我们看看里面的基础代码
class IndexAction extends Action{
public function index(){
}
}
理论知识:
1 在TP开发中,要增加一个应用模块,就在Action文件夹里建立一个类,类的文件命名格式是模块名称+Action.class.php。例如我们这里的应用模块是Index,所以定义文件名为IndexAction.class.php
2 应用模块类的定义要继承框架的Action类。要为这个应用模块添加一个操作,则定义一个以此操作为命名的function.例如上面的index操作。
通常一个应用模块中,会有若干操作(function)需要有与用户交互的页面,这就需要用到模板输出, TP本身已内置了一套具有TP特色的,很强大易扩展但应用非常方便兼简单的模板引擎。
在应有模块中,如果某个操作是需要页面显示的,只要对应在Myapp/Tpl/default/里建立一个文件夹,文件夹以应用模块的名称来命名,然后在这个文件夹下,建立一个以这个function名称来命名的html文件,就可以在这个方法中使用$this->display()方法来直接调用该模板。(当然也可以调用其它模块下的其它模板或显式指定模板文件位置和名称,由于是循序渐进式的学习,就让我们先忽略吧)了解这些理论后,我们先简单实操一下这些知识。
1 在Myapp/Tpl/default/下建立一个文件夹,根据应用模块的名称,我们将这个文件夹命名为Index
2 在Myapp/Tpl/default/Index/下建立一个html文件,根据操作名称,我们命名该文件为index.html
3 打开Myapp/Lib/Action/IndexAction.class.php文件,修改代码为
<?php
class IndexAction extends Action{
public function index(){
$value = ‘hello,ThinkPHP‘;
$this->assign(‘name‘,$value);
$this->display();
}
}
?>
代码知识要点(摘自手册:ThinkPHP模板指南,此后的知识要点均来自TP官方手册,不再申明)
在Action类里面使用 assign方法对模板变量赋值,无论何种变量类型都统一使用assign赋值。
$this->assign(‘name‘,$value);
// 下面的写法是等效的
$this->name = $value ;
// 模板变量赋值后就需要调用模板文件来输出相关的变量,模板调用通过display方法来实现
$this->display();
4 打开Myapp/Tpl/default/Index/index.html文件,代码为
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>{$name}</title>
</head>
<body>
测试输出: {$name}
</body>
</html>
代码知识要点:
模板变量使用{$变量名称}这种标签进行输出。
不同的模板变量类型,使用不同的标签,标签可以自行另外定义,暂且不理会。
附加补充知识:
$array = array();
$array[‘name‘] = ‘thinkphp‘;
$array[‘email‘] = ‘liu21st@gmail.com‘;
$array[‘phone‘] = ‘12335678‘;
$this->assign($array);
这样,就可以在模板文件中同时输出name、email和phone三个变量。
2 我们使用上面的变量定义,将整个数组定义为一个模板变量来输出
$array = array();
$array[‘name‘] = ‘thinkphp‘;
$array[‘email‘] = ‘liu21st@gmail.com‘;
$array[‘phone‘] = ‘12335678‘;
$this->assign(‘array‘,$array);
$this->display();
在html中,要输出$array[‘name‘]的值,代码是
{$array.name} 或 {$array[‘name‘]}
3.1 IndexAction.class.php中代码更改如下
<?php
class IndexAction extends Action{
public function index(){
$array = array();
$array[‘name‘] = ‘thinkphp‘;
$array[‘email‘] = ‘liu21st@gmail.com‘;
$array[‘phone‘] = ‘12335678‘;
$value = ‘hello,ThinkPHP‘;
$this->assign(‘array‘,$array);
$this->assign(‘name‘,$value);
$this->display();
}
}
?>
3.2 将Myapp/Tpl/default/Index/index.html代码更改如下:
<html>
<head>
<title>{$name}</title>
</head>
<body>
<iterate name="array" id="vo">
{$vo}<br />
</iterate>
</body>
</html>
代码知识要点:
name=‘array‘是指要循环的模板变量是array,id=‘vo‘是指这个数据在模板输出时所使用的名称
对于TP的模板引擎输出先简单了解到这里。
$this->assign(‘data‘,$arr);
$this->display();
<volist name=‘data‘ id=‘vo‘>
<{$vo.id}>----<{$vo.username}>-----<{$vo.sex}><br/>
</volist>
1.PATHINFO 模式 -- 重点!!!!!!
http://域名/项目名/入口文件/模块名/方法名/键1/值1/键2/值2
2.普通模式
http://域名/项目名/入口文件?m=模块名&a=方法名&键1=值1&键2=值2
3.REWRITE模式
http://域名/项目名/模块名/方法名/键1/值1/键2/值2
4.兼容模式
http://域名/项目名/入口文件?s=模块名/方法名/键1/值1/键2/值2
http://localhost/thinkphp/index.php/Index/index
而这一节将简单讲解如何通过URL来访问操作。由于我们要循序渐进,所以先只介绍下面章节要用到的知识,要想深入学习还请参看官方手册《URL设计和SEO支持》
TP支持四种URL访问方式,默认是智能模式,所以我们就这种模式进行简要说明。
智能模式下的URL基本结构是这样的
http://servername/appName/moduleName/actionName/params
你可以理解为
http:// 服务器地址 / 入口文件位置 / 应用模块名称 /具体操作名称/ GET变量参数
解释一下上面文字中所译的URL结构,为什么appname项目名称我将它译成了入口文件位置:通常情况下,我们的入口文件index.php都是放在根目录中,这时项目名称就会被入口文件index.php所替代。在前几节里,我们已知道,一个Action文件就是一个应用模块,而应用模块中的每个方法(function)就是一个具体操作。因此,假如要访问我们实例中的Index模块下的index操作。地址应该是
http://127.0.0.1/index.php/Index/index
如果入口文件不是在服务器的根目录,而是在myapp目录下,那么入口文件位置就是 Myapp/index.php,此时上面的URL访问就相应要换成
http://127.0.0.1/Myapp/index.php/Index/index
简单实操:
1 打开Myapp/Lib/Action/IndexAction.class.php文件,在index这个function下再添加一个操作test
<?php
class IndexAction extends Action{
public function index(){
$array = array();
$array[‘name‘] = ‘thinkphp‘;
$array[‘email‘] = ‘liu21st@gmail.com‘;
$array[‘phone‘] = ‘12335678‘;
$value = ‘hello,ThinkPHP‘;
$this->assign(‘array‘,$array);
$this->assign(‘name‘,$value);
$this->display();
}
public function test(){
header("Content-Type:text/html; charset=utf-8");
echo ‘哈,访问正确!!‘;
}
} // 类定义end
?>
2 尝试对应URL结构,来访问这个test操作,如果能成功看到echo的文字。则这节我们又学会了多一点知识。
附加知识点:
要去掉URL里的入口文件index.php,使URL类似这样的形式
可以按官方手册所提供的方法进行如下操作。
1 确认httpd.conf配置文件中加载了mod_rewrite.so模块
2 AllowOverride None 将None改为 All
3 打开Myapp/Conf/config.php文件,在配置的数组中添加一行:‘URL_MODEL‘=>2,在入口文件所在的同级目录下,新建一个.htaccess文件,内容是
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L]
</IfModule>
如果你的服务器环境支持rewrite,使用http://127.0.0.1/Index/index就可以正常访问到Index模块的index操作了。 此后我们的学习中用到的URL,都是假定你使用了rewrite的情况
对数据的读取 Read
$m=new Model(‘User‘);
$m=M(‘User‘);
select
$m->select();//获取所有数据,以数组形式返回
当数据只有一条时,var_dump($user):
array(1) {
[0]=>
array(10) {
["id"]=>
string(2) "12"
["username"]=>
string(6) "等等"
["grade"]=>
string(1) "4"
["password"]=>
string(2) "ss"
["lastlogtime"]=>
NULL
["lastlogip"]=>
NULL
["studentno"]=>
NULL
["email"]=>
NULL
["qq"]=>
NULL
["regtime"]=>
NULL
}
}
find
$m->find($id);//获取单条数据
getField(字段名)//获取一个具体的字段值
$arr=$m->where(‘id=2‘)->getField(‘username‘);
a、字符串
$arr=$m->where("sex=0 and username=‘gege‘")->find();
b、数组
$data[‘sex‘]=0;
$data[‘username‘]=‘gege‘;
$arr=$m->where($data)->find();
注意:这种方式默认是and的关系,如果使用or关系,需要添加数组值
$data[‘sex‘]=0;
$data[‘username‘]=‘gege‘;
$data[‘_logic‘]=‘or‘;
$data[‘id‘]=array(‘lt‘,6);
$arr=$m->where($data)->select();
EQ (eq)等于
NEQ不等于
GT 大于
EGT大于等于
LT(lt) 小于
ELT小于等于
LIKE 模糊查询
$data[‘username‘]=array(‘like‘,‘%ge‘);
$arr=$m->where($data)->select();
NOTLIKE
$data[‘username‘]=array(‘notlike‘,‘%ge%‘); //notlike中间没有空格
$arr=$m->where($data)->select();
注意:如果一个字段要匹配多个通配符
$data[‘username‘]=array(‘like‘,array(‘%ge%‘,‘%2%‘,‘%五%‘),‘and‘);//如果没有第三个值,默认关系是or关系
$arr=$m->where($data)->select();
BETWEEN
$data[‘id‘]=array(‘between‘,array(5,7));
$arr=$m->where($data)->select();
//SELECT * FROM `tp_user` WHERE ( (`id` BETWEEN 5 AND 7 ) )
$data[‘id‘]=array(‘not between‘,array(5,7));//注意,not 和 between中间一定要有空格
$arr=$m->where($data)->select();
IN
$data[‘id‘]=array(‘in‘,array(4,6,7));
$arr=$m->where($data)->select();
//SELECT * FROM `tp_user` WHERE ( `id` IN (4,6,7) )
$data[‘id‘]=array(‘not in‘,array(4,6,7));
$arr=$m->where($data)->select();
//SELECT * FROM `tp_user` WHERE ( `id` NOT IN (4,6,7) )
$data[‘id‘]=array(array(‘gt‘,4),array(‘lt‘,10));//默认关系是 and 的关系
//SELECT * FROM `tp_user` WHERE ( (`id` > 4) AND (`id` < 10) )
$data[‘id‘]=array(array(‘gt‘,4),array(‘lt‘,10),‘or‘) //关系就是or的关系
$data[‘name‘]=array(array(‘like‘,‘%2%‘),array(‘like‘,‘%五%‘),‘gege‘,‘or‘);
count //获取个数 $c = $m->count();
max //获取最大数 $c = $m->max(‘id‘); id字段的最大值
min //获取最小数 $c = $m->min(‘id‘);
avg //获取平均数 $c = $m->avg(‘id‘); id字段的平均值
sum //获取总和 $c = $m->sum(‘id‘);
a、query 主要是处理读取数据的
成功返回数据的结果集
$m=M();
$result=$m->query("select * from tp_user where id >5"); //失败返回boolean false
var_dump($result);
b、execute 用于更新个写入操作
成功返回影响行数
$m=M();
$result=$m->execute("insert into t_user(`username`) values(‘ztz3‘)"); //失败返回boolean false
var_dump($result);
$m->字段名=值
$m->字段名=值
$m->add(); //返回值是新增的id号
$m->delete(2); //删除id为2的数据
$m->where(‘id=2‘)->delete(); //与上面效果相同,返回值是受影响行数
$m=M(‘User‘);
$data[‘username‘]=‘ztz2‘;
$m->save($data); //返回值是受影响行数
在数据库中,已建立了一个think_form数据库表,并且在配置文件config.php中,我们定义了数据表的前缀是think_,模型类(Model)文件的命名规则是:不包括前缀的数据库表表名并且首字母大写+Model.class.php所以之前我们为think_form数据表在Myapp/Lib/Model目录下建立一个文件FormModel.class.php模型类的特殊命名还可以智能识别驼峰式的表命名,假设我们有个是类似think_new_table这样的表可以命名为NewTableModel.class.php。默认配置便可以智能识别自动对应think_new_table表,因此不必修改配置。
如何让这个模型支持自动验证,自动填充,自动过滤这些知识,在下面涉及到时会作相应讲解。
使用TP以表单提交数据到库,流程和你所了解的表单提交没有什么区别,只是TP简化了数据操作的处理过程。我们在实际操作中体会一下。
先来看看我们所定义的数据表form的字段:
‘id‘,//自动编号
‘title‘,//标题
‘content‘,//内容
‘create_time‘,//创建时间
‘update_time‘//更新时间
‘email‘,//邮箱
‘status‘,//状态
其中的create_time字段是为了记录数据插入的时间,我们可以利用TP的数据自动填充来处理。
在Model类定义 $_auto 属性,可以完成数据自动处理功能,用来处理默认值和其他系统写入字段。
注意1:该自动填充可能会覆盖表单提交项目。其目的是为了防止表单非法提交字段。
注意2:要使用Model类的create方法创建数据对象的时候才会自动进行表单数据处理。
1 打开Myapp/Lib/Model/FormModel.class.php文件,更改代码为
<?php
class FormModel extends Model {
// 自动填充设置
protected $_auto = array(
array(‘status‘,‘1‘,‘ADD‘),
array(‘create_time‘,‘time‘,‘ADD‘,‘function‘),
);
}
?>
代码知识要点:
Model类的$_auto属性由多个填充因子组成的数组,填充因子定义格式:
array(填充字段,填充内容,填充条件,附加规则)
填充字段:就是需要进行处理的表单字段,这个字段并不一定要是数据库表中的字段,对于表单内的辅助检测字段比如重复密码和验证码也可以处理。
填充条件:ADD | UPDATE | ALL(注意,此是1.5版本说明,1.6已改为1 2 3,后续教程有说明)
当为ADD时,会在新增数据时自动填充,这是默认的处理方式
当为UPDATE时,在更新数据的时候会自动填充
当为ALL时,所有情况下都会进行自动填充
附加规则:附加规则是针对填充内容而言,表示该内容填充的方式,包括function,callback,field,string
对于field使用其它字段进行填充和string直接标示字符串作为值进行填充很好理解。例如上面
array(‘status‘,‘1‘,‘ADD‘),
就是将状态status字段的值直接以1填充.
下面主要说说function与callback这两个附加规则。
protected $_auto = array(
array(‘create_time‘,‘time‘,‘ADD‘,‘function‘),
);
上面create_time代表了要处理的字段,填充内容是time,附加规则是function使用函数,填充条件是ADD新增时处理,那么整行代码表示对create_time字段在新增的时候使time函数作为该字段的值进行自动填充。
再看一个function作为附加规则的例子
protected $_auto = array(
array(‘password‘,‘md5‘,‘ADD‘,‘function‘),
);
当使用function作为附加规则时,第二个填充内容就代表了这个因子是一个函数名称,该函数的参数就是代表填充字段的值,比如password的值是123456,则上面的代码会先将这个值使用函数md5(‘123456‘),这样处理后再插入到数据表中去。
对于function(函数)和callback(回调方法)理论是一样的,只不过一个是表示填充内容所写的是函数名,另一个是表示填充内容是类中的方法名。函数可以是PHP5内置函数或你自己所写的函数。而callback是当前模型类所能调用的一个方法。
有关数据填充方面的详细资料,请参看官方使用手册 《ThinkPHP数据操作指南》
2 打开Myapp/Tpl/default/Index/index.html文件,我们修改代码将form写进去
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>{$title}</title>
</head>
<body>
<form action="__URL__/add" method="post" name="formname" id="formname">
<p>
<label for="title">标题:</label>
<input name="title" type="text" id="title" />
</p>
<p>
<label for="email">邮箱:</label>
<input name="email" type="text" id="email" />
</p>
<p><label for="content">内容:</label></p>
<p>
<textarea name="content" rows="5" cols="25" id="content" class="textarea" ></textarea>
</p>
<p><input type="submit" value="提交" /></p>
</form>
</body>
</html>
在上面代码中,我们简单地建了一个form表单,并将标题统一使用一个模板变量{$title}。action提交到的处理地址是__URL__/add,其中__URL__是一个常量定义,表示当前模块地址,TP模板引擎会自动将这句解释为/index.php/Index/add,常见的模板使用的常量有
__ROOT__ 网站根目录地址
__APP__ 当前项目(入口文件)地址
__URL__ 当前模块地址
__ACTION__ 当前操作地址
__SELF__ 当前 URL 地址
3 设置模板变量 {$title} ,增加add操作方法
打开Myapp/Lib/Action/IndexAction.class.php文件,修改代码如下
<?php
class IndexAction extends Action{
public function index() {
$this->assign(‘title‘,‘添加数据‘);//这里设置了模板变量{$title}
$this->display();
}
// 处理表单数据的方法
function add() {
$Form = D("Form");
if($Form->create()) {
$Form->add();
$this->redirect();
}else{
header("Content-Type:text/html; charset=utf-8");
exit($Form->getError().‘ [ <A HREF="javascript:history.back()">返回</A> ]‘);
}
}
}//类定义 end
?>
代码知识要点:
模板变量赋值前面的章节已介绍过。这里就不啰嗦了。
我们来看看add方法的代码。
$Form = D("Form");
这里使用了TP的特色单字母函数D,是操作数据库时最常用的TP函数,表示实例化Form对象,即$Form = new FormModel();D函数的具体代码可以查看ThinkPHP目录内的common/function.php中的片段,它会自动引入Model类,并判断之前如果实例化过这个Model,就不再实例化,若然该Model不存在,就会抛出异常错误,另外,D可以跨项目访问Model,暂且忽略。
$Form->create()
使用Model类的Create方法创建一个Form对象,失败会返回false。
$Form->add(); //add方法会将表单数据进行写入
$this->redirect(); //执行跳转
就这么简单几行代码,便完成了对数据插入的处理。
现在我们可以尝试一下在表单中输入数据进行提交试试了。
基于本学习系列1-4过渡到本节的补充说明
说明1 从这节开始,TP或dev这两个简称指的都是ThinkPHP 1.6版,可以到svn下载。http://thinkphp.googlecode.com/svn/branches/dev下载后删除原来的ThinkPHP框架文件,将dev同样命名为ThinkPHP,并仍是放置于网站的根目录下。
另外,为了方便需要时可以对实例截图而不引起读者你误解,基于我的网站目录结构,现将Myapp的index.php位置更改到Myapp文件夹下(不再是原来的根目录).
步1:将根目录中的原index.php剪切并粘贴到Myapp文件夹下。更改代码为:
<?php
// 定义ThinkPHP路径
define(‘THINK_PATH‘,‘../ThinkPHP‘);
// 定义项目名称
define(‘APP_NAME‘,‘Myapp‘);
// 定义项目路径
define(‘APP_PATH‘,‘.‘);
// 加载框架入口文件
require(THINK_PATH.‘/ThinkPHP.php‘);
// 实例化这个项目
$App = new App();
// 执行初始化
$App->run();
?>
代码补充注释:代码中项目名称其实是可以不必定义的,系统会自动根据当前的目录名定义名称。
步2: 将.htaccess文件复制一份粘贴到Myapp目录下。
步3: 将Cache目录,Temp目录,Data目录清空。
说明2 第五节中对于URL访问的默认模式
在dev版默认配置中,dispatch即URL调度功能是关闭的,所以在默认模式下,要访问Index模块的index操作,地址应是:index.php?m=index&a=index,m指的是模块变量(Model),a指的是操作变量(Action)。
如果要使用类似第五节中的index.php/moduleName/actionName方式,则要在config.php文件里设置DISPATCH_ON为true;并且,也只有开启了此功能后,URL_MODEL的设置才会有效。
(在上面的章节我们提及过,可以设置URL_MODEL为2来使用rewrite功能去除显示index.php)
所以,现在我们的config.php代码是
<?php
if (!defined(‘THINK_PATH‘)) exit();
return array(
‘DB_TYPE‘=>‘mysql‘, // 使用的数据库是mysql
‘DB_HOST‘=>‘localhost‘,
‘DB_NAME‘=>‘myapp‘,// 数据库名
‘DB_USER‘=>‘root‘,
‘DB_PWD‘=>‘123456‘,// 填写你连接数据库的密码
‘DB_PORT‘=>‘3306‘,
‘DB_PREFIX‘=>‘think_‘, // 数据表表名的前缀请参看http://thinkphp.cn/Article/10
‘DISPATCH_ON‘=> true,
‘URL_MODEL‘=>2,
‘TMPL_VAR_IDENTIFY‘=>‘array‘,// 模板变量识别留空自动判断 array 数组 obj 对象
‘DEBUG_MODE‘=>true,//开启调试模式
);
?>
代码补充注释:此节之后的内容会涉及到模板输出及数据操作,所以在这里明确设置了模板变量为数组,并开启TP的调试模式。
dev版的自动填充功能已移到AdvModel类中。要使用此功能,我们需要在Myapp/Lib/Model类文件中引入该类.
那么再来看看我们所定义的数据表form的字段:
‘id‘,//自动编号
‘title‘,//标题
‘content‘,//内容
‘create_time‘,//创建时间
‘update_time‘//更新时间
‘email‘,//邮箱
‘status‘,//状态
引用tdweb手记:
如果需要新建数据时自动填入时间戳,只需要设置数据表字段名"create_time",如果需要修改时自动保存时间戳,那么设置数据表字段名为"update_time"。如果你的数据表字段名不是默认的,那么需要指定,例如
protected $autoCreateTimestamps = ‘ctime‘;
protected $autoUpdateTimestamps = ‘utime‘;
故此,让我们打开Myapp/Lib/Model/FormModel.class.php文件,将代码更改为如下:
<?php
// 导入AdvModel类
alias_import(‘AdvModel‘);
class FormModel extends AdvModel {
/* 如果数据表的创建时间字段不是create_time,需要另外设置成如下方式*/
//protected $autoCreateTimestamps = ‘ctime‘;
// 自动填充设置
protected $_auto = array(
array(‘status‘,‘1‘),//自动填充的定义规则请参看第六节内容填充规则有所不同,见附注
);
}
?>
代码补充注释:
注意:dev版的填充条件改为1 新增 2 更新 3 包含1和2,默认为新增。所以定义更方便了.而使用ADD | UPDATE | ALL会无效的(TO流年大哥:TP代码还有一个特色就是函数或方法的命名很符合语言使用习惯,原来的add,update,all很直观,易记,可惜没了)
array(‘field‘,‘填充内容‘,‘填充条件‘,‘附加规则‘,[额外参数])
alias_import($alias,$classfile=‘‘)快速定义和别名导入
这是新增的函数,我尝试注解一下,不正确请指正。
该函数可以动态设置文件列表导入和直接导入文件,常会用到的文件别名有
‘Page‘ 分页类 Page.class.php‘,
‘ViewModel‘ 视图模型类 ViewModel.class.php‘,
‘AdvModel‘ 高级模型类 AdvModel.class.php‘,
‘RelationModel‘ 关联模型类 RelationModel.class.php‘,
当要引入已定义别名的文件,我们只要直接在参数中传递别名就行了。
别名可以自行在ThinkPHP/Common/alias.php中扩充。
除了使用已定义的别名外,也可以使用动态指定。
alias_import(‘zzg‘,APP_PATH.‘/Lib/ORG/zzg.php‘);
或一次引入多个文件
$ailias=array(
‘Abc‘=> APP_PATH.‘/Lib/ORG/abc.php‘,
‘Efg‘ => APP_PATH.‘/Lib/Other/efg.php‘,
);
alias_import($ailias);
注:指定文件或别名不存在时会返回false
类似的函数还有
import($class,$baseUrl = ‘‘,$ext=‘.class.php‘)
由于性能问题,这个方式以后不再支持
这是ThinkPHP内建的类库和文件导入的一个函数,具有缓存和检测机制,相同的文件不会重复导入,冲突时会提示错误。使用这方法时,系统会自动识别导入类库文件的位置:
Think 代表TP框架基类库 即ThinkPHP/Lib/Think目录
ORG 代表第三方公共类库 即ThinkPHP/Lib/ORG目录
@ 代表当前项目类库,例如Import("@.Action.xxxAction") ----即Myapp/Lib/Action/xxxAction.class.php@指的是当前项目名称,这和使用Import("Myapp.Action.xxxAction")是一样的。
TP的约定是Think、ORG等导入的是以系统(TP)基类库为相对起始目录,否则就认为是项目(Myapp)应用类库为起始目录。
注意1 使用时要注意文件名的大小写
注意2 Import会自动将.转换为/,如果文件名中含有.,则要将.改为#才能正常导入。
注意3 导入的类文件后缀默认是.class.php
2 第三方框架类库导入:vendor($class,$baseUrl = ‘‘,$ext=‘.php‘),
起始目录统一是ThinkPHP/Vendor 默认后缀是.php
注:vendor也使用了Import函数。因此...
说明3,原来的模板文件index.html保持不变,因为我们已启用了DISPATCH_ON开关。仅将Myapp/Lib/Action/IndexAction.class.php代码更改如下
<?php
class IndexAction extends Action{
public function index()
{
$Form = D("Form")->select(); //也可以D("Form")->findAll()
dump ( $Form );// 输出查询结果
$this->assign(‘title‘,‘添加数据‘);//定义标题的模板变量
$this->display();// 渲染模板
}
// 插入数据
function add()
{
header("Content-Type:text/html; charset=utf-8");//为了提示时不乱码
$Form = D(‘Form‘);//实例化Form模型
$vo = $Form->create();//创建数据对象
if(false === $vo) {
exit($Form->getError().‘ [ <a htef="javascript:history.back()">返回</a> ]‘);
}
$rs = $Form->add();//插入数据
if ($rs) {
redirect(‘/Myapp/‘,2,‘数据添加成功!‘);
}else{
exit($Form->getError().‘ [ <a HREF="javascript:history.back()">返回</a> ]‘);
}
}
}//类定义 end
?>
代码补充解释:
select($options=array());为了方便查看数据,index操作中使用到了一个基本查询方法
select()方法代替了之前比较流行的findAll(),不传任何参数则是获取全部数据。
我们仍是循序渐进,具体关于查询的方法会在用到的时候陆续一起学习。
dump($var, $echo=true,$label=null, $strict=true)函数。浏览器友好的变量输出。
当只有一个$var参数时,会直接打印这个$var。dump($Form);会输出类似
array(1) {
[0] => array(7) {
["id"] => string(1) "1"
["title"] => string(18) "这是测试数据"
["content"] => string(4) "dfdf"
["create_time"] => string(10) "1212724876"
["update_time"] => string(1) "0"
["status"] => string(1) "1"
["email"] => string(12) "dddd@ddd.com"
}
}
也可以设置参数
dump($要输出的变量, 是否直接打印0或1,变量说明 , 是否以正规数组形式0,1)
例如:
dump($Form,1,‘Form数据‘,0);
输出类似
Form数据 Array
(
[0] => Array
(
[id] => 1
[title] => 这是测试数据
[content] => dfdf
[create_time] => 1212724876
[update_time] => 0
[status] => 1
[email] => dddd@ddd.com
)
)
$vo = $Form->create();
创建数据对象 但不保存到数据库
模型类的create方法,如果没有设值,则默认是通过表单提交的数据$_POST进行创建。
同时Create方法也支持从其它方式例如数据对象或者数组等来创建,对这方法说法有点不一样,望指正。
由于简洁版并不自动生成数据表data缓存,所以如果你用create()而不给他传递任何值,仅仅依靠$_POST得到值,那么不管你的表单POST什么,程序都会认为是数据库的字段拼入QL,所以,如果你POST过来的数据而数据表里没有这个字段,则会出现添加失败。
但我看到代码里似乎已自动过滤不符合的数据,并测试过添加数据表里没有的字段没有出现添加失败。
函数原型:redirect($url,$time=0,$msg=‘‘)
看参数就已经很清楚了。
在上一节实操中,我们已成功插入了多条测试的数据,但是很明显,我们发现其中的邮箱地址一项不符合格式也可以提交,这当然不是我们所希望见到的,这时我们可以利用TP的数据自动验证功能去控制用户输入。
而之前已提交的数据,也可以通过编辑数据将原来错误格式的邮箱地址重新更改为正确的email,这些处理让我们都放在下一节的数据编辑中一起学习。
在此之前,先来看看TP最基本的数据查询。
TP的查询很人性化地分成单数据返回或多数据返回,视乎自己项目过程中的实际需要,按照这系列教程的学习习惯,我们仍是在实际操作中去了解,以代码来学习,一起动手吧。
1 先将Myapp/Lib/Action/indexAction.class.php代码稍作修改,以方便更直观地查看查询结果。
public function index()
{
$Form = D("Form");
$data = $Form->order(‘id desc‘)->select();
$this->assign(‘data‘,$data);
$this->assign(‘title‘,‘添加数据‘);
$this->display();
}
2 在第四节时,我们了解过模板变量的循环输出,现在使用这知识点将对应的模板文件Myapp/Tpl/default/Index/index.html修改如下
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>{$title}</title>
<style type="text/css">
h3{color:green;}
.box{width:600px;margin:10px auto;border:1px dashed #e8e8e8;padding:5px;}
.submit{height:2em;padding:0 5px;cursor:pointer;}
</style>
</head>
<body>
<form action="__URL__/add" method="post" name="formname" id="formname" class="box">
<p><label for="title">标题:</label><input name="title" type="text" id="title" /></p>
<p><label for="email">邮箱:</label><input name="email" type="text" id="email" /></p>
<p><label for="content">内容:</label></p>
<p>
<textarea name="content" rows="6" cols="50" id="content" class="textarea" ></textarea>
</p>
<p><input name="submit" type="submit" value=" 提交 " class="submit" /></p>
</form>
{//Form表数据循环输出}
<iterate name="data" id="vo">
<div class="box">
<h3>NO.{$vo.id}:{$vo.title}</h3>
<p>-----------由 {$vo.email} 发表于: <em>{$vo.create_time|date=‘Y-m-d‘,###}</em></p>
<p>内容:</p>
<p>{$vo.content}</p>
</div>
</iterate>
</body>
</html>
在浏览器中输入http://127.0.0.1/Myapp/,可以看到现
在已将数据显示在模板了
下载 (28.7 KB)
2009-3-24 01:49
这时我们只要每输入一条数据信息,就会按照id倒序的方式显示在模板上。
现在模板显示已设定好,我们来一起学习查询的各种方法:
先了解一个TP词汇:连贯操作
所谓连贯操作,是TP利用__call方法来实现的一些特殊的Model类方法,可以对数据对象连续调用数个方法(参数)来设定特定的条件,然后执行最终所指定的操作,这类特定方法的命名都很直观易记,主要方法(参数)有
用于查询表达式的
‘field‘//要查询的字段名
‘table‘//数据表名称不指定时,默认为当前数据对象所对应的表
‘where‘//查询条件
‘order‘// 排序
‘limit‘//结果限制
‘having‘//having支持
‘group‘//group支持
‘distinct‘//distinct筛选支持
‘lazy‘//惰性查询支持
用于统计查询的
‘count‘//统计满足条件的记录个数
‘sum‘//统计某个字段的总和
‘min‘//取得某个字段的最小值
‘max‘//取得某个字段的最大值
‘avg‘//统计某个字段的平均值
用于动态查询的
getby
利用find方法进行查询,会自动加上limit=1来得到指定条件的一条记录。
我们可以在IndexAction.class.php中的index操作进行实际了解。
查询主键为2的数据
//SELECT * FROM `think_form` WHERE id=‘2‘ LIMIT 1
$data = $Form->find(2);
dump ( $data );
dump( $Form->getLastSql() );
exit;
代码注释:
1 由于我们这里查询出来的是单条数据,以模板设定的循环输出不合,所以用dump查看,下同。
2 $Form->getLastSql()方法可以输出最后一条查询sql。
查询符合某个特定条件的数据
// SELECT * FROM `think_form` WHERE ( `email` = ‘abcdefg‘ ) LIMIT 1
$where[‘email‘] = ‘abcdefg‘;
$options[‘where‘] = $where;
$data = $Form->find($options);
dump ( $data );
dump( $Form->getLastSql() );
exit;
但是这样未免麻烦了些,这时我们可以使用连贯操作,结果是一样的。不过这样就直观了很多
$where[‘email‘] = ‘abcdefg‘;//多个条件时同样如此设置
$data = $Form->where($where)->find();
dump ( $data );
dump( $Form->getLastSql() );
exit;
2 getField($field,$condition=‘‘) 获取一条记录的某个字段的值
//SELECT `email` AS `abcdefg` FROM `think_form` LIMIT 1
$where[‘email‘] = ‘abcdefg‘;
$data = $Form->getField($where);
注意:这里如果我们使用$data = $Form->where($where)->getField();返回的将会是这个条件的id值sql将会是SELECT * FROM `think_form` WHERE ( `email` = ‘abcdefg‘ ) LIMIT 1
3 getFields($field,$condition=‘‘,$sepa=‘ ‘)获取数据集的个别字段值
$data = $Form->getFields(‘email‘);// SELECT `email` FROM `think_form
得出的结果是一个数组,可以指定需要的字段,条件和指定分隔符号
//SELECT id,create_time,email,content FROM `think_form
$data = $Form->getFields(‘id,create_time,email,content‘,‘‘,‘||‘);
3 getby字段名(‘字段值‘) --动态根据指定字段获取一条数据
$data = $Form->getbyemail(‘abcdefg‘);
//SELECT * FROM `think_form` WHERE email=‘abcdefg‘ LIMIT 1
select($options=array()) 或 findAll($options=array())两者是一样的,findAll可以理解为select的别名。
我们先了解一下$option 这参数。$option 参数设定的是查询表达式,不设置时,默认是获取全部数据.
参数支持如下键名
table 指定表名称,可以跨表访问,若不填则是当前DAO
distinct 指定是否只列出不重复数据,如果想列出不重复数据,则为true,不填则无此功能
field 指定要搜索的字段,不填则为*
join 填写join,可以支持数组或者直接使用字符串,如果是字符串,那么只支持LEFT JOIN,如果想使用RIGHT JOIN,那么只能使用数组定义,并且必须写清RIGHT JOIN where 指定查询条件
where可以多说点,功能很多。
首先说最简单的字符串查询,直接执行就OK了。
使用数组指定查询条件
$whereArr=array(
"account"=>"tdweb",
"home"=>"China"
);
$options=array(
"where"=>$whereArr
);
$Dao->select($options);
这样默认是查询SELECT * FROM `test` WHERE ( `account` = ‘tdweb‘ ) AND ( `home` = ‘China‘ )是查询相等条件的。
那如果我想查询like条件怎么办?
解决方法之一是定义LIKE_MATCH_FIELDS配置,这里指明要进行like查询的字段。现在我们稍微改下程序
public function testDb()
{
C(‘LIKE_MATCH_FIELDS‘,"account");
$Dao=D("Test");
$whereArr=array(
"account"=>"tdweb",
"home"=>"China"
);
$options=array(
"where"=>$whereArr
);
dump($Dao->select($options));
echo $Dao->getLastSql();
}
得到的SQL就是SELECT * FROM `test` WHERE ( `account` LIKE ‘%tdweb%‘ ) AND ( `home` = ‘China‘ )
接着,可以看如下代码
public function testDb()
{
$Dao=D("Test");
$whereArr=array(
"account"=>array("eq","tdweb"),
"home"=>array("like","%Chi%"),
"age"=>array("between","1,3")
);
$options=array(
"where"=>$whereArr
);
dump($Dao->select($options));
echo $Dao->getLastSql();
}
这里的$whereArr定义为一个二级数组,没一个字段都定义了一个数组,这样可以支持更高级复杂的查询。
其中支持的关键字:
EQ 相等
NEQ 不相等
GT 大于
EGT 不大于
LT 小于
ELT 不小于
NOTLIKE 不相似
LIKE 相似
NULL 空
NOTNULL 不为空
EXP 使用自己写的表达式
同样,select也支持一个名为"_after_select"的回调方法
如何去消化这段内容,将在下一节以我们的实例Myapp中实操去体会。
ThinkPHP提供了视图查询应用,利用视图查询可以将多个数据表的字段内容按需要进行指定和筛选,组织成一个基于这些数据表的视图模型,然后就可以通过该模型直接进行多表联合查询,非常方便和简单。
例如在项目中,我们定义有三个表,
user 用户基础表,
user_info 用户详细信息表,
dept 部门分类表
现在我们需要获取某个用户信息,该信息要包括用户的帐号名称和相关资料与及所在部门的名称,这时候我们可以利用视图查询进行处理。
让我们在实际操作中去体会。
1 构建一个新项目并进行相关配置(可参考前面的教程,这里省略)
2 创建一个数据库tpview,并添加这三个表
2.1 用户表
CREATE TABLE `think_user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT ‘ID编号‘,
`name` varchar(20) NOT NULL COMMENT ‘帐户‘,
`password` varchar(32) NOT NULL COMMENT ‘密码‘,
`dept_id` smallint(6) unsigned NOT NULL,
`status` tinyint(1) unsigned NOT NULL DEFAULT ‘1‘ COMMENT ‘开放状态‘,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT=‘会员表‘ AUTO_INCREMENT=2 ;
INSERT INTO `think_user` (`id`, `name`, `password`, `dept_id`, `status`) VALUES
(1, ‘zzguo28‘, ‘123456‘, 2, 1);
2.2 用户信息表
CREATE TABLE `think_user_info` (
`user_id` int(11) NOT NULL COMMENT ‘用户id‘,
`nick_name` varchar(30) NOT NULL COMMENT ‘用户昵称‘,
`email` varchar(100) NOT NULL COMMENT ‘邮箱地址‘,
`address` varchar(100) NOT NULL COMMENT ‘详细地址‘,
`gender` tinyint(1) NOT NULL DEFAULT ‘0‘ COMMENT ‘性别‘,
`mobile` varchar(100) NOT NULL COMMENT ‘手机号码‘,
`telephone` varchar(100) NOT NULL COMMENT ‘电话号码‘,
KEY `user_id` (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT=‘用户信息表‘;
INSERT INTO `think_user_info` (`user_id`, `nick_name`, `email`, `address`, `gender`, `mobile`, `telephone`) VALUES
(1, ‘国‘, ‘zzguo28@gmail.com‘, ‘TP路think街1.6号‘, 1, ‘12345678901‘, ‘123456‘);
2.3 部门分类表
CREATE TABLE `think_dept` (
`id` smallint(3) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
INSERT INTO `think_dept` (`id`, `name`) VALUES
(1, ‘开发部‘),
(2, ‘销售部‘),
(3, ‘财务部‘);
3 在项目/Lib/Model下创建这三个表的基础模型Model
本示例没涉及到验证等其它功能,所以只要简单定义测可,例如
<?php
class UserModel extends Model {
}
?>
其实视图模型对应的数据表并非一定要有相应的的基础模型,但是建议您创建,这样单表和视图都可以操作。
(最新svn上已增加动态扩展模型功能,使用新版需要将protected属性改为public属性,建议使用动态扩展功能去使用视图查询,而不再是本教程的继承方式。那样使用会更灵活。<?php
import(‘ViewModel‘);
class UserViewModel extends ViewModel{
protected $viewFields = array(
‘User‘ =>array(‘id‘,‘name‘,‘_as‘=>‘u‘,‘_type‘=>‘left‘),
‘UserInfo‘ =>array(‘email‘,‘mobile‘,‘_as‘=>‘ui‘,‘_on‘=>‘ui.user_id=u.id‘),
‘Dept‘ =>array(‘name‘=>‘dept‘,‘_on‘=>‘u.dept_id=Dept.id‘),
);
}
?>
逐行解释上面的代码:
在行2代码中,因为TP1.6版已将视图查询分离出原Model类,所以这里需要使用import方法引入了视图模型类。
行3代码中,定义了该模型名称为UserViewModel,视图模型的名称Model前的命名是随意的,只是为了有别于其它模型,通常我们会以xxxViewModel这样的方式去命名。并且一定要继承ViewModel。(1.6版无需再设置模型的viewModel属性为true,只要继承ViewModel则可)
第四行代码$viewFields 属性表示视图模型包含的字段,每个元素定义了各个数据表或者模型所需的字段。
格式是
protected $viewFields = array(
‘表名‘=>array(‘所需字段‘,‘_as‘=>‘别名定义‘,‘_on‘=>‘筛选条件‘,‘_type‘=>‘指定join类型,支持right,inner,left三种‘),
);
是否留意到行7代码中的‘name‘=>‘dept‘,因为User模型里面已经存在了一个name字段,所以我们通过这种方式把Dept模型的name字段映射为dept字段,如果有多个字段,可以使用同样的方式添加。
定义完毕后,我们在Action中进行测试,代码如下
<?php
class IndexAction extends Action{
public function index(){
$dao = D(‘UserView‘);
$where[‘u.id‘] = 1;
dump($dao->where($where)->find());
dump($dao->getLastSql());
}
}
?>
然后访问该操作,可以看到我们成功取得所需的查询内容:
array(1) {
[0] => array(5) {
["id"] => string(1) "1"
["name"] => string(7) "zzguo28"
["email"] => string(17) "zzguo28@gmail.com"
["mobile"] => string(11) "12345678901"
["dept"] => string(9) "销售部"
}
}
并可以看到使用的sql如下
"SELECT u.id AS id,u.name AS name,ui.email AS email,ui.mobile AS mobile,Dept.name AS dept FROM think_user u LEFT JOIN think_user_info ui ON ui.user_id=u.id JOIN think_dept Dept ON u.dept_id=Dept.id WHERE ( u.id = 1 ) LIMIT 1 "
视图模型在查询上和普通单表并没有多大分别,可以使用我们所熟悉的各种连贯操作,例如order,limit这些。
不过视图模型只能用于查询,要关联更新写入可以使用关联模型
ThinkPHP高级模型类中提供了对关联操作的支持。由于关联查询在定义和操作方面相对来说略显复杂,如果仅仅是一对一的关联查询,官方建议采用视图模型的方式或直接使用连贯操作进行处理。
掌握关联操作的使用,重点在于理解如何定义模型之间的关联关系,只要理清了规则和脉络,其实使用起来也并不算复杂,对于需要同时对多表进行写入、更新、查询和删除的情况,比起在项目中使用分步处理各个数据表的方式,关联操作仍然是一个不错的选择,而且在效率上并不会相差多少。
下面我们以一个实例来讲述表的关联操作简单用法。
需求分析:
在一个项目中,需要建立一个全面的会员资料档案,这个档案要包括:
会员的基本资料;
会员的详细资料;
会员所在地区:东区、南区、西区、北区;
会员的兴趣群组:琴、棋、书、画;
会员持有的兴趣消费卡;
数据表初步拟定:
user 会员基本表
user_info 会员详细资料表
area 地区表
group 兴趣群组表
card 消费卡帐号记录表
TP支持的关联关系包括:
HAS_ONE 1
BELONGS_TO 2
数据表关联分析:
1 所有的资料都是以会员来联系,因此我们将以会员基本表user为核心。
2 每个会员都有一份自己的详细资料,属于一对
HAS_MANY 3
MANY_TO_MANY 4
一关系,因此user_info表使用HAS_ONE关联
3 每个会员都属于其中一个地区,是从属关系,因此area表使用BELONGS_TO关联
4 会员的兴趣可能是一种或几种,各种兴趣自然也有多个用户,是多对多关系,因此group表使用MANY_TO_MANY关联
5 每个会员的兴趣消费卡帐号可以有多个,是一对多关系,因此card表使用HAS_MANY关联
6 由于将以user表为核心读取关联,会员表user和兴趣群组表group之间需要一个中间表作关联。
TP关联操作的中间表定义有个默认规则(主表_关联表),如果不是这种形式,可以使用relation_table属性进行设置。
这里使用默认规则,所以要多添加一个表 user_group 作为中间表。
理清结构后,我们构建一个项目应用并进行配置,然后在数据库中建立上面分析出来的数据表
会员基本表
CREATE TABLE `think_user` (
`id` mediumint(6) NOT NULL AUTO_INCREMENT,
`name` varchar(25) NOT NULL COMMENT ‘名称‘,
`area_id` smallint(3) NOT NULL COMMENT ‘地区id‘,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;
会员详细资料表
CREATE TABLE `think_user_info` (
`id` mediumint(6) NOT NULL AUTO_INCREMENT,
`user_id` mediumint(6) NOT NULL COMMENT ‘会员ID‘,
`email` varchar(255) NOT NULL COMMENT ‘邮箱地址‘,
`nickname` varchar(50) NOT NULL COMMENT ‘昵称‘,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT=‘详细资料表‘ AUTO_INCREMENT=1 ;
地区表
CREATE TABLE `think_area` (
`id` smallint(3) NOT NULL AUTO_INCREMENT COMMENT ‘地区id编号‘,
`name` varchar(50) NOT NULL COMMENT ‘地区名称‘,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT=‘地区表‘ AUTO_INCREMENT=5 ;
INSERT INTO `think_area` (`id`, `name`) VALUES
(1, ‘东区‘),
(2, ‘南区‘),
(3, ‘西区‘),
(4, ‘北区‘);
兴趣群组表
CREATE TABLE `think_group` (
`id` mediumint(6) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT ‘群组名‘,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=5 ;
INSERT INTO `think_group` (`id`, `name`) VALUES
(1, ‘琴‘),
(2, ‘棋‘),
(3, ‘书‘),
(4, ‘画‘);
消费卡帐号记录表
CREATE TABLE `think_card` (
`id` mediumint(6) NOT NULL AUTO_INCREMENT COMMENT ‘消费卡ID编号‘,
`user_id` mediumint(6) NOT NULL COMMENT ‘会员ID编号‘,
`card` varchar(25) NOT NULL COMMENT ‘消费卡帐号‘,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT=‘兴趣消费卡表‘ AUTO_INCREMENT=5 ;
会员与群组的中间表
CREATE TABLE `think_user_group` (
`id` mediumint(6) NOT NULL AUTO_INCREMENT,
`group_id` mediumint(5) NOT NULL COMMENT ‘群组ID‘,
`user_id` mediumint(5) NOT NULL COMMENT ‘会员ID‘,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
关联操作必需建立基础表,在项目配置文件里面定义好数据库的连接信息后,我们在 项目/Lib/Model下对应数据表建立各个基础模型类(中间表无需建立模型)
class UserInfoModel extends Model{} // 会员详细信息 UserInfoModel.class.php
class AreaModel extends Model{} // 地区表 AreaModel.class.php
class GroupModel extends Model{} // 兴趣群组表 GroupModel.class.php
class CardModel extends Model{} // 消费卡表 CardModel.class.php
会员表
UserModel.class.php (注意:关联操作只需要在主表中定义关联,其他模型无需再定义关联关系。如果是2.0版,需要注意$_link属性要为public。)
<?php
// 引入关联模型类
import(‘RelationModel‘);
// 需要继承RelationModel模型类
class UserModel extends RelationModel{
// 关联定义统一在 $_link 属性中定义
protected $_link = array(
‘UserInfo‘ => 1, // HAS_ONE,
‘Area‘ => 2, // BELONGS_TO,
‘Card‘ => 3, // HAS_MANY,
‘Group‘ => 4, // MANY_TO_MANY,
);
}
?>
上面的关联定义,我们采用了最简洁的定义方式,也就是所有规则都按照系统的默认规则进行,这些规则包括了主键、外键、表名的规范等。
关联操作完整定义详解
在复杂的关联操作中,如果要给关联定义增加可选的属性,我们可以采用完整定义的方式。
完整定义的格式是:
protected $_link = array(
‘关联表名1‘ => array(
‘该表的关联方式的属性1‘ => ‘定义‘,
‘该表的关联方式的属性N‘ => ‘定义‘,
),
‘关联表名2‘ => array(
‘该表的关联方式的属性1‘ => ‘定义‘,
‘该表的关联方式的属性N‘ => ‘定义‘,
),
...
);
在上面的格式描述中,对于属性我们使用了一个修饰词:该表的关联方式的属性。
关联操作的定义之所以觉得复杂和容易出错,也许是因为可定义的属性比较多,而且因为各种关联关系不同,可定义的属性也有部分区别,其实只要理解透这个知识点,你会发现关联操作其实也很简单。
我们换一种方式去啃下这个最麻烦的难点,先来了解所有允许定义的属性含义,然后再按照各种关联关系去对号入座。
完整方式的定义,替换上面的格式,会类似这样,这里以Area表进行示例说明允许定义的属性这个概念
class UserModel extends Model{
protected $_link = array(
‘Area‘=>array(
‘mapping_type‘=> BELONGS_TO,
‘mapping_name‘=> ‘Area‘,
‘class_name‘ => ‘Area‘,
‘foreign_key‘ => ‘area_id‘,
),
);
}
用以定义该表与主表的关联类型mapping_type所定义的内容必须是常量,共有下面四种方式:
HAS_ONE 表示当前模型(主表)拥有一个子对象,例如本例中的user_info,每个会员对应一个资料表
Belongs_to 表示当前模型从属于另外一个父对象,例如每个会员都从属于一个地区
HAS_MANY 表示当前模型拥有多个子对象,例如每个用户有多个兴趣消费卡
MANY_TO_MANY 表示当前模型可以属于多个对象,而父对象则可能包含有多个子对象,通常两者之间需要一个中间表类约束和关联。例如本例中的每个会员可以属于多个兴趣组,每个兴趣组可以有多个用户
class_name
要关联的模型类名称。
例如示例代码所定义的Area,再次提醒,一定要确保已定义了此模型类(AreaModel)。
mapping_name
关联的映射名称,用于获取数据用,作为数据集中与主表字段并列的数据索引。
该名称不能与当前模型的字段重复,否则会导致关联数据获取的冲突mapping_name没有定义的话,会取class_name的定义作为mapping_name。即模型类名称
foreign_key
关联的外键名称
foreign_key如果没定义,则默认是 当前数据对象名称+_id 作为外键
例如:UserModel对应的表是think_user(注意:think只是一个数据表前缀,可以随意配置)
那么think_user表的外键默认为user_id,如果不是,就用该属性进行显式指定,例如数据表定义的外键是userId,则使用‘foreign_key‘=>‘userId‘,这种方式去申明
Condition
关联条件,在关联查询的时候会自动带上外键的值,如果有额外的查询条件,可以使用该属性进行定义
mapping_fields
关联要查询的字段
如果没有定义,默认关联查询的关联数据是该关联表的全部字段。
如果仅仅是需要查询其中的个别字段,可以使用该属性进行指定
as_fields
直接把关联的字段值映射成数据对象中的某个字段
这个特性是ONE_TO_ONE(包括HAS_ONE 和 BELONGS_TO)关联关系特有的,可以直接把关联数据映射到数据对象中,而不是作为一个关联数据。例如当关联数据的字段名和当前数据对象的字段名称有冲突时,可以使用下面这种方式进行定义:
‘as_fields‘=>‘email,nickname:username‘,没有定义该属性时数据类似于:
["id"] => string(1) "2"
["name"] => string(8) "thinkphp"
["Card"] => array(1) {
[0] => array(1) {
["card"] => string(8) "88888888"
}
如果定义了映射,则类似于
["id"] => string(1) "2"
["name"] => string(8) "thinkphp"
["card"] => string(8) "88888888"
parent_key
自引用关联的关联字段
默认为parent_id (BELONGS_TO和HAS_MANY才有此属性)
自引用关联是一种比较特殊的关联,也就是关联表就是当前表。
mapping_limit
关联要返回的记录数目(HAS_MANY和MANY_TO_MANY才有此属性)
mapping_order
关联查询的排序 (HAS_MANY和MANY_TO_MANY才有此属性)
relation_foreign_key
关联表的外键名称 (MANY_TO_MANY独有属性)
默认的关联表的外键名称是 表名+_id
例如本示例中的relation_foreign_key是group_id
如果不符合,可通过此属性进行指定
relation_table
关联的中间表名称 (MANY_TO_MANY独有属性)
如果没有设置该属性,默认通过当前模型的getRelationTableName方法来自动获取
例如当前模型是User,关联模型是Group,那么关联表的名称会默认使用user_group这样的格式。
默认的中间表命名规则是:
如果两个表存在一个对应的中间表,该中间表的命名方式为
数据表前缀+_ +关联操作的主表名+_+关联表名
例如本示例中,由think_user表操作关联表,所以和think_group表的中间表名称是think_user_group,如果是从think_group表来操作,那么中间命名是think_group_user,也就是说,多对多关联的设置,必须有一个Model类里面需要显式定义中间表,否则双向操作会出错。
另外,中间表的字段无需要有id主键,(不过就算建立了这个字段也不会影响中间表操作的)中间表通常只是由 user_id 和 group_id 构成就可以了。
如果中间表不符合上面的规则,则需要通过该属性去显式指定中间表名称。
各种关联关系类型可用属性归纳如下,使用中可对照此表和参考上面定义解释
HAS_ONE 可用属性
mapping_type
class_name
mapping_name
foreign_key
condition
mapping_fields
as_fields
BELONGS_TO 可用属性
mapping_name
foreign_key
mapping_fields
condition
parent_key
as_fields
HAS_MANY 可用属性
mapping_name
foreign_key
parent_key
mapping_fields
mapping_limit
mapping_order
MANY_TO_MANY 可用属性
mapping_name
foreign_key
elation_foreign_key
mapping_limit
mapping_order
relation_table
关联定义好后,我们就可以轻松使用关联操作了。
以下示例代码来自官方总工程师的关联操作示例,先贴出所有代码再对重点要注意的地方进行讲解。
<?php
class IndexAction extends Action{
public function index(){
$User = D("User");
/*-------------- 关联写入 -------------------------------*/
// 添加用户数据
$User->name = ‘thinkphp‘;
$User->area_id = 1;//东区
// 用户档案数据
$User->UserInfo = array(
‘email‘ =>‘liu21st@gmail.com‘,
‘nickname‘ =>‘流年‘,
);
// 用户的消费卡数据
$User->Card = array(
array(‘id‘=>1,‘card‘=>‘12345678‘),// 消费卡帐号1
array(‘id‘=>2,‘card‘=>‘88888888‘),// 消费卡帐号2
);
// 用户的所属兴趣组数据
$User->Group = array(
array(‘id‘=>1),// 琴
array(‘id‘=>2),// 棋
);
// 关联添加用户数据
$id = $User->relation(true)->add();
// 如果用户数据不是User模型而是一个Data数组
// 可以使用
// $id = $User->relation(true)->add($Data);
$this->assign(‘info1‘, ‘用户数据关联写入完成!‘);
/*-------------- 关联查询 -------------------------------*/
$user = $User->relation(true)->find($id);
$this->assign(‘user1‘,$user);
$user = $User->relation(‘UserInfo‘)->find($id);
$this->assign(‘user2‘,$user);
$list = $User->relation(true)->findAll();
$this->assign(‘list‘,$list);
/*-------------- 关联更新 -------------------------------*/
$user[‘id‘] = $id;
$user[‘name‘] = ‘tp‘;
// HAS_ONE 关联数据的更新直接赋值
$user[‘UserInfo‘][‘email‘] = ‘thinkphp@qq.com‘;
// 注意HAS_MANY 的关联数据要加上主键的值
// 可以更新部分数据
$user[‘Card‘] = array(
array(‘id‘=>1,‘card‘=>‘66666666‘), // 更新主键为1的记录
array(‘card‘=>‘77777777‘),// 增加一条新的记录
);
// MANY_TO_MANY 的数据更新是重新写入
$user[‘Group‘] = array(
array(‘id‘=>2),
);
$User->where(‘id=‘.$id)->relation(true)->save($user);
// 查询更新后的数据
$user = $User->relation(true)->find($id);
$this->assign(‘user3‘,$user);
/*-------------- 关联删除 -------------------------------*/
$User->relation(true)->delete($id);
/*--------------------------------------------------------*/
$this->assign(‘info2‘, ‘用户ID为‘.$id.‘的数据已经关联删除!‘);
$this->assign(‘id‘,$id);
$this->display();
}
}
?>
代码注解:
TP关联操作支持手动关联和自动关联,我们看到上面代码使用的是手动关联,通过在连贯操作中的relation()方法进行控制,如果需要自动关联操作,可以在UserModel中设置相应属性。
class UserModel extends RelationModel{
protected $autoSaveRelations = true; // 自动关联保存
protected $autoDelRelations = true; // 自动关联删除
protected $autoAddRelations = true; // 自动关联写入
protected $autoReadRelations = true; // 自动关联查询
protected $_link = array(....);
}
例如当设置了自动关联查询属性为true时,使用$User->select(),系统会自动把所有定义的关联数据都查询出来,自动关联的应用可以自己进行测试体验,官方的建议是:当给模型定义了多个关联的时候,要尽量避免使用自动关联操作,以免影响性能。
再来看上面的代码,插入数据时我们使用了这种方式:
// 用户档案数据
$User->UserInfo = array(
‘email‘ =>‘liu21st@gmail.com‘,
‘nickname‘ =>‘流年‘,
);
是因为我们以User模型为核心对其它模型类进行关联定义的,其它模型类的mapping_name属性所定义的值,会被映射到User数据对象中成为User数据对象的其中一个属性,黙认mapping_name属性是关联的模型类名称。
如果是ONE_TO_ONE关联关系,并且定义了as_fields字段映射,则as_fields所设置的字段,会直接作为user数据对象的字段,这种情况在数据处理方式上,和单表模型是相同的,即直接赋值就可以:
$User->email = ‘liu21st@gmail.com‘;
$User->nickname = ‘流年‘;
relation()方法在ThinkPHP1.6版中进行了改进,简化了以前的几种我们所熟悉的xFind(),xFindAll(),relationGet(),relationAdd(),relationSave(),relationDel()这些方法,全部整合到了新定义的relation()中,直接支持使用参数进行连贯操作。
当需要进行关联处理时,我们使用relation(ture)这种方式:
$id = $User->relation(true)->add();
如果要指定关联数据,可以如34行代码中那样,在relation方法中传入参数:
$user =$User->relation(‘UserInfo‘)->find($id);
至于关联更新的需要注意的是HAS_ONE 关联数据的更新直接赋值则可,
$user[‘UserInfo‘][‘email‘] = ‘thinkphp@qq.com‘;
如果是HAS_MANY 的关联数据则要加上主键的值,可以更新部分数据或添加新的数据
$user[‘Card‘] = array(
array(‘id‘=>1,‘card‘=>‘66666666‘), // 更新主键为1的记录
array(‘card‘=>‘77777777‘),// 增加一条新的记录
);
MANY_TO_MANY关联在插入和更新时,都会自动先将原来关联的数据删除再进行处理。
打开Runtime/Logs/的log日志或开启debug模式时,可以留意一下生成的sql。
其它代码都很浅显,大家一看就明白,这里就不多说了,全部源码在附件中,教程只能引导你熟悉概念,体会是需要动手尝试的,所以建议可能的话,尽量自行测试一次。至此,关联操作的大概内容已讲述完毕,难点在于如何定义关联关系,不过只要清楚知道每个属性的含义,和关联关系对应可定义的属性,关联操作也就轻松掌握了。
原文地址:http://blog.csdn.net/wzwdcld/article/details/43925431