我的个人博客:逐步前行STEP
项目中有一个商品表(production) ,有一个库存表(repertory),两者一对一关系,production有发布字段(release),需求是在repertory的grid中,有一个switch开关,用于发布production。
如果直接使用
$grid->column('production.release')->switch($states);
该列只会显示一个复选框,样式崩了。
经过调试,发现:
vendor/encore/laravel-admin/src/Grid/Displayers/SwitchDisplay.php中的
public function display($states = [])
{
$this->updateStates($states);
$name = $this->column->getName();
$class = "grid-switch-{$name}";
//这里直接拼接的类名中会有dot导致样式失效
.........
}
所以将列名改成release:
$grip->column('release')->switch($states);
然后在repertory模型中设置该值:
protected $appends = ['release'];
......
public function getReleaseAttribute()
{
......
return $release;
}
此时grid中的release开关能正确显示,但是无法正确执行操作,因为repertory模型中没有release字段,需要在更新的时候将release改成关联production的release:。
查看vendor/encore/laravel-admin/src/Form.php中的update函数中获取数据的代码如下:
$data = ($data) ?: Input::all();
说明update函数直接使用request的数据,那么可以重写update函数,修改request的数据之后再传入:
public function update()
{
if(!is_null(request()->get('release'))){
$status = request()->get('release')==='on'?1:2;
//获取到switch开关传的值了,需要将值设置到一个新的数据上
}
$data = request()->all();
$id_arr = request()->route()->parameters();
return $this->form()->update($id_arr['repertory'],$data);
}
要设置一个新的数据作为关联关系去更新,应该有什么样的格式呢?带着这个疑问,先来走一下update的流程:
public function update($id, $data = null)
{
//在这里获取到了数据
$data = ($data) ?: Input::all();
//是否可编辑
$isEditable = $this->isEditable($data);
//处理可编辑的数据
$data = $this->handleEditable($data);
//这里不太清楚,大概是处理文件删除,没验证过
$data = $this->handleFileDelete($data);
//如果是排序操作的话,执行到这,获取排序后的数据就结束了
if ($this->handleOrderable($id, $data)) {
return response([
'status' => true,
'message' => trans('admin.update_succeeded'),
]);
}
/* @var Model $this->model */
//获取当前模型
$this->model = $this->model->with($this->getRelations())->findOrFail($id);
//设置字段原始值
$this->setFieldOriginalValue();
// Handle validation errors.
//处理表单验证
if ($validationMessages = $this->validationMessages($data)) {
if (!$isEditable) {
return back()->withInput()->withErrors($validationMessages);
} else {
return response()->json(['errors' => array_dot($validationMessages->getMessages())], 422);
}
}
//预处理,得到$this->updates和$this->relations
if (($response = $this->prepare($data)) instanceof Response) {
return $response;
}
DB::transaction(function () {
//预更新,更新该模型字段
$updates = $this->prepareUpdate($this->updates);
foreach ($updates as $column => $value) {
/* @var Model $this->model */
$this->model->setAttribute($column, $value);
}
$this->model->save();
//更新关联模型字段
$this->updateRelation($this->relations);
});
if (($result = $this->complete($this->saved)) instanceof Response) {
return $result;
}
if ($response = $this->ajaxResponse(trans('admin.update_succeeded'))) {
return $response;
}
return $this->redirectAfterUpdate();
}
进入$this->prepare($data):
/**
* Prepare input data for insert or update.
*
* @param array $data
*
* @return mixed
*/
protected function prepare($data = [])
{
if (($response = $this->callSubmitted()) instanceof Response) {
return $response;
}
$this->inputs = $this->removeIgnoredFields($data);
if (($response = $this->callSaving()) instanceof Response) {
return $response;
}
//在这里得到关联关系
$this->relations = $this->getRelationInputs($this->inputs);
//在这里得到去除关联关系的本模型数据
$this->updates = array_except($this->inputs, array_keys($this->relations));
}
进入$this->getRelationInputs:
/**
* Get inputs for relations.
*
* @param array $inputs
*
* @return array
*/
protected function getRelationInputs($inputs = [])
{
$relations = [];
foreach ($inputs as $column => $value) {
//判断函数是否存在
if (method_exists($this->model, $column)) {
//说明正确的关联关系数据是一个数组并且key为关联关系函数名
$relation = call_user_func([$this->model, $column]);
if ($relation instanceof Relation) {
//在此设置关联关系的字段和值,
$relations[$column] = $value;
}
}
}
return $relations;
}
在上一个步骤找出了关联关系数据的大略格式:以关联函数为键的数组。
再回到update函数,进入$this->updateRelation($this->relations):
protected function updateRelation($relationsData)
{
foreach ($relationsData as $name => $values) {
;
if (!method_exists($this->model, $name)) {
continue;
}
$relation = $this->model->$name();
$oneToOneRelation = $relation instanceof \Illuminate\Database\Eloquent\Relations\HasOne
|| $relation instanceof \Illuminate\Database\Eloquent\Relations\MorphOne;
// echo "start\r\n";
//上面只检测了$name,如果关联关系数据的键为关联函数名的话,可以顺利到这一步,而且因为当前要操作的数据的关联关系是1对1,所有$oneToOneRelation为true
$prepared = $this->prepareUpdate([$name => $values], $oneToOneRelation);
if (empty($prepared)) {
continue;
}
switch (get_class($relation)) {
..........................
}
................
}
进入:$prepared = $this->prepareUpdate([$name => $values], $oneToOneRelation);:
protected function prepareUpdate(array $updates, $oneToOneRelation = false)
{
$prepared = [];
// print_r($updates);
foreach ($this->builder->fields() as $field) {
$columns = $field->column();
//获取该模型对应的form的所有列名,我们关注的release在form中应为repertory.release
// If column not in input array data, then continue.
//这个函数很重要,决定了流程是否可以往下走,进去看一看
if (!array_has($updates, $columns)) {
continue;
}
........
}
进入:array_has($updates, $columns):
/**
* Check if an item or items exist in an array using "dot" notation.
*
* @param \ArrayAccess|array $array
* @param string|array $keys
* @return bool
*/
function array_has($array, $keys)
{
return Arr::has($array, $keys);
}
看注释!!!,该函数用于检查第二个参数根据dot处理成数组后,是否存在于第一个参数的数组中,由于在form中该字段是repertory.release,即$columns等于repertory.release,要使程序往下运行,则$updates必为['repertory'=>['release'=>?]],到此,可以确定关联关系数据必是
['repertory'=>['release'=>1]]
于是,在控制器中重写的update中重写一个关联关系数据如下:
public function update()
{
$release = request()->get('release');
if(!is_null($release)){
$status = $release === 'on' ? 1 : 2;
//添加一个request字段
request()->offsetSet('repertory',['release'=>$release]);
}
$data = request()->all();
$id_arr = request()->route()->parameters();
return $this->form()->update($id_arr['repertory'],$data);
}
这样就修改了request提交的数据,现在request中有一个数组:
['repertory'=>['release'=>1]]
去除调试打印,在表格中操作开关,结果正确。