Paginazione risultati con Laravel

Installare innanzitutto la paginazione con Laravel (non sarebbe nescessario, ma se si vuole personalizzare occorre farlo)

$ php artisan vendor:publish --tag=laravel-pagination

Vengono installate delle nuove view sotto resources/view/vendor/pagination

Delle viste che vengono installate, ho trovato che il tradeoff più conveniente sia questa: bootstrap-4.

Modificare così a view (es. resources/views/companies/index.blade.php):

            @foreach($companies as $v)
            ...
            @endforeach
            <tr>
                <td colspan="9">
                    {{ $companies->links('vendor.pagination.bootstrap-4') }}
                </td>
            </tr>

Il controller deve richiamare la stessa vista; l’ho un attimo modificato per aggiungere anche un filtro di ricerca:

    public function index(Request $request)
    {
        if (($request->has('name')) and ($request->input('name') != '')) {
            $name = $request->input('name');
            $param = "%$name%";
            $companies = Company::where('name', 'like', $param)->orderBy('name', 'asc')->paginate(config('app.results_per_page'));
        } else {
            $companies = Company::select('*')->orderBy('name', 'asc')->paginate(config('app.results_per_page'));
        }

        return view('companies.index', compact('companies'));
    }

È conveniente estrapolare la costante “numero di risultati per pagina” nel file delle constanti applicative (config/app.php):

    /*
    |--------------------------------------------------------------------------
    | Local constants
    |--------------------------------------------------------------------------

    */
    'results_per_page' => 5,
];

Il risultato è il seguente

Laravel pagination
Paginazione risultati con Laravel

Risorse web

Pillole Java: Gestione della memoria

Architettura Hotspot JVM

La Java Virtual Machine è un software che fornisce un ambiente operativo virtuale per applicazioni Java.

L’architettua Java Hotspot è composta di tre elementi

  • il motore di esecuzione:
    • è composto di un compilatore a tempo immediato (just in time o JIT compiler) che traduce il codice in chiaro in bytecode (compilazione) al momento in cui deve essere utilizzato
    • e di un garbage collector (che è l’aspetto su cui ci focalizziamo in questo articolo), un collettore di spazzatura che fa la raccolta differenziata degli oggetti usati e li rimuove con una specifica politica
  • l’area dati di esecuzione (runtime data area) che è una memoria organizzata in cui vengono caricati i metodi, i threads, lo heap (che è la zona che ci interessa) e altre zone. In particolare lo heap (mucchio) è la zona riservata ai dati transienti trattati dall’applicazione Java
  • il class loader che è un regista che istanza in memoria RAM gli oggetti e i metodi quando richiesti
Gestione della memoria in Java: Architettura HotSpot JVM
Gestione della memoria in Java: Architettura HotSpot JVM

Lo heap

Lo heap è una zona di memoria RAM nella quale Hotspot gestisce i dati dell’applicazione. In questa zona viene implementata una vera e propria raccolta differenziata degli oggetti in base all’età di occupazione. L’età di occupazione è calcolata con un contatore per ogni oggetto: ogni volta che viene istanziato, incremento il suo contatore. Ogni volta che lo distruggo (imposto la variabile) a null, lo decremento di 1. Quando il contatore raggiunge 0 è tempo di buttare via l’oggetto. Quindi programmaticamente è buona norma impostare a null una variabile se non la si vuole più usare, perché così riesco a raggiungere la condizione che scatena l’evento minor garbage collection che consiste nello spostamento o l’eliminazione fisica dell’area di memoria che contiene la variabile.

I dati vengono diversificati in tre zone “temporali”:

  • Young Generation
  • Old Generation
  • Permanent Generation
Struttura dello Java Heap (Wikipedia)
Struttura dello Java Heap (Wikipedia)

La zona Young Generation è quella in cui vengono allocati tutti gli oggetti istanziati da poco tempo; è a sua volta suddivisa in Eden (oggetti appena creati) e Survivor. Il raccoglitore differenziato (minor garbage collector) sposta gli oggetti dall’Eden alla zone di Sopravvivenza e da qui alla Old Generation. Uno spostamento da Young a Old è considerato un evento di minor garbage collcection.

Gli oggetti un po’ più datati vivono nella Old generation; vengono spostati da qui dal racccoglitore differenziato maggiore ed eliminati, Questo evento è condierato una major garbage collection.

