Database

Introduzione

Quasi tutte le applicazioni web moderne interagiscono con un database. Laravel rende estremamente semplice l’interazione con i database supportati utilizzando SQL grezzo, un builder di query fluente e l’Eloquent ORM. Attualmente, Laravel offre supporto ufficiale per cinque database:

Inoltre, MongoDB è supportato tramite il pacchetto mongodb/laravel-mongodb, ufficialmente mantenuto da MongoDB. Consulta la documentazione di Laravel MongoDB per maggiori informazioni.

Configurazione

La configurazione dei servizi database di Laravel si trova nel file di configurazione config/database.php della tua applicazione. In questo file, puoi definire tutte le tue connessioni al database, oltre a specificare quale connessione deve essere utilizzata di default. La maggior parte delle opzioni di configurazione in questo file si basa sui valori delle variabili d’ambiente della tua applicazione. Sono forniti esempi per la maggior parte dei sistemi database supportati da Laravel in questo file.

Di default, la configurazione di esempio environment configuration di Laravel è pronta per l’uso con Laravel Sail, che è una configurazione Docker per sviluppare applicazioni Laravel sulla tua macchina locale. Tuttavia, puoi modificare la configurazione del database secondo le necessità del tuo database locale.

Configurazione di SQLite

I database SQLite sono contenuti in un unico file nel tuo filesystem. Puoi creare un nuovo database SQLite usando il comando touch nel terminale: touch database/database.sqlite. Dopo aver creato il database, puoi facilmente configurare le variabili d’ambiente per puntare a questo database inserendo il percorso assoluto nel valore DB_DATABASE:

DB_CONNECTION=sqlite
DB_DATABASE=/percorso/assoluto/a/database.sqlite

Di default, i vincoli delle chiavi esterne sono abilitati per le connessioni SQLite. Se vuoi disabilitarli, imposta la variabile d’ambiente DB_FOREIGN_KEYS a false:

DB_FOREIGN_KEYS=false

Se usi l’installer di Laravel per creare la tua applicazione Laravel e selezioni SQLite come database, Laravel creerà automaticamente un file database/database.sqlite ed eseguirà le migrazioni del database predefinite per te.

Configurazione di Microsoft SQL Server

Per utilizzare un database Microsoft SQL Server, assicurati di avere installate le estensioni PHP sqlsrv e pdo_sqlsrv, oltre a tutte le dipendenze necessarie come il driver Microsoft SQL ODBC.

Configurazione Tramite URL

Normalmente, le connessioni al database vengono configurate usando diversi valori come host, database, username, password, ecc. Ciascuno di questi valori ha la propria variabile d’ambiente corrispondente. Questo significa che, quando configuri le informazioni di connessione al database su un server di produzione, devi gestire diverse variabili d’ambiente.

Alcuni provider di database gestiti, come AWS e Heroku, forniscono un unico "URL" del database che contiene tutte le informazioni di connessione in un’unica stringa. Un esempio di URL del database potrebbe essere simile al seguente:

mysql://root:password@127.0.0.1/forge?charset=UTF-8

Questi URL seguono tipicamente una convenzione di schema standard:

driver://username:password@host:port/database?options

Per comodità, Laravel supporta questi URL come alternativa alla configurazione del database con molteplici opzioni. Se l’opzione di configurazione url (o la corrispondente variabile d’ambiente DB_URL) è presente, verrà utilizzata per estrarre le informazioni di connessione e le credenziali del database.

Connessioni di Lettura e Scrittura

A volte potresti voler usare una connessione al database per le istruzioni SELECT e un’altra per INSERT, UPDATE e DELETE. Laravel rende questo semplice e utilizzerà sempre le connessioni corrette, sia che tu stia usando query raw, il query builder o l’ORM Eloquent.

Per vedere come configurare le connessioni di lettura/scrittura, guarda questo esempio:

'mysql' => [
    'read' => [
        'host' => [
            '192.168.1.1',
            '196.168.1.2',
        ],
    ],
    'write' => [
        'host' => [
            '196.168.1.3',
        ],
    ],
    'sticky' => true,

    'database' => env('DB_DATABASE', 'laravel'),
    'username' => env('DB_USERNAME', 'root'),
    'password' => env('DB_PASSWORD', ''),
    'unix_socket' => env('DB_SOCKET', ''),
    'charset' => env('DB_CHARSET', 'utf8mb4'),
    'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
    'prefix' => '',
    'prefix_indexes' => true,
    'strict' => true,
    'engine' => null,
    'options' => extension_loaded('pdo_mysql') ? array_filter([
        PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
    ]) : [],
],

