@Toc

所用的知识点

1.迁移文件的创建
2.数据填充
3.通过观察者监听模型事件
4.注册观察者
5.模型

素材需要

数据库

字段 value
id 分类id
name 分类名
parent_id 父级id
image 分类图标
level 当前分类的等级
sort 排序
possess 该分类的所有父类

GoodsCategory模型

解决访问器与数据填充冲突 - 图1
在category定义一个与上级以及子级的模型关联。然后添加三个获取器;1.获取所有的上级id,2.根据获取的id获取子级的所有上级,并且根据level排序。 至于之后一个只是获取所有的上级名称

  1. <?php
  2. namespace App\Models;
  3. use Illuminate\Database\Eloquent\Model;
  4. /**
  5. * 商品分类
  6. */
  7. class GoodsCategory extends Model
  8. {
  9. protected $fillable = ['name', 'category_image'];
  10. public function parent()
  11. {
  12. //反向关联
  13. return $this->belongsTo(GoodsCategory::class);
  14. }
  15. public function children() {
  16. //一对多
  17. return $this->hasMany(GoodsCategory::class, 'parent_id');
  18. }
  19. //定义一个访问器,获取所有祖先类目的ID值
  20. public function getPossessIdsAttribute()
  21. {
  22. //array_filter 将数组中的空值移除
  23. return array_filter(explode('-', trim($this->possess, '-')));
  24. }
  25. //定义一个访问器,获取祖先类目并按层级排序
  26. public function getAncestorsAttribute()
  27. {
  28. return GoodsCategory::query()
  29. ->whereIn('id', $this->possess_ids)
  30. //按层级排序
  31. ->orderBy('level')->get();
  32. }
  33. //定义一个访问器,获取以 - 为分隔的所有祖先类目的名称以及当前类目的名称
  34. public function getFullNameAttribute()
  35. {
  36. return $this->ancestors //获取所有祖先类
  37. ->pluck('name') //获取祖先类目的name 字段为一个数组
  38. ->push($this->name)//获取当前类目的 name 字段加到数组的末尾
  39. ->implode(' - '); //用 - 符合将数组的值组成一个字符串
  40. }
  41. public function getLevelAttribute($value) {
  42. $data = [
  43. '0' => '根目录',
  44. '1' => '二级',
  45. '2' => '三级',
  46. ];
  47. return (is_null($value)) ? $data : $data[$value];
  48. }
  49. /**
  50. * 测试方法
  51. * @return [type] [description]
  52. */
  53. public function test() {
  54. $category = GoodsCategory::where('id', 10)->first();
  55. $data = $category->ancestors->toArray();
  56. return $data;
  57. }
  58. }

创建GoodsCategoryTableSeeder.php数据填充文件

