Tutorial: sviluppare un semplice blog con Laravel

Web Development 2934 Leggi in circa 13 minuti

Se hai già letto la mia introduzione ai Framework PHP, sei nel posto giusto. Se invece non l'hai ancora fatto per questa volta sei perdonato, ma corri a leggerla subito!

Spiegando velocemente alcuni dei vantaggi dei framework PHP, ci eravamo lasciati con una domanda: perché abbiamo scelto proprio Laravel? Quali sono i vantaggi rispetto agli altri framework? Scopriamolo subito.

I vantaggi

Documentazione

Esatto, proprio così. Lo so che molti sviluppatori hanno la brutta abitudine di non leggere mai le documentazioni passando subito al codice (me compreso). Però, c'è da dire che la documentazione di Laravel è veramente completa, è forse una delle poche su cui riesco a muovermi con facilità riuscendo sempre a trovare ciò che mi serve in poco tempo. Gli esempi al suo interno sono molto semplici da capire e spiegati al meglio, a volte sono anche autoesplicativi: anche se sei nuovo su Laravel, ti basta guardare una riga del suo codice per intuire il comportamento e la logica che ci sta dietro.

Curva di apprendimento alle stelle

È semplice, fin troppo. Ti troverai subito a tuo agio con Laravel. Se hai già provato altri framework, ti dimenticherai presto i loro nomi. La struttura dei vari file è molto ordinata. Controllers, models e views si integrano talmente bene che ti sembrerà quasi naturale: "perché non ti ho trovato prima?".

Eloquent ORM

Iniziamo con un esempio: nel mio database, ho una tabella posts, con i campi title, content e user_id. Voglio prendere dal database tutti i post? Dopo aver definito la relativa model, dopo vedremo come, potrei fare così:

$posts = Post::all();

Facile, vero? In questo caso i miei posts sono legati all'utente che li ha scritti attraverso il campo user_id. Una volta configurate le varie relazioni fra le due models, posso ricevere da Eloquent un oggetto completo di tutte le mie relationships:

$post = Post::with('user')->first();

In questo modo abbiamo preso il primo post che c'è nella nostra tabella, dicendo ad Eloquent di caricarne anche la relazione user collegata, ed il risultato è il seguente:

{
    id: 1,
    title: 'Il mio fantastico post',
    content: 'Il mio bellisimo contenuto',
    user: {
        id: 1,
        name: 'Angelo'
    }
}

$post->title conterrà "Il mio fantastico post" e $post->user->name conterrà "Angelo".

Awesome! In questo caso, una query leggermente più complessa che in raw SQL avremmo dovuto fare con una JOIN, si riduce ad una riga di pochi caratteri. Con Eloquent, query da decine di righe possono essere riassunte in una singola riga.

Let's do it!

Ma ora realizziamo il nostro primo progetto con Laravel!

Installazione

Laravel utilizza il gestore di pacchetti PHP composer, una volta installato composer potremo procedere all'installazione attraverso questo comando:

composer global require "laravel/installer=~1.1"

Ricordati di aggiungere la directory contenente l'eseguibile laravel al PATH di sistema, altrimenti non verrà riconosciuto il comando laravel

export PATH="~/.composer/vendor/bin:$PATH"

Ora non ci resta altro che creare il nostro progetto con il comando

laravel new blog

Struttura Directory

Elenchiamo la struttura delle directory e dei file più importanti per il tutorial che iniziamo oggi:

  • app
    • Http
      • Controllers
      • Requests
      • routes.php
  • config
  • database
    • migrations
  • resources
    • lang
    • views
  • storage
    • logs

Durante il tutorial spiegeremo quali file troverete dentro a queste cartelle e a cosa servono.

Configurazione

Ci sono due modi per configurare laravel, il primo è attraverso il file .env modificando le variabili dell'environment, il secondo attraverso i file dentro la cartella /config. Noi tratteremo solo il secondo metodo dato che è più semplice, ma fate attenzione al file .env che ha la priorità sulle impostazioni. Di default laravel crea un file .env.example, quindi disattivo. Nella directory /config troverete alcuni file, fra i più importanti app.php e database.php, che serviranno per modificare alcune impostazioni base della nostra applicazione e i vari dati del database.

