Startseite
Teil 3 der Blogreihe Extension-Entwicklung mit TYPO3 Extbase & Fluid

Extension-Entwicklung mit TYPO3 Fluid & Extbase: Teil 3 – Implementierung

Click Hier wenn du die Social-Plugins aktivieren willst.

Teil 3:
Model als erste Implementierung

Als nächstes wenden wir uns den Domainmodels zu, definieren die Properties und deren Abbildung in der Datenbank und kümmern uns um das TCA (Table Configuration Array). Das TCA wird für das Property Mapping benötigt und der Benutzer kann mit dessen Hilfe seine Daten im Backend eingeben. An dieser Stelle gibt es kein Entkommen, das TCA zu vermeiden ist nicht möglich. Als ein Entwickler, der oft TCA schreibt und manipuliert, verfügt meine IDE (Integrated Development Environment) über eine Sammlung von Snippets, die mir eine Grundstruktur jedes TCA-Teils mit wenigen Tastaturanschlägen zur Verfügung stellt – das erweist sich immer wieder als eine große Hilfe.

 

Convention over Configuration

Extbase folgt dem Ansatz, möglichst viele Dinge über Konventionen zu regeln und so Konfiguration zu sparen. Dazu zählt zum Beispiel die Annahme, dass die Template-Dateien einer Extension innerhalb des Extension-Ordners unter Resources/Private/Templates/Controllername zu finden sind oder dass beim Zugriff auf eine Model-Instanz auch alle abhängigen Datensätze mit aus der Datenbank geholt werden (sogenanntes eager loading, ich erkläre das bei den Relationen von Objekten). Wenn man das angenommene Verhalten nutzen möchte, muss man nichts tun. Will man es ändern, muss es – meist über TypoScript – entsprechend konfiguriert werden.

Es ist fest vorgegeben, dass Klassennamen UpperCamelCase und Variablen lowerCamelCase formuliert werden. Letzteres ist wichtig für das Mapping auf Datenbank Felder, denn jeder Großbuchstabe im Variablennamen wird zu einem Unterstrich im Feldnamen. Hält man sich daran, ist eine explizite Mapping-Konfiguration für die Properties überflüssig.

Noch ein Wort zur namespace Deklaration und dem Vendor: Für Kompatibilität und bessere Lesbarkeit wechselte das gesamte TYPO3 CMS Projekt mit der Version 6.0 zu PHP namespaces. Das bedeutet in der Theorie, dass wir keine Extensionkeys mit Präfix (wie tt_news) mehr brauchen, da eine Extension eindeutig über die Kombination aus Vendor und Extensionkey identifiziert werden kann. Wenn das funktioniert, wird es möglich sein, zwei Extensions mit dem gleichen Key aber unterschiedlichem Vendor in der gleichen Installation zu verwenden. So weit sind wir leider noch nicht, da die Dateistruktur für TYPO3 CMS Extensions den Vendor noch nicht einschließt und sich damit die Ordner überschreiben würden. Den Vendor benutzen wir trotzdem. Er sollte eindeutig einer Person bzw. einem Unternehmen zuordenbar sein, besteht aus einem Wort und beginnt mit einem Grossbuchstaben. Eine Ausnahme bildet der Namespace des TYPO3 Cores, der heißt TYPO3\CMS. Dieser Namespace darf nicht von Extensions verwendet werden.

 

Model Bestandsobjekt und seine Kinder

Beginnen wir mit der Implementierung der Klasse ‘Bestandobjekt’. Da ich hoffe, für mein Spielwiesenprojekt später Akzeptanz und Unterstützung zu gewinnen, die nicht zwangsläufig deutschsprachig sein muss, ist mein Quellcode englisch, sowohl Benennung als auch Kommentare. Das ist keine Vorgabe, jeder kann hier seinen eigenen Neigungen und Umständen Rechnung tragen. Ich persönlich ziehe Englisch vor.

 

<?php
namespace Typovision\Biblio\Domain\Model;
/** copyright notice **/
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

/**
 * Inventory Object represents the abstract definition of all kind
 * of borrowable objects.
 *
 */
class InventoryObject extends AbstractEntity {

	/**
	 * The identifier used by common understanding
	 * every book and other object has its own, so it is unique in the table
	 *
	 * @var string
	 */
	protected $isbn;

	/**
	 * objects title
	 * must not be unique, think about books with the same title
	 *
	 * @var string
	 */
	protected $title;

	/**
	 * objects author
	 * the initiator of an object. Might be the author writing a book
	 * or an artist featuring music
	 *
	 * @var string
	 */
	protected $author;