Nella zona Permament Generation vengono collezionati metodi e classi, che sono considerati metadati. Anche l’eliminazione da parte del GC degli oggetti di questa zona è considerata major.

Quindi gli oggetti vengono spostati gradualmente da una zona all’altra fino alla loro eliminazione completa.

Come si produce garbage?

Consideriamo questo programma

public void factory (int num) {
  Die d1 = new Die();
  Die d2 = new Die(num);
  int numfaces = d1.getFaces();
  Die d3 = d2;
} 

Questo metodo istanzia due oggetti d1 e d2, poi un terzo d3; e una variabile locale numfaces.

Ci sono due tipi di variabili: le variabili di istanza o campi e le variabili locali o parametri.

  • Le variabili di istanza sono le aree di variabili dove memorizziamo le proprietà della classe
  • Le variabili locali sono variabili temporanee definite all’interno del metodo. I parametri sono semplicemente variabili locali di Java con la differenza che esse non vengono inizializzate dal metodo ma dal programma chiamante.

Il tempo di vita delle variabili locali dal tempo di esecuzione del metodo che le contiene, da quando viene chiamato a quando ritorna al programma chiamante.

A questo proposito dobbiamo tenere a mente che le variabili locali o i parametri di tipo primitivo vengono memorizzati in un’area di memoria detto stack (pila); ogni metodo ha il suo stack. Mentre le variabili locali e i parametri di tipo oggetto vengono invece memorizzati nello heap. Tuttavia nello stack vengono mantenuti i riferimenti (i puntatori) a questi oggetti dello heap, come illustrato nella figura seguente

Stack e heap per il metodo Java factory
Stack e heap per il metodo Java factory

Il tempo di vita dell’oggetto è in sostanza il tempo di esecuzione del metodo. Ora d1, d2 e d3 sono variabili locali del metodo sopra.

Se aggiungiamo una ulteriore riga alla fine

   d1 = d3

cosa succede?

Ciò che succede è che ogni riferimento all’oggetto Die 1 viene perso e l’oggetto può essere rimosso dalla RAM dal garbage collector.

L’algoritmo di gestione dello hep consente, soprattutto per programmi di grandi dimensioni, di rendere efficiente la raccolta differenziata.

Un algoritmo più ingenuo potrebbe passare in rassegna tutti gli oggetti istanziati e controllare se il programma in qualche punto li chiama (cioè: se esiste un riferimento). Gli oggetti lasciati fuori da questa scansione possono essere buttati via. Però questo algoritmo diventa rapidamente inefficiente all’aumentare del numero di oggetti istanziati; ovvero, in ultima analisi, dalla complessità del programma.

L’analisi che ha portato a questo tipo di gestione, si rifaceva allo studio della quantità di memoria allocata per oggetti che venivano eliminati quasi subito: ad esempio iterators che duravano per un solo ciclo. Questa statistica ha messo in luce la preponderanza di molti oggetti che durano poco, una specie di mortalità infantile, per cui ci si è concentrati su algoritmi che liberavano prima questa parte di memoria.

Tipi di GC

I garbage collectors di Hotspot sono 4:

  • serial
  • parallel
  • CMS
  • G1

Questi si comportano sostanzialmente in modo diverso, ma dobbiamo pensare che ci sono processi per così dire “attivi” che portano avanti l’esecuzione del programma principale e processi per così dire “passivi” che invece si occupano di fare pulizia. A volte i secondi devono interrompere i primi e questa modalità è detta “Stop-the-world mode GC”.

Serial è un unico thread che si occupa di eliminare molti garbage uno alla volta.

Parallel si occupa di eventi minori (come lo spostamento da un contentore all’altro) e ogni garbage è spostato da un thread separato, quindi si gestiscono più segmenti alla volta.

CMS (Concurrent Mark-sweep) vengono marcati e cancellati più segmenti tramite thread paralleli per minimizzare l’arresto del programma principale (per minimizzare gli Stop-the-world).

G1 (Garbage First) è un’alternativa concorrente di CMS.

Compattazione

Il modo di gestire la cancellazione degli oggetti non più usati è allo stesso modo critica: si possono adottare diverse tecniche, la più ingenua è cancellare loggetto sic et simpliciter (tecnica Mark-Sweep), ma questo reimpirebbe la memoria di “buchi” e sarebbe diffficile riutilizzae lo spazio reso disponibile.

Mark-sweep

