Model-View-Controller (MVC)

Aus THM-Wiki
Wechseln zu: Navigation, Suche

Einleitung

Motivation

Die Idee für diesen Vortrag entstand im SoSe 2005 im Rahmen eines Sourcecode-Audits des eStudy-Projektes. Dabei ist aufgefallen, dass der Großteil der eStudy-EntwicklerInnen über wenig bis gar keine Erfahrung in PHP verfügen. Theoretische Grundlagen zu patternorientiertem Design werden zwar in Softwaretechnik ausreichend vermittelt, für praktische Erfahrungen reicht aber oft die Zeit nicht. Wenn das Projekt anfängt ist der Zeitdruck meist so groß, dass keine vorgefertigten Pakete benutzt werden, sondern das Rad einfach schnell neu erfunden wird. Dies führt dann zu unübersichtlichem, schlecht wartbarem und erweiterungsfeindlichem Code. Dieser Vortrag soll anhand einiger Beispiele zeigen, wie es besser gemacht werden kann. Leider ist natürlich auch das vorgestellte Lernquizmodul alles andere als perfekt und enthält einige Workarounds, über deren Beseitigung ich mich sehr freuen würde. Des Weiteren gibt es viele andere Module für den selben Zweck.

Benötigte (PHP)-Grundlagen

Dieser Vortrag richtet sich ausdrücklich an fortgeschrittene PHP-EntwicklerInnen. Zumindest die folgenden Konstrukte sollten geläufig sein, ein rudimentäres Verständnis von Datenbanken wird auch erwartet.

  • for, while, if
  • echo
  • Arrays
  • foreach
  • Klassen, normale und statische Methodenaufrufe (bla->blub() vs. bla::blub())
  • default parameter (function bla($blub = 1234) { echo $blub; })
  • var_dump(), print_r()
  • $_SESSION, $_REQUEST
  • require_once
  • a == b vs. a === b (der Fallstrick in "typenlosen" Sprachen)
  • array_key_exists(), isset()
  • unset()
  • explode(), implode()

Was bedeutet MVC / Warum sollte man es benutzen

MVC ist ein Muster der Softwaretechnik.

Bei dem MVC-Pattern geht es um die Trennung von Datenmodell, Programmierlogik (im Java-Neudeutsch Business-Logic) und Layout. Damit wird eine geringere Kopplung gewährleistet, was die Wiederverwendbarkeit erhöht. Der Einsatz macht selbst in kleinen "ein-Mensch"-Projekten Sinn, da man nach einmaliger Einarbeitungsphase deutlich effektiver wird. Bei professionellen Projekten bewahrt MVC vor dem Wahnsinn bzw. dem Unmut des Teams. Man stelle sich vor alles wäre in einer Datei oder der Designer will das Layout umstellen, ProgrammiererIn A will ein paar Bugs fixen, C will ein neues Fremdmodul anbinden und morgen ist Vorführung bei dem Kunden. Mit MVC passiert alles in verschiedenen Dateien oder zumindest in voneinander entkoppelten Klassen in einer Datei, die der Versionsverwaltung keine Probleme bereiten.

Verschiedene Umsetzungen am Markt

In der Praxis unterscheiden sich die einzelnen Auslegungen des Patterns deutlich. Oftmals, z.B. bei QuickForm_Controller und JavaServer Faces, wird die Trennung zwischen Model und View zu Gunsten der Bedienbarkeit aufgeweicht, hier rückt das Model näher an das View.

  • JavaServer Faces (JSF)
  • Apache Struts (Java) / Struts 4 PHP
  • Ruby on Rails
  • PHP on Trax
  • PHP Quickform_Controller
  • PHP MVC
  • Symfony Project Den Admin Generator Screencast ansehen und vor Glück heulen :-)
  • ...

View

Bei der vorgestellten MVC-Implementierung wird das View mittels dem PEAR-Modul HTML_Quickform umgesetzt. Es bietet verschiedene vorgefertigte Elemente, wie z.B. Inputfelder, Buttons, Dropdown-Menüs sowie vorgefertigte Regeln zur Validierung der Benutzereingaben.

Beispiel

Im Folgenden wird ein einfaches Formular erstellt und im Browser angezeigt. Analog zu z.B. JavaServer Faces wird der Inhalt des Formulars deklarativ angegeben (wenn auch nicht in einer XML Datei). Somit sind Zeit und Ort des Erstellens und der Definition voneinander entkoppelt, was Flexibilität schafft. So könnte man z.B. bei einer Wiederverwendung des Formulars noch ein Element an beliebiger Stelle hinzufügen oder entfernen.

