Имплементиране на коментари в Laravel - Част 1

Прегледа 6745 | Коментари

Така или иначе коментарите в блога реших да ги оставя за по-късен етап, та реших да споделя в няколко  статии процеса за имплементиране на коментари в ларавел, които ще използвам и за този блог, пък на някой може да му е интересно, какви простотии ще се изпишат по-надолу.

Има и много по-лесен вариант, като се използва 3rd party за коментари - за пример Discuss или facebook биха свършили чудесна работа, вместо да се пише логика по back end-a.  Дори ще дойдат с една камара благинки, като oauth logins, reply,  user edit на коментарите и т.н. , така че винаги има и по-лесен вариант, ако не ви се пише всичко от нулата. 

Ще имате възможност да ме нахраните стабилно ако желаете, след като коментарите са готови. Ще са прости откъм възможности - към този етап не смятам да добавям  login / register към блога, затова и ще липсва възможността потребителите да редактират коментарите си.

Няколко изречения за Laravel - framework, бих казал най-приятният за работа, със сигурност и най-популярен вмомента, последната една година добавиха ужасно много полезни неща, както излезе 5.0,  та към този момент вече е на 5.7 версия. Laravel залага на MVC , като имплементира Eloquent ORM, който използва Active Record за работа с база данни.

 

1: Добавяне на нужните файлове

Миграциите се използват за менажиране на колоните / таблиците, без да се налага да пипаме по mysql директно.  Първото, което ще направя е да създадем Model, Controller и Migration файлове. Тук идва в помощ Artisan - command line интерфейса на Laravel - изключително приятен и лесен за работа. Отиваме в root директорията на Laravel и пишем в терминала

php artisan make:model Comment -mc --resource

 

Въпросната команда ще генерира 3 файла - Модел, Контролер и Миграция. Модела на файла се намира директно в app/ , контролера се намира в  app/Http/Controllers , а миграцията database/migrations.  --resource ще направи --resource контролер  с готови методи.

На мен ще ми трябва и още един контролер:

 php artisan make:controller Admin\\CommentController --resource

Идеята на втория контролер, е да се заеме с back end логиката, а първият да се заеме с визуализирането на нещата във front end-a. Честа практика е в Ларавел да се използват едни и същи контролери, обаче в различен namespace, в моят случай с този блог, в стандартната папка с контролери, имаме папка Admin, която естествено е в неймспейс Admin, които използвам за добавяне, изтриване, редактиране на неща по блога, докато другият контролер за грижи за визуализацията.

Нещата изглеждат по този начин.

 

2 Миграции

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateCommentsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('comments', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('email');
            $table->text('body');
            $table->integer('user_id')->nullable();
            $table->boolean('active')->default(false);
            $table->unsignedInteger('article_id')->nullable();
            $table->foreign('article_id')->references('id')->on('articles')->onDelete('cascade');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('comments');
    }
}

Както се вижда от кода, Laravel е достатъчно хитър, за да добави множествено число към таблицата ( командата беше : php artisan make:model Comment ).

Това, което ще ни е нужно за коментарите са : name, email, body, user_id ( nullable ), active ( bool ) , timestamps ( идват наготово с ларавел ) и разбира се article_id, което е свързано с таблицата 'articles'.   За одобряване на коментарите, ще използваме много прост механизъм, всеки коментар ще има bool - 'active', който по default ще е false, и след това в админ панела ще се одобряват.

едит: Възможно е да получите грешка ако пък вече имате въпросната таблица в базата ( да кажем празна или с други полета, които вече не ви трябват ). Влизате и триете ръчно от mysql, или може да се възползваме от друга благинка на Laravel -

php artisan tinker

tinker ни позволява да пишем php код в конзолата, като просто трябва да премахнем старата таблица, и ще използваме ларавел, като това става чрез:

Schema::drop('comments')

 

 

Използваме artisan migrate, което ще ни създаде таблица "comments"  като ще ни попълни колони, както сме описали във файла.

php artisan migrate

Тук е възможно да получите грешка, ако нямате таблица "articles" все още ( artisan migrate или artisan migrate:fresh ) обхожда файловете спрямо timestamps имената им (2018_11_02_114211_create_comments_table.php ). Ако articles е след Comments, ще трябва първо да създадете articles и след това comments. Всъщност може да напишете всички миграции в един файл, но не е препоръчително.