	/**
	 * year of publishing
	 * 
	 * @var int
	 */
	protected $publishYear;

	/**
	 * objects publisher
	 * 
	 * @var string
	 */
	protected $publisher;

	/**
	 * objects identifier used to identify the object inside the biblio inventory
	 * needs to be unique
	 * 
	 * @var string
	 */
	protected $identifier;
}

 

So sieht die Klasse mit definierten Properties aus. Für jede Property brauchen wir noch getter und setter, die die IDE erzeugen kann. Somit muss man diese nicht tippen.

Dem aufmerksamen Leser wird nicht entgangen sein, dass die Property-Kategorie aus dem Model-Diagramm nicht vorhanden ist. TYPO3 CMS hat mit Version 6.0 globale Kategorien in den Core eingeführt. Das Konzept ist inzwischen gereift und auch für Extensions nutzbar, so dass ich die globalen Kategorien auch nutzen werde. Zunächst gilt es aber die grundlegenden Funktionen aufzubauen, die globalen Kategorien werden dann in einem eigenen Kapitel integriert. Als Konvention gilt, dass alle Properties als protected deklariert werden. Das stellt einerseits sicher, dass der Zugriff nur über die getter- und setter-Funktionen erfolgt und ermöglicht andererseits, das Erweitern der Klasse, wie es für unser Beispiel ja sowieso vorgesehen ist und in der Praxis oft ohne Kontrolle des Extensionautors mittels erweiternder Extensions stattfindet. Es gilt als arrogant, aktiv die Erweiterbarkeit der Extensions zu erschweren –  das kann ja nicht die Absicht sein, nicht wahr?

An diesem Punkt haben wir die Definition einer Basisklasse, die gar nicht direkt verwendet werden soll. Es macht also keinen Sinn hierfür Datenbanktabelle und TCA zu definieren. Dies tun wir für die Unterklassen. Ich benutze die Buch-Klasse (Book) stellvertretend für beide, denn die Models werden initial einfach leer sein, da alle Eigenschaften bereits in der Elternklasse definiert sind. Also werden wir nun die Datenbanktabellen und TCA doppelt erstellen müssen, für jede Klasse einmal. Es gibt das Konzept der Single Table Inheritance, die den umgekehrten Weg geht und verschiedene Objekte in einer Tabelle abbildet, aber auch das ist ein Thema für einen anderen Tag.

Die Tonträger-Klasse heißt Digital. Zugegeben, hier könnte man kreativer sein. Die Benennung der Klassen, Variablen und Methoden ist entscheidend für die Lesbarkeit des Codes. Besser zwei Minuten länger überlegen. In der Annahme, dass diese Bibliothek im 21. Jahrhundert angekommen ist und keine Videobänder und Audiokassetten mehr führt, repräsentiert die Tonträger-Klasse also vor allem CDs und DVDs. Sollte sich diese Annahme als falsch herausstellen, kann man ja noch eine weitere Kindklasse implementieren.

So sieht erstmal verkürzt das Buch-Model (Classes/Domain/Model/Book.php) aus:

<?php
namespace Typovision\Biblio\Domain\Model;
/** copyright notice **/
/**
 * Book Model Class
 */
class Book extends InventoryObject {
}

 

Für die SQL-Datei (ext_tables.sql im Extension Root Verzeichnis) kann eine beliebige Datei einer anderen Extension genutzt werden, um die Systemfelder aufzusetzen. Die Eigenschaften unserer Models müssen wir dann nachtragen. Ich habe mir von der Systemextension core die SQL-Datei angeschaut und die Tabelle sys_category eignet sich da prima zum kopieren. Selbst die Indizes kann man mitnehmen, nur natürlich nicht die category-spezifischen.

 

