Laravel Vuejs 实战:开发知乎 (15)问题 Feed 和删除问题
1.取全部数据:
将all方法添加到QuestionRepository:
1 <?php 2 3 namespace App\Repositories; 4 5 use App\Models\Question; 6 use App\Topic; 7 8 class QuestionRepository 9 { 10 11 /** 12 * @return Question[]|\Illuminate\Database\Eloquent\Collection 13 */ 14 public function all() 15 { 16 return Question::all(); 17 } 18 19 /** 20 * 根据提供的数据,使用Eloquent创建question,存储到数据库中并且返回该实例 21 * @param array $data 22 * @return mixed 23 */ 24 public function create(array $data) 25 { 26 $question = Question::create($data); 27 return $question; 28 } 29 30 /** 31 * @param array $topics 32 * @return array 33 */ 34 public function normalizeTopics(array $topics) 35 { 36 //返回topic的id序列,如果不是数字,则强制认为是数据库中的topic的id, 37 //这样虽然会漏掉用户如果设置的topic就是数字的情况,但是因为是测试,所以暂时忽略 38 $normalizedTopics = collect($topics)->map(function ($topic) { 39 if (is_numeric($topic))//是数字 40 { 41 //在数据库中找id 42 $num_topic = Topic::query()->find($topic); 43 if (isset($num_topic) && $num_topic->count() > 0) //在数据库中找得到id 44 { 45 //因为是找到了 且是从question处提交,其内部的question_count应该增加1个 46 $num_topic->increment('questions_count'); 47 //返回id 48 return (int)$num_topic->id; 49 } 50 } 51 //否则创建一个新的topic 52 //再之前先判断是否找得到,因为有时候重复提交,select2在提交的数据中不为数字,但是实际上数据库中已经有值 53 $already = Topic::query()->select('id', 'name')->where('name', '=', $topic); 54 55 if ($already->count() > 0) { 56 //因为是找到了 且是从question处提交,其内部的question_count应该增加1个 57 $already->increment('questions_count'); 58 //返回id 59 return $already->first()->id; 60 } 61 $data_of_topic = [ 62 'name' => $topic,//目前view中topic只有一个选择框,没有设置content的输入框 63 'questions_count' => 1,//因为是找到了 且是从question处提交,其内部的question_count应该初始化为1个 64 ]; 65 //入库 66 $newTopic = Topic::create($data_of_topic); 67 //返回id 68 return $newTopic->id; 69 })->toArray(); 70 71 return $normalizedTopics; 72 } 73 } 74 75
修改QuestionController index方法逻辑:
1 <?php 2 3 namespace App\Http\Controllers; 4 5 use App\Http\Requests\QuestionStoreRequest; 6 use App\Models\Question; 7 use App\Repositories\QuestionRepository; 8 9 10 class QuestionController extends Controller 11 { 12 13 /** 14 * @var QuestionRepository 15 */ 16 private $questionRepository; 17 18 public function __construct(QuestionRepository $questionRepository) 19 { 20 $this->middleware( 21 'auth', 22 [ 23 'except' => 24 [ 25 'index', 26 'show', 27 ]//非注册用户只能查看不能编辑添加更改删除 28 ] 29 ); 30 31 $this->questionRepository = $questionRepository; 32 } 33 34 35 /** Display a listing of the resource. 36 * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View 37 */ 38 public function index() 39 { 40 // 41 $questions = $this->questionRepository->all(); 42 return view('questions.index', compact('questions')); 43 } 44 45 46 /** 47 * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View 48 */ 49 public function create() 50 { 51 // 52 return view('questions.create'); 53 } 54 55 56 /** 57 * @param QuestionStoreRequest $request 58 * @return \Illuminate\Http\RedirectResponse 59 */ 60 public function store(QuestionStoreRequest $request)//依赖注入QuestionStoreRequest实例 61 { 62 // 63 // $data = $request->validate([ 64 // 'title' => 'required|min:8', 65 // 'content' => 'required|min:28', 66 // ]); 67 //存储topics 68 $topics = $this->questionRepository->normalizeTopics($request->get('topics')); 69 //初始化question要用到的数据 70 $data = $request->all(); 71 $data['user_id'] = auth()->user()->id; 72 73 // $question=Question::create($data); 被下方代码取代 74 $question = $this->questionRepository->create($data); 75 76 //使用我们再question model里面添加的topics方法获得 topics关联,再使用attach方法 77 $question->topics()->attach($topics); 78 79 return redirect()->route('questions.show', $question); 80 } 81 82 83 /** 84 * @param Question $question 85 * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View 86 */ 87 public function show(Question $question) 88 { 89 //使用关系关联加载,with方法会将分类之下的商品一起查询出来,而且不会出现N+1影响性能的问题 90 $question->with('topics')->get(); 91 92 return view('questions.show', compact('question')); 93 } 94 95 96 /**判断权限 返回视图 97 * @param Question $question 98 * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\View\View 99 */ 100 public function edit(Question $question) 101 { 102 if (auth()->user()->can('update', $question)) //判断当前用户是否有权编辑更新该question实例 103 { 104 //返回编辑视图 105 return view('questions.edit', compact('question')); 106 } else { 107 //返回警告 没有权限 108 return redirect()->back()->with('warning', '你不能编辑不属于你的问题!'); 109 } 110 } 111 112 113 /** Update the specified resource in storage. 114 * @param QuestionStoreRequest $questionStoreRequest 115 * @param Question $question 116 * @return \Illuminate\Http\RedirectResponse 117 */ 118 public function update(QuestionStoreRequest $questionStoreRequest, Question $question) 119 { 120 //更新前 判断下权限 121 if (!(auth()->user()->can('update', $question))) { 122 //返回警告 没有权限 123 return redirect()->back()->with('warning', '你不能编辑不属于你的问题!'); 124 } 125 //取得更新的字段 使用Eloquent提供的update方法执行问题更新 126 $question->update([ 127 'title' => $questionStoreRequest->get('title'), 128 'content' => $questionStoreRequest->get('content'), 129 ]); 130 131 132 //topics的操作这时候看起来有点臃肿 可以使用TopicController来管理,暂时省略 133 //存储topics 134 $topics = $this->questionRepository->normalizeTopics($questionStoreRequest->get('topics')); 135 //使用我们再question model里面添加的topics方法获得 topics关联, 136 //再使用sync方法同步tag 【删除的会被删除掉,没删除的就保留,新的就增加】 137 $question->topics()->sync($topics); 138 139 //更新完成,跳转回去 140 return redirect()->back(); 141 } 142 143 /** 144 * Remove the specified resource from storage. 145 * 146 * @param int $id 147 * @return \Illuminate\Http\Response 148 */ 149 public function destroy($id) 150 { 151 // 152 } 153 154 155 } 156
2.展示全部问题页面,创建index.blade.php
1 @extends('layouts.app') 2 @section('content') 3 <div class="contaier"> 4 <div class="row"> 5 <div class="col-md-8 col-md offset-2"> 6 @forelse($questions as $question) 7 <div class="card mt-4"> 8 <div class="card-header"> 9 <a href="{{ route('questions.show',$question) }}" 10 class="text text-primary">{{ $question->title }}</a> 11 @if(session()->has('warning')) 12 <div class="alert alert-warning">{{ session()->get('warning') }}</div> 13 @endif 14 @can('update',$question) 15 <a href="{{ route('questions.edit',$question) }}" class="btn btn-danger">编辑</a> 16 @endcan 17 @forelse($question->topics as $topic) 18 <p class="badge badge-dark float-md-right m-1">{{ $topic->name }}</p> 19 @empty 20 <p class="badge badge-warning float-md-right "> "No Topics"</p> 21 @endforelse 22 </div> 23 <div class="card-body"> 24 {!! $question->content !!} 25 </div> 26 </div> 27 @empty 28 <p class="text text-danger">找不到问题</p> 29 @endforelse 30 </div> 31 </div> 32 </div> 33 34 @endsection
3.再添加一个新建,【用户可以查看也可以新建】,附带问题属于的用户的数据:
要添加模型关联【数据关联已经在question表里添加了 user_id】
修改Question.php:
Question.php1 <?php 2 3 namespace App\Models; 4 5 use App\Topic; 6 use App\User; 7 use Illuminate\Database\Eloquent\Model; 8 9 class Question extends Model 10 { 11 // 12 protected $fillable = ['title', 'content', 'user_id']; 13 14 public function topics() 15 { 16 return $this->belongsToMany( 17 Topic::class, 18 'questions_topics' //表名我设置的是questions_topics,可能不是系统自动解析的question_topic 19 )->withTimestamps();//withTimestamps操作questions_topics表中create_at及updated_at字段的 20 } 21 //添加user用户模型关联 22 public function user() 23 { 24 return $this->belongsTo(User::class); 25 } 26 } 27user表里应该添加question_id字段
执行:
1 php artisan make:migration add_question_id_to_users_table --table=users
修改****_add_question_id_to_users_table如下:
AddQuestionIdToUsersTable1 <?php 2 3 use Illuminate\Database\Migrations\Migration; 4 use Illuminate\Database\Schema\Blueprint; 5 use Illuminate\Support\Facades\Schema; 6 7 class AddQuestionIdToUsersTable extends Migration 8 { 9 /** 10 * Run the migrations. 11 * 12 * @return void 13 */ 14 public function up() 15 { 16 Schema::table('users', function (Blueprint $table) { 17 // 18 $table->bigInteger('question_id')->after('avatar')->nullable(); 19 }); 20 } 21 22 /** 23 * Reverse the migrations. 24 * 25 * @return void 26 */ 27 public function down() 28 { 29 Schema::table('users', function (Blueprint $table) { 30 // 31 $table->dropColumn('question_id'); 32 }); 33 } 34 } 35执行迁移:如果有不懂的可以查看 laravel 数据库操作(表、字段)
1 php artisan migrate
添加模型关联,修改User.php:
User.php1 <?php 2 3 namespace App; 4 5 use App\Models\Question; 6 use Illuminate\Contracts\Auth\MustVerifyEmail; 7 use Illuminate\Foundation\Auth\User as Authenticatable; 8 use Illuminate\Notifications\Notifiable; 9 10 class User extends Authenticatable implements MustVerifyEmail 11 { 12 use Notifiable; 13 14 /** 15 * The attributes that are mass assignable. 16 * 17 * @var array 18 */ 19 protected $fillable = [ 20 'name', 'email', 'password', 'avatar', 'activation_token' 21 ]; 22 23 /** 24 * The attributes that should be hidden for arrays. 25 * 26 * @var array 27 */ 28 protected $hidden = [ 29 'password', 'remember_token', 30 ]; 31 32 /** 33 * The attributes that should be cast to native types. 34 * 35 * @var array 36 */ 37 protected $casts = [ 38 'email_verified_at' => 'datetime', 39 ]; 40 41 //添加用户模型和问题模型的模型关联 42 public function questions() 43 { 44 return $this->hasMany(Question::class); 45 } 46 } 47 48
根据取得的问题数据,判断其中字段is_hidden确定此问题是否可以被其他用户查看【或者说是否允许发布在首页】
3.Scope :
参考:
Laravel
中模型中可以定义scope
开头方法,这类方法可以模型直接使用。这类方法也称作查询作用域例子:
现有
Post
模型,内部定义一个scopeTitle()
方法1 public function scopeTitle($query, $title="") { 2 return $query->where('title', $title); 3 }第一个参数不可省略。 第二个可以调用时传入。
在控制器中使用该方法 title()1 $posts = Post::where('id', '<', 3)->title('test 2')->orderBy('id', 'desc')->get();必须以scope开头。后面第一个字母大写。
后面括号中第一个必须是Builder,第二个参数可以根据需要定义。
返回值也必须是Builder
我们在Quesion.php添加一个scope方法:
1 <?php 2 3 namespace App\Models; 4 5 use App\Topic; 6 use App\User; 7 use Illuminate\Database\Eloquent\Model; 8 9 class Question extends Model 10 { 11 // 12 protected $fillable = ['title', 'content', 'user_id']; 13 14 public function topics() 15 { 16 return $this->belongsToMany( 17 Topic::class, 18 'questions_topics' //表名我设置的是questions_topics,可能不是系统自动解析的question_topic 19 )->withTimestamps();//withTimestamps操作questions_topics表中create_at及updated_at字段的 20 } 21 22 public function user() 23 { 24 return $this->belongsTo(User::class); 25 } 26 27 /** scope+请求名命名的 28 * @return bool 29 */ 30 public function scopePublished($query) 31 { 32 return $query->where('is_hidden', 'F');//等于F表示不隐藏 33 } 34 } 35
然后就可以在请求Question的时候使用这个方法,下面的代码添加到QuestionRepository中:
1 public function getQuestionPublished() 2 { 3 return Question::published() 4 ->latest('updated_at') //排序按照更新时间 5 ->with('user')//带上用户模型 使用懒加载 6 ->get(); 7 } 8
QuestionRepository代码:
1 <?php 2 3 namespace App\Repositories; 4 5 use App\Models\Question; 6 use App\Topic; 7 8 class QuestionRepository 9 { 10 11 12 public function all() 13 { 14 return Question::query() 15 ->latest() //排序按照时间 16 ->with('user')//带上用户模型 使用懒加载 17 ->get(); 18 } 19 20 public function getQuestionPublished() 21 { 22 return Question::published() 23 ->latest('updated_at') //排序按照更新时间 24 ->with('user')//带上用户模型 使用懒加载 25 ->get(); 26 } 27 28 /** 29 * 根据提供的数据,使用Eloquent创建question,存储到数据库中并且返回该实例 30 * @param array $data 31 * @return mixed 32 */ 33 public function create(array $data) 34 { 35 $question = Question::create($data); 36 return $question; 37 } 38 39 /** 40 * @param array $topics 41 * @return array 42 */ 43 public function normalizeTopics(array $topics) 44 { 45 //返回topic的id序列,如果不是数字,则强制认为是数据库中的topic的id, 46 //这样虽然会漏掉用户如果设置的topic就是数字的情况,但是因为是测试,所以暂时忽略 47 $normalizedTopics = collect($topics)->map(function ($topic) { 48 if (is_numeric($topic))//是数字 49 { 50 //在数据库中找id 51 $num_topic = Topic::query()->find($topic); 52 if (isset($num_topic) && $num_topic->count() > 0) //在数据库中找得到id 53 { 54 //因为是找到了 且是从question处提交,其内部的question_count应该增加1个 55 $num_topic->increment('questions_count'); 56 //返回id 57 return (int)$num_topic->id; 58 } 59 } 60 //否则创建一个新的topic 61 //再之前先判断是否找得到,因为有时候重复提交,select2在提交的数据中不为数字,但是实际上数据库中已经有值 62 $already = Topic::query()->select('id', 'name')->where('name', '=', $topic); 63 64 if ($already->count() > 0) { 65 //因为是找到了 且是从question处提交,其内部的question_count应该增加1个 66 $already->increment('questions_count'); 67 //返回id 68 return $already->first()->id; 69 } 70 $data_of_topic = [ 71 'name' => $topic,//目前view中topic只有一个选择框,没有设置content的输入框 72 'questions_count' => 1,//因为是找到了 且是从question处提交,其内部的question_count应该初始化为1个 73 ]; 74 //入库 75 $newTopic = Topic::create($data_of_topic); 76 //返回id 77 return $newTopic->id; 78 })->toArray(); 79 80 return $normalizedTopics; 81 } 82 }
最后QuestionController中的 index方法为:
现在就会隐藏掉is_hidden为T的question了
1 public function index() 2 { 3 // 4 $questions = $this->questionRepository->getQuestionPublished(); 5 return view('questions.index', compact('questions')); 6 } 7
再在index.blade.php中显示对应的用户信息即可:
1 @extends('layouts.app') 2 @section('content') 3 <div class="contaier"> 4 <div class="row"> 5 <div class="col-md-8 col-md offset-2"> 6 <div class="card"> 7 <div class="card-header"> 8 发布问题 9 </div> 10 <div class="card-body"> 11 <form action="{{ route('questions.store') }}" method="post"> 12 {{--注意要有csrftoken--}} 13 @csrf 14 <div class="form-group"> 15 <label for="title">标题</label> 16 <input type="text" name="title" class="form-control" placeholder="标题" id="title" 17 value="{{ old('title') }}"> 18 <p class="text text-danger"> @error('title') {{ $message }} @enderror </p> 19 </div> 20 <!-- Select2 Topic Select --> 21 <div class="form-group"> 22 <label for="topic_list">选择主题</label> 23 <select id="topic_list" class="js-example-basic-multiple form-control" 24 name="topics[]" multiple></select> 25 </div> 26 <!-- 编辑器容器 --> 27 <script id="container" name="content" type="text/plain" 28 style="width: 100%">{!! old('content') !!}</script> 29 <p class="text text-danger"> @error('content') {{ $message }} @enderror </p> 30 <!--发布按钮--> 31 <button type="submit" class="btn btn-primary mt-2 float-md-right">发布问题</button> 32 </form> 33 </div> 34 </div> 35 36 @forelse($questions as $question) 37 <div class="card mt-4"> 38 <div class="card-header"> 39 <a href="{{ route('questions.show',$question) }}" 40 class="text text-primary">{{ $question->title }}</a> 41 <a href="{{ route('users.show',$question->user) }}" 42 class="btn btn-secondary">{{ $question->user->name }}</a> 43 @if(session()->has('warning')) 44 <div class="alert alert-warning">{{ session()->get('warning') }}</div> 45 @endif 46 @can('update',$question) 47 <a href="{{ route('questions.edit',$question) }}" class="btn btn-danger">编辑</a> 48 @endcan 49 @forelse($question->topics as $topic) 50 <p class="badge badge-dark float-md-right m-1">{{ $topic->name }}</p> 51 @empty 52 <p class="badge badge-warning float-md-right "> "No Topics"</p> 53 @endforelse 54 </div> 55 <div class="card-body"> 56 {!! $question->content !!} 57 </div> 58 </div> 59 @empty 60 <p class="text text-danger">找不到问题</p> 61 @endforelse 62 </div> 63 </div> 64 </div> 65 @endsection 66 @section('footer-js') 67 @include('questions._footer_js') 68 @endsection
注意我们还没有创建用户的显示页面,所以可以暂时用#替换 {{ route('users.show',$question->user) }},就是替换:
1 <a href="{{ route('users.show',$question->user) }}" 2 class="btn btn-secondary">{{ $question->user->name }}</a> 3 替换为: 4 5 <a href="#" 6 class="btn btn-secondary">{{ $question->user->name }}</a>
效果:
4.删除问题按钮 与 QuestionController处理方法destroy逻辑
在问题详细页面,添加一个删除的入口:
因为删除同样涉及到权限判定,所以在QuestionPolicy.php中添加:
1 /** 2 * 判断用户是否有权删除问题 3 * @param User $user 4 * @param Question $question 5 * @return bool 6 */ 7 public function destroy(User $user, Question $question) 8 { 9 return $user->id === $question->user_id; 10 }
1 @can('destroy',$question) 2 <form action="{{ route('questions.destroy',$question) }}" method="post"> 3 @csrf 4 @method('DELETE') 5 <button type="submit" class="btn btn-danger">删除</button> 6 </form> 7 @endcan 8
QuestionController中删除的逻辑为:
1 /**Remove the specified resource from storage. 2 * @param Question $question 3 * @return \Illuminate\Http\RedirectResponse 4 * @throws \Exception 5 */ 6 public function destroy(Question $question) 7 { 8 // 9 if (auth()->user()->can('destroy', $question)) { 10 $question->delete(); 11 return redirect()->route('questions.index')->with('success', "删除成功!"); 12 } 13 return redirect()->back()->with('danger', "你不能删除不属于你的问题!"); 14 } 15
注意我们将之前的
1 <style scoped> 2 .card-body img { 3 width: 100%; 4 } 5 </style> 6 7 从show.blade.php中移除,在app.scss中添加: 8 .card-body img { 9 width: 100%; 10 } 11 然后执行npm run dev 12 13
index.blade.php代码为:
1 @extends('layouts.app') 2 @section('content') 3 <div class="contaier"> 4 <div class="row"> 5 <div class="col-md-8 col-md offset-2"> 6 @foreach(['success','warning','danger'] as $info) 7 @if(session()->has($info)) 8 <div class="card"> 9 <div class="card-body"> 10 <div class="alert alert-{{$info}}">{{ session()->get($info) }}</div> 11 </div> 12 </div> 13 @endif 14 @endforeach 15 <div class="card"> 16 <div class="card-header"> 17 发布问题 18 </div> 19 <div class="card-body"> 20 <form action="{{ route('questions.store') }}" method="post"> 21 {{--注意要有csrftoken--}} 22 @csrf 23 <div class="form-group"> 24 <label for="title">标题</label> 25 <input type="text" name="title" class="form-control" placeholder="标题" id="title" 26 value="{{ old('title') }}"> 27 <p class="text text-danger"> @error('title') {{ $message }} @enderror </p> 28 </div> 29 <!-- Select2 Topic Select --> 30 <div class="form-group"> 31 <label for="topic_list">选择主题</label> 32 <select id="topic_list" class="js-example-basic-multiple form-control" 33 name="topics[]" multiple></select> 34 </div> 35 <!-- 编辑器容器 --> 36 <script id="container" name="content" type="text/plain" 37 style="width: 100%">{!! old('content') !!}</script> 38 <p class="text text-danger"> @error('content') {{ $message }} @enderror </p> 39 <!--发布按钮--> 40 <button type="submit" class="btn btn-primary mt-2 float-md-right">发布问题</button> 41 </form> 42 </div> 43 </div> 44 45 @forelse($questions as $question) 46 <div class="card mt-4"> 47 <div class="card-header"> 48 <a href="{{ route('questions.show',$question) }}" 49 class="text text-primary">{{ $question->title }}</a> 50 <a href="#" 51 class="btn btn-secondary">{{ $question->user->name }}</a> 52 53 @can('update',$question) 54 <a href="{{ route('questions.edit',$question) }}" 55 class="btn btn-info">编辑</a> 56 @endcan 57 58 @can('destroy',$question) 59 <form action="{{ route('questions.destroy',$question) }}" method="post"> 60 @csrf 61 @method('DELETE') 62 <button type="submit" class="btn btn-danger">删除</button> 63 </form> 64 @endcan 65 66 @forelse($question->topics as $topic) 67 <p class="badge badge-dark float-md-right m-1">{{ $topic->name }}</p> 68 @empty 69 <p class="badge badge-warning float-md-right "> "No Topics"</p> 70 @endforelse 71 </div> 72 <div class="card-body"> 73 {!! $question->content !!} 74 </div> 75 </div> 76 @empty 77 <p class="text text-danger">找不到问题</p> 78 @endforelse 79 </div> 80 </div> 81 </div> 82 @endsection 83 @section('footer-js') 84 @include('questions._footer_js') 85 @endsection
show.blade.php代码为:
1 @extends('layouts.app') 2 @section('content') 3 <div class="container"> 4 <div class="row"> 5 <div class="col-md-8 col-md offset-2"> 6 <div class="card"> 7 <div class="card-header"> 8 {{ $question->title }} 9 10 @foreach(['success','warning','danger'] as $info) 11 @if(session()->has($info)) 12 <div class="alert alert-{{$info}}">{{ session()->get($info) }}</div> 13 @endif 14 @endforeach 15 16 @can('update',$question) 17 <a href="{{ route('questions.edit',$question) }}" class="btn btn-warning">编辑</a> 18 @endcan 19 20 @can('destroy',$question) 21 <form action="{{ route('questions.destroy',$question) }}" method="post"> 22 @csrf 23 @method('DELETE') 24 <button type="submit" class="btn btn-danger">删除</button> 25 </form> 26 @endcan 27 28 @forelse($question->topics as $topic) 29 <button class="btn btn-secondary float-md-right m-1">{{ $topic->name }}</button> 30 @empty 31 <p class="text text-warning float-md-right"> "No Topics"</p> 32 @endforelse 33 </div> 34 <div class="card-body"> 35 {!! $question->content !!} 36 </div> 37 </div> 38 </div> 39 </div> 40 </div> 41 @endsection
QuestionPolicy.php代码:
1 <?php 2 3 namespace App\Policies; 4 5 use App\Models\Question; 6 use App\User; 7 use Illuminate\Auth\Access\HandlesAuthorization; 8 9 class QuestionPolicy 10 { 11 use HandlesAuthorization; 12 13 /** 14 * Create a new policy instance. 15 * 16 * @return void 17 */ 18 public function __construct() 19 { 20 // 21 22 } 23 24 25 /** 26 * 判断用户是否有权编辑更新问题 27 * @param User $user 28 * @param Question $question 29 * @return bool 30 */ 31 public function update(User $user, Question $question) 32 { 33 return $user->id === $question->user_id; 34 } 35 36 37 /** 38 * 判断用户是否有权删除问题 39 * @param User $user 40 * @param Question $question 41 * @return bool 42 */ 43 public function destroy(User $user, Question $question) 44 { 45 return $user->id === $question->user_id; 46 } 47 }
QuestionController.php代码为:
1 <?php 2 3 namespace App\Http\Controllers; 4 5 use App\Http\Requests\QuestionStoreRequest; 6 use App\Models\Question; 7 use App\Repositories\QuestionRepository; 8 9 10 class QuestionController extends Controller 11 { 12 13 /** 14 * @var QuestionRepository 15 */ 16 private $questionRepository; 17 18 public function __construct(QuestionRepository $questionRepository) 19 { 20 $this->middleware( 21 'auth', 22 [ 23 'except' => 24 [ 25 'index', 26 'show', 27 ]//非注册用户只能查看不能编辑添加更改删除 28 ] 29 ); 30 31 $this->questionRepository = $questionRepository; 32 } 33 34 35 /** Display a listing of the resource. 36 * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View 37 */ 38 public function index() 39 { 40 // 41 $questions = $this->questionRepository->getQuestionPublished(); 42 return view('questions.index', compact('questions')); 43 } 44 45 46 /** 47 * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View 48 */ 49 public function create() 50 { 51 // 52 return view('questions.create'); 53 } 54 55 56 /** 57 * @param QuestionStoreRequest $request 58 * @return \Illuminate\Http\RedirectResponse 59 */ 60 public function store(QuestionStoreRequest $request)//依赖注入QuestionStoreRequest实例 61 { 62 // 63 // $data = $request->validate([ 64 // 'title' => 'required|min:8', 65 // 'content' => 'required|min:28', 66 // ]); 67 //存储topics 68 $topics = $this->questionRepository->normalizeTopics($request->get('topics')); 69 //初始化question要用到的数据 70 $data = $request->all(); 71 $data['user_id'] = auth()->user()->id; 72 73 // $question=Question::create($data); 被下方代码取代 74 $question = $this->questionRepository->create($data); 75 76 //使用我们再question model里面添加的topics方法获得 topics关联,再使用attach方法 77 $question->topics()->attach($topics); 78 79 return redirect()->route('questions.show', $question); 80 } 81 82 83 /** 84 * @param Question $question 85 * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View 86 */ 87 public function show(Question $question) 88 { 89 //使用关系关联加载,with方法会将分类之下的商品一起查询出来,而且不会出现N+1影响性能的问题 90 $question->with('topics')->get(); 91 92 return view('questions.show', compact('question')); 93 } 94 95 96 /**判断权限 返回视图 97 * @param Question $question 98 * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\View\View 99 */ 100 public function edit(Question $question) 101 { 102 if (auth()->user()->can('update', $question)) //判断当前用户是否有权编辑更新该question实例 103 { 104 //返回编辑视图 105 return view('questions.edit', compact('question')); 106 } else { 107 //返回警告 没有权限 108 return redirect()->back()->with('warning', '你不能编辑不属于你的问题!'); 109 } 110 } 111 112 113 /** Update the specified resource in storage. 114 * @param QuestionStoreRequest $questionStoreRequest 115 * @param Question $question 116 * @return \Illuminate\Http\RedirectResponse 117 */ 118 public function update(QuestionStoreRequest $questionStoreRequest, Question $question) 119 { 120 //更新前 判断下权限 121 if (!(auth()->user()->can('update', $question))) { 122 //返回警告 没有权限 123 return redirect()->back()->with('warning', '你不能编辑不属于你的问题!'); 124 } 125 //取得更新的字段 使用Eloquent提供的update方法执行问题更新 126 $question->update([ 127 'title' => $questionStoreRequest->get('title'), 128 'content' => $questionStoreRequest->get('content'), 129 ]); 130 131 132 //topics的操作这时候看起来有点臃肿 可以使用TopicController来管理,暂时省略 133 //存储topics 134 $topics = $this->questionRepository->normalizeTopics($questionStoreRequest->get('topics')); 135 //使用我们再question model里面添加的topics方法获得 topics关联, 136 //再使用sync方法同步tag 【删除的会被删除掉,没删除的就保留,新的就增加】 137 $question->topics()->sync($topics); 138 139 //更新完成,跳转回去 140 return redirect()->back(); 141 } 142 143 144 /**Remove the specified resource from storage. 145 * @param Question $question 146 * @return \Illuminate\Http\RedirectResponse 147 * @throws \Exception 148 */ 149 public function destroy(Question $question) 150 { 151 // 152 if (auth()->user()->can('destroy', $question)) { 153 $question->delete(); 154 return redirect()->route('questions.index')->with('success', "删除成功!"); 155 } 156 return redirect()->back()->with('danger', "你不能删除不属于你的问题!"); 157 } 158 159 160 }
5.支持软删除:
关于软删除:
- 要让Eloquent模型支持软删除,首先在模型类中要使用
SoftDeletes
trait;- 此外还要设置
$date
属性数组,将deleted_at
置于其中- 然后对应的数据库
posts
中添加deleted_at
列,使用迁移来实现其他资料:
Question.php:
1 <?php 2 3 namespace App\Models; 4 5 use App\Topic; 6 use App\User; 7 use Illuminate\Database\Eloquent\Model; 8 use Illuminate\Database\Eloquent\SoftDeletes; 9 10 class Question extends Model 11 { 12 //软删除 添加 13 use SoftDeletes; 14 // 15 protected $fillable = ['title', 'content', 'user_id']; 16 //支持软删除 添加 17 protected $dates = ['deleted_at']; 18 19 public function topics() 20 { 21 return $this->belongsToMany( 22 Topic::class, 23 'questions_topics' //表名我设置的是questions_topics,可能不是系统自动解析的question_topic 24 )->withTimestamps();//withTimestamps操作questions_topics表中create_at及updated_at字段的 25 } 26 27 public function user() 28 { 29 return $this->belongsTo(User::class); 30 } 31 32 /** scope+请求名命名的 33 * @return bool 34 */ 35 public function scopePublished($query) 36 { 37 return $query->where('is_hidden', 'F');//等于F表示不隐藏 38 } 39 }
执行命令:
1 php artisan make:migration add_deleted_at_to_questions_table --table=questions
修改****_add_deleted_at_to_questions_table.php文件:
1 class AddDeletedAtToQuestionsTable extends Migration 2 { 3 /** 4 * Run the migrations. 5 * 6 * @return void 7 */ 8 public function up() 9 { 10 Schema::table('questions', function (Blueprint $table) { 11 // 12 $table->softDeletes(); 13 }); 14 } 15 16 /** 17 * Reverse the migrations. 18 * 19 * @return void 20 */ 21 public function down() 22 { 23 Schema::table('questions', function (Blueprint $table) { 24 // 25 }); 26 } 27 } 28
执行
1 php artisan migrate
可以看到软删除会在deleted_at列存储时间,但数据并未真正删除: