首页
Preview

Laravel使用嵌套资源创建CRUD

本文将会讲解如何创建菜单及菜单项的 CRUD 操作。一个菜单可以有多个菜单项,Laravel 已经为此提供了一种嵌套资源的解决方案。我们将使用 Laravel 的 嵌套资源 来创建菜单及菜单项的 CRUD 操作。

创建路由

首先,我们需要在 routes/admin.php 文件中添加嵌套资源路由。

    Route::resource('menu', 'MenuController')->except([
        'show'
    ]);
    Route::resource('menu.item', 'MenuItemController');

“点”符号被用来声明嵌套资源。对于菜单的 CRUD 操作,我们不需要显示页面,因此使用 except 函数来加载 Partial Resource Routes

这个 menu.item 路由定义将会创建以下路由:

菜单模型

我们将会在控制器中使用 Laravel Menu 包中的模型。在上一篇文章中,我们已经创建了一个通用的菜单包。

创建菜单控制器

app\Http\Controllers\Admin\ 中创建菜单控制器。

app\Http\Controllers\Admin\MenuController.php

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use BalajiDharma\LaravelMenu\Models\Menu;
use BalajiDharma\LaravelAdminCore\Requests\StoreMenuRequest;
use BalajiDharma\LaravelAdminCore\Requests\UpdateMenuRequest;