#
# Table structure for table 'tx_biblio_domain_model_book'
#
CREATE TABLE tx_biblio_domain_model_book (

  uid int(11) NOT NULL auto_increment,
  pid int(11) DEFAULT '0' NOT NULL,

  isbn varchar(255) DEFAULT '' NOT NULL,
  title varchar(255) DEFAULT '' NOT NULL,
  author varchar(255) DEFAULT '' NOT NULL,
  publish_year int(4) unsigned DEFAULT '0' NOT NULL,
  publisher varchar(255) DEFAULT '' NOT NULL,
  identifier varchar(255) DEFAULT '' NOT NULL,

  tstamp int(11) unsigned DEFAULT '0' NOT NULL,
  crdate int(11) unsigned DEFAULT '0' NOT NULL,
  cruser_id int(11) unsigned DEFAULT '0' NOT NULL,
  deleted tinyint(4) unsigned DEFAULT '0' NOT NULL,
  hidden tinyint(4) unsigned DEFAULT '0' NOT NULL,
  starttime int(11) unsigned DEFAULT '0' NOT NULL,
  endtime int(11) unsigned DEFAULT '0' NOT NULL,

  t3ver_oid int(11) DEFAULT '0' NOT NULL,
  t3ver_id int(11) DEFAULT '0' NOT NULL,
  t3ver_wsid int(11) DEFAULT '0' NOT NULL,
  t3ver_label varchar(255) DEFAULT '' NOT NULL,
  t3ver_state tinyint(4) DEFAULT '0' NOT NULL,
  t3ver_stage int(11) DEFAULT '0' NOT NULL,
  t3ver_count int(11) DEFAULT '0' NOT NULL,
  t3ver_tstamp int(11) DEFAULT '0' NOT NULL,
  t3ver_move_id int(11) DEFAULT '0' NOT NULL,
  sorting int(11) DEFAULT '0' NOT NULL,
  t3_origuid int(11) DEFAULT '0' NOT NULL,
  sys_language_uid int(11) DEFAULT '0' NOT NULL,
  l10n_parent int(11) DEFAULT '0' NOT NULL,
  l10n_diffsource mediumblob,

  PRIMARY KEY (uid),
  KEY parent (pid),
  KEY t3ver_oid (t3ver_oid,t3ver_wsid),
  KEY language (l10n_parent,sys_language_uid)
);

 

Seit TYPO3 CMS 6.2 gibt es die Zweiteilung der TCA-Definition nicht mehr. Das gewohnte Vorgehen wäre, den Teil des Arrays mit dem Key ctrl in die Datei ext_tables.php zu legen (daher hat sie übrigens ihren Namen). Aber wie umständlich ist das? Wir definieren statt dessen pro Tabelle ein Datei mit dem Namen der Tabelle in Configuration/TCA und schreiben das gesamte Array da hinein.

So sieht die Datei Configuration/TCA/tx_biblio_domain_model_book.php aus:

 

<?php
return array(
	'ctrl' => array(
		'title' => 'LLL:EXT:biblio/Resources/Private/Language/locallang_db.xlf:tx_biblio_domain_model_book',
		'label' => 'title',
		'tstamp' => 'tstamp',
		'crdate' => 'crdate',
		'cruser_id' => 'cruser_id',
		'dividers2tabs' => TRUE,
		'sortby' => 'sorting',
		'versioningWS' => 2,
		'versioning_followPages' => TRUE,
		'origUid' => 't3_origuid',
		'languageField' => 'sys_language_uid',
		'transOrigPointerField' => 'l10n_parent',
		'transOrigDiffSourceField' => 'l10n_diffsource',
		'delete' => 'deleted',
		'enablecolumns' => array(
			'disabled' => 'hidden',
			'starttime' => 'starttime',
			'endtime' => 'endtime',
		),
		'iconfile' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extRelPath('biblio') . 'Resources/Public/Icons/tx_biblio_domain_model_book.gif',
		'searchFields' => 'isbn, title, identifier'
	),
	'interface' => array(
		'showRecordFieldList' => 'sys_language_uid, l10n_parent, l10n_diffsource, hidden, isbn, title, author, publish_year, publisher, identifier',
	),
	'columns' => array(
<< insert system field definition here >>
		'isbn' => array(
			'exclude' => 0,
			'label' => 'LLL:EXT:biblio/Resources/Private/Language/locallang_db.xlf:tx_biblio_domain_model_book.isbn',
			'config' => array(
				'type' => 'input',
				'size' => 30,
				'max' => 255,
				'eval' => 'trim'
			),
		),
		'title' => array(
			'exclude' => 0,
			'label' => 'LLL:EXT:biblio/Resources/Private/Language/locallang_db.xlf:tx_biblio_domain_model_book.title',
			'config' => array(
				'type' => 'input',
				'size' => 30,
				'max' => 255,
				'eval' => 'trim'
			),
		),
		'author' => array(
			'exclude' => 0,
			'label' => 'LLL:EXT:biblio/Resources/Private/Language/locallang_db.xlf:tx_biblio_domain_model_book.author',
			'config' => array(
				'type' => 'input',
				'size' => 30,
				'max' => 255,
				'eval' => 'trim'
			),
		),
		'publish_year' => array(
			'exclude' => 0,
			'label' => 'LLL:EXT:biblio/Resources/Private/Language/locallang_db.xlf:tx_biblio_domain_model_book.publish_year',
			'config' => array(
				'type' => 'input',
				'size' => 30,
				'max' => 255,
				'eval' => 'trim'
			),
		),
		'publisher' => array(
			'exclude' => 0,
			'label' => 'LLL:EXT:biblio/Resources/Private/Language/locallang_db.xlf:tx_biblio_domain_model_book.publisher',
			'config' => array(
				'type' => 'input',
				'size' => 30,
				'max' => 255,
				'eval' => 'trim'
			),
		),
		'identifier' => array(
			'exclude' => 0,
			'label' => 'LLL:EXT:biblio/Resources/Private/Language/locallang_db.xlf:tx_biblio_domain_model_book.identifier',
			'config' => array(
				'type' => 'input',
				'size' => 30,
				'max' => 255,
				'eval' => 'trim'
			),
		)
	),
'types' => array(
	'1' => array('showitem' => 'hidden, title, isbn, author;;1, identifier')
),
'palettes' => array(
	'1' => array('showitem' => 'publisher, --linebreak--, publish_year'),
),
);

 

