PHP-Applikationen und Caching – I

Einige Web-Hosting-Anbieter – u.a. 1&1 – haben mit ihren aktuellen Webhosting-Angeboten eine Politik verbunden, die die Performance eines angebotenen Web-Hosting-Leistungspakets von dessen Preis abhängig macht. Dies wirkt sich vor allem bei Web-Seiten aus, die dynamisch mittels PHP, Perl oder anderen serverbasierten Scriptsprachen auf der Basis von Datenbankdaten generiert werden.

Einige preisbewusste Kunden diskutieren deshalb immer häufiger mit uns, was man tun kann, um die Performance von serverbasierten Web-Applikationen zu verbessern. In unserem Fall sind dies vor allem PHP5-Applikationen. Wir meinen, dass in vielen Fällen ein systematischer und situationsgerechter Einsatz von verschiedenen “Caching”-Verfahren zu einer substanziellen Verbesserung der Situation führen kann.

Ich stelle deshalb in diesem und in nachfolgenden Artikeln einige Caching-Ansätze für PHP-Applikationen zusammen, die in unserer praktischen Entwicklungsarbeit eine Rolle spielen.

Im vorliegenden Teil I der Beitragsserie gehe ich zuerst auf generelle Faktoren ein, die die Performance von Web-Applikationen beeinflussen. Caching-Verfahren wirken sich auf verschiedene dieser Faktoren aus.

Ich beschreibe danach die Grund-Prinzipien einiger gängiger Caching-Verfahren für PHP-Anwendungen. Ziel ist es aufzuzeigen, wie man den Interaktionsaufwand mit dem Web-Server durch die Kombination dieser Cache-Methoden sukzessive reduzieren kann.

Zudem betrachte ich auch ein paar fachliche und technische Voraussetzungen für Caching. Dabei ergibt sich, dass und warum ein konservativer Ansatz für bestimmte Caching-Verfahren oder deren Kombination sich sowohl an zeitbasierten Regeln als auch an Merkmalen für Änderungen der Datenbestände des Servers orientieren sollte.

Dieser erste Teil ist als Einführung also am ehesten für Leute gedacht, die sich erstmalig mit der Thematik des Cachens im Zusammenhang mit PHP-Applikationen befassen.

In den nachfolgenden Teilen der Beitragsserie widme ich mich dann dem Coding einzelner, spezifischer Caching-Lösungen unter PHP.

Performance von PHP-basierten Webseiten aus Sicht des Nutzers

Der Betrachter von Webseiten servergestützter Webapplikationen begreift die Performance der Applikationen und ihrer einzelnen Seiten hauptsächlich unter dem Aspekt, wie lange es dauert, bis der Browser nach einer Benutzerinteraktion mit einer bereits dargestellten Seite das gewünschte Resultat – in der Regel eine Webseite oder Teile davon – darstellt. Diese “Antwortzeit” ist natürlich ganz entscheidend davon abhängig, ob überhaupt ein Datentransfer zum oder vom Server und ein Seitenaufbau auf dem Server stattfinden:

In vielen modernen Applikationen werden kleinere lokale Zwischenschritte am Browser von einer lokalen Javascript-/JQuery-Logik übernommen, die per DOM Seiteninhalte manipuliert. Oftmals werden auch nicht ganze Webseiten neu generiert, sondern per AJAX/DOM nur Teile einer geöffneten Seite mittels der Ergebnisse einer asynchronen Serveranfrage im Hintergrund modifiziert. Von der Interaktion der Webseite mit dem Server bekommt der Anwender am Browser dabei bei einem asynchronen Datenaustausch im Hintergrund nur bedingt etwas mit.

Kritisch mit der Performance wird es in der Regel immer dann,

wenn vor der nächsten Userinteraktion eine (synchrone) Interaktion mit dem Server stattfindet oder stattfinden muss. Hier addieren sich Datentransferzeiten zwischen Browser/Client und Web-/Datenbank-Servern zu den erforderlichen Zeiten, um das jeweilige Programm am Server durchzuführen.

Wann erfolgt ein solcher Neu-Aufbau von Webseiten durch den Server ? Eine typische, einfache, aber regelmäßig auftretende Situation während der Benutzerinteraktion hierfür ergibt sich ggf. bereits durch den Seitenwechsel eines Nutzers am Browser zu Web-Seiten, die PHP-
basiert sind.

Nicht ist störender als beim Hin- und Herwechseln zwischen Webseiten als bei denjenigen Seiten, die am Server durch PHP-Programme generiert werden, unverhältnismäßig lang warten zu müssen ! Und das, obwohl sich ggf. seit dem letzten Besuch der PHP-basierten Seite vielleicht gar nichts an deren Inhalt geändert hat.

Serverbasierte Applikationen wie PHP-Applikationen trifft die Seiten-Neugenerierung am Server besonders, weil die meisten Browser den Output von serverbasierten Script-Programmen nicht ohne Zusatzmaßnahmen cachen oder dies nur gemäß einer eigenen Heuristik tun. Die Webserver der meisten Hoster sind zudem praktisch nie so eingestellt, dass für PHP-Seiten defaultmäßig ein Caching angewiesen wird. Um diesen Punkt muss man sich der Entwickler schon selbst kümmern !

Bei ein wenig Nachdenken wird man feststellen, dass Caching in Kombination mit “Ajax/PHP” oder gar “PHP/SOAP/REST/Webservices” ein komplexeres Thema ist als Caching im Falle des Abrufes ganzer PHP-Seiten. Wir klammern Ajax und Web-Services aus den Betrachtungen dieser Beitragsserie weitgehend aus.

Hingewiesen sei auch darauf, dass bestimmte Caching-Verfahren natürlich nicht nur in Bezug auf Browser sondern auch gegenüber anderen Clients (Apps, andere serverbasierte Applikationen) interessant sind. Ob und inwieweit der Client explizite HTTP-Header-Anweisungen zum Cachen tatsächlich in ähnlicher Form wie ein Browser nutzen kann, lassen wir mal dahingestellt.

Performance-limitierende Faktoren für serverbasierte Web-Applikationen und für dynamisch generierte Webseiten

Die Performance von Webseiten, deren Inhalte z.B. vollständig oder teilweise durch PHP-Programme auf dem Webserver generiert werden, wird durch eine Vielzahl von Faktoren beeinflusst. Zu nennen sind u.a.:

  1. Die Auslastung des Servers und dessen Puffer-Mechanismen
  2. die benötigte Zeit zum aktiven Laden der dynamisch eingesetzten Script-Module des Webservers,
  3. die Ladezeit von benötigten Include-Dateien am Web-Server,
  4. die benötigte Zeit für die Interpretation und Durchführung des Server-Skript-Codes,
  5. die Zeit für den Verbindungsaufbau vom Webserver zu Datenbankservern und die evtl. Nutzung eines Verbindungspoolings,
  6. die benötigte Zeit für Datenbanktransaktionen und die Zeit für Datentransfers zwischen Webserver und Datenbankserver und umgekehrt,
  7. das Caching-Verhalten des Web-Servers bzgl. der PHP-Module und bzgl. des Ladens der Include-Dateien,
  8. die Effizienz von Sessionspeichern und zugehörigen Technologien (RAM, Filesystem, Datenbank),
  9. das Caching von Provider- und Proxy-Servern,
  10. die Geschwindigkeit der Internetanbindung und Datentransferzeiten zwischen Browser und Webserver,
  11. das Caching am Browser selbst (Nutzung des Browser-Caches).

Die Performance-Politik von Providern beeinflusst praktisch alle Punkte bis auf die letzten zwei.

Wie stark ist nun die Abhängigkeit der Performance von unterschiedlichen Hosting-Paketen und deren Preis? Wir haben je nach Vertrag unserer Kunden Unterschiede in der Größenordnung bis zu einem Faktor 5 für das gleiche Programm unter verschiedenen Hosting-Paketen registriert:

Ein Programm, das in einem lokalen Netzwerk mit Gigabit-Netzwerk, nicht ausgelastetem Web- und Datenbankserver ca. 70 bis 80 Millisekunden (ohne das Nutzen von Caching-Mechanismen) benötigt, erforderte typischerweise ca. 120 – 220 Millisekunden unter besten Bedingungen beim Provider. Je nach Vertrag und auch Auslastung der Server
des Providers kamen jedoch auch Zeiten deutlich oberhalb von 500 Millisekunden oder gar 1,5 Sekunden vor.

Bei preismäßig “günstigen” Verträgen bewegt man sich also u.U. in einem vom Kunden deutlich wahrnehmbaren Reaktionsbereich der Webserver-Leistung. Eine Optimierung von Web-Applikationen durch den Entwickler ist also angesagt, wenn der Kunde “preisgünstige” Angebote wahrnehmen will oder muss. Optimierung kann grundsätzlich an vielen Punkten ansetzen – doch nicht immer kann ein Entwickler auf einfache Weise spürbare Effekte erzielen:

Etliche der genannten Punkte sind partiell natürlich abhängig von dem, was der Provider an Netz- und Server-Technik nutzt und anbietet. Dennoch kann der Entwickler einiges tun:

Optimierungsmaßnahmen bzgl. des obigen Punktes 3) benötigen z.B. eine intelligente Klassen- und Programm-Politik auf der PHP-Seite. Unter besten Bedingungen kann das Laden von Klassenbibliothek-Files bei umfangreichen Systemen durchaus ein performance-limitierender Faktor sein. Hat man den Webserver selbst unter Kontrolle, so mag sich hier ein Griff zu Tools wie APC lohnen.

Unter Punkt 4 ist natürlich nicht nur eine performante Server-Hard- und Software gefragt. Selbstverständlich lassen sich auch PHP-Programme durch intelligente Maßnahmen des Entwicklers optimieren. Dies kann jedoch sehr aufwändig werden – und Kosten/Nutzen-Betrachtungen sind wichtig. Und welcher Entwickler geht schon ohne echte Not an die Grundfesten von Applikationen heran, die seit Jahren im Einsatz sind ?

Um eine optimale SQL-Performance muss sich der Entwickler zu einem essentiellen Anteil in jedem Falle selbst kümmern. Die Wahl geeigneter Tabellen-Varianten des RDBMS, die Wahl der Aufgabe angemessener Sperrmechanismen, Performance-Optimierung des Datenmodells, wo sinnvoll auch Einsatz von Denormalisierung und optimierte Indizierung sind hier erste Stichworte.

Man sollte sich als Kunde jedoch darüber im Klaren sein, dass der Aufwand für eine nachträgliche Performance-Optimierung von Web-Applikationen beträchtliche Ausmaße annehmen kann. Für die entsprechenden Kosten kann man sich dann ggf. auch ein besseres Hosting-Paket oder einen eigene Root-Server leisten.

Will man als Entwickler in der Kooperation mit dem Kunden schnell und effizient positive Effekte hinsichtlich der Performance erzielen, lohnt es sich daher durchaus, Caching-Methoden zu untersuchen und diese über klar abgegrenzte, überschaubare Funktionen oder Klassen-Methoden in die PHP-Applikationen zu implementieren. Der Aufwand dafür hält sich nämlich in Grenzen.