命令:php artisan make:seeder GoodsCategoryTableSeeder
解决访问器与数据填充冲突 - 图2

  1. <?php
  2. use App\Models\GoodsCategory;
  3. use Illuminate\Database\Seeder;
  4. class GoodsCategoryTableSeeder extends Seeder
  5. {
  6. /**
  7. * Run the database seeds.
  8. *
  9. * @return void
  10. */
  11. public function run()
  12. {
  13. $categories = [
  14. [
  15. 'name' => '手机配件',
  16. 'sort' => '0',
  17. 'children' => [
  18. [
  19. 'name' => '手机壳',
  20. 'sort' => '0',
  21. 'children' => [
  22. [
  23. 'name' => '华为V10手机',
  24. 'sort' => '0',
  25. ],
  26. [
  27. 'name' => '小米',
  28. 'sort' => '1',
  29. ],
  30. ],
  31. ],
  32. [
  33. 'name' => '数据线',
  34. 'sort' => '4',
  35. 'children' => [
  36. [
  37. 'name' => '苹果数据线',
  38. 'sort' => '0',
  39. ],
  40. [
  41. 'name' => '安卓数据线',
  42. 'sort' => '1',
  43. ],
  44. ],
  45. ],
  46. [
  47. 'name' => '耳机',
  48. 'sort' => '0',
  49. 'children' => [
  50. [
  51. 'name' => '有线耳机',
  52. 'sort' => '1',
  53. ],
  54. [
  55. 'name' => '蓝牙耳机',
  56. 'sort' => '0',
  57. ],
  58. ],
  59. ],
  60. ],
  61. ],
  62. [
  63. 'name' => '六星果园',
  64. 'sort' => '0',
  65. 'children' => [
  66. [
  67. 'name' => '国产水果',
  68. 'sort' => '0',
  69. 'children' => [
  70. [
  71. 'name' => '苹果',
  72. 'sort' => '0',
  73. ],
  74. [
  75. 'name' => '梨',
  76. 'sort' => '1',
  77. ],
  78. ],
  79. ],
  80. ]
  81. ]
  82. ];
  83. foreach ($categories as $data) {
  84. $this->createCategory($data);
  85. }
  86. }
  87. public function createCategory($data, $parent = null)
  88. {
  89. // 创建一个分类
  90. $category = new GoodsCategory([
  91. 'name' => $data['name'],
  92. 'sort' => $data['sort'],
  93. ]);
  94. // 如果有父级参数,代表有父类目
  95. if (!is_null($parent)) {
  96. // 将模型实例与给定的父实例关联。
  97. $category->parent()->associate($parent);
  98. }
  99. // 保存到数据库
  100. $category->save();
  101. // 如果有children字段并且 children字段是一个数组
  102. if (isset($data['children']) && is_array($data['children'])) {
  103. foreach ($data['children'] as $child) {
  104. $this->createCategory($child, $category);
  105. }
  106. }
  107. }
  108. }

创建GoodsCategoryObserver.php观察者

命令:php artisan make:observer GoodsCategoryObserver —model=GoodsCategory
解决访问器与数据填充冲突 - 图3

  1. <?php
  2. namespace App\Observers;
  3. use Log;
  4. use App\Models\GoodsCategory;
  5. class GoodsCategoryObserver
  6. {
  7. public function creating(GoodsCategory $goodsCategory) {
  8. //如果创建的是一个根类目
  9. if (is_null($goodsCategory->parent_id)) {
  10. //讲层级设置为0
  11. $goodsCategory->level = 0;
  12. //将path 设为 -
  13. $goodsCategory->possess = '-';
  14. }else {
  15. //将层级设为父类目层级 + 1
  16. $goodsCategory->level = $goodsCategory->parent->level +1;
  17. // 将path 设为父级目的的PATH 追加父级的id 并最后 跟上一个 - 分隔符
  18. $goodsCategory->possess = $goodsCategory->parent->possess.$goodsCategory->parent_id.'-';
  19. }
  20. }
  21. /**
  22. * Handle the goods category "created" event.
  23. *
  24. * @param \App\GoodsCategory $goodsCategory
  25. * @return void
  26. */
  27. public function created(GoodsCategory $goodsCategory)
  28. {
  29. }
  30. /**
  31. * Handle the goods category "updated" event.
  32. *
  33. * @param \App\GoodsCategory $goodsCategory
  34. * @return void
  35. */
  36. public function updated(GoodsCategory $goodsCategory)
  37. {
  38. //
  39. }
  40. /**
  41. * Handle the goods category "deleted" event.
  42. *
  43. * @param \App\GoodsCategory $goodsCategory
  44. * @return void
  45. */
  46. public function deleted(GoodsCategory $goodsCategory)
  47. {
  48. //
  49. }
  50. /**
  51. * Handle the goods category "restored" event.
  52. *
  53. * @param \App\GoodsCategory $goodsCategory
  54. * @return void
  55. */
  56. public function restored(GoodsCategory $goodsCategory)
  57. {
  58. //
  59. }
  60. /**
  61. * Handle the goods category "force deleted" event.
  62. *
  63. * @param \App\GoodsCategory $goodsCategory
  64. * @return void
  65. */
  66. public function forceDeleted(GoodsCategory $goodsCategory)
  67. {
  68. //
  69. }
  70. }

创建数据库迁移文件

