hyperf 二十一 数据库 模型关系

教程:Hyperf

一 定义关联

根据文档

  • 一对一:Model::hasOne(被关联模型,被关联模型外键,本模型被关联的字段)
  • 一对多:Model::hasMany(被关联模型,被关联模型外键,本模型被关联的字段)
  • 反向一对多:Model::belongsTo(被关联模型,本模型外键,被关联模型的对应字段,关联关系)
  • 多对多:Model::belongsToMany(被关联模型,自定义连接表名,该模型在连接表里的外键名,被关联模型在连接表里的外键名,该模型关联键,被关联模型关联键,关联关系)

1.1 一对一、一对多

根据文档说明,需要在model中设置方法调用hasOne()方法。获取的使用在查询出来的数据中获取对应方法名的属性。

#model
class User extends Model
{
    public function role()
    {
        return $this->hasOne(Role::class, 'user_id', 'id');
    }
    public function articles() {
        return $this->hasMany(Article::class, 'user_id', 'id');
    }
}
class Article extends Model {
    public function author() {
        return $this->belongsTo(User::class, 'user_id', 'id');
    }
}

#查询
//一对一
$role = User::query()->find(1)->role;
//返回Role类对象

//一对多
$info = User::query()->find(1)->articles;
//返回Hyperf\Database\Model\Collection类

//一对多反向
$info = Article::find(1)->author;
//返回App1\Model\User




 根据上面的例子,实际上find(1)查询user表id为1的行,获取role时调用role()方法,并且结果写入user对象的relations属性中。

model中定义的方法返回Relation对象,调用model的对应属性时执行Hyperf\Database\Model\Model::__get(),使用Hyperf\Database\Model\Concerns\HasAttributes::getRelationValue()设置Moel::relations属性,并设置执行结果。Relation对象是包含查询信息的Builder对象。

一对多和一对一都是调用相同父类,通过HasOneOrMany::matchOneOrMany()通过最后参数$type的值'one'、'many'处理区分。

1.2 多对多

和一对多、一对一流程一样,但是用的父类不用,使用Hyperf\Database\Model\Relations\BelongsToMany::addConstraints()构造查询,被调用对应属性时执行查询。

BelongsToMany::performJoin()以设置的model作为基础表关联中间表,BelongsToMany::addWhereConstraints()关联被调用的model。

#数据库
CREATE TABLE `role_user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `role_id` int(11) DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
CREATE TABLE `roles` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `role_name` varchar(255) DEFAULT NULL,
  `status` tinyint(1) DEFAULT NULL COMMENT '状态 1可用 0不可用',
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
CREATE TABLE `userinfo` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `age` tinyint(2) DEFAULT '0',
  `deleted_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=24 DEFAULT CHARSET=utf8;


#模型设置
class User extends Model {
    public function role() {
        return $this->belongsToMany(Role::class);
    }
}

#查询
public function testmodel2() {
     $obj2 = User::query()->find(1);
     $list = $obj2->role;
     foreach ($list as $key => $value) {
            $role_id = $value->pivot->role_id;
            $user_id = $value->pivot->user_id;
            var_dump($role_id, $user_id);
     }
}
#结果
int(1)
int(1)
int(2)
int(1)

 

 实际Hyperf\Database\Model\Concerns\HasRelationships::belongsToMany()参数包括:

table 中间表、foreignPivotKey 中间表外键、relatedPivotKey 中间表关联键、parentKey 调用模型主键、relatedKey 设置模型主键、relation 关系。

在上面例子中,User::query()->find(1)->role中User为调用模型,Role为设置的模型,中间模型为RoleUser。belongsToMany()参数默认值为role_user、user_id、role_id、id、id、role。

例子中其他值未设置,因为关联表中对应的id都是为对应的表名加id,符合框架设置默认值的格式。若把role_user中rule_id设置为rule1_id,查询会报错找不到role_user.rule_id字段。

1.2.1 获取中间表字段

中间表获取通过pivot获取,其属性名可改。BelongsToMany::accessor默认pivot,可使用BelongsToMany::as()设置accessor属性名。

