Laravel 5.8 做个知乎 9 —— 编辑问题、显示问题列表 、与创建问题

1 路由

outesweb.php

//首页重定义
Route::get('/','QuestionsController@index');
Route::resource('questions','QuestionsController',[
  'names'=>[
      'create'  =>'question.create',
      'show'    =>'question.show'
  ]
]);

outesapi.php

Route::get('/topics',function (Request $request){
    $topics = AppTopic::select(['id','name'])
      ->where('name','like','%'.$request->query('q').'%')
      ->get();
    return $topics;
});

2 控制器

appHttpControllersQuestionsController.php

<?php

namespace AppHttpControllers;

use AppHttpRequestsStoreQuestionRequest;
//use AppQuestion;
//use AppTopic;
use IlluminateHttpRequest;
use IlluminateSupportFacadesAuth;

use AppRepositoriesQuestionRepository;





class QuestionsController extends Controller
{
    protected  $questionRepository;
    
    /**
     * 依赖注入
     * QuestionsController constructor.
     * @param AppRepositoriesQuestionRepository $questionRepository
     */
    public function __construct(QuestionRepository $questionRepository)
    {
        $this->middleware('auth')->except(['index','show']);
        $this->questionRepository = $questionRepository;
    }
    
    /**
     * Display a listing of the resource.
     *
     * @return IlluminateHttpResponse
     */
    public function index()
    {
        $questions = $this->questionRepository->getQuestionsFeed();
        return view('questions.index',compact('questions'));
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return IlluminateHttpResponse
     */
    public function create()
    {
        //
    
        $oldTopicList = $this->questionRepository->getOldTopicList(old('topics'));
        
        
        
        return view('questions.create',compact('oldTopicList'));
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  IlluminateHttpRequest  $request
     * @return IlluminateHttpResponse
     */
    //public function store(Request $request)
    public function store(StoreQuestionRequest $request)
    {
        /*$rules = [
           'title'  =>'required|min:6|max:196',
           'body'   =>'required|min:20',
           
        ];*/
        /*$message = [
            'body.required'=>'内容不得为空' ,
            'body.min'=>'内容不得少于20个字符' ,
        ];*/
        //自动过滤掉token值
        //$this->validate($request,$rules,$message);
        
        $topics = $this->questionRepository->normalizeTopic($request->get('topics'));
        $data = $request->all();
        $save = [
            'title'     =>$data['title'],
            'body'      =>$data['body'],
            'user_id'   =>Auth::id()
        ];
        //$rs = Question::create($save);
        $rs = $this->questionRepository->create($save);
    
        $rs->topics()->attach($topics);
        
        return redirect()->route('question.show',[$rs->id]);
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return IlluminateHttpResponse
     */
    public function show($id)
    {
        //$question = Question::where('id',$id)->with('topics')->first();
        $question =  $this->questionRepository->byIdWithTopics($id);
        if(!$question){
            abort('404','你可能来到了没有知识的荒漠');
        }
        return view('questions.show',compact('question'));
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return IlluminateHttpResponse
     */
    public function edit($id)
    {
        $question = $this->questionRepository->byId($id);
        
        if (Auth::user()->owns($question)){
            return view('questions.edit',compact('question'));
        }
        return back();
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  IlluminateHttpRequest  $request
     * @param  int  $id
     * @return IlluminateHttpResponse
     */
    public function update(StoreQuestionRequest $request, $id)
    {
        $question = $this->questionRepository->byId($id);
        $topics = $this->questionRepository->normalizeTopic($request->get('topics'));
        $question->update([
            'title'=>$request->get('title'),
            'body' =>$request->get('body')
        ]);
        //sync 同步
        $question->topics()->sync($topics);
        return redirect()->route('question.show',[$question->id]);
        
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return IlluminateHttpResponse
     */
    public function destroy($id)
    {
        DB::beginTransaction();
        $question = $this->questionRepository->byId($id);
        
       
        if(Auth::user()->owns($question)){
            $question = $this->questionRepository->byIdWithTopics($id);
    
            $rs1 = $question->delete();
            
            $topicsIds = $question->topics->keyBy('id')->toArray();
            if ($topicsIds){
                $rs2  = $topics =  $this->questionRepository->normalize2Topic($topicsIds);
            }else{
                $rs2 = true;
            }
    
           
           if($rs1 && $rs2 ){
              DB::commit();
               
           } else{
               DB::rollBack();
           }
            //sync 同步
            if ($topicsIds){
                //移除问题的某些的标签
                //$question->topics()->detach($topics);
                //移除问题的所有的标签
                $question->topics()->detach();
            }
           
           return redirect('/');
        }
        abort('403','你没有权限');
    }
    

}
View Code

3 验证

appHttpRequestsStoreQuestionRequest.php

<?php

namespace AppHttpRequests;

use IlluminateFoundationHttpFormRequest;

class StoreQuestionRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        //是否需要验证
        //return false;
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
          'title'  =>'required|min:6|max:196',
          'body'   =>'required|min:20',
    
        ];
        
    }
    
    public function messages()
    {
        return [
          'body.required'=>'内容不得为空' ,
          'body.min'=>'内容不得少于20个字符' ,
        ];
    }
}
View Code

4 数据仓库层

appRepositoriesQuestionRepository.php

<?php
/**
 * Created by PhpStorm.
 * User: SUN
 * Date: 2021/6/29
 * Time: 17:49
 */
namespace AppRepositories;
use AppQuestion;
use AppTopic;


class QuestionRepository  
{
    /**
     * 根据question的id查找topic 列表
     * @param $id
     */
    public function byIdWithTopics($id)
    {
        $question = Question::where('id',$id)->with('topics')->first();
        return $question;
    }
    
    public  function  create(array $attributes)
    {
        return Question::create($attributes);
    }
    
    /**
     * @param array $topics
     *
     * @return array
     *              
     */
    public function normalizeTopic(array $topics)
    {
        //collect 遍历方法
        return collect($topics)->map(function ($topic){
            //如果是数字
            if(is_numeric($topic)){
                Topic::find($topic)->increment('questions_count');
                return (int)$topic;
            }else{
                 $oldTopic = Topic::where('name','=',$topic)->first();
                 if ($oldTopic){
                     //查得到
                     Topic::find($oldTopic->id)->increment('questions_count');
                     return (int)$oldTopic->id;
                 }else{
                     //查不到
                     $newTopic = Topic::create(['name'=>$topic,'questions_count'=>1]);
                     return $newTopic->id;
                 }
            }
        })->toArray();
    }
    
    
    public function normalize2Topic(array $topics)
    {
        //collect 遍历方法
        return collect($topics)->map(function ($topic){
            if($topic['questions_count']>1){
                //dd($topic['questions_count']);
                $topic = Topic::find($topic['id']);
                $topic->decrement('questions_count');
                return $topic['id'];
            }
            Topic::destroy($topic);
            return $topic['id'];
        });
    }
    
    public function byId($id)
    {
        return Question::find($id);
        
    }
    
    public function getOldTopicList($oldTopics)
    {
        if($oldTopics){
            foreach ( $oldTopics as $v){
                if(is_numeric($v)){
                    $topics[] = $v;
                }else{
                    $oldTopicsList[$v]= $v ;
                }
            }
            if (isset($topics)){
                $oldTopicsList = array_merge(Topic::find($topics)->pluck('name','id')->toArray(),$oldTopicsList);
            }
            
            
        } else{
            $oldTopicsList = [];
        }
        return $oldTopicsList;
    }
    
    /**
     * 问题列表
     */
    public function getQuestionsFeed()
    {
       return Question::published()
         ->latest('updated_at')
         ->with('user')
         ->get();
    }
}
View Code

5 模型

5.1 app/Question.php

<?php

namespace App;

use IlluminateDatabaseEloquentModel;

class Question extends Model
{
    //fillable为白名单,表示该字段可被批量赋值;guarded为黑名单,表示该字段不可被批量赋值。
    protected $fillable = ['title','body','user_id'];
    
    public function isHidden()
    {
        return $this->is_hidden === 'T';
    }
    
    /**
     * laravel自带的功能    使用的时候要把  scope 去掉,并转化为小写
     * @param $query
     *
     * @return mixed
     */
    public function scopePublished($query)
    {
        return $query->where('is_hidden','F');
    }
    
    public function topics()
    {
        //多对多的关系
        //belongsToMany如果第二个参数不是question_topic的话 可以通过第二个参数传递自定义表名
        return $this->belongsToMany(Topic::class,'question_topic')
          ->withTimestamps();
    }
    
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}
View Code

5.2 appTopic.php

<?php

namespace App;

use IlluminateDatabaseEloquentModel;

class Topic extends Model
{
    //
    protected $fillable = ['name','questions_count'];
    
    public function questions()
    {
        return $this->belongsToMany(Question::class)
          ->withTimestamps();
    }
    
}
View Code

6 模板

6.1 resourcesviewsquestionscreate.blade.php

@extends('layouts.app')

@section('content')
     @include('vendor.ueditor.assets')
     <div class="container">
         <div class="row justify-content-center">
            <div class="col-md-8 col-md-offset-2">
                <div class="card">
                    <div class="card-header">发布问题</div>
                    <div class="card-body">
                        <form action="/questions" method="post">
                            {!! csrf_field() !!}
                            <div class="form-group">
                                <label for="title"><h5>标题 </h5> </label>
                                <input id="title" type="text" name="title" class="form-control @error('title') is-invalid @enderror" placeholder="标题" value="{{ old('title') }}">

                                @if($errors->has('title'))
                                    <div class="alert alert-danger">
                                        <ul>
                                            <li>{{ $errors->first('title') }}</li>
                                        </ul>
                                    </div>
                                @endif

                                @if ($errors->any())
                                    <div class="alert alert-danger">
                                        <ul>
                                            @foreach ($errors->all() as $error)
                                                <li>{{ $error }}</li>
                                            @endforeach
                                        </ul>
                                    </div>
                                @endif

                            </div>
                            <div class="form-group">
                                <label for="topic"><h5>话题</h5></label>
                                <select  class="js-example-basic-multiple js-example-data-ajax form-control" name="topics[]" multiple="multiple">

                                    @foreach ($oldTopicList as $k=>$topic)
                                        <option value="{{is_numeric($k)?:$topic}}" selected="selected">
                                            {{ $topic }}
                                        </option>
                                    @endforeach
                                   
                                </select>



                                {{--<select name="topics[]"   class="js-example-basic-multiple js-example-data-ajax form-control" multiple>
                                    @foreach($settings->includes->get('topics') as $option)
                                        <option value="{{ $option->id }}" {{ (collect(old('topics'))->contains($option->id)) ? 'selected':'' }}>{{ $option->name }}</option>
                                    @endforeach
                                </select>

                                <select id="forWhom" name="topics[]" multiple class="form-control chosen">
                                    <option value="">--- Select ---</option>
                                    @foreach ($desgInfo as $key => $value)
                                        <option value="{{ $key }}"
                                                {{ (collect(old('forWhom'))->contains($key)) ? 'selected':'' }}  />
                                        {{ $value }}
                                        </option>
                                    @endforeach
                                </select>--}}

                                
                            </div>
                            <div class="form-group">
                                <label for="container"><h5>描述 </h5> </label>
                                <!--text/plain的意思是将文件设置为纯文本的形式,浏览器在获取到这种文件时并不会对其进行处理-->
                                <!-- 转义 {{ old('body') }} -->
                                <!-- 非转义 {!! old('body') !!} -->
                               
                                <!-- 编辑器容器 -->
                                <script id="container" name="body" type="text/plain" style = "height: 200px;">{!! old('body') !!}</script>
                                
                                <button class="btn btn-success pull-right" type="submit">发布问题</button>
                             </div>
                        </form>

                    </div>
                </div>
            </div>
        </div>
     </div>



    @section('my-js')


    <script type="text/javascript">

                                        


    $(function(){
        $(".js-example-data-ajax").select2({
            tags: true,
            placeholder: '请选择相关的话题',
            minimumInputLength: 1,
            ajax: {
                url: "/api/topics",
                dataType: 'json',
                delay: 250,
                data: function (params) {
                    return {
                        q: params.term, // search term
                        page: params.page
                    };
                },
                processResults: function (data, params) {
                    // parse the results into the format expected by Select2
                    // since we are using custom formatting functions we do not need to
                    // alter the remote JSON data, except to indicate that infinite
                    // scrolling can be used
                    params.page = params.page || 1;

                    return {
                        results: data,
                        pagination: {
                            more: (params.page * 30) < data.total_count
                        }
                    };
                },
                cache: true
            },
            templateResult: formatRepo,
            templateSelection: formatRepoSelection
        });


        
        function formatRepo (repo) {
            if (repo.loading) {
                return repo.text;
            }

            return "<div class='select2-result-repository clearfix'>"+
            "<div class='select2-result-repository__meta'>" +
            "<div class='select2-result-repository__title'>" +
            repo.name?repo.name:"laravel" +
                "</div></div></div>";
        }

        function formatRepoSelection (repo) {
            return repo.name || repo.text;
        }
    });
    </script>


    <!-- 实例化编辑器 -->
    <script type="text/javascript">
        //编辑器
        var ue = UE.getEditor('container', {
            toolbars: [
                ['bold', 'italic', 'underline', 'strikethrough', 'blockquote', 'insertunorderedlist', 'insertorderedlist', 'justifyleft','justifycenter', 'justifyright',  'link', 'insertimage', 'fullscreen']
            ],
            elementPathEnabled: false,
            enableContextMenu: false,
            autoClearEmptyNode:true,
            wordCount:false,
            imagePopup:false,
            autotypeset:{ indent: true,imageBlockLine: 'center' }
        });

        ue.ready(function() {
            ue.execCommand('serverparam', '_token', '{{ csrf_token() }}'); // 设置 CSRF token.
        });

    </script>
    @endsection
@endsection
View Code

6.2 resourcesviewsquestionsedit.blade.php

@extends('layouts.app')

@section('content')
@include('vendor.ueditor.assets')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8 col-md-offset-2">
            <div class="card">
                <div class="card-header">发布问题</div>
                <div class="card-body">
                    <form action="/questions/{{$question->id}}" method="post">
                        {{ method_field('PATCH') }}
                        {!! csrf_field() !!}
                        <div class="form-group">
                            <label for="title"><h5>标题 </h5> </label>
                            <input id="title"  value="{{ $question->title }}" type="text" name="title" class="form-control @error('title') is-invalid @enderror" placeholder="标题">

                            @if($errors->has('title'))
                                <div class="alert alert-danger">
                                    <ul>
                                        <li>{{ $errors->first('title') }}</li>
                                    </ul>
                                </div>
                            @endif

                            @if ($errors->any())
                                <div class="alert alert-danger">
                                    <ul>
                                        @foreach ($errors->all() as $error)
                                            <li>{{ $error }}</li>
                                        @endforeach
                                    </ul>
                                </div>
                            @endif

                        </div>
                        <div class="form-group">
                            <label for="topic"><h5>话题</h5></label>/
                            <select  class="js-example-basic-multiple js-example-data-ajax form-control" name="topics[]" multiple="multiple">
                                @foreach($question->topics as $topic)
                                    <option value="{{$topic->id}}" selected="selected">
                                        {{ $topic->name }}
                                    </option>
                                @endforeach
                            </select>
                        </div>
                        <div class="form-group">
                            <label for="container"><h5>描述 </h5> </label>
                            <!--text/plain的意思是将文件设置为纯文本的形式,浏览器在获取到这种文件时并不会对其进行处理-->
                            <!-- 转义 {{ old('body') }} -->
                            <!-- 非转义 {!! old('body') !!} -->
                            <!-- 编辑器容器 -->
                            <script id="container" name="body" type="text/plain" style = "height: 200px;">{!! $question->body !!}</script>
                            <button class="btn btn-success pull-right" type="submit">发布问题</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>



@section('my-js')


<script type="text/javascript">
$(function(){
$(".js-example-data-ajax").select2({
    tags: true,
    placeholder: '请选择相关的话题',
    minimumInputLength: 1,
    ajax: {
        url: "/api/topics",
        dataType: 'json',
        delay: 250,
        data: function (params) {
            return {
                q: params.term, // search term
                page: params.page
            };
        },
        processResults: function (data, params) {
            // parse the results into the format expected by Select2
            // since we are using custom formatting functions we do not need to
            // alter the remote JSON data, except to indicate that infinite
            // scrolling can be used
            params.page = params.page || 1;

            return {
                results: data,
                pagination: {
                    more: (params.page * 30) < data.total_count
                }
            };
        },
        cache: true
    },
    templateResult: formatRepo,
    templateSelection: formatRepoSelection
});
function formatRepo (repo) {
    if (repo.loading) {
        return repo.text;
    }

    return "<div class='select2-result-repository clearfix'>"+
    "<div class='select2-result-repository__meta'>" +
    "<div class='select2-result-repository__title'>" +
    repo.name?repo.name:"laravel" +
        "</div></div></div>";
}

function formatRepoSelection (repo) {
    return repo.name || repo.text;
}
});
</script>


<!-- 实例化编辑器 -->
<script type="text/javascript">
//编辑器
var ue = UE.getEditor('container', {
toolbars: [
    ['bold', 'italic', 'underline', 'strikethrough', 'blockquote', 'insertunorderedlist', 'insertorderedlist', 'justifyleft','justifycenter', 'justifyright',  'link', 'insertimage', 'fullscreen']
],
elementPathEnabled: false,
enableContextMenu: false,
autoClearEmptyNode:true,
wordCount:false,
imagePopup:false,
autotypeset:{ indent: true,imageBlockLine: 'center' }
});

ue.ready(function() {
ue.execCommand('serverparam', '_token', '{{ csrf_token() }}'); // 设置 CSRF token.
});
</script>
@endsection
@endsection
View Code

6.3  resourcesviewsquestionsindex.blade.php

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8 col-md-offset-2">
              @foreach($questions as $question)
                  <div class="media-left">
                      <a href="">
                          <img width="50px" src="{{$question->user->avatar}}" alt="{{$question->user->name}}">
                      </a>
                  </div>
                  <div class="media-body">
                      <h4 class="media-heading">
                          <a href="/questions/{{$question->id}}"> {{$question->title}}</a>

                      </h4>
                  </div>
              @endforeach
            </div>
        </div>
    </div>

@endsection
View Code

6.4  resourcesviewsquestionsshow.blade.php

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">{{ $question->title }}</div>
                    @foreach($question->topics as $topic)
                        <span > <a class="topic" href="/topic/{{ $topic->id }}">{{$topic->name}}</a></span>

                    @endforeach
                    <div class="card-body card-img">
                       {!! $question->body !!}

                    </div>
                    <div class="action">
                        @if(Auth::check() && Auth::user()->owns($question))
                            <span class="edit" style="display: inline">
                                <a href="/questions/{{$question->id}}/edit">编辑</a>
                                
                                <form action="/questions/{{$question->id}}" method="POST" class="delete-form">
                                    {{method_field("DELETE")}}
                                    {{csrf_field()}}
                                    <button class="button is-naked delete-button">删除</button>
                                </form>
                            </span>
                        @endif
                    </div>
                </div>
            </div>
        </div>
    </div>

@endsection
<style>
    .card-img p img {
        max-width: 100%  !important;
    }
</style>
View Code
原文地址:https://www.cnblogs.com/polax/p/14958594.html