Home-Produkte-Testarea-Kontakt-Datenschutz-Aktualisiert: 09-Apr-2010
< Voriger Tag   Nächster Tag >

Freitag, 09. April 2010

PHP: RuBisCO ORM -- 1:n-Beziehung

Als Beispiel für die 1:n-Beziehung dient, dass einem User beliebig viele Aufgaben zugewiesen werden können.

ID Task Login
1Task 1alice
2Task 2alice
3Task 3alice

SQL-Tabellen

Die SQL-Tabellen bleiben gegenüber dem vorigen 1:1 Beispiel gleich. Womit sich die Frage stellt wie dann später anhand der SQL-Deklaration zwischen 1:1 und 1:n unterschieden wird? Im Moment ist die Unterscheidung erst einmal per "Hack" in rorm.php enthalten.

CREATE TABLE IF NOT EXISTS users (
id     INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
login  VARCHAR(256) NOT NULL
) ENGINE=INNODB;


CREATE TABLE IF NOT EXISTS tasks (
id       INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
task     VARCHAR(256) NOT NULL,
assigned INT, FOREIGN KEY (assigned) REFERENCES users(id)
				ON UPDATE RESTRICT
				ON DELETE SET NULL,
INDEX (assigned)
) ENGINE=INNODB;

PHP-Objekte

Bei den PHP-Objekten gibt es jetzt statt einer Referenz nun ein Array mit den n-Objekten.

class User {
    protected $_id    = null;
    protected $_login = null;

    protected $_tasks = array();
}


class Task {
    protected $_id       = null;
    protected $_task     = null;
    protected $_assigned = null;
}

Read

Das Einlesen kann mit einer oder zwei SQL-Abfragen erfolgen.

SELECT id, login FROM users WHERE id = 1;
SELECT id, task, assigned FROM tasks WHERE assigned = 1;

Und mit einer SQL-Abfrage:

SELECT * FROM users LEFT JOIN tasks ON users.id = tasks.assigned
         WHERE users.id = 3;

Die zwei Abfragen haben den Vorteil, dass weniger Daten übertragen werden und der Fall "Keine Tasks zugewiesen" sich einfacher verarbeiten lässt, weil nicht zusätzlich auf NULL geprüft werden muss. Benutzt wird daher die Variante mit zwei SQL-Abfragen. Die zweite ist in readMany($id) ausgelagert. Aufgerufen wird sie indirekt durch die Zuweisung an $users->id.

public function read($id = null) {
    if(null === $id) {
        $id = $this->_id;
    }

    // TODO Lock?

    $sql = 'SELECT users.id AS users_id, users.login AS users_login FROM users WHERE users.id = :id';
    fetch();

    $this->id    = $result['users_id'];
    $this->login = $result['users_login'];

    // $this->readMany($id) is triggered by $this->id = $result['users_id'];
}// read


private function readMany($id) {
    $sql = 'SELECT tasks.id AS tasks_id, tasks.task AS tasks_task, tasks.assigned AS tasks_assigned FROM tasks WHERE assigned = :id';

    $sth = $this->dbh->prepare($sql);
    $sth->execute(array(':id'  => $id));

    while($result = $sth->fetch()) {
        $obj = new TaskDB();

        $obj->id = $result['tasks_id'];
        $obj->task = $result['tasks_task'];
        $obj->assigned = $result['tasks_assigned'];

        $this->_tasks[] = $obj;
    }
}// readMany

Insert, Update, InsertOrUpdate

Arbeiten wie die vorigen Methoden. Der Unterschied besteht nur darin, dass jetzt statt einer Referenz eine foreach-Schleife benutzt wird, um die einzelnen, zugehörigen Objekte zu speichern.

foreach($this->_tasks as $obj) {
    $obj->insertOrUpdate();
}

Delete

Das Löschen erfolgt ebenfalls wie gehabt. Nur der User wird gelöscht. Die assigned = NULL-Zuweisung in den Tasks wird der Datenbank per CASCADE überlassen.

setID($value)

Wenn sich die id des Users ändert, werden die Tasks neu eingelesen:

public function setId($value) {
    $oldValue = $this->id;

    parent::setId($value);

    if($oldValue !== $this->id) {
        try {
            $this->_tasks = array();

            $this->readMany($this->id);

           return $this;
        } catch (\Exception $e) {
            $this->_tasks = array();
            $this->_id   = null;

            throw $e;
        }
    }
}// setId

Frage: Sollen geänderte Tasks vorher gespeichert werden?

$task->assigned = 42

Fragen: Was ist, wenn einem Task innerhalb des $user->tasks-Array ein anderer User zugewiesen wird? In diesem Fall müsste er aus jenen Array entfernt werden und dem tasks-Array des anderer Users zugefügt werden? Was ist, wenn dieser User erst später geladen wird? Das Task-Objekt müsste dann zwischengelagert und später dem neu geladenen User zugefügt werden. Als Alternative könnte $task->assigned nur lesbar sein.

TODO: Add/Delete/Modify Tasks

Im Moment fehlt noch eine API, um die $user->tasks bearbeiten zu können.

Optimierung: Dirty-Flag

Bei insert(), update(), ... werden im Moment immer alle zugehörigen Objekte gespeichert, selbst wenn sie unverändert sind. Zur Optimierung muss es später noch ein Dirty-Flag geben, das neue oder veränderte Objekte markiert, so dass dann nur diese Objekte gespeichert werden.

Zyklische Abhängigkeiten

Einem User können beliebig viele Tasks zugeordnet werden. Wenn zusätzlich die 1:1-Beziehung aus dem vorigen Beispiel hinzugenommen wird, kann einem Task wiederum ein User zugeordnet werden. Alle im Array $users->tasks enthaltenen Tasks könnten also eine Referenz auf dem User enthalten: $task->userRef = $user.

Wenn ein solcher Task gespeichert wird, ruft er das Speichern des Users auf und dieses Speichern wiederum das Speichern der Tasks, die wieder den User speichern usw. -- eine Endlosschleife. Die müsste erkannt und verhindert werden. Die jetzige Alternative ist, dass die Tasks keine Referenz auf dem User enthalten.

Implementierung

Weil sich die Methoden ähnlich zur vorigen 1:1-Beziehung verhalten ist der generierte Quelltext recht ähnlich zum vorigen. Um die Generierung zu vereinfachen, liegt er aber zunächst einmal seperat im classDBMany.rtpl-Template. Außerdem kann es derzeit nur ein Feld mit einer 1:1- oder 1:n-Beziehung geben.

Der Programmcode für die neuen many-Variablen liegt in erster Linie im case 'onetomany':-Abschnitt von Table.php.

Die jetzige Implementierung ist einfach aber unvollständig, da mit der 1:n-Beziehung viele offene Fragen hinzugekommen sind.

Quelltext (MIT Lizenz)

Der komplette Quelltext von RuBisCO ORM liegt in Subversion: Heutige Revision 28, aktuelle Version.

Das generierte Beispiel steht in examples/onetomany/src/.

[Direktlink]

< Voriger Tag   Nächster Tag >

  RSS V0.91

<April 2010 >
   01020304
05060708091011
12131415161718
19202122232425
2627282930  

Home-Produkte-Testarea-Kontakt-Datenschutz-Aktualisiert: 09-Apr-2010
(C) 2000-2018 by Sven Drieling