通过BelongsToMany::match()调用BelongsToMany::buildDictionary()设置$this->accessor的值。

一对一、一对多、多对多的过程中math()执行都是通过Builder::get()执行。

#model
 public function role() {
        return $this->belongsToMany(Role::class)->as("role");
 }

#测试
$obj2 = User::query()->find(1);
$list = $obj2->role;
foreach ($list as $key => $value) {
   $role_id = $value->role->role_id;
   $user_id = $value->role->user_id;
   var_dump($role_id, $user_id);
}

#测试结果
int(1)
int(1)
int(2)
int(1)

1.2.2 通过中间表过滤关系

并且可以对中间表设置查询条件,比如BelongsToMany::wherePivot()、BelongsToMany::wherePivotIn()、BelongsToMany::wherePivotIn()、BelongsToMany::orWherePivot()、BelongsToMany::withPivotValue()等,可以设置Builder的where的方法。

#model
 public function role() {
        return $this->belongsToMany(Role::class)->as("role")->wherePivot('role_id', "=", 1);
    }

#测试
public function testmodel2() {
        $log = User::query()->getConnection()->enableQueryLog();
        $obj2 = User::query()->find(1);
        $list = $obj2->role;
        foreach ($list as $key => $value) {
            $role_id = $value->role->role_id;
            $user_id = $value->role->user_id;
            var_dump($role_id, $user_id);
        }
        $log = User::query()->getConnection()->getQueryLog();
        var_dump($log);
}

#测试结果
int(1)
int(1)
array(2) {
  [0]=>
  array(3) {
    ["query"]=>
    string(94) "select * from `userinfo` where `userinfo`.`id` = ? and `userinfo`.`deleted_at` is null limit 1"
    ["bindings"]=>
    array(1) {
      [0]=>
      int(1)
    }
    ["time"]=>
    float(47.56)
  }
  [1]=>
  array(3) {
    ["query"]=>
    string(238) "select `roles`.*, `role_user`.`user_id` as `pivot_user_id`, `role_user`.`role_id` as `pivot_role_id` from `roles` inner join `role_user` on `roles`.`id` = `role_user`.`role_id` where `role_user`.`user_id` = ? and `role_user`.`role_id` = ?"
    ["bindings"]=>
    array(2) {
      [0]=>
      int(1)
      [1]=>
      int(1)
    }
    ["time"]=>
    float(2.79)
  }
}

 原本想把中间表的条件放到controller层,就是通过参数设置。那么model需要改为role($roleid),controller层调用就得是User::query->find(1)->role(1),但是结果并没有执行第二次包含join的查询。因为Hyperf\Database\Model\Model::__get()和Hyperf\Database\Model\Model::__call()逻辑不同。所以中间表的过滤条件,controller大概不能控制。

二 源码

3.1 match()

3.1.1 多对多

public function match(array $models, Collection $results, $relation)
    {
        $dictionary = $this->buildDictionary($results);

        // Once we have an array dictionary of child objects we can easily match the
        // children back to their parent using the dictionary and the keys on the
        // the parent models. Then we will return the hydrated models back out.
        foreach ($models as $model) {
            if (isset($dictionary[$key = $model->{$this->parentKey}])) {
                $model->setRelation(
                    $relation,
                    $this->related->newCollection($dictionary[$key])
                );
            }
        }

        return $models;
    }
protected function buildDictionary(Collection $results)
    {
        // First we will build a dictionary of child models keyed by the foreign key
        // of the relation so that we will easily and quickly match them to their
        // parents without having a possibly slow inner loops for every models.
        $dictionary = [];

        foreach ($results as $result) {
            $dictionary[$result->{$this->accessor}->{$this->foreignPivotKey}][] = $result;
        }

        return $dictionary;
    }

3.1.2 一对一、一对多

#Hyperf\Database\Model\Relations\HasOneOrMany
public function matchOne(array $models, Collection $results, $relation) {
        return $this->matchOneOrMany($models, $results, $relation, 'one');
    }
