模型
模型类的作用大多数情况是操作数据表的,如果按照系统的规范来命名模型类的话,大多数情况下是可以自动对应数据表。
模型类的命名规则是除去表前缀的数据表名称,采用驼峰法命名,并且首字母大写,然后加上模型层的名称(默认定义是Model)
数据表对应
在ThinkPHP的模型里面,有几个关于数据表名称的属性定义:
属性
说明 | |
---|---|
tablePrefix | 定义模型对应数据表的前缀,如果未定义则获取配置文件中的DB_PREFIX参数 |
tableName | 不包含表前缀的数据表名称,一般情况下默认和模型名称相同,只有当你的表名和当前的模型类的名称不同的时候才需要定义。 |
trueTableName | 包含前缀的数据表名称,也就是数据库中的实际表名,该名称无需设置,只有当上面的规则都不适用的情况或者特殊情况下才需要设置。 |
dbName | 定义模型当前对应的数据库名称,只有当你当前的模型类对应的数据库名称和配置文件不同的时候才需要定义。 |
数据库里面有一个think_categories
表,模型类名称是CategoryModel
,按照系统的约定,这个模型的名称是Category,对应的数据表名称应该是think_category
(全部小写),但是现在的数据表名称是think_categories
,因此就需要设置tableName
属性来改变默认的规则(假设已经在配置文件里面定义了DB_PREFIX
为 think_)。
namespace Home\Model;
use Think\Model;
class CategoryModel extends Model {
protected $tableName = 'categories';
}
注意这个属性的定义不需要加表的前缀think_
如果需要CategoryModel模型对应操作的数据表是 top_category
,那么只需要设置数据表前缀即可:
namespace Home\Model;
use Think\Model;
class CategoryModel extends Model {
protected $tablePrefix = 'top_';
}
如果数据表直接就是category
,而没有前缀,则可以设置tablePrefix
为空字符串。(没有表前缀的情况必须设置,否则会获取当前配置文件中的DB_PREFIX
。)
而对于另外一种特殊情况,我们需要操作的数据表是top_categories
,这个时候我们就需要定义 trueTableName
属性
namespace Home\Model;
use Think\Model;
class CategoryModel extends Model {
protected $trueTableName = 'top_categories';
}
除了数据表的定义外,还可以对数据库进行定义(用于操作当前数据库以外的数据表),例如 top.top_categories
:
namespace Home\Model;
use Think\Model;
class CategoryModel extends Model {
protected $trueTableName = 'top_categories';
protected $dbName = 'top';
}
系统的规则下,tableName会转换为小写定义,但是trueTableName定义的数据表名称是保持原样。因此,如果你的数据表名称需要区分大小写的情况,那么可以通过设置trueTableName定义来解决。
模型实例化
根据不同的模型定义,有几种实例化模型的方法,根据需要采用不同的方式:
直接实例化
$User = new \Home\Model\UserModel();
$Info = new \Admin\Model\InfoModel();
// 带参数实例化
$New = new \Home\Model\NewModel('blog','think_',$connection);
模型类通常都是继承系统的\Think\Model类,该类的架构方法有三个参数,分别是:
Model(['模型名'],['数据表前缀'],['数据库连接信息']);
三个参数都是可选的,大多数情况下,我们根本无需传入任何参数即可实例化。
数据库连接信息参数支持三种格式:
1、字符串定义
字符串定义采用DSN格式定义,格式定义规范为:
type://username:passwd@hostname:port/DbName
// 3.2.1以上版本还可以支持字符集设定
type://username:passwd@hostname:port/DbName#charset
例如:
new \Home\Model\NewModel('blog','think_','mysql://root:1234@localhost/demo');
2、数组定义
可以传入数组格式的数据库连接信息,例如:
$connection = array(
'db_type' => 'mysql',
'db_host' => '192.168.1.2,192.168.1.3',
'db_user' => 'root',
'db_pwd' => '12345',
'db_port' => 3306,
'db_name' => 'demo',
'db_charset' => 'utf8', //3.2.1以上版本还可以支持数据编码设定
'db_deploy_type'=> 1, //3.2.3开始还可以支持数据的部署模式和调试模式设定
'db_rw_separate'=> true, //3.2.3开始还可以支持数据的部署模式和调试模式设定
'db_debug' => true, //3.2.3开始还可以支持数据的部署模式和调试模式设定
);
// 分布式数据库部署 并且采用读写分离 开启数据库调试模式
new \Home\Model\NewModel('new','think_',$connection);
注意,如果设置了db_debug参数,那么数据库调试模式就不再受APP_DEBUG常量影响。
3、配置定义
可以事先在配置文件中定义好数据库连接信息,然后在实例化的时候直接传入配置的名称即可,例如:
//数据库配置1
'DB_CONFIG1' => array(
'db_type' => 'mysql',
'db_user' => 'root',
'db_pwd' => '1234',
'db_host' => 'localhost',
'db_port' => '3306',
'db_name' => 'thinkphp'
),
//数据库配置2
'DB_CONFIG2' => 'mysql://root:1234@localhost:3306/thinkphp',
在配置文件中定义数据库连接信息的时候也支持字符串和数组格式,格式和上面实例化传入的参数一样。
然后,我们就可以这样实例化模型类传入连接信息:
new \Home\Model\NewModel('new','think_','DB_CONFIG1');
new \Home\Model\BlogModel('blog','think_','DB_CONFIG2');
事实上,实例化的时候没有传入任何的数据库连接信息的时候,系统其实默认会获取配置文件中的相关配置参数,包括:
'DB_TYPE' => '', // 数据库类型
'DB_HOST' => '', // 服务器地址
'DB_NAME' => '', // 数据库名
'DB_USER' => '', // 用户名
'DB_PWD' => '', // 密码
'DB_PORT' => '', // 端口
'DB_PREFIX' => '', // 数据库表前缀
'DB_DSN' => '', // 数据库连接DSN 用于PDO方式
'DB_CHARSET' => 'utf8', // 数据库的编码 默认为utf8
如果应用配置文件中有配置上述数据库连接信息的话,实例化模型将会变得非常简单。
D方法实例化
上面实例化的时候我们需要传入完整的类名,系统提供了一个快捷方法D用于数据模型的实例化操作。
要实例化自定义模型类,可以使用下面的方式:
<?php
//实例化模型
$User = D('User');
// 相当于 $User = new \Home\Model\UserModel();
// 执行具体的数据操作
$User->select();
当 \Home\Model\UserModel
类不存在的时候,D函数会尝试实例化公共模块下面的 \Common\Model\UserModel
类。
如果在Linux环境下面,一定要注意D方法实例化的时候的模型名称的大小写。
D方法可以自动检测模型类,如果存在自定义的模型类,则实例化自定义模型类,如果不存在,则会实例化系统的\Think\Model基类,同时对于已实例化过的模型,不会重复去实例化。 D方法还可以支持跨模块调用,需要使用:
//实例化Admin模块的User模型
D('Admin/User');
//实例化Extend扩展命名空间下的Info模型
D('Extend://Editor/Info');
注意:跨模块实例化模型类的时候 不支持自动加载公共模块的模型类。
M方法实例化模型
D方法实例化模型类的时候通常是实例化某个具体的模型类,如果你仅仅是对数据表进行基本的CURD操作的话,使用M方法实例化的话,由于不需要加载具体的模型类,所以性能会更高。
// 使用M方法实例化
$User = M('User');
// 和用法 $User = new \Think\Model('User'); 等效
// 执行其他的数据操作
$User->select();
如果你的模型类有自己的业务逻辑,M方法是无法支持的,就算是你已经定义了具体的模型类,M方法实例化的时候是会直接忽略。
实例化空模型类
仅仅是使用原生SQL查询的话,不需要使用额外的模型类,实例化一个空模型类即可进行操作了,例如:
//实例化空模型
$Model = new Model();
//或者使用M快捷方法是等效的
$Model = M();
//进行原生的SQL查询
$Model->query('SELECT * FROM think_user WHERE status = 1');
实例化空模型类后还可以用table方法切换到具体的数据表进行操作
D方法和M方法区别:M方法实例化模型无需用户为每个数据表定义模型类,如果D方法没有找到定义的模型类,则会自动调用M方法。
字段定义
系统会在模型首次实例化的时候自动获取数据表的字段信息(而且只需要一次,以后会永久缓存字段信息,除非设置不缓存或者删除),如果是调试模式则不会生成字段缓存文件,则表示每次都会重新获取数据表字段信息。
字段缓存保存在Runtime/Data/_fields/
目录下面,缓存机制是每个模型对应一个字段缓存文件(注意:并非每个数据表对应一个字段缓存文件),命名格式是:
数据库名.模型名(小写).php
demo.user.php // User模型生成的字段缓存文件
demo.think_user.php // 3.2.3版本开始,User模型生成的字段缓存文件
字段缓存包括数据表的字段信息、主键字段和是否自动增长,如果开启字段类型验证的话还包括字段类型信息等等,无论是用M方法还是D方法,或者用原生的实例化模型类一般情况下只要是不开启调试模式都会生成字段缓存(字段缓存可以单独设置关闭)。
// 关闭字段缓存
'DB_FIELDS_CACHE'=>false
注意:调试模式下面由于考虑到数据结构可能会经常变动,所以默认是关闭字段缓存的。
获取当前数据表的字段信息,可以使用模型类的getDbFields方法来获取当前数据对象的全部字段信息,例如:
$User = M('User');
$fields = $User->getDbFields();
在部署模式下面修改了数据表的字段信息,可能需要清空Data/_fields
目录下面的缓存文件,否则会发生新增的字段无法写入数据库的问题。
不希望依赖字段缓存或者想提高性能,也可以在模型类里面手动定义数据表字段的名称,可以避免IO加载的效率开销,例如:
namespace Home\Model;
use Think\Model;
class UserModel extends Model {
protected $fields = array('id', 'username', 'email', 'age');
protected $pk = 'id';//定义当前数据表的主键名,默认值就是id,因此如果是id的话可以无需定义。
}
3.2.3版本以上,支持定义复合主键 :rotected $pk = array('user_id','lession_id');
除了可以设置数据表的字段之外,我们还可以定义字段的类型,用于某些验证环节。例如:
namespace Home\Model;
use Think\Model;
class UserModel extends Model {
protected $fields = array('id', 'username', 'email', 'age',
'_type'=>array('id'=>'bigint','username'=>'varchar','email'=>'varchar','age'=>'int')
);
}
模型数据操作
表达式
含义 | |
---|---|
EQ | 等于(=) |
NEQ | 不等于(<>) |
GT | 大于(>) |
EGT | 大于等于(>=) |
LT | 小于(<) |
ELT | 小于等于(<=) |
LIKE | 模糊查询 |
[NOT] BETWEEN | (不在)区间查询 |
[NOT] IN | (不在)IN 查询 |
EXP | 表达式查询,支持SQL语法 |
where
使用字符串条件直接查询和操作,例如:
$User = M("User"); // 实例化User对象
$User->where('type=1 AND status=1')->select();
使用3.1以上版本的话,使用字符串条件的时候,建议配合预处理机制,确保更加安全,例如:
$Model->where("id=%d and username='%s' and xx='%f'",array($id,$username,$xx))->select();
$Model->where("id=%d and username='%s' and xx='%f'",$id,$username,$xx)->select();
如果$id
变量来自用户提交或者URL地址的话,如果传入的是非数字类型,则会强制格式化为数字格式后进行查询操作。
字符串预处理格式类型支持指定数字、字符串等,具体可以参考vsprintf方法的参数说明。
查询表达式的使用格式:
$map['字段1'] = array('表达式','查询条件1');
$map['字段2'] = array('表达式','查询条件2');
$Model->where($map)->select(); // 也支持
3.1.3版本开始,where方法支持多次调用,但字符串条件只能出现一次,例如:
$map['a'] = array('gt',1);
$where['b'] = 1;
$Model->where($map)->where($where)->where('status=1')->select();
table
操作模型的时候系统能够自动识别当前对应的数据表,使用table方法的情况通常是为了:
1.切换操作的数据表;
2.对多表进行操作;
需要对多表进行操作,可以这样使用:
$Model->field('user.name,role.title')
->table('think_user user,think_role role')
->limit(10)->select();
为了尽量避免和mysql的关键字冲突,可以建议使用数组方式定义,例如:
$Model->field('user.name,role.title')
->table(array('think_user'=>'user','think_role'=>'role'))
->limit(10)->select();
一般情况下,无需调用table方法,默认会自动获取当前模型对应或者定义的数据表。
alias
用于设置当前数据表的别名,便于使用其他的连贯操作例如join方法等。
$Model = M('User');
$Model->alias('a')->join('__DEPT__ b ON b.user_id= a.id')->select();
最终生成的SQL语句类似于:
SELECT * FROM think_user a INNER JOIN think_dept b ON b.user_id= a.id
data
用于设置当前要操作的数据对象的值。
$Model = M('User');
$data = 'name=流年&email=thinkphp@qq.com';
$Model->data($data)->add(); <code><span class="pln">
$Model</span><span class="pun">-></span><span class="pln">add</span><span class="pun">(</span><span class="pln">$data</span><span class="pun">);</span></code> //这种方式data参数只能使用数组。
读操作,data方法还可以用于读取当前的数据对象,例如:
$User = M('User');
$map['name'] = '流年';
$User->where($map)->find();
// 读取当前数据对象
$data = $User->data();
field
主要目的是标识要返回或者操作的字段,可以用于查询和写入操作。
可以在field方法中直接使用函数,例如:
$Model->field('id,SUM(score) as test')->select(); //可以给某个字段设置别名
field方法的参数可以支持数组,例如:
<pre class="prettyprint linenums prettyprinted" style=""><code><span class="pln">$Model</span><span class="pun">-></span><span class="pln">field</span><span class="pun">(</span><span class="pln">array</span><span class="pun">(</span><span class="str">'id'</span><span class="pun">,</span><span class="str">'nickname'</span><span class="pun">=></span><span class="str">'name'</span><span class="pun">))-></span><span class="kwd">select</span><span class="pun">(); //数组方式的定义可以为某些字段定义别名</span></code>
对于一些更复杂的字段要求,数组的优势则更加明显,例如:
$Model->field(array('id','concat(name,'-',id)'=>'truename','LEFT(title,7)'=>'sub_title'))->select();
field(true)
的用法会显式的获取数据表的所有字段列表,哪怕你的数据表有100个字段。
希望获取排除数据表中的content
字段(文本字段的值非常耗内存)之外的所有字段值:
$Model->field('content',true)->select();
除了查询操作之外,field方法还有一个非常重要的安全功能--字段合法性检测(注意:该功能3.1版本开始才能支持)。
field可以结合create、add和save方法,进行字段过滤,例如:
$Model->field('title,email,content')->save($data);
如果data数据中包含有title,email,content之外的字段数据的话,也会过滤掉。
order
用于对操作的结果排序
$Model->where('status=1')->order('
id ,status'
)->limit(5)->select(); //支持对多个字段的排序
如果没有指定desc或者asc排序规则的话,默认为asc。
如果字段和mysql关键字有冲突,那么建议采用数组方式调用array('order','id'=>'desc');
limit
主要用于指定查询和操作的数量,特别在分页查询的时候使用较多。ThinkPHP的limit方法可以兼容所有的数据库驱动类的。
用于文章分页查询是limit方法比较常用的场合,例如:
$Article = M('Article');
$Article->limit('10,25')->select(); //在3.1版本后,可以这样limit(10,25)使用
对于大数据表,尽量使用limit限制查询结果,否则会导致很大的内存开销和性能问题。
page
是完全为分页查询而诞生的一个人性化操作方法。
利用扩展类库中的分页类Page可以自动计算出每个分页的limit参数,例如:
$Article = M('Article');
$Article->page('1,10')->select(); // 查询第一页数据
$Article->page('2,10')->select(); // 查询第二页数据
3.1版本以后,page方法也支持2个参数的写法,例如:
$Article->page(1,10)->select();
page方法还可以和limit方法配合使用,例如:
$Article->limit(25)->page(3)->select();
当page方法只有一个值传入的时候,表示第几页,而limit方法则用于设置每页显示的数量
group
只有一个参数,并且只能使用字符串。
$this->field('username,max(score)')->group('user_id,test_time')->select(); //也支持对多个字段进行分组
having
用于配合group方法完成从分组的结果中筛选(通常是聚合条件)数据。
只有一个参数,并且只能使用字符串,例如:
$this->field('username,max(score)')->group('user_id')->having('count(test_time)>3')->select();
join
不同类型的join操作会影响返回的数据结果:
INNER JOIN: 如果表中有至少一个匹配,则返回行,等同于 JOIN
LEFT JOIN: 即使右表中没有匹配,也从左表返回所有的行
RIGHT JOIN: 即使左表中没有匹配,也从右表返回所有的行
FULL JOIN: 只要其中一个表中存在匹配,就返回行
join方法可以支持以上四种类型,例如:
$Model = M('Artist');
$Model
->join('think_work ON think_artist.id = think_work.artist_id')
->join('think_card ON think_artist.card_id = think_card.id')
->select();
join方法支持多次调用,但指定的数据表必须是全称,但我们可以这样来定义:
$Model
->join('__WORK__ ON __ARTIST__.id = __WORK__.artist_id')
->join('__CARD__ ON __ARTIST__.card_id = __CARD__.id')
->select();
__WORK__
和 __CARD__
在最终解析的时候会转换为 think_work
和 think_card
。
默认采用INNER JOIN 方式,如果需要用其他的JOIN方式,可以改成:
$Model->join('RIGHT JOIN __WORK__ ON __ARTIST__.id = __WORK__.artist_id')->select();
$Model->join('__WORK__ ON __ARTIST__.id = __WORK__.artist_id','RIGHT')->select(); //join方法的第二个参数支持的类型包括:INNER LEFT RIGHT FULL。
如果join方法的参数用数组的话,只能使用一次join方法,并且不能和字符串方式混合使用。
UNION
每个union方法相当于一个独立的SELECT语句。
$Model->field('name')
->table('think_user_0')
->union('SELECT name FROM think_user_1')
->union('SELECT name FROM think_user_2')
->select();
数组用法:
$Model->field('name')
->table('think_user_0')
->union(array('field'=>'name','table'=>'think_user_1'))
->union(array('field'=>'name','table'=>'think_user_2'))
->select();
$Model->field('name')
->table('think_user_0')
->union(array('SELECT name FROM think_user_1','SELECT name FROM think_user_2'))
->select();
支持UNION ALL 操作,例如:
$Model->field('name')
->table('think_user_0')
->union('SELECT name FROM think_user_1',true)
->union('SELECT name FROM think_user_2',true)
->select();
注意:UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同。
fetchSql
3.2.3新增的,用于直接返回SQL而不是执行查询。例如:
$result = M('User')->fetchSql(true)->find(1);
输出result结果为: SELECT * FROM think_user where id = 1