La Prima Applicazione con Laravel 5.2 - Task List!

Creiamo la nostra prima, semplice applicazione con Laravel 5.2. Una semplice task list, passo dopo passo.
francesco
Francesco Malatesta
04/02/2016 in Tutorial

Introduzione

L'articolo che state per leggere è una traduzione, dalla documentazione ufficiale di Laravel, della pagina che potete trovare a questo indirizzo.


Questa guida di "avvio veloce" fornisce un'introduzione basilare a Laravel ed alle sue funzionalità principali. Partendo dalle migration del database fino alle view, passando per Eloquent ORM, il sistema di routing e quello di valutazione. Senza scordarsi del sistema di templating Blade. Perchè una cosa del genere? Semplice: iniziare è molto più semplice se si ha a disposizione, insieme alla documentazione, qualcosa di più pratico su cui mettere le mani.

Continuando a leggere, impareremo insieme ad usare Laravel attraverso l'uso pratico di una selezione di alcune delle sue feature principali, con l'obiettivo di creare un'applicazione che tenga traccia di tutte le cose che abbiamo da fare. Insomma: la classica "to-do" list. Il codice completo dell'applicazione è già disponibile su GitHub.

Installazione

Per cominciare, abbiamo bisogno di un'installazione fresca fresca del Framework, nella sua ultima versione. Ovviamente dovremo avere a disposizione anche un ambiente di lavoro su cui esercitarci: consiglio di usare Homestead, la vagrant box ufficiale di Laravel. Per creare un nuovo progetto con l'ultima versione di Laravel, eseguiamo

composer create-project laravel/laravel quickstart --prefer-dist

... e via! Al resto penserà il nostro caro Composer. Comodo, vero?

Installazione del Progetto (Opzionale)

Se vuoi, puoi leggerti questo articolo senza andare a dover programmare l'applicazione. Come detto poco fa, infatti, il codice dell'applicazione è già disponibile su un repository GitHub. Esegui le seguenti istruzioni per "portarti avanti con il lavoro".

git clone https://github.com/laravel/quickstart-basic quickstart
cd quickstart
composer install
php artisan migrate

Preparazione del Database

Migration

Innanzitutto, usiamo una migration per definire quella che sarà la struttura delle tabelle, sul database, necessarie a tenere traccia dei nostri task. Il sistema di migration di Laravel è perfetto per definire tali strutture con una sintassi fluida, semplice e soprattutto senza doversi scostare dal PHP. Tra l'altro, le migration fungono anche da sistema di versioning del tuo database: una volta messe sotto source control i nostri colleghi non dovranno fare altro che eseguirle e saranno sempre al passo con lo sviluppo dell'applicazione, senza dover aggiungere manualmente questa o quella colonna.

Torniamo a noi: abbiamo bisogno di una tabella per i task. Il tool da linea di comando di Laravel, Artisan, viene in nostro soccorso. Precisamente, il comando che ci serve è

php artisan make:migration create_tasks_table --create=tasks

Il file di migration verrà piazzato, di default, nella cartella database/migrations del tuo progetto. Inoltre, il comando aggiunge ad ogni file, automaticamente, un timestamp ed un ID univoco. Modifichiamo il file appena creato:

<?php

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

class CreateTasksTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('tasks', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->timestamps();
        });
    }

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

A questo punto la nostra migration è pronta ad essere eseguita per creare la tabella sul database. Nota: nel caso in cui ci stessimo affidando ad Homestead per ciò che riguarda l'ambiente di sviluppo, il comando seguente dovrà essere eseguito dalla shell della macchina virtuale (in quanto la macchina host non ha accesso diretto al suo database).

php artisan migrate

Fatto? Bene! Questo comando creerà le tabelle del database. Andando a dare uno sguardo alla struttura del database (con un qualsiasi client a tua scelta) vedremo la nuova tabella pronta ad essere usata, esattamente come definita nella migration.

Ora che il database c'è, bisogna definire quali model andranno ad interagire con esso. Passiamo a definire i model Eloquent!

I Model Eloquent

Eloquent è l'ORM (object relational mapper) presente out of the box in Laravel. Semplifica tutte quelle operazioni di lettura e scrittura dal/sul database, fornendo un livello di astrazione che trova la sua "concretizzazione" in quelle classi dette model. Nella maggior parte dei casi, per ogni model c'è una tabella nel database.

