Doctrine: Eine Einführung

von Patrick Froch (Kommentare: 0)

Kaum eine moderne Webanwendung kommt ohne Datenbank aus. Wenn man mit Symfony entwickelt, führt deshalb kaum ein Weg an Doctrine vorbei. In diesem Beitrag möchte ich zeigen, wie man Doctrine in Symfony einrichtet und nutzt. Ich werde auf das Erstellen der nötigen Klassen, sowie das einfache Laden und Speichern von Daten eingehen.

Konfiguration

Als erstes müssen die Zugangsdaten für die Datenbank eingegeben werden. Dies kann in der Datei app/config/parameters.yml geschehen:
# app/config/parameters.yml
parameters:
    database_driver:    pdo_mysql
    database_host:      localhost
    database_name:      test_project
    database_user:      root
    database_password:  password
Nun kann man sich die Datenbank von Doctrine erstellen lassen (muss man aber nicht, man kann sie auch von Hand oder anderen Tools anlegen lassen):
    php app/console doctrine:database:create
Es ist darauf zu achten, dass als Vorgabewert für die Zeichenkodierung des MySQL-Servers UTF-8 eingestellt ist!

Entity

Als nächstes muss eine Klasse erstellt werden, die eine Tabelle der Datenbank repräsentiert. Sie wird Eigenschaften für die einzelnen Felder enthalten, sowie die Metadaten für die Erstellunge und Zuordnung der Eigenschaften zu den einzelnen Spalten der Tabelle in der DB. Ich werde hier die Metadaten als Annotations angeben. Die Infos über die anderen Möglichkeiten findet man im Symfony-Handbuch .
Die Metadate in einem Bundle können immer nur auf eine Art angegeben werden, ein Mischen der verschiedenen Möglichkeiten (z.B. YAML und Annotations ) ist nicht möglich!
// src/Acme/StoreBundle/Entity/Product.php
namespace Acme\StoreBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="product")
 */
class Product
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\Column(type="string", length=100)
     */
    protected $name;

    /**
     * @ORM\Column(type="decimal", scale=2)
     */
    protected $price;

    /**
     * @ORM\Column(type="text")
     */
    protected $description;
}
Damit Doctrine weiß, dass diese Klasse relevant ist, muss im Kommentar über der Klasse festgelegt werden, dass es sich um ein Entity handelt (Zeile 7) @ORM\Entity. In Zeile 8 wird der Name der Tabelle festgelegt. Ab Zeile 12 kommen die einzelnen Felder der Tabelle. Es werden verschiedene Einstellungen vorgenommen. In Zeile 13 - 15 wird die id als PRIMARY KEY festgelegt. Bei den anderen Spalten wird jeweils der Typ und evtl. nötige Einstellungen wie Länge oder Kommastellen angegeben. Eine Übersicht der Feldtypen gibt es hier .
Wird der Name der Tabelle nicht angegeben, wird der Name der Klasse verwendet!
Damit die Eingenschaften gesetzt und abgerufen werden können, müssen Getter- und Setter-Methoden erstellt werden. Hierbei greift einem Symfony etwas unter die Arme, so dass man nicht alles manuell machen muss.
    php app/console doctrine:generate:entities Acme/StoreBundle/Entity/Product
Nun werden die entsprechenden Methoden erstellt und die erste Klasse ist fertig. Es muss nur noch die Tabelle mit den Feldern erstellt werden. Dies geschieht ebenfalls über die Symfony-Konsole:
    php app/console doctrine:schema:update --force

Erstellen der Daten

Nach dem nun die Klasse angelegt und die Tabelle erzeugt ist, kann man die ersten Daten eingeben. Will man die eben erstellte Klasse z.B. im Controller verwenden, geht man wie folgt vor:
// src/Acme/StoreBundle/Controller/DefaultController.php

// ...
use Acme\StoreBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Response;