php artisan make:mmigration create_goods_category_table —create=goods_category
解决访问器与数据填充冲突 - 图4

  1. <?php
  2. use Illuminate\Support\Facades\Schema;
  3. use Illuminate\Database\Schema\Blueprint;
  4. use Illuminate\Database\Migrations\Migration;
  5. class CreateGoodsCategoriesTable extends Migration
  6. {
  7. /**
  8. * Run the migrations.
  9. *
  10. * @return void
  11. */
  12. public function up()
  13. {
  14. Schema::create('goods_categories', function (Blueprint $table) {
  15. $table->increments('id')->comment('商品类别主键id');
  16. $table->string('name')->comment('类别名称');
  17. $table->integer('parent_id')->default(0)->comment('父级类别id');
  18. $table->string('image')->nullable()->comment('分类图片');
  19. $table->integer('level')->default(0)->comment('分类等级');
  20. $table->integer('sort')->default(0)->comment('分类排序');
  21. $table->timestamps();
  22. });
  23. }
  24. /**
  25. * Reverse the migrations.
  26. *
  27. * @return void
  28. */
  29. public function down()
  30. {
  31. Schema::dropIfExists('goods_categories');
  32. }
  33. }

注册观察者

命令:php artisan make:provider ModelObserverServiceProvider
解决访问器与数据填充冲突 - 图5

  1. <?php
  2. namespace App\Providers;
  3. use App\Observers\GoodsCategoryObserver;
  4. use App\Models\GoodsCategory;
  5. use Illuminate\Support\ServiceProvider;
  6. class ModelObserverProvider extends ServiceProvider
  7. {
  8. /**
  9. * Register services.
  10. *
  11. * @return void
  12. */
  13. public function register()
  14. {
  15. //
  16. }
  17. /**
  18. * Bootstrap services.
  19. *
  20. * @return void
  21. */
  22. public function boot()
  23. {
  24. GoodsCategory::observe(GoodsCategoryObserver::class);
  25. }
  26. }

然后在app里边注册进去即可
解决访问器与数据填充冲突 - 图6

重现数据填充与访问器冲突

执行数据填充命令

php artisan db:seed —class=GoodsCategoryTableSeeder
解决访问器与数据填充冲突 - 图7
然后打开数据库
解决访问器与数据填充冲突 - 图8

日志检测问题

我们会发现我们自定义的数据都没有进去,只进去了第一行,这是为什么呢!
我们可以看到有个一报错信息是 A non-numeric value encountered,并且报到了观察者的19行,我们在这里写个日志看看原因
解决访问器与数据填充冲突 - 图9
在把数据填充的命令执行一次
php artisan db:seed —class =GoodsCategoryOberserver
同样也给出了这里的报错信息,那么咱们看看这level字段到底怎么回事
解决访问器与数据填充冲突 - 图10

检测level字段的问题

可以看到在模型里边定义了一个访问器来重新定义了level字段,那么问题就在这里了,这个时候我们在注释了以后去执行填充命令
解决访问器与数据填充冲突 - 图11
执行
解决访问器与数据填充冲突 - 图12
查看数据库
这个时候就是可以的了
解决访问器与数据填充冲突 - 图13

解决访问器与数据填充时的冲突

虽然我们在上边解决了这个问题,但是我们定义的这个访问器是在后台显示数据的时候使用的。其实数据填充出现的问题,也就是我们在后台进行数据添加时会报出的问题,所以这个问题不是一个注释解决的

1.在模型里边定义一个虚拟字段
解决访问器与数据填充冲突 - 图14
2.修改之前的访问器
解决访问器与数据填充冲突 - 图15
3.然后在执行填充命令
解决访问器与数据填充冲突 - 图16
4.查看数据库
解决访问器与数据填充冲突 - 图17
5.打开后台查看数据
解决访问器与数据填充冲突 - 图18

解决设置的访问器不能正常使用

我们在添加了虚拟属性后, 我们的数据填充是好了,但是我们在后台获取数据时并没有作用
这个时候还需要一步操作,那就是把字段也给换成自定义的字段
解决访问器与数据填充冲突 - 图19
这个时候在来查看,就没有问题了
解决访问器与数据填充冲突 - 图20