Laravel – primi passi: il CRUD

Spread the love
CRUD con Laravel
Laravel logo

CRUD è l’acronimo per Create, Read, Update, Delete che sono le quattro operazioni fondamentali sui dati.

Laravel non ha molta automazione da questo punto di vista, dobbiamo costruirci le form HTML per creare e modificare i file, così come le viste per visualizzare tutti i dati oppure solo un record e alla fine la funzionalità di cancellazione.

Però ha un sistema di creazione di pagine di Blade che ci permettono il riutilizzo intelligente di molte parti di layout.

In questo aritcolo ci muoveremo su tutti e tre i domini del paradigma MVC.

CRUD 1: creiamo il template

Come prima operazione creiamo un template che riutilizzeremo in tutta l’applicazione. Lo creiamo in resources/views/layouts/app.blade,php:

<!doctype html>
<html lang="it">
<head>
    <meta charset="UTF-8">
    <title>Logistic Mapper</title>
</head>
<body>
<div class="container">

    @yield('content')

</div>

@yield('footer')

</body>
</html>

La direttiva @yield prenderà il contenuto della sezione content della vista chiamata e la metterà qui. Quindi se il controller chiama la view ‘edit.blade’ il suo contenuto verrà calcolato (cioè verranno messi dentro i dati) e poi prenderà posto dentro il tag “container”. È analoga ad una direttiva include. Quindi tutte le view avranno il titolo Logistic Mapper in questo caso.

CRUD 2: rassegna dei metodi da implementare

Create

Cominciamo con la creazione di nuovi records, operazione che nel dominio dei database è una insert.

La rotta per l’operazione create è la seguente:

| GET|HEAD  | company/create     | company.create  | App\Http\Controllers\CompanyController@create     |

Attenzione: questa azione consiste nella visualizzazione della form vuota (non stiamo ancora creando alcun record).
Il verbo HTTP a cui viene associata è GET e il nome della rotta è company.create. Ciò significa che la pagina web che dobbiamo progettare starà sotto /resources/view/company e si chiamerà create.blade.phpe verrà invocata dal metodo create del controller CompanyController.

La view che creiamo è questa:

@extends('layouts.app')

@section('content')
<h1>Brand New Company</h1>
<form action="/company" method="post">
    @csrf
<table>
    <tr>
        <th>ID</th>
        <th>Name</th>
        <th>Website</th>
        <th>Email</th>
        <th>latitude</th>
        <th>longitude</th>
        <th>company_type_id</th>
    </tr>
    <tr>
        <td>
            <input type="text" size="2" readonly name="id" >
        </td>
        <td>
            <input type="text" name="name" >
        </td>
        <td>
            <input type="text" name="website" >
        </td>
        <td>
            <input type="text" name="email" >
        </td>
        <td>
            <input type="text" name="latitude" >
        </td>
        <td>
            <input type="text" name="longitude" >
        </td>
        <td>
            <select name="company_type_id">
                @foreach($company_types as $v)
                <option value="{{$v->id}}">
                    {{$v->name}}
                </option>
                @endforeach
            </select>
        </td>
    </tr>
    <tr>
        <td colspan="7"><input type="submit"></td>
    </tr>
</table>
    <p><a href="{{route('company.index')}}"><< Companies</a></p>
</form>

@endsection

La prima direttiva (@extends) indica in quale template deve essere inclusa questa pagina web, ed è layouts/app.blade.php (blade.php è un suffisso standard, il formato in dot notation della vista è layouts.app).

La seconda dichiarazione @section è la sezione content, che è il frammento che dev’essere incluso nel template layouts.app.

Segue la form. Attenzione!

  • la action dev’essere semplicemente /company: la risorsa da invocare davvero non è decisa qui ma è decisa a livello della tabella di routing, in questo caso è il metodo CompanyController@create
  • il method dev’essere conguente con il metodo espresso nella tabella di routing , quindi GET.
  • c’è un’ulteriore direttiva @csrf che serve per evitare che il metodo venga chiamato via HTTP da un’altro sito. Ci siamo già occupati di questo attacco in questo post.

Come si vede ho una serie di campi di tipo testo e una combobox che devo popolare in anticipo. Il controller deve fare tutte queste cose:

    public function create()
    {
        $company_types= CompanyType::all();

        return view('company.create', compact('company_types'));
    }

La query su CompanyType serve appunto per procurarsi la combo box. Per questo si usa il metodo all() del model. Il risultato questo:

Create company
Create company

Store

DOpo aver compilato il modulo, occorre salvare il dato. L’operazion Eloquent equivalente all’insert come detto è store. Quindi per vedere come dobbiamo comprtarci dobbiamo studiarci la riga corrispondente della tabella di routing:

| POST      | company   | company.store | App\Http\Controllers\CompanyController@store     |

In questo caso il form HTML dovrà utlizzare il metodo POST che in questo caso viene preso davvero in consderazionie e non viene riscritto da alcuna direttiva. Inoltre il metodo di controller sarà CompanyController@store . Anche se è prevista la view company.store non si verrà usata perché verrà fatto un redirecto verso l’elenco di tutte le Company (company.index) in caso di successo.