Dobbiamo subito cambiare la key, per funzionare correttamente deve essere una stringa di 32 caratteri. Potete cambiarlo con php artisan key:generate da terminale.

I file sono autoesplicativi e molto semplici, quindi non ci soffermiamo troppo sulla questione. Se avete problemi potete scriverci attraverso i commenti.

Migrations

Laravel ha al suo interno un sistema per tenere traccia delle modifiche al database, un "versioning" del database. Le migrations stanno alla base di questo sistema. Qualora dovessimo creare, modificare o eliminare le tabelle del nostro database, Laravel consiglia di utilizzare una migration, invece di intervenire direttamente sul database. Facendo così, possiamo facilmente tornare indietro ad uno stato precedente del database. Tu starai pensando: "ok, ma non posso usare PHPMyAdmin?". Certo, Laravel integra delle features, ma non ti obbliga ad utilizzarle, però tutte queste features sono state pensate per mantenere l'architettura della vostra applicazione ordinata. Nel caso in cui devi collaborare con più persone sullo stesso progetto, installarlo su più macchine diverse, interagire con varie versioni diverse che hanno database personalizzati, non ti basterà che copiare i file del progetto, cambiare le impostazioni del database e digitare nel terminale php artisan migrate, per generare la struttura corretta del database in un istante.

Larvel integra già una migration che crea la tabella users, la trovate in /database/migrations, diamo quindi il comando da terminale, spostandoci nella directory del progetto:

cd blog
php artisan make:migration create_posts_table --create=posts

che genererà il nostro file della migration, composto da una classe contenente due funzioni, up e down. Ovviamente la prima sarà richiamata durante il comando migrate, la seconda invece durante un rollback per tornare allo stato precedente del db.

public function up(){
    Schema::create('posts', function (Blueprint $table) {
        $table->increments('id');
        $table->string('title');
        $table->text('content');
        $table->timestamps();
        $table->integer('user_id')->unsigned();
    });
}

Ho aggiunto gli attributi title, content e user_id alla migration di posts. $table->timestamps() aggiunge alla tabella due colonne di tipo timestamp: created_at e updated_at, che vengono gestite automaticamente da Laravel. Ricordati sempre di creare tutte le chiavi unsigned, come nell'esempio sopra. Il prossimo passo è creare la migration che definisce i costraints per la foreign key user_id. Attenzione però all'ordine delle vostre migrations: non potete definire una foreign key, su una colonna che non è ancora stata creata. Ad esempio, io ho una migration per gli utenti ed un'altra per i posts. Nel caso in cui fosse stata creata prima la migration dei posts, Laravel eseguirebbe in ordine posts, e poi users. Se avessi messo i vincoli della foreign key nella stessa migration dei posts, non avrebbe funzionato, dato che user_id si riferisce alla colonna id di users, che ancora non è stata creata. Per evidenziare questo tipo di problema ho scelto di creare un'altra migration per i constraints, ma se fate attenzione all'ordine delle migrations nessuno ti vieta di definirle nella specifica migration, in questo caso funzionerebbe in entrambi i modi.

Detto questo, diamo:

php artisan make:migration define_post_user_constraints --table=posts

Quindi modifichiamo la migration in questo modo:

public function up(){
    Schema::table('posts', function (Blueprint $table) {
        $table->foreign('user_id')->references('id')->on('users');
    });
}
public function down(){
    Schema::table('posts', function (Blueprint $table) {
        $table->dropForeign('posts_user_id_foreign');
    });
}

Ovviamente durante la creazione del constraint potete decidere le azioni da svolgere durante gli eventi on delete e on update utilizzando le funzioni ->onDelete('cascade'); e ->onUpdate('cascade');. Le foreign_key vengono chiamate nome_tabella_nome_foreign_key_foreign, quindi in questo caso posts_user_id_foreign. Una volta terminate le modifiche, ritorniamo sul nostro terminale e diamo il via alla procedura di aggiornamento della struttura del database con il comando