Quindi ci sono dei metodi più evoluti, come il Copy o il Mark-Compact che riallocano i dati da cancellare in modo da renderli contigui e creare delle zone libere più grandi possibili e continue (senza buchi).

Mark-Compact

Risorse nel blog

Fondamentali di JVM: la differenza tra JDK e JRE

Bibliografia

Fondamenti sui formati raster

Sto lavorando ad una applicazione GIS, per cui mi documento sugli aspetti teorici di base dell’applicazione.

Queste informazioni si trovano sul sito di ArcGIS anche se in questo lavoro sto utilizzando principalmente QGIS.

Faccio una breve premessa.

Raster è sinonimo di reticolo: le informazioni di un file raster sono l’unione di una informazione geometrica (coordinate piane bidimensionali) e del valore scalare di una o più grandezze associate al punto geometrico:

(x, y, g_1, g_2, \ldots)

Più specificatamente, nell’ambito GIS, l’informazione geometrica è in realtà un’informazione geografica, una copia di coordinate piane che in genere sono associate alla rappresentazione di una porzione del geoide secondo un determinato sistema di riferimento locale.

C’è infatti il problema matematico di rappresentare localmente le coordinate curvilinee di una sfera su un piano attraverso una mappatura univoca e che deformi il meno possibile. Ci sono molti modi di mappare una superficie sferica su una piana e questi modi prendono il nome di proiezioni. Queste nozioni fanno parte della disciplina della Matematica che sia chiama Geodesia.

Lo standard di riferimento per i file raster utilizzati in cartografia è il GeoTIFF. Nei riferimenti web c’è un link alle specifiche.

I set di dati raster rappresentano le caratteristiche geografiche ottenute dividendo il terreno in celle discrete quadrate o rettangolari disposte su una griglia. Ogni cella ha un valore che viene utilizzato per rappresentare una grandezza fisica di quella posizione, come la temperatura, l’elevazione o un valore spettrale.

raster dataset diagram
raster 1

I set di dati raster sono comunemente usati per rappresentare e gestire immagini, modelli digitali di elevazione e numerosi altri fenomeni. Spesso i raster vengono utilizzati per rappresentare feature di punti, linee e poligoni. Nell’esempio seguente, puoi vedere come una serie di linee e poligoni (set di dati vettoriali) viene rappresentata come un set di dati raster.

Un diagramma vettoriale rappresentato come un raster
Un diagramma vettoriale rappresentato come un raster

I raster possono essere utilizzati per rappresentare tutte le informazioni geografiche (caratteristiche, immagini e superfici) e dispongono di un ricco set di operatori di geoprocessing analitico. Oltre ad essere un tipo di dati universale per contenere le immagini in GIS, i raster sono anche ampiamente utilizzati per rappresentare le caratteristiche, consentendo a tutti gli oggetti geografici di essere utilizzati nella modellazione e nell’analisi basate su raster.

Raster nel geodatabase

Un raster è un insieme di celle disposte in righe e colonne — una matrice bitmap — ed è un set di dati comunemente usato in GIS. Gli utenti in genere utilizzano molti file raster, tuttavia molti utenti vedono una crescente necessità di gestire i dati raster, insieme alle altre informazioni geografiche, in un DBMS. Il geodatabase fornisce un mezzo molto efficace per la gestione dei dati raster.

Strategie di gestione raster

Sono importanti due strategie di gestione dei dati per i raster:

  • Approvvigionamento di raster: mettere rapidamente “in gioco” i set di dati raster nel tuo GIS significa che molto probabilmente li utilizzerai così come sono, in genere come una serie di file raster. Può trattarsi di una serie di file indipendenti oppure è possibile utilizzare una tecnologia come l’estensione Image per ArcGIS for Server per gestire e servire questi set di dati esistenti come una raccolta.
  • Raster nel geodatabase: questa strategia è utile quando si desidera gestire i raster, aggiungere comportamenti e controllare lo schema; gestire un insieme ben definito di set di dati raster come parte del tuo DBMS; necessità di ottenere prestazioni elevate senza perdita di contenuti e informazioni (nessuna compressione); e un’unica architettura dati per la gestione di tutti i tuoi contenuti.

Proprietà geografiche dei dati raster

In genere vengono registrate quattro proprietà geografiche per tutti i set di dati raster. Questi diventano utili per la georeferenziazione e aiutano a spiegare come sono strutturati i file di dati raster. Questo concetto è importante da capire: aiuta a spiegare come vengono archiviati e gestiti i raster nel geodatabase.

