Laravel 5.7 嵌套集基础教程

Laravel中的嵌套集是今天的主题的嵌套集模型是根据树遍历对节点进行编号,遍历每个节点两次,按访问顺序分配编号,并在两次访问时分配。这为每个节点留下了两个数字编号,它们存储为两个属性。查询变得更便宜:可以通过比较这些数字来测试层次结构成员资格。 更新则需要重新编号。

什么是 Nested Set

嵌套集或嵌套集模型(NSM)是有效地将分层数据存储在关系表中的方法。

用例

当树很少更新时,NSM显示出良好的性能。 它被创建为快速获取相关节点。
它非常适合为商店构建多个深度菜单或类别。

关系

嵌套集是一个填充了不同节点的树结构。 所以他们之间的关系如下。

  • 节点属于父节点
  • 节点有许多子节点
  • 节点有很多祖先
  • 节点有很多后代

Nested Set 在Laravel中的例子

我们将使用以下命令安装Framework来开始此Laravel 5.7嵌套集教程。

Step 1: 安装Laravel

我正在使用Valet。 所以,我需要输入以下命令来创建一个项目。

laravel new nestedset

当然如果你没有用Homestead或者Valet, 你也可以用下面的命令。

composer create-project laravel/laravel nestedset --prefer-dist

安装好后进入项目目录。

cd nestedset

.env文件中配置你的数据库。

Step 2: 安装 laravel-nestedset composer 包。

又可以在这里找到官方的Github仓库。

使用下面的composer命令来安装。

composer require kalnoy/nestedset

装好了后,我们来配置下它。

首先,我们正在努力建立一个电子商务商店。所以这是一个完美的场景,我们可以使用嵌套集,因为我们有第一个类别,然后是子类别和子子类别等等。 所以我们试图制作一个列表,其中,根是主要类别,然后它的所有子节点都是子类别。

Step 3: 创建一个模型和数据库迁移。

在你的终端中,使用如下命令:

php artisan make:model Shop -m

上面的命令就是同时生产模型和迁移文件的。

现在,在迁移文件中,我们需要添加一些由嵌套集库提供的额外列。

    <?php

    use Illuminate\Support\Facades\Schema;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Database\Migrations\Migration;
    // 以下仅在laravel版本小于5.5时用
    // use Kalnoy\Nestedset\NestedSet;

    class CreateShopsTable extends Migration
    {
        /**
         * Run the migrations.
         *
         * @return void
         */
        public function up()
        {
            Schema::create('shops', function (Blueprint $table) {
                $table->increments('id');
                $table->string('category_name');
                // NestedSet::columns($table); //低于laravel 5.5的老版本用这个命令
                $table->nestedSet();
                $table->timestamps();
            });
        }

        /**
         * Reverse the migrations.
         *
         * @return void
         */
        public function down()
        {
            Schema::dropIfExists('shops');
        }
        //以下的命令只单独删除Nested Set的字段,不会删除表,注意!
        // public function down()
        // {
        //     Schema::table('shops', function (Blueprint $table){
        //         $table->dropNestedSet();
        //     });
        // }
    }

因此,这个商店表包含category_name和Nested Set列。

现在我们使用下面的命令创建表。

    php artisan migrate

我们可以看到在我们的商店表中有三列,与嵌套集有关。

    _lft
    _rgt
    parent_id

shops表里多出来的字段

Step 3: 填充数据库。

对于此示例,我不是在创建动态数据,而是构建静态数据,然后将其提供给数据库。

首先,使用以下命令创建Seeder文件。

php artisan make:seeder ShopTableSeeder

打开App\Shop.php文件,写入以下代码,启用Nested Set。(低于laravel5.5的版本不需要设置)

    <?php
    namespace App;

    use Illuminate\Database\Eloquent\Model;
    use Kalnoy\Nestedset\NodeTrait;

    class Shop extends Model
    {
        use NodeTrait;
    }

好了,现在在ShopTableSeeder.php文件中编写以下代码。

    <?php

    use Illuminate\Database\Seeder;

    class ShopTableSeeder extends Seeder
    {
        public function run()
        {
            $shops = [
                [
                    'category_name' => '图书',
                        'children' => [
                            [    
                                'category_name' => '漫画书',
                                'children' => [
                                        ['category_name' => '曼威漫画书'],
                                        ['category_name' => '言情漫画书'],
                                        ['category_name' => '动作漫画书'],
                                ],
                            ],
                            [    
                                'category_name' => '教科书',
                                    'children' => [
                                        ['category_name' => '商业'],
                                        ['category_name' => '金融'],
                                        ['category_name' => '电脑科学'],
                                ],
                            ],
                        ],
                    ],
                    [
                        'category_name' => '电器',
                            'children' => [
                            [
                                'category_name' => '电视',
                                'children' => [
                                    ['category_name' => '显示器'],
                                    ['category_name' => '蓝光'],
                                ],
                            ],
                            [
                                'category_name' => '手机',
                                'children' => [
                                    ['category_name' => '华为'],
                                    ['category_name' => 'iPhone'],
                                    ['category_name' => '小米'],
                                ],
                            ],
                        ],
                    ],
            ];

            \App\Shop::create($shops);

        }
    }

