initModel($modelOrRelations); } /** * 初始化模型. * * @param EloquentModel|Builder|array|string $modelOrRelations */ protected function initModel($modelOrRelations) { if (is_string($modelOrRelations) && class_exists($modelOrRelations)) { $this->eloquentClass = $modelOrRelations; } elseif ($modelOrRelations instanceof EloquentModel) { $this->eloquentClass = get_class($modelOrRelations); $this->model = $modelOrRelations; } elseif ($modelOrRelations instanceof Builder) { $this->model = $modelOrRelations->getModel(); $this->eloquentClass = get_class($this->model); $this->queryBuilder = $modelOrRelations; } else { $this->setRelations($modelOrRelations); } $this->setKeyName($this->model()->getKeyName()); $traits = class_uses($this->model()); $this->setIsSoftDeletes( in_array(SoftDeletes::class, $traits, true) || in_array(DcatSoftDeletes::class, $traits, true) ); } /** * @return string */ public function getCreatedAtColumn() { return $this->model()->getCreatedAtColumn(); } /** * @return string */ public function getUpdatedAtColumn() { return $this->model()->getUpdatedAtColumn(); } /** * 获取列表页面查询的字段. * * @return array */ public function getGridColumns() { return ['*']; } /** * 获取表单页面查询的字段. * * @return array */ public function getFormColumns() { return ['*']; } /** * 获取详情页面查询的字段. * * @return array */ public function getDetailColumns() { return ['*']; } /** * 设置关联关系. * * @param mixed $relations * @return $this */ public function setRelations($relations) { $this->relations = (array) $relations; return $this; } /** * 查询Grid表格数据. * * @param Grid\Model $model * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator|Collection|array */ public function get(Grid\Model $model) { $this->setSort($model); $this->setPaginate($model); $query = $this->newQuery(); if ($this->relations) { $query->with($this->relations); } return $model->apply($query, true, $this->getGridColumns()); } /** * 设置表格数据排序. * * @param Grid\Model $model * @return void */ protected function setSort(Grid\Model $model) { [$column, $type, $cast] = $model->getSort(); if (empty($column) || empty($type)) { $orders = $model->getSortQueries(); $model->resetOrderBy(); $orders->each(function ($orderBy) use ($model) { $column = $orderBy['arguments'][0]; $type = in_array($orderBy['method'], $model->getSortDescMethods(), true) ? 'desc' : ($orderBy['arguments'][1] ?? 'asc'); $cast = null; $this->addOrderBy($model, $column, $type, $cast); }); return; } $model->resetOrderBy(); $this->addOrderBy($model, $column, $type, $cast); } /** * @param Grid\Model $model * @param string $column * @param string $type * @param string $cast * * @throws \Exception */ protected function addOrderBy(Grid\Model $model, $column, $type, $cast) { $explodedCols = explode('.', $column); $isRelation = empty($explodedCols[1]) ? false : method_exists($this->model(), $explodedCols[0]); if (count($explodedCols) > 1 && $isRelation) { $this->setRelationSort($model, $column, $type, $cast); return; } $this->setOrderBy( $model, str_replace('.', '->', $column), $type, $cast); } /** * @param Grid\Model $model * @param $column * @param $type * @param $cast */ protected function setOrderBy(Grid\Model $model, $column, $type, $cast) { $isJsonColumn = Str::contains($column, '->'); if ($isJsonColumn) { $explodedCols = explode('->', $column); // json字段排序 $col = $this->wrapMySqlColumn(array_shift($explodedCols)); $parts = implode('.', $explodedCols); $column = "JSON_UNQUOTE(JSON_EXTRACT({$col}, '$.{$parts}'))"; } if (! empty($cast)) { $column = $this->wrapMySqlColumn($column); $model->addQuery( 'orderByRaw', ["CAST({$column} AS {$cast}) {$type}"] ); return; } if ($isJsonColumn) { $model->addQuery('orderByRaw', ["{$column} {$type}"]); } else { $model->addQuery('orderBy', [$column, $type]); } } /** * @param string $column * @return string */ protected function wrapMySqlColumn($column) { if (Str::contains($column, '`')) { return $column; } $columns = explode('.', $column); foreach ($columns as &$column) { if (! Str::contains($column, '`')) { $column = "`{$column}`"; } } return implode('.', $columns); } /** * 设置关联数据排序. * * @param Grid\Model $model * @param string $column * @param string $type * @param string $cast * * @throws \Exception */ protected function setRelationSort(Grid\Model $model, $column, $type, $cast) { [$relationName, $relationColumn] = explode('.', $column, 2); $relation = $this->model()->$relationName(); $model->addQuery('select', [$this->model()->getTable().'.*']); $model->addQuery('join', $this->joinParameters($relation)); $this->setOrderBy( $model, $relation->getRelated()->getTable().'.'.str_replace('.', '->', $relationColumn), $type, $cast ); } /** * 关联模型 join 连接查询. * * @param Relation $relation * @return array * * @throws \Exception */ protected function joinParameters(Relation $relation) { $relatedTable = $relation->getRelated()->getTable(); if ($relation instanceof BelongsTo) { $foreignKeyMethod = version_compare(app()->version(), '5.8.0', '<') ? 'getForeignKey' : 'getForeignKeyName'; return [ $relatedTable, $relation->{$foreignKeyMethod}(), '=', $relatedTable.'.'.$relation->getRelated()->getKeyName(), ]; } if ($relation instanceof HasOne) { return [ $relatedTable, $relation->getQualifiedParentKeyName(), '=', $relation->getQualifiedForeignKeyName(), ]; } throw new AdminException('Related sortable only support `HasOne` and `BelongsTo` relation.'); } /** * 设置分页参数. * * @param Grid\Model $model * @return void */ protected function setPaginate(Grid\Model $model) { $paginateMethod = $model->getPaginateMethod(); $paginate = $model->findQueryByMethod($paginateMethod)->first(); $model->rejectQuery(['paginate', 'simplePaginate']); if (! $model->allowPagination()) { $model->addQuery('get', [$this->getGridColumns()]); } else { $model->addQuery($paginateMethod, $this->resolvePerPage($model, $paginate)); } } /** * 获取分页参数. * * @param Grid\Model $model * @param array|null $paginate * @return array */ protected function resolvePerPage(Grid\Model $model, $paginate) { if ($paginate && is_array($paginate)) { if ($perPage = request()->input($model->getPerPageName())) { $paginate['arguments'][0] = (int) $perPage; } return $paginate['arguments']; } return [ $model->getPerPage(), $this->getGridColumns(), $model->getPageName(), $model->getCurrentPage(), ]; } /** * 查询编辑页面数据. * * @param Form $form * @return array|\Illuminate\Contracts\Support\Arrayable */ public function edit(Form $form) { $query = $this->newQuery(); if ($this->isSoftDeletes) { $query->withTrashed(); } $this->model = $query ->with($this->getRelations()) ->findOrFail($form->getKey(), $this->getFormColumns()); return $this->model; } /** * 查询详情页面数据. * * @param Show $show * @return array|\Illuminate\Contracts\Support\Arrayable */ public function detail(Show $show) { $query = $this->newQuery(); if ($this->isSoftDeletes) { $query->withTrashed(); } $this->model = $query ->with($this->getRelations()) ->findOrFail($show->getKey(), $this->getDetailColumns()); return $this->model; } /** * 新增记录. * * @param Form $form * @return mixed */ public function store(Form $form) { $result = null; DB::transaction(function () use ($form, &$result) { $model = $this->model(); $updates = $form->updates(); [$relations, $relationKeyMap] = $this->getRelationInputs($model, $updates); if ($relations) { $updates = Arr::except($updates, array_keys($relationKeyMap)); } foreach ($updates as $column => $value) { $model->setAttribute($column, $value); } $result = $model->save(); $this->updateRelation($form, $model, $relations, $relationKeyMap); }); return $this->model()->getKey(); } /** * 查询更新前的行数据. * * @param Form $form * @return array|\Illuminate\Contracts\Support\Arrayable */ public function updating(Form $form) { return $this->edit($form); } /** * 更新数据. * * @param Form $form * @return bool */ public function update(Form $form) { /* @var EloquentModel $builder */ $model = $this->model(); if (! $model->getKey()) { $model->exists = true; $model->setAttribute($model->getKeyName(), $form->getKey()); } $result = null; DB::transaction(function () use ($form, $model, &$result) { $updates = $form->updates(); [$relations, $relationKeyMap] = $this->getRelationInputs($model, $updates); if ($relations) { $updates = Arr::except($updates, array_keys($relationKeyMap)); } foreach ($updates as $column => $value) { /* @var EloquentModel $model */ $model->setAttribute($column, $value); } $result = $model->update(); $this->updateRelation($form, $model, $relations, $relationKeyMap); }); return $result; } /** * 数据行排序上移一个单位. * * @return bool */ public function moveOrderUp() { $model = $this->model(); if (! $model instanceof Sortable) { throw new RuntimeException( sprintf( 'The model "%s" must be a type of %s.', get_class($model), Sortable::class ) ); } return $model->moveOrderUp() ? true : false; } /** * 数据行排序下移一个单位. * * @return bool */ public function moveOrderDown() { $model = $this->model(); if (! $model instanceof Sortable) { throw new RuntimeException( sprintf( 'The model "%s" must be a type of %s.', get_class($model), Sortable::class ) ); } return $model->moveOrderDown() ? true : false; } /** * 删除数据. * * @param Form $form * @param array $originalData * @return bool */ public function delete(Form $form, array $originalData) { $models = $this->collection->keyBy($this->getKeyName()); collect(explode(',', $form->getKey()))->filter()->each(function ($id) use ($form, $models) { $model = $models->get($id); if (! $model) { return; } $data = $model->toArray(); if ($this->isSoftDeletes && $model->trashed()) { $form->deleteFiles($data, true); $model->forceDelete(); return; } elseif (! $this->isSoftDeletes) { $form->deleteFiles($data); } $model->delete(); }); return true; } /** * 查询删除前的行数据. * * @param Form $form * @return array */ public function deleting(Form $form) { $query = $this->newQuery(); if ($this->isSoftDeletes) { $query->withTrashed(); } $id = $form->getKey(); $this->collection = $query ->with($this->getRelations()) ->findOrFail( collect(explode(',', $id))->filter()->toArray(), $this->getFormColumns() ); return $this->collection->toArray(); } /** * 获取父级ID字段名称. * * @return string */ public function getParentColumn() { $model = $this->model(); if (method_exists($model, 'getParentColumn')) { return $model->getParentColumn(); } } /** * 获取标题字段名称. * * @return string */ public function getTitleColumn() { $model = $this->model(); if (method_exists($model, 'getTitleColumn')) { return $model->getTitleColumn(); } } /** * 获取排序字段名称. * * @return string */ public function getOrderColumn() { $model = $this->model(); if (method_exists($model, 'getOrderColumn')) { return $model->getOrderColumn(); } } /** * 保存层级数据排序. * * @param array $tree * @param int $parentId */ public function saveOrder($tree = [], $parentId = 0) { $this->model()->saveOrder($tree, $parentId); } /** * 设置数据查询回调. * * @param \Closure|null $query * @return $this */ public function withQuery($queryCallback) { $this->model()->withQuery($queryCallback); return $this; } /** * 获取层级数据. * * @return array */ public function toTree() { if ($this->relations) { $this->withQuery(function ($model) { return $model->with($this->relations); }); } return $this->model()->toTree(); } /** * @return Builder */ protected function newQuery() { if ($this->queryBuilder) { return clone $this->queryBuilder; } return $this->model()->newQuery(); } /** * 获取model对象. * * @return EloquentModel */ public function model() { return $this->model ?: ($this->model = $this->createModel()); } /** * @param array $data * @return EloquentModel */ public function createModel(array $data = []) { $model = new $this->eloquentClass(); if ($data) { $model->setRawAttributes($data); } return $model; } /** * @param array $relations * @return $this */ public static function with($relations = []) { return (new static())->setRelations($relations); } /** * 获取模型的所有关联关系. * * @return array */ public function getRelations() { return $this->relations; } /** * 获取模型关联关系的表单数据. * * @param EloquentModel $model * @param array $inputs * @return array */ protected function getRelationInputs($model, $inputs = []) { $map = []; $relations = []; foreach ($inputs as $column => $value) { $relationColumn = null; if (method_exists($model, $column)) { $relationColumn = $column; } elseif (method_exists($model, $camelColumn = Str::camel($column))) { $relationColumn = $camelColumn; } if (! $relationColumn || ! (new ReflectionMethod($model, $relationColumn))->isPublic()) { continue; } $relation = call_user_func([$model, $relationColumn]); if ($relation instanceof Relations\Relation) { $relations[$column] = $value; $map[$column] = $relationColumn; } } return [&$relations, $map]; } /** * 更新关联关系数据. * * @param Form $form * @param EloquentModel $model * @param array $relationsData * @param array $relationKeyMap * * @throws \Exception */ protected function updateRelation(Form $form, EloquentModel $model, array $relationsData, array $relationKeyMap) { foreach ($relationsData as $name => $values) { $relationName = $relationKeyMap[$name] ?? $name; if (! method_exists($model, $relationName)) { continue; } $relation = $model->$relationName(); $oneToOneRelation = $relation instanceof Relations\HasOne || $relation instanceof Relations\MorphOne || $relation instanceof Relations\BelongsTo; $prepared = $oneToOneRelation ? $form->prepareUpdate([$name => $values]) : [$name => $values]; if (empty($prepared)) { continue; } switch (true) { case $relation instanceof Relations\BelongsToMany: case $relation instanceof Relations\MorphToMany: if (isset($prepared[$name])) { $relation->sync($prepared[$name]); } break; case $relation instanceof Relations\HasOne: $related = $model->$relationName; // if related is empty if (is_null($related)) { $related = $relation->getRelated(); $qualifiedParentKeyName = $relation->getQualifiedParentKeyName(); $localKey = Arr::last(explode('.', $qualifiedParentKeyName)); $related->{$relation->getForeignKeyName()} = $model->{$localKey}; } foreach ($prepared[$name] as $column => $value) { $related->setAttribute($column, $value); } $related->save(); break; case $relation instanceof Relations\BelongsTo: case $relation instanceof Relations\MorphTo: $parent = $model->$relationName; // if related is empty if (is_null($parent)) { $parent = $relation->getRelated(); } foreach ($prepared[$name] as $column => $value) { $parent->setAttribute($column, $value); } $parent->save(); // When in creating, associate two models $foreignKeyMethod = version_compare(app()->version(), '5.8.0', '<') ? 'getForeignKey' : 'getForeignKeyName'; if (! $model->{$relation->{$foreignKeyMethod}()}) { $model->{$relation->{$foreignKeyMethod}()} = $parent->getKey(); $model->save(); } break; case $relation instanceof Relations\MorphOne: $related = $model->$relationName; if (is_null($related)) { $related = $relation->make(); } foreach ($prepared[$name] as $column => $value) { $related->setAttribute($column, $value); } $related->save(); break; case $relation instanceof Relations\HasMany: case $relation instanceof Relations\MorphMany: foreach ($prepared[$name] as $related) { /** @var Relations\Relation $relation */ $relation = $model->$relationName(); $keyName = $relation->getRelated()->getKeyName(); $instance = $relation->findOrNew(Arr::get($related, $keyName)); if (Arr::get($related, Form::REMOVE_FLAG_NAME) == 1) { $instance->delete(); continue; } Arr::forget($related, Form::REMOVE_FLAG_NAME); $key = Arr::get($related, $relation->getModel()->getKeyName()); if ($key === null || $key === '') { Arr::forget($related, $relation->getModel()->getKeyName()); } $instance->fill($related); $instance->save(); } break; } } } }