I set di dati raster hanno un modo speciale per definire la posizione geografica. Una volta che le celle o i pixel possono essere georeferenziati con precisione, è facile avere un elenco ordinato di tutti i valori delle celle in un raster. Ciò significa che ogni set di dati raster ha in genere

  • un record di intestazione che ne contiene le proprietà geografiche e
  • il corpo del contenuto è semplicemente un elenco ordinato di valori di cella.

Le proprietà geografiche per un raster in genere includono

  • il suo sistema di coordinate
  • Una coordinata di riferimento o una posizione x,y (in genere l’angolo superiore sinistro o inferiore sinistro del raster)
  • le dimensioni della cella
  • Il conteggio di righe e colonne

Queste informazioni possono essere utilizzate per trovare la posizione di una cella specifica. Avendo queste informazioni disponibili, la struttura dei dati raster elenca tutti i valori delle celle in ordine dalla cella in alto a sinistra lungo ogni riga alla cella in basso a destra, come illustrato di seguito.

Diagramma dei valori delle celle
Diagramma dei valori delle celle

La tabella dei blocchi raster nel geodatabase

I dati raster sono in genere di dimensioni molto maggiori rispetto alle funzionalità e richiedono una tabella laterale per l’archiviazione. Ad esempio, una tipica ortoimmagine può avere fino a 6.700 righe per 7.600 colonne (più di 50 milioni di valori di cella).

Per ottenere prestazioni elevate con questi set di dati raster più grandi, un geodatabase raster viene suddiviso in riquadri più piccoli (denominati blocchi) con una dimensione tipica di circa 128 righe per 128 colonne o 256 x 256. Questi blocchi più piccoli vengono quindi tenuti in un lato tabella per ogni raster. Ogni riquadro separato è contenuto in una riga separata in una tabella a blocchi come mostrato di seguito.

Diagramma di visualizzazione della tabella a blocchi
Diagramma di visualizzazione della tabella a blocchi

Questa semplice struttura significa che solo i blocchi per un’estensione devono essere recuperati quando sono necessari, invece dell’intera immagine. Inoltre, i blocchi ricampionati utilizzati per costruire le piramidi raster possono essere archiviati e gestiti nella stessa tabella di blocchi come righe aggiuntive.

Ciò consente di gestire raster di enormi dimensioni in un DBMS; produrre prestazioni molto elevate; e fornire un accesso multiutente e sicuro.

Estensione dei raster

I raster sono molto e sempre più utilizzati nelle applicazioni GIS. Il geodatabase può gestire i raster per molti scopi: come singoli insiemi di dati, raccolte logiche di insiemi di dati e attributi di immagini nelle tabelle.

Una serie di funzionalità del geodatabase consente agli utenti di estendere il modo in cui gestiscono le proprie informazioni raster come segue:

Usa…… se devi…
Set di dati rasterGestire set di dati di immagini contigue molto grandi e raster a mosaico.
diagramma di raster
Set di dati a mosaicoUn mosaico di dati è un modello di dati che è un ibrido di un catalogo raster e un set di dati raster, che rappresenta una vista al volo di un catalogo raster. Consentono di archiviare, gestire, visualizzare ed eseguire query su raccolte di dati di immagini raster.
Set di dati a mosaico
Cataloghi rasterGestire un layer raster affiancato, in cui ogni riquadro è un raster separato.
Schema piastrelle

Gestire qualsiasi serie di raster in un DBMS
Serie di diagrammi raster

Gestire una serie temporale raster.
Diagramma della serie temporale
Colonne di attributi raster nelle tabelleArchivia immagini o documenti scansionati come attributi nelle tabelle.

Raster come diagramma degli attributi

Riferimenti web

Applicazione AJAX con Laravel

ajax logo
ajax logo

Premetto che, a quanto ho visto, non c’è un vero e proprio standard in Laravel per gestire chiamate asincrone (AJAX). Quindi il tutto rimane sempre un po’ sporco perché si è costretti a mescolare il codice generando, ad esempio, codice HTML al volo con Javascript. Non ho trovato un modo diverso.

Laravel ci viene incontro con le rotte, i controller, ma il momento topico di AJAX, cioè il momento in cui un singolo brano della pagina web viene aggiornato asincroncamente, è del tutto dirty.