public function matchMany(array $models, Collection $results, $relation) {
        return $this->matchOneOrMany($models, $results, $relation, 'many');
    }

protected function matchOneOrMany(array $models, Collection $results, $relation, $type) {
        $dictionary = $this->buildDictionary($results);

        // Once we have the dictionary we can simply spin through the parent models to
        // link them up with their children using the keyed dictionary to make the
        // matching very convenient and easy work. Then we'll just return them.
        foreach ($models as $model) {
            if (isset($dictionary[$key = $model->getAttribute($this->localKey)])) {
                $model->setRelation(
                    $relation,
                    $this->getRelationValue($dictionary, $key, $type)
                );
            }
        }

        return $models;
    }
protected function getRelationValue(array $dictionary, $key, $type) {
        $value = $dictionary[$key];

        return $type === 'one' ? reset($value) : $this->related->newCollection($value);
    }



#Hyperf\Database\Model\Relations\HasOne
public function match(array $models, Collection $results, $relation) {
        return $this->matchOne($models, $results, $relation);
    }

#Hyperf\Database\Model\Relations\HasMany
public function match(array $models, Collection $results, $relation)
    {
        return $this->matchMany($models, $results, $relation);
    }


#Hyperf\Database\Model\Concerns\HasRelationships
public function hasOne($related, $foreignKey = null, $localKey = null) {
        $instance = $this->newRelatedInstance($related);

        $foreignKey = $foreignKey ?: $this->getForeignKey();

        $localKey = $localKey ?: $this->getKeyName();

        return $this->newHasOne($instance->newQuery(), $this, $instance->getTable() . '.' . $foreignKey, $localKey);
    }
public function hasMany($related, $foreignKey = null, $localKey = null) {
        $instance = $this->newRelatedInstance($related);

        $foreignKey = $foreignKey ?: $this->getForeignKey();

        $localKey = $localKey ?: $this->getKeyName();

        return $this->newHasMany(
            $instance->newQuery(),
            $this,
            $instance->getTable() . '.' . $foreignKey,
            $localKey
        );
    }
protected function newHasOne(Builder $query, Model $parent, $foreignKey, $localKey) {
        return new HasOne($query, $parent, $foreignKey, $localKey);
    }
protected function newHasMany(Builder $query, Model $parent, $foreignKey, $localKey) {
        return new HasMany($query, $parent, $foreignKey, $localKey);
    }

3.1.3 调用

#Hyperf\Database\Model\Builder
protected function eagerLoadRelation(array $models, $name, Closure $constraints)
    {
        // First we will "back up" the existing where conditions on the query so we can
        // add our eager constraints. Then we will merge the wheres that were on the
        // query back to it in order that any where conditions might be specified.
        $relation = $this->getRelation($name);

        $relation->addEagerConstraints($models);

        $constraints($relation);

        // Once we have the results, we just match those back up to their parent models
        // using the relationship instance. Then we just return the finished arrays
        // of models which have been eagerly hydrated and are readied for return.
        return $relation->match(
            $relation->initRelation($models, $name),
            $relation->getEager(),
            $name
        );
    }
public function eagerLoadRelations(array $models)
    {
        foreach ($this->eagerLoad as $name => $constraints) {
            // For nested eager loads we'll skip loading them here and they will be set as an
            // eager load on the query to retrieve the relation so that they will be eager
            // loaded on that query, because that is where they get hydrated as models.
            if (strpos($name, '.') === false) {
                $models = $this->eagerLoadRelation($models, $name, $constraints);
            }
        }

        return $models;
    }
public function get($columns = ['*'])
    {
        $builder = $this->applyScopes();

        // If we actually found models we will also eager load any relationships that
        // have been specified as needing to be eager loaded, which will solve the
        // n+1 query issue for the developers to avoid running a lot of queries.
        if (count($models = $builder->getModels($columns)) > 0) {
            $models = $builder->eagerLoadRelations($models);
        }

        return $builder->getModel()->newCollection($models);
    }
#Hyperf\Database\Model\Model 
public function __get($key)
    {
        return $this->getAttribute($key);
    }

