美文网首页PHP实战
Thinkphp 模型和数据库:模型高级用法

Thinkphp 模型和数据库:模型高级用法

作者: 寒冬夜行人_51a4 | 来源:发表于2019-07-10 11:49 被阅读0次

    本章带你了解下模型的一些高级技巧,这些技巧会让你在使用模型的过程中更加高效和简单,学习内容主要包含:

    条件查询

    我们前面的大多数例子都是使用主键进行模型数据查询,事实上,业务逻辑并非都是如此简单,条件查询的必要性是存在的。

    最简单的方法是直接使用Db类的查询构造器查询,方法是在模型中静态调用Db类(其实是查询类)的任何方法(包括动态查询)进行查询。

    // 查询单个记录
    User::where('name', 'thinkphp')->find();
    // 调用动态查询方法
    User::getByName('thinkphp');
    // 查询数据集
    User::where('id', '>', 0)->limit(10)->order('id desc')->select();
    // 删除数据
    User::where('status', 0)->delete();
    
    

    如果你的查询代码在模型内部,可以直接支持动态方式调用查询构造器的方法,用法就是从静态调用改为动态调用。

    // 查询单个记录
    $this->where('name', 'thinkphp')->find();
    // 调用动态查询方法
    $this->getByName('thinkphp');
    // 查询数据集
    $this->where('id', '>', 0)->limit(10)->order('id desc')->select();
    // 删除数据
    $this->where('status', 0)->delete();
    
    

    不过类似的查询场景应当极力避免(个别场景例如查询范围等可能会涉及到此类用法),查询操作应当是静态调用,更新和删除操作则是动态方法调用。如果是在模型方法中查询其它模型的数据,第一种静态调用方式仍然适用。尤其不建议使用table方法在同一个模型实例中切换数据表查询,在模型中动态设置table属性的方式更加不可取(经常发现这种奇葩的用法),模型和数据表以及相应的业务逻辑是应当在创建的时候就相对固定的,应当极力避免在一个模型对象实例中查询操作多次不同数据。

    模型查询的原则应当是每个模型对象实例操作一个唯一记录,对于数据集来说这个原则也不变,只是每个数据集对象实例则包含多个模型对象实例而已。

    对于自定义查询,如果统一使用模型类提供的getall进行查询也一样可以达到目的,改成下面代码即可:

    // 查询单个记录
    User::get(['name' => 'thinkphp']);
    // 查询数据集
    User::all(function ($query) {
        $query->where('id', '>', 0)
            ->limit(10)
            ->order('id desc');
    });
    // 删除数据
    User::destroy(['status' => 0]);
    User::destroy(function ($query) {
        $query->where('id', 'in', [1, 2, 3]);
    });
    
    

    getall以及destroy方法的参数用法记住一个原则,如果是数字、字符串或者普通数组都表示一个或者多个主键,如果是索引数组则表示查询条件,闭包则支持查询条件以外的其它链式操作方法。对于get方法的参数最好做一次非null检查,否则查询的就会是第一个数据(V5.0.8+已经改进,不需要检查是否为null了)。

    查询范围

    对于一些常用的查询条件,我们可以事先定义好,以便快速调用,这个事先定义的查询条件方法有一个统一的前缀scope,我们称之为查询范围,例如下面给User模型定义了两个查询范围方法。

    <?php
    
    namespace app\index\model;
    
    use think\Model;
    
    class User extends Model
    {
    
        // email查询
        protected function scopeEmail($query)
        {
            $query->where('email', 'thinkphp@qq.com');
        }
    
        // status查询
        protected function scopeStatus($query)
        {
            $query->where('status', 1);
        }
    }
    
    

    现在我们直接使用

    $users = User::scope('email,status')->select();
    
    

    或者使用

    $users = User::scope('email')->scope('status')->select();
    
    

    生成的查询语句是

    SELECT * FROM `user` WHERE `email` = 'thinkphp@qq.com' AND `status` = 1 
    
    

    查询范围之外仍然可以使用额外的查询条件,例如:

    $users = User::scope('email,status')
        ->where('nickname', 'like', '%think%')
        ->order('id desc')
        ->select();
    
    

    查询范围方法必须首先被调用

    查询范围方法支持额外的参数,例如scopeEmail方法改为:

        // email查询
        protected function scopeEmail($query, $email = '')
        {
            $query->where('email', $email);
        }
    
    

    查询范围的方法的第一个参数必须是查询对象,并且支持多个额外参数。

    然后,使用下面的方式调用即可(带参数调用的时候每次只能调用一个查询范围):

    $list = User::scope('email', 'thinkphp@qq.com')->select();
    
    

    查询范围有一个特殊的方法base,一旦在模型中定义了base方法后,无需显式调用scope方法,系统会在每次查询的时候自动调用,我们称之为全局查询范围。

    <?php
    
    namespace app\index\model;
    
    use think\Model;
    
    class User extends Model
    {
        // 全局查询范围
        protected static function base($query)
        {
            // 查询状态为1的数据
            $query->where('status', 1);
        }
    
        // email查询
        protected function scopeEmail($query)
        {
            $query->where('email', 'thinkphp@qq.com');
        }
    
    }
    
    

    当使用下面的查询操作

    User::get(1);
    User::scope('email')->select();
    
    

    最后生成的SQL语句分别是:

    SELECT * FROM `user` WHERE `status` = 1 AND `id` = 1 LIMIT 1    
    SELECT * FROM `user` WHERE `status` = 1 AND `email` = 'thinkphp@qq.com' 
    
    

    无论是什么查询都会默认带上全局查询范围中的条件。

    可以临时关闭全局查询范围进行查询

    // 关闭全局查询范围
    User::useGlobalScope(false)->get(1);
    
    

    查询范围方法中不仅支持where方法,任何查询构造器的方法都可以被支持。

    字段过滤

    经常我们会直接使用表单提交的数据来作为模型数据写入,但并不是所有的数据都是数据表字段(直接写入会导致数据库异常),或者不希望某些数据被用户通过表单提交的方式更新(为了安全或者逻辑考虑),Request类自身提供了only方法来获取部分想要的数据,例如:

    // 只获取请求变量中的nickname和address变量
    $data = request()->only(['nickname', 'address']);
    // 获取当前用户对象
    $user = User::get(request()->session('user_id'));
    // 更新用户数据
    $user->data($data, true)->save();
    
    

    模型类提供了allowField方法用于在数据写入操作的时候设置字段过滤,从而避免数据库因为字段不存在而报错,上面的写法可以简化为。

    // 获取当前用户对象
    $user = User::get(request()->session('user_id'));
    // 只允许更新用户的nickname和address数据
    $user->allowField(['nickname', 'address'])
        ->data(requst()->param(), true)
        ->save();
    
    

    如果仅仅是希望去除数据表之外的字段,可以使用

    // 只允许更新数据表字段数据
    $user->allowField(true)
        ->data(requst()->param(), true)
        ->save();
    
    

    为了不必每次都调用allowField方法,我们可以直接在模型类里面设置field属性,例如:

    <?php
    
    namespace app\index\model;
    
    use think\Model;
    
    class User extends Model
    {
        protected $field = ['name', 'nickname', 'email', 'address'];
    }
    
    

    当调用allowField方法的时候,当前模型实例中的该配置的值会被覆盖。

    如果使用的是模型的静态方法(如createupdate方法)进行数据写入的话,可以使用下面的方式进行字段过滤。

    User::create(request()->param(), ['nickname', 'address']);
    User::update(request()->param(), ['id' => 1], ['nickname', 'address']);
    
    

    同样可以传入true表示过滤非数据表字段

    User::create(request()->param(), true);
    User::update(request()->param(), ['id' => 1], true);
    
    

    只读字段

    有些数据字段在写入以后就不允许被更改,例如name字段和email字段,那么我们可以设置该字段为只读字段,在更新的时候就会被自动忽略掉。

    设置方式:

    <?php
    
    namespace app\index\model;
    
    use think\Model;
    
    class User extends Model
    {
        protected $readonly = ['name','email'];
    }
    
    

    举个例子说明下:

    $user = User::get(5);
    echo $user->name;
    echo $user->email;
    // 更改某些字段的值
    $user->name    = 'TOPThink';
    $user->email   = 'Topthink@gmail.com';
    $user->address = '上海静安区';
    // 保存更改后的用户数据
    $user->save();
    echo $user->name;
    echo $user->email;
    
    

    事实上,由于我们对nameemail字段设置了只读,因此只有address字段的值被更新了,而nameemail的值仍然还是更新之前的值。

    软删除

    在实际项目中,对数据频繁使用删除操作会导致性能问题,因此不推荐直接物理删除数据,而是用逻辑删除替代,也就是下面要讲的软删除。软删除的作用就是把数据加上删除标记,而不是真正的删除,同时也便于需要的时候进行数据的恢复。

    要使用软删除功能,需要引入SoftDelete trait,例如User模型按照下面的定义就可以使用软删除功能:

    <?php
    
    namespace app\index\model;
    
    use think\Model;
    use traits\model\SoftDelete;
    
    class User extends Model
    {
        use SoftDelete;
    }
    
    

    为了配合软删除功能,你需要在数据表中添加delete_time字段,ThinkPHP5的软删除功能使用时间戳类型(数据表默认值为Null),用于记录数据的删除时间。

    如果你的软删除标记字段名称不是delete_time的话,需要添加属性定义:

    <?php
    
    namespace app\index\model;
    
    use think\Model;
    use traits\model\SoftDelete;
    
    class User extends Model
    {
        use SoftDelete;
        protected $deleteTime = 'delete_field_name';
    }
    
    

    可以用类型转换指定软删除字段的类型,建议数据表的所有时间字段统一使用autoWriteTimestamp属性规范时间类型(支持datetimedatetimestamp以及integer)。

    <?php
    
    namespace app\index\model;
    
    use think\Model;
    use traits\model\SoftDelete;
    
    class User extends Model
    {
        use SoftDelete;
        protected $autoWriteTimestamp = 'datetime';
        protected $deleteTime = 'delete_field_name';
    }
    
    

    定义好模型后,我们就可以使用:

    // 软删除
    User::destroy(1);
    // 真实删除
    User::destroy(1,true);
    $user = User::get(1);
    // 软删除
    $user->delete();
    // 真实删除
    $user->delete(true);
    
    

    默认情况下查询的数据不包含软删除数据,如果需要包含软删除的数据,可以使用下面的方式查询:

    User::withTrashed()->find();
    User::withTrashed()->select();
    
    

    如果仅仅需要查询软删除的数据,可以使用:

    User::onlyTrashed()->find();
    User::onlyTrashed()->select();
    
    

    如果你的查询条件比较复杂,尤其是某些特殊情况下使用OR查询条件会把软删除数据也查询出来,可以使用闭包查询的方式解决,如下:

    User::where(function($query) {
        $query->where('id', '>', 10)
            ->whereOr('name', 'like', 'think');
    })->select();
    
    

    使用闭包查询条件会在查询条件两边添加括号,从而不会和软删除条件产生混淆或者冲突。

    如果你的模型定义了base基础查询,请确保添加软删除的基础查询条件,例如:

    protected function base($query)
    {
        // 添加软删除条件
        $query->whereNull('delete_time')
            // 添加额外的基础查询条件
            ->where('id','>',0);
    }
    
    

    自定义查询类

    默认情况下,默认使用的查询类是核心内置的think\db\Query类,如果你需要自己扩展额外的查询方法,可以自定义查询类,例如:

    <?php
    
    namespace app\db;
    
    use think\db\Query;
    
    class ModelQuery extends  Query
    {
    
        public function top($num)
        {
            return $this->limit($num)-select();
        }
    }
    
    

    在模型类中设置query属性如下

    <?php
    
    namespace app\index\model;
    
    use think\Model;
    
    class User extends Model
    {
        protected $query = '\app\db\ModelQuery';
    }
    
    

    设置后,User模型就可以使用top方法查询

    User::where('id desc')->top(10);
    
    

    如果全局都使用该查询类的话,建议直接在数据库配置文件中使用下面配置:

    // 设置Query类
    'query'           => '\\app\\db\\ModelQuery',
    
    

    总结

    本章我们学习了模型的一些高级用法,下一章我们就来学习下模型关联的使用。

    上一篇:第六章:模型数据处理
    下一篇:第八章:模型关联

    相关文章

      网友评论

        本文标题:Thinkphp 模型和数据库:模型高级用法

        本文链接:https://www.haomeiwen.com/subject/ipbfkctx.html