Ma andiamo con ordine. Il mio esempio è una semplice form di ricerca in un tabella che visualizza i risultati mano a mano che si digita nella casella di testo.

Step 1: rotta con la quale invocare il server

La chiamata server to server è definita nel file routes/web.php oppure in route/api.php ed è la seguente

Route::post('companies/search', 'App\Http\Controllers\CompanyController@search')->name('companies.search');

Definisco cioè una nuova rotta che viene invocata con il metodo POST con la quale utilizzerò un nuovo metodo del controller.

Nota: il metodo POST implica che il payload (la stringa di ricerca) sia trasmesso al server nel corpo del messaggio, non nell’URL. Quindi non si deve aggiungere la stringa di ricerca nell’url:

Route::post('companies/search/{nome}', 'App\Http\Controllers\CompanyController@search')->name('companies.search');

Step 2: il controller

Il controller implementa un nuovo metodo, search(), che accetta un parametro di ingresso che è la Request che contiene la stringa che scriveremo nella casella di testo, esegue una query e ritorna il recordset con encoding JSON:

    public function search(Request $request)
    {
        $name = $request->all()['name'];
        $companies = Company::where('name', 'LIKE', '%'.$name.'%')->get();
        return json_encode($companies);
    }

So far so good.

Step 3: il controller per il Frontoffice

Ho creato un secondo controller che ha il ruolo di disegnatore della form del frontoffice: non è strettamente necessario ma ho in mente una serie di funzinalità che non sono soltanto inerenti al modello Company per cui astraggo da quest’ultimo.

<?php

namespace App\Http\Controllers;

use App\Models\Company;
use Illuminate\Http\Request;

class FrontofficeController extends Controller
{
    function index()
    {
        return view('frontoffice.index');
    }

}

Step 4: la view

@extends('layouts.app')

@section('content')
<div class="container">
    <form action="" method="post">
        <div class="form-group">
            <label for="name">Company</label>
            <input type="text" name="name" id="name" class="form-control" placeholder="Nome" autocomplete="off">
        </div>
        <div class="form-group">
            <button class="btn btn-success btn-submit">Invia</button>
        </div>
    </form>

    <div class="table">
        <table class="table" id="companies">
            <thead>
            <tr>
                <th scope="col">id</th>
                <th scope="col">name</th>
                <th scope="col">website</th>
                <th scope="col">email</th>
            </tr>
            </thead>
            <tbody>
            </tbody>
        </table>
    </div>
</div>
@endsection

Nota 1: La casella di testo che servirà da trigger per la chiamata AJAX si chiama #name.

Nota 2: c’è un blocco <tbody/> che andrà popolato asincronicamente

Step 5: jQuery does the magic!

Questo metodo, anche se lo trovate in 10000 forum, ha una sua complessità che spiego un po’ alla volta.

L’ho implementato dentro al file <APP>/resources/layouts/app.blade.php, anche se potrei isolarlo in un file javascript a parte e includerlo nel blocco HTML di head, tra i tag di <script>:

       $(document).ready(function() {
            $.ajaxSetup({
                headers: {
                    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                }
            });

            $("#name").keyup(function (e) {
                e.preventDefault();

                let name = $("input[name=name]").val();
                let CSRF_TOKEN = '{{ csrf_token() }}';

                if (name != '') {
                    $.ajax({
                        type: 'POST',
                        url: "{{ route('companies.search') }}",
                        data: {name: name, _token: CSRF_TOKEN},
                        success: function (data) {
                            let result_tag = "";
                            $.each(JSON.parse(data), function (i, item) {
                                result_tag += "<tr>" +
                                    "<td>" + item.id + "</td>" +
                                    "<td>" + item.name + "</td>" +
                                    "<td>" + item.website + "</td>" +
                                    "<td>" + item.email + "</td>" +
                                    "</tr>";
                            });
                            $('#companies tbody').html(result_tag);
                        }
                    });

                } else {
                    $('#companies tbody').html('');
                }
            });
        });

L’evento che scatena il Javascript è keyup, quando rilascio il tasto parte la chiamata asincrona:

Leggo il valore della casella di testo (linea 11); se nella variabile name c’è qualcosa, preparo la ricerca AJAX (linea 15), predisponendo il metodo POST (linea 16), l’url che ho definito nella rotta (linea 17) ed il passaggio dati nel corpo del messaggio HTTP (linea 18).

