如何在 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 协议》,转载必须注明作者和本文链接
推荐文章: