Einstieg in den GenericORMapper (GORM)

Aus APF Wiki [de]
Wechseln zu: Navigation, Suche

Inhaltsverzeichnis

Zielsetzung

Im vorliegenden Tutorial soll anhand eines einfachen Beispiels die Verwendung des APF-eigenen GenericORMappers verdeutlicht werden.

Es wird eine Beispielanwendung erzeugt, die es ermöglicht, zunächst Autoren zu erfassen. In einer weiteren Maske stehen diese Autoren zur Verfügung und es wird das Hinzufügen eines Buchtitels ermöglicht. Des weiteren soll die Möglichkeit bestehen, die bereits erfassten Autoren sowie die Bücher in Listenform darzustellen. Auch eine Darstellung der Zuordnung Autor => Bücher soll möglich sein. Weiterhin soll eine Löschfunktion das Löschen eines einzelnen Buches sowie das Löschen eines Autors und aller ihm zugeordneten Bücher möglich sein.

Funktionsumfang:

  • Hinzufügen eines Autors
  • Anzeigen aller erfassten Autoren
  • Hinzufügen eines Buches mit der Auswahlmöglichkeit eines zuvor erfassten Autors
  • Anzeigen aller Bücher
  • Anzeigen aller Bücher, geordnet nach dem Autor
  • Löschen eines Buches
  • Löschen eines Autors und aller ihm zugeordneten Bücher

Voraussetzungen

Das vorliegende Tutorial wurde unter Verwendung des APF 1.14 erstellt.

Der Anwender sollte Grundkenntnisse im Umgang mit dem APF (Ordnerstruktur, Namespaces) haben. Da dass Tutorial einfach gehalten ist, sollte es jedoch auch für APF-Einsteiger nachvollziehbar sein.

Eingesetzte APF-Komponenten

Page-Controller

Front-Controller

Templates

Document-Controller

Konfiguration

Standard-TagLibs

Formulare

Verwendung von Formulare

HeaderManager

Generischer-OR-Mapper

Verzeichnis- und Dateistruktur

/
index.php
-/css
--default.css
-/apps
--/books
---/pres
----/controller
------AddAuthorController.php
------AddBookController.php
------AuthorBooksListController.php
------AuthorsListController.php
------BooksListController.php
------DeleteAuthorController.php
------DeleteBookController.php
----/model
----/view
------author.html
------authorbookslist.html
------authorlist.html
------book.html
------booklist.html
------deleteauthor.html
------deletebook.html
------main.html
------start.html
--/config
---/core
----/database
-----/books
-------DEFAULT_connections.ini
----/modules
-----/gorm
------/books
--------DEFAULT_books_objects.ini
--------DEFAULT_books_relations.ini

Vorbereitung Datenbank

DEFAULT_connections.ini

apps/config/core/database/books/DEFAULT_connections.ini

[books]
DB.Host = "localhost"
DB.User = "user"
DB.Pass = "pwd"
DB.Name = "books"
DB.Type = "MySQLx"
DB.Charset = "utf8"
DB.Collation = "utf8_general_ci"

DEFAULT_books_objects.ini

apps/config/modules/gorm/books/DEFAULT_books_objects.ini

[Authors]
Author = "VARCHAR(30)"
 
[Books]
Title = "VARCHAR(30)"
Price = "DECIMAL(6,2)"

DEFAULT_books_relations.ini

apps/config/modules/gorm/books/DEFAULT_books_relations.ini

[Author2Book]
Type = "COMPOSITION"
SourceObject = "Authors"
TargetObject = "Books"

Automatisiertes GORM Datenbank-Setup

apps/modules/genericormapper/data/tools/setup.php

<?php
    require('../../../../../apps/core/pagecontroller/pagecontroller.php');
    $langProv = new IniConfigurationProvider();
    $langProv->setOmitContext(false);
    ConfigurationManager::registerProvider('ini', $langProv);
    import('modules::genericormapper::data::tools','GenericORMapperSetup');
    $setup = new GenericORMapperSetup();
    $setup->setContext('books');
    $setup->setupDatabase('modules::gorm','books','books');
    $setup->setupDatabase('modules::gorm','books');
?>

Ergebnis des GORM Datenbank-Setup

Das Ausführen der setup.php erzeugt folgende Ausgabe:

CREATE TABLE IF NOT EXISTS `ent_authors` (
  `AuthorsID` INT(5) UNSIGNED NOT NULL AUTO_INCREMENT,
  `Author` VARCHAR(30) character SET utf8 NOT NULL DEFAULT '',
  `CreationTimestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `ModificationTimestamp` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`AuthorsID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
CREATE TABLE IF NOT EXISTS `ent_books` (
  `BooksID` INT(5) UNSIGNED NOT NULL AUTO_INCREMENT,
  `Title` VARCHAR(30) character SET utf8 NOT NULL DEFAULT '',
  `Price` DECIMAL(6,2),
  `CreationTimestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `ModificationTimestamp` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`BooksID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
CREATE TABLE IF NOT EXISTS `cmp_author2book` (
  `Source_AuthorsID` INT(5) UNSIGNED NOT NULL DEFAULT '0',
  `Target_BooksID` INT(5) UNSIGNED NOT NULL DEFAULT '0',
  KEY `JOIN` (`Source_AuthorsID`, `Target_BooksID`),
  KEY `REVERSEJOIN` (`Target_BooksID`, `Source_AuthorsID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Inhalt der Datenbank

In der entsprechenden Datenbank sollten automatisch folgende Tabellen erzeugt worden sein:

  • cmp_author2book
  • ent_authors
  • ent_books

HIermit sind die Vorbereitungen zum Einsatz des GORM abgeschlossen und es kann die Umsetzung des Projekts beginnen.

Umsetzung

Bootstrap erstellen

Die zentrale Datei der Anwendung ist die index.php, welche direkt in / liegt.

<?php
// Fehlermeldungen einschalten
ini_set('html_errors', 'on');
 
// Zeitzone setzen
date_default_timezone_set('Europe/Berlin');
 
// Pagecontroller starten
require_once './apps/core/pagecontroller/pagecontroller.php';
 
// Frontcontroller starten
import('core::frontcontroller', 'Frontcontroller');
 
$fC = &Singleton::getInstance('Frontcontroller');
$fC->setContext('books');
 
// Startseite anzeigen
echo $fC->start('books::pres::view', 'main');
?>

Hauptseite

Die Hauptseite (apps/books/pres/view/main.html) ist das zentrale Element der Anwendung. Über sie werden später die einzelnen Unterseiten angesteuert.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>Books</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link rel="stylesheet" type="text/css" href="./css/default.css" />
  </head>
  <body>
 
    <h1>Navigation</h1>
    <ul>
	<li><a href="?page=start" title="Home">Home</a></li>
	<li><a href="?page=author" title="Add author">Add author</a></li>
	<li><a href="?page=authorlist" title="List authors">List authors</a></li>
  	<li><a href="?page=book" title="Add book">Add book</a></li>
  	<li><a href="?page=booklist" title="List books">List books</a></li>
	<li><a href="?page=authorbookslist" title="List authors and books">List authors and books</a></li>
	<li><a href="?page=deletebook" title="Delete a book">Delete a book</a></li>
	<li><a href="?page=deleteauthor" title="Delete an author and his books">Delete an author and his books</a></li>
    </ul>
 
    <core:importdesign namespace="books::pres::view" template="[page=start]" incparam="page"/>
 
  </body>
</html>

Dazu noch eine Startseite apps/books/pres/view/start.html:

<h2>Welcome to GenericORMapper-Demopage</h2>
<p>This example shows the use of the APF GenericORMapper.</p>
<ol>
    <li>First, you should create a new author.</li>
    <li>After that, you are redirected to an list of existing authors.</li>
    <li>Finally you can create a new book and assign an author.</li>
    <li>At last you are redirected to an list of existing books.</li>
    <li>Further a book can be deleted or an author and all assigned books.</li>
</ol>

CSS-Datei

Um bei fehlerhaften Eingaben (z.B. Autorenname zu kurz) die entsprechenden Felder farblich zu markieren wird noch unter /css/default.css eine entsprechende CSS-Definition abgelegt.

.apf-form-error{
    border: 2px solid red;
}

View zur Erfassung eines Autors

Unter apps/books/pres/views/author.html wird das Template zur Erfassung eines Autors abgelegt. In diesem kann wie im folgenden Beispiel umgesetzt auch eine Validierung stattfinden (Textlänge des Autorennamens von mindestens 3 und maximal 30 Zeichen, sowie eine Filterung der Eingabe hinsichtlich bestimmter Zeichen.

<@controller namespace="books::pres::controller" file="AddAuthorController" class="AddAuthorController" @>
<core:addtaglib namespace="tools::form::taglib" prefix="html" class="form" />
<h2>Add a new author</h2>
<html:form name="AddAuthor">
    <label for="Author">Name : </label><form:text name="Author" id="Author" minlength="3" maxlength="30"/><br/>
    <form:reset name="Reset" value="Clear form"/>
    <form:button name="SubmitAuthor" value="Add Author"/>
 
    <form:addvalidator class="TextLengthValidator" control="Author" button="SubmitAuthor"/>
 
    <form:addfilter class="SpecialCharacterFilter" control="Author" button="SubmitAuthor"/>
 
</html:form>

Controller zur Erfassung eines Autors

Der entsprechende Controller wird unter apps/books/pres/controller/AddAuthorController.php abgelegt. Nach dem Speichern des neuen Autors erfolge eine automatische Weiterleitung zur Auflistung aller vorhandenen Autoren mittels dem HeaderManager

<?php
import('modules::genericormapper::data','GenericDomainObject');
import('tools::http', 'HeaderManager');
 
class AddAuthorController extends base_controller{
 
    public function  transformContent() {
 
	$form = &$this->getForm('AddAuthor');
 
	if ($form->isSent() && $form->isValid()){
 
	    $Author = new GenericDomainObject('Authors');
	    $Author->setProperty('Author', $form->getFormElementByName('Author')->getAttribute('value'));
 
	    $orm = $this->getMapper();
	    $orm->saveObject($Author);
 
	    // Redirect to the authors list
	    HeaderManager::forward('?page=authorlist');
 
	}
	else{
	    $form->transformOnPlace();
	}
    }
 
    private function getMapper(){
 
	$ormFact = &$this->getServiceObject('modules::genericormapper::data', 'GenericORMapperFactory');
	return $ormFact->getGenericORMapper('modules::gorm', 'books', 'books');
    }
}
?>

View zur Ausgabe aller Autoren

Mit diesem View werden alle vorhandenen Autoren aus der Datenbank ausgelesen und in einer Tabelle dargestellt.

Der View zur Ausgabe aller Bücher ist identisch aufgebaut. Hier sind nur entsprechende Änderungen hinsichtlich der Feldnamen sowie des Controllers vorzunehmen.

<@controller namespace="books::pres::controller" file="AuthorsListController" class="AuthorsListController" @>
<h2>Available authors</h2>
<table>
    <tr>
	<th>Author</th>
	<th>Date</th>
    </tr>
    <html:placeholder name="AuthorsList" />
</table>
 
<html:template name="AuthorsList">
    <tr>
	<td><template:placeholder name="AuthorName" /></td>
	<td><template:placeholder name="CreateDate" /></td>
    </tr>
</html:template>

Controller zur Ausgabe aller Autoren

Der Controller holt unter Verwendung des GORM sämtliche vorhandenen Datensätze, weist sie den Platzhaltern im Template zu und gibt dieses anschließend aus.

Der Controller zur Ausgabe aller Bücher ist identisch aufgebaut. Hier sind lediglich die Bezeichnungen usw. anzupassen.

<?php
import('modules::genericormapper::data','GenericDomainObject');
import('tools::http', 'HeaderManager');
 
class AuthorsListController extends base_controller{
 
    public function  transformContent() {
 
	$orm = $this->getMapper();
 
	$Crit = new GenericCriterionObject();
	$Crit->addPropertyIndicator('Author','%');
 
	$AuthorsList = $orm->loadObjectListByCriterion('Authors',$Crit);
 
	$template = &$this->getTemplate('AuthorsList');
 
	$buffer = '';
 
	foreach ($AuthorsList as $Author){
	    $template->setPlaceHolder('AuthorName', $Author->getProperty('Author'));
	    $template->setPlaceHolder('CreateDate', $Author->getProperty('CreationTimestamp'));
	    $buffer .= $template->transformTemplate();
	}
 
        $this->setPlaceHolder('AuthorsList', $buffer);
    }
 
    private function getMapper(){
 
	$ormFact = &$this->getServiceObject('modules::genericormapper::data', 'GenericORMapperFactory');
	return $ormFact->getGenericORMapper('modules::gorm', 'books', 'books');
    }
}
?>

View zur Erfassung eines Buches

Hier wird die Möglichkeit geboten, ein neues Buch zu erfassen. Hierzu erhält der Benutzer eine Auswahlmenü, in dem alle erfassten Autoren erscheinen.

<@controller namespace="books::pres::controller" file="AddBookController" class="AddBookController" @>
<core:addtaglib namespace="tools::form::taglib" prefix="html" class="form" />
<h2>Add a new book</h2>
<html:form name="AddBook">
    <label for="Author">Author : </label>
    <form:select name="Author"/><br/>
    <label for="Title">Title : </label><form:text name="Title" id="Title"/><br/>
    <label for="Price">Price : </label><form:text name="Price" id="Price"/><br/>
    <form:reset name="Reset" value="Clear forms"/>
    <form:button name="SubmitBook" value="Add book"/>
</html:form>
 
<html:template name="AuthorsList">
	<select:option value="<template:placeholder name="ID"/>"><template:placeholder name="AuthorName"/></select:option>
</html:template>

Controller zur Erfassung eines Buches

Wichtiger Hinweis

Die Befüllung von select-Feldern muss immer stattfinden, da sonst dem Controller die Möglichkeit genommen wird, den übermittelten Inhalt zu überprüfen. Wird die Befüllung z.B. erst im else-Teil der if-Bedingung durchgeführt, erhält der Controller keine Daten für das Feld AuthorID. Das wiederum hat zur Folge, dass ein neuer, namenloser Autor angelegt wird und mit diesem das neu erfasste Buch verknüpft wird.

<?php
import('modules::genericormapper::data','GenericDomainObject');
import('tools::http', 'HeaderManager');
 
class AddBookController extends base_controller{
 
    public function  transformContent() {
 
	$form = &$this->getForm('AddBook');
 
	$orm = $this->getMapper();
 
	$Crit = new GenericCriterionObject();
	$Crit->addPropertyIndicator('Author','%');
 
	$AuthorsList = $orm->loadObjectListByCriterion('Authors',$Crit);
 
	$selectOption = &$form->getFormElementByName('Author');
 
	foreach ($AuthorsList as $Author){
	    $selectOption->addOption($Author->getProperty('Author'), $Author->getProperty('AuthorsID'));
	}
 
	if ($form->isSent() && $form->isValid()){
 
	    $Book = new GenericDomainObject('Books');
	    $Book->setProperty('Title', $form->getFormElementByName('Title')->getAttribute('value'));
	    $Book->setProperty('Price', $form->getFormElementByName('Price')->getAttribute('value'));
 
	    $Author = new GenericDomainObject('Authors');
	    $Author->setObjectId($form->getFormElementByName('Author')->getSelectedOption()->getValue());
 
	    $Book->addRelatedObject('Author2Book', $Author);
 
	    $orm = $this->getMapper();
 
	    $orm->saveObject($Book);
 
	    HeaderManager::forward('?page=booklist');
 
	}
	else{
	    $form->transformOnPlace();
	}
    }
 
    private function getMapper(){
 
	$ormFact = &$this->getServiceObject('modules::genericormapper::data', 'GenericORMapperFactory');
	return $ormFact->getGenericORMapper('modules::gorm', 'books', 'books');
    }
}
?>

View zur Ausgabe einer Auflistung der Autoren und ihrer Bücher

Die Datei apps/books/pres/view/authorbookslist.html enthält das Grundgerüst, welches durch den Controller befüllt wird.

<@controller namespace="books::pres::controller" file="AuthorBooksListController" class="AuthorBooksListController" @>
<h2>List of all authors and their books</h2>
<table>
    <tr>
	<th>Author</th>
	<th>Title</th>
	<th>Price</th>
	<th>Date</th>
    </tr>
    <html:placeholder name="DisplayAll" />
</table>
 
<html:template name="DisplayAll">
    <tr>
	<td><template:placeholder name="AuthorName" /></td>
	<td><template:placeholder name="Title" /></td>
	<td><template:placeholder name="Price" /></td>
	<td><template:placeholder name="CreateDate" /></td>
    </tr>
</html:template>

Controller zur Ausgabe der Bücherliste nach Autoren

Der Controller apps/books/pres/controller/AuthorBooksListController.php:

<?php
import('modules::genericormapper::data','GenericDomainObject');
import('tools::http', 'HeaderManager');
 
class AuthorBooksListController extends base_controller{
 
    public function  transformContent() {
 
	$orm = $this->getMapper();
 
	$Crit = new GenericCriterionObject();
	$Crit->addPropertyIndicator('Author','%');
	$Crit->addOrderIndicator('Author');
 
	$AuthorsList = $orm->loadObjectListByCriterion('Authors',$Crit);
 
	$template = &$this->getTemplate('DisplayAll');
 
	$buffer = '';
 
	foreach ($AuthorsList as $Author){
 
	    $BooksList= $orm->loadRelatedObjects($Author, 'Author2Book');
 
	    foreach ($BooksList as $Book){
 
		$template->setPlaceHolder('AuthorName', $Author->getProperty('Author'));
 
		$template->setPlaceHolder('Title', $Book->getProperty('Title'));
		$template->setPlaceHolder('Price', number_format($Book->getProperty('Price'), 2, ',', '.'));
		$template->setPlaceHolder('CreateDate', $Book->getProperty('CreationTimestamp'));
		$buffer .= $template->transformTemplate();
 
	    }
	}
 
        $this->setPlaceHolder('DisplayAll', $buffer);
    }
 
    private function getMapper(){
 
	$ormFact = &$this->getServiceObject('modules::genericormapper::data', 'GenericORMapperFactory');
	return $ormFact->getGenericORMapper('modules::gorm', 'books', 'books');
    }
 
}
?>

View zum Löschen eines Buches

<@controller namespace="books::pres::controller" file="DeleteBookController" class="DeleteBookController" @>
<core:addtaglib namespace="tools::form::taglib" prefix="html" class="form" />
<h2>Delete a book</h2>
<html:form name="DeleteBook">
    <label for="Book">Book : </label>
    <form:select name="Books"/><br/>
    <form:button name="SubmitBook" value="Delete book"/>
</html:form>
 
<html:template name="BooksList">
	<select:option value="<template:placeholder name="ID"/>"><template:placeholder name="Title"/></select:option>
</html:template>

Controller zum Löschen eines Buches

Zum Löschen eines Objekts (in diesem Fall eines Buches), stellt das APF die Methode deleteObject() zur Verfügung.

<?php
import('modules::genericormapper::data','GenericDomainObject');
import('tools::http', 'HeaderManager');
 
class DeleteBookController extends base_controller{
 
    public function  transformContent() {
 
	$form = &$this->getForm('DeleteBook');
 
	$orm = $this->getMapper();
 
	$Crit = new GenericCriterionObject();
	$Crit->addPropertyIndicator('Title','%');
 
	$BooksList = $orm->loadObjectListByCriterion('Books',$Crit);
 
	$selectOption = &$form->getFormElementByName('Books');
 
	foreach ($BooksList as $Book){
	    $selectOption->addOption($Book->getProperty('Title'), $Book->getProperty('BooksID'));
	}
 
	if ($form->isSent() && $form->isValid()){
 
	    $Book = new GenericDomainObject('Books');
	    $Book->setProperty('BooksID',$form->getFormElementByName('Books')->getSelectedOption()->getValue());
 
	    $orm = $this->getMapper();
 
	    $orm->deleteObject($Book);
 
	    HeaderManager::forward('?page=booklist');
 
	}
	else{
	    $form->transformOnPlace();
	}
    }
 
    private function getMapper(){
 
	$ormFact = &$this->getServiceObject('modules::genericormapper::data', 'GenericORMapperFactory');
	return $ormFact->getGenericORMapper('modules::gorm', 'books', 'books');
    }
}
?>

View zum Löschen eines Autors und ihm zugeordneter Bücher

<@controller namespace="books::pres::controller" file="DeleteAuthorController" class="DeleteAuthorController" @>
<core:addtaglib namespace="tools::form::taglib" prefix="html" class="form" />
<h2>Delete an autor and assigned books</h2>
<html:form name="DeleteAuthor">
    <label for="Author">Author : </label>
    <form:select name="Author"/><br/>
    <form:button name="SubmitBook" value="Delete author and assigned books"/>
</html:form>
 
<html:template name="AuthorList">
	<select:option value="<template:placeholder name="ID"/>"><template:placeholder name="AuthorName"/></select:option>
</html:template>

Controller zum Löschen eines Autors und ihm zugeordneter Bücher

<?php
import('modules::genericormapper::data','GenericDomainObject');
import('tools::http', 'HeaderManager');
 
class DeleteAuthorController extends base_controller{
 
    public function  transformContent() {
 
	$form = &$this->getForm('DeleteAuthor');
 
	$orm = $this->getMapper();
 
	$Crit = new GenericCriterionObject();
	$Crit->addPropertyIndicator('Author','%');
 
	$AuthorsList = $orm->loadObjectListByCriterion('Authors',$Crit);
 
	$selectOption = &$form->getFormElementByName('Author');
 
	foreach ($AuthorsList as $Author){
	    $selectOption->addOption($Author->getProperty('Author'), $Author->getProperty('AuthorsID'));
	}
 
	if ($form->isSent() && $form->isValid()){
 
	    $Author = new GenericDomainObject('Authors');
	    $Author->setProperty('AuthorsID',$form->getFormElementByName('Author')->getSelectedOption()->getValue());
 
	    $BooksList= $orm->loadRelatedObjects($Author, 'Author2Book');
 
	    foreach ($BooksList as $Book){
 
		$orm = $this->getMapper();
		$orm->deleteObject($Book);
 
	    }
 
	    $orm = $this->getMapper();
 
	    $orm->deleteObject($Author);
 
	    HeaderManager::forward('?page=authorbookslist');
 
	}
	else{
	    $form->transformOnPlace();
	}
    }
 
    private function getMapper(){
 
	$ormFact = &$this->getServiceObject('modules::genericormapper::data', 'GenericORMapperFactory');
	return $ormFact->getGenericORMapper('modules::gorm', 'books', 'books');
    }
}
?>
Meine Werkzeuge
Namensräume
Varianten
Aktionen
menü
misc
Werkzeuge