Quindi il metodo store è il seguente:

    public function store(Request $request)
    {
        $company = new Company($request->all());
        $company->save();

        return redirect(route('company.index'));
    }

È semplicissimo: si crea un nuovo oggetto della classe Company inizializzandolo con i valori dell’array $request->all().

Attenzione! se tentiamo di passare come argomento semplcemente $request, Laravel s’incazza perché $request è un oggetto, mentre il costruttore del model vuole un array.

Il metodo save() di Eloquent si trasforma in una insert della tabella companies.

Finalmente, viene inviata al browser una direttiva di redirect verso la view company.index.

Edit

Il terzo metodo ci consente di editare i dati per poi eseguire un update. Quindi questo metodo serve per visualizzare la form di modifica e non per modificare i dati, operazioe che sarò in carico al metodo update.

Al solito ci facciamo guidare dalla tabella di routing

GET|HEAD |company/{company}/edit| company.edit |App\Http\Controllers\CompanyController@edit |

Qui l’URI di accesso alla pagiba di edit era già stato generato nella pagina index dalla funzione

route('company.edit', $v->id)

l’URI generato è proprio del tipo company/{company}/edit come indicato dalla tabella di routing, dove il segnaposto {company} è sostituito dal valore numerico della chiave primaria della company. Il verbo HTTP utilizzato anche in quetso è GET (Apache deve servirci una pagina web).

Il metodo edit del CompanyController deve caricare dal database i dati da inserire nella form di modifica:

    public function edit($id)
    {
        $company = Company::find($id);
        $company_type = CompanyType::all();
        $array = array(
            'company' => $company,
            'company_type' => $company_type,
            'company_type_id' => $company->company_type_id
        );

        return  view('company.edit', compact('array'));
    }

Viene caricato

  • l’array $company con i dati del record;
  • l’array $company_type
  • il valore $company_type_id che ci serve solo per posizionare correttamente la combo box; non sarebbe necessaria ma la scrittura della view ne risulta così un po’ più chiara.

La view è la seguente:

@extends('layouts.app')

@section('content')
<h1>Company edit</h1>
<form action="/company/{{$array['company']->id}}" method="post">
    @csrf
    @method('PUT')
<table>
    @if ($array['company']->id != null)
    <tr>
        <th>ID</th>
        <th>Name</th>
        <th>Website</th>
        <th>Email</th>
        <th>latitude</th>
        <th>longitude</th>
        <th>company_type_id</th>
    </tr>
    <tr>
        <td>
            <input type="text" size="2" readonly name="id" value="{{$array['company']->id}}">
        </td>
        <td>
            <input type="text" name="name" value="{{$array['company']->name}}" >
        </td>
        <td>
            <input type="text" name="website" value="{{$array['company']->website}}" >
        </td>
        <td>
            <input type="text" name="email" value="{{$array['company']->email}}" >
        </td>
        <td>
            <input type="text" name="latitude" value="{{$array['company']->latitude}}" >
        </td>
        <td>
            <input type="text" name="longitude" value="{{$array['company']->longitude}}" >
        </td>
        <td>
            <select name="company_type_id">
                @foreach($array['company_types'] as $v)
                <option value="{{$v->id}}" @if ($v->id == $array['company_type_id']) selected @endif>
                    {{$v->name}}
                </option>
                @endforeach
            </select>
        </td>
    </tr>
    <tr>
        <td colspan="7"><input type="submit"></td>
    </tr>
    @else
    <tr>
        <td colspan="7">No records found</td>
    </tr>
    @endif
</table>
    <p><a href="{{route('company.index')}}"><< Companies</a></p>
</form>

@endsection

Qui la riflessione più grossa la dobbiamo fare sulla action della form: essa deve invocare il metodo update, quindi, guardando la tabella di routing:

PUT|PATCH|company/{company}| company.update | App\Http\Controllers\CompanyController@update |

dobbiamo utillizzare il verbo HTTP PUT e impostare la action su company/{company}.

Il metodo di Controller update sarà

    public function update(Request $request, $id)
    {
        $company = Company::find($id);

        $company->update($request->all());

        return redirect(route('company.index'));
    }

Viene creato l’oggetto $company da una query sul model per ID, viene passato al metodo update l’array $request->all() e alla fine viene rediretto il browser sull’indice.

Perché per operazioni diverse siamo verbi HTTP diversi?

La RFC-2616 (Hyper Text Transfer Protocol HTTP/1.1) stabilisce che il metodo PUT va usato per sostituire una risorsa esistente nel server. Ci si ricordi che all’inizio HTTP era un protocollo che si usava solo per lavorare coi file.

Il metodo POST invece va usato per trasferire dati (nuovi) dal client al srever, quindi il risultato è la creazione di una nuova risorsa.

Per questo il metodo PUT è idempotente: se applicato più volte, produce lo stesso risultato.

Il metodo POST invece non lo è, ogni volta che lo si applica vengono create nuove risorse; questo è proprio il comportamento delle operazioni database di Update (PUT) e Insert (Post), per cui questo è il senso di utilizzare i verbi HTTP in modo appropriato per le varie operazioni.

Questa distinzione va fatta anche quando si progetta l’interfaccia API di un server.

Riferimenti

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.