public function createAction()
{
    $product = new Product();
    $product->setName('A Foo Bar');
    $product->setPrice('19.99');
    $product->setDescription('Lorem ipsum dolor');

    $em = $this->getDoctrine()->getManager();
    $em->persist($product);
    $em->flush();

    return new Response('Created product id '.$product->getId());
}
In Zeile 9 wird eine Instanz der Klasse erzeugt und in den Zeilen 10 - 12 mit Daten befüllt. In Zeile 14 wird der EntityManager geladen. Dieser kümmert sich um das Speichern der Daten. Hierzu wird ihm in Zeile 15 das Objekt übergeben. Diese Aktion dient allerdings noch nicht dem Speichern, es wird Doctrine nur bekannt gegeben, dass es sich ab jetzt um dieses Objekt kümmern soll. Das eigentliche Speichern geschieht in Zeile 16. Dieses Vorgehen hat Vorteile, bei der Verarbeitung größerer Datenmengen. Man kann Doctrine die einzelnen Objekte übergeben (z.B. in einer Schleife) und erst ganz am Ende die flush-Methode aufrufen. Ob es sich bei einem flush um ein INSERT oder ein UPDATE handelt, entscheidet Doctrine selber und natürlich können diese auch gemischt vorkommen. Doctrine ist intelligent genug, um dies für jedes Element herauszufinden und den effizientesten Weg zum Speichern der Daten zu ermitteln.

Auslesen der Daten

Will man nun die Daten aus der Datenbank auslesen, ist dies genau so einfach:
public function showAction($id)
{
    $product = $this->getDoctrine()
        ->getRepository('AcmeStoreBundle:Product')
        ->find($id);

    if (!$product) {
        throw $this->createNotFoundException(
            'No product found for id '.$id
        );
    }

    // ... do something, like pass the $product object into a template
}
In Zeile 3 und 4 wird über Doctrine das Repository für die Entity geladen. In Zeile 5 wird mit der find-Methode und der Id die Klasse mit den Daten geladen. In Zeile 7 wird geprüft, ob ein Objekt erstellt wurde, ist dies nicht der Fall (z.B. weil keine entsprechenden Daten gefunden wurden) wird ein Fehler erzeugt. Dieses Vorgehen setzt natürlich voraus, dass die Id bekannt ist, häufig ist aber gerade dies nicht der Fall. Man kann dann mit den findBy-Methoden die Daten suchen. Diese Methoden gibt es für jedes Feld. (Natürlich wird nicht für jedes Feld eine solche Methode angelegt, es handelt sich um eine magische Methode .) Will man also z.B. ein Produkt mit einem bestimmten Namen finden, nutzt man die Methode findByName().
$repository = $this->getDoctrine()
    ->getRepository('AcmeStoreBundle:Product');
$product = $repository->findOneByName('foo');
Nun kam man mit dem Objekt ($product) weiter arbeiten. Will man mehrere Bedingungen angeben, nutzt man ein Array:
// query for one product matching be name and price
$product = $repository->findOneBy(array('name' => 'foo', 'price' => 19.99));

// query for all products matching the name, ordered by price
$products = $repository->findBy(
    array('name' => 'foo'),
    array('price' => 'ASC')
);
Natürlich kann man sich auch alle Daten geben lassen:
// find *all* products
$products = $repository->findAll();

Daten ändern

Nun haben wir bereits alles, um die Daten ändern zu können. Als erstes wird auf eine der oben gezeigten Arten ein Objekt aus der DB geladen. Dann werden die neuen Daten zugewiesen und das Objekt wird gespeichert. Zusammen sieht dies dann z.B. so aus:
public function updateAction($id)
{
    $em = $this->getDoctrine()->getManager();
    $product = $em->getRepository('AcmeStoreBundle:Product')->find($id);

    if (!$product) {
        throw $this->createNotFoundException(
            'No product found for id '.$id
        );
    }

    $product->setName('New product name!');
    $em->flush();

    return $this->redirect($this->generateUrl('homepage'));
}

Daten löschen

Für das Löschen benötigen wir eine neue Methode, nämlich die remove-Methode des EntityManager. Dieser Methode wird das zu löschenden Objekt übergeben. Es wird also erst wie gewohnt ein Objekt mit den Daten aus der DB geladen und dann an den EntityManager zum Löschen übergeben:
$em = $this->getDoctrine()->getManager();
    $product = $em->getRepository('AcmeStoreBundle:Product')->find($id);

    if (!$product) {
        throw $this->createNotFoundException(
            'No product found for id '.$id
        );
    }
$em->remove($product);
$em->flush();
Dies soll als kleine Einführung reichen. Für weitere Informationen stehen die Handbücher von Symfony und Doctrine bereit.

Zurück

Einen Kommentar schreiben