Detto questo: abbiamo creato la nostra tabella tasks, adesso abbiamo bisogno di un model Task. Anche qui possiamo usare Artisan per generare velocemente il file di cui abbiamo bisogno.

php artisan make:model Task

Di default, il model verrà posizionato nella cartella app dell'applicazione. Allo stato attuale, inoltre, il model è praticamente vuoto. Tra l'altro, non dobbiamo neanche specificare un granchè: Eloquent, infatti, capisce da solo che a Task va collegata la tabella tasks (il suo plurale) andando a controllare i nomi di model e tabelle.

Ecco come appare un model vuoto:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
    //
}

A breve vedremo come collegare al meglio un model Eloquent con una route della nostra applicazione. Nel frattempo, per saperne di più, diamo uno sguardo alla documentazione di Eloquent.

Routing

Siamo pronti ad aggiungere delle route alla nostra applicazione. Cosa sono? Semplice: sono delle definizioni di endpoint, collegate a funzioni che verranno eseguite nel momento in cui un utente punterà il suo browser ad un indirizzo specifico nella nostra applicazione. Di default, le route sono definite in app/Http/routes.php.

Per la nostra applicazione sappiamo già che avremo bisogno di tre route. La prima, per elencare tutti i task memorizzati. La seconda per aggiungerne di nuovi, e la terza per cancellarne uno esistente. Queste tre route verranno raggruppate e tale gruppo verrà assegnato il middleware web, che si occupa di fornire alcune funzionalità quali l'uso delle sessioni e la protezione CSRF. Apriamo il file app/Http/routes.php ed inseriamo:

<?php

use App\Task;
use Illuminate\Http\Request;

Route::group(['middleware' => 'web'], function () {

    /**
     * Show Task Dashboard
     */
    Route::get('/', function () {
        //
    });

    /**
     * Add New Task
     */
    Route::post('/task', function (Request $request) {
        //
    });

    /**
     * Delete Task
     */
    Route::delete('/task/{task}', function (Task $task) {
        //
    });
});

Mostrare una View

Le nostre route, anche se definite, al momento non fanno praticamente nulla. Vediamo di cambiare le cose. Abbiamo la route / che corrisponde alla pagina principale dell'applicazione. Vogliamo fare in modo che qui vengano mostrati i vari task memorizzati nel sistema, oltre ad un form che ne permetta l'aggiunta.

In Laravel, tutte le view sono memorizzate in resources/views. Tramite l'helper view possiamo renderizzare uno di questi template da una route in modo molto semplice.

Route::get('/', function () {
    return view('tasks');
});

Passando la stringa "tasks" all'helper view stiamo chiedendo a Laravel di cercare il file in questione e renderizzarlo. Precisamente, il file cercato è resources/views/tasks.blade.php. Che, tuttavia, ancora non esiste!

Rimediamo.

Costruire Layout e View

Nel caso della nostra applicazione manterremo le cose semplici: una sola view, che conterrà il form per l'aggiunta di un nuovo task e subito dopo la lista di quelli già inseriti. Per aiutarti a visualizzare il risultato finale, ecco uno screenshot di quello che sarà il lavoro finito.

Screenshot Task List

Definiamo il Layout

Quasi tutte le web application esistenti usano, come "fondamenta" per la grafica, lo stesso layout di base. Come appena visto nello screen, nella nostra task list sarà presente una barra di menu che potrebbe tranquillamente essere riproposta su ogni eventuale pagina. Il sistema di template di Laravel rende semplice realizzare dei risultati all'altezza delle aspettative senza troppi sforzi, attraverso Blade.

Tutte le view di un'applicazione Laravel vengono generalmente messe nella directory resources/views. Definiamo quindi una nuova view resources/views/layouts/app.blade.php. L'estensione .blade.php serve a spiegare al framework che per quel file dovrà essere usato il sistema di template.

Nulla, ovviamente, ti vieta di usare il classico PHP, anche se Blade può aumentare enormemente la tua produttività per quanto riguarda i layout e le view.

Ecco come apparirà il nostro file:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Laravel Quickstart - Basic</title>

        <!-- CSS And JavaScript -->
    </head>

    <body>
        <div class="container">
            <nav class="navbar navbar-default">
                <!-- Navbar Contents -->
            </nav>
        </div>

        @yield('content')
    </body>
</html>