Wenn man sich das jetzt mal genauer ansieht stellt man fest, dass wir immer noch nicht alle Dateien verfügbar haben, die die Extension braucht. So fehlen das ext_icon.gif file, das im ExtensionManager und in der List View angezeigt wird, sowie die Tabellen-Icons und auch die Datei mit den Übersetzungen.

Der Reihe nach, damit nichts verloren geht:

  1. das Extension Icon. Wer nicht künstlerisch begabt ist kopiert sich eines, ich habe mich für das TYPO3 Icon entschieden.
  2. Icon für das Book Model. Dafür habe ich mich beim Extension-Builder bedient und das Standard-Icon für eine Entity-Klasse benutzt.
  3. die Übersetzungsdatei. Liegt in Resources/Private/Language, heisst locallang_db.xlf und enthält die englischen Label für die Backend-Seite der Extension. Das Format ist Xliff.
  4. kopieren von Model, TCA und Tabellenicon für die Digital Klasse. Ergänzen der ext_tables.sql um die Tabellendefiniton für dieses Model.

Damit sind die Bestandsobjekte fertig. Im nächsten Beitrag kümmern wir uns um die Kunden und am Ende kommt noch das Bibliotheksmodel dazu. Erst dann sind wir bereit für den ersten Controller. Hier geht es zum nächsten Beitrag: Implementierung Kunden Model.

 

Start verpasst?
Blogreihe Extension-Entwicklung mit TYPO3 Extbase & Fluid
Teil 1: Entstehung der Blogreihe, Idee & Datenmodellierung
Teil 2: Das Grundgerüst

5 Gedanken zu “Extension-Entwicklung mit TYPO3 Fluid & Extbase: Teil 3 – Implementierung

  1. Sehr gut geschriebener Artikel, besser kann man es kaum beschreiben. Wenn man diese Sachen einmal verstanden hat, kann man auch gern auf den ExtensionBuilder zurück greifen und weiß dann genau, wo und warum man etwas verändern muss. Macht weiter so, vielen Dank!

    Kommentar
  2. anja-leichsenring Post author

    Danke Christian. Natuerlich kann (und sollte) jeder den Extension Builder benutzen, es ist ein grossartiges Tool. Ich verzichte darauf, weil ich so step by step erklaeren kann.

    Kommentar
  3. Carsten Kettner

    Auch der 3. Teil ganz hervorragend. *thumbs up*

    Kommentar
  4. kevin

    Es wäre hilfreich gewesen, wenn noch nicht angelegte Ordner erwähnt werden, damit man sie nicht vergisst und mein zweiter Kritikpunkt ist, dass bei vielen Dateien kein genauer Speicherort angegeben ist, welchen man sich so umständlich zusammensuchen muss… ein Nachtrag diesbezüglich wäre super bzw. am besten auch eine Definition der einzelnen Schritte die man macht.

    Ansonsten gut und vor allem angenehmer Schreibstil

    Kommentar
    1. Zu allererst vielen Dank für diese wunderbaren Anleitungen.

      Da ich mich in fremdem Revier befinde, geht es mir an dieser Stelle wie ‚kevin‘. Ich kann die einzelnen zu erstellenden Dateien (z. B. Klassen) keinen konkreten Orten und Namen zuordnen. Es wäre hilfreich an dieser Stelle (evtl. als Bildunterschrift) den Namen und Speicherort der jeweiligen Datei anzugeben. Ansonsten besteht hier sehr viel Fehler- und Frustrationspotenzial.

      Allerbest
      Pilgrim

      Kommentar

Hinterlasse einen Kommentar zu kevin Antworten abbrechen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

Du kannst folgende HTML-Tags benutzen: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>