<?php

// Ein ganz einfaches Formular
class simplePage extends HTML_QuickForm_Page {

    function buildForm() { 
        $this->_formBuilt = true;
        $this->addElement('textarea','Question', 'Frage: ');
        $this->addElement('checkbox','check1', 'Alles klar? ');
        $this->addElement('submit','button1','weiter');
    }
}
// Instanz erstellen
$view1 = new simplePage("view1");

// Formular erstellen
$view1->buildForm();

// Formular anzeigen
$view1->display();
?>

So sieht das erstellte Formular aus: Vortrag2.png

Controller

In einen Controller kann man mehrere verschiedene Instanzen von unterschiedlichen Viewelementen stecken. Er kennt deren Schnittstellen und verwaltet sie für uns.

Beispiel

<?php

require_once ('HTML/QuickForm/Controller.php');

// Ein ganz einfaches Formular
class simplePage extends HTML_QuickForm_Page {

    function buildForm() { 
        $this->_formBuilt = true;
        $this->addElement('textarea','Question', 'Frage: ');
        $this->addElement('checkbox','check1', 'Alles klar? ');
    }
}

// Erstellen einer Instanz des Controllers
$cont = new HTML_Quickform_Controller("cont1");

// Erstellen einer Instanz des Formulars
$view1 = new simplePage("irgendeinName");

// Übergabe des Formulars an den Controller
$cont->addPage($view1);

// Controller starten
$cont->run();

?>

...und Action!

Natürlich sollte nach dem Absenden eines Formulars auch etwas passieren. Bei HTML_Quickform_Controller werden hierfür Actions eingesetzt, die das Interface HTML_QuickForm_Action implementieren. Sie enthalten einerseits die sogenannte Business Logic (alle Vorgänge, die dem Wirtschaftler klar sind, z.B. Quiz anlegen, Quiz löschen, etc.) und können andererseits die nachfolgende Navigation beeinflussen.

<?php

require_once ('HTML/QuickForm/Controller.php');
require_once ('HTML/QuickForm/Action.php');

// Ein ganz einfaches Formular mit einem Button
class simplePage extends HTML_QuickForm_Page {
  
    function buildForm() { 
        $this->_formBuilt = true;
        $this->addElement('textarea', 'Question', 'Frage: ');
        $this->addElement('checkbox', 'check1', 'Alles klar? ');
        // Für die Namen von Buttons gibt es strenge Regeln, wir können
        // einen Button für eine Action anlegen, bevor wir die Actions anlegen
        $this->addElement('submit', $this->getButtonName("show"), 'weiter');
    }
}

// Eine einfache Action
class ShowAction extends HTML_QuickForm_Action {

    function perform(&$page, $actionName) {
        // Nötig, damit die $_REQUEST Variablen ausgewertet werden
        $page->buildForm();
        // Herauslösen der Nutzdaten aus dem View
        $model = $page->exportValues();
        var_dump($model);
        exit();
    }
}

$cont = new HTML_Quickform_Controller("cont1");
$view1 = new simplePage("irgendeinName");
// Die Action wird dem Controller bekannt gemacht
$cont->addAction("show", new ShowAction());
$cont->addPage($view1);
$cont->run();

?>

Model

Bei dem vorgestellten Modul ist das Model leider sehr schlecht umgesetzt. Die Daten werden direkt aus dem Elementbaum des Quickform-Objektes ausgelesen. Es gibt keinen eigenen Objekttyp für das Model und auch keine Konventionen wie z.B. bei Managed Beans in JavaServer Faces. Dies geht sogar so weit, dass man das Model konvertieren muss wenn man es in Verbindung mit DataObjects nutzen will (dazu später mehr).

Beispiel

(s.o.)

$model = $page->exportValues();
var_dump($model);

Als Ergebnis erhält man einen komplexen Datentyp mit den Nutzdaten des Views:

array
  'Question' => 'test'
  'check1' => '1'
  '_qf_irgendeinName_show' => 'weiter'

Anwedungsbeispiel aus dem eStudy-Modul Lernquiz

Einleitung

Das Lernquiz ist im Wintersemester 05/06 im Zuge einer SWT3- (bzw. MSP-) Arbeitsgruppe entstanden. Es soll einerseits Studierenden die Möglichkeit geben, mit Hilfe von Multiple-Choice-Aufgaben das Gelernte zu vertiefen und andererseits zur Leistungsüberwachung durch die DozentInnen beitragen. Jedes eStudy-Mitglied kann eigene Quizzes anlegen und anderen zur Verfügung stellen.

