Contao 2.11.3 ist verfügbar

Das kleine Update behebt wieder mal einige Fehler im Core. Außerdem wurde die .htaccess.default angepasst. Es wird empfohlen die Änderungen in eine ggf. eigene .htaccess zu übernehmen. Das hier einiges geändert wurde zeigt ein Ausschnitt aus einem SVN-Vergleich:

Htaccess Änderungen in Contao-2.11.3

Htaccess Änderungen in Contao-2.11.3

Noch ein paar weitere Änderungen in der folgenden Aufzählung.

  • Die Dropdown-Menüs im Modulwizard (Seitenlayout) werden nun korrekt dupliziert
  • Die Dropdown-Menüs werden nun korrekt ausgerichtet
  • Die Mediabox unterstützt nun wieder .mp4-Dateien
  • Hochgeladene Bilder werden jetzt korrekt verkleinert (maximale Abmessungen)
  • Die E-Mail-Adresse aus dem Startpunkt wird als Absender von Formularen verwendet
  • Die IDNA-Bibliothek (URLs mit Umlauten) ist nun mit PHP 5.2 kompatibel
  • Das FAQ- und Kommentarmodul geben keinen Fehler mehr aus wenn sie leer sind
  • Der Erweiterungsmanager überspringt keine Module mehr bei der runonce.php-Abfrage

Alle weiteren Anpassungen finden sich wie immer im Changelog.

Mehr CSS – mit Less?

Nach dem ich mit Bootstrap von Twitter in Berührung kam, stieß ich auch auf Less. Bootstrap selbst bietet bereits einfache und flexible HTML, CSS und Javascript Kompontenten und setzt bei CSS auf Less, das CSS um Variablen, Operationen, Funktionen, Mixins und einiges mehr, erweitert.

Im Prinzip wird die erweiterte Syntax von einem Compiler in reguläres CSS überführt. Die Vorteile liegen scheinbar auf der Hand:

  • Variablen können ausgelagert werden, somit können z. B. unterschiedliche Farbgebungen oder Layout-Breiten realisiert werden.
  • Während der Programmierung können codesparende Techniken genutzt werden z. B. ermöglichen die sogenannten Mixins eine Wiederverwendung von bereits genutzten Anweisungen.
  • Mehrere Less-Definitionen können durch die import-Anweisung in eine CSS-Datei kompiliert werden.
  • Das CSS kann optional durch den Compiler minimiert werden,
  • Es können Berechnungen durchgeführt werden, …

Im Gegenzug sehe ich jedoch auch ein paar Probleme. Zum einem muß ein zusätzliches CSS-Framework (?) in den Entwicklungsprozess eingebettet werden. Zum anderen gefällt mir eine entscheidende Funktionalität nicht besonders. Dazu gleich mehr. Jetzt erstmal alle Nachteile die ich sehe:

  • Überschreiben von 3rdparty-Less-Definitionen (was für ein Wort…) ist umständlich bzw. erzeugt unnötigen Code.
  • Erschwertes Debuggen, da die Zeilennummern des generierten CSS nicht mit denen der Less-Definition übereinstimmen.
  • Aufwand für die Einarbeitung.
  • Die Kompilier sind teilweise fehleranfällig (z. B. werden keine Schleifen bei den import-Anweisungen erkannt).

Nun zu dem erwähnten Beispiel. Wir haben z. B. fremden Code z. B. Twitter Bootstrap in unser Projekt eingebunden. Wir hüten uns natürlich davor diesen Code zu verändern, damit er leicht zu aktualisieren bleibt (z. B. könnte auch über svn:externals Code eingebunden werden). Nun möchten wir das Layout entsprechend den eigenen Designvorstellungen anpassen. Ich nehme exemplarisch folgende Konstellation an:

Less Beispiel

Die Datei new/default.less stellt die neu zu schreibende Less-Definition dar, die Datei 3rdparty/lib.css ist eine Definition einer Fremdbibliothek.

Der Inhalt der Dateien nehme ich wie folgt an:

Less Dateien in der Gegenüberstellung

Im Compiler (WinLess) wird nur die Datei new/default.less angegeben und kompiliert:

Less Kompilierer

Nun die spannende Frage: Was wird wohl rauskommen? Unter der Annahme, dass der Kompilierer zuerst die 3rdparty-Datei abarbeitet und anschließend alle weiteren Dateien (override.less und enthaltene Anweisungen) könnte man meinen, er würde im schlechtesten Fall einfach alle Anweisungen nacheinander aufreihen. Im besten Fall würde eine einzige body-Anweisung erzeugt werden, die lautet