Резултата в mysql след това е :

 

3 - Модели

Следващата стъпка е да се нагласят моделите за да може  Eloquent да знае, че имаме релации между двата Модела. Както казах в началото, Eloquent имплементира Active Record,  което прави моделите да изглеждат много чисти откъм код. Например в Doctrine ( Symfony ) всички пропъртита трябва да се изпишат в Модела съответно с гетерите и сетерите им. 

Отиваме в Comment модела и добавяме публичен метод comments() , който ще укаже на Ларавел, че един коментар може да има много коментари. Не подаваме други параметри на hasMany, но ако id колоните са различни от  ID, може да добавим втори и трети параметър, чрез който да укажем точно кои ID-та ни интересуват

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    protected $table = 'articles';
    protected $guarded = [];

  

    public function comments() {
        return $this->hasMany('App\Comment');
    }
}

 

protected $table се използва за да се укаже на ларавел с коя таблица да работи, в случая няма много смисъл, защото по дифолт ларавел се оправя с познаването на имена и плурализацията им ( множественото число за имената на таблиците и съответно единствено число за имената на моделите).

protected $guarded =[]  всъщност оказва на ларавел, кои полета от таблицата НЕ може да се пипат. Ако го оставим празно, това означава, че можем да работим свободно с всички полета.

Правим същото и в Comment модела

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;


class Comment extends Model
{
    protected $guarded = [];

    public function post() {
        return $this->belongsTo('App\Article');
    }

}

Въпросните два метода дефинират релация, която ни позволява бързо да достъпваме коментарите спрямо article чрез $article->comments() , както и $comment->post() 

4 Вюта и html bullshit

Следва скучната част, да направим няколко вюта, които ще използваме за CRUD операциите свързани с коментарите.  Единият от начините, е да разделим напълно html-a на коментарите от articles и да го извикваме чрез Blade секции / или слотове, другият е всичко да се прави в същия файл, където се намира и html-a за статиите. В моя случай ще използвам втория начин, защото така или иначе блогът е доста лек и вютата ми са доста кратки, но ако проекта е голям е препоръчително да се разделят на различни файлове с цел по-добра четимост и редакция. Тук ще го карам през просото

<div class="card mb-4">
        <div class="card-body">
            <h4 class="card-title">Коментари</h4>
                <div class="row">
                    <div class="col-lg-12">
                        <div class="card-body">
                            <p class="small text-muted"> #1 | Автор:  | Дата: </p>
                            <p class="card-text">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.Lo</p>
                        </div>
                        <hr>

                    </div>
                </div>
            <form>
                <div class="form-group row">
                    <label for="name" class="col-2 col-form-label">Име</label>
                    <div class="col-5">
                        <div class="input-group">

                            <input id="name" name="name" type="text" required="required" class="form-control here">
                        </div>
                    </div>
                </div>
                <div class="form-group row">
                    <label for="email" class="col-2 col-form-label">Имейл</label>
                    <div class="col-5">
                        <div class="input-group">

                            <input id="email" name="email" type="text" required="required" class="form-control here">
                        </div>
                    </div>
                </div>
                <div class="form-group row">
                    <label for="body" class="col-2 col-form-label">Коментар</label>
                    <div class="col-7">
                        <textarea id="body" name="body" cols="40" rows="4" required="required" class="form-control"></textarea>
                    </div>
                </div>
                <div class="form-group row">
                    <label for="name" class="col-4 col-form-label">Моля Пресметнете:   2 + 6</label>
                    <div class="col-3">
                        <div class="input-group">
                            <input id="antispam" name="antispam" type="text" required="required" class="form-control here">
                        </div>
                    </div>
                </div>
                <div class="form-group row">
                    <div class=" col-7">
                        <button name="submit" type="submit" class="btn btn-primary">Изпращане</button>
                    </div>
                </div>
            </form>


        </div>

    </div>

 

Резултата е това

 

Получихме една доволно грозна форма, но пък ще ми върши работа. Тук нещата са ясни, 3 полета, и една капча, която ще си е направим сами. Всеки път, когато рендерираме single article, заедно с article, ще пращаме към view-то и един масив от random числа, които ще се събират и ще валидираме резултата.  Доста първобитна капча, но ще свърши работа.

 

