hyperf 二十二 数据库 模型关系

教程:Hyperf

一 预加载

1.1 原理

通过设置 Hyperf\Database\Model\Builder::eagerLoad加载需查询用的model, 查询条件子查询使用in。

eagerLoad在Builder::eagerLoadRelations()被调用,传入Builder::eagerLoadRelation()。eagerLoadRelation()中调用addEagerConstraints()构造查询。

1.2 测试

#测试
$log = User::query()->getConnection()->enableQueryLog();
$info = User::query()->with('role')->find(1);
$log = User::query()->getConnection()->getQueryLog();
var_dump($info, $log);


#测试结果
object(App1\Model\User){
……
}
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(106.81)
  }
  [1]=>
  array(3) {
    ["query"]=>
    string(241) "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`.`role_id` = ? and `role_user`.`user_id` in (1)"
    ["bindings"]=>
    array(1) {
      [0]=>
      int(1)
    }
    ["time"]=>
    float(20.69)
  }
}

1.3 源码

#Hyperf\Database\Model\Model
public static function with($relations) {
        return static::query()->with(is_string($relations) ? func_get_args() : $relations);
    }
public function newCollection(array $models = []) {
        return new Collection($models);
    }


#Hyperf\Database\Model\Builder
public function with($relations) {
        $eagerLoad = $this->parseWithRelations(is_string($relations) ? func_get_args() : $relations);
        $this->eagerLoad = array_merge($this->eagerLoad, $eagerLoad);
        return $this;
    }


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);
    }
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;
    }
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 find($id, $columns = ['*']) {
        if (is_array($id) || $id instanceof Arrayable) {
            return $this->findMany($id, $columns);
        }

        return $this->whereKey($id)->first($columns);
    }
public function findMany($ids, $columns = ['*']) {
        if (empty($ids)) {
            return $this->model->newCollection();
        }

        return $this->whereKey($ids)->get($columns);
    }


#Hyperf\Database\Model\Relations\BelongsToMany
public function addEagerConstraints(array $models)
    {
        $whereIn = $this->whereInMethod($this->parent, $this->parentKey);

        $this->query->{$whereIn}(
            $this->getQualifiedForeignPivotKeyName(),
            $this->getKeys($models, $this->parentKey)
        );
    }


#Hyperf\Database\Model\Relations\HasOneOrMany
public function addEagerConstraints(array $models) {
        $whereIn = $this->whereInMethod($this->parent, $this->localKey);

        $this->query->{$whereIn}(
            $this->foreignKey,
            $this->getKeys($models, $this->localKey)
        );
    }

二 多态关联

2.1 数据库

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;

