
Arbeiten mit der Laravel-Datenbank - 8 nützliche Tipps
Laravel ist bekannt für seine hohe Intuitivität und den Komfort beim Schreiben von Code. Viele Dinge wurden von den Laravel-Entwicklern entworfen, um die Arbeit der Programmierer mit diesem Framework zu vereinfachen. Das Gleiche gilt für die Arbeit mit der Datenbank. Die Dokumentation wird eine wertvolle Informationsquelle für Sie sein. In diesem Text zeige ich Ihnen jedoch die Aspekte, die dort nicht enthalten sind. Dank ihnen wird Ihre Arbeit mit der Datenbank in Laravel ein höheres Niveau erreichen.
Datenbankverbindung
Bevor Sie mit der Arbeit an einem neuen Projekt beginnen, sollten Sie die Verbindung zur Datenbank in Laravel konfigurieren. Wie Sie sich vielleicht denken können, ist das sehr einfach. Im Hauptverzeichnis des Projekts, in der .env-Datei, müssen Sie nur die Zugangsdaten zur Datenbank eingeben:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=”your_db_name”
DB_USERNAME=”your_db_user”
DB_PASSWORD=”your_db_pass”
Natürlich müssen Sie einen laufenden MySQL-Server und eine entsprechende Datenbank haben. Manchmal jedoch – besonders bei einem neuen kleinen Projekt – lohnt es sich, statt MySQL SQLite zu verwenden. Dazu erstellen Sie eine Datei namens database/database.sqlite und ersetzen dann den obigen Eintrag in der .env-Datei durch den folgenden:
DB_CONNECTION=sqlite
Wenn alles konfiguriert ist, können Sie sich darauf konzentrieren, Ihre Arbeit mit der Datenbank zu verbessern.
1. DB::transaction()
Angenommen, Sie haben eine Anwendung, in der der Benutzer ein Zahlungs-Gateway nutzen kann, um Punkte zu kaufen, die den Status seines Kontos von „beschränkt“ auf „premium“ ändern. Für dieses Beispiel nehmen wir an, dass die Zahlungsregistrierungsdaten, die Punkte und der Kontostatus in drei separaten Tabellen geführt werden. Ihr Controller sieht in etwa so aus:
public function store()
{
Payment::where(['user_id' => auth()->user()->id])->update(['payment_success' => 1]);
UserPoint::create([
'user_id' => auth()->user()->id,
'points' => 1000,
]);
UserStatus::where(['user_id' => auth()->user()->id])->update(['status' => 'premium']);
}
Was passiert jedoch, wenn aus irgendeinem Grund die zweite oder dritte Operation, bei der Sie Punkte hinzufügen und dann den Status ändern, fehlschlägt? Ihre Datenbank in Laravel wird inkonsistent. Die Methode DB::transaction() kommt zur Rettung – dank der Ihre Daten im Fehlerfall auf den vorherigen Zustand zurückgesetzt wird:
{
DB::beginTransaction();
try {
Payment::where(['user_id' => auth()->user()->id])->update(['payment_success' => 1]);
UserPoint::create([
'user_id' => auth()->user()->id,
'points' => 1000,
]);
UserStatus::where(['user_id' => auth()->user()->id])->update(['status' => 'premium']);
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
}
}
2. Datenbankabfrage – ersetze die if-Bedingung durch die when-Bedingung
Sehr oft möchte man beim Erstellen einer Abfrage an eine Datenbank mit Eloquent eine Bedingung hinzufügen. Ein gutes Beispiel wäre das Laden aller Benutzer aus der Datenbank oder aller Benutzer mit aktivem/inaktivem Status, wenn die URL den Parameter „active“ enthält.
public function index()
{
$users = User::orderBy('name', 'ASC');
if (request()->has('active')) {
$users->where('active', \request('active'));
}
$users->get();
}
Nicht jeder weiß jedoch, dass dieselbe Bedingung auch ohne die Verwendung eines if-Blocks, sondern nur mit Eloquent geschrieben werden kann:
public function index()
{
$users = User::orderBy('name', 'ASC')
->when(request()->has('active'), function ($query) {
$query->where('active', request('active'));
})
->get();
}
3. Das Modell mit zusätzlichen Daten erweitern
Angenommen, Sie speichern die Daten zu den Produkten in einem Laden in der Tabelle „products“. Die Spalte „price“ zeigt den Preis des Produkts in Form einer natürlichen Zahl in Cents an. Daher wird ein Produkt, das 2 EUR kostet, als 200 (200 Cents) geschrieben. Das ist eine sehr verbreitete, allgemein akzeptierte Praxis. Natürlich werden Sie dem Benutzer den Preis nicht in Cents anzeigen – Sie müssen ihn zuerst umrechnen. Diese Umrechnung kann mit dem Accessor in Eloquent durchgeführt werden. Alles ist sehr gut in der Laravel-Dokumentation beschrieben.
Was aber, wenn Sie den Preis in Euro in andere Währungen umrechnen und in die vom Eloquent zurückgegebenen Produktdaten aufnehmen möchten? Dafür können Sie ebenfalls den Accessor verwenden.
Wenn Sie also Informationen über Preise in anderen Währungen in das Attribut „prices“ aufnehmen möchten, müssen Sie eine Methode namens public getPricesAttribute erstellen und dann eine $appends-Tabelle im Modell mit dem Attributnamen (in diesem Fall „prices“) anlegen. So sollte das Ganze aussehen:
class Product extends Model
{
protected $appends = ['prices'];
public function getPricesAttribute()
{
return [
'EUR' => $this->price,
'USD' => $this->price, // Verwenden Sie hier einen Währungsumrechner.
'GBP' => $this->price, // Verwenden Sie hier einen Währungsumrechner.
];
}
}
Wichtig: Wenn der Attributname aus zwei oder mehr Wörtern besteht, z. B. „converted prices“, sollte der Methodenname gemäß dem Standard im CamelCase-Format gespeichert werden (getConvertedPricesAttribute), und der Name in der $appends-Tabelle im SnakeCase-Format (converted_prices).
4. Automatische Löschung von Beziehungen in der Laravel-Datenbank
Die häufigste Datenbankbeziehung ist die Eins-zu-Viele-Beziehung. Angenommen, Sie haben Informationen über den Benutzer in der Tabelle „users“, und Sie speichern Informationen über die Kontakte, die der Benutzer in seinem Profil erstellt hat, in der Tabelle „contacts“. Bei der Erstellung einer Anwendung müssen Sie immer im Blick behalten, was in ein paar Jahren damit passiert. Dann sollten Sie sicherstellen, dass beim Löschen des Benutzerkontos alle damit verbundenen Daten in anderen Tabellen ebenfalls gelöscht werden. Andernfalls kann die Datenbank nach ein paar Jahren sogar auf eine Größe von mehreren Dutzend Gigabyte anwachsen, und das wird zu einem großen Problem.
Sie können die automatische Löschung von Beziehungen auf verschiedene Weise einrichten, aber mein Favorit ist die Definition auf Ebene der Migration.
So sieht die Migration der Tabelle „contacts“ aus:
public function up()
{
Schema::create('contacts', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('fist_name');
$table->string('last_name');
$table->string('address');
$table->string('email');
$table->timestamps();
});
}
Die Spalte „user_id“ bezieht sich offensichtlich auf die ID des Benutzers, der den Kontakt in der Tabelle „contacts“ erstellt hat. Daher müssen Sie diese Beziehung in der Migration definieren, zusammen mit den entsprechenden Informationen dazu, was im Fall einer Löschung des Datensatzes mit den Daten geschehen soll.
public function up()
{
Schema::create('contacts', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('first_name');
$table->string('last_name');
$table->string('address');
$table->string('email');
$table->timestamps();
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('cascade');
});
}
5. Verwenden von map() statt foreach
Ich habe bereits die Accessoren erwähnt, dank denen Sie die Daten, die aus der Datenbank heruntergeladen werden, ändern können. Diese Methode funktioniert gut, wenn Sie die Daten jedes Mal in geänderter Form erhalten möchten. Aber was, wenn Sie die Änderungen nur in einem Fall vornehmen möchten? Kehren wir zum Beispiel mit den Produkten zurück, deren Preise in Form einer natürlichen Zahl geschrieben sind. Angenommen, dieses Mal möchten Sie die Cents in Euro umrechnen und das Attribut „currency“ hinzufügen, das Informationen über die Währung enthält. Natürlich können Sie dafür eine foreach-Schleife verwenden:
class ProductController extends Controller
{
public function index()
{
$products = Product::all();
foreach ($products as $product) {
$product->price = $product->price / 100;
$product->currency = 'EUR';
}
}
}
Wie so oft bei Laravel haben die Ersteller dieses Frameworks eine elegantere Lösung vorbereitet. Sie können die map()-Methode anstelle einer foreach-Schleife verwenden:
class ProductController extends Controller
{
public function index()
{
$products = Product::query()->get()->map(function (Product $product) {
$product->price = $product->price / 100;
$product->currency = 'EUR';
return $product;
});
}
}
6. Bequemere Suche nach Datum
Übereinstimmend mit der Konvention werden die Daten in Laravel im Format Y-m-d H:i:s geschrieben. Dies ermöglicht eine bequeme Suche nach Einträgen über einen bestimmten Zeitraum mit den Methoden whereDate, whereMonth, whereDay, whereYear oder whereTime. Sehen Sie sich die folgenden Beispiele an:
$products = Product::whereDate('created_at', '2018-01-31')->get();
$products = Product::whereMonth('created_at', '12')->get();
$products = Product::whereDay('created_at', '31')->get();
$products = Product::whereYear('created_at', date('Y'))->get();
$products = Product::whereTime('created_at', '=', ’14:13:58')->get();
7. N+1-Problem
Beziehungen zwischen Tabellen zu erstellen, ist in Laravel extrem einfach. Die in diesem Rahmen genutzten Lösungen haben viele Vorteile, aber sie sind nicht frei von Nachteilen. Kehren wir zum vorherigen Beispiel der Eins-zu-Viele-Beziehung zurück, bei der es mehrere Kontakte gibt, die einem Benutzer zugeordnet sind. Die Definition der Beziehung sieht so aus:
class User extends Authenticatable
{
public function contacts()
{
return $this->hasMany(Contact::class);
}
}
Angenommen, Sie möchten alle Benutzer laden und die über diese Beziehung verbundenen Kontakte anzeigen:
public function index()
{
$users = User::all();
foreach ($users as $user) {
echo $user->name;
foreach ($user->contacts as $contact) {
echo $contact->last_name;
}
}
}
Unglücklicherweise bedeutet das, was Ihnen die Arbeit mit der Datenbank dank Eloquent erleichtert, auch, dass Sie nicht genau wissen, was „unter der Haube“ passiert. Zum Glück kommt ein weiteres großartiges Laravel-Tool namens Telescope zur Hilfe. Dank dieses Tools sehen Sie genau, wie die Datenbankabfrage in diesem Fall aussieht:
Wie Sie sehen können, lädt Laravel zunächst alle Benutzer aus der Tabelle „users“ und führt dann eine separate Abfrage für die Tabelle „contacts“ für jeden Benutzer aus. Das ist ein großes Problem in Bezug auf die Leistung. Bei einer größeren Anzahl von Benutzern haben Sie eine Menge unnötiger Datenbankabfragen. Glücklicherweise ist die Lösung sehr einfach. Sie müssen die Methode all() durch with() ersetzen, um anzugeben, dass Sie möchten, dass Ihre Ergebnisse zusammen mit den Daten in der Spalte „contacts“ geladen werden.
public function index()
{
$users = User::with('contacts')->get();
foreach ($users as $user) {
echo $user->name;
foreach ($user->contacts as $contact) {
echo $contact->last_name;
}
}
}
8. Sortieren der Daten in einer Beziehung
Für das letzte Beispiel kehren wir noch einmal zum Benutzer und seinen Kontakten zurück. Es wäre nützlich, die Kontakte alphabetisch zu sortieren. Natürlich können Sie das so machen:
$users = User::with(['contacts' => function ($query) {
$query->orderBy('last_name', 'ASC');
}])->get();
Sie können jedoch getrost annehmen, dass Sie jedes Mal, wenn Sie diese Daten laden, sie auf genau diese Weise sortieren müssen. Was können Sie tun, damit Sie diesen Schritt nicht ständig wiederholen müssen? Betrachten wir noch einmal, wie die Definition der Beziehung im Modell aussieht:
public function contacts()
{
return $this->hasMany(Contact::class);
}
Hier müssen Sie die orderBy-Methode hinzufügen, damit Ihre Ergebnisse jedes Mal auf die richtige Weise sortiert werden:
public function contacts()
{
return $this->hasMany(Contact::class)
->orderBy('last_name' ,'ASC');
}
Bleiben Sie auf dem neuesten Stand mit Laravel
Laravel ist bekannt für viele praktische Lösungen. Deshalb ist es wichtig, Ihr Wissen ständig zu erweitern und neue Techniken zu erlernen, die die Arbeit mit diesem System erleichtern. Nachfolgende Versionen dieses Frameworks werden systematisch veröffentlicht und bringen oft viele neue Funktionalitäten mit sich, die unsere Laravel-Entwickler gerne erforschen.