Francesco Lettera
This book is for sale at http://leanpub.com/laravel5inpratica
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.
Premessa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Perché Laravel? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Conoscenze richieste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
In profondità . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Per Iniziare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Precisazioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Installazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Rimuovere la cartella PUBLIC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Download dell’applicazione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Appendice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Laravel 5.2, le novità . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Premessa
Questo libro segna il naturale prosieguo del “fortunoso” mio precedente “Laravel in pratica (versione
4)”. Non occorre aver letto quest’ultimo. Il vecchio lettore (o affezionato lettore) aggiornerà il suo
knowhow su Laravel, il nuovo ne assaporerà la magia e la potenza.
1
Perché Laravel?
Laravel è un framework PHP agile ed effervescente ideato da Taylor Otwell. Ha una sintassi unica
e intuitiva che lo differenzia dagli altri framework. Non solo, Laravel offre un efficace RESTful
routing, un template engine semplice da utilizzare ed è basato su Composer: il tool per eccellenza
per la gestione di pacchetti di terze parti. Laravel utilizza, inoltre, molti componenti di Symfony, un
altro potente e ricco framework. Una scelta che garantisce codice ben testato.
2
A chi è rivolto questo libro
A chi vuole utilizzare subito Laravel. Questo libro ha un approccio pratico. Il mio obiettivo è aiutarti
a realizzare un progetto web funzionante, sin dal primo capitolo. Eventuali approfondimenti sul
framework saranno trattati in un secondo momento. Come webdeveloper sono spesso preso dalla
smania di provare l’ultima novità. Se me ne innamorò, approfondisco. Può non essere un approccio
ortodosso, ma se si ha un lavoro, non c’è molto tempo libero per studiare e migliorarsi: tutte le
decisioni per intraprendere un nuovo studio, quindi, devono essere ponderate. Provare “sul campo”
Laravel ti aiuterà a decidere se questo è il framework giusto per te. Non cianciamo oltre, buona
lettura.
3
Conoscenze richieste
E’ richiesta la conoscenza di PHP, perché Laravel è scritto in PHP. E’ richiesto l’utilizzo e la
conoscenza base di Composer. Il web è pieno di tutorial su Composer. Con Composer puoi scaricare
pacchetti a prescindere dal framework: se non lo si è mai utilizzato occorre esercitarsi un po’ prima
di affrontare questo testo.
4
Download codice di esempio
Ogni qualvolta raggiungeremo un traguardo, sarà disponibile il link per scaricare il codice dell’appli-
cazione che diventerà, passo dopo passo, sempre più complessa. Il presente libro parte dalla versione
Laravel 5.1.x. Il mio consiglio è utilizzare questa versione per seguire con agilità gli esempi.
5
In profondità
In appendice al libro è presente una sezione chiamata “In profondità” che affronterà nel dettaglio
alcuni aspetti teorici omessi durante la stesura dei capitoli. Non è una sezione esclusivamente
teorica. Saranno presenti, invece, ulteriori esempi che chiariranno altrettanti aspetti di Laravel.
Sono in appendice perché voglio che non intralcino il normale flusso dei capitoli. Nel progetto che
realizzeremo non sarà possibile, infatti, trattare tutte le caratteristiche di Laravel ed è qui che entra
in gioco la sezione “In profondità”.
6
Feedback
Mi farebbe piacere ricevere osservazioni su quanto ho scritto. Ecco come contattarmi:
• fralette@gmail.com
• Twitter: @FrancescoLetter
7
Per Iniziare
Precisazioni
L’installazione di Laravel può essere effettuata in diversi modi e in ambienti diversi (MAMP, Vagrant
BOX, WAMP, Laragon, ecc). Nel corso dei capitoli faremo sempre riferimento al seguente URL per
indicare le pagine del progetto: http://localhost:8888/… Noi utilizzeremo MAMP come ambiente, ma
sono valide tutte le alternative citate.
Installazione
I requisiti minimi per installare Laravel sono i seguenti:
Se qualcosa va storto durante l’installazione del framework è molto probabile che qualche requisito
non sia stato rispettato.
Laravel utilizza Composer e la stringa per installarlo è la seguente. Nel nostro caso creeremo una
cartella chiamata “laravelbook”, al suo interno, quindi, ci sarà il nostro framework preferito:
8
Per Iniziare 9
MAMP (Mac)
Con MAMP risulta abbastanza semplice: fai puntare il percorso della tua applicazione direttamente
nella cartella public. E il gioco è fatto. Controlla che il rewrite_module e vhost_alias_module sia
attivo tra i moduli Apache.
WampServer (Win)
Cartelle?
Nell’esempio qui sotto Laravel è installato direttamente nella root principale “www” di
WampServer, ma - come anticipato - si può decidere di creare una sottocartella e chiamarla
“laravelbook” all’interno della quale far “girare” la nostra app.
Prima di tutto abilitiamo il rewrite_module tra i moduli di Apache attraverso il comodissimo admin
di WampServer. Assicuriamoci inoltre che sia abilitato anche il modulo vhost_alias_module: mi è
capitato di vederlo abilitato o meno a seconda della versione di WampServer scaricata. Apriamo
quindi il file C:/wamp/bin/apache/Apachex.x.x/conf/httpd.conf e rimuoviamo il cancelletto (#)
dalla seguente linea
1 #Include conf/extra/httpd-vhosts.conf
1 C:\wamp\bin\apache\Apachex.x.x\conf\extra\httpd-vhosts.conf
1 <VirtualHost *:80>
2 ServerAdmin email@miaemailpersonale.com
3 DocumentRoot "C:/wamp/www/public"
4 ServerName lar.dev
5 </VirtualHost>
6
7 <Directory "C:/wamp/www/public">
8 Options Indexes FollowSymLinks
9 AllowOverride all
10 # onlineoffline tag - don't remove
11 Order Deny,Allow
12 Deny from all
13 Allow from 127.0.0.1
14 </Directory>
Per Iniziare 10
Facciamo attenzione al percorso di DocumentRoot: deve puntare alla cartella public di Laravel. Al
ServerName, invece, diamo un nome di fantasia: lar.dev. Sarà quest’ultimo il nome che utilizzeremo
sul browser per accedere alle tue pagine web.
L’ultimo step riguarda il file hosts di Windows. Questo dovrebbe essere il percorso (potrebbe variare
leggermente in base alla diversa versione di Windows):
1 C:/Windows/System32/drivers/etc
1 127.0.0.1 lar.dev
Riavviamo WampServer e - se tutto è andato per il verso giusto - non scriveremo più localhost/-
public, ma lar.dev per accedere al nostro progetto.
Autenticazione bella e pronta
Laravel fornisce, out of the box, un sistema di autenticazione corredato di registrazione. Vediamo
come implementarlo velocemente.
In database troveremo la cartella migrations. A cosa serve?
Migration
Per creare le tabelle del nostro database, Laravel utilizza il suo migration system. Questo sistema
permette la gestione completa delle tabelle. Ma non entriamo nei dettagli. Per il progetto è necessario
creare due tabelle
In database/migrations troveremo due file:
1 - 2014_10_12_000000_create_users_table.php
2 - 2014_10_12_100000_create_password_resets_table.php
Apriamo il primo file. All’interno del metodo up() saranno presenti tutte le modifiche necessarie
alla tabella. Il metodo down ci permetterà di effettuare un rollback della migration: cioè nel caso
della tabella users, questa sarà cancellata.
Il secondo file, invece, creerà la tabella password_resets. Il funzionamento è simile al file precedente.
Ma come fare per creare materialmente le due tabelle? Il primo step è configurare i dati d’accesso al
db. In base al setup effettuato (MAMP, WAMP, Homestead, ecc) creiamo un db (es. laravel). Subito
dopo apriamo il file .env presente nella root della nostra applicazione e indichiamo le credenziali
d’accesso al db nelle opportune variabili.
1 // .env
2 ...
3 DB_HOST=localhost
4 DB_DATABASE=laravel
5 DB_USERNAME=root
6 DB_PASSWORD=root
7 ...
Da riga di comando (posizioniamoci sempre nella root della nostra applicazione) scriviamo:
php artisan migrate
11
Autenticazione bella e pronta 12
E voilà, il nostro database avrà dunque la tabella users e password_resets con i suoi campi. Siamo
pronti per testare l’autenticazione fornita di default da Laravel.
Cos’è .env?
Bella domanda. Per adesso è sufficiente sapere che si avvicina molto ad un file di
configurazione.
Nel corso del libro approfondiremo l’argomento.
1 // app/Http/routes.php
2
3 // home
4 Route::get('/home', function(){
5 return view('home.main');
6 });
7
8 // Autenticazione
9 Route::get('auth/login', 'Auth\AuthController@getLogin');
10 Route::post('auth/login', 'Auth\AuthController@postLogin');
11 Route::get('auth/logout', 'Auth\AuthController@getLogout');
12
13 // Registrazione
14 Route::get('auth/register', 'Auth\AuthController@getRegister');
15 Route::post('auth/register', 'Auth\AuthController@postRegister');
Benissimo, mancano però le view. Siamo liberi di crearle come vogliamo purchè siano rispettati i
campi di input, naturalmente. Ecco un suggerimento (preso dalla documentazione ufficiale):
Questo è il form di autenticazione:
Autenticazione bella e pronta 13
19 <div>
20 Confirm Password
21 <input type="password" name="password_confirmation">
22 </div>
23
24 <div>
25 <button type="submit">Register</button>
26 </div>
27 </form>
Andiamo su
1 http://localhost:8888/auth/register
e registriamo l’utente (Sì, lo so, non è formattato. Ma nei capitoli successivi renderemo tutto più
piacevole). Subito dopo risulteremo loggati nella nostra applicazione (e saremo, quindi, redirezionati
verso “/home”). Il db sarà popolato dei dati della nostra registrazione. Se effettuiamo il logout:
http://localhost:8888/auth/logout
possiamo testare il login:
1 http://localhost:8888/auth/login
Insomma un ottimo sistema di autenticazione base. Nei capitoli successivi vedremo come customiz-
zare l’autenticazione e come presentarla con tutti i crismi.
Blade, un template chiaro e snello
Blade è il templating engine di Laravel. E lo andremo ad usare per tutti i nostri progetti. Per intenderci
sarà usato in tutte le nostre view. Per far comprendere a Laravel che stiamo usando blade (non è
obbligatorio, intendiamoci) dobbiamo salvare i nostri file con la seguente estensione .blade.php.
Procediamo creando un file main.blade.php:
1 // resources/views/template/main.blade.php
2 <!doctype html>
3 <html lang="en">
4 <head>
5 <meta charset="UTF-8">
6 <title>Document</title>
7 </head>
8 <body>
9 @section('mioheader')
10 <header>Questo è il mio header</header>
11 @show
12
13 @yield('content')
14
15 </body>
16 </html>
1 // resources/views/homepage/index.blade.php
2 @extends('template/main')
3 @section('content')
4 Questo è il contenuto della view homepage/index.blade.php
5 @stop
Nel template abbiamo definito le aree. C’è l’area section che contiene informazioni di default. Poi
c’è l’istruzione yield(‘content’) che indica un “segnaposto” utilizzato dalla view index.blade.php per
“incastrarsi” nel template. Nella view homepage/index.blade.php è quindi presente il contenuto
content che si posizionerà al posto di yield(‘content’). Vediamo come utilizzare questi due file.
Agiamo sul file di route. Sì, ma cos’è? E’ il file di rotta che indica a Laravel come muoversi tra
le pagine. Lo scopriremo poco a poco. Per adesso andiamolo ad editare per far funzionare il nostro
template e la nostra view:
15
Blade, un template chiaro e snello 16
1 // app/Http/routes.php
2 Route::get('/homepage', function(){
3 return view('homepage.index');
4 });
Questo codice va inserito alla fine (lasciamo perdere le indicazioni per l’autenticazione). Cosa
abbiamo indicato al file di route? Se si digita http://localhost:8888/laravelbook/homepage¹ Laravel
deve mostrare la view nella cartella homepage. Osserviamo inoltre come la sintassi per indicare un
file all’interno della cartella è la dot notation. Se tutto è andato per il verso giusto, quindi, vedremo
la nostra view all’interno template.
Strutture di controllo
Vediamo come “passare” le variabili all’interno della view. E’ una procedura tanto semplice, quanto
efficace. Partiamo dalla route e andiamo a creare una semplice variabile:
1 // app/Http/routes.php
2 Route::get('/homepage', function(){
3 $data['nome'] = 'Francesco';
4 return view('homepage.index', $data);
5 });
1 // resources/views/homepage/index.blade.php
2 @extends('template/main')
3 @section('content')
4 Caro {{$nome}}, questo è il contenuto della view homepage/index.blade.php
5 @stop
Può accadere che la variabile $nome non sia impostata (i motivi possono essere svariati). Laravel ci
viene in aiuto con una sintassi molto intuitiva. Eccola:
¹http://localhost:8888/laravelbook/homepage
Blade, un template chiaro e snello 17
1 // resources/views/homepage/index.blade.php
2 @extends('template/main')
3 @section('content')
4 Caro {{$nome or 'Anonimo'}}, questo è il contenuto della view homepage/index\
5 .blade.php
6 @stop
Qual è la differenza? In questo caso non è eseguito l’escaping. Può accadere, infatti, che sia necessario.
IF e altre istruzioni
Per gestire ulteriore codice all’interno di un template blade, ci sono le onnipresenti istruzioni di
controllo.
1 @if($variabile == 1)
2 <p>Qui va i codice HTML, ad esempio </p>
3 @elseif($variabile == 2)
4 <p>Qui va l'alternativa</p>
5 @endif
1 @unless(Auth::check())
2 <p>Non sei autenticato</p>
3 @endunless
Loop
Eccoli i nostri loop con altre interessanti istruzioni.
Blade, un template chiaro e snello 18
1 @forelse($utenti as $utente)
2 <li>{{ $utente->nome }}</li>
3 @empty
4 <p>Nessun utente</p>
5 @endforelse
Poi, il while:
1 @while (true)
2 <p>Faccio il ciclo</p>
3 @endwhile
Ci sono ulteriori istruzioni per Blade, ma sarebbe inutile elencarle senza “incastrarle” in progetti
reali. Ne parleremo nel testo quando occorrerà usarle.
Scarichiamo pacchetti per un Form di
lusso
Chi ha usato in precedenza Laravel, ed in particolare la versione 4, si ricorderà della gestione dei
form con istruzioni ad hoc. Nella versione attuale (la 5.1), invece, questo pacchetto non è incluso.
Possiamo tuttavia installarlo con composer. In soccorso ci viene in aiuto il sito laravecollective.com²
il cui obiettivo è mantenere i componenti rimossi dal core del framework. Uno di questi è, appunto,
quello che si occupava della gestione dei form.
Un nuovo inizio
Andiamo a reinstallare Laravel. Sì, il mio suggerimento è di reinstallare nuovamente laravel. E’ una
prassi che aiuterà a prendere dimestichezza con il framework e a non impigrirsi.
Con una installazione “fresca” occupiamoci di installare il pacchetto per gestire i Form. Lo stesso
sito citato ci viene in soccorso:
Nel file composer.json aggiungiamo in require:
1 "require": {
2 "laravelcollective/html": "5.1.*"
3 }
1 composer update
Poi aggiungiamo il nuovo provider nella lista dei provider presenti in /config/app.php
²http://laravelcollective.com
19
Scarichiamo pacchetti per un Form di lusso 20
1 // config/app.php
2 'providers' => [
3 // ...
4 Collective\Html\HtmlServiceProvider::class,
5 // ...
6 ],
Più giù, sempre nello stesso file, aggiungiamo le due classi alias che ci faciliteranno il compito:
1 // config/app.php
2 'aliases' => [
3 // ...
4 'Form' => Collective\Html\FormFacade::class,
5 'Html' => Collective\Html\HtmlFacade::class,
6 // ...
7 ],
Perfetto, siamo pronti per testare il nostro form. Partiamo dalla view alla quale abbiamo dato anche
un tocco di stile:
1 // resources/views/iscrizione/form.blade.php
2
3 <!doctype html>
4 <html lang="en">
5 <head>
6 <meta charset="UTF-8">
7 <title>Iscrizione</title>
8 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5\
9 /css/bootstrap.min.css">
10
11
12 </head>
13 <body>
14
15 <div class="container">
16 <div class="row">
17 <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
18 {!! Form::open(['url' => 'iscriviti/dati']) !!}
19
20 <div class="form-group">
21 {!! Form::label('nome', 'Il tuo nome') !!}
22 {!! Form::text('nome', null, ['class' => 'form-control']) !!}
Scarichiamo pacchetti per un Form di lusso 21
23 </div>
24
25 <div class="form-group">
26 {!! Form::label('cognome', 'Il tuo cognome') !!}
27 {!! Form::text('cognome', null, ['class' => 'form-control'])\
28 !!}
29 </div>
30
31 {!! Form::submit('salva', ['class' => 'btn btn-success']) !!}
32
33 {!! Form::close() !!}
34 </div>
35 </div>
36 </div>
37
38 </body>
39 </html>
1 // views/iscrizione/recupera.blade.php
2 <!doctype html>
3 <html lang="en">
4 <head>
5 <meta charset="UTF-8">
6 <title>Iscrizione</title>
7 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5\
8 /css/bootstrap.min.css">
9
10
11 </head>
12 <body>
13
14 <div class="container">
15 <div class="row">
16 <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
17 Ciao {{$nome}} {{$cognome}}
18 </div>
19 </div>
20 </div>
21
22 </body>
23 </html>
Scarichiamo pacchetti per un Form di lusso 22
1 //app/Http/routes.php
2 Route::get('iscriviti', function(){
3 return view('iscrizione.form');
4 });
5
6 Route::post('iscriviti/dati', function(){
7 $data = [
8 'nome' => Input::get('nome')
9 ,'cognome' => Input::get('cognome')
10 ];
11
12 return view('iscrizione.recupera', $data);
13 });
La prima istruzione di route è una chiamata get, la seconda è post perché il metodo di invio dati è
post. Se diamo un’occhiata al codice generato dalla pagina: http://localhost:8888/iscriviti noteremo
anche un campo hidden chiamato _token. Questo campo è indispensabile per evitare attacchi di
tipo CSRF (Cross-site request forgeries) e il Middleware di Laravel deputato a questo controllo è
HTTPMiddleware. Quindi se usiamo il pacchetto appena scaricato, il campo hidden _token sarà
inserito automaticamente, altrimenti dovremmo inserirlo a mano in questo modo:
Installa e reinstalla
Installare e reinstallare Laravel e pacchetti annessi e connessi è una pratica salutare per
prendere dimestichezza con questo framework e con quelli che desidererai nel tempo
imparare.
Adoro gli address book
Il nostro microprogetto sarà incentrato sulla creazione di un address book. Ok, una rubrica. E
utilizzeremo CRUD (Create, Read, Update, Delete) come approccio al progetto.
Database, onnipresente
Prima di tutto occorre creare un Model che gestisca la nostra tabella. Nella shell digitiamo:
1 //app/Agenda.php
2 namespace App;
3
4 use Illuminate\Database\Eloquent\Model;
5
6 class Agenda extends Model {
7
8 protected $table = 'agenda';
9
10 }
Laravel, di default, usa i plurali all’inglese, aggiungendo una “s”. Quindi intende la nostra tabella
come “agendas”. Con questa istruzione sovrascriviamo le sue intenzioni e possiamo dare qualsiasi
nome alla tabella.
Subito dopo lanciamo un altro comando:
23
Adoro gli address book 24
1 //database/2015_xx_xx_181606_create_agenda_table.php
2 use Illuminate\Database\Schema\Blueprint;
3 use Illuminate\Database\Migrations\Migration;
4
5 class CreateAgendaTable extends Migration
6 {
7 /**
8 - Run the migrations.
9 *
10 - @return void
11 */
12 public function up()
13 {
14 //
15 }
16
17 /**
18 - Reverse the migrations.
19 *
20 - @return void
21 */
22 public function down()
23 {
24 //
25 }
26 }
Adesso tocca ai campi della nostra rubrica. Nel metodo up() inseriamo i seguenti campi
1 //database/2015_xx_xx_181606_create_agenda_table.php
2 ...
3 public function up()
4 {
5 Schema::create('agenda', function(Blueprint $table)
6 {
7 $table->increments('id');
8 $table->string('nome', 255);
9 $table->string('cognome', 255);
10 $table->string('email', 255);
11 $table->string('telefono',20)->default(0);
12 $table->timestamps();
13 });
Adoro gli address book 25
14 }
15 ...
Ogni campo nel db ha il suo “tipo”. Nel nostro caso non c’è molta differenza. Laravel, di default,
utilizza anche $table->timestamps() che aggiunge un paio di campi nella tabella: created_at e
updated_at: sono due campi molto utili (in alcuni casi) che tracciano gli aggiornamenti del record
della tabella.
Dopo aver confezionato la nostra migration è giunta l’ora di innescarla per creare la tabella nel
nostro db. Procediamo dalla shell:
Se tutto è andato per il verso giusto, abbiamo una tabella agenda nel nostro db.
Ed ecco il risultato:
1 //app/Http/Controllers/AgendaController.php
2 namespace App\Http\Controllers;
3
4 use Illuminate\Http\Request;
5
6 use App\Http\Requests;
7 use App\Http\Controllers\Controller;
8
9 class AgendaController extends Controller
10 {
11 /**
12 - Display a listing of the resource.
13 *
14 - @return Response
15 */
16 public function index()
17 {
18 //
Adoro gli address book 26
19 }
20
21 /**
22 - Show the form for creating a new resource.
23 *
24 - @return Response
25 */
26 public function create()
27 {
28 //
29 }
30
31 /**
32 - Store a newly created resource in storage.
33 *
34 - @param Request $request
35 - @return Response
36 */
37 public function store(Request $request)
38 {
39 //
40 }
41
42 /**
43 - Display the specified resource.
44 *
45 - @param int $id
46 - @return Response
47 */
48 public function show($id)
49 {
50 //
51 }
52
53 /**
54 - Show the form for editing the specified resource.
55 *
56 - @param int $id
57 - @return Response
58 */
59 public function edit($id)
60 {
Adoro gli address book 27
61 //
62 }
63
64 /**
65 - Update the specified resource in storage.
66 *
67 - @param Request $request
68 - @param int $id
69 - @return Response
70 */
71 public function update(Request $request, $id)
72 {
73 //
74 }
75
76 /**
77 - Remove the specified resource from storage.
78 *
79 - @param int $id
80 - @return Response
81 */
82 public function destroy($id)
83 {
84 //
85 }
86 }
Perché tutti questi metodi? Come accennato all’inizio del capitolo, per costruire la nostra agenda
seguiremo il metodo CRUD (Create, Read, Update, Delete), cioè le quattro operazione fondamentali
per gestire i nostri dati. Laravel, dunque, quando creiamo un controller da artisan, ci fornisce già i
metodi (vuoti). Partiamo dal metodo create, ma prima indichiamo al nostro file di route che esiste
questo nuovo controller e che vogliamo usare tutti i suoi metodi. Come? Ecco l’istruzione.
1 Route::resource('agenda', 'AgendaController');
Con questa semplice istruzione Laravel gestirà tutti i metodi presenti nel controller. Vediamo quali
in questa tabella:
Adoro gli address book 28
E’ uno schema è molto importante perché illustra bene il funzionamento di Laravel quando ha a che
fare con un controller in CRUD mode. In sostanza ciò che abbiamo scritto nel file di route in una
sola riga, risolve tutte quelle chiamate Http. Adesso ci occuperemo, come detto, del secondo verbo
Http: agenda/create
Il nostro metodo conterrà la view:
1 //app/Http/Controllers/AgendaController.php
2 public function create()
3 {
4 return view('agenda.create');
5 }
Per la view, questa volta, comportiamoci meglio: realizziamo un piccolo template (utilizzando
Twitter Bootstrap) e “incastriamo la view all’interno”.
Ecco il template:
1 // resources/views/template/main.blade.php
2 <!doctype html>
3 <html lang="en">
4 <head>
5 <meta charset="UTF-8">
6 <title>Address Book</title>
7
8 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5\
9 /css/bootstrap.min.css">
10
11 </head>
12 <body>
13
14 <nav class="navbar navbar-default">
15 <div class="container-fluid">
16 <div class="navbar-header">
Adoro gli address book 29
Nel template abbiamo inserito l’istruzione @yield(‘content’), dunque facciamo attenzione alla view:
1 //resources/views/agenda/create.blade.php
2 @extends('templates.main')
3
4 @section('content')
5
6 @stop
All’interno del tag section(‘content’) (il nome richiama @yield(‘content’)) presente nel template.
Inseriamo dunque il nostro form. Eccolo:
1 //resources/views/agenda/create.blade.php
2 @extends('templates.main')
3
4 @section('content')
5
6 <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
7 {!! Form::open(['url' =>'agenda']) !!}
8
9 <div class="form-group">
10 {!! Form::label('nome', 'Il tuo nome:') !!}
11 {!! Form::text('nome', null, ['class' => 'form-control']) !!}
12 </div>
13
Adoro gli address book 30
14 <div class="form-group">
15 {!! Form::label('cognome', 'Il tuo cognome:') !!}
16 {!! Form::text('cognome', null, ['class' => 'form-control']) !!}
17 </div>
18
19 <div class="form-group">
20 {!! Form::label('email', 'La tua email:') !!}
21 {!! Form::text('email', null, ['class' => 'form-control']) !!}
22 </div>
23
24 <div class="form-group">
25 {!! Form::label('telefono', 'Il tuo telefono:') !!}
26 {!! Form::text('telefono', null, ['class' => 'form-control']) !!}
27 </div>
28
29 {!! Form::submit('invia', ['class' => 'btn btn-success']) !!}
30
31 {!! Form::close() !!}
32 </div>
33 @stop
Possiamo premere sul pulsante “invia” e ci ritroveremo su una pagina bianca. Intanto, però, Laravel
ha gestito la nostra richiesta. Se facciamo riferimento alla tabella precedente la chiamata in questione
è quella espressa nella terza riga: POST | /agenda | store
E’ in questo metodo che dobbiamo intervenire per accogliere i dati e validarli, prima di inserirli nel
db. Lo vedremo nel prossimo paragrafo.
1 // app/Http/Controllers/AgendaController.php
2
3 public function store(Request $request)
4 {
5 $this->validate($request, [
6 'nome' => 'required'
7 ,'cognome' => 'required'
8 ,'email' => 'required|email'
9 ,'telefono' => 'required'
10 ],
11 [
12 'nome.required' => 'Il nome è obbligatorio!'
13 ,'cognome.required' => 'Per favore, anche il cognome'
14 ,'email.required' => 'E l\'email è importante'
15 ,'telefono.required' => 'Per favore, inserisci il numero di telefono'
16 ,'email.email' => 'L\'email non è in formato corretto'
17 ]);
18
19 $agenda = new \App\Agenda();
20
21 $agenda->nome = $request->input('nome');
22 $agenda->cognome = $request->input('cognome');
23 $agenda->email = $request->input('email');
24 $agenda->telefono = $request->input('telefono');
25 $agenda->save();
26
27 return redirect('agenda')->with('ok_message', 'La tua rubrica è stata ag\
28 giornata con un nuovo utente');
29 }
Quando abbiamo creato con artisan il controller, Laravel si è preoccupato di passare nel metodo
store anche la classe Request
Ma a cosa serve questa classe? Gestisce le richieste Http, i dati che passiamo da un modulo all’altro
e molto di più. Per adesso non approfondiamo, accontentiamoci del suo utilizzo pratico.
Subito dopo la validazione prende forma (questo metodo $this->validate è parte della classe
ValidateRequest di Laravel che si occupa proprio della validazione). Accoglie quattro parametri:
il primo è la classe Request, il secondo le regole per validare, il terzo i messaggi di ritorno in caso di
errore, il quarto riguarda attributi custom (ma sorvoleremo per il momento).
Vorrei sottolineare l’ultimo messaggio di validazione tradotto.
Adoro gli address book 32
Siccome per l’email abbiamo richiesto che sia required e che sia formattata come email i messaggi
potenziali di errore sono due. Il primo required e il secondo email. Seguendo la sintassi con punto
(dot), abbiamo personalizzato anche il messaggio relativo alla formattazione. Se non l’avessimo fatto,
Laravel ci avrebbe restituito quello suo di default (in inglese).
Una volta validati i campi, creiamo un’istanza del model Agenda (anche questo creato con artisan)
e valorizziamo i campi con gli stessi nomi degl’input. Alla fine lanciamo il metodo $agenda->save
L’ultima riga è un redirect ad una pagina inesistente. O meglio, sembra la stessa che abbiamo
utilizzato nel form, giusto? In realtà non lo è. Quest’ultima è una chiamata GET, quella del form è una
chiamata POST. Diamo un’occhiata alla tabella Http di prima e noteremo la differenza. Quest’ultimo
redirect si riferisci alla prima riga della tabella:
Sembra tutto ok. Ma cosa accade se la validazione non passa? Laravel ci riporta nella pagina di
agenda/create, ma sembra non accadere nulla. Visualizziamo i messaggi di errore valorizzati nel
metodo store. Poco prima dell’apertura del form:
1 // resources/views/agenda/create.blade.php
2 @if(count($errors->all()) > 0)
3 <div class="alert alert-danger" role="alert">
4 <p><b>OOOPS!</b></p>
5 <ul>
6 @foreach($errors->all() as $e)
7 <li>{{$e}}</li>
8 @endforeach
9 </ul>
10 </div>
11 @endif
1 //app/Http/Controllers/AgendaController.php
2 public function index()
3 {
4 $data['listaNominativi'] = \App\Agenda::all();
5 return view('agenda.index', $data);
6 }
Ecco una classica istruzione Eloquent (l’ORM di Laravel): con il metodo all() “peschiamo” tutti i
record del tabella agenda. Sono “passati” nella view con il secondo attributo del metodo view.
Poi la view, immediatamente:
1 // resources/views/agenda/index.blade.php
2 @extends('templates.main')
3 @section('content')
4
5 <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
6 @if(Session::has('ok_message'))
7 <div class="alert alert-success">{{Session::get('ok_message')}}</div>
8 @endif
9 <table class="table table-bordered">
10 <thead>
11 <tr>
12 <th>Nome</th>
13 <th>Cognome</th>
14 <th>Email</th>
15 <th>Telefono</th>
16 <th>Azioni</th>
17 </tr>
18 </thead>
19
20 <tbody>
21 @foreach($listaNominativi as $ln)
22 <tr>
23 <td>{{$ln['nome']}}</td>
24 <td>{{$ln['cognome']}}</td>
25 <td>{{$ln['email']}}</td>
Adoro gli address book 34
26 <td>{{$ln['telefono']}}</td>
27 <td><a href="/agenda/{{$ln['id']}}/edit">modifica</a></td>
28 </tr>
29 @endforeach
30 </tbody>
31 </table>
32 </div>
33 @stop
Il ciclo foreach non ha da essere spiegato: è autospiegante. Idem per i record. C’è una picco-
la novità: l’istruzione if (Session::has(‘ok_message’) esegue un controllo sulla presenza o me-
no della sessione flash (meglio flash data). In fase di creazione non abbiamo visto gli effetti
del return redirect(‘agenda’)->with(‘ok_message’,…) perchè non era ancora pronta la view in
resources/views/agenda/index.blade.php. Adesso è pronta ed è chiara l’utilità.
Non occorre intervenire nella route perché siamo ancora nell’ambito CRUD e la route intercetta
tutto correttamente. Ecco l’estratto della famosa tabella. Questa è la richesta HTTP:
Dunque:
1 locahost:8888/agenda
1 //app/Http/Controllers/AgendaController.php
2 public function edit($id)
3 {
4 $data['datiRecuperati'] = \App\Agenda::find($id);
5 return view('agenda.edit', $data);
6 }
Poi della view, con una leggera differenza nell’apertura del form:
1 @extends('templates.main')
2
3 @section('content')
4
5 <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
6
7 @if(count($errors->all()) > 0)
8 <div class="alert alert-danger" role="alert">
9 <p><b>OOOPS!</b></p>
10 <ul>
11 @foreach($errors->all() as $e)
12 <li>{{$e}}</li>
13 @endforeach
14 </ul>
15 </div>
16 @endif
17
18 {!! Form::model($datiRecuperati,
19 ['method' => 'put', 'url' =>'agenda/'. $datiRecuperati['id']])
20 !!}
21
22 <div class="form-group">
23 {!! Form::label('nome', 'Il tuo nome:') !!}
24 {!! Form::text('nome', null, ['class' => 'form-control']) !!}
25 </div>
26
27 <div class="form-group">
28 {!! Form::label('cognome', 'Il tuo cognome:') !!}
29 {!! Form::text('cognome', null, ['class' => 'form-control']) !!}
30 </div>
31
32 <div class="form-group">
33 {!! Form::label('email', 'La tua email:') !!}
Adoro gli address book 36
Prima di premere sul pulsante di aggiornamento, andiamo nel metodo che accoglierà i dati per
aggiornarli:
Adoro gli address book 37
1 //app/Http/Controllers/AgendaController.php
2 public function update(Request $request, $id)
3 {
4 $this->validate($request, [
5 'nome' => 'required'
6 ,'cognome' => 'required'
7 ,'email' => 'required|email'
8 ,'telefono' => 'required'
9 ],
10 [
11 'nome.required' => 'Il nome è obbligatorio!'
12 ,'cognome.required' => 'Per favore, anche il cognome'
13 ,'email.required' => 'E l\'email è importante'
14 ,'telefono.required' => 'Per favore, inserisci il numero di telefono'
15 ,'email.email' => 'L\'email non è in formato corretto'
16 ]);
17
18 $agenda = \App\Agenda::find($id);
19 $agenda->nome = $request->input('nome');
20 $agenda->cognome = $request->input('cognome');
21 $agenda->email = $request->input('email');
22 $agenda->telefono = $request->input('telefono');
23 $agenda->save();
24
25 return redirect('agenda')->with('ok_message', 'La tua rubrica è stata aggior\
26 nata con un nuovo utente');
27 }
Molto simile all’inserimento. Ma vediamo nel dettaglio: la differenza che salta agli occhi è un’altra
istruzione Eloquent: AppAgenda:.find($id), cioè Laravel cerca prima il record da aggiornare, poi
aggiorna le variabili ed infine salva tutto con il comando save().
1 //app/Http/Controllers/AgendaController.php
2 public function destroy($id)
3 {
4 \App\Agenda::destroy($id);
5 return redirect('agenda')->with('ok_message', 'La tua rubrica è stata aggior\
6 nata con un nuovo utente');
7
8 }
1 //resources/views/agenda/edit.blade.php
2
3 ...
4
5 {!! Form::open([
6 'method' => 'DELETE',
7 'route' => ['agenda.destroy', $datiRecuperati['id']]
8 ]) !!}
9 {!! Form::submit('Cancella', ['class' => 'btn btn-danger']) !!}
10 {!! Form::close() !!}
In questo caso, utilizziamo un attributo alternativo a quello già utilizzato: ‘route’ ⇒ […] dove
indichiamo esattamente la rotta.
Ok, siamo pronti per cancellare il record. Nel prossimo capitolo ci occuperemo del refactoring di
questo CRUD. Non lo trovi troppo ridondante?
Refactoring a piccole dosi
Il refactoring prima si fa, meglio è per tutti. I controller devono essere snelli, non tutta la logica
di business deve essere assorbita dai suoi metodi. E ridurre al minimo il codice ridondante è un
obbligo, non una necessità. Inoltre dobbiamo tatuarci il principio della single responsibility: ogni
metodo deve svolgere pochissime attività. Vediamo come: il metodo del nostro controller store() può
esser concettualmente così migliorato:
1 //app/Handlers/AgendaHandler.php
2 namespace App\Handlers;
3
4 use Illuminate\Http\Request;
5 use Validator;
6
7 class AgendaHandler
8 {
9 protected $request;
10
11 public function __construct(Request $request){
12 $this->request = $request;
13 }
14
15 public function datiValidi()
16 {
17 $valida = Validator::make($this->request->all(), [
18 'nome' => 'required'
19 , 'cognome' => 'required'
20 , 'email' => 'required|email'
21 , 'telefono' => 'required'
39
Refactoring a piccole dosi 40
22 ],
23 [
24 'nome.required' => 'Il nome è obbligatorio!'
25 , 'cognome.required' => 'Per favore, anche il cognome'
26 , 'email.required' => 'E l\'email è importante'
27 , 'telefono.required' => 'Per favore, inserisci il numero di tel\
28 efono'
29 , 'email.email' => 'L\'email non è in formato corretto'
30 ]);
31
32 if ($valida->fails()) {
33 return $valida->errors();
34 }
35 return true;
36 }
37
38 public function recuperaDati($agenda){
39 $agenda->nome = $this->request->input('nome');
40 $agenda->cognome = $this->request->input('cognome');
41 $agenda->email = $this->request->input('email');
42 $agenda->telefono = $this->request->input('telefono');
43 return $agenda;
44 }
45
46 public function inserisciDati(){
47 $agenda = new \App\Agenda();
48 $this->recuperaDati($agenda);
49 $agenda->save();
50 }
51
52 public function aggiornaDati($id){
53 $agenda = \App\Agenda::find($id);
54 $this->recuperaDati($agenda);
55 $agenda->save();
56 }
57 }
Non sono proprio un paio: ho barato. Prima di tutto richiamiamo le due classi che ci serviranno:
IlluminateHttpRequest; e Validator. Questa è una nostra classe e a Laravel dobbiamo dire tutto, ma
proprio tutto.
Nel metodo __construct passiamo la classe Request che si occuperà di recuperare i dati così come
faceva nel controller. Nel metodo datiValidi(), invece, effettueremo la validazione. Il codice è molto
Refactoring a piccole dosi 41
simile a quello presente nel controller: la differenza sostanziale è che si usa la classe Validator e il suo
metodo make per inizializzare la validazione e customizzare i messaggi di errore. Se la validazione
fallisce, il metodo ritorna gli errori che verrano usati per un redirect nel form di inserimento. Se la
validazione NON fallisce, il metodo restituisce true.
Il secondo metodo inserisciDati(), invece, inserirà i dati, così come li ricordiamo nel controller. Ma
vediamo cosa è accaduto nel controller? Certo. Nella parte alta aggiungiamo:
1 //app/Http/Controllers/AgendaController.php
2 use App\Handlers\AgendaHandler as Ah;
E’ la nostra nuova classe. Dobbiamo rendere noto a Laravel che esiste e che la useremo nel controller.
Il metodo store() invece:
1 //app/Http/Controllers/AgendaController.php
2 public function store(Ah $ah)
3 {
4 $esitoValidazioneDati = $ah->datiValidi();
5 if ($esitoValidazioneDati === true) {
6 $ah->inserisciDati();
7 return redirect('agenda')->with('ok_message', 'La tua rubrica è stata ag\
8 giornata con un nuovo utente');
9 }
10 return redirect('agenda/create')->withErrors($esitoValidazioneDati)->withInp\
11 ut();
12 }
Prima di tutto “innestiamo” la nuova classe che ha come alias Ah (lo abbiamo scritto in cima) poi…
Se l’esito restituisce true, allora inserisci e redirezione come al solito. Se restituisce i messaggi di
errore, invece, redireziona nel metodo create con i messaggi inclusi. Moooolto più chiaro e pulito,
no?
Passiamo al metodo update(), molto simile a store(). Ecco cosa accade:
Refactoring a piccole dosi 42
1 //app/Http/Controllers/AgendaController.php
2 public function update(Ah $ah, $id)
3 {
4 $esitoValidazioneDati = $ah->datiValidi();
5 if ($esitoValidazioneDati === true) {
6 $ah->aggiornaDati($id);
7 return redirect('agenda')->with('ok_message', 'La tua rubrica è stata ag\
8 giornata');
9 }
10 return redirect('agenda/'.$id.'/edit')->withErrors($esitoValidazioneDati)->w\
11 ithInput();
12 }
Download dell’applicazione
I file trattati in questo capitolo puoi scaricarli a questo indirizzo⁵.
⁴http://code.tutsplus.com/tutorials/solid-part-1-the-single-responsibility-principle--net-36074
⁵https://www.dropbox.com/s/klyof3r7ingb6wc/chapter_7.zip?dl=0
Eloquent, Factory, Faker e impostori
vari
Eloquent è l’ORM di Laravel ed è fornito “out of the box”: quindi non è necessario installare alcunché.
Lo abbiamo già usato nei precedenti capitoli, senza illustrarne la versatilità, però. Ricominciamo
dunque con una installazione di Laravel. E partiamo dalle origini. Lanciamo il nostro composer:
Pigro?
Se sei alle prime armi con Laravel (e hai usato poche volte Composer), reinstallare
continuamente è un’attività che fa bene alla salute.
1 APP_ENV=local
2 APP_DEBUG=true
3 APP_KEY=SomeRandomString
4
5 DB_HOST=localhost
6 DB_DATABASE=biblioteca
7 DB_USERNAME=root
8 DB_PASSWORD=root
9
10 CACHE_DRIVER=file
11 SESSION_DRIVER=file
12 QUEUE_DRIVER=sync
13
14 MAIL_DRIVER=smtp
15 MAIL_HOST=mailtrap.io
43
Eloquent, Factory, Faker e impostori vari 44
16 MAIL_PORT=2525
17 MAIL_USERNAME=null
18 MAIL_PASSWORD=null
19 MAIL_ENCRYPTION=null
Andiamo dunque ad aprire il file per indicare quali campi debba avere questa tabella:
1 // database/migrations/xxxx_xx_xx_xxxxxx_create_autori_table
2
3 public function up(){
4
5 }
6
7 public function down(){
8
9 }
Il metodo up serve per aggiungere nuove tabelle, colonne, o indici del db. Il metodo down serve
per l’inverso. Entrambi i metodi utilizzano lo schema builder. Vediamo, in pratica, come creare la
tabella:
1 // database/migrations/xxxx_xx_xx_xxxxxx_create_autori_table
2 public function up()
3 {
4 Schema::create('autori', function (Blueprint $table) {
5 $table->increments('id');
6 $table->string('nome');
7 $table->string('cognome');
8 $table->timestamps();
9 });
10 }
Il primo id è incrementale, poi abbiamo nome e cognome nel metodo string() che equivale a
VARCHAR(255)
Lanciamo la migrazione per creare la tabella:
Eloquent, Factory, Faker e impostori vari 45
Apriamo il file:
1 // database/migrations/ xxxx_xx_xx_xxxxxx_create_libri_table
2
3 public function up()
4 {
5 Schema::create('libri', function (Blueprint $table) {
6 $table->increments('id');
7 $table->string('nome');
8 $table->string('descrizione');
9 $table->string('ISBN');
10 $table->integer('autore_id')->unsigned();
11 $table->foreign('autore_id')->references('id')->on('autori')->onDele\
12 te('cascade');
13 $table->timestamps();
14 });
15 }
Abbiamo un intero: autore_id, che nella riga successiva è indicato come foreign key della tabella
autori. Il metodo finale onDelete rappresenta l’azione in caso di cancellazione dei record della tabella
madre (autori). Con $table->timestamps(), infine, indichiamo a Laravel di creare due campi created_-
at e updated_at: molto utili per comprendere creazione e aggiornamento del record.
Lanciamo la migrazione per creare la tabella. Siamo pronti per interrogare il db? Non ancora: sono
tabelle vuote: possiamo riempirle “a mano”, oppure sfruttare un’altra opzione di Laravel. Come
procediamo? E’ una domanda retorica :)
Nella cartella /database/seeds/ troveremo il file appena creato. Adesso tocca al model factory. Diamo
un’occhiata a questo file:
1 // database/factories/ModelFactory.php
2 $factory->define(App\User::class, function ($faker) {
3 return [
4 'name' => $faker->name,
5 'email' => $faker->email,
6 'password' => str_random(10),
7 'remember_token' => str_random(10),
8 ];
9 });
L’istruzione si riferisce al model User ed indica come riempire i rispettivi campi. A noi, invece,
interessa, la tabella Autori e quella dei Libri: sostituiamo il codice con queste istruzioni:
1 // database/factories/ModelFactories.php
2 $factory->define(App\Autori::class, function ($faker) {
3 return [
4 'nome' => $faker->name,
5 'cognome' => $faker->lastName
6 ];
7 });
8
9 $factory->define(App\Libri::class, function ($faker) {
10 return [
11 'nome' => $faker->name,
12 'descrizione' => $faker->lastName,
13 'ISBN' => $faker->text(10),
14 'autore_id' => factory(App\Autori::class)->create()->id,
15 ];
16 });
Siamo quasi pronti: mancano i model di riferimento. Ancora artisan, il comando è pane quotidiano:
1 // app/Autori.php
2
3 class Autori extends Model
4 {
5 protected $table = 'autori';
6 //
7
8
9 public function libri()
10 {
11 return $this->hasMany('App\Libri', 'autore_id');
12 }
13 }
Per via dell’inglese, indichiamo il nome della tabella. E creiamo un metodo che stabilisce una
relazione tra la tabella Autori e Libri: è una relazione uno a molti e il secondo attributo autore_id,
indica la foreign key presente nella tabella libri. Questo è un frammento di Eloquent che discuteremo
in maniera più approfondita più in là. Questo metodo, tuttavia, è utile sia per il seeding, ma
soprattutto per le future query delle nostre app. Proseguiamo e apriamo l’altro model:
1 // app/Libri.php
2
3 class Libri extends Model
4 {
5 protected $table = 'libri';
6 }
In questo caso abbiamo solo aggiunto il nome in italiano della nostra tabella.
1 //database/seeds/AutoriTableSeeder.php
2
3 public function run()
4 {
5 factory(App\Autori::class, 20)->create()->each(function($u) {
6 $u->libri()->save(factory(App\Libri::class)->make());
7 });
8 }
Creeremo 20 record di autori e per ogni record autori un record nella tabella Libri. La gestione del
foreign key presente in quest’ultima tabella è gestita direttamente nel ModelFactory.php:
1 //database/factories/ModelFactory.php
2 ...
3 'autore_id' => factory(App\Autori::class)->create()->id,
4 ...
E come per incanto ambedue le tabelle saranno riempite con tanti dati.
Faker, dove?
La documentazione completa la trovi qui⁶
⁶https://github.com/fzaninotto/Faker
Eloquent: un soffice inizio
La prima istruzione che ci tornerà utile è una query molto semplice. Apriamo la nostra route e
creiamo una nuova pagina. Il nostro onnipresente controller:
Poi lo apriamo:
1 // app/Http/Controllers/AutoriController.php
2
3 use App\Autori;
4 ...
5
6 public function lista()
7 {
8 $listaAutori = Autori::all();
9
10 foreach($listaAutori as $la)
11 {
12 echo $la['nome'] . ' ' . $la['cognome'] . '<br>';
13 }
14 }
In cima alla classe abbiamo richiamato il Model “Autori” con use…. Poi il metodo lista() del
controller richiama al suo interno un metodo Eloquent: Autori::all(). Il foreach è chiaro, non necessita
spiegazioni.
Subito dopo apriamo il file di route:
1 // app/Http/routes.php
2
3 Route::get('autori', 'AutoriController@lista');
Poi lanciamo:
49
Eloquent: un soffice inizio 50
1 http//localhost:8888/autori
1 // app/Http/Controllers/AutoriController.php
2
3 public function lista()
4 {
5 $listaAutori = Autori::all()->take(5);
6
7 foreach($listaAutori as $la)
8 {
9 echo $la['nome'] . ' ' . $la['cognome'] . '<br>';
10 }
11 }
E’ stato aggiunto anche un ulteriore metodo: get(), necessario quando non è più presente all().
1 // app/Autori.php
2 public function libri()
3 {
4 return $this->hasMany('App\Libri', 'autore_id');
5 }
Questo metodo è stato già utilizzato nel precedente capitolo per il factory. Vediamo come utilizzarlo
per la nostra query:
Eloquent: un soffice inizio 51
1 // app/Http/Controllers/AutoriController.php
2 public function listaLibri()
3 {
4 $listaLibri = Autori::find(5)->libri()->get();
5
6 foreach($listaLibri as $li)
7 {
8 echo $li['nome'] . ' ' . $li['descrizione'] . '<br>';
9 }
10 }
1 // app/Http/routes.php
2 Route::get('listalibri', 'AutoriController@listaLibri');
e lanciamo:
1 http://localhost:8888/listalibri
1 // app/Libri.php
2
3 public function autore()
4 {
5 return $this->belongsTo('App\Autori', 'autore_id');
6 }
Vogliamo sapere l’autore di un determinato libro? Ecco fatto: con l’istruzione belongsTo() indichiamo
l’autore di appartenenza. E’ necessaria anche l’indicazione della foreign_key (secondo attributo).
Il nostro controller sarà (ricordiamoci di richiamare questo metodo nel file di route: evitiamo di
scriverlo qui, ma diamo per scontato di averlo fatto):
Eloquent: un soffice inizio 52
1 // app/Http/AutoriController.php
2 public function qualeAutore()
3 {
4 $autore = \App\Libri::find(5)->autore->nome;
5 echo $autore;
6 }
Qual è la query? Eccola: “cercami il nome dell’autore del libro che ha come id = 5”.
1 // app/Http/Controllers/AutoriController.php
2 public function bigList()
3 {
4 $mieiLibri = \App\Libri::all();
5 return view('libri.lista', compact('mieiLibri'));
6 }
Poi la view:
1 // resources/views/libri/lista.blade.php
2 <table >
3 <thead>
4 <tr>
5 <th>Titolo libro</th>
6 <th>Autore</th>
7 </tr>
8 </thead>
9 <tbody>
10 @foreach($mieiLibri as $ml)
11 <tr>
12 <td>{{ $ml['descrizione'] }}</td>
Eloquent: un soffice inizio 53
13 <td>{{$ml->autore->nome}}</td>
14 </tr>
15 @endforeach
16 </tbody>
17 </table>
1 // app/Http/routes.php
2 Route::get('bigList', 'AutoriController@bigList');
1 http://localhost:8888/bigList
Fatto. Mostruosamente semplice. La relazione è tutta nella view. Ma è possibile l’operazione perché
nel Model Libri c’è il metodo autore
Nel prossimo capitolo affronteremo la bestia nera: relazioni molti a molti e invasioni aliene. Stay
tuned!
Download dell’applicazione
I file trattati in questo capitolo puoi scaricarli a questo indirizzo⁸.
⁷http://laravel.com/docs/5.1/queries
⁸https://www.dropbox.com/s/komlve58uf7cgkm/chapter_9.zip?dl=0
Eloquent: relazioni a triangolo
Ampliamo la nostra biblioteca con un nuovo attore: l’utente che prende in prestito l’ebook (accadrà
presto! O forme ibride già sono in corso). Una tipica relazione “molti a molti” consiste nella possibilità
di un utente di prendere in prestito più ebook e un ebook può essere preso in prestito da più utenti.
Prestiamo libri
Creiamo subito la nostra tabella utenti:
1 // xxxx_xx_xx_xxxxxx_create_utenti_table
2 public function up()
3 {
4 Schema::create('utenti', function (Blueprint $table) {
5 $table->increments('id');
6 $table->string('nome');
7 $table->string('cognome');
8 $table->timestamps();
9 });
10 }
Adesso tocca alla tabella intermedia (detta anche “pivot”) che ospiterà gli ID delle due tabelle “utenti”
e “libri”:
54
Eloquent: relazioni a triangolo 55
1 // xxxx_xx_xx_xxxxxx_create_utenti_table
2 public function up()
3 {
4 Schema::create('prestiti', function (Blueprint $table) {
5 $table->increments('id');
6 $table->integer('utente_id')->unsigned();
7 $table->foreign('utente_id')->references('id')->on('utenti')->onDelete('\
8 cascade');
9 $table->integer('libro_id')->unsigned();
10 $table->foreign('libro_id')->references('id')->on('libri')->onDelete('ca\
11 scade');
12 $table->timestamps();
13 });
14 }
E’ importante notare le foreign key “utente_id” e “libro_id” che fanno rispettivamente riferimento
alla tabella “utenti” e “libri”. Lanciamo il comando per innescare la migration:
Creiamo i due model delle due tabelle. In sequenza questi due comandi:
1 // app/Prestiti.php
2 class Prestiti extends Model
3 {
4 protected $table = 'prestiti';
5 }
1 // app/Utenti.php
2 class Utenti extends Model
3 {
4 protected $table = 'utenti';
5 }
Riempiamo le nostre tabelle (“utenti” e “prestiti”) a piacere assicurandoci che nella tabella “prestiti”
gli ID (“utente_id” e “libro_id”) siano validi, cioè presenti nelle rispettive tabelle “utenti” e “libri” (è
probabile che “libri” ce l’hai già piena di dati). L’obiettivo è riuscire a visualizzare chi ha preso in
prestito il libro oppure più libri. Partiamo dal model “Utenti”:
1 //app/Utenti.php
2
3 public function libriInPrestito()
4 {
5 return $this->belongsToMany('App\Libri', 'prestiti', 'utente_id', 'libro_id'\
6 );
7 }
Creiamo un controller:
1 //app/Http/Controllers/LibriController.php
2 public function inPrestito()
3 {
4 $data['utente'] = \App\Utenti::find(1);
5
6 return view('prestiti.inprestito', $data);
7 }
Cerchiamo l’utente che ha come id = 1 e vediamo quanti libri ha preso in prestito (se abbiamo
assegnato id diversi, dobbiamo cambiare questo numero, naturalmente).
Poi creiamo la nostra view:
Eloquent: relazioni a triangolo 57
1 // resources/views/prestiti/inprestito.blade.php
2 <table class="table table-hover">
3 <thead>
4 <tr>
5 <th>Utente</th>
6 <th>Libro in Prestito</th>
7
8 </tr>
9 </thead>
10 <tbody>
11 @foreach($utente->libriInPrestito as $libro)
12 <tr>
13 <td>{{$utente['nome']}}</td>
14 <td>{{ $libro['nome'] }}</td>
15
16 </tr>
17 @endforeach
18 </tbody>
19 </table>
1 //app/Http/routes.php
2 Route::get('libriPrestito', 'LibriController@inPrestito');
1 http://laravelbook:8888/libriPrestito
1 // app/Libri.php
2 public function miLeggono()
3 {
4 return $this->belongsToMany('App\Utenti', 'prestiti', 'utente_id', 'libro_id\
5 ');
6 }
Il metodo differisce poco da quello presente nel model “Utenti”: cambia solo l’attributo model. In
quest’ultimo caso, infatti, è ‘AppUtenti’.
Andiamo nel nostro controller:
1 //app/Http/Controllers/LibriController.php
2 public function quantiMiLeggono()
3 {
4 $data['libro'] = \App\Libri::find(1);
5
6 return view('prestiti.leggono', $data);
7 }
1 //resources/views/prestiti/leggono.blade.php
2 <table class="table table-hover">
3 <thead>
4 <tr>
5 <th>Libro</th>
6 <th>Chi lo ha preso in prestito</th>
7
8 </tr>
9 </thead>
10 <tbody>
11 @foreach($libro->miLeggono as $ut)
12 <tr>
13 <td>{{$libro['nome']}}</td>
14 <td>{{ $ut['nome'] . ' ' . $ut['cognome'] }}</td>
15 </tr>
16 @endforeach
17 </tbody>
18 </table>
1 Route::get('UtentiCheLeggono', 'LibriController@quantiMiLeggono');
1 http://laravelbook:8888/UtentiCheLeggono
Download dell’applicazione
I file trattati in questo capitolo puoi scaricarli a questo indirizzo⁹.
⁹https://www.dropbox.com/s/lr72zc46zebub3q/chapter_10.zip?dl=0
Eloquent: relazioni complesse
Le polimorfiche
Due tabelle: “Libri” e “Autori” potrebbero condividere una tabella “Genere”. Questo triangolo
particolare può essere espresso con le relazioni polimorfiche. Analizziamo le nostre tabelle:
Autori - id - nome - cognome
Libri - id - nome - descrizione
Genere - id (integer) - nome_genere (string) - genereble_id (integer) - genereble_type (stringa)
La novità, in questo elenco, è rappresentata dalla nuova tabella “Genere”: ha il suo ID e il suo
NOME_GENERE, la GENEREBLE_ID (che può essere indifferentemente l’ID di “Autori” oppure
“Libri”) e la GENEREBLE_TYPE che non contiene altro che il nome del model a cui GENEREBLE_ID
fa riferimento.
Creiamo subito la migration per la tabella “Genere”:
1 // database/migrations/xxxx_xx_xx_xxxxxx_create_genere_table.php
2 ...
3 public function up()
4 {
5 Schema::create('genere', function (Blueprint $table) {
6 $table->increments('id');
7 $table->string('nome_genere');
8 $table->morphs('genereble'); // genera automaticamente generable_id e ge\
9 nerable_type
10
11 });
12 }
60
Eloquent: relazioni complesse 61
Popoliamola, infine con qualche dato, facendo attenzione ai due campi sensibili come generable_id
e generable_type. Ecco un esempio:
1 generable_id (1)
2 generable_type ('Libri')
3
4 generable_id (2)
5 generable_type ('Libri')
6
7 generable_id (10)
8 generable_type ('Autori')
9
10 generable_id (12)
11 generable_type ('Autori')
Eccolo:
1 //app/Genere.php
2 namespace App;
3
4 use Illuminate\Database\Eloquent\Model;
5
6 class Genere extends Model
7 {
8 protected $table = 'genere';
9
10 public function genereble(){
11 return $this->morphTo();
12 }
13 }
Il metodo genereble sarà utile per creare la relazione con le altre due tabelle (“LIBRI” e “AUTORI”).
Nel model Libri aggiungeremo un nuovo metodo:
Eloquent: relazioni complesse 62
1 //app/Libri.php
2 ...
3 protected $morphClass = 'Libri';
4
5 public function generi()
6 {
7 return $this->morphMany('\App\Genere', 'genereble');
8 }
Il metodo generi() restituisce la relazione polimorfica attraverso il metodo morphMany() che accoglie
due attributi: il Model e il suo metodo (lo abbiamo definito prima). E’ bene notare anche la variabile
protetta $morphClass, anch’essa necessaria perché funzioni il tutto.
Apriamo il nostro file di route e verifichiamo il funzionamento:
1 //app/Http/route.php
2 Route::get('LibriPoly', function () {
3 $libri = App\Libri::find(2);
4
5 foreach ($libri->generi as $gen) {
6 echo $libri->nome . ' [' . $gen->nome_genere . ']';
7 }
8 });
1 //app/Autori.php
2 ...
3 protected $morphClass = 'Autori';
4
5 public function generi()
6 {
7 return $this->morphMany('\App\Genere', 'genereble');
8 }
1 //app/Http/routes.php
2 Route::get('AutoriPoly', function(){
3 $autori = App\Autori::find(2);
4
5 foreach($autori->generi as $gen){
6 echo $autori->nome . ' [' . $gen->nome_genere . ']';
7 }
8 });
1 $libri = App\Libri::all();
2
3 foreach($libri as $libro)
4 {
5 echo $libro->autore->nome;
6 }
Laravel effettuerebbe una query per i libri e “n” query per ogni autore del libro. Gosh: se avessimo
200 libri, una query per i libri e 200 query per sapere il nome di autore del libro. C’è un modo per
ovviare questo problema di eccessivo uso di risorse: L’Eager Loading. Ecco come:
1 $libri = App\Libri::with('autore')->get();
2
3 foreach($libri as $libro)
4 {
5 echo $libro->autore->nome;
6 }
Capperi, abbiamo solo aggiunto il metodo with e il gioco è fatto: ecco le query reali:
Eloquent: relazioni complesse 64
Yes, davvero conveniente. Proviamo nella nostra route:visivamente il risultato non cambierà, ma
ci troviamo di fronte ad un risparmio “energetico” meraviglioso. L’eager loading dovrebbe essere
utilizzato praticamente sempre, sempre, sempre.
Download dell’applicazione
I file trattati in questo capitolo puoi scaricarli a questo indirizzo¹⁰.
¹⁰https://www.dropbox.com/s/0cdmolqa1i1a87i/chapter_11.zip?dl=0
Middleware, il tuo cane da guardia
Un fondamentale meccanismo per filtrare le richieste HTTP è rappresentato dal Middleware.
Lo abbiamo (passivamente) incontrato nel capitolo sull’autenticazione. Grazie al Middleware di
autenticazione, infatti, possiamo proteggere agevolmente le pagine rendendole visibili solo previa
autenticazione. Possiamo però creare il nostro Middleware ed un esempio leggermente diverso
dall’autenticazione può essere il seguente: stiamo realizzando un sito ecommerce e per la pagina
legata al metodo di pagamento, abbiamo bisogno di verificare che sia presente almeno un prodotto
nel carrello. In caso contrario l’utente NON può accedere alla pagina del metodo di pagamento.
Il setup
Ripartiamo con una installazione di Laravel (pochi minuti e via). Poi lanciamo il comando artisan
per creare il nostro middleware:
Ricordiamoci di dare sempre nomi significativi al MiddleWare: questo approccio ci aiuterà nella
leggibilità del codice e del filtro (middleware) che stiamo creando. Ottima prassi anche per i colleghi
che lavoreranno sullo stesso progetto. Dopo aver creato il nostro Middleware, registriamolo nel file
Kernel:
1 // app/Http/Kernel.php
2 namespace App\Http;
3
4 use Illuminate\Foundation\Http\Kernel as HttpKernel;
5
6 class Kernel extends HttpKernel
7 {
8 /**
9 * The application's global HTTP middleware stack.
10 *
11 * @var array
12 */
13 protected $middleware = [
14 \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
15 \App\Http\Middleware\EncryptCookies::class,
65
Middleware, il tuo cane da guardia 66
16 \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
17 \Illuminate\Session\Middleware\StartSession::class,
18 \Illuminate\View\Middleware\ShareErrorsFromSession::class,
19 \App\Http\Middleware\VerifyCsrfToken::class,
20 ];
21
22 /**
23 * The application's route middleware.
24 *
25 * @var array
26 */
27 protected $routeMiddleware = [
28 'auth' => \App\Http\Middleware\Authenticate::class,
29 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::c\
30 lass,
31 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
32 ];
33 }
Nell’array “$middleware” registriamo i middleware globali che prescindono dalle chiamate HTTP:
e infatti troviamo la verifica CSRF, la gestione dei Cookie, gestione Sessione, e altro. Nell’array
“$routeMiddleware” registriamo i middleware che filtrano le chiamate Http presenti nel file di route
ed è qui che andremo a registrare il nostro “IlCarrelloEPieno”:
1 // app/Http/Kernel.php
2 ...
3 protected $routeMiddleware = [
4 ...
5 'carrellopieno' => \App\Http\Middleware\IlCarrelloEPieno::class,
6 ];
La route e il Model
Adesso apriamo il file di route e applichiamo questo middleware:
1 // app/Http/routes.php
2 Route::get('/metodospedizione', ['middleware' => 'carrellopieno', function(){
3 return 'Pagina Metodo spedizione';
4 }]);
Bene, abbiamo impostato il filtro, ma non possiamo ancora usarlo: dobbiamo infatti simulare un
controllo nel db e precisamente nel carrello utente per verificare che sia presente almeno un prodotto.
Prima artisan per creare il Model:
Middleware, il tuo cane da guardia 67
Apriamo il file e creiamo un nuovo metodo che simula una chiamata al db:
1 // app/Carrello.php
2 public function ePieno()
3 {
4 $prodottiCarrello = 1;
5
6 if ( $prodottiCarrello > 0)
7 {
8 return true;
9 }
10 return false;
11 }
Nel metodo ePieno() simuliamo che il prodotto sia effettivamente presente. Il metodo restituisce
semplicemente “true” o “false”. Subito dopo gestiamo il metodo già presente nel middleware:
1 // app/Http/Middleware/IlCarrelloEPieno.php
2 public function handle($request, Closure $next)
3 {
4 $carrello = new \App\Carrello;
5 if ($carrello && $carrello->ePieno())
6 {
7 return $next($request);
8 }
9 return redirect('/');
10 }
Il significato è chiaro. Se il metodo restituisce “true” la pagina viene visualizzata, altrimenti c’è un
reindirizzamento alla homepage. Proviamo:
1 http://localhost:8888/metodospedizione
1 // app/Carrello.php
2 public function ePieno()
3 {
4 $prodottiCarrello = 0;
5
6 if ( $prodottiCarrello > 0)
7 {
8 return true;
9 }
10 return false;
11 }
Se digitiamo:
1 http://localhost:8888/metodospedizione
Download dell’applicazione
I file trattati in questo capitolo puoi scaricarli a questo indirizzo¹¹.
¹¹https://www.dropbox.com/s/vb0c6zn9h1b7ou7/chapter_12.zip?dl=0
Paginare è la miglior cosa
Prima o poi capiterà a tutti di avere indecorose liste dati da visualizzare. E la paginazione è il
primo step da valutare per renderle più fruibili. Laravel, per fortuna, offre una serie di soluzioni
comodissime.
Simple pagination
E’ la paginazione più sbrigativa. Eccola:
1 $libri = DB::table('libri')->simplePaginate(15);
Ok, apriamo il nostro model e creiamo un nuovo metodo (ricordiamoci di aggiungere la variabile
protetta “$table”: ci servirà dopo):
1 // app/Libri.php
2 public function lista()
3 {
4 return \DB::table('libri')->simplePaginate(3);
5 }
69
Paginare è la miglior cosa 70
1 // app/Http/Controllers/LibriController.php
2 public function index()
3 {
4 $libri = new \App\Libri();
5 $lista = $libri->lista();
6 return view('libri.lista', compact('lista'));
7 }
compact(), cos’è?
Non è una istruzione Laravel, ma PHP: crea un array con il nome delle variabili e con i
relativi valori (maggiori info qui¹² )
1 // resources/views/libri/lista.blade.php
2 <ul>
3 @foreach($lista as $lib)
4 <li>{{$lib->titolo}}</li>
5 @endforeach
6 </ul>
1 // app/Http/routes.php
2 Route::get('/listalibri', 'LibriController@index');
All’indirizzo:
1 http://localhost:8888/listalibri
avremo la lista dei nostri libri, ma senza link “next”, “previous”, o numeri di paginazione.
1 // app/Libri.php
2 public function listaEloquent()
3 {
4 return \App\Libri::where('id', '>', 3)->paginate(15);
5 }
abbiamo usato anche una condizione. Il risultato è il medesimo: cambia il nome del metodo.
Testiamolo subito con un nuovo metodo nel controller:
1 // app/Http/Controllers/LibriController.php
2 public function eloquentLibri()
3 {
4 $libri = new \App\Libri();
5 $lista = $libri->listaEloquent();
6 return view('libri.lista_elo', compact('lista'));
7 }
1 // app/Http/routes.php
2 Route::get('/listaeloquent', 'LibriController@eloquentLibri');
1 // resources/views/libri/lista_elo.blade.php
2 <ul>
3 @foreach($lista as $lib)
4 <li>{{$lib->titolo}}</li>
5 @endforeach
6 </ul>
7
8 {!! $lista->render() !!}
Verifichiamo:
1 http://laravelbook:8888/listaeloquent
Con una grafica molto triste, comparirà un ulteriore elenco puntato corredato di numeri e freccette
per spostarsi nella paginazione. In realtà la paginazione è pre-formattata per aderire perfettamente al
Framework CSS Bootstrap. Dunque, se includiamo il file css del framework Bootstrap, la paginazione
renderizzata avrà un altro effetto.
Paginare è la miglior cosa 72
Paginazione personalizzata
Se, invece, vogliamo personalizzare la paginazione con le nostre classi CSS, Laravel offre una serie
di metodi per recuperare la pagina precedente, quella successiva, e così via. Ecco l’elenco:
1 $results->count()
2 $results->currentPage()
3 $results->hasMorePages()
4 $results->lastPage() (non disponibile per il metodo simplePaginate)
5 $results->nextPageUrl()
6 $results->perPage()
7 $results->previousPageUrl()
8 $results->total() (non disponibile per il metodo simplePaginate)
9 $results->url($page)
1 // resources/views/libri/lista_elo.blade.php
2 {!! $lista->appends(['categoria' => 'gialli'])->render() !!}
1 http://laravelbook:8888/listaeloquent?categoria=gialli&page=2
Laravel, infine, permette anche di variare l’URL con il metodo setPath(). Se infatti aggiungessimo
nel metodo del controller, l’istruzione:
¹³https://github.com/Landish/Pagination
Paginare è la miglior cosa 73
1 // app/Http/Controllers/LibriController.php
2 public function eloquentLibri()
3 {
4 $libri = new \App\Libri();
5 $lista = $libri->listaEloquent();
6 $lista->setPath('custom/url');
7 return view('libri.lista_elo', compact('lista'));
8 }
1 http://laravelbook:8888/custom/url?categoria=gialli&page=2
Download dell’applicazione
I file trattati in questo capitolo puoi scaricarli a questo indirizzo¹⁴.
¹⁴https://www.dropbox.com/s/fw1flyi4k692rtd/chapter_13.zip?dl=0
Caching, non solo quando hai poche
risorse
La nostra applicazione è talmente popolare che il server non ce la fa a gestire migliaia di pagine.
Ogni richiesta della pagina è una query al db, il server rallenta, poi va in crash. Diverse le soluzioni:
un server più potente, refactoring del codice, caching delle query. Il servizio di caching offerto da
Laravel è piuttosto intuitivo e di facile applicazione. Può essere utilizzato con i più importanti sistemi
di caching backend (Memcached o Redis, ad esempio).
Ma può gestire anche il caching attraverso file, ed è questa l’impostazione di default che andremo
ad utilizzare. Prima di proseguire è consigliabile utilizzare una installazione fresh di Laravel.
Dove si configura
Il file di configurazione è in config/cache.php e quello che ci interessa sapere è che la cartella per il
caching è localizzata qui:
1 'file' => [
2 'driver' => 'file',
3 'path' => storage_path('framework/cache'),
4 ],
Non occorre modificare nulla per adesso (verifichiamo che la cartella in questione, però, abbia i
permessi di scrittura).
74
Caching, non solo quando hai poche risorse 75
1 // app/Http/routes.php
2 Route::get('/salva_dati_in_cache', function(){
3 $scadenza = \Carbon\Carbon::now()->addMinutes(10);
4 \Cache::put('nome', 'Francesco', $scadenza);
5 return 'Dati salvati';
6 });
L’istruzione put accoglie tre valori: il primo è la chiave (che poi utilizzeremo per richiamare il dato),
il secondo è il valore, il terzo è la quantità di minuti di “resistenza” della cache: allo scadere di
questi ultimi il dato non sarà più disponibile. Ecco perché prima valorizzeremo una variabile che
rappresenta la data e l’ora con 10 minuti in più dell’ora attuale (now()).
Prima di procedere oltre, richiamiamo questa pagina per salvare i dati in cache:
1 http://localhost:8888/salva_dati_in_cache
Cos’è Carbon?
E’ un pacchetto molto conosciuto che Laravel include di default nel suo set di pacchetti.
Con Carbon la gestione delle date in PHP risulta piuttosto semplice. (maggiori info qui¹⁵)
1 // app/Http/routes.php
2 Route::get('/recupera_dati_in_cache', function(){
3 return \Cache::get('nome');
4 });
Con get, dunque, andremo a recuperare ciò che abbiamo salvato. Per sapere se il dato è in cache,
modifichiamo l’istruzione precedente con un if :
¹⁵http://carbon.nesbot.com/
Caching, non solo quando hai poche risorse 76
1 //app/Http/routes.php
2 if ( \Cache::has('nome'))
3 {
4 return \Cache::get('nome');
5 }
Aggiornare i dati
E’ possibile gestire anche il caso in cui i dati in cache non siano presenti e si desideri una chiamata
ad un db per recuperarli e salvarli, dunque, in cache. Tutto in un colpo solo! Vediamo come:
1 // app/Http/routes.php
2 Route::get('/se_non_ci_sono_recupera', function(){
3 $scadenza = \Carbon\Carbon::now()->addMinutes(10);
4 \Cache::remember('lavoro', $scadenza, function(){
5 return 'Web Developer'; // questo potrebbe essere il risultato di una qu\
6 ery
7 });
8 });
1 // app/Http/routes.php
2 Route::get('/esiste_cache', function(){
3 if (\Cache::has('lavoro'))
4 {
5 return \Cache::get('lavoro');
6 }
7 });
1 \Cache::forever('nome', 'Francesco');
1 // app/Http/routes.php
2 Route::get('/se_non_ci_sono_recupera_per_sempre', function(){
3 \Cache::rememberForever('lavoro', function(){
4 return 'Web Developer'; // questo potrebbe essere il risultato di una qu\
5 ery
6 });
7 });
Cancellare la cache
Qualsiasi dato salvato in cache sia con il metodo put, sia con il metodo forever può essere cancellato:
l’istruzione è tanto semplice quanto importante:
1 \Cache::forget('nome');
Può essere utile anche richiamare il dato e cancellarlo subito dopo. Ecco come:
1 $variabile = \Cache::pull('nome');
1 \Cache::flush();
Download dell’applicazione
I file trattati in questo capitolo puoi scaricarli a questo indirizzo¹⁶.
¹⁶https://www.dropbox.com/s/554v7u4jyleg7u8/chapter_14.zip?dl=0
Le mie classi, i miei Helper: tutto mio
Laravel è un framework molto potente, ma i web developer di una certa portata potrebbero avere
la necessità di portarsi nello zaino le proprie classi realizzate in anni di fatica. Il framework “alza le
mani” e ne permette un facile utilizzo. Dalla nostra fresca installazione di Laravel vediamo come.
1 // app/Services/Messaggio.php
2
3 namespace App/Services;
4
5 class Message{
6
7 public function sayHello(){
8 return 'hello';
9 }
10 }
Una classe molto semplice. Vogliamo però utilizzarla all’interno della nostra applicazione. Creiamo
un classico controller con artisan:
78
Le mie classi, i miei Helper: tutto mio 79
1 // app/Http/Controllers/Homepage.php
2
3 use App\Services\Message;
4
5 class Homepage extends Controller
6 {
7
8 public function index()
9 {
10 $m = new Message();
11 return $m->sayHello();
12 }
13 }
La prima cosa che dovrebbe saltare all’occhio è il richiamo alla classe “use AppServicesMessage”.
All’interno del metodo, invece, istanzieremo la classe e richiameremo il metodo: troppo semplice!
Un ultimo accorgimento nel file di route:
1 // app/Http/routes.php
2
3 Route::get('/', 'Homepage@index');
Ok, all’indirizzo:
1 http://localhost:8888
1 // app/Services/Message.php
2 public function mySessionId()
3 {
4 return \Session::getId();
5 }
1 // app/Http/Controllers/Homepage.php
2
3 ...
4 public function index()
5 {
6 $m = new Message();
7 return $m->mySessionId();
8 }
Al solito indirizzo:
1 http://localhost:8888
1 // app/helper.php
2
3 function moltiplicaPerCento($numero)
4 {
5 return $numero * 100;
6 }
Tutto qui? In un certo senso sì. Non bisogna creare una classe, ma solo metodi. Dobbiamo però dire
a Laravel che c’è questa classe. Apriamo il file composer e aggiungiamo nella sezione “autoload”:
Le mie classi, i miei Helper: tutto mio 81
1 "autoload": {
2 "classmap": [
3 "database"
4 ],
5 "psr-4": {
6 "App\\": "app/"
7 },
8 "files": [
9 "app/helper.php"
10 ]
11
12 },
1 composer dump-autoload
1 // app/Http/routes.php
2
3 Route::get('numeri', function(){
4 return moltiplicaPerCento(150);
5 });
Alla pagina:
1 http://localhost:8888/numeri
Download dell’applicazione
I file trattati in questo capitolo puoi scaricarli a questo indirizzo¹⁷.
¹⁷https://www.dropbox.com/s/w343e157wpslj4g/chapter_15.zip?dl=0
Service Provider, un pacchetto cotto e
mangiato
Utilizzare nelle nostre applicazioni un ServiceProvider ha senso se di applicazioni complesse si tratta.
E’ un libro pratico e passiamo alla pratica. Nella lettura se ne comprenderà l’utilità.
La nostra classe
Abbiamo la necessità di costruire un sistema di classi: immaginiamo di avere una classe Biblioteca
e due sottoclassi Autori e Libri. Partiamo:
1 // app/Services/BibliotecaClass.php
2
3 namespace App\Services;
4
5 class BibliotecaClass{
6
7 public function listaAutori()
8 {
9
10 }
11
12
13 public function listaLibri()
14 {
15
16 }
17 }
82
Service Provider, un pacchetto cotto e mangiato 83
1 // app/Services/AutoriClass;
2 namespace App\Services;
3
4 class AutoriClass{
5
6 public function lista()
7 {
8 return [
9 'Umberto Eco'
10 ,'Sandro Veronesi'
11 ,'Vittorio Sgarbi'
12 ];
13 }
14 }
In ultimo LibriClass:
1 // app/Services/LibriClass.php
2 namespace App\Services;
3
4 class LibriClass{
5
6 public function lista()
7 {
8 return [
9 'Il nome della rosa'
10 ,'Caos Calmo'
11 ,'Piene di Grazia'
12 ];
13 }
14 }
Dunque, a questo punto consideriamo la classe BibliotecaClass come quella principale, cioè quella
che utilizzerà, al suo interno, AutoriClass e LibriClass. Vediamo come:
Service Provider, un pacchetto cotto e mangiato 84
1 // app/Services/BibliotecaClass.php
2 namespace App\Services;
3 use App\Services\AutoriClass;
4
5 class BibliotecaClass{
6
7 public function listaAutori()
8 {
9 $autori = new AutoriClass();
10 return $autori->lista();
11 }
12
13
14 public function listaLibri()
15 {
16
17 }
18 }
1 // app/Http/routes.php
2 Route::get('/', function () {
3 $bi = new App\Services\BibliotecaClass;
4 return $bi->listaAutori();
5 });
Il nostro ServiceProvider
La procedura è identica se vogliamo utilizzare la classe LibriClass: evitiamo l’esempio e andiamo
avanti. Siamo pronti per il ServiceProvider. Artisan ci viene in aiuto:
1 // app/Providers/BibliotecaServiceProvider.php
2 namespace App\Providers;
3
4 use Illuminate\Support\ServiceProvider;
5
6 class BibliotecaServiceProvider extends ServiceProvider
7 {
8 /**
9 - Bootstrap the application services.
10 *
11 - @return void
12 */
13 public function boot()
14 {
15 //
16 }
17
18 /**
19 - Register the application services.
20 *
21 - @return void
22 */
23 public function register()
24 {
25 //
26 }
27 }
E i metodi “statici”
L’obiettivo è rendere “statici” i metodi di BibliotecaClass. Per fare questo occorre una classe Facade.
Eccola:
Service Provider, un pacchetto cotto e mangiato 86
1 // app/Services/Facade/BibliotecaClass.php
2 namespace App\Services\Facades;
3
4 use Illuminate\Support\Facades\Facade;
5
6 class BibliotecaClass extends Facade{
7
8 protected static function getFacadeAccessor() { return 'bibliotecaclass'; }
9 }
Questa classe ha solo un metodo getFacadeAccessor con all’interno il nome della classe. Facciamo
interagire questa classe con il ServiceProvider:
1 // app/Providers/BibliotecaServiceProvider.php
2
3 ...
4 public function register()
5 {
6 \App::bind('bibliotecaclass', function () {
7 return new \App\Services\BibliotecaClass;
8 });
9 }
1 // config/app.php
2 'providers' => [
3 ...
4 App\Providers\BibliotecaServiceProvider::class,
5
6 ];
1 // config/app.php
2
3 'aliases' => [
4 ...
5 'Biblio' => App\Services\Facades\BibliotecaClass::class,
6 ];
1 // app/Http/routes.php
2 Route::get('/', function () {
3
4 return Biblio::listaAutori();
5 });
Precisazioni
L’esempio mostrato è ridicolo. Ma, come accennato all’inizio del capitolo, il ServiceProvider ha una
sua utilità in progetti complessi, dove l’interazione tra classi è essenziale. E’ inoltre un approccio
molto elegante e pulito se vogliamo creare dei pacchetti per Laravel da scaricare nella cartella vendor.
Ad esempio:
Come? Sarebbe bello sapere come farlo? Nei prossimi capitoli affronteremo anche questo argomento
:)
Download dell’applicazione
I file trattati in questo capitolo puoi scaricarli a questo indirizzo¹⁸.
¹⁸https://www.dropbox.com/s/sftwsbguh1pfj0f/chapter_16.zip?dl=0
View composer: evitare ridondanze
Quante volte sarà capitato di ripetere query in ogni metodo del controller? Sempre, mai? Qualunque
sia la risposta, le View Composer sono un “must” per chi vuol fare questo mestiere con serietà.
L’utilità è proprio quella di evitare ridondanze nel codice, ottimizzando il trasferimento di variabili
all’interno delle view.
1 // resources/views/template.blade.php
2 <!DOCTYPE html>
3 <html lang="en">
4 <head>
5 <meta charset="utf-8">
6 <meta http-equiv="X-UA-Compatible" content="IE=edge">
7 <meta name="viewport" content="width=device-width, initial-scale=1">
8 <!-- The above 3 meta tags *must* come first in the head; any other head content\
9 must come *after* these tags -->
10 <title>Bootstrap 101 Template</title>
11
12 <!-- Bootstrap -->
13 <!-- Latest compiled and minified CSS -->
14 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css\
15 /bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZl\
16 pLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
17
18 <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queri\
19 es -->
20 <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
21 <!--[if lt IE 9]>
22 <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
23 <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
24 <![endif]-->
25
26 </head>
88
View composer: evitare ridondanze 89
27 <body>
28 <nav class="navbar navbar-inverse navbar-fixed-top">
29 <div class="container">
30 <div class="navbar-header">
31 <button type="button" class="navbar-toggle collapsed" data-toggle="colla\
32 pse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
33 <span class="sr-only">Toggle navigation</span>
34 <span class="icon-bar"></span>
35 <span class="icon-bar"></span>
36 <span class="icon-bar"></span>
37 </button>
38 <a class="navbar-brand" href="#">Project name</a>
39 </div>
40 <div id="navbar" class="collapse navbar-collapse">
41 <ul class="nav navbar-nav">
42 <li class="active"><a href="#">Home</a></li>
43 <li><a href="#about">About</a></li>
44 <li><a href="#contact">Contact</a></li>
45 </ul>
46 </div><!--/.nav-collapse -->
47 </div>
48 </nav>
49
50 <div class="container">
51 <div class="row">
52 <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
53 Hello
54 </div>
55 </div>
56 </div>
57
58 <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
59 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js">\
60 </script>
61 <!-- Include all compiled plugins (below), or include individual files as needed\
62 -->
63
64 <!-- Latest compiled and minified JavaScript -->
65 <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js\
66 " integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9a\
67 J7xS" crossorigin="anonymous"></script>
68
View composer: evitare ridondanze 90
69 </body>
70 </html>
1 // resources/views/includes/partialnav.blade.php
2 <nav class="navbar navbar-inverse navbar-fixed-top">
3 <div class="container">
4 <div class="navbar-header">
5 <button type="button" class="navbar-toggle collapsed" data-toggle="colla\
6 pse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
7 <span class="sr-only">Toggle navigation</span>
8 <span class="icon-bar"></span>
9 <span class="icon-bar"></span>
10 <span class="icon-bar"></span>
11 </button>
12 <a class="navbar-brand" href="#">Project name</a>
13 </div>
14 <div id="navbar" class="collapse navbar-collapse">
15 <ul class="nav navbar-nav">
16 <li class="active"><a href="#">Home</a></li>
17 <li><a href="#about">About</a></li>
18 <li><a href="#contact">Contact</a></li>
19 </ul>
20 </div><!--/.nav-collapse -->
21 </div>
22 </nav>
1 // resources/views/template.blade.php
2
3 @include('includes.partialnav')
4 ...
1 // app/Http/routes.php
2 Route::get('/', function () {
3 return view('template');
4 });
All’indirizzo localhost (o come è stato chiamato in base al vostro ambiente), avremmo il template
Bootstrap.
La tabella
Prima di tutto ci occorre un Model per gestire la nostra tabella “cart”:
Nel file appena creato indichiamo che la tabella non sarà al plurale:
1 // app/Cart.php
2
3 class Cart extends Model
4 {
5 protected $table = 'cart';
6 }
Andiamo a cancellare nella cartella “database/migrations/” i due file che riguardano la tabella “users”
e “password”: non ci interessa creare queste tabelle. Lavoriamo, invece, sulla tabella “cart”:
1 // database/migrations/2015_xx_xx_101927_create_cart_table.php
2
3 public function up()
4 {
5 Schema::create('cart', function (Blueprint $table) {
6 $table->increments('id');
7 $table->integer('idprodotto');
8 $table->timestamps();
9 });
10 }
La tabella è imbarazzante per la sua semplicità. A noi serve solo riempirla con un paio di record. E
lo faremo senza alcuna illustrazione.
View composer: evitare ridondanze 92
Variabile
Nel nostro file di inclusione ‘partialnavbar’ vogliamo aggiungere la quantità di prodotti presenti nel
carrello (mi auguro siano stati aggiunti un paio di record). Ecco come: lo faremo sostituendo la riga
“About” (ma può essere posizionato ovunque):
1 // resources/views/includes/partialnav.blade.php
2 <ul class="nav navbar-nav">
3 <li class="active"><a href="#">Home</a></li>
4 <li><a href="">Carrello ({{$prodottiCarrello}})</a></li>
5 <li><a href="#contact">Contact</a></li>
6 </ul>
Naturalmente se richarichiamo la pagina, Laravel ci restituirà l’errore. Risolviamolo nel file di route:
1 // app/Http/routes.php
2
3 Route::get('/', function () {
4
5 $prodottiCarrello = App\Cart::count();
6 return view('template', compact('prodottiCarrello'));
7 });
Bene, fin qui nulla di eccezionale. Ma se dovessimo visualizzare la quantità dei prodotti in ogni
metodo del controller (quindi in ogni pagina), dovremmo portarci appresso questa query ovunque
perché si trova nel template, o meglio nella barra di navigazione. Quindi, in un’ipotetica nuova
pagina dovremmo scrivere questo:
1 // app/Http/routes.php
2 Route::get('prodotti', function(){
3 $prodottiCarrello = App\Cart::count();
4 return view('template', compact('prodottiCarrello'));
5 });
E così per tutte. Laravel ci viene in aiuto con le View Composer. Vediamo come ottimizzare questo
processo. Creiamo un nostro ServiceProvider con il comando artisan:
1 // app/Providers/ViewComposerServiceProvider.php
2 namespace App\Providers;
3
4 use Illuminate\Support\ServiceProvider;
5
6 class ViewComposerServiceProvider extends ServiceProvider
7 {
8 /**
9 - Bootstrap the application services.
10 *
11 - @return void
12 */
13 public function boot()
14 {
15 view()->composer('includes.partialnav', function($view){
16 $view->with('prodottiCarrello', \App\Cart::count());
17 });
18 }
19
20 /**
21 - Register the application services.
22 *
23 - @return void
24 */
25 public function register()
26 {
27 //
28 }
29 }
1 // app/config.php
2
3 'providers' => [
4 ...
5 App\Providers\ViewComposerServiceProvider::class,
6 ],
1 // app/Http/routes.php
2 Route::get('/', function () {
3 return view('template', compact('prodottiCarrello'));
4 });
5
6
7 Route::get('prodotti', function(){
8 return view('template', compact('prodottiCarrello'));
9 });
Adesso richiamiamo le nostre pagine sul browser e, come per incanto, funzionerà tutto alla
perfezione. Ma facciamo qualcosina di più, rendiamo il nostro codice ancora più “lungimirante”. Nel
nostro ServiceProvider spostiamo la porzione del codice in un ulteriore metodo, perché potremmo
aver bisogno di più View Composer in base alle esigenze e il metodo “boot” si sovraffollerebbe:
1 // app/Providers/ViewComposerServiceProvider.php
2
3 public function boot()
4 {
5 $this->viewForNavigation();
6 }
7
8 private function viewForNavigation()
9 {
10 view()->composer('includes.partialnav', function ($view) {
11 $view->with('prodottiCarrello', \App\Cart::count());
12 });
13 }
Inoltre possiamo evitare di utilizzare una Closure nel metodo “composer”, passando a quest’ultimo
direttamente un metodo di una classe ulteriore. Vediamo in pratica:
1 // app/Providers/ViewComposerServiceProvider.php
2 private function viewForNavigation()
3 {
4 view()->composer('includes.partialnav', 'App\Http\Composers\Navigation');
5 }
La classe composer che andremo a creare ha un metodo di default “compose” all’interno del quale
andremo a definire la nostra funzione. Creiamola:
View composer: evitare ridondanze 95
1 // app/Http/Composers/Navigation.php
2 namespace App\Http\Composers;
3
4 use Illuminate\Contracts\View\View;
5
6 class Navigation
7 {
8
9 public function compose(View $view){
10
11 $view->with('prodottiCarrello', \App\Cart::count());
12 }
13 }
1 // app/Http/Composers/Navigation.php
2 namespace App\Http\Composers;
3
4 use Illuminate\Contracts\View\View;
5
6 class Navigation
7 {
8
9 public function nav(View $view){
10
11 $view->with('prodottiCarrello', \App\Cart::count());
12 }
13 }
1 // app/Providers/ViewComposerServiceProvider.php
2 private function viewForNavigation()
3 {
4 view()->composer('includes.partialnav', 'App\Http\Composers\Navigation@nav');
5 }
Conclusioni
Le View Composer sono uno strumento efficacissimo per le operazioni ridondanti e per l’ottimiz-
zazione del codice. Quando si presentano query da ripetere in diverse pagine il loro utilizzo è
d’obbligo.
View composer: evitare ridondanze 96
Download dell’applicazione
I file trattati in questo capitolo puoi scaricarli a questo indirizzo¹⁹.
¹⁹https://www.dropbox.com/s/jr3mt1vl325vjdo/chapter_17.zip?dl=0
Appendice
Laravel 5.2, le novità
A questo punto la tabella users è creata. Con la classe Factory riempiamo velocemente la tabella
con un po’ di dati. Ecco come: nel file database/factories/ModelFactory.php troveremo una closure
che riceverà un’istanza dalla libreria PHP Faker che - a sua volta - genererà una serie di dati dalla
tipologia indicata in questo file. Non tocchiamo nulla ed apriamo il file di route.
1 // app/Http/routes.php
2 Route::get('genera', function(){
3 factory(App\User::class, 3)->create();
4 });
Abbiamo appena utilizzato la closure che creerà tre record casuali nel nostro db. Dopo aver riempito
un db vediamo la procedura per visualizzare un record ed entriamo nel vivo dell’implicit route
binding. Ecco un esempio:
1 // app/Http/routes.php
2
3 Route::get('utenti/{user}', function(App\User $user){
4 return $user;
5 });
97
Appendice 98
1 // app/Http/routes.php
2 Route::get('utenti/{user}', function(App\User $utente){
3 return $utente;
4 });
non avremmo alcun risultato perché la variabile non è identica alla wildcard {user}, quindi è una
comodità, ma può diventare una scomodità in caso di refactoring del codice. Questa procedura è
inoltre rapida se vogliamo procedere per ID, ma cosa accade se volessimo utilizzare come segmento
il campo name presente nella tabella? Se volessimo utilizzare lo stesso esempio di prima:
1 // app/Http/routes.php
2 Route::get('utenti/{name}', function(App\User $name){
3 return $name;
4 });
Laravel continuerebbe a considerare la wildcard {name} come ID. Per variare questo comportamento,
andiamo nel nostro RouteServiceProvider e scriviamo:
1 // app/Providers/RouteServiceProvider.php
2
3 ...
4
5 public function boot(Router $router)
6 {
7 parent::boot($router);
8
9 \Route::bind('name', function($name){
10 return \App\User::where('name', $name)->firstOrFail();
11 });
12 }
1 http://localhost/utenti/Markus Block
(nel mio caso uno dei nomi è Markus Block: è una variabile!).
Ed ecco che il risultato è sempre il record indicato. E’ bene osservare come il primo parametro di
Route::bind() sia importante: deve coincidere con la wildcard nel file di route.