CREATE TABLE `articles` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `title` varchar(255) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  `deleted_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

CREATE TABLE `photo` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `img_url` varchar(255) DEFAULT NULL,
  `ref_id` int(11) DEFAULT NULL COMMENT '关联id',
  `ref_type` tinyint(1) DEFAULT NULL COMMENT '关联类型 1用户 2文章',
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

 用户和图片一对多关系,文章和图片一对一关系。

2.2 测试

#model
#User
public function photo() {
        return $this->morphMany(Photo::class, 'ref');
}

#Article
public function author() {
        return $this->belongsTo(User::class, 'user_id', 'id');
    }
public function photo() {
        return $this->morphOne(Photo::class, 'ref');
}

#Photo
public function ref() {
        return $this->morphTo('ref');
}




#listener
class MorphMapRelationListener implements ListenerInterface {
    public function listen(): array {
        return [
            BootApplication::class,
        ];
    }

    public function process(object $event) {
        Relation::morphMap([
            '1' => User::class,
            '2' => Article::class,
        ]);
    }
}





#config\autoload\listeners.php
return [
    "App\Listener\MorphMapRelationListener",
];

 一对多

#测试
$obj2 = User::query()->find(1);
$list = $obj2->photo->all();
foreach ($list as $key => $value) {
     var_dump($value->toArray());
}

#测试结果
array(4) {
  ["id"]=>
  int(1)
  ["img_url"]=>
  string(143) "https://gitee.com/lsswear/plane_design/raw/master/%E4%B8%AD%E5%9B%BD%E5%A4%8D%E5%8F%A4%E5%9B%BE%E6%A1%88/001O1JA0ly1hl17zd4l2qj60u01hcazo02.jpg"
  ["ref_id"]=>
  int(1)
  ["ref_type"]=>
  int(1)
}
array(4) {
  ["id"]=>
  int(3)
  ["img_url"]=>
  string(141) "https://gitee.com/lsswear/plane_design/raw/master/%E4%B8%AD%E5%9B%BD%E5%A4%8D%E5%8F%A4%E5%9B%BE%E6%A1%88/e9fbfa79gy1hku6j75abvj20j60ic76s.jpg"
  ["ref_id"]=>
  int(1)
  ["ref_type"]=>
  int(1)
}

 一对一

#测试
$log = Article::query()->getConnection()->enableQueryLog();
$obj1 = Article::query()->find(1);
$info = $obj1->photo->toArray();
var_dump($info);
$log = Article::query()->getConnection()->getQueryLog();
var_dump($log);

#测试结果
array(4) {
  ["id"]=>
  int(2)
  ["img_url"]=>
  string(143) "https://gitee.com/lsswear/plane_design/raw/master/%E4%B8%AD%E5%9B%BD%E5%A4%8D%E5%8F%A4%E5%9B%BE%E6%A1%88/001O1JA0ly1hlnbt7gu7nj60u00u0wwg02.jpg"
  ["ref_id"]=>
  int(1)
  ["ref_type"]=>
  int(2)
}
array(2) {
  [0]=>
  array(3) {
    ["query"]=>
    string(94) "select * from `articles` where `articles`.`id` = ? and `articles`.`deleted_at` is null limit 1"
    ["bindings"]=>
    array(1) {
      [0]=>
      int(1)
    }
    ["time"]=>
    float(63.42)
  }
  [1]=>
  array(3) {
    ["query"]=>
    string(116) "select * from `photo` where `photo`.`ref_id` = ? and `photo`.`ref_id` is not null and `photo`.`ref_type` = ? limit 1"
    ["bindings"]=>
    array(2) {
      [0]=>
      int(1)
      [1]=>
      int(2)
    }
    ["time"]=>
    float(1.76)
  }
}

嵌套关联

#测试
$log = Photo::query()->getConnection()->enableQueryLog();
$photo = Photo::query()->with([
   'ref' => function (MorphTo $morphTo) {
       $morphTo->morphWith([
            Article::class => ["author"],
       ]);
    },
])->get();
$log = Photo::query()->getConnection()->getQueryLog();
var_dump($photo->toArray(), $log);


#测试结果
array(3) {
  [0]=>
  array(5) {
    ["id"]=>
    int(1)
    ["img_url"]=>
    string(143) "https://gitee.com/lsswear/plane_design/raw/master/%E4%B8%AD%E5%9B%BD%E5%A4%8D%E5%8F%A4%E5%9B%BE%E6%A1%88/001O1JA0ly1hl17zd4l2qj60u01hcazo02.jpg"
    ["ref_id"]=>
    int(1)
    ["ref_type"]=>
    int(1)
    ["ref"]=>
    array(4) {
      ["id"]=>
      int(1)
      ["name"]=>
      string(3) "123"
      ["age"]=>
      int(22)
      ["deleted_at"]=>
      NULL
    }
  }
  [1]=>
  array(5) {
    ["id"]=>
    int(2)
    ["img_url"]=>
    string(143) "https://gitee.com/lsswear/plane_design/raw/master/%E4%B8%AD%E5%9B%BD%E5%A4%8D%E5%8F%A4%E5%9B%BE%E6%A1%88/001O1JA0ly1hlnbt7gu7nj60u00u0wwg02.jpg"
    ["ref_id"]=>
    int(1)
    ["ref_type"]=>
    int(2)
    ["ref"]=>
    array(7) {
      ["id"]=>
      int(1)
      ["user_id"]=>
      int(1)
      ["title"]=>
      string(5) "test1"
      ["created_at"]=>
      string(19) "2024-01-13 10:05:51"
      ["updated_at"]=>
      string(19) "2024-01-13 10:05:53"
      ["deleted_at"]=>
      NULL
      ["author"]=>
      array(4) {
        ["id"]=>
        int(1)
        ["name"]=>
        string(3) "123"
        ["age"]=>
        int(22)
        ["deleted_at"]=>
        NULL
      }
    }
  }
  [2]=>
  array(5) {
    ["id"]=>
    int(3)
    ["img_url"]=>
    string(141) "https://gitee.com/lsswear/plane_design/raw/master/%E4%B8%AD%E5%9B%BD%E5%A4%8D%E5%8F%A4%E5%9B%BE%E6%A1%88/e9fbfa79gy1hku6j75abvj20j60ic76s.jpg"
    ["ref_id"]=>
    int(1)
    ["ref_type"]=>
    int(1)
    ["ref"]=>
    array(4) {
      ["id"]=>
      int(1)
      ["name"]=>
      string(3) "123"
      ["age"]=>
      int(22)
      ["deleted_at"]=>
      NULL
    }
  }
}
array(4) {
  [0]=>
  array(3) {
    ["query"]=>
    string(21) "select * from `photo`"
    ["bindings"]=>
    array(0) {
    }
    ["time"]=>
    float(65.45)
  }
  [1]=>
  array(3) {
    ["query"]=>
    string(89) "select * from `userinfo` where `userinfo`.`id` in (1) and `userinfo`.`deleted_at` is null"
    ["bindings"]=>
    array(0) {
    }
    ["time"]=>
    float(1.68)
  }
  [2]=>
  array(3) {
    ["query"]=>
    string(89) "select * from `articles` where `articles`.`id` in (1) and `articles`.`deleted_at` is null"
    ["bindings"]=>
    array(0) {
    }
    ["time"]=>
    float(2.13)
  }
  [3]=>
  array(3) {
    ["query"]=>
    string(89) "select * from `userinfo` where `userinfo`.`id` in (1) and `userinfo`.`deleted_at` is null"
    ["bindings"]=>
    array(0) {
    }
    ["time"]=>
    float(1.33)
  }
}
#测试
$list = Photo::query()->whereHasMorph(
            'ref',
            [
                User::class,
                Article::class,
            ],
            function (Builder $query) {
                $query->where('ref_id', 1);
            }
        )->get();
foreach ($list as $key => $value) {
      $item = $value->toArray();
      var_dump($item);
}

#测试结果
array(4) {
  ["id"]=>
  int(1)
  ["img_url"]=>
  string(143) "https://gitee.com/lsswear/plane_design/raw/master/%E4%B8%AD%E5%9B%BD%E5%A4%8D%E5%8F%A4%E5%9B%BE%E6%A1%88/001O1JA0ly1hl17zd4l2qj60u01hcazo02.jpg"
  ["ref_id"]=>
  int(1)
  ["ref_type"]=>
  int(1)
}
array(4) {
  ["id"]=>
  int(3)
  ["img_url"]=>
  string(141) "https://gitee.com/lsswear/plane_design/raw/master/%E4%B8%AD%E5%9B%BD%E5%A4%8D%E5%8F%A4%E5%9B%BE%E6%A1%88/e9fbfa79gy1hku6j75abvj20j60ic76s.jpg"
  ["ref_id"]=>
  int(1)
  ["ref_type"]=>
  int(1)
}

根据测试内容和源码,model设置morphMany()、morphOne()都使用Hyperf\Database\Model\Relations\HasOneOrMany::matchOneOrMany()方法。两者参数,第一个参数为有对应关系的model,第二个参数有对应id和对应键的前缀,但是如果对应id或对应键不为“前缀_id”、“前缀_type”格式,可以将id设置为第三个参数,type设置为第四个参数吗,第五个参数为被调用model的对应键。

例如:

#mysql
photo 
    refid
    reftype
user
    id1
article
    id2

#model
#User
public function photo() {
    return $this->morphMany(Photo::class,null,'refid','reftype','id1');
}
#Article
public function photo() {
    return $this->morphMany(Photo::class,null,'refid','reftype','id2');
}
#photo
public function ref() {
        return $this->morphTo(null,'refid','reftype');
}

一对多时返回集合对象,需要用all()等方法再获取数据,之后可以用list。一对一直接返回model对象,再调用all()等,是针对这个返回model的操作。

比如,根据上面的例子,$obj1 = Article::query()->find(1)->photo->all(),返回photo表的全部数据。

作为区分多态的字段type,字段名可自定义,字段值系统默认为类名,不方便使用,可以设置监听做对应关系。Relation::morphMap()参数中键名为对应关系的值,键值为类名。listen()方法设置执行process()的类。

2.3 原理

参考:

hyperf 二十一 数据库 模型关系-CSDN博客

hyperf console 执行-CSDN博客

和模型关系实现的原理差不多都是使用__get()查询,通过match()执行查询。

有点区别是中间件的设置,中间件通过ProviderConfig::load();加载配置。ListenerProviderFactory::register()执行监听。

根据上述例子中监听设置为Relation::morphMap(),返回静态static::$morphMap()。Relation::morphMap()传入数组为设置,无参数为获取。其中使用array_search()通过传入的类名,获取对应的键名并返回。addConstraints()方法调用返回的键名构造sql。

whereHasMorph()使用Hyperf\Database\Model\Concerns\HasRelationships::belongsTo()实现。

2.4 源码

2.4.1 监听执行

#Hyperf\Framework\ApplicationFactory
class ApplicationFactory
{
    public function __invoke(ContainerInterface $container)
    {
        if ($container->has(EventDispatcherInterface::class)) {
            $eventDispatcher = $container->get(EventDispatcherInterface::class);
            $eventDispatcher->dispatch(new BootApplication());
        }
        ……
        $application = new Application();

        if (isset($eventDispatcher) && class_exists(SymfonyEventDispatcher::class)) {
            $application->setDispatcher(new SymfonyEventDispatcher($eventDispatcher));
        }

        foreach ($commands as $command) {
            $application->add($container->get($command));
        }
        return $application;
    }
}

#Hyperf\Event\EventDispatcherFactory
class EventDispatcherFactory
{
    public function __invoke(ContainerInterface $container)
    {
        $listeners = $container->get(ListenerProviderInterface::class);
        $stdoutLogger = $container->get(StdoutLoggerInterface::class);
        return new EventDispatcher($listeners, $stdoutLogger);
    }
}

#Hyperf\Event\ConfigProvider
class ConfigProvider
{
    public function __invoke(): array
    {
        return [
            'dependencies' => [
                ListenerProviderInterface::class => ListenerProviderFactory::class,
                EventDispatcherInterface::class => EventDispatcherFactory::class,
            ],
            'annotations' => [
                'scan' => [
                    'paths' => [
                        __DIR__,
                    ],
                ],
            ],
        ];
    }
}

#Hyperf\Event\ListenerProviderFactory
public function __invoke(ContainerInterface $container)
    {
        $listenerProvider = new ListenerProvider();

        // Register config listeners.
        $this->registerConfig($listenerProvider, $container);

        // Register annotation listeners.
        $this->registerAnnotations($listenerProvider, $container);

        return $listenerProvider;
    }
private function registerAnnotations(ListenerProvider $provider, ContainerInterface $container): void
    {
        foreach (AnnotationCollector::list() as $className => $values) {
            /** @var Listener $annotation */
            if ($annotation = $values['_c'][Listener::class] ?? null) {
                $this->register($provider, $container, $className, (int) $annotation->priority);
            }
        }
    }
private function register(ListenerProvider $provider, ContainerInterface $container, string $listener, int $priority = 1): void
    {
        $instance = $container->get($listener);
        if ($instance instanceof ListenerInterface) {
            foreach ($instance->listen() as $event) {
                $provider->on($event, [$instance, 'process'], $priority);
            }
        }
    }

2.4.2 自定义多态映射

#Hyperf\Database\Model\Relations\Relation
public static function morphMap(array $map = null, $merge = true) {
        $map = static::buildMorphMapFromModels($map);

        if (is_array($map)) {
            static::$morphMap = $merge && static::$morphMap
            ? $map+static::$morphMap : $map;
        }

        return static::$morphMap;
    }
#Hyperf\Database\Model\Concerns\HasRelationships
public function getMorphClass() {
        $morphMap = Relation::morphMap();

        if (!empty($morphMap) && in_array(static::class, $morphMap)) {
            return array_search(static::class, $morphMap, true);
        }

        return static::class;
    }

#Hyperf\Database\Model\Relations\MorphOneOrMany
public function __construct(Builder $query, Model $parent, $type, $id, $localKey)
    {
        $this->morphType = $type;

        $this->morphClass = $parent->getMorphClass();

        parent::__construct($query, $parent, $id, $localKey);
    }
public function addConstraints()
    {
        if (Constraint::isConstraint()) {
            parent::addConstraints();

            $this->query->where($this->morphType, $this->morphClass);
        }
    }

2.4.3 多态关联查询

#Hyperf\Database\Model\Concerns\QueriesRelationships
public function whereHasMorph($relation, $types, Closure $callback = null, $operator = '>=', $count = 1)
    {
        return $this->hasMorph($relation, $types, $operator, $count, 'and', $callback);
    }
public function hasMorph($relation, $types, $operator = '>=', $count = 1, $boolean = 'and', Closure $callback = null)
    {
        $relation = $this->getRelationWithoutConstraints($relation);

        $types = (array) $types;

        if ($types === ['*']) {
            $types = $this->model->newModelQuery()->distinct()->pluck($relation->getMorphType())->filter()->all();

            foreach ($types as &$type) {
                $type = Relation::getMorphedModel($type) ?? $type;
            }
        }

        return $this->where(function ($query) use ($relation, $callback, $operator, $count, $types) {
            foreach ($types as $type) {
                $query->orWhere(function ($query) use ($relation, $callback, $operator, $count, $type) {
                    $belongsTo = $this->getBelongsToRelation($relation, $type);

                    if ($callback) {
                        $callback = function ($query) use ($callback, $type) {
                            return $callback($query, $type);
                        };
                    }

                    $query->where($relation->getMorphType(), '=', (new $type())->getMorphClass())
                        ->whereHas($belongsTo, $callback, $operator, $count);
                });
            }
        }, null, null, $boolean);
    }
protected function getBelongsToRelation(MorphTo $relation, $type)
    {
        $belongsTo = Relation::noConstraints(function () use ($relation, $type) {
            return $this->model->belongsTo(
                $type,
                $relation->getForeignKeyName(),
                $relation->getOwnerKeyName()
            );
        });

        $belongsTo->getQuery()->mergeConstraintsFrom($relation->getQuery());

        return $belongsTo;
    }

你可能感兴趣的:(php,hyperf,php)