Notiamo il @yield('content') presente nel layout. Si tratta di un'istruzione speciale di Blade: indica che tutte le pagine che estendono questo layout dovranno iniettare proprio lì il loro contenuto. Come? Vediamolo subito, definendo una "child view" che faccia uso del layout appena costruito.

Definiamo una Child View

Dunque: abbiamo bisogno di definire una nuova view che contenga un form per la creazione di un nuovo task, ed una tabella che elenchi quelli già presenti. Creiamo un nuovo file resources/views/tasks.blade.php.

Salteremo la parte relativa a Bootstrap per evitare di andare fuori tema. D'altronde, l'intero progetto può essere trovato su GitHub. Ecco il contenuto del file:

@extends('layouts.app')

@section('content')

    <!-- Bootstrap Boilerplate... -->

    <div class="panel-body">
        <!-- Display Validation Errors -->
        @include('common.errors')

        <!-- New Task Form -->
        <form action="{{ url('task') }}" method="POST" class="form-horizontal">
            {!! csrf_field() !!}

            <!-- Task Name -->
            <div class="form-group">
                <label for="task" class="col-sm-3 control-label">Task</label>

                <div class="col-sm-6">
                    <input type="text" name="name" id="task-name" class="form-control">
                </div>
            </div>

            <!-- Add Task Button -->
            <div class="form-group">
                <div class="col-sm-offset-3 col-sm-6">
                    <button type="submit" class="btn btn-default">
                        <i class="fa fa-plus"></i> Add Task
                    </button>
                </div>
            </div>
        </form>
    </div>

    <!-- TODO: Current Tasks -->
@endsection

Prima di andare avanti, vediamo cosa c'è da capire qui. Innanzitutto, questo template estende il layout creato in precedenza tramite la direttiva @extends. Di conseguenza, tutto il contenuto tra @section('content') e @endsection verrà iniettato in corrispondenza della direttiva @yield('content') del layout principale.

La direttiva @include('common.errors') specifica invece un'inclusione di un altro template, che si dovrebbe trovare in resources/views/common/errors.blade.php. Diciamo "dovrebbe" perché non ancora esiste: rimedieremo a breve.

Ora che abbiamo definito un layout ed una view per la nostra applicazione possiamo effettivamente usare il codice qui di seguito

Route::get('/', function () {
    return view('tasks');
});

per mostrare qualcosa all'utente finale.

Adesso facciamo un altro passo avanti, capendo come aggiungere un nuovo task al sistema creando una route POST che risponda all'URL /task.

Aggiunta di Task

Validazione

Dobbiamo aggiungere, finalmente, del codice alla route POST /task per validare l'input inserito dall'utente e creare, di conseguenza, un nuovo task. Partiamo dalla validazione.

Per questo form, diciamo che vogliamo fare in modo che sia obbligatorio inserire il nome del task, e che questo deve avere una lunghezza massima di 255 caratteri. Nel caso in cui la validazione dovesse fallire, il nostro utente deve essere rimandato alla pagina principale con un messaggio extra di "spiegazioni".

Route::post('/task', function (Request $request) {
    $validator = Validator::make($request->all(), [
        'name' => 'required|max:255',
    ]);

    if ($validator->fails()) {
        return redirect('/')
            ->withInput()
            ->withErrors($validator);
    }

    // Creiamo il task...
});

La Variabile $errors

Fermiamoci un secondo per parlare dell'istruzione ->withErrors($validator). Questa chiamata, infatti, mette in una sessione "flash" i dati ottenuti dalla procedura di validazione (gli errori, praticamente) in maniera tale da poter essere visti, nella view successiva, usando la variabile $errors.

Ricordiamoci che poco fa abbiamo usato la direttiva @include('common.errors') per renderizzare una view appositamente concepita per gli eventuali errori. Finalmente, è arrivato il momento di crearla (file resources/views/common/errors.blade.php):

@if (count($errors) > 0)
    <!-- Form Error List -->
    <div class="alert alert-danger">
        <strong>Whoops! Something went wrong!</strong>

        <br><br>

        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif

Nota: la variabile $errors è disponibile in ogni view Laravel. Se non ci sono degli errori di validazione in sessione, tale variabile verrà "popolata" con un'istanza vuota della classe ViewErrorBag.

Creazione del Task

Validata la nostra richiesta possiamo procedere con la creazione vera e propria. Dopo la creazione del task, che ovviamente avverrà facendo uso dei dati passati in POST, l'utente verrà reindirizzato alla schermata principale dell'applicazione. Per creare il task, fondamentalmente, creiamo una nuova istanza del model relativo, popolandone le proprietà e salvandola tramite save().