php artisan migrate

Models

Una volta creato il nostro database, dobbiamo definire le Model inerenti alle nostre tabelle. Le model sono le classi che rappresentano le entità di dati che andremo a manipolare nel database. Vediamo come crearle.

Ancora una volta, utilizziamo artisan per creare la model inerente alla tabella posts

php artisan make:model Post

Uno shortcut che ho imparato col tempo è quello di aggiungere -m al comando di creazione della Model, in questo modo vengono create sia model che migration inerente, con il nome della tabella corretto, secondo le convenzioni utilzzate da Laravel.

php artisan make:model Post -m

Il file verrà creato sotto /app, dove troverete già di default la model per la tabella users (User.php). I nomi delle models dovranno essere in singolare ed in CamelCase, al contrario dei nomi delle tabelle, in plurale e in snake_case. Laravel non ha bisogno di sapere il nome o gli attributi della tabella, li troverà da solo. Tutte queste convenzioni sui nomi dei file o delle tabelle servono per mantenere standardizzato il nostro database durante il lavoro di gruppo e per facilitarci il lavoro. Di default Laravel cercherà la tabella con nome lowercase e plurale della model.

Ma allora a cosa servono le model se fa tutto lui?

Nel caso in cui siamo cocciuti e decidiamo di rompere gli standard Laravel, possiamo fornire il nome della tabella così:

class User extends Model {
    //...
    protected $table = 'my_users';
    //...
}

Nel caso in cui siamo ancora più cocciuti e non forniamo la chiave primaria chiamata id, possiamo specificarla con l'attributo di classe primaryKey.

Nel nostro tutorial, le Models ci serviranno per definire la relazione fra i posts e l'utente. È una relazione di tipo 1:N, un utente può possedere più posts, ma un posts è legato solo ad un utente. Quindi apriamo la model User.php e aggiungiamo un metodo alla classe:

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

In questo modo diciamo a Laravel che la Model User è legata a "Many" Post. Per definire la relazione inversa, nel file Post.php:

public function author(){
    return $this->belongsTo('App\User','user_id');
}

Ricordiamoci che le Model sono sotto il namespace App\. Ho anche specificato quale foreign key deve utilizzare Laravel, perché ho utilizzato un nome author non convenzionale. Se avessi usato user avrebbe intuito da solo la foreign key. Ma quindi ora cosa succede? Se ad esempio, vogliamo prendere i posts dell'utente con id=1:

$posts = User::find(1)->posts;

Al contrario, se dato un post voglio l'autore.

$author = Post::find(1)->with('author')->get();

Routing

Nel file routes.php situato in /app/Http possiamo definire le routes della nostra applicazione. Troverai già una route di base, ma come funziona?

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

Possiamo definire le varie routes per metodo, ad esempio GET, POST, PUT o DELETE. In questo caso, la route intercetta tutte le richieste di tipo GET, provenienti da /. Nei casi più semplici, il primo parametro rappresenta la path dell'url, il secondo è la callback function che viene eseguita quando la route viene interpellata.

In ogni routes posso ritornare varie tipologie di risposte: tra le quali del semplice testo, del JSON o come nell'esempio qui sopra una view. Per continuare la nostra applicazione di esempio, vogliamo definire 2 routes: /posts e /post/{id}. La prima dovrà mostrare la lista dei posts, la seconda mostrerà il singolo post, secondo il parametro dinamico {id}. Da notare che ogni qual volta che utilizziamo una model, dobbiamo ricordarci di dichiararla come dipendenza specificandolo nell'intestazione del file attraverso lo statement use.

use App\Post;

Route::get('/posts', function () {
    $posts = Post::with('author')->get(); //Prendo tutti i post, integrando i dati dell'autore
    return view('blog.list')->with('posts',$posts); //Ritorno la view, con i posts
});

