如何在 Laravel 中去構建部門樹形結構 API

finecho發表於2019-01-25

@這是小豪的第八篇文章

這兩天在折騰組織架構的人員選擇器,很難受啊,每次遇到這種需要用到遞迴的計算腦袋就轉不過來,不過好在還是折騰出來了,今天給大家介紹一下到底折騰出來了啥,優不優雅,哈哈。

準備

做什麼?組織架構的人員選擇器的 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 找到指定父級,並 pushchildren 集合中,同樣剔除已經使用過的 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 協議》,轉載必須註明作者和本文連結
finecho # Lhao

相關文章