Se il servizio web risponde con un 200 (linea 19) creo i tag HTML di riga contenenti i risultati provenienti in JSON dal servizio web (il controller infatti li codifica in JSON) . Terminato il ciclo metto tutto il contenuto all’interno dei tag <tbody> (linea 29).

Nota che il tbody viene riscritto ad ogni pressione del tasto sulla tastiera.

Quest’ultimo Step è un po’ messy, si mescolano in un solo file HTML, Blade (e fin qui OK) ma anche Javascript. È una soluzione sporca, che non mi piace. Il problema è che qui affido la generazione dell’HTML ad un programma Javascript anziché a Blade. In sostanza però la logica di generazione del’HTML è in un file blade, quindi a rigore non starei derogando del tutto dal paradigma MVC.

Ultimo step: il csfr_token

Eh, se avete letto con attenzione fin qui, vi sarete chiesti come mai sorvolavo sempre su questo aspetto. Era importante dedicargli una sezione a parte.

Come detto in quest’altro articolo, è importante verificare che l’interrogzione del webservice search sia fatta dal sito stesso e non da qualcun altro, per evitare i problemi anche gravi conseguenti alla non adozione di contromisure.

La tecnica è quella di aggiungere al form html il tag @csfr che fa inviare al server una stringa complessa che poi deve venire controllata lato server. Siccome solo il server conosce questa stringa ci siamo protetti dagli abusi.

Il tag @csfr si può anche definire in un tag meta dellHTML e leggerlo poi in sede di servizo web confrontandolo con quello che ci arriva.

In realtà dobbiamo solo preoccuparci della trasmissione del token in quanto già Laravel controlla per noi la congurenza: se non arriva il token arriva con un valore sbagiato il server Apache emetterà un errore 419 e non funzionerà i servizio.

Il token può essere generato con la direttva @csfr scritta nel Form html , oppure con la funzione di blade {{csrf_token()}}, oppure inserito in un tag meta dell’intestazione html:

<!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

per poi essere letta dal modulo javascript:

$('meta[name="csrf-token"]').attr('content')

Visto che è la stessa pagina posso anche calcolarlo direttamente (come ho fatto in linea 12) per poi essere spedito in più modi al server; o nel payload della richesta (linea 18) oppure nell’intestazione HTTP (linee 2-6). O in entrambe.

L’omissione di questo passaggio provoca un errore 419 (unknown status).

È tutto.

Riferimenti in rete

Laravel: i18n di una applicazione

i18n localizzazione di un'applicazione
i18n localizzazione di un’applicazione

La localizzazione (o internationalization o i18n) di Laravel è già gestita da un apposito middleware. Seguiamo questi pochi passi necessari ad atttivare la funzionalità multilingua per un’applicazione Laravel.

Attivazione e configurazione del middleware i18n

$ php artisan make:middleware Localization

Questo comando crea un nuovo file in app/Http/Middleware/Localization.php che va personalizzato se vogliamo gestire la lingua con le variabili di sessione. L’alternativa sarebbe specificare sempre in ogni rotta la lingua che si vuole usare. Un incubo!

Modifichiamo così il file:

    public function handle(Request $request, Closure $next)
    {
        if (Session::has('locale')) {
            App::setLocale(Session::get('locale'));
        }

        return $next($request);
    }

Con il comando setLocale si valorizza la lingua usata in quel momento dall’applicazione con il valore contenuto dalla variabile di sessione.

Configurazione della rotta per lo switch della lingua

Ora occorre un metodo per fare lo switch e lo facciamo in una rotta che scriviamo in routes/web.php:

Route::get('language/{locale}', function ($locale) {
    app()->setLocale($locale);
    session()->put('locale', $locale);
    return redirect()->back();
});

Possiamo anche scegliere di portare il codice che si trova nella funzione anonima dentro al controller HomeController.

Creazione dei file di localizzazione

Poi creiamo due file di lingua dentro a resources/lang/ e li chiamiamo en.json e it.json. Possiamo fare quanti file vogliamo, attenzione che ad ogni rotta deve corrispondere un file!

Per esempio un estratto del file italiano può essere il seguente:

{
    "Dashboard": "Cruscotto",
    "logout": "Esci",
    "Companies": "Aziende",
    "Users": "Utenti",
    "Name": "Ragione Sociale",
    "Website": "Sito web",
    "Email":  "Email",
    "Main Phone #": "Telefono principale",
    "latitude":  "Latitudine",
    "longitude": "Longitudine",
    "company_type":  "Tipo Azienda",
    "Commands": "Comandi",
    "Create new": "Nuovo record"
}