Ziele von Caching-Verfahren

Das grundlegende Ziel des Einsatzes von Caching-Verfahren macht sich an folgender Frage fest:

Wie kann ich die Interaktionsdauer mit dem Web-Server durch das Ausnutzen von Pufferspeichern – also Caches – und die Analyse von Änderungsinformationen zur Datenbasis der Webseiten so weit als möglich reduzieren ?

Strategien in diesem Zusammenhang können sein:

  • bereits erzielte Ergebnisse – sprich Webseiten – in direkt verwertbarer Form zwischenzuspeichern und aus entsprechenden Pufferspeichern zu laden, statt den Server zu einer Seitengenerierung aufzufordern,
  • den Kommunikationsaufwand und Datenaustausch zwischen Browsern und Servern auf das notwendige Maß zu reduzieren

und vor allem:

  • die Vermeidung von ressoucenintensiven Programmabläufen und Datenbanktransaktionen auf den Servern des Providers, soweit das unter dem Aspekt einer dynamischen Änderung der Datenbasis vertretbar ist.

Pufferspeicher – zeitbasierte Regeln – Änderung der Datenbasis von Webseiten

Um die eben genannten
Ziele zu erreichen, ist es zum einen notwendig, Pufferspeicher und Caches zu nutzen. Hierfür kommen Browser-Caches, Server-Session-Speicher oder auch selbst-verwaltete “Datei-Caches” am Server in Betracht. Intelligentes Daten- und Datei-Caching kann PHP-seitig teilweise auch im PHP-Code-Ablauf realisiert werden. Aber natürlich muss man primär die “Public-” bzw. “Private-” Caches im Sinne von Proxy- und Browser-Caches für die generierten Webseiten und deren Elemente ausnutzen.

Neben der Notwendigkeit der Nutzung von “Pufferspeichern” ist es aber auch erforderlich, den Bedarf nach einer Aktualisierung von Webseiten und deren Inhalten

  • anhand von Regeln – z.B. für Zeitintervalle – zu steuern
  • und aufgrund der Analyse von definierten Änderungsmerkmalen der Datenbasis am Server festzustellen.

Beide Aspekte sind auszutarieren: Ein zu langes Zeitintervall für die Nutzung von Pufferspeichern steht ggf. in Konflikt mit dem Anspruch, aktuelle Informationen auch dann zu liefern, wenn sich die Datenbasis einer Webseite geändert hat.

Fachliche Voraussetzungen für Caching

Der Aspekt der Änderung der Datenbasis für den Inhalt einer Webseite oder generell von serverbasiertem Output führt uns zu den fachlichen Voraussetzungen für den Einsatz von Caching-Verfahren. Aus meiner Sicht gilt ganz allgemein:

Voraussetzung für den Einsatz von Caching-Verfahren ist, dass sich der Inhalt einer generierten Webseite aus Client- bzw. Kundensicht nicht laufend und zeitnah ändern muss, sondern über einen definierbaren Zeitraum konstant bleiben darf.
 
Auf der Client- oder Kundenseite darf kein Schaden entstehen, wenn eine Änderung erst nach einem definierten Zeitintervall am Client oder Browser bekannt wird.

Wie groß das zu wählende Zeitintervall ist, hängt natürlich vom Einsatzzweck der Website ab. Caching-Zeitintervalle können sich zwischen Sekunden und Wochen bewegen. Bei einem Newsticker wird das Zeitintervall konstanter Information natürlich viel kleiner sein als bei einer Informationsseite, deren Inhalt sich bekanntermaßen nur zu Beginn eines jeden Monats ändert.

Macht man sich jedoch klar, dass die typische Interaktionsdauer eines Kunden mit Web-Informationsseiten vielleicht zwischen 1 und 15 Minuten liegt, so wird klar, dass bereits das Ausnutzen von Caching-Zeiten im Bereich weniger Minuten bis zu einer Viertelstunde einen großen Effekt auf den subjektiven Performance-Eindruck eines Kunden haben können.

Wir erkennen hier übrigens, dass Ajax tatsächlich ein schwieriges Thema im Zshg. mit Caching ist:
Wann setze ich denn Ajax ein? Meist doch dann,

  • wenn eine Benutzereingabe zu einer direkten “Aktualisierung” von Datenbeständen auf dem Server und nachgelagert auch von bestimmten Inhalten der aktuell am Browser geöffneten Webseite führen soll (synchron oder asynchron). Caching ist hier in der Regel kontraproduktiv. Man steuert vielmehr aktionsgerecht selbst, was zwischen Server und Browser transferiert werden soll.
  • wenn eine Abfrage von listenartigen Vorschlägen aufgrund von Benutzereingaben gestartet wird. Das Problem ist hier, dass das Ergebnis solcher Abfragen noch während der Benutzereingabe Modifikationen erfahren soll – z.B. im Sinne von sich laufend modifzierenden Filtern. Hier hängt der Sinn eines möglichen Cachings vor allem davon ab, ob man die Filterung am Server oder am Client vornimmt und davon, wie vorhersagbar und wohldefiniert bestimmte Benutzerabfragen absehbar sind.
  • generell wenn Abfragen asynchron im Hintergund vorgenommen werden sollen, ohne die Webseite zu verlassen. Hier stellt sich die Frage, ob und wie das Ergebnis – oft ein XML-formatierter Datenstrom oder per JSON sequentialisierter
    Datenstrom – wo und wie gecached werden könnte.

Einige Caching-Verfahren

Kommen Browser- und Proxy-Caches zum Einsatz werden statische Webseiten ohne besondere Vorkehrungen nur einmal oder periodisch vom Server geladen, danach kommen ihre Inhalte für definierte Zeitintervalle aus dem Cache des Browsers oder eben eines Proxys. Bei dynamisch generierten Webseiten – wie PHP-Seiten – hängt die Caching-Politik vom Browserhersteller und von Einstellungen der Proxys wie des Webservers selbst ab – falls keine besonderen Vorkehrungen getroffen werden.

Gezieltes, anweisungsgesteuertes Caching im Cache des Browsers macht ggf. den Unterschied zwischen einigen zehn Millisekunden für den Seitenaufbau im Gegensatz zu einer Sekunde aus – pro Seite ! Das ist ziemlich durchschlagend. Unter bestimmten Bedingungen kann es aber auch interessant sein, Webseiten aus einem selbst verwalteten File-Cache aus dem Webserver zu ziehen.

In einer “HTTP 1.1”-Umgebung sind für normale PHP-Applikationen vor allem folgende vier Verfahren zur Ausnutzung von Caching-Mechanismen interessant, wobei alle Methoden explizit die Unterstützung des jeweiligen PHP-Programms (und des Webservers) erfordern. Wir ordnen die Verfahren nach dem Grad der Reduktion der Interaktion mit einem Web/Datenbank-Server und der Vermeidung von unnötigen Prozessen auf dem Server an :

  1. Methode 1 – Explizite Caching-Anweisung an “Public” und “Private” Caches mit einem limitierenden Zeitintervall:
    Der Browser oder ein chache-gestütztes Client-System erhalten den Hinweis, den Server über ein definiertes Zeitintervall hinweg nicht abzufragen, sondern explizit ihren Cache zu benutzen. Erst nach Ablauf des vorgegebenen Zeitintervalls wird der Server erneut kontaktiert: die geforderte Seite wird dann über das angesprochene PHP-Programm vollständig neu generiert (oder ggf. aus einem serverseitigen Filecache geladen) und auf dem aktuellen Stand zum Browser, in dessen Cache und die Caches von Proxy-Systemen etc. übertragen.
  2. Methode 2 – Check auf Änderungen von Webseiten und “Not Modified”-Rückmeldung im HTTP-Header an den Browser:
    Die auf dem Server angesprochenen PHP-Programme überprüfen anhand bestimmter Merkmale als allererstes, ob sich die Datenbasis der dynamisch zu generierenden Website seit der letzten Serveranfrage geändert hat. Ist dies nicht der Fall, erhalten der Browser und andere Cache-Systeme per HTTP-Header einen expliziten Hinweis (Code 304-Meldung), ihren Inhalt weiter zu verwenden, aber bei der nächsten Seitenabfrage den Server erneut bzgl. möglicher Änderungen abzufragen. Nur falls in der Zwischenzeit Änderungen aufgetreten waren, wird der geänderte Webseitencode entweder neu aus Datenbankinhalten erzeugt oder aus einem intermediären Datei-Puffer (s. Punkt 4) des Webserver geladen und zum Client übertragen.
  3. Methode 3 – Kombination der Methoden 1 und 2:
    Methode 3 kombiniert die erste und die zweite Methode: Grundsätzlich wird ein zeitlimitiertes Caching zugelassen. Nach Ablauf des Zeitintervalls überprüft das angesprochen PHP-Programm aber zunächst, ob sich die Datenbasis seit der letzten Seitenauslieferung überhaupt geändert hat. Wenn nicht, wird erneut ein clientseitiges Caching für ein definiertes Zeritintervall erlaubt – wobei die Seite nicht (!) neu generiert oder geladen wird. Im Falle einer Änderung der Datenbasis wird die Seite dagegen neu auf dem Server erzeugt und in die Caches übertragen – das Caching-Zeitintervall auf der Kunden- oder Proxy-Seite beginnt erneut.
  4. Methode 4 – PHP-gesteuertes File-Caching am Server:
    Wohldefinierte Ereignisse auf dem Server werden genutzt, um den HTML/XHTML/XML-Code der benötigten Webseiten oder Informationen auf der Basis von erkannten
    Änderungen der Datenbasis neu zu generieren. Der durch das PHP-Programm generierte HTML/XHTML- oder auch XML-Code wird dann für ein definiertes Zeitintervall oder aber bis zum nächsten wohldefinierten Änderungs-Event als “Datei” auf dem Server in einem selbstverwalteten “Cache”-Verzeichnis abgelegt. Bei der nächsten Browserabfrage wird vom PHP-Programm direkt der Datei-Inhalt aus dem Verzeichnis-Cache geladen und zum Browser übertragen, falls keine sonstige Änderung der Datenbasis erfolgt ist. Ressourcenintensive Prozesse zur Webseitengenerierung werden so nur periodisch oder aber aufgrund definierter Änderungsereignisse fällig.
  5. Methode 5 – Kombination der Methoden 3 und 4:
    Grundsätzlich wird ein zeitlimitiertes Caching zugelassen. Nach Ablauf des Zeitintervalls überprüft das angesprochen PHP-Programm aber zunächst, ob sich die Datenbasis geändert hat. Ist ein aktuelles HTML/XHTML/XML-File zur geänderten Datenbasis vorhanden, wird dieses gezogen. Ist das nicht der Fall, wird die Seite neu generiert, en passant am Server als File abgelegt und zudem zum Client übertragen.

Einschränkungen, Vor- und Nachteile der Verfahren

Die einzelnen Verfahren weisen Vor- und nachteiel auf, die ich nachfolgend kurz diskutiere:

Einschränkungen für den Einsatz der ersten Methode – Caching während eines definierten Zeitintervalls

