1.在question的show blade文件中提供编辑问题的入口【判断用户是否有权编辑,没有权限不显示编辑按钮】:
使用Policy,执行命令:
1 php artisan make:Policy QuestionPolicy
生成 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 * @param User $user 27 * @param Question $question 28 * @return bool 29 */ 30 public function update(User $user, Question $question) 31 { 32 return $user->id === $question->user_id; 33 } 34 }
在 AuthServiceProvider
的 policies
数组属性里添加授权映射关系:
1 <?php 2 3 namespace AppProviders; 4 5 use AppModelsQuestion; 6 use AppPoliciesQuestionPolicy; 7 use IlluminateFoundationSupportProvidersAuthServiceProvider as ServiceProvider; 8 use IlluminateSupportFacadesGate; 9 10 class AuthServiceProvider extends ServiceProvider 11 { 12 /** 13 * The policy mappings for the application. 14 * 15 * @var array 16 */ 17 protected $policies = [ 18 // 'AppModel' => 'AppPoliciesModelPolicy', 19 //添加授权映射关系 20 Question::class => QuestionPolicy::class, 21 ]; 22 23 /** 24 * Register any authentication / authorization services. 25 * 26 * @return void 27 */ 28 public function boot() 29 { 30 $this->registerPolicies(); 31 32 // 33 } 34 } 35 36
使用方法:
视图中:
1 @can('update', $question) 2 <a href="#">编辑</a> 3 @endcanController文件中:
1 auth()->user()->can('update', $question)
关于Policy可以参考:Laravel 用户授权 Gate和Policy ,Laravel Policy 使用 ,Laravel-权限系统
2.在请求编辑交由QuestionController edit方法处理的时候,判断用户是否有权编辑,返回问题编辑的页面view,并传入question参数,
1 /**判断权限 返回视图 2 * @param Question $question 3 * @return IlluminateContractsViewFactory|IlluminateHttpRedirectResponse|IlluminateViewView 4 */ 5 public function edit(Question $question) 6 { 7 if (auth()->user()->can('update', $question)) //判断当前用户是否有权编辑更新该question实例 8 { 9 //返回编辑视图 10 return view('questions.edit', compact('question')); 11 } else { 12 //返回警告 没有权限 13 return redirect()->back()->with('warning', '你不能编辑不属于你的问题!'); 14 } 15 } 16
跳转没有权限需要给一个提示给用户,所以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 @if(session()->has('warning')) 10 <div class="alert alert-warning">{{ session()->get('warning') }}</div> 11 @endif 12 @can('update',$question) 13 <a href="{{ route('questions.edit',$question) }}" class="btn btn-danger">编辑</a> 14 @endcan 15 @forelse($question->topics as $topic) 16 <button class="btn btn-secondary float-md-right m-1">{{ $topic->name }}</button> 17 @empty 18 <p class="text text-warning float-md-right"> "No Topics"</p> 19 @endforelse 20 </div> 21 <div class="card-body"> 22 {!! $question->content !!} 23 </div> 24 </div> 25 </div> 26 </div> 27 </div> 28 <style scoped> 29 .card-body img { 30 100%; 31 } 32 </style> 33 @endsection
3.展示question edit编辑视图view
如果有权限的,视图如下:
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 发布问题 9 </div> 10 <div class="card-body"> 11 <form action="{{ route('questions.update',$question) }}" method="post"> 12 {{--注意要有csrftoken--}} 13 @csrf 14 @method('PUT') 15 <div class="form-group"> 16 <label for="title">标题</label> 17 <input type="text" name="title" class="form-control" placeholder="标题" id="title" 18 value="{{ $question->title }}"> 19 <p class="text text-danger"> @error('title') {{ $message }} @enderror </p> 20 </div> 21 <!-- Select2 Topic Select --> 22 <div class="form-group"> 23 <label for="topic_list">选择主题</label> 24 <select id="topic_list" class="js-example-basic-multiple form-control" 25 name="topics[]" multiple> 26 @forelse( $question->topics as $topic ) 27 <option value="{{ $topic->id }}" selected>{{ $topic->name }}</option> 28 @empty 29 @endforelse 30 </select> 31 </div> 32 <!-- 编辑器容器 --> 33 <script id="container" name="content" type="text/plain" 34 style=" 100%">{!! $question->content !!}</script> 35 <p class="text text-danger"> @error('content') {{ $message }} @enderror </p> 36 <!--发布按钮--> 37 <button type="submit" class="btn btn-primary mt-2 float-md-right">更新问题</button> 38 </form> 39 </div> 40 </div> 41 </div> 42 </div> 43 </div> 44 @endsection 45 @section('footer-js') 46 <script type="text/javascript"> 47 // 实例化编辑器 48 var ue = UE.getEditor('container', { 49 toolbars: [ 50 ['bold', 'italic', 'underline', 'strikethrough', 'blockquote', 'insertunorderedlist', 'insertorderedlist', 'justifyleft', 'justifycenter', 'justifyright', 'link', 'insertimage', 'fullscreen'] 51 ], 52 elementPathEnabled: false, 53 enableContextMenu: false, 54 autoClearEmptyNode: true, 55 wordCount: false, 56 imagePopup: false, 57 autotypeset: {indent: true, imageBlockLine: 'center'} 58 }); 59 ue.ready(function () { 60 ue.execCommand('serverparam', '_token', '{{ csrf_token() }}'); // 设置 CSRF token. 61 }); 62 $(document).ready(function () { 63 // Select2多选js 64 $('.js-example-basic-multiple').select2({ 65 // 设置属性及初始化值 66 tags: true, 67 placeholder: '选择相关话题', 68 miniumInputLength: 2, 69 70 ajax: { 71 url: '/api/topics', 72 dataType: 'json', 73 // Additional AJAX parameters go here; see the end of this chapter for the full code of this example 74 delay: 250, 75 data: function (params) { 76 return { 77 // term : The current search term in the search box. 78 // q : Contains the same contents as term. 79 // _type: A "request type". Will usually be query, but changes to query_append for paginated requests. 80 // page : The current page number to request. Only sent for paginated (infinite scrolling) searches. 81 q: params.term, // search term 82 // page: params.page 暂时不需要分页 83 }; 84 }, 85 processResults: function (data, params) { 86 // 解析结果为Select2期望的格式 87 // parse the results into the format expected by Select2 88 // since we are using custom formatting functions we do not need to 89 // alter the remote JSON data, except to indicate that infinite 90 // scrolling can be used 91 return { 92 results: data 93 }; 94 }, 95 cache: true, 96 }, 97 //模板样式 98 templateResult: formatTopic, 99 //模板样式 【选择项】 100 templateSelection: formatTopicSelection, 101 escapeMarkup: function (markup) { 102 return markup; 103 } 104 }); 105 }); 106 107 //格式化话题 108 function formatTopic(topic) { 109 return "<div class='select2-result-repository clearfix'>" + 110 "<div class='select2-result-repository__meta'>" + 111 "<div class='select2-result-repository__title'>" + 112 topic.name ? topic.name : "Laravel" + 113 "</div></div></div>"; 114 } 115 116 //格式化话题选项 117 function formatTopicSelection(topic) { 118 return topic.name || topic.text; 119 } 120 </script> 121 @endsection
更改说明:
我们原样输出了之前问题里的数据,然后使用method put来进行更新【method域】,更改了action 属性值为questions.update路由,路由传入了$question参数
注:option因为是用户之前已经选过,所以要selected
4.提交更新结果到QuestionController的update方法内部处理逻辑:
1 /** Update the specified resource in storage. 2 * @param QuestionStoreRequest $questionStoreRequest 3 * @param Question $question 4 * @return IlluminateHttpRedirectResponse 5 */ 6 public function update(QuestionStoreRequest $questionStoreRequest, Question $question) 7 { 8 //更新前 判断下权限 9 if (!(auth()->user()->can('update', $question))) { 10 //返回警告 没有权限 11 return redirect()->back()->with('warning', '你不能编辑不属于你的问题!'); 12 } 13 14 //取得更新的字段 使用Eloquent提供的update方法执行问题更新 15 $question->update([ 16 'title' => $questionStoreRequest->get('title'), 17 'content' => $questionStoreRequest->get('content'), 18 ]); 19 20 21 //topics的操作这时候看起来有点臃肿 可以使用TopicController来管理,暂时省略 22 //存储topics 23 $topics = $this->questionRepository->normalizeTopics($questionStoreRequest->get('topics')); 24 //使用我们再question model里面添加的topics方法获得 topics关联, 25 //再使用sync方法同步tag 【删除的会被删除掉,没删除的就保留,新的就增加】 26 $question->topics()->sync($topics); 27 28 //更新完成,跳转回去 29 return redirect()->back(); 30 }
注:可以参考 推荐使用 更新一下授权判断:
1 //更新前 判断下权限 2 if (!(auth()->user()->can('update', $question))) { 3 //返回警告 没有权限 4 return redirect()->back()->with('warning', '你不能编辑不属于你的问题!'); 5 } 6改为
1 //更新前 判断下权限 2 if (!($this->authorize('update', $question))) { 3 //返回警告 没有权限 4 return redirect()->back()->with('warning', '你不能编辑不属于你的问题!'); 5 }这样用户不具有更新问题的权限时候会直接跳403权限不足:
更新后代码:
QuestionController.php1 <?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 * 37 * @return IlluminateHttpResponse 38 */ 39 public function index() 40 { 41 // 42 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 ($this->authorize('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 (!($this->authorize('update', $question))) { 122 //返回警告 没有权限 123 return redirect()->back()->with('warning', '你不能编辑不属于你的问题!'); 124 } 125 126 //取得更新的字段 使用Eloquent提供的update方法执行问题更新 127 $question->update([ 128 'title' => $questionStoreRequest->get('title'), 129 'content' => $questionStoreRequest->get('content'), 130 ]); 131 132 133 //topics的操作这时候看起来有点臃肿 可以使用TopicController来管理,暂时省略 134 //存储topics 135 $topics = $this->questionRepository->normalizeTopics($questionStoreRequest->get('topics')); 136 //使用我们再question model里面添加的topics方法获得 topics关联, 137 //再使用sync方法同步tag 【删除的会被删除掉,没删除的就保留,新的就增加】 138 $question->topics()->sync($topics); 139 140 //更新完成,跳转回去 141 return redirect()->back(); 142 } 143 144 /** 145 * Remove the specified resource from storage. 146 * 147 * @param int $id 148 * @return IlluminateHttpResponse 149 */ 150 public function destroy($id) 151 { 152 // 153 } 154 155 156 } 157
我这里暂时不去实现authorized失败之后页面的定制,所以没有使用$this->authorize方法。
最后QuestionContoller.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 * 37 * @return IlluminateHttpResponse 38 */ 39 public function index() 40 { 41 // 42 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 157
效果示例:
打开 http://zhihu.test/questions/16
打开 http://zhihu.test/questions/16/edit
打开没有权限的示例【我用tinker设置了user_id=10,当前用户id是3】:
打开http://zhihu.test/questions/18 可以看到,没有编辑按钮
手动输入url打开http://zhihu.test/questions/18/edit 看到提示,【注意代码bug因为上面有redirect()->back()如果之前是从http://zhihu.test/questions/18/edit 打开 然后一直会跳回 导致请求跳转过多异常,不过实际使用这种情况很少见这里就忽略了】