#Hyperf\Database\Model\Concerns\HasAttributes
public function getAttribute($key)
    {
        if (!$key) {
            return;
        }

        // If the attribute exists in the attribute array or has a "get" mutator we will
        // get the attribute's value. Otherwise, we will proceed as if the developers
        // are asking for a relationship's value. This covers both types of values.
        if (array_key_exists($key, $this->getAttributes())
            || $this->hasGetMutator($key)
            || $this->isClassCastable($key)) {
            return $this->getAttributeValue($key);
        }
        // Here we will determine if the model base class itself contains this given key
        // since we don't want to treat any of those methods as relationships because
        // they are all intended as helper methods and none of these are relations.
        if (method_exists(self::class, $key)) {
            return;
        }
        return $this->getRelationValue($key);
    }
public function getRelationValue($key)
    {
        // If the key already exists in the relationships array, it just means the
        // relationship has already been loaded, so we'll just return it out of
        // here because there is no need to query within the relations twice.
        if ($this->relationLoaded($key)) {
            return $this->relations[$key];
        }

        // If the "attribute" exists as a method on the model, we will just assume
        // it is a relationship and will load and return results from the query
        // and hydrate the relationship's value on the "relationships" array.
        if (method_exists($this, $key)) {
            return $this->getRelationshipFromMethod($key);
        }
    }
protected function getRelationshipFromMethod($method)
    {
        $relation = $this->{$method}();

        if (!$relation instanceof Relation) {
            if (is_null($relation)) {
                throw new LogicException(sprintf(
                    '%s::%s must return a relationship instance, but "null" was returned. Was the "return" keyword used?',
                    static::class,
                    $method
                ));
            }

            throw new LogicException(sprintf(
                '%s::%s must return a relationship instance.',
                static::class,
                $method
            ));
        }

        return tap($relation->getResults(), function ($results) use ($method) {
            $this->setRelation($method, $results);
        });
    }

#Hyperf\Database\Model\Relations\HasMany
public function getResults()
    {
        return $this->query->get();
    }
#Hyperf\Database\Model\Relations\HasOne
public function getResults() {
        return $this->query->first() ?: $this->getDefaultFor($this->parent);
    }
#Hyperf\Database\Model\Relations\BelongsToMany
public function getResults()
    {
        return $this->get();
    }
#Hyperf\Database\Model\Relations\BelongsTo
public function getResults()
    {
        return $this->query->first() ?: $this->getDefaultFor($this->parent);
    }

3.2 pivot属性名自定义

#Hyperf\Database\Model\Relations\BelongsToMany
protected $accessor = 'pivot';
public function as($accessor)
    {
        $this->accessor = $accessor;

        return $this;
    }

3.3 通过中间件顾虑

#Hyperf\Database\Model\Relations\BelongsToMany
public function wherePivot($column, $operator = null, $value = null, $boolean = 'and')
    {
        $this->pivotWheres[] = func_get_args();

        return $this->where($this->table . '.' . $column, $operator, $value, $boolean);
    }
public function wherePivotIn($column, $values, $boolean = 'and', $not = false)
    {
        $this->pivotWhereIns[] = func_get_args();

        return $this->whereIn($this->table . '.' . $column, $values, $boolean, $not);
    }
public function orWherePivot($column, $operator = null, $value = null)
    {
        return $this->wherePivot($column, $operator, $value, 'or');
    }
public function withPivotValue($column, $value = null)
    {
        if (is_array($column)) {
            foreach ($column as $name => $value) {
                $this->withPivotValue($name, $value);
            }

            return $this;
        }

        if (is_null($value)) {
            throw new InvalidArgumentException('The provided value may not be null.');
        }

        $this->pivotValues[] = compact('column', 'value');

        return $this->wherePivot($column, '=', $value);
    }
public function orWherePivotIn($column, $values)
    {
        return $this->wherePivotIn($column, $values, 'or');
    }

3.4 __get()、__call()

#Hyperf\Database\Model\Model
public function __get($key)
    {
        return $this->getAttribute($key);
    }