Così:

Route::post('/task', function (Request $request) {
    $validator = Validator::make($request->all(), [
        'name' => 'required|max:255',
    ]);

    if ($validator->fails()) {
        return redirect('/')
            ->withInput()
            ->withErrors($validator);
    }

    $task = new Task;
    $task->name = $request->name;
    $task->save();

    return redirect('/');
});

Voilà! Fine! Adesso puoi creare tutti i task che vuoi. Rimane solo da capire come visualizzarli! Direi che è arrivato il momento di costruire la nostra lista.

La Lista dei Task

Prima di tutto, dobbiamo tornare a modificare la nostra route /, per fare in modo tale da passare, alla view che abbiamo creato in precedenza, l'insieme dei task già presenti. Come? Così:

Route::get('/', function () {
    $tasks = Task::orderBy('created_at', 'asc')->get();

    return view('tasks', [
        'tasks' => $tasks
    ]);
});

Una volta passati i dati, possiamo usare un qualsiasi ciclo in tasks.blade.php per stamparli in una tabella. Il costrutto @foreach di Blade permette agevolmente di fare una cosa del genere. In questo modo:

@extends('layouts.app')

@section('content')
    <!-- Create Task Form... -->

    <!-- Current Tasks -->
    @if (count($tasks) > 0)
        <div class="panel panel-default">
            <div class="panel-heading">
                Current Tasks
            </div>

            <div class="panel-body">
                <table class="table table-striped task-table">

                    <!-- Table Headings -->
                    <thead>
                        <th>Task</th>
                        <th>&nbsp;</th>
                    </thead>

                    <!-- Table Body -->
                    <tbody>
                        @foreach ($tasks as $task)
                            <tr>
                                <!-- Task Name -->
                                <td class="table-text">
                                    <div>{{ $task->name }}</div>
                                </td>

                                <td>
                                    <!-- TODO: Delete Button -->
                                </td>
                            </tr>
                        @endforeach
                    </tbody>
                </table>
            </div>
        </div>
    @endif
@endsection

La nostra applicazione è quasi completa. Manca solo un'ultima cosa... la possibilità di cancellare un task una volta completato!

Cancellazione dei Task

Aggiungere il Tasto "Delete"

Poco fa abbiamo lasciato una nota "TODO" nel nostro codice, un segnaposto per indicare dove il pulsante di cancellazione dovrebbe trovarsi. Bene, andiamo a fare questa ultima sostituzione. Useremo un semplice pulsante per ogni task in lista. Una volta premuto, partirà una richiesta verso la route DELETE /task.

<tr>
    <!-- Task Name -->
    <td class="table-text">
        <div>{{ $task->name }}</div>
    </td>

    <!-- Delete Button -->
    <td>
        <form action="{{ url('task/'.$task->id) }}" method="POST">
            {!! csrf_field() !!}
            {!! method_field('DELETE') !!}

            <button>Delete Task</button>
        </form>
    </td>
</tr>

Un Cenno sul Method Spoofing

Abbiamo appena visto che la richiesta viene mandata tramite un form per ogni task... ma il method usato è il POST! Nel caso tu non lo sapessi già, i form HTML supportano solo ed esclusivamente richieste GET e POST. Gli altri HTTP Verb, come DELETE appunto, non vengono contemplati. Tramite la funzione method_field, tuttavia, è possibile scavalcare l'ostacolo e mandare a tutti gli effetti una richiesta che verrà, poi, riconosciuta da Laravel come DELETE e non POST.

Precisamente, Laravel controllerà il valore della variabile "speciale" _method inviata nella richiesta.

<input type="hidden" name="_method" value="DELETE">

Problema risolto!

Cancellazione del Task

A questo punto non rimane che creare la logica vera e propria di cancellazione del task desiderato. Per velocizzare il lavoro, possiamo usare una tecnica chiamata "implicit model binding", che fa in modo, partendo dall'id di uno specifico task, di recuperare direttamente la relativa istanza e, quindi, di poterci lavorare agevolmente.

A questo punto, una volta cancellato il task, bisognerà reindirizzare l'utente verso la pagina principale, come al solito.

Route::delete('/task/{task}', function (Task $task) {
    $task->delete();

    return redirect('/');
});

Non è stato poi così difficile... vero?