class MenuController extends Controller
{
    public function __construct()
    {
        $this->middleware('can:menu list', ['only' => ['index']]);
        $this->middleware('can:menu create', ['only' => ['create', 'store']]);
        $this->middleware('can:menu edit', ['only' => ['edit', 'update']]);
        $this->middleware('can:menu delete', ['only' => ['destroy']]);
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\View\View
     */
    public function index()
    {
        $menus = (new Menu)->newQuery();

        if (request()->has('search')) {
            $menus->where('name', 'Like', '%'.request()->input('search').'%');
        }

        if (request()->query('sort')) {
            $attribute = request()->query('sort');
            $sort_order = 'ASC';
            if (strncmp($attribute, '-', 1) === 0) {
                $sort_order = 'DESC';
                $attribute = substr($attribute, 1);
            }
            $menus->orderBy($attribute, $sort_order);
        } else {
            $menus->latest();
        }

        $menus = $menus->paginate(5)->onEachSide(2);

        return view('admin.menu.index', compact('menus'));
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\View\View
     */
    public function create()
    {
        return view('admin.menu.create');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  StoreMenuRequest  $request
     * @return \Illuminate\Http\RedirectResponse
     */
    public function store(StoreMenuRequest $request)
    {
        Menu::create([
            'name' => $request->name,
            'machine_name' => $request->machine_name,
            'description' => $request->description,
        ]);

        return redirect()->route('menu.index')
                        ->with('message', 'Menu created successfully.');
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  \BalajiDharma\LaravelMenu\Models\Menu  $menu
     * @return \Illuminate\View\View
     */
    public function edit(Menu $menu)
    {
        return view('admin.menu.edit', compact('menu'));
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  UpdateMenuRequest  $request
     * @param  \BalajiDharma\LaravelMenu\Models\Menu  $menu
     * @return \Illuminate\Http\RedirectResponse
     */
    public function update(UpdateMenuRequest $request, Menu $menu)
    {
        $menu->update($request->all());

        return redirect()->route('menu.index')
                        ->with('message', 'Menu updated successfully.');
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \BalajiDharma\LaravelMenu\Models\Menu  $menu
     * @return \Illuminate\Http\RedirectResponse
     */
    public function destroy(Menu $menu)
    {
        $menu->delete();

        return redirect()->route('menu.index')
                        ->with('message', __('Menu deleted successfully'));
    }
}

StoreMenuRequestUpdateMenuRequest 被添加到 admin core 包中。

创建菜单视图

resources/views/admin/menu 文件夹中创建 indexcreateedit 视图。

从 GitHub 上复制这些视图 https://github.com/balajidharma/basic-laravel-admin-panel/tree/1.x/resources/views/admin/menu

machine_name 被用来标识菜单,machine_name 应该是唯一的。

在菜单包中添加 Traits

对于菜单项,我们需要将它们作为树形结构显示,并且需要将菜单项构建为下拉菜单。

为了实现这些功能,我们将会在 menu 包 中创建新的 Traits。这个 MenuTree Trait 是参考了 https://github.com/z-song/laravel-admin/blob/master/src/Traits/ModelTree.php 创建的。

src\Traits\MenuTree.php

<?php

namespace BalajiDharma\LaravelMenu\Traits;

use BalajiDharma\LaravelMenu\Exceptions\InvalidParent;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Request;

trait MenuTree
{
    /**
     * @var \Closure
     */
    protected $queryCallback;

    /**
     * Get children of current node.
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function children()
    {
        return $this->hasMany(static::class, $this->getParentColumn());
    }

    /**
     * Get parent of current node.
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function parent()
    {
        return $this->belongsTo(static::class, $this->getParentColumn());
    }

    /**
     * GET all parents.
     *
     * @return \Illuminate\Support\Collection
     */
    public function parents()
    {
        $parents = collect([]);

        $parent = $this->parent;

        while (!is_null($parent)) {
            $parents->push($parent);
            $parent = $parent->parent;
        }

        return $parents;
    }

    /**
     * @return string
     */
    public function getParentColumn()
    {
        if (property_exists($this, 'parentColumn')) {
            return $this->parentColumn;
        }

        return 'parent_id';
    }

    /**
     * @return mixed
     */
    public function getParentKey()
    {
        return $this->{$this->getParentColumn()};
    }

    /**
     * Set parent column.
     *
     * @param string $column
     */
    public function setParentColumn($column)
    {
        $this->parentColumn = $column;
    }

    /**
     * Get title column.
     *
     * @return string
     */
    public function getTitleColumn()
    {
        if (property_exists($this, 'titleColumn')) {
            return $this->titleColumn;
        }

        return 'name';
    }

    /**
     * Set title column.
     *
     * @param string $column
     */
    public function setTitleColumn($column)
    {
        $this->titleColumn = $column;
    }

    /**
     * Get order column name.
     *
     * @return string
     */
    public function getOrderColumn()
    {
        if (property_exists($this, 'orderColumn')) {
            return $this->orderColumn;
        }

        return 'weight';
    }

    /**
     * Set order column.
     *
     * @param string $column
     */
    public function setOrderColumn($column)
    {
        $this->orderColumn = $column;
    }

     /**
     * @return string
     */
    public function getMenuRelationColumn()
    {
        if (property_exists($this, 'menuRelationColumn')) {
            return $this->menuRelationColumn;
        }

        return 'menu_id';
    }

    /**
     * Set menu relation column.
     *
     * @param string $column
     */
    public function setMenuRelationColumn($column)
    {
        $this->menuRelationColumn = $column;
    }

    /**
     * Set query callback to model.
     *
     * @param \Closure|null $query
     *
     * @return $this
     */
    public function withQuery(\Closure $query = null)
    {
        $this->queryCallback = $query;

        return $this;
    }

    /**
     * Format data to tree like array.
     *
     * @return array
     */
    public function toTree($menuId)
    {
        return $this->buildNestedArray($menuId);
    }

    /**
     * Build Nested array.
     *
     * @param array $nodes
     * @param int   $parentId
     *
     * @return array
     */
    protected function buildNestedArray($menuId, array $nodes = [], $parentId = 0)
    {
        $branch = [];

        if (empty($nodes)) {
            $nodes = $this->allNodes($menuId);
        }

        foreach ($nodes as $node) {
            if ($node[$this->getParentColumn()] == $parentId) {
                $children = $this->buildNestedArray($menuId, $nodes, $node[$this->getKeyName()]);

                if ($children) {
                    $node['children'] = $children;
                }

                $branch[] = $node;
            }
        }

        return $branch;
    }

    /**
     * Get all elements.
     *
     * @return mixed
     */
    public function allNodes($menuId, $ignoreItemId = null)
    {
        $self = new static();

        if ($this->queryCallback instanceof \Closure) {
            $self = call_user_func($this->queryCallback, $self);
        }

        if($ignoreItemId) {
            return $self->where($this->getMenuRelationColumn(), $menuId)
                ->where(function ($query) use ($ignoreItemId) {
                    $query->where($this->getParentColumn(), '!=', $ignoreItemId)->orWhereNull($this->getParentColumn());
                })
                ->orderBy($this->getOrderColumn())->get()->toArray();
        }

        return $self->where($this->getMenuRelationColumn(), $menuId)->orderBy($this->getOrderColumn())->get()->toArray();
    }

    /**
     * Get options for Select field in form.
     *
     * @param \Closure|null $closure
     * @param string        $rootText
     *
     * @return array
     */
    public static function selectOptions($menuId, $ignoreItemId = null, \Closure $closure = null)
    {
        $options = (new static())->withQuery($closure)->buildSelectOptions($menuId, $ignoreItemId);

        return collect($options)->all();
    }

    /**
     * Build options of select field in form.
     *
     * @param array  $nodes
     * @param int    $parentId
     * @param string $prefix
     * @param string $space
     *
     * @return array
     */
    protected function buildSelectOptions($menuId, $ignoreItemId, array $nodes = [], $parentId = 0, $prefix = '', $space = '&nbsp;')
    {
        $prefix = $prefix ?: '┝'.$space;

        $options = [];

        if (empty($nodes)) {
            $nodes = $this->allNodes($menuId, $ignoreItemId);
        }

        foreach ($nodes as $index => $node) {
            if ($node[$this->getParentColumn()] == $parentId) {
                $node[$this->getTitleColumn()] = $prefix.$space.$node[$this->getTitleColumn()];

                $childrenPrefix = str_replace('┝', str_repeat($space, 6), $prefix).'┝'.str_replace(['┝', $space], '', $prefix);

                $children = $this->buildSelectOptions($menuId, null, $nodes, $node[$this->getKeyName()], $childrenPrefix);

                $options[$node[$this->getKeyName()]] = $node[$this->getTitleColumn()];

                if ($children) {
                    $options += $children;
                }
            }
        }

        return $options;
    }

    /**
     * Build the link based on uri
     *
     */
    protected function getLinkAttribute()
    {
        $uri = trim($this->uri);
        
        if (strpos($uri, '<nolink>') !== false) {
            $uri = '';
        }

        if (strpos($uri, '<admin>') !== false) {
            $uri = str_replace('<admin>', config('admin.prefix', 'admin'), $uri);
        }

        return $uri;
    }

    /**
     * {@inheritdoc}
     */
    public function delete()
    {
        $parentColumn = $this->getParentColumn();
        $newParent = $this->$parentColumn ?? null;
        $this->where($this->getParentColumn(), $this->getKey())->update([$this->getParentColumn() => $newParent]);

        return parent::delete();
    }

    public function initializeMenuTree()
    {
        $this->appends = array_unique(array_merge($this->appends, ['link']));
    }

    /**
     * {@inheritdoc}
     */
    protected static function boot()
    {
        parent::boot();

        static::saving(function (Model $branch) {
            $parentColumn = $branch->getParentColumn();

            if (Request::filled($parentColumn) && Request::input($parentColumn) == $branch->getKey()) {
                throw InvalidParent::create();
            }

            return $branch;
        });
    }
}

现在,在我们的 MenuItem 模型中包含这个 trait。

src/Models/MenuItem.php

<?php

namespace BalajiDharma\LaravelMenu\Models;

use BalajiDharma\LaravelMenu\Traits\MenuTree;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class MenuItem extends Model
{
    use MenuTree {
        MenuTree::boot as treeBoot;
    }

创建菜单项控制器

创建 MenuItemController 来管理菜单项。

app\Http\Controllers\Admin\MenuItemController.php

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use BalajiDharma\LaravelAdminCore\Requests\StoreMenuItemRequest;
use BalajiDharma\LaravelAdminCore\Requests\UpdateMenuItemRequest;
use BalajiDharma\LaravelMenu\Models\Menu;
use BalajiDharma\LaravelMenu\Models\MenuItem;

class MenuItemController extends Controller
{
    public function __construct()
    {
        $this->middleware('can:menu list', ['only' => ['index', 'show']]);
        $this->middleware('can:menu create', ['only' => ['create', 'store']]);
        $this->middleware('can:menu edit', ['only' => ['edit', 'update']]);
        $this->middleware('can:menu delete', ['only' => ['destroy']]);
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\View\View
     */
    public function index(Menu $menu)
    {
        $items = (new MenuItem)->toTree($menu->id);

        return view('admin.menu.item.index', compact('items', 'menu'));
    }

        /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\View\View
     */
    public function create(Menu $menu)
    {
        $item_options = MenuItem::selectOptions($menu->id);
        return view('admin.menu.item.create', compact('menu', 'item_options'));
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  StoreMenuItemRequest  $request
     * @param  \BalajiDharma\LaravelMenu\Models\Menu $menu
     * @return \Illuminate\Http\RedirectResponse
     */
    public function store(StoreMenuItemRequest $request, Menu $menu)
    {
        $menu->menuItems()->create($request->all());

        return redirect()->route('menu.item.index', $menu->id)
                        ->with('message', 'Menu created successfully.');
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  \BalajiDharma\LaravelMenu\Models\Menu $menu
     * @return \Illuminate\View\View
     */
    public function edit(Menu $menu, MenuItem $item)
    {
        $item_options = MenuItem::selectOptions($menu->id, $item->parent_id ?? $item->id);
        return view('admin.menu.item.edit', compact('menu', 'item', 'item_options'));
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  UpdateMenuItemRequest  $request
     * @param  \BalajiDharma\LaravelMenu\Models\Menu $menu
     * @param  \BalajiDharma\LaravelMenu\Models\MenuItem $item
     * @return \Illuminate\Http\RedirectResponse
     */
    public function update(UpdateMenuItemRequest $request, Menu $menu, MenuItem $item)
    {
        $item->update($request->all());

        return redirect()->route('menu.item.index', $menu->id)
                        ->with('message', 'Menu Item updated successfully.');
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \BalajiDharma\LaravelMenu\Models\Menu $menu
     * @param  \BalajiDharma\LaravelMenu\Models\MenuItem $menuItem
     * @return \Illuminate\Http\RedirectResponse
     */
    public function destroy(Menu $menu, MenuItem $item)
    {
        $item->delete();

        return redirect()->route('menu.item.index', $menu->id)
                        ->with('message', __('Menu deleted successfully'));
    }
}

所有的菜单项视图都被添加到 resources/views/admin/menu/item 文件夹中。

从 GitHub 上复制并使用这些视图文件 https://github.com/balajidharma/basic-laravel-admin-panel/tree/1.x/resources/views/admin/menu/item

我们已经创建了菜单及菜单项的 CRUD 操作。

Laravel 后台管理面板可以在 https://github.com/balajidharma/basic-laravel-admin-panel 中找到。安装后,请分享你的反馈。

译自:https://blog.devgenius.io/laravel-creates-crud-with-nested-resources-ac97667609a4

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
anko
宽以待人处事,严于律己修身。

评论(0)

添加评论