Bei der Umsetzung wurde das Front-Controller-Pattern gewählt, fast alle Funktionalitäten werden über die Datei showExams.php angesteuert. Diese speichert den aktuellen Zustand des Quiz (wie z.B. Teilnahme, Editieren, Anzeigen, ...) und dessen ID in der Session und sorgt in einem zweiten Schritt für die entsprechenden Ausgaben im Browser. Diese Lösung hat den Vorteil, dass nach einem (versehentlichen) Klick in einen anderen Bereich des Portals immer wieder die richtige Seite angezeigt wird. Das Erstellen der einzelnen Viewkomponenten wurde zur besseren Übersicht in eine Factory ausgelagert und befindet sich in der Datei classes/formFactory.php. Die einzelnen Actions liegen in classes/actions.php und enthalten die "Businesslogic" sowie die Entscheidungslogik für die Navigation. Kurz zusammengefasst: Die showExams.php nutzt die Formfactory um den Controller abhängig vom aktuellen Zustand mit einzelnen Viewinstanzen und Actions zu füllen.

DB_DataObjects als Model

Mit MVC haben die PEAR-DB_DataObjects nichts direkt zu tun, sie dienen uns aber als komfortable Schnittstelle zu der Persistenzschicht (Datenbank). Sie werden im kompletten Lernquiz anstatt zu ezSQL benutzt. Mit viel gutem Willen könnte man sie als die Implementierung des Models bezeichnen. Bevor sie verwendet werden können, muss eine Konfigurationsdatei erstellt werden, welche zumindest die Pfade für die erstellten PHP-Klassen und die Zugangsdaten zur Datenbank enthält.

[DB_DataObject]
database        = mysql://Benutzer:Passwort@localhost/Datenbankname
schema_location = ./DataObjects
class_location  = ./DataObjects
require_prefix  = 
class_prefix    = 

Auf der Kommandozeile wird das Script createTables.php aus dem PEAR-Paket mit der Konfigurationsdatei als Parameter ausgeführt.

andi@XXX:~ php /usr/share/php/DB/DataObject/createTables.php quiz.ini

Die erstellten Klassen erhalten den Namen der jeweiligen Tabelle und alle Spalten als Eigenschaften.

class Quiz_Answer extends DB_DataObject 
{
    ###START_AUTOCODE
    /* the code below is auto generated do not remove the above tag */
    /**
     *  @access private
     */
    var $__table = 'quiz_Answer';         // table name
    var $AnswerID;                        // int(10)  not_null primary_key unsigned auto_increment
    var $Answer;                          // blob(65535)  blob
    var $Correct;                         // int(11)  
    var $QuestionID;                      // int(10)  multiple_key unsigned
    var $Point;                           // int(10)  

    /* ZE2 compatibility trick*/
    function __clone() { return $this;}

    /* Static get */
    function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('Quiz_Answer',$k,$v); }

    /* the code above is auto generated do not remove the tag below */
    ###END_AUTOCODE
}

Des Weiteren erben sie einige sehr praktische Methoden, wie z.B. setFrom(array), welches die einzelnen Eigenschaften anhand des Inhalts eines Arrays überschreibt. Bei einer sauberen Implementierung des MVC-Patterns wird es sehr einfach, die Daten aus dem View in die Persistenzschicht zu bekommen:

class QuizCreateAction extends HTML_Quickform_Action {

    function perform(&$page, $actionName) { 
        $exam = new Quiz_Exam();
        $arr = $page->exportValues();
        $exam->setFrom($arr);
        $exam->UserID = $_SESSION['userid'];
        $ExamID = $exam->insert();
        $page->ExamID = $ExamID;
    }
}

Eine zweite Konfigurationsdatei im Verzeichnis der Dataobjekt-Klassen regelt die Beziehungen zwischen den Tabellen. Die 1:n Beziehung zwischen den Tabellen quiz_Exam und quiz_Course_Exam wird z.B. wie folgt dargestellt:

[quiz_Exam]
ExamID = quiz_Course_Exam:ExamID

Ausführliches Beispiel: Quiz Editieren Use-Case

Edit Tabs

Auch wenn die einzelnen Links zu den Fragen untereinander stehen, ist das zu Grunde liegende Konzept genau das selbe, wie bei den altbekannten Browsertabs.