我们将静态数组插入数据库。 在实时项目中,我们需要动态地完成它,但是对于这个例子,我只是使用静态数组。

现在我们找到DatabaseTableSeeder.php文件并且添加以下代码。

    <?php

    use Illuminate\Database\Seeder;

    class DatabaseSeeder extends Seeder
    {
        /**
         * Seed the application's database.
         *
         * @return void
         */
        public function run()
        {
            $this->call(ShopTableSeeder::class);
        }
    }

去终端执行填充命令如下:

php artisan db:seed

shops表填充完成的字段

Step 4: 创建路由和控制器

好了,现在我们进入终端创建一个ShopController.php文件

php artisan make:controller ShopController

将以下代码写入其中:

<?php

    namespace App\Http\Controllers;

    use App\Shop;
    use Illuminate\Http\Request;

    class ShopController extends Controller
    {
        public function index()
        {
            $shops = Shop::get()->toTree();
            return view('shop', compact('shops'));
        }
    }

那么,它将做的是,我们以树格式获取所有数据,我们可以在其上调用children方法,并且我们可以在视图中显示确切的分层数据。

我们还没有创建shop的view文件,不过在这之前我们先创建下路由文件。找到web.php文件,修改代码如下:

    // web.php

    Route::get('/', 'ShopController@index');

Step 5: 在视图中显示分层数据

现在,我们已经获得了数据,我们只需要将数据传递到View中并根据父子关系显示数据。
在views文件夹中,创建一个名为layout的文件夹,在该文件夹中创建一个名为app.blade.php的文件。

<!DOCTYPE html>
    <html lang='{{ app()->getLocale() }}'>
    @include('layouts.partials._head')
    <body>
        <div id='app'>
        <main class='py-4'>
            @yield('content')
        </main>
        </div>
    </body>
    </html>

我们还没有创建partials文件夹, 现在在layout文件夹下面创建partials文件夹。
完成后再在partials文件夹里面,创建一个_head.blade.php文件。


<!-- _head.blade.php  -->

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="https://fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Raleway:300,400,600" rel="stylesheet" type="text/css">

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>

最后,创建shop.blade.php视图,代码如下:

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="card">
        <div class="row">
            <div class="card-body">
                @foreach($shops as $shop)
                <div class="col-md-12">
                    <h3>{{ $shop->category_name }}</h3>
                    <hr />
                    <div class="row">
                        @foreach($shop->children as $cats)
                        <div class="col-md-4">
                            <h4>{{ $cats->category_name }}</h4>
                            <hr />
                            @foreach($cats->children as $cat)
                            <h5>{{$cat->category_name}}</h5>
                            @endforeach
                        </div>
                        @endforeach
                    </div>
                </div>
                @endforeach
            </div>
        </div>
    </div>
</div>
@endsection

现在,保存文件,进入浏览器,点击这个链接,http://nestedset.test

你将看到如下图片显示的:

显示层级关系

因此,这也就意味着,你获取到了所有基于父分类的数据,怎么样,是不是很爽!根本不用写额外的代码,直接用包就解决了。

在我们上述的例子中,我们的主要分类有两个:

  • 图书
  • 电器
    图书的子类又有:
  • 漫画书
  • 教科书
    电器的子类又有:
  • 电视
  • 手机

如果有更多子类别,我们可以进一步进入递归。
我们可以使用此层次结构为购物类别构建多级菜单。
所以这就是嵌套集模型的基本示例。

最后

关于Laravel Nested Set的基础示例就结束了,如果你想了解更多,多去读读官方文档吧!
本教程的源码上传到了github,大家有兴趣可以看一下,后续我会继续更新一些实战存储动态数据的例子!

本作品采用《CC 协议》,转载必须注明作者和本文链接
求知若饥,虚心若愚!
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 3

@lovecn 我刚开始用Laravel,写的文章都比较简单,当作学习笔记的。

5年前 评论

博主,你好,同样的代码在TableSeeder上 , 当执行下列代码的时候可以正常注入 。

$goods = > [
     'category_name' => 'top'
     'children' => [
         ['category_name' => 'sub']
     ]
]

而当注入多组数据的时候

$goods = > [
[
'category_name' => 'top'
'children' => [
['category_name' => 'sub']
]
],[
'category_name' => 'top2'
'children' => [
['category_name' => 'sub2']
]
]
]

则会产生以下错误 :

Symfony\Component\Debug\Exception\FatalThrowableError  : Argument 1 passed to Illuminate\Database\Grammar::parameterize() must be of the type array, null given, called in /home/vagrant/code/NFTS-Shop/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php on line 869
at /home/vagrant/code/NFTS-Shop/vendor/laravel/framework/src/Illuminate/Database/Grammar.php:138

找了一通没在 issue上找到相关问题 ,英语表述能力不佳因此想请教下博主 :grin:

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!