#5 - default Comment Controller

  public function store(Request $request,$article_id)
    {

        $this->validate($request, array(
           'name' => 'required|min:2|max:20',
           'email' => 'required|email|max:45',
           'body' => 'required| min:5| max:1000',
           'antispam' => 'required|in:'.$request->get('invisible'),
        ));
        $article = Article::findOrFail($article_id);
        $comment = new Comment();
        $comment->name = $request['name'];
        $comment->email = $request['email'];
        $comment->body = $request['body'];
        $comment->article_id = $article->id;
        $comment->save();
        return back()->with('message', 'Коментара е добавен успешно');
    }

Валидираме коментара, валидираме и капчата, която я получаваме от hidden input field.  Всъщност капчата идва при всяко извикване на ArticleController@single.

 $captcha = array(rand(1,10),rand(1,10));
 $result = array_sum($captcha);
 $captcha[] = $result;

След което двете числа, които събираме + резултата се съдържат в масив, като съответно на първите 2 позиции са числата, а на третата е резултата ( $captcha[2] държи сбора между първите 2 елемента.  Тук сигурно някой който разбира повече започва да си скубе косите и да псува, но този вариант на капчата ме устройва напълно.

Сега ще трябва да променим някои неща по view-то, което изглежда по този начин.

   @foreach($article->comments as $comment)

                        <div class="card-body">
                            <p class="small text-muted"><a id="{{$loop->iteration}}" href="#{{$loop->iteration -1}}">#{{$loop->iteration}}</a>| Автор: {{$comment->name}}  | Дата: {{$comment->created_at}} </p>
                            <p class="card-text">{{$comment->body}}</p>
                        </div>
                        <hr>
             @endforeach


            <form class="small" method="POST" action="{{route('comment.store',$article->id)}}">
                @csrf
                <div class="form-group row ">
                    <label for="name" class="col-2 col-form-label">Име</label>
                    <div class="col-5">
                        <div class="input-group">

                            <input id="name" name="name" type="text" required="required" class="form-control here">
                        </div>
                    </div>
                </div>
                <div class="form-group row">
                    <label for="email" class="col-2 col-form-label">Имейл</label>
                    <div class="col-5">
                        <div class="input-group">

                            <input id="email" name="email" type="text" required="required" class="form-control here">
                        </div>
                    </div>
                </div>
              <input name="invisible" type="hidden" value="{{$captcha[2]}}">
                <div class="form-group row">
                    <label for="body" class="col-2 col-form-label">Коментар</label>
                    <div class="col-7">
                        <textarea id="body" name="body" cols="40" rows="4" required="required" class="form-control"></textarea>
                    </div>
                </div>
                <div class="form-group row">
                    <label for="name" class="col-4 col-form-label">Моля Пресметнете:  {{$captcha[0]}} + {{$captcha[1]}}</label>
                    <div class="col-3">
                        <div class="input-group">
                            <input id="antispam" name="antispam" type="text" required="required" class="form-control here">
                        </div>
                    </div>
                </div>
                <div class="form-group row">
                    <div class=" col-7">
                        <button name="submit" type="submit" class="btn btn-primary">Изпращане</button>
                    </div>
                </div>
            </form>

Ако се чудите що за глупост е това: 

<a id="{{$loop->iteration}}" href="#{{$loop->iteration -1}}">#{{$loop->iteration}}</a>

По този начин може да се скача на въпросните коментари директно, без да се скролва. Същото ще трябва да се направи и за кутията за написване на нов коментар. А защо $loop->iteration -1? -  куц ми е дизайна, и благодарение на хедъра, който е stick-нат горе постоянно,  не се визуализира коректно коментара, на който искаме да скочим и точно навигацията го крие, та затова отиваме на 1 коментар надолу, което ни показва точно коментара който искаме.

 

Засега ще приключа със статията, че стана малко дълга, но до няколко дни ще има и втора част. Евентуално трябва да се направят още малко валидации, особено когато се взема article_id, също така и да има някакъв различен стил коментара, когато шефа е коментирал ( Аз ).   Ще имаме и трета част евентуално, където ще трябва пък да работим с CommentController, обаче този, който се намира в namespace Admin, където ще одобряваме коментари, ще изтриваме коментари и така нататък.

 

Коментари

За Връзка
Можете да ми пишете на remindbg @ gmail.com