Nota che sono state aggiunte tre chiavi all’array di configurazione: read, write e sticky. Le chiavi read e write contengono array con una singola chiave: host. Le altre opzioni del database per le connessioni read e write verranno unite dall’array di configurazione principale mysql.

Devi inserire elementi negli array read e write solo se vuoi sovrascrivere i valori dell’array principale mysql. In questo caso, 192.168.1.1 sarà usato come host per la connessione "read", mentre 192.168.1.3 sarà usato per la connessione "write". Le credenziali del database, il prefisso, il set di caratteri e tutte le altre opzioni nell’array principale mysql saranno condivise tra entrambe le connessioni. Quando ci sono più valori nell’array di configurazione host, verrà scelto casualmente un host del database per ogni richiesta.

L’opzione sticky

L’opzione sticky è un valore opzionale che può essere utilizzato per permettere la lettura immediata dei record scritti nel database durante il ciclo di richiesta corrente. Se l’opzione sticky è abilitata e un’operazione di "write" è stata eseguita sul database durante il ciclo di richiesta corrente, qualsiasi ulteriore operazione di "read" utilizzerà la connessione "write". Questo garantisce che i dati scritti durante il ciclo di richiesta possano essere letti immediatamente dal database nella stessa richiesta. Sta a te decidere se questo comportamento è quello desiderato per la tua applicazione.

Eseguire Query SQL

Dopo aver configurato la connessione al database, puoi eseguire query utilizzando il facade DB. Il facade DB offre metodi per ogni tipo di query: select, update, insert, delete e statement.

Eseguire una Query Select

Per eseguire una query SELECT di base, puoi usare il metodo select sulla facade DB:

    <?php

    namespace App\Http\Controllers;

    use App\Http\Controllers\Controller;
    use Illuminate\Support\Facades\DB;
    use Illuminate\View\View;

    class UserController extends Controller
    {
        /**
         * Mostra una lista di tutti gli utenti dell'applicazione.
         */
        public function index(): View
        {
            $users = DB::select('select * from users where active = ?', [1]);

            return view('user.index', ['users' => $users]);
        }
    }

Il primo argomento passato al metodo select è la query SQL, mentre il secondo argomento sono i binding dei parametri che devono essere legati alla query. Tipicamente, questi sono i valori delle condizioni della clausola where. Il binding dei parametri fornisce protezione contro le iniezioni SQL.

Il metodo select restituirà sempre un array di risultati. Ogni risultato all’interno dell’array sarà un oggetto stdClass di PHP che rappresenta un record del database:

    use Illuminate\Support\Facades\DB;

    $users = DB::select('select * from users');

    foreach ($users as $user) {
        echo $user->name;
    }

Selezione di Valori Scalari

A volte una query al database restituisce un singolo valore scalare. Invece di dover ottenere il risultato scalare della query tramite un oggetto record, Laravel permette di recuperare direttamente questo valore usando il metodo scalar:

$burgers = DB::scalar(
    "select count(case when food = 'burger' then 1 end) as burgers from menu"
);

Selezionare più set di risultati

Se la tua applicazione chiama stored procedures che restituiscono più set di risultati, puoi usare il metodo selectResultSets per ottenere tutti i set di risultati restituiti dalla stored procedure:

    [$options, $notifications] = DB::selectResultSets(
        "CALL get_user_options_and_notifications(?)", $request->user()->id
    );

Utilizzo dei Named Bindings

Invece di utilizzare ? per rappresentare i parametri di binding, puoi eseguire una query usando i named bindings:

$results = DB::select('select * from users where id = :id', ['id' => 1]);

Eseguire una insert

Per eseguire una insert, puoi usare il metodo insert sulla facade DB. Come select, questo metodo accetta la query SQL come primo argomento e i binding come secondo argomento:

use Illuminate\Support\Facades\DB;

DB::insert('insert into users (id, name) values (?, ?)', [1, 'Marc']);

Eseguire un update

Il metodo update deve essere utilizzato per aggiornare i record esistenti nel database. Il numero di righe interessate dalla query viene restituito dal metodo:

use Illuminate\Support\Facades\DB;

$affected = DB::update(
    'update users set votes = 100 where name = ?',
    ['Anita']
);