public function __call($method, $parameters)
    {
        if (in_array($method, ['increment', 'decrement'])) {
            return $this->{$method}(...$parameters);
        }

        return call([$this->newQuery(), $method], $parameters);
    }

三 理解

        不管关系如何,都是先查一遍主表,再执行对应sql,可以通过不同设置拼接查询sql。

        可能是使用习惯问题,平时还是不太喜欢这种比较隐晦的方式设置对应关系,可能用会手动多查几次。

        根据这种方式,在不了解框架的情况下,会增加二开的难度。

        而且设置中间表的条件也不是很自由。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/332404.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker 安装 PHP

Docker 安装 PHP 安装 PHP 镜像 方法一、docker pull php 查找 Docker Hub 上的 php 镜像: 可以通过 Sort by 查看其他版本的 php,默认是最新版本 php:latest。 此外,我们还可以用 docker search php 命令来查看可用版本: runoobrunoob:…

【51单片机】数码管的静态与动态显示(含消影)

数码管在现实生活里是非常常见的设备,例如 这些数字的显示都是数码管的应用。 目录 静态数码管:器件介绍:数码管的使用:译码器的使用:缓冲器: 实现原理:完整代码: 动态数码管&#…

python222网站实战(SpringBoot+SpringSecurity+MybatisPlus+thymeleaf+layui)-热门帖子推荐显示实现

锋哥原创的SpringbootLayui python222网站实战: python222网站实战课程视频教程(SpringBootPython爬虫实战) ( 火爆连载更新中... )_哔哩哔哩_bilibilipython222网站实战课程视频教程(SpringBootPython爬虫实战) ( 火…

WAF攻防相关知识点总结1--信息收集中的WAF触发及解决方案

什么是WAF WAF可以通过对Web应用程序的流量进行过滤和监控,识别并阻止潜在的安全威胁。WAF可以检测Web应用程序中的各种攻击,例如SQL注入、跨站点脚本攻击(XSS)、跨站请求伪造(CSRF)等,并采取相…

web前端项目-中国象棋【附源码】

中国象棋 【中国象棋】是一款历史悠久、深受人们喜爱的策略类游戏。在Web前端技术中,我们可以使用HTML、CSS和JavaScript等语言来制作一款中国象棋游戏。玩家使用棋子(帅/相/士/炮/马/车/炮/卒)在棋盘上相互对弈,将对手的“帅”棋…

python入门,函数的进阶

1.函数的多返回值 加上逗号,一个函数每次就能返回多个值 2.函数的多种参数使用形式 1.位置参数 调用函数时根据参数位置来传递参数 就是我们平时写函数时所使用的形式 注意: 传递的参数和定义的参数的顺序以及个数必须一致 2.关键字参数 通过键值…

【汉诺塔】经典递归问题(Java实现)图文并茂讲解

📚博客主页:爱敲代码的小杨. ✨专栏:《Java SE语法》 ❤️感谢大家点赞👍🏻收藏⭐评论✍🏻,您的三连就是我持续更新的动力❤️ 🙏小杨水平有限,欢迎各位大佬指点&#…

Mysql 数据库DML 数据操作语言—— 对数据库表中的数据进行更改UPDATE 和删除DELETE

更改数据UPDATE UPDATE 表名(注意这里不加TABLE) SET 字段名1值1, 字段名2值2,...[where 条件] 示例1:只修改一个字段 示例二,修改name age字段 ; update tt4 set name 丹,age18 where id5;示例三、将所…

k8s---ingress对外服务(traefik)

目录 ingress的证书访问 traefik traefik的部署方式: deamonset deployment nginx-ingress与traefix-ingress相比较 nginx-ingress-controller ui访问 deployment部署 ingress的证书访问 ingress实现https代理访问: 需要证书和密钥 创建证书 密钥 secre…

Android WorkManager入门(二)

WorkManager入门 上一篇前言创建 WorkRequest并提交 定时的任务(PeriodicWorkRequest)配合约束使用定义执行范围失败后的重试为WorkRequest打上TAG其他取消方法 传参和返回参数总结参考资料 上一篇 Android WorkManager入门(一) …

【图解数据结构】深度解析时间复杂度与空间复杂度的典型问题

🌈个人主页:聆风吟 🔥系列专栏:图解数据结构、算法模板 🔖少年有梦不应止于心动,更要付诸行动。 文章目录 一. ⛳️上期回顾二. ⛳️常见时间复杂度计算举例1️⃣实例一2️⃣实例二3️⃣实例三4️⃣实例四5…

基于R语言的NDVI的Sen-MK趋势检验

本实验拟分析艾比湖地区2010年至2020年间的NDVI数据,数据从MODIS遥感影像中提取的NDVI值,在GEE遥感云平台上将影像数据下载下来。代码如下: import ee import geemap geemap.set_proxy(port7890)# 设置全局网络代理 Map geemap.Map()# 指定…

HCIP-7

IPV6: 为什么使用IPV6: V4地址数量不够V4使用NAT,破坏了端到端原则 IPV6的优点: 全球单播地址聚合性强(IANA组织进行合理的分配)多宿主----一个接口可以配置N个地址--且这些地址为同一级别自动配置---1)…