Route::get('/post/{id}', function ($id) {
    $post = Post::findOrFail($id); //Prendo il post con id=$id
    return view('blog.post')->with('post',$post); //ritorno la view con il post
});

Da notare:

  • Il metodo ->with serve per specificare le variabili da ritornare alle views, primo parametro nome della variabile che sarà accessibile dalla views, secondo parametro la variabile attuale con i dati.
  • Il metodo findOrFail ritorna un errore 404 standard se non trova nessun Post con id=$id, se vogliamo personalizzare l'errore basta creare la view 404.blade.php dentro alla directory /resource/views/errors.

Una volta costruite le nostre due routes, con le due logiche per prendere i dati dal database, possiamo passare alle views.

Views

Spostiamoci sotto la directory /resource/views. Qui dentro possiamo creare tutte le views della nostra applicazione. Laravel ha al suo interno un Template Engine chiamato Blade, ma non sei obbligato ad utilizzare questo template engine, puoi utilizzare php senza problemi, ma dato che ne abbiamo la possibilità, perché non utilizzarlo? Grazie ad esso si ha qualche funzionalità utile a mantenere il codice ordinato e pulito. Blade viene attivato automaticamente su tutte le views che terminano con .blade.php, al posto di .php. Laravel utilzza una dot notation per definire le views, ad esempio, ho creato una sottocartella blog, situata in /resource/views. Cosa vuol dire?

Prima abbiamo utilizzato return view('blog.post')->with('post',$post); per ritornare la view inerente al post. Se avete notato, come argomento alla funzione view abbiamo dato blog.post, Laravel capirà da solo che la view si troverà sotto la directory blog ed è chiamata post.blade.php oppure post.php.

Fra le funzionalità di Blade utilizzate in questo esempio, abbiamo l'if, il foreach ed uno shortcut che ci permette di stampare velocemente le variabili.

Per stampare le variabili delle nostre views quindi basterà scrivere il nome della variabile fra due parentesi graffe, per esempio {{$variabile}}.

Possiamo definire un'if in sintassi Blade così:

@if($variabile === 0)
    {{$variabile}}
@endif

Invece per quanto riguarda un ciclo foreach:

@foreach($array as $key => $value)
    Valore della chiave "{{$key}}": {{$value}} <br/>
@endforeach

Detto questo, definiamo le due view per il nostro blog:

list.blade.php

<html>
	<head>
		<title>Lista posts</title>
	</head>	
	<body>
		@if(count($posts)===0)
		<h1>Nessun post trovato.</h1>
		@endif
		@foreach($posts as $post)
		<div>
			<h1><a href="/post/{{$post->id}}">{{$post->title}}</a></h1>
			<h2>Scritto da {{$post->author->name}}</h2>
			<p>{{$post->content}}</p>
		</div>
		@endforeach
	</body>
</html>

post.blade.php

<html>
	<head>
		<title>{{$post->title}}</title>
	</head>	
	<body>
		<div>
			<h1>{{$post->title}}</h1>
			<h2>Scritto da {{$post->author->name}}</h2>
			<p>{{$post->content}}</p>
		</div>
	</body>
</html>

Fatto ciò, se riempiamo manualmente il nostro database di post, possiamo trovare al'url /posts la lista dei nostri post e all'url /post/{id} un post specifico. Lo scopo di questo tutorial non è creare un vero e proprio blog, perché come hai potuto notare è molto semplice, ma introdurti al mondo Laravel e alle potenzialità che offre. Utilizzeremo questo progetto come base per i prossimi tutorial, che comprenderanno l'utilizzo dei Layout, della validazione e di altre funzionalità che ti permetteranno di creare un vero e proprio blog completo. Ci diamo quindi appuntamento al prossimo tutorial, per restare aggiornato sui nostri articoli lasciaci un like sulla nostra pagina Facebook o iscriviti al nostro feed RSS. Lascia pure un commento qui sotto se hai bisogno di aiuto.