class formFactory {

    function getEditQuiz($id, &$cont) {
        $frage = new Quiz_Question();
        $frage->ExamID=$id;
        $anz = $frage->find();
        $x=0;
        $cont->addAction('abort', new AbortAction()); 
        if ($anz > 0) {   
            $cont->addAction('saveandnext', new SaveAndNextAction());
            $cont->addAction('saveandback', new SaveAndBackAction());     
            $cont->addAction('delete', new deleteButtonAction()); 
            while ($frage->fetch()) {
                $page[$x] = new editQuestion($frage->QuestionID, $x);        
                $info[$x]['var'] = $page[$x]->getAttribute('id');
                $info[$x]['question'] = $frage->Question;
                $cont->addPage($page[$x]);
                $cont->addAction($info[$x]['var'], new directJump());
                $x++;
            }
        } else $info=false;
        $_SESSION['quiz_info']=$info;

        $page[$x] = formFactory::getInsertQuestion($id, $cont, true);
        $info[$x]['var'] = $page[$x]->getAttribute('id');
        $info[$x]['question'] = 'Neue Frage';
        $_SESSION['quiz_info']=$info;
        $cont->addAction($info[$x]['var'], new directJump());
        $cont->addPage($page[$x]); 
}

Hier zeigt das MVC-Konzept seine Mächtigkeit. Die Factory-Methode bekommt nur die ID des Quizes übergeben und erstellt für jede Seite eine Quickform, welche sie dem Controller hinzufügt. Dabei werden die Seiten nicht alle komplett erstellt, sondern nur ihr Name und somit das entsprechende Objekt zu deren Erstellung werden dem Controller bekannt gegeben. Dieser sorgt dann bei Bedarf für die Erstellung und das Anzeigen der einzelnen Seiten. Das $info-Array speichert die IDs und Titel der einzelnen Frage für die Navigation, dieses Feature sollte man besser in den Controller implementieren. Hier sorgen die Seiten selbst für die entsprechenden Navigationsbuttons:

class editQuestion extends HTML_QuickForm_Page {
    [...]
    function buildForm() {
        $this->_formBuilt = true;
        [...]
      	if (isset($_SESSION['quiz_info'])) foreach ($_SESSION['quiz_info'] as $i) {
            $group[] = new HTML_QuickForm_static(null, Data::toHTML(cutQuestion($i['question'])));
	    $group[] = new HTML_QuickForm_submit($this->getButtonName($i['var']),"bearbeiten");
	    $this->addGroup($group, '', 'Frage:' );
	    $group=array();
        }
    }
}

Validierung, Trimmung

classes/forms/editQuestion.php

$this->addElement('textarea', 'Question', 'Frage: ', array('cols' => 70));
$this->addRule('Question', 'Eine Frage sollten Sie eingeben...', 'required');

$this->addElement('file', 'MyFile', 'Bild: ');
$allowed_picture_types = array ('jpg', 'jpeg', 'png', 'gif');
$this->addRule('MyFile', 'Nur Bilder erlaubt', 'filetype', $allowed_picture_types);

Es gibt eine ganze Reihe vorgefertigter Regeln zur Validierung und Methoden zur Trimmung, auf die hier nicht eingegangen wird. Natürlich ist es auch möglich, Regeln für Gruppen von Elementen oder für ganze Seiten zu definieren. Siehe: QuickForm Manual

Des Weiteren gibt es die Möglichkeit, beliebige Funktionen als Filter für ein Element festzulegen. Filter werden vor der Validierung angewandt!

$this->applyFilter('Question', 'trim');

Kaskadierte Actions

classes/actions.php

Da alle Actions das selbe Interface implementieren, kann man bei Bedarf auch mehrere Actions hintereinander ausführen. Somit lässt sich die Wiederverwendbarkeit weiter erhöhen:

class SaveAndNextAction extends HTML_QuickForm_Action {
    function perform(&$page, $actionName){
        QuestionUpdateAction::perform($page, $actionName);
	directNextAction::perform($page, $actionName);
    }
}

Kontextsensitive Folgeseiten

Die für eine Seite angegebenen Bedingungen für Validität werden mit Hilfe der Quickform-Methode validate() geprüft. Diesen Prozess kann man in einer Action selbst anstoßen und dann entscheiden, ob das selbe Formular versehen mit einer Fehlermeldung eingeblendet wird, oder ob man eine andere Seite anzeigt.

class QuestionUpdateAction extends HTML_QuickForm_Action {
    function perform(&$page, $actionName) {
        $page->buildForm();
        if (!$page->validate()) {
            $page->display();
        } else {
            // Code für das speichern einer Frage und anzeigen der Startseite
        }
    }
}

Die Fehlermeldungen werden von Quickform selbstständig an den im Template vorgegebenen Stellen eingefügt.

Navigation zwischen mehreren Seiten

Der Controller verfügt über Methoden, welche Referenzen zu verschiedenen Seiten zurückgeben können. So ist es sehr einfach, die Letzte, Erste oder eine bestimmte Seite anzusteuern.

class directNextAction extends HTML_QuickForm_Action {

