前段時間在學laravel admin,做人員外出管理後臺demo的時候遇到一個坑,就是需要在資料儲存之前,對錶單提交的部分資料進行處理。
場景如下:
人員申請休假,建立休假記錄時,用結束休假時間減去開始休假時間,得出花費的時間,然後用這個時間和members表的剩餘休假時間進行比較,如果剩餘假期不足則無法休假,如果足夠,則更新members表的剩餘假期。
起初,我是想直接前端模板裡面加members.remaing_holiday
的隱藏域,提交的時候計算賦值,不過這樣可能不安全,我改為了後端處理。
根據文件,使用$form->saving()
回撥處理資料,對$form的remaing_holiday巴拉巴拉一頓操作,這時候就出問題了,雖然我已經修改了欄位的值,但是資料庫儲存的卻沒改過來,後來我直接在saving裡面手動儲存,卻儲存成功了。
花了一些時間後,用xdebug找到了框架執行儲存操作的程式碼。
檔案位於vendor/encore/laravel-admin/src/Form.php
public function store()
{
// 獲取請求的表單資料
$data = \request()->all();
// Handle validation errors. 驗證是否符合rules
if ($validationMessages = $this->validationMessages($data)) {
return $this->responseValidationError($validationMessages);
}
// 重點就是這裡,進到$this->prepare
if (($response = $this->prepare($data)) instanceof Response) {
return $response;
}
// 開啟事務,當時還有一個疑惑此時也得到答案:如果儲存失敗會回滾,但是自增id沒有變回去
DB::transaction(function () {
// 插入的預處理,傳進去的是$this->updates,也就是說要修改updates,那麼如何改他呢,再繼續找
$inserts = $this->prepareInsert($this->updates);
foreach ($inserts as $column => $value) {
$this->model->setAttribute($column, $value);
}
// 儲存
$this->model->save();
// 更新關聯模型
$this->updateRelation($this->relations);
});
if (($response = $this->callSaved()) instanceof Response) {
return $response;
}
if ($response = $this->ajaxResponse(trans('admin.save_succeeded'))) {
return $response;
}
return $this->redirectAfterStore();
}
接下來看$this->prepare()
protected function prepare($data = [])
{
// 觸發submitted hook
if (($response = $this->callSubmitted()) instanceof Response) {
return $response;
}
// $data移除忽略欄位後和舊的$this->inputs合併,再賦值給$this->inputs
$this->inputs = array_merge($this->removeIgnoredFields($data), $this->inputs);
// 觸發saving hook
if (($response = $this->callSaving()) instanceof Response) {
return $response;
}
// 獲取$this->inputs的關聯關係
$this->relations = $this->getRelationInputs($this->inputs);
// 這裡排除掉inputs裡面的relations再賦值給updates(更新欄位的陣列)
$this->updates = Arr::except($this->inputs, array_keys($this->relations));
}
看到這裡,也就知道之前改$form->xxx為什麼不生效了,$data是在store()執行開始就獲取了\request()->all(),而saving則是在prepare()裡面,$this->input賦值之後才執行的,此時改$form->xxx沒有用,要改$form->inputs才有用。
那麼換一個方向想,save儲存的是$this->updates處理後的資料,而updates又是Arr::except($this->inputs, array_keys($this->relations));
之後的資料,那麼我們要改updates就要改inputs。
可以在saving裡面修改inputs嗎?不行,$inputs是protected。
原先我是換一個辦法,既然儲存前處理不了,我就儲存後再處理。用saved回撥,獲取到儲存後的model然後update或者save。
不過這個辦法很low,最近在學dcat,正好今天在dcat群看到有群友遇到了類似的問題,於是交流了一下,發現一個更好的辦法,既然是繼承關係,其實也可以在子類重寫store方法,這樣就方便修改了。
但是這樣就行了嗎?如果有很多地方都要重寫,感覺很麻煩,有沒有更好的辦法?
我又去社群搜了搜相關的帖子,恰好看到了dcat作者寫的Dcat Admin 教程 - 如何優雅地更改表單值的資料型別?
根據文章,在dcat中可以這樣寫
$form->text('title')->saving(function ($value) {
// 強制轉化為string型別
return (string) $value;
});
更加簡潔的寫法是:利用 Laravel 的 macro 功能來擴充套件這個功能。
開啟app/Admin/bootstrap.php
,寫入以下程式碼
// 擴充套件表單欄位方法
// 儲存為字串
Form\Field::macro('saveAsString', function () {
return $this->saving(function ($v) {
return (string) $v;
});
});
然後就可以直接在表單使用$form->text('title')->saveAsString();
非常方便,nice
在dcat嘗試之後,我想laravel admin能不能也這麼用,於是試了一下,可惜沒有這個巨集指令。
BadMethodCallException In Macroable.php line 103 :
Method Encore\Admin\Form\Field\Number::saving does not exist.
於是我又比較了一下二者的原始碼,dcat的裡面有這個saving方法,並且prepare也多了一些東東
檔案位於:vendor/encore/laravel-admin/src/Form/Field.php
public function saving(\Closure $closure)
{
$this->savingCallbacks[] = $closure;
return $this;
}
final public function prepare($value)
{
$value = $this->prepareInputValue($value);
if ($this->savingCallbacks) {
foreach ($this->savingCallbacks as $callback) {
$value = $callback->call($this->data(), $value);
}
}
return $value;
}
看來就是這裡呼叫了回撥吧,既然二者都差不多的,感覺laravel admin應該也可以做到,可能是因為我比較懶,我就沒繼續深入瞭解了,以後有機會再繼續吧。
最後總結一下:
- 解決一個問題有多種方法,思路開啟,說不定就會有更好的辦法
- 框架只是工具,怎麼方便,怎麼合適就怎麼來
- 多交流想法,同樣的問題,其他人很可能也遇到過,看看別人是如何解決的
- 社群裡大神很多,要多多滴學習大佬們的經驗
- 要善用各種除錯工具,比如xdebug,\Log::info等等
- 遇到網上搜不到的問題時,可以試著看原始碼或者請教大佬,有些坑還是要看原始碼才能解決,文件不可能面面俱到。
- 舊的疑問,過段時間再看,說不定就有了答案。每一次看看過去遇到的問題,都可能有新的收穫,新的見解。
我之前在b站學了前端,php,laravel。然後學laravel的框架,由於之前都是看視訊教程在學,突然看文件其實還是遇到了不少困難,好在社群裡面有很多網友的答疑解惑,感謝社群提供了一個這麼好的學習交流的地方。
本作品採用《CC 協議》,轉載必須註明作者和本文連結