This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Österreich 10,80 € / Schweiz 19,20 SFR Luxemburg 11,25 €
Authentifizierung mit PHP-Bordmitteln und Klassenbibliotheken
Undo-Mechanismen Das Memento-Entwurfsmuster Verpackungskünstler Arbeit mit komprimierten Daten Elektronisch bezahlen Payment-Systeme im Überblick Daten auf Knopfdruck Excel ohne Excel Graphen erzeugen mit PEAR::Image_Graph Datenbankabstraktion mit ADOdb
Die Magazin-CD •Content Management-Systeme •Tools & Utilities •PHP-IDEs •Quellcodes •Datenbanken zu den Artikeln
Anzeige
Anzeige
Inhalt März 2004
Authentifizierung mit PHP 30 Benutzerauthentifizierung mit PHP-Bordmitteln und -Klassenbibliotheken „Who are you?“ – nicht nur vorlonische Botschafter bestehen regelmäßig auf die ordnungsgemäße Beantwortung dieser Frage. Auch für viele Webanwendungen ist es essenziell wichtig, einen Benutzer zweifelsfrei identifizieren zu können, bevor ihm bestimmte Bereiche oder Funktionen zur Verfügung gestellt werden. Bei dieser an sich einfach klingenden Aufgabe stellt uns die Natur des Internets vor einige Probleme: Es gibt zunächst einmal keine Erkennungsmerkmale, die eine über jeden Zweifel erhabene Identifikation möglich machen. Verschiedene Authentifizierungsmechanismen schaffen jedoch Abhilfe.
16 Kommando zurück! Das Memento-Entwurfsmuster Annähernd jede moderne Applikation verfügt über eine Funktion, um die letzten Arbeitsschritte rückgängig zu machen. Den meisten browserbasierten Anwendungen hingegen fehlt diese komfortable Möglichkeit zur Korrektur vorheriger Befehle. Dieser Artikel zeigt eine Möglichkeit auf, UndoMechanismen zumindest für objektorientierte Software vergleichsweise einfach zu implementieren.
20 Verpackungskünstler: Mit komprimierten Dateien arbeiten Neben dem wenig effizienten und vor allem fehleranfälligen und notorisch unsicheren Weg, Archiven per exec(), also per passendem Entpacker über die Kommandozeile, zu Leibe zu rücken, gibt es, man ahnt es bereits, auch eine „saubere“ Methode: Mittels eines API für den Einsatz mit PHP. Im Gegensatz zu den Unwägbarkeiten eines externen Prozesses („Was ist eigentlich warum schief gegangen? Sind alle Daten da, wo sie sein sollten?“) steht einem über ein API eine strukturierte Übergabe von Informationen zur Verfügung, auf die man schon aus Sicherheitsgründen nicht verzichten sollte.
45 Zur Kasse, bitte! Payment-Systeme im Überblick Die einstigen Sicherheitsbedenken sind vergessen: Mittlerweile nutzt jeder Deutsche mit Online-Anschluss die Möglichkeit, Waren im Internet zu kaufen. Das beliebteste Zahlungsmittel ist hier die Kreditkarte. Zahlreiche Unternehmen sind angetreten, um für die verschiedensten Shop-Betreiber den Billing-Prozess abzuwickeln. Die meisten Anbieter unterstützen dabei PHP-Entwickler beim Aufbau eines Shop-Systems mit dem Bereitstellen von eigenen APIs.
4
PHP Magazin 3.2004
Enterprise 16 Kommando zurück! Das Memento-Entwurfsmuster
Startup 20 Verpackungskünstler Mit komprimierten Dateien arbeiten 23 Automatenfotos Dynamische Bildbearbeitung mit PHP
Tools & Tipps 28 PEARcing PEAR-News für Insider Lesestoff für PHP-Lover 29 Das PHP-Kochbuch/PostgreSQL – Professionell und praxisnah
Titelthema 30 Benutzerauthentifizierung Rezepte unter Einsatz von PHPBordmitteln und -Klassenbibliotheken
Development 45 Zur Kasse, bitte! Payment-Systeme im Überblick 50 Daten auf Knopfdruck Excel-Funktionalität ohne Excel nutzen 54 PEAR::Image_Graph Graphen erzeugen mit PHP 60 Ohne Umweg PDF-Dateien ohne Zusatzsoftware im Browser anzeigen 63 Neue Birnen braucht das Land Kritische Betrachtung der neu gegründeten PEAR Group
XML extra 68 Alles Definitionssache XML-Strukturen mit DTDs und Schemata verifizieren 72 Wahlerfolg Generierung von SVG-Code mithilfe von XSLT
Datenbanken 76 Wunderbar wechselbar Datenbankabstraktion mit ADOdb 81 Vom Orakel zum Elefanten Datenbankmigration von Oracle auf PostgreSQL
Solutions 84 Gute Kontakte Kommunikation im Gesundheitsbereich 87 Test it! Versuchsdaten-Management in der Automotive-Industrie
Rubriken
50 Daten auf Knopfdruck: Excel-Funktionalität ohne Excel nutzen Es ist eine Crux mit Microsoft Office: Viele schimpfen darüber, aber jeder verwendet es. Noch viel „schlimmer“ ist es mit den Dateiformaten. Unter Linux beispielsweise ist das Microsoft-Word-Format das mit am Häufigsten eingesetzte Textverarbeitungsformat. Wieso? Nun, obwohl es nicht offiziell standardisiert ist, ist es ein De-Facto-Standard. In diesem Teil der Serie „x ohne x“ geht es um die Erstellung von Excel-Worksheets, ohne selbst Excel zu verwenden. Ermöglicht wird das ganze wie so oft durch ein PEAR-Paket.
60 Ohne Umweg: PDF-Dateien ohne Zusatzsoftware im Browser anzeigen Zum Lesen von PDF-Dateien steht eine Reihe von kostenlosen Programmen zur Verfügung, jedoch hat nicht jeder Benutzer ein solches installiert. Möchte man sich in seiner Webapplikation nicht darauf verlassen, müssen PDF-Dateien im Browser ohne Zusatzsoftware angezeigt werden.
76 Wunderbar wechselbar: Datenbankabstraktion mit ADOdb Häufig wird bei Neuerungen leider immer wieder das Rad neu erfunden. So wird Code oft plattformabhängig entwickelt bzw. an eine bestimmte Datenbank oder Programmiersprache gebunden. All diese Punkte machen die Entwicklung von Anwendungen, die das Prädikat dynamisch im wahrsten Sinne des Wortes verdienen, schwierig. In diesem Artikel wird gezeigt, wie Sie Ihren Code wirklich dynamisch, nämlich mittels Datenbankabstraktion, entwickeln können.
„Zend kassiert freien PHP Bytecode-Cache!“ Liebe Leserin, lieber Leser, So oder so ähnlich war vor kurzem der Aufschrei, als bekannt wurde, dass der Autor und Maintainer des GPL PHP-BytecodeCaches Turck MMCache, Dmitri Stogov, nun bei Zend festangestellt ist und sich nicht mehr um den Turck MMCache kümmern kann. Die Verunsicherung war groß, schnell wurde Kritik an Zend laut, dass sie nun anfangen würden, alle Open SourceVarianten eines PHP-Bytecode-Caches einzukassieren und nur ihr Produkt, die Zend Performance Suite, damit stärken wollen. Selbst das Gerücht, dass der Autor des ebenfalls freien Bytecode-Caches PHPAccelerator, Nick Lindridge, bei Zend arbeitet, machte die Runde. Doch wie immer waren die Gerüchte einfach nur Gerüchte. Eine Klärung im SourceForge-Forum von Zeev Suraski höchstpersönlich brachte Licht ins Dunkel: Dmitri ist nicht nur als Autor eines erstklassigen PHPBytecode-Caches aufgefallen, nein, er hat auch noch ein tiefes Wissen innerhalb der ZendEngine. Zend hat ihn mitnichten eingestellt, um die Zend Performance Suite zu
verbessern, sondern um an PHP (5) selbst weitere Verbesserungen innerhalb der Engine hinzuzufügen. Zudem steht Dmitris Bytecode-Cache unter der GPL, es gab also keine/kaum Möglichkeiten für ihn, mit Hilfe seines Know-hows an der ZendEngine Geld zu verdienen. So sind nun bereits erste Commits im CVS-Repository sichtbar: Dmitri kümmert sich seit einiger Zeit intensiv um die SOAPExtension, die bisher ein eher unbekanntes Dasein fristete. Diese Extension ist wie alle PHP-Extensions in C geschrieben und damit wesentlich schneller als die PHP-Userland-Implementierungen wie NuSOAP (vormals SOAPx4) oder PEAR::SOAP. Zudem kennt die SOAP-Extension, die sich im PECL-Repository findet, weitere Features wie z.B. Schema Support und eine Verbesserung der Zusammenarbeit mit fremden SOAP-Servern. Damit bekommt PHP end-
lich einen noch stärkeren SOAP-Support, der insbesondere dem Einsatz im EnterpriseBereiche zu Gute kommt. Für den TurckMMCache bedeutet dies zunächst, dass Dmitri keine Zeit haben wird, ihn weiter zu maintainen. Hier sind also Personen gefragt, die sich in der ZendEngine auskennen und die Software weiter betreuen und weiter entwickeln wollen. Sie läuft bis zur dato aktuellen PHP 4-Release und sogar mit der jüngsten PHP 5 Beta 3. In diesem Sinne: Danke Zend! Viel Spaß mit dieser Ausgabe wünscht
Björn Schotte, Chefredakteur
Anzeige
News & Trends Dev.Talk Beförderung von Sebastian Bergmann Standard PHP Library (SPL) Die Standard PHP Library, kurz – in Anlehnung an C++ und dessen Standard Template Library (STL) – SPL genannt,von Marcus Börger wurde rechtzeitig vor dem PHP 5.0.0 Release-Prozess von PECL in die Standarddistribution „befördert“. Sie stellt einige Klassen und Schnittstellen zur Verfügung, die das Iteratormodell der Zend Engine 2 unterstützen und beispielsweise das Traversieren von Verzeichnissen erleichtern. So wird beispielsweise in Listing 1 ein (verschachtelter) Verzeichnisiterator zusammen mit einem Filteriterator verwendet, um in einer Liste von Verzeichnissen nach einer Datei mit gegebenem Dateinamen zu suchen. Neue SOAP-Erweiterung Dmitry Stogov, der sich vor seiner Anstellung bei Zend Technologies Ltd. mit der BytecodeCaching Lösung TurckSoft MMCache einen Namen gemacht hatte,arbeitet an einer neuen PHP-Erweiterung für das Simple Object Access
Listing 1 Suche nach einer Datei in einer Menge von Verzeichnissen
Protocol (SOAP). Gegenüber den in PHP geschriebenen SOAP-Implementierungen kann die in C implementierte Erweiterung vor allem in Bezug auf die Geschwindigkeit überzeugen. Die Erweiterung ist über die PHP Extension Community Library (PECL), dem Gegenstück zu PEAR für in C/C++ geschriebene PHP-Erweiterungen,zu beziehen. Beliebige Bibliotheken aus PHP heraus nutzen Mit dem Foreign Function Interface (FFI), vormals unter dem Codenamen „ext/dangerous“ bekannt, hat PHP-Entwickler Wez Furlong eine Erweiterung für PHP 5 vorgestellt, die plattformübergreifend die Verwendung beliebiger Bibliotheken aus PHP heraus ermöglicht, ohne dass eine eigene PHP-Erweiterung für diese implementiert werden muss. Hierfür bedient sich die Erweiterung der Funktionalität der libffi-Bibliothek der GNU Compiler Collection (GCC) und stellt diese dem PHP-Programmierer zur Verfügung. Dieser braucht nun lediglich eine Deklaration der fremden Funktion mit Hilfe der Interface Definition Language (IDL) anzugeben und schon kann diese verwendet werden:
8
char *caption, int type);“
$this->dateiname = $dateiname;
return !strcmp($this->current(),
PHP Magazin 3.2004
Mit dem MySQL Administrator hat MySQL AB auf der LinuxWorld Expo in New York ein grafisches Administrationswerkzeug für seinen Open Source-Datenbankserver vorgestellt, das im 2. Quartal unter dem von MySQL AB gewohnten dualen Lizenzsystem (kostenlos unter der GNU Public License bzw. kommerzielle Lizenz) für Linux und Windows erscheinen soll. Der MySQL Administrator ermöglicht Entwicklern und Datenbankadministratoren alle Operationen,die bislang nur über die Befehlszeilenebene möglich waren, über eine einheitliche grafische Benutzeroberfläche auszuführen: Konfiguration des Servers, Verwaltung der Benutzer und Überwachung der Datenbanken. Auch andere Arbeiten wie die Überwachung von Datenbankreplikationen, Backup und Restore und das Überprüfen von Protokollen können mit dem MySQL Administrator erledigt werden. Eine Vorführung der Software durch Michael Zinner, der für die Windows-Version verantwortlich zeichnet, hinterließ einen sehr positiven Eindruck und lässt auf ein durchweg gelungenes Produkt hoffen. Zumal sich Zinner bereits mit dem DBDesigner 4, einer gelungenen Open Source-Lösung für Design und Modellierung von Datenbankanwendungen, einen Namen gemacht hat.
PHP-Power auf CD!!! CD-Inhalt: Quellcodes zu den Artikeln im Heft PHP-Releases:
Packprogramme: gzip 1.2.4 lzf 1.2 Tar 1.12 UnZip 5.5 & Zip 2.3 & WiZ 5.02 zip 1.0 ZZIPlib 0.10.82
PHP Magazin 3.2004
9
News & Trends PEAR Neue Packages und Releases im PEAR von Alexander Merz
MDB/MDB2 Bei MDB gab es nur kleine Patches; neue Funktionen wird es zukünftig nur noch bei MDB2 geben. Dessen erstes Alpha-Release steht zum Download bereit. Die API wurde vereinfacht, nicht nur für den Nutzer, sondern auch für die Entwickler der Datenbank-Treiber. Leider wurden noch nicht nicht alle Treiber für MDB2 angepasst, bislang gibt es sie nur für MySQL, PostgreSQL und Oracle. Validate Das Package bot bereits eine große Vielfalt an Methoden zur Überprüfung von Zeichenketten (URLs, eMail-Adressen) und Kodes (z.B. Kontonummern, ISBN). Mit der neuen 0.3-Version ist das Package gerade für deutschsprachige Anwender interessanter geworden: es können jetzt deutsche und österreichische Postleitzahlen geprüft werden. Translation2 Das Package Translation zur einfacheren Unterstützung mehrsprachiger Oberflächen hat ebenfalls ein neues Major-Release bekommen und heißt deshalb Translation2. Neben der überarbeiteten API wird jetzt auch MDB als Datenbank-Backend unterstützt. PHPUnit2 Sebastian Bergmann schrieb das Package PHPUnit für automatiserte Quellcode-Tests komplett neu. Das Package unterstützt neue Prüfmethoden und -Verfahren. Um sie zu realisieren, war aber der Umstieg auf PHP 5 notwendig – PHPUnit 2 ist damit das erste PHP 5only Package in PEAR. Auth Der Sprung auf 1.2.3 bringt nur wenig öffentliche Änderungen, dafür einige interne Verbesserungen für die einfachere Entwicklung von Backends – als wenn die Liste der Speicherquellen für Login-Name und Passwort nicht schon lang genug wäre.Wichtige Änderung: Ein lange existierender „Fehler“ wurde
10
PHP Magazin 3.2004
endlich korrigiert: Auth.php kann gemäß den Coding Standards mit include(“Auth. php“) aufgerufen werden, statt bisher mit include(“Auth/Auth.php“). Für eine Übergangszeit wird der erste Aufruf aber noch unterstützt. HTML_BBCodeParser UBB ist ein populäres System für die Erstellung Web-Foren. Es unterstützt spezielle Formatierungs-Tags für Meldungen, um auch HTML-unerfahrenen Benutzern das Schreiben zu erleichtern. Das Package bietet einen Parser, um diese Tags zu verarbeiten. Der Parser ist dabei nicht nur auf die vordefinierten UBB-Tags beschränkt, sondern kann auch leicht um eigene Tags erweitert werden. Damit ist der Parser auch auf eigene Zwecke zuschneidbar: z.B. für das eigene CMS-System. Aktuell ist die Version 1.1. Pager/Pager_Sliding Pager ist ein PEAR-Urgestein und mittlerweile bei Version 2.2 angekommen – leider noch bevor es die entsprechenden Namens-Regelungen für neue Major-Releases gab.Somit hat sich der Package-Name nicht geändert,also Vorsicht beim Update.Es übernimmt nun die Funktionen von Pager_Sliding, welches damit als „deprecated“ markiert wurde. Eine Migration von Pager auf Pager_Sliding ist allen Anwendern angeraten. Date Einige wichtige Bugfixes und neue Funktionen gab es für Date 1.4. Für den Historiker interessant, ist wohl folgende Meldung: improve Date_Calc isLeapYear() and daysInMonth() for year 1582 (in diesem Jahr er-
folgte eine Kalenderreform durch Papst Gregor XIII). Log Log unterstützt ab Version 1.8.3 auch SQLite als Backend für die Speicherung von LoggingDaten.
File_Passwd Zum Redaktionsschluss zwar noch 1.0-beta, sollte das Package mittlerweile als stable bereitstehen. Mit der Klasse können Sie verschiedenste Arten von Passwörtern erzeugen und verwalten: z.B. für Unix, SMB-Server oder für die Authentifizierung über den Apache-Webserver. HTML_Quickform Diverse Bugs wurden in Version 3.2.1 korrigiert, sie betreffen die JavaScript-Generierung und die Benutzung von Template-Engines zum Erzeugen von Formularen. PEAR_PackageFileManager Für den „einfachen“ Programmierer ist das Package weniger interessant, aber wer selbst Code im PEAR-Package-Format weitergeben will, wird es als wahre Erleichterung sehen. Die Klasse erzeugt bzw. überarbeitet die package. xml-Datei, die der PEAR-Installer zur korrekten Installation benötigt. Damit wird die PackageErzeugung sehr einfach. XML_HTMLSax Mit dem Package verarbeiten Sie HTML analog zu XML-Dokumenten mit der Expat-Erweiterung in PHP. Der Unterschied: XML_HTMLSax ist weniger pingelig. Steigt ein klassischer XML-Interpreter bereits beim ersten nicht-geschlossenen Tag mit einer Fehlermeldung aus , berücksichtigt das Package typische HTML-Eigenheiten wie nichtgeschlossene Listen-Tags oder fehlende Anführungszeichen bei Attributen. Damit ist es ideal zum Parsen beliebiger Webseiten. Aktuell ist die Version 2.1.2. DB Die Datenbank-Abstraktion erreichte die Versionsnummer 1.6 – wichtigste Neuheit:ein Treiber für die Nutzung der mysqli-Extension, die speziell für die MySQL 4.x entwickelt wurde. Sie erreichen Alexander Merz unter [email protected].
News & Trends PHP 4.3.5 RC 2 Neben einigen Bugfixes wird ab der RC2 die GD 2.0.17 mit ausgeliefert. Das Release soll das letzte vor PHP 4.3.5 sein.
qa.php.net/
PHP 5.0 beta4 Mitte Februar wurde die 4. Beta-Version von PHP 5 veröffentlicht. Diese Beta-Version soll die letzte sein. Viele Bugfixes kamen hinzu, außerdem kann man erstmals die von Dmitri verbesserte SOAP-Extension hierbei intensiv testen.
theKompany.com bringt dbRadar 1.1 heraus theKompany.com hat Version 1.1 der Datenbank-Monitoring- und Administrations-Lösung dbRadar herausgegeben. Die Software ist unter Linux und Windows lauffähig, eine Version für Mac OS X soll demnächst folgen. Unterstützt werden die Datenbanken Oracle, PostgreSQL, MySQL und DB2. Zu den neuen Features gehören die simultane Unterstützung von mehreren Datenbankverbindungen.
www.thekompany.com/products/dbradar/
www.php.net/downloads.php#v5
Contentpapst 2.0 ist draußen Die Sandoba Medienagentur hat Version 2.0 ihres Content Management-Systems Contentpapst herausgebracht. Das neue Release soll vor allem eine verbesserte Benutzer- und Rechteverwaltung bieten.Außerdem beinhaltet das System nun standardmäßig ein Modul zur Sprachverwaltung,sodass mehrsprachiger Content leichter gepflegt werden kann.
Modularization of XHTML 1.0 – Second Edition frei gegeben Die HTML Working Group hat den überarbeiteten Entwurf „Modularization of XHTML 1.0“ als so genannte Second Edition veröffentlicht. Das Dokument soll einige Änderungen und Verbesserungen enthalten, die in den letzten drei Jahren der Anwendung erarbeitet wurden. Zusätzlich enthält die Spezifikation eine neue Implementierung der XML Schemas nutzenden Abstract Modules.
Mozilla-Stützpunkt in Europa gegründet Wie einer aktuellen Pressemeldung zu entnehmen ist, hat die Mozilla Foundation zusammen mit europäischen Mozilla-Mitwirkenden die gemeinnützige Organisation „Mozilla Europe“ gegründet und die dazugehörige Webseite www. mozilla-europe.org/ in Betrieb genommen. Die Mitwirkenden von Mozilla Europe wollen künftig für die Förderung, die Entwicklung und den Vertrieb von Mozilla-Produkten, darunter auch der Browser Mozilla 1.6, verantwortlich zeichnen. Die Webseite ist derzeit in den vier Sprachen deutsch, englisch, französisch und spanisch abrufbar, Übersetzungen in holländisch, italienisch und slowakisch befinden sich bereits in Arbeit.
www.mozilla-europe.org/
Erste Beta von Script Running Machine veröffentlicht Das Entwicklerteam um Derick Rethans hat eine erste öffentliche Beta-Version von Script Running Machine (SRM) freigegeben. Der SRM Daemon kann als Storage Container für das PHP Session
Management verwendet werden. Außerdem können mit SRM Application Level-Variablen in PHP genutzt und persistente Ressourcen wie Datenbankverbindungen gepoolt werden. SRM führt Bananas ein, die als Objekte persistent im SRM Daemon existieren.
www.vl-srm.net/
Web Forms-Tool XFormation 1.0 erschienen XFormation, ein Tool für die Entwicklung von Web-Formularen, das auf die W3C-Spezifikation XForms zurückgreift, liegt aktuell in Version 1.0 vor. Diese soll unter anderem mit dem XFormsProzessor „formsPlayer Version 1.0“ aufwarten, welcher Funktionen zum Debuggen von WebFormularen und eine Preview-Funktion bietet. Die XFormation 1.0 Standard Edition kann zu einem Preis von 499 US-Dollar bezogen werden.Eine auf 30 Tage begrenzte Testversion ist über die offizielle Webseite erhältlich.
www.xformation.com/
Web Performance Trainer 2.6 ist draußen Der Web Performance Trainer steht in Version 2.6 zum Download zur Verfügung.Das Load Testing Tool dient der Evaluierung von Serverlasten. Es lassen sich Performance-Kriterien eingeben und die Software errechnet, wie viele User dann vom Webserver bedient werden können. Das System unterstützt J2EE, ASP, .NET, PHP und ColdFusion.
www.webperformanceinc.com/
Anzeige
Jigsaw in Version 2.2.4 erhältlich Die auf Java basierende Open Source Webserver-Plattform Jigsaw steht ab sofort in Version 2.2.4 zum Download bereit. Ein Update auf die aktuelle Version wird von den Entwicklern nachdrücklich empfohlen,da Jigsaw wichtige Security-Fixes hinzugefügt wurden. Neben den gestopften Sicherheitslecks wartet die WebserverPlattform mit einem überarbeiteten HTTP-Client und SSL-Erweiterungen auf.
www.w3.org/Jigsaw/
PHP Magazin 3.2004
11
News & Trends Platform for Privacy Preferences (P3P) Version 1.1 veröffentlicht Das W3C hat Version 1.1 der Spezifikation „Platform for Privacy Preferences (P3P)“ als so genannten First Public Working Draft online gestellt. P3P soll den Prozess des Lesens von Web Site Privacy Policies vereinfachen und automatisieren, indem die Datenschutz-Präferenzen der Benutzer als Snapshots in einem standardisierten, maschinenlesbaren Format gespeichert werden. Die aktuelle Version von P3P soll neue Features beinhalten, ermöglicht durch Anwendung des P3P 1.0 ExtensionMechanismus.
www.w3.org/TR/2004/WD-P3P11-20040210/
Flying Dog Software auf der CeBIT Flying Dog Software präsentiert auf der diesjährigen CeBIT in Halle 3, Stand D03, seine Softwarelösungen in jeweils neuen Versionen. Präsentiert werden das Enterprise Information Portal 2.0, Powerslave Rapid Prototyping Server 5.0 und Powerslave Enterprise Workflow Engine 2.5. Die Rechercheplattform Enterprise Information Portal soll in der Version 2.0 unter anderem einen eigenen Retrieval-Manager sowie neue Visualisierungsmöglichkeiten beinhalten. Weiterhin präsentiert Flying Dog seine Powerslave Workflow Engine 2.5 mit integriertem Portal Server und neuen Desktop-Funktionen. Die aktuelle Version 5.0 des Enterprise Content Management Systems Powerslave mit WebDAV-Unterstützung und Integration der Volltextsuchmaschine Xapian rundet den Messeauftritt ab.
www.flyingdog.de/
Neues deutschsprachiges PEAR-Forum im Web Das neue PEAR-Forum will zur Anlaufstelle für alle deutschsprachigen PEAR-Nutzer werden. Das Projekt bietet ein Forum, in dem auch spezielle Themen wie PHP-Gtk und PHP.NET abgehandelt werden sollen, eine Knowledge Base und eine RSS-Integration der PEAR-Newsfeeds. Initiator des Forums ist Marco Grätsch.
www.pear-forum.de/
eBay erweitert Web Services-Support Das Online-Auktionshaus will sein Entwicklerprogramm in der nächsten Zeit weiter ausbauen und unter anderem die Unterstützung für Web Services-Protokolle sowie Programmiersprachen ausbauen. Dies kündigte das Unternehmen im Rahmen der in San Diego abgehaltenen
12
PHP Magazin 3.2004
O’Reilly Emerging Technology Conference an. Konkret sollen im zweiten Quartal Unterstützung für das Protokoll SOAP und die Programmiersprache Java angeboten werden. eBay bietet zurzeit im Rahmen seines Developer Programs bereits ein SDK für .NET-Developer an, das diesen die Integration von eBay-Funktionalität in eigene Applikationen ermöglichen soll.
der der Open Source-Datenbank Firebird (dem Open Source-Pendant zur Datenbank Interbase von Borland) gegen die Namensgleichheit aussprachen, wurde nun der schlanke Browser erneut umbenannt.Version 0.8 soll unter anderem einen neuen Download-Manager enthalten,verbesserte Verwaltung von Bookmarks bieten sowie die Installation und Auswahl von Erweiterungen.
developer.ebay.com/DevProgram/
mozilla.org/products/firefox/
Neues Release von myXML für PHP
PHP Registry 0.1.3 ist draußen
myXML, eine PHP-Implementierung der W3CEmpfehlungen DOM, XPath und XSLT, liegt auf freshmeat.net aktuell als so genannter Default Branch vor.myXML soll die Anwendung von XML/ XSLT-Technologie ermöglichen, selbst wenn der jeweilige Provider diese nicht unterstützt. Das DOM-API soll laut Release Notes Standardbezeichnungen für Methoden und Eigenschaften bieten und mit künftigen Versionen von PHP zusammen arbeiten. Nähere Einzelheiten über das Open Source-Tool finden sich auf:
PHP Registry liegt in Version 0.1.3 zum Download vor. Das PHP-Tool dient der Sammlung von Applikations-Konfigurationen in einer Datei. Das Package bietet auch eine Subklasse, die als Web Interface zur Bearbeitung der Dateien verwendet werden kann.
freshmeat.net/projects/phpmyxml/
MySQL präsentiert auf der CeBIT Version 5.0 und MaxDB Datenbankanbieter MySQL AB stellt auf der CeBIT die neue Version 5.0 der Open Source-Datenbank vor,die kürzlich als Alpha-Release freigegeben wurde.Die neue Version unterstützt erstmals Stored Procedures und soll die Entwicklung umfassender Geschäftsanwendungen deutlich vereinfachen. Zudem zeigt das Unternehmen den neuen MySQL Administrator zur erleichterten Verwaltung des DBMS. Am Stand von SAP wird MySQL außerdem die neue Datenbank MaxDB präsentieren, eine Weiterentwicklung der früheren SAP DB, die auf den Einsatz in mySAP-Umgebungen ausgerichtet ist.
www.koorb.co.uk/
MySQL-Anwenderkonferenz in Orlando Vom 14. bis 16. April 2004 lädt Datenbank-Anbieter MySQL AB zur MySQL-Anwenderkonferenz nach Orlando ein. Das Event wird von Unternehmen wie Intel,Apple, Hewlett-Packard und Novell unterstützt. Im Mittelpunkt stehen Technologie- und Hands-on-Sessions zu MySQL und MaxDB. Ein Business-Track richtet sich speziell an Entscheider.
www.mysql.de/events/uc2004/index.html
openEngine 1.1 ist fertig Das PHP-basierte Content Management-System openEngine steht in Version 1.1 zum Download zur Verfügung. Es bietet Layouts auf Template-Basis, eine integrierte Benutzerverwaltung und eine Newsletter-Anwendung mit automatisierter Benutzer- und Profilverwaltung. Außerdem verfügt openEngine über ein Statistikmodul.
www.mysql.de/
www.openengine.de/
Version 0.8 von Firebird unter neuem Namen veröffentlicht
Dynamische Links: phpGiggle 1.1.0 verfügbar
Die Mozilla Foundation hat die Verfügbarkeit eines neuen Preview Release von Mozilla Firefox (ehemals Firebird) bekannt gegeben. Dieser steht auf den Seiten des Mozilla-Projektes ab sofort in Version 0.8 zum Download bereit. Der vor knapp anderthalb Jahren unter dem Namen Phoenix erschienene Standalone-Browser wurde im vergangenen Jahr in Firebird umgetauft, passend zu dem damals zeitgleich veröffentlichten eMail-Client Thunderbird.Da sich jedoch bereits zu diesem Zeitpunkt die Mitglie-
phpGiggle,ein Tool zur dynamischen Linkgenerierung, steht in Version 1.1.0 zum Download zur Verfügung. Das Skript kann benutzerdefinierte Wörter oder Phrasen in HTML-Dokumenten durch benutzerdefinierte URLs ersetzen. Es beinhaltet vordefinierte Anker-Tags für Google, Dictionary.com, Webopedia, Vivisimo, CPAN, Freshmeat und andere Webseiten. Die Anker-Tags für eine gesamte Website können in einer einzigen Datei gespeichert werden.
www.biermana.org/index.php?p=12
Anzeige
News & Trends Firebird-Tutorial: Es muss nicht immer MySQL oder PostgreSQL sein
Auf der Website von Linux Journal hat Masroor Farooqi ein Tutorial zur Firebird-Datenbank bereitgestellt, das für den Einsatz der Open Source DB, die aus Borlands InterBase hervorgegangen ist, wirbt. Zwar seien MySQL und PostgreSQL nach wie vor die erste Wahl bei Entwicklern,doch will der Autor den Blick auf zahlreiche Features von Firebird lenken, die die Datenbank auch für den professionellen Einsatz interessant machen. So unterstützt Firebird Stored Procedures,Trigger und Hot Backups. Außerdem kommt die Datenbank in zwei Variationen. Der Classic Server verwendet für jede Datenbankverbindung einen eigenen Prozess und ist damit vor allem bei nur wenigen Datenbankverbindungen sehr performant. Der Super Server dagegen arbeitet mit Multithreading, was sich bei vielen Datenbankverbindungen günstiger auswirkt. Das Tutorial gibt Tipps zur Installation von Firebird und praktische Einweisungen in die verschiedenen Features.
Mit dem neuen Produkt Clusgres will Linux Labs der Open Source-Datenbank PostgreSQL zu einem neuen Schub im Enterprise Computing verhelfen. Clusgres soll den parallelisierten Betrieb von PostgreSQL auf mehreren Computern ermöglichen. Die libOPUS von Linux Labs ermöglicht die Aufsplittung von Server-Prozessen,ohne dass dazu der Programm-Code geändert werden muss.
theKompany.com hat Version 1.0.2 der PIM-, eMail- und Kommunikationssuite Aethera herausgebracht. Die Software ist sowohl unter Linux als auch unter Windows nutzbar. Eine Version für Mac OS X ist demnächst ebenfalls geplant.Damit will der Anbieter seinen Kunden eine plattformübergreifende Lösung bieten, die Features wie Kalender, eMail und Groupware mitbringt. Aethera, im Kern als GPL-Version erhältlich, lässt sich durch verschiedene kommerzielle Plugins um Instant Messaging, Whiteboarding und Voice over IP erweitern. Module für die PDA-Synchronisierung und CRM sollen zusätzlich demnächst erhältlich sein.
www.linuxjournal.com/article.php?sid=7010
www.linuxlabs.com/clusgres.html
ErfurtWiki R1.01d ist draußen Das PHP-basierte Wiki-System ErfurtWiki steht in Version R1.01d zum Download zur Verfügung. Das System verwendet zur Speicherung der Daten MySQL, PEARDB/dbx/ADOdb, dba/dbm, PhpWiki13 oder ein Flat File-System. Die neue Entwicklerversion fixt einige Bugs und verfügt über einen erneuerten Markup-Kernel, der allerdings noch einige Bugs enthalten könnte. Bestehende Installationen sollten dennoch aktualisiert werden, da das Plugin-Verzeichnis überarbeitet wurde.
Pearson bringt neue MySQL-Reihe auf den Markt
erfurtwiki.sourceforge.net/
Person Education und MySQL AB starten gemeinsam eine neue Buchreihe. „MySQL Press“ soll Datenbankentwicklern Hintergrundinfos zur Nutzung der populären Open Source-Datenbank bieten. Der erste Titel der Reihe „MySQL Tutorial“ von Like Welling und Laura Thomson ist bereits erschienen. Im Frühjahr sollen drei weitere Titel folgen, und zwar ein Administrator Guide,eine Sprachreferenz und ein Lehrbuch zur MySQL-Zertifizierung.
Auto Directory Index PHP Script 1.3.8 freigegeben Das PHP-Skript Auto Directory Index bietet die Möglichkeit,Verzeichnislisten in einer Tabelle zu erfassen, über die der Zugriff auf die Dateien und Unterverzeichnisse dann für verschiedene Anwender ermöglicht wird. Das Skript beinhaltet eine Suchfunktion,verfügt über die Möglichkeit zur Bandbreiten-Einschränkung und bietet Access Logging. Die neue Version 1.3.8 bietet kleinere Bugfixes.
www.thekompany.com/projects/aethera
H+BEDV bringt Dazuko für FreeBSD heraus Der Sicherheitsanbieter H+BEDV hat das Sicherheits-Modul Dazuko, das bereits für Linux erhältlich war, nun auch für FreeBSD freigegeben. Mit Hilfe des Moduls können On-Access-Virenscanner und weitere Sicherheits-Tools von Drittanbietern unter Linux und FreeBSD integriert werden. Das Kernel-Modul war ursprünglich als Plugin Bestandteil von AntiVir für Linux. Die neue Version 2.0.0 bietet nun auch die Möglichkeit, Dazuko als Interface für Security- und Monitoring-Anwendungen anderer Anbieter zu verwenden.
www.antivir.de/
MandrakeSoft will Entwicklungsprozess verbessern
Apache Friends wollen sich gegen Marken-Attacke wehren
Six zeigt auf der CeBIT neue Version 6 von SixCMS
Das Apache Friends-Projekt,das die freie ApacheDistribution XAMPP bereitstellt, sieht sich mit einer „Marken-Attacke“ konfrontiert.Im Dezember 2003 wurde „XAMPP“ beim Deutschen Patent- und Markenamt als Marke angemeldet, allerdings nicht von den Initiatoren des Projekts. Diese wollen sich nun mit rechtlichen Schritten gegen die Markenanmeldung zur Wehr setzen und rufen deshalb zu einer Spendenaktion auf, um die Anwaltskosten zu bezahlen. XAMPP gehört mit etwa 42.000 Downloads pro Monat zu den populärsten Apache-Distributionen.
Die Six Offene Systeme GmbH stellt auf der CeBIT die neue Version 6 ihres Content Management-Systems SixCMS vor. Die Enterprise-Version wird neben MySQL auch die Datenbank Oracle 9i unterstützen.Upgrades von bestehenden MySQL-Installationen sollen möglich sein. Zu den neuen Features zählen eine enger verzahnte integrierte Bildbearbeitung und EventTrigger, sodass eine noch engere Einbindung in bestehende IT-Landschaften gegeben sei. Über ein PHP-API lässt sich das System um Funktionen erweitern.
Linux-Distributor MandrakeSoft hat eine Verbesserung seines Entwicklungsprozesses der Linux-Distribution angekündigt. Bereits bisher hatte das Unternehmen das Material für künftige Distributionen frühzeitig öffentlich zur Verfügung gestellt und so zahlreiche Anwender in die Tests einbezogen. Künftig soll unter dem Label „Mandrake Linux Community“ eine Distribution mit State-of-the-Art-Technologien erscheinen, die via Download und als DVD erhältlich sein soll. Diese Distribution soll zwei bis drei Monate für die Anwender der neuesten Technologien verfügbar sein, sodass breitere Erfahrungen in die Erstellung der dann folgenden „Mandrake Linux Official“-Version einfließen sollen. Der neue Prozess wird bereits für die kommende Distribution 10.0 eingeführt, teilte das Unternehmen mit.
www.apachefriends.org/genugistgenug.html
www.six.de/
www.mandrakelinux.com/en/100beta.php3
www.pearsoned.de/ www.mysql.de /
14
PHP Magazin 3.2004
autoindex.sourceforge.net/
Anzeige
Enterprise Entwurfsmuster – Teil III
Kommando zurück! von Andreas Demmer
Das Memento-Entwurfsmuster Annähernd jede moderne Applikation verfügt über eine Funktion, um die letzten Arbeitsschritte rückgängig zu machen.Den meisten browserbasierten Anwendungen hingegen fehlt diese komfortable Möglichkeit zur Korrektur vorheriger Befehle.Dieser Artikel zeigt eine Möglichkeit auf, UndoMechanismen zumindest für objektorientierte Software vergleichsweise einfach zu implementieren.
Diese Serie beschäftigt sich mit Entwurfsmustern in PHP. Die im folgenden benötigten Grundlagen und Termini wurden im ersten Teil der Serie (Ausgabe 01.04) behandelt und mit dem Singleton ein erstes Muster eingeführt. Der zweite Teil vertiefte die Grundlagen und stellte mit dem Adapter ein weiteres Muster vor. Der nun-
Quellcode Der Quellcode zum Artikel befindet sich auf der beiliegenden CD.
16
PHP Magazin 3.2004
mehr dritte Teil behandelt einen Vertreter der objektbasierten Verhaltensmuster: Das Memento.
Die Problembeschreibung Die meisten Benutzer schätzen bei komplexen Applikationen die Möglichkeit, ihre letzte Aktion rückgängig machen zu können oder einen Zwischenstand festhalten und wiederherstellen zu können. Eine Undo-Funktion ermöglicht es beispielsweise, einen Befehl zu korrigieren, der unerwartete Auswirkungen hatte. Aber auch Speicher-Mechanismen sind vielseitig verwendbar: So bieten manche
Quellcode-Editoren etwa die Möglichkeit, den momentanen Status der Benutzeroberfläche zu speichern. Der so genannte „Snapshot“ (Schnappschuss) enthält Informationen über die geöffneten Dateien, die Menüleisten, die Position von Fenstern und vieles mehr. In einer späteren Session kann dadurch genau dieser Zustand wiederhergestellt werden und die Arbeit an der unterbrochenen Stelle wiederaufgenommen werden. Auf den ersten Blick bieten browserbasierte Anwendungen eine einfache Möglichkeit, zum letzten Arbeitsschritt zurückzukehren: Die Zurück-Taste. Spätestens beim zweiten Blick wird aber schnell klar, dass die Browser-History kein echtes Undo ist. Man kehrt zwar zur vorherigen Darstellung zurück, die getätigten Änderungen aber sind keinesfalls rückgängig gemacht worden. Bei einfachen Änderungsformularen hilft eventuell ein erneutes Absenden des Formulars mit den unveränderten Werten, gelöschte Einträge lassen sich damit nicht wieder herstellen. Ein richtiger Ansatz, eine solche Funktion zu implementieren, besteht darin, verschiedene Snapshots unserer Applikation (und aller verwalteten Daten) anzulegen und diese bei Bedarf wieder „einzuspielen“.
Die Problemlösung Die Implementierung von Undo- oder Speicher-Mechanismen bei objektorientierter Software stellt einen Programmierer vor vielerlei Aufgaben. Unter anderem sind die inneren Zustände diverser Klassen-Exemplare (Objekte) zu speichern, denn diese bilden den Status der gesamten Applikation ab. Aber auch Skripte, die nur teilweise von der Objektorientierung Gebrauch machen, lassen sich mit einem Trick in Snapshots abbilden. Dabei hilft uns eine eigens für diese Situation gedachte Klasse. Man legt alle Skript-Variablen in diesem Exemplar ab, damit sie an einer Stelle gekapselt sind. Der Vorgang kann entweder als Exemplar-Methode realisiert sein (siehe Listing 1) oder überladene Klassen und deren __set() und __get() Methoden nutzen (siehe Listing 2). Voraussetzung allerdings für das Überladen ist unter PHP 4 eine installierte Overloading-Extension; PHP 5 unterstützt Überladen Out-of-the-Box.
Entwurfsmuster – Teil III Abb. 1: Die Kategorie-Tabelle ergänzt um das Memento-Muster
Die wünschenswerte Kapselung der Exemplare stellt uns vor das Problem, den inneren Zustand eines Objektes zu speichern, ohne Details über dessen Implementierung zu verraten. PHP beherrscht zwar die vollständige Serialisierung von Exemplaren, doch spätestens mit den in PHP 5 eingeführten Sprach-Konstrukten private und protected untergräbt die Serialisierung die Kapselung von Exemplaren. Diese Problematik umgehen wir mit dem Einsatz des Memento-Musters. Ein Memento kann als Speicherstand verstanden werden. Dieser Speicherstand besitzt Kenntnis über die konkrete Implementierung des zu speichernden Zustands eines Exemplars, ist aber völlig passiv: Nur das Exemplar weist ihm einen Zustand zu oder fragt diesen wieder ab. Das API des Memento ist folgerichtig auch einzig auf das zu speichernde Exemplar ausgelegt, eine Kommunikation mit dem Skript ist nicht vorgesehen und auch nicht erwünscht. Das Skript kann aber ein vom Exemplar erzeugtes Memento vorhalten, ohne mit dem Memento-API zu kommunizieren: Das Memento wird dazu beispielsweise in einer Session abgelegt. Soll es aber auch nach Ablauf der Session noch verfügbar sein, muss das MementoExemplar in eine speicherbare Form gebracht werden. Ein mit serialize() serialisierter String des Mementos kann leicht in einer Datei oder einer Datenbank gespeichert werden. Der String selbst gibt keine Auskunft über die Implementierung des Exemplars, er enthält ausschließlich benötigte Daten zum Wiederherstellen von dessen Status. Ein Zurückwandeln des Strings
in ein Memento-Exemplar ist mit unserialize() jederzeit problemlos möglich. Der Ablauf einer Exemplar-Externalisierung (Speicherung) sieht nun wie folgt aus:
Listing 1
Nach erfolgter Speicherung in einem Memento kann der Zustand eines Exemplars jederzeit wiederhergestellt werden:
class storage { var $storage = array(); function store($index, $value) { $this->storage[$index] = $value; } function get($index) { return $this->storage[$index; } } $storage = new storage(); $storage->store(‘myVar’, 42); echo $storage->get(‘myVar’); //gibt 42 aus
Enterprise
• Das Skript erteilt dem Exemplar den Befehl, eine speicherbare Repräsentation (Memento) zu erzeugen. • Das Exemplar erzeugt ein neues Exemplar der Memento-Klasse und weist dem Memento über dessen API einen Zustand zu. • Das Exemplar liefert dem Skript eine Referenz auf das Memento (oder das Memento selbst) zurück, damit dieses im Bedarfsfalle so abgelegt werden kann, dass es auch nach Ablauf eines Skripts im Folgeskript noch zur Verfügung steht. In unserem Beispiel wird das Memento-Exemplar by-value zurückgeliefert.
ble namens $geheim. Der Wert dieser Variable wird zu keinem Zeitpunkt an das Memento übergeben und bleibt somit auch verborgen; er ist irrelevant für den Status des Exemplars. Das Ausblenden solcher Details ist der größte Vorteil des Memento-Musters, dadurch wird auch Speicherplatz gespart. Ein weiterer Vorteil des Musters kommt zum Tragen, wenn man dem Memento zusätzliche Aufgaben überträgt. Damit verlässt man zwar die engen Grenzen des Lehrbuchs, gewinnt aber völlig neue Möglichkeiten hinzu. Denkbar wäre, das Memento beim Setzen sich selbst in einer Datei oder Datenbank speichern zu lassen. Beim
Listing 2 class storage var $storage = array(); function __get($index) { if(isset($this->storage[$index])) { return $this->storage[$index]; } else {
• Das Skript erteilt dem Exemplar den Befehl, seinen im Memento gespeicherten Zustand wiederherzustellen. • Das Exemplar fragt den Zustand des Mementos über dessen API ab und stellt daraus seinen eigenen Zustand wieder her.
Auf die Frage hin, wieso ein Exemplar nicht direkt serialisiert werden darf, weise ich erneut darauf hin, dass die Implementierungsdetails eines Exemplars dem Skript nicht bekannt sein sollten. Das Exemplar des späteren Beispiels enthält eine Varia-
} overload(‘storage’); $storage = new storage(); $storage->myVar = 42; echo $storage->myVar; //gibt 42 aus
PHP Magazin 3.2004
17
Enterprise Entwurfsmuster – Teil III Abb. 2: Das MementoMuster
Lesen des Mementos könnte ebenfalls automatisiert ein beliebiger Speicherstand aus der Datenbank geholt werden. Der Vorteil: Das Memento erledigt nun die komplexe Aufgabe der dauerhaften Speicherung eines Exemplar-Status und dessen Wiederherstellung. Das Skript beauftragt dies mit zwei einfachen Methoden-Aufrufen. Die hierfür notwendigen und im Hintergrund ablaufenden Vorgänge bleiben ihm verborgen.
Listing 3 require_once(‘class_counter.inc.php’); $counter = new counter(); $snapshots = array(); //Counter erhöhen und Memento speichern $counter->increaseBy(10); $snapshots[] = $counter->createMemento(); echo $counter->read(), “\n“; //gibt 10 aus //Counter verringern und Memento speichern $counter->decreaseBy(5); $snapshots[] = $counter->createMemento(); echo $counter->read(), “\n“; //gibt 5 aus //Counter erhöhen $counter->increaseBy(10); echo $counter->read(), “\n“; //gibt 15 aus //den letzen Arbeitsschritt rückgängig machen $counter->restoreMemento(array_pop($snapshots)); echo $counter->read(), “\n“; //gibt wieder 5 aus //einen weiteren Arbeitsschritt rückgängig machen $counter->restoreMemento(array_pop($snapshots)); echo $counter->read(), “\n“; //gibt wieder 10 aus //speicherbares Memento erstellen $mementoString = serialize ($counter->createMemento()); echo $mementoString;
18
PHP Magazin 3.2004
Die Implementierung Die Möglichkeiten der Objektorientierung sind unter PHP beschränkt. Zwar bietet PHP 5 eine deutlich erweiterte Unterstützung für objekt-orientiertes Programmieren, aber einige Merkmale anderer Hochsprachen fehlen doch. Das Lehrbuch spricht beim Memento-Muster von „breiten“ und „schmalen“ Schnittstellen. Dies bezieht sich auf die Idee, einem befreundeten Kommunikationspartner (dem Exemplar) ein umfangreicheres API als einem fremden (dem Skript) offen zu legen. Dies ist unter PHP nicht ohne weiteres realisierbar, was beim Memento-Muster allerdings keinen gravierenden Nachteil darstellt. Der Programmierer sollte nur diszipliniert genug sein, die Zuständigkeiten bei der Implementierung zu beachten und nicht zu verletzen. Wie in den Beispielen der bereits vorgestellten Entwurfsmuster, ist auch dieses Beispiel sehr einfach gehalten, wodurch leider ein Stück Aussagekraft verloren geht. Der Vorteil des Memento-Musters wächst mit der Komplexität der von ihm abgebildeten Exemplare. Wenn ein Exemplar mehrere Klassen repräsentiert und/ oder auf anderen Exemplaren aufsetzt, läuft das Memento-Muster zur Hochform auf. Dies ist in folgendem Beispiel aber nicht der Fall, zu viel Code würde es erschweren, das Beziehungsgeflecht schnell zu erfassen. Dennoch lässt sich das Beispiel einfach auf komplexere Anwendungsfälle adaptieren. Die vorgestellte Konstellation kennt drei Teilnehmer: Das Skript (Listing 3), ein
Exemplar einer Counter-Klasse (Listing 4) und deren Memento (Listing 5). Das Skript verwendet das Array $snapshots, um in ihm die Mementos des Counter-Exemplars abzulegen. Nachdem das Exemplar instanziert und sein Zustand mit einem Methodenaufruf modifiziert wurde, wird ein Memento angefordert und in $snapshots abgelegt. Das Memento ist Exemplar einer einfachen Klasse, welche lediglich zwei Methoden kennt: Eine, um Werte im Memento-Exemplar abzulegen, und eine, um diese wieder abzufragen. Die CounterMethode $counter->read() gibt einen Teil des inneren Counter-Zustands aus und dient zur Kontrolle der mit $counter->increaseBy($value) und $counter->decreaseBy ($value) gemachten Modifikationen. Es folgen weitere Methodenaufrufe an das Counter-Exemplar, deren Auswirkung wieder in neuen Mementos festgehalten wird. Zum Wiederherstellen der Mementos existiert die Methode $counter->restoreMemento($memento). Der Parameter $memento ist hierbei optional: Fehlt dieser, wird das letzte Memento genutzt. Da zuvor aber mehrere Mementos angelegt wurden, übergibt array_pop($array) das jeweils letzte Element (bzw. Memento) aus $snapshots und entfernt es gleichzeitig aus dem Array. Danach wird jeweils per $counter->read() der Zustand des Counter-Exemplars kontrolliert. Die Ausgaben zeigen nun, dass das Counter-Exemplar nach jedem Aufruf der restoreMementoMethode chronologisch invers seine letzten Zustände wieder annimmt. Die letzten beiden Zeilen des Skripts erfüllen keine für das Beispiel nötige Funktion mehr, zeigen aber einen einfachen und schnellen Weg, ein Memento in eine speicherbare Form zu bringen. Die Besonderheit des soeben vorgestellten Beispiels liegt darin, dass dem Skript statt einer Referenz auf das Memento eine Kopie zurückgeliefert wird. Da mit mehreren Mementos gearbeitet wird, würde eine erneute Anforderung eines Mementos vorherige überschreiben, alle Referenzen in $snapshots würden auf ein und dasselbe Memento verweisen. Auf der beiliegenden Heft-CD findet sich das Beispiel noch einmal in digitaler Form mit ausführlichen phpDoc-Kommentaren versehen. Unter [1] können so-
Entwurfsmuster – Teil III wohl Funktion als auch Quelltext des Beispiels online betrachtet werden.
Die Bewertung Der Ansatz, den momentanen Status einer Applikation am inneren Zustand einiger
Listing 4 class counter { var $counter = 0; var $memento = FALSE; var $geheimnis = 'streng geheim!'; function increaseBy($value) { $this->counter += $value; } function decreaseBy($value) { $this->counter -= $value; } function read() { return $this->counter; } function createMemento() { if(!is_object($this->memento)) { require_once('class_memento. inc.php'); $this->memento = new memento(); } $this->memento->set ('counter', $this->counter);
benutzter Objekte festzumachen, ist praktikabel: Die Kapselung der Objekte bringt den Vorteil mit sich, dass auch alle zur Wiederherstellung des Status benötigten Daten gekapselt sind. Das Memento bietet eine Möglichkeit, den inneren Zustand der Objekte zu externalisieren, ohne deren Kapselung zu verletzen. Dies versetzt uns in die Lage, jeden beliebigen Zustand eines Objekts (und damit den Status der gesamten Applikation an sich) wiederherstellen zu können. Gerade bei komplexeren, mehrschrittigen Arbeitsabläufen ist dies ein großer Zugewinn an Usability, da unerwartete Resultate eines Befehls einfach rückgängig gemacht werden können. Wie bei allen musterbasierten Entwürfen sind auch beim Memento der Overhead an Code und die damit einhergehenden Performance-Einbusen ein nicht zu vernachlässigender Nachteil. Es existiert jedoch keine sinnvolle Alternative, die eine objektorientierte Undo-Funktionalität mit gleichem, geringen Aufwand realisiert. Eine Problematik, welche von diesem Artikel ausgeklammert wird, ist die Verwendung einer Datenbank: Einmal physisch gelöschte Datensätze lassen sich
Rückblick: Das AdapterMuster und Overloading Das im letzten Teil der Serie vorgestellte AdapterMuster lässt sich mit dem Funktionsumfang von PHP 4 nur mit großem Aufwand an Quellcode umsetzen. Einen einfacheren und zugleich effizienteren Weg bietet die Overloading-Extension, die unter PHP 4 nur experimentell integriert, in PHP 5
$this->counter = $this-> memento->get('counter');
jedoch fester Bestandteil ist.
return TRUE;
se wird überladen und ein Exemplar instanziert.
}
Das Prinzip ist denkbar einfach: Die Adapter-KlasDiesem wird mitgeteilt, auf welchen Host die Adapter-Klasse aufsetzen soll. Nun weist man einer nicht vorhandenen Variable dieses Exemplars ein
Listing 5 class memento { var $storage = array();
}
Array, gefüllt mit Parametern, als Wert zu: $adapter->variable = array(„param1“, „param2“); . Die Overload-Methode __set() bekommt dadurch zwei Parameter übergeben. Der erste Parameter
function get($key) { return $this->storage[$key]; }
vom Typ String enthält den Namen der von uns ge-
function set($key, $value) { $this->storage[$key] = $value; }
enthält das zugewiesene Array. Das Adapter-Exem-
nutzten, aber nicht existenten Variable. Wir interpretieren ihn als Methodenname. Der zweite Parameter plar verfügt nun über alle nötigen Informationen, um
Enterprise
auch beim Rückgängigmachen eines Befehls nicht wiederherstellen. Ein möglicher Ansatz ist hier das Kennzeichnen von Datensätzen als gelöscht, etwa durch setzen einer Flag. Eine solche Kennzeichnung kann beim Undo leicht wieder aufgehoben werden. Im Gegensatz zu vielen anderen Entwurfsmustern ist speziell in Bezug auf PHP wenig bis gar keine Literatur über das Memento-Muster vorhanden. Andere in PHP realisierte Ansätze sind selbst nach einiger gewissenhafter Recherche weder auf php::patterns [2] noch in Google zu finden. Leser, welche das Memento-Muster aus anderen Sprachen kennen, mögen mir deshalb bitte verzeihen, dass ich stellenweise recht eigene Wege bei der Implementierung des Beispiels eingeschlagen habe. Auch ist die Theorie für eine Adaption in PHP ausgelegt und deshalb etwas vereinfacht dargestellt. Abschließend betrachtet, stellt das Memento-Muster eine oftmals nicht ganz triviale, aber doch effektive Methode dar, den Benutzerkomfort einer Applikation durch Undo- und Speichermechanismen zu erhöhen. Der nächste Teil der Serie stellt mit dem Beobachter (Observer) ein weiteres Entwurfsmuster vor. Es definiert Abhängigkeiten zwischen Exemplaren so, dass eine Änderung am Zustand eines Exemplars abhängige Exemplare benachrichtigt. Diese können sich daraufhin selbstständig aktualisieren. Im Bezug auf das Memento-Muster bedeutet dies, dass bei der Anforderung eines Mementos alle beteiligten Exemplare ihre Mementos aktualisieren und zur Verfügung stellen können. Andreas Demmer ist Senior Developer bei der regiomedia gesellschaft für neue medien mbH und Mitbegründer von php::bar, dem Treffpunkt für Einsteiger und Profis (www.phpbar.de/). Mehr über sich und seine Projekte verrät er auf seiner Website www.andreas-demmer.de/. Fragen beantwortet er gerne unter mail@ andreas-demmer.de.
Links
die gewünschte Methode des Hosts aufzurufen und
[1] memento.andreas-demmer.de/
ihr die Parameter zu übergeben.
[2] www.phppatterns.com/
PHP Magazin 3.2004
19
Startup Komprimierte Dateien es, man ahnt es bereits, auch eine „saubere“ Methode: Mittels eines API für den Einsatz mit PHP. Im Gegensatz zu den Unwägbarkeiten eines externen Prozesses („Was ist eigentlich warum schief gegangen? Sind alle Daten da, wo sie sein sollten?“), steht einem über ein API eine strukturierte Übergabe von Informationen zur Verfügung, auf die man schon aus Sicherheitsgründen nicht verzichten sollte.
Gzip aka zlib
Verpackungskünstler von Arne Blankerts
Mit komprimierten Daten arbeiten
Das Web-Formular zum Übertragen von Dateien auf den Server funktioniert eigentlich perfekt, doch um große Dokumente oder gleich eine ganze Gruppe von Dateien zu senden, fordert der Kunde die Möglichkeit, mit gepackten Dateien arbeiten zu können, welche dann natürlich vollautomatisch auf dem Server ausgepackt werden sollen.
20
PHP Magazin 3.2004
Bandbreite ist kostbar – oder zumindest war sie es einmal. Und um mit möglichst wenig Daten möglichst viel an Information zu übertragen und vor allem, um auf Datenträgern Platz sowie beim Übertragen Zeit zu sparen, wurden wohl Packprogramme entwickelt. Die bekanntesten ihrer Art sind in Form von gzip [1], bzip2 [2] oder infoZIP [3] im Netz zu finden. In Zusammenarbeit mit dem Urahn aller Backup-Programme, dem tar [4] (was für Tape ARchiver steht), werden Daten effizient und vor allem platzsparend gesichert oder verschickt. Doch kommen wir zurück zu unserem Problem: Neben dem wenig effizienten und vor allem fehleranfälligen und notorisch unsicheren Weg, den Archiven per exec(), also per passendem Entpacker über die Kommandozeile, zu Leibe zu rücken, gibt
Beginnen wir mit der bekanntesten und am weitesten verbreiteten Bibliothek: der zlib. Auch wenn die zlib-Funktionalität nicht standardmäßig aktiviert ist, so kenne ich keinen ISP, der diese nicht eingebunden hat. Wer sein PHP selbst baut, kann die Erweiterung, auf deren Vorhandensein auch etliche andere Erweiterungen angewiesen sind, beim Configure mit --with-lib einbinden. Mit Hilfe der zlib ist es möglich, auf .gzkodierte Daten und Datenströme zuzugreifen oder sie zu erzeugen. Der in PHP eingebaute gz_output_buffer beispielsweise ermöglicht das Übertragen der erzeugten Seite im komprimierten Format an den Browser. An der von PHP dann automatisch im Header mitgeschickten Information erkennt dieser, dass es sich um gzipkodierte Daten handelt und extrahiert die Seite automatisch und für den Benutzer vollkommen transparent. Nach dem gleichen Prinzip arbeitet im übrigen das Apache-Modul mod_gzip. Für die eigentliche Arbeit in PHP stehen die gewohnten File-Funktionen zur Verfügung: Statt fopen() wird jedoch gzopen() verwendet, an die Stelle von fwrite() und fread() treten entsprechend gzwrite() und gzread(). Wem das mit Recht zu umständlich ist, der kann auch auf den seit PHP 4.0.4 verfügbaren fopen-Wrapper zlib: zurückgreifen: fopen(‘zlib:dateiname.gz’,’r’), wer mit PHP 4.3.0 oder später arbeitet, kann sich auch die neu eingeführte StreamTechnik zu nutze machen: fopen(‘compress.zlib://dateiname.gz’,’r’); Da mit gzip keine Struktur verwaltet werden kann, ist die Verwendung auf eine Datei oder einen Datenstrom beschränkt. Dass diese Limitierung nicht zwangsweise ein Nachteil sein muss, zeigt die Verbindung mit tar-Archiven, denen wir uns später widmen werden.
Komprimierte Dateien bzip2 bzip2 ist nicht nur vom Namen her dem gzip sehr ähnlich, sein API sowie die Funktionsweise sind nahezu identisch – was viele als Vorteil betrachten und bzip2 deshalb sowie der deutlich besseren Packrate wegen als designierten Nachfolger von gzip handeln. Wenn schon die weiter verbreitete zlib nicht standardmäßig aktiviert ist, so wundert es wenig, dass man für den Einsatz von bzip2 ebenfalls selbst Hand anlegen muss: -with-bz2 heißt die Option, die dem Configure mitteilt, dass man neben diversen anderen Erweiterungen auch die Funktionalität dieser Bibliothek verwenden möchte. Wenn das API schon sehr ähnlich ist, wird statt mit gzopen() folgerichtig mit bzopen()
Listing 1 aArchive_tar Beispiel
Anzeige
Startup Komprimierte Dateien wird durch tar praktisch aufgehoben: Da ein Bandlaufwerk kein Dateisystem kennt, musste die auf ihm gespeicherte Information seine Strukturinformation selbst mitbringen. Da tar getreu seinem ursprünglichen Verwendungszweck genau dieses tut, erhält man eine große, wenn auch noch unkomprimierte Datei, die man dafür jetzt umso effektiver verkleinern kann. Wer sich auf Download-Seiten umgesehen hat, wird vielleicht schon gemerkt haben, dass gzip bzw. bzip2 komprimierte TarArchive in der Regel deutlich kleiner sind, als andere Formate. Die Verwendung der PEAR-Klasse Archive_Tar ist denkbar einfach und kapselt den Benutzer fast vollständig von der eigentlichen Arbeit ab. Die genaue Verwendung ist dem erklärenden Beispiel in Listing 1 zu entnehmen. Eine genaue Dokumentation aller Funktionen liegt der PEAR-Klasse in einem Readme bei, welches mit kleinen Beispielen unterlegt eine leicht zu verstehende Einführung bietet.
ZIP Das von der Firma pkware [6] zu „seligen“ DOS-Zeiten entwickelte Format ZIP wird heute zumeist im Windows-Umfeld angetroffen. Neben dem wohl bekanntesten GUI-Programm WinZIP sind vor allem die freien Versionen der InfoZipGruppe [3] im Einsatz. Um unter PHP mit diesem Packformat zu arbeiten, bedarf es zum einen der Erweiterung zip (--withzip), zum anderen der von dieser Erweiterung verwendeten Bibliothek zziplib [7]. Da weder Suse Linux noch Red Hat/Fedora diese in ihrer Distribution haben (für
Mandrake liefert das Sourceforge-Projekt passende RPM-Pakete), ist zumindest für diese Linux-Versionen Handarbeit gefragt. Ist diese (kleine) Hürde erst einmal genommen, wartet noch eine kleine Enttäuschung: Die ZIP-Erweiterung kann nur lesen, das Schreiben von zip-komprimierten Dateien ist bis dato nicht möglich. Folglich beschränkt sich das Beispiel in Listing 2 auf das Anzeigen von Informationen über alle im Archiv enthaltenen Dateien sowie den Inhalt der zuletzt gefundenen Datei. Wohl ebenfalls aus diesem Grund haben sich die Entwickler von PHP für die neueren Versionen entschieden, die ZIP-Erweiterung aus der Standarddistribution in die PECL [8] zu verschieben. Wer also mit einer aktuellen PHP-Version arbeitet, kann anstatt seine Installation von Grund auf neu zu bauen, auch mit dem PEAR-Installer diese Erweiterung aktivieren: pear install zip
LZF Eine ebenfalls im PECL-Repository befindliche Erweiterung ist LZF [9]. Bei der Installation gilt es zunächst einmal, zwischen Geschwindigkeit oder besserer Packrate zu wählen, danach stehen einem ganze drei Funktionen bereit: Neben einer, die einem die eben getroffene Entscheidung in Form einer 0 für Packrate oder 1 für Geschwindigkeit bestätigt und treffend lzf_optimized_for() lautet, die beiden Funktionen lzf_compress() und lzf_decompress(). Der einzige Sinn und Zweck dieser Erweiterung ist es, den übergebenen Text zur weiteren Verarbeitung zu verpacken und dabei natürlich
Anzeige
22
PHP Magazin 3.2004
seine Größe drastisch zu reduzieren. Gerade im Einsatz mit Datenbanken könnte dies – solange keine Volltextsuche implementiert werden soll – von Vorteil sein.
Geht doch! Nachdem wir jetzt einen Überblick über unsere Möglichkeiten haben, scheint die anfangs vielleicht eher schwierig anmutende Aufgabe, auch mit komprimierten Daten zu arbeiten, schon gar nicht mehr so schwer. Sofern die Datei also im ZIP- oder Tar-Format (egal ob gzip/bzip2 gepackt oder ganz unkomprimiert) vorliegt, bietet PHP uns mit den passenden Erweiterungen die passenden Werkzeuge, um die Daten sicher und kontrolliert zu extrahieren. Auch das Erzeugen – zumindst von TarArchiven – stellt kein Hindernis mehr da. Und Sie wollten dem Kunden schon sagen, er muss leider alle Dateien einzeln übertragen und bei großen Dateien halt ein wenig warten … Arne Blankerts ([email protected]) ist Leiter der Entwicklung bei der Sales Emotion GmbH in Hamburg.
Automatenfotos von Thorsten Olscha und Jörg Stroisch
Dynamische Bildbearbeitung mit PHP Für die meisten Arbeitsschritte gibt es in der Küche Helfer: Den Teig knetet eine Maschine, Obst wird mit dem Mixer püriert und Nüsse mit einer Mühle fein gemahlen.Es wäre auch zu zeitraubend, wenn alle diese kleinen Schritte immer wieder neu und von Hand gemacht werden müssten.Wie könnte es auch anders sein – das gilt natürlich auch fürs Web. Es macht keinen Spaß, eine Website immer wieder neu in HTML zu gestalten. Wenn sie dann auch noch aktuell bleiben soll, also ständig neue Artikel und Fotos online gestellt werden, ist eine Automatisierung der Vorgänge sehr sinnvoll. Sollen Grafiken bearbeitet werden, helfen dabei Programmcodebibliotheken. Und: Dank zahlreicher kostenloser Tools müssen heute selbst komplexe Darstellungsideen wie Bildergalerien nicht mehr von Hand programmiert werden. Obwohl alle hier vorgestellten Programme ihren Schwerpunkt im „Backoffice“ haben, beeinflussen sie doch oft maßgeblich das Erscheinungsbild einer Site.
Codebibliotheken Bibliotheken sind Codesammlungen, die dem Programmierer die Lösung spezieller Probleme erleichtern. Natürlich muss so auch beim Thema Bilder nicht jedes Mal das Rad neu erfunden werden. Alle Skriptsprachen des Webs bieten Erleichterungen, wenn ein JPEG-Bild verkleinert oder eine GIF-Grafik verzerrt werden soll. Voraussetzung ist meist, dass eben eine entsprechende Bibliothek auf dem Server installiert ist. „On the fly“ – also erst bei Anforderung durch den Nutzer – lassen sich dabei beliebig Grafikaktionen steuern. Da dies allerdings meist gewaltige Probleme bei der Performance bringt, werden Bilder selten dynamisch auf der Homepage einer frei zugänglichen Site erzeugt. Für einen Großteil der Bilder ist das auch völlig unnötig. Und auch bei hochaktuellen Grafi-
ken – etwa einer Wetterkarte oder der DAX-Kurve – ist es sinnvoll, zwischen statischem Hintergrund und dynamischer Kurve oder Temperatur auch technisch zu unterscheiden. Im verwaltenden Bereich – etwa bei Content Management-Systemen – werden ebenfalls Bibliotheken eingesetzt, um für Zeitersparnis und mehr Komfort in der Bildintegration zu sorgen.
Pro und Kontra Nur Vorteile haben Grafikbibliotheken dabei nicht: Es ist derzeit nicht sinnvoll, sie direkt in die Website zu integrieren, weil dadurch die Performance gemindert wird. Außerdem sind sie reine Programmcodes: Programmierspaß und -kenntnis ist deshalb unbedingte Voraussetzung für einen vernünftigen Einsatz. Entweder hilft bei den Grundzügen ein gutes Handbuch – oder manche Aktionen werden im stundenlangen Versuchs- und Irrtumsverfahren gemacht. Ein weiterer möglicher Nachteil: Auch auf die Qualität der Bilder sollte ein wachsames Auge geworfen werden. Zumindest bei einfachen Aktionen – wie dem Verkleinern von Bildern – haben wir aber etwa bei der gängigen GD-Bibliothek für die Skriptsprache PHP nur gute Erfahrungen gemacht. In der Bearbeitungsvielfalt sind aber die Offline-Vorbilder wie Photoshop oder Fireworks noch wesentlich besser als alle Bibliotheken. Ein entscheidender Vorteil ist aber: Wurden die benötigten Anforderungen einmal intelligent programmiert, gehen sonst sehr aufwändige Aufgaben plötzlich schnell
Startup
und automatisiert von der Hand: Thumbnail-Galerien zum Beispiel lassen sich dann mittels Eingabeinterface sehr einfach installieren. In der Offline-Welt muss für solche Zwecke jedes Foto zwei Mal abgespeichert werden und hinterher noch mehr oder minder kompliziert in eine HTMLSeite integriert werden. Mit dem Tool ist nur noch das Hochladen einer Bildgröße auf dem Server notwendig.
Bildgröße verändern mit der GD-Bibliothek Das Arbeiten mit der GD-Bibliothek ist nicht ganz einfach. Anders als in HTML muss die Programmierung ganz genau sitzen – ein fehlendes Anführungszeichen sorgt sonst für unzählige und ungenaue Fehlermeldungen. Systemvoraussetzung für dieses Beispiel ist wieder die Skriptsprache PHP und eine auf dem Server vorinstallierte GD-Bibliothek. Fast alle Hoster, die PHP anbieten, arbeiten auch mit der GD-Bibliothek, sodass das kein Problem sein sollte. Schritt 1 – Einladedatei anlegen: Zunächst müssen Sie ein Formular schaffen, mit dem die gewünschte Grafik später auf den Server geladen werden kann. Das ist ganz einfach – in unserem Beispiel heißt diese Datei bild.html (siehe Listing 1). Der Code ist schnell erklärt: In Zeile 1 wird das Formular geöffnet. Wichtig ist, dass auf die PHP-Datei bild.php verwiesen und das als ENCTYPE der Wert multipart/form-data angegeben wird, damit eindeutig geklärt ist, dass Daten von unterschiedlicher Qualität übertragen werden. In den Zeilen 3 bis 5 wird die Datei eingeladen sowie der Verkleinerungsfaktor und der Wunschdateiname eingetragen. Natürlich könnten diese Formularfelder
Listing 1 1 2
Bild verkleinern
3
Bild einladen:
4
Verkleinerungsfaktor:
5
Bildname:
6 7
PHP Magazin 3.2004
23
Startup Dynamische Bildbearbeitung Gewinnspiel In Kooperation mit dem Markt + Technik Verlag verlost das PHP Magazin fünf Mal das Buch „Webgrafik-Optimierung. Was Sie über das Arbeiten mit Grafiken im Web wissen müssen“ von Thorsten Olscha und Jörg Stroisch, das 2003 bei Markt + Technik erschienen ist. Beantworten Sie einfach die folgende Frage Welche Werte liefert der Befehl GetImageSize? im Formular unter www.phpmag.de/gewinnspiel. Die ersten fünf richtigen Einsendungen gewinnen. Das Gewinnspiel ist ab dem 17.03.04 online.
noch wesentlich umfangreicher gestaltet werden – wir haben uns das aber bei diesem Beispiel gespart. In Zeile 6 wird der Absendebutton erzeugt und in Zeile 7 das Formular geschlossen. Schritt 2 – Umwandlungsprogramm erstellen: Die Aufnahme der Grafik und die Umwandlung in ein verkleinertes Bild ist hingegen nicht mehr so einfach. Ein recht komplexes Programm ist dazu nötig – wir nennen es bild.php (vergl. Listing 2). Die Erklärung des Programms: In Zeile 1 und 49 wird das PHP-Programm geöffnet und geschlossen. Für persönliche Einstellungen sind ausschließlich die Zeilen 2 und 3 wichtig: Hier müssen Sie Ihren Root-Pfad eingeben sowie den Namen des Bilderverzeichnisses benennen. Beachten Sie dabei: Sofern Sie das Programm mit dem falschen Root-Pfad starten, funktioniert gar nichts. Sie erhalten aber auch gleich in der Fehlermeldung den richtigen Root-Pfad angegeben. In den Zeilen 4 bis 12 wird jetzt das Ursprungsbild erzeugt. Das heißt, dass die durch den Upload-Vorgang erzeugte temporäre Bilddatei als Kopie in einem tatsächlich zugänglichen Verzeichnis angelegt wird. In den Zeilen 13 bis 15 werden die Rahmendaten der Ursprungsdatei ermittelt. GetImageSize ist der dazu nötige Befehl der GD-Bibliothek. Wird dieser Befehl aufgerufen, dann wird ein so genanntes Array mit vier Werten zurückgegeben. 0 steht für die Breite, 1 für die Höhe, 2 für den Typ und 3 für die Breite und Höhe als String (width=150 height=300) des Bildes. Wir benötigen nur die Breite und Höhe, die in den Zeilen
24
PHP Magazin 3.2004
14 und 15 dann in eine Variable gelegt werden. Zeile 16 ist eine zentrale Funktion der GD-Bibliothek. Mit der Funktion ImageCreateFromJpeg wird aus einer Datei heraus ein JPEG gelesen. Will heißen: Das vorhandene Bild wird als Variable in den Speicher geholt, die fortan als JPEG bearbeitet werden kann. Die Funktion besteht analog für die Grafikformate GIF, PNG und WBMP. Es ändern sich einfach die letzten Buchstaben. In den Zeilen 17 und 18 berechnet das Programm nun das angestrebte neue Format des Neubildes. Dazu wird einfach Höhe und Breite durch die Variable $faktor dividiert. Zeile 19 und 20 gehen auf Nummer Sicher: Die neuen Werte werden als Variablen vom Typ Integer definiert, weil sonst die Folgefunktion nicht klappt. In den Zeilen 22 bis 34 wird mit der Funktion ImageCopyResized zum einen eine definierte Kopie der Ursprungsdatei erzeugt und außerdem gleich in ihren Maßen verändert – also in unserem Beispiel verkleinert. Dieser Funktion können zehn Werte übergeben werden. Dabei müssen Sie die Reihenfolge beachten (!): Zielvariable für das Bild (neues_bild), Ursprungsvariable (uebergabe_bild), zwei Koordinaten für den Beginn des neuen Bildes (gemessen links oben), zwei Koordinaten für den Beginn des Ursprungsbildes, Breite des neuen Bildes (neue_breite), Höhe des neuen Bildes (neue_hoehe), Breite des Ursprungsbildes (alte_breite), Höhe des Ursprungsbildes (alte_hoehe). Die vier Variablen für die Koordinaten sind im Beispiel nicht gesetzt – damit ließe sich die Kopie verschieben. In Zeile 35 wird nun aus der Bildvariablen eine tatsächliche Datei erzeugt, sie wird „geschrieben“. Der GD-Befehl ImageJpeg erledigt dies – auch hier gibt es analog für andere Grafikformate einen Befehl. Zeile 36 und 37 sind noch einmal wichtig: Damit werden die erzeugten temporären Dateien nun wieder vom Server gelöscht. In den Zeilen 39 bis 48 werden die Ergebnisse im Browser ausgegeben. Natürlich lässt sich das dargestellte Beispielprogramm noch beliebig erweitern. Wichtig wäre so eine Fehlerabfangroutine bei zu großen Dateien und falschen Dateiformaten. Das Programm
kann auch gleich für die anderen unterstützten Grafikformate gültig sein. Eine Einschränkung gibt es aber für die GD-Bi-
Listing 2 1
Dynamische Bildbearbeitung
Startup
oder JavaScript, aber auch in den weniger gängigen freien Konkurrenten Perl oder Python, finden Sie auf Seiten wie www. sourceforge.com/ oder www.hotscripts. com/. Und das Gute daran: Fast jede Programmidee ist auch frei zugänglich erhältlich und kostet keine Lizenzgebühr. Abb. 1: Die fertige Datei bild.html sieht so aus
bliothek: Durch die Lizenzforderungen an das GIF-Format unterstützt sie in der neueren Version GIF nicht mehr, wenn der Hoster nicht ausdrücklich dafür sorgt. Schritt 3 – Auf den Server laden und verwenden: Die Dateien werden mit einem einfachen Editor abgetippt, mit den entsprechenden Dateiendungen gespeichert und dann anschließend mit einem FTP-Programm in ein Verzeichnis gelegt. Außerdem müssen Sie noch das Unterverzeichnis bilder anlegen, weil wir dieses im PHP-Programm zur Abspeicherung der Bilder ansteuern. Nun können Sie die Datei bild.htm einfach mit einem Browser anwählen und dort die Einstellungen vornehmen. Funktioniert alles fehlerfrei, sind nach dem Klicken das Absendebuttons zwei unterschiedlich große Bilder auf Ihrem Webserver gespeichert.
Nützliche Erweiterungen Für fast jedes Problem gibt es eine Lösung: Das Web ist – vor allem dank der Open Source-Gemeinde – zu einer riesigen Fundstelle für alle nur erdenklichen Programmiercodes geworden. Besonders profitieren davon Websiteentwickler: Unendlich viele kleine Codeschnipsel und unzählige ausgewachsene Serverprogramme vor allem in den Programmiersprachen PHP
Thumbnail-Gallerien Ein häufiges Anwendungsbeispiel im Web sind Thumbnail-Galerien: Ob auf privaten Websites oder bei Fotoagenturen – gerne werden die angebotenen Bilder zunächst mit einer fingernagelgroßen Version – englisch: Thumbnail – des Bildes angekündigt. Beim Klick auf das Bild wird es dann in einer ladeintensiven, großen Version angezeigt. Thumbnail-Galerien sind außerdem ein exzellentes Beispiel für dynamische Bildbearbeitung, werden doch oft mit speziellen Administrationswerkzeugen automatisch solche Seiten erstellt.
Workshop: Thumbnail-Gallerien mit PHPGraphy Am Beispiel der Website www.webwave. de/, einem Onlinemagazin im Web, wird nun die Integration einer Thumbnail-Galerie mit PHPGraphy demonstriert. Ein Nachteil dieses Programms: Es ist noch nicht voll ausgereift und erst in einer BetaVersion vorhanden. Einige Darstellungsund Funktionsfehler sind deshalb noch nicht ausgeräumt. Ein Vorteil: Der Code lässt sich relativ einfach an die eigenen Bedürfnisse anpassen. Schritt 1 – Einrichten von PHPGraphy: Zunächst muss das Paket von der Website heruntergeladen und anschließend entpackt werden. Diese Galerie kommt mit
Bildbibliotheken
Thumbnail-Galerien
GD ist eine in ANSI C geschriebene Grafikbiblio-
Die Menalto-Galerie ist sehr gut ausgebaut und
thek zur dynamischen Bildbearbeitung. Sie findet
bietet allen Komfort. Dafür ist sie in der Einrich-
zum Beispiel als Standard in PHP-Installationen ih-
tung auch nicht ganz einfach.
ren Einsatz. Viele Hoster setzen sie ein.
gallery.menalto.com/
www.boutell.com/gd/
Eine etwas einfacher gehaltene Galerie, gut zum Ausbau geeignet. Sie benötigt dabei keine Daten-
Die Bibliothek Imagemagick erlaubt Grafikmanipu-
bank und arbeitet mit Perl.
lationen mit vielen Programmiersprachen und
ids.sourceforge.net/
unterstützt dabei die Konvertierung in sehr viele
PHPGraphy ist eine sehr flexible, kleine Oberfläche
Grafikformate.
zum Erstellen von Thumbnail-Galerien. PHP und ei-
www.imagemagick.org/
ne MySQL-Datenbank ist Voraussetzung. phpgraphy.sourceforge.net/
Anzeige
Startup Dynamische Bildbearbeitung Abb. 2: Diese Bilder erzeugt das kleine Programm nun automatisch im Browserfenster und speichert sie gleich statisch ab
Abb. 3: WebWave begleitet einen Fotoworkshop von Zivildienstleistenden im Internet. Die Artikel sind reichlich bebildert.
nur wenigen Skripts aus, was sie in der Anpassung sehr einfach macht. Die Datei config.inc im Hauptverzeichnis beinhaltet alle notwendigen Installationsoptionen. Mit einem einfachen Texteditor wird diese Datei geöffnet. Anschließend geht es um Wunschoptionen zur Größe der Thumbnails und zur verwendeten Grafikbibliothek. Sehr wichtig ist auch der Pfad, in dem die Bilder gehortet werden sollen. Wenn er hinterher nicht stimmt, klappt gar nichts. Sie können dann aber anhand der Fehlermeldung sehr leicht erahnen, wie der richtige Pfad aussehen muss. Schritt 2 – Anpassung der Seitendarstellung: Auch die Anpassung des Grunddesigns ist ziemlich einfach lösbar. Über die Dateien footer.inc.php und header.inc.php kann der Quellcode der eigenen Website um den Galeriecode herumgepackt werden. Wem das zu umständlich ist und wer sich mit einem Standarddesign zufrieden gibt, der kann auch einfach ein paar Vorgaben über style.inc.php machen. In unserem Beispiel wurden die Logozeile und die Navigation einfach als header.inc.php definiert und in footer.inc.php die zahlreich geöffneten Haupttabellen wieder geschlossen. Außerdem wurde die Datei index.php in bilder.php umbenannt, da die Website nicht mit der ThumbnailGalerie starten soll. In Zeile 465 muss dann allerdings auch der Dateiname geändert werden, damit hinterher noch eine Bearbei-
Tipp Verzichten Sie auf die Einbindung von Countern auf Ihrer Website! Das wirkt unprofessionell, zumal die Ergebnisse leicht zu manipulieren sind. Die Zugriffe Ihrer Site können Sie besser über eine Weblog-Auswertung begutachten. Hier geht es nur um das Programmierprinzip.
26
PHP Magazin 3.2004
tung der Seite im gesicherten Bereich möglich ist. Weitere Details von der Webseite zu entfernen, ist leider etwas kompliziert. Schritt 3 – Installation von PHPGraphy: Nun wird der komplette entpackte Ordner mit den veränderten Seiten auf den Server gespielt – am besten in das neu angelegte Verzeichnis phpgraphy. Die MySQL-Datenbank muss nun ebenfalls noch eingerichtet werden. Leider klappt dies nicht automatisch, sondern nur via Eingabebefehle. Empfehlenswert ist hier eine Plattform wie phpMyAdmin – da kann dann die Datei sql_tables einfach hochgeladen werden. Leider ist damit die Installation noch immer nicht abgeschlossen: Damit nicht jeder an die Bilder kommt, müssen noch in der Tabelle users das Passwort, der Benutzername und der cookieval verändert werden. In unserem Beispiel haben wir für beides test ausgewählt. Eine Administrationsschnittstelle zur Veränderung bietet PHPGraphy leider nicht. Schritt 4 – Einrichten der Fotos: Alles weitere ist nun recht einfach: Die Fotos, die in der Thumbnail-Galerie dargestellt werden sollen, werden nun einfach in den dafür vorgesehenen Ordner geladen. Beim ersten Aufruf der Seite erzeugt das Programm nun automatisch die Galerie. Über die Funktion login.php gelangen Sie nun in das Administrationsmenü: In unserem Beispiel müssen Sie sich mit test einwählen. Hier können Sie nun zu den einzelnen Fotos die Beschriftungen verändern oder auch neue Fotos generieren. Wichtig beim Arbeiten mit dem Skript: Werfen Sie ein wachsames Auge auf die Qualität der Thumbnails, denn sie ist nicht immer optimal. Auch die Textbeschriftungen werden nicht immer richtig
abgespeichert. Da gibt es also notfalls noch Handlungsbedarf. Beim Webwave-Magazin werden die Fotos zu den Artikeln mit einem eigenen Content Management-System in verschiedenen Größen abgespeichert. PHPGraphy ist allerdings ein für sich genommen unabhängiges Programm, das im CMS schon vorhandene Bildunterschriften, Quellenangaben und andere Informationen nicht so einfach übernehmen kann. Aber: Der Code ist insgesamt so einfach gehalten, dass er sich relativ problemlos an eigene Bedürfnisse anpassen lässt.
Counter Counter sind nach wie vor beliebt im Internet: Obwohl gerade auf professionellen Sites die kleinen Zähler des Seitenaufrufs fast komplett verschwunden sind, binden Hobby-Webgestalter diese immer noch sehr gerne ein. Ein Zählmechanismus kann aber zum Beispiel interessant sein, wenn Bannerwerbung an einem Platz auf der Homepage wechseln soll. Mit einem Zählmechanismus wird dann einfach bei vorbestimmten Zahlen auf unterschiedliche Banner geschaltet. Die Programmierung ist einfach. Wir wollen an dieser Stelle nicht mit statischen Grafiken für die einzelnen Zahlen arbeiten, sondern demonstrieren, wie die Zählung wirklich „on the fly“, also dynamisch erzeugt wird. Also: Der aktuelle Zählerstand wird mit Hilfe von PHP und der GD-Bibliothek bei jedem Aufruf der Seite neu erzeugt.
Programmierbeispiel mit PHP und GD-Bibliothek Wir teilen das Programmierbeispiel in zwei einzelne Dateien auf, damit es übersicht-
Dynamische Bildbearbeitung licher wird. Später müssen aber beide in das gleiche Verzeichnis auf dem Webserver gespielt werden. counter.php beinhaltet alle notwendigen Funktionen, um jeweils die Hitzählung zu erhöhen und anschließend aus der aktuellen Zahl ein Bild zu generieren. Außerdem wird auch gleich das Ergebnis an den Browser ausgegeben. .Zaehler ist dann eine kleine Datei, in der die aktuelle Hitzahl abgespeichert wird. Schritt 1 – Erstellen der Datei counter.php: Das Programm counter.php besitzt drei Abteilungen. Die Funktion function zaehlen (Zeilen 2 bis 12) zählt nicht nur die Hits, sondern liest zunächst die aktuelle Zahl aus der Datei .zaehler heraus, um sie hinterher wieder aktualisiert dort abzuspeichern. • Dazu wird zunächst in Zeile 5 die Datei .zaehler zum Lesen (r) geöffnet. • In Zeile 6 wird dann der gerade aktuelle Zählerstand ermittelt, also konkret die Werte per fread. Wichtig ist hierbei, per filesize die Größe der Gesamtdatei zu bestimmen, damit auch wirklich der komplette Inhalt ausgelesen wird. • Zeile 7 verzweigt zur zweiten Programmabteilung, ruft also die Funktion function bild_erzeugung auf. • In Zeile 8 wird nun einfach der Zählerstand um 1 erhöht. • Der Befehl rewind in Zeile 9 setzt den Schreibpunkt in der Datei .zaehler wieder an den Anfang. • Mit fwrite in Zeile 10 ist nun die Datei .zaehler aktualisiert. Die function bild_erzeugung (Zeile 13 bis 35) übernimmt die komplette Umsetzung der Zahl in eine PNG-Datei. Wir haben hier das PNG-Format gewählt, weil GIF von vielen Hostern nicht mehr unterstützt wird, da dafür eine Lizenzgebühr anfällt. • Zeile 17 ermittelt die Anzahl der Zahlen des Zählerstandes. • Wir benötigen auch die Höhe und Breite der Schriftart, die wir zur Ausgabe benutzen. Das wird in Zeile 18 und 19 ermittelt. 5 ist dabei einfach die gewählte Schriftart. • Zeile 20 bis 25 werden nun benötigt, um die Gesamtgröße des Bildes zu ermitteln
und später auch die Position des Schriftzuges im Bild. • In Zeile 26 wird das Bild erzeugt. • In Zeile 27 und 28 kommt Farbe ins Spiel. • Der Hintergrund ist in Zeile 29 rot geschaltet. Die zweite und dritte Variable (im Beispiel auf 0 gesetzt) definieren, an welcher Position im Bild das farbige Dreieck aufgezogen werden soll. • Nun muss noch die neue Zahl gezeichnet werden. Das bereiten die Zeilen 30 und 31 vor, indem hier die konkrete Position des Schriftzuges im Bild ermittelt wird. • Mit dem Befehl ImageString wird nun der Zählerstand mit der Schriftart Nummer 5 in schwarzer Farbe in das Gesamtbild gemalt (Zeile 32). • Jetzt muss das Bild noch als PNG abgespeichert werden. Das erledigt der Befehl ImagePNG in Zeile 33 – alternativ können Sie auch ImageGIF oder ImageJPEG verwenden. Nun wird nur noch das Ergebnis auf dem Bildschirm ausgegeben. In Zeile 36 wird dazu die Funktion zaehlen aufgerufen und in Zeile 37 zum erstellten Bild verknüpft. Schritt 2 – Datei .zaehler erstellen: Mit einem einfachen Texteditor muss eine Datei .zaehler abgespeichert werden. Schreiben Sie zuvor den gewünschten Beginn Ihres Zählerstandes ein. Da können Sie ganz ehrlich mit 1 beginnen oder auch einen wesentlich höheren Wert vorgeben. Wichtig ist nur: Tragen Sie eine Zahl ein. Wir haben in unserem Programmierbeispiel bewusst auf lange Fehlerabfangroutinen verzichtet, um es einfach zu halten. Ist die Datei .zaehler später nicht in der richtigen Form vorhanden, gibt es sehr unschöne Systemfehlermeldungen. Schritt 3 – Anpassen und aufspielen: In den Zeilen 4, 15 und 16 der Datei counter.php müssen Sie nun unbedingt noch die richtigen Pfade angeben. Wichtig: Sie müssen vom so genannten Root-Verzeichnis ausgehen. Sollten Sie einen Fehler machen oder den Pfad Ihres Rootverzeichnises nicht kennen, wirft die Fehlermeldung das richtige Rootverzeichnis aus. Spielen Sie zuletzt die Dateien counter.php und .zaehler auf Ihren Webserver. Fertig! Dieser Beitrag ist ein Auszug aus dem Buch „Webgrafik-Optimierung. Was Sie
Startup
über das Arbeiten mit Grafiken im Web wissen müssen“ von Thorsten Olscha und Jörg Stroisch, das 2003 im Markt + Technik Verlag erschienen ist (227 Seiten, € 34,95, ISBN 3-8272-6530-4). Weitere Informationen finden Sie unter www.mut.de/main/ main.asp?page=deutsch/bookdetails&pro ductid=16988.
Listing 3 1
PHP Magazin 3.2004
27
Tools & Tipps PEAR
PEARcing von Alexander Merz
Der Rück-, Ein- und Ausblick auf das PHP Extension and Application Repository Heute widmen wir uns grundsätzlichen Dingen: Kritischen Anmerkungen zu PEAR und dem Einsatz von PEAR-Packages bei kommerziellen Projekten.
Rückblick Christian Wenz schrieb im vorherigen PHP Magazin (02/04) einen guten Artikel darüber, wie Sie PEAR-Packages manuell installieren können. Dabei bemerkte er am Anfang einiges über PEAR, das Sie vielleicht verunsichert hat. Stichwort Diskussionen: Die PHPCommunity insgesamt neigt nicht gerade zur Freundlichkeit – und betrachtet dies sogar als Markenzeichen, wenn man einem Posting in der Newsgroup de.comp.lang. php glauben darf.[1] In der PEAR-Entwickler-Mailingliste finden Sie harte Diskussionen, aber nicht weniger hart als manche Diskussion um PHP 5 zum Beispiel. Und selbst diese bleiben weit unter dem Grad der aktuellen Auseinandersetzungen um Xfree86. Letztlich sollten Sie nicht vergessen, dass die Karriere von Linux auch mit einem kleinen Flamewar zwischen Linus Torvald und Andrew Tanenbaum begann.[2] Zum ominösen „PEAR2“ – davon haben Sie an dieser Stelle nie etwas gelesen, weil es nie etwas sinnvolles darüber zu berichten gab. Kurz zur Einstimmung: PEARs Ziel sind hochwertige Packages – nur wann ist ein Package „hochwertig“? Setzt man die Messlatte zu hoch an, bleibt die Anzahl der Pakete gering; setzt man sie zu niedrig an, wäre PEAR nur ein weiteres Skript-Archiv. Wie ist dieses Problem zu lösen? Durch formale Dinge wie Coding Standards und den Proposal-Prozess ist eine Grundlinie vorgegeben. Mit den PEAR Foundation
28
PHP Magazin 3.2004
Classes (PFC) ist eine zweite, höhere Messlatte entstanden. Aber was ist mit dem Anspruch nach Vielfalt? Das bezieht sich nicht nur auf neue Packages – sondern auch auf experimentellen Code, (nicht-akzeptierte) Erweiterungen für bestehende Packages und nicht mehr betreute Packages. Einfach zu sagen, „mach doch ein Projekt auf SourceForge auf“, widerstrebt vielen Entwicklern. Denn davon hätten weder die Entwickler etwas noch die Nutzer, wenn sich das gleiche Package mit nur leichten Unterschieden auf PEAR und SourceForge befindet. Aus solchen Überlegungen heraus gab es immer wieder Ideen für einen Bereich in PEAR, der diese Dinge aufnehmen kann – bislang gab es aber keine Idee, die allgemein akzeptiert wurde.
PEAR und Kommerz Gehen wir zu einem Thema über, das immer wieder auftaucht: In wie weit dürfen bzw. können PEAR-Packages in kommerziellen PHP-Programmen eingebunden werden, insbesondere bei Closed Source-Programmen? Grundsätzlich gibt es dafür keine pauschale Antwort. Welcher Lizenz ein Package unterliegt, ist allein vom Maintainer abhängig – einzige Bedingung: es muss eine Open Source-Lizenz sein gemäß [3]. Die am häufigsten benutzten Lizenzen sind die PHP-Lizenz Version 3, LGPL und BSD-artige Lizenzen. Welche für ein Package konkret gilt, erfahren Sie von der Package-Homepage [4] oder über
den Kommandozeilen-Befehl pear info , wenn das Package bereits installiert ist. Allen ist gemeinsam, dass der Einsatz in kommerziellen Projekten erlaubt ist auf jeden Fall wenn: • der Quellcode offen ist, • die benötigten Packages nicht Bestandteil der Programmdistribution sind. Letzteres heißt, Ihr Programm setzt bestimmte installierte PEAR-Packages auf dem Zielcomputer voraus – wie z.B. der IMP Webmailer (auch wenn er nicht kommerziell ist). Kritischer wird es, wenn Sie nicht den Quellcode freigeben wollen bzw. eine Distribution bauen, die keinerlei installierte Komponenten voraussetzt und/oder bereits kompiliert ist. In diesem Fall müssen Sie dafür sorgen, dass die notwendigen PEAR-Bestandteile trotzdem dem Käufer im Quelltext bereitstehen und auf die entsprechenden Copyrights der Package-Autoren hinweisen. Haben Sie ein PEAR-Package für Ihre Zwecke umgeschrieben, sollten Sie diese Änderungen unbedingt öffentlich machen – nicht nur Ihrem Kunden gegenüber. Die genannten Punkte sind der kleinste gemeinsame Nenner – aber gerade wenn Sie ein Package selbst verändern oder Quellcode von Packages direkt übernehmen, sind die Unterschiede zwischen den Lizenzen groß, eine allgemeine Aussage nicht mehr möglich. In diesen Fällen sollten Sie unbedingt mit dem Maintainer Kontakt aufnehmen – unter Umständen ergeben sich dann auch Möglichkeiten zur Doppel-Lizenzierung, die Ihnen mehr Freiheiten einräumen. Alexander Merz ist Herausgeber des PEAR-Manuals. Wenn er nicht gerade schreibt, arbeitet er als Java- und PHPProgrammierer für die Community4you GmbH. Sie erreichen ihn unter alexander. [email protected].
Das PHP-Kochbuch David Sklar, Adam Trachtenberg Sogar knallrote Küchenschürzen gibt es mittlerweile als Merchandising-Material von O’Reilly. Man sagt von sich selbst, der Verlag zu sein, wenn es um Kochbücher geht. Und so ist es kaum verwunderlich, dass vor einiger Zeit auch ein PHP-Kochbuch bei O’Reilly erschienen ist. Thematisch ist dieses sehr weit gefächert. Zunächst geht es um Datentypen wie Strings, Zahlen, Daten und Uhrzeiten sowie Arrays. Häufig wird dabei die Manipulation und Konvertierung beschrieben, so zum Beispiel, wie man Dezimalzahlen in Hexadezimalbeziehungsweise Oktal-Zahlen umrechnet.
PostgreSQL Jens Hartwig Das freie Datenbanksystem PostgreSQL ist sehr umfangreich und bietet vielfältige Einsatzmöglichkeiten. In dieses System praxisnah einzuführen, diesen Anspruch erhebt das Buch von Jens Hartwig für sich. Tatsächlich wird der Leser stets an Beispielen mit der Materie vertraut gemacht und am Ende vieler Unterkapitel finden sich Übungsbeispiele. Der Praxisteil des Buches beginnt mit einer ausführlichen Beschreibung der Installation von PostgreSQL und der Benutzung von psql, dem StandardClient. In den folgenden Kapiteln geht es dann zur Sache, wenn mit der Einrichtung von Datenbanken begonnen wird. Hierunter fällt unter anderem auch alles, was mit
Darauf folgt ein weiterer Block zu Programmiergrundlagen mit Kapiteln zu Variablen, Funktionen, Klassen und Objekten, bevor es zum „Herzstück“ des Buches geht, das aus fünf Kapiteln zum Thema Webprogrammierung besteht. Das Spektrum geht von halbwegs primitiven Themen, wie zum Beispiel eine Umgebungsvariable auszulesen ist, zu Komplizierterem wie der Überprüfung von Formularinhalten. Außerdem werden Datenbankabfragen und XML behandelt. Die Kapitel 13 bis 19 widmen sich vielen anderen Problemen aus der Programmierwelt wie beispielsweise Regulären Ausdrücken, Verschlüsselung, Internetdiensten und Dateizugriff. Die letzten 50 Seiten gehen schließlich mit den Themen PHP-GTK und PEAR über den Grundfunktionsumfang von PHP hinaus. Die Rezepte sind allesamt sehr starr in die Teile „Problem“, „Lösung“, „Diskussion“ und „Siehe auch“ unterteilt. „Siehe auch“ verweist jeweils auf andere Rezepte, Dokumentationen und Web-Adressen. Die strikte Trennung zwischen „Lösung“ und „Diskussion“ ist gleichermaßen praktisch
Tabellen oder Views zu tun hat. Im SQLKapitel geht der Autor zuerst darauf ein, inwieweit der SQL/92-Standard in PostgreSQL umgesetzt wurde, und erklärt im Folgenden dann die SQL-Basisfunktionen von einfachen SELECT-Anweisungen bis zu Joins. Das sehr umfangreiche fünfte Kapitel widmet sich hingegen den fortgeschrittenen SQL-Funktionen und führt hier wirklich alles an, was der Administrator benötigen könnte. Transaktionen werden ebenso wie die Administration des Datenbankservers jeweils in einem eigenen Kapitel besprochen. Gerade das Administrationskapitel wird man bei der Arbeit mit PostgreSQL schnell zu schätzen wissen, da es solch wichtige Themen wie Backup ebenso behandelt wie Tuning-Möglichkeiten. Der restliche Teil des Buches stellt dann die unterschiedlichen Entwicklungsmöglichkeiten vor. Zuerst wird dabei auf die Programmierung von PostgreSQL selbst mit PL/PGSQL oder C eingegangen. Anschließend geht es um den Zugriff aus anderen Programmen, wobei hier nicht nur C oder Java, sondern auch PHP Gegenstand
Tools & Tipps
wie störend. Einerseits wirken die Lösungen zum Teil trivial und erst die Diskussion interessant, sodass das getrennt wird, was eigentlich zusammen gehört. Andererseits ist es jedoch bei einem schnellen Blick so wesentlich einfacher, die für den Moment passende Lösung zu finden, ohne sich durch viele Worte kämpfen zu müssen. Insgesamt lässt sich sagen, dass es bestimmt nicht falsch ist, dieses Buch im Regal zu haben. Zu vielen Aufgaben, die sich im Laufe der Programmierung mit PHP stellen, findet man hier schnell eine Antwort. Und diese ist in den meisten Fällen von der Länge angenehm passend. Ob und in wie weit gerade diese Rezepte für den eigenen Programmieralltag nötig und hilfreich sind – das muss jeder selber feststellen. Thomas Kesselheim
David Sklar, Adam Trachtenberg PHP-Kochbuch 632 Seiten, € 44,O’Reilly, 2003 ISBN 3987213516
des Kapitels sind. Zusätzlich liegt dem Buch eine CD-ROM bei, auf der sich PostgreSQL, JDBC- und ODBC-Treiber sowie sämtliche Beispiele aus dem Buch befinden. „PostgreSQL – Professionell und praxisnah“ erfüllt die Erwartungen zum großen Teil. Das Buch ist tatsächlich ausgesprochen praxisnah und behandelt dermaßen viele Aspekte, wie man es anfangs nicht erwartet. Aber eben dies stellt sich auch als Problem heraus, denn bei einem Umfang von knapp 460 Seiten wird dabei ein paar Mal zu oft auf Tabellen zurückgegriffen, die Funktionen näher erläutern sollen. So wäre an manchen Stellen mehr Tiefgang wünschenswert. Trotzdem ein sehr empfehlenswertes Buch für jeden, der mit PostgreSQL zu tun hat. Julius Stiebert Jens Hartwig PostgreSQL – Professionell und praxisnah 456 Seiten, € 49,95 Addison-Wesley, 2001 ISBN 3-8273-1860-2
PHP Magazin 3.2004
29
Titelthema Benutzerauthentifizierung nen, um sehr schnell und einfach eine Authentifizierung zu realisieren. In komplexeren Situationen reichen die vom Webserver mitgebrachten Funktionen allerdings oft nicht aus – aber auch für diesen Fall gibt es diverse frei erhältliche Klassenbibliotheken, derer man sich bedienen kann. Aber immer eines nach dem anderen!
Basic Authentication mit Apache
Wer bist du? von Markus Wolff
Die einfachste Art der Authentifizierung ist die so genannte „Basic Authentication“. Sie ist direkt im HTTP-Protokoll vorgesehen und wird daher von eigentlich jedem Webserver von Haus aus unterstützt. Beim Apache Webserver kann die Authentifizierung für ein bestimmtes Verzeichnis zum Beispiel dadurch aktiviert werden, dass man eine Datei mit dem Namen .htaccess in das zu schützende Verzeichnis legt, die die nötigen Konfigurationsanweisungen enthält: AuthName GeschuetzterBereich AuthType Basic AuthUserFile /pfad/zu/.htpasswd require valid-user
Benutzerauthentifizierung mit PHP-Bordmitteln und -Klassenbibliotheken
„Who are you?“ – nicht nur vorlonische Botschafter bestehen regelmäßig auf die ordnungsgemäße Beantwortung dieser Frage. Auch für viele Webanwendungen ist es essenziell wichtig, einen Benutzer zweifelsfrei identifizieren zu können, bevor ihm bestimmte Bereiche oder Funktionen zur Verfügung gestellt werden.
30
PHP Magazin 3.2004
Bei dieser an sich einfach klingenden Aufgabe stellt uns die Natur des Internets vor einige Probleme: Es gibt zunächst einmal keine Erkennungsmerkmale, die eine über jeden Zweifel erhabene Identifikation möglich machen. Auch IP-Adressen können sich selbst während des Verlaufs einer einzigen Session ändern und taugen hierfür somit ebenfalls nicht. Dementsprechend laufen die meisten gängigen Authentifizierungsmechanismen darauf hinaus, dass die Anwendung in einen Dialog mit dem User tritt und von ihm Informationen verlangt, die nur er selbst kennt (oder zumindest kennen sollte). Da Authentifizierung eine häufig vorkommende Funktion darstellt, haben die meisten Webserver eingebaute Mechanismen, die von PHP genutzt werden kön-
In der Datei .htpasswd (dieser Name wird üblicherweise hierfür benutzt, es kann aber auch ein anderer gewählt werden) muss eine Liste aller zugriffsberechtigten User stehen, wobei die Passwörter mittels Unix-Crypt verschlüsselt sein müssen. Am einfachsten wird dies mit dem Kommandozeilenbefehl htpasswd erreicht: htpasswd -c .htpasswd username
Das Passwort für den User wird über den dann folgenden Dialog eingegeben. Der Parameter -c wird nur benötigt, falls die Datei noch nicht existiert. Ist alles korrekt konfiguriert, kann auf den Verzeichnisbaum, in dem die .htaccess-Datei liegt, nun nicht mehr ohne die Eingabe eines gültigen Benutzernamens und Passwortes zugegriffen werden. Ein Problem besteht hier aber noch: Da in dem obigen Beispiel die Authentifizierung ausschließlich durch den Webserver erfolgt, weiß ein PHP-Skript damit noch nicht, um welchen Benutzer es sich
Benutzerauthentifizierung handelt – dies gilt es also noch herauszufinden, um zum Beispiel personalisierte Inhalte anbieten zu können, Zugriffe bestimmter User zu protokollieren oder ähnliche feine Dinge zu realisieren. PHP stellt hierzu einige Variablen bereit, die nach erfolgter Authentifizierung automatisch gefüllt werden: • $_SERVER[“PHP_AUTH_USER“] • $_SERVER[“PHP_AUTH_PW“] • $_SERVER[“REMOTE_USER“] Sofern die Authentifizierung nicht über einen externen Mechanismus erfolgt ist (z.B. NTLM), stehen der eingegebene Benutzername und das Passwort in PHP_ AUTH_USER und PHP_AUTH_PW zur Verfügung. Ansonsten enthält REMOTE_ USER zumindest den Namen des aktuell angemeldeten Benutzers.
Externe Authentifizierung Bei einem externen Authentifizierungsmechanismus wird das Passwort des Benutzers nicht notwendigerweise übermittelt. Gerade in mittleren bis größeren Unternehmen mit Windows-Netzwerken ist eine zentrale Authentifizierung mittels des NTLM-Protokolls (NT Lan Manager) sehr beliebt. Sie bietet den Vorteil, dass die Benutzer im Netz sich nur ein einziges Mal, nämlich bei der Anmeldung an ihrem Windows-Client, mit Benutzername und Passwort anmelden müssen. Hierbei wird beim Domänencontroller registriert, dass an der besagten Maschine dieser User angemeldet ist. Anwendungen, die eine Anmeldung per NTLM unterstützen, müssen nur noch den Domänencontroller fragen, wer an der Maschine mit der zugreifenden IP-Adresse angemeldet ist, wodurch der User sich nicht erneut authentifizieren muss. Lange stand dieses bequeme Feature nur Anwendern des IIS und des Internet Explorer zur Verfügung, inzwischen kann NTLM jedoch auch mit Apache unter Linux und Mozilla genutzt werden. Hierzu muss das Apache-Modul MOD_NTLM (modntlm.sourceforge.net/) eingebunden und die .htaccess-Datei wie folgt geändert werden: AuthType NTLM NTLMAuth on
NTLMAuthoritative on NTLMDomain MyDomain NTLMServer DomainController1 NTLMBackup DomainController2 Require valid-user
Den Namen des somit angemeldeten Benutzers kann man wie oben erwähnt mit $_SERVER[‘REMOTE_USER’] auslesen, die Variablen mit dem Präfix PHP_ bleiben hingegen leer. Es sollte nicht unerwähnt bleiben, dass NTLM ein relativ unsicheres Protokoll ist – die Datenpakete können recht einfach gefälscht und dem Webserver somit ein falscher Benutzer untergejubelt werden. In neueren Windows-Versionen (ab Windows 2000) wird daher primär Kerberos für die Authentifizierung verwendet, das ein Challenge-Response-Verfahren mit starker Verschlüsselung implementiert. Trotz der Sicherheitsprobleme sollte man dennoch erwägen, NTLM-Unterstützung zumindest optional in die eigenen Applikationen einzubauen – gerade, wenn sie an Firmenkunden gerichtet sind und hauptsächlich in Intranets genutzt werden: Zum einen wird es häufig nachgefragt, zum anderen kann speziell bei weniger computerbegeisterten Nutzern das Wegfallen einer erneuten Authentifizierung die Nutzerakzeptanz erheblich steigern.
Authentifizierung mit Bordmitteln Die schon behandelten Möglichkeiten zur Authentifizierung haben einige Haken: So kann man sich beispielsweise nicht darauf verlassen, dass die Möglichkeit, eine Liste mit Usernamen und Passwörtern in Dateiform zu hinterlegen, überhaupt zur Verfügung steht – der Internet Information Server (IIS) von Microsoft verwaltet die Zugriffsrechte zum Beispiel über interne Access Control Lists. Auch bei ApacheServern ist die Möglichkeit, überhaupt mittels einer .htaccess-Datei die Konfiguration auf Verzeichnisebene zu ändern, nicht immer gegeben, falls der Administrator dies seinen Anwendern nicht zugesteht (auf durchsatzstarken Servern wird diese Option auch gerne aus Performancegründen abgeschaltet). Da die „Basic Authentication“ jedoch ein im HTTP-Protokoll integrierter Standard ist, lässt sich der Authentifizierungs-
Titelthema
prozess auch direkt von PHP aus auslösen. Dazu müssen nur die passenden HTTPHeader an den Browser gesendet werden, falls kein angemeldeter Benutzer erkannt wurde: if (!isset($_SERVER[‘PHP_AUTH_USER’])) { header(‘WWW-Authenticate: Basic realm= “GeschuetzterBereich“‘); header(‘HTTP/1.0 401 Unauthorized’); echo ‘Sie sind nicht angemeldet!’; exit; }
Diese Lösung hat ebenfalls noch den besonderen Charme, dass man nicht mehr auf die Verwendung von Textdateien oder das Vorhandensein bestimmter ApacheModule für die Benutzerverwaltung angewiesen ist – nach der Eingabe von Benutzername und Passwort können diese gegen eine beliebige Datenquelle geprüft werden, beispielsweise eine MySQL-Datenbank (siehe Listing 1). Wie man sieht, ist die Realisierung einer effizienten Benutzerauthentifizierung mit den Bordmitteln des Apache und/oder PHP sehr einfach machbar. Einen Haken hat das ganze aber noch: Es funktioniert nur mit der Modulversion von PHP – läuft PHP als CGI-Interpreter, stehen diese Möglichkeiten nicht zur Verfügung. Um ganz sicher gehen zu können, dass die eigene Anwendung auf allen denkbaren Plattformen läuft, ist also ein selbstgestrickter Authentifizierungsmechanismus vonnöten. Glücklicherweise haben natürlich andere PHP-Entwickler schon vor längerer Zeit vor dem gleichen Problem gestanden und einige frei erhältliche Klassenbibliotheken zur Verfügung gestellt, die uns davor bewahren, das Rad noch einmal erfinden zu müssen.
PEAR::Auth Als einziges der hier vorgestellten Pakete konzentriert sich PEAR::Auth auf das Wesentliche: Die Authentifizierung der Benutzer. Die weiteren Pakete beinhalten ebenfalls die Autorisierung, was ein ganz eigenes Thema ist, das für sich genommen noch einmal einen eigenen Artikel rechtfertigen würde. Doch immer der Reihe nach!
PHP Magazin 3.2004
31
Titelthema Benutzerauthentifizierung PEAR::Auth setzt das bewährte Containersystem um: Es hat keine exklusive, fest definierte Datenquelle, gegen die die Benutzerdaten geprüft werden können, sondern es unterstützt eine große Aus-
Listing 1 PHP-Authentifizierung mit MySQL session_start(); function isLoggedIn() { if (isset($_SESSION[‘USER’])) { return true; } return false; } // Falls noch kein Benutzer angemeldet ist, // Authentifizierungsheader schicken if (!isset($_SERVER[‘PHP_AUTH_USER’]) || !isset($_SESSION[‘USER’])) { header(‘WWW-Authenticate: Basic realm= “GeschuetzterBereich“‘); header(‘HTTP/1.0 401 Unauthorized’); } // Der Benutzer ist noch nicht angemeldet, // aber ein Passwort wurde geliefert? if (!isset($_SESSION[‘USER’]) && isset($_SERVER[‘PHP_AUTH_USER’])) { // Mit der Datenbank verbinden ... $dbc = mysql_connect(‘localhost’, ‘root’) or die(mysql_error()); mysql_select_db(‘test’); // Nach dem User mit dem angebebenen Usernamen // und Passwort suchen... $res = mysql_query(“SELECT * FROM users WHERE username=’“. mysql_escape_string ($_SERVER[‘PHP_AUTH_USER’]). “‘ AND passwd=’“. mysql_escape_string ($_SERVER[‘PHP_AUTH_PW’]).“‘“, $dbc) or die(mysql_error());
wahl an möglichen Datenquellen. Derzeit sind dies: • SQL-Datenbanken (alle von DB bzw. MDB unterstützten) • LDAP-Server • Passwort-Dateien (im .htpasswd- oder SMBPasswd-Format) • IMAP-Server • POP3-Server • RADIUS-Server • SOAP-Services • Vpopmail-Services Abgesehen von NTLM und Kerberos (für das mir derzeit leider keine brauchbare PHP-Implementation bekannt ist), gibt es somit kaum eine Datenquelle, gegen die es sich mit PEAR::Auth nicht authentifizieren lässt. Der Einsatz von PEAR::Auth ist denkbar einfach. Da das Paket im PEAR-Repository zur Verfügung steht, lässt es sich sehr einfach über den PEAR-Installer installieren: pear install Auth
Die nötigen Klassendefinitionen können anschließend in das eigene Skript eingebunden werden: require_once(‘Auth/Auth.php’);
Listing 2 Beispiel für eine Login-Funktion Diese für die Verwendung von PEAR::Auth notwendige Funktion gibt lediglich ein einfaches HTML-Formular mit den Feldern username und password aus. Zur einfacheren Wartbarkeit des
auslagern.
32
Das obige Beispiel nimmt die Authentifizierung gegen einen IMAP-Server vor, der auf der gleichen Maschine installiert ist, wie der Webserver. Jeder Benutzer mit einem IMAP-Postfach auf diesem Server kann sich nun mit seinen vom eMailDienst gewohnten Zugangsdaten anmelden (dies funktioniert im Übrigen auch mit Microsoft Exchange Servern, da diese sich auch als „normale“ IMAP-Server ansprechen lassen). Die Login-Funktion habe ich loginForm() genannt, was sich im dritten Parameter für den Auth-Konstruktor wiederfindet. Als nächstes muss jetzt das frisch erzeugte Auth-Objekt mit der Methode start() initialisiert werden. Diese prüft, ob bereits eine Authentifizierung vorgenommen wurde und ruft die zuvor definierte Funktion loginForm() auf, falls dies nicht der Fall ist. Anschließend kann mit der Methode getAuth() geprüft werden, ob der User sich erfolgreich angemeldet hat – in diesem Fall darf der eigentliche Inhalt der Seite dann auch ohne Gewissensbisse präsentiert werden: $a->start(); if ($a->getAuth()) { echo “Ich bin drin - das war ja einfach!“; }
{ echo ‘’; echo ‘Name: ’;
PHP Magazin 3.2004
$a = new Auth(“IMAP“, $options, “loginForm“);
function loginForm()
} if (isLoggedIn()) { echo ‘Ich bin drin - das war ja einfach :-))’; } else { echo ‘Das wahr wohl nichts!’; }
Designs sollte man den HTML-Code im realen Einsatz allerdings besser in eine eigene Datei
// Falls ein User mit diesen Daten gefunden wurde, // den Namen in die Session schreiben ... if (mysql_num_rows($res) > 0) { $row = mysql_fetch_assoc($res); print_r($row); $_SESSION[‘USER’] = $_SERVER[‘PHP_AUTH_USER’]; }
Anschließend muss eine Funktion deklariert werden, die bei Aufruf ein HTML- Formular zur Anmeldung ausgibt. Das Formular muss Felder namens username und password enthalten, damit PEAR::Auth die Eingaben entsprechend zuordnen kann (Beispiel siehe Listing 2). Der Name der Login-Funktion wird dem Konstruktor übergeben:
echo ‘Passwort: ’; echo ‘’; echo ‘’; }
Sollte die Anmeldung fehlgeschlagen oder der User seit seinem letzten Zugriff zu lange untätig gewesen sein, gibt die Methode getStatus() Auskunft über die Ursache, indem sie eine der drei Konstanten AUTH_IDLED, AUTH_EXPIRED oder AUTH_WRONG_LOGIN zurückgibt.
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Titelthema
Benutzerauthentifizierung PEAR::LiveUser Einen etwas anderen Ansatz als PEAR:: Auth verfolgt LiveUser. Grundsätzlich wird auch hier das Containerkonzept verfolgt, allerdings ist eine Managerklasse vorgeschaltet, die einige Komfortfunktionen mit sich bringt und es außerdem erlaubt, über die reine Authentifizierung hinaus Benutzerrechte, also Autorisierung, mit einzubeziehen. Der Clou ist, dass nicht nur die Datenquellen für die Authentifizierung per Container austauschbar sind, sondern auch die Verwaltung der Benutzerrechte. Durch den Einsatz der Managerklasse, die auch als Wrapper dient, ist es daher nach Belieben möglich, völlig unterschiedliche Rechtekonzepte zu realisieren – und dennoch gegenüber der Anwendung eine gleichbleibende API-Schnittstelle zu bewahren. Zum gegenwärtigen Zeitpunkt existieren für LiveUser bei weitem nicht so viele verschiedene Containertypen wie für PEAR::Auth. Es gab vor einiger Zeit zwar kurz einen Wrapper, der es erlaubte, die Container von PEAR::Auth auch mit LiveUser zu verwenden, dieser wurde in der Zwischenzeit allerdings von der Entwicklung überholt und ist nicht mehr funktionstüchtig – möglicherweise hat sich das bis zum Erscheinen dieses Artikels aber auch schon wieder zum Besseren gewendet. Für die Authentifizierung existieren derzeit drei Container: Zwei für SQLDatenbanken (einmal mit PEAR::DB als Abstraktionslayer, einmal mit MDB) und einer für XML-Dateien. Für die SQL-Varianten lassen sich ebenso wie bei PEAR:: Auth der Tabellenname sowie die wesentlichen Feldnamen konfigurieren, zusätzlich lässt sich die Art der Passwortverschlüsselung bestimmen. Die Konfiguration erfolgt in Form eines PHP-Arrays, dessen Definition optimalerweise in eine eigene Datei ausgelagert wird. Dies erlaubt es, die Konfiguration alternativ in einfach weiterzubearbeitenden Dateiformaten wie XML zu halten und daraus aus Performancegründen nach erfolgten Änderungen wieder die Datei mit der Arraydefinition zu generieren – die Arraystruktur wurde so gewählt, dass dies z.B. mit dem Paket PEAR::Config problemlos möglich ist.
Eine Besonderheit von LiveUser ist es, dass es möglich ist, in der Konfiguration mehrere Authentifizierungscontainer zu definieren. Bei einem Login-Versuch werden diese dann von der Managerklasse der Reihe nach abgearbeitet, bis entweder ein passender Benutzereintrag gefunden oder die Liste vollständig ohne Erfolg durchlaufen wurde. Dies kann zum Beispiel in der folgenden Situation nützlich sein: Nehmen wir an, eine größere Firma hat ein Intranet für die eigenen Mitarbeiter sowie ein Extranet für externe Partner. Die externen Partner werden in einer eigenen CRM-Datenbank verwaltet, die auf dem Webserver läuft. Darin sind auch deren Benutzerkonten gespeichert. Die eigenen Mitarbeiter werden jedoch von einem internen LDAP-Server verwaltet, der auch für die Benutzerdaten des Intranet verantwortlich ist. Damit die Mitarbeiter nun nicht doppelt verwaltet werden müssen, werden beide Server in die LiveUser-Konfiguration eingetragen. Benutzerdaten werden dann zum Beispiel zuerst in der Kundendatenbank gesucht – und wenn sie dort nicht gefunden werden, wird die Suche im LDAP-Server fortgesetzt. Zugegebenermassen ist dies im Moment allerdings mangels verfügbarem LDAPContainer für LiveUser in dieser Form eher ein Planspiel – wohin die Reise geht, ist jedoch klar. Trotz der größeren Komplexität von Live User muss die Anwendung nicht zwangsläufig schwieriger sein, wie das folgende Beispiel zeigen soll. Wie zuvor erfolgt die Installation via PEAR-Installer:
struktor das Konfigurationsarray übergeben bekommt. Anschließend kann mit der Methode isLoggedIn() herausgefunden werden, ob der Login-Vorgang von Erfolg gekrönt war: $user =& LiveUser::factory($liveuserConfig); if ($user->isLoggedIn()) { echo “Ich bin drin - das war ja einfach!“; }
Listing 3 LiveUser-Beispielkonfiguration Das nachstehende Konfigurationsarray stammt aus dem Beispiel 3 des LiveUser-Pakets und zeigt bereits die wichtigsten Konfigurationsoptionen:
Das eigene Skript muss nun zunächst in die Definition der Managerklasse LiveUser eingebunden werden und bei der Gelegenheit zusätzlich auch gleich die Datei, die die Definition des Konfigurationsarrays enthält (ein Beispiel hierfür findet sich in Listing 3):
Dann folgt nur noch die Instantiierung des LiveUser-Objekts, dessen Kon-
);
PHP Magazin 3.2004
41
Titelthema Benutzerauthentifizierung LiveUser besitzt ebenfalls ein Gültigkeitsflag für Benutzer, das dazu dient, Useraccounts temporär zu sperren bzw. es zu ermöglichen, dass Benutzer sich zunächst über ein Antragsformular selbst eintragen können, aber anschließend noch von einem Administrator freigeschaltet werden müssen, bevor sie sich erfolgreich anmelden können. Dies kann man über die Methode isInactive() abfragen, falls das Login fehlschlägt: if ($user->isLoggedIn()) { echo “Ich bin drin - das war ja einfach!“; } elseif ($user->isInactive()) { echo “Du bist noch nicht freigeschaltet!“; }
Bei erfolgreichem Login lädt LiveUser auch gleich die entsprechenden Rechte des Users aus dem Autorisierungscontainer, sofern ein solcher konfiguriert und der zu dem im Authentifizierungscontainer passende User auch dort vorhanden ist. Die Rechte werden hierbei über numerische IDs referenziert, die über ein bei LiveUser mitgeliefertes Shellskript aus der Datenbank gelesen und entsprechend der Rechtenamen als Konstanten definiert werden. Bindet man die resultierende Datei mit den Konstantendefinitionen ein, können Rechte z.B. so abgefragt werden: if ($user->checkRight(MEINPROGRAMM_MEINBEREICH_ MEINRECHT)) { echo “Ich bin schwer im Recht“; }
Im Detail kann an dieser Stelle leider nicht auf die Rechteverwaltung eingegangen werden, da LiveUser von Haus aus vier Rechtecontainer mit unterschiedlichen Komplexitätsstufen mitbringt (genaugenommen sieben, wenn man die MDB-Varianten der datenbankbasierten Container mitzählt) – und außerdem geht es ja immerhin um Authentifizierung und nicht Autorisierung (was gern verwechselt wird). Damit das Rad auch im administrativen Bereich nicht immer neu erfunden werden muss, bringt LiveUser neben den für das Frontend gedachten Klassen auch eigene Administrationsklassen mit, mit denen sich User anlegen, ändern und Rechte verteilen lassen – diese sind derzeit
42
PHP Magazin 3.2004
zwar noch im Betastadium, machen aber sehr gute Fortschritte.
$dbc =& DB::connect(‘mysql://root@localhost/test’); $tpl = new patTemplate(); $tpl->setBasedir(‘templates’);
patUser Es gibt ein Leben neben PEAR! Auch wenn diese Tatsache von vielen aufgrund der extremen Medienpräsenz als Legende abgetan wird, so gibt es sie dennoch: Code-Repositories, die nicht PEAR heißen. Ein eindrucksvolles Beispiel hierfür, das qualitativ mindestens gleichwertig zu den PEARStandards ist, ist das Projekt „PHP Application Tools“ (www.phptools.de/). PatUser verfolgt als einziges der hier beleuchteten Tools nicht das Containerkonzept, sondern authentifiziert ausschließlich gegen SQL-Datenbanken als Datenquelle. Als Abstraktionslayer wird seit Version 2.2 nun PEAR::DB genutzt, sodass theoretisch alle hiervon unterstützten Datenbanken mit patUser funktionieren sollten. Ebenso wie LiveUser bringt es ein integriertes Rechtemanagement mit, das von Haus aus Benutzergruppen unterstützt – allerdings ist auch dieses Rechtesystem vom Leistungsumfang her fix und nicht austauschbar (was aber nicht unbedingt schlecht sein muss – letztlich klagen viele Neueinsteiger bei LiveUser über die Komplexität des Pakets). Als Bonusfeature ermöglicht patUser das Hinterlegen von beliebigen Zusatzdaten neben der Standard-Usertabelle sowie das Mitführen einer History der innerhalb einer Session besuchten Seiten. Die Klassen aus dem PAT-Framework enthalten keine eigenen Include-Anweisungen – man muss also wirklich alle abhängigen Klassen selbst einbinden. Bei patUser wird zusätzlich zur gleichnamigen Klasse noch patTemplate benötigt, sofern man ein eigenes Login-Formular benutzen möchte. Da patUser auch noch PEAR::DB als Datenbankabstraktionslayer benötigt, muss auch dieses eingebunden werden: require_once ‘DB.php’; require_once ‘include/patUser.php’; require_once ‘include/patTemplate.php’;
Als Nächstes wird mittels PEAR::DB die Datenbankverbindung aufgebaut, ein Template-Objekt erzeugt und das Basisverzeichnis für die Templates festgelegt, die patUser erwartet:
Fehlt noch das patUser-Objekt selbst, das dann noch mitgeteilt bekommen muss, welches Template- und Connection-Objekt benutzt werden soll: $user = new patUser(true); $user->setAuthDbc($dbc); $user->setTemplate($tpl);
Mittels $user->requireAuthentication() werden dann die Benutzerdaten aus der Session gelesen. Ist noch kein Benutzer angemeldet, wird der Anmeldedialog eingeblendet, für den patUser das Vorhandensein einer Template-Datei namens patUserLogin.tmpl voraussetzt. Verzichtet man übrigens auf die Zuweisung eines Template-Objekts, benutzt patUser automatisch die weiter oben schon behandelte Basic Authentication via HTTP, bei der ja von dem Standardanmeldedialog des Browsers Gebrauch gemacht wird. Eine weitere Alternative ist die Verwendung eines so genannten AuthHandlerObjekts – die entsprechende Klasse muss dabei mindestens eine Methode patUserGetAuthData() implementieren, der dann die gesamte Arbeit zukommt, eine LoginMöglichkeit anzuzeigen, die Daten entgegenzunehmen und in Form eines Arrays mit den entsprechenden Feldern wieder zurückzugeben. Auf diese Weise könnte man also nicht nur ein eigenes Login-Formular auch ohne die Verwendung von patTemplate realisieren, sondern ebenfalls externe Authentifizierungsmethoden einbinden – allerdings besteht trotz allem noch die Einschränkung, dass patUser die gelieferten Daten noch selbst gegenprüft, womit wiederum ausschließlich SQL-Datenbanken als Datenquelle in Frage kommen. Weitere interessante Einstellungsmöglichkeiten des Userobjekts sind setMaxLoginAttempts(), womit die Anzahl der maximal möglichen Fehlschläge bei einem Login-Versuch festgelegt wird, bevor patUser dem User das Login endgültig verweigert, sowie keepHistory(), was dafür sorgt, dass die letzten Seitenaufrufe des Users in der Session protokolliert werden – letzteres ist nicht nur für die Statistik
Anzeige
Titelthema Benutzerauthentifizierung nützlich, sondern auch, um dem User bequem einen Rückweg durch die bisher annavigierten Seiten bieten zu können. Mittels setCryptFunction() kann schließlich noch bestimmt werden, auf welche Weise die in der Datenbank gespeicherten Passwörter verschlüsselt werden. Ist die notwendige Initialisierung erfolgt, kann nun geprüft werden, ob der User tatsächlich angemeldet ist: if ($user->isAuthenticated()) { echo “Ich bin drin - das war ja einfach!“; }
Zum Thema Sicherheit … Bei allen vorgestellten Methoden ist zu beachten, dass es immer in der Hand des Entwicklers bleibt, für zusätzliche Sicherheit zu sorgen – keine der Klassen überprüft von sich aus die Herkunft der vom User gelieferten Daten, außerdem gehen Benutzername und Passwort ohne weiteres Zutun des Entwicklers oder Serveradministrators unverschlüsselt über das Netz und können somit von bösen Buben abgefangen und missbraucht werden. Der gesamte Anmeldedialog sollte daher nach Möglichkeit grundsätzlich mit Hilfe von SSL
verschlüsselt werden. An sich ist dies zwar eine Aufgabe des Serveradministrators, allerdings kann der Entwickler von vornherein dafür sorgen, dass ein Zugriff auf den Anmeldedialog ausschließlich per SSL erfolgen kann, indem das ankommende Protokoll überprüft wird. Lautet dieses nicht https, so wird der Anmeldedialog nicht dargestellt. Dies sorgt gleichzeitig auch dafür, dass die Benutzer der Anwendung bei manueller Eingabe der URL nicht aus Versehen die Verschlüsselung aushebeln können. Ein weiterer Schritt in Richtung mehr Sicherheit ist es, die Passwörter aller Benutzer grundsätzlich niemals im Klartext in der Datenquelle zu speichern. Eine übliche Methode ist es, aus dem Passwort einen MD5-Hash zu generieren. Dieser besteht immer aus genau 32 alphanumerischen Zeichen. Mit dem immer gleichen String gefüttert, liefert die MD5-Funktion zuverlässig den gleichen Hashcode zurück, sodass ein Vergleich eines im Klartext gelieferten Passworts nach Erzeugung des Hashcodes mit der in der Datenquelle gespeicherten Version einfach und schnell möglich ist. Von einem MD5Hash auf den ursprünglichen String zu-
rückzuschließen, ist hingegen nicht ohne weiteres möglich.
Fazit Möglichkeiten, mit Hilfe von PHP eine effiziente Benutzerauthentifizierung auf die Beine zu stellen, gibt es reichlich. Nicht nur bietet PHP selbst bereits mächtige Bordmittel, die gerade im Zusammenspiel mit dem Apache Webserver bereits für die meisten Ansprüche ausreichen können, auch gibt es mit PEAR::Auth, LiveUser, patUserund einigen weiteren sehr mächtige frei erhältliche Bibliotheken, deren Funktionsumfang zum Teil noch weit über die reine Authentifizierung hinausgeht. So bieten LiveUser und patUser nicht nur integriertes Usermanagement über die administrativen Funktionen, sondern auch komplette und im Falle von LiveUser sogar nahtlos austauschbare Rechtesysteme, mit deren Hilfe man auch das verwandte Thema Autorisierung abdecken kann. Die meisten Datenquellen unterstützt derzeit definitiv PEAR::Auth. LiveUser muss hier noch stark aufholen, bei patUser wird es meiner Vermutung nach zumindest mittelfristig bei der Konzentration auf SQL-Datenbanken bleiben.
Erklärungen zu den Optionen im Einzelnen: reicht, um sich anmelden zu können. Mögliche Ele-
autoInit: Boolean. Wenn true, initialisiert es sich bei
nicht erneut eingeben muss), function (Name einer
der Erzeugung des Objekts selbst, d.h. alle nötigen
Callback-Funktion, die aufgerufen wird, falls ein Sei-
mente: name (Name des Cookies), lifetime (Lebens-
Einstellungen müssen bereits im Konfigurationsarray
tenaufruf ohne vorheriges Login erfolgt ist), force
dauer des Cookies in Tagen), path (Pfad, für den der
stehen, da vor dem Einlesen von Userdaten keine
(Boolean, legt fest, ob das Login erzwungen werden
Cookie gültig ist), domain (Domain, innerhalb derer
Einstellungen mehr gemacht werden können. Wenn
soll).
der Cookie gültig ist), secret (Schlüsselbegriff/Salt
false, muss nach dem Setzen aller Konfigurationsop-
logout: Assoziatives Array, welches das Verhalten
für die Verschlüsselung der Zugangsdaten im Coo-
tionen manuell noch die Methode init() aufgerufen
von LiveUser bei einem Logout des Users steuert.
kie).
werden.
Mögliche Elemente: trigger (Name der GET- oder
authContainers: Zweidimensionales Array – für je-
session: Ein assoziatives Array mit den Elementen
POST-Variable, bei deren Vorhandensein ein Logout
den Container wird ein assoziatives Array mit den
name (Name der Session) und varname (Name der
ausgelöst wird), redirect (Adresse der Seite, auf die
jeweiligen Containereinstellungen als Element in ei-
Sessionvariable, in der LiveUser alle Daten spei-
nach einem erfolgten Logout weitergeleitet werden
nem indexier ten Array angelegt. Die Containerar-
chert).
soll), function (Name einer Callback-Funktion, die bei
rays werden bei einem Login-Versuch dann der Rei-
login: Assoziatives Array, welches das Verhalten
einem Logout aufgerufen werden soll), destroy (Boo-
he nach durchlaufen. Welche Elemente für die
des Formulars steuer t, mit dem der User sich an-
lean, falls true, wird bei einem Logout gleich die ge-
Einstellungen des Containers zur Ver fügung stehen,
melden kann. Mögliche Elemente: method (GET/
samte Session zerstört).
hängt von dem jeweiligen Container ab (im Zweifel
POST), username (Name des Formular felds für den
cookie: Dieses assoziative Array muss nur gesetzt
der Inline-Dokumentation des Containers zu ent-
Usernamen), password (Name des Formular felds
werden, falls man die RememberMe-Funktion (setzen
nehmen).
für das Passwor t), remember (Name der Checkbox
eines Cookies zur Vermeidung späterer Login-Auffor-
permContainer: Assoziatives Array, welches die Ein-
– falls vorhanden – für die Anforderung eines Coo-
derungen) nutzen will. Diese Funktion ist bei Usern
stellungen für den gewählten Autorisierungscontai-
kies, durch das sich LiveUser beim nächsten Login
sehr gefragt, lädt aber zu Missbrauch ein, da fortan
ner enthält. Die möglichen Elemente hängen von
an den Benutzer erinner t, sodass er sein Passwor t
der bloße Zugang zum Computer des Users aus-
dem jeweiligen Container ab.
44
PHP Magazin 3.2004
Payment-Systeme
Zur Kasse, bitte! von Markus Nix
PHP und Payment-Systeme Die einstigen Sicherheitsbedenken sind vergessen: Mittlerweile nutzt jeder Deutsche mit OnlineAnschluss die Möglichkeit,Waren im Internet zu kaufen.Das beliebteste Zahlungsmittel ist hier die Kreditkarte.Zahlreiche Unternehmen sind angetreten, um für die verschiedensten Shop-Betreiber den Billing-Prozess abzuwickeln.Die meisten Anbieter unterstützen dabei PHP-Entwickler beim Aufbau eines Shop-Systems mit dem Bereitstellen von eigenen APIs.
Es gibt heute eine Vielzahl von Zahlungsmethoden, die über das Internet genutzt werden können (Abbildung 1). Die für Deutschland derzeit gebräuchlichen Bezahlsysteme lassen sich in etwa so kategorisieren: Traditionell (Offline-Zahlung) – Kreditkarte – Bankeinzug – Vorauszahlung. Dabei steht der Aspekt der Endgültigkeit (Finalität) einer Zahlung für den Kunden im Vordergrund. Im Zusammenhang mit dem Zeitpunkt, zu dem er die Ware erhält, ist die Zahlungsfinalität ein Ansatz zur Unterscheidung und Bewertung verschiedener Internet-Bezahlmodelle aus Kundensicht. Bei den vorauszahlenden Systemen kann man wiederum zwischen Lösungen trennen, die mit Hilfe von Smart Cards funktionieren und solchen Ansätzen, bei denen Zahlungen ohne zusätzliche technische Geräte und Hilfsmittel durchgeführt werden können. Auch beim Bankeinzug gibt es zwei verschiedene Verfahrensweisen: entweder wird dem jeweiligen Händler die Einzugsermächtigung direkt erteilt oder es wird ein Mittler eingeschaltet, der den Geldtransfer übernimmt. Ein solcher Mittler bietet darüber hinaus noch zusätzliche Leistungen oder auch eine gesicherte Infrastruktur für den Bezahlvorgang an.
Die Bezahlmethoden lassen sich aber auch nach der Höhe des zu bezahlenden Betrags kategorisieren. Es gibt Waren, die nur wenige Cent kosten. Bei digitalen Gütern wie Musiktiteln oder Zeitungsartikeln steht vor allem die Wirtschaftlichkeit des Geldtransfers im Vordergrund: es ist z.B. nicht sinnvoll, für jeden Kleinstbetrag eine eigene Rechnung zu verschicken. Vor allem für den Bereich Micropayment wurden neue Bezahlmethoden entwickelt, die wiederum nach zwei verschiedenen Konzepten arbeiten: vorausbezahlende Systeme und Inkassosysteme. Im Bereich Macropayment steht vor allem die Sicherheit des Geldtransfers im Vordergrund. Hier geht es um größere Summen, häufig kommen klassische Bezahlmethoden wie Kreditkarten zum Zuge. Die Einteilung in Micro- und Macropayment ist oft nicht eindeutig; es gibt auch Systeme, die sich für beide Bereiche eignen. Im Vordergrund dieses Artikels stehen Systeme, die speziell für die Zahlungsab-
Development
wicklung im Internet entwickelt worden sind. Wie kommt ein Händler an sein Geld und wie kann der Kunde sicherstellen, dass die Transaktion sicher und reibungslos verlaufen ist? Internetbasierte Zahlungssysteme müssen sich dabei an „klassischen“ Zahlungssystemen messen, die dem Kunden vertrauter sind und immer noch als sicherer angesehen werden. Die traditionellen Bezahlfunktionen, die im Versandhandel bereits seit Jahrzehnten erprobt sind, wurden mit als erste Variante in Internetshops eingesetzt. So finden sich auch heute noch zahlreiche Anbieter, die eine Lieferung der im Internet bestellten Ware gegen Nachnahme, Rechnung oder Vorauskasse akzeptieren. Die gesamte Kategorie der Offline-Zahlungen ist aufgrund des Medienbruchs in der Abwicklung nicht für alle Angebote im Internet sinnvoll. Sinnvoller ist hier der Einsatz anderer Zahlungssysteme, die versuchen, den Bestell-, den Liefer- und den Bezahlvorgang zeitlich und/oder vom Ablauf her miteinander zu verknüpfen und damit den zusätzlichen und zeitversetzten Aufwand der klassischen Bezahlvarianten zu vermeiden. Der größte Anteil der Transaktionen im Internet-Zahlungsverkehr erfolgt heute auf Basis von Kreditkartenzahlungen. Eine Vielzahl von Anbietern wie VeriSign oder Firstgate nimmt Shop-Betreibern dabei den Billing-Prozess ab. Kreditkarte und Internetanschluss genügen, um in einem solchen Shop auf Schnäppchenjagd zu gehen. Bei diesem Szenario nutzt der Shop-Betreiber hinter den Kulissen einen Anbieter, der für ihn die Überprüfung der Kreditkarte und – bei Richtigkeit aller Angaben – die Abbuchung vornimmt. Der Bankeinzug mittels elektronischer Lastschrift für den Einsatz im Internet ist dem Verfahren mit Kreditkarte vom Ablauf
Quellcode Der Quellcode zum Artikel befindet sich auf der beiliegenden CD.
Abb. 1: Internetzahlungssysteme
PHP Magazin 3.2004
45
Development Payment-Systeme her sehr ähnlich. Allerdings werden die Zahlungsbeträge dem Kundenkonto unmittelbar belastet (und nicht erst zum späteren, wiederkehrenden Abrechnungstermin). Die grundlegende Problematik aller Einzugsverfahren besteht auch hier: Der Konsument kann im Internet nicht nachweisen, dass seine Kontoangaben korrekt sind und sein Konto gedeckt ist. Es gibt verschiedene Lösungsansätze, um die Probleme mit elektronischen Lastschriften zu lösen. Internet-Lastschriften mit dem SETProtokoll ermöglichen eine Verschlüsselung der Daten, außerdem wird die Identität des Kunden durch ein Zertifikat nachgewiesen, eine Art digitaler Ersatz für die persönliche Unterschrift. Bei anderen Ansätzen werden Mittler (Inkassostellen) eingesetzt. Häufig muss sich der Konsument einmalig bei einem solchen Mittler anmelden und ihm eine Einzugsermächtigung erteilen. Bei einem Kauf wird der Mittler über den zu zahlenden Betrag informiert, der Konsument identifiziert sich dann gegenüber dem Mittler, der daraufhin eine Abbuchung vom Konto des
Call, es ist ein Framework zum Versenden von XML-Dateien. Auf der CD zu diesem Heft befinden sich einige Klassen, die als Wrapper um populäre Bezahlsysteme fungieren. Ebenso auf der Heft-CD: der Ansatz zu einem generalisierten Payment-Framework, mit dem sich die einzelnen Systeme über eine factory-Methode ansprechen lassen: function &factory( $driver, $options = array() ) { /* Return a base Payment object if no driver is specified. */ $driver = strtolower( $driver ); if ( empty( $driver ) || ( strcmp( $driver, ‘none’ ) == 0 ) ) return new Payment( $options ); $payment_class = “Payment_“ . strtolower( $driver ); include_once $payment_class . ‘.php’; if ( class_exists( $payment_class ) ) return new $payment_class( $options ); else return false; }
Konsumenten veranlasst. Das Geld wird dann, nach Abzug einer Provision, an den Händler weitergeleitet. Für diese Transaktion stellt der Mittler eine gesicherte Leitung zu Verfügung. Solche Mittler lohnen sich besonders im Bereich des Micropayment, wo die Rechnungsbeträge sehr klein sind. Der Markt für solche Systeme ist schwer zu überschauen. Viele Anbieter sind angetreten, wenige sind geblieben. Die Hersteller bieten APIs, die es Site-Betreibern ermöglichen, via HTTP-Request, XML-RPC oder SOAP die komplexe Transaktionslogik für den eigenen Shop zu nutzen. Einige Hersteller bieten auch mehrere Varianten an. Das Billing-Unternehmen prüft dabei die Daten, nimmt bei Richtigkeit die Transaktion vor und gibt an den Kunden oder Shop ein Okay zurück. Die Anwendung von SOAP folgt in diesen Szenarien dem populären Fehlschluss, der sich unbemerkt in die momentane Diskussion eingeschlichen hat, nämlich SOAP als Nachfolger von XML-RPC zu handeln. Diese Sichtweise greift jedoch zu kurz: SOAP ist mehr als ein Remote Procedure
case “REDIRECT“: // Bezahlung erfordert Redirect // ... Redirect-Code here and vars in $res // [“redirect_details“] break; } ?>
Payment-Systeme Wenn Sie z.B. mit dem Authorize.netBackend kommunizieren möchten, dann sollten Sie dies wie in Listing 1 gezeigt schreiben. Um beim schnellen Geschäft in der ersten Reihe sein zu können, haben die meisten Anbieter umfangreiches Informationsmaterial ins Netz gestellt, das zeigt, wie jedermann von seiner Internetpräsenz aus Kontakt mit den Billing Services aufnehmen kann. Cybercash und VeriSign haben die komplexe Business-Logik sogar in eigenen PHP-Extensions gekapselt. Dies mag aus performance- und sicherheitstechnischen Aspekten nachvollziehbar sein, dürfte aber im Allgemeinen einer umfassenden Akzeptanz des Zahlungssystems eher abträglich sein. Man darf an dieser Stelle nicht vergessen, dass die meisten populären Bezahlsysteme ihren Ursprung in den USA haben, wo es eine ungleich höhere Akzeptanz für Kreditkarten und OnlineZahlsysteme gibt, als dies in Deutschland der Fall ist.
ipayment Mit dem ipayment-Service [1] des Internet-Providers Schlund + Partner gibt es auch aus deutschen Reihen seit geraumer Zeit ein zuverlässiges Abrechnungssystem, das serverseitig auf PHP basiert. Mit ipayment können die Betreiber elektronischer Shops ihren Kunden wahlweise die Zahlung per Kreditkarte, elektronischer Lastschrift oder Prepaid-Karte anbieten. Unterstützt werden die verbreiteten Kreditkarten Visa, American Express, MasterCard, Diners Club und JCB, das elektronische Lastschriftverfahren (ELV) sowie die Prepaid-Karte paysafecard. Über Moxmo, den Nachfolger des Zahlungssystems paybox, ist auch das Bezahlen per Handy möglich. Das System ist nicht nur auf die Shops von Schlund + Partner begrenzt, über CGI und SOAP kann jeder Shop-Betreiber mit ipayment kommunizieren. Für ein PHP-basiertes Backend hat man sich bei Schlund + Partner besonders wegen
Listing 3
POST /v2/ip_service.php HTTP/1.0 User-Agent: NuSOAP/0.6.6
der geringen Entwicklungszeit entschieden. Die Implementierung basiert auf der NuSOAP-Bibliothek von Dietrich Ayala [2]. NuSOAP ist Nachfolger von soap4x, auf dem auch das SOAP-Package im PEAR basiert. Besonders die Möglichkeit zur Generierung und Verwendung von WSDL-
Listing 4 HTTP/1.1 200 OK Date: Wed, 11 Feb 2004 15:49:59 GMT Server: Apache/1.3.26 (Unix) Debian GNU/Linux mod_ssl/2.8.9 OpenSSL/0.9.6c X-Powered-By: PHP/4.3.4 X-SOAPed-By: NuSOAP Server v0.6.6 Content-Length: 1196 Content-Type: text/xml; charset=ISO-8859-1 X-Pad: avoid browser bug ERROR 5002 0 Fehlerhafte Kreditkartennummer. The Creditcard-Number (4111111111111110) is invalid [email protected]
PHP Magazin 3.2004
47
Development Payment-Systeme Files mit NuSOAP hat zur Wahl dieser Bibliothek geführt. Der ipayment-Server bietet eine Vielzahl von Remote-Funktionen, die man unter https://ipayment.de/v2/ip_ service.php ausführlich studieren kann. Listing 2 zeigt ein Code-Beispiel für den Aufruf der Methode checksave_cc. NuSOAP erzeugt einen Request, der inkl. Header wie in Listing 3 aussieht. Der ipay-
ment-Server antwortet in unserem Fall mit der Fehlermeldung in Listing 4.
Firstgate click & buy Dieses System der Firstgate Internet AG aus Köln ist ein Micropaymentsystem, mit dem kostenpflichtig digitale Inhalte im Internet abgerufen werden können. Um das System nutzen zu können, muss
sich der Surfer einmal kostenlos bei Firstgate anmelden. Weitere Kosten entstehen für ihn nicht, es muss auch keine zusätzliche Software installiert werden. Der Kunde erteilt Firstgate bei der Anmeldung eine Einzugsermächtigung oder gibt die Daten seiner Kreditkarte an, die dann mit dem zu zahlenden Betrag belastet wird.
Payment-Systeme VeriSign Bekanntester Vertreter von Online-Abrechnungssystemen ist VeriSign. Das Unternehmen betreibt eine aggressive Expansionspolitik und beschreibt damit zugleich ein Dilemma der PHP-Entwickler: Die cybercash-Erweiterung für PHP, die gesondert mittels --with-cybercash=[DIR] einkompiliert werden musste, ist schon wieder obsolet, weil CyberCash von VeriSign aufgekauft wurde. Die besten Methoden aus den Systemen CyberCash und Payflow Pro sol-
len nun in die Entwicklung eines einzigen Abrechnungssystems fließen. Hier zeigt sich, dass native PHP-Implementierungen für die Kommunikation mit AbrechnungsServern eindeutig Extensions vorzuziehen sind. Der Markt ändert sich einfach zu schnell. Die Payflow-Extension funktioniert einstweilen noch. Diese Erweiterung macht es möglich, Geschäftsabwicklungen via Kreditkarte über die VeriSign Payment Services abzuwickeln (vergl. Listing 5). Die entsprechenden Funktionen sind nur ver-
Listing 6 class Paypal { var $paypal_post_vars; var $paypal_response; var $timeout;
fügbar, wenn PHP mit der Option --withpfpro=[DIR] kompiliert wurde. Die nötige Extension erhält man mit dem SDK, das registrierten Nutzern zum Download bereitsteht. Detaillierte Informationen zu den Payflow-Funktionen finden sich im Payflow Pro Developers Guide.
Paypal Ein Lieblingskind der PHP-Gemeinde ist der Micropayment-Anbieter Paypal. Viele Open Source-Projekte versuchen über einen „Donate!“-Button ein wenig Geld zu verdienen. Die eBay-Tochter Paypal ist vermutlich deshalb so erfolgreich, weil sie es Site-Betreibern besonders einfach macht: Nach vorheriger Anmeldung genügt schon ein Simpler „Buy Now“-Button, um das Paypal-Backend gewinnbringend zu nutzen. Darüber hinaus sind auch komplexe Transaktionen, Auktionen und Shop-Systeme möglich. Joel De Gan hat auf der Zend-Site ein Tutorial veröffentlicht, das sehr eindrucksvoll das Zusammenspiel von PHP und Paypal demonstriert [3]. Auf der Heft-CD findet sich eine einfache Klasse, mit der man mit dem Paypal Instant Payment Notification System (IPN) kommunizieren kann. Die Klasse in Listing 6 illustriert die Kommunikation mit Paypal via Post. Seit kurzem bietet Paypal seine Dienste auch mit einem deutschsprachigen Backend an, um die Akzeptanz hierzulande zu erhöhen. Markus Nix ([email protected]) realisiert Internetauftritte mit Unterstützung von Content Management-Systemen für Unternehmungen der freien Wirtschaft und aus dem Non-Profit-Bereich. Seine Schwerpunkte sind die Themen PHP, XML und Content Management.
Literatur & Links [1] ipayment von Schlund + Partner: www.ipayment.de/ [2] NuSOAP-Bibliothek von Dietrich Ayala: dietrich.ganx4.com/nusoap/ [3] Paypal-Tutorial von Joel De Gan: www.zend.com/zend/tut/tutorial-paypal.php [4] Payment Mechanisms designed for the Internet (Liste wird nicht mehr gepflegt) ganges.cs.tcd.ie/mepeirce/Project/ oninternet.htm
PHP Magazin 3.2004
49
Development Excel ohne Excel
Daten auf Knopfdruck von Christian Wenz
Excel-Funktionalität ohne Excel nutzen In diesem Teil der Serie „x ohne x“ geht es um die Erstellung von Excel-Worksheets, ohne selbst Excel zu verwenden. Ermöglicht wird das ganze wie so oft durch ein PEAR-Paket.
Die Anführungszeichen machen Sinn, denn innerhalb einer Zeichenkette könnte ja auch ein Komma auftreten. Das zu unterscheiden, also Kommas zwischen einzelnen Werten und Kommas innerhalb eines Wertes, ist machbar, aber aufwändig. Wie so oft haben die PHP-Entwickler vorgesorgt. Die Funktion fgetcsv() [1] liest aus einer Datei eine Zeile ein und liefert diese als Array zurück. Damit kann unsere Beispielsdatei (die ersten vier Plätze der Bundesliga-Saison 1999/2000) beispielsweise schnell in HTML ausgegeben werden:
Die Funktion fgetcsv() erwartet vier Parameter, wobei der letzte optional ist. Der erste Parameter ist ein Dateihandle, das zuvor durch fopen() oder Ähnliches erzeugt worden ist. Der zweite Parameter ist die Menge an Zeichen, die von der aktuellen Zeile eingelesen werden soll; es wird also beim Zeilenende aufgehört. Der dritte Parameter ist das Trennzeichen zwischen den Einträgen, meistens ein Komma. Der vierte Parameter, der wie eingangs erwähnt optional ist, gibt das Zeichen an, das die einzelnen Werte einschließt. Standardmäßig geht PHP davon aus, dass das doppelte Anführungszeichen sind; für spezielle Anforderungen können auch andere Zeichen verwendet werden. Diesen Parameter gibt es erst seit PHP 4.3.0. Kleiner Pferdefuß: Das „Umschließen“ der einzelnen Einträge muss mit demselben Zeichen geschehen; eckige Klammern wären also beispielsweise nicht möglich. Auch wenn Sie versuchen, als vierten Parameter mehr als ein Zeichen anzugeben, ignoriert PHP alles nach dem ersten Zeichen. Das gilt im Übrigen auch für den dritten Parameter, das Trennzeichen.
Excel ohne Excel
Development
Abb. 1: Die CSVDatei wird mit wenigen Zeilen Code ausgegeben.
Abb. 2: Die von fputscvs() erzeugte CSV-Datei
C.D.: “Ich tue das, weil ich ein absolut reines Gewissen habe.“
perated-values; dieser wird jedoch nicht von allen Systemen unterstützt. Hier gibt es jedoch einen kleinen Trick: Alle Tabellenkalkulationen können CSV-Dateien öffnen. Außerdem unterstützen (fast) alle Tabellenkalkulationen das Microsoft-ExcelFormat. Dieses Format hat einen wohl bekannten MIME-Typ. Also schicken Sie einfach mitsamt der CSV-Datei den ExcelMIME-Typ (application/vnd.ms-excel) mit; die Chancen sind groß, dass – die Tabellenkalkulation sich dann der zurückgegebenen Daten annimmt.
kompatibilität zu alten Versionen nicht mehr gewährleistet sein könnte; Nutzer von Spreadsheet_Excel_Writer sollten mit dem Upgrade warten, bis das ExcelPEAR-Paket aktualisiert werden würde. Allerdings scheint die aktuelle Version 0.6 von Spreadsheet_Excel_Writer (erschienen Mitte November 2003) doch nicht betroffen zu sein; im Test funktionierte sie tadellos. Sollten Probleme auftreten, ist immer noch ein Downgrade möglich. Die Installation geht mit dem Kommandozeilen-Installer von PEAR leicht
wird also:
Mehr Möglichkeiten mit Excel
Listing 1
Aber auch die Gegenrichtung ist möglich. Ein Array kann problemlos in ein CSV-Format umgewandelt werden. Dazu müssen die einzelnen Werte einfach mit Kommata (oder einem beliebigen Trennzeichen) aneinander gehängt werden. Ist in einem der Werte selbst das Trennzeichen vorhanden, werden Anführungszeichen benötigt. Zu guter Letzt: Enthält der Wert selbst Anführungszeichen, müssen diese verdoppelt werden. Aus
“C.D.: ““Ich tue das, weil ich ein absolut reines Gewissen habe.“““
Leider bietet PHP keine Funktion fputcsv() an; sie ist aber selber schnell erstellt (siehe Listing 1). Ein kleiner Nachteil bleibt jedoch: Das Verdoppeln der Anführungszeichen ist (nach Ansicht des Autors) Standard und wird beispielsweise auch vom CSV-Modul für Perl [2] so realisiert. Leider kommt fgetcsv() damit noch nicht zurecht; aus den doppelten Anführungszeichen werden keine einfachen. Während der Erstellung des Artikels wurde das bereits behoben; vielleicht schafft es die Korrektur bereits in Version 4.3.5. So lassen sich schön CSV-Dateien erzeugen und diese auch in die „üblichen Verdächtigen“ (Tabellenkalkulationen) importieren. Viele Erweiterungen sind denkbar, beispielsweise könnten die Daten aus einer Datenbank geholt werden oder der Browser gibt sie direkt aus. Der letzte Fall soll kurz skizziert werden, denn hier kommt es auf den korrekten MIME-Typ an. Da kursieren im Web mehrere Varianten. Viele benutzen text/csv, das ist aber kein registrierter MIME-Typ. Was es allerdings gibt, ist der MIME-Typ text/tab-se-
Aber wenn ohnehin schon der ExcelMIME-Typ verwendet wird, kann ja direkt Excel erzeugt werden. Das ist allerdings nicht so einfach. Das Excel-Format selbst ist nirgendwo offiziell dokumentiert. Glücklicherweise haben sich in der Vergangenheit einige Entwickler bemüht, diesem Umstand abzuhelfen. So hat sich beispielsweise heraus gestellt, dass die Daten in einer Excel-Datei im OLE-Format (Object Linking and Embedding) abgespeichert werden. Dieses Format ist inoffiziell, aber zutreffend unter [3] dokumentiert. Zudem gibt es ein Perl-Modul [4] zur Verarbeitung von Excel-Daten. Dessen Portierung in PEAR [5] wurde von Xavier Noguer und Mika Tuupola vorgenommen. Das Modul unterstützt das Format von Excel 5.0 (genannt BIFF); die zum Redaktionsschluss aktuelle Version 0.6 enthält erstmals eine experimentelle Unterstützung von Daten im Excel-97-Format. Das PEAR-Paket besitzt eine Abhängigkeit, es ist mindestens Version 0.3 von PEAR::OLE [6] erforderlich. Dieses Paket (von Xavier Noguer) kann OLE-Daten erzeugen. Auf der Homepage des OLEPakets ist für die Version 0.5 (erschienen Mitte Dezember 2003) erwähnt, dass aufgrund der PEAR-Standards die Abwärts-
CSV-Datei erzeugt!
PHP Magazin 3.2004
51
Development Excel ohne Excel ligatabelle von zuvor und speichert sie als Excel-Datei ab. Der Reihe nach: Zunächst muss das PEAR-Paket eingebunden werden: require_once “Spreadsheet/Excel/Writer.php“;
Abb. 3a: Drei Darstellungsarten: Excel
Als nächstes instanziieren Sie das zugehörige Objekt. Als Parameter geben Sie den Dateinamen an, den die erzeugte Excel-Datei erhalten soll: $workbook = new Spreadsheet_Excel_Writer(“excel1.xls“);
Ein Tabellenverarbeitungsdokument ist in mehrere Arbeitsblätter unterteilt, auf Englisch worksheet. Bevor Sie überhaupt Daten in das Dokument schreiben können, müssen Sie zunächst ein solches Worksheet erzeugen. Das geht mit der Methode addWorksheet(). Der Rückgabewert ist eine Referenz auf das Arbeitsblatt: Abb. 3b: Drei Darstellungsarten: Word $worksheet =& $workbook->addWorksheet (“1. Bundesliga 1999-2000“);
Abb. 3c: Drei Darstellungsarten: StarOffice
von der Hand: Erst wird OLE installiert, dann Spreadsheet_Excel_Writer: $ pear install OLE $ pear install Spreadsheet_Excel_Writer
Aktuellere PEAR-Versionen sind allerdings so konfiguriert, dass per pear install Paket nur als stabil gekennzeichnete Module installiert werden. Dies trifft sowohl für OLE als auch für Spreadsheet_Excel_Writer leider nicht zu, sodass auf solchen Systemen direkt die Versionsnummer mit angegeben werden muss: $ pear install OLE-0.5 $ pear install Spreadsheet_Excel_Writer-0.6
Zeit für einen kleinen Test. Der Code in Listing 2 erzeugt die (gekürzte) Bundes-
52
PHP Magazin 3.2004
Als Parameter wird also der Name des Arbeitsblatts übergeben. Vorsicht, der Schrägstrich ist beispielsweise nicht erlaubt. Zwar gibt das PEAR-Paket keine Warnung aus, aber spätestens Excel beschwert sich beim Öffnen des Dokuments. Also wäre „1. Bundesliga 1999/2000“ keine gültige Bezeichnung. Nun müssen noch die Daten in das Arbeitsblatt geschrieben werden. Die gewünschte Zelle wird dabei per Zeile und Spalte angegeben, wobei die Zählung jeweils bei 0 beginnt. Im Beispiel wird das Array mit den Bundesligadaten eingelesen und stückweise in das Dokument geschrieben; das erledigt die Methode write() des Arbeitsblatts (nicht des Dokuments!): $worksheet->write($i, $j, $bundesliga[$i] [$j]). Abschließend muss die Datei noch geschlossen werden: $workbook->close(). Und das war es auch schon. In Excel öffnet sich die Datei mit den angegebenen Werten. In vielen Fällen ist es jedoch wünschenswert, dass die Daten nicht auf der Festplatte gespeichert, sondern direkt ausgegeben werden. Auch das ist möglich: Lassen Sie einfach beim Instanziieren der Klasse den Dateinamen weg ...
$workbook = new Spreadsheet_Excel_Writer(“excel1.xls“);
... und lassen Sie dafür die PEAR-Klasse die notwendigen HTTP-Header an den Browser schicken, inklusive dem vorgeschlagenen Dateinamen: $workbook->send(“excel1.xls“);
Achten Sie allerdings darauf, dass Sie nach dem Erzeugen der Excel-Datei keine HTML-Ausgaben, auch keinen Whitespace mehr in Ihrem PHP-Skript haben; andernfalls landet das in den Excel-Daten und könnte die Datei unleserlich machen.
Formatieren und rechnen Zum Abschluss sollen noch einige Möglichkeiten gezeigt werden, wie die erzeugte Excel-Datei verschönert und anderweitig angepasst werden kann. Als Erstes würden der Datei noch ein paar Verschönerungsmaßnahmen gut tun; insbesondere Mitarbeiter von Vertriebsabteilungen sollen Gerüchten zufolge mit sagenhaften ExcelKenntnissen gesegnet sein. Dazu dient die Methode addFormat() der Spreadsheet_ Excel_Writer-Klasse: Damit kann eine Formatierungsvorschrift erzeugt werden. Diese kann mit zahlreichen Methoden angepasst werden, hier eine Auswahl: • setBold() setzt den Text fett • setItalic() setzt den Text kursiv • setUnderline() unterstreicht den Text • setShadow() versieht den Text mit einem Schlagschatten •setColor() färbt die Zelle • setBgColor() färbt den Hintergrund der Zelle
Neue Excel-Version Wie im Text bereits erwähnt, bietet Spreadsheet_Excel_Writer 0.6 erstmals eine Unterstützung für das etwas modernere (und auch komplexere) Format von Excel 97. Mit der noch undokumentierten Methode setVersion() kann dies aktiviert werden. $workbook->setVersion(8); In einer der zukünftigen Versionen des Pakets wird diese Excel-Version wohl zur Standardversion werden; die Methode setVersion() könnte also irgendwann wieder verschwinden.
Excel ohne Excel Eine komplette Übersicht befindet sich in der Dokumentation der Klasse [7]. Hier ein konkretes Einsatzbeispiel: Ein Format wird definiert, fett und mit einem Schatten versehen soll die Schrift werden:
Development
Abb. 4: Die endgültige Version: Automatisch berechnete Tordifferenz und eingebundene Grafik
In der ersten Zeile der Tabelle wird dieses Format dem Text zugewiesen; damit wird eine Überschrift erstellt. Dazu wird die Ausgabeschleife wie folgt angepasst:
$worksheet->write(0, 4, “Tordifferenz“, $format); for ($i=2; $i writeFormula($i-1, 4, “=C$i-D$i“); for ($i=0; $i < count($bundesliga); $i++) {
Lohn der Mühen: Die erste Zeile ist umformatiert. In Excel erscheint sie fett, in Word (wenn man die Excel-Datei dort importiert) ist sie schattiert. StarOffice beispielsweise zeigt beides an. Weiterhin fällt bei genauerem Betrachten der Bundesligatabelle auf, dass die Tordifferenz nicht angegeben worden ist. Diese entscheidet aber bei Teams mit gleich vielen Punkten (wie im Beispiel die beiden erstplatzierten Mannschaften) über die Reihenfolge. In Excel kann das (beispielsweise) mit einer Formel berechnet werden. Diese beginnt immer mit = und danach kommt entweder ein Funktionsname (etwa SUM zum Aufsummieren) oder eine Rechenvorschrift. Letzteres soll gezeigt werden: In einer fünften Spalte soll die Tordifferenz automatisch berechnet und ausgegeben werden. Beginnen wir mit der zweiten Zeile, dem FC Bayern München. Innerhalb von Excel hat die Spalte, in der die Zahl der geschossenen Tore steht, die Bezeichnung C2 – C bezeichnet die (dritte) Spalte, 2 die Zahl der Zeile. Analog stehen die Gegentore in D2. Die „Formel“, um die Differenz zu berechnen, lautet hier also: =C2-D2. Mit diesem Vorwissen kann das Skript angepasst und um die Formeln erweitert werden:
Die finale Verschönerung erfährt die Tabelle, indem noch das Logo der Liga eingeblendet wird. Mit der Methode insertBitmap() (erneut: eine Methode des Arbeitsblatts, nicht des gesamten Dokuments) können Grafiken eingebunden werden. Als Parameter wird neben den Daten der Zelle lediglich der Dateiname angegeben; leider muss die Datei lokal vorhanden sein, es ist keine direkte HTTPAdresse möglich, was ja sonst unter PHP kein Problem ist. Außerdem gibt es noch vier optionale Parameter, nämlich den Abstand vom Zellenrand (horizontal und
Erzeugung von Graphen mit PEAR::Image_Graph In unserem Alltag fallen laufend Unmengen von Zahlenwerten an, wie z.B. die Besucherzahlen einer Webseite, Messwerte aus technischen Anlagen oder die aktuellen Umsatzzahlen einer Firma/Abteilung. Diese gilt es, in geeigneter Form zu veranschaulichen. Die meisten Werte lassen sich in Form von tabellarischen Aufstellungen zusammenfassen und ordnen. Aber richtig übersichtlich werden Daten häufig erst, wenn man sie grafisch aufbereitet, um sich sowohl einen schnellen Überblick zu verschaffen als auch direkte Vergleiche ziehen zu können. Klassische Office-Anwendungen wie Excel oder Open-Office bieten standardmäßig Möglichkeiten, numerische Werte unkompliziert in eine grafische Darstellung
Quellcode Der Quellcode zum Artikel befindet sich auf der beiliegenden CD.
54
PHP Magazin 3.2004
zu verwandeln. Aber wie können grafische Auswertungen mittels PHP automatisiert erzeugt werden – z.B. eingebettet in eine Webseite mit aktuellen Werten aus einer Datenbank? Es existiert eine große Auswahl verschiedener Klassen und Funktionsbibliotheken zur Erzeugung von Graphen. Diese unterscheiden sich zum Teil erheblich in Bezug auf Funktionsumfang, Flexibilität oder Geschwindigkeit – und, nicht zu ver-
gessen, in Bezug auf unterschiedliche Lizenzen. Stellvertretend möchte ich an dieser Stelle kurz auf PHPlot und JpGraph eingehen. PHPlot bietet ein einfaches, prozedurales API und eignet sich als „schlanke“ Klasse gut für schnelle Ergebnisse. Nach außen ist PHPlot als Klasse gekapselt. Im Inneren jedoch werden vielfach ähnliche Funktionsnamen (z.B. SetDrawXGrid(), SetDrawYGrid()) verwendet. Im Gegensatz hierzu würde ein OO-API gleiche Funktionsnamen auf einzelne Objekte anwenden (z.B. axisX->setDrawGrid()). Viele häufig genutzte Features sind implementiert und erlauben den Einsatz für die meisten Standard-Aufgaben. PHPlot verwendet zur Grafikerzeugung die GD-Library. Nach eigenen Angaben ist sie für die Verwendung mit GD 1.2 bis 1.8.3 und PHP 3/PHP 4 ausgelegt. Hieran wird deutlich, dass PHPlot, vielleicht auch aus historischen Gründen, ganz klar vor allem auf (Abwärts-)Kompatibilität ausgelegt ist. Verbreitet wird die Klasse unter der GPLLizenz, was ihre Verwendung in NichtGPL-Projekten erschwert. JpGraph hingegen bietet ein stark objektorientiertes API und darf aufgrund seines reichhaltigen Funktionsumfangs eindeutig als „eierlegende Wollmilchsau“ bezeichnet werden. Durch die starke Objektorientierung ermöglicht JpGraph die individuelle Anpassungen von Feinheiten der zu zeichnenden Graphen über klar strukturierte, hierarchische Methoden/Eigenschaften. Geboten werden auch einige Funktionen, die nur in sehr speziellen Anwendungsgebieten Einsatz finden werden. Neben häufig verwendeten Linien-, Balken- oder Tortengrafiken hat JpGraph auch Spinnennetz-Diagramme oder GanttCharts (welche man von ZeitplanungsTools wie MS Project kennt) im Angebot. Es ist Ansichtssache, ob man JpGraph als universelle Allround-Lösung betrachten oder als teilweise vielleicht etwas überladen bezeichnen möchte. Auch JpGraph verwendet die GD-Library (GD1 oder GD2) zur Grafikerzeugung. Die Verwendung ist ab PHP 4.1.0 möglich, es wird jedoch ausdrücklich PHP 4.3.0 oder höher empfohlen. Als Lizenzmodell haben sich die Entwickler für einen Dual-LicenseVertrieb entschieden: Die QPL 1.0 (Qt Free
Anzeige
Development PEAR::Image_Graph
Abb. 2: Graph zu Listing 2
Abb. 1: Graph zu Beispiel-Skript 1
License) erlaubt die freie Verwendung im nichtkommerziellen, Open Source- bzw. Forschungs-Umfeld. Zur kommerziellen Verwendung steht die JpGraph Professional License gegen Zahlung von Lizenzgebühren zur Verfügung.
Listing 1
56
PHP Magazin 3.2004
Die Idee zu PEAR::Image_Graph Anhand der beiden genannten Beispiele wird deutlich, welche sehr unterschiedlichen Ansätze die am Markt befindlichen Klassen und Funktionsbibliotheken verfolgen. Auch innerhalb der PEAR-Community wurden bereits häufiger der Wunsch nach einer „idealen“ Library zur Erzeugung von Graphen geäußert und verschiedene Lösungswege diskutiert. Ziel war die Entwicklung eines Pakets mit klar strukturiertem API, universeller Erweiterbarkeit und falls möglich Grafik-Funktionen, die nicht nur auf die GD-Library beschränkt bleiben sollten. Außerdem galt es beim Konzept, häufig benötigte Funktionen zu berücksichtigen und eine flexible Erweiterung zu ermöglichen, aber gleichzeitig auch eine Überfrachtung mit unnötigen Details und Möglichkeiten zu vermeiden, welche sich evtl. negativ auf die Performance auswirken könnten. Die Idee zu PEAR:: Image_Graph war geboren! Im Folgenden möchte ich die grundlegenden Überlegungen erläutern sowie einen kurzen Überblick über die verwendeten Funktionen/Strukturen und die Erweiterungsmöglichkeiten des API geben.
Das Konzept (OO-API) Basierend auf dem Herzstück, der Klasse Image_Graph, setzt sich ein Graph aus einzelnen Objekten zusammen. Hierbei wurde darauf geachtet, übermäßig viele Hierarchie-Ebenen zu vermeiden, aber trotzdem alle sinnvollen Gruppierungen von Methoden und Eigenschaften mittels einer hierarchischen Objekt-Struktur abzubilden. So existieren für den Graphen beispielsweise ein Objekt für die x-Achse sowie zwei y-Achsen-Objekte, welche
wiederum von einer gemeinsamen Achsenklasse abgeleitet sind und die auf den jeweiligen Achsen benötigte Funktionalität anbieten. Jede Datenreihe im Graph wird als Data-Objekt (z.B. vom Typ line oder bar) verwaltet. Es enthält die darzustellenden Daten sowie zugehörige Methoden und Attribute, welche die spätere Darstellung des Data-Objekts beschreiben (z.B. die Farbe). Fügt man dem Graphen mittels addData() eine neue Datenreihe hinzu, erhält man als Rückgabewert der Funktion auch gleichzeitig eine Referenz auf das neu erzeugte Data-Objekt. Dieses stellt dann individuell passende Methoden zur Verfügung, um Eigenschaften des Objektes gezielt modifizieren zu können. Häufig möchte man die Datenpunkte zusätzlich zur eigentlichen Darstellung (z.B. als Linie) mit Markern hervorheben. Hierzu kann einem Data-Objekt ein DataMarker hinzugefügt werden. Durch die hierarchische Struktur (Graph → DataObjekt → Marker) ist der Marker fest mit einem bestimmten Data-Objekt verbunden. Dies hat später bei der Ausgabe des Graphen den Vorteil, dass der Marker lediglich an einem gegebenen Punkt eine Markierung setzen können muss. An welchem Punkt diese Markierung gesetzt wird, bestimmt das Daten-Objekt. Beim Hinzufügen eines DataMarkers zu einem Data-Objekt liefert die Funktion setDataMarker ebenfalls wieder eine Referenz auf die erzeugte Objekt-Instanz zurück, über welche dann die Anpassung der MarkerEigenschaften (Farbe, Größe …) möglich ist. Aktuell wird Image_Graph mit DataMarkern wie Viereck, Dreieck oder Diamant ausgeliefert. Es ist aber genauso einfach möglich, einen neuen DataMarker zu
PEAR::Image_Graph schaffen, der beispielsweise ein BitmapIcon an der zu markierenden Stelle platziert. Hierfür wäre dann vielleicht zusätzlich zur für DataMarker üblichen API auch eine Methode für das Setzen des Mittelpunktes notwendig. Diese lässt sich jedoch problemlos hinzufügen und über die Objekt-Referenzen genauso einfach ansprechen wie die Standard-Methoden. Analog ist es mittels Füll-Objekten möglich, einem Bar-Graph-Balken oder der Fläche unter einer Linie eine flexible Füllung zuzuweisen. Ein BalkendiagrammObjekt ist hierbei standardmäßig mit einer Vollflächenfüllung vorbelegt. Mitgeliefert werden Objekte für eine einfarbige Vollflächenfüllung sowie einen linearen Farbverlauf zwischen beliebig vielen Farben. Ebenso lassen sich aufgrund des offenen API aber auch eine Radial-Füllung, eine Füllung mit einem Muster sowie Füllungen mit Bitmaps realisieren. Abhängig vom verwendeten Ausgabemedium (aktuell einem GD-Bitmap, später z.B. einer SVG-Vektorgrafik) ist es in den einzelnen Modulen möglich, optimierte Routinen zu verwenden. Eine Radial-Füllung eines beliebigen Objektes ist in der SVG-Vektorgrafik beispielsweise wesentlich einfacher und performanter implementierbar als die Radial-Füllung eines Polygon auf GD-Bitmap-Ebene. Das Konzept von Image_Graph sieht vor, die eigentliche Darstellung des Graphen (das „Zeichnen“) erst als letzten Schritt mittels eines einfachen Aufrufs durchzuführen. Bis dahin modellieren wir lediglich die Daten und Eigenschaften des Graphen. Auf diese Weise ist es möglich, den Graphen über eine nahezu beliebige Ausgabeschnittstelle darstellen zu können. Auch ist es möglich, den einmal modellierten Graphen über mehrere Ausgabewege zu zeichnen. In Version 0.2 von Image_ Graph ist bisher lediglich die GD-Library als Ausgabeschnittstelle vorgesehen. Aber der bereits erwähnte SVG-Export ist ebenso geplant wie eine direkte Anbindung an PIMP, die zukünftige PHP 5 Imaging Library. Sofern man über ein bestehendes Ausgabe-Objekt (z.B. eine GD Image Resource, gefüllt mit Bitmap-Daten) verfügt, ist es auch möglich, den Graph direkt dort einfügen zu lassen. Dies ermöglicht es dann auch, den Graphen beliebig auf einen beste-
henden Untergrund zu zeichnen oder z.B. mehrere Graphen in einer gemeinsamen Grafik zu kombinieren. Wer gern eine Image-Map realisieren möchte, um beispielsweise in einer Besucherstatistik beim Klick auf einen Monat eine neue Seite mit einer Tagesübersicht anzuzeigen, wird mittels weniger Funktionsaufrufe auch die Möglichkeit haben, diese passend zur aktuellen Grafikausgabe generieren zu lassen. Bei Redaktionsschluss stand diese Funktion noch nicht zur Verfügung, sie ist aber auf jeden Fall noch fest für die Versionsreihe 0.2.x eingeplant.
Ein einfacher Graph Nach diesem groben Überblick kommen wir nun zu unserem ersten Graphen (Listing 1). Zuerst setzen wir ein paar FontParameter, um der verwendeten FreeTypeBibliothek z.B. den Pfad für die True Type-Schriften, sowie die zu verwendende Schriftdatei anzugeben. Bitte passen Sie diese Angaben an Ihre lokalen Einstellungen an! Bei obigen Schriftangaben handelt es sich um die defaultFontOptions, welche standardmäßig für alle Beschriftungen gelten. Sofern Sie für einzelne Elemente (hier: die Achsenbeschriftung) z.B. eine andere Schriftgröße oder eine andere Schrift-Datei wählen möchten, ist es nicht notwendig, weitere Details erneut anzugeben. Diese werden automatisch von den StandardOptionen übernommen. Die Darstellung der Beschriftungen wird über das PEARPaket Image_Text realisiert. Im zweiten Schritt nehmen wir ein paar Einstellungen für das allgemeine Erscheinungsbild unseres Graphen vor. Selbstverständlich ist es auch möglich, die Standardeinstellungen einfach beizubehalten. Hier wird deutlich, wie über das hierarchische Objekt-Konzept z.B. die erste y-Achse (Y0) verändert werden kann. Im Beispiel werden sowohl die Farbe als auch die Min-/ Max-Werte der Achse vorgegeben. Setzt man die Achsengrenzen nicht, ermittelt Image_Graph sie automatisch aus den im Graphen dargestellten Werten. Die Achseneinteilungen können auch manuell gesetzt werden. In den meisten Fällen bietet es sich allerdings an, die automatische Achseneinteilung einfach beizubehalten. Um das Beispiel nicht unnötig zu überla-
Development
den, behalten wir die automatische Achseneinteilung bei. Die zweite y-Achse ist in diesem Beispiel nicht relevant, lässt sich aber analog konfigurieren. Sie wird nicht angezeigt, da sie keine Daten enthält. Standardmäßig werden Daten immer auf der ersten y-Achse (Y0) hinzugefügt. Die Verwendung einer separaten y-Achse Y1 ist lediglich in Spezialfällen, z.B. mit sehr unterschiedlichen Wertebereichen, sinnvoll. Image_Graph verwendet für alle Farbangaben übrigens intern die RGBA-Darstellung (also mit Rot-Grün-Blau mit einem Alpha-Kanal). Bei der Zuweisung von Farbwerten aber ist Image_Graph sehr flexibel. Das PEAR-Paket Image_Color dient als Grundlage, um neben Farbangaben in Array-Form (mit Werten im Bereich 0 bis 255) auch Strings nutzen zu können. So werden neben der typischen HTML-Farbnotation (z.B. #4040FF) auch die Angabe über Prozentwerte für die einzelnen Farbkanäle (z.B. 25 %, 25 %, 100 %) oder die Verwendung von Farbnahmen (z.B. blue) unterstützt. In String-Notation kann der gewünschte Alpha-Wert optional als FloatWert im Bereich von 0 bis 1 mittels eines @ als Trennzeichen angehängt werden (z.B. [email protected]). Diese Notation ist einigen Lesern evtl. bereits von JpGraph vertraut. Die aktuelle Version 0.4 von Image_Color stellt einige hilfreiche Funktionen zur Verfügung, unterstützt allerdings bisher weder die automatische Erkennung der Farbangaben-Art noch die Verwendung von Alpha-Kanälen. Diese Funktionen wurden über die interne Klasse Image_Graph_Color „nachgerüstet“. Eine mögliche Integration der neuen Funktionen in Image_Color ist selbstverständlich wünschenswert und wird momentan geprüft. Abschließend gilt es noch, die anzuzeigenden Daten mit der gewünschten Darstellungsart hinzuzufügen. Fertig! Der Aufruf von getGDImage liefert eine GD Image-Ressource, welche wir als Bitmap an den Client schicken und/oder in eine Datei schreiben können. Auch macht es meist Sinn, beispielsweise mittels der Pakete PEAR::Cache bzw. PEAR::Cache_ Lite den generierten Graphen für eine gewisse Zeit zu cachen, um bei erneuten Aufrufen unnötige Serverlast zu vermeiden.
PHP Magazin 3.2004
57
Development PEAR::Image_Graph Stapeln von Daten Manchmal ist es sinnvoll, Daten in einem Diagramm zu stapeln, um zusätzlich zu den Größenverhältnissen der Einzelwerte auch noch einen Eindruck des Gesamtwertes zu erhalten. So ist es beispielsweise möglich, verschiedene Ausgaben in einem Diagramm aufzutragen und sofort einen Überblick über die gesamte Kostenstruktur einer Firma oder eines Projektes zu bekommen. Natürlich lassen sich nur Daten gleichen Typs (z.B. Balken-Diagramme)
stapeln. Auch kann man nur Werte stapeln, welche sich auf die gleiche y-Achse beziehen. Einen Eindruck der StapelFunktion bietet Ihnen der Sourcecode in Listing 2. Zum Stapeln von Daten wird die Funktion stackData() verwendet. Diese kann entweder selbstständig alle im Diagramm enthaltenen Elemente übereinander schichten oder man gibt vor, welche Daten-Typen gestapelt werden sollen. Im Beispiel entscheiden wir uns lediglich, alle Elemente vom Typ bar zu stapeln. Möchte
auf einen nicht stapelbaren Daten-Typ hin – die Daten dieses Typs werden also einfach weiterhin ungestapelt ausgegeben. Die Ausgabe der gestapelten Objekte übernimmt, im Falle eines GD-Bitmaps, die ebenfalls statische Funktion stackingDrawGD. Sie verfügt über detailliertes Wissen des gewählten Daten-Typs und kann somit beispielsweise entscheiden, in welcher Reihenfolge die gestapelten Daten ausgegeben werden. Hierüber ist eine Optimierung auf die Fähigkeiten des jeweiligen Ausgabemediums möglich.
Wer möchte, hat mittels eines optionalen Rasters (grid) die Möglichkeit, horizontale und/oder vertikale Unterteilungen in sein Diagramm einfließen zu lassen. Es gibt Fälle, in denen z.B. horizontale Linien als Trennung eine bessere Ablesbarkeit der Werte im Diagramm sowie ein besseres Gefühl für Größenverhältnisse vermitteln. Unabhängig vom Linienraster ist es auch möglich, die Bereiche auf der x-/y-Achse mit einer Füllung zu versehen. Variante c unseres Beispielskripts zeigt die Verwendung von zwei abwechselnden, einfarbigen Füllungen. Hierbei werden standardmäßig die Bereiche zwischen den „Major Ticks“ (den großen, mit Zahlen beschrifteten Achsenunterteilungen) gefüllt. Über einen Aufruf der Funktion setFillType() können auch die „Minor Ticks“ bei der Füllung mit einbezogen werden. Analog ist selbstverständlich auch eine senkrechte Füllung (des gridX) möglich. Mit horizontalen und vertikalen Füllungen können bei geeigneter Wahl der Farben/Alpha-Kanäle interessante Effekte erzielt werden. Aufgrund des flexibel gestalteten API ist neben einer einfarbigen Vollfüllung natürlich auch die Verwendung von beliebigen anderen Image_ Graph_Fill_…-Objekten (z.B. einem linearen Farbverlauf) möglich. Hierbei sollte man allerdings vorsichtig sein, mit „technischen Spielereien“ nicht die Lesbarkeit des Graphen zu beeinflussen. Die Wahl von beispielsweise verschiedenartig schraffierten Flächen in Schwarz/Weiß für einen optimalen Ausdruck auf Papier kann aber durchaus sinnvoll sein.
gramme realisieren. Aber es gibt noch genügend Ideen, um den Funktionsumfang der Klasse Image_Graph zu erweitern. Geplant sind unter anderem:
Grafische Möglichkeiten Selbstverständlich wirkt ein Diagramm umso besser, je ansprechender sein Layout ist – beispielsweise durch die gezielte Verwendung einer Füllung. Aktuell steht neben einer einfarbigen Vollflächenfüllung noch ein linearer Farbverlauf zur Verfügung. Aber durch das Design einer eigenen Füllmethode sind der Fantasie (fast) keine Grenzen gesetzt. Wie aus Listing 3 ersichtlich, ist es mit nur wenigen Zeilen möglich, eine ansprechende Füllung zu erzeugen. Die mit den Kommentaren /*b*/ und /*c*/ am Zeilenanfang markierten Zeilen sind mögliche Alternativen. Verwenden Sie zur Erzeugung der Abbildung 3 vorerst bitte nur die mit a markierten Zeilen. Mittels der Angabe einer zusätzlichen Füllfarbe für den Farbverlauf sowie der Verwendung von Alpha-Werten lässt sich die Füllung noch ein wenig verfeinern. Außerdem möchten wir an dieser Stelle demonstrieren, welche einfachen Effekte durch die Übergabe einer bereits existierenden GD-Ressource an getGDImage erzeugt werden können. Durch Verwendung der mit Kommentar /*b*/ versehenen Zeilen erhalten Sie das in Abbildung 4 zu sehende Ergebnis. Durch die Verwendung von Alpha-Kanälen ist das PEAR-Logo im Hintergrund klar erkennbar.
• Bitmap-Support (für Hintergrundbilder, Füllungen …) • neue Füll-Methoden • 3-D-Bars • Kurven (als „weiche“ Alternativen zum Daten-Typ line) • eine Legende • Support für PHP-interne Bitmap-Fonts, sodass auch ohne FreeType-Library die Ausgabe von Beschriftungen möglich ist • eine numerische x-Achse sowie logarithmische y-Achsen • Integration von SVG- und PIMP-Ausgabemöglichkeiten Durch das flexibel und offen gestaltete API ist es voraussichtlich möglich, alle genannten Features durch Nutzung und Erweiterung des vorhandenen API zu implementieren. Ich möchte allerdings an dieser Stelle erwähnen, dass sich Image_Graph mit der Version 0.2.x noch im Alpha-Stadium befindet. Die Release Notes des Pakets sind jederzeit eine hilfreiche Quelle für Erweiterungen sowie (hoffentlich nur geringfügige) Änderungen. Stefan Neufeind ist Informatik-Student und beschäftigt sich seit mehreren Jahren intensiv mit Open Source/Linux/ PHP. Neben seinem Studium ist er als freier Mitarbeiter für die Internet-Agentur Speed Partner tätig. Kontakt: neufeind@ speedpartner.de
Ausblick Beim Wechsel zur Version 0.2 wurde das API grundlegend neu designt und durch die Objektorientierung wesentlich flexibler gestaltet. Mit den zur Verfügung stehenden Features lassen sich bereits eine ganze Reihe individuell gestalteter Dia-
PDF-Dateien ohne Zusatzsoftware im Browser anzeigen lassen Zum Lesen von PDF-Dateien steht eine Reihe von kostenlosen Programmen zur Verfügung, jedoch hat nicht jeder Benutzer ein solches installiert. Möchte man sich in seiner Webapplikation nicht darauf verlassen, müssen PDF-Dateien im Browser ohne Zusatzsoftware angezeigt werden. Einen grundlegenden Ansatz dafür soll dieser Artikel vorstellen.
Für das elektronische Archivieren und Verbreiten von Dokumenten hat sich in den letzten Jahren Adobes PDF-Format etabliert. Entsprechend groß ist die Anzahl an freien und kommerziellen Tools zum Erstellen, Lesen und Verwalten von PDF-Dateien. Da verwundert es kaum, dass auch für PHP mehrere Bibliotheken zur Verfügung stehen, mit denen sich PDF-Dateien generieren lassen. Möchte man in seiner Webapplikation auch PDF-Dateien anzeigen, so schickt man diese einfach an den Browser. Dieser ruft auf dem Client das dafür vorgesehene Programm, z.B. Adobe Reader, auf. Ist eine solche Anwendung nicht vorhanden, geht die ganze Sache schief. Sicherlich sind auf den Computern der meisten Privatanwender solche Anwendungen vorhanden bzw. lassen sich schnell nachinstallieren. In großen Firmennetzwerken mit mehreren Hunderten oder gar Tausenden von Clients und unterschiedlichen Plattformen steigt der Administrationsaufwand mit jeder Anwendung an. Soll eine Webapplikation in einer solchen Umgebung sich auf die Darstellung
Quellcode Der Quellcode zum Artikel befindet sich auf der beiliegenden CD.
60
PHP Magazin 3.2004
von PDF-Dokumenten verlassen können oder will man innerhalb seiner Internetseite auf Nummer sicher gehen, so muss man einen anderen Weg einschlagen. Mit einigen wenigen Tools, die den meisten Linux-Distributionen beiliegen bzw. unter Windows schnell installiert sind, lassen sich serverseitig aus PDF-Dateien Bilder erstellen, die jeder Browser ohne zusätzliche Mittel darstellen kann. Damit ist der Weg zu einem Web-basierten Anzeigeprogramm für PDFDateien nicht mehr weit.
Vorbereitungen Die Hauptaufgabe besteht nun darin, aus einem vorhandenen PDF-Dokument jede Seite einzeln auszulesen und diese in eine Bilddatei umzuwandeln. Hierfür benutzen wir GhostScript [1]. Es dient in erster Linie zum Darstellen bzw. Ausdrucken von PostScript-Dateien, ist jedoch auch in der Lage, sowohl PostScript- als auch PDFDokumente in PNG-Dateien umzuwandeln. Der folgende Aufruf konvertiert die Datei test.pdf: gs -dBATCH -dNOPAUSE -sDEVICE= png16m -sOutputFile=page%d.png temp.pdf
Die ersten beiden Parameter sorgen dafür, dass GhostScript nicht nach jeder konvertierten Seite auf neue Befehle wartet und sich zudem nach Abarbeitung des Quell-
dokuments selbst beendet. Mit DEVICE wird das Format der Ausgabedatei definiert. GhostScript bietet hier eine Fülle an Möglichkeiten an, die in der Dokumentationsdatei catalog.devices beschrieben sind. Tabelle 1 listet die für uns wichtigsten Ausgabeformate auf. Da GhostScript für jede Seite der Quelldatei eine neue Bilddatei erstellt, müssen wir bei der Angabe der Ausgabedatei den Platzhalter %d einfügen. An dessen Stelle setzt GhostScript die Seitennummer ein. Sollten Sie diesen Befehl ausprobieren und die daraus erzeugten Bilder betrachten, so fällt die nicht gerade überzeugende Bildqualität auf. Mit einem weiteren Tool lässt sich jedoch zum einen die Bildqualität deutlich steigern und zum anderen eine Zoom-Funktion in die Webapplikation einbauen. Zuerst weisen wir GhostScript mithilfe des Parameters -r300 (setzt den DPI-Wert auf 300) an, die erzeugten Bilder deutlich größer zu generieren, als wir sie schlussendlich benötigen. Die Qualität ist damit zwar immer noch nicht besser, mit dem Befehl mogrify (ein Bestandteil von ImageMagick [2]) können wir die großen Bilder auf eine normale Größe herunterrechnen lassen. Da Mogrify hierfür deutlich bessere Algorithmen implementiert hat als GhostScript, kann sich das resultierende Ergebnis durchaus sehen lassen. Für die Verwaltung von mehreren PDFDokumenten möchte man einige Informationen aus den Dokumenten gewinnen, z.B. die Anzahl und Größe der Seiten. Hierzu machen wir von dem kleinen Tool pdfinfo aus dem Programm xpdf [2] Gebrauch. Es liefert auf der Shell die wichtigsten Daten zu einer angegebenen PDFDatei.
Grundgerüst Zunächst benötigen wir ein Verzeichnis, in dem wir alle PDF-Dokumente hinterlegen können, die von der Webapplikation angepnggray
8-bit-Graustufen-PNG-Datei
png256
8-bit-PNG-Datei
png16m
24-bit-PNG-Datei
jpeggray
RGB-JPEG-Datei
jpeg
Graustufen-JPEG-Datei
Tabelle 1: Ausgabeformate von GhostScript
PDFs direkt im Browser anzeigen zeigt werden sollen. Dieses nennen wir hier sinngemäß pdf und stellen es unterhalb des Web-Roots, damit die Dateien nicht direkt aus dem Browser heraus angezeigt bzw. aufgerufen werden können. Diese Dateien können von einem anderen PHP-Skript erstellt oder von einem Benutzer mittels einer Upload-Funktion kopiert worden sein. Im Web-Root selbst erstellen wir das Verzeichnis img, in das unser PHP-Skript die aus den PDF-Dateien gewonnene Bilder abspeichert. Das Anzeigeprogramm für PDF-Dateien soll in der hier vorgestellten Version nur zwei Ansichten haben: eine Hauptansicht, in der alle verfügbaren Dokumente zur Auswahl angezeigt werden, und eine Ansicht zum Darstellen der eigentlichen Dokumente. Die Steuerung der Ansichten übernimmt die Datei index.php (Listing 1). Wird sie ganz ohne Parameter aufgerufen oder hat der Parameter action den Wert 0, wird die Hauptansicht angezeigt. Bei action=1 wird die angegebene Seite page der Datei name mit dem Zoom-Faktor zoom angezeigt. Die Parameter action, page und name werden mit der URL übergeben. Da der Zoom-Faktor in einer HTMLForm eingegeben wird, befindet sich die dazugehörige Variable zoom in dem Feld $_POST.
Herzstück Alle Funktionen zum Verwalten der PDFDateien und zum Generieren der Bilder stellen wir zu der Klasse pdfview zusammen, die sich in der Datei pdfview.php (Listing 2) befindet. Zum Darstellen der Hauptansicht wird die Funktion showMainWindow() verwendet. Sie liest alle PDF-Dateien in dem Verzeichnis ../pdf ein und holt sich über die Funktion getPdf Info() die dazugehörigen Informationen. Viel interessanter ist die Funktion show Pdf(). Diese erzeugt aus einer PDF-Datei in mehreren Schritten die benötigten Bilder. Da diese natürlich nur einmal erzeugt werden müssen, prüft die Funktion, ob das ausgewählte Dokument bereits konvertiert worden ist. Schauen wir uns die einzelnen Schritte einer Konvertierung genauer an: • Es wird zuerst das Grundverzeichnis für das Dokument erzeugt. Es trägt den gleichen Namen, bis auf die Dateiendung,
Development
Abb. 1: Online PDF-Dateien lesen
wie die Datei selbst. In diesem Verzeichnis erzeugen wir wiederum zwei Unterverzeichnisse. In small wollen wir später aus den einzelnen Dokumentseiten kleine Icons generieren, anhand derer man die einzelnen Seiten auswählen kann. Die eigentlichen Seiten werden in normal gespeichert. • Jetzt rufen wir GhostScript zum Erzeugen der Bilddateien auf. Da dies etwas länger dauern kann, muss am Anfang der Funktion set_time_limit() aufgerufen werden, das die maximale Laufzeit des Skripts (Standardwert beträgt 30 Sekunden) erhöht. • Ist GhostScript fertig, so erstellen wir Kopien der Bilddateien in den beiden Unterverzeichnissen. • Mit Mogrify erzeugen wir zuerst die Icons. Dafür geben wir mit dem Parameter scale eine Breite von 120 Pixeln an. Mogrify berechnet die Höhe so, dass das ursprüngliche Seitenverhältnis erhalten bleibt. • Die Standardbilder skalieren wir mit Mogrify auf ihre eigentliche Größe herunter. Dazu lesen wir mit der Funktion getPdfInfo() die Größenangaben aus der PDF-Datei heraus.
Anzeige Da wir eine Zoom-Funktion einbauen wollen, wird vor der Anzeige der ausgewählten Dokumentseite geprüft, ob der Benutzer die Darstellungsgröße geändert hat. Der Zoom-Wert ist auf den Standardwert 100 Prozent gesetzt. Dies entspricht den Bildern im Verzeichnis normal. Möch-
Anpassen an Windows Ohne größere Änderungen lässt sich das Programm auch auf einem Windows-Server aufbauen. Die drei benötigten Programme GhostScript, ImageMagick und xpdf liegen auf den jeweiligen Internet-Seiten auch in kompilierter Form für Windows vor. Da die ausführbaren Dateien der Programme meistens nicht im Suchpfad des Systems eingetragen sind, müssen hier die kompletten Verzeichnisnamen angegeben werden. Zusätzlich ist noch zu beachten, dass gs unter Linux lediglich ein Link auf gs_x11 ist. Unter Windows muss stattdessen gsw32 aufgerufen werden.
Listing 1 index.php
PHP Magazin 3.2004
61
Development PDFs direkt im Browser anzeigen te der Benutzer die Seite kleiner oder größer dargestellt haben, so macht es wenig Sinn, die bereits heruntergerechneten Bilder zu verwenden. Genau dafür haben wir uns die von GhostScript erzeugten Origi-
naldateien im Grundverzeichnis des Dokuments erhalten. Aus der angegebenen Seitennummer $page wissen wir, welche Datei mit Mogrify skaliert werden muss. Zuvor gilt es jedoch, einen Dateinamen zu
Mitglied als Unterstützer, bevor es zur eigentlichen Abstimmung kommt • eine Abstimmungsperiode beginnt um Mitternacht (UTC) und dauert maximal vier Tage • stimmt ein Mitglied innerhalb dieser vier Tage nicht ab, so wird seine Stimme automatisch als Enthaltung („abstain“) gewertet
von Kai Schröder
Diese Regeln sind auf den ersten Blick klar formuliert, bieten aber dennoch Anlass zur Kritik. Die originale Formulierung „more than half of the voting members“ schließt den Fall „exakt die Hälfte“ nicht mit ein, greift in der aktuellen Konstallation der PEAR Group von acht Mitgliedern bei einem 4:4 nicht. An dieser Stelle wäre meiner Meinung nach „half or more“ die bessere Formulierung gewesen. Des Weiteren spricht der Zusatz „voting“ dafür, dass nur die tatsächlich abgegebenen Stimmen berücksichtigt werden, nicht aber die automatisch als Enthaltung gewerteten. An dieser Stelle sollte man noch mal sprachlich nachbessern, um den Interpretationsspielraum so gering wie möglich zu halten. Weiterhin fällt auf, dass eine anonyme Bekanntgabe der Ergebnisse empfohlen wird. Nun mag man meinen, „closed decisions“ und Open Source würden sich beißen, ich sehe das aber nicht so. Dadurch, dass hinterher niemand weiß, welches Mitglied wie abgestimmt hat, entfällt die Überlegung des Abstimmenden, ob er sich unbeliebt macht oder nicht und es werden persönliche Konflikte (ich erinnere da nur an den Metabase-Autor Manuel Lemos) vermieden. Die Länge einer Abstimmungsperiode von vier Tagen mag man als kurz betrachten, sie führt aber andererseits zu einer raschen Entscheidungsfähigkeit des Gremiums. Eine abstimmungsfreie Zeit ist zwar nicht explizit vorgesehen, lässt sich aber sicher für Fälle wie gemeinsame Abwesenheit mehrerer Mitglieder intern vereinbaren. Was ich allerdings ernsthaft vermisse, ist eine bindende Möglichkeit seitens der User, auf die PEAR Group einzuwirken. Es ist weder vorgesehen, dass per „Volksentscheid“ eine Abstimmung initiiert werden kann, noch gibt es ein Vetorecht seitens der Nicht-Mitglieder. In der ungünstigsten Konstellation können also drei Ent-
Eine kritische Betrachtung der neu gegründeten PEAR Group Am 9. Mai 2003 fand am Rande der vom Software und Support Verlag ausgerichteten International PHP Conference in Amsterdam auch ein von Lukas Smith initiiertes PEAR-Meeting statt. Als ein Ergebnis dieses Treffens gab Stig S. Bakken, der Vater des PEAR-Projektes, am 12. August 2003 die Gündung der PEAR Group [1] bekannt. Diese hat zur Aufgabe, sich zukünftig um die Verwaltungsbelange von PEAR (PHP Extension and Application Repository) zu kümmern. Seit dieser Ankündigung ist inzwischen ein gutes halbes Jahr ins Land gegangen und wir werden nun einmal schauen, ob bisher mehr als nur Vorsätze entstanden sind.
Als Gründungsmitglieder werden neben Stig auch Lukas Smith (MDB), Pierre-Alain Joye (pearfr.org, GD-Extension), Martin Jansen (dclp*-FAQ, PEAR-Dokumentation und -Administration), Alan Knowles (Midgard, phpmole-IDE, php-gtk, bcompiler), Jesus M. Castagnetto (diverse MathPakete), Tomas V. V. Cox (DB, diverse XML-Pakete) und Jon Parise (Horde-Projekt) benannt. Diese acht Mitglieder haben sich neben den genannten Projekten auch allgemein um die Verbreitung von PEAR verdient gemacht und sind alle gleichberechtigt.
Beschlüsse und Mitgliedschaft Diese Gleichstellung sowie die Mitgliedschaft in der PEAR Group sind im ersten Beschluss vom 20. August 2003 (siehe [2]) festgelegt. Dieser beschreibt den Ablauf eines Abstimmungsverfahrens wie folgt: • jedes Mitglied kann mit „yes“, „no“ oder „abstain“ (Enthaltung) stimmen • eine Entscheidung gilt als unentschieden, bis ein positives Abstimmungsergebnis erzielt wird • enthält sich mehr als die Hälfte der Mitglieder, so ist die Entscheidung ebenfalls unentschieden • es wird empfohlen, das Ergebnis außerhalb der PEAR Group zu anonymisieren, d.h., es wird nur die Anzahl der jeweiligen Stimmen veröffentlicht, nicht aber wer wie gestimmt hat • jedes Mitglied kann eine Abstimmung initiieren, benötigt aber ein weiteres
PHP Magazin 3.2004
63
Development PEAR Group wickler die Belange des gesamten Projektes bestimmen, aber warten wir es ab. Die Mitgliedschaft selbst ist verhälnismäßig einfach geregelt: • man kann selber gehen • man kann von den restlichen Mitgliedern aus der Gruppe rein- oder rausgewählt werden • die maximale Anzahl von Mitgliedern sollte neun nicht übersteigen Offen bleibt, wie es zur Abstimmung über ein neues Mitglied kommt, naheliegend ist aber der Vorschlag, durch ein bestehendes Mitglied verbunden mit der Unterstützung eines weiteren Mitglieds (siehe oben).
Neue Pakete Eines der wohl wichtigsten Themen innerhalb des PEAR-Projektes sind die Pakete, deshalb verwundert es nicht, dass dieses Thema bereits frühzeitig, nämlich am 4. September 2003, in einem Beschluss (siehe [3]) abgehandelt wurde. Leider merkt man diesem Beschluss aber einen gewissen Zeitdruck an, aber dazu später mehr. Generell wurde beschlossen, dass zukünftige Pakete erst einen Genehmigungsprozess durchlaufen müssen, um Bestandteil von PEAR zu werden, und dass alte Pakete, die die Bedingungen nicht erfüllen, entfernt werden. Gänzlich offen bleiben dabei aber ein ungefährer Zeitplan sowie die Zuständigkeit für die Prüfung der bereits vorhandenen Pakete. Des Weiteren wurde eine Weboberfläche in Aussicht gestellt, um diesen Prozess zu vereinfachen, auch hier fehlt aber eine genaue Planung. Um ein neues Paket in PEAR einzubringen, kann jeder einen entsprechenden Antrag an die PEAR-Entwicklerliste stellen. In diesem hat der Antragsteller sein Paket zu beschreiben und mit eventuell bereits vorhandenen ähnlichen Paketen zu vergleichen. Des Weiteren hat er vorhandenen Code als Links auf .phps-Dateien zur Verfügung zu stellen und Beispiele zu veröffentlichen, wie man den Code benutzt. In Ausnahmefällen kann der Antrag aber auch ohne vorhandenen Code gestellt werden. Dies ist aus meiner Sicht insofern unlogisch, da der Abstimmung über ein neues
64
PHP Magazin 3.2004
Paket die Beurteilung des Codes vorausgehen sollte. Das Ergebnis von zu häufiger Nutzung dieser Regelung führt recht schnell zu einer Ansammlung von Projektleichen, wie sie zum Beispiel SourceForge zu Hunderten hat. Auch die Veröffentlichung von direkt einsehbaren Quellen als .phps-Dateien macht nicht sonderlich viel Sinn aus Sicht des Testers. Besser wäre aus meiner Sicht die Veröffentlichung eines gepackten Archivs zum Download. Dazu könnte man einen Bereich auf dem PEARWebserver zur Verfügung stellen und die eingereichten Pakete gleich durch ein Mitglied der PEAR Group hinsichtlich des Inhalts (ausschließlich PHP-Quellcode und Dokumentation) vor der Freigabe prüfen lassen. Damit verbunden könnte auch gleich der dazugehörige Antrag in ein Webformular eingegeben werden. Dadurch wäre sichergestellt, dass es nur vollständige Anträge auf die Liste schaffen und dass der Code allen ohne zusätzliche Kosten für den Antragsteller zur Verfügung steht. Ich denke dabei speziell an Leute, die keinen eigenen Webspace besitzen oder über recht geringe Traffic-Kontingente verfügen. Des Weiteren fehlt dem Beschluss eine Aussage über die Form des Codes (Verweis auf die PEAR Coding Standards) sowie die Dokumentation. An dieser Stelle ist dringend Nachbesserung erforderlich, weil ansonsten das Entfernen von regelwidrigem Code nicht erfolgen kann – es gibt nämlich keine Regeln. Ist der Antrag eingereicht, so kann er auf der PEAR-Entwicklerliste diskutiert werden. Sobald sich ein Konsens (insbesondere über den endgültigen Paketnamen) abzeichnet, kann vom Antragsteller ein „call for a vote“ (Aufruf zur Abstimmung) eingereicht werden. Dazu muss er eine Mail an die PEAR-Entwicklerliste und die Mailingliste der PEAR Group schicken. Diese Abstimmung läuft dann eine Woche und wird anschließend ausgewertet. Stimmberechtigt ist jeder, gewertet werden aber nur die Stimmen von Inhabern eines gültigen PEAR-Web-Accounts und der Antragsteller ist ebenfalls ausgeschlossen. Abgestimmt werden kann mit +1 (dafür) oder -1 (dagegen) wobei im letzten Fall eine Begründung zwingend anzugeben ist, bei Zustimmung ist sie lediglich erwünscht. Aus der Begründung sollte klar hervorgehen,
wie weit sich der Abstimmende mit dem vorliegenden Code befasst hat, sofern dieser überhaupt vorhanden ist (siehe oben). Die Abstimmungsergebnisse werden aufsummiert, das heißt, ein -1 hebt ein +1 auf. Offen ist aber, auf welcher Mailingliste abgestimmt wird, wobei eine Abstimmung auf der PEAR-Entwicklerliste (pear-dev) naheliegend ist und die Abstimmung per Webinterface ja bereits in Aussicht gestellt wurde. Aus der Summe der Begründungen bildet sich die PEAR Group ihre Meinung und macht von ihrem Veto-Recht Gebrauch, wenn die Meinung der PEAR Group und das Abstimmungsergebnis sich nicht decken. Auch wenn es nicht explizit ausformuliert ist, so gehe ich aufgrund des Kontextes davon aus, dass das Veto-Recht nur zur Ablehnung eines Paketes vorgesehen ist – auch hier würde eine präzisere Formulierung Klarheit schaffen. Später soll das noch zu bildende PEAR-QA-Team (zuständig für Qualitätssicherung) die PEAR Group bei der Entscheidungsfindung unterstützen. Um noch etwas mehr Verwirrung zu stiften, gibt es die Möglichkeit der abhängigen Stimme („conditional vote“). Diese Stimme ist an eine Bedingung gebunden und sollte nicht mitgezählt werden. Aus meiner Sicht ist diese Form der Abstimmung aber völlig fehl am Platz, denn wenn ich bestimmte Veränderungen vorschlagen will, so kann ich das auch vorher in der Diskussionsphase tun und nicht erst, wenn ein Teil der Stimmen bereits abgegeben ist. Des Weiteren soll ja diese Stimme nicht mitgezählt werden, sodass es wohl eher selten vorkommen sollte, dass jemand seine einzige Stimme an eine Bedingung bindet. Liegt ein positiver Überhang von fünf Stimmen vor, so gilt das Paket als angenommen (sofern kein Veto der PEAR Group erfolgt). Warum an dieser Stelle keine prozentuale Regelung (z.B. einfache Mehrheit, 2/3-Mehrheit) in Verbindung mit einer Mindeststimmenzahl getroffen wurde, ist mir leider völlig unklar. Eine Annahme des Paketes durch die Abstimmenden führt aber nicht zwangsläufig zur Aufnahme in PEAR, diese ist durch den Antragsteller separat bei der PEAR Group unter Berufung auf die Abstimmung einzureichen. An dieser Stelle
PEAR Group ergibt sich der nächste Knackpunkt des Verfahrens: erst jetzt ist die Lizenz anzugeben, welche nicht explizit eingeschränkt ist. Weder kommerzielle noch mit der PHP License unverträgliche Lizenzen sind ausgeschlossen, was spätestens dann zu Problemen führt, wenn PHP zusammen mit betroffenen PEAR-Paketen ausgeliefert wird (wir erinnern uns an MySQL). Neben allen angesprochenen Mängeln bleibt zu überlegen, ob man dem Antragsteller nicht einen Betreuer zur Seite stellt, wie es bei Abstimmungen im Usenet seit Jahren üblich ist. Dieser könnte für die Einhaltung des Verfahrens sorgen und den Antrag inhaltlich hinsichtlich Formulierung und Vollständigkeit betreuen.
Versionierung Am 14. November folgte neben den Dokumenten „Forming of the PEAR Core List“ und „Package Directory Structure“ auch eines namens „New Guidelines for BC breaking Releases“. Aus meiner Sicht ist der Name etwas unpassend gewählt, weil sich dieses Dokument (siehe [4]) primär mit der Versionierung (speziell der Hauptversionen) befasst und weniger auf Rückwärtskompatibilität eingeht. Generell wird festgelegt, dass jede Hauptversion ein eigenes Paket ist. Dies kommt dem Anwender entgegen, weil man nun getrennte Dokumentationen veröffentlichen und leichter überschaubare Abhängigkeiten formulieren kann. Um ein wenig Ordnung in die Benennung zu bringen, gibt es drei erlaubte Formen: • PaketHauptversion (z.B. Foo3) • PaketvHauptversion (z.B. Foov3) • Paket_vHauptversion (z.B. Foo_v3) Bei dieser Regelung haben die Mitglieder der PEAR Group bedingt Weitblick bewiesen und eventuelle Unklarheiten von vornherein ausgeschlossen (sofern man die Regeln intelligent einsetzt und die Hauptversion größer 1 ist). So wäre aus dem Paketnamen IPv4 nicht ersichtlich, ob es sich um Version 4.x von IP oder um Version 1.x von IPv4 (im Gegensatz zu IPv6) handelt. Ein weiteres Beispiel wäre DB2, welches DB 2.x oder ein Paket für IBMs DB2 sein könnte. Die letzte Variante ist allerdings nur für den
Notfall vorgesehen, weil sie sich mit der Benennung zur Abgrenzung von Verzeichnissen deckt. Offen bleibt allerdings die initiale Versionsnummer, diese könnte 0.x oder 1.x sein, sowie die Regel zur Benennung solcher Pakete. Auch das generelle Format von Versionsnummern ist hier nicht enthalten. Des Weiteren wurde festgelegt, dass der PEAR Installer zukünftig zwar auf neuere Hauptversionen hinweisen soll, diese aber nicht automatisch zu installieren hat. Dadurch bleibt man kompatibel zu Tutorials, die die Installation in der Form pear install Foo wiedergeben.
Mailinglisten Wirft man einen Blick in das PHP-Mailinglisten-Archiv (siehe [5]), so findet man gleich neun Listen zum Thema PEAR, nämlich php.pear (stillgelegt am 15. März 2001), php.pear.bot (Entwicklung von pearbot, kein Traffic seit 26. Juli 2003), php.pear.core (neu seit 15. Dezember 2003), php.pear.cvs (die DiffMessages vom CVS-Server), php.pear.dev, php.pear.doc (die PEAR-Dokumentation), php.pear.general, php.pear.qa (neu seit 22. Mai 2003) und php.pear.webmaster (hier sammelt sich jede Menge Spam an). Um in dieses Chaos ein wenig mehr Ordnung zu bringen, wurde eine Neuregelung der Zuständigkeiten beschlossen (siehe [6]). Zum einen gibt es die neue Liste pear-core, die sich mit allen Themen befassen soll, die das gesamte Projekt betreffen, insbesondere die Entwicklung von pearweb (die Website pear.php.net/) und dem PEAR Installer, die Qualitätssicherung innerhalb von PEAR und die Festlegung von Standards mit PEAR-Relevanz. Die Mitglieder der bestehenden pear-qaListe sind automatisch auch Mitglied von pear-core und die Mitglieder von php-qa und pecl-dev sind herzlich eingeladen. Des Weiteren wird darauf verwiesen, dass die PEAR-Entwicklerliste zukünftig nur noch für die eigentliche Entwicklung von Paketen zuständig ist, die Rolle von peardoc sollte selbsterklärend sein. Offen bleibt allerdings, was zukünftig bezüglich Qualitätssicherung nach pear-qa und was nach pear-core gehört, Crosspostings sind zu erwarten. Weiterhin ist die Rolle von pear-general nicht weiter ausgeführt,
Development
ich persönlich würde eine Umbenennung in pear-user erwägen, um Kollisionen mit pear-core zu vermeiden.
Verzeichnisstruktur Das letzte der drei Dokumente vom 14. November 2003 befasst sich mit der Vereinheitlichung der Verzeichnisstruktur im CVS und nach der Installation (siehe [7]). Für ein Paket namens Name unterhalb von Foo/Bar würde die Verzeichnisstruktur so aussehen, wie im Kasten dargestellt. Foo_Bar_Name | +— Name (enthält Modul.php) | +— data | +— docs | | | +— examples | +— misc | +— scripts | +— tests
Das enthaltene Verzeichnis Name ist entsprechend dem eigentlichen Paketnamen umzubenennen, für Cache_Lite hieße es also Lite. In diesem Verzeichnis befinden sich alle tiefer in der Hierarchie angeordneten Module, also zum Beispiel Foo_Bar_Name_Modul. Die beiden Verzeichnisse data und misc sind optional, weil sie für die meisten Pakete nicht benötigt werden. Dem entsprechend braucht man sie auch nicht als leere Verzeichnisse ins CVS einzufügen. Zwingend erforderlich sind jedoch die Verzeichnisse docs/example und tests. Ersteres enthält Beispielcode für die Verwendung des Paketes und ersetzt (hoffentlich temporär) die Dokumentation des Paketes. Im zweiten Verzeichnis sind Testskripte unterzubringen, deren Format zwar (vorläufig) grundsätzlich frei ist, es wird aber PHPUnit oder .phpt empfohlen. Das Packet PHPUnit selbst findet man unter [8], einen Einführungsartikel des PHPUnitMaintainers Sebastian Bergmann sowie einen weiteren zur allgemeinen Entwicklung von Unit Tests findet der interessierte Leser im PHP Magazin 03.2003 (Titelthema „Unit Tests“). Eine kurze Einführung in die
PHP Magazin 3.2004
65
Development PEAR Group Entwicklung von .phpt-Dateien findet man auf der Website des PHP-QA-Teams (siehe [9]). Der Inhalt von scripts wird bei der Installation in ein Verzeichnis kopiert, das in $PATH mit beinhaltet ist, z.B. /usr/local /bin. Wer es genau wissen will, der schaue einfach mittels pear config-get bin_dir auf seinem Rechner selber nach, unter SuSE 9.0 erhält man bin_dir=/usr/bin als Antwort. Und wohin mit dem ganzen Rest, sofern es überhaupt welchen gibt (siehe oben)? Ganz einfach, ab damit nach misc.
Fazit In den ersten Monaten der PEAR Group wurden einige wichtige Themen angefasst, aber aus meiner Sicht zu früh wieder fallen gelassen. Die vorhandenen Beschlüsse sind entweder nicht komplett zu Ende gedacht oder aber zumindest ungenau ausformuliert, andere wichtige Themen wurden bisher noch gar nicht angegangen. So fehlen zum Beispiel fixierte Regeln, wie ein PEARPaket zukünftig auszuschauen hat und wie es zu dokumentieren ist. Man kann sich diese Informationen zwar auf der PEARWebsite zusammensuchen, ob diese aber auch zukünftig bindend sind, bleibt (vorläufig) unklar. Des Weiteren sollte das Verfahren zum Einbringen eines neuen Paketes noch einmal überdacht werden, weil meiner Meinung nach einfach zu viele Kritikpunkte vorhanden sind. Am Ende wären konkrete Timelines noch wünschenswert. Auch wenn ich in diesem Artikel sehr viel gemeckert habe, sollte dennoch nicht vergessen werden, das sowohl die PEAR Group als auch die Community dahinter eine Gruppe von Freiwilligen ist, die hoffentlich auch weiterhin anwächst. Vor diesem Hintergrund wird verständlich, warum bestimmte Dinge nicht so schnell gehen, wie man selbst es gerne hätte. Abschließend hoffe ich, dass zumindest die beiden deutschen Mitglieder der PEAR Group, Lukas Smith und Martin Jansen, diesen Artikel lesen und nach gründlicher Abwägung in ihre zukünftige Arbeit innerhalb der PEAR Group einfließen lassen und freue mich schon auf den Dialog zum Thema, der zumindest mit Lukas als Quasi-Nachbar in Kürze persönlich stattfinden wird. Alle anderen können sich gerne per eMail bei mir melden.
66
PHP Magazin 3.2004
Nachtrag PHP ist ein schnelles Thema, es ändern sich zum Teil täglich Details. Was vorgestern noch harte Fakten waren, kann heute schon der Schnee von gestern sein und so ist es auch zum Teil mit diesem Artikel. Er war bereits fertig in der Redaktion, da änderten sich die Details und weil das Magazin noch nicht gedruckt war, will ich auch diese Änderungen noch einbringen. Während im PHP Magazin 2.04 noch zu lesen war, dass sich das PEAR Proposal System (PEPr) im Testbetrieb befindet, so wurde es inzwischen von Tobias Schlitt fertiggestellt und wird inzwischen auch aktiv zur Paketabstimmung verwendet. Der interessierte Leser findet es unter pear.php. net/pepr/. Zum ersten fällt auf, dass man zur Teilnahme einen PEAR Developer Account braucht. Mein Versuch, den bereits vorhandenen PHP Developer Account zu nutzen, schlug fehl. Dies äußert sich lediglich darin, dass ich erneut die LoginSeite sah, eine Fehlermeldung wäre hier nett gewesen. DesWeiteren fehlt ein Hinweis darauf, wie man einen PEAR-Websiteaccount erhält. Üblicherweise steht das neben dem Login-Formular, zumindest sollte es aber im Handbuch stehen, bei PEAR ist beides nicht der Fall. Nach längerem Suchen fand ich den passenden Link pear.php.net/account-request.php ganz unten auf der Startseite. Nachdem diese Hürde genommen ist, kann man dann abstimmen, ein wesentliches Detail, nämlich die Lizenz, ist aber nach wie vor nicht fixer Bestandteil eines Proposals. Zum anderen ergab sich am Rande des letzten Treffens der Berliner PHP-Usergroup die Möglichkeit, mit Lukas Smith zu reden. Dabei ergaben sich einige interessante Informationen, die ich Ihnen nicht vorenthalten möchte. Die wohl wichtigste bezieht sich auf die von mir bezüglich ihrer Detailtiefe bemängelten Ankündigungen der PEAR Group. Diese werden laut Lukas so verfasst, dass der Leser bereits Kenntnisse über PEAR haben muss bzw. das mit der Ankündigung gelöste Problem bereits kennt. Zukünftig will er aber darauf achten, dass auch der PEAR-Einsteiger mit der Ankündigung alle nötigen Details erfährt (zum Beispiel in Form von Querlinks auf das Manual). Zum anderen erfuhr ich, dass der Beschluss zum Versionieren gar nicht
komplett ist. Vielmehr liegt ein kompletter Antrag zur Versionierung seinerseits seit Monaten vor, über den man sich aber nicht vollständig einigen konnte, sodass nur der unstrittige Teil bisher den Schritt bis zum Beschluss durchlaufen konnte. Wäre dieser Beschluss bereits erfolgt, so wäre die Handhabung im Fall von XML_Transformer 0.9 wesentlich einfacher gewesen. Im konkreten Fall folgte auf die Version 0.8.2 (stable) eine ebenfalls als stable markierte Version 0.9. Diese benötigt aber im Unterschied zum Vorgänger XML_Util 0.5.1, was aus Sicht verschiedener PEAR-Entwickler einen Kompatibilitätsbruch darstellt. Dem entsprechend müsste die Major-Version erhöht werden, was 1.0 und eben nicht 0.9 als Versionsnummer zur Folge hätte. Des Weiteren gibt es verschiedene Ansichten darüber, was beta und was stable ist und ob es überhaupt vor Version 1.0 ein stable Release geben kann. Hier bleibt zu hoffen, dass die Vorschläge von Lukas schnell zum Beschluss kommen, um wieder ein Stück Streitpotenzial zu eliminieren. Kai Schröder ist selbstständiger Programmierer in Berlin und entwickelt seit 1993 Internet-Anwendungen. Zu PHP kam er 1999 berufsbedingt, vorher war Perl die Sprache seiner Wahl. Seine Mitarbeit am coWiki-Projekt ruht zur Zeit, Ursache hierfür sind die ständigen Änderungen innerhalb der Beta-Versionen von PHP 5. Dadurch bleibt mehr Zeit zur Beantwortung von eMail, welche Sie gerne an die Adresse [email protected] richten können.
Links [1] PEAR Group: pear.php.net/group/ [2] „Handling Votings and Membership“: pear.php.net/group/docs/20030820-vm.php [3] „Handling Package Proposals“: pear.php.net/group/docs/20030904-pph.php [4] „New guidelines for BC breaking releases“: pear.php.net/group/docs/20031114-bbr.php [5] PHP Mailinglisten-Archiv: lists.php.net [6] „Forming of the PEAR Core list“: pear.php. net/group/docs/20031114-pcl.php [7] „Package Directory Structure“: pear.php.net/group/docs/20031114-pds.php [8] PHPUnit: pear.php.net/package/PHPUnit [9] .phpt-Tests schreiben: qa.php.net/write-test.php
XML-Strukturen mit DTDs und Schemata verifizieren
Alles Definitionssache XML-Strukturen mit DTDs und Schemata verifizieren Kein Zweifel, als universelles Datenaustauschformat hat sich XML durchgesetzt. Und so verlangt der Kunde von Ihnen, dass auch die neue Website mit den Partnern via XML kommunizieren soll. Zu diesem Zweck haben Sie für die Zulieferer eine aufwändige Dokumentation verfasst und es trudeln nun die ersten Daten ein. Auf den ersten Blick ist die Struktur zwar XML-konform, aber wurden alle Ihre Vorgaben beachtet?
von Arne Blankerts Neben dem Prüfen der rein syntaktischen Vorgaben, die ein Dokument erfüllen muss, um als XML-konform zu gelten, könnte man natürlich erst einmal davon ausgehen, dass schon alles seine Richtigkeit haben wird, was von einem Zulieferer kommt. Das aus dieser offensichtlich gefährlichen Annahme resultierende PHP-Programm darf sich im ungünstigsten Fall mit einer Vielzahl an Problemen herum ärgern und wird im „Worst Case“ sogar eine inkonsistente Struktur zurücklassen, nämlich dann, wenn Sie die inhaltlichen Fehler erst mitten beim Verarbeiten feststellen. Eine mögliche Alternative wäre es hier, von Hand diverse Checks durchzuführen und erst nach dem erfolgreichen Passieren dieser Routinen mit der eigentlichen Arbeit zu beginnen. Hört sich aber irgendwie nach viel zu viel Arbeit an, oder? Document Type Definition (DTD)
Bereits aus dem HTML-Umfeld bekannt, sind die Document Type Definitions – kurz DTD – mit das elementarste Werkzeug, um die erlaubten Tags und Attribute sowie deren Anordnung im Dokument zu definieren. So ist für jede HTML-Version eine entsprechende DTD verfügbar, die genauestens den Aufbau einer „gültigen“ HTML-Datei beschreibt. Streng genommen sind von der DTD abweichende Daten kein HTML, sondern einfach nur
„Tagsuppe“, nicht zuletzt deswegen müssen moderne Browser ständig Schwerstarbeit leisten, um zu erraten, was der Entwickler bei nicht validem HTML wohl gemeint haben könnte. Über die Angabe des wird dem Parser mitgeteilt, welcher Vorgabe er zu folgen hat. Dabei kann wie im Beispiel „XML mit internem Doctype“ die gesamte Struktur im Dokument mit eingebunden werden. Sobald es jedoch mehrere gleichartige Dokumente gibt, macht dies wenig Sinn: Die Angabe einer externen DTD wird notwendig. Zur Auslagerung der interne Definition wird der Inhalt zwischen den eckigen Klammern [...] in eine externe Datei übertragen. Für die Einbindung derselben gibt es die zwei Varianten SYSTEM und PUBLIC. Über die gängigste Variante SYSTEM lässt sich gezielt die Datei spezifizieren: Es kann sowohl eine lokale Datei – auch mit relativer Pfadangabe – bezeichnet werden als auch ein über eine URL erreichbares Dokument:
wählen und nur beim Fehlen der Regeln ein Nachladen der DTD zu erzwingen.
XML mit internem Doctype
]>
Die Variante PUBLIC dürfte eher selten in eigenen Projekten Verwendung finden – sie dient dazu, über eine zusätzlich angegebene ID die passende DTD auszu-
Hier steht der Inhalt
PHP Magazin 3.2004
67
XML-Strukturen mit DTDs und Schemata verifizieren
Vor allem bei öffentlichen Standards wie HTML oder xHTML macht dies Sinn, da so nicht zwingend eine lokale Dateinamenskonvention eingehalten oder die DTD selbst heruntergeladen werden muss, der Parser aber dennoch alle Regeln überprüfen kann. Die ID sollte dem Format -//Herausgeber//DTD Name Version //Sprache entsprechen, wobei die Angabe der Sprache hier nicht die Sprache des Dokuments selbst bezeichnet, sondern die, welcher die verwendeten Tags entliehen sind – bei HTML zum Beispiel wäre dies Englisch. Für den xHTML 1.1-Standard [1] sieht das ganze dann folglich so aus: [...] &medium; [...]
Wie man eine so angelegte DTD zur Validierung eines XML-Dokuments mittels PHP heranzieht, zeigt das Beispiel „DTD
Validierung mit PHP“. Ist die DomXMLErweiterung [2] aktiviert und mit ihr die Funktionalität der libxml2 [3] verfügbar, gibt es in PHP 4 sowohl die Möglichkeit, direkt beim Laden des Dokuments eine Überprüfung zu veranlassen als auch die Option, dies später manuell nachzuholen. Ist die DomXML-Erweiterung nicht installiert, kann auch auf diverse PEARPakete [4] zurückgegriffen werden, deren Installation und Anwendung jedoch nicht Teil dieses Artikels ist. Wer sich etwas ausgiebiger mit DTDs beschäftigt hat, wird schnell merken, so vielseitig die Möglichkeiten zur Definition auch sein mögen, logische Abhängigkeiten lassen sich nicht abbilden. Auch inhaltliche Verknüpfungen, die z.B. das Vorhandensein einer Gruppe von Elementen
“http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd“>
Nach der grundsätzlichen Entscheidung, welchen Typs das XML-Dokument sein soll, wenden wir uns dem Aufbau der DTD selbst zu. Zunächst einmal ist wichtig, dass eine DTD, auch wenn sie XML beschreibt, selbst kein XML-Format ist; der grundsätzliche Aufbau ist daher ziemlich einfach. Es gibt genau drei Angaben, welche sich beliebig oft im Dokument wiederholen können: • ELEMENT: legt ein Tag und dessen (mögliche) Unterelemente fest • ATTLIST: beschreibt alle Attribute eines vorher mit ELEMENT angelegten Elements
DTD Validierung mit PHP4 via domXML
Syntax
Beschreibung
Beispiel
Ein leeres Element
Ein Element mit undefiniertem, vom Parser zu ignorierenden Inhalt
Hier steht Text
Ein Element mit undefiniertem Inhalt, der aber vom Parser ausgewertet wird
Etwas text
Ein Element mit festgelegten Kind-Elementen; nur die angegebenen Elemente sind unterhalb vom Bezeichner erlaubt und müssen exakt in dieser Reihenfolge und genau jeweils einmal vorkommen
...
Ein Element bezeichner mit entweder bezeichner1 oder bezeichner2 als Kind
Das einzige Kindelement bezeichner1 muss mindestens einmal unterhalb von bezeichner vorkommen.
Das einzige Kindelement bezeichner1 kann beliebig oft oder auch gar nicht unterhalb von bezeichner vorkommen.
68
Tabelle 1: Möglichkeiten für die ELEMENT-Notierung
PHP Magazin 3.2004
XML-Strukturen mit DTDs und Schemata verifizieren
erzwingt, wenn bei einem anderen Element oder Attribut ein bestimmter Wert eingetragen ist, lassen sich mit einer einfachen DTD nicht abbilden. XML Schema Definition (XSD)
Spätestens mit der Übernahme des ursprünglichen Microsoft-Entwurfs zu XML Schema als W3C-Standard gilt diese Form der Struktur-Definition als dedizierter Nachfolger für die DTDs. Neben der Tatsache, dass Schemata selbst in XML-Notierung geschrieben werden, gibt es eine ganze Reihe von praktischen Vorteilen: So lassen sich XML Schemata leicht durch Programmcode erweitern (z.B. über DOMFunktionen ), bieten erweiterte Datentypen z.B. zur Prüfung von Datumsangaben oder legen verschärfte Kriterien wie Länge oder Format für Werte fest. Ein weiterer, nicht zu vernachlässigender Vorteil mag die stärker an der (menschlichen) Sprache angelehnte, wortreichere
Typ
Beschreibung
CDATA
Als Wert sind beliebige Zeichen zulässig.
NMTOKEN
Der Wert muss aus Buchstaben, Ziffern oder einem Punkt [.], Bindestrich [-], Unterstrich [_] und Doppelpunkt [:] bestehen
NMTOKENS
Wie NMTOKEN, jedoch zusätzlich mit Leerzeichen, Zeilenumbruch oder Tabulator.
(wert1|wert2|... Der Wert muss einem der angege|wertN) benen entsprechen. ID
Der Wert stellt eine (dokumentweit) eindeutige ID dar.
IDREF
Der Wert stellt eine Referenz auf ein anderes Element mit dieser ID dar.
Tabelle 2a: Die wichtigsten Typen der ATTLIST-Notierung
Typ
Beschreibung
#REQUIRED
Die Angabe dieses Attributs ist zwingend notwendig.
#FIXED “WERT“
Dieses Attribut ist fest auf den angegebenen Wert eingestellt (z.B. für Versionen ).
#IMPLIED
Das Attribut ist optional; einen Vorgabewert gibt es nicht.
“WERT“
Das Attribut ist optional; fehlt es, wird der angegebene Wert angenommen.
Tabelle 2b: Die möglichen Vorgabewerte der ATTLIST-Notierung
Form der Beschreibung sein, was eine verbesserte Lesbarkeit mit sich bringt – wenngleich dies natürlich mit einem erhöhten Schreibaufwand verbunden ist. Eine XSD ist, ähnlich wie vielleicht schon durch XSL bekannt, in einem eigenen Namespace xs definiert und vom RootTag xs:schema umschlossen: ....
Eine mögliche vollständige XSD ist im Kasten „XSD-Beispiel“ zu finden. Die Aussagen der DTD sowie der XSD sind inhaltlich fast identisch, wenn auch in der Schema-Version deutlich länger. Als einzige Abweichung ist das Attribut jahr im Element ausgabe vom Typ xi:gYear und somit klar auf eine vierstellige Integer-Zahl festgelegt. Da die ohnehin schon weitreichenden Möglichkeiten der XML Schema-Definitionen beliebig durch eigene Strukturen erweitert werden können, ist es illusorisch, in einem kurzen Artikel auf alle Optionen eingehen zu wollen. Wir werden uns deshalb auf die grundlegenden Elemente einer Schema-Definition beschränken. Für weitere, detailliertere Informationen sei auf die Seiten des W3C zum Thema XSD [5] sowie auf das englischsprachige Tutorial bei w3school. com/ [6] verwiesen. Die einfachste Form ist aus Sicht von XSD ein Simple Type, ein einfacher Typ. Die nachfolgende Liste zeigt die Definition der sechs häufigsten:
XSD-Beispiel
XML für XSD
Durch die Erweiterung um die Attribute fixed für feste sowie default für Grundwerte lassen sich wie bei der DTD weitere Vorgaben einbinden. Doch wäre eine XSD keine Weiterentwicklung, wenn sich hier nicht noch mehr tun ließe: Über die Notierung von Restriktionen kann genauestens eingestellt werden, in
Hier steht der Inhalt
PHP Magazin 3.2004
69
XML-Strukturen mit DTDs und Schemata verifizieren
welchem Wertebereich sich Integer-Angaben bewegen müssen, welches Format ein String haben muss oder welchem Zeitraum für ein Datum Gültigkeit beigemessen wird:
Wem diese Schreibweise zu lang ist, der kann auch zur kürzeren Variante greifen:
Wer sich bereits mit Regular Expressions auskennt, wird sich freuen, denn ihm wird die im nachstehenden Snippet verwendete Syntax vermutlich bekannt vorkommen:
Eine Übersicht aller verfügbarer Restriktoren findet sich in Tabelle 3. Die mehrstufige Verschachtelung von Elementen ist nicht mehr ganz so „simple“ und daher auch aus Sicht der XSD ein ComplexType:
Dieser Ausschnitt aus dem XSD-Beispiel zeigt zudem gleich die Einbindung von Attributen für ein Element. Für XSD ist es egal, ob eine nachgeordnete Angabe ein Element oder Attribut darstellt, beides gilt als complexType, da das ElternElement um eine Zuordnung erweitert wird. Nachdem so Stück für Stück die Schema-Definition erstellt wurde, muss noch das XML-Dokument selbst von seinem „Glück“ erfahren: Die Verbindung mit einer Schema-Definition erfolgt im Gegensatz zur DTD nicht über einen eigenen Pseudo-Tag wie , sondern über das Root-Element des eigentlichen XML. Als erstes wird der XSD-
RelaxNG Schema-Beispiel
Name
Beschreibung
xs:enumeration
Wird für eine Liste von möglichen Optionen verwendet. Das Element sollte zum sinnvollen Einsatz mehrfach vorkommen.
xs:fractionDigits
Definiert die Anzahl der Dezimalstellen beim Typ xs:decimal
xs:length
Definiert die genaue Länge (Anzahl Zeichen)
xs:maxLength
Maximale Länge (Anzahl Zeichen)
xs:minLength
Minimale Länge (Anzahl Zeichen)
xs:maxExclusive
Maximaler Wert bei Zahltypen (xs:decimal, xs:integer) exklusive diesem Wert
xs:maxInclusive
Maximaler Wert bei Zahltypen (xs:decimal, xs:integer) inklusive diesem Wert
xs:minExclusive
Minimaler Wert bei Zahltypen (xs:decimal, xs:integer) exklusive diesem Wert
xs:minInclusive
Minimaler Wert bei Zahltypen (xs:decimal, xs:integer) inklusive diesem Wert
xs:pattern
An die Syntax der Regular Expression angelehnte Vorgabe über den Inhalt z.B. [a-zA-Z09]* für nur Buchstaben und Zahlen
Y N
xs:totalDigits
Angabe über die Gesamtanzahl an Stellen einer Dezimalzahl (xs:decimal)
xs:whiteSpace
Beschreibt die Behandlung von Leerzeichen, Zeilenumbrüchen und Tabulatoren. Als mögliche Werte sind definiert: - collapse: Entfernt überflüssige Leerzeichen, ansonsten wie replace - replace: Ersetzt alle Zeilenumbrüche und Tabulatoren durch Leerzeichen - preserve: Erhält die Werte wie sie waren
Tabelle 3: Mögliche XSD-Restriktionen
70
PHP Magazin 3.2004
XML-Strukturen mit DTDs und Schemata verifizieren
Namespace xsi definiert und gleich drauf mit xsi:SchemaLocation bzw., da wir keinen eigenen Namespace vorgegeben haben, hier mit xsi:noNamespaceSchemaLocation, die Verknüpfung zur passenden Datei mit auf den Weg gegeben:
Schreibweise. Um derartige Schemata in PHP verwenden zu können, bedarf es wie schon bei den W3C-XSD der Version 5 und der libxml2. Der Aufruf zur Validierung nach RelaxNG unterscheidet sich kaum von dem für XSD:
....
ständliche Schreibaufwand – und der damit verbundene Zeitverlust – wird sich spätestens bei der Wiederverwendung von Teilen und der Über- prüfung von Abhängigkeiten oder anderen Vorgaben als lohnende Investition herausstellen. Bleibt zu hoffen, das PHP bald fertig gestellt ist. Ob man sich für den W3C-Standard XSD oder RelaxNG entscheidet, dürfte weitestgehend Geschmackssache sein, die XSD sind jedoch mächtiger. Arne Blankerts ([email protected]) ist Leiter der Entwicklung bei der Sales Emotion GmbH in Hamburg.
Was nutzen?
Links
RelaxNG Schema Definition (RNG)
Neben der „offiziellen“ Schema-Definition des W3C gibt es eine weitere weit verbreitete Schema-Sprache: RelaxNG [7]. Wie die W3C-Sprache wird ein RelaxNGSchema in XML notiert, erhebt aber den Anspruch, einfacher und erlernbarer zu sein. Folglich ist die Syntax noch mal um einiges wortreicher und ausformulierter:
Anzeige
....
Der hier verwendete Namespace rng ist frei gewählt und nicht zwingend. Er kann sogar wie bei XML üblich unter der Angabe als Default-Namespace vollkommen entfallen, wovon aus Lesbarkeitsund Interoperabilitätsgründen nach Meinung des Autors abzuraten ist, auch wenn die Tutorial-Webseite von RelaxNG.org [8] dies selbst tut. Das RelaxNG Schema-Beispiel zeigt ein passend zum hier verwendeten Beispiel-XML erstelltes Schema in RelaxNG-
PHP Magazin 3.2004
71
SVG-Codegenerierung mit XSLT
Wahlerfolg Generierung von SVG-Code mithilfe von XSLT Der XML-Vektorgrafikstandard SVG ermöglicht unter anderem die Erzeugung dynamischer Webgrafiken und eignet sich daher gut für den Einsatz im Bereich Reporting. Bei der Generierung des eigentlichen SVG-Codes kann dabei unter anderem auf die XSLT, die eXtensible Stylesheet Language Transformations, zurückgegriffen werden.
von Herbert Bader Wenn Sie außerhalb rein didaktischer Zwecke mit SVG zu tun haben, werden Sie nur selten in einem Texteditor SVG-Tags verfassen. Dem Screendesigner stehen Tools zur Verfügung, um komplexe Seiten zu erstellen, dem Report-Kreator zusätzlich Programmiersysteme. Ein typischer Werdegang eines Online-Reports in SVG wird folgendermaßen verlaufen: • Der Systemarchitekt legt zusammen mit der Fachseite die Menge der darzustellenden Geschäftsdaten fest und beschreibt die Aufbereitung (Model). • Ein Screendesigner kreiert das Layout nach fachseitiger Maßgabe, aber ohne Kenndaten. Die Objekte, die Kenndaten benötigen, werden mit Ersatzwerten als Platzhalter erstellt (View). Das Ergebnis ist ein Template (zu Deutsch: Schablone). • Programmierer sorgen dafür, dass zur Online-Zeit diese Platzhalter automatisch durch Geschäftsdaten – oder davon abgeleitete Größen – ersetzt werden (Controller). Die Aufgabe der Programmierer kann dabei unterschiedlich tief in SVG eingreifen. Bei Balkengrafiken muss vermutlich
nur die Höhe als einziger Parameter dynamisch ersetzt werden. Dagegen müssen bei einem Kuchendiagramm eine Reihe von (zum Teil trigonometrischen) Berechnungen angestellt werden und es sind etliche Objekte betroffen. Auch die Wahl der serverseitigen Programmiersprache hängt von der Komplexität dieses Ersetzungsprozesses ab. Bei einfachen Grafiken genügt die XSLT. XSL-Transformation
Die prädestinierte Sprache zur Wandlung eines XML-Formats in ein anderes sind die eXtensible Stylesheet Language Transformations, kurz die XSLT. In einer XSLDatei werden Regeln definiert, wie Tags und Elemente des einen Formats in das andere Format umgebaut werden müssen. Diese Regeln werden ihrerseits in einem XML-Format festgelegt; das heißt, auch XSL ist ein Substandard von XML. Allerdings erlaubt diese deklarative Sprache nur einfache mathematische Operationen, wenn man keine Zusatzmodule einbinden möchte. Das Einbinden solcher zusätzlicher Erweiterungen ist für sich ein solcher Aufwand, dass man in den meisten Fällen besser von vornherein eine funktionale Programmiersprache wählt. Deshalb lassen wir diesen Fall außen vor.
Den Quellcode zum Artikel finden Sie auf der beiliegenden CD.
72
PHP Magazin 3.2004
• xml-Datei = Model • xsl-Datei = View • Transformator = Controller sind deutlich von einander getrennt, was zu einer guten Strukturierung der Gesamtanwendung führt. Im Laufe dieses Beitrags erfahren Sie, wie Sie die Möglichkeiten von XSLT dazu nutzen können, einfache Grafiken wie Abbildung 1 (absolute Wählerstimmen mit Gewinnen und Verlusten) aus einer allgemeinen XML-Datei transformieren können. Daten in XML sind oft das Ergebnis eines vorgelagerten Prozesses und liegen meist mit beschreibenden Element- und Attributsnamen vor (im Unterschied zu (X)HTML, wo die Metadaten nur gestalterischen, aber keinen informativen Charakter haben). Die darzustellenden Werte
Operationen
Die Operationen, die mit XSLT möglich sind, sind:
Quellcode
Trigonometrische Funktionen, Wurzeln oder dergleichen sind im Sprachumfang von XSL nicht vorgesehen. Gegenüber dem Nachteil der eingeschränkten Funktionalität wartet XSLT bei der Codegenerierung mit einem großen Vorteil auf: Das Model-View-Controller-Konzept ist hierbei in Reinform umgesetzt. Die einzelnen Komponenten
• die Grundrechenarten, • logische Abfragen, • Schleifen.
Für eine XSL-Transformation brauchen wir die darzustellenden Werte auf jeden Fall in XML-Form. Dies ist jedoch keine wirkliche Einschränkung mehr, denn moderne Tabellenkalkulationsprogramme und Datenbanken geben Abfrageergebnisse optional im XML-Format aus.
SVG-Codegenerierung mit XSLT
Dabei wird typischerweise jedes Datenfeld zum Text eines Textelements (oder Attributwerts). Die Tag-Namen entsprechen sinnigerweise meist den Spaltennamen. Im konkreten Beispiel sehe die XML-Ausgangsdatei aus wie in Listing 1 gezeigt. Ein XML-Parser braucht zusätzliche Informationen über die Strukturierung des Dokuments, die wir ihm über die DTD mitteilen: ]>
Die DTD kann im XML-Dokument selbst oder als externe Datei darin referenziert sein. Im Begleitquellcode heißt die Datei (inklusive DTD) wahl.xml. Allgemeines zu Stylesheets
Durch die Vorgabe, die Transformationsregeln in XML zu formatieren, ist die Syntax etwas gewöhnungsbedürftig. (Auch XSL-Code ist letztlich nicht dazu gedacht, direkt editiert zu werden, sondern als Output eines grafischen Tools erzeugt zu werden.) Hier gebe ich einige Hinweise, um mithilfe von Sekundärliteratur [1] den Einstieg in die Materie zu erleichtern:
• Der Transformator geht – als Parsererweiterung – elementweise voran. Er arbeitet die Elemente von außen nach innen und von oben nach unten hin ab. • Dabei prüft er bei jedem Element, ob er es einer Behandlung unterziehen muss. Das sieht er in der Liste der Einträge in der XSL-Datei. Findet er dort keinen Eintrag, verwirft er das Element selbst und alle dessen untergeordnete Elemente. • Ein XSL-Element der Form ... zeigt dem Transformator an, dass er ein Element namens tag-name auf die in “...“ beschriebene Weise prozessieren soll. Dort können unter anderem XMLkonforme Konstrukte stehen, die das Ausgangs-Tag tag-name ersetzen. • Innerhalb eines xsl:template-Elements kann stehen. Es fordert den Transformator auf, an dieser Stelle die untergeordneten Elemente zu prozessieren. Weitere XSL-Konstrukte werden im Rahmen der einzelnen Beispiele erläutert. Als Erstes transformieren wir die wahl .xml in eine html-Datei.
Abb. 1: Positive und negative Werte in einer Balkengrafik
Mit JavaScript-Code verliefe dies völlig analog (siehe Listing 2). So weit die Behandlung des Root-Elements. Es wird ersetzt durch den angegebenen HTML-Code. HTML-Kommentarzeichen müssen besonders bezeichnet werden, um nicht fehlinterpretiert zu werden. Im Element partei wird der Text des Elements farbe über eine lokale Variable col einem Attribut als Wert zugewiesen. Der Zuweisungsoperator für den Variablenwert an ein Attribut ist “{$variablen_name}“. Dagegen meint den Attributswert von id im XML:
Einfache Transformation nach HTML
Da wir bei einer HTML-Tabellendarstellung ohne grafische Elemente auskommen müssen, hinterlegen wir die Felder wenigstens mit der in wahl.xml mitgelieferten Farbe. Das Listing der wahl2html.xsl wird jeweils unterbrochen für Kommentare.
deklariert das Root-Element einer jeden XSL-Datei mit Angabe des Namensraums xsl der Transformation,
die Anweisung zur Formatierung in deutsches Zahlenformat. Es folgt die erste Template-Regel, die auf das Root-Element von wahl.xml wirkt. In den head-Bereich des XHTML-Ergebnisses wird ein Stück CSS-Code gepflanzt.
Listing 2 td { color: white; font-weight: bold; font-size: 16pt; } // Wahlergebnis in HTML
Partei
Prozente
geg. 1998
PHP Magazin 3.2004
73
SVG-Codegenerierung mit XSLT
Format teilen wir durch einen ersten waagerechten Strich in zwei übereinander liegende Hälften auf:
Die Wirkung der xsl:apply-templates wurde zweimal eingeschränkt. Das Erste wird durch den Attributwert von id ersetzt, das Zweite weist an, nur die Kind-Elemente prozente und trend (und nicht farbe) zu prozessieren. Die Textknoten von beiden kommen jeweils in ein Tabellenfeld:
Die Definition der Behandlung des partei-Elements gibt es in zwei Modi. In den beiden Hälften kommen die beiden unterschiedlichen Behandlungen zum Tragen. Es folgt der partei-Modus “absolut“ für die obere Zeichnungshälfte:
Die Variable top ergibt die Oberkante (y-Wert). In die Berechnung eingesetzt wird $value als Wert der Variablen value:
%
Ohne Fallunterscheidung kommt die Belegung der Variablen valpos aus:
Neu sind in diesem Codeausschnitt die Berechungen der Koordinatenwerte. Der Wert eines untergeordneten Textknotens (wie farbe) wird einfach in geschweiften Klammern zu einem Attributswert. Bei der Wertzuweisung von height und y sehen Sie, dass mit diesem (automatisch in einen Zahlenwert transformierten) Wert auch gerechnet werden kann. Die XSLTSystemfunktion position() schließlich gibt die fortlaufende Nummer des aktuellen Kontexts an (Schleifenparameter). Sie liefert die Antwort auf die Frage: Die wievielte partei bearbeiten wir gerade? Wir kommen zur unteren Hälfte der Grafik, zur Erzeugung der Differenzwertbalken. Die Schwierigkeit ist dabei, dass wir entscheiden müssen, ob der Balken von oben zur Linie (positiver Wert) verläuft oder von der Linie nach unten (negativer Wert). Was für eine funktionale Sprache nicht schwer zu implementieren wäre, macht mit XSLT durchaus Mühe:
Mit Verwendung dieser Definitionen gestaltet sich die eigentliche Berechnung der Balken ohne größere Überraschungen: java org.apache.xalan.xslt.Process -inwahl.xml -xsl wahl2svg.xsl -out wahlxslt.svg ↵
Wenn Ihre Java-Umgebung dagegen noch nicht über Xalan verfügt (Sie merken das an der Fehlermeldung NoClassDefFoundError), müssen Sie Xalan für Java von der genannten Adresse bei apache.org besorgen. Installieren Sie das System und merken Sie sich die Adresse des Binary-Pakets xalan.jar. Der Aufruf erweitert sich dann um die Einbindung dieser Datei in den Klassenpfad:
hältlich) erweitert sich die Klassenpfaddefinition zu: -cp /pfad/zu/xalan.jar:/pfad/zu/xerces.jar
Falls Sie die Installationsmühen als übertrieben empfinden, um doch nur eine simple Konsolenanwendung zu Übungszwecken betreiben zu können, steht Ihnen dazu eine Alternative offen: die Web Publishing-Frameworks. Web Publishing-Frameworks
Dies sind im Allgemeinen umfangreiche Softwarepakete mit erheblichem Installationsbedarf. Doch einmal installiert, fungieren sie als universelle Server für eine Vielzahl von Darstellungskanälen. Die gleichen Dateninhalte werden dabei in Online-Zeit – je nach Abruf vom Client – in HTML, WML, SVG, X3D, Java-GUI, Excel-Tabelle, PDF und viele andere Formate ausgegeben. Auch für Web Publishing-Frameworks steht Ihnen eine vortreffliche Open Source-Implementierung zur Verfügung. Es handelt sich dabei um das Projekt Cocoon [6]. Andere Beispiele in der Literatur
Nicht alle Autoren vertreten meinen Standpunkt, bei aufwändigeren Berechnungen besser auf eine funktionale Programmiersprache zu wechseln. Folgende beiden Literaturstellen zeigen Alternativen dazu auf: „Mit XSLT und SVG Diagramme für Geschäftsdokumente erzeugen“ [7]: Der Artikel von Jens Kleemann und Aidan Mark Humphreys beschreibt unter anderem einen Workaround über Taylorreihen, wie komplexe mathematische Funktionen
auch mit den Grundrechenarten realisiert werden können. Auch zeigen die Autoren, dass solche Funktionalität in template-Abschnitten gekapselt und diese wie Funktionen verwendet werden können. Deshalb sprechen sie von XSLT in diesem Sinne folgerichtig als einer „funktionalen Sprache“. J. David Eisenberg, „SVG Essentials“ [8]: Eisenberg bringt ebenfalls ein aufwändiges Beispiel und erklärt die Grundzüge von XSLT sehr elementar. Er führt sein Beispiel so weit fort, bis die Berechnung eine komplexe mathematische Anforderung erreicht. Eisenbergs Lösung ist es, Java-Funktionen zu benutzen, und er zeigt, wie externe Funktionalität in XSLT eingebunden wird. Dieser Beitrag ist ein Auszug aus dem Buch „SVG Reporting – Vektorgrafiken im Web einsetzen“ von Herbert Bader, das im Januar 2004 im Software & Support Verlag erschienen ist (ISBN 3-935042-43-4).
Links & Literatur [1] Zum Beispiel Mike Bach: XSL und XPath, Addison-Wesley, 2000 (deutsch) oder Harold & Means: XML in a Nutshell, O’Reilly, 2001 (englisch) [2] JDK: java.sun.com/ [3] Xalan: xml.apache.org/xalan-j/index.html; von Xalan gibt es auch eine C++-Version [4] Saxon: saxon.sourceforge.net/ [5] Aztecrider: www.aztecrider.com/xslt/index.html [6] Cocoon: cocoon.apache.org/ [7] Jens Kleemann und Aidan Mark Humphreys, Tortenbäcker: Mit XSLT und SVG Diagramme für Geschäftsdokumente erzeugen, XML & Web Services Magazin 2.2003 [8] J. David Eisenberg, SVG Essentials, O’Reilly, 2002, Kapitel 12
(auf Windows-Konsolen stattdessen: ... -cp \pfad\zu\xalan.jar ...) Falls Sie ein älteres Paket xalan.jar benutzen, müssen Sie unter Umständen auch noch einen externen Parser inkludieren. Mit Xerces (ebenfalls bei apache.org er-
Anzeige
PHP Magazin 3.2004
75
Datenbanken Datenbankabstraktion mit ADOdb
Wunderbar wechselbar von Daniel Koch
Datenbankabstraktion mit ADOdb
nalität macht die Umstellung von einer Datenbank auf die andere so schwierig. Dieses Problem zu umgehen, hilft die Datenbankabstraktion, die in diesem Artikel anhand von ADOdb vorgestellt wird. Mögliche Einwände gegen ADOdb, wie bspw. die PEAR-Alternative PEAR:DB oder die PHPLib, werden hier bewusst außen vor gelassen. All denjenigen, die dennoch einen Vergleich zwischen ADOdb und PEAR::DB anstrengen wollen, sei die Seite [1] empfohlen. Hier werden die wichtigsten Features beider Systeme miteinander verglichen.
Was ist eine Datenbankabstraktion?
Die Entwicklung im Hinblick auf Datenbanken und Websprachen schreitet rasend schnell voran. Auch für Sie als Entwickler bedeutet dies ein stetiges in Bewegung bleiben müssen. Häufig wird bei entsprechenden Neuerungen aber leider immer wieder das Rad neu Erfunden. So wird Code oft plattformabhängig entwickelt bzw. an eine bestimmte Datenbank oder Programmiersprache gebunden. All diese Punkte machen die Entwicklung von Anwendungen, die das Prädikat dynamisch im wahrsten Sinne des Wortes verdienen, schwierig. Auf den folgenden Seiten wird gezeigt, wie Sie Ihren Code wirklich dynamisch, nämlich mittels Datenbankabstraktion, entwickeln können.
Quellcode Der Quellcode zum Artikel befindet sich auf der beiliegenden CD.
76
PHP Magazin 3.2004
Einführung Die Entwicklung von Datenbankanwendungen ist zeit- und kostenintensiv. Gehen wir davon aus, dass Sie den Erfolg Ihres Internetauftritts unterschätzt haben und bereits nach kurzer Zeit nicht mehr mit einer MySQL-Datenbank auskommen. Stattdessen fassen Sie nun beispielsweise die Anschaffung einer Oracle-Datenbank ins Auge. Was an sich kein gravierendes Problem darstellt, entwickelt sich rasch zu einem, wenn Sie an die Umstellung Ihrer PHP-Skripte denken. Schließlich müssen diese nun an die Oracle-Datenbank angepasst werden. Warum diese Skript-Umstellung vorgenommen werden muss, liegt auf der Hand. Zwar orientieren sich Datenbanken an einem gewissen SQL-Standard, dennoch hat jede Datenbank so ihre eigenen Erweiterungen und Eigenheiten. So versuchen beispielsweise MySQL und Oracle den SQL-92-Standard einzuhalten. Wobei Oracle diesen Standard zwar einhält, aber eben auch noch viel mehr leisten kann. Und eben dieses Mehr an Funktio-
Wenn Sie mit Datenbanken arbeiten, haben Sie sicherlich bereits gemerkt, dass jede ihre Eigenheiten hat (so unterscheiden sich beispielsweise häufig die einsetzbaren Datentypen). Wollen Sie also Ihren PHPCode von einer MySQL- auf eine OracleDatenbank umstellen, müssen Sie all diese Unterschiede kennen und beseitigen. Und genau an diesem Punkt greift die Datenbankabstraktion. Denn hierdurch ist es möglich, einen einmal erstellten Programmcode auch nach einem Datenbankwechsel (fast) unverändert zu übernehmen. Datenbankabstraktionen erlauben einen einfacheren Zugriff auf Datenbanken durch • eine automatische Verbindungsaufnahme zur Datenbank • die Speicherung der Zugriffsparameter in einer Include-Datei • das Verbergen von datenbankspezifischen Eigenschaften. Microsoft-Entwickler kennen diese Möglichkeit bereits. Schließlich setzt Microsoft seit geraumer Zeit auf ADO (ActiveX Data Objects). Daher wird vor allem für ASP-Erfahrene ein möglicher Umstieg von ASP- auf PHP-Anwendungen via ADOdb sehr einfach ausfallen. Denn nicht nur, dass das Grundprinzip identisch ist, auch die
Die folgenden Datenbanken werden von ADOdb unterstützt: MySQL, Oracle, Microsoft SQL Server, Sybase, Informix, PostgreSQL, FrontBase, SQLite, Interbase, Foxpro, Access, ADO, DB2, SAP DB, ODBC
Datenbankabstraktion mit ADOdb Syntax ist vergleichbar. Was wenig verwunderlich ist, schließlich setzt ADOdb auf ADO auf. Um die folgenden Beispiele auch praktisch nachvollziehen zu können, sollten Sie sich ADOdb unter [2] herunterladen. Beachten Sie, dass Sie mindestens PHP 4.0.4 benötigen. Entpacken Sie das Archiv in ein Verzeichnis des Webservers.
Ein einführendes Beispiel Die im Zusammenhang mit PHP am häufigsten eingesetzte Datenbank ist MySQL. Demzufolge kann davon ausgegangen werden, dass der folgende Code allgemein verstanden wird: $db = mysql_connect(“localhost“, “user“, “password“); mysql_select_db(“mydb“,$db); $ergebnis = mysql_query(“SELECT * FROM kontakt“,$db);
Die Syntax dürfte wohl vertraut sein. Es wird lediglich eine Verbindung zur MySQL-Datenbank hergestellt und die Datensätze der Tabelle kontakt werden selektiert. Schauen wir uns nun den glei-
chen Part, wie er über ADOdb realisiert wird, an: include(“adodb.inc.php“); $db = &ADONewConnection(‘mysql’); $db->Connect(“localhost“, “usert“, “password“, “mydb“); $ergebnis = $db->Execute(“SELECT * FROM kontakt“)
Bereits bei dem ersten Blick auf den Quellcode fällt auf, dass der ADOdb-Teil etwas aufwändiger ist. Dies ist allerdings kein Hinweis darauf, dass ADOdb kompliziert ist, sondern zeigt, dass ADOdb bei der Verbindungsaufnahme höhere Anforderungen erfüllen muss. Um dieser Komplexität Rechnung zu tragen, wird ein objektorientierter Ansatz verwendet. Wobei Sie von dieser Objektorientierung zunächst einmal nicht viel bemerken werden, liegt diese doch ausschließlich in den IncludeDateien. Über adodb.inc.php wird die Include-Datei eingebunden. Hierin sind alle datenbankrelevanten Informationen enthalten. Mittels der Funktion ADONewConnection() werden die entsprechenden Funktionen für die als Parameter angegebe-
Datenbanken
ne Datenbank eingebunden. In unserem Beispiel werden folglich also die MySQLFunktionen geladen. Soll stattdessen der Zugriff auf eine Oracle-Datenbank erfolgen, müsste der Funktionsaufruf folgendermaßen aussehen: ADONewConnection(‘oracle’) Mittels Connect() wird die Verbindung zur Datenbank hergestellt. In dem Verzeichnis drivers finden Sie die entsprechenden Include-Dateien für alle von ADOdb unterstützten Datenbanken. Wollen Sie sich mit den Codebesonderheiten der verschiedenen Datenbanken vertraut machen, hilft ein Blick hierhinein. Nach der Verbindungsaufnahme können jetzt die Datensätze ausgelesen werden. Um an die Datensätze zu gelangen, wird hier auf eine while()-Schleife zurückgegriffen: while (!$ergebnis->EOF) { for ($i=0, $max=$ergebnis->FieldCount(); $i < $max; $i++) print $ergebnis->fields[$i].’ ‘; $ergebnis->MoveNext(); print “n“; }
Anzeige
PHP Magazin 3.2004
77
Datenbanken Datenbankabstraktion mit ADOdb Das Auslesen der Datensätze wird hier wie das Auslesen einer Datei realisiert. Für jede Zeile wird mittels EOF (End-ofFile) überprüft, ob das Ende der Datei erreicht ist. Solange die letzte Zeile noch nicht erreicht ist, greift die innere for()Schleife. Nach jedem Schleifendurchlauf wird mittels MoveNext() der nächste Datensatz ausgewählt. Noch ein Wort zu dem Array fields[]: Das Array fields[] wird von der PHP-Datenbankerweiterung bereitgestellt. Dabei ist allerdings zu berücksichtigen, dass nicht alle Datenbankerweiterungen das Array nach Feldnamen indizieren. Um eine Indizierung nach Feldnamen zu erreichen, müssen Sie die globale Variable &ADODB_FETCH_ MODE verwenden:
Positionierung. Bookmarks werden ebenfalls nicht unterstützt. • ADO Parameter-Objekte werden nicht unterstützt. Stattdessen stellt ADOdb die ADOConnection::Parameter()-Funktion zur Verfügung.
Das Beispiel macht deutlich, dass die beiden Datensätze rs1 und rs2 unterschiedliche Modi zum Abholen verwenden. Somit ist es möglich, dass auf die Bedürfnisse der jeweiligen Datenbank entsprechend reagiert werden kann.
Wie Sie sehen, gibt es zwar Unterschiede, diese fallen aber im großen und ganzen so marginal aus, dass sie nicht wirklich einen Hinderungsgrund für eine erste Kontaktaufnahme mit ADOdb darstellen.
Probleme mit Sonderzeichen
Wenn Sie bereits mit mehreren unterschiedlichen Datenbanken gearbeitet haben, werden Sie wissen, dass jede Datenbank in gewissen Bereichen einer anderen Syntax folgt. So kommt es vor allem bei Datumsangaben und Sonderzeichen häufig zu Problemen. Nehmen wir an, dass die folgenden Daten in eine Datenbank eingefügt werden sollen: ID = 3 Datum=mktime(0,0,0,2,19,2004) Kommentar= Don’t Worry, Be Happy
ADOdb und Microsofts ADO Gerade für Microsoft-Programmierer stellt ADOdb einen guten Einstieg in die PHPDatenbankwelt dar. Schließlich ist ADOdb eng mit Microsofts ADO verwandt. Bis auf einige wenige Unterschiede ist die Syntax identisch. Sind Sie also beispielsweise ein Verfechter von ASP, dürften Sie sich bereits nach kurzer Zeit in ADOdb zu Hause fühlen. Die folgende Auflistung zeigt die wichtigsten Unterschiede zwischen ADOdb und ADO: • ADO-Eigenschaften wurden und werden in ADOdb als Funktionen implementiert. • Recordsets werden nur dann unterstützt, wenn sie von einem ConnectionObjekt erstellt wurden. • ADORecordSet->Move() verwendet nicht die relative, sondern die absolute
Probleme beim Umgang mit unterschiedlichen Datenbanken
$ADODB_FETCH_MODE = ADODB_FETCH_NUM;
dung der beiden ADOdb-Funktionen DBDate() und qstr() das Datums- und das Sonderzeichen-Dilemma:
Wahrscheinlich funktioniert INSERT nach dem Wechsel der Datenbank nicht mehr. Dies hat in diesem Beispiel zwei Gründe: Zunächst einmal behandeln fast alle Datenbanken Datumsangaben unterschiedlich. Während in MySQL-Datenbanken das Format für Datumsangaben YYYY-MM-DD ist, folgen Oracle-Datenbanken der Syntax DD-MON-YY. Das nächste Problem der Beispielsyntax stellt Dont’t dar. Kann dies in MySQL-Datenbanken in dieser Form übernommen werden, muss beispielsweise für Access „Dont’’t“ verwendet werden. Aber sowohl für das Datums- wie auch für das Sonderzeichen-Problem stellt ADOdb geeignete Funktionen bereit, durch die die Inkompatibilitäten zwischen den Datenbanken aufgehoben werden können. Die folgende Syntax löst durch die Verwen-
. $db->qstr($Kommentar).“)“; $db->Execute($sql);
Ausführliche Informationen zu den beiden Funktionen erhalten Sie unter [3]. An dieser Stelle nur so viel: Durch die gezeigte Syntax können auch die als normalerweise schwierig geltenden Datumsund Sonderzeichen-INSERTs ohne Probleme bei einem Datenbankwechsel übernommen werden. ADOdb hält aber noch eine Vielzahl anderer Funktionen bereit, durch die die Datenbankunterschiede ausgeglichen werden können. So existiert beispielsweise die MetaType()-Funktion, durch die Feldtypen „standardisiert“ werden können. Um wirklich effektiv mit ADOdb arbeiten zu können, müssen Sie aber auch entsprechendes SQL verwenden. Denn zwar arbeiten Datenbanken auf SQL-Basis, die SQL-Dialekte unterscheiden sich allerdings. Das gilt zumindest für speziellere Anwendungen. Denn die grundlegenden SQL-Elemente beherrschen die meisten Datenbanken. Gehen wir aber beispielsweise davon aus, dass aus einer Tabelle die ersten zehn Datensätze ausgelesen werden sollen: select * from table limit 10 // MySQL select * from (select * from table) where rownum SelectLimit(‘select * from table’, 10);
Um ein Menü zu generieren, wird die Funktion GetMenu() verwendet. Weiterführende Informationen zu dieser Funktion finden Sie unter [6]. In dem hieraus generierten Menü KundenMenue wird der Eintrag Tony Hawks als Default-Wert angezeigt. Um die Funktionsweise der GetMenu()-Funktion nachvollziehen zu können, reicht bereits ein Blick in den HTMLCode.
Wenn Sie auf SelectLimit() zurückgreifen, funktioniert Ihre Syntax auch nach einem Datenbankwechsel. Wie Sie Ihre Anwendung so programmieren, dass diese auch auf andere Datenbanken übertragen werden kann, erfahren Sie in dem erstklassigen Tutorial „Tips on Writing Portable SQL Code“. Das Tutorial steht unter [4] zur Verfügung.