Comandi HTML per lo switch

Ora inseriamo i comandi per cambiare lingua nell’header dell’applicazione: lo facciamo in un modo grezzo che può essere migliorato, ma è questione di Blade e Vue:

                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <!-- Left Side Of Navbar -->
                    <ul class="navbar-nav me-auto">
                        <li class="nav-item">
                            <a class="nav-link" href="dashboard/">{{ __('Dashboard') }}</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="{{ route('companies.index') }}">{{ __('Companies') }}</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="{{ route('users.index') }}">{{ __('Users') }}</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="/language/it">it</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link" href="/language/en">en</a>
                        </li>
                    </ul>

Sostituire tutte le stringhe cablate con i segnaposto

Qui si può vedere anche come vengono utilizzati i segnaposto definiti in resources/lang: occorre sostituire ad esempio la stringa “Companies” cablata nell’HTML/Blade con il segnaposto {{__('Companies')}}.

Risorse web

Laravel primi passi: generare model, migrazione e seeding in un solo comando

Laravel logo
Laravel logo

La creazione di un model in Laravel può generare contestualmente anche altre classi, come le migrazioni per la tabella associata, il controller, le rotte resource, fino addirittura alle classi Faker/Seeder per riempire di dati di test la tabella.

$ php artisan make:model Role -mcrf

Questo comando oltre a creare il model creerà anche la migrazione (m), il controller (c), le resources (r) o azioni CRUD del controller. Inoltre creerà le classi Faker e Seeder (f) che consentono di specificare il tipo di dati di test con cui riempire le tabelle (Faker) e istruire il Seeder con il numero di record da creare.

Tutto in un solo comando!

Poi però occorre personalizzare ogni classe creata in base a cosa vogliamo fare.

Ho già creato il model, voglio creare le resources

Si possono dare i comandi anche in modo asincrono. In questo esempio:

$ php artisan make:controller UserController --resources

Sul package Faker

François Zaninotto è l’autore di questo package veramente formidabile. Egli ha deciso di abbandonare il progetto poiché “has a design problem”. Un difetto di progettazione iniziale ha fatto sì che le varie localizzazioni non potessero sessere scaricate separatamente dal pacchetto ma si dovesse scaricar tutto insieme.

Nel tempo le dimensioni del package con tutte le localizzazioni ha raggiunto dimensioni ragguardevoli e il numero di download è aumentato esponenzialmente (a fine 2020 era a 125 M download) e Zaninotto si è posto il problema dell’impatto ecologico in termini carbon footprint.

Be’ il suo calcolo lo ha impressionato, ha preferito chiudere e abbandonare il progetto, che ora è solo readonly.

Tutta la storia completa la potete leggere qui.

Altre risorse Laravel

Laravel: protezione con login Auth/Vue.js

Vue.js
Vue.js

Aggiungiamo Auth + Vue.js, il modo più diretto di aggiungere al progetto Laravel una protezione con password. Il plugin Auth è uno scaffold di autenticazione che utilizza Vue.js.

Installazione di Vue/Auth

composer require laravel/ui:^2.4
php artisan ui vue --auth

npm install
npm run dev
php artisan migrate

Il comando ui installa le componenti Vue.js e lo scaffolding necessario per le maschere di autenticazione, recupero password, eccetera e le funzionalità javascript delle interfacce.

npm

Il Node.js Packet Manager va invocato perché il prodotto Auth ha parecchie component javascript da installare.

Infine, il comando di migrazione serve per le tabelle dei recuperi password e dei token (“Ricordami questo accesso”).

Al termine dell’installazione saranno già state impostate tutte le rotte per Auth:

laravel route list con Auth
laravel route list con Auth

Vengono già predisposti i comandi di registrazione, recuper passord, login, logout. Però i conenuti rimangono ad accesso libero quidni va gestito cosa vogliamo proteggere con la login e cosa rimane publico.

Per fare questo occorre agire sul controller.

Per prtoteggere l’intera applicazione occorre fare in modo che ogni volta che si invoca la classe controller del modello che si vuole proteggere: avvenga l’interposizone del mìddleware Auth. Per esempio si può deìcidere tabella per tabella se si vuole la protezione oppure no:

class CompanyController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
    }