body {
  background-color:#ff0000; // übersetzter Wert "red"
  border:none;
  font-family:verdana;
  width:500px;
}

Das Ergebnis sieht – wie vermutet – folgendermaßen aus:

/* Einbinden fremde Definition */
body {
  background-color: #ff0000;
  font-family: verdana;
  border: 1px solid black;
}
/* Überschreiben */
body {
  background-color: #ff0000;
  border: none;
}
/* neue Anweisungen */
body {
  width: 500px;
}

Von schlankem CSS will ich hier nicht sprechen. Außerdem ist nicht mehr ersichtlich, dass der Hintergrund in der ursprünglichen Anweisung den Wert “#5B83AD” hatte.

Fazit

Auf den ersten Blick erscheint eine zusätzliche Abstraktionsschicht interessant, insbesondere die Verwendung der Variablen und der sogenannten Mixins. In der Praxis erhöht sich der Entwicklungsaufwand jedoch nicht unerheblich (Stichwort: Debugging und Integration) und das erzeugte CSS ist in den meisten Fällen nicht kleiner als eigener Code. Der eigentlich Kernnutzen für mich läge im einfachen und optimierten Überschreiben von bestehenden Less-Definitionen. Leider sind dafür die Kompilier noch nicht optimiert.

4. Zend Tutorial

Nachdem ich im dritten Tutorial erklärt hatte, wie die (vorläufige) Struktur der Datenbank aussieht, müssen wir noch ein paar Vorbereitungen schaffen, damit wir einfach unsere Datenbank aktualisieren können. Das ganze habe ich bereits in meinem Artikel Zend Framework und Liquibase beschrieben. Ich möchte dazu auch nur die Abweichungen niederschreiben.

Ant Build Datei

Die Ant-Build-Datei sieht folgendermaßen aus:

<?xml version="1.0" encoding="UTF-8"?>
<project name="liquibase-test">
  <description>
    Zend Framework Tutorial
  </description>
 
  <!-- prepare -->
  <target name="prepare">
    <path id="library.classpath">
      <fileset dir="library/3rdparty/mysql">
        <include name="*.jar" />
      </fileset>
      <fileset dir="library/3rdparty/liquibase">
        <include name="*.jar" />
      </fileset>
    </path>
 
    <taskdef resource="liquibasetasks.properties">
      <classpath refid="library.classpath" />
    </taskdef>
 
    <!-- set global properties for this build -->
    <property name="changelog.file" location="db/changelog.xml" />
    <property name="base.path" location="." />
  </target>
 
  <!-- properties -->
  <target name="properties_development" depends="prepare">
    <property name="database.driver" value="com.mysql.jdbc.Driver" />
    <property name="database.url" 
       value="jdbc:mysql://localhost/zend_volleyball" />
    <property name="database.user" value="root" />
    <property name="database.password" value="" />
  </target>
 
  <!-- development -->
  <target name="development" depends="properties_development">
    <updateDatabase changeLogFile="${changelog.file}" 
      driver="${database.driver}"
      url="${database.url}" username="${database.user}" 
      password="${database.password}"
      classpathref="library.classpath" />
  </target>
</project>

Damit das ganze funktioniert müssen folgende Voraussetzungen erfüllt sein:

  • Im Ordner library/3rdparty/mysql muß ein MySQL-Connector verfügbar sein,
  • Im Ornder library/3rdparty/liquidbase muss Liquidbase verfügbar sein,
  • Ant muss installiert sein. Das habe ich in Ant unter Windows 7 installieren bereits beschrieben.
  • Java muß auf dem System installiert sein.
  • Die Datei db/changelog.xml muss angelegt werden. Inhalt wie nachfolgend beschrieben.

Liquidbase

Die Datei changelog.xml stellt den Einstiegspunkt  für Liquidbase dar. Dort referenzieren wir bisher nur eine weitere Datei:

<?xml version="1.0" encoding="UTF-8"?>
 
<databaseChangeLog
  xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
    http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd">
 
 <include file="/db/001.setup.xml" /> 
 
</databaseChangeLog>

Nun formulieren wir unser Datenbank Model in Liquidbase:

<?xml version="1.0" encoding="UTF-8"?>
 
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
         http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd">
 
  <preConditions>
    <dbms type="mysql" />
  </preConditions>
 
  <changeSet id="10" author="set">
 
    <!-- Team -->
    <createTable tableName="volleyball_team">
      <!-- common -->
      <column name="id" type="int" autoIncrement="true">
        <constraints primaryKey="true" nullable="false" />
      </column>
      <column name="name" type="varchar(255)">
        <constraints nullable="false" />
      </column>
      <column name="available" type="boolean" />
      <column name="fee_paid" type="boolean" />
      <column name="competition_id" type="int" />
      <column name="group_id" type="int" />
    </createTable>
 
    <!-- Competition -->
    <createTable tableName="volleyball_competition">
      <!-- common -->
      <column name="id" type="int" autoIncrement="true">
        <constraints primaryKey="true" nullable="false" />
      </column>
      <column name="name" type="varchar(255)">
        <constraints nullable="false" />
      </column>
      <column name="start" type="datetime" />
      <column name="single_duration" type="time" />
      <column name="number_of_sets" type="int" />
    </createTable>
    
    <!-- Group -->
    <createTable tableName="volleyball_group">
      <!-- common -->
      <column name="id" type="int" autoIncrement="true">
        <constraints primaryKey="true" nullable="false" />
      </column>
      <column name="name" type="varchar(255)">
        <constraints nullable="false" />
      </column>
    </createTable>
    
    <!-- Fremdschlüssel -->
    <addForeignKeyConstraint constraintName="fk_competition_team" 
      baseTableName="volleyball_team" baseColumnNames="competition_id" 
      referencedTableName="volleyball_competition" 
      referencedColumnNames="id" />
    <addForeignKeyConstraint constraintName="fk_group_team" 
      baseTableName="volleyball_team" baseColumnNames="group_id" 
      referencedTableName="volleyball_group" 
      referencedColumnNames="id" />
  </changeSet>
</databaseChangeLog>

An sich nichts spannendes. Wenn man sich ein bisschen in die Dokumentation von Liquidbase einliest, ist das kein Problem mehr. Wenn alles richtig gemacht wurde können wir das Ant-Skript in der Konsole starten und sollten folgende Ausgabe erhalten:

Ant Skript ausführen

So sollte die Ausgabe in der Konsole aussehen.

3. Zend Tutorial

Im ersten und zweiten Tutorial haben wir die Grundlagen geschaffen, nun will ich mit der Planung bzw. Architektur zu der Beispiel-Anwendung beginnen. Ich habe mir eine Turnier-Verwaltung ausgedacht, da man sich darin gleich reindenken kann und die Anforderungen zu Beginn nicht zu hoch sind. Also was brauchen wir auf Datenbank-Ebene? Eine Tabelle für die Mannschaften und eine für die Turniere. Außerdem möchte ich die Mannschaften einer Gruppe zuordnen können (Gruppierung). Somit sieht das erste Datenbank-Diagramm folgendermaßen aus:

Erstes Datenbankschema der Beispielanwendung

Erstes Datenbankschema der Beispielanwendung

Zur Tabelle Competition: Auch ein Turnier hat einen Namen und ein geplanten Startermin. Für das Turnier wird auch festgelegt, wie lange ein Spiel gehen soll (single_duration) und wieviele Sätze pro Spiel gemacht werden (number_of_sets). Das wären z. B. beim Volleyball die einzelnen Sätze eines Spiels.

Zur Tabelle Team: Ein Team besteht aus einem Namen und muß am Tag des Turniers auch da sein (available). Außerdem muß vorher eine Gebühr bezahlt werden (fee_paid). Diese zwei Spalten speichern lediglich Bool-Werte ab.

Zur Tabelle Group: Eine Gruppe hat lediglich einen Namen. Dadurch kann z. B. gekennzeichnet werden, dass ein Verein mehrere Mannschaften stellen kann. Dies kann dann später im Spielplan berücksichtigt werden.

Der Zend-Server kann gleich mit einer mySql-Datenbank installiert werden. Deswegen werde ich diese Datenbank zugrunde legen.

Datenbankverbindung

Eine Datenbankverbindung kann über das zf-Tool eingerichtet werden. Ich habe das schon in dem Artikel Datenbank unter Zend ausführlich beschrieben.

ORM

ORM steht für Object-Relational Mapping und ist nichts anderes, als dass rationale Datenbanken auf Objekte abgebildet werden. Im offiziellen Zend-Tutorial werden dazu Model-, Mapper-, und Table-Klassen verwendet. Nachfolgende Skizze soll das verdeutlichen:

Zend Datenbank Abbildungen

Prinzip des Zend Datenbank ORM

Im Pseudocode der Funktion fetchAll sehen wir, dass im Mapper eine Instanz der Datenbank-Abstraktion geholt wird (z. B. eine MySql-Adapter-Klasse) über die Daten von der Datenbank geholt wird. Anschließend werden die Daten auf das Model abgebildet und zurückgegeben.

zf-Tool

Den Adapter konfigurieren und die ersten Klassen anlegen können wir einfach mit dem zf-Tool erledigen:

zf configure dbadapter "adapter=Pdo_Mysql&username=test&
                        password=test&dbname=zend_volleyball"
zf create db-table Team team
zf create db-table Competition competition
zf create db-table Group group
zf create model Team
zf create model Competition
zf create model Group

 

SVN Externals

Meistens gibt es in einer Anwendung Bestandteile, die man immer wieder benötigt z. B. eine externe Bibliothek oder ähnliches. Dies sollte man nicht jedesmal von neuem in den SVN Zweig einspielen, sondern über die Properties svn:externals als externe Resource einbinden. Unter Eclipse ist das ganz einfach:

  • Den übergeordneten Ordner selektieren
  • Rechtsklick -> Team -> “Set property…” auswählen
  • Dort svn:externals auswählen
  • Als Wert die externe Resource hinterlegen z. B. [Zielordner] [SVN-URL]

2. Zend Tutorial

Heute will ich die Grundlage des Layouts auf ordentliche Füße stellen. In meinen letzten Projekten habe ich mit dem HTML Kickstarter 99limes gearbeitet und möchte nun etwas anderes probieren. Ich habe von Twitter Bootstrap gelesen und die Webseite macht für mich einen guten Eindruck. Wir laden im ersten Schritt den Quellcode von der Webseite. Das ist eine einzelne Zip-Datei, die wir im public verzeichnis unserer Applikation entpacken – genauer gesagt werden die Dateien im Verzeichnis /public/3rdparty/twitter/bootstrap entpackt.

Twitter Bootstrap Verzeichnisstruktur

Wie integrieren wir das nun in unser Projekt? Richtig! In der Bootstrap.php ergänzen wir die folgenden zwei Zeilen:

// css
$view->headLink()->appendStylesheet('/3rdparty/twitter/bootstrap/css/bootstrap.min.css');
// js
$view->headScript()->appendStylesheet('/3rdparty/twitter/bootstrap/js/bootstrap.min.js');

Wir haben noch das Problem, dass wir mit dem falschen Doctype arbeiten. Wir müssen das also in der settings.ini anpassen:

resources.view.doctype=HTML5

Es lohnt sich übrigens in die Framework-Datei /Zend/View/Helper/Doctype.php zu schauen. Dort sind diese Konstanten vordefiniert.

Nun können wir noch etwas Beispiel HTML-Code in unsere index.phtml einfügen und schon erhalten wir eine schöne Ausgabe. Der Quellcode für die Datei /application/views/scripts/index/index.phtml hab ich aufgrund der Menge mal hier hinterlegt.

end Beispiel Inhalt

1. Zend Tutorial

Mal sehen wie weit ich komme, aber ich möchte für mich den Einstieg ins Zend Framework dokumentieren. Das ist der erste Artikel eine hoffentlich längeren Serie.

Voraussetzung

Die Voraussetzung für dieses Tutorial ist eine Zend Server CE Installation mit einer MySQL Installation und ein wenig Grundwissen in PHP.

Einrichtung

Wir beginnen mit der Einrichtung des Vhost-Eintrags zu meinem Beispiel-Projekt:

<VirtualHost *:80>
   ServerAdmin kontakt@tobias-seckinger.de
   DocumentRoot "C:/wwwroot/zend_volleyball/public"
   ServerName volleyball.local   
   <Directory "C:/wwwroot/zend_volleyball/public">
        Options Indexes FollowSymLinks Includes +ExecCGI
        AllowOverride All
        Order allow,deny
        Allow from all
    </Directory>
</VirtualHost>

Ich habe also im Verzeichnis

C:/wwwroot/

erstmal nur ein Ordner zend_volleyball angelegt. Anschließend erstellen wir im Verzeichnis

C:\Program Files (x86)\Zend\ZendServer/etc/sites.d/

eine Datei mit dem Namen vhost_volleyball.conf mit dem obigen Inhalt. Die Datei wird nach dem Apache-Neustart ausgelesen, weil der Zend-Server in der httpd.conf folgende Zeile enthält:

Include "C:\Program Files (x86)\Zend\ZendServer/etc/sites.d/vhost_*.conf"

Nun öffnen wir ein Windows Konsole und wechseln in das Verzeichnis

C:/wwwroot/

und tippen:

zf create project zend_volleyball

Anschließend aktivieren wir noch ein Standard-Layout. Dazu wechseln wir aber erst ins Verzeichnis zend_volleyball und tippen:

zf enable layout

Wir müssen ggf. noch die hosts Datei von Windows ergänzen, damit die Seite lokal geladen wird. Wir öffnen die Datei

C:\Windows\System32\drivers\etc\hosts