Beim Einsatz der ersten Methode, versucht man, eine Interaktion mit dem Server über definierte Zeitintervalle hinweg zu vermeiden. Diese Methode hat den Vorteil eines schnellen Seitenaufbaus auf der Basis lokaler Informationen und Objekte im Cache des Browsers. Das wirkt sich am Browser vor allem beim Wechsel zu und zwischen (eigentlich PHP-basierten) Webseiten sehr vorteilhaft auf die Performance aus. Durch die Nutzung des Browser-Caches wird nicht zuletzt auch der Server spürbar entlastet.

Der Nachteil der Methode 1 besteht jedoch darin, dass während des vorgegebenen Zeitintervalls eben auch keinerlei Serverinteraktion mehr stattfindet (soweit der Browser/Proxy die Caching-Vorgaben des Servers respektiert). Das cachende System – i.d.R. der Browser – erfährt (bei normaler Bedienung) innerhalb des vorgegebenen Zeitintervalls nichts (!) mehr über eventuelle Änderungen der Datenbasis des Webseiten-Inhalts. Die aus dem Cache gezogene Information ist also möglicherweise schon veraltet. Und selbst wenn der Browser die Seite neu anfordern würde, kämen ggf. veraltete Inhalte aus Proxy-Caches zurück.

Eine kluge, dem Einsatzzweck der Web-Applikation angemessene Vorgabe des Caching-Zeitintervalls an beteiligte Systeme ist also eine wichtige Voraussetzung der Anwendung der Methode 1.

Die zweite Voraussetzung für den Einsatz dieser Methode ist die, dass die Benutzerinteraktion nicht selbst zu einer Datenbestandsänderung führt und dass für den Aufbau der geforderten Webseite nicht unmittelbar eine komplette Neukalkulation und Neu-Generierung der Seite auf dem Server erforderlich ist. (Gezielt gesteuerte Ajax-Prozesse können den Cache natürlich immer umgehen. Ich lasse das aber mal außer Acht).

Man erkennt deshalb,

dass diese erste Methode des Cachings  nicht  für Previews und Voransichten von Webseiten geeignet ist , die mit Hilfe von CM-Systemen oder anderen Pflegemodulen aus Datenbankinhalten generiert werden.

Will man den Effekt von Datenbankänderungen sofort sehen, muss das Caching der zugehörigen Webseiten durch bestimmte PHP-Übergabe-Parameter, die bei den normalen Web-Adressen nicht auftauchen, umgangen bzw. explizit abgestellt werden.

Dies ist jedoch leicht realisierbar, da sich das Browser-Caching immer auf eine vollständige URL-Adresse – also
inkl. aller GET-Parametern – bezieht. Bei der Übergabe von POST-Parametern muss eben ein bestimmter Zusatz-Parameter zur Situationsanalyse eingesetzt werden und die Namen für die Previewer-Programme sollten sich dann auch von denen der normalen Web-Generator-Programme unterscheiden.

Vor-und Nachteile der zweiten Methode – Prüfung der Änderung der Datenbasis vor einer Neugenerierung oder Cache-Nutzung

Die zweite Methode für sich erspart einem nicht eine Kommunikation zum und vom Server. Aber sie stellt ein Schlüsselelement dar, um den Aufwand auf dem Server zu begrenzen:

Sie erspart u.U. die Durchführung ressourcenintensiver PHP-Programmschritte und von RDBMS-Prozessen auf dem Web- und den Datenbank-Servern.

Die Latenz der PHP-Applikation hängt dann hauptsächlich von der Internetanbindung und im wesentlichen einer Datei- oder Datenbankabfrage auf Änderungen ab. Beides kann aber erheblich (!) schneller sein, als eine vollständige Neugenerierung der Seiteninformation durch komplexe Programme und Datenbanktransaktionen – wenn sich der Datenbestand nicht geändert haben sollte.

Der Einsatz der zweiten Methode ist aus meiner Sicht grundsätzlich sinnvoll. Hier muss man sich allerdings überlegen, wie und was man auf Änderungen prüft. In meinen Programmen sind das

  • erstens Datenbankänderungen
  • zweitens Änderungen von Files
  • drittens ggf. Änderungen von speziellen Files für die Caching-Methode 4
  • viertens Änderungen der Inhalte von Session-Speichern

Änderungen an anderen Datenbestände, die hier nicht genannt wurden, sind denkbar.

Datenbankänderungen bekommt man grundsätzlich über die Analyse von Timestamps in Tabellen mit. Hierbei muss man aber die Zusammenhänge der Daten – sprich das Datenmodell beachten ! Das Ergebnis des PHP-Programms wird ja i.d.R. von einer Kombination von Tabellen abhängen. Man muss sich also darüber klar werden, welche Timestamps eigentlich für den gewünschten Output relevant sind. Ggf. lohnt es sich, für die einfache Abfrage relevanter Änderungen im Rahmen von Caching-Verfahren spezielle Einträge in selbst verwalteten Timestamp-Tabellen explizit zu setzen oder z.B. über Datenbank-Trigger setzen zu lassen.

Es zeigt sich also, dass die Möglichkeit zum einfachen Erkennen der Änderungen von Datenbeständen für ein späteres Caching schon frühzeitig beim Design einer Applikation berücksichtigt werden muss.

Analoge Argumente gelten für andere Datenbestände, die in Kombination zur Erzeugung eines aktuellen Seiteninhalts wichtig sein mögen.

Ein weiterer Nachteil dieser Methode besteht übrigens ggf. darin, dass man die Verbindung zur Datenbank in einem PHP-Programm sehr frühzeitig aufbauen muss. Dies kann vor allem in sicherheitsrelevanten Applikationen eine Rolle spielen.

Vorteile der dritten Methode – Kombination aus zeitgesteuertem Caching und Prüfung der Datenbasis

Sind die Voraussetzungen für den Einsatz der Methoden 1 und 2 gegeben, bringt deren Kombination aus meiner Sicht nur Vorteile mit sich. Man cached lokal solange, wie das eben vertretbar ist. Erst danach wendet sich der Client/Browser wieder an den Server. Aber dort wird nur wirklich etwas getan, wenn dies aufgrund eingetretener Änderungen notwendig ist. Ansonsten wird erneut auf den am Client vorhanden Cache-Inhalt zurückgegriffen. Besser kann es kaum kommen – deswegen setze ich diese Kombination auch in vielen Applikationen ein.

Vor-und Nachteile der vierten und fünften Methode – Einsatz eines selbstverwalteten File-Cache für PHP-Output

nGrundsätzlich ist es gut, einen selbstverwalteten File-Cache zu realisieren. Sein Einsatz lohnt sich vor allem unter der Voraussetzung, dass sich die Generierung der Webseite zeitlich und logisch weitgehend von der konkreten Nutzer- und Abfragesituation am Client abkoppeln lässt:

  • Eine durch Ereignisse oder Pflegepersonen ausgelöste Datenbestandsänderung führt für sich alleine und unabhängig von der aktuellen Benutzerinteraktion bereits zu einem wohldefinierten Aufbau der betroffenen Webseiten. Diese können im Prinzip also unmittelbar nach der Datenänderung als Files am Server erzeugt werden.
  • Der Inhalt der Seiten ist relativ unabhängig von dynamischen Parameterkonstellationen, die situationsspezifisch erst durch den Anwender der Web-Applikation definiert werden.

Sind diese Voraussetzungen gegeben, so gilt zusätzlich Folgendes:

Sollen oder müssen die generierten Files im selbst verwalteten Cache-Verzeichnis alle möglichen Abfragesituationen abdecken, so erkennt an schnell ein potentielles Mengenproblem. Bei komplexen Applikationen mit großen Datenbeständen kann dies durchaus ein Thema werden. Man muss sich also genau überlegen, welchen Output von PHP-Applikationen man in Puffer-Files überführen will. Für den einen oder anderen mag zudem der technisch erforderliche Einsatz des sog. Output-Bufferings unter PHP5 zur Erzeugung der Files ein unbekanntes Terrain darstellen.

Ein weiteres mögliches Problem ist, dass man den eigenen File-Cache natürlich auch im Rahmen seiner Applikationen selbst verwalten muss. Wie bei jedem anderen Cache wird man hier Zeitregeln und eine Mengenbegrenzung einführen. Dies führt dann zu FIFO-Mechanismen oder ähnlichem.

Insgesamt lohnt sich der Einsatz von selbstverwalteten Filecaches vor allem dann, wenn

  • die Generierung des HTML/XHTML/XML-Outputs substanzielle Ressourcen beansprucht,
  • die Änderung der Datenbasis von der aktuellen Benutzerinteraktion weitgehend entkoppelt ist,
  • die im File hinterlegte Information eine langfristige Gültigkeit hat.

Solche Voraussetzungen sind oft in CM- oder Pflege-Systemen gegeben, in denen Seiten erzeugt werden, deren Inhalt nicht dynamisch durch viele Parameter gesteuert wird und über relativ lange Zeitintervalle hinweg konstant bleibt.

Ein Beispiel hierfür wäre etwa eine Webseite, die einen CV darstellt. Der HTML-Code der Webseite kann unmittelbar nach Änderung der Datenbankinhalte zum CV erzeugt werden und bleibt dann lange konstant, also statisch. Hier lohnt sich die Ablage in Dateiform unmittelbar nach Durchführung der Datenänderung. Das PHP-Programm, das für eine Anfrage des Kunden zu dieser CV-Webseite verantwortlich ist, prüft auf Änderungen der Datei durch andere Pflegeprogramme ab und schickt den vorab generierten und hinterlegten Datei-Inhalt direkt weiter, wenn keine Änderungen vorliegen.

Ein Gegenbeispiel wäre etwa eine Projekt- oder Produktliste, auf die der Anwender interaktiv viele verschiedene Filter anwenden kann und deren Inhalt sich aufgrund äußerer Umstände in kurzen, unperiodischen Zeitabständen ändert. Gegenbeispeile stellen auch Seiten zu sehr vielen unterschiedlichen Items dar, die sich erratisch ändern.

Genannt sei ein weiterer Einsatzbereich des vierten Verfahrens – nämlich Ajax-Transaktionen: Es gibt bestimmte Ajax-Abfragen (z.B. die Generierung von relativ statischen Listen), deren Ergebnis sich nicht immer auf dem alleraktuellsten Status befinden muss. Dann lohnt es sich durchaus, XML- oder JSON-Daten nicht immer neu erzeugen zu lassen, sondern direkt Cache-Files zu entnehmen.

Insgesamt meine ich, dass man vor dem Einsatz der Methoden 4 und 5 zunächst Methode 3 ausprobieren sollte. Methode 3 ist – wie wir sehen werden – auch einfacher zu implementieren als ein selbst verwalteter File-Cache.
Natürlich kann man die Methode 4 auch mit der Methode 3 kombinieren:

Wenn Änderungen am Datenbestand festgestellt werden, kann man zunächst überprüfen, ob seit der letzten Änderung bereits ein neueres File erzeugt wurde und dessen Inhalt dann als Output an den Browser weiterreichen. Ist kein hinreichend junges File gegeben, so kann man ein solches “en passant” anlegen.

