Con il termine ORM (Object Relational Mapper) si intende una tecnica di programmazione informatica che permette, nella pratica, di mettere in relazione un database relazionale e un oggetto/classe nel proprio codice.
In Laravel viene fornito l’ORM Eloquent e si basa sulla corrispondenza tra una tabella del database e una classe di tipo “Model”. Nello specifico, implementazione ORM di Eloquent è del tipo “Active Record”: una tabella sul database corrisponde ad una classe PHP e una singola riga della tabella corrisponde ad una istanza della classe.
L’utilizzo di Eloquent e dei Model semplifica molto l’interazione con il database, eliminando, in molti casi, la necessità di scrivere query dedicate.
Creare un model in Laravel
Il collegamento tra tabella e classe con Eloquent è realizzato utilizzando una convenzione sui rispettivi nomi. La classe del modello è indicata al singolare, la tabella collegata al plurale.
È, ovviamente, disponibile un comando Artisan per creare un nuovo modello, make:model, che supporta varie opzioni per creare anche altre classi collegate al model (controller, form, …). Quello che, sicuramente, ci interessa è creare un nuovo modello con l’annessa migrazione, per creare, quindi, automaticamente anche la nuova tabella (opzione -m).
$ php artisan make:model Post -m INFO Model [app/Models/Post.php] created successfully. INFO Migration [database/migrations/2023_01_24_211341_create_posts_table.php] created successfully.
// in app/Models/Post.php class Post extends Model { use HasFactory; } // database/migrations/2023_01_24_181341_create_posts_table.php // ... public function up() { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->timestamps(); }); } // ...
Alla classe Post è, quindi, collegata e collegabile la tabella posts. Prima di eseguire la migrazione per creare la tabella, aggiungiamo qualche colonna d’esempio al metodo up della migrazione stessa. Non tocchiamo la classe App\Model\Post.
$table->tinyText('title'); $table->enum('status', ['draft', 'published', 'retired'])->default('draft') $table->timestamp('published_at')->nullable();
Utilizzare un model
Senza ulteriori modifiche al nostro codice, in ogni punto della nostra applicazione Laravel, sarà possibile interagire con la classe Post nei modi opportuni per interagire, di fatto, con la tabella nel database.
// da qualche parte nella nostra app Laravel ... $post = new App\Model\Post; $post->title = "Another Post Bites the Dust"; $post->save(); $status = $post->status; // 'draft', il valore di default della colonna sul database $id = $post->id; // generato automaticamente $post->status = 'published'; $post->published_at = now(); $post->save() $post->status = "foo" $post->save() // genera una Illuminate\Database\QueryException
Notare in particolare che:
- possiamo creare istanze della classe ($post) ed eseguire su di esse metodi della classe Model (->save()) che hanno effetto sul database, creando o modificando una riga
- possiamo accedere alla colonne della riga tramite attributi dell’istanza che non abbiamo mai definito, ma che Eloquent crea dando loro gli stessi nomi della corrispettiva colonna (status, published_at)
- al salvataggio della istanza su tabella, vengono accettati solo valori ammessi dalla colonna
NOTA: è possibile visualizzare gli attributi del proprio modello tramite il comando Artisan php artisan model:show <NOME_MODELLO>
Il nostro modello Post estende la classe astratta Model di Eloquent e da essa riceve molti(ssimi) metodi che ci permettono di interagire con i dati sul database senza dover scrivere alcuna riga di codice.
Oltre, quindi, ai metodi che operano sulla singola istanza/riga, da Model vengono ereditati metodi statici che operano sulla tabella collegata. Questo grazie all’ implementazione “Active Record” citata all’inizio.
// restituisce la riga della tabella `posts` con `id=10` $anotherPost = Post::find(10); // restituisce tutte le righe della tabella `posts` $posts = Post::all(); // crea query tramite il builder e recupera le righe che corrispondono $thisYearPosts = Post::where('status', 'published') ->where('published_at', '>=', '2022-12-31') ->get()
I vari metodi statici del model restituiscono istanze della classe quando servono recuperate singole righe (per esempio find) oppure istanze della classe Illuminate\Database\Eloquent\Collection nel caso di metodi che intendono recuperare array di righe.
Laravel mette a disposizione la classe Illuminate\Support\Collection come wrapper per interagire in modo agevole con gli array PHP. La specifica classe per Eloquent estende la classe di base con metodi espressamente dedicati per i model.
Convenzioni e funzionalità dei modelli Eloquent in Laravel
Per comodità, riportiamo le principali convenzioni di Eloquent riguardanti i model.
- la classe ha il nome della “risorsa” al singolare (es. FavoritePost)
- la tabella ha lo stesso nome al plurale e in “snake case” (es. favorite_posts)
- la colonna chiave primaria si chiama id, è un intero con autoincrement
- esistono le colonne created_at e updated_at di tipo “timestamp”
È possibile, quindi, creare modelli Eloquent che possono collegarsi a tabelle che non rispettano queste convenzioni, indicando nella classe del model opportune property protected.
// in app/Models/Post.php posso indicare cosa non segue la convezione dei Model class Post extends Model { // il nome della tabella protected $table = 'post_table'; // il nome della colonna primary key protected $primaryKey = 'post_id'; // la colonna primary key non è auto-increment public $incrementing = false; // la colonna primary key non è di tipo intero protected $keyType = 'string'; // ... cfr Illuminate\Database\Eloquent\Model per altre property }
È anche possibile attivare particolari funzionalità in un Model, come ad esempio la “soft delete” o l’hidden di determinate colonne.
Con soft delete in Laravel si intende un’operazione che invece di rimuovere una riga da una tabella (hard delete), la contrassegna come non più disponibile. Attivare la soft delete su un Model Eloquent richiede uno sforzo minimo:
// nella `up` della migrazione va dichiarato che è una tabella "soft delete" // viene in pratica aggiunta una colonna `deleted_at` $table->softDeletes(); // nella classe del Model dichiaro che il model usa le soft delete { use SoftDeletes; // ... } // nel codice dell'app, si continua a usare il metodo standard per rimuovere un record $post = Post::find(44531); $post->delete(); // che non comparirà nelle varie richieste $activePosts = Post::all(); // ma sarà comunque recuperabile tramite metodi dedicati $softDeletedPosts = Post::::onlyTrashed()->get();
La funzionalità di hidden delle colonne è invece utile nel caso in cui si voglia evitare che i valori di determinate colonne vengano inclusi nella istanza recuperata dal database. Le motivazioni per questa scelta sono le più disparate; l’esempio più immediato ci viene dal Model presente nelle classi fornite direttamente da Laravel quando si crea una nuova applicazione con Composer.
class User extends Model { protected $hidden = [ 'password', ]; // ... }
La tabella degli utenti include informazioni sui singoli utenti registati, il model può essere recuperato tali informazioni e farle arrivare ad una view user.profile, ma non vogliamo di certo che l’attuale password dell’utente arrivi, anche se cifrata, fino a una view.
L’attributo $hidden permette di elencare le colonne che, seppure presenti e valorizzate sul database, non verranno incluse come property dell’istanza del model. Ovviamente, sarà possibile accedervi tramite opportuni metodi della classe Model