Eseguire un’eliminazione

Il metodo delete si usa per eliminare i record dal database. Come per update, il metodo restituisce il numero di righe interessate:

use Illuminate\Support\Facades\DB;

$deleted = DB::delete('delete from users');

Esecuzione di una query generica

Alcune istruzioni del database non restituiscono alcun valore. Per questo tipo di operazioni, puoi utilizzare il metodo statement sulla facade DB:

    DB::statement('drop table users');

Eseguire un’Unprepared

A volte potresti voler eseguire un’istruzione SQL senza legare alcun valore. Puoi usare il metodo unprepared della facade DB per farlo:

    DB::unprepared('update users set votes = 100 where name = "Dries"');

Le istruzioni unprepared potrebbero essere vulnerabili a SQL injection. Non dovresti mai permettere valori controllati dagli utenti in un’istruzione non preparata.

Commit Impliciti

Quando utilizzi i metodi statement e unprepared della facade DB all’interno delle transazioni, devi fare attenzione ad evitare istruzioni che causano commit impliciti. Queste istruzioni fanno sì che il motore del database effettui indirettamente il commit dell’intera transazione, lasciando Laravel ignaro del livello di transazione del database. Un esempio di tale istruzione è la creazione di una tabella nel database:

    DB::unprepared('create table a (col varchar(1) null)');

Consulta il manuale di MySQL per un elenco di tutte le istruzioni che attivano commit impliciti.

Utilizzare più Connessioni al Database

Se la tua applicazione definisce più connessioni nel file di configurazione config/database.php, puoi accedere a ciascuna connessione tramite il metodo connection fornito dal facade DB. Il nome della connessione passato al metodo connection deve corrispondere a una delle connessioni elencate nel file config/database.php o configurato a runtime usando l’helper config:

use Illuminate\Support\Facades\DB;

$users = DB::connection('sqlite')->select(/* ... */);

Puoi accedere all’istanza PDO sottostante di una connessione usando il metodo getPdo su un’istanza di connessione:

$pdo = DB::connection()->getPdo();

Ascoltare gli Eventi delle Query

Se desideri specificare una closure che viene invocata per ogni query SQL eseguita dalla tua applicazione, puoi utilizzare il metodo listen della facade DB. Questo metodo può essere utile per registrare le query o per il debug. Puoi registrare la tua closure come listener delle query nel metodo boot di un service provider:

    <?php

    namespace App\Providers;

    use Illuminate\Database\Events\QueryExecuted;
    use Illuminate\Support\Facades\DB;
    use Illuminate\Support\ServiceProvider;

    class AppServiceProvider extends ServiceProvider
    {
        /**
         * Register any application services.
         */
        public function register(): void
        {
            // ...
        }

        /**
         * Bootstrap any application services.
         */
        public function boot(): void
        {
            DB::listen(function (QueryExecuted $query) {
                // $query->sql;
                // $query->bindings;
                // $query->time;
                // $query->toRawSql();
            });
        }
    }

Monitoraggio del Tempo Cumulativo delle Query

Un collo di bottiglia comune nelle applicazioni web moderne è il tempo speso per interrogare i database. Fortunatamente, Laravel può eseguire una closure o un callback a tua scelta quando impiega troppo tempo a interrogare il database durante una singola richiesta. Per iniziare, fornisci una soglia di tempo per le query (in millisecondi) e una closure al metodo whenQueryingForLongerThan. Puoi invocare questo metodo nel metodo boot di un service provider:

<?php

namespace App\Providers;

use Illuminate\Database\Connection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Events\QueryExecuted;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Registra i servizi dell'applicazione.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Avvia i servizi dell'applicazione.
     */
    public function boot(): void
    {
        DB::whenQueryingForLongerThan(500, function (Connection $connection, QueryExecuted $event) {
            // Notifica il team di sviluppo...
        });
    }
}

Transazioni di Database

Puoi usare il metodo transaction fornito dalla facade DB per eseguire un insieme di operazioni all’interno di una transazione di database. Se viene lanciata un’eccezione all’interno della closure della transazione, la transazione verrà automaticamente annullata e l’eccezione sarà rilanciata. Se la closure viene eseguita con successo, la transazione verrà automaticamente confermata. Non devi preoccuparti di annullare o confermare manualmente quando usi il metodo transaction:

use Illuminate\Support\Facades\DB;

