In questo tutorial vedremo come aggiungere le recensioni ai prodotti del nostro e-commerce in Laravel.

Creiamo un nuovo modello in Artisan aggiungendo il file per effettuare la migration.

php artisan make:model Review  --migration

A questo punto definiamo i campi della tabella del database nel file della migration. Avremo il nome, l'e-mail, il testo della recensione e l'ID del prodotto che abiliterà la relazione one-to-many nel modello del prodotto.

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

class CreateReviewsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('reviews', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name', 255);
            $table->string('email', 255);
            $table->text('review');
            $table->bigInteger('product_id');
            $table->timestamps();
        });
    }

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

A questo punto modifichiamo il file del modello delle recensioni specificando i campi che potremo riempire dal frontend.

namespace App;

use Illuminate\Database\Eloquent\Model;

class Review extends Model
{
    protected $table = 'reviews';
    protected $primaryKey = 'id';
    public $incrementing = true;
    protected $fillable = ['name', 'email', 'review','product_id'];
}

Nel modello del prodotto dobbiamo creare ora il metodo pubblico che abiliterà la relazione one-to-many tra prodotti e recensioni poiché ciascun prodotto può avere più di una recensione associata.

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

Il setup relativo al database è concluso e possiamo quindi dedicarci alla pagina del singolo prodotto dove non solo vi sarà il form per inserire una recensione ma anche l'elenco delle recensioni già inserite ordinate dalla più recente alla meno recente.

Sapendo che Laravel aggiunge i campi created_at e updated_at con i riferimenti temporali alla data di creazione e modifica di un record, nel metodo del controller principale del nostro e-commerce possiamo scrivere:

public function single($slug)
    {
        //...
                
        $reviews = $product->reviews()->orderBy('created_at', 'desc')->get();

        return view('single',[
           'title' => $product->title . ' | PHP E-commerce',
           'product' => $product,
            'reviews' => $reviews
        ]);
}

Nella view del singolo prodotto effettuiamo una verifica sulle recensioni presenti. Se ce ne sono, effettuiamo un loop sull'array $reviews e le mostriamo.

@if(count($ratings) > 0)
            <h2>Reviews</h2>
            @foreach($ratings as $rating)
                <div class="card mt-4 mb-5">
                    <div class="card-body">
                        <h5 class="card-title">{{ $rating->name }}</h5>
                        <p class="card-text">{{ $rating->review }}</p>
                    </div>
                </div>
            @endforeach
        @endif

Subito dopo l'elenco delle recensioni, inseriamo il form per l'invio della recensione:

<form action="" method="post" id="review-form" class="mt-5" novalidate>
            <h2>Leave a review</h2>
            <div class="form-group">
                <label for="name">Name</label>
                <input type="text" name="name" id="name" class="form-control">
            </div>
            <div class="form-group">
                <label for="email">E-mail</label>
                <input type="text" name="email" id="email" class="form-control">
            </div>
            <div class="form-group">
                <label for="review">Review</label>
                <textarea name="review" id="review" class="form-control" rows="5"></textarea>
            </div>
            <p>
                @csrf
                <input type="hidden" name="product_id" value="{{ $product['id'] }}">
                <input type="submit" class="btn btn-success" value="Send">
            </p>
        </form>

Il form contiene anche il campo per il token CSRF richiesto da Laravel per le richieste HTTP POST. Possiamo optare per inviare il form tramite AJAX. Creiamo quindi un metodo dedicato nel nostro controller AJAX.

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use App\Review;

class AjaxController extends Controller
{
    public function createReview(Request $request)
    {
        $messages = [
            'required' => 'Required field.',
            'email' => 'Invalid e-mail address.'
        ];
        $validator = Validator::make($request->all(), [
            'name' => 'required',
            'email' => 'required|email:rfc',
            'review' => 'required'
        ], $messages);

        if ($validator->fails()) {
            return response()->json($validator->messages());
        }

        $review = new Review([
           'name' => strip_tags($request->get('name')),
            'email' => $request->get('email'),
            'review' => strip_tags($request->get('review')),
            'product_id' => (int) $request->get('product_id')
        ]);

        $rating->save();

        return response()->json(['success' => 'Thanks for your review!']);
    }
}    

I dati vengono prima validati. Se ci sono errori, viene restituito un oggetto JSON i cui nomi delle proprietà sono i valori degli attributi name dei campi del form e i loro valori sono array contenenti uno o più messaggi di errore definiti nella variabile $messages. Questo array ha come chiavi il nome dei filtri che Laravel userà e come valori i messaggi che mostreremo nel frontend.

Se non ci sono errori, viene creata una nuova istanza del modello delle recensioni con i dati presi dal form. Viene quindi invocato il metodo Review::save() per salvare i dati nel database.

Usiamo strip_tags() per maggiore sicurezza sulla possibilità di ricevere codice HTML non voluto. In effetti questa misura può apparire ridondante, in quanto Blade già adotta l'escaping per questo tipo di contenuto.

Ora possiamo collegare il nostro metodo AJAX ad una specifica route:

Route::post('/ajax/review', 'AjaxController@createReview')->name('create-review');

Nel frontend con jQuery dobbiamo ricordarci di aggiungere l'header HTTP contenente il token CSRF prima di effettuare la richiesta AJAX.

$.ajaxSetup({
        headers: {
            'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
        }
    });

    if( $( "#review-form" ).length ) {
        $( "#review-form" ).on( "submit", function( e ) {
             e.preventDefault();
             var $form = $( this );
             $form.find( ".alert" ).remove();

             $.post( "/ajax/review", $form.serialize(), function ( res ) {
                if( res.success ) {
                    $form.append( '<div class="alert alert-success mt-4">' + res.success + '</div>' );
                } else {
                    for( var field in res ) {
                        $( "#" + field, $form ).after( '<div class="alert alert-danger mt-4">' + res[field][0] + '</div>' );
                    }
                }
             });

        });
    }

Volendo perfezionare in futuro la nostra implementazione nel frontend, potremmo aggiungere dinamicamente la nostra recensione nel DOM quando siamo certi che la proprietà success è presente il che indica che non ci sono errori di validazione.

Una soluzione alternativa a livello frontend è quella di ricaricare dinamicamente la pagina per mostrare la recensione appena inserita.

// messaggio di successo inserito

setTimeout(function() {
   window.location = location.href;
}, 2000);

location.reload() non andrebbe usato poiché alcuni browser possono mostrare un messaggio di avvertimento agli utenti per confermare l'azione.

Conclusione

L'inserimento delle recensioni è simile a quanto abbiamo visto nel tutorial per la votazione di tipo star rating. Ancora una volta la flessibilità dei modelli di Laravel rende questa operazione molto semplice.