绝地求生【违规处罚工作公示】1月8日-1月14日

1月8日至1月14日期间,共计对174,636个违规账号进行了封禁,其中164,757个账号因使用外挂被永久封禁。 若您游戏中遇到违规行为,建议您优先在游戏内进行举报; 另外您也可以在官方微信公众号【PUBG国际版】中点击“ 服务中心 - 举报…

Visual Studio 与 SQL Server 常见报错解决方案(工作向)

前言 这篇文章从今天创建开始,会一直更新下去,以后遇到常见但是比较容易解决的报错会在本文进行更新,有需要的朋友可以收藏再看 目录 Visual Studio lc.exe已退出,代码为-1无法导入以下密钥文件xxx.pfx,该密钥文件…

SG-9101CGA(汽车+125°C可编程晶体振荡器)

SG-9101CGA是用于汽车CMOS输出的可编程晶体振荡器,彩用2.5 x 2.0 (mm)封装,0.67 MHz至170 MHz频率范围、工作温度范围为-40℃~125℃,符合车规级晶振,无铅,绿色环保,满足汽车工业标准,电源电压范…

【音视频原理】图像相关概念 ② ( 帧率 | 常见帧率标准 | 码率 | 码率单位 )

文章目录 一、帧率1、帧率简介2、常见帧率标准3、帧率 刷新率 二、码率1、码率简介2、码率单位 一、帧率 1、帧率简介 帧率 Frame Rate , 帧 指的是 是 画面帧 , 帧率 是 画面帧 的 速率 ; 帧率 的 单位是 FPS , Frames Per Second , 是 每秒钟 的 画面帧 个数 ; 帧率 是 动画…

文件共享服务(一)——DAS、NAS、SAN存储类型

一、存储类型 存储类型主要有三种 1. DAS直连式存储 通常由数据线直连电脑就可以用,比如一块新硬盘,只需要利用磁盘模拟器分区,创建文件系统,挂载就可以使用了。 PC中的硬盘或只有一个外部SCSI接口的JBOD存储设备(即…

Intel杀回车载计算领域,极氪首发其第一代AI SoC

作者 |德新 编辑 |王博 Intel低调地重新杀回车载计算领域。 在两个月前,在上海举办的进博会上,Intel对外展示了基于新一代酷睿核心打造的智能座舱平台。 在此之前,这家芯片巨头任命了服役公司20多年的老将Jack Weast作为汽车业务的全球负责…

Redis三种缓存读写策略

1. Cache Aside Pattern 旁路缓存模式 1.1 读 1.2 写 1.3 为什么要先更新db再删除cache? 缓存的写入速度是比数据库的写入速度快很多,因此相比于先删除cache后更新db带来数据不一致性问题的概率更小。 1.4 特点 平时使用比较多的一个缓存读写模式同时维系db 和 cache&#…