Server-Entlastung

Caching ist auch interessant und wichtig für Leute, die ihre eigenen Webserver betreiben. Es liegt nach dem bisher Gesagten auf der Hand, dass ein vernünftiges Caching auf Basis der oben genannten Methoden insgesamt die Auslastung der Web-Server reduziert. Alle genannten Verfahren haben ja gerade zum Ziel, die Interaktion mit dem Server zu verringern. Im zeitlichen Mittel bleiben bei Einsatz von Caching-Verfahren mehr Serverressourcen übrig.

Nun haben wir das notwendige Rüstzeug beieinander, um uns den einzelnen Methoden zuzuwenden. Dies geschieht in einem weiteren Beitrag dieser Reihe unter der Rubrick “Lamp / Webentwicklung”. Bis dann !

Umlaute, JQuery, Ajax, Formulare und PHP

Wir setzen im Rahmen unserer Web-Projekte neben dem serverseitigen PHP natürlich auch JQuery auf der Javascript-Seite ein – zunehmend auch, um in unsere CM-Applikationen ajax-basierte Features zu integrieren.

Gerne würden wir alles – Webseiten, JQuery/Ajax, PHP-Programme, MySQL etc. – stromlinienförmig auf UTF-8 ausrichten. Aber das geht leider nicht immer. Teils aus historischen Gründen, teils weil man es kundenseitig mit Apache-Webserver-Installationen zu tun bekommt, die per “AddDefaultCharset”-Directive auf ISO-8859-1 als Standard-Character-Set eingestellt sind, teils aber auch, weil man nicht beliebig an den vorgefundenen Datenbanken herumdrehen kann.

In diesem Umfeld stolpere ich dann ab und zu in das eine oder andere Problem mit Umlauten – zuletzt tatsächlich mal wieder im Zusammenhang mit den Ajax-Tools von JQuery. JQuery/Ajax kann in einem serverseitigen ggf. heterogenen Zeichensatz-Umfeld durchaus für Überraschungen sorgen. So sind im Internet haufenweise Fälle beschrieben, in denen eine bislang laufende PHP-Anwendung nach dem Einsatz von JQuery/Ajax z.B. deutsche Umlaute in den üblichen kryptischen Zeichensalat verwandelt hat, der bei einer ungewollten Zeichensatz-Konversion halt entsteht. Links zu Beispielen findet man weiter unten.

Nachfolgend deshalb ein paar Hinweise darauf, warum und wann man beim Einsatz von JQuery/Ajax aufpassen muss, und wie man mit den eventuell resultierenden Problemen umgehen kann. Auf Code-Beispiele habe ich verzichtet, da die Kenntnis der Problemursache einem Entwickler i.d.R. schon zeigt, was er zu tun hat. Ich hoffe, die Ausführungen sind trotzdem für den einen oder anderen, der sich mit JQuery, PHP und Umlauten herumschlagen muss, nützlich.

Ich rede nachfolgend übrigens nur über Systeme, in denen ISO-8859-1 bzw. Latin1-Zeichensätze sowie der UTF-8-Zeichensatz eine Rolle spielen.

Problemseparation: Klassische Umlautprobleme aufgrund von Webserver, PHP- und MySQL-Einstellungen

Es ist wichtig, JQuery/Ajax-induzierte Umlautprobleme von anderen Umlaut-Problemen bei Einsatz von PHP-Anwendungen abzutrennen.

Umlaute sind im PHP-, MySQL-Umfeld auch ohne JQuery von Haus aus ein Thema, das eine gewisse Komplexität aufweisen kann:

Dies liegt daran, dass neben Einstellungen des Webservers und neben der php.ini-Konfiguration natürlich auch die Zeichensatz-Einstellungen des Datenbankservers eine Rolle spielen.

Zeichensätze spielen grundsätzlich eine essentielle Rolle im Datenaustausch zwischen Client- und Serverkomponenten einer Applikation – also bei der Client/Server-Kommunikation. Hier gelten zwischen Web-Client und Web-Server Regeln und Konventionen, die man u.U. mit geeigneten Mitteln beeinflussen kann. Server und Client können sich im Rahmen bestimmter Protokolle in vielen Fällen auch darüber informieren, welchem Zeichensatz die eben ausgetauschten (Web-Daten) unterworfen sein sollen und wie die Daten dementsprechend vom Empfänger zu interpretieren und darzustellen sind.

In manchen Fällen gelten aber auch harte Regeln, die man dann schlicht kennen muss. Wie wir sehen werden, liefert JQuery/Ajax u.U. für letzteres ein wichtiges Beispiel.

(Hinweis zur Sicherheit: Ich spreche hier bei der Betrachtung der Web-Server-Client-Kommunikation nicht von URL-Encoding oder ähnlichem, sondern davon, welchem Zeichensatz die empfangenen Daten von der empfangenden Applikation letztlich zuzuordnen sind. Das hat mit URL-Encoding nichts zu tun.)

Klarmachen muss man sich ferner, dass auch viele Datenbanksysteme definierte oder einstellbare Zeichensätze in der Kommunikation und beim Datenaustausch mit ihren jeweiligen Clients einsetzen. Hierfür liefert MySQL ein schönes Beispiel (s.u.).

Andererseits gelten definierte Zeichensätze auch für die interne Repräsentation von Daten in einer Applikation und /oder einer
Datenbank. Der Zeichensatz für die interne Datenrepräsentation oder die Ablage in einer Datenbank kann dabei vom Zeichensatz, den die Datenbank in der Kommunikation mit ihren Clients anwendet, durchaus abweichen! Für die korrekte Zeichenumsetzung sorgt die Bank selbst, man muss als Entwickler aber dennoch wissen, was für ein Zeichensatz für die Kommunikation gilt oder gelten soll und wie man das im Bedarfsfall beeinflussen kann.

Der Unterschied zwischen Zeichensätzen für die Datenbanktabellen und den Zeichensätzen für die Kommunikation mit den Datenbank-Clients führt im Zusammenspiel von MySQL und PHP oftmals zu erheblichen Verwirrungen. Man schaut auf die Kollation der Datenbank-Tabellen und vergisst leider oftmals, dass der Zeichensatz für den Transfer der Daten zwischen Datenbank und PHP-Applikation im Umfeld von Umlautproblemen viel entscheidender ist!

Als Konsequenz mussten auch wir immer wieder lernen, dass es sinnvoll und wichtig ist, die Datenbehandlung in den Basisklassen unseres hauseigenen Frameworks auf der PHP-Seite mit zeichensatzbezogenen Kodier- und Dekodier-Funktionen (unter Einsatz von z.B. “utf8_decode”, “utf8_encode”, “iconv” ) sowie MySQL-Anweisungen der Art

auszustatten – und zwar so, dass die Durchführung einer De- oder Enkodierung und/oder der jeweils zu beachtende Zeichensatz sich über Konfigurations- oder Setup-Parameter der Applikation steuern lassen. Damit kann man sich dann flexibel an die vorgefundene Server-Umgebung anpassen!

Dass man sich ohne Vorkehrungen und systematische Analyse der verschiedenen Server-Einstellungen sehr schnell in einem Gestrüpp wiederfinden kann, zeigen z.B. folgende Artikel:

http://www.php-resource.de/forum/php-developer-forum/100181-umlauteproblem-bei-formulardaten-ms-sql.html
http://www.sebastianviereck.de/de/mysql-php-umlaute-sonderzeichen-utf8-iso/
http://www.php.de/datenbanken/45101-problem-mit-utf8-latin1.html
http://www.winfuture-forum.de/index.php?showtopic=193063

Wie gesagt: Bei der MySQL-Datenbank muss man begrifflich sorgsam unterscheiden zwischen den “Collations”-Einstellungen für das Hinterlegen der Daten in den Datenbanktabellen und dem Zeichensatz, den die Bank in der Kommunikation mit ihren Clients (hier: PHP-Modulen des Webservers) einsetzt. Das sind verschiedene Dinge – vor allem eine fehlerhafte oder unerwartete Einstellung des Zeichensatzes für die Kommunikation mit DB-Clients ist nach meiner Erfahrung Ursache für viele Umlaut- und Zeichensatz-Probleme im PHP/MySQL-Umfeld.

Man führe sich im Falle des Falles nochmals den Artikel http://dev.mysql.com/doc/refman/5.1/de/charset-connection.html und die Möglichkeiten des Einsatz des “SET NAMES”-Kommandos bei der Initialisierung der Datenbankverbindung zu Gemüte! Ein kurzer Test mit “SET NAMES” hat mir schon oft geholfen – genau deswegen habe ich ja irgendwann eine generelle Möglichkeit zur Parametrierung in unseren PHP-Applikationssetup vorgesehen.

Bevor man JQuery-induzierte Umlaut-Probleme angeht, sollte man also wissen,

  • ob und mit welchen Zeichensätzen der Webserver und die Datenbank arbeiten und
  • auf Basis welchen Zeichensatzes jede dieser Serverkomponenten Informationen zu ihren jeweiligen Clients überträgt.

Insgesamt sollte man sich beim Analysieren von JQuery/Ajax-induzierten Umlaut-Problemen sicher sein, dass die eigene Applikation mit den unterschiedlichen Webserver- und Datenbankeinstellungen konsistent umgeht und dass nicht schon ohne JQuery ein Umlaut-Problem vorliegt.

Warum muss ich zusätzlich an JQuery denken?

Bei der Parametrierung, der Konfiguration und der Konzeption eigener PHP-Applikationssysteme tut man in jedem Fall gut daran, von vornherein an den Einsatz von JQuery/Ajax zu denken. Warum?

Weil JQuery und seine Ajax-Tools bestimmte Regeln für die Kommunikation mit Webservern befolgen, auf die man sich zumindest serverseitig einstellen muss ! Zu finden sind solche Regeln auf der Seite

http://api.jquery.com/jQuery.ajax/

Es lohnt sich, sich diese Seite in Gänze (!) zu Gemüte zu führen. Liest man dort vorschnell nur den nachfolgend zitierten Abschnitt zum “ContentType” beim Ajax-Setup, so kann man schnell Schiffbruch erleiden.

contentTypeString
Default: ‘application/x-www-form-urlencoded; charset=UTF-8’
 
When sending data to the server, use this content type. Default is “application/x-www-form-urlencoded; charset=UTF-8”, which is fine for most cases. If you explicitly pass in a content-type to $.ajax(), then it’ll always be sent to the server (even if no data is sent). If no charset is specified, data will be transmitted to the server using the server’s default charset; you must decode this appropriately on the server side.

Der letzte Satz deutet schon mal Schwierigkeiten an – man muss sich ggf. um die Servereinstellungen kümmern! Andererseits könnte man aber auch glauben, dass man den Zeichensatz für die Kommunikation mit dem Server clientseitig durch ein geeignetes Setup für die JQuery/Ajax-Transaktion vorgeben kann. Dies ist aber nicht durchgehend der Fall, denn es gilt auch folgendes weitere Zitat von der genannten api.jquery.com-Webseite:

Sending Data to the Server
 
