I primi controller CRUD
Dopo aver definito la struttura dei dati e dopo aver introdotto una reale persistenza dei dati, possiamo finalmente concentrarci sui controller più o meno ufficiali per il nostro progetto. La prima funzionalità che aggiungeremo metterà a disposizione del cliente alcune interfacce per le operazioni CRUD (Create, Read, Update, Delete) sulle varie entità disponibili. L'acronimo CRUD incapsula tutte le funzionalità base che vengono spesso implementate nei backoffice. Per il momento ci concentreremo sulle funzionalità core e tralasceremo altri aspetti come le performance e la sicurezza, questi argomenti, non secondari, li affronteremo in futuro.
Il core di Laravel non presenta un modulo per la gestione dell'HTML e dei form, ma esiste una dipendenza esterna, chiamata Laravel Collective Form, che permette di creare form in maniera dichiarativa e object-oriented. Laravel Collective è una suite che può includere diverse funzionalità, per il momento utilizzeremo solamente il modulo HTML. Una volta installato tramite Composer, dobbiamo configurare il provider e i due alias. Per il momento ignoriamo lo scopo di queste attività in quanto le approfondiremo nelle prossime lezioni.
Passiamo ora alla creazione dei controller tramite i comandi Laravel. Lanciamo quindi php artisan make:controller AuthorController
e php artisan make:controller BookController
e configuriamo le rotte in questo modo per il mapping automatizzato tra rotte e metodi:
Route::resource('author', 'AuthorController');
Route::resource('book', 'BookController');
L'implementazione dei controller è molto simile, all'interno dell'articolo approfondiremo in dettaglio solamente AuthorController
e per quanto riguarda BookController
le sole caratteristiche peculiari.
AuthorController
Partiamo quindi con la definizione del controller AuthorController
analizzando metodo per metodo, ricordando che ogni metodo corrisponde ad un URL mappato automaticamente tramite Route::resource
.
Il metodo index
si occupa di mostrare l'elenco degli autori disponibili:
public function index()
{
$authorList = Author::orderBy('firstname', 'asc')->orderBy('lastname', 'asc')->get();
return View::make('author/index')->with('authorList', $authorList);
}
Il metodo è abbastanza banale: tramite Eloquent recuperiamo l'elenco degli autori ordinati per firstname
e lastname
e passiamo la lista ottenuta alla view author/index
. I metodi create
e store
si occupano della creazione di un autore. Il primo permette di inizializzare un form vuoto, mentre il secondo della validazione e della persistenza:
public function create()
{
$author = new Author();
return View::make('author/form')->with('author', $author);
}
public function store(Request $request)
{
$this->validate($request, Author::$rules);
Author::create($request->all());
return Redirect::to(route('author.index'));
}
Il primo metodo non fa altro che istanziare un nuovo autore vuoto e di passarlo a author/form
, store
invece invoca la validazione e in caso positivo salva l'autore all'interno del database e redirige l'utente alla pagina precedente.
Riguardo alla validazione, l'implementazione di store
utilizza il metodo validate
, ereditato dal controller grazie al trait ValidatesRequests
che richiede l'oggetto request
e un elenco di regole di validazione, nella fattispecie la proprietà statica $rules
presente in Author
. Avere le regole di validazione definite dentro il modello è una prassi consolidata in modo da poterle condividere senza doverle ripetere. Ecco come sono implementate:
public static $rules = [
'firstname' => 'required|min:3|max:40',
'lastname' => 'required|min:3|max:40'
];
I metodi edit
ed update
rappresentano gli speculari di create
e store
ma si occupano della modifica di un record esistente. Il primo recupera l'autore corrente dal database mentre il secondo valida e persiste:
public function edit($id)
{
$author = Author::findOrFail($id);
return View::make('author/form')->with('author', $author);
}
public function update(Request $request, $id)
{
$this->validate($request, Author::$rules);
$author = Author::findOrFail($id);
$author->update($request->all());
return Redirect::to(route('author.index'));
}
Rispetto ai due metodi precedenti l'unica differenza risiede nel fatto che non partiamo da un autore vuoto, ma lo recuperiamo, grazie a findOrFail
dal database. L'ultimo metodo si occupa dell'eliminazione di un record:
public function destroy($id)
{
Author::destroy($id);
return Redirect::to(route('author.index'));
}
Una volta definiti i metodi passiamo alle view: index
e form
(riutilizzato sia in fase di create che di edit).
@section('content')
<p>
<a href="{{ route('author.create') }}">Create new author</a>
</p>
<table class="table">
@foreach($authorList as $author)
<tr>
<td>{{ $author->firstname }} {{ $author->lastname }}</td>
<td>{{ $author->bookCount }}</td>
<td>
<a class="btn btn-default" href="{{ route('author.edit', ['author' => $author->id]) }}">Modifica</a>
</td>
<td>
{!! Form::open(['route' => ['author.destroy', $author->id], 'method' => 'delete' ]) !!}
{!! Form::submit('Elimina', ['class' => 'btn btn-danger']) !!}
{!! Form::close() !!}
</td>
</tr>
@endforeach
</table>
@endsection
index.blade.php
si occupa di mostrare all'interno di una table i record ottenuti dal controller e di inserire i link per la modifica e l'eliminazione. Da notare che il metodo delete
deve essere invocato utilizzando POST, quindi è necessario mascherare il form con un pulsante.
@section('content')
{!! Form::model($author, [
'route' => isset($author->id) ? ['author.update', $author->id] : 'author.store',
'method' => isset($author->id) ? 'put' : 'post'
]) !!}
<div class="form-group {{ $errors->has('firstname') ? 'has-error' : '' }}">
{!! Form::label('firstname', 'Firstname') !!}
{!! Form::text('firstname', null, ['class' => 'form-control']) !!}
@foreach($errors->get('firstname') as $error)
<span class="help-block">{{ $error }}</span>
@endforeach
</div>
<div class="form-group {{ $errors->has('lastname') ? 'has-error' : '' }}">
{!! Form::label('lastname', 'Lastname') !!}
{!! Form::text('lastname', null, ['class' => 'form-control']) !!}
@foreach($errors->get('lastname') as $error)
<span class="help-block">{{ $error }}</span>
@endforeach
</div>
<div class="form-group">
{!! Form::submit('Save', ['class' => 'btn btn-primary']) !!}
</div>
{!! Form::close() !!}
@endsection
La seconda view, form.blade.php
, è senza dubbio più interessante. Innanzitutto si nota come il form viene instanziato grazie all'helper Form::model
che permette di associare automaticamente i campi HTML con le proprietà del modello passato come parametro. Inoltre differenziamo la rotta in base alla presenza o meno dell'id (se non ha id
sarà una create
altrimenti una edit
). Per ogni campo viene mostrata label, campo di testo (gestito automaticamente da Form::model
) e l'eventuale messaggio di errore.
BookController
Il secondo controller è sviluppato con gli stessi pattern visti in precedenza tranne per alcuni aspetti. Per esempio nel metodo index
utilizziamo with
per usare l'eager loading e ottimizzare il numero di query eseguite:
$bookList = Book::orderBy('title', 'asc')->with('author')->get();
Ciò è perché l'informazione dell'autore sarà necessaria all'interno della view:
<td>{{ $book->author->lastname }}</td>
Nei metodi create
e edit
, che devono preparare la form per la creazione e la modifica, sarà necessario recuperare la lista degli autori disponibili che serviranno per popolare una select dando all'utente la possibilità di scegliere l'autore del libro corrente. Cosi facendo costruiamo un array associativo chiave/valore (in particolare id
/lastname
) da passare alla view:
$authorList = Author::orderBy('lastname', 'asc')->orderBy('firstname', 'asc')->lists('lastname', 'id');
per poi utilizzarlo nella view grazie a Form::select
:
{!! Form::select('author_id', $authorList, null, ['class' => 'form-control'])!!}
Conclusioni
Biblios ha finalmente ha raggiunto un livello di utilizzabilità accettabile. Dal prossimo articolo introdurremo aspetti avanzati che ci permetteranno di arricchire l'applicazione.