in questo caso il CRUD della tabella Company sarà intercettato dal middleware Auth e se si tenta di accedere ad una delle rotte, si presenterà la maschera di login:

Login
Login

Risorse

Laravel – impostare il nome dell’applicazione

Laravel logo

Il nome dell’applicazione può essere visualizzato nelle views di Laravel e può essere definito in almeno tre modi

file .env

Il file di configurazione delle variabil di ambiente è situato nella radice dell’albero dell’applicazione e si chiama .env.

Al suo interno si trova una variabile che possiamo personalizzare:

APP_NAME=MyBeautifulAppName

Poi, all’interno della view, ci riferiremo a questa variabile nel modo seguente

 {{env('APP_NAME')}}

Tuttavia il file .env non viene normalmente sottoposto a versione perché è caratteristico del singolo deploy su ogni server. Quindi tendo a non utilizzare questa variabile.

file config/app.php

Questo file contiene una variabile predefinita:

    'name' => env('APP_NAME', 'MyBeautifulAppName'),

Qui in realtà fa un’override del file .env, infatti utilizza la stessa variabile env, però è a runtime, cioè acquisisco il valore di default e lo metto in un’altra variabile name, eventualmente modificandolo. Per accedere al valore della variabile name in una view si scrive:

{{ config('app.name', 'Default name') }}

il secondo argomento essendo il default.

Condivisione di una variabile tra tutte le view

Il terzo metodo è il più elaborato ma consente una maggiore flessibilità: si definisce una variabile dal nome arbitrario nel file

<APP>/app/Providers/AppServiceProvider.php

e al suo interno, nel metodo boot(), si invoca il metodo statico View::share():

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        View::share('appTitle', "MyBeautifulAppName - powered by Laravel");
    }

La variabile è poi disponibile nelle view: {{$appTitle}}.

Se questi contenuti vi risultano utili, condivideteli. Ciao 🙂

Risorse web

Laravel – primi passi: localizzare un’applicazione con BabelEdit

BabelEdit è un programma che ci aiuta a localizzare l’applicazione. Si devono infatti personalizzare i file di localizzazione della lingua che già Laravel mette a disposizione nella cartella resources/lang. Fondamentalmente l’applicazione fa uso di etichette che poi recuperano per associazione il valore esteso del testo da visualizzare.

Per aiutare in questo tipo di attività, soprattuto se il lavoro di traduzione viene svolto da non programmatori, si può utilizzare questo software. Non sto facendo endorsement, voglio solo riportare una possibile soluzione cooperativa.

Download e installazione

Una volta installato, (al solito io uso Linux) lanciarlo con

$ BabelEdit
Dashboard di BabelEdit
Dashboard di BabelEdit

Utilizzo di BabelEdit

Ovviamente prima occorre dare l’OK per l’accettazione dei termini di licenza e per l’attivazione della Trial di 7 giorni.

Si carica il file delle etichette originarie (che sono quelle del plugin Auth):

Seleziona la sorgente dei dati
Seleziona la sorgente dei dati

Per il nostro progetto, i file di dizionario sono PHP. Si apre così la maschera per caricare i file da tradurre:

Configurazione dei linguaggi
Configurazione dei linguaggi

Si possono aggiungere i file origine e destinazione

BabelEdit aggiungere le lingue
BabelEdit aggiungere le lingue

Si associa il file alla lingua

Aggiunta nuova lingua
Aggiunta nuova lingua

Si ripetono questi passaggi per tutte le lingue che vogliamo trattare.

Cliccando su “Primary Language” selezioniamo qual è la lingua di riferimento da cui derivieremo tutte le traduzioni:

BabelEdit selezione lingua primaria
BabelEdit selezione lingua primaria

si apre la maschera di selezione del servizio

Babel configurazione del servizio di traduzione online
Babel configurazione del servizio di traduzione online

Apriamo un messaggio, vengono visualizzate le etichette così come sono state configurate nei due file di lingua. Cliccando in basso a destra lo scudo con stellina

Selezione del servizio di traduzione online
Selezione del servizio di traduzione online

si può vedere la traduzione proposta dal servizio online (ad esempio Google)

Babel traduzione automatica
Babel traduzione automatica

cliccando sulla proposta, il valore dell’etichetta viene modificato con quello proposto da Google. In assenza del servizio si può tradurre in autonomia.

Risorse web

Laravel – primi passi: il CRUD

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