By default, Ajax requests are sent using the GET HTTP method. If the POST method is required, the method can be specified by setting a value for the type option. This option affects how the contents of the data option are sent to the server. POST data will always be transmitted to the server using UTF-8 charset, per the W3C XMLHTTPRequest standard.

Das spielt natürlich eine essentielle Rolle, wenn man – wie wir – bei der Erstellung und dem Einsatz von CM-Systemen größere Nutzdaten aus umfangreichen Formularen per JQuery/Ajax zum Server übertragen muss. Eine solche Übertragung wird man typischerweise statt mit GET mit POST durchführen (Datenmenge, Sicherheit).

Anders als beim Datenaustausch über GET ist JQuery/Ajax im Falle eines Datenversands mit POST offenbar aus guten Gründen eigen und legt sich auf UTF-8 fest!

Blöd nur, wenn die serverseitige Zielumgebung konsistent auf ISO-8859-1 und nicht auf UTF-8 ausgelegt ist.

Sprich:
Du hast es mühsam geschafft, in einer konsistenten “ISO-8859-1/latin1”- Apache/MySQL-Kundenumgebung eine interaktive PHP-Applikation einwandfrei zum Laufen zu bringen. Dann schaltest du irgendwann nette JQuery/Ajax-Features auf POST-Basis dazu und bekommst möglicherweise und “überraschend” Zeichensalat in allen Texten.

Von solchen Erlebnissen zeugen viele Anfragen in Foren mit z.T. halbgaren Lösungsansätzen – ein paar Beispiele:

http://stackoverflow.com/
questions/2539090/jquery-ajax-umlauts-special-characters-are-a-mess

http://forum.de.selfhtml.org/archiv/2009/7/t188450/
http://www.ajax-community.de/javascript/6113-jquery-formular-umlaute.html
http://www.ajax-community.de/javascript/8872-problem-umlauten.html
http://www.buildblog.de/2009/05/01/ajax-und-umlaute-das-ewig-wahrende-utf-8-problem-und-seine-losung/
http://www.php.de/javascript-ajax-und-mehr/71100-formular-umlaute.html
http://www.perl-community.de/bat/poard/thread/17021
http://www.fundstücke-im-netz.de/2012/03/28/umlaute-mit-ajax-verwenden/
http://ecotronics.ch.honorius.sui-inter.net/wordpress/2010/jquery-ajax-und-encoding/
http://de.softuses.com/68699
http://www.mail-archive.com/discuss@jquery.com/msg19364.html

Das ungewollte Erzeugen von Zeichensalat kann dabei selbst in Testumgebungen ein Problem werden, denn die nach dem Ajax-Einsatz durch kryptische Zeichen ersetzten Umlaute der deutschen, französischen oder skandinavischen Texte muss man auch da ggf. erstmal wieder aus Backups restaurieren. Wenn es sich um größere Texte handelt, wird ja keiner anfangen, das wieder von Hand zu bereinigen!

Was hilft beim JQuery/Ajax-Einsatz in eventuellen ISO-8859-1/Latin1-Umgebungen ?

Nachdem man einmal verinnerlicht hat, dass JQuery/Ajax bei POST als Datentransfermechanismus UTF-8 in der Kommunikation mit dem Webserver erzwingt, ist klar: Man muss serverseitig etwas tun, wenn in der dortigen Verarbeitungskette nicht UTF-8 vorgesehen ist.

Das Zauberwort – besser die einzusetzende Funktion – heißt dann:

utf8_decode().

Siehe hierzu

http://de.php.net/manual/de/function.utf8-decode.php

Spätestens an den Stellen, an denen die PHP-Module sich des übertragenen und im $_POST-Array gelandeten Inhalts annehmen (Prüfungen, Vorverarbeitung, etc.), muss man die per JQuery/Ajax übertragenen Strings in einer ISO-8859-Umgebung prophylaktisch mit “utf8_decode()” dekodieren!

Ob man eine pauschale initiale Decodierung für alle Strings im $_POST-Array vor allen weiteren Schritten vornehmen will, hängt ein wenig von den eingesetzten Analyse- und Prüfroutinen für die Daten ab ab. Das gilt im besonderen für sicherheitsbezogene Prüfungen – hier muss jeder selber wissen, was sinnvoll und richtig ist. Irgendwann im Zuge der Verarbeitung ist die Dekodierung unter den angenommenen Voraussetzungen aber unvermeidbar.

Aber:
Das gilt nur im Fall eines vorhergehenden Ajax/JQuery-Transfers auf ein auf ISO-8859-1 eingestelltes Zielsystem und genau genommen nur im Falle von Strings.

Wie weiss oder erfährt mein PHP-Programm darüber etwas?

Bzgl. der String-Eigenschaft:

Man kann z.B. is_string(), is_numeric(), etc. zum Testen einsetzen. Aus meiner Sicht sollte man aber sowieso durch andere Mechanismen bei der Übertragung von Formulardaten dafür gesorgt haben, dass das empfangende PHP-Programm sehr genau weiß, was es an welcher Stelle des
POST-Arrays als Datentyp erwartet. Schon aus Sicherheits- und Validierungsgründen! Dieses Thema hat man also in der Regel von Haus aus schon im Griff.

Bzgl. der Kennzeichnung eines Ajax-Transfers für die PHP-Seite:

Ich benutze hierfür typischerweise ein wohldefiniertes zusätzliches “Hidden Input-Feld” im selben Web-Formular, dessen Nutz-Daten übertragenen werden sollen. Da ich ja eh’ JQuery im Falle eines Ajax-Transfers bemühe, kann ich vor der Durchführung der POST-basierten Ajax-Transaktion dieses Input-Feld von JQuery auch noch schnell mit einem numerischen Wert füllen lassen, der dem PHP-Programm dann anzeigt, dass ein Ajax-Transfer vorliegt. Einfache numerische Werte sind von UTF-8 / ISO-8859-Transformationen ja nicht gefährdet (ASCII-Kompatibilität von UTF-8)!

Das serverseitige PHP-Programm kann aufgrund der übertragenen Zusatz-Information in einem speziellen, mit numerischem Inhalt belegten $_POST-Array-Feld herausbekommen, ob die POST-Daten per JQuery/Ajax-Transaktion geschickt wurden. Hat das PHP-Programm auf Basis seines parametrierten Grund-Setups nun noch Informationen darüber, ob es in einer UTF8-Server-Umgebung oder in einer ISO-8859/Latin1-Umgebung läuft, so ist klar, wann utf8_decode() einzusetzen ist und wann nicht.

Client- bzw. serverseitig läßt sich so ein informatives “Zusatz-Feature” natürlich sehr schön generalisiert und über entsprechende Methoden eigener Bibliotheksobjekte für Datentransaktionen abhandeln – sowohl unter Javascript als auch auf der PHP-Seite. Die benötigte Code-Menge ist wirklich minimal!

Hat man das erstmal codeseitig erledigt, kann man anschließend normale POST-Transfers mit Web-Seitenwechsel locker und entspannt mit Ajax-Transaktionen ohne Web-Seitenwechsel jederzeit kombinieren und zwischen beiden hin- und herwechseln.

Den geringen Aufwand, den man für die notwendigen Vorsorgemaßnamen client- und serverseitig investieren muss, spielt man danach locker wieder durch den bequemen, einfachen Einsatz der Ajax-Tools von JQuery ein.

Ohne jeden Zeichensalat! Zumindest in “ISO-8859-1”-Umgebungen. Mit anderen Zeichensätzen als ISO-8859-Varianten und UTF-8 bin ich nicht so vertraut, so dass ich das, was ich oben beschrieben habe, nicht auf andere Zeichensätze generalisieren möchte.

Viel Spaß nun mit Umlauten, JQuery/Ajax und PHP in UTF-8 und ISO-8859-Umgebungen!

Dynamische Höhensteuerung mehrer DIVs

Im HTML/CSS2-Bereich gibt es immer mal wieder Dinge, von denen man glaubt, man bekäme sie aus grundsätzlichen Erwägungen nie vernünftig hin. Probleme, über die ich immer wieder stolpere, sind Kombinationen aus mehreren Anforderungen:

  • vertikal expandierbare Webseiten mit gefloateten und/oder mehreren übereinander liegenden DIV-Layern,
  • gegenseitige Höhensteuerung oder Höhen-Synchronisation unabhängiger, vertikal frei expandierbarer DIV-Layer mit vorab unbekanntem (!) Inhalt und damit initial nicht definiertem Wert der Höhe,
  • em-skalierbare Webseiten mit Layern, in denen ebenfalls em-skalierbare Hintergrundsbilder untergebracht werden müssen.

Warum sind solche Anforderungskombinationen problemträchtig? Dies liegt daran, dass die Anforderungen zum Teil im Widerstreit miteinander liegen.

Konkurrierende Anforderungen unter CSS2 bei unbestimmten Höhen, die erst zur Laufzeit festgelegt werden

Schwierigkeiten entstehen vor allem dann, wenn es in der DIV-Schichtung Layer gibt, deren Höhe erst zur Laufzeit durch eingefügte Elemente – z.B. Text – festgelegt wird und nicht von Anfang an bestimmt ist (dynamische Höhenfestlegung). Solche Situationen tauchen typischerweise immer dann auf, wenn man DIVs mit Datenbank-Inhalten füllt. Dies ist u.a. typisch für Content-Management-Systeme und zugrunde liegende Template-Strukturen. So auch bei unserem hauseigenen CMS, das auf einem Framework beruht, welches sich ITX-Templates zunutze macht.

Bei der Generierung dynamischer Webseiten hängt die Menge des Inhalts und damit auch die vertikale Ausdehnung mancher DIVs sowie ggf. auch der gesamten Web-Seite davon ab, was der Kunde eingepflegt hat. Das kann man natürlich nicht im vorhinein wissen. Die sich zur Laufzeit ergebende DIV-Höhe mit Javascript zu bestimmen, finde ich aber einfach unelegant. Lässt man Javascript aber außen vor, wird es schwierig,

  • die Höhe von Seitenleisten mit unabhängigen DIVs zur Laufzeit zu synchronisieren,
  • die Höhe verschiedener DIVs in einer Layerschichtung in z-Richtung oder in gefloateten Kontexten zur Laufzeit zu synchronisieren
  • oder aber den sichtbaren Bereich “em”-skalierbarer Hintergrundsbilder zusammen mit einem DIV, in das erst zum Zeitpunkt der Seitengenerierung Texte eingelesen werden, auf einen initial unbekannten Höhenwert wachsen zu lassen.

So kann man etwa absolut positionierte DIVs per CSS über die “z-index”-Anweisung gut übereinander schichten. Damit entkoppelt man sie dimensionsmäßig aber auch voneinander!

Oftmals soll gerade der oberste Layer einen Text tragen, dessen Länge nicht von vornherein bekannt ist. Dennoch soll die Textlänge letztlich die sich ergebende vertikale Ausdehnung des umgebenden DIV-Layers, die Höhenausdehnung der gesamten Seite und verschiedener anderer dort enthaltener Layer – z.B. mit Hintergrundsbildern – bestimmen. Das geht mit absolut positionierten DIVs allein überhaupt nicht!