und ergänzen folgende Zeile

127.0.0.1           volleyball.local

Achso: Den Apache Neustart nicht vergessen.

Grundgerüst

Wir wollen natürlich ein schönes HTML Grundgerüst haben inklusive eigenen Javascript und CSS-Dateien. Zuerst ergänzen wir unsere Bootstrap-Datei um folgende Methode (/application/Bootstrap.php):

protected function _initApplication()
{
  $this->bootstrap('view');
  $view=$this->getResource('view');
 
  $view->headMeta()->appendHttpEquiv('Content-Type',
                                     'text/html;charset=utf-8');
  // meta
  $view->headMeta()->appendName('keywords', 'Zend, Tutorial, Volleyball');
  // css
  $view->headLink()->appendStylesheet('/css/default.css');
  // js
  $view->headScript()->appendFile('/js/common.js');
  // title
  $view->headTitle()->setSeparator(' - ');
  $view->headTitle('Volleyball - Zend-Tutorial');
}

Damit das auch ausgegen werden kann, müssen wir in unserem Layout auch die passenden Anweisungen integrieren (/application/layouts/scripts/layout.phtml):

<?php echo $this->doctype(); ?>
<html xmlns="http://www.w3.org/1999/xhtml" lang="de">
<head>
  <?php echo $this->headTitle(); ?>
  <?php echo $this->headMeta(); ?>
  <?php echo $this->headLink(); ?>
  <?php echo $this->headStyle(); ?>
  <?php echo $this->headScript(); ?>
</head>
<body>
  <?php echo $this->layout()->content; ?>
</body>
</html>

Wenn wir nun die Seite neu laden erhalten wir erstmal eine Fehlermeldung. Wir müssen noch eine Kleinigkeit in der application.ini ergänzen:

resources.view[] =
; Doctype setzen
resources.view.doctype=xhtml1_strict

Außerdem müssen wir die CSS und Javascript-Dateien anlegen:

  • /public/css/common.css
  • /public/js/common.js

Die Datei /application/views/scripts/index/index.phtml können wir aufräumen:

<div>
  Übersicht
</div>

Anschließend sollten wir ungefähr folgende Ausgabe im Browser erhalten:

Weiterführende Informationen:

Lokale Datenbank mit JavaScript – LocalStorage

Mit jStorage steht eine Javascript-Bibliothek, die den lokalen Speichermechanismus von HTML5 abstrahiert. Es werden sogar ältere Browser unterstützt, jedoch muß dann mit einer Speicherplatzbeschänkung gerechnet werden.

Browserunterstützung von jStorage

Browserunterstützung von jStorage - Quelle: http://www.jstorage.info

Es gibt einige Vorteile die für diese Technologie sprechen:

  • Cookies können nur eine sehr begrenzte Datenmenge aufnehmen (max. 80 kB).
  • Gespeicherten Daten werden nicht wie bei den Cookies bei einem Request mitgeschickt.
  • Lokal gespeicherte Daten laufen nicht ab (sofern kein Gültigkeitszeitraum definiert wurde).
  • Cookies können (leicht) deaktiviert werden und haben zum Teil einen schlechten Ruf.

Funktionsumfang von jStorage

Die wichtigsten Funktionen sind:

$.jStorage.set(key, value)

Mit der Set-Funktion können Schlüssel-Werte-Paare gespeichert werden. Der Schlüssel muß ein String sein. Der Wert kann außer einem skalaren Wert auch ein JSON-Objekt, ein Array oder ein XML-Node sein.

$.jStorage.get(key)

Mit der Get-Funktion werden die Werte abgerufen.

$.jStorage.storageAvailable()

Mit dieser Funktion wird abgefragt, ob die Funktion im Browser überhaupt zur Verfügung steht.

Ich habe mir diesmal die Mühe gemacht eine kleine Demo zu erstellen:

Demo im neuen Fenster öffnen.

Demo

Doctype unter Zend setzen

Über die application.ini kann dies leicht gemacht werden:

resources.view[] =
resources.view.doctype=xhtml1_strict

Es gibt auch die Möglichkeit das in der Bootstrap.php zu machen, wobei ich erste Methode bevorzuge:

protected function _initDoctype() 
{ 
  $this->bootstrap('view'); 
  $view = $this->getResource('view'); 
  $view->doctype('XHTML1_STRICT'); 
}

Es sollte natürlich ein Layout aktiv sein. Das macht man am besten mit der Konsole:

zf enable layout

In der layout.phtml kann dann die Doctype-Anweisung ausgegeben werden:

<!-- application/layouts/scripts/layout.phtml -->
 
<?php echo $this->doctype() ?>
 
....