Laravel Vuejs 实战:开发知乎 (15)问题 Feed 和删除问题

1.取全部数据:

将all方法添加到QuestionRepository:

  1 <?php
  2 
  3 namespace AppRepositories;
  4 
  5 use AppModelsQuestion;
  6 use AppTopic;
  7 
  8 class QuestionRepository
  9 {
 10 
 11     /**
 12      * @return Question[]|IlluminateDatabaseEloquentCollection
 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 
QuestionRepository.php

修改QuestionController index方法逻辑:

  1 <?php
  2 
  3 namespace AppHttpControllers;
  4 
  5 use AppHttpRequestsQuestionStoreRequest;
  6 use AppModelsQuestion;
  7 use AppRepositoriesQuestionRepository;
  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 IlluminateContractsViewFactory|IlluminateViewView
 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 IlluminateContractsViewFactory|IlluminateViewView
 48      */
 49     public function create()
 50     {
 51         //
 52         return view('questions.create');
 53     }
 54 
 55 
 56     /**
 57      * @param QuestionStoreRequest $request
 58      * @return IlluminateHttpRedirectResponse
 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 IlluminateContractsViewFactory|IlluminateViewView
 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 IlluminateContractsViewFactory|IlluminateHttpRedirectResponse|IlluminateViewView
 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 IlluminateHttpRedirectResponse
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 IlluminateHttpResponse
148      */
149     public function destroy($id)
150     {
151         //
152     }
153 
154 
155 }
156 
QuestionController.php

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
index.blade.php

3.再添加一个新建,【用户可以查看也可以新建】,附带问题属于的用户的数据:

要添加模型关联【数据关联已经在question表里添加了 user_id】

修改Question.php:

  1 <?php
  2 
  3 namespace AppModels;
  4 
  5 use AppTopic;
  6 use AppUser;
  7 use IlluminateDatabaseEloquentModel;
  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 }
 27 
Question.php

user表里应该添加question_id字段

执行:

  1 php artisan make:migration add_question_id_to_users_table --table=users

修改****_add_question_id_to_users_table如下:

  1 <?php
  2 
  3 use IlluminateDatabaseMigrationsMigration;
  4 use IlluminateDatabaseSchemaBlueprint;
  5 use IlluminateSupportFacadesSchema;
  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 
AddQuestionIdToUsersTable

执行迁移:如果有不懂的可以查看 laravel 数据库操作(表、字段)

  1 php artisan migrate

添加模型关联,修改User.php:

  1 <?php
  2 
  3 namespace App;
  4 
  5 use AppModelsQuestion;
  6 use IlluminateContractsAuthMustVerifyEmail;
  7 use IlluminateFoundationAuthUser as Authenticatable;
  8 use IlluminateNotificationsNotifiable;
  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 
User.php

根据取得的问题数据,判断其中字段is_hidden确定此问题是否可以被其他用户查看【或者说是否允许发布在首页】


3.Scope :

参考:

Laravel 模型使用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();

Laravel模型的scope过滤:

必须以scope开头。后面第一个字母大写。
后面括号中第一个必须是Builder,第二个参数可以根据需要定义。
返回值也必须是Builder


Laravel 模型的 scope 普通用法和全局用法

我们在Quesion.php添加一个scope方法:

  1 <?php
  2 
  3 namespace AppModels;
  4 
  5 use AppTopic;
  6 use AppUser;
  7 use IlluminateDatabaseEloquentModel;
  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 中添加scope方法

然后就可以在请求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 AppRepositories;
  4 
  5 use AppModelsQuestion;
  6 use AppTopic;
  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 }
QuestionRepository.PHP

最后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=" 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
index.blade.php

注意我们还没有创建用户的显示页面,所以可以暂时用#替换 {{ 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>
index.blade.php

效果:

批注 2020-02-29 155105


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 IlluminateHttpRedirectResponse
  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      100%;
  4 }
  5 </style>
  6 
  7 从show.blade.php中移除,在app.scss中添加:
  8 .card-body img {
  9      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=" 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
index.blade.php

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
show.blade.php

QuestionPolicy.php代码:

  1 <?php
  2 
  3 namespace AppPolicies;
  4 
  5 use AppModelsQuestion;
  6 use AppUser;
  7 use IlluminateAuthAccessHandlesAuthorization;
  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 }
QuestionPolicy.php

QuestionController.php代码为:

  1 <?php
  2 
  3 namespace AppHttpControllers;
  4 
  5 use AppHttpRequestsQuestionStoreRequest;
  6 use AppModelsQuestion;
  7 use AppRepositoriesQuestionRepository;
  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 IlluminateContractsViewFactory|IlluminateViewView
 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 IlluminateContractsViewFactory|IlluminateViewView
 48      */
 49     public function create()
 50     {
 51         //
 52         return view('questions.create');
 53     }
 54 
 55 
 56     /**
 57      * @param QuestionStoreRequest $request
 58      * @return IlluminateHttpRedirectResponse
 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 IlluminateContractsViewFactory|IlluminateViewView
 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 IlluminateContractsViewFactory|IlluminateHttpRedirectResponse|IlluminateViewView
 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 IlluminateHttpRedirectResponse
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 IlluminateHttpRedirectResponse
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 }
QuestionController.php

5.支持软删除:

关于软删除:

Laravel软删除


  • 要让Eloquent模型支持软删除,首先在模型类中要使用SoftDeletestrait;
  • 此外还要设置$date属性数组,将deleted_at置于其中
  • 然后对应的数据库posts中添加deleted_at列,使用迁移来实现

其他资料:

Laravel 软删除操作  laravel 软删除softDelete

Question.php:

  1 <?php
  2 
  3 namespace AppModels;
  4 
  5 use AppTopic;
  6 use AppUser;
  7 use IlluminateDatabaseEloquentModel;
  8 use IlluminateDatabaseEloquentSoftDeletes;
  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列存储时间,但数据并未真正删除:

批注 2020-02-29 202302

原文地址:https://www.cnblogs.com/dzkjz/p/12383060.html