如何在 Laravel 中去构建部门树形结构 API
@这是小豪的第八篇文章
这两天在折腾组织架构的人员选择器,很难受啊,每次遇到这种需要用到递归的计算脑袋就转不过来,不过好在还是折腾出来了,今天给大家介绍一下到底折腾出来了啥,优不优雅,哈哈。
准备
做什么?组织架构的人员选择器的 API 接口,其中组织架构的层级是无下限的,姑且当做是无限极的吧。。。无限极最近貌似有点火呀,哈哈。
我们先来看一下表结构,直接从模型中看吧:
class Department extends Model
{
use SoftDeletes;
protected $fillable = [
'parent_id', 'name', 'alias', 'level',
];
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function children()
{
return $this->hasMany(__CLASS__, 'parent_id');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function users()
{
return $this->hasMany(User::class);
}
}
模型中声明了两种模型关联,部门子集以及部门员工,如果对模型关联不太熟悉的建议看一下之前的文章,《如何更快的找到自己所需的模型关联类型?》
再来看一下控制器:
class DepartmentController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Support\Collection
*/
public function index(Request $request)
{
if ($request->has('tree')) {
return DepartmentResource::tree();
}
return new DepartmentResource(Department::where($request->all())->get());
}
}
因为树形结构不是必选项,所以在有接收到 tree 参数的时候才进行处理。
最后看一下 API 资源:
class DepartmentResource extends JsonResource
{
}
不熟悉 API 资源的,建议看一下之前的又一篇文章,哈哈。《如何优雅的去处理 API 数据格式》,我都佩服自己打广告的能力了,哈哈。
开始了噢
1. 我们先在 DepartmentResource 中定义一个静态方法 tree,并获取所有部门数据
public static function tree()
{
// 我这里默认把 users 给加上去了,大家可以根据自己的需求来决定
$departments = Department::with('users')->get();
}
2. 现在我们需要对部门所有的数据进行处理了,这是最头疼的。。。 我们来看一下小豪是如何处理的
/**
* @param \Illuminate\Support\Collection $departments
* @param \Illuminate\Support\Collection $parents
*
* @return \Illuminate\Support\Collection
*/
protected static function departmentsTree(Collection $departments, Collection $parents)
{
$departments->map(function ($department, $key) use ($departments, $parents) {
$department->children = \collect([]);
if (empty($department->parent_id)) {
$parents->push($department);
$departments->forget($key);
}
$parents->map(function ($parentDepartment, $parentKey) use ($key, $department, $departments, $parents) {
if ($department->parent_id == $parentDepartment->id) {
$parents->get($parentKey)->children->push($department);
$departments->forget($key);
}
});
});
$parents->map(function ($parentDepartment, $key) use ($departments, $parents) {
if ($parentDepartment->children->isNotEmpty()) {
$parents->get($key)->children = self::departmentTree($departments, $parentDepartment->children);
}
});
return $parents;
}
因为
Department::with('users')->get()获取的是一个集合,所以我们都是用集合的方式进行处理的。
-
我们接收了两个参数:
$departments和$parents,第一个参数就不多说了,第二个参数用来装最外层的部门,也就是一级部门(父级 id 为 0 的部门)。 -
我们先对所有部门进行遍历,给每一个部门初始化一个
children空集合,然后将一级部门装进$parents中,并从$departments剔除已经使用过的department。 -
对
$parents进行遍历,为department找到指定父级,并push到children集合中,同样剔除已经使用过的department。 -
上面的操作只进行了两层,现在到了关键点了,我们对已经经过一层筛选的
$parents进行遍历,将那些存在children的挑出来,然后继续进行上面的操作,同时第二个参数为$parentDepartment->children,反复处理之后,就能得到最终的结果啦。
不知道说清楚没有,哈哈。
3. 现在来调用一下:
/**
* @return \Illuminate\Support\Collection
*/
public static function tree()
{
$departments = Department::with(\request()->includes())->get();
return self::departmentTree($departments, \collect([]));
}
4. 大公告成,哈哈。
不过还没完,我们来看一下基于模型的树状结构该怎么去写,你想不到的优雅,哈哈。
优雅的方式开始
我们在模型中添加这样一个方法:
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function subDepartments()
{
return $this->children()->with('subDepartments');
}
Api 资源中处理:
class DepartmentResource extends JsonResource
{
public function __construct(\Illuminate\Database\Eloquent\Model $resource)
{
parent::__construct($resource);
return $resource->subDepartment;
}
}
调用:
class DepartmentController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Support\Collection
*/
public function index(Request $request)
{
return DepartmentResource::collection(Department::where($request->all())->get());
}
}
这样就 ok 了,是不是大开眼界,反正我是的,哈哈。不过这做有个弊病就是在不指定部门的时候,所有的部门全部展示出来了,不过都可以灵活的运用啦。
结束语
写的很简陋,大家如果看出可以优化的,或者有错误的地方,指正一下哈,共同进步呀,哈哈。
本作品采用《CC 协议》,转载必须注明作者和本文链接
关于 LearnKu
这个好 优雅 很laravel
$department->children = \collect([]);这种写法第一次见啊,\collect([])是啥意思啊@mamahaha 哈哈,就是定义一个空集合
@jake666 哈哈,是吧,刚开始看到这种处理方式的时候,我觉得真的是厉害。
好高端啊,
$departments->map()这个能解释下吗@Mr_White_DT 这是集合的方法呢,集合《Laravel 5.7 中文文档》
@Lhao 多谢,我去研究下,还没用过集合
@Lhao 多谢,我去研究下,还没用过集合
为何不直接获取一级(顺便带上是否有下级的标识), 然后点开的时候再加载下级呢
laravel集合 还是挺不错的
之前写过一个扩展:https://packagist.org/packages/betterde/tr...
部门、菜单这种需要有树形结构的都可以!
Model 里写个递归就好了
public function child($parent_id){
return User::where('parent_id', $parent_id)->get()->each(function($v){
$v->children = $this->children($v->id);
})
}
事实上是应该拆分成两个方法的,调用一个,递归自己是一个,这只是个示例 :grinning:
Department
获取
Department::topLevel()->with(['users','childrenTree'])->all();获取层级嵌套关系(无限极)
或者
Department::topLevel()->with(['users'])->all();获取顶层数据
果然很哈哈