Hinzu kommt, dass gefloatete DIVs, die auf vielen Web-Seiten eingesetzt werden, ebenfalls eine eigene Layerstruktur definieren, so dass besondere Vorkehrungen getroffen werden müssen, wenn umgebende DIVs auf eine potentielle vertikale Expansion der gefloateten DIVs reagieren sollen.

Noch problematischer wird es, wenn man zusätzlich alle Elemente einer Website in “em”-Einheiten definiert hat. Ein Ziel eines solchen Vorgehens kann es sein, die gesamte Seite auf Knopfdruck mit vordefinierten Faktoren zu skalieren. Dies ist jedoch mit Hintergrundsbildern, die per CSS-Anweisung “background-image” z.B. in DIV-Layer eingesetzt wurden, nicht möglich. Solche Hintergrundsbilder skalieren nämlich nicht mit. Womit die Behandlung “em
“-skalierbarer Hintergrundsbilder wieder zu einem Lösungsansatz mit per z-Index übereinander geschichteten DIVs führt.

Betrachten wir einmal das Beispiel einer über den z-Index geschichteten Layer-Struktur, wie sie in der nachfolgenden Abbildung dargestellt ist:

div_layer

Die Layer mit unterschiedlichem z-Index wird man in der Regel mit CSS-Anweisungen der Form

position:absolute; z-index:n;”

mit wachsendem “n” übereinanderlegen. Es ist dann natürlich einfach, das Hintergrundsbild ebenso wie die Größe des halbtransparenten Layers “em”-skalierbar zu machen. Die Transparenz erreicht man entweder über die CSS-Anweisung “opacity:0.x;” – was inzwischen die meisten Browser verstehen – oder aber über browserspezifische Anweisungen (Browserweichen). Kennt man die Größe – Breite und Höhe – des Bereiches, den der Text einnehmen wird, von vornherein, hat man alles unter Kontrolle.

Typischer Code für eine einfache Website mit solchen Elementen sieht dann wie folgt aus:

<div id=”cont” style=”position:relative; width:50.0em; height:60.0em; background-color:#CCC; “>
    <div id=”bg_img” style=”position:absolute; top:0; left:0; width:53.0em; height:60.0em; z-index:1;”><img src=”….” style=”width:100%; …..”></div>
    <div id=”bg_transp” style=”position:absolute; top:0; left:0; width:53.0em; height:60.0em; background-color: #FFF; opacity:0.7; z-index:2;”></div>
    <div id=”bg_transp” style=”position:absolute; top:0; left:0; width:53.0em; height:60.0em; z-index:3;”><p>LANGER TEXT</p></div>
</div>

Man kann natürlich die Bilddimensionen auch explizit in “em”-Einheiten vorgeben. Hier passt dann natürlich alles, weil wir die Höhen von vornherein explizit so festlegen, dass der bekannte Text hinein passt. Alternativ legt man die Höhen der Seitenelemente von vornherein fest und aktiviert bei zu langem Text einen vertikalen Scrollbar.

Wir allerdings wollen etwas anderes: Verschiedene Seitenelemente sollen sich höhenmäßig und synchron an einen Textinhalt beliebiger Länge anpassen.

Hinweis:
Hier wie in den nachfolgenden Beispielcodes werden wir CSS-Vorgaben immer direkt in den TAGs angeben. Im realen Leben würde man natürlich CSS-Klassen definieren.

Anforderungen an automatische Höhenbestimmungen zur Laufzeit bei gleichzeitiger em-Skalierung von Hintergrundsbildern

Nun stellen genauer folgende Anforderungen:

  1. Wir möchten eine dynamische Skalierung aller (!) Seitenelemente mit der Schriftgröße, die für das BODY-Tag definiert wird (“em”-Skalierung). Dies soll auch für eventuelle Hintergrundsbilder gelten.
  2. Die Höhe eines zentralen Textbereiches der Webseite soll durch die beliebige Länge eines unbekannten (!) Textes aus einer Datenbank definiert werden.
  3. Wir möchten eine dynamische Expansion der gesamten Webseite in vertikaler Richtung mit der Höhe des Textbereichs.
  4. Die Layer für das Hintergrundsbild, einen ggf. zwischengeschalteten semitransparenten Layer sowie für den Text unbekannter Länge sollen in z-Richtung übereinander geschichtet sein.
  5. Wir möchten gleichzeitig mit der dynamischen Expansion des Text-DIVs auch eine entsprechende Höhen-Expansion des halbtransparenten DIV-Layers.
  6. Wir möchten gleichzeitig eine dynamische Expansion des DIV-Layers, der das Hintergrundsbild enthält. Dabei
    soll das Bild selbst eine wesentlich größere Basishöhe als das eingrenzende DIV haben. Das DIV, welches das IMG-Tag beinhaltet, soll automatisch die gleiche Höhe annehmen, wie das DIV mit dem Textbereich. Ferner soll dieses DIV während seines vertikalen Wachstums einen entsprechenden Bereich des von Haus aus größeren “Hintergrundsbildes” in vertikaler Richtung freigeben und den Rest abschneiden.
  7. Wir möchten, dass die vertikale Expansion der gesamten Seite auch dann gegeben ist, wenn sich die dargestellte Layer-Schichtung in z-Richtung innerhalb eines gefloateten DIV-Bereichs befindet.
  8. Wir möchten ferner, dass die Hintergrundsbereiche von optisch unabhängigen Seitenbereichen, die über gefloatete DIVs erzeugt wurden, synchron die Höhe annehmen, die durch einen zentralen Textbereich innerhalb eines der gefloateten DIVs zur Laufzeit vorgegeben wird.

Anforderung 1 ist nicht mit CSS-definierten Hintergrundsbildern (background-image) erfüllbar. Also müssen die Bilder in eigenen DIVs mit niedrigem z-Index untergebracht werden.
Anforderung 2 lässt sich einfach dadurch erfüllen, dass man die Höhe des entsprechenden relativ positionierten DIVs offen lässt.
Anforderung 3 läuft darauf hinaus, dass sich mehrere ineinander geschachtelte DIVs an der Höhe des Text-DIVs orientieren, dessen Ausdehnung erst zur Laufzeit bestimmt wird. Dies setzt einerseits zwingend den Einsatz relativ positionierter DIVs für den Text und dessen Container voraus (position:relative;).
Anforderung 4 spricht insgesamt für absolut positionierte und per z-Index geschichtete DIVs.
Die Anforderungen 5) und 6) bedeuten, dass das DIV mit dem Text die Höhe der darunter liegenden DIVs definiert. Das geht aber mit unabhängigen, absolut positionierten DIVs nicht ohne weiteres !
Anforderung 7 erschwert die Aufgabe zusätzlich, da sich die Höhe gefloateter DIVs nicht automatisch auf die umgebender DIVs auswirkt.
Anforderung 8 ist mehr als problematisch, da gefloatete Bereiche nicht gegenseitig ihre Höhen festlegen können.

Das schwierigste Problem liegt zunächst eindeutig in Anforderung 5. Wie kann ein Layer mit Inhalt unbekannter Länge die Höhe darunter liegender – notwendigerweise – absolut positionierter DIVs vorgeben? Auf die Lösung bin ich Zufall gestoßen, als ich mich mal aus einem anderen Grund mit der Vererbung in CSS2 (inherit-Anweisung) befassen musste.

Absolut positionierte DIVs können die dynamisch berechnete Höhe eines umgebenden relativ positionierten DIVs annehmen

Der erste Schlüssel für einen Lösungsansatz ist folgender:

Absolut positionierte DIVs können die Höhe eines umgebenden relativ-positionierten DIVs über die “inherit”-Anweisung oder Anweisungen der Art “height:100%;” übernehmen, sobald die Höhe des relativ positionierten DIVs zur Laufzeit von der Engine des Browsers bestimmbar ist oder aber über eine Analyse des Platzbedarfs seines Inhalts berechnet wurde.

Zum Beispiel nimmt in folgendem Beispiel dass “inner”-DIV die vorgegebene Höhe des “cont”-Divs an. :

<div id=”cont” style=”position:relative; height:40.0em; width:50.0em;”>
    <div id=”inner” style=”position:absolute; top:0px; left:10.0em; height:100%; width:20.0em; background-color:#F00; “></div>
</div>

Natürlich kann man die Höhe des inneren DIVS über eine geeignete %-Angabe auch anders mit der Höhe des umgebenden DIVs mitskalieren lassen.

Im Fall dynamisch generierten Inhalts ist die gewünschte Höhe der DIVs aber leider nicht vorab bekannt. Die Höhe des Inhalts eines absoluten DIVs kann in W3C-konformen Browsern zudem nicht die Höhe eines umgebenden relativ oder auch absolut positionierten Container-DIVs festlegen. Wir können den gewünschten
Inhalt also eigentlich nicht wie im obigen grafischen Beispiel in absolut positionierte DIVs einfüllen.

Höhenfestlegung verschachtelter DIVs zur Laufzeit – der umgebende Container als Vermittler der Höhe

Was aber, wenn man ein inneres, relativ positioniertes DIV verwendet, um die Höhe des umgebenden relativ positionierten DIVs festzulegen? Würde sich diese Höhenfestlegung dann indirekt auch auf das absolut positionierte, innere DIV auswirken?

Tatsächlich funktioniert Folgendes:

<div id=”cont” style=”position:relative; width:50.0em; background-color:#CCC; “>
    <div id=”in_rel” style=”position:relative; width:80%; height:200px; background-color: #999; margin-left:auto; margin-right:auto; “></div>
    <div id=”in_abs” style=”position:absolute; top:0px; left:20.0em; width:10.0em; height:80%; background-color: #F00;”></div>
</div>

Wir können also die Höhe des inneren absolut positionierten DIVs durch ein auf gleicher Verschachtelungs-Ebene befindliches relativ positioniertes DIV steuern. Der umgebende DIV-Container wirkt dabei quasi als Vermittler der Höhe!

[Einschränkung: Auf der MS Seite geht das erst ab dem MS IE7, nicht aber mit dem MS IE6!]

Hier das Ergebnis für moderne Browser (wobei arme IE6-Anwender natürlich nichts Vernünftiges zu Gesicht bekommen werden) :

Nun sind wir nur noch einen kleinen Schritt von unserem ersten Ziel entfernt. Wir unterlassen eine Festlegung der Höhe des inneren relativ positionierten DIVs “<div id=”in_rel” >” per CSS und steuern die Höhe dieses DIVs vielmehr dynamisch durch seinen Inhalt.

Also z.B. folgendermaßen:

<div id=”cont” style=”position:relative; width:50.0em; background-color:#CCC; “>
    <div id=”in_rel” style=”position:relative; width:80%; background-color: #999; margin-left:auto; margin-right:auto; “>
        <p>Lorem ipsum dolor sit amet, … LANGER TEXT … ut laoreet dolore magna aliquam erat volutpat.</p>
    </div>
    <div id=”in_abs” style=”position:absolute; top:0px; left:20.0em; width:10.0em; height:80%; background-color: #FEFE99;”></div>
</div>

Auch dies geht offenbar, wie man testen kann:

Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril elenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, ed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.

Der vertikale Platzbedarf des Textes wird vom Browser erst zur Laufzeit bestimmt. Dies gilt selbst dann, wenn wir den Text in unserem Beispiel vorgeben und nicht aus einer Bank auslesen haben lassen. Entscheidend ist, dass der Browser die benötigte Höhe des umgebenden DIVs erst zur Laufzeit ermitten muss, weil sie nicht per “height”-Anweisungen vorgegeben ist ! Daher zeigen auch die nachfolgenden Beispiele durchaus auch, wie man im Fall von dynamisch ermitteltem Content vorgehen kann.

Der Inhalt des DIVs mit der id=”in_rel” bestimmt also zur Laufzeit dessen Höhe. Die Höhe des DIVs mit der id=”in_rel” wiederum wird vom umgebenden relativ positionierten Container-Div (id=”cont”) übernommen. Nach dessen vom Browser ermittelter Höhe richtet sich dann das enthaltene, innere absolut positionierte DIV mit der id=”in_abs”.

Schichtung relativer und absolut positionierter DIVs in aktuellen Browsern

Nun gefällt uns die Schichtung noch nicht – wir wollen ja eigentlich das DIV mit dem Text im Vordergrund haben. Ferner müssen wir die Breite des absolut positionierten DIVs so anpassen, dass es als Hintergrund dienen kann. Also :

<div id=”cont” style=”position:relative; width:50.0em; background-color:#CCC; “>
    <div id=”in_rel” style=”position:relative; width:80%; margin-left:auto; margin-right:auto; padding:2.0em 0 2.0em 0; z-index:10;”>
        <p> Lorem ipsum dolor sit amet, … LANGER TEXT … ut laoreet dolore magna aliquam erat volutpat. </p>
    </div>
    <div id=”in_abs” style=”position:absolute; top:0; left:0; width:100%; height:100%; background-color: #FEFE99; z-index:1;”></div>
</div>

Hinweis: Man beachte die z-Index-Werte! Bei älteren Browsern – speziell beim MS IE – musste man übrigens höllisch aufpassen, wenn man die gleichzeitige Schichtung relativ und absolut positionierter DIVs richtig hinbekommen wollte. Im besonderen hing beim MS IE ( < Version 8 ) das Ergebnis davon ab, in welcher Reihenfolge die DIVs im HTML-Code definiert wurden. Es empfahl sich, innerhalb des Codeblocks für einen Container zuerst die relativ positionierten DIVs und dann die absolut positionierten anzugeben. Dies ist heute in der Regel nicht mehr nötig.

Dass das Übereinanderlegen von relativen und absoluten DIVs in modernen Browsern bei gleichzeitiger dynamischer Höhensteuerung des absolut positionierten DIVs durch den umgebenden Container funktioniert, sieht der Leser hoffentlich am nachfolgenden Ergebnis:

Testbeispiel – relatives DIV über absolutem DIV und Höhenvermittlung durch den Container

Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril elenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, ed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.

Hinweis: Alle Beispiele/Ergebnisse setzen die in diesem Beitrag diskutierten HTML-Code direkt auf der vorliegenden Blog-Seite um. Man kann sich den Code also auch aus dem Quelltext kopieren.

Testbeispiel zur inhaltsabhängigen Höhenfestlegung verschachtelter DIVs mit Hintergrundsbild

Nun unterlegen wir den Text noch mit einem Hintergrunds-Bild:

<div id=”cont” style=”position:relative; width:50.0em; background-color:#CCC; “>
    <div id=”in_rel” style=”position:relative; width:80%; margin-left:auto; margin-right:auto; z-index:10; padding:2.0em 0 2.0em 0; “>
        <p> Lorem ipsum ……</p>
    </div>
    <div id=”in_abs” style=”position:absolute; top:0; left:0; width:100%; height:100%; z-index:1;”>
        <img src=”….” style=”width:100%;”>
    </div>
</div>

Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril elenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, ed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.

yellow_bryce

Bei einer Höhenveränderung des Textes (hier durch künstliche Umbrüche erzwungen) passt sich die Umgebung automatisch an und der vertikal längere Text gleitet über das Hintergrundsbild weg, das dabei in entsprechender Länge enthüllt wird ( vergl. mit Anforderung 6 weiter oben ) :

Lorem ipsum dolor sit amet, consectetur adipisici elit,
sed eiusmod tempor incidunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquid ex ea commodi consequat.
Quis aute iure reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur.
Excepteur sint obcaecat cupiditat non proident,
sunt in culpa qui officia
deserunt mollit anim id est laborum.
Duis autem vel eum iriure dolor in hendrerit in vulputate
velit esse molestie consequat, vel illum dolore eu
feugiat nulla facilisis at vero eros et accumsan
et iusto odio dignissim qui blandit praesent luptatum zzril
delenit augue duis dolore te feugait nulla facilisi.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit,

ed diam nonummy nibh euismod tincidunt ut laoreet
dolore magna aliquam erat volutpat.

yellow_bryce

In vielen Fällen muss man das Hintergrundsbild abdämpfen, um die Lesbarkeit zu verbessern. Einen für alle gängigen Browser funktionierenden Semi-Transparenz-Layer hinzuzufügen, überlassen wir dem Erfindungsreichtum des Lesers. Das nachfolgende Code-Beispiel funktioniert zumindest im aktuellen Firefox und demonstriert das Prinzip:

<div id=”cont” style=”position:relative; width:50.0em; background-color:#CCC; “>
    <div id=”in_rel” style=”position:relative; width:80%; margin-left:auto; margin-right:auto; z-index:10; padding:20px 0 20px 0; “>
        <p> Lorem ipsum ……</p>
    </div>
    <div id=”in_abs” style=”position:absolute; top:0; left:0; width:100%; height:100%; z-index:1; background-color:#FFF; opacity:0.70; “></div>
    <div id=”in_abs” style=”position:absolute; top:0; left:0; width:100%; height:100%; z-index:1;”>
        <img src=”….” style=”width:100%;”>
    </div>
</div>

Lorem ipsum dolor sit amet, consectetur adipisici elit,
sed eiusmod tempor incidunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquid ex ea commodi consequat.
Quis aute iure reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur.
Excepteur sint obcaecat cupiditat non proident,
sunt in culpa qui officia
deserunt mollit anim id est laborum.
Duis autem vel eum iriure dolor in hendrerit in vulputate
velit esse molestie consequat, vel illum dolore eu
feugiat nulla facilisis at vero eros et accumsan
et iusto odio dignissim qui blandit praesent luptatum zzril
delenit augue duis dolore te feugait nulla facilisi.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit,
ed diam nonummy nibh euismod tincidunt ut laoreet
dolore magna aliquam erat volutpat.

yellow_bryce

Hinweis: Im MS IE muss man halt mit proprietären CSS-Anweisungen der Art “filter: alpha(opacity=70);” arbeiten!

Höhensteuerung gekoppelter DIVs zur Laufzeit und gefloatete Container

Nun das Ganze in einer Umgebung mit floatenden Seitenbereichen. Eine typische Anwendung ist folgende:

Wir wollen in einem “right”-gefloateten Layer zur Laufzeit einen Text noch unbekannter Länge unterbringen. Dieser soll jenseits einer Minimalhöhe die vertikale Ausdehnung der gesamten Web-Seite bestimmen (welche im nachfolgenden Beispiel über ein umfassendes DIV repräsentiert wird). In einem “left”-
gefloateten DIV soll ferner eine vertikale Menü-Struktur untergebracht werden, deren Länge typischerweise kürzer als die des Textbereichs (rechts) ist. Der Hintergrund des Menübereichs soll sich jedoch parallel zur Länge des Textbereiches ausdehnen. Der Anspruch ist also:

Der Textbereich in einem “right”-gefloateten Bereich determiniert die Höhe des Hintergrunds eines “left”-gefloateten Bereichs!

Eigentlich unmöglich, denkt man! Mit folgendem Trick geht es aber doch – wie das Beispiel zeigt ! Und es geht auf der MS-Seite ab dem MS IE7.

Testbeispiel mit gefloateten DIvs und gekoppelter vertikaler Ausdehnung

Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.

yellow_bryce

Links erkennt man den gelben Hintergrund eines Menübereichs. Der Menütext befindet sich in einem “left”-gefloateten Container-DIV. Der gelbe Hintergrundsbereich – ein DIV (in dem sich auch ein Bild befinden könnte) – dehnt sich auf exakt die Höhe aus, die der Textbereich rechts in einem “right”-gefloateten Container-DIV vorgibt. Gleichzeitig folgt die gesamte Webseite der Größe des Textes im rechten Float-Container. Alles klar?

Natürlich haben wir ein wenig unterschlagen: Nachfolgend wird das DIV für den Menübereich mal mit einer blauen “Border” umgeben:

Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.

yellow_bryce

Wir sehen, dass die Größe des left-gefloateten Bereichs kleiner ist als die des Hintergrunds. Also steuert der rechte Textbereich nicht wirklich die Höhe des links gefloateten Bereichs. Und dennoch erhält der linksseitige gelbe Hintergrund die richtige Höhe ….

Wie sieht der Code dazu aus?

<div id=”bsp_cont” style=”position: relative; margin-left: auto; margin-right: auto; width: 50.0em; border: #666 0.4em solid; background-color: #EEE; “>
    <div id=”float_cont” style=”position:relative; min-height:5em; border:2px #0F0 solid;”>
        <div id=”menu_left” style=” position:relative; float:left; width:12.6em; padding:1em; border: #CCC 0.2em solid; z-index:2; “>
                <p style=”margin:0;”>menu<br>menu<br>menu<br>menu</p>
        </div>
        <div id=”main_right” style=”position: relative; float:right; width:25.6em; border: red 0.2em solid; background-color:CCC; z-index: 2; “>
             <div id=”txtbox_right” style=”position:relative; color:#000; z-index:3; padding:0.6em; “>
                <p style=”margin:0;”>Lorem ipsum dolor sit amet, ……. mollit anim id est laborum.</p>
            </div>
            <div id=”transp” style=”position:absolute; top:0; left:0; width:100%; height:100%; background-color: #FFF; opacity:0.7; filter: alpha(opacity=70); z-index:2; “></div>
            <div id=”bg_img” style=”position:absolute; top:0; left:0; height:100%; width:100%; overflow:hidden; z-index: 1; “>
                <img src=’https://linux-blog.anracom.com/wp-content/uploads/2011/07/yellow_weit_ext_1_640.jpg’ alt=’yellow_bryce’ style=”width:100%;”>
            </div>
        </div>
&
nbsp;       <p style=”clear: both; line-height: 0; font-size: 0; height: 0; margin: 0; padding: 0;”> </p>
        <div id=”bg_left” style=”position:absolute; top:0; left:0; width:15.0em; height: 100%; border: red 1% solid; background-color: #FEFE77; color: blue; z-index:1;”></div>
    </div>
    <div id=”bsp_footer” style=”position:relative; background-color: #CCC; width: 49.6em; height:2.0em; text-align:center; border: green 0.2em solid; margin-top:1.0em; margin-bottom:0; “><p style=”margin:0;”>Footer</p></div>
</div>

Man erkennt nach eine wenig Analyse vier Punkte:

  • Im rechts gefloateten Container-DIV mit der id=”main_right” nutzen wir das oben besprochene Muster zur Höhenvermittlung zwischen einem relativ positinierten DIV (id=”txtbox_right”) für den Text, dessen vertikaler Platzbedarf erst zur Laufzeit bestimmt wird, und den absolut positionierten DIVs (id=”transp” und id=”bg_img”) für die Gestaltung des zugehörigen Hintergrunds.
  • Die gesamte vertikale Ausdehnung des äußeren Containers mit der id=”bsp_cont” ist zunächst unbestimmt. Sie wird festgelegt durch die Höhe des DIV-Containers mit der ID id=”float_cont” plus der Höhe des DIVs mit der id=”bsp_footer”. Die Höhe des DIVs mit der id=”float_cont” hängt ab von den beiden beinhalteten und gefloateten DIVs. Dass deren Höhe nach außen an das umgebende DIV (id=”float_cont”) weitergegeben wird, ist dem <p>-Tag zu verdanken, dass noch innerhalb des Containers die Float-Strukturen (eig. Ebene) per “clear”-Anweisung beendet.
  • Die Höhe des äußersten Containers mit der id=”bsp_cont” hängt ab von der Höhe des DIVs mit der id=”float_cont”. Dessen Höhe wiederum wird durch die Höhe des gefloateten DIVs mit der id=”main_right” bestimmt. Die Höhe des DIVs mit der id=”main_right” wird dagegen definiert durch die Höhe des DIVs mit der id=”txtbox_right”. Und dessen Höhe hängt von einem Textinhalt ab, den wir in CM-Systemen des realen Lebens erst (mit PHP oder Java) aus einer Datenbank auslesen würden. Obwohl wir hier im Beispiel einen Text vorgeben, wird auch dessen Höhe erst vom Browser zur Laufzeit berechnet.
  • Aus logischen Konsistenzgründen kann es nicht so sein, dass ein gefloatetes Element die Höhe eines anderen gefloateten Elements festlegt. Also kann man die Höhe des Hintergrunds auf der linken Seite nicht direkt über die Höhe des “left”-gefloateten DIVs beeinflussen. Der Trick besteht nun darin, ein weiteres DIV in dem DIV-Container mit der id=”float_cont” zu positionieren und zwar mit “position:absolute; top:0; left:0;” ! Denn dieses absolut positionierte DIV mit der id=”bg_left” kann die dynamisch ermittelte Höhe des umgebenden relativ positionierten Containers übernehmen. Das war ja genau das, was wir schon weiter oben gesehen hatten !
  • Im MS IE7 ist die Reihenfolge der Codezeilen für gefloatete, relativ oder absolut positionierte DIVs in einem Container für die Ergebnisdarstellung leider nicht egal! Anweisungen für absolut positionierte DIVs sollten auf Anweisungen für relativ positionierte DIVs und auch nach Anweisungen für gefloatete DIVs folgen. Für den MS IE 7 ist es in unserem Beispiel also wichtig, dass die Zeile für das absolut positionierte DIV mit der id=”bg_left” im Code erst nach (!) den Zeilen für die gefloateten DIVs und nach dem P-Tag mit der “clear”-Anweisung gesetzt ist. Eine Positionierung vor den Zeilen mit den gefloateten DIVs führt zu einer fehlerhaften Abarbeitung. Im MS IE8 ist dieses Problem behoben. (Wer ganz auf Nummer sicher gehen will, der umgibt die Zeilen für die gefloateten DIVs und das P-Tag auch noch mit einem weiteren kapselnden DIV, das dann relativ
    positioniert werden muss.)

Will man dagegen eine Lösung haben, bei der auch der links gefloatete Textbereich – falls er denn länger als der rechte werden würde – die Gesamtausdehnung steuern soll, so muss man den obigen HTML-Code etwas umstellen:

<div id=”bsp_cont” style=”position: relative; margin-left: auto; margin-right: auto; width: 50.0em; border: #666 0.4em solid; background-color: #EEE; “>
    <div id=”float_cont” style=”position:relative; min-height:5em; border:2px #0F0 solid;”>
        <div id=”menu_left” style=” position:relative; float:left; width:12.6em; padding:1em; border: blue 0.2em solid; z-index:3; “>
            <p style=”margin:0;”>menu<br>menu<br>menu<br> menu<br>menu<br>menu<br>menu<br>menu<br>menu</p></div>
        <div id=”main_right” style=”position: relative; float:right; width:25.6em; border: red 0.2em solid; background-color:CCC; z-index: 3; “>
            <div style=”position:relative; color:#000; z-index:2; padding:0.6em; “>
                <p style=”margin:0;”>Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. </p></div></div>
        <p style=”clear: both; line-height: 0; font-size: 0; height: 0; margin: 0; padding: 0;”> </p>
        <div id=”bg_left” style=”position:absolute; top:0; left:0; width:15.0em; height: 100%; border: red 1% solid; background-color: #FEFE77; color: blue; z-index:1;”></div>
        <div id=”bg_right” style=”position:absolute; top:0; right:0; width:25.6em; height: 100%; overflow:hidden; z-index:1;”><img src=’…..’ alt=’yellow_bryce’ style=”width:100%;”></div>
        <div style=”position:absolute; top:0; right:0; width:25.6em; height:100%; background-color: #FFF; opacity:0.7; filter: alpha(opacity=70); z-index:2; “></div>
    </div>
    <div id=”bsp_footer” style=”position:relative; background-color: #CCC; width: 49.6em; height:2.0em; text-align:center; border: green 0.2em solid; margin-top:1.0em; margin-bottom:0; “><p style=”margin:0;”>Footer</p></div></div>

Das muss man nach dem oben Gesagten nicht mehr erläutern. Das Ergebnis sieht wie folgt aus:

Testbeispiel mit gefloateten DIvs und gekoppelter vertikaler Ausdehnung

Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.

yellow_bryce

Im dargestellten Beispiel bestimmt nun der zur Laufzeit längere der beiden Inhalte der gefloateten DIVs die Höhenskalierung der Seite und die Höhe der eingesetzten Hintergründe (ggf. mit Bildern).

Wir haben nun eine Lösung, die alle unsere Anforderungen erfüllt, und zudem haben wir eine sehr flexible Art gefunden, den Hintergrund gefloateter Bereiche dynamisch vom Inhalt innerer DIVs abhängig zu machen. Ohne Javascript !

Ferner bleibt die Erkenntnis, dass man die Höhe optisch entkoppelter Bereiche sehr wohl auch erst zur Laufzeit durch einen dynamisch erzeugten Inhalt festlegen und synchronisieren kann.

Viel Spaß weiterhin mit den Möglichkeiten von CSS2!

Opensuse, Firefox 4, Flashplayer 32 oder 64 Bit?

Unter Opensuse 11.4 ist der Firefox 4 Standard. Ich habe – wie immer schon – die 64 Bit Variante des Betriebssystems und des Firefox installiert. Zudem läuft KDE 4.6.2.

Nun denkt man so, dass der von Suse als RPM ausgelieferte aktuelle Flashplayer 10.2 in der 32Bit-Variante über den üblichen “nspluginwrapper”-Mechanismus mit Firefox 4 (64 Bi) vernünftig zusammenarbeiten würde. Schießlich ging das mit Firefox 3.6.16 (64 bit) unter Opensuse 11.3 ja auch problemfrei !

Beim Firefox 4 (64 Bit) ist dem leider nicht so! Komplexe Flash-Filme oder sogar Adobe eigene Flashelemente (etwa die Audio oder Video-Player) werden häufig durch Darstellungsfehler so entstellt, dass es keinen Spaß mehr macht, Flash-Seiten anzusehen. Fehler über Fehler ! Man fragt sich, ob das jemals vernünftig getestet wurde ….

In meiner Not habe ich dann mal wieder nach einer halbwegs aktuellen 64Bit -Variante des Flash-Players für Linux umgesehen. Damit habe ich früher schon ganz gute Erfahrungen gemacht. Fündig wird man zur Zeit unter

http://labs.adobe.com/technologies/flashplayer10/square/

Ich habe mir dort die aktuelle Version heruntergeladen. Zur Installation der “libflashplayer.so”-Datei unter Opnsuse sehen Sie bitte folgenden früheren Beitrag:

https://linux-blog.anracom.com/2009/01/26/kurztest-der-64-bit-version-des-flash-plugins-von-adobe/

Bitte nicht die vorher notwendige Deinstallation des “nspluginwrapper” vergessen.

Das Ergebnis des Tests der 64-Bit-Variante: Das allermeiste an Flash-Seiten wird richtiger und schneller dargestellt als mit der für 64-bit Linux und 64-Bit Firefox 4 völlig ungeeigneten offiziellen 32Bit-Variante des Players.

Bleibt erneut die Frage offen: Wann sehen die verantwortlichen bei Adobe endlich ein, dass sie für Linux eine vernünftige “offizielle” 64Bit-Unterstützung bieten müssen?

FF 3.6 behebt Flacker-Problem unter Opensuse 11.2

Viele haben ja die neue Opensuse 11.2-Version gelobt – u.a., weil der Firefox-Browser so gut in den KDE4-Desktop integriert wurde.

Meine Begeisterung hielt sich allerdings 2 Monate in Grenzen. Zwischen ein und derselben FF Version 3.5.x (z.B. 3.5.6) unter Opensuse 11.1 (x86_64) und Opensuse 11.2 (x86_64) gab/gibt es nämlich auch sehr unangenehme Unterschiede. Als Entwickler von Web-Formularen fiel mir besonders auf, dass wann immer DIVs mit “overflow:auto” im Spiele waren, der Seitenaufbau sehr imperformant und “wackelig” vonstatten ging. Z.T. unter Flackern und kurzzeitigem Auftauchen von in der Position versetzten Geisterbildern des DIV-Inhalts.

Eine frische Test-Installation von Opensuse 11.2 auf einer Maschine, auf der auf einer anderen Partition auch Opensuse 11.1 installiert war, ergab, dass das Phänomen seien Ursache allein in den unterschiedlichen Opensuse-Versionen hatte. Es war nicht Gnome, nicht KDE abhängig, tauchte für dieselbe FF-Version nicht unter Opensuse 11.1 auf und hatte auch nichts mit Grafikkartentreibern zu tun. Genauer habe ich es leider nicht analysieren können.

Verschwunden ist das Problem nun mit FF 3.6. Ganz generell ist diese Browserversion auch deutlich performanter geworden.

Tja, man sieht: Im Opensource-Geschäft gibt es Etliches, was einen als professionellen Anwender nur den Kopf schütteln läßt. Aber im Unterschied zu MS Windows hofft man hier nicht vergebens, und es lohnt sich immer wieder, Fehler an die Entwickler zu melden.

https://bugzilla.mozilla.org/show_bug.cgi?id=537392