    function perform(&$page, $actionName) {
        $nextName = $page->controller->getNextName($page->getAttribute('id'));
        $next = $page->controller->getPage($nextName);
        unset($next->_submitValues);
        $next->buildForm();
        $next->display();
    }
}

Quickform mit Templates

classes/forms/editQuestion.php

Im Folgenden wird das HTML nicht direkt von Quickform erstellt, sondern von einem Templatesystem, welches sich über einen "Renderer" in Quickform einbinden lässt.

class editQuestion extends HTML_QuickForm_Page {
    [...]
    function display() {
        $template =& new HTML_Template_Sigma('.');
        $template->loadTemplateFile('./classes/forms/qformEdit.html');
        // Instantiate the renderer and process the form
        $renderer =& new HTML_QuickForm_Renderer_ITDynamic($template);
        $this->accept($renderer);
        $template->show();
    }
}

Hierfür wird das Visitor-Pattern genutzt, der Renderer kommt als Gast in das Haus von Quickform: Er wird zwar umsorgt, darf aber nichts anfassen. Hier wurden dynamische Templates verwendet, für jedes Designelement (wie z.B. einen Tabellenkopf) wird nur einmal das Layout vorgegeben: Tabellenkopf und Tabellenzeilen. In den geschweiften Klammern stehen die Platzhalter, welche mit durch die gleichnamigen Eigenschaften der Quickformelemente ersetzt werden.

<!-- BEGIN qf_main_loop -->
    <!-- BEGIN qf_header -->
    <tr>
        <th colspan="2"><h1>{qf_header}</h1></th>
    </tr>
    <!-- END qf_header -->

    <!-- BEGIN qf_element -->
    <tr valign="top">
        <td align="right">
            <b>{qf_label}</b>
	    <!-- BEGIN qf_element_required --><span style="color: #FF0000;">*</span><!-- END qf_element_required -->    
        </td>
        <td>
            <!-- BEGIN qf_element_error --><span style="color: #FF0000;">{qf_error}</span><br /><!-- END qf_element_error -->
            {qf_element}
        </td>
    </tr>
    <!-- END qf_element -->

Neben HTML_Template_Sigma lassen sich diverse andere Templatesysteme ansteuern, unter anderem auch Flexy. Siehe: QuickForm Renderers

Kleine Bilanz

Die eierlegende Wollmilchsau ist QuickForm_Controller leider auch nicht. Kritikpunkte sind u.a. die schlechte Fehlerbehandlung, mangelnde Dokumentation und Erweiterungsbedarf an einigen Stellen. Trotzdem lohnt sich die Nutzung. Wenn man einmal das Konzept verstanden hat, kann man schnell auf andere MVC-Frameworks umsteigen. Positiv ist, dass der Sourcecode wie auch der Funktionsumfang sehr kompakt ist und man sich dadurch relativ schnell sehr tief einarbeiten kann. Im Rahmen von FOSS wäre es sehr wünschenswert, wenn sich irgendwann eine SWT-Projektgruppe finden würde, die dieses Projekt ein wenig überarbeitet bzw. wenn die einzelnen Gruppen, die es nutzen, ihre Optimierungen zu den Projekten zurückfließen lassen.

Häufige Fehlerquellen und Fehlerbehebungen

Hier seid ihr alle gefragt, allgemein geht bei der Wiederverwendung von Softwarekomponenten eine Menge Zeit für das Finden und Verstehen von Fehlern drauf.

Stack Trace or die

Leider ist eine Fehlerbehandlung bei HTML_Quickform und Konsorten kaum vorhanden. Die Fehlermeldungen des PHP-Interpreters sind von Haus aus auch nicht sonderlich hilfreich.

<?php
require_once ('HTML/QuickForm/Controller.php');
$view1 = new HTML_QuickForm_Page();
?>

Dieses Script verursacht folgende Warnung:

Warning: Missing argument 1 for html_quickform_page() in /usr/share/php/HTML/QuickForm/Page.php on line 61

Für AnfängerInnen ist dies höchst verwunderlich, da man ja normalerweise nichts direkt in den PEAR-Modulen verändert. Selbst wenn man ahnt, dass hier der Name der Seite nicht als Parameter an das PEAR-Modul übergeben wurde, ist bei komplexen Projekten keineswegs klar, an welcher Stelle sich der Fehler befindet. Abhilfe schafft hier ein Trace des Callstacks, den z.B. Xdebug automatisch in den Browser ausgibt:

Warning: Missing argument 1 for html_quickform_page() in /usr/share/php/HTML/QuickForm/Page.php on line 61
Call Stack
#	Function	Location
1	{main}()	/home/andi/workspace/eStudyQUIZ/eStudy/quiz/tutorial/Stacktrace1.php:0
2	html_quickform_page->html_quickform_page()	/home/andi/workspace/eStudyQUIZ/eStudy/quiz/tutorial/Stacktrace1.php:3

So erkennt man schnell, wo der Fehler entstanden ist.

DB_DataObject debugLevel

Wenn es mit der Datenbankverbindung mal nicht klappt, lohnt es sich, das interne Debugging zu nutzen. DB_DataObject_Formbuilder schaltet sein Debugging automatisch an, falls er ein DataObject mit eingeschaltetem Debugging übergeben bekommt.

$do = new User();
$do->debugLevel(4);

QuickForm und Checkboxen

Sehr verwirrend ist, dass Checkboxen nur dann in den exportValues erscheinen, wenn sie "gechecked" sind. Ist kein Häkchen gesetzt ist also die Variable weg, was zu sehr schwer zu findenden Fehlern führen kann.

ToDo

Leider ist das Modul (wie fast jede verfügbare Software) noch nicht fertig. Es gibt noch einiges aufzuräumen, zu erweitern und vor allem zu verschönern:

  • Einheitliche Schnittstelle zur Formfactory, entweder immer Controller mit übergeben oder nie
  • Einheitliches Model incl. Schnittstellen zu Dataobjects und Quickform schaffen
  • "Navigationstabs" (editQuiz) sollten die benötigten Daten aus dem Controller bekommen und nicht aus einem Array, das zusätzlich in die Session gepackt wird...
  • Aufräumen und Code-Shrinking

Beyond Lernquiz

Rapid Prototyping

Wer sich jetzt denkt, dass man ja eigentlich aus den vorhandenen Metadaten der Datenbank Formulare zum Bearbeiten der einzelnen Tabellen automatisch erstellen kann, hat Recht. DB_DataObject_Formbuilder tut genau dies. Ein eStudy-Modul zum Editieren der Benutzerdaten lässt sich z.B. in wenigen Minuten wie folgt erstellen:

  • DataObjects erstellen (s.o.)
  • Script erstellen
<?php
require_once 'init.php';
require_once (PATH_TO_ROOT."common/init.inc.php");
require_once (PATH_TO_ROOT."common/header.inc.php");
require_once ('classes/initDataObjects.php');
require_once ("/usr/share/php/DB/DataObject/FormBuilder.php");

$do = new User();
$do->debugLevel(0);
$do->find();
$do->fetch();
$fg = DB_DataObject_FormBuilder::create($do);
$form = $fg->getForm();
if ($form->validate()) {
    $form->process(array(&$fg,'processForm'), false);
    $form->freeze();
}
$form->display();
?>

Fremdschlüssel-Beziehungen können recht einfach mit Hilfe von Selectboxen abgebildet werden. Soll z.B. für das Land eine Selectbox mit den einzelnen Ländern angezeigt werden:

  • In der Datei DataObjects/Quiz.ini die Option einschalten
    [DB_DataObject_FormBuilder] linkDisplayFields = title 
  • In der Datei DataObjects/Countries.php festlegen, welches Feld anstatt der ID des Landes angezeigt werden soll:
    var $fb_linkDisplayFields = array('country');
  • Die Foreign-Key Beziehung in der Datei quiz.links.ini angeben (Vorsicht Stolperfalle: DB_Dataobject erwartet den Namen der erstellten Klasse (Countries), nicht den der Tabelle in der Datenbank (countries)
[user]
countryID = Countries:countryID

Das fertige Formular

Quellen und Links

DB_Dataobject_Formuilder Tutorial