DB::transaction(function () {
    DB::update('update users set votes = 1');

    DB::delete('delete from posts');
});

Gestione dei Deadlock

Il metodo transaction accetta un secondo argomento opzionale che definisce il numero di volte che una transazione deve essere ritentata quando si verifica un deadlock. Una volta esauriti questi tentativi, verrà lanciata un’eccezione:

use Illuminate\Support\Facades\DB;

DB::transaction(function () {
    DB::update('update users set votes = 1');

    DB::delete('delete from posts');
}, 5);

Uso Manuale delle Transazioni

Se vuoi iniziare una transazione manualmente e controllare completamente rollback e commit, puoi usare il metodo beginTransaction fornito dal facade DB:

use Illuminate\Support\Facades\DB;

DB::beginTransaction();

Puoi fare il rollback della transazione tramite il metodo rollBack:

DB::rollBack();

Infine, puoi fare il commit della transazione tramite il metodo commit:

DB::commit();

I metodi per le transazioni del facade DB gestiscono le transazioni sia per il query builder sia per l’Eloquent ORM.

Collegarsi alla CLI del Database

Se desideri connetterti alla CLI del tuo database, puoi utilizzare il comando Artisan db:

php artisan db

Se necessario, puoi specificare il nome di una connessione al database per connetterti a una connessione diversa da quella predefinita:

php artisan db mysql

Ispezionare i Tuoi Database

Usando i comandi Artisan db:show e db:table, puoi ottenere informazioni utili sul tuo database e sulle tabelle associate. Per vedere una panoramica del tuo database, inclusi dimensione, tipo, numero di connessioni aperte e un riepilogo delle sue tabelle, puoi utilizzare il comando db:show:

php artisan db:show

Puoi specificare quale connessione al database deve essere ispezionata fornendo il nome della connessione tramite l’opzione --database:

php artisan db:show --database=pgsql

Se desideri includere il conteggio delle righe delle tabelle e i dettagli delle viste del database nell’output del comando, puoi usare rispettivamente le opzioni --counts e --views. Su database grandi, ottenere il conteggio delle righe e i dettagli delle viste può essere lento:

php artisan db:show --counts --views

Inoltre, puoi usare i seguenti metodi Schema per ispezionare il tuo database:

    use Illuminate\Support\Facades\Schema;

    $tables = Schema::getTables();
    $views = Schema::getViews();
    $columns = Schema::getColumns('users');
    $indexes = Schema::getIndexes('users');
    $foreignKeys = Schema::getForeignKeys('users');

Se desideri ispezionare una connessione al database che non è la connessione predefinita della tua applicazione, puoi usare il metodo connection:

    $columns = Schema::connection('sqlite')->getColumns('users');

Panoramica della Tabella

Se vuoi ottenere una panoramica di una singola tabella nel tuo database, puoi eseguire il comando Artisan db:table. Questo comando fornisce una visione generale di una tabella del database, comprese le colonne, i tipi, gli attributi, le chiavi e gli indici:

php artisan db:table users

Monitorare i Tuoi Database

Utilizzando il comando Artisan db:monitor, puoi istruire Laravel a inviare un evento Illuminate\Database\Events\DatabaseBusy se il tuo database gestisce più di un numero specificato di connessioni aperte.

Per iniziare, dovresti pianificare il comando db:monitor per eseguire ogni minuto. Il comando accetta i nomi delle configurazioni di connessione al database che desideri monitorare, così come il numero massimo di connessioni aperte che dovrebbero essere tollerate prima di inviare un evento:

php artisan db:monitor --databases=mysql,pgsql --max=100

Pianificare solo questo comando non è sufficiente per attivare una notifica che ti avvisi del numero di connessioni aperte. Quando il comando rileva un database che ha un conteggio di connessioni aperte superiore alla tua soglia, verrà inviato un evento DatabaseBusy. Dovresti ascoltare questo evento all’interno del AppServiceProvider della tua applicazione per inviare una notifica a te o al tuo team di sviluppo:

use App\Notifications\DatabaseApproachingMaxConnections;
use Illuminate\Database\Events\DatabaseBusy;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Notification;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Event::listen(function (DatabaseBusy $event) {
        Notification::route('mail', 'dev@example.com')
                ->notify(new DatabaseApproachingMaxConnections(
                    $event->connectionName,
                    